一个简单高效的命令解析代码
命令解析其实应用到很多地方,常用的例如:
- 管理命令:GM(game manager),直播管理命令;一般是运营,管理人员,管理场景,管理房间等使用。
- 调试命令:例如实时查看进程信息,机器资源使用情况,打印详细日志等等。
- 权限命令:例如实时开通白名单/黑名单,给用户分配临时权限等等。
- 调度命令:人为实时更改函数调度,进程调度,机器调度等,流量分发、流量限制等,服务升降级等等。
命令的应用场景,好处,无须多讲。下面说说这个命令解析的格式,大致跟mysql client 连接差不多:
mysql -h test.command.com -P 6379 -u root -p
当然,有一些更简单的一些处理方法,例如:直接根据空格来进行参数分割。比如下面这个游戏gm命令,增加类型为1的货币1000。
addmoney 1 1000
这样看起来很简单,很清晰。但是,如果操作这个命令,需要加上操作日志呢?
addmoney 1 1000 "重启 补偿"
这时候以空格来分割就有问题了。因为日志字符串中存在空格。当然有些东西,你可以通过妥协来解决。再例如,需要增加一个新命令,减少货币。
submoney 1 1000
其实从货币的角度,增加,减少本质只是对同种事物的不同操作,很多时候,我们设计代码的时候,就会设计成
money 1 1 1000 // 增加 类型1 1000 money 2 1 1000 // 减小 类型1 1000
当然,这仅仅是增加、减少;还有货币转换、货币清空、货币转移等等。如果用同一个命令money,根据这种格式,扩展性就会显得很无力。
所以,这里实现了开头所说的格式,虽然看起来相对并不太人性化。但是,兼容性,扩展性,是好于空格分割的。
接下来,上代码,核心入口函数,就是parseArgs。
#include <iostream> #include <sstream> using namespace std; struct ArgInfo { void clear() { sOpt.clear(); sVal.clear(); } // 值能为空,键不能为空 inline bool empty() { return sOpt.empty(); } string sOpt; string sVal; }; enum { IN_VALUE = 1, AFTER_VALUE, }; #define LOG() cout inline void skipSpace(const char *sArgs, size_t lArgsLen, size_t &i) { for (; i < lArgsLen; ++i) { switch (sArgs[i]) { case ' ': case '\t': break; default: return; } } return; } bool parseOpt(const char *sArgs, size_t lArgsLen, size_t &i) { for (size_t lOld = i; i < lArgsLen; ++i) { switch (sArgs[i]) { case ' ': case '\t': case '\n': if (i == lOld) return false; return true; case '\'': case '\"': case '-': return false; default: break; } } return true; } bool parseVal(const char *sArgs, size_t lArgsLen, size_t &i) { for (size_t lOld = i; i < lArgsLen; ++i) { switch (sArgs[i]) { case ' ': case '\t': case '\n': if (i == lOld) return false; return true; case '-': return false; default: break; } } return true; } bool parseQuotesVal(const char *sArgs, size_t lArgsLen, size_t &i) { for (; i < lArgsLen; ++i) { switch (sArgs[i]) { case '\"': return true; case '\\': ++i; break; default: break; } } return false; } bool parseArgs(const string &sArgs, vector<ArgInfo> &vArgs) { if (sArgs.empty()) return true; ArgInfo tInfo; size_t i = 0; skipSpace(sArgs.c_str(), sArgs.size(), i); while (i < sArgs.size()) { size_t lEnd = i; const char c = sArgs[i]; if ( c == '-') { ++i; if (!parseOpt(sArgs.c_str(), sArgs.size(), ++lEnd)) { return false; } if (!tInfo.empty()) { vArgs.emplace_back(tInfo); tInfo.clear(); } tInfo.sOpt = string(sArgs, i, lEnd - i); } else { if (tInfo.sOpt.empty()) { return false; } if (c == '\"') { ++i; if (!parseQuotesVal(sArgs.c_str(), sArgs.size(), ++lEnd)) { return false; } } else { if (!parseVal(sArgs.c_str(), sArgs.size(), ++lEnd)) { return false; } } tInfo.sVal = string(sArgs, i, lEnd - i); vArgs.emplace_back(tInfo); tInfo.clear(); } skipSpace(sArgs.c_str(), sArgs.size(), ++lEnd); i = lEnd; } if (!tInfo.empty()) { vArgs.emplace_back(tInfo); } return true; } void unitTest(const vector<string> &vTest) { for (auto & sTest : vTest) { vector<ArgInfo> vRet; if (parseArgs(sTest, vRet)) { ostringstream oss; oss << " " << sTest << " => "; for (auto & tRet : vRet) { oss << " " << tRet.sOpt << "#" << tRet.sVal; } LOG() << oss.str() << endl; } else { LOG() << " Format error:" << sTest << endl; } } } void testRightFormat() { vector<string> vTest = { "", "-abc", "-a 123 -b abc", "-a -b 123abc", "-a 123abc -b", "-a \"12'ab\" -b \"cd 34\"", "-a -b \"123abc\"", "-a \"123abc\" -b", "-a \"123-abc\" -b", "-a \"\\\"123-abc\\\"\" -b" }; unitTest(vTest); } void testWrongFormat() { vector<string> vTest = { "a", "- a", "-a- 123 -b abc", "--a -b 123abc", "-\"a 123abc -b", "-\"a\" 123abc -b", "-a \"12'ab -b \"cd 34\"", "-a \"123abc\" -b ab cd", "-a \"123abc\" -b ab-cd", "-a 123 -b \"ab\"c" }; unitTest(vTest); } int main() { testRightFormat(); //testWrongFormat(); return 0; }