boost spirit 学习

编译原理初探

Posted by Matrixtang on September 20, 2019

boost 语法解析器学习

主要参考

EBNF学习

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以及由它们和操作符组成的整个或部分都可以称为解析器

  1. !符号代表其后的表达式是可选的,它代替了EBNF里的中括号功能。
  2. ​ ch_p()是一个Spirit预置的解析器生成函数,这个解析器用于匹配单个字符
  3. »用于代替逗号顺序连接后面的解析器
  4. +符号代表1次或多次重复
  5. ​ 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;
}