简单来说,Spirit 是一个 parser generator,功能与 Yacc,ANTLR 类似,且也是基于 EBNF 来描述文法,再基于文法生成 parser,但与前面这些工具相比,它最大的不同点在于它使用了 C++ 代码来对文法进行描述,通过非常残暴的模板编程技巧,在编译阶段就生成了相应的 parser。从使用者的角度来看,文法是用代码进行描述的,因此它天生就能直接加入到你当前的工程中与现成代码揉合在一起。
当然,Spirit 的文法在形式上 EBNF 还是有一点点的出入,比如说,用 ">>" 来连接不同表达式,表示重复的符号放在了表达式的前面等,这些都是受 c++ 语法的限制所做出的折衷,文法的语义其实未变。
一个经典的整数四则运算如用 EBNF 来描述的话,可以写成如下的形式:
group ::= '(' expression ')'
factor ::= integer | group
term ::= factor (('*' factor) | ('/' factor))*
expression ::= term (('+' term) | ('-' term))*
其中 group, factor, term, expression 分别称为一个 rule,用于表示怎么去匹配一条相应的文本,上述 EBNF 文法在 Spirit 中可以写成如下形式:
group = '(' >> expression >> ')';
factor = integer | group;
term = factor >> *(('*' >> factor) | ('/' >> factor));
expression = term >> *(('+' >> term) | ('-' >> term));
看起来语法好像差不多,只是运算符有些不同,需要指明的是,在 Spirit 中一个 rule 就是一个 parser 对象,一个 parser 对象包含了相应的语法规则使得该 parser 只能 parse 符合这些规则的文本,比如说,我们现在想 parse 出一组用逗号隔开的整数(CSV),则我们可以定义如下一个 rule:
boost::spirit::rule<> csv_int = int_p >> *(',' >> int_p);
上述代码中,int_p 是 Spirit 内建的一个 rule 或者说 parser,该 parser 专门用于 parse 一个整型(除了 int_p,Spirit 还内建了一系列用于 parse 其它基本数据类型的 parser, 具体列表参考这里)。parser 与 parser 通过 ">>" 运算符连接在一起后就组成了一个新的 parser,那么怎么来使用 csv_int 这个新生成的 parser 呢? Spirit 内建定义了一个函数,原型大概如下:
boost::spirit::parse_info<> parse(const char* text, parser, separator);
通过调用 parse() 函数传入需要 parse 的文本与相应的 parser 就能对该文本进行相应的解释,返回结果会指明 parse 的过程是否成功了,及如果出错,在哪个位置出错了。
前面定义的 csv_int 这个 parser 虽然定义了文法,但它基本没做什么事情,只能用来检查一下某段文本是不是一组逗号隔开的整型,功能显然太弱了,因为通常来说,我们是需要从文本中提取出数据来的,因此 parse 的过程需要支持某些动作,我们需要 parser 在 parse 到某些内容时,能够执行用户指定的行为动作,在 Spirit 中,这个些动作就叫作 semantic action.
Semantic action 是属于一个 parser 的,它的意义在于指明当该 parser 执行成功了之后,要执行哪些操作,而这些操作是由用户指定的。我们可以通过如下方式将一个 semantic action 与一个 parser 联系起来:
parser[func];
至于 func 的原型,当然是有要求的,而且这个要看具体的 parser, 比如说 int_p 这样的 parser,它就只能接受 void func(const int val);
这样的函数,很简洁的语法!现在我们来在将前面用于解释一组整型的代码中加入一个新功能,在 parse 完每一个整型后,我们将得到的数据保存下来。
#include <vector>
#include <boost/spirit.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
std::vector<int> g_output;
int on_parse_int(const int val)
{
g_output.push_back(val);
}
int main()
{
boost::spirit::rule<> int_csv_rule = int_p[on_parse_int] >> *(',' >> int_p[on_parse_int]);
boost::spirit::parse("2,3,4", int_csv_rule);
return 0;
}
semantic action 的实现依赖于 boost 里另一个名声显赫的函数式模板库:phoenix, 上面的例子只是一个简单示范,未及冰山一角,spirit 其实还支持用 lambda 来写回调函数,以及用闭包来在不同的 rule 之间传递用户定义的上下文信息等,功能很强大,有兴趣的读者可以参考下这里相对完整点的一个例子,它实现了一个简单的四则运算及基本的函数调用。
Parser 是整个 Spirit 库的核心功能所在,那么 Spirit 生成的 parser 是怎么进行工作呢? 结论是,spirit 所生成的 parser 就是所谓的 LL recursive decent parser,因此 parse 的时候是从左往右扫描输入,而对 parser 中的文法,采取先左后右的顺序进行匹配的,因此左递归之类的问题需要使用者自己消除,对如下一个例子:
rule<> rule1 = (int_p >> ',' >> int_p) | real_p;
rule1 在 parse 文本时,会优先匹配 (int_p >> ',' >> int_p),如若失败,则再去匹配 real_p。
因为使用该库的时间还不是很长,初步上手的感觉,优点上个人觉得有如下几点:
结论就是:很好很强大,但与此同时,缺点也明显:
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/karma.hpp>
#include <boost/spirit/include/karma_stream.hpp>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib>
using namespace boost::spirit;
using namespace boost::spirit::ascii;
///
int main()
{
///
// vector
std::vector<int> v (8);
std::generate(v.begin(), v.end(), std::rand); // randomly fill the vector
std::cout << "Output 8 integers from a std::vector<int>..." << std::endl;
// output the container as a sequence without any separation
std::cout << "...without any separation" << std::endl;
std::cout <<
karma::format(
*int_, // format description
v // data
) << std::endl << std::endl;
// output the container as a space separated sequence
std::cout << "...as space delimited list" << std::endl;
std::cout <<
karma::format_delimited(
*int_, // format description
space, // delimiter
v // data
) << std::endl << std::endl;
std::cout <<
karma::format_delimited(
'[' << *int_ << ']', // format description
space, // delimiter
v // data
) << std::endl << std::endl;
// output the container as a comma separated list
std::cout << "...as comma separated list" << std::endl;
std::cout <<
karma::format(
int_ % ", ", // format description
v // data
) << std::endl << std::endl;
std::cout <<
karma::format(
'[' << (int_ % ", ") << ']', // format description
v // data
) << std::endl << std::endl;
// output the container as a comma separated list of double's
std::cout << "...as comma separated list of doubles" << std::endl;
std::cout <<
karma::format(
double_ % ", ", // format description
v // data
) << std::endl << std::endl;
// output the container as a comma separated list of items enclosed in '()'
std::cout << "..as list of ints enclosed in '()'" << std::endl;
std::cout <<
karma::format(
('(' << int_ << ')') % ", ", // format description
v // data
) << std::endl << std::endl;
std::cout <<
karma::format(
'[' << (
('(' << int_ << ')') % ", "
) << ']', // format description
v // data
) << std::endl << std::endl;
// output the container as a HTML list
std::cout << "...as HTML bullet list" << std::endl;
std::cout <<
karma::format_delimited(
"<ol>" <<
// no delimiting within verbatim
*verbatim[" <li>" << int_ << "</li>"]
<< "</ol>", // format description
'\n', // delimiter
v // data
) << std::endl;
// output the container as right aligned column
std::cout << "...right aligned in a column" << std::endl;
std::cout <<
karma::format_delimited(
*verbatim[
"|" << right_align[int_] << "|"
], // format description
'\n', // delimiter
v // data
) << std::endl;
std::cout << std::endl;
return 0;
}
https://www.boost.org/doc/libs/1_41_0/libs/spirit/example/karma/quick_start1.cpp
template<class T>
std::string spiritKarmaJoin(const T &Arr)
{
return static_cast<std::stringstream&>(
std::stringstream().seekp(0)
<< boost::spirit::karma::format((boost::spirit::karma::auto_ % ","), Arr)).str();
}
其中:
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>
int main()
{
using boost::adaptors::transformed;
using boost::algorithm::join;
std::vector<double> v{1.1, 2.2, 3.3, 4.4};
std::cout
<< join( v |
transformed( static_cast<std::string(*)(double)>(std::to_string) ),
", " );
}
参考:
https://www.cnblogs.com/catch/p/3921751.html
https://www.ibm.com/developerworks/cn/aix/library/au-boost_parser/index.html
https://blog.csdn.net/GW569453350game/article/details/47807123
https://panthema.net/2018/0912-Boost-Spirit-Tutorial/
https://panthema.net/2018/0912-Boost-Spirit-Tutorial/examples/