使用 Boost.Spirit Qi 和 Lex 时的空格跳过
Whitespace skipper when using Boost.Spirit Qi and Lex
让我们考虑以下代码:
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
namespace lex = boost::spirit::lex;
namespace qi = boost::spirit::qi;
template<typename Lexer>
class expression_lexer
: public lex::lexer<Lexer>
{
public:
typedef lex::token_def<> operator_token_type;
typedef lex::token_def<> value_token_type;
typedef lex::token_def<> variable_token_type;
typedef lex::token_def<lex::omit> parenthesis_token_type;
typedef std::pair<parenthesis_token_type, parenthesis_token_type> parenthesis_token_pair_type;
typedef lex::token_def<lex::omit> whitespace_token_type;
expression_lexer()
: operator_add('+'),
operator_sub('-'),
operator_mul("[x*]"),
operator_div("[:/]"),
value("\d+(\.\d+)?"),
variable("%(\w+)"),
parenthesis({
std::make_pair(parenthesis_token_type('('), parenthesis_token_type(')')),
std::make_pair(parenthesis_token_type('['), parenthesis_token_type(']'))
}),
whitespace("[ \t]+")
{
this->self
= operator_add
| operator_sub
| operator_mul
| operator_div
| value
| variable
;
std::for_each(parenthesis.cbegin(), parenthesis.cend(),
[&](parenthesis_token_pair_type const& token_pair)
{
this->self += token_pair.first | token_pair.second;
}
);
this->self("WS") = whitespace;
}
operator_token_type operator_add;
operator_token_type operator_sub;
operator_token_type operator_mul;
operator_token_type operator_div;
value_token_type value;
variable_token_type variable;
std::vector<parenthesis_token_pair_type> parenthesis;
whitespace_token_type whitespace;
};
template<typename Iterator, typename Skipper>
class expression_grammar
: public qi::grammar<Iterator, Skipper>
{
public:
template<typename Tokens>
explicit expression_grammar(Tokens const& tokens)
: expression_grammar::base_type(start)
{
start %= expression >> qi::eoi;
expression %= sum_operand >> -(sum_operator >> expression);
sum_operator %= tokens.operator_add | tokens.operator_sub;
sum_operand %= fac_operand >> -(fac_operator >> sum_operand);
fac_operator %= tokens.operator_mul | tokens.operator_div;
if(!tokens.parenthesis.empty())
fac_operand %= parenthesised | terminal;
else
fac_operand %= terminal;
terminal %= tokens.value | tokens.variable;
if(!tokens.parenthesis.empty())
{
parenthesised %= tokens.parenthesis.front().first >> expression >> tokens.parenthesis.front().second;
std::for_each(tokens.parenthesis.cbegin() + 1, tokens.parenthesis.cend(),
[&](typename Tokens::parenthesis_token_pair_type const& token_pair)
{
parenthesised %= parenthesised.copy() | (token_pair.first >> expression >> token_pair.second);
}
);
}
}
private:
qi::rule<Iterator, Skipper> start;
qi::rule<Iterator, Skipper> expression;
qi::rule<Iterator, Skipper> sum_operand;
qi::rule<Iterator, Skipper> sum_operator;
qi::rule<Iterator, Skipper> fac_operand;
qi::rule<Iterator, Skipper> fac_operator;
qi::rule<Iterator, Skipper> terminal;
qi::rule<Iterator, Skipper> parenthesised;
};
int main()
{
typedef lex::lexertl::token<std::string::const_iterator> token_type;
typedef expression_lexer<lex::lexertl::lexer<token_type>> expression_lexer_type;
typedef expression_lexer_type::iterator_type expression_lexer_iterator_type;
typedef qi::in_state_skipper<expression_lexer_type::lexer_def> skipper_type;
typedef expression_grammar<expression_lexer_iterator_type, skipper_type> expression_grammar_type;
expression_lexer_type lexer;
expression_grammar_type grammar(lexer);
while(std::cin)
{
std::string line;
std::getline(std::cin, line);
std::string::const_iterator first = line.begin();
std::string::const_iterator const last = line.end();
bool const result = lex::tokenize_and_phrase_parse(first, last, lexer, grammar, qi::in_state("WS")[lexer.self]);
if(!result)
std::cout << "Parsing failed! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
{
if(first != last)
std::cout << "Parsing succeeded! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
std::cout << "Parsing succeeded!" << std::endl;
}
}
}
它是一个用于带有值和变量的算术表达式的简单解析器。它是使用 expression_lexer
提取标记构建的,然后使用 expression_grammar
解析标记。
在这么小的情况下使用词法分析器可能看起来有点矫枉过正,而且可能确实如此。但这是简化示例的成本。另请注意,使用词法分析器允许使用正则表达式轻松定义标记,同时允许通过外部代码(特别是用户提供的配置)轻松定义它们。使用提供的示例,从外部配置文件读取令牌的定义完全没有问题,例如允许用户将变量从 %name
更改为 $name
.
代码似乎运行良好(在 Visual Studio 2013 年使用 Boost 1.61 检查)。除了我注意到如果我提供像 5++5
这样的字符串它正确地失败但作为提醒报告只是 5
而不是 +5
这意味着违规 +
是 [=41= 】 消耗。显然,生成但与语法不匹配的标记绝不会返回到原始输入。但这不是我要问的。只是我在检查代码时意识到的一个旁注。
现在的问题是空格跳过。我非常不喜欢它是如何完成的。虽然我是这样做的,因为它似乎是由许多示例提供的,包括 Whosebug 上问题的答案。
最糟糕的事情似乎是(没有记录?)qi::in_state_skipper
。此外,我似乎必须像那样添加 whitespace
标记(带有名称),而不是像所有其他标记一样添加,因为使用 lexer.whitespace
而不是 "WS"
似乎不起作用。
最后不得不 "clutter" 带有 Skipper
参数的语法看起来不太好。我不应该摆脱它吗?毕竟我想创建基于标记而不是直接输入的语法,并且我希望从标记流中排除空格——那里不再需要它了!
我还有哪些其他选项可以跳过空格?像现在这样有什么好处?
出于某种奇怪的原因,我现在才发现一个不同的问题,Boost.Spirit SQL grammar/lexer failure, where some other solution 提供了空格跳过。一个更好的!
下面是根据那里的建议修改的示例代码:
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
namespace lex = boost::spirit::lex;
namespace qi = boost::spirit::qi;
template<typename Lexer>
class expression_lexer
: public lex::lexer<Lexer>
{
public:
typedef lex::token_def<> operator_token_type;
typedef lex::token_def<> value_token_type;
typedef lex::token_def<> variable_token_type;
typedef lex::token_def<lex::omit> parenthesis_token_type;
typedef std::pair<parenthesis_token_type, parenthesis_token_type> parenthesis_token_pair_type;
typedef lex::token_def<lex::omit> whitespace_token_type;
expression_lexer()
: operator_add('+'),
operator_sub('-'),
operator_mul("[x*]"),
operator_div("[:/]"),
value("\d+(\.\d+)?"),
variable("%(\w+)"),
parenthesis({
std::make_pair(parenthesis_token_type('('), parenthesis_token_type(')')),
std::make_pair(parenthesis_token_type('['), parenthesis_token_type(']'))
}),
whitespace("[ \t]+")
{
this->self
+= operator_add
| operator_sub
| operator_mul
| operator_div
| value
| variable
| whitespace [lex::_pass = lex::pass_flags::pass_ignore]
;
std::for_each(parenthesis.cbegin(), parenthesis.cend(),
[&](parenthesis_token_pair_type const& token_pair)
{
this->self += token_pair.first | token_pair.second;
}
);
}
operator_token_type operator_add;
operator_token_type operator_sub;
operator_token_type operator_mul;
operator_token_type operator_div;
value_token_type value;
variable_token_type variable;
std::vector<parenthesis_token_pair_type> parenthesis;
whitespace_token_type whitespace;
};
template<typename Iterator>
class expression_grammar
: public qi::grammar<Iterator>
{
public:
template<typename Tokens>
explicit expression_grammar(Tokens const& tokens)
: expression_grammar::base_type(start)
{
start %= expression >> qi::eoi;
expression %= sum_operand >> -(sum_operator >> expression);
sum_operator %= tokens.operator_add | tokens.operator_sub;
sum_operand %= fac_operand >> -(fac_operator >> sum_operand);
fac_operator %= tokens.operator_mul | tokens.operator_div;
if(!tokens.parenthesis.empty())
fac_operand %= parenthesised | terminal;
else
fac_operand %= terminal;
terminal %= tokens.value | tokens.variable;
if(!tokens.parenthesis.empty())
{
parenthesised %= tokens.parenthesis.front().first >> expression >> tokens.parenthesis.front().second;
std::for_each(tokens.parenthesis.cbegin() + 1, tokens.parenthesis.cend(),
[&](typename Tokens::parenthesis_token_pair_type const& token_pair)
{
parenthesised %= parenthesised.copy() | (token_pair.first >> expression >> token_pair.second);
}
);
}
}
private:
qi::rule<Iterator> start;
qi::rule<Iterator> expression;
qi::rule<Iterator> sum_operand;
qi::rule<Iterator> sum_operator;
qi::rule<Iterator> fac_operand;
qi::rule<Iterator> fac_operator;
qi::rule<Iterator> terminal;
qi::rule<Iterator> parenthesised;
};
int main()
{
typedef lex::lexertl::token<std::string::const_iterator> token_type;
typedef expression_lexer<lex::lexertl::actor_lexer<token_type>> expression_lexer_type;
typedef expression_lexer_type::iterator_type expression_lexer_iterator_type;
typedef expression_grammar<expression_lexer_iterator_type> expression_grammar_type;
expression_lexer_type lexer;
expression_grammar_type grammar(lexer);
while(std::cin)
{
std::string line;
std::getline(std::cin, line);
std::string::const_iterator first = line.begin();
std::string::const_iterator const last = line.end();
bool const result = lex::tokenize_and_parse(first, last, lexer, grammar);
if(!result)
std::cout << "Parsing failed! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
{
if(first != last)
std::cout << "Parsing succeeded! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
std::cout << "Parsing succeeded!" << std::endl;
}
}
}
区别如下:
whitespace
标记作为所有其他标记被添加到词法分析器的 self
。
- 但是,一个动作与之关联。该操作使词法分析器忽略该标记。这正是我们想要的。
- 我的
expression_grammar
不再采用 Skipper
模板参数。因此它也从规则中删除。
使用 lex::lexertl::actor_lexer
而不是 lex::lexertl::lexer
因为现在有一个与令牌关联的操作。
- 我打电话给
tokenize_and_parse
而不是 tokenize_and_phrase_parse
因为我不再需要通过船长了。
- 此外,我将词法分析器中对
this->self
的第一个赋值从 =
更改为 +=
,因为它看起来更灵活(抵抗顺序更改)。不过不影响这里的解法。
我很擅长这个。它非常适合我的需求(或者更确切地说是我的品味)。但是我想知道这种变化是否还有其他后果?在某些情况下是否首选任何方法?那个我不知道。
让我们考虑以下代码:
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
namespace lex = boost::spirit::lex;
namespace qi = boost::spirit::qi;
template<typename Lexer>
class expression_lexer
: public lex::lexer<Lexer>
{
public:
typedef lex::token_def<> operator_token_type;
typedef lex::token_def<> value_token_type;
typedef lex::token_def<> variable_token_type;
typedef lex::token_def<lex::omit> parenthesis_token_type;
typedef std::pair<parenthesis_token_type, parenthesis_token_type> parenthesis_token_pair_type;
typedef lex::token_def<lex::omit> whitespace_token_type;
expression_lexer()
: operator_add('+'),
operator_sub('-'),
operator_mul("[x*]"),
operator_div("[:/]"),
value("\d+(\.\d+)?"),
variable("%(\w+)"),
parenthesis({
std::make_pair(parenthesis_token_type('('), parenthesis_token_type(')')),
std::make_pair(parenthesis_token_type('['), parenthesis_token_type(']'))
}),
whitespace("[ \t]+")
{
this->self
= operator_add
| operator_sub
| operator_mul
| operator_div
| value
| variable
;
std::for_each(parenthesis.cbegin(), parenthesis.cend(),
[&](parenthesis_token_pair_type const& token_pair)
{
this->self += token_pair.first | token_pair.second;
}
);
this->self("WS") = whitespace;
}
operator_token_type operator_add;
operator_token_type operator_sub;
operator_token_type operator_mul;
operator_token_type operator_div;
value_token_type value;
variable_token_type variable;
std::vector<parenthesis_token_pair_type> parenthesis;
whitespace_token_type whitespace;
};
template<typename Iterator, typename Skipper>
class expression_grammar
: public qi::grammar<Iterator, Skipper>
{
public:
template<typename Tokens>
explicit expression_grammar(Tokens const& tokens)
: expression_grammar::base_type(start)
{
start %= expression >> qi::eoi;
expression %= sum_operand >> -(sum_operator >> expression);
sum_operator %= tokens.operator_add | tokens.operator_sub;
sum_operand %= fac_operand >> -(fac_operator >> sum_operand);
fac_operator %= tokens.operator_mul | tokens.operator_div;
if(!tokens.parenthesis.empty())
fac_operand %= parenthesised | terminal;
else
fac_operand %= terminal;
terminal %= tokens.value | tokens.variable;
if(!tokens.parenthesis.empty())
{
parenthesised %= tokens.parenthesis.front().first >> expression >> tokens.parenthesis.front().second;
std::for_each(tokens.parenthesis.cbegin() + 1, tokens.parenthesis.cend(),
[&](typename Tokens::parenthesis_token_pair_type const& token_pair)
{
parenthesised %= parenthesised.copy() | (token_pair.first >> expression >> token_pair.second);
}
);
}
}
private:
qi::rule<Iterator, Skipper> start;
qi::rule<Iterator, Skipper> expression;
qi::rule<Iterator, Skipper> sum_operand;
qi::rule<Iterator, Skipper> sum_operator;
qi::rule<Iterator, Skipper> fac_operand;
qi::rule<Iterator, Skipper> fac_operator;
qi::rule<Iterator, Skipper> terminal;
qi::rule<Iterator, Skipper> parenthesised;
};
int main()
{
typedef lex::lexertl::token<std::string::const_iterator> token_type;
typedef expression_lexer<lex::lexertl::lexer<token_type>> expression_lexer_type;
typedef expression_lexer_type::iterator_type expression_lexer_iterator_type;
typedef qi::in_state_skipper<expression_lexer_type::lexer_def> skipper_type;
typedef expression_grammar<expression_lexer_iterator_type, skipper_type> expression_grammar_type;
expression_lexer_type lexer;
expression_grammar_type grammar(lexer);
while(std::cin)
{
std::string line;
std::getline(std::cin, line);
std::string::const_iterator first = line.begin();
std::string::const_iterator const last = line.end();
bool const result = lex::tokenize_and_phrase_parse(first, last, lexer, grammar, qi::in_state("WS")[lexer.self]);
if(!result)
std::cout << "Parsing failed! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
{
if(first != last)
std::cout << "Parsing succeeded! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
std::cout << "Parsing succeeded!" << std::endl;
}
}
}
它是一个用于带有值和变量的算术表达式的简单解析器。它是使用 expression_lexer
提取标记构建的,然后使用 expression_grammar
解析标记。
在这么小的情况下使用词法分析器可能看起来有点矫枉过正,而且可能确实如此。但这是简化示例的成本。另请注意,使用词法分析器允许使用正则表达式轻松定义标记,同时允许通过外部代码(特别是用户提供的配置)轻松定义它们。使用提供的示例,从外部配置文件读取令牌的定义完全没有问题,例如允许用户将变量从 %name
更改为 $name
.
代码似乎运行良好(在 Visual Studio 2013 年使用 Boost 1.61 检查)。除了我注意到如果我提供像 5++5
这样的字符串它正确地失败但作为提醒报告只是 5
而不是 +5
这意味着违规 +
是 [=41= 】 消耗。显然,生成但与语法不匹配的标记绝不会返回到原始输入。但这不是我要问的。只是我在检查代码时意识到的一个旁注。
现在的问题是空格跳过。我非常不喜欢它是如何完成的。虽然我是这样做的,因为它似乎是由许多示例提供的,包括 Whosebug 上问题的答案。
最糟糕的事情似乎是(没有记录?)qi::in_state_skipper
。此外,我似乎必须像那样添加 whitespace
标记(带有名称),而不是像所有其他标记一样添加,因为使用 lexer.whitespace
而不是 "WS"
似乎不起作用。
最后不得不 "clutter" 带有 Skipper
参数的语法看起来不太好。我不应该摆脱它吗?毕竟我想创建基于标记而不是直接输入的语法,并且我希望从标记流中排除空格——那里不再需要它了!
我还有哪些其他选项可以跳过空格?像现在这样有什么好处?
出于某种奇怪的原因,我现在才发现一个不同的问题,Boost.Spirit SQL grammar/lexer failure, where some other solution 提供了空格跳过。一个更好的!
下面是根据那里的建议修改的示例代码:
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/spirit/include/qi.hpp>
#include <algorithm>
#include <iostream>
#include <string>
#include <utility>
#include <vector>
namespace lex = boost::spirit::lex;
namespace qi = boost::spirit::qi;
template<typename Lexer>
class expression_lexer
: public lex::lexer<Lexer>
{
public:
typedef lex::token_def<> operator_token_type;
typedef lex::token_def<> value_token_type;
typedef lex::token_def<> variable_token_type;
typedef lex::token_def<lex::omit> parenthesis_token_type;
typedef std::pair<parenthesis_token_type, parenthesis_token_type> parenthesis_token_pair_type;
typedef lex::token_def<lex::omit> whitespace_token_type;
expression_lexer()
: operator_add('+'),
operator_sub('-'),
operator_mul("[x*]"),
operator_div("[:/]"),
value("\d+(\.\d+)?"),
variable("%(\w+)"),
parenthesis({
std::make_pair(parenthesis_token_type('('), parenthesis_token_type(')')),
std::make_pair(parenthesis_token_type('['), parenthesis_token_type(']'))
}),
whitespace("[ \t]+")
{
this->self
+= operator_add
| operator_sub
| operator_mul
| operator_div
| value
| variable
| whitespace [lex::_pass = lex::pass_flags::pass_ignore]
;
std::for_each(parenthesis.cbegin(), parenthesis.cend(),
[&](parenthesis_token_pair_type const& token_pair)
{
this->self += token_pair.first | token_pair.second;
}
);
}
operator_token_type operator_add;
operator_token_type operator_sub;
operator_token_type operator_mul;
operator_token_type operator_div;
value_token_type value;
variable_token_type variable;
std::vector<parenthesis_token_pair_type> parenthesis;
whitespace_token_type whitespace;
};
template<typename Iterator>
class expression_grammar
: public qi::grammar<Iterator>
{
public:
template<typename Tokens>
explicit expression_grammar(Tokens const& tokens)
: expression_grammar::base_type(start)
{
start %= expression >> qi::eoi;
expression %= sum_operand >> -(sum_operator >> expression);
sum_operator %= tokens.operator_add | tokens.operator_sub;
sum_operand %= fac_operand >> -(fac_operator >> sum_operand);
fac_operator %= tokens.operator_mul | tokens.operator_div;
if(!tokens.parenthesis.empty())
fac_operand %= parenthesised | terminal;
else
fac_operand %= terminal;
terminal %= tokens.value | tokens.variable;
if(!tokens.parenthesis.empty())
{
parenthesised %= tokens.parenthesis.front().first >> expression >> tokens.parenthesis.front().second;
std::for_each(tokens.parenthesis.cbegin() + 1, tokens.parenthesis.cend(),
[&](typename Tokens::parenthesis_token_pair_type const& token_pair)
{
parenthesised %= parenthesised.copy() | (token_pair.first >> expression >> token_pair.second);
}
);
}
}
private:
qi::rule<Iterator> start;
qi::rule<Iterator> expression;
qi::rule<Iterator> sum_operand;
qi::rule<Iterator> sum_operator;
qi::rule<Iterator> fac_operand;
qi::rule<Iterator> fac_operator;
qi::rule<Iterator> terminal;
qi::rule<Iterator> parenthesised;
};
int main()
{
typedef lex::lexertl::token<std::string::const_iterator> token_type;
typedef expression_lexer<lex::lexertl::actor_lexer<token_type>> expression_lexer_type;
typedef expression_lexer_type::iterator_type expression_lexer_iterator_type;
typedef expression_grammar<expression_lexer_iterator_type> expression_grammar_type;
expression_lexer_type lexer;
expression_grammar_type grammar(lexer);
while(std::cin)
{
std::string line;
std::getline(std::cin, line);
std::string::const_iterator first = line.begin();
std::string::const_iterator const last = line.end();
bool const result = lex::tokenize_and_parse(first, last, lexer, grammar);
if(!result)
std::cout << "Parsing failed! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
{
if(first != last)
std::cout << "Parsing succeeded! Reminder: >" << std::string(first, last) << "<" << std::endl;
else
std::cout << "Parsing succeeded!" << std::endl;
}
}
}
区别如下:
whitespace
标记作为所有其他标记被添加到词法分析器的self
。- 但是,一个动作与之关联。该操作使词法分析器忽略该标记。这正是我们想要的。
- 我的
expression_grammar
不再采用Skipper
模板参数。因此它也从规则中删除。
使用 lex::lexertl::actor_lexer
而不是lex::lexertl::lexer
因为现在有一个与令牌关联的操作。- 我打电话给
tokenize_and_parse
而不是tokenize_and_phrase_parse
因为我不再需要通过船长了。 - 此外,我将词法分析器中对
this->self
的第一个赋值从=
更改为+=
,因为它看起来更灵活(抵抗顺序更改)。不过不影响这里的解法。
我很擅长这个。它非常适合我的需求(或者更确切地说是我的品味)。但是我想知道这种变化是否还有其他后果?在某些情况下是否首选任何方法?那个我不知道。