最近工作中要用到一些静态文本分析的功能, 大体说来就是按照内部定义的格式解析一个配置文件.以前也做过类似的工作, 当时想当然直接用win32 API读取INI文件.当然项目是完成了,而时隔几年之后, 又要用到类似的功能, 有什么其他解决方案呢.
这里不得不提到DSL和EBNF.具体的定义实在不是三言两语能讲得清楚的, 不过好在我们有wiki.
基本上, 我们定义自己的配置文件格式, 相当于定义了自己的文法.而在应用中, 按照我们事先定义好的文法来解析并读取这些配置文件, 也就完成了这一循环.
忘记在哪里看到这么一句话"C++程序员都是脑力劳动的施虐者和受虐者", 所以我们有了Boost. 而我们的boost::spirit, 帮我们提供了一个绝好的定义DSL的工具.
从这篇文章开始, 我会用一个个的小例子来逐渐引入spirit, 最后实现并超过原有的需求.
很多例子以及源代码都是来在网络, 包括但不限于(http://boost-spirit.com/home/, http://www.codeproject.com/, 等等)
<一> 计算器
这是Spirit的作者推荐的快速开始的例子,废话少说, 主要的代码如下(稍微做了些修改)
struct CalculatorParser : public grammar<CalculatorParser>
{
template<typename ScannerType>
struct definition
{
definition(const CalculatorParser & c)
{
factor = real_p[& push_real]
| '(' >> expression >> ')'
| ('-' >> factor[&do_neg])
| ('+' >> factor);
term = factor >>
*(
('*' >> factor[Calculator<multiplies<double> >()])
| ('/' >> factor[Calculator<divides<double> >()])
);
expression = term >>
*(
('+' >> term[Calculator<plus<double> >()])
| ('-' >> term[Calculator<minus<double> >()])
) >>
ch_p(';');
}
rule<ScannerType> expression, term, factor;
rule<ScannerType> const&
start() const
{
return expression;
}
};
};
文法的核心就定义在expression, term, factor中了
factor是基本单元, 在满足real_p的时候(即factor是个real variable), 执行push_real(函数指针)
而factor也可以是括号内的表达式, 负数或显式指定的正数
term在做乘除运算而expression则是在做加减运算,同时整个表达式以";"作为结束符
以上我们就定义了一个非常简单的EBNF,如何,方便吧
使用时,直接parse即可
parse_info<> info = parse("1+2;", CalculatorParser());
以上, 我们简单接触了EBNF, 可以看出了,要想解析自定义的格式, 使用spirit是非常方便的一件事
下一篇, 我们会来看一个稍微复杂些的例子, 并用到更多的功能