使用Boost.Spirit Qi和Lex时的空白队长

时间:2016-09-13 10:54:10

标签: c++ boost boost-spirit boost-spirit-qi boost-spirit-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提醒,这意味着违规的+是&#34;无法恢复的&#34;消耗。显然,生成但与语法不匹配的令牌绝不会返回到原始输入。但这不是我要问的问题。我在检查代码时意识到了这一点。

现在的问题是空白跳过。我非常不喜欢它是如何完成的。虽然我已经这样做了,因为它似乎是许多例子提供的,包括StackOverflow上的问题答案。

最糟糕的事情似乎是(没有记录?)qi::in_state_skipper。此外,似乎我必须添加whitespace令牌(使用名称),而不是像所有其他令牌一样,因为使用lexer.whitespace代替"WS"似乎并不像工作

最后不得不&#34;混乱&#34; Skipper参数的语法似乎并不好看。难道我不能解脱它吗?毕竟我想基于令牌而不是直接输入来制作语法,我希望将空格从令牌流中排除 - 不再需要它了!

我还有哪些其他选项可以跳过空格?这样做有什么好处呢?

1 个答案:

答案 0 :(得分:4)

由于一些奇怪的原因,我现在发现了一个不同的问题,Boost.Spirit SQL grammar/lexer failure,其中some other solution提供了空格跳过。一个更好的!

以下是根据建议重新编写的示例代码:

public function store(Illuminate\Http\Request $request)
{
    $this->validate($request, [
        'time_start' => 'date_format:H:i',
        'time_end' => 'date_format:H:i|after:time_start',
    ]);

    // do other stuff
}

差异如下:

  1. #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
  2. 但是,操作与之关联。该动作使词法分析器忽略该标记。这正是我们想要的。
  3. 我的self不再需要expression_grammar模板参数。因此它也从规则中删除。
  4. Skipper用于代替lex::lexertl::actor_lexer,因为现在有一个与令牌相关联的操作。
  5. 我打电话给lex::lexertl::lexer代替tokenize_and_parse,因为我不再需要通过队长。
  6. 此外,我将词法分析中的tokenize_and_phrase_parse的第一个作业从this->self更改为=,因为它似乎更灵活(抵制订单更改)。但它并没有影响到这里的解决方案。
  7. 我很擅长这个。它完美地满足了我的需求(或更好地说出我的口味)。不过我想知道这种变化是否还有其他后果?在某些情况下,是否有任何方法?我不知道。