在 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)写入标准输出。