boost 语法解析器学习
EBNF基本形式
** | ** 分隔符,表示由它分隔的某一个子表达式都可供选择 |
**** 重复,和正则表达式里的类似,表示它之前的子表达式可重复多次
- 排除,不允许出现跟在它后面的那个子表达式
, 串接,连接左右子表达式
; 终止符,一条规则定义结束
’‘ 字符串
”“ 字符串
(…) 分组,就是平时括号的功能啦,改变优先级用的。
(*…*) 注释
[…] 可选,综括号内的子表达式允许出现或不出现
{…} 重复,大括号内的子表达式可以多次出现
在Spirit中语法和EBNF有一定的差异
- 星号重复符(*)由原来的后置改为前置
- 逗号串接符(,)由»或&&代替
- 中括号可选功能([表达式])改为(!表达式)
- 大括号重复功能({表达式})由重复符(*表达式)替代
- 取消注释功能。
Spirit入门
#include <boost/spirit.hpp>
引入 浮点数的计算
N=M×R^E^ 把规格化的浮点数用BNF表达
digit = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9";
real = ["+"|"-"], digit, [{digit}], [".", digit, [{digit}]], ["E"|"e", ["+"|"-"], digit, {digit}]
在EBNF中的表示
!(ch_p('+')|ch_p('-'))>>+digit_p>>(ch_p('.')>>+digit_p)>>
!((ch_p('e')|ch_p('E')) >> !(ch_p('+')|ch_p('-'))>>+digit_p)
重要的parse函数
解释一下上面的表示
在Spirit中,用于匹配表达式的对象叫解析器,如这里的ch_p, digit_p以及由它们和操作符组成的整个或部分都可以称为解析器。
- !符号代表其后的表达式是可选的,它代替了EBNF里的中括号功能。
- ch_p()是一个Spirit预置的解析器生成函数,这个解析器用于匹配单个字符。
- »用于代替逗号顺序连接后面的解析器
- +符号代表1次或多次重复
- digit_p也是一个Spirit预置的解析器,它匹配数字字符。
parse_info<charT const*> parse(字符串, 解析器);
parse_info<charT const*> parse(字符串, 解析器1, 解析器2);
====
以及迭代器的版本
parse_info parse(IteratorT first, IteratorT last, 解析器);
parse_info parse(IteratorT first, IteratorT last, 解析器1, 解析器2);
其中的==IteratorT==是任何迭代器
通过简单的解析器能生成更复杂的解析器
parse函数 通过调用解析器来解析字符串
#include <iostream>
#include "boost/spirit.hpp"
using namespace std;
using namespace boost::spirit;
int main()
{
parse_info<> r = parse("-12.33E-10",
!(ch_p('+') | ch_p('-')) >> +digit_p >>
!(ch_p('.') >> +digit_p) >>
!((ch_p('e') | ch_p('E')) >>
!(ch_p('+') | ch_p('-')) >> +digit_p)
);
cout << "parsed " << (r.full ? "successful" : "failed") << endl;
return 0;
}
可以写出这样的测试代码
支持的重载操作符
字符解析器支持的操作符
- ~a 排除操作,如~ch_p(‘x’)表示排除’x’字符
-
a b 二选一操作,或称为联合,匹配a or b - a&b 交集,同时匹配a和b
- a-b 差,匹配a但不匹配b
- a^b 异或,匹配a 或 匹配b,但不能两者同时匹配
- a»b 序列连接,按顺序先匹配a,接下来的字符再匹配b
- a&&b 同上(象C语言一样,有短路效果,若a不匹配,则b不会被执行)
-
a b 连续或,按顺序先匹配a,接下来的字符匹配b(象C语言一样,有短路效果,若a已匹配,则b不会被执行) - *a 匹配0次或多次
- +a 匹配1次或多次
- !a 可选,匹配0或1次
- a%b 列表,匹配a b a b a b a…,效果与 a » *(b » a)相同
解析实数序列
#include <iostream>
#include <boost/spirit.hpp>
using namespace std;
using namespace boost::spirit;
void showreal(double v)
{
cout << v << endl;
}
int main()
{
const char* szNumberList = "2333,6666,4242";
//加入函数
parse_info<> r = parse(szNumberList, real_p[&showreal] % ','); // [] 被重载过 可以放入Actor(函数对象)
cout << "parsed " << (r.full ? "successful" : "failed") << endl;
cout << szNumberList << endl;
//使用parse_info::stop确定最后解析的位置便于查错
cout << string(r.stop - szNumberList, ' ') << '^' << endl;
return 0;
}
但是 如果在带解析字符串中有空白字符存在的话 是不能解析成功的
就可以使用space_p跳过 (XXX_P 就是相应的解析器名称)
parse_info<> r = parse( szNumberList,
*real_p[push_back_a(reallist)],
space_p|ch_p(','));
这是parse的一个重载版本
parse(带解析字符串,解析器1,解析器2)
而push_back_a 这个函数是类似与vector的push_back函数 用于添加对象
解析四则运算
四则运算规则
1. 因子(factor) = 实数 (real) | ' ( ' , 表达式(exp) , ' ) '
2. 乘除操作(mul_div) factor,(( '*' , factor ) | ( ' / ' , factor))
3. 表达式(exp) = mul_div , ( ' + ' , factor) | ( ' - ' , factor ) // 包括运算优先级
转为 spirit 解析组合
rule<phrase_scanner_t> factor, mul_div, exp;
factor = real_p | ('(' >> exp >> ')');
mul_div = factor >> *(('*' >> factor) | ('/' >> factor));
exp = mul_div >> *(('+' >> factor) | ('-' >> factor));
其中的rule是规则类
template<
typename ScannerT = scanner<>,
typename ContextT = parser_context<>,
typename TagT = parser_address_tag>
class rule;
ScannerT 扫描器策略类 它有两类工作模式,一种是字符模式,一种是语法模式,默认的scanner<>是工作于字符模式的。
ContextT 内容策略类 它决定了rule里的成员变量以及Actor的类型,稍后会有利用这个模板参数来加入自定义的成员变量的例子
TagT 标识策略类
而后面的phrase_scanner_t 是工作在语法模式的参数 , 可以使用space_p跳过
测试代码如下
#include <iostream>
#include <vector>
#include <boost/spirit.hpp>
using namespace std;
using namespace boost::spirit;
int main()
{
rule<phrase_scanner_t> factor, mul_div, exp;
factor = real_p | ('(' >> exp >> ')');
mul_div = factor >> *(('*' >> factor) | ('/' >> factor));
exp = mul_div >> *(('+' >> factor) | ('-' >> factor));
const char szExp[] = { "1+(2*(3/(4+5)))" };
parse_info<> r = parse(szExp, exp, space_p);
cout << "parsed " << (r.full ? "successful" : "failed") << endl;
printf("%s", r.stop - r.length ); // 输出
return 0;
}
解析四则运算结果
可以使用Spirit 的 phoenix 来创建匿名函数
#include <boost/spirit/phoenix.hpp>
#include <iostream>
#include <vector>
#include "boost/spirit.hpp"
#include "boost/phoenix.hpp"
using namespace std;
using namespace boost::spirit;
using namespace phoenix;
int main()
{
//为rule准备一个val变量,类型为double
//准确地说:是一个phoenix类(这里的member1),它和其它phoenix类组成lambda表达式,在lambda中可以把它看成是一个double。
struct calc_closure : boost::spirit::closure<calc_closure, double>
{
member1 val;
};
//定义ContextT策略为calc_closure::context_t
rule<phrase_scanner_t, calc_closure::context_t> factor, mul_div, exp;
//直接使用phoenix的lambda表达式作为Actor
factor = real_p[factor.val = arg1] | ('(' >> exp[factor.val = arg1] >> ')');
mul_div = factor[mul_div.val = arg1] >> *(('*' >> factor[mul_div.val *= arg1]) | ('/' >> factor[mul_div.val /= arg1]));
exp = mul_div[exp.val = arg1] >> *(('+' >> mul_div[exp.val += arg1]) | ('-' >> mul_div[exp.val -= arg1]));
const char szExp[] = { "1+(2*(3/(4+5)))" };
double res = 0.0;
parse_info<> r = parse(szExp, exp[assign_a(res)], space_p);
cout << "parsed " << (r.full ? "successful" : "failed") << endl;
cout << szExp;
if (r.full)
{cout << " = " << res << endl;}
else
{cout << endl << string(r.stop - szExp, '') << '^' << endl;}
return 0;
}