Grundlagen der Programmierung Compiler: Parser (5C) Prof. Dr. Manfred Schmidt-Schauß Sommersemester 2017 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/61 – 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/61 – 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/61 – 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/61 – 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/61 – Parse-Methoden: Vorgehensweisen: Top-Down: Es wird versucht eine Herleitung vorwärts, vom Startsymbol der Grammatik aus, zu bilden ( forward-chaining“) ” Bottom-Up: Es wird versucht eine Herleitung rückwärts, vom Wort aus, zu bilden; bis das Startsymbol der Grammatik erreicht ist. ( backward-chaining“). ” Grundlagen der Programmierung 2 Parser – 7/61 – 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/61 – Beispiel S A B ::= ::= ::= AB 0|1 8|9 Frage: Kann 09“ aus dieser Grammatik hergeleitet werden? ” Grundlagen der Programmierung 2 Parser – 9/61 – 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. Grundlagen der Programmierung 2 Parser – 10/61 – Beispiel S A B ::= ::= ::= AB 0|1 8|9 Eingabe (N + T )∗ -Wort Herleitung Grundlagen der Programmierung 2 Parser 09 S S – 11/61 – Beispiel S A B ::= ::= ::= AB 0|1 8|9 Eingabe (N + T )∗ -Wort Herleitung Grundlagen der Programmierung 2 Parser 09 S S → 09 AB AB – 11/61 – Beispiel S A B ::= ::= ::= AB 0|1 8|9 Eingabe (N + T )∗ -Wort Herleitung Grundlagen der Programmierung 2 Parser 09 S S → 09 AB AB → 9 B 0B – 11/61 – Beispiel S A B ::= ::= ::= AB 0|1 8|9 Eingabe (N + T )∗ -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 – 11/61 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den Eingabestring anwenden; das Startsymbol der Grammatik ist zu erreichen! 09 Grundlagen der Programmierung 2 Parser – 12/61 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den Eingabestring anwenden; das Startsymbol der Grammatik ist zu erreichen! 09 ← A9 Grundlagen der Programmierung 2 Parser – 12/61 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den Eingabestring anwenden; das Startsymbol der Grammatik ist zu erreichen! 09 ← A9 ← AB Grundlagen der Programmierung 2 Parser – 12/61 – 09-Beispiel: Bottom-up: Vorgehen: Regeln rückwärts auf den Eingabestring anwenden; das Startsymbol der Grammatik ist zu erreichen! 09 ← A9 ← AB ← S anders geschrieben: S → AB → A9 → 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 0 Grundlagen der Programmierung 2 Parser B 9 – 12/61 – 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 – 13/61 – 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 – 13/61 – 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 – 13/61 – 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 – 13/61 – 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 ? Sackgasse 002“ kann nur aus B hergeleitet werden: ” Ziel 002 NT-Wort S Herleitung S Grundlagen der Programmierung 2 Parser – 13/61 – 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 ? Sackgasse B hergeleitet werden: 002 B B – 13/61 – 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 ? Sackgasse B hergeleitet werden: 002 B B 02 B 0B – 13/61 – 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 ? Sackgasse B hergeleitet werden: 002 B B 02 B 0B 2 B 00B – 13/61 – 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 Sackgasse B hergeleitet werden: 002 B B 02 B 0B 2 B 00B – 13/61 – Beispiel: Bemerkungen S A B ::= ::= ::= A|B 0A | 1 0B | 2 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 – 14/61 – Parsemethoden Wir betrachten im folgenden: rekursiv absteigende Parser: Allgemeine optimierte: rekursiv-prädiktive Parser (LL-Parser) Bottom-Up-Parser (LR-Parser) Grundlagen der Programmierung 2 Parser – 15/61 – 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/61 – 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/61 – 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 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/61 – 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/61 – Rekursiv-absteigende Parser Programmierung Grundlagen der Programmierung 2 Parser – 20/61 – Funktionale Kombinator-Parser Bausteine 1 Einfache Parser; z.B erkennt ein Zeichen 2 Kombinatoren zum Zusammenbauen von Parsern aus bereits vorhandenen 3 Kombinatoren zum Aufbau der Ausgabe Grundlagen der Programmierung 2 Parser – 21/61 – Funktionale Kombinator-Parser Aufgaben der kombinierten Parser 1 Lesen und Prüfen, 2 Aufbau des Syntaxbaums 3 Berechnung von Ergebnissen Beispiel p <* q = (p <*> q) <@ fst <*> ist ein Kombinator zum Lesen und Prüfen. <@ bewirkt die Nachbearbeitung des Ergebnisses p, q sind Parser (-programme) Grundlagen der Programmierung 2 Parser – 22/61 – Funktionale Kombinator-Parser Implementierung von rekursiv-absteigenden Parsern in Haskell Vorteile • relativ leicht verständliche Programmierung • 1-1-Übersetzung der Regeln in Programmcode Pro Nichtterminal N eine Funktion parserN:: String -> [(String, Syntaxbaum)] Bei Eingabe einer Tokenliste: parserN:: [Token] -> [([Token], Syntaxbaum)] Grundlagen der Programmierung 2 Parser – 23/61 – Funktionale Kombinator-Parser Um Backtracking zu implementieren: Liste von erfolgreichen Ergebnissen verzögerte Auswertung ergibt richtige Reihenfolge der Abarbeitung. kann oft deterministisch gemacht werden Es gibt erweiterte Kombinatoren zur Fehlererkennung und mit guten Fehlerhinweisen Grundlagen der Programmierung 2 Parser – 24/61 – Haskell-Implementierung der Parser-Kombinatoren Kombinator (kombiniert Parser) Z.B. Alternative, Sequenz, Resultat-Umbau module CombParser where 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 – 25/61 – 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 – 26/61 – 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 – 27/61 – 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 – 28/61 – 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 Eingabe angewendet. Alternativkombinator-2: maximal ein Ergebnis: (<!>) :: Parser s a -> Parser s a -> Parser s a (p1 <!> p2) xs = take 1 (p1 xs ++ p2 xs) Grundlagen der Programmierung 2 Parser – 29/61 – 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] f ist der Modifikator des Ergebnisses v des Parsers p. Typischer Fall: p <|> q kann ungetypt sein, wenn p, q verschiedene Ergebnis-Typen liefern. Dann z.B. (p <@ f) <|> (q <@ g) benutzen. Grundlagen der Programmierung 2 Parser – 30/61 – Haskell: Parser-Kombinatoren (6) Kombinatoren, die die Nachverarbeitung miterledigen: 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 – 31/61 – 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 – 32/61 – Haskell: Parser-Kombinatoren (8) Nimmt nur die erste (maximale) Alternative des many; Vorsicht: ist immer erfolgreich!: 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 – 33/61 – 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 Zum Beispiel: pack (symbol ’(’) naturalex (symbol ’)’) "7799" Erkennt Infix-Folge wie z.B. 1+2+3+4+5: Resultat: Liste der Argumente opSeqInf psymb parg = (parg <*> many (psymb *> parg)) <@ list Grundlagen der Programmierung 2 Parser – 34/61 – Einfaches Beispiel Grammatik-Regeln: Grundlagen der Programmierung 2 Parser S → AB A→a B→b – 35/61 – 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 – 35/61 – Leicht komplexeres Beispiel Grammatik-Regeln: S → AB A → aA | a B → bB | b Ein typisches Wort der Sprache: “aaaaaabb” Grundlagen der Programmierung 2 Parser – 36/61 – Leicht komplexeres Beispiel Grammatik-Regeln: S → AB A → aA | a B → bB | b Ein typisches Wort der Sprache: “aaaaaabb” Programm: Grundlagen der Programmierung 2 Parser parseS = parseA <*> parseB parseA = (symbol ’a’) <*> parseA <|> (symbol ’a’) (parseB = (symbol ’b’) <*> parseB) <|> (symbol ’b’) – 36/61 – Leicht komplexeres Beispiel S → AB A → aA | a B → bB | b Typgerecht programmieren mit Modifikatoren und Syntaxbaum-Erzeugung: Grammatik-Regeln: parseS = (parseA <*> parseB) <@ (\(x,y)-> [x,y]) parseA = ((symbol ’a’) <*> parseA) <@ (list) <|> (symbol ’a’) <@ (\x -> (x:[])) parseB = (((symbol ’b’) <*> parseB) <@ (list)) <|> (symbol ’b’) <@ (\x -> (x:[])) list (x,xs) = x:xs Grundlagen der Programmierung 2 Parser – 37/61 – Leicht komplexeres Beispiel Zusätzliche Verwendung des Kombinators many um neue Parser zu generieren: parse S = parseA <*> parseB <@ (\(x,y) -> [x,y]) parseA = many (symbol ’a’) parseB = many (symbol ’b’) oder, noch besser: parse S = parseA <*> parseB <@ (\(x,y) -> [x,y]) parseA = manyex (symbol ’a’) parseB = manyex (symbol ’b’) oder, wenn mindestens ein a bzw b erkannt werden soll parse S = parseA <*> parseB <@ (\(x,y) -> [x,y]) parseA = many1ex (symbol ’a’) parseB = many1ex (symbol ’b’) Grundlagen der Programmierung 2 Parser – 38/61 – Beispiel: Tupel aus Zahlen parsen Erkennen von z.B. (111, 2, 33303198737, 0) klammern = pack (symbol ’(’) kommaListe (symbol ’)’) kommaListe = ((naturalex) <*> paarKommaliste) <@ list paarKommaliste = (manyex ((symbol ’,’) *> naturalex)) Grundlagen der Programmierung 2 Parser – 39/61 – Beispiel: Tupel von Tupeln von Zahlen parsen Erkennen von z.B. ((1, 2, 3), (55, 66, 789), (3303198737, 0)) .... matrixzeile = klammern matrix = pack (symbol ’(’) zeilenListe (symbol ’)’) zeilenListe = ((matrixzeile) <*> paarZeilenliste) <@ list paarZeilenliste = (manyex((symbol ’,’) *> matrixzeile)) Grundlagen der Programmierung 2 Parser – 40/61 – Grammatik und formale Sprachen Grammatiken und Herleitungen Grundlagen der Programmierung 2 Parser – 41/61 – Beispiel: Polymorphe Typ-Ausdrücke Grammatik AT ::= AT -> AT (klammerfreie ->-Typen) | (AT) | [AT] | Var | TCA TCA ::= Grundlagen der Programmierung 2 Parser TC | (TC AT . . . AT) | (AT1 ,. . . ,ATn ), n > 1 – 42/61 – Beispiel: Polymorphe Typ-Ausdrücke Grammatik AT ::= AT -> AT (klammerfreie ->-Typen) | (AT) | [AT] | Var | TCA TCA ::= TC | (TC AT . . . AT) | (AT1 ,. . . ,ATn ), n > 1 Grammatik ist linksrekursiv! Grundlagen der Programmierung 2 Parser – 42/61 – Beispiel: Polymorphe Typ-Ausdrücke Vorgehen zur Elimination der Linksrekursion in AT->AT : mit NOAT-> . . . modellieren, wobei NOAT kein Pfeil-Typ. 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] – 43/61 – Kombinatorparser mit Fehlerbehandlung Erweiterte Bibliothek Kombinatoren zu Fehlererkennung ((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 – 44/61 – 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 – 45/61 – Kombinatorparser mit Fehlerbehandlung Programme und Vorführung html-parser.hs main prelex (linPosNumbering "<DD> xxx </DD>\n<br> text </br>") Grundlagen der Programmierung 2 Parser – 46/61 – 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 – 47/61 – Evaluation nächste Woche Am Anfang der Vorlesung: am 7.6. von 8:00 bis 10:00 http://r.sd.uni-frankfurt.de/1ccbf5eb Grundlagen der Programmierung 2 Parser – 48/61 – Rekursiv-absteigende Parser Rekursiv-prädiktive Parser Grundlagen der Programmierung 2 Parser – 49/61 – 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 – 50/61 – Rekursiv-prädiktive Parser Zweidimensionale Tabelle: (Lookahead-Symbol, Nichtterminal) ⇒ 7→ Regel oder Fehlereintrag Tabellengesteuerter rekursiv-prädiktiver Parser: Grundlagen der Programmierung 2 Parser – 51/61 – Rekursiv-prädiktive Parser Eindeutigkeitsbedingung: Wenn A → w1 | . . . | wn alle Regeln zu A sind: Falls Parser im Zustand A: Für jedes erst Symbol x der Eingabe: nur eine Regel A → wi darf anwendbar sein! Beispiel: Grundlagen der Programmierung 2 Parser A → bCD | aEF | cG | H H → dabc ... – 52/61 – 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 – 53/61 – 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 – 54/61 – FIRST- und FOLLOW-Mengen Vorberechnung zur Steuerung des Parsers. 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 – 55/61 – 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 – 56/61 – 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 +,) – 57/61 – 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 – 57/61 – 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 – 58/61 – 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) = {+, −}. U.a. ist follow(Rest) = ∅. Diese Grammatik hat die LL(1)-Eigenschaft. Grundlagen der Programmierung 2 Parser – 59/61 – Beispielparser zur Grammatik Parsebaum: Syntaxbaum: 1+2-3 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, um den Syntaxbaum zu erstellen! Grundlagen der Programmierung 2 Parser – 60/61 – 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 es ist frei von Backtracking. Grundlagen der Programmierung 2 Parser – 61/61 –