当前位置: 首页 > 工具软件 > Spirit > 使用案例 >

【BOOST C++字串专题11】Boost.Spirit(三)

马阳曦
2023-12-01

五、rule规则

        在 Boost.Spirit 中,解析器由规则组成。由于规则通常基于 Boost.Spirit 提供的解析器,因此没有明显的区别。例如, boost::spirit::ascii::digit 既可以是解析器,也可以是规则。通常,规则指的是更复杂的表达式,例如 qi::int_ % ','。

        在迄今为止的所有示例中,规则都直接传递给 boost::spirit::qi::parse() 或 boost::spirit::qi::phrase_parse()。通过 boost::spirit::qi::rule,Boost.Spirit 提供了一个类来定义规则变量。例如,如果应该将规则存储在类的成员变量中,则需要 boost::spirit::qi::rule。

Example 11.13. Defining rules with boost::spirit::qi::rule

#include <boost/spirit/include/qi.hpp>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>
#include <iostream>

using namespace boost::spirit;

int main()
{
  std::string s;
  std::getline(std::cin, s);
  auto it = s.begin();
  qi::rule<std::string::iterator, std::vector<int>(),
    ascii::space_type> values = qi::int_ % ',';
  std::vector<int> v;
  if (qi::phrase_parse(it, s.end(), values, ascii::space, v))
  {
    std::ostream_iterator<int> out{std::cout, ";"};
    std::copy(v.begin(), v.end(), out);
  }
}

        示例 11.13 的工作方式与示例 11.12 类似。如果您输入多个以逗号分隔的整数,它们将用分号显示。与前面的示例相比,解析器没有直接传递给 boost::spirit::qi::phrase_parse(),而是在 boost::spirit::qi::rule 变量中定义。

        boost::spirit::qi::rule 是一个类模板。唯一的强制参数是被解析字符串的迭代器类型。在示例中,还传递了另外两个可选模板参数。

        第二个模板参数是 std::vector<int>(),它是返回 std::vector<int> 类型向量且不需要参数的函数的签名。该模板参数表示解析的属性类型为int向量。

        第三个模板参数是 boost::spirit::qi::phrase_parse() 使用的 skipper 的类型。在示例中,使用了船长 boost::spirit::ascii::space。该船长的类型可通过 boost::spirit::ascii::space_type 获得,并作为模板参数传递给 boost::spirit::qi::rule。

        如果您希望您的代码独立于平台并使用 C++11 开发环境,您应该更喜欢 boost::spirit::qi::rule 而不是关键字 auto。如果值是用 auto 定义的,则该示例在 GCC 和 Clang 中可以正常工作。然而,在 Visual C++ 2013 中,只有第一个数字被解析并写入标准输出。

Example 11.14. Nesting Rules

#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace boost::spirit;

struct print : public boost::static_visitor<>
{
  template <typename T>
  void operator()(T t) const
  {
    std::cout << std::boolalpha << t << ';';
  }
};

int main()
{
  std::string s;
  std::getline(std::cin, s);
  auto it = s.begin();
  qi::rule<std::string::iterator, boost::variant<int, bool>(),
    ascii::space_type> value = qi::int_ | qi::bool_;
  qi::rule<std::string::iterator, std::vector<boost::variant<int, bool>>(),
    ascii::space_type> values = value % ',';
  std::vector<boost::variant<int, bool>> v;
  if (qi::phrase_parse(it, s.end(), values, ascii::space, v))
  {
    for (const auto &elem : v)
      boost::apply_visitor(print{}, elem);
  }
}

Example 11.14. Nesting Rules

#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <string>
#include <vector>
#include <algorithm>
#include <iostream>

using namespace boost::spirit;

struct print : public boost::static_visitor<>
{
  template <typename T>
  void operator()(T t) const
  {
    std::cout << std::boolalpha << t << ';';
  }
};

int main()
{
  std::string s;
  std::getline(std::cin, s);
  auto it = s.begin();
  qi::rule<std::string::iterator, boost::variant<int, bool>(),
    ascii::space_type> value = qi::int_ | qi::bool_;
  qi::rule<std::string::iterator, std::vector<boost::variant<int, bool>>(),
    ascii::space_type> values = value % ',';
  std::vector<boost::variant<int, bool>> v;
  if (qi::phrase_parse(it, s.end(), values, ascii::space, v))
  {
    for (const auto &elem : v)
      boost::apply_visitor(print{}, elem);
  }
}

        example11.14 定义了两个规则,其中一个引用另一个:values 定义为 value % ',',value 设置为 qi::int_ | qi::bool_。 values 表示可以解析由逗号分隔的任意数量的值。 value 将值定义为整数或布尔值。总之,规则规定用逗号分隔的整数和布尔值可以按任何顺序输入。

        为了存储任意数量的值,提供了一个 std::vector 类型的容器。因为值的类型是 int 或 bool,所以需要一个可以存储 int 或 bool 值的类。根据属性类型和运算符的概述,必须使用来自 Boost.Variant 的类 boost::variant。

        如果您启动示例并输入以逗号分隔的整数和布尔值,则这些值将写入以分号分隔的标准输出流。这是在 Boost.Variant 提供的函数 boost::apply_visitor() 的帮助下完成的。这个函数需要一个访问者——在这个例子中是类 print 的一个对象。

        请注意,布尔值必须输入为 true 和 false。

六、语法

        如果要解析复杂的格式,需要定义多个相互引用的规则,可以用 boost::spirit::qi::grammar 进行分组。

Example 11.15. Grouping rules in a grammar

#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <string>
#include <vector>
#include <iostream>

using namespace boost::spirit;

template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator,
  std::vector<boost::variant<int, bool>>(), Skipper>
{
  my_grammar() : my_grammar::base_type{values}
  {
    value = qi::int_ | qi::bool_;
    values = value % ',';
  }

  qi::rule<Iterator, boost::variant<int, bool>(), Skipper> value;
  qi::rule<Iterator, std::vector<boost::variant<int, bool>>(), Skipper>
    values;
};

struct print : public boost::static_visitor<>
{
  template <typename T>
  void operator()(T t) const
  {
    std::cout << std::boolalpha << t << ';';
  }
};

int main()
{
  std::string s;
  std::getline(std::cin, s);
  auto it = s.begin();
  my_grammar<std::string::iterator, ascii::space_type> g;
  std::vector<boost::variant<int, bool>> v;
  if (qi::phrase_parse(it, s.end(), g, ascii::space, v))
  {
    for (const auto &elem : v)
      boost::apply_visitor(print{}, elem);
  }
}

        示例 11.15 的工作方式与示例 11.14 类似:您可以按任何顺序输入整数和布尔值,并以逗号分隔。它们将以相同的顺序写入标准输出流,但用分号分隔。该示例使用与前一个相同的规则——值和值。然而,这一次规则被分组在一个语法中。语法在一个名为 my_grammar 的类中定义,该类派生自 boost::spirit::qi::grammar。

        my_grammar 和 boost::spirit::qi::grammar 都是类模板。 boost::spirit::qi::grammar 期望的模板参数与 boost::spirit::qi::rule 期望的模板参数相同。要解析的字符串的迭代器类型必须传递给 boost::spirit::qi::grammar。您还可以传递定义属性类型和船长类型的函数的签名。

        在 my_grammar 中, boost::spirit::qi::rule 用于定义规则值和值。规则被定义为成员变量并在构造函数中初始化。

        请注意,最外层的规则必须与 base_type 一起传递给基类的构造函数。这样,Boost.Spirit 就知道哪个规则是语法的入口点。

        一旦定义了语法,就可以像解析器一样使用它。在示例 11.15 中,my_grammar 在 main() 中实例化以创建 g。然后将 g 传递给 boost::spirit::qi::phrase_parse()。

Example 11.16. Storing parsed values in structures

#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <string>
#include <vector>
#include <iostream>

using namespace boost::spirit;

typedef boost::variant<int, bool> int_or_bool;

struct int_or_bool_values
{
  int_or_bool first;
  std::vector<int_or_bool> others;
};

BOOST_FUSION_ADAPT_STRUCT(
  int_or_bool_values,
  (int_or_bool, first)
  (std::vector<int_or_bool>, others)
)

template <typename Iterator, typename Skipper>
struct my_grammar : qi::grammar<Iterator, int_or_bool_values(), Skipper>
{
  my_grammar() : my_grammar::base_type{values}
  {
    value = qi::int_ | qi::bool_;
    values = value >> ',' >> value % ',';
  }

  qi::rule<Iterator, int_or_bool(), Skipper> value;
  qi::rule<Iterator, int_or_bool_values(), Skipper> values;
};

struct print : public boost::static_visitor<>
{
  template <typename T>
  void operator()(T t) const
  {
    std::cout << std::boolalpha << t << ';';
  }
};

int main()
{
  std::string s;
  std::getline(std::cin, s);
  auto it = s.begin();
  my_grammar<std::string::iterator, ascii::space_type> g;
  int_or_bool_values v;
  if (qi::phrase_parse(it, s.end(), g, ascii::space, v))
  {
    print p;
    boost::apply_visitor(p, v.first);
    for (const auto &elem : v.others)
      boost::apply_visitor(p, elem);
  }
}

 

        示例 11.16 基于前面的示例,但需要至少两个值。规则值定义为 value >> ',' >> value % ','。

        values 中的第一个组件是 value,第二个是 value % ','。第一个组件解析的值必须存储在 boost::variant 类型的对象中。第二个组件解析的值必须存储在容器中。使用 int_or_bool_values,该示例提供了一个结构来存储由规则值的两个组件解析的值。

        要将 int_or_bool_values 与 Boost.Spirit 一起使用,必须使用宏 BOOST_FUSION_ADAPT_STRUCT。该宏由 Boost.Fusion 提供。该宏可以将 int_or_bool_values 视为具有 int_or_bool 和 std::vector<int_or_bool> 类型的两个值的元组。因为这个元组具有正确数量的正确类型的值,所以可以使用签名 int_or_bool_values() 定义值。 values 将在 first 中存储第一个解析值,在其他中存储所有其他解析值。

        int_or_bool_values 类型的对象作为属性传递给 boost::spirit::qi::phrase_parse()。如果您启动示例并输入至少两个以逗号分隔的整数或布尔值,则它们都存储在属性中并写入标准输出流。

 

        解析器已从上一个示例中使用的内容进行了更改。如果值是用 value % ',' 定义的,则 int_or_bool_values 将只有一个成员变量,并且所有解析的值都可以存储在一个向量中,如前面的示例所示。因此,int_or_bool_values 就像一个只有一个值的元组——Boost.Spirit 不支持。只有一个成员变量的结构将导致编译器错误。该问题有多种解决方法。

练习
        创建一个可以加减整数的解析器。解析器应该能够处理像 1+2-5+8 这样的输入,并将结果(这里是 6)写入标准输出。

        扩展您的解析器:它现在应该也支持浮点数。此外,应该可以使用分数。新的解析器应该能够处理像 1.2+6/5-0.9 这样的输入,并且应该将结果(这里是 1.5)写入标准输出。

 类似资料: