Boost.Spirit.Qi - Parser bricht einfach ab



  • Halloele,

    Ich hab mir mit boost.spirit einen Parser fuer Expressions gebaut. Folgendes Programm:

    #include <iostream>
    #include <string>
    #include <typeinfo>
    
    #include <boost/spirit/include/qi.hpp>
    
    #include "ExpressionGrammar.hpp"
    
    int main()
    {
    	constexpr char const input[] = "++ blubb /* f(// x)";
    
    	auto begin = input, end = input + sizeof input - 1;
    
    	ExpressionGrammar<char const*> g;
    	Expression* result;
    
    	if(!bsq::phrase_parse(begin, end, g, bsq::blank, result) || begin != end)
    	{
    		std::cerr << "parse error here: " << begin << '\n';
    		return 1;
    	}
    
    	std::cerr << "success\n";
    	std::cout << *result;
    }
    

    Erwartetes Ergebnis: GeneralizedExpression("++", Variable("blubb"), "/", FunctionCall("f", { GeneralizedExpression("//", Variable("x")) }))
    Tatsaechlicher Output: parse error here: blubb /
    f(// x)

    So sieht meine ExpressionGrammar aus:

    template <typename Iter>
    struct ExpressionGrammar : bsq::grammar<Iter, Expression*()>
    {
    	ExpressionGrammar() : ExpressionGrammar::base_type(start)
    	{
    		using namespace bsq;
    		using namespace bp;
    
    		identifier      = alpha >> *(alnum | '_');
    		variable        = identifier[_val = new_<Variable>(_1)];
    		literal         = int_[_val = new_<IntLiteral>(_1)] | double_[_val = new_<DoubleLiteral>(_1)];
    		functionCall    = identifier[_val = new_<FunctionCall>(_1)] >> '(' >> generalizedExpr[push_back(bp::at_c<1>(*_val), _1)] % ',' >> ')';
    		primaryExpr    %= literal | variable | ('(' >> generalizedExpr >> ')') | functionCall;
    		opChar         %= bsq::char_('+') | '-' | '*' | '/' | '%' | '^' | '!' | '$' | '&' | '|' | '=' | '?' | '<' | '>' | '~';
    		op             %= +opChar;
    		generalizedExpr = eps[_val = new_<GeneralizedExpression>()] >> +(op | primaryExpr)[push_back(bp::at_c<0>(*_val), _1)];
    		start          %= generalizedExpr;
    	}
    
    private:
    	bsq::rule<Iter, std::string()> identifier;
    	bsq::rule<Iter, Variable*()> variable;
    	bsq::rule<Iter, Literal*()> literal;
    	bsq::rule<Iter, FunctionCall*()> functionCall;
    	bsq::rule<Iter, Expression*()> primaryExpr;
    	bsq::rule<Iter, char()> opChar;
    	bsq::rule<Iter, std::string()> op;
    	bsq::rule<Iter, GeneralizedExpression*()> generalizedExpr;
    	bsq::rule<Iter, Expression*()> start;
    };
    

    Ich habe schon zuerst geglaubt, die Grammatik sei mehrdeutig aufgrund von identifier und functionCall, aber dem ist offenbar nicht so.

    Hat jemand eine Idee, woran das liegen koennte? Ich hab ja schon damit gekaempft, das kompiliert zu bekommen, aber jetzt weiss ich echt nicht mehr weiter. 😞
    Sollte es notwendig sein, kann ich natuerlich auch noch meine AST Klassen reinstellen.

    Der Kellerautomat



  • primaryExpr := literal 
                      |  variable
                      |  '(' generalizedExpr ')'
                      |  functionCall
    

    variable -> identifier
    functionCall -> identifier

    Die Regeln variable und functionCall haben die gleiche First Menge und sind damit mehrdeutig. Jede versucht ein functionCall ableiten zu wollen, wird eine Produktion mit variable versucht und dann als Fehler abgebrochen.

    Ob die Rekursion mit generalizedExpr klappt, bin ich mit greade nicht sicher.



  • Ich habe versucht, einfach mal functionCall aus der Grammatik rauszunehmen und das Ergebnis ist dasselbe.



  • Kellerautomat schrieb:

    Hat jemand eine Idee, woran das liegen koennte?

    Daran:

    Kellerautomat schrieb:

    Ich hab mir mit boost.spirit einen Parser fuer Expressions gebaut.

    Es ist prinzipiell nicht möglich Boost.Spirit, zu debuggen. Entweder es geht, oder es geht nicht, dann wird aufs neue probiert. Davon abgesehen ist es unmöglich, für den Nutzer ansprechende Fehlermeldungen zu generieren. Ich bin bisher immer besser damit gefahren, meine Parser (für komplexere Grammatiken) selber zu schreiben.

    Aber wenn du schon Boost.Spirit verwendest, solltest du zumindest den Schwächen (Undebuggbarkeit) ins Auge schauen und dich damit abfinden.

    http://boost-spirit.com/home/articles/doc-addendum/best-practices/ schrieb:

    Take things one step at a time. Don’t try to write a grammar that covers all the complexity of your input. Start with the simplest piece of the input and write a parser for that. Gradually add more rules to your grammar as you cover more complexity in the input.



  • Kellerautomat schrieb:

    Ich habe versucht, einfach mal functionCall aus der Grammatik rauszunehmen und das Ergebnis ist dasselbe.

    Ok und was machst du um whitespaces abzufangen? Zwischen "++" und "blubb" ist ein " ". Das "++" scheint Spirit geschluckt zu haben und den Rest der Eingabe als Fehler ausgespuckt.

    Nachtrag:
    Ops den Code hast du programmiert.



  • Zeus schrieb:

    Ok und was machst du um whitespaces abzufangen? Zwischen "++" und "blubb" ist ein " ". Das "++" scheint Spirit geschluckt zu haben und den Rest der Eingabe als Fehler ausgespuckt.

    Dazu uebergebe ich blank an phrase_parse. Der Whitespace nach ++ wurde auch noch geschluckt, das erste "fehlerhafte" Zeichen ist das 'b' von blubb.

    Zeus schrieb:

    Nachtrag:
    Ops den Code hast du programmiert.

    Was meinst du?



  • Okay, ich habe das Problem nun selbst gefunden. Ich muss offenbar bei jeder einzelnen Regel sowie bei der Grammatik einen Skipper angeben. Warum das so ist, ist mir allerdings nicht klar. Meine Grammatik sieht nun so aus:

    identifier     %= alpha >> *(alnum | '_');
    		variable        = identifier[_val = new_<Variable>(_1)];
    		literal         = int_[_val = new_<IntLiteral>(_1)] | double_[_val = new_<DoubleLiteral>(_1)];
    		functionCall    = identifier[_val = new_<FunctionCall>(_1)] >> '(' >> generalizedExpr[push_back(bp::at_c<1>(*_val), _1)] % ',' >> ')';
    		primaryExpr    %= literal | functionCall | variable | ('(' >> generalizedExpr >> ')');
    		opChar         %= char_('+') | char_('-') | char_('*') | char_('/') | char_('%') | char_('^') | char_('!') | char_('$') | char_('&') | char_('|') | char_('=') | char_('?') | char_('<') | char_('>') | char_('~');
    		op             %= +opChar;
    		generalizedExpr = eps[_val = new_<GeneralizedExpression>()] >> +(op | primaryExpr)[push_back(bp::at_c<0>(*_val), _1)];
    		start          %= generalizedExpr;
    

    Warum die ganzen char_? Weil wenn ich char_ ueberall ausser beim ersten Zeichen weglasse, die ganzen anderen Zeichen zwar erkannt werden, aber vom parser nicht zurueckgegeben werden - stattdessen bekomme ich \0. Waere auch dankbar, wenn mir jemand erklaeren koennte warum das so ist und mir evtl einen einfacheren Weg zeigen koennte.

    Den functionCall musste ich uebrigens vor variable schieben, damit er zuerst versucht wird.


Anmelden zum Antworten