повысить дух семантических параметров действия - PullRequest
52 голосов
/ 18 июня 2010

В этой статье о семантических действиях буст-духа упоминается, что

На самом деле есть еще 2 аргумента передается: контекст синтаксического анализатора и ссылка на логическое «хит» параметр. Контекст парсера имеет смысл только если семантическое действие прикреплен где-то справа сторона правила. Мы увидим больше Информация об этом в ближайшее время. логическое значение может быть установлено в false внутри семантического действия делает недействительным матч в ретроспективе, что делает сбой синтаксического анализатора.

Все хорошо, но я пытался найти пример, передающий функциональный объект как семантическое действие, которое использует другие параметры (контекст синтаксического анализатора и хит булево), но я не нашел ни одного. Я хотел бы видеть пример, использующий обычные функции или функциональные объекты, так как я с трудом справляюсь с Фениксом Вуду

1 Ответ

66 голосов
/ 18 июня 2010

Это действительно хороший вопрос (а также банка червей), потому что он находится на стыке ци и феникса.Я тоже не видел примера, поэтому немного расширю статью в этом направлении.

Как вы говорите, функции для семантических действий могут принимать до трех параметров

  1. Соответствующий атрибут - рассмотрен в статье
  2. Context -содержит интерфейс qi-phoenix
  3. Флаг соответствия - манипулирует состоянием соответствия

Флаг соответствия

Как указано в статье, второй параметрне имеет смысла, если выражение не является частью правила, поэтому давайте начнем с третьего.Заполнитель для второго параметра все еще необходим, хотя и для этого используйте boost::fusion::unused_type.Таким образом, измененная функция из статьи для использования третьего параметра:

#include <boost/spirit/include/qi.hpp>
#include <string>
#include <iostream>

void f(int attribute, const boost::fusion::unused_type& it, bool& mFlag){
    //output parameters
    std::cout << "matched integer: '" << attribute << "'" << std::endl
              << "match flag: " << mFlag << std::endl;

    //fiddle with match flag
    mFlag = false;
}

namespace qi = boost::spirit::qi;

int main(void){
   std::string input("1234 6543");
   std::string::const_iterator begin = input.begin(), end = input.end();

   bool returnVal = qi::phrase_parse(begin, end, qi::int_[f], qi::space);

   std::cout << "return: " << returnVal << std::endl;
   return 0;
}

, которая выводит:

matched integer: '1234'
match flag: 1
return: 0

Все, что делает этот пример, это переключение на несоответствие, котороеотражается на выходе парсера.По словам hkaiser, при повышении 1.44 и выше при установке флага совпадения на ложь совпадение будет неуспешным.Если определены альтернативы, анализатор откатится назад и попытается сопоставить их, как и следовало ожидать.Тем не менее, в boost <= 1.43 ошибка Spirit предотвращает возврат, что вызывает странное поведение.Чтобы увидеть это, добавьте phoenix include <code>boost/spirit/include/phoenix.hpp и измените выражение на

qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]

Можно ожидать, что при сбое парсера qi :: int альтернативный qi :: digit будет совпадать с началомвход на «1», но на выходе:

matched integer: '1234'
match flag: 1
6
return: 1

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

Когда буст 1.44 отсутствует, флаг соответствия будет полезен для применения критериев соответствия, которые иначе было бы трудно выразить в последовательности синтаксического анализатора.Обратите внимание, что флагом соответствия можно манипулировать в выражениях феникса, используя заполнитель _pass.

Параметр контекста

Более интересным является второй параметр, который содержитинтерфейс ци-феникс или, на языке ци, контекст семантического действия.Чтобы проиллюстрировать это, сначала рассмотрим правило:

rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>

Параметр context содержит параметры шаблона Attribute, Arg1, ... ArgN и qi :: locals, заключенные в boost :: spirit :: contextтип шаблона.Этот атрибут отличается от параметра функции: атрибут параметра функции является проанализированным значением, а этот атрибут является значением самого правила.Семантическое действие должно отображать первое на второе.Вот пример возможного типа контекста (указаны эквиваленты выражений феникса):

using namespace boost;
spirit::context<              //context template
    fusion::cons<             
        int&,                 //return int attribute (phoenix: _val)
        fusion::cons<
            char&,            //char argument1       (phoenix: _r1)
            fusion::cons<
                float&,       //float argument2      (phoenix: _r2) 
                fusion::nil   //end of cons list
            >,
        >,
    >,
    fusion::vector2<          //locals container
        char,                 //char local           (phoenix: _a)
        unsigned int          //unsigned int local   (phoenix: _b)
    > 
>

Обратите внимание, что атрибут return и список аргументов принимают форму списка в стиле lisp ( cons list ).Чтобы получить доступ к этим переменным внутри функции, обратитесь к attribute или locals членам шаблона структуры context с помощью fusion :: at <> ().Например, для переменной контекста con

//assign return attribute
fusion::at_c<0>(con.attributes) = 1;

//get the second rule argument
float arg2 = fusion::at_c<2>(con.attributes);

//assign the first local
fusion::at_c<1>(con.locals) = 42;

Чтобы изменить пример статьи для использования второго аргумента, измените определение функции и вызовы фразу_parse:

...
typedef 
    boost::spirit::context<
        boost::fusion::cons<int&, boost::fusion::nil>, 
        boost::fusion::vector0<> 
    > f_context;
void f(int attribute, const f_context& con, bool& mFlag){
   std::cout << "matched integer: '" << attribute << "'" << std::endl
             << "match flag: " << mFlag << std::endl;

   //assign output attribute from parsed value    
   boost::fusion::at_c<0>(con.attributes) = attribute;
}
...
int matchedInt;
qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule = qi::int_[f];
qi::phrase_parse(begin, end, intRule, ascii::space, matchedInt);
std::cout << "matched: " << matchedInt << std::endl;
....

Этоочень простой пример, который просто отображает проанализированное значение на выходное значение атрибута, но расширения должны быть достаточно очевидными.Просто сделайте так, чтобы параметры шаблона структуры контекста соответствовали выходным данным, входным и локальным типам правил.Обратите внимание, что этот тип прямого соответствия между проанализированным типом / значением и выходным типом / значением может быть выполнен автоматически с использованием автоматических правил с %= вместо = при определении правила:

qi::rule<std::string::const_iterator,int(void),ascii::space_type> 
    intRule %= qi::int_;

ИМХО, написание функции для каждого действия было бы довольно утомительным по сравнению с краткими и читаемыми эквивалентами выражений феникса.Я сочувствую точке зрения voodoo, но если вы немного поработаете с Phoenix, семантика и синтаксис не так уж и сложны.

Редактировать: Доступ к контексту правила с Phoenix

Переменная контекста определяется только тогда, когда анализатор является частью правила.Думайте о парсере как о любом выражении, которое потребляет ввод, где правило переводит значения парсера (qi :: _ 1) в значение правила (qi :: _ val).Разница часто нетривиальна, например, когда qi :: val имеет тип Class, который должен быть составлен из проанализированных значений POD.Ниже приведен простой пример.

Допустим, часть нашего ввода представляет собой последовательность из трех целых чисел CSV (x1, x2, x3), и мы выделяем только арифметическую функцию этих трех целых чисел (f = x0 + (x1)+ x2) * x3), где x0 - значение, полученное в другом месте.Одним из вариантов является чтение целых чисел и вычисление функции или использование Phoenix для выполнения обоих действий.

В этом примере используйте одно правило с выходным атрибутом (значением функции) и input (x0),и локальный (для передачи информации между отдельными парсерами с помощью правила).Вот полный пример.

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <string>
#include <iostream>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;

int main(void){
   std::string input("1234, 6543, 42");
   std::string::const_iterator begin = input.begin(), end = input.end();

   qi::rule<
      std::string::const_iterator,
      int(int),                    //output (_val) and input (_r1)
      qi::locals<int>,             //local int (_a)
      ascii::space_type
   >
      intRule =
            qi::int_[qi::_a = qi::_1]             //local = x1
         >> ","
         >> qi::int_[qi::_a += qi::_1]            //local = x1 + x2
         >> ","
         >> qi::int_
            [
               qi::_val = qi::_a*qi::_1 + qi::_r1 //output = local*x3 + x0
            ];

   int ruleValue, x0 = 10;
   qi::phrase_parse(begin, end, intRule(x0), ascii::space, ruleValue);
   std::cout << "rule value: " << ruleValue << std::endl;
   return 0;
}

В качестве альтернативы все целые могут быть проанализированы как вектор, и функция оценивается одним семантическим действием (ниже % - оператор списка и элементы векторадоступны с помощью phoenix :: at):

namespace ph = boost::phoenix;
...
    qi::rule<
        std::string::const_iterator,
        int(int),
        ascii::space_type
    >
    intRule =
        (qi::int_ % ",")
        [
            qi::_val = (ph::at(qi::_1,0) + ph::at(qi::_1,1))
                      * ph::at(qi::_1,2) + qi::_r1
        ];
....

Для вышеизложенного, если ввод неправильный (два целых вместо трех), во время выполнения может произойти плохая вещь, поэтому лучше указатьчисло проанализированных значений явно, поэтому при неправильном вводе синтаксический анализ не удастся.Ниже используются _1, _2 и _3 для ссылки на первое, второе и третье значение соответствия:

(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
    qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];

Это надуманный пример, но он должен дать вам идею.Я нашел, что семантические действия феникса действительно полезны при построении сложных объектов непосредственно из ввода;это возможно, потому что вы можете вызывать конструкторы и функции-члены в рамках семантических действий.

...