12.4. 动作
到目前为止,你已经知道了如何定义一个语法,以得到一个新的词法分析器,用于识别一个给定的文本是否具有该语法的规则所规定的结构。 但是此刻,数据的格式仍未被解释,因为从结构化格式如 JSON 中所读取的数据并没有被进一步处理。
要对由分析器识别出来的符合某个特定规则的数据进行处理,可以使用动作(action)。 动作是一些与规则相关联的函数。 如果词法分析器识别出某些数据符合某个特定的规则,则相关联的动作会被执行,并把识别得到的数据传入进行处理,如下例所示。
- #include <boost/spirit.hpp>
- #include <string>
- #include <fstream>
- #include <sstream>
- #include <iostream>
- struct json_grammar
- : public boost::spirit::grammar<json_grammar>
- {
- struct print
- {
- void operator()(const char *begin, const char *end) const
- {
- std::cout << std::string(begin, end) << std::endl;
- }
- };
- template <typename Scanner>
- struct definition
- {
- boost::spirit::rule<Scanner> object, member, string, value, number, array;
- definition(const json_grammar &self)
- {
- using namespace boost::spirit;
- object = "{" >> member >> *("," >> member) >> "}";
- member = string[print()] >> ":" >> value;
- string = "\"" >> *~ch_p("\"") >> "\"";
- value = string | number | object | array | "true" | "false" | "null";
- number = real_p;
- array = "[" >> value >> *("," >> value) >> "]";
- }
- const boost::spirit::rule<Scanner> &start()
- {
- return object;
- }
- };
- };
- int main(int argc, char *argv[])
- {
- std::ifstream fs(argv[1]);
- std::ostringstream ss;
- ss << fs.rdbuf();
- std::string data = ss.str();
- json_grammar g;
- boost::spirit::parse_info<> pi = boost::spirit::parse(data.c_str(), g, boost::spirit::space_p);
- if (pi.hit)
- {
- if (pi.full)
- std::cout << "parsing all data successfully" << std::endl;
- else
- std::cout << "parsing data partially" << std::endl;
- std::cout << pi.length << " characters parsed" << std::endl;
- }
- else
- std::cout << "parsing failed; stopped at '" << pi.stop << "'" << std::endl;
- }
动作被实现为函数或函数对象。 如果动作需要被初始化或是要在多次执行之间维护某些状态信息,则后者更好一些。 以上例子中将动作实现为函数对象。
类 print
是一个函数对象,它将数据写出至标准输出流。 当其被调用时,重载的 operator()()
操作符将接受一对指向数据起始点和结束点的指针,所指范围即为被执行该动作的规则所识别出来的数据。
这个例子将这个动作关联至在 member
之后作为第一个符号出现的非终结符号 string
。 一个类型为 print
的实例被放在方括号内传递给非终结符号 string
。 由于 string
表示的是 JSON 对象的键-值对中的键,所以每次找到一个键时,类 print
中的重载 operator()()
操作符将被调用,将该键写出到标准输出流。
我们可以定义任意数量的动作,或将它们关联至任意数量的符号。 要把一个动作关联至一个字面值,必须明确给出一个词法分析器。 这与在非终结符号 string
的定义中指定 boost::spirit::ch_p
类没什么不同。 以下例子使用了 boost::spirit::str_p
类来将一个 print
类型的对象关联至字面值 true
。
- #include <boost/spirit.hpp>
- #include <string>
- #include <fstream>
- #include <sstream>
- #include <iostream>
- struct json_grammar
- : public boost::spirit::grammar<json_grammar>
- {
- struct print
- {
- void operator()(const char *begin, const char *end) const
- {
- std::cout << std::string(begin, end) << std::endl;
- }
- void operator()(const double d) const
- {
- std::cout << d << std::endl;
- }
- };
- template <typename Scanner>
- struct definition
- {
- boost::spirit::rule<Scanner> object, member, string, value, number, array;
- definition(const json_grammar &self)
- {
- using namespace boost::spirit;
- object = "{" >> member >> *("," >> member) >> "}";
- member = string[print()] >> ":" >> value;
- string = "\"" >> *~ch_p("\"") >> "\"";
- value = string | number | object | array | str_p("true")[print()] | "false" | "null";
- number = real_p[print()];
- array = "[" >> value >> *("," >> value) >> "]";
- }
- const boost::spirit::rule<Scanner> &start()
- {
- return object;
- }
- };
- };
- int main(int argc, char *argv[])
- {
- std::ifstream fs(argv[1]);
- std::ostringstream ss;
- ss << fs.rdbuf();
- std::string data = ss.str();
- json_grammar g;
- boost::spirit::parse_info<> pi = boost::spirit::parse(data.c_str(), g, boost::spirit::space_p);
- if (pi.hit)
- {
- if (pi.full)
- std::cout << "parsing all data successfully" << std::endl;
- else
- std::cout << "parsing data partially" << std::endl;
- std::cout << pi.length << " characters parsed" << std::endl;
- }
- else
- std::cout << "parsing failed; stopped at '" << pi.stop << "'" << std::endl;
- }
另外,这个例子还将一个动作关联至 boost::spirit::real_p
。 大多数分析器会传递一对指向被识别数据起始点和结束点的指针,而 boost::spirit::real_p
则将所找到的数字作为 double
来传递。 这样可以使对数字的处理更为方便,因为这些数字不再需要被显式转换。 为了传递一个 double
类型的值给这个动作,我们相应地增加了一个重载的 operator()()
操作符给 print
。
除了在本章中介绍过的分析器,如 boost::spirit::str_p
或 boost::spirit::real_p
以外,Boost.Spirit 还提供了很多其它的分析器。 例如,如果要使用正则表达式,我们有 boost::spirit::regex_p
可用。 此外,还有用于验证条件或执行循环的分析器。 它们有助于创建动态的词法分析器,根据条件来对数据进行不同的处理。 要对 Boost.Spirit 提供的这些工具有一个大概的了解,你应该看一下这个库的文档。