File indexing completed on 2025-01-12 06:47:25
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 ¶ms) 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