más locura espiritual: tipos de analizadores (reglas vs int_parser <>) y técnicas de metaprogramación

80

La pregunta está en negrita en la parte inferior, el problema también se resume en el fragmento de código de destilación hacia el final.

Estoy tratando de unificar mi sistema de tipos (el sistema de tipos hace y de un tipo a una cadena) en un solo componente (como lo define Lakos). Estoy usando boost::array, boost::varianty boost::mpl, para lograr esto. Quiero tener las reglas del analizador y del generador para mis tipos unificadas en una variante. hay un tipo indefinido, un tipo int4 (ver más abajo) y un tipo int8. La variante se lee como variant<undefined, int4,int8>.

rasgos int4:

struct rbl_int4_parser_rule_definition
{
  typedef boost::spirit::qi::rule<std::string::iterator, rbl_int4()> rule_type;

  boost::spirit::qi::int_parser<rbl_int4> parser_int32_t;

  rule_type rule;

  rbl_int4_parser_rule_definition()
  {
    rule.name("rbl int4 rule");
    rule = parser_int32_t;  
  }
};

template<>
struct rbl_type_parser_rule<rbl_int4>
{
  typedef rbl_int4_parser_rule_definition string_parser;
};

la variante anterior comienza como indefinida y luego inicializo las reglas. Tuve un problema, que causó 50 páginas de errores, y finalmente logré rastrearlo, los usos de Variant operator=durante la asignación y boost::spirit::qi::int_parser<>no se pueden asignar a otro (operator =).

En contraste, no tengo ningún problema con mi tipo indefinido:

struct rbl_undefined_parser_rule_definition
{
  typedef boost::spirit::qi::rule<std::string::iterator, void()> rule_type;
  rule_type rule;

  rbl_undefined_parser_rule_definition()
  {
    rule.name("undefined parse rule");
    rule = boost::spirit::qi::eps;
  }
};

template<>
struct rbl_type_parser_rule<rbl_undefined>
{
  typedef rbl_undefined_parser_rule_definition string_parser;
};

Destilación del problema:

#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/variant.hpp>
#include <boost/cstdint.hpp>

typedef boost::spirit::qi::rule<std::string::iterator,void()> r1;
typedef boost::spirit::qi::rule<std::string::iterator,int()> r2;

typedef boost::variant<r1,r2> v;

int main()
{
  /*
  problematic
  boost::spirit::qi::int_parser<int32_t> t2;
  boost::spirit::qi::int_parser<int32_t> t1;


  t1 = t2;
  */

  //unproblematic
  r1 r1_;
  r2 r2_;
  r1_ = r2_;

  v v_;
  // THIS is what I need to do.
  v_ = r2();
}

Existe una brecha semántica entre los analizadores concretos y las reglas. Mi cerebro está fumando en este momento, así que no voy a pensar en el pramatismo. Mi pregunta es, ¿cómo soluciono este problema? Puedo pensar en tres enfoques para resolver el problema.

uno: miembros de la función estática:

struct rbl_int4_parser_rule_definition
{
  typedef boost::spirit::qi::rule<std::string::iterator, rbl_int4()> rule_type;

  //boost::spirit::qi::int_parser<rbl_int4> parser_int32_t;

  rule_type rule;

  rbl_int4_parser_rule_definition()
  {
    static boost::spirit::qi::int_parser<rbl_int4> parser_int32_t;

    rule.name("rbl int4 rule");
    rule = parser_int32_t;  
  }
};

¿Supongo que el enfoque uno previene el código seguro para subprocesos? ?

dos: el analizador integral está envuelto en un shared_ptr. Hay dos razones por las que me molesto con TMP para el sistema de mecanografía: 1 eficiencia, 2 preocupaciones de centralización en componentes. el uso de punteros anula la primera razón.

tres: operador = se define como no operativo. La variante garantiza que lhsse construye por defecto antes de la asignación.

Editar: Estoy pensando que la opción 3 tiene más sentido (operador = no es una operación). Una vez que se crea el contenedor de reglas, no cambiará, y solo estoy asignando para forzar el rasgo de regla de un tipo en su desplazamiento.

Hassan Syed
fuente
1
la opción 1 es un subproceso inseguro solo si: parser_int32_ttiene un estado y se toma una referencia. Si es apátrida o se hace una copia, entonces es seguro. Desde la semántica, diría que se hace una copia.
Matthieu M.
Es una preocupación bastante confusa, no puedo estar seguro de que el objeto del analizador no tenga estado. Además, hay semánticas de referencia y concretas con la mecánica de reglas, es decir, una regla puede contener referencias a otras reglas, pero también pueden ser analizadores concretos (creo), y no sé cómo se aplica esta semántica a los analizadores concretos .
Hassan Syed
@MatthieuM: Correcto, se hace una copia a menos que .alias()se use.
ildjarn
@ildjarn pero una regla no es un analizador concreto: D el contenido de una regla es una expresión, el equivalente a un árbol de análisis.
Hassan Syed
1
No puedo evaluar si el # 1 sería seguro para subprocesos o no, pero puedo dar un poco de consejo que es fácil de olvidar. El compilador solo evalúa una asignación estática una vez. Imagínese una pequeña comprobación en el código (si (! Evaluado_yet) evalúe () más noop ()). la primera vez que se llama a cualquier objeto miembro relevante de rbl_int4_parser_rule_definition en cualquier lugar, se construirá esa vez. es casi absolutamente equivalente a usar un singleton global. ¿Podrías usar un singleton global de ese tipo para resolver el mismo problema? (ignorando el orden interno, etc.) si es así, esto debería ser seguro para subprocesos.
std''OrgnlDave

Respuestas:

11

No estoy tan seguro de entender todo el alcance de la pregunta, pero aquí hay algunas sugerencias

  • La línea comentada con se // THIS is what I need to do.compila bien para mí (¿problema resuelto? Supongo que en realidad se refería a asignar un analizador, no una regla).

  • La inicialización de la función local staticse ha definido para que sea segura para subprocesos en el último estándar (C ++ 11). Compruebe el soporte de su compilador para subprocesos C ++ 0x. (Si el inicializador arroja, una pasada de la instrucción de inicialización intentará inicializar nuevamente, por cierto).

  • reglas alias()

    Como se describe en http://boost-spirit.com/home/articles/doc-addendum/faq/#aliases

    Puede crear 'copias lógicas' de reglas sin tener que copiar valores de la protoexpresión. Como dice la pregunta frecuente, esto es principalmente para permitir el enlace diferido

  • El truco de Nabialek puede ser precisamente lo que necesita, básicamente, selecciona un analizador para el análisis posterior.

    one = id;
    two = id >> ',' >> id;
    
    keyword.add
        ("one", &one)
        ("two", &two)
        ;
    
    start = *(keyword[_a = _1] >> lazy(*_a));
    

    En su contexto, podría ver keyworddefinido como

    qi::symbols<char, qi::rule<Iterator>*> keyword;
    

    haciendo todo el trabajo con atributos de acciones semánticas. Alternativamente,

    qi::symbols<char, qi::rule<Iterator, std::variant<std::string,int>() >*> keyword;
    
  • Traiga las reglas del mismo tipo (como se muestra en la línea anterior, básicamente)

    Esta es la parte en la que me confundo: dice que quiere unificar su sistema de tipos. Puede que no sea necesario utilizar analizadores de tipo fuerte (firmas de atributos distintos).

    typedef boost::variant<std::string,int> unified_type;
    typedef qi::rule<std::string::iterator, unified_type() > unified_rule;
    
    unified_rule rstring = +(qi::char_ - '.');
    unified_rule rint    = qi::int_;
    
    unified_rule combine = rstring | rint;
    
sehe
fuente