mehr Geisteswahnsinn - Parser-Typen (Regeln vs int_parser <>) und Meta-Programmiertechniken

80

Die Frage ist unten fett gedruckt, das Problem wird auch durch das Destillationscodefragment gegen Ende zusammengefasst.

Ich versuche, mein Typsystem (das Typsystem wechselt vom Typ zum String) zu einer einzigen Komponente (wie von Lakos definiert) zu vereinheitlichen. Ich benutze boost::array, boost::variantund boost::mpl, um dies zu erreichen. Ich möchte die Parser- und Generatorregeln für meine Typen in einer Variante vereinheitlichen. Es gibt einen undefinierten Typ, einen int4-Typ (siehe unten) und einen int8-Typ. Die Variante lautet wie folgt variant<undefined, int4,int8>.

int4 Merkmale:

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

Die obige Variante beginnt als undefiniert, und dann initialisiere ich die Regeln. Ich hatte ein Problem, das 50 Seiten Fehler verursachte, und ich habe es endlich geschafft, es aufzuspüren, das Variant operator=während der Zuweisung verwendet und a boost::spirit::qi::int_parser<>kann keinem anderen zugewiesen werden (operator =).

Im Gegensatz dazu habe ich kein Problem mit meinem undefinierten Typ:

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

Destillation des Problems:

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

Es gibt eine semantische Lücke zwischen konkreten Parsern und Regeln. Mein Gehirn raucht im Moment, also werde ich nicht über Pramatismus nachdenken. Meine Frage ist, wie löse ich dieses Problem? Ich kann mir drei Ansätze vorstellen, um das Problem zu lösen.

eins: Statische Funktionsmitglieder:

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

Ich denke, Ansatz eins verhindert Thread-sicheren Code? ?

zweitens: Der integrale Parser wird in einen shared_ptr eingeschlossen. Es gibt zwei Gründe, warum ich mich mit TMP für das Schreibsystem beschäftige: 1 Effizienz, 2 Zentralisierung von Bedenken in Komponenten. Die Verwendung von Zeigern besiegt den ersten Grund.

Drei: Operator = ist als No-Op definiert. Variante garantiert, dass die lhsvor der Zuweisung standardmäßig erstellt wird.

Bearbeiten: Ich denke, Option 3 ist am sinnvollsten (Operator = ist ein No-Op). Sobald der Regelcontainer erstellt wurde, ändert er sich nicht mehr, und ich weise nur zu, das Regelmerkmal eines Typs in seinen Versatz zu zwingen.

Hassan Syed
quelle
1
Option 1 ist nur dann Thread-unsicher, wenn: parser_int32_tStatus hat und eine Referenz verwendet wird. Wenn es zustandslos ist oder eine Kopie erstellt wird, ist es sicher. Aus der Semantik würde ich sagen, dass eine Kopie gemacht wird.
Matthieu M.
Es ist ziemlich verwirrend, ich kann nicht sicher sein, dass das Parser-Objekt keinen Status hat. Es gibt auch Referenz- und konkrete Semantik mit der Regelmechanik - dh eine Regel kann Verweise auf andere Regeln enthalten, aber sie können auch selbst konkrete Parser sein (glaube ich), und ich weiß nicht, wie diese Semantik auf konkrete Parser angewendet wird .
Hassan Syed
@MatthieuM: Richtig, eine Kopie wird erstellt, sofern sie nicht .alias()verwendet wird.
ildjarn
@ildjarn, aber eine Regel ist kein konkreter Parser: D Der Inhalt einer Regel ist ein Ausdruck, der einem Analysebaum entspricht.
Hassan Syed
1
Ich kann nicht beurteilen, ob Nummer 1 threadsicher ist oder nicht, aber ich kann eine Unze Ratschläge geben, die leicht zu vergessen sind. Eine statische Zuordnung wird vom Compiler immer nur einmal ausgewertet. Stellen Sie sich eine kleine Überprüfung im Code vor (if (! Evaluated_yet) evalu () else noop ()). Wenn das relevante Mitgliedsobjekt einer rbl_int4_parser_rule_definition zum ersten Mal irgendwo aufgerufen wird, wird es einmal erstellt. Es ist fast absolut gleichbedeutend mit der Verwendung eines globalen Singletons. Könnten Sie einen globalen Singleton dieses Typs verwenden, um das gleiche Problem zu lösen? (ohne Berücksichtigung der Reihenfolge usw.) Wenn dies der Fall ist, sollte dies threadsicher sein.
std''OrgnlDave

Antworten:

11

Ich bin mir nicht sicher, ob ich das volle Ausmaß der Frage verstehe, aber hier sind ein paar Hinweise

  • Die mit // THIS is what I need to do.Kompilierungen kommentierte Zeile passt gut zu mir (Problem gelöst? Ich vermute, Sie meinten tatsächlich, einen Parser zuzuweisen, keine Regel?)

  • Die Initialisierung von function-local staticwurde im neuesten Standard (C ++ 11) als threadsicher definiert. Überprüfen Sie die Compiler-Unterstützung für C ++ 0x-Threading. (Wenn der Initialisierer auslöst, versucht ein Durchlauf der Initialisierungsanweisung übrigens erneut zu initialisieren).

  • Regeln alias()

    Wie unter http://boost-spirit.com/home/articles/doc-addendum/faq/#aliases beschrieben

    Sie können 'logische Kopien' von Regeln erstellen, ohne den Proto-Ausdruck tatsächlich kopieren zu müssen. Wie in den FAQ angegeben, dient dies hauptsächlich dazu, eine verzögerte Bindung zu ermöglichen

  • Der Nabialek-Trick könnte genau das sein, was Sie brauchen. Im Grunde wählt er träge einen Parser für die nachfolgende Analyse aus

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

    In Ihrem Kontext könnte ich keyworddefiniert sehen als

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

    die ganze Arbeit mit Attributen aus semantischen Aktionen erledigen. Alternative,

    qi::symbols<char, qi::rule<Iterator, std::variant<std::string,int>() >*> keyword;
    
  • Bringen Sie die Regeln unter den gleichen Typ (wie in der vorherigen Zeile gezeigt).

    Dies ist der Teil, in dem ich verwirrt bin: Sie sagen, Sie möchten Ihr Typsystem vereinheitlichen. Möglicherweise sind keine stark typisierten Parser (eindeutige Attributsignaturen) erforderlich.

    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 sehen
quelle