目录
二、 boost::spirit::qi::parse()解析格式
本章介绍库 Boost.Spirit。 Boost.Spirit 用于开发文本格式的解析器。例如,您可以使用 Boost.Spirit 开发解析器来加载配置文件。 Boost.Spirit 也可以用于二进制格式,尽管它在这方面的用处有限。
Boost.Spirit 简化了解析器的开发,因为格式是用规则描述的。规则定义格式的外观——其余的由 Boost.Spirit 完成。您可以将 Boost.Spirit 与正则表达式进行比较,因为它可以让您处理复杂的过程——正则表达式的模式搜索和 Boost.Spirit 的解析——而无需编写代码来实现该过程。
Boost.Spirit 期望使用解析表达式语法 (PEG) 来描述规则。 PEG 与扩展巴库斯-瑙尔形式 (EBNF) 有关。即使您不熟悉这些语言,本章中的示例也足以帮助您入门。
Boost.Spirit 有两个版本。第一个版本称为 Spirit.Classic。这个版本不应该再使用了。当前版本是 2.5.2。这是本章介绍的版本。
从 2.x 版本开始,Boost.Spirit 可用于生成生成器和解析器。解析器读取文本格式,生成器编写它们。 Boost.Spirit 中用于开发解析器的组件称为 Spirit.Qi。 Spirit.Karma 是用于开发生成器的组件。命名空间被相应地划分:用于开发解析器的类和函数可以在 boost::spirit::qi 中找到,用于开发生成器的类和函数可以在 boost::spirit::karma 中找到。
除了 Spirit.Qi 和 Spirit.Karma,该库还包含一个名为 Spirit.Lex 的组件,可用于开发词法分析器。
本章的重点是开发解析器。示例主要使用来自 boost::spirit 和 boost::spirit::qi 的类和函数。对于这些类和函数,包含头文件 boost/spirit/include/qi.hpp 就足够了。
如果您不想包含像 boost/spirit/include/qi.hpp 这样的主头文件,您可以单独包含来自 boost/spirit/include/ 的头文件。仅包含此目录中的头文件很重要。 boost/spirit/include/ 是用户界面。其他目录中的头文件可以在新的库版本中更改。
Boost.Spirit 提供 boost::spirit::qi::parse() 和 boost::spirit::qi::phrase_parse() 来解析格式。
Example 11.1. Using boost::spirit::qi::parse()
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::parse(it, s.end(), ascii::digit);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
例 11.1 引入了 boost::spirit::qi::parse()。这个函数需要两个被解析字符串的迭代器和一个解析器。该示例使用由 Boost.Spirit 提供的解析器 boost::spirit::ascii::digit。这是几个字符分类解析器之一。这些解析器测试字符是否属于某个类。 boost::spirit::ascii::digit 测试字符是否为 0 到 9 之间的数字。
该示例传递从 std::cin 读取的字符串的迭代器。请注意,开始迭代器没有直接传递给 boost::spirit::qi::parse()。它存储在变量 it 中,然后传递给 boost::spirit::qi::parse()。这样做是因为 boost::spirit::qi::parse() 可能会修改迭代器。
如果您键入一个数字,然后按 Enter,该示例将显示 true。如果您输入两位数然后回车,则输出将为真,后跟第二位数字。如果你输入一个字母然后回车,输出将是假的,然后是字母。
例 11.1 中使用的解析器 boost::spirit::ascii::digit 只测试一个字符以查看它是否是数字。如果第一个字符是数字,boost::spirit::qi::parse() 返回 true,否则返回 false。 boost::spirit::qi::parse() 的返回值表示解析器是否成功。
boost::spirit::qi::parse() 如果您输入多个数字,也会返回 true。因为解析器 boost::spirit::ascii::digit 只测试第一个字符,所以它会在这样的字符串上成功。第一个之后的所有数字都将被忽略。
为了让您确定可以成功解析多少字符串,boost::spirit::qi::parse() 更改了它的迭代器。调用 boost::spirit::qi::parse() 后,它指向最后一个解析成功后的字符。如果输入多个数字,则指第二个数字。如果您只输入一位数字,则它等于 s 的结束迭代器。如果你输入一个字母,它指的是那个字母。
boost::spirit::qi::parse() 不会忽略空格。如果运行示例 11.1 并输入空格,则会显示 false。 boost::spirit::qi::parse() 测试第一个输入的字符,即使该字符是空格。如果你想忽略空格,使用 boost::spirit::qi::phrase_parse() 而不是 boost::spirit::qi::parse()。
Example 11.2. Using boost::spirit::qi::phrase_parse()
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(), ascii::digit, ascii::space);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
boost::spirit::qi::phrase_parse() 的工作方式与 boost::spirit::qi::parse() 类似,但需要另一个名为 skipper 的参数。船长是应该被忽略的字符的解析器。示例 11.2 使用 boost::spirit::ascii::space,一个字符分类解析器来检测空格,作为船长。
boost::spirit::ascii::space 丢弃空格作为分隔符。如果您开始该示例并输入一个空格后跟一个数字,则显示为 true。与前面的示例不同,解析器 boost::spirit::ascii::digit 不应用于空格,而是应用于不是空格的第一个字符。
请注意,此示例忽略了任意数量的空格。因此,如果您输入多个空格后跟一个数字, boost::spirit::qi::phrase_parse() 将返回 true。
与 boost::spirit::qi::parse() 一样,boost::spirit::qi::phrase_parse() 修改了作为第一个参数传递的迭代器。这样,您就知道解析器能够成功工作到字符串多远。示例 11.2 跳过成功解析字符后出现的空格。如果您输入一个数字后跟一个空格,然后是一个字母,迭代器将引用该字母,而不是它前面的空格。如果您希望迭代器引用空间,请将 boost::spirit::qi::skip_flag::dont_postskip 作为另一个参数传递给 boost::spirit::qi::phrase_parse()。
Example 11.3. phrase_parse()
with boost::spirit::qi::skip_flag::dont_postskip
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(), ascii::digit, ascii::space,
qi::skip_flag::dont_postskip);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
示例 11.3 将 boost::spirit::qi::skip_flag::dont_postskip 传递给 boost::spirit::qi::phrase_parse() 以告诉解析器不要跳过在成功解析数字之后但在第一个不成功数字之前出现的空格解析的字符。如果你输入一个数字后跟一个空格再跟一个字母,它指的是调用 boost::spirit::qi::phrase_parse() 之后的空格。
标志 boost::spirit::qi::skip_flag::postskip 是默认值,如果 boost::spirit::qi::skip_flag::dont_postskip 和 boost::spirit::qi::skip_flag 都不是,则使用该标志: :postskip 已指定。
Example 11.4. boost::spirit::qi::phrase_parse()
with wide strings
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::wstring s;
std::getline(std::wcin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(), ascii::digit, ascii::space,
qi::skip_flag::dont_postskip);
std::wcout << std::boolalpha << match << '\n';
if (it != s.end())
std::wcout << std::wstring{it, s.end()} << '\n';
}
boost::spirit::qi::parse() 和 boost::spirit::qi::phrase_parse() 接受迭代器到一个宽字符串。示例 11.4 与前面的示例类似,只是使用了宽字符串。
Boost.Spirit 还支持来自 C++11 标准库的字符串类型 std::u16string 和 std::u32string。
本节说明如何定义解析器。您通常从 Boost.Spirit 访问现有的解析器——例如 boost::spirit::ascii::digit 或 boost::spirit::ascii::space。通过组合解析器,您可以解析更复杂的格式。该过程类似于定义正则表达式,它们也是由基本构建块构建的。
Example 11.5. A parser for two consecutive digits
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(), ascii::digit >> ascii::digit,
ascii::space);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
示例 11.5 测试是否输入了两个数字。 boost::spirit::qi::phrase_parse() 仅在两个数字连续时才返回 true。空格被忽略。
与前面的示例一样, boost::spirit::ascii::digit 用于识别数字。因为 boost::spirit::ascii::digit 只测试一个字符,所以解析器使用了两次来测试两位数字的输入。要连续两次使用 boost::spirit::ascii::digit,必须使用运算符。 Boost.Spirit 为解析器重载 operator>>。使用 ascii::digit >> ascii::digit 创建了一个解析器,用于测试字符串是否包含两个数字。
如果您运行该示例并输入两位数,则会显示 true。如果您只输入一位数字,该示例将显示为 false。
请注意,如果您在两位数之间输入空格,该示例也会显示 true。无论在解析器中使用运算符 operator>> 的任何位置,都允许使用被船长忽略的字符。因为示例 11.5 使用 boost::spirit::ascii::space 作为跳过符,所以您可以在两个数字之间输入任意数量的空格。
如果您希望解析器仅在两个数字之间没有空格的情况下才接受它们,请使用 boost::spirit::qi::parse() 或指令 boost::spirit::qi::lexeme。
Example 11.6. Parsing character by character with boost::spirit::qi::lexeme
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(),
qi::lexeme[ascii::digit >> ascii::digit], ascii::space);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
示例 11.6 使用解析器 qi::lexeme[ascii::digit >> ascii::digit]。现在, boost::spirit::qi::phrase_parse() 仅在数字之间没有空格时才返回 true。
boost::spirit::qi::lexeme 是可以改变解析器行为的几个指令之一。如果你想禁止在使用 operator>> 时会被船长忽略的字符,你可以使用 boost::spirit::qi::lexeme。
Example 11.7. Boost.Spirit rules similar to regular expressions
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(), +ascii::digit, ascii::space);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
例 11.7 用 +ascii::digit 定义了一个解析器,它至少需要一个数字。这种语法,特别是加号 (+),类似于正则表达式中使用的语法。加号标识一个字符或字符组,该字符或字符组预计在字符串中至少出现一次。如果您启动示例并输入至少一位数字,则会显示 true。数字是否由空格分隔并不重要。如果解析器应该只接受没有空格的数字,请再次使用 boost::spirit::qi::lexeme。
Example 11.8. Numeric parsers
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>
using namespace boost::spirit;
int main()
{
std::string s;
std::getline(std::cin, s);
auto it = s.begin();
bool match = qi::phrase_parse(it, s.end(), qi::int_, ascii::space);
std::cout << std::boolalpha << match << '\n';
if (it != s.end())
std::cout << std::string{it, s.end()} << '\n';
}
示例 11.8 需要一个整数。 boost::spirit::qi::int_ 是一个可以识别正整数和负整数的数值解析器。与 boost::spirit::ascii::digit 不同,boost::spirit::qi::int_ 可以将多个字符(例如 +1 或 -23)识别为整数。
Boost.Spirit 提供了额外的逻辑解析器。 boost::spirit::qi::float_、boost::spirit::qi::double_ 和 boost::spirit::qi::bool_ 是可以读取浮点数和布尔值的数值解析器。使用 boost::spirit::qi::eol,您可以测试行尾字符。 boost::spirit::qi::byte_ 和 boost::spirit::qi::word 可用于读取一个或两个字节。 boost::spirit::qi::word 和其他二进制解析器识别平台的字节顺序并进行相应的解析。如果要基于特定的字节序进行解析,无论平台如何,都可以使用 boost::spirit::qi::little_word 和 boost::spirit::qi::big_word 等解析器。