使用Boost时的空白提示符.灵气和莱克斯

Whitespace skipper when using Boost.Spirit Qi and Lex

本文关键字:提示符 Boost 空白 使用      更新时间:2023-10-16

考虑以下代码:

#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来解析令牌。

在这种小情况下使用lexer似乎有点小题大做,而且很可能就是小题大做。但这是一个简化例子的代价。还要注意,lexer的使用允许使用正则表达式轻松定义令牌,同时允许通过外部代码轻松定义它们(特别是用户提供的配置)。通过提供的示例,从外部配置文件读取令牌定义完全没有问题,例如允许用户将变量从%name更改为$name

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

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

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

最后,不得不用Skipper参数"混乱"语法似乎不太好。难道我不应该摆脱它吗?毕竟,我想让语法基于令牌而不是直接输入,我想把空格从令牌流中排除——那里不再需要它了!

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

由于一些奇怪的原因,现在我发现了一个不同的问题,Boost。Spirit SQL语法/词法分析器失败,其中提供了其他解决空白跳过的方案。一个更好的!

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

#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令牌与所有其他令牌一样被添加到lexer的self
  2. 但是,一个动作与它相关联。该操作使词法分析器忽略令牌。这正是我们想要的。
  3. 我的expression_grammar不再接受Skipper模板参数。因此,它也被从规则中删除。
  4. lex::lexertl::actor_lexer被用来代替lex::lexertl::lexer,因为现在有一个与令牌相关的动作。
  5. 我调用tokenize_and_parse而不是tokenize_and_phrase_parse,因为我不再需要通过船长了。
  6. 我还将lexer中的this->self=更改为+=,因为它似乎更灵活(抵抗订单更改)。但它不影响这里的解决方案。

我对此很满意。它完美地满足了我的需求(或者更好地说是我的品味)。但是我想知道这种变化是否有其他的后果?在某些情况下是否有更可取的方法?这个我不知道