File indexing completed on 2024-04-21 15:08:19

0001 //
0002 // C++ Implementation: cCmdParser
0003 //
0004 // Description: command parser
0005 //
0006 /*
0007 Copyright 2005-2011 Tomas Mecir <kmuddy@kmuddy.com>
0008 
0009 This program is free software; you can redistribute it and/or
0010 modify it under the terms of the GNU General Public License as
0011 published by the Free Software Foundation; either version 2 of 
0012 the License, or (at your option) any later version.
0013 
0014 This program is distributed in the hope that it will be useful,
0015 but WITHOUT ANY WARRANTY; without even the implied warranty of
0016 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017 GNU General Public License for more details.
0018 
0019 You should have received a copy of the GNU General Public License
0020 along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 // TODO: parts of this are rather ugly, we need to redesign the whole parsing
0024 
0025 #include "ccmdparser.h"
0026 
0027 #include "caliaslist.h"
0028 #include "cglobalsettings.h"
0029 #include "clistmanager.h"
0030 #include "cprofilesettings.h"
0031 
0032 #include <KLocalizedString>
0033 
0034 cCmdParser::cCmdParser (int sess)
0035   : cActionBase ("cmdparser", sess)
0036 {
0037   separstr = ";";
0038   walkstr = ".";
0039   multistr = "#";
0040   macrostr = "/";
0041   noparsestr = "'";
0042 
0043   isparsing = true;
0044   allowemptywalkstr = false;
0045   stripSpaces = false;
0046   expandbackslashes = true;
0047 
0048   addGlobalEventHandler ("global-settings-changed", 50, PT_NOTHING);
0049 }
0050 
0051 cCmdParser::~cCmdParser()
0052 {
0053   removeGlobalEventHandler ("global-settings-changed");
0054 }
0055 
0056 void cCmdParser::eventNothingHandler (QString event, int /*session*/)
0057 {
0058   if (event == "global-settings-changed") {
0059     cGlobalSettings *gs = cGlobalSettings::self();
0060     setStripSpaces (gs->getBool ("trim-spaces"));
0061     setCmdSeparatorString (gs->getString ("str-separator"));
0062     setSpeedWalkString (gs->getString ("str-speedwalk"));
0063     setMacroString (gs->getString ("str-macro"));
0064     setMultiCommandString (gs->getString ("str-multi"));
0065     setNoParseString (gs->getString ("str-noparse"));
0066     setAllowEmptyWalkStr (gs->getBool ("empty-walk"));
0067     setExpandBackslashes (gs->getBool ("expand-backslashes"));
0068   }
0069 }
0070 
0071 void cCmdParser::setCmdSeparatorString (QString str)
0072 {
0073   separstr = str.trimmed();
0074 }
0075 
0076 void cCmdParser::setMultiCommandString (QString str)
0077 {
0078   multistr = str.trimmed();
0079 }
0080 
0081 void cCmdParser::setSpeedWalkString (QString str)
0082 {
0083   walkstr = str.trimmed();
0084 }
0085 
0086 void cCmdParser::setNoParseString (QString str)
0087 {
0088   noparsestr = str.trimmed();
0089 }
0090 
0091 void cCmdParser::setMacroString (QString str)
0092 {
0093   macrostr = str.trimmed();
0094 }
0095 
0096 void cCmdParser::setAllowEmptyWalkStr (bool val)
0097 {
0098   allowemptywalkstr = val;
0099 }
0100 
0101 void cCmdParser::setParsing (bool value)
0102 {
0103   isparsing = value;
0104 }
0105 
0106 QStringList cCmdParser::parse (const QString &command, bool expandAliases)
0107 {
0108   QStringList result;
0109 
0110   // no parsing or empty string- only return what was given
0111   if ((!isparsing) || command.isEmpty())
0112   {
0113     result.append (command);
0114     return result;
0115   }
0116 
0117   // look whether we should send the text as-is...
0118   if ((noparsestr.length() > 0) && (command.startsWith (noparsestr)))
0119   {
0120     QString t = command.mid (noparsestr.length());
0121     result.append (markAsRaw (t));
0122     return result;
0123   }
0124 
0125   // split into individual commands, then process each of them
0126   QStringList commands = splitIntoCommands (command);
0127   QStringList::iterator it;
0128   for (it = commands.begin(); it != commands.end(); ++it) {
0129     if ((*it).isEmpty())
0130       // empty command is simply added to the list
0131       result.append (*it);
0132     else
0133       // non-empty command - parse it, then add the results to our list
0134       result += parseCommand (*it, expandAliases);
0135   }
0136 
0137   return result;
0138 }
0139 
0140 QStringList cCmdParser::parseCommand (const QString &command, bool expandAliases)
0141 {
0142   QStringList result, res;
0143   bool aliasesExpanded = false;
0144 
0145   // look if this command should be sent as-is
0146   if ((noparsestr.length() > 0) && (command.startsWith (noparsestr)))
0147   {
0148     QString t = command.mid (noparsestr.length());
0149     result.append (markAsRaw (t));
0150     return result;
0151   }
0152 
0153   QString cmd = command;   // we need to modify the command
0154 
0155   // first look if there isn't a repeater sequence
0156   // if not, then repeater keeps original command and returns 1,
0157   // so we can handle both situations together
0158   int rep = repeater (cmd);
0159 
0160   // perform alias expansion
0161   if (expandAliases) {
0162     cList *al = cListManager::self()->getList (sess(), "aliases");
0163     cAliasList *aliases = al ? dynamic_cast<cAliasList *>(al) : nullptr;
0164     if (aliases && aliases->matchString (cmd))
0165     {
0166       aliasesExpanded = true;
0167       QStringList cmds = aliases->commandsToExec ();
0168       QStringList::iterator it;
0169       for (it = cmds.begin(); it != cmds.end(); ++it)
0170         // recursive call, but this time no alias expansion will occur
0171         // (we don't want recursive aliases)
0172         res += parse (*it, false);
0173     }
0174   }
0175 
0176   // if we expanded aliases, these things were already done for commands in the list, hence we
0177   // only do this if no expansion occured
0178   if (!aliasesExpanded) {
0179     // expand backslashes
0180     expandBackslashes (cmd);
0181 
0182     // add the command to the list, expanding speed-walk if needed
0183     int spdpos = isSpeedWalkCommand (cmd);
0184     if (spdpos != -1)
0185       res = expandSpeedWalk (cmd, spdpos);
0186     else
0187       res.append (cmd);
0188   }
0189 
0190   // return results ...
0191 
0192   // only one repeat - return the resulting list
0193   if (rep == 1) return res;
0194 
0195   // more repeats - generate the result, using requested number of repeats
0196   for (int r = 0; r < rep; r++)
0197     result += res;
0198   return result;
0199 }
0200 
0201 static QStringList mergeEscaped (const QStringList &cmds, QString separator)
0202 {
0203   // for each command in the list, check if it ends in an odd number of \-s, if it does, and if
0204   // backslash expansion is allowed, we assume that the user wanted to protect this occurence
0205   // or command separator from expansion - hence we merge commands back ...
0206   QStringList cmds2;
0207   QString command = QString();
0208   QStringList::const_iterator it;
0209   for (it = cmds.begin(); it != cmds.end(); ++it)
0210   {
0211     command += *it;
0212     int len = command.length();
0213     if (!len) continue;
0214     int cnt = 0;
0215     if (command[len-1] == '\\') {
0216       // count trailing \s
0217       for (int i = 1; i <= len; ++i)
0218         if (command[len - i] == '\\')
0219           cnt = i;
0220         else
0221           break;
0222     }
0223     if (cnt % 2 == 1)
0224       command += separator;
0225     else
0226     {
0227       cmds2.append (command);
0228       command = QString();
0229     }
0230   }
0231   if (!command.isEmpty())
0232     cmds2.append (command);
0233 
0234   return cmds2;
0235 }
0236 
0237 QStringList cCmdParser::splitIntoCommands (const QString &text)
0238 {
0239   QStringList cmds;
0240   if (text.length() == 0)
0241     return cmds;
0242 
0243   // split the command, basing on the separator string
0244   if (separstr.length() > 0)
0245     cmds = text.split (separstr);
0246   else
0247     cmds << text;
0248   QStringList::iterator it;
0249 
0250   // if we don't want to expand backslashes, most further things don't need to be done
0251 
0252   if (expandbackslashes) {
0253     QStringList cmds2 = mergeEscaped (cmds, separstr);
0254 
0255     // Now we have a list of commands, but we still need to expand \n separators ...
0256     cmds.clear ();
0257     for (it = cmds2.begin(); it != cmds2.end(); ++it)
0258     {
0259       // look if there's any "\n" in the string
0260       if ((*it).indexOf ("\\n") != -1) {
0261         // there is some \n
0262         QStringList c = (*it).split ("\\n");
0263         cmds += mergeEscaped (c, "\\n");
0264       }
0265       else {
0266         // there is no \n
0267         cmds.append (*it);
0268       }
0269     }
0270   }
0271 
0272   // finally, remove leading/trailing spaces from each command, if needed
0273   if (stripSpaces) {
0274     QStringList cmds2;
0275     for (it = cmds.begin(); it != cmds.end(); ++it)
0276       cmds2.append ((*it).trimmed());
0277     return cmds2;
0278   }
0279 
0280   return cmds;
0281 }
0282 
0283 bool cCmdParser::mustSendRaw (const QString &command)
0284 {
0285     //This is to fix a qt4 empty string crash
0286     if (command.isEmpty()) return false;
0287   return (command[0].toLatin1() == 0x01);
0288 }
0289 
0290 QString cCmdParser::fixRaw (const QString &command)
0291 {
0292   if (!mustSendRaw (command))
0293     return command;
0294   return command.mid (1);
0295 }
0296 
0297 QString cCmdParser::markAsRaw (const QString &command)
0298 {
0299   return QChar (0x01) + command;
0300 }
0301 
0302 int cCmdParser::isSpeedWalkCommand (const QString &command)
0303 {
0304   bool sw = false;
0305 
0306   if ((walkstr.length() > 0) && (command.startsWith (walkstr)))
0307     sw = true;
0308 
0309   //empty walk-string (if allowed)
0310   if ((walkstr.length() == 0) && allowemptywalkstr)
0311     sw = true;
0312 
0313   int pos = -1;
0314   if (sw)
0315   {
0316     pos = walkstr.length();
0317     for (int i = pos; i < command.length(); i++)
0318       switch (command[i].toLatin1()) {
0319         case ' ':   //spaces are ignored
0320         case 'n':
0321         case 'e':
0322         case 's':
0323         case 'w':
0324         case 'h':
0325         case 'j':
0326         case 'k':
0327         case 'l':
0328         case 'u':
0329         case 'd':
0330         case '0':
0331         case '1':
0332         case '2':
0333         case '3':
0334         case '4':
0335         case '5':
0336         case '6':
0337         case '7':
0338         case '8':
0339         case '9': break;
0340         default: return -1;
0341       };
0342   }
0343   else
0344     return -1;
0345 
0346   //passed all the tests - it's speed-walk command!
0347   return pos;
0348 }
0349 
0350 QStringList cCmdParser::expandSpeedWalk (const QString &command, int pos)
0351 {
0352   //we could have computed the position again, but it would be inefficient
0353   QStringList list;
0354   QString cmd;
0355   bool wascmd;
0356   int count = 0;
0357   cProfileSettings *sett = settings();
0358   QString movecmds[10];
0359   for (int i = 0; i < 10; ++i)
0360     movecmds[i] = sett->getString ("movement-command-" + QString::number (i));
0361   for (int i = pos; i < command.length(); i++)
0362   {
0363     wascmd = true;
0364     switch (command[i].toLatin1()) {
0365       case 'n': cmd = movecmds[0]; break;
0366       case 'e': cmd = movecmds[2]; break;
0367       case 's': cmd = movecmds[4]; break;
0368       case 'w': cmd = movecmds[6]; break;
0369       case 'j': cmd = movecmds[1]; break;
0370       case 'l': cmd = movecmds[3]; break;
0371       case 'k': cmd = movecmds[5]; break;
0372       case 'h': cmd = movecmds[7]; break;
0373       case 'u': cmd = movecmds[8]; break;
0374       case 'd': cmd = movecmds[9]; break;
0375       case '0':
0376       case '1':
0377       case '2':
0378       case '3':
0379       case '4':
0380       case '5':
0381       case '6':
0382       case '7':
0383       case '8':
0384       case '9':
0385         wascmd = false;
0386         count = count * 10 + (command[i].toLatin1() - '0');
0387         break;
0388       case ' ':
0389         //and spaces are ignored...
0390         wascmd = false;
0391         break;
0392     }
0393     if (wascmd)
0394     {
0395       if (count == 0) count = 1;
0396       for (int j = 0; j < count; j++)
0397         list.append (cmd);
0398       count = 0;
0399     }
0400   }
0401 
0402   return list;
0403 }
0404 
0405 unsigned int cCmdParser::repeater (QString &command)
0406 {
0407   bool multi = false;
0408   if ((multistr.length() > 0) && (command.startsWith (multistr)))
0409     multi = true;
0410 
0411   if (multi)
0412   //chance is that there is some repeater
0413   {
0414     QString cmd = command;
0415     cmd.remove (0, multistr.length());
0416     cmd = cmd.trimmed ();
0417     QString rep = cmd.section (' ', 0, 0); //first section
0418     //if rep is a number, then this is a multi-command
0419     bool ok;
0420     unsigned int repc = rep.toUInt (&ok);
0421     if (ok) //GREAT!!! This really is a repeater command!!!
0422     {
0423       command = command.section (' ', 1); //everything except the 1st section
0424 
0425       //limit repeat count if needed
0426       cProfileSettings *sett = settings ();
0427       bool limitRepeats = sett ? sett->getBool ("limit-repeater") : true;
0428 
0429       if ((repc > 100) && limitRepeats)
0430       {
0431         invokeEvent ("message", sess(), i18n ("Sorry, count is limited to 100.\n"));
0432         if (sett)
0433           invokeEvent ("message", sess(),
0434               i18n ("You can disable this limit in Profile / MUD Preferences."));
0435         //return 0 - nothing will be sent...
0436         return 0;
0437       }
0438       return repc;
0439     }
0440     else       //no repeater here...
0441       return 1;
0442   }
0443   else  //no repeater -> count is 1
0444     return 1;
0445 }
0446 
0447 bool cCmdParser::isMacroCall (const QString &command, QString &mname, QString &params)
0448 {
0449         //This is to fix a qt4 empty string crash
0450     if (command.isEmpty()) return false;
0451   bool sc1 = false, sc2 = false;
0452   if ((macrostr.length() > 0) && (command.startsWith (macrostr)))
0453     sc1 = true;
0454   else
0455     if (expandbackslashes && (command[0].toLatin1() == 0x02))
0456       sc2 = true;
0457 
0458   if (sc1 || sc2)
0459   {
0460     QString cmd = command;
0461     //remove the leading macrostr or 0x02 (\m gets expanded to this)
0462     if (sc1)
0463       cmd.remove (0, macrostr.length());
0464     else
0465       cmd.remove (0, 1);
0466     cmd = cmd.trimmed();
0467     //first word
0468     mname = cmd.section (' ', 0, 0);
0469     //everything else
0470     params = cmd.section (' ', 1);
0471     params = params.trimmed();
0472     return true;
0473   }
0474   else
0475     return false;
0476 }
0477 
0478 void cCmdParser::expandBackslashes (QString &command)
0479 {
0480   //do nothing if we don't want to expand here
0481   if (!expandbackslashes)
0482     return;
0483 
0484   QString cmd = "";
0485 
0486   bool backslash = false;
0487   int len = command.length();
0488   for (int i = 0; i < len; i++)
0489   {
0490     QChar ch = command[i];
0491     if (backslash)
0492     {
0493       if (ch.toLatin1() == 't')  //got \t
0494         cmd += (QChar) '\t';
0495       else if (ch.toLatin1() == 'e')  //got \e
0496         cmd += (QChar) '\e';
0497       else if ((ch.toLatin1() == 'm') && (i == 1))  //got \m on the beginning of the string
0498         cmd += (QChar) 0x02;
0499       else
0500         cmd += ch;
0501       backslash = false;
0502     }
0503     else
0504     {
0505       if (ch == QChar ('\\'))
0506         backslash = true;
0507       else
0508         cmd += command[i];
0509     }
0510   }
0511   command = cmd;
0512 }
0513