对于C ++程序员来说,更复杂的任务之一是在合理的时间段内对解析器进行编码。 在为SQL或C ++等成熟的语言开发编译器时,使用GNU Flex / Bison或ANTLR解析器生成器通常很有意义。 但是对于使用更简单的Backus Naur Form(BNF)的语法而言,这些工具的陡峭学习曲线并不总是能够证明投资的合理性。 另一种选择是使用与标准Linux®发行版或Boost regex
或tokenizer
库捆绑在一起的regex
库,但是这些库在使用更多涉及的语法时无法很好地扩展。
本文介绍了Boost的高度可扩展的Spirit解析器框架。 该解析器生成器使用C ++编码的扩展Backus Naur形式(EBNF)规范工作,从而大大减少了开发时间。 有关进一步的阅读,请参阅非常详细的Spirit文档。
安装精神
您可以从Boost网站免费下载Spirit框架(请参阅参考资料部分)。 在开始使用Spirit进行开发之前,请注意以下几点:
- 您必须在源代码中包含
<spirit.hpp>
标头。 该头文件大量使用模板元编程和函子。 本文中的所有代码均已使用g ++-3.4.4进行了编译。 确保您使用的是支持这些C ++功能的编译器。 - Spirit框架的一部分在内部使用Boost的正则表达式库。 安装后,在已安装的代码库中检查
regex.h
标头。 - 确保Boost安装的根目录在编译器
include
搜索路径中。 - Spirit是仅标头的库,因此在链接时不需要额外的库。 对于
regex
库而言并非如此。 要将regex
源仅包括为标头,define BOOST_SPIRIT_NO_REGEX_LIB
在代码中使用preprocessor指令define BOOST_SPIRIT_NO_REGEX_LIB
。
您的第一个Spirit程序
给定一个随机的单词列表,采用真正的C ++风格,您的第一个Spirit程序列出在列表中找到的唯一出现的Hello World (即输入流中连续出现的Hello和World单词)的数量。 参见清单1; 输出为2。
清单1.代码列出单词Hello World在输入流中出现的次数
#define BOOST_SPIRIT_NO_REGEX_LIB
#include "regex.h"
#include "spirit.hpp"
#include "boost/spirit/actor.hpp"
using namespace boost::spirit;
const string input = "This Hello World program using Spirit counts the number of
Hello World occurrences in the input";
int main ()
{
int count = 0;
parse (input.c_str(),
*(str_p("Hello World") [ increment_a(count) ]
|
anychar_p)
);
cout << count >> endl;
return 0;
}
Spirit框架的强大之处在于它为许多基本类型(例如单个字符,数字和字符串)提供了内置的解析器。 通常使用这些内置的解析器对象创建更复杂的解析器。 在清单1中 , str_p
和anychar_p
从精神预定义解析器- str_p
它与提供的字符串匹配(在这种情况下, 世界您好 ),并成功调用increment_a
由1例行增量次数anychar_p
是另一个预定义解析器,任何比赛字符。
现在让我们来看一下parse
函数,它可以说是Spirit框架中最重要的例程。 它接受输入流和语法,并在内部通过该语法运行流。 在这种情况下,输入流来自input.c_str()
,而str_p
和anychar_p
提供了语法的语义。 如果您熟悉解析,您会很快意识到parse
函数的第二个参数等效于提供BNF。
其他预定义的Spirit解析器
考虑遵循此模式的字符串: <employee name: string> <employee id: int> <employee rating: float>
。 您需要基于从此字符串提取的数据来填充Employee
数据结构。 这是一个典型的字符串: "Alex 8 9.2 Jim 91 5.6"
。
Spirit具有用于字符( alpha_p
),整数( int_p
)和实数( real_p
)的预定义解析器。 基于此,可以肯定地说应该使用如下语法调用parse
例程: parse(input.c_str(), alpha_p >> int_p >> real_p)
。 逻辑是, parse