Grundlagen der Programmierung Compiler: Parser (5C) Prof. Dr Manfred Schmidt-Schauß Sommersemester 2013 Syntaktische Analyse (Parsen) Gegeben: eine kontextfreie Grammatik G und ein String w. Fragen: (1) gehört w zu L(G)? (2) Welchen Syntaxbaum hat w? (3) Welche Bedeutung hat w? Vorgehen: Konstruiere Herleitungsbaum zu w Grundlagen der Programmierung 2 Parser – 2/53 – Syntaktische Analyse eines Programms Gegeben: Syntax einer Programmiersprache und der Quelltext eines Programms. Fragen: Ist das Programm syntaktisch korrekt? Was soll dieses Programm bewirken? Aufgabe: Ermittle Bedeutung“ des Programms, ” Konstruktionsverfahren für Herleitungsbäume (bzw. Syntaxbäume) Grundlagen der Programmierung 2 Parser – 3/53 – Syntaktische Analyse bzgl einer CFG • Für jede CFG gibt es einen Parse-Algorithmus mit worst case Laufzeit O(n3 ) (n : Anzahl der Eingabesymbole) CYK: Cocke, Younger, Kasami, falls Grammatik in Chomsky-Normalform (Alle Regeln von der Form N → W mit |W | ≤ 2 oder Earley-Algorithmus • CYK benutzt dynamisches Programmieren. erzeugt eine Tabelle: pro Paar (N, w) von Nichtterminal N und Subwort w der Eingabe ein Eintrag True wenn N →∗G w, sonst False Grundlagen der Programmierung 2 Parser – 4/53 – Syntaktische Analyse bzgl einer CFG Praxis: Für jede Programmiersprache gibt es einen Parser, der effizient arbeitet, d.h. in O(n), oder in O(n ∗ log(n)) Grundlagen der Programmierung 2 Parser – 5/53 – Parse-Methoden und Beschränkungen Beschränkung in dieser Vorlesung auf • einfach implementierbare oder effiziente Parser • Nur für eingeschränkte CFGs • Verarbeitung des Zeichenstroms bzw. des Eingabewortes von links nach rechts • evtl. auch mit Vorausschau um einige Zeichen. Grundlagen der Programmierung 2 Parser – 6/53 – Parse-Methoden: Vorgehensweisen: Top-Down: Es wird versucht eine Herleitung vorwärts, vom Startsymbol aus, zu bilden ( forward-chaining“) ” Bottom-Up: Es wird versucht eine Herleitung rückwärts, vom Wort aus, zu bilden ( backward-chaining“). ” Grundlagen der Programmierung 2 Parser – 7/53 – Parse-Methoden: Vorgehensweisen: Weiteres Unterscheidungsmerkmal: R : Konstruktion einer Rechtsherleitung L : Konstruktion einer Linksherleitung Gängige Kombinationsmöglichkeiten: • • Top-Down-Verfahren zur Konstruktion einer Linksherleitung Bottom-Up-Verfahren zur Konstruktion einer Rechtsherleitung Grundlagen der Programmierung 2 Parser – 8/53 – Beispiel S A B ::= ::= ::= AB 0|1 8|9 Frage: Kann 09“ aus dieser Grammatik hergeleitet werden? ” Grundlagen der Programmierung 2 Parser – 9/53 – 09-Beispiel: Top-down: Start mit Startsymbol S Rate die Produktionen; Nutze den zu parsenden String zur Steuerung Bilde Restproblem Ziel: Eingabestring bis zum Ende verarbeiten. Ziel NT-Wort Herleitung 09 S S Das ergibt eine Linksherleitung. Beachte 09“ wird von links nach rechts bearbeitet ” Jedes Eingabezeichen bestimmt eindeutig die Produktion Grundlagen der Programmierung 2 Parser – 10/53 – 09-Beispiel: Top-down: Start mit Startsymbol S Rate die Produktionen; Nutze den zu parsenden String zur Steuerung Bilde Restproblem Ziel: Eingabestring bis zum Ende verarbeiten. Ziel NT-Wort Herleitung 09 S S → 09 AB AB Das ergibt eine Linksherleitung. Beachte 09“ wird von links nach rechts bearbeitet ” Jedes Eingabezeichen bestimmt eindeutig die Produktion Grundlagen der Programmierung 2 Parser – 10/53 – 09-Beispiel: Top-down: Start mit Startsymbol S Rate die Produktionen; Nutze den zu parsenden String zur Steuerung Bilde Restproblem Ziel: Eingabestring bis zum Ende verarbeiten. Ziel NT-Wort Herleitung 09 S S → 09 AB AB → 9 B 0B Das ergibt eine Linksherleitung. Beachte 09“ wird von links nach rechts bearbeitet ” Jedes Eingabezeichen bestimmt eindeutig die Produktion Grundlagen der Programmierung 2 Parser – 10/53 – 09-Beispiel: Top-down: Start mit Startsymbol S Rate die Produktionen; Nutze den zu parsenden String zur Steuerung Bilde Restproblem Ziel: Eingabestring bis zum Ende verarbeiten. Ziel NT-Wort Herleitung 09 S S → 09 AB AB → 9 B 0B ε → 09 Das ergibt eine Linksherleitung. Beachte 09“ wird von links nach rechts bearbeitet ” Jedes Eingabezeichen bestimmt eindeutig die Produktion Grundlagen der Programmierung 2 Parser – 10/53 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den gegebenen String anwenden das Startsymbol der Grammatik ist zu erreichen 09 Eine Rechtsherleitung wurde konstruiert Beachte: Manchmal sind mehrere Regeln anwendbar zudem muss man i.a. den Teilstring raten, auf den eine Produktion (rückwärts) anzuwenden ist Im Beispiel: Gleicher Herleitungsbaum S A 1 Grundlagen der Programmierung 2 Parser B 2 – 11/53 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den gegebenen String anwenden das Startsymbol der Grammatik ist zu erreichen 09 ← A9 Eine Rechtsherleitung wurde konstruiert Beachte: Manchmal sind mehrere Regeln anwendbar zudem muss man i.a. den Teilstring raten, auf den eine Produktion (rückwärts) anzuwenden ist Im Beispiel: Gleicher Herleitungsbaum S A 1 Grundlagen der Programmierung 2 Parser B 2 – 11/53 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den gegebenen String anwenden das Startsymbol der Grammatik ist zu erreichen 09 ← A9 ← AB Eine Rechtsherleitung wurde konstruiert Beachte: Manchmal sind mehrere Regeln anwendbar zudem muss man i.a. den Teilstring raten, auf den eine Produktion (rückwärts) anzuwenden ist Im Beispiel: Gleicher Herleitungsbaum S A 1 Grundlagen der Programmierung 2 Parser B 2 – 11/53 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den gegebenen String anwenden das Startsymbol der Grammatik ist zu erreichen 09 ← A9 ← AB ← S Eine Rechtsherleitung wurde konstruiert Beachte: Manchmal sind mehrere Regeln anwendbar zudem muss man i.a. den Teilstring raten, auf den eine Produktion (rückwärts) anzuwenden ist Im Beispiel: Gleicher Herleitungsbaum S A 1 Grundlagen der Programmierung 2 Parser B 2 – 11/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 NT-Wort S Herleitung S 002“ kann nur aus B hergeleitet werden: ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 NT-Wort S A Herleitung S A 002“ kann nur aus B hergeleitet werden: ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 NT-Wort S A A Herleitung S A 0A 002“ kann nur aus B hergeleitet werden: ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 2 NT-Wort S A A A Herleitung S A 0A 00A 002“ kann nur aus B hergeleitet werden: ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 2 NT-Wort S A A A Herleitung S A 0A 00A ? 002“ kann nur aus B hergeleitet werden: ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 2 NT-Wort S A A A Herleitung S A 0A 00A 002“ kann nur aus ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser ? B hergeleitet werden: 002 B B – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 2 NT-Wort S A A A Herleitung S A 0A 00A 002“ kann nur aus ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser ? B hergeleitet werden: 002 B B 02 B 0B – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 2 NT-Wort S A A A Herleitung S A 0A 00A 002“ kann nur aus ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser ? B hergeleitet werden: 002 B B 02 B 0B 2 B 00B – 12/53 – Beispiel: Suche nach der Herleitung S A B ::= ::= ::= A |B 0A | 1 0B | 2 Kann 002“ hergeleitet werden? ” Ziel 002 002 02 2 NT-Wort S A A A Herleitung S A 0A 00A ? 002“ kann nur aus ” Ziel 002 NT-Wort S Herleitung S 002 Grundlagen der Programmierung 2 Parser B hergeleitet werden: 002 B B 02 B 0B 2 B 00B – 12/53 – Beispiel: Bemerkungen Ein deterministischer Top-Down-Parser muss beim ersten Zeichen von 002“ entscheiden, ” ob A, oder B. Diese Wahl kann falsch sein. Misslingt eine Herleitung, so muss der Parser zurücksetzen: Backtracking“ ” Grundlagen der Programmierung 2 Parser – 13/53 – Parsemethoden Wir betrachten im folgenden: rekursive absteigende Parser: Allgemeine optimierte: rekursive-prädiktive Parser (LL-Parser) Bottom-Up-Parser (LR-Parser) Grundlagen der Programmierung 2 Parser – 14/53 – Rekursiv absteigende Parser Rekursiv absteigender Parser / Syntaxanalyse ist an der Form der Regeln der Grammatik orientiert. Methode: Top-Down-Prüfung der Anwendbarkeit der Regeln Grundlagen der Programmierung 2 Parser – 15/53 – Struktur eines rekursiv absteigenden Parsers • Top-Down bzgl. der Grammatik. • Eingabewort von links nach rechts • Backtracking, falls Sackgasse • Konstruktion einer Linksherleitung Grundlagen der Programmierung 2 Parser – 16/53 – Struktur eines rekursiv absteigenden Parsers • Pro Nichtterminal N wird ein Parser PN programmiert. Eingabe: String (bzw. Tokenstrom) Ausgabe: Syntaxbaum zum Prefix der Eingabe; und Reststring • N → w1 | . . . | wn (das sind alle Regeln zu N ) PN probiert alle wi aus • Prüfung, ob ein wi passt: wi = wi1 wi2 . . . wim von links nach rechts durchgehen Jeweils Parser Pwij aufrufen und Reststring weitergeben I.a. rekursiver Aufruf, falls wij Nichtterminal. Grundlagen der Programmierung 2 Parser – 17/53 – Eigenschaften: rekursiv-absteigender Parser • Liefert alle Linksherleitungen für alle Präfixe des Tokenstroms (wenn der Parser terminiert) • Leicht implementierbar • Leicht erweiterbar auf weitere Einschränkungen • I.a. exponentiell oder sogar: • Terminiert nicht für bestimmte (linksrekursive) Grammatiken, obwohl eine Herleitung existiert: Beispiel A ::= A+A | A-A | 1 | . . . | 9 Eingabe: 1+1 : Aber: nur die erste Regel wird (jeweils rekursiv) versucht: (A,1+1) → (A+A,1+1) → ((A+A)+A, 1+1) → . . . Grundlagen der Programmierung 2 Parser – 18/53 – Rekursiv-absteigende Parser Programme von Programmiersprachen kann man i.a. in O(n) oder O(n ∗ log(n)) parsen, Effiziente rekursiv-absteigende Parser benötigen i.a.: • Erweiterungen wie Vorausschau • Umbau der Grammatik (Optimierung der Grammatik) Grundlagen der Programmierung 2 Parser – 19/53 – Funktionale Kombinator-Parser Programmierung Grundlagen der Programmierung 2 Parser – 20/53 – Funktionale Kombinator-Parser Implementierung von rekursiv-absteigenden Parsern in Haskell Vorteile • relativ leicht verständliche Programmierung • 1-1-Übersetzung der Regeln in Programmcode • Nach Erweiterung und Optimierung kann der Parser Fehler gut erkennen und deterministisch werden. Pro Nichtterminal N eine Funktion parserN:: String -> [(String, Syntaxbaum)] bzw. parserN:: [Token] -> [([Token], Syntaxbaum)] Präfix der Eingabe 7→ (Rest der Eingabe, Resultat (z.B. Syntaxbaum) ) ..... Liste aller Möglichkeiten Grundlagen der Programmierung 2 Parser – 21/53 – Funktionale Kombinator-Parser Um Backtracking zu implementieren: Liste von erfolgreichen Ergebnissen verzögerte Auswertung ergibt richtige Reihenfolge der Abarbeitung. Grundlagen der Programmierung 2 Parser – 22/53 – Haskell-Implementierung der Parser-Kombinatoren Kombinator (kombiniert Parser) Z.B. Alternative, Sequenz, Resultat-Umbau module CombParser where --- bzw. CombParserWithError import Char infixr 6 <*>, <*, *> infixr 4 <|>, <!> infixl 5 <@ type Parser a b = [a] -> [([a],b)] erkennt ein Zeichen: symbol :: Eq s => s -> Parser s s symbol a [] = [] symbol a (x:xs) | a ==x = [(xs,x)] | otherwise = [] Grundlagen der Programmierung 2 Parser – 23/53 – Haskell: Parser-Kombinatoren (2) erkennt einen String: token :: Eq s => [s] -> Parser s [s] -- token :: Eq s => [s] -> Parser s [s] token k xs | k == (take n xs) = [(drop n xs, k)] | otherwise = [] where n = length k testet ein Zeichen der Eingabe: satisfy :: (s -> Bool) -> Parser s s satisfy p [] = [] satisfy p (x:xs) = [(xs,x) | p x] epsilon :: Parser s () epsilon xs = [(xs,())] Grundlagen der Programmierung 2 Parser – 24/53 – Haskell: Parser-Kombinatoren (3) immer erfolgreich: succeed :: r -> Parser s r succeed v xs = [(xs,v)] immer fehlschlagend: pfail :: Parser s r pfail xs = [] Grundlagen der Programmierung 2 Parser – 25/53 – Haskell: Parser-Kombinatoren (4) Sequenzkombinator : (<*>) :: Parser s a -> Parser s b -> Parser s (a,b) (p1 <*> p2) xs = [(xs2, (v1,v2)) | (xs1,v1) <- p1 xs, (xs2,v2) <- p2 xs1] xs: xs2 p1 p2 | • • • {z } p1 <*> p2 p1 parst den Anfang der Eingabe; gibt den Reststring xs1 weiter an p2 p2 parst danach den Anfang des Reststrings gibt den Reststring zurück Gesamtresultat = Tupel aus den zwei Resultaten Grundlagen der Programmierung 2 Parser – 26/53 – Haskell: Parser-Kombinatoren (4b) Alternativkombinator : (<|>) :: Parser s a -> Parser s a -> Parser s a (p1 <|> p2) xs = p1 xs ++ p2 xs Es werden beide Parser p1 und p2 auf die gleiche Eingaben angewendet Alternativkombinator-2: nur das erste Ergebnis: (<!>) :: Parser s a -> Parser s a -> Parser s a (p1 <!> p2) xs = take 1 (p1 xs ++ p2 xs) Grundlagen der Programmierung 2 Parser – 27/53 – Haskell: Parser-Kombinatoren (6) Operation auf dem Ergebnis des Parse : (<@) :: Parser s a -> (a-> b) -> Parser s b (p <@ f) xs = [(ys, f v) | (ys,v) <- p xs] ignoriert rechtes Ergebnis: (<*) :: Parser s a -> Parser s b -> Parser s a p <* q = p <*> q <@ fst ignoriert linkes Ergebnis: (*>) :: Parser s a -> Parser s b -> Parser s b p *> q = p <*> q <@ snd Grundlagen der Programmierung 2 Parser – 28/53 – Funktionale Kombinator-Parser Aufgaben der Kombinatoren 1 Lesen und Prüfen, 2 Kombinatoren zum Aufbau des Syntaxbaums Beispiel p <* q = p <*> q <@ fst <*> ist ein Kombinator zum Lesen und Prüfen. <@ bewirkt die Nachbearbeitung. Grundlagen der Programmierung 2 Parser – 29/53 – Haskell: Parser-Kombinatoren (7) erkennt Folge. d.h. entspricht *: many :: Parser s a -> Parser s [a] many p = p <*> many p <@ list <|> succeed [] many1 p = p <*> many p <@ list digit :: Parser Char Int digit = satisfy isDigit <@ f where f c = ord c - ord ’0’ erkennt Zahl: natural :: Parser Char Int natural = many1 digit <@ foldl f 0 where f a b = a*10 + b Grundlagen der Programmierung 2 Parser – 30/53 – Haskell: Parser-Kombinatoren (8) Nimmt nur die erste (maximale) Alternative des many: nur erlaubt, wenn der Parser die weggelassenen Alternativen nicht benötigt manyex :: Parser s a -> Parser s [a] manyex p = p <*> many p <@ list <!> succeed [] many1ex p = p <*> manyex p <@ list option p = p <@ (\x->[x]) <!> epsilon <@ (\x-> []) Nimmt nur die erste (maximale) Alternative bei Zahlen: naturalex :: Parser Char Int naturalex = many1ex digit <@ foldl f 0 where f a b = a*10 + b Grundlagen der Programmierung 2 Parser – 31/53 – Haskell: Parser-Kombinatoren (9) Erkennt Klammerung; Klammern kommen nicht in den Syntaxbaum: pack:: Parser s a -> Parser s b -> Parser s c -> Parser s b pack s1 p s2 = s1 *> p <* s2 Erkennt Infix-Folge wie z.B. (1+2+3+4+5): Liste der Argumente: opSeqInf psymb parg = (parg <*> many (psymb *> parg)) <@ list Grundlagen der Programmierung 2 Parser – 32/53 – Einfaches Beispiel Grammatik-Regeln: Grundlagen der Programmierung 2 Parser S → AB A→a B→b – 33/53 – Einfaches Beispiel S → AB A→a B→b parse S = parse A <*> parse B parse A = (symbol ’a’) parse B = (symbol ’b’) Grammatik-Regeln: Programm: Grundlagen der Programmierung 2 Parser – 33/53 – Leicht komplexeres Beispiel Grammatik-Regeln: Grundlagen der Programmierung 2 Parser S → AB A → aA B → bB – 34/53 – Leicht komplexeres Beispiel S → AB A → aA B → bB parse S = parse A <*> parse B parse A = (symbol ’a’) <*> parse A parse B = (symbol ’b’) <*> parse B Grammatik-Regeln: Programm: Grundlagen der Programmierung 2 Parser – 34/53 – Leicht komplexeres Beispiel S → AB A → aA B → bB parse S = parse A <*> parse B parse A = (symbol ’a’) <*> parse A parse B = (symbol ’b’) <*> parse B Grammatik-Regeln: Programm: Typgerecht programmieren mit Syntaxbaum-Erzeugung: parse S = parse A <*> parse B <@ (\(x,y) -> [x,y]) parse A = many (symbol ’a’) parse B = many (symbol ’b’) Grundlagen der Programmierung 2 Parser – 34/53 – Beispiel: Polymorphe Typ-Ausdrücke Grammatik AT ::= AT -> AT | (AT) | [AT] | Var | TCA TCA ::= Grundlagen der Programmierung 2 Parser TC | (TC AT . . . AT) | (AT1 ,. . . ,ATn ), n > 1 – 35/53 – Beispiel: Polymorphe Typ-Ausdrücke Grammatik AT ::= AT -> AT | (AT) | [AT] | Var | TCA TCA ::= TC | (TC AT . . . AT) | (AT1 ,. . . ,ATn ), n > 1 Grammatik ist linksrekursiv! Grundlagen der Programmierung 2 Parser – 35/53 – Beispiel: Polymorphe Typ-Ausdrücke umgebaute Grammatik; nicht linksrekursiv und optimiert für den Parser AT NOARNX NOAR TCT KLRUND KLECK Grundlagen der Programmierung 2 Parser ::= ::= ::= ::= ::= ::= NOAR { NOARNX | ε } -> AT Var | TCT | KLRUND | KLECK TC NOAR . . . NOAR (AT,. . . ,AT) Mindestens 2-Tupel [AT] – 36/53 – Kombinatorparser Mit Fehlerbehandlung Erweiterte Bibliothek mit neuen Kombinatoren ((p1 <*>!) errStr) p2 Ergibt Fehler mit Text errStr Wenn p2 fehlschlägt ((p1 *>!) errStr) p2 Wie <*>! aber nur Ergebnis von p2 ((p1 *<!) errStr) p2 Wie <*>! aber nur Ergebnis von p1 Grundlagen der Programmierung 2 Parser – 37/53 – Kombinatorparser; Beispiele AT NOARNX NOAR TCT KLRUND KLECK ::= ::= ::= ::= ::= ::= NOAR { NOARNX | ε } -> AT Var | TCT | KLRUND | KLECK TC NOAR . . . NOAR (AT,. . . ,AT) Mindestens 2-Tupel [AT] parseKLRUND = (parseSymbol ’(’ *> (parseINKLRUND <*! ") erwartet") (parseSymbol ’)’)) <@ id parseINKLRUND = (parseAT <*> (manyex (((parseSymbol ’,’) *>! "Typ nach , erwartet") parseAT))) <@@ (\(t1,t2) er -> if null t2 then t1 else (Fn ("Tup"++(show ((length t2) +1))) (t1:t2) er)) Grundlagen der Programmierung 2 Parser – 38/53 – Kombinatorparser mit Fehlerbehandlung Programme und Vorführung typeUnifErr combParserWithError parseEquation parseAT . prelex printUnif "(a,a) = (b,[b])“ Grundlagen der Programmierung 2 Parser – 39/53 – Kombinatorparser mit Fehlerbehandlung Programme und Vorführung html-parser.hs main prelex (linPosNumbering "<D> xxx </D>\n<br> text </br>") Grundlagen der Programmierung 2 Parser – 40/53 – Fehler-Meldungen: Bemerkungen Die Fehlererkennung und -meldung sollte spezifisch sein und möglichst genau die Ursache und Stelle melden. Schlecht: Gut Keine Alternativen mehr gefunden in Zeile... “ ” Fehler in Zeile ... Spalte... Möglicher Grund: ... “ ” Bei deterministischen Parsern (und Kombinatorparser mit Fehlerbehandlung) Die Fehlerstelle ist klar; die Fehlerursache ist auch meist spezifisch genug Bei Parsern mit Backtracking und ohne Fehlerbehandlung Der richtige Fehlerstelle ist meist unklar Der Backtracking-Parser kann meist nur melden: keine Alternativen mehr Grundlagen der Programmierung 2 Parser – 41/53 – Rekursiv-prädiktive Parser Optimierte rekursiv absteigende Parser für eingeschränkte Grammatiken ( LL(1) ). Eigenschaften: • Die anzuwendende Produktion ist immer eindeutig festgelegt abhängig vom aktuellen Nichtterminal und dem nächsten Symbol (Lookahead-Symbol) der Resteingabe • kein Zurücksetzen notwendig, • deterministische Abarbeitung der Eingabe von links nach rechts Aber: man kann nicht für jede eindeutige kontextfreie Grammatik einen rekursiv-prädiktiven Parser konstruieren. Grundlagen der Programmierung 2 Parser – 42/53 – Rekursiv-prädiktive Parser Zweidimensionale Tabelle: (Lookahead-Symbol, Nichtterminal) ⇒ 7→ Regel oder Fehlereintrag Tabellengesteuerter rekursiv-prädiktiver Parser: Grundlagen der Programmierung 2 Parser – 43/53 – Rekursiv-prädiktive Parser Eindeutigkeitsbedingung: Wenn A → w1 | . . . | wn alle Regeln zu A sind: Falls Parser im Zustand A Für jedes erste Symbol a der Eingabe: nur eine Regel A → wi darf anwendbar sein! Beispiel: Grundlagen der Programmierung 2 Parser A → bCD | aEF | cG | H H → dabc ... – 44/53 – Rekursiv-prädiktive Parser Sonderfall: Es gibt eine Regel A → wi mit wi →∗ ε: Diese wird ausgewählt, wenn: • • • keine passende rechte Seite für das Lookahead-Symbol und das Lookahead-Symbol kann auf A folgen und es gibt nur eine solche Regel für A Grundlagen der Programmierung 2 Parser – 45/53 – Rekursiv-prädiktive Parser, ε-Fall Beispiel: S A H ... B C → AB | AC → bCD | aEF | cG | H → ε → dA → eA Im Zustand A und bei Eingabesymbol d: A → H wird ausgewählt. Grundlagen der Programmierung 2 Parser – 46/53 – FIRST- und FOLLOW-Mengen Wenn Grammatik G gegeben ist: first(A) := Terminal-Symbole die am Anfang eines erkannten A-Wortes stehen können. (auch ε) follow(A) := Terminal-Symbole die auf ein erkanntes A-Wort folgen können. Diese Mengen kann man in allen rekursiv-absteigenden Parsern zur Eindämmung, evtl. zur Vermeidung, von Backtracking verwenden. Grundlagen der Programmierung 2 Parser – 47/53 – Beispiel für first Ex Plus PlusRest SigZ B Z ::= ::= ::= ::= ::= ::= Plus SigZ Plusrest + SigZ PlusRest | ε B|-B Z | ( Ex ) 0 | ... | 9 Man erhält als first-Mengen: Ex Plus Plus Rest SigZ B Z 0,...,9, (,- 0,...,9, (,- +, ε 0,...,9, (,- 0,...,9, ( 0,...,9 Grundlagen der Programmierung 2 Parser – 48/53 – Beispiel für follow : Ex Plus PlusRest SigZ B Z ::= ::= ::= ::= ::= ::= Plus SigZ Plusrest + SigZ PlusRest | ε B|-B Z | ( Ex ) 0 | ... | 9 Man erhält als follow- Mengen: Ex ) Plus ) Grundlagen der Programmierung 2 Parser PlusRest ) SigZ +,) B +,) Z +,) – 49/53 – Beispiel für follow : Ex Plus PlusRest SigZ B Z ::= ::= ::= ::= ::= ::= Plus SigZ Plusrest + SigZ PlusRest | ε B|-B Z | ( Ex ) 0 | ... | 9 Man erhält als follow- Mengen: Ex ) Plus ) PlusRest ) SigZ +,) B +,) Z +,) Grammatik ist LL(1) parsebar, da: First-Mengen zu Regelalternativen passen und first(PlusRest) ∩ follow(PlusRest) = ∅ Grundlagen der Programmierung 2 Parser – 49/53 – Vorgehen des LL(1)-Parsers Bei Symbol a, und aktuellem Nichtterminal A: • Ist a ∈ first(wi ) für eine Regel A ::= wi , dann nehme diese Regel. (ε 6∈ first(wi ) für alle i muss gelten. ) • Ist a 6∈ first(wi ) für alle Regeln A ::= wi , dann gibt es maximal eine Regel A ::= w mit first(w) = ∅ Falls a ∈ follow(A), dann diese Regel. • Wenn auch dann keine passende Alternative existiert, wird mit Fehler abgebrochen. Vorteil: genaue und frühe Fehlererkennung Grundlagen der Programmierung 2 Parser – 50/53 – Beispiel: vereinfachte Grammatik für Ausdrücke Expr Rest Term • • • • • • • ::= ::= ::= Term Rest + Term Rest | − Term Rest | ε 0 | ... | 9 first(Term Rest) = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} first(+ Term Rest) = {+}, first(− Term Rest) = {−} first(Expr ) = first(Term ) = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} first(Rest) = {+, −, ε} follow(Expr) = ∅. follow(Rest) = ∅. follow(Term) = {+, −}. Diese Grammatik hat somit die LL(1)-Eigenschaft. Grundlagen der Programmierung 2 Parser – 51/53 – Beispielparser zur Grammatik Parsebaum: Syntaxbaum: PExp 1 + % | PRest + y & 2 − 1 y PRest 3 % 2 − ~ 3 PLeer Der Parsebaum entspricht der Grammatik, aber noch nicht der gewünschten Struktur des arithmetischen Ausdrucks. Man braucht eine Nachbearbeitung des Parsebaumes. Grundlagen der Programmierung 2 Parser – 52/53 – Prädiktiv vs. Kombinatoren Meistens kann man für Grammatiken die geeignet sind für rekursiv-prädiktive Parser (LL(1)) auch einen deterministischen Kombinator-Parser schreiben. (Nach etwas Analyse und Nachdenken) Dabei ist im Parserprogramm überall der Parserkombinator <|> durch <!> ersetzt. und man kann teilweise die um Fehlermeldungen erweiterten Kombinatoren verwenden D.h der Parser ist frei von Backtracking. Grundlagen der Programmierung 2 Parser – 53/53 –