więcej duchowego szaleństwa - typy parserów (reguły vs int_parser <>) i techniki metaprogramowania

80

Pytanie jest pogrubione u dołu, problem jest również podsumowany fragmentem kodu destylacji pod koniec.

Próbuję ujednolicić mój system typów (system typów robi do i od typu do ciągu) w jeden komponent (zgodnie z definicją Lakos). Używam boost::array, boost::variantoraz boost::mplw celu osiągnięcia tego celu. Chcę, aby reguły parsera i generatora dla moich typów były ujednolicone w wariancie. istnieje niezdefiniowany typ, typ int4 (patrz poniżej) i typ int8. Wariant ma postać variant<undefined, int4,int8>.

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

powyższy wariant zaczyna się jako niezdefiniowany, a następnie inicjuję reguły. Miałem problem, który spowodował 50 stron błędów i w końcu udało mi się go wyśledzić, Variant używa operator=podczas przypisywania, a boost::spirit::qi::int_parser<>nie można przypisać do innego (operator =).

Dla kontrastu, nie mam problemu z moim niezdefiniowanym typem:

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

Destylacja problemu:

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

Istnieje semantyczna luka między konkretnymi parserami a regułami. Mój mózg w tej chwili pali, więc nie zamierzam myśleć o pramatyzmie. Moje pytanie brzmi: jak rozwiązać ten problem? Przychodzą mi do głowy trzy podejścia do rozwiązania problemu.

one: Statyczne składowe funkcji:

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

Wydaje mi się, że pierwsze podejście zapobiega kodowi bezpiecznemu wątkowo? ?

dwa: integralny parser jest opakowany w shared_ptr. Są dwa powody, dla których zawracam sobie głowę TMP dla systemu pisania: 1 wydajność, 2 centralizacja problemów na komponenty. używanie wskaźników obala pierwszy powód.

trzy: operator = jest zdefiniowany jako brak działania. wariant gwarantuje, że lhsjest domyślnie skonstruowany przed przypisaniem.

Edycja: Myślę, że opcja 3 ma największy sens (operator = nie działa). Po utworzeniu kontenera reguł nie zmieni się, a ja przypisuję tylko po to, aby wymusić przesunięcie cechy reguły typu.

Hassan Syed
źródło
1
opcja 1 nie jest bezpieczna dla wątków tylko wtedy, gdy: parser_int32_tma stan i pobierane jest odwołanie. Jeśli jest bezpaństwowcem lub została wykonana kopia, jest to bezpieczne. Z semantyki powiedziałbym, że kopia jest zrobiona.
Matthieu M.
Jest to dość myląca obawa, nie mogę być pewien, że obiekt parsera nie ma stanu. Istnieją również referencje i semantyka konkretna z mechaniką reguł - tj. Reguła może zawierać odwołania do innych reguł, ale mogą one również same być konkretnymi parserami (myślę) i nie wiem, jak te semantyki mają zastosowanie do konkretnych parserów .
Hassan Syed
@MatthieuM: Tak, kopia jest wykonywana, chyba że .alias()jest używana.
ildjarn
@ildjarn, ale reguła nie jest konkretnym parserem: D zawartość reguły jest wyrażeniem, odpowiednikiem drzewa parsowania.
Hassan Syed
1
Nie mogę ocenić, czy # 1 byłby bezpieczny dla wątków, czy nie, ale mogę udzielić uncji rad, o których łatwo zapomnieć. Przypisanie statyczne jest oceniane przez kompilator tylko raz. Wyobraź sobie małe sprawdzenie w kodzie (if (! Assessment_yet) assessment () else noop ()). za pierwszym razem, gdy jakikolwiek obiekt członkowski rbl_int4_parser_rule_definition zostanie wywołany w dowolnym miejscu, zostanie on utworzony tylko raz. jest to prawie absolutnie równoważne użyciu globalnego singletona. czy możesz użyć globalnego singletona tego typu do rozwiązania tego samego problemu? (ignorując kolejność inti. itp.) Jeśli tak, powinno to być bezpieczne dla wątków.
std''OrgnlDave

Odpowiedzi:

11

Nie jestem pewien, czy w pełni zrozumiałem pytanie, ale oto kilka wskazówek

  • Wiersz z komentarzem // THIS is what I need to do.kompiluje się dobrze ze mną (problem rozwiązany? Domyślam się, że chodziło ci o przypisanie parsera, a nie reguły?)

  • Inicjalizacja funkcji lokalnej staticzostała zdefiniowana jako bezpieczna wątkowo w najnowszym standardzie (C ++ 11). Sprawdź, czy Twój kompilator obsługuje wątki C ++ 0x. (Nawiasem mówiąc, jeśli inicjator zgłasza, przekazanie instrukcji inicjalizacji spowoduje ponowną próbę zainicjowania).

  • zasady alias()

    Jak opisano w http://boost-spirit.com/home/articles/doc-addendum/faq/#aliases

    Możesz tworzyć „logiczne kopie” reguł bez konieczności kopiowania wartości-wyrażenia proto. Jak mówi FAQ, ma to głównie na celu umożliwienie leniwego wiązania

  • Nabiałek sztuczka może być dokładnie to, czego potrzebujesz, w zasadzie to leniwie wybiera parser dla późniejszej analizy składniowej

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

    W twoim kontekście mógłbym keywordzdefiniować jako

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

    wykonanie całej pracy z atrybutami z działań semantycznych. Alternatywnie,

    qi::symbols<char, qi::rule<Iterator, std::variant<std::string,int>() >*> keyword;
    
  • Doprowadź reguły do ​​tego samego typu (jak pokazano w poprzednim wierszu, w zasadzie)

    To jest ta część, w której jestem zdezorientowany: mówisz, że chcesz ujednolicić swój system typów. Może nie być potrzeby stosowania silnych parserów (odrębnych podpisów atrybutów).

    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
źródło