3. Kapitel (Teil 1) SYNTAXANALYSE – TOP-­‐DOWN-­‐ANALYSE Compilerbau Prof. Dr. Wolfgang Schramm Syntak'sche Analyse (Parser) 1/2 1 Aufgaben des Parsers (1) Überprüfung des Programms hinsichtlich seines strukturellen AuAaus und Erzeugung eines Strukturbaums. (2) Bearbeitung (Neueinträge/Ergänzungen) der Symboltabelle. (3) Fehlererkennung / Fehlerbesei'gung. (4) Unterstützung der seman'schen Analyse. Syntak'sche Analyse (Parser) 2/2 2 StrukturQuellprogramm Eintragen Scanner Anforderung Token baum Parser Symbol Attribut Symboltabelle Eintragen/ Nachschlagen Parser – Beispiele zu den Aufgaben 1/2 3 (1) if (a <= 10) max = a; stmt cond_stmt if numexpr id ( boolexpr cop numexpr const ) stmt ; assignment id = expr numexpr id Parser – Beispiele zu den Aufgaben 2/2 4 (2) int a, b, c; In der Deklaration erkennt der Scanner die lexikalischen Elemente int (Token: keyword), a, b und c (Token jeweils: id). Er erkennt aber nicht den Zusammenhang: int ist der Typ von a, b und c. (3) if a <= 10 max = a; Der Parser erkennt einen Fehler und korrigiert ihn zu: if (a <= 10) max = a; (4) if (a <= 10) max = a; Der Parser weiß, dass der Ausdruck a <= 10 vom Typ boolean sein muss. Beschreibung der Syntax von Programmiersprachen 5 Parser Eingabe Ausgabe Syntaxbeschreibung à Kontextfreie Grammatik (in BNF) Grammatik ist Basis für den Parser ... um festzustellen (= analysieren), ob ein gegebenes Wort (= Programm) zur Sprache gehört. Gramma'k (kontexPrei) 1/2 6 Eine Gramma'k zur Beschreibung von Syntax ist ein 4-­‐Tupel: G = (T, N, P, S), mit T: Menge von Token, sog. Terminalsymbole (TS). N: Menge von Nonterminalsymbolen (NTS). P: Menge von Produk'onen (oder Produk'onsregeln), wobei jede Produk'on aus einem Nonterminalsymbol (linke Seite der Produk'on) einem Pfeil (→) und einer Folge von Terminalsymbolen und/oder Nonterminalsymbolen (rechte Seite der Produk'on) besteht. S: Ein ausgezeichnetes Nonterminalsymbol -­‐ das Startsymbol. Gramma'k (kontexPrei) 2/2 7 Die Sprache L(G) einer GrammaMk besteht aus allen aus dem Startsymbol S abgeleiteten Zeichenke[en (Wörtern), die nur Terminalsymbole enthalten. Ein Wort ist eine Folge von Terminalsymbolen, die durch wiederholtes Anwenden von Regeln ( = Subs'tu'on „rechte Seite einer Produk'on ersetzt linke Seite“) erzeugt werden kann, wobei das Startsymbol S der Ausgangspunkt der Erzeugung ist. Es gibt verschiedene Formalismen zur Beschreibung von kontexPreien Gramma'ken: ¤ Backus Naur Form (BNF). ¤ Erweiterte Backus Naur Form (EBNF). ¤ Syntaxdiagramme. Defini'on Ableitung 8 Produk'onen sind Ersetzungsregeln. A → α besagt, dass man ein Aubreten des NTS A innerhalb eines Wortes ϕ durch die Folge von Symbolen α ersetzen darf. Damit verwandelt sich das Ausgangswort ϕ in ein Wort ψ. Sei G = (T, N, P, S) eine kontexPreie Gramma'k. ψ ist aus ϕ direkt ableitbar (Nota'on: ϕ ⇒ ψ), wenn es Worte σ, τ gibt und eine Produk'on A → α , so dass gilt: ϕ = σΑτ und ψ = σατ. Man sagt ψ ist aus ϕ ableitbar (ϕ produziert ψ; Nota'on: ϕ ⇒* ψ), wenn es eine Folge von Worten ϕ1, ϕ2, ..., ϕn (n ≥ 1) gibt, so dass gilt ϕ = ϕ1, ψ = ϕn und ϕi ⇒ ϕi+1, für 1 ≤ i< n. Beispiel folgt! Die Folge von Worten, für die ja gilt ϕ1 ⇒ ϕ2 ⇒ . . . ⇒ ϕn heißt eine Ableitung von ψ aus ϕ in G. Backus-­‐Naur-­‐Form (BNF) 9 o o Einfacher Formalismus für die Syntaxbeschreibung von Kunstsprachen (= Programmiersprachen). Dieser Formalismus hat selbst wiederum eine Syntax – man spricht deshalb von der Metasyntax der BNF: ¤ Ersetzungsregeln in der Form: linke Seite ::= rechte Seite ¤ . Markiert das Regelende ¤ | Alterna've ¤ ( ) Klammerung zusammengehöriger Symbole ¤ < > schließen Nonterminalsymbole ein ¤ Terminalsymbole werden (wegen der besseren Kenntlichkeit) ob in “ “ eingeschlossen oder fe_ gedruckt Backus-­‐Naur-­‐Form (BNF) -­‐ Beispiel 10 o Bezeichner einer Programmiersprache müssen mit einem Buchstaben beginnen, dürfen nach dem ersten Buchstaben aber auch Ziffern enthalten: <Ziffer> ::= 1|2|3|4|5|6|7|8|9|0. <Buchstabe> ::= a|b|c| ... |z. <Zeichenke[e> ::= <Buchstabe> | <Ziffer> | <Buchstabe> <Zeichenke[e> | <Ziffer> <Zeichenke[e>. Rekursion <Bezeichner> ::= <Buchstabe> | <Buchstabe> <Zeichenke[e>. Das lässt sich einfacher mit regulären Ausdrücken beschreiben. Erweiterte Backus-­‐Naur-­‐Form (EBNF) 11 o o o Erweitert die BNF um einige Metasymbole, um die Syntax bequemer bzw. leichter verständlich zu beschreiben. Es gibt viele verschiedene EBNFs – ob gibt es für die Beschreibung der Syntax einer Programmiersprache eine eigene EBNF Bekannteste Erweiterung der Metasyntax der EBNF: ¤ Ersetzungsregeln in der Form: linke Seite → rechte Seite ¤ [ ] op'onale Klammerung ¤ { } Wiederholungsklammerung (0 -­‐ n mal) ¤ { }+ Wiederholungsklammerung (1 -­‐ n mal) -­‐ . Markiert das Regelende -­‐ | Alterna've -­‐ ( ) Klammerung zusammengehöriger Symbole -­‐ < > schließen Nonterminalsymbole ein -­‐ Terminalsymbole werden (der besseren Kenntlichkeit) ob in “ “ eingeschlossen oder fe[ gedruckt Erweiterte Backus-­‐Naur-­‐Form (EBNF) – Beispiel 12 o Bezeichner einer Programmiersprache müssen mit einem Buchstaben beginnen, dürfen nach dem ersten Buchstaben aber auch Ziffern enthalten: <Ziffer> ::= 1|2|3|4|5|6|7|8|9|0. <Buchstabe> ::= a|b|c| ... |z. <Bezeichner> ::= <Buchstabe> {<Buchstabe>| <Ziffer> }. 13 Erweiterte Backus-­‐Naur-­‐Form (EBNF) – komplizierteres Beispiel 1/3 Einfache arithmetische Ausdrücke: Startsymbol expr → term | expr add_op term. term → factor | term mul_op factor. factor → number | id | "(" expr ")". add_op → "+" | "-­‐". mul_op → "*" | "/". number und id seien Terminalsymbole expr ⇒ expr add_op term ⇒ term add_op term ⇒ factor add_op term ⇒ id add_op term ⇒ id + term ⇒ id + term mul_op factor ⇒ id + factor mul_op factor ⇒ id + id mul_op factor ⇒ id + id * factor ⇒ id + id * id. 14 Erweiterte Backus-­‐Naur-­‐Form (EBNF) – komplizierteres Beispiel: Erweiterung 2/3 Einfache arithmetische Ausdrücke (erweitert um Vorzeichen): expr → term | expr add_op term. term → factor | term mul_op factor. factor → [sign] ( number | id | "(" expr ")" ). sign → add_op → "+" | "-­‐". mul_op → "*" | "/". "+" | "-­‐". 15 Erweiterte Backus-­‐Naur-­‐Form (EBNF) – komplizierteres Beispiel: Umformung 3/3 Einfache arithmetische Ausdrücke (umgeformt): expr → term {add_op term}. term → factor {mul_op factor}. factor → [sign] ( number | id | "(" expr ")" ). sign → add_op → "+" | "-­‐". mul_op → "*" | "/". "+" | "-­‐". 16 Erweiterte Backus-­‐Naur-­‐Form (EBNF) –größere Erweiterung Einfache arithmetische Ausdrücke - erweitert um relationale Operatoren und boolesche Operatoren: expr → s_expr | s_expr rel_op s_expr . s_expr → term | s_expr add_op term. term → factor | term mul_op factor. factor → [sign] ( number | id | "(" expr ")" ) | "not" ( id | "(" expr ")" ) . sign → "+" | "-­‐". add_op → "+" | "-­‐" | "or". mul_op → "*" | "/" | "and". rel_op "<" | "<=" | "=" | "!=" | ">=" | ">". → Ableitung aus dem Startsymbol 17 expr ⇒ expr add_op term ⇒ term add_op term ⇒ factor add_op term ⇒ id add_op term ⇒ id + term ⇒ id + term mul_op factor ⇒ id + factor mul_op factor ⇒ id + id mul_op factor ⇒ id + id * factor ⇒ id + id * id. expr expr add_op term term Ableitungsbaum factor id + term factor id mul_op * factor id Defini'on Ableitungsbaum 18 Sei G = (T, N, P, S) eine kontexPreie Gramma'k. Sei B ein Baum, dessen innere Knoten mit NTS und dessen Blä[er mit TS von G oder mit dem leeren Wort ε markiert sind. B heißt Ableitungsbaum (oder Syntaxbaum) für das Wort w ∈ T* und für X ∈ N, falls gilt: 1. Für jeden inneren Knoten p, der mit Y ∈ N markiert ist und dessen Söhne (von links nach rechts) q1 . . . qn mit Q1 . . . Qn ∈ (N ∪ T) markiert sind, gibt es eine Produk'on Y → Q1 . . . Qn in P. Falls p einen einzigen Sohn hat, der mit ε markiert ist, so exis'ert eine Produk'on Y → ε. 2. Die Wurzel des Baumes ist mit X markiert, und die Konkatena'on der Blä[er ergibt w. Defini'on: Links-­‐ und Rechtsableitung 19 Sei ϕ1, . . ., ϕn eine Ableitung mit S = ϕ1 , ϕ = ϕn . ϕ1, . . ., ϕn heißt Linksableitung von ϕ, falls in jedem Schri[ von ϕι nach ϕι+1 in ϕι jeweils das am weitesten links stehende NTS ersetzt wird, also gilt ϕι = wAσ und ϕι+1 = wασ. Analog heißt ϕ1, . . ., ϕn Rechtsableitung von ϕ, falls in jedem Schri[ von ϕι nach ϕι+1 in ϕι jeweils das am weitesten rechts stehende NTS ersetzt wird, das heißt ϕι = σAw und ϕι+1 = σαw. Eine Satzform (das ist jeder Zwischenzustand eines Ableitungsbaums) innerhalb einer Linksableitung (Rechtsableitung) heißt Linkssatzform (Rechtssatzform). Mehrdeu'ge Gramma'ken 1/4 20 Führen verschiedene Ableitungen zum selben Syntaxbaum, dann spielt das wegen derselben Struktur des Wortes w keine Rolle. Ist es aber möglich zu einem Wort w verschiedene Ableitungsbäume anzugeben, dann nennt man die zugrunde liegende Gramma'k mehrdeuMg. Mehrdeu'ge Gramma'k è seman'schen Mehrdeu'gkeiten Mehrdeu'ge Gramma'ken 2/4 21 Beispiel: stmt à if expr then stmt | if expr then stmt else stmt. if . . . then . . . if . . . then . . . else . . . stmt if expr if then expr stmt then stmt else stmt Mehrdeu'ge Gramma'ken 3/4 22 Beispiel: stmt à if expr then stmt | if expr then stmt else stmt. if . . . then . . . if . . . then . . . else . . . stmt if expr then if expr stmt then else stmt stmt Mehrdeu'ge Gramma'ken 4/4 23 Auflösung der Mehrdeu'gkeit è Änderung der Sprache è Änderung der Gramma'k stmt à if expr then stmt endif | if expr then stmt else stmt endif. Sprache Grammatik stmt à matched_stmt | unmatched_stmt. matched_stmt à if expr then matched_stmt else matched_stmt. unmatched_stmt à if expr then matched_stmt else unmatched_stmt | if expr then stmt. Top-­‐Down-­‐Analyse – Allgemeine Strategie 1/2 24 Ziel: Finde eine Linksableitung Startsymbol bereits fertig fehlt noch noch nicht verarbeitete Eingabe bereits verarbeitete Eingabe Am weitesten links vorkommendes NTS A neu Top-­‐Down-­‐Analyse – Allgemeine Strategie 2/2 25 Man baut den Ableitungsbaum von der Wurzel aus auf. Dabei wird der AuAau des Baums (irgendwie) durch Betrachtung der Eingabefolge kontrolliert. Strategie: ¤ ¤ ¤ ¤ Vergleiche die Bla•olge des bisher erzeugten Ableitungsbaums mit der Eingabesymbolfolge, d.h. beide Symbolfolgen werden von links nach rechts gelesen. Solange beide Symbolfolgen Terminalsymbole enthalten, wird weiter gelesen. Enthält die Eingabefolge ein TS und das entsprechende Bla[ des Baums ein NTS, wird eine Produk'on der Gramma'k ausgewählt, die auf dieses NTS anwendbar ist, die Bla•olge des Baums wird dadurch lokal verändert. Werden 2 nicht übereins'mmende TS angetroffen, dann n n war eine vorher ausgewählte Produk'on falsch und muss rückgängig gemacht werden. oder die Eingabefolge ist syntak'sch falsch. Kategorien von Top-­‐Down-­‐Parsern 26 determinis'sch mit Rücksetzungen rekursiver Abstieg nicht-deterministisch ohne Rücksetzungen Tabellenmethode Beispielgramma'k 27 stmt → assignment | cond | loop. (1) assignment → id := expr. (2) cond → if boolexpr then stmt fi | (3) if boolexpr then stmt else stmt fi. loop → while boolexpr do stmt od. (4) expr → boolexpr | numexpr. (5) boolexpr → numexpr cop numexpr . (6) numexpr → id | const . (7) numexpr → numexpr + term | term . (7) term → term * factor | factor. (8) factor → id | const | (numexpr) . (9) Top-­‐Down-­‐Analyse mit Backtracking – Beispiel 1/3 28 stmt Startsymbol Eingabefolge (von Scanner) if id cop const then id := const fi stmt assignment stmt assignment if id cop const then id := const fi id BACKTRACKING := expr if id cop const then id := const fi Top-­‐Down-­‐Analyse mit Backtracking – Beispiel 2/3 29 stmt cond if boolexpr then stmt stmt fi cond if id cop const then id := const fi if boolexpr then numexpr cop numexpr stmt id cond if boolexpr then numexpr cop numexpr id const stmt stmt fi assignment id := if id cop const then id := const fi expr if id cop const then id := const fi fi Top-­‐Down-­‐Analyse mit Backtracking – Beispiel 3/3 30 stmt cond if boolexpr then numexpr cop numexpr id const stmt fi assignment id := expr boolexpr numexpr cop numexpr const BACKTRACKING if id cop const then id := const fi Top-­‐Down-­‐Analyse -­‐ Probleme 31 • Verlaufen in Sackgassen. ⇒ Ineffizienz • Linksrekursive Produktionen (Regeln (7) und (8)). ⇒ Analyse ist nicht möglich! ⇒ Grammatiken so konstruieren bzw. modifizieren, dass beide Probleme nicht auftreten. Linksrekursion 32 • Direkte Linksrekursion: A → Aα • Indirekte Linksrekursion: A ⇒* Aα Eliminierung direkte Linksrekursion Beispiel: A → Aα | β. A A A A A α α α β A‘ β A → βA‘. A‘ → αA‘ | ε. A‘ α A‘ α α A‘ ε linksrekursive Produktionen rechtsrekursive Produktionen Algorithmus zur Besei'gung der Linksrekursion 33 Eingabe: Gramma'k G – ohne Zyklen und ε-­‐Produk'onen. o o Ausgabe: Äquivalente Gramma'k ohne Linksrekursion. 1. Ordne die NTS in der Reihenfolge A1, A2, . . ., An an. 2. for i := 1 to n do for j := 1 to i-­‐1 do { Ersetze jede Produk'on der Form Ai → Ajγ durch die Produk'onen Ai → δ1γ | δ2γ | . . . | δkγ, wobei Aj → δ1 | δ2| . . . | δk alle aktuellen Aj-­‐ Produk'onen sind. } Eliminiere die direkten Linksrekursionen unter den Ai-­‐Produk'onen. Beispielgramma'k (geändert) 34 stmt → assignment | cond | loop. (1) assignment → id := expr. (2) cond → if boolexpr then stmt fi| (3) if boolexpr then stmt else stmt fi. loop → while boolexpr do stmt od. (4) expr → boolexpr| numexpr. (5) boolexpr → numexpr cop numexpr . (6) numexpr → term numexpr‘. (7a) numexpr‘ → + term numexpr‘ | ε . (7b) term → factor term‘. (8a) term‘ → * factor term‘ | ε. (8b) factor → id | const | (numexpr) . (9) Predic've Parsing – vorausschauende Syntaxanalyse 35 Ziel: Vermeiden von Sackgassen und damit von Backtracking. Idee: Durch gemeinsames Betrachten des aktuell zu expandierenden Baumknoten und des aktuellen Zeichens der Eingabefolge, kann man eindeu'g entscheiden welche Alterna've bei mehreren möglichen Produk'onen auszuwählen ist. Voraussetzung: Die Gramma'k muss vom Typ LL(1) sein. Wenn sie das nicht ist, muss sie in LL(1)-­‐Form gebracht werden. LL(k)-­‐Gramma'ken 36 Bei dieser Klasse von Grammatiken kann immer eine eindeutige Entscheidung durch Ansehen der nächsten k Terminalsymbole der Eingabefolge getroffen werden. Definition: Worte, Anfangsstücke von Worten einer Sprache Sei L ⊆ T* eine beliebige Sprache und sei k > 0. Dann ist startk(L) := {w| (w ∈ L und |w| < k) oder (es existiert wu ∈ L und |w| = k)}. Für ein Wort v ∈ T* sei v falls |v| < k startk(v) := u falls u, t existieren mit |u| = k, ut = v LL(k)-­‐Gramma'ken -­‐ Defini'on 37 Definition: LL(k)-Grammatik Eine kontextfreie Grammatik G = (N, T, P, S) heißt LL(k)-Grammatik, wenn gilt: Aus S ⇒* wAσ ⇒ wασ ⇒* wx, S ⇒* wAσ ⇒ wβσ ⇒* wy, und startk(x) = startk(y) S folgt α = β. A Lesen der Eingabe von links nach rechts und berechnen einer Linksableitung unter Vorausschau auf die nächsten k-Zeichen. σ α w x startk(x) Starke LL(k)-­‐Gramma'ken -­‐ Defini'on 38 Defini'on: Starke LL(k)-­‐Gramma'k Eine kontexPreie Gramma'k G = (N, T, P, S) heißt starke LL(k)-­‐ Gramma'k, wenn gilt: Aus S ⇒* w1Aσ1 ⇒ w1ασ1 ⇒* w1x, S ⇒* w2Aσ2 ⇒ w2βσ2 ⇒* w2y, und startk(x) = startk(y) folgt α = β. Hier spielt der Kontext des zu expandierenden NTS A keine Rolle. FIRST-­‐ Menge 39 Die FIRST-­‐Menge einer Zeichenfolge α (α ∈ (N ∪ T)* ) besteht aus allen TS, mit denen Zeichenke[en beginnen können, welche von α abgeleitet werden. Defini'on: FIRST-­‐Menge Sei G = (N, T, P, S) eine kontexPreie Gramma'k, α ∈ (N ∪ T)* und k > 0, dann ist FIRSTk(α) := startk ({w | α ⇒* w}). Die Menge FIRSTk(α) beschreibt also gerade die Anfangsstücke bis zur Länge k von aus α ableitbaren Terminalworten. FOLLOW-­‐ Menge 40 Die FOLLOW-­‐Menge eines NTS A enthält alle TS, die in einer (Links-­‐) Satzform direkt rechts von A stehen können. Defini'on: FOLLOW-­‐Menge Sei G = (N, T, P, S) eine kontexPreie Gramma'k, A ∈ N und k > 0, dann ist FOLLOWk(A) := {w | S ⇒* uAv und w = FIRSTk(v) }. Die Menge FOLLOWk(A) beschreibt also Terminalzeichenfolgen bis zur Länge k, die innerhalb von Ableitungen in G auf das NTS A folgen können. Steuermenge 41 Falls αi die rich'ge Entscheidung ist, dann muss die Folge der nächsten k Zeichen, auf die wir vorausschauen, in der Konkatena'on der Mengen FIRSTk(αi) und FOLLOWk(A) liegen. Defini'on: Steuermenge Sei G = (N, T, P, S) eine kontexPreie Gramma'k, A ∈ N, k > 0, und A→ α1 | α2 | . . . | αn die Menge der A-­‐Produk'onen, dann ist für 1 ≤ i ≤ n die Steuermenge Dk(A→ αi) definiert als Dk(A→ αi) := startk (FIRSTk(αi) . FOLLOWk(A)) . Die Entscheidung unter den αi kann eindeu'g getroffen werden, wenn die Mengen Dk(A→ α1), ..., Dk(A→ αn) alle paarweise disjunkt sind. LL(1)-­‐Gramma'k 42 Eine Gramma'k ist genau dann eine LL(1)-­‐GrammaMk, wenn für jedes NTS A mit A-­‐Produk'onen A→ α1 | α2 | . . . | αn gilt: Die Mengen FIRST1(α1), . . ., FIRST1(αn) sind paarweise disjunkt. Genau eine der Mengen FIRST1(α1), . . ., FIRST1(αn) darf das leere Wort ε enthalten. Wenn ε ∈ FIRST1(αi), dann gilt: FOLLOW1(A) ist disjunkt von allen anderen Mengen FIRST1(αj), i ≠ j. Für k = 1 reduziert sich die Defini'on der Steuermengen zu: D1(A→ αi) := FIRST1(αi), falls ε ∉FIRST1(αi) FIRST1(αi) – {ε } ∪ FOLLOW1(A) sonst Linksfaktorisierung 43 cond → if boolexpr then stmt fi| (3) if boolexpr then stmt else stmt fi. Mit k = 1, d.h. durch Ansehen des ersten Symbols if, ist die richtige Alternative nicht eindeutig zu bestimmen à Verletzung der LL(1)-Eigenschaft. è Grammatik so umschreiben, dass LL(1)-Eigenschaft erfüllt wird. Verschiedene Alternativen einer Produktion haben ein gemeinsames Präfix: A → αβ1 | αβ2 è Linksfaktorisierung A → α A‘ A‘ → β1 | β2 cond → if boolexpr then stmt cond-rest. (3a) cond-rest → fi | else stmt fi. (3b) Berechnung der FIRST-­‐Mengen 44 Berechnung von FIRST(A) für alle Gramma'ksymbole A ∈ NTS ∪ TS. Ini'alisiere FIRST(A) := ∅. Anwendung der folgenden Regeln, solange, bis keine weiteren TS oder ε der Menge hinzugefügt werden können. Wenn A ∈ TS: FIRST (A) := { A }. Wenn A ∈ NTS und A → ε ∈ P: FIRST (A) := FIRST (A) ∪ ε. Wenn A ∈ NTS und A → X1 X2 . . . Xk ∈ P: FIRST (A) := FIRST (A) ∪ FIRST (X1) \ { ε }. ∀ i, 2 ≤ i ≤ k, mit ε ⊆ FIRST(X1), ..., ε ⊆ FIRST(Xi-­‐1): FIRST (A) := FIRST (A) ∪ FIRST (Xi) \ { ε }. Wenn ε ⊆ FIRST(Xj) für j = 1,2, . . ., k: FIRST (A) := FIRST (A) ∪ ε Diese Berechnung erfolgt für jede Alterna've in A → α1|α2| ...| αn. FIRST-­‐Mengen Berechnungsreihenfolge 45 In welcher Reihenfolge berechnet man denn die FIRST-­‐Mengen? 1. 2. Bes'mme Menge Nε von NTS, aus denen ε abgeleitet werden kann: Nε := {X ∈ N | X ⇒* ε}. Zeichne Graph, dessen Knoten NTS sind. Für jede Produk'on A → X1 . . . Xm i. Füge für NTS X1 eine gerichtete Kante (A → X1) ein. ii. Falls X1 ∈ Nε und X2 ∈ N, füge eine gerichtete Kante (A → X2) ein. iii. Weiter so, wenn aufeinander folgende Xi ∈ Nε. Kante A → B bedeutet: berechne FIRST(B) vor FIRST(A). FIRST-­‐Mengen für Beispielgramma'k 1/3 46 Nε = { term‘, numexpr‘ } stmt assignment cond loop cond-rest term‘ expr numexpr‘ boolexpr numexpr term factor FIRST-­‐Mengen für Beispielgramma'k 2/3 47 FIRST-Mengen assignment → id := expr. { id } cond → if boolexpr then stmt cond-rest. { if } loop → while boolexpr do stmt od. stmt → assignment | cond | loop. { id, if, while } factor → id | const | (numexpr) . { id, const, ( } term → factor term‘. { id, const, ( } numexpr → term numexpr‘. { id, const, ( } boolexpr → numexpr cop numexpr . { id, const, ( } expr → boolexpr | numexpr. { id, const, ( } cond-rest → fi | else stmt fi . term‘ → * factor term‘ | ε. { *, ε } numexpr‘ → + term numexpr‘ | ε. { +, ε } { while } { fi, else } FIRST-­‐Mengen für Beispielgramma'k 3/3 48 Beobachtung: Die FIRST-Mengen für die Alternativen boolexpr und numexpr sind nicht disjunkt – LL(1)-Konflikt !!! ⇒ Bei Produktion expr → boolexpr | numexpr kann nicht eindeutig bestimmt werden, welche Alternative ausgewählt werden soll. Ursache: Ein Ausdruck beginnt stets mit einem numerischen Ausdruck, ob er ein boolescher Ausdruck ist, kann erst beim Antreffen eines cop-Symbols entschieden werden. Lösung: Linksfaktorisierung expr → boolexpr | numexpr. (5) expr → numexpr bool-rest. { id, const, ( } (5a) bool-rest → cop numexpr | ε . { cop, e } (5b) Beispielgramma'k (geändert) 49 stmt assignment | cond | loop. (1) assignment → id := expr. (2) cond → if boolexpr then stmt cond-rest. (3a) cond-rest → fi | else stmt fi. (3b) loop → while boolexpr do stmt od. (4) expr → numexpr bool-rest. (5a) bool-rest → cop numexpr | ε. (5b) boolexpr → numexpr cop numexpr . (6) numexpr → term numexpr‘. (7a) numexpr‘ → + term numexpr‘ | ε . (7b) term → factor term‘. (8a) term‘ → * factor term‘ | ε. (8b) factor → id | const | (numexpr) . (9) → Berechnung der FOLLOW-­‐Mengen 50 Berechnung der FOLLOW-­‐Mengen FOLLOW (A) ∀ A ∈ NTS. 1. FOLLOW(S) := { $ }, S = Startsymbol, $ = Endemarkierung der Eingabe. Anwendung der folgenden Regeln, solange, bis keine weiteren TS der Menge hinzugefügt werden können: 2. 3. Für A → αBβ ∈ P mit B ∈ NTS und α, β ∈ NTS ∪ TS mit β ≠ ε : FOLLOW (B) := FOLLOW (B) ∪ FIRST (β) \ { ε }, β ≠ ε. Für A → αBβ oder A → αB, wobei gilt ε ∈ FIRST(β): FOLLOW (B) := FOLLOW (B) ∪ FOLLOW (A ). FOLLOW-­‐Mengen Berechnungsreihenfolge 51 In welcher Reihenfolge berechnet man denn die FOLLOW-­‐Mengen? 1. 2. Zeichne Graph: jedes NTS = ein Knoten. Die Knoten werden markiert (mit TS und $). Markiere Startsymbol S mit $. Betrachte alle Produk'onen aus P. Für jede Produk'on betrachte die NTS auf deren rechter Seite. i. ii. 3. 4. A → αBβ ∈ P, B ∈ NTS, β ≠ ε : -­‐ markiere Knoten B mit allen Symbolen aus FIRST (β) außer ε - ε ∈ FIRST (β): füge Kante A → B ein (falls noch nicht drin in Graphen). A → αB ∈ P, B ∈ NTS, füge Kante A → B ein (analog ε ∈ FIRST (β)) Berechne alle starken Komponenten des Graphen und behandle jede Komponente wie einen einzigen Knoten K; Markierung (K) := ∪ Markierungen all seiner Knoten. FOLLOW (B) := Markierung(B) ∪ Markierungen seiner Vorgänger. FOLLOW-­‐Mengen für Beispielgramma'k 1/2 52 Hinweis: Es sind nicht alle FOLLOWMengen vollständig angegeben. stmt assignment cond loop Abhängigkeiten nach 2i cond-rest Abhängigkeiten nach 2ii Direkte Markierungen (2i) expr bool-rest numexpr then, do od, fi, else, $ cop, ) numexpr‘ term boolexpr * factor Propagierte Markierungen (4) cop, do, then, ), od, fi, else, $ + term‘ cop, do, then, ), od, fi, else, +, $ FOLLOW-­‐Mengen für Beispielgramma'k 2/2 53 FIRST- und FOLLOW-Mengen bool-rest → { od, fi, else, $ } ε. term‘ → → {*} * factor term‘ | { cop, do, then, ), od, fi, else, +, $ } ε. numexpr‘ { cop } cop numexpr | {+} + term numexpr‘ | { cop, do, then, ), od, fi, else, $ } ε. Steuermengen FIRST- und FOLLOW-Mengen bzw. die Steuermengen für die Alternativen sind disjunkt à Grammatik hat LL(1)-Eigenschaft. Zusammenfassendes Beispiel 54 Gegeben ist folgende Gramma'k für arithme'sche Ausdrücke: E → E + T | T. T → T * F | F. F → ( E ) | id. Sorgen Sie dafür, dass die Gramma'k LL(1)-­‐Eigenschab hat und berechnen Sie die FIRST-­‐ und FOLLOW-­‐Mengen. Top-­‐Down-­‐Parser mit Analysetabelle 55 id := id + const $ X Predictive Parser Y Z $ Analysetabelle (M) Ausgabe: Folge von Produktionen, die eine Linksableitung darstellen Parser-­‐Konfigura'on 56 Aktueller Zustand der Analyse: ($αX, xw$) Stackinhalt Eingabe(rest) Top of Stack Startsymbol Aktuelles Eingabesymbol Programm Startkonfiguration des Parsers: ($S, p$) Arbeitsschritte des Parsers: Übergang von Konfiguration K nach K‘: K → K‘ oder K →i K‘ (wenn Übergang mit Produktion i). Endkonfiguration des Parsers: ($, $) – im Erfolgsfall oder ($αX, xw$) – im Fehlerfall Arbeitsweise des Parsers 57 1. 2. 3. 4. 5. ($αX, xw$) mit X ∈T und X = x → ($α, w$) ($αX, xw$) mit X ∈T und X ≠ x → error ($αX, xw$) mit X ∈ N und M(X, x) = i, Pi = X → X1 ... Xm → i ($α Xm ... X1, xw$) ($αX, xw$) mit X ∈ N und M(X, x) = error → Abbruch mit Fehlermeldung ($, $) Stack und Eingabe sind abgearbeitet → Ende mit Erfolgsmeldung „accept“ Konstruk'on der Analysetabelle 58 Ausgangspunkt: Produktionen Für jedes NTS A sei die Menge der A-Produktionen i1 A → α1 | D1 i2 A → α2 | D2 ..... in-1 A → αn-1 | Dn-1 in A → αn Dn M[A, b] = ij falls ∃ j ∈ {1, ..., n}: b ∈ Dj error sonst für alle A ∈ N und b ∈ (T ∪ $) Beispielgramma'k – Nummerierung der Produk'onen und Steuermengen 59 (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) stmt assignment cond cond-rest loop expr bool-rest boolexpr numexpr numexpr‘ term term‘ factor → → → → → → → → → → → → → assignment | cond | loop. id := expr. if boolexpr then stmt cond-rest. fi | else stmt fi. while boolexpr do stmt od. numexpr bool-rest. cop numexpr | ε. numexpr cop numexpr . term numexpr‘. + term numexpr‘ | ε. factor term‘. * factor term‘ | ε. id | const | (numexpr) . {id} {if} {while} {id} {if} {fi} {else} {while} {id, const, ( } {cop} {od, fi, else, $} {id, const, ( } {id, const, ( } {+} {cop, do, then, ), od, fi, else, $} {id, const , ( } {*} {cop, do, then, ), od, fi, else, +, $} {id} {const} {(} Analysetabelle für Beispielgramma'k 60 stmt assignment cond cond-rest loop expr bool-rest boolexpr numexpr numexpr' term term' factor id 1 4 := if then else 2 fi while do 3 od cop + * const ( 9 9 ) $ 5 7 6 8 9 11 11 11 10 11 12 13 12 13 15 15 15 15 15 15 14 16 16 18 19 18 18 18 18 18 12 13 18 15 18 18 16 17 20 15 21 Kein Eintrag è error Beispiel für Ablauf Top-­‐Down-­‐Analyse 1/2 61 Eingabe Ausgabe $ stmt id:=c*c+c$ 1 $ assignment id:=c*c+c$ 4 $ expr := id id:=c*c+c$ Stack $ expr := :=c*c+c$ $ expr c*c+c$ 9 $ bool-rest numexpr c*c+c$ 13 $ bool-rest numexpr‘ term c*c+c$ 16 $ bool-rest numexpr‘ term‘ factor c*c+c$ 20 $ bool-rest numexpr‘ term‘ c c*c+c$ $ bool-rest numexpr‘ term‘ *c+c$ $ bool-rest numexpr‘ term‘ factor * *c+c$ $ bool-rest numexpr‘ term‘ factor c+c$ $ bool-rest numexpr‘ term‘ c c+c$ $ bool-rest numexpr‘ term‘ +c$ 17 20 18 Top-­‐Down-­‐Parser mit Analysetabelle 62 Eingabe Ausgabe $ bool-rest numexpr‘ +c$ 14 $ bool-rest numexpr‘ term + +c$ Stack $ bool-rest numexpr‘ term c$ 16 $ bool-rest numexpr‘ term‘ factor c$ 20 $ bool-rest numexpr‘ term‘ c c$ $ bool-rest numexpr‘ term‘ $ 18 $ bool-rest numexpr‘ $ 15 $ bool-rest $ 11 $ $ accept Prinzip des rekursiven Abs'egs (recursive descent) 63 Für jedes NTS N gibt es eine Prozedur, welche testet, ob die nächsten lexikalischen Elemente ein aus N ableitbares Wort bilden. Ausgangspunkt: Menge der A-Produktionen i1 A → α1 | D1 i2 A → α2 | D2 ..... in-1 A → αn-1 | Dn-1 in A → αn Dn procedure A { if symbol ∈ D1 then output(i1); bearbeite(α1); elsif symbol ∈ D2 then output(i2); bearbeite(α2); ....... if symbol ∈ Dn then output(in); bearbeite(αn); else error; fi; } Rekursiver Abs'eg am Beispiel 64 procedure stmt { if symbol = id then output(1); assignment; elsif symbol = if then output(2); cond; elsif symbol = while then output(3); loop; else error; fi; } procedure assignment { if symbol = id then output(4); match(id); match(:=); expr; else error; fi; } Baum der wechselseitigen Prozeduraufrufe spiegelt die Struktur des Ableitungsbaums wider. Scanner-Aufruf procedure match (symboltype t) { if symbol = t then nextsymbol; else error; fi; } Fehlerbehandlung -­‐ Ausgangssitua'on 65 o AusgangssituaMon ¤ Das nächste Token entspricht nicht dem erwarteten Token: n n TS an Top of Stack ≠ Token. NTS A ist Top of Stack und a das nächste Token und Eintrag M[A, a] = error. Fehlerbehandlung – Strategien 1/2 66 o Panic mode ¤ o Überspringen/Verwerfen von Symbolen bei der Eingabe. Wiederherstellung auf Satzebene (phrase-­‐level recovery) ¤ ¤ ¤ ¤ ¤ ¤ Ändern von Symbolen in der Eingabe (lokale Korrektur: Ersetzung eines Präfix der verbleibenden Eingabe durch einen String, der das Fortsetzen der Verarbeitung erlaubt). EnPernen von Symbolen auf dem Stack. Typische Korrekturen: Ersetzen eines Kommas durch ein Semikolon, Löschen eines zusätzlichen oder Einfügen eines Einfügen eines fehlenden Semikolons. Problem: Ersetzungen könnten zu Endlosschleifen führen, wenn man grundsätzlich vor dem aktuellen Symbol etwas einfügt. Vorteil: Kann jeden beliebigen Eingabestring berücksich'gen. Nachteil: Situa'onen, in denen der eigentliche Fehler vor dem Zeitpunkt der Aufdeckung liegt. Fehlerbehandlung – Strategien 2/2 67 o Fehlerproduk'onen ¤ ¤ o Durch Vorhersehen häufig vorkommender Fehler à Gramma'k wird um Produk'onen erweitert, welche die fehlerhaben Konstrukte erzeugen. Kommt der Parser in eine Fehlerproduk'on à Fehler kann exakt beschrieben und vernünbig behoben werden. Globale Korrektur ¤ ¤ ¤ ¤ ¤ Compiler soll bei Erkennen eines nicht korrekten Eingabestrings möglichst wenige Veränderungen vornehmen. Einsatz von Algorithmen zur Auswahl einer möglichst geringen Folge von Änderungen, um die global kostengüns'gste Korrektur zu erreichen. Beispiel: Für fehlerhabe Eingabe x wird ein Parse-­‐Tree für ähnlichen String < gefunden, bei dem zur Umwandlung von x nach y eine möglichst geringe Anzahl von Einfügungen, Löschungen und Ersetzungen erforderlich ist Nachteil: Aufwand (Speicher, Laufzeit) sehr (= unrealis'sch) hoch. Einsatz: Bewertung von Fehlerbehebungstechniken, Finden op'maler Ersetzungsstrings für die Fehlerkorrektur auf Satzebene. Fehlerbehandlung – Panic Mode 68 o Idee ¤ ¤ ¤ o Ziel ¤ ¤ o Überspringe Symbole bei der Eingabe, bis ein Token aus einer ausgewählten Menge von Synchronisierungstoken erscheint. Synchronisierungstoken werden vom Designer des Compilers festgelegt. Synchronisierungstoken sind normalerweise Begrenzer (;, }), die eine eindeu'ge Bedeutung haben. Einfaches, determiniertes Verfahren. Der Parser soll sich nach Erkennen eines Fehlers schnell wieder erholen. Probleme/Schwierigkeit ¤ ¤ Erhebliche Teile des Code werden ohne weitere Fehlerprüfung übersprungen. Effizienz des Verfahrens hängt von der Auswahl der Menge der Synchronisierungstoken ab. n n Auswahl der Token der Synchronisierungsmenge entsprechend der Fehlerwahrscheinlichkeit. Benutzung von Heuris'ken. Panic Mode – Fehlersitua'on 69 o o Oben auf dem Stack liegt das NTS A, nächstes Eingabesymbol = a und M[A, a] = error. Oben auf dem Stack liegt das TS a ≠ b (Token der Eingabe), d.h. a wurde erwartet und b vorgefunden. Panic Mode – Heuris'ken 70 Voraussetzung: Oben auf dem Stack liegt das NTS A. Es ist bei der Erstellung des Ableitungsbaums für A ein Syntaxfehler aufgetreten. 1. sync (A) = FOLLWOW (A) ¤ ¤ ¤ ¤ Es werden solange Token übersprungen, bis man auf ein Element von aus FOLLOW (A) triŠ. Dann nimmt man A von Stack herunter und setzt die Analyse fort. Problem: In Sprachen wie C/Java werden Anweisungen mit Semikolon abgeschlossen. Schlüsselwörter, mit denen Anweisungen beginnen, erscheinen möglicher Weise nicht in der FOLLOW-­‐Menge des NTS, das für einen Ausdruck steht. Beispiel: a = b + c (Semikolon wurde vergessen). In der FOLLOW-­‐Menge von Ausdruck steht dann zwar das Semikolon, nicht aber das Schlüsselwort der nächsten Anweisung (z.B. if). Man würde dann alle Token bis zum nächsten Semikolon überspringen. Das ist zu viel, weil man mit dem nächsten Schlüsselwort forPahren könnte. Beobachtung: Häufig gibt es bei Sprachkonstrukten eine hierarchische Struktur; Ausdrücke erscheinen innerhalb von Anweisungen, Anweisungen innerhalb von Blöcken etc. Panic Mode – Heuris'ken 71 2. sync (A) wird erweitert um Symbole, mit denen Konstrukte der nächst höheren Ebene beginnen. Beispiel: sync (assign) = sync (assing) ∪ FIRST (stmt) 3. Es ist möglich, dass Symbole aus FIRST (A) in synch (A) enthalten sind à es ist möglich, die Syntaxanalyse mit A wiederaufzunehmen, wenn ein Token aus FIRST (A) in der Eingabe aubaucht. 4. Gibt es eine Alterna've A → ε dann nimmt man die ε-­‐Produk'on als Standardfall, enPernt also einfach das NTS A vom Stack, ohne Token aus der Eingabe zu überlesen. Die Entdeckung eines Fehlers kann dadurch verschoben, nicht aber verhindert werden. ⇒ Reduziert die Anzahl der NTS, die bei der Fehlerbehandlung berücksich'gt werden müssen. 5. Oben auf dem Stack liegt das TS a ≠ b (Token der Eingabe), d.h. a wurde erwartet und b vorgefunden. à Fehlermeldung (Token wurde eingefügt) und EnPernen des Token vom Stack. Man verhält sich so, als enthielte die Synchronisa'onsmenge eines Token alle anderen Token. Beispielgramma'k – Nummerierung der Produk'onen und Steuermengen 72 (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20) (21) stmt assignment cond cond-rest loop expr bool-rest boolexpr numexpr numexpr‘ term term‘ factor → → → → → → → → → → → → → assignment | cond | loop. id := expr. if boolexpr then stmt cond-rest. fi | else stmt fi. while boolexpr do stmt od. numexpr bool-rest. cop numexpr | ε. numexpr cop numexpr . term numexpr‘. + term numexpr‘ | ε. factor term‘. * factor term‘ | ε. id | const | (numexpr) . {id} {if} {while} {id} {if} {fi} {else} {while} {id, const, ( } {cop} {od, fi, else, $} {id, const, ( } {id, const, ( } {+} {cop, do, then, ), od, fi, else, $} {id, const , ( } {*} {cop, do, then, ), od, fi, else, +, $} {id} {const} {(} 73 Analysetabelle für Beispielgramma'k – nach Hinzufügen der Synchronisierungstoken stmt assignment cond cond-rest loop expr bool-rest boolexpr numexpr numexpr' term term' factor id 1 4 9 11 12 13 15 16 18 19 := if 2 then else sync sync 5 sync 7 sync sync 11 11 11 11 sync sync sync sync 15 15 15 15 sync sync 18 18 18 18 sync sync fi while do od cop + * const sync 3 sync sync sync sync sync 6 sync sync 8 sync sync sync 9 11 11 11 11 10 11 11 11 sync sync sync sync 12 sync sync sync sync 13 15 15 15 15 15 14 15 15 sync sync sync sync sync 16 18 18 18 18 18 18 17 18 sync sync sync sync sync sync 20 ( ) 9 11 12 13 15 16 18 21 11 sync sync 15 sync 18 sync Kein Eintrag è error Rot: Token aus der Synchronisationsmenge $ sync sync sync sync sync sync 11 sync sync 15 sync 18 sync 74 o Beispiel mit fehlerhaben Eingabe, um Error-­‐Handling zu zeigen. Wiederherstellung auf Satzebene 75 o o Einträge der Parsetabelle werden durch Referenzen auf Fehlerrou'nen ersetzt. Fehlerrou'nen: ¤ ¤ ändern, fügen ein, löschen, geben Fehlermeldungen aus, enPernen Symbole vom Stack.