Мне не совсем понятно, чего вы пытаетесь достичь.Самое главное, вас не беспокоит ассоциативность операторов?Я просто покажу простые ответы, основанные на использовании правой рекурсии - это приведет к левоассоциативным операторам , которые будут проанализированы.
Прямой ответ на ваш видимый вопрос будетбыть жонглировать 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 проверьте, используя 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;
Как видите, это имеет потенциалуменьшить сложность.Это всего лишь маленький шаг, чтобы удалить промежуточный биноп (который стал довольно избыточным):
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 %=
)
В заключение, ради забавы и позвольте мне показать, как вы моглиобработали предложенный вами код вместе со смещающими привязками _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
таким способом - не так весело!