impulso :: iterador de posición de acceso al espíritu desde acciones semánticas

Digamos que tengo un código como este (números de línea para referencia):

1: 2:function FuncName_1 { 3: var Var_1 = 3; 4: var Var_2 = 4; 5: ... 

Quiero escribir una gramática que analice dicho texto, ponga todos los identificadores (nombres de funciones y variables) en un árbol (¿utree?). Cada nodo debe conservar: line_num, column_num y el valor del símbolo. ejemplo:

 root: FuncName_1 (line:2,col:10) children[0]: Var_1 (line:3, col:8) children[1]: Var_1 (line:4, col:9) 

Quiero ponerlo en el árbol porque planeo atravesar ese árbol y para cada nodo debo conocer el ‘contexto’: (todos los nodos principales de los nodos actuales).

Por ejemplo, mientras se procesa el nodo con Var_1, debo saber que este es un nombre para la variable local para la función FuncName_1 (que actualmente se está procesando como nodo, pero un nivel anterior)

No puedo entender algunas cosas.

  1. ¿Se puede hacer esto en espíritu con acciones semánticas y de utree? ¿O debería usar variante árboles?
  2. ¿Cómo pasar al nodo esas tres informaciones (columna, línea, nombre de símbolo) al mismo tiempo? Sé que debo usar pos_iterator como tipo de iterador para la gramática, pero ¿cómo acceder a esa información en acción semática?

Soy un novato en Boost, así que leo la documentación del Spirit una y otra vez. Intento buscar mis problemas en Google, pero de alguna manera no puedo juntar todas las piezas para encontrar la solución. Parece que no había nadie con un caso de uso como el mío antes (o simplemente no puedo encontrarlo) Parece que las únicas soluciones con iterador de posición son las que manejan los errores de análisis, pero este no es el caso. Estoy interesado en. El código que solo analiza el código que estaba tomando está abajo, pero no sé cómo seguir adelante.

  #include  #include  namespace qi = boost::spirit::qi; typedef boost::spirit::line_pos_iterator pos_iterator_t; template struct ParseGrammar: public qi::grammar { ParseGrammar():ParseGrammar::base_type(SourceCode) { using namespace qi; KeywordFunction = lit("function"); KeywordVar = lit("var"); SemiColon = lit(';'); Identifier = lexeme [alpha >> *(alnum | '_')]; VarAssignemnt = KeywordVar >> Identifier >> char_('=') >> int_ >> SemiColon; SourceCode = KeywordFunction >> Identifier >> '{' >> *VarAssignemnt >> '}'; } qi::rule SourceCode; qi::rule KeywordFunction; qi::rule VarAssignemnt; qi::rule KeywordVar; qi::rule SemiColon; qi::rule Identifier; }; int main() { std::string const content = "function FuncName_1 {\n var Var_1 = 3;\n var Var_2 = 4; }"; pos_iterator_t first(content.begin()), iter = first, last(content.end()); ParseGrammar resolver; // Our parser bool ok = phrase_parse(iter, last, resolver, qi::space); std::cout << std::boolalpha; std::cout << "\nok : " << ok << std::endl; std::cout << "full : " << (iter == last) << std::endl; if(ok && iter == last) { std::cout << "OK: Parsing fully succeeded\n\n"; } else { int line = get_line(iter); int column = get_column(first, iter); std::cout << "-------------------------\n"; std::cout << "ERROR: Parsing failed or not complete\n"; std::cout << "stopped at: " << line << ":" << column << "\n"; std::cout << "remaining: '" << std::string(iter, last) << "'\n"; std::cout << "-------------------------\n"; } return 0; } 

Este ha sido un ejercicio divertido, donde finalmente on_success una demostración de trabajo de on_success [1] para anotar los nodos AST.

Supongamos que queremos un AST como:

 namespace ast { struct LocationInfo { unsigned line, column, length; }; struct Identifier : LocationInfo { std::string name; }; struct VarAssignment : LocationInfo { Identifier id; int value; }; struct SourceCode : LocationInfo { Identifier function; std::vector assignments; }; } 

Sé que la ‘información de ubicación’ es probablemente una exageración para el nodo SourceCode , pero ya sabes … De todos modos, para facilitar la asignación de atributos a estos nodos sin requerir acciones semánticas o muchos constructores específicamente diseñados:

 #include  BOOST_FUSION_ADAPT_STRUCT(ast::Identifier, (std::string, name)) BOOST_FUSION_ADAPT_STRUCT(ast::VarAssignment, (ast::Identifier, id)(int, value)) BOOST_FUSION_ADAPT_STRUCT(ast::SourceCode, (ast::Identifier, function)(std::vector, assignments)) 

Ahí. Ahora podemos declarar las reglas para exponer estos atributos:

 qi::rule SourceCode; qi::rule VarAssignment; qi::rule Identifier; // no skipper, no attributes: qi::rule KeywordFunction, KeywordVar, SemiColon; 

No (esencialmente) modificamos la gramática, en absoluto: la propagación del atributo es “simplemente automática” [2] :

 KeywordFunction = lit("function"); KeywordVar = lit("var"); SemiColon = lit(';'); Identifier = as_string [ alpha >> *(alnum | char_("_")) ]; VarAssignment = KeywordVar >> Identifier >> '=' >> int_ >> SemiColon; SourceCode = KeywordFunction >> Identifier >> '{' >> *VarAssignment >> '}'; 

La magia

¿Cómo obtenemos la información de ubicación de origen adjunta a nuestros nodos?

 auto set_location_info = annotate(_val, _1, _3); on_success(Identifier, set_location_info); on_success(VarAssignment, set_location_info); on_success(SourceCode, set_location_info); 

Ahora, annotate es solo una versión perezosa de un calleable que se define como:

 template struct annotation_f { typedef void result_type; annotation_f(It first) : first(first) {} It const first; template void operator()(Val& v, First f, Last l) const { do_annotate(v, f, l, first); } private: void static do_annotate(ast::LocationInfo& li, It f, It l, It first) { using std::distance; li.line = get_line(f); li.column = get_column(first, f); li.length = distance(f, l); } static void do_annotate(...) { } }; 

Debido a la forma en que funciona get_column , el functor es de estado (ya que recuerda el iterador de inicio) [3] . Como puede ver, do_annotate solo acepta cualquier cosa que se derive de LocationInfo .

Ahora, la prueba del pudín:

 std::string const content = "function FuncName_1 {\n var Var_1 = 3;\n var Var_2 = 4; }"; pos_iterator_t first(content.begin()), iter = first, last(content.end()); ParseGrammar resolver(first); // Our parser ast::SourceCode program; bool ok = phrase_parse(iter, last, resolver, qi::space, program); std::cout << std::boolalpha; std::cout << "ok : " << ok << std::endl; std::cout << "full: " << (iter == last) << std::endl; if(ok && iter == last) { std::cout << "OK: Parsing fully succeeded\n\n"; std::cout << "Function name: " << program.function.name << " (see L" << program.printLoc() << ")\n"; for (auto const& va : program.assignments) std::cout << "variable " << va.id.name << " assigned value " << va.value << " at L" << va.printLoc() << "\n"; } else { int line = get_line(iter); int column = get_column(first, iter); std::cout << "-------------------------\n"; std::cout << "ERROR: Parsing failed or not complete\n"; std::cout << "stopped at: " << line << ":" << column << "\n"; std::cout << "remaining: '" << std::string(iter, last) << "'\n"; std::cout << "-------------------------\n"; } 

Esto imprime:

 ok : true full: true OK: Parsing fully succeeded Function name: FuncName_1 (see L1:1:56) variable Var_1 assigned value 3 at L2:3:14 variable Var_2 assigned value 4 at L3:3:15 

Progtwig de demostración completa

Véalo en vivo en Coliru

También mostrando:

  • manejo de errores, por ejemplo:

     Error: expecting "=" in line 3: var Var_2 - 4; } ^---- here ok : false full: false ------------------------- ERROR: Parsing failed or not complete stopped at: 1:1 remaining: 'function FuncName_1 { var Var_1 = 3; var Var_2 - 4; }' ------------------------- 
  • Macros de BOOST_SPIRIT_DEBUG

  • Una forma un poco pirata de transmitir de manera conveniente la parte de LocationInfo de cualquier nodo AST, lo siento 🙂
 //#define BOOST_SPIRIT_DEBUG #define BOOST_SPIRIT_USE_PHOENIX_V3 #include  #include  #include  #include  #include  namespace qi = boost::spirit::qi; namespace phx= boost::phoenix; typedef boost::spirit::line_pos_iterator pos_iterator_t; namespace ast { namespace manip { struct LocationInfoPrinter; } struct LocationInfo { unsigned line, column, length; manip::LocationInfoPrinter printLoc() const; }; struct Identifier : LocationInfo { std::string name; }; struct VarAssignment : LocationInfo { Identifier id; int value; }; struct SourceCode : LocationInfo { Identifier function; std::vector assignments; }; /////////////////////////////////////////////////////////////////////////// // Completely unnecessary tweak to get a "poor man's" io manipulator going // so we can do `std::cout << x.printLoc()` on types of `x` deriving from // LocationInfo namespace manip { struct LocationInfoPrinter { LocationInfoPrinter(LocationInfo const& ref) : ref(ref) {} LocationInfo const& ref; friend std::ostream& operator<<(std::ostream& os, LocationInfoPrinter const& lip) { return os << lip.ref.line << ':' << lip.ref.column << ':' << lip.ref.length; } }; } manip::LocationInfoPrinter LocationInfo::printLoc() const { return { *this }; } // feel free to disregard this hack /////////////////////////////////////////////////////////////////////////// } BOOST_FUSION_ADAPT_STRUCT(ast::Identifier, (std::string, name)) BOOST_FUSION_ADAPT_STRUCT(ast::VarAssignment, (ast::Identifier, id)(int, value)) BOOST_FUSION_ADAPT_STRUCT(ast::SourceCode, (ast::Identifier, function)(std::vector, assignments)) struct error_handler_f { typedef qi::error_handler_result result_type; template qi::error_handler_result operator()(T1 b, T2 e, T3 where, T4 const& what) const { std::cerr << "Error: expecting " << what << " in line " << get_line(where) << ": \n" << std::string(b,e) << "\n" << std::setw(std::distance(b, where)) << '^' << "---- here\n"; return qi::fail; } }; template struct annotation_f { typedef void result_type; annotation_f(It first) : first(first) {} It const first; template void operator()(Val& v, First f, Last l) const { do_annotate(v, f, l, first); } private: void static do_annotate(ast::LocationInfo& li, It f, It l, It first) { using std::distance; li.line = get_line(f); li.column = get_column(first, f); li.length = distance(f, l); } static void do_annotate(...) {} }; template struct ParseGrammar: public qi::grammar { ParseGrammar(Iterator first) : ParseGrammar::base_type(SourceCode), annotate(first) { using namespace qi; KeywordFunction = lit("function"); KeywordVar = lit("var"); SemiColon = lit(';'); Identifier = as_string [ alpha >> *(alnum | char_("_")) ]; VarAssignment = KeywordVar > Identifier > '=' > int_ > SemiColon; // note: expectation points SourceCode = KeywordFunction >> Identifier >> '{' >> *VarAssignment >> '}'; on_error(VarAssignment, handler(_1, _2, _3, _4)); on_error(SourceCode, handler(_1, _2, _3, _4)); auto set_location_info = annotate(_val, _1, _3); on_success(Identifier, set_location_info); on_success(VarAssignment, set_location_info); on_success(SourceCode, set_location_info); BOOST_SPIRIT_DEBUG_NODES((KeywordFunction)(KeywordVar)(SemiColon)(Identifier)(VarAssignment)(SourceCode)) } phx::function handler; phx::function> annotate; qi::rule SourceCode; qi::rule VarAssignment; qi::rule Identifier; // no skipper, no attributes: qi::rule KeywordFunction, KeywordVar, SemiColon; }; int main() { std::string const content = "function FuncName_1 {\n var Var_1 = 3;\n var Var_2 - 4; }"; pos_iterator_t first(content.begin()), iter = first, last(content.end()); ParseGrammar resolver(first); // Our parser ast::SourceCode program; bool ok = phrase_parse(iter, last, resolver, qi::space, program); std::cout << std::boolalpha; std::cout << "ok : " << ok << std::endl; std::cout << "full: " << (iter == last) << std::endl; if(ok && iter == last) { std::cout << "OK: Parsing fully succeeded\n\n"; std::cout << "Function name: " << program.function.name << " (see L" << program.printLoc() << ")\n"; for (auto const& va : program.assignments) std::cout << "variable " << va.id.name << " assigned value " << va.value << " at L" << va.printLoc() << "\n"; } else { int line = get_line(iter); int column = get_column(first, iter); std::cout << "-------------------------\n"; std::cout << "ERROR: Parsing failed or not complete\n"; std::cout << "stopped at: " << line << ":" << column << "\n"; std::cout << "remaining: '" << std::string(iter, last) << "'\n"; std::cout << "-------------------------\n"; } return 0; } 

[1] Lamentablemente un (der) documentado, a excepción de la (s) muestra (s) de evocación

[2] bueno, utilicé as_string para obtener la asignación adecuada al Identifier sin demasiado trabajo

[3] Podría haber formas más inteligentes sobre esto en términos de rendimiento, pero por ahora, seamos sencillos