boost::spirit 的一些记录

作者:李嘉
日期:2007-10-11
未经书面许可,谢绝任何商业目的的转载,转载请保留以上声明

最近的工作跟 boost::spirit 多一些,本来想整理一个较为系统的笔记,不过感觉目前思路还比较凌乱,先随便记录一些。

spirit 是 boost 中的一个 LL解析器框架,他完成实现一个 EBNF 语法解析器的功能,但它的输入为C++语言。(LL parser framework  represents parsers directly as EBNF grammars in inlined C++, from Joel de Guzman and team),它可以与 lex ,yacc 进行类比。lex 是生成词法解析器的一种工具,它将符合 lex 语法的源码生成相应的 C 代码,通常,lex产生一个成为yylex的函数,该函数接受一个字符串,并按照预定的词法规则将其分段成一系列的 token,而 yacc 通常则使用 lex 产生的结果,按照预定的语法规则(EBNF语法),生成一个成为 yyparse 的函数,它可以用于语法的解析。lex 和 yacc 分别有其不同语法格式,所以,使用 lex 和 yacc 的主要工作就是编写相应的 lex 源码和 yacc 源码。有关 lex 和 yacc 的介绍可以参考这里的文章 Yacc 与 Lex 快速入门 。

spirit 则更像 yacc,它的创新之处在于,它使用普通的 C++ 语法来表达 EBNF,而这样的表达是直接可以由C++编译器编译的。当然,出于 C++ 语法的限制,在一些细微的书写上,spirit 还是与 EBNF 写法略有不同,如一些标记的顺序,一些连接符的处理等,但总的来说,spirit 的语法描述对于熟悉 EBNF 描述的程序员来说,还是很像的。

考虑对于一个字符串的数字数组,我们希望用 spirit 解析出这个数组
1,2,3,4,5,6,7,8,9
那么,第一步,我们要描述出这个字符串的格式,用 spirit 描述的结果是 real_p >> *(ch_p(',') >> real_p),real_p 表示一个数字,括号表示将一些东西组合成一组,* 表示重复0或多次,这个描述用自然语言来说就是 数字跟着由 , 和 数字的组合0次或多次。

第二步,我们假设希望将这个解析到一个 std::vector<double> 类型的数组容器中以备后用。通过以下的代码完成这个功能

bool parse_numbers(char const* str, vector<double>& v)
{
  return parse(str,
    // Begin grammar
    ( real_p[push_back_a(v)] >> *(',' >> real_p[push_back_a(v)]) ) ,
    // End grammar
    space_p).full;
}

parse 函数是 spirit 的一个 API,它用于按照指定的语法规则解析一个字符串(未必是字符串,一个能够通过 iterator 操作的容器都可以),注意上述代码中加粗的中括号,它被称为语义动作(Semantic Actions),上述语法描述的自然语言就是说,解析那个字符串,当解析到一个数字时,就把它插入到数组容器 v 的后端。push_back_a 是 spirit 自带的一个标准语法动作,可以编写自定义的语法动作,它可以是一个函数或是一个函子,如
void print(double d)
{
  cout << d << endl;
}

struct printor
{
  void operator()(double d)
  {
    cout << d << endl;
  }
};
对应的实现分别是

bool parse_numbers(char const* str)
{
  return parse(str,
    // Begin grammar
    ( real_p[&print] >> *(',' >> real_p[&print]) ) ,
    // End grammar
    space_p).full;
}

bool parse_numbers(char const* str)
{
  return parse(str,
    // Begin grammar
    ( real_p[printor] >> *(',' >> real_p[printor]) ) ,
    // End grammar
    space_p).full;
}
这将一行一个数字将其打印到 stdout 上。

简单的说,使用 spirit 的过程就是,1.按照你的语法规则用 spirit 的格式编写语法声明,2.编写处理解析到某个元素的动作,3. 用 parse 函数将他们组合在一起。

这样,学习使用 spirit 的第一步就是了解 spirit 编写语法规则。spirit 的语法由 原子(Primitives)(某种字符), 操作符(Operators) 构成,原子通过操作符组合得到规则(rule),有一些规则被特化成原子,如数字(Numrics)。
操作符 包括连接操作符 >> ,组合操作符 (),或操作符 |等。
还有一些被称为指令(Directives)的东西也可以放置在语法规则中,他们的作用事实上是一些特定的解析属性,如将某一部分忽略大小写,尽可能长的解析等。
事实上,你会发现,语法规则的编写类似于编写正则表达式,其实,他们本身就是一回事。

然后,你需要编写语义动作,这个,spirit 没有提供任给你多少帮助,语义动作的编程类似于基于窗口的GUI编程,你编写一些响应函数,并跟你你感兴趣的事件注册到一起,这样,当某个事件发生时(spirit为解析到某个元素时),你的处理函数就会被调用。

最后,spirit 提供了多种类型的 parse 实现,他们有的只是简单的返回一个状态告诉你解析是否成功,有的则为你生成一颗二叉树,特别的,spirit 也提供了将解析结果表达成标准 AST (抽象语法树)的 parse 实现。

 

 

你可能感兴趣的:(C++,正则表达式,iterator,yacc,Numbers,Semantic)