2010-06-20 2 views
9

Я пытаюсь разобрать C-функцию, как выражения дерева, как следующее (с помощью Spirit Parser Framework):Синтаксической грамматику с Boost Духом

F(A() , B(GREAT(SOME , NOT)) , C(YES)) 

Для этого я пытаюсь использовать три правил по следующему грамматика:

template< typename Iterator , typename ExpressionAST > 
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), space_type> { 

    InputGrammar() : InputGrammar::base_type() { 
     tag = (qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"))[ push_back(at_c<0>(qi::_val) , qi::_1) ]; 
     command = tag [ at_c<0>(qi::_val) = at_c<0>(qi::_1) ] >> "(" >> (*instruction >> ",") 
             [ push_back(at_c<1>(qi::_val) , qi::_1) ] >> ")"; 
     instruction = (command | tag) [qi::_val = qi::_1]; 
    } 
    qi::rule< Iterator , ExpressionAST() , space_type > tag; 
    qi::rule< Iterator , ExpressionAST() , space_type > command; 
    qi::rule< Iterator , ExpressionAST() , space_type > instruction; 
}; 

Обратите внимание, что мое правило тег просто пытается захватить идентификаторы, используемые в выражениях (в «функции» имен). Также обратите внимание, что подпись правила тега возвращает ExpressionAST вместо std::string, как в большинстве примеров. Причина, по которой я хочу сделать это, на самом деле довольно проста: я ненавижу использовать варианты, и, если это возможно, я избегаю их. Было бы здорово сохранить торт и съесть его, я думаю.

Команда должна начинаться с тега (имя текущего узла, поля первой строки узла AST) и переменное количество аргументов, заключенных в круглые скобки, и каждый из аргументов может быть самим тегом или другой командой ,

Однако этот пример не работает вообще. Он компилируется и все, но во время выполнения он не анализирует все мои тестовые строки. И то, что меня действительно раздражает, заключается в том, что я не могу понять, как это исправить, поскольку я не могу отлаживать вышеупомянутый код, по крайней мере, в традиционном значении этого слова. В принципе, единственный способ, которым я вижу, я могу исправить вышеупомянутый код, зная, что я делаю неправильно.

Итак, вопрос в том, что я не знаю, что не так с вышеуказанным кодом. Как бы вы определили вышеупомянутую грамматику?

Тип ExpressionAST я использую:

struct MockExpressionNode { 
    std::string name; 
    std::vector<MockExpressionNode> operands; 

    typedef std::vector<MockExpressionNode>::iterator iterator; 
    typedef std::vector<MockExpressionNode>::const_iterator const_iterator; 

    iterator begin() { return operands.begin(); } 
    const_iterator begin() const { return operands.begin(); } 
    iterator end() { return operands.end(); } 
    const_iterator end() const { return operands.end(); } 

    bool is_leaf() const { 
     return (operands.begin() == operands.end()); 
    } 
}; 

BOOST_FUSION_ADAPT_STRUCT(
    MockExpressionNode, 
    (std::string, name) 
    (std::vector<MockExpressionNode>, operands) 
) 
+0

Что-то, что я обнаружил недавно, состоит в том, что идентификаторы C и C++ могут иметь символы «$» в своих именах. Так что a-z, A-Z, 0-9 (кроме первого символа), _ и $ действительны в C/C++-идентификаторе. – Cthutu

+2

@Cthutu MSVC позволяет акцентировать символы в идентификаторах. Это не означает, что он соответствует стандарту. –

+0

Что еще более важно, какой смысл вы пытаетесь сделать @Cthutu? Есть ли недостаток в идентификаторах? Ваш компилятор не поддерживает правильные пространства имен? – sehe

ответ

11

Насколько отладки, его можно использовать обычный перерыв и смотреть подход. Это затруднено тем, как вы отформатировали правила. Если вы отформатируете по примерам духа (~ один парсер в строке, один оператор феникса на строку), точки останова будут намного более информативными.

Ваша структура данных не имеет возможности отличить A() от SOME тем, что они оба являются листами (сообщите мне, если я что-то упустил). Из вашего варианта комментария я не думаю, что это было вашим намерением, поэтому, чтобы отличить эти два случая, я добавил переменную-член bool commandFlag в MockExpressionNode (true для A() и false для SOME) с соответствующей линией адаптера сопряжения.

Для кода конкретно, вам необходимо пройти правило запуска в базовый конструктор, т.е .:

InputGrammar() : InputGrammar::base_type(instruction) {...} 

Это точка входа в грамматике, и поэтому вы не получали никаких данных разобранные. Я удивлен, что он скомпилирован без него, я думал, что тип грамматики должен соответствовать типу первого правила. Тем не менее, это удобная конвенция.

Для tag правило, есть на самом деле два парсеры qi::char_("a-zA-Z_"), который _1 с типом char и *qi::char_("a-zA-Z_0-9") который _2 с типом (в основном) vector<char>.Его не возможно принуждать их в строку без autorules, но это может быть сделано путем присоединения правила к каждому разобранного полукокса:

tag = qi::char_("a-zA-Z_") 
     [ at_c<0>(qi::_val) = qi::_1 ]; 
    >> *qi::char_("a-zA-Z_0-9")   //[] has precedence over *, so _1 is 
     [ at_c<0>(qi::_val) += qi::_1 ]; // a char rather than a vector<char> 

Однако его гораздо чище, чтобы дух сделать это преобразование. Поэтому определите новое правило:

qi::rule< Iterator , std::string(void) , ascii::space_type > identifier; 
identifier %= qi::char_("a-zA-Z_") >> *qi::char_("a-zA-Z_0-9"); 

И не беспокойтесь об этом;). Тогда тег становится

tag = identifier 
     [ 
      at_c<0>(qi::_val) = qi::_1, 
      ph::at_c<2>(qi::_val) = false //commandFlag 
     ] 

Для команды, первая часть в порядке, но Theres пару проблем с (*instruction >> ",")[ push_back(at_c<1>(qi::_val) , qi::_1) ]. Это проанализирует нулевые или несколько правил команды, за которыми следует «,». Он также пытается push_back a vector<MockExpressionNode> (не знаете, почему это скомпилировано, возможно, не создано из-за отсутствующего правила запуска?). Я думаю, что вы хотите следующее (с изменением идентификатора):

command = 
     identifier 
     [ 
      ph::at_c<0>(qi::_val) = qi::_1, 
      ph::at_c<2>(qi::_val) = true //commandFlag 
     ] 
    >> "(" 
    >> -(instruction % ",") 
     [ 
      ph::at_c<1>(qi::_val) = qi::_1 
     ] 
    >> ")"; 

При этом используется дополнительный оператор - и оператор списка %, последний эквивалентно instruction >> *("," >> instruction). Затем выражение phoenix просто назначает вектор непосредственно члену структуры, но вы также можете приложить действие непосредственно к совпадению команд и использовать push_back.

Правило инструкции в порядке, я просто упомянул, что это эквивалентно instruction %= (command|tag).

Одна последняя вещь, если есть на самом деле нет никакого различия между A() и SOME (т.е. исходная структура, не commandFlag), вы можете написать этот анализатор, используя только autorules:

template< typename Iterator , typename ExpressionAST > 
struct InputGrammar : qi::grammar<Iterator, ExpressionAST(), ascii::space_type> { 
    InputGrammar() : InputGrammar::base_type(command) { 
     identifier %= 
      qi::char_("a-zA-Z_") 
     >> *qi::char_("a-zA-Z_0-9"); 
     command %= 
      identifier 
     >> -(
      "(" 
     >> -(command % ",") 
     >> ")"); 
    } 
    qi::rule< Iterator , std::string(void) , ascii::space_type > identifier; 
    qi::rule< Iterator , ExpressionAST(void) , ascii::space_type > command; 
}; 

Это большая выгода используя фьюжн-обернутую структуру, которая тщательно моделирует вход.

+0

Привет AcademicRobot, отличный пост. Я взял несколько дней, чтобы ответить только потому, что было так много, чтобы переварить операторов, которые я действительно не читал в документах. Также пытался заменить ваши setFlag-сеттеры на qi :: _ val.setAsFlag(); но, по-видимому, тип _val - это не то же самое, что ExpressionAST, а какая-то актерская феникс-обертка – lurscher

+1

@lurscher - Рад, что вы сочли это полезным. Да, qi :: _ val будет оценивать ExpressionAST, но на самом деле этого типа нет. Чтобы вызвать функции-члены, вы будете использовать привязку phoenix (для memfun 'void setAsFlag (флаг bool)'): 'phoenix :: bind (& ExpressionAST :: setAsFlag, qi :: _ val, true)'. – academicRobot

+0

странный, у которого есть определенный void * аромат для него .. – lurscher

Смежные вопросы