2010-06-18 20 views
50

en este article about boost spirit semantic actions se menciona queparámetros de acción semántica espíritu impulso

En realidad, hay argumentos que se pasan 2 más : el contexto analizador y una referencia a un valor lógico ‘golpean’ parámetro. El contexto del analizador es significativo solo si la acción semántica está unida al lado derecho de la regla . Veremos más información sobre sobre esto en breve. El valor booleano se puede establecer en falso dentro de la acción semántica invalida la coincidencia en retrospectiva, lo que hace que el analizador falle.

Todo bien, pero he estado tratando de encontrar un ejemplo pasando un objeto de función como acción semántica que utiliza los otros parámetros (contexto del analizador y hit booleano) pero no he encontrado ninguno. Me encantaría ver un ejemplo usando funciones normales u objetos funcionales, ya que apenas puedo asimilar el vudú de Fénix

Respuesta

62

Esta es una muy buena pregunta (y también una lata de gusanos) porque se pone en la interfaz de qi y phoenix. Tampoco he visto un ejemplo, por lo que extenderé el artículo un poco en esta dirección.

como usted dice, funciones para semantic actions pueden tardar hasta tres parámetros

  1. atributo Matched - cubierta en el artículo
  2. Contexto - contiene la interfaz qi-Phoenix
  3. bandera
  4. Partido - manipular el partido estado

indicador de ajuste de

Como dice el artículo, el segundo parámetro no es significativo a menos que la expresión sea parte de una regla, así que comencemos con el tercero. Sin embargo, todavía se necesita un marcador de posición para el segundo parámetro y para este uso, boost::fusion::unused_type. Por lo que una función modificada del artículo de utilizar el tercer parámetro es:

#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; 
} 

que da salida:

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

Todo este ejemplo no es cambiar el partido a un no-partido, que se refleja en el salida del analizador. De acuerdo con hkaiser, en el impulso 1.44 y el establecimiento de la bandera de partido en falso hará que la coincidencia falle de la manera normal. Si se definen alternativas, el analizador retrocederá e intentará unirlas como cabría esperar. Sin embargo, en el impulso < = 1.43, un error de Spirit evita el retroceso, lo que causa un comportamiento extraño.Para ver esto, añadir Phoenix incluyen boost/spirit/include/phoenix.hpp y cambiar la expresión de

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

Es de esperar que, cuando el analizador qi :: int falla, la alternativa qi :: dígitos para que coincida con el comienzo de la entrada al " 1" , pero la salida es:

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

el 6 es el primer dígito de la segunda int en la entrada que indica la alternativa es tomada usando el patrón y sin dar marcha atrás. Tenga en cuenta también que el partido se considera exitoso, según la alternativa.

Una vez que ha salido el impulso 1.44, la bandera de coincidencia será útil para aplicar criterios de coincidencia que de otro modo podrían ser difíciles de expresar en una secuencia de analizador. Tenga en cuenta que la bandera de partido se puede manipular en expresiones de fénix utilizando el marcador de posición _pass.

parámetro de contexto

El parámetro más interesante es la segunda, que contiene la interfaz qi-Phoenix, o en la jerga qi, el contexto de la acción semántica. Para ilustrar esto, primero examinar una regla:

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

El parámetro de contexto encarna el atributo, arg1, ... argN, y Qi :: lugareños parametros plantilla, envuelto en un impulso :: tipo de plantilla espíritu :: contexto . Este atributo difiere del parámetro de función: el atributo de parámetro de función es el valor analizado, mientras que este atributo es el valor de la regla en sí. Una acción semántica debe mapear lo primero a lo último. He aquí un ejemplo de un posible tipo de contexto (equivalentes expresión Phoenix indicados):

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) 
    > 
> 

Nota el atributo de retorno y lista de argumentos toman la forma de una lista de estilo Lisp (un cons list). Para acceder a estas variables dentro de una función, acceda a los miembros attribute o locals de la plantilla de estructura context con fusion :: at <>(). Por ejemplo, para un contexto variable de 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; 

Para modificar el ejemplo de artículo para utilizar el segundo argumento, cambiar la definición y phrase_parse llamadas a funciones:

... 
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; 
.... 

Este es un ejemplo muy sencillo que solo los mapas el valor analizado para el valor del atributo de salida, pero las extensiones deberían ser bastante evidentes. Simplemente haga que los parámetros de la plantilla struct del contexto coincidan con la salida de la regla, la entrada y los tipos locales. Tenga en cuenta que este tipo de una correspondencia directa entre el tipo analizada/valor al tipo de salida/valor puede hacerse automáticamente utilizando reglas de automóviles, con un %= en lugar de un = en la definición de la regla:

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

en mi humilde opinión, escribiendo una función para cada acción sería bastante tedioso, en comparación con los equivalentes de expresión de fénix breves y legibles. Simpatizo con el punto de vista del vudú, pero una vez que trabajas con Fénix por un tiempo, la semántica y la sintaxis no son terriblemente difíciles.

Editar: Acceso contexto regla w/Phoenix

La variable de contexto sólo se define cuando el analizador es parte de una regla. Piense en un analizador como cualquier expresión que consuma datos de entrada, donde una regla traduce los valores del analizador (qi :: _ 1) en un valor de regla (qi :: _ val). La diferencia a menudo no es trivial, por ejemplo, cuando qi :: val tiene un tipo de clase que debe construirse a partir de los valores analizados POD. A continuación se muestra un ejemplo simple.

Digamos que parte de nuestra entrada es una secuencia de tres enteros CSV (x1, x2, x3), y solo cuidamos una función aritmética de estos tres enteros (f = x0 + (x1 + x2) * x3), donde x0 es un valor obtenido en otro lugar. Una opción es leer en los enteros y calcular la función, o alternativamente usar Phoenix para hacer ambas cosas.

Para este ejemplo, utilice una regla con un atributo de salida (el valor de la función), y la entrada (x0), y un local (para pasar información entre los analizadores individuales con la regla). Aquí está el ejemplo completo.

#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; 
} 

Alternativamente, todos los enteros se pudo analizar como un vector, y la función evaluada con una sola acción semántica (el % a continuación es el operador de la lista y se accede a los elementos del vector con Phoenix :: a):

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 
     ]; 
.... 

Por lo anterior, si la entrada es incorrecta (dos enteros en lugar de tres), algo malo podría suceder en tiempo de ejecución, por lo que sería mejor para especificar el número de valores analizados de forma explícita, por lo que el análisis se producirá un error por una mala entrada. Los siguientes usos _1, _2 y _3 para hacer referencia al primer, segundo y tercer valor de coincidencia:

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

Este es un ejemplo artificial, pero debe darle la idea. He encontrado que las acciones semánticas de Fénix son realmente útiles para construir objetos complejos directamente desde la entrada; esto es posible porque puede llamar constructores y funciones miembro dentro de acciones semánticas.

+3

Gracias por esa excelente explicación. ¿Le molesta que "robe" esto para volver a publicarlo en el sitio web de Spirit (sin duda dando los créditos correspondientes)? El problema de retroceso que está mencionando es un error en Spirit. El comportamiento correcto después de fallar la primera alternativa debe ser que la segunda alternativa comience nuevamente en el mismo punto en la entrada que la primera alternativa. Veré qué puedo hacer para arreglar esto. Además, no debe usar los marcadores de posición de fénix en acciones semánticas. Utilice siempre los marcadores de posición Spirit correspondientes, es decir, qi :: _ 1. – hkaiser

+1

Bien, el problema de retroceso está solucionado ahora y estará bien en la próxima versión (Boost V1.44). – hkaiser

+1

@hkaiser Me alegra que le guste y, por favor, reutilícelo si lo desea. Iba a preguntar sobre ese problema de retroceso en la lista de correo, gracias por ocuparme de él. Una pregunta para usted sobre los marcadores de posición: ambos 'phoenix :: _ 1' y' qi :: _ 1' se definen como 'const phoenix :: actor >', ¿está sujeto a cambios? – academicRobot

Cuestiones relacionadas