最近为了解析SQL语法,怀着试一试的心态去翻了翻boost的spirit库,因为该库的文档的简介里写着LL parser framework represents parsers directly as EBNF grammars in inlined C++。看着framework这个词自然觉得这个库很牛B,试用了一下果然如此。
所谓EBNF即扩展巴克斯范式,是一种描述Context-Free Language的文法。在目前常见的非自然语言中,大部分都可以用EBNF表示。例如:
group ::='('exp ')'
factor ::=integer| group
term ::=factor(('*'factor)|('/'factor ))*
exp ::=term(('+'term)|('-'term ))*
这是一个整数表达式的EBNF。该段描述用spirit在C++中的实现则是:






这里使用=代替::=, 用>>代替空格连接。并且由于C++语法所限,EBNF中后置的*在spirit中改为前置。
等式左边的单词被称为一个rule,等式右边为rule的定义。我们可以看出一个group是一个exp加上一对括号,一个factor是一个整数或者一个group,一个term是一个或多个factor用*/连接,一个exp是一个或多个term用+-连接。处于最顶端的exp可以据此识别出以下表达式













得到一个rule之后,我们就可以用 parse函数对一个串进行识别了。例如
该函数返回一个结构parse_info,可以通过访问其中的full成员来判断是否成功识别,也可以访问stop成员来获知失败的位置。这里要特别提一点,关于各个符号之间的空格,spirit的文档的正文说的是给parse再传一个参数space_p,通知parse跳过所有的空格,然而在FAQ中又提到,如果使用以上方法定义rule,第三个参数传space_p会失败。原因是使用rule默认定义的规则被称为character level parsing,即字符级别解析,而parse的第3个参数仅适用于phrase level parsing,即语法级别解析。要使用第3个参数可以有几种方法。
1。在parse的第二个参数直接传入一个EBNF表达式,不创建rule对象。
2。以rule<phrase_scanner_t>创建rule。
注意虽然可以用这两个办法屏蔽空格,但是这样可能完全改变EBNF文法的语义,尤其是在语言本身需要识别空格的时候。对于这种情况,可以不使用第三个参数,并在需要出现空格的地方加上space_p,或者+space_p及*space_p,其中+和*分别表示后面的符号连续出现一次以上和0次以上。例如一个以空格分隔的整数列表可以写成int_p >> *(+space_p >> int_p)
如上使用parse可以识别一个串,但并不能做更多的操作,例如将语法里的各个成分提取出来。对于这样的需求,可以通过actor实现。下面是使用actor的一个简单例子
















注意到real_p后面的[],中括号里面是一个仿函数(函数指针或者函数对象),该仿函数具有如下调用型别



一旦spase发现了匹配real_p的子串,就会调用该functor。不同的rule可能会对应不同的调用型别。
第一个型别针对一般规则,first和last为两个指向字符的迭代器(一般为char*),匹配的子串为[first, last)
第二个型别针对数字型规则,如real_p和int_p, 参数val是一个数字类型。
第三个性别针对单字符型规则,如space_p, 参数ch是一个字符类型。
real_p[push_back_a(v)]中的push_back_a是一个spirit已经定义好的functor,它会将匹配好的内容依照匹配到的时间顺序调用v的push_back函数加入到v中。
到此spirit的常用功能就都介绍完了。要详细深入了解可以参考spirit的文档。
最后在题一个注意要点。spirit的各种EBNF连接都是指针连接,因此才能在expression被赋值前就在group的定义里面使用。所以在使用EBNF的时候一定要小心不要将局部变量的rule提供给全局或者类成员变量使用,例如:
















如果真想使用局部作用域,可以在局部的rule前面加上static.