由于您似乎只是在寻找触发函数以响应已解析文本的方法,因此我提出了这个基于 Boost Spirit 解析器的示例:
目标,示例服务
我希望能够调用各种类的已有函数:/exec <functionName> <param1> <param2>
假设您的应用程序具有以下现有类,这些类代表用户应该能够使用文本命令调用的服务:
struct Echo
{
void WriteLine(const std::string& s) { std::cout << "WriteLine('" << s << "');" << std::endl; }
void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
void WriteInt (int i) { std::cout << "Write(int: " << i << ");" << std::endl; }
void WriteDbl (double d) { std::cout << "Write(double: " << d << ");" << std::endl; }
void NewLine () { std::cout << "NewLine();" << std::endl; }
} echoService;
struct Admin
{
void Shutdown(const std::string& reason, int retval)
{
std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
// exit(retval);
}
} adminService;
方法和解释(TL;DR?跳过这个)
那么我们如何将基于行的输入绑定到这个“接口”?
有两个工作
您一直强调为评估提供基础设施。但是,您会遇到不知道要传递哪些参数的问题。当然,您在解析时知道。您真的很想避免将参数存储在通用容器中。 (当然,你可以抛出 boost 选项,boost(递归)变体,一次boost任何一个,但让我们面对现实吧:这仍然是繁琐的工作)。
对于具有语义操作的解析器来说,这是一个绝佳的机会。 Lex/yacc、Coco/R C++、ANTLR 等等都支持它们。 Boost Spirit Qi 也是如此。
事不宜迟,这就是上述服务的完整、简约的行语法的样子:
parser = "/execute" > (
(lit("WriteLine") > stringlit)
| (lit("Write") >> +(double_ | int_ | stringlit))
| lit("NewLine")
| (lit("Shutdown") > (stringlit > -int_))
// stringlit is just a quoted string:
stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];
注意:我决定向您展示如何在 /execute Write 调用中接受任意数量的各种类型的参数
添加语义操作以将其绑定到我们的 echoService 和 adminService 对象,我们让这个 REPL 引擎解析和评估一行:
void execute(const std::string& command)
{
typedef std::string::const_iterator It;
It f(command.begin()), l(command.end());
if (!phrase_parse(f,l, "/execute" > (
(lit("WriteLine")
> stringlit [ bind(&Echo::WriteLine, ref(echoService), _1) ])
| (lit("Write") >> +(
double_ [ bind(&Echo::WriteDbl, ref(echoService), _1) ]
| int_ [ bind(&Echo::WriteInt, ref(echoService), _1) ]
| stringlit [ bind(&Echo::WriteStr, ref(echoService), _1) ]
))
| (lit("NewLine") [ bind(&Echo::NewLine, ref(echoService)) ])
| (lit("Shutdown") > (stringlit > (int_ | attr(0)))
[ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
), space))
{
// handle error, see full code below
}
}
现在我们要做的就是编写一个主循环:
int main()
{
std::string command;
while (std::getline(std::cin, command))
execute(command);
}
这很简单,不是吗?
完整的工作示例程序
我在 github 1 上发布了这个程序的完整工作示例:https://gist.github.com/1314900
有
- 添加了完整的错误处理/报告
- 使用输入文件而不是 std::cin 来实现(易于测试)
- 将帮助您开始这项工作(命名空间,包括)
您只需要 Boost。我用 g++ 4.6.1(没有特殊选项)和 Boost 1.47 对此进行了测试。对于以下测试输入(input.txt):
/execute WriteLine "bogus"
/execute Write "here comes the answer: "
/execute Write 42
/execute Write 31415e-4
/execute Write "that is the inverse of" 24 "and answers nothing"
/execute Shutdown "Bye" 9
/execute Shutdown "Test default value for retval"
演示程序的输出是
WriteLine('bogus');
Write(string: 'here comes the answer: ');
Write(double: 42);
Write(double: 3.1415);
Write(string: 'that is the inverse of');
Write(double: 24);
Write(string: 'and answers nothing');
Shutdown(reason: 'Bye', retval: 9)
Shutdown(reason: 'Test default value for retval', retval: 0)
- 注意:我注释掉了
exit(...) 调用,以便演示如何在提供默认值 (attr(0)) 的同时使 retval 参数可选
- 注意:双重
31415e-4 如何正确打印为3.1415
- 注意:
/execute Write 的多个参数如何在对echoService.Write(...) 的单独调用中被转换,具体取决于输入参数的实际类型
- 注意:如何完全忽略内联空格(字符串文字之外)的存在。您可以使用任意数量的制表符/空格
1 为了子孙后代的利益,github是否应该停止承载我的要点:
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <fstream>
namespace qi = boost::spirit::qi;
namespace phx = boost::phoenix;
///////////////////////////////////
// 'domain classes' (scriptables)
struct Echo
{
void WriteLine(const std::string& s) { std::cout << "WriteLine('" << s << "');" << std::endl; }
void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
void WriteInt (int i) { std::cout << "Write(int: " << i << ");" << std::endl; }
void WriteDbl (double d) { std::cout << "Write(double: " << d << ");" << std::endl; }
void NewLine () { std::cout << "NewLine();" << std::endl; }
} echoService;
struct Admin
{
void Shutdown(const std::string& reason, int retval)
{
std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
// exit(retval);
}
} adminService;
void execute(const std::string& command)
{
typedef std::string::const_iterator It;
It f(command.begin()), l(command.end());
using namespace qi;
using phx::bind;
using phx::ref;
rule<It, std::string(), space_type> stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];
try
{
if (!phrase_parse(f,l, "/execute" > (
(lit("WriteLine")
> stringlit [ bind(&Echo::WriteLine, ref(echoService), _1) ])
| (lit("Write") >> +(
double_ [ bind(&Echo::WriteDbl, ref(echoService), _1) ] // the order matters
| int_ [ bind(&Echo::WriteInt, ref(echoService), _1) ]
| stringlit [ bind(&Echo::WriteStr, ref(echoService), _1) ]
))
| (lit("NewLine") [ bind(&Echo::NewLine, ref(echoService)) ])
| (lit("Shutdown") > (stringlit > (int_ | attr(0)))
[ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
), space))
{
if (f!=l) // allow whitespace only lines
std::cerr << "** (error interpreting command: " << command << ")" << std::endl;
}
}
catch (const expectation_failure<It>& e)
{
std::cerr << "** (unexpected input '" << std::string(e.first, std::min(e.first+10, e.last)) << "') " << std::endl;
}
if (f!=l)
std::cerr << "** (warning: skipping unhandled input '" << std::string(f,l) << "')" << std::endl;
}
int main()
{
std::ifstream ifs("input.txt");
std::string command;
while (std::getline(ifs/*std::cin*/, command))
execute(command);
}