Boost-Geist semantische Aktionsparameter
-
28-09-2019 - |
Frage
in diesem Artikel über boost Geist es semantische Aktionen, die erwähnt wird,
Es gibt 2 tatsächlich mehr Argumente wird übergeben: der Parser Kontext und ein Verweis auf eine Boolesche ‚Treffer‘ Parameter. Der Parser Kontext ist nur dann sinnvoll, wenn die semantische Aktion wird irgendwo auf der rechten Seite angebracht Seite einer Regel. Wir werden sehen, mehr Informationen über diese kurz. Das boolean Wert auf false gesetzt werden innerhalb der semantischen Aktion Ungültigmachungseinträge das Spiel in retrospektiven, so dass die Parser fehlschlagen.
Alle in Ordnung, aber ich habe versucht, ein Beispiel Leiten eines Funktionsobjekt als semantische Aktion zu finden, die die anderen Parameter (Parser Kontext und Hit boolean) verwendet, aber ich gefunden habe, nicht. Ich würde gerne ein Beispiel mit regulären Funktionen oder Funktionsobjekte zu sehen, wie ich kaum den Phönix Voodoo grok kann
Lösung
Dies ist eine wirklich gute Frage (und auch eine Dose Würmer), weil es an der Schnittstelle von Qi und Phoenix wird. Ich habe nicht ein Beispiel entweder gesehen, so dass ich den Artikel ein wenig in dieser Richtung erstrecken würde.
Wie Sie sagen, Funktionen für semantische Aktionen kann bis zu drei Parametern übernehmen
- Matched Attribut - in dem Artikel bedeckt
- Kontext - enthält die Qi-Phönix-Schnittstelle
- Match flag - manipulieren das Spiel Zustand
Match-Flag
Wie die Artikel heißt es, ist der zweite Parameter nicht sinnvoll, wenn der Ausdruck Teil einer Regel ist, können so mit dem dritten starten. Ein Platzhalter für den zweiten Parameter ist nach wie vor, obwohl und für diese Verwendung boost::fusion::unused_type
benötigt. So eine modifizierte Funktion aus dem Artikel den dritten Parameter zu verwenden:
#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;
}
die Ausgänge:
matched integer: '1234' match flag: 1 return: 0
All dieses Beispiel macht, ist das Spiel zu einem nicht-Spiel wechseln, die in der Parser Ausgabe widerspiegeln. Nach hkaiser, im Boost 1,44 und bis das Spiel Flag auf false bewirkt, dass das Spiel in der normalen Art und Weise zum Scheitern verurteilt. Wenn Alternativen definiert sind, wird der Parser wieder ansetzen und versuchen, sie zu passen, wie man erwarten würde. Doch im Boost <= 1,43 ein Geist Bug verhindert Rückzieher, das seltsame Verhalten verursacht. Um dies zu sehen, add phoenix umfassen boost/spirit/include/phoenix.hpp
und ändern Sie den Ausdruck
qi::int_[f] | qi::digit[std::cout << qi::_1 << "\n"]
Sie würden erwarten, dass, wenn die Qi :: int Parser fehlschlägt, die alternative Qi :: Ziffer den Anfang der Eingabe paßt auf „1“, aber der Ausgang ist:
matched integer: '1234' match flag: 1 6 return: 1
Die 6
ist die erste Ziffer des zweiten int im Eingang, der die Alternative anzeigt, getroffen, um den Skipper mit und ohne Rückzieher. Beachten Sie auch, dass das Spiel erfolgreich betrachtet wird, auf der Grundlage der Alternative.
Sobald Boost 1.44 out ist, wird die Übereinstimmungsflagge für die Anwendung Einstimmungskriterien nützlich sein, die sonst schwierig sein könnte, in einem Parser Sequenz zu exprimieren. Beachten Sie, dass das Spiel Flag kann in Phoenix Ausdrücke mit dem _pass
Platzhalter manipuliert werden.
Context Parameter
Je interessanter Parameter ist die zweite, die das Qi-Phönix-Schnittstelle enthält, oder in Qi parlance, der Kontext der semantischen Aktion. Um dies zu verdeutlichen, untersucht zunächst eine Regel:
rule<Iterator, Attribute(Arg1,Arg2,...), qi::locals<Loc1,Loc2,...>, Skipper>
Der Kontextparameter verkörpert das Attribut, arg1, ... argN und Qi :: Einheimische Vorlage paramters, eingewickelt in einem boost :: spirit :: Kontext Vorlagentyp. Dieses Attribut unterscheidet sich von der Funktionsparameter: der Funktionsparameter Attribut ist der geparsten Wert, während dieses Attribut der Wert der Regel selbst ist. Eine semantische Aktion muss die ersteren zum letzteren abzubilden. Hier ist ein Beispiel für einen möglichen Kontexttyp (phoenix Ausdruck Äquivalente angegeben):
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)
>
>
Beachten Sie die Rückkehr Attribut und Argumentliste in Form einer Lisp-Stil-Liste (ein cons Liste ). Um diese Variablen in einer Funktion zuzugreifen, Zugriff auf die attribute
oder locals
Mitglieder der context
struct Vorlage mit Fusion :: at <> (). Zum Beispiel für eine Kontextvariable 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;
den Artikel Beispiel modifizieren das zweite Argument zu verwenden, ändern Sie die Funktionsdefinition und phrase_parse Anrufe:
...
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;
....
Dies ist ein sehr einfaches Beispiel, das nur ordnet den geparsten Wert in den Ausgabeattributwert, aber Erweiterungen sollten ziemlich offensichtlich sein. So stellen Sie die Kontext struct Template-Parameter der Regel Ausgang, Eingang und lokale Typen entsprechen. Beachten Sie, dass diese Art der direkten Übereinstimmung zwischen geparsten Typ / Wert Ausgabetyp / Wert kann automatisch Regeln mit Auto erfolgen, mit einem %=
anstelle eines =
bei der Definition der Regel:
qi::rule<std::string::const_iterator,int(void),ascii::space_type>
intRule %= qi::int_;
IMHO, wäre eine Funktion für jede Aktion Schreiben eher mühsam, im Vergleich zu den kurzen und lesbarem phoenix Ausdruck Äquivalente. Ich sympathisiere mit der Voodoo-Sicht, aber wenn man mit Phoenix für eine kleine Weile arbeiten, die Semantik und Syntax ist nicht sehr schwierig.
Bearbeiten: Zugriff Regel Kontext w / Phoenix
Die Kontextvariable ist nur definiert, wenn der Parser Teil einer Regel ist. Denken Sie an einen Parser als jeder Ausdruck, dass Verbraucht Eingang, wo in der Regel die Parser Werte übersetzt (qi :: _ 1) in der Regel Wert (qi :: _ val). Der Unterschied ist oft nicht trivial, zum Beispiel, wenn das Qi :: val einen Klasse-Typen hat, dass der Bedarf konstruiert wird von POD Werten analysiert. Im Folgenden finden Sie ein einfaches Beispiel.
Lassen Sie uns sagen Teil unserer Eingang ist eine Folge von drei CSV-Zahlen (x1, x2, x3
), und wir kümmern uns nur eine arithmetische Funktion dieser drei Zahlen aus (f = x0 + (x1 + x2) * 3 x), wobei x 0 a Wert an anderer Stelle erhalten. Eine Option ist in den ganzen Zahlen und berechnen Sie die Funktion oder alternativ verwenden phoenix zu lesen, beides zu tun.
Für dieses Beispiel eine Regel mit einem Ausgangsattribut verwenden (der Funktionswert) und Eingang (x0) und einem lokalen (an Informationen zwischen einzelnen Parsern mit der Regel). Hier ist das vollständige Beispiel.
#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;
}
Alternativ kann alle ints als Vektor analysiert werden können, und die Funktion mit einer einzigen semantischen Aktion ausgewertet (die %
unten ist die Liste Operator und Elemente des Vektors sind mit Phoenix zugegriffen :: at):
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
];
....
Für die oben, wenn der Eingang nicht korrekt ist (zwei Ints statt drei), könnten schlechte Sache zur Laufzeit geschehen, so dass es besser wäre, die Anzahl der analysierten Werte explizit angeben, wird so Parsing für eine schlechte Eingabe fehlschlagen . Die folgenden Anwendungen _1
, _2
und _3
den ersten, zweiten und dritten Übereinstimmungswert zu verweisen:
(qi::int_ >> "," >> qi::int_ >> "," >> qi::int_)
[
qi::_val = (qi::_1 + qi::_2) * qi::_3 + qi::_r1
];
Dies ist ein konstruiertes Beispiel, aber sollten Sie die Idee geben. Ich habe phoenix semantische Aktionen wirklich hilfreich direkt bei der Konstruktion komplexe Objekte aus Eingang; Dies ist möglich, weil Sie Konstrukteure und Elementfunktionen innerhalb semantischen Aktionen aufrufen können.