【问题标题】:Whitespace skipper when using Boost.Spirit Qi and Lex使用 Boost.Spirit Qi 和 Lex 时的空格跳过
【发布时间】:2016-09-13 10:54:10
【问题描述】:

让我们考虑以下代码:

#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

代码似乎运行良好(在带有 Boost 1.61 的 Visual Studio 2013 上进行了检查)。除了我注意到,如果我提供像5++5 这样的字符串,它会正确失败,但报告为提醒只是5 而不是+5,这意味着违规的+ 被“不可恢复地”消耗。显然,生成但与语法不匹配的标记绝不会返回到原始输入。但这不是我要问的。只是我在检查代码时意识到的一个旁注。

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

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

最后不得不用Skipper 参数“弄乱”语法似乎不太好。我不应该摆脱它吗?毕竟我想基于标记而不是直接输入来制作语法,并且我希望将空格从标记流中排除——那里不再需要它了!

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

【问题讨论】:

    标签: c++ boost boost-spirit boost-spirit-qi boost-spirit-lex


    【解决方案1】:

    直到现在我发现了一个不同的问题,Boost.Spirit SQL grammar/lexer failure,其中提供了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;
            }
        }
    }
    

    区别如下:

    1. whitespace 标记与所有其他标记一样添加到词法分析器的 self 中。
    2. 但是,一个动作与之相关联。该操作使词法分析器忽略该标记。这正是我们想要的。
    3. 我的expression_grammar 不再采用Skipper 模板参数。因此它也从规则中删除。
    4. 使用lex::lexertl::actor_lexer 代替lex::lexertl::lexer,因为现在有一个与令牌关联的操作。
    5. 我打电话给tokenize_and_parse 而不是tokenize_and_phrase_parse,因为我不再需要通过skipper。
    6. 此外,我在 lexer 中将第一个赋值为 this-&gt;self= 更改为 +=,因为它看起来更灵活(可以抵抗订单更改)。但这并不影响这里的解决方案。

    我很擅长这个。它完全符合我的需求(或者更好地说是我的口味)。但是我想知道这种变化是否还有其他后果?在某些情况下是否首选任何方法?这个我不知道。

    【讨论】:

    • 在“简化和盈利”中的stackoverflow.com/questions/13361519/… 中给出了相同的答案。我怎么会错过这些?!
    • 感谢您的自我回答和信用。我没有时间(坦率地说,我不太喜欢 Lex……)
    • @sehe 如果只有 Qi 支持基于正则表达式的匹配...或者是吗?
    • 不错!您可以添加到您的答案中,在词法分析器中有一个用于跳过空格的单独状态有另一个相当大的缺点:缺乏调试支持。如果您使用单独的状态进行跳过,然后使用 BOOST_SPIRIT_DEBUG_NODE,您将无法获得令牌流的完整输出。这是因为默认调试处理程序推进词法分析器迭代器以获取令牌,词法分析器当然处于初始状态。一旦遇到空格,迭代就会停止,因为词法分析器找不到匹配项。令牌流将在规则跟踪中的第一个空白处被剪切。
    • 我发现了这种方法不起作用的地方。如果您有多个空格表达式,您可以在状态内规则中将它们一起列出:“this->self("WS") = token_def("[ \\t\\n]+") | "\\/\*[^*]*\*+([^/*][^*]*\*+)*\\/" | “\\/\\/[^\n]*”。在您在答案中列出的代码中,您必须定义几个标记。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多