Boost :: анализатор выражений духа - PullRequest
10 голосов
/ 11 декабря 2011

У меня еще одна проблема с моим boost :: spirit parser.

template<typename Iterator>
struct expression: qi::grammar<Iterator, ast::expression(), ascii::space_type> {
    expression() :
        expression::base_type(expr) {
        number %= lexeme[double_];
        varname %= lexeme[alpha >> *(alnum | '_')];

        binop = (expr >> '+' >> expr)[_val = construct<ast::binary_op<ast::add>>(_1,_2)]
              | (expr >> '-' >> expr)[_val = construct<ast::binary_op<ast::sub>>(_1,_2)]
              | (expr >> '*' >> expr)[_val = construct<ast::binary_op<ast::mul>>(_1,_2)]
              | (expr >> '/' >> expr)[_val = construct<ast::binary_op<ast::div>>(_1,_2)] ;

        expr %= number | varname | binop;
    }

    qi::rule<Iterator, ast::expression(), ascii::space_type> expr;
    qi::rule<Iterator, ast::expression(), ascii::space_type> binop;
    qi::rule<Iterator, std::string(), ascii::space_type> varname;
    qi::rule<Iterator, double(), ascii::space_type> number;
};

Это был мой парсер. Он просто проанализировал "3.1415" и "var", но когда я попытался проанализировать "1+2", он сказал мне parse failed. Затем я попытался изменить правило binop на

    binop = expr >>
           (('+' >> expr)[_val = construct<ast::binary_op<ast::add>>(_1, _2)]
          | ('-' >> expr)[_val = construct<ast::binary_op<ast::sub>>(_1, _2)]
          | ('*' >> expr)[_val = construct<ast::binary_op<ast::mul>>(_1, _2)]
          | ('/' >> expr)[_val = construct<ast::binary_op<ast::div>>(_1, _2)]);

Но теперь, конечно, он не может построить AST, потому что _1 и _2 установлены по-разному. Я видел только что-то вроде _r1, но как новичок я не совсем понимаю, как взаимодействуют boost::phoenix и boost::spirit.

Как это решить?

1 Ответ

20 голосов
/ 12 декабря 2011

Мне не совсем понятно, чего вы пытаетесь достичь.Самое главное, вас не беспокоит ассоциативность операторов?Я просто покажу простые ответы, основанные на использовании правой рекурсии - это приведет к левоассоциативным операторам , которые будут проанализированы.

Прямой ответ на ваш видимый вопрос будетбыть жонглировать fusion::vector2<char, ast::expression> - что не очень весело, особенно в Phoenix lambda семантических действиях.(Я покажу ниже, как это выглядит).

Между тем, я думаю, вам следует прочитать документы Духа

  • здесь в old Spirit docs (устранение левой рекурсии);Хотя синтаксис больше не применяется, Spirit по-прежнему генерирует синтаксические анализаторы рекурсивного спуска LL, поэтому концепция, лежащая в основе левой рекурсии, по-прежнему применяется.Приведенный ниже код показывает, что это применимо к Spirit Qi
  • здесь : примеры Qi содержат три calculator выборки, которые должны дать вам подсказку о том, почему ассоциативность операторов имеет значение, и как бы вы выразилиграмматика, которая фиксирует ассоциативность бинарных операторов.Очевидно, он также показывает, как поддерживать выражения в скобках для переопределения порядка оценки по умолчанию.

Код:

У меня есть три версии кода, которые работают, анализвведите как:

std::string input("1/2+3-4*5");

в ast::expression, сгруппированных как (используя BOOST_SPIRIT_DEBUG):

<expr>
  ....
  <success></success>
  <attributes>[[1, [2, [3, [4, 5]]]]]</attributes>
</expr>

Ссылки на код здесь:

Шаг 1: Сокращение семантических действий

Во-первых, я бы избавился от альтернативных выражений разбора для каждого оператора;это приводит к чрезмерному откату 1 .Кроме того, как вы узнали, это усложняет грамматику.Итак, вот более простой вариант, который использует функцию для семантического действия:

1 проверьте, используя BOOST_SPIRIT_DEBUG!

static ast::expression make_binop(char discriminant, 
     const ast::expression& left, const ast::expression& right)
{
    switch(discriminant)
    {
        case '+': return ast::binary_op<ast::add>(left, right);
        case '-': return ast::binary_op<ast::sub>(left, right);
        case '/': return ast::binary_op<ast::div>(left, right);
        case '*': return ast::binary_op<ast::mul>(left, right);
    }
    throw std::runtime_error("unreachable in make_binop");
}

// rules:
number %= lexeme[double_];
varname %= lexeme[alpha >> *(alnum | '_')];

simple = varname | number;
binop = (simple >> char_("-+*/") >> expr) 
    [ _val = phx::bind(make_binop, qi::_2, qi::_1, qi::_3) ]; 

expr = binop | simple;

Шаг 2: Удалите избыточные правила, используйте _val

Как видите, это имеет потенциалуменьшить сложность.Это всего лишь маленький шаг, чтобы удалить промежуточный биноп (который стал довольно избыточным):

number %= lexeme[double_];
varname %= lexeme[alpha >> *(alnum | '_')];

simple = varname | number;
expr = simple [ _val = _1 ] 
    > *(char_("-+*/") > expr) 
            [ _val = phx::bind(make_binop, qi::_1, _val, qi::_2) ]
    > eoi;

Как видите,

  • в рамках правила exprленивый заполнитель _val используется в качестве псевдо-локальной переменной, которая накапливает binops.По всем правилам вам придется использовать qi::locals<ast::expression> для такого подхода.(Это был ваш вопрос относительно _r1).
  • теперь есть явные точки ожидания, что делает грамматику более устойчивой
  • правило expr больше не должно быть автоматическим правилом (expr = вместо expr %=)

Шаг 0: Бороться с типами фьюжн напрямую

В заключение, ради забавы и позвольте мне показать, как вы моглиобработали предложенный вами код вместе со смещающими привязками _1, _2 и т. д.:

static ast::expression make_binop(
        const ast::expression& left, 
        const boost::fusion::vector2<char, ast::expression>& op_right)
{
    switch(boost::fusion::get<0>(op_right))
    {
        case '+': return ast::binary_op<ast::add>(left, boost::fusion::get<1>(op_right));
        case '-': return ast::binary_op<ast::sub>(left, boost::fusion::get<1>(op_right));
        case '/': return ast::binary_op<ast::div>(left, boost::fusion::get<1>(op_right));
        case '*': return ast::binary_op<ast::mul>(left, boost::fusion::get<1>(op_right));
    }
    throw std::runtime_error("unreachable in make_op");
}

// rules:
expression::base_type(expr) {
number %= lexeme[double_];
varname %= lexeme[alpha >> *(alnum | '_')];

simple = varname | number;
binop %= (simple >> (char_("-+*/") > expr)) 
    [ _val = phx::bind(make_binop, qi::_1, qi::_2) ]; // note _2!!!

expr %= binop | simple;

Как вы можете заметить, написание функции make_binop таким способом - не так весело!

...