Was ist das? Ein Parsergenerator erzeugt einen Parser zur grammatikalischen Analyse in einer gewünschten Programmiersprache. Übernimmt Vorverarbeitung, besonders bei LL, LR u.ä. Syntaxanalyse Parsergeneratoren Unterschiede: Verfahren Erzeugte Programmiersprache Allgemeiner Aufbau Ingrid Fischer Lehrstuhl für Programmiersysteme Universität Erlangen-Nürnberg 2 Beispiele LEX / YACC GOLD ANTLR Spirit An YACC: Codeinklusion An GOLD: Tabellen An ANTLR: Auswertung An Spirit: Außergewöhnliche Techniken GOLD Parser Kurzform für: Generalized Object-oriented Language Developer http://www.devincook.com/goldparser/ Mehrere Freizeitentwickler Closed Source Kostenlos Verfügt über Benutzeroberfläche Generischer Parser als Windows OCX/ActiveX verfügbar 3 4 Funktionsweise G.O.L.D. Grammatiken Grammatik mittels normalem Editor oder GOLD GUI beschreiben GOLD Parser prüft die Grammatik und bildet LALR(1) Automat und Tabelle Speichert proprietäres Format Parser analysiert mittels.cgt File einen Text Kann z.b. bei jedem Reduce/Shift benutzerdefinierte Aktion ausführen Source Grammar Outline Gold Parser Compiled Grammar Table DFA & LALR Parser Engine Source text Parsed Data 5 6 1
G.O.L.D. Grammatiken G.O.L.D. Grammatiken 7 8 G.O.L.D. Grammatiken G.O.L.D. Grammatiken EBNF orientiert Definition von Terminalzeichen und Grammatikregeln sind in einem zusammen gefasst Häufig benutzte Terminale bereits eingebaut (z.b.: digit oder letter ) 9 10 Ausschnitte aus der Pascal Definition in G.O.L.D. G.O.L.D. Benutzeroberfläche DecLiteral HexLiteral FloatLiteral <constant> = [123456789]{digit}* = '$'{Hex Digit}+ = {Digit}*.{Digit}+ ::= DecLiteral StringLiteral FloatLiteral HexLiteral CharLiteral <ConstantDef> ::= id '=' <constant> '' <VariableDeclarations> ::= VAR <VarDeclList> <VarDeclList> ::= <VariableDec> <VariableDec> <VarDeclList> <VariableDec> ::= <IdList> ':' <TypeSpecifier> '' Symbol Definitionen Konstanten Definitionen Variablen Definition Normaler Arbeitsablauf: 1. Grammatik editieren 2. Grammatik in das G.O.L.D. Format übersetzen 3. Ggf. Blick auf die LALR Zustände und den DFA werfen 4. Testtext parsen 5. Parsebaum betrachten 11 12 2
Integration Generischer Parser wird eingebunden: Visual Basic : ActiveX / OCX Komponente C / C++ / Java : Open Source Klassen Delphi : VCL Komponente dot net : Source Klasse oder Komponente Wertet Quelltext mittels DFA aus und teilt dem Anwender shift und reduce Operationen mit. Demonstration 13 14 G.O.L.D. - Beurteilung Vorteile Grammatiken leicht les- und schreibbar Besitzt Benutzeroberfläche, die Entwickeln und Testen von Grammatiken sehr angenehm macht LALR Parser erlaubt Linksrekursionen In sich abgeschlossen, leicht zu benutzen und zu integrieren Parser Engine für viele Sprachen verfügbar Nachteile Mäßige Fehlerlokalisierung Quellcode nicht verfügbar Unter Umständen langsamer, da ein generischer Parser verwendet wird. Unmöglich Parser zu debuggen Yacc bzw. gliedert sich, wie jeder Parser in 2 Teile. 1. LEX: Fungiert als Abtaster (Scanner) erkennt bedeutungstragende Einheiten (Token), entfernt überflüssige Zeichen wie Kommentare, Einrückungen etc. 2. YACC: Eigentlicher Zerteiler (Parser), setzt auf Ergebnisse von LEX auf. Erkennt Struktur des Programms, da er mittels Grammatik über den Aufbau und Syntax informiert ist. 15 16 Stephen Johnson: Yet Another Compiler Compiler (1979) Entwickelt bei den Bell Labs von AT&T für Unix Eingabe: EBNF Ausgabe: C Parservariante: LALR Fester Bestandteil von Unix Viele Varianten Berkeley Yacc http://dickey.his.com/byacc/byacc.html GNU bison http://www.gnu.org/software/bison/ MKS yacc http://www.mkssoftware.com/docs/wp/wp_lyuse.asp %{ C-Deklarationen %} yacc-deklarationen %% yacc-regeln (Grammatikteil) %% Benutzerdefinierte Routinen (Hilfsfunktionsteil) z.b. main, yyerror, yylex Zitat: A successful tool is one that was used to do something undreamt of by its author. 17 18 3
%{ #include <ctype.h> #include <stdio.h> %} %token NUMBER %% lines : lines expr '\n' { printf("%d \n", $2) } lines '\n' expr : expr '+' term { $$= $1 + $3 } expr '-' term { $$= $1 - $3 } term term : term '*' factor { $$= $1 * $3 } term '/' factor { $$= $1 / $3 } factor factor : '(' expr ')' { $$=$2 } NUMBER 19 20 21 22 23 24 4
Demonstration 25 26 Beispiel Beispiel 27 28 Beispiel Yacc - Beurteilung Vorteile Lange Entwicklungszeit hinter sich Deshalb hoher Grad an Ausgereiftheit Ausführlichst dokumentiert In sich abgeschlossen, leicht zu benutzen und zu integrieren LALR Parser erlaubt Linksrekursionen Nachteile Sehr schlechte Fehlerlokalisierung im zerteilten Text: Syntax error in line XXX Fehler in Grammatiken unverständlich dargestellt. Erzeugt C code Wegen LALR Tabelle ist es sehr schwierig den Parser zu debuggen 29 30 5
ANTLR Kurz für: ANother Tool for Language Recognition hieß vorher: PCCTS (Purdue Compiler Construction Tool Set) wird seit 1989 von Terence Parr entwickelt und gepflegt Parr ist Professor für Informatik an der Universität San Francisco ANTLR Ursprünglich für/in Java entwickelt. Unterstützt auch C++, C#, Python LL(k) Parser. Mächtiges Werkzeug. Fasst Scanner und Parser zusammen. Zitat: "Why program by hand in five days what you can spend five years of your life automating." 31 32 Taschenrechner - Lexer Beispiel Parser class ExprLexer extends Lexer options { k=2 // needed for newline junk charvocabulary='\u0000'..'\u007f' // allow ascii} Optionen Taschenrechner Grammatik - analysierend class ExprParser extends Parser Parserklasse LPAREN: '(' RPAREN: ')' PLUS : '+' MINUS : '-' STAR : '*' INT : ('0'..'9')+ WS : ( ' ' '\r' '\n' '\n' '\t' ) {$settype(token.skip)} Operatoren Zahl Überflüssige Zeichen expr: mexpr ((PLUS MINUS) mexpr)* mexpr : atom (STAR atom)* atom: INT LPAREN expr RPAREN Ausdruck, schachtelbar mit plus und minus Multiplikation Atomarer Ausdruck: Integer oder Klammerausdruck 33 34 Erkennende Testausgabe Taschenrechner - berechnend class ExprParser extends Parser Testausgaben 3+(4*5) $ 3+(4 line 1:6: expecting RPAREN, found 'null' $ 3++ line 1:3: unexpected token: + $ Grammatik Grammatik: expr: mexpr ((PLUS MINUS) mexpr)* mexpr : atom (STAR atom)* atom: INT LPAREN expr RPAREN expr returns [int value=0] {int x} : value=mexpr ( PLUS x=mexpr {value += x} MINUS x=mexpr {value -= x} )* mexpr returns [int value=0] {int x} : value=atom ( STAR x=atom {value *= x} )* atom returns [int value=0] : i:int {value=integer.parseint(i.gettext())} LPAREN value=expr RPAREN Ausgabe Variablen Deklaration 35 36 6
Erzeugter Code Abstract Syntax Trees public int mexpr() { int value=0 int x // local variable def from rule mexpr value = atom() while ( LA(1)==STAR ) { match(star) x = atom() value *= x} return value} public int atom() { int value=0 switch ( LA(1) ) { // switch on lookahead token type case INT : Token i = LT(1) // make label i point to next lookahead token object match(int) value=integer.parseint(i.gettext()) // compute int value of token break case LPAREN : match(lparen) value = expr() // return whatever expr() computes match(rparen) break default : // error} return value} ANTLR kann selbstständig ASTs erzeugen. Dazu wird die Grammatik geringfügig geändert, um ANTLR anzuzeigen, wo die Wurzeln der Unterbäume liegen sollen (Änderungen sind unterstrichen). class ExprParser extends Parser options { buildast=true } expr: mexpr ((PLUS^ MINUS^) mexpr)* mexpr : atom (STAR^ atom)* atom: INT LPAREN! expr RPAREN! class ExprParser extends Parser expr: mexpr ((PLUS MINUS) mexpr)* mexpr : atom (STAR atom)* atom: INT LPAREN expr RPAREN 37 38 Testausgaben 3+4 ( + 3 4 ) + 3+4*5 ( + 3 ( * 4 5 ) ) 3 4 + Übung! (3+4)*5 ( * ( + 3 4 ) 5 ) * 3 * 4 5 + 5 3 4 39 40 ANTLR - Beurteilung Vorteile Großes Paket, unterstützt eine Vielzahl an Funktionen Für mehrere Sprachen verfügbar (Java, C++, C#) Platformunabhängig Mittlerweile große Gemeinschaft (Google liefert ~ 126000 Treffer) Kann direkt mit ASTs umgehen Spirit Teil der BOOST Bibliothek. (www.boost.org) Boost ist eine C/C++ Bibliothek um das Schreiben von platformunabhängigen Code zu vereinfachen. Verschiedene Autoren: Joel de Guzman oder Martin Wille Nachteile Lange Einarbeitungszeit Fehler in der Grammatik kann u.u. schwer zu finden sein, wenn Fehlermeldung in einer entfernteren Produktion angezeigt wird ANTLR ist schwer mit anderen Komponenten zu verknüpfen (z.b. ANTLR-Parser verwenden aber anderen Scanner) Keine Linksrekursion 41 42 7
Spirit Fakten Ausschließlich für C++ entwickelt. basiert auf generischem Programmieren, das heißt "C++ Templates". Lässt mittels Operatorenoverloading Grammatiken direkt in C++ schreiben. Für kleine bis mittlere Parser gedacht. LL(k) Parser. Beispiel EBNF: group ::= '(' expression ')' factor ::= integer group term ::= factor (('*' factor) ('/' factor))* expression ::= term (('+' term) ('-' term))* Spirit: group = '(' >> expression >> ')' factor = integer group term = factor >> *(('*' >> factor) ('/' >> factor)) expression = term >> *(('+' >> term) ('-' >> term)) Die Ähnlichkeit zwischen den beiden Formen ist sehr leicht erkennbar. Grammatiken können also in Spirit leicht gelesen und geschrieben werden. 43 44 Weiter Beispiele Taschenrechner - analysierend real_p >> *(',' >> real_p) Real_p ist ein Parser, der eine reele Zahl parst. Das Komma ist eine Abkürzung für ch_p(','), was wiederum ein Parser für Kommas ist. Das Beispiel liest also eine beliebig lange, durch Komma getrennte Liste an reellen Zahlen ein. real_p[&f] >> *(',' >> real_p[&f]) Dieses Beispiel funktioniert wie obiges allerdings mit dem Zusatz, dass für jede gelesene reelle Zahl, die Funktion f aufgerufen wird. range_p('a','z') range_p überprüft, ob ein Zeichen innerhalb bestimmter Werte ist. In diesem Beispiel zwischen a und z. struct calculator : public grammar<calculator> { template <typename ScannerT> struct definition { definition(calculator const& self) { expression = term >> *( ('+' >> term)[&do_add] ('-' >> term)[&do_subt] ) term = factor >> *( ('*' >> factor)[&do_mult] ('/' >> factor)[&do_div] ) factor = lexeme_d[(+digit_p)[&do_int]] '(' >> expression >> ') ' ('-' >> factor)[&do_neg] ('+' >> factor) } rule<scannert> expression, term, factor rule<scannert> const& start() const { return expression } } } Funktionsaufruf 45 46 Funktionseinsprünge namespace { void do_int(char const* str, char const* end) { string s(str, end) cout << "PUSH(" << s << ')' << endl } void do_add(char const*, char const*) { cout << "ADD\n" } void do_subt(char const*, char const*) { cout << "SUBTRACT\n" } void do_mult(char const*, char const*) { cout << "MULTIPLY\n" } void do_div(char const*, char const*) { cout << "DIVIDE\n" } void do_neg(char const*, char const*) { cout << "NEGATE\n" } } Zusammenfassung Grammatik wird direkt in den Source eingefügt, der dann mittels generischem Programmieren zur Laufzeit Parser ist. Ausführen einer Methode in einem Template startet den Parse Vorgang. Semantische Aktionen führen zu: Eingabe: (-1 + 2) * (3 + -4) Ausgabe: -1 2 ADD 3-4 ADD MULT 47 48 8
Spirit - Beurteilung Vorteile Sehr handlich und kompakt Entwickelt von anerkannten Profis (<> Boost) Professionelle Programmiermethodik Sehr gut brauchbar um kleine Dinge, wie Email-Adressen oder URLs zu parsen Nachteile Nur für C++ verfügbar Fehler in der Grammatik kann u.u. schwer zu finden sein. Beschränkter Funktionsumfang Kompliziert zu lernen Unter Umständen langsam, da intensives OO Programmieren Keine Linksrekursion 49 9