12.4. 动作

到目前为止,你已经知道了如何定义一个语法,以得到一个新的词法分析器,用于识别一个给定的文本是否具有该语法的规则所规定的结构。 但是此刻,数据的格式仍未被解释,因为从结构化格式如 JSON 中所读取的数据并没有被进一步处理。

要对由分析器识别出来的符合某个特定规则的数据进行处理,可以使用动作(action)。 动作是一些与规则相关联的函数。 如果词法分析器识别出某些数据符合某个特定的规则,则相关联的动作会被执行,并把识别得到的数据传入进行处理,如下例所示。

  1. #include <boost/spirit.hpp>
  2. #include <string>
  3. #include <fstream>
  4. #include <sstream>
  5. #include <iostream>
  6.  
  7. struct json_grammar
  8. : public boost::spirit::grammar<json_grammar>
  9. {
  10. struct print
  11. {
  12. void operator()(const char *begin, const char *end) const
  13. {
  14. std::cout << std::string(begin, end) << std::endl;
  15. }
  16. };
  17.  
  18. template <typename Scanner>
  19. struct definition
  20. {
  21. boost::spirit::rule<Scanner> object, member, string, value, number, array;
  22.  
  23. definition(const json_grammar &self)
  24. {
  25. using namespace boost::spirit;
  26. object = "{" >> member >> *("," >> member) >> "}";
  27. member = string[print()] >> ":" >> value;
  28. string = "\"" >> *~ch_p("\"") >> "\"";
  29. value = string | number | object | array | "true" | "false" | "null";
  30. number = real_p;
  31. array = "[" >> value >> *("," >> value) >> "]";
  32. }
  33.  
  34. const boost::spirit::rule<Scanner> &start()
  35. {
  36. return object;
  37. }
  38. };
  39. };
  40.  
  41. int main(int argc, char *argv[])
  42. {
  43. std::ifstream fs(argv[1]);
  44. std::ostringstream ss;
  45. ss << fs.rdbuf();
  46. std::string data = ss.str();
  47.  
  48. json_grammar g;
  49. boost::spirit::parse_info<> pi = boost::spirit::parse(data.c_str(), g, boost::spirit::space_p);
  50. if (pi.hit)
  51. {
  52. if (pi.full)
  53. std::cout << "parsing all data successfully" << std::endl;
  54. else
  55. std::cout << "parsing data partially" << std::endl;
  56. std::cout << pi.length << " characters parsed" << std::endl;
  57. }
  58. else
  59. std::cout << "parsing failed; stopped at '" << pi.stop << "'" << std::endl;
  60. }

动作被实现为函数或函数对象。 如果动作需要被初始化或是要在多次执行之间维护某些状态信息,则后者更好一些。 以上例子中将动作实现为函数对象。

print 是一个函数对象,它将数据写出至标准输出流。 当其被调用时,重载的 operator()() 操作符将接受一对指向数据起始点和结束点的指针,所指范围即为被执行该动作的规则所识别出来的数据。

这个例子将这个动作关联至在 member 之后作为第一个符号出现的非终结符号 string。 一个类型为 print 的实例被放在方括号内传递给非终结符号 string。 由于 string 表示的是 JSON 对象的键-值对中的键,所以每次找到一个键时,类 print 中的重载 operator()() 操作符将被调用,将该键写出到标准输出流。

我们可以定义任意数量的动作,或将它们关联至任意数量的符号。 要把一个动作关联至一个字面值,必须明确给出一个词法分析器。 这与在非终结符号 string 的定义中指定 boost::spirit::ch_p 类没什么不同。 以下例子使用了 boost::spirit::str_p 类来将一个 print 类型的对象关联至字面值 true

  1. #include <boost/spirit.hpp>
  2. #include <string>
  3. #include <fstream>
  4. #include <sstream>
  5. #include <iostream>
  6.  
  7. struct json_grammar
  8. : public boost::spirit::grammar<json_grammar>
  9. {
  10. struct print
  11. {
  12. void operator()(const char *begin, const char *end) const
  13. {
  14. std::cout << std::string(begin, end) << std::endl;
  15. }
  16.  
  17. void operator()(const double d) const
  18. {
  19. std::cout << d << std::endl;
  20. }
  21. };
  22.  
  23. template <typename Scanner>
  24. struct definition
  25. {
  26. boost::spirit::rule<Scanner> object, member, string, value, number, array;
  27.  
  28. definition(const json_grammar &self)
  29. {
  30. using namespace boost::spirit;
  31. object = "{" >> member >> *("," >> member) >> "}";
  32. member = string[print()] >> ":" >> value;
  33. string = "\"" >> *~ch_p("\"") >> "\"";
  34. value = string | number | object | array | str_p("true")[print()] | "false" | "null";
  35. number = real_p[print()];
  36. array = "[" >> value >> *("," >> value) >> "]";
  37. }
  38.  
  39. const boost::spirit::rule<Scanner> &start()
  40. {
  41. return object;
  42. }
  43. };
  44. };
  45.  
  46. int main(int argc, char *argv[])
  47. {
  48. std::ifstream fs(argv[1]);
  49. std::ostringstream ss;
  50. ss << fs.rdbuf();
  51. std::string data = ss.str();
  52.  
  53. json_grammar g;
  54. boost::spirit::parse_info<> pi = boost::spirit::parse(data.c_str(), g, boost::spirit::space_p);
  55. if (pi.hit)
  56. {
  57. if (pi.full)
  58. std::cout << "parsing all data successfully" << std::endl;
  59. else
  60. std::cout << "parsing data partially" << std::endl;
  61. std::cout << pi.length << " characters parsed" << std::endl;
  62. }
  63. else
  64. std::cout << "parsing failed; stopped at '" << pi.stop << "'" << std::endl;
  65. }

另外,这个例子还将一个动作关联至 boost::spirit::real_p。 大多数分析器会传递一对指向被识别数据起始点和结束点的指针,而 boost::spirit::real_p 则将所找到的数字作为 double 来传递。 这样可以使对数字的处理更为方便,因为这些数字不再需要被显式转换。 为了传递一个 double 类型的值给这个动作,我们相应地增加了一个重载的 operator()() 操作符给 print

除了在本章中介绍过的分析器,如 boost::spirit::str_pboost::spirit::real_p 以外,Boost.Spirit 还提供了很多其它的分析器。 例如,如果要使用正则表达式,我们有 boost::spirit::regex_p 可用。 此外,还有用于验证条件或执行循环的分析器。 它们有助于创建动态的词法分析器,根据条件来对数据进行不同的处理。 要对 Boost.Spirit 提供的这些工具有一个大概的了解,你应该看一下这个库的文档。