archetype | title | author | readings | outcomes | attachments | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
lecture-bc |
CFG |
BC George (HSBI) |
|
|
|
- Wie sind DFAs und NFAs definiert?
- Was sind reguläre Ausdrücke?
- Was sind formale und reguläre Grammatiken?
- In welchem Zusammenhang stehen all diese Begriffe?
- Wie werden DFAs und reguläre Ausdrücke im Compilerbau eingesetzt?
Für z. B. alle Sprachen, in deren Wörtern Zeichen über eine Konstante hinaus gezählt werden müssen. Diese Sprachen lassen sich oft mit Variablen im Exponenten beschreiben, die unendlich viele Werte annehmen können.
-
$a^ib^{2*i}$ ist nicht regulär -
$a^ib^{2*i}$ für$0 \leq i \leq 3$ ist regulär
\smallskip
- Wo finden sich die oben genannten Variablen bei einem DFA wieder?
- Warum ist die erste Sprache oben nicht regulär, die zweite aber?
- PDAs: mächtiger als DFAs, NFAs
- kontextfreie Grammatiken und Sprachen: mächtiger als reguläre Grammatiken und Sprachen
- DPDAs und deterministisch kontextfreie Grammatiken: die Grundlage der Syntaxanalyse im Compilerbau
- Der Einsatz kontextfreier Grammatiken zur Syntaxanalyse mittels Top-Down-Techniken
Einordnung: Erweiterung der Automatenklasse DFA, um komplexere Sprachen als die regulären akzeptieren zu können
Wir spendieren den DFAs einen möglichst einfachen, aber beliebig großen, Speicher, um zählen und matchen zu können. Wir suchen dabei konzeptionell die "kleinstmögliche" Erweiterung, die die akzeptierte Sprachklasse gegenüber DFAs vergrößert.
-
Der konzeptionell einfachste Speicher ist ein Stack. Wir haben keinen wahlfreien Zugriff auf die gespeicherten Werte.
-
Es soll eine deterministische und eine indeterministische Variante der neuen Automatenklasse geben.
-
In diesem Zusammenhang wird der Stack auch Keller genannt.
Def.: Ein Kellerautomat (PDA)
Ein PDA ist per Definition nichtdeterministisch und kann spontane Zustandsübergänge durchführen.
Strukturen mit paarweise zu matchenden Symbolen.
Bei jedem Zustandsübergang wird ein Zeichen (oder
Ein PDA für
Def. Ein PDA
-
$\delta(q, a, X)$ hat höchstens ein Element für jedes$q \in Q, a \in\Sigma$ oder$(a = \epsilon$ und$X \in \Gamma)$ . -
Wenn
$\delta (q, a, x)$ nicht leer ist für ein$a \in \Sigma$ , dann muss$\delta (q, \epsilon, x)$ leer sein.
Deterministische PDAs werden auch DPDAs genannt.
Satz: Die von DPDAs akzeptierten Sprachen sind eine echte Teilmenge der von PDAs akzeptierten Sprachen.
Die regulären Sprachen sind eine echte Teilmenge der von DPDAs akzeptierten Sprachen.
Def. Eine kontextfreie (cf-) Grammatik ist ein 4-Tupel
\vspace{-2.5cm}
Ableitungsbäume für
:::notes Hier entsteht ein Tafelbild. :::
Def.: Gibt es in einer von einer kontextfreien Grammatik erzeugten Sprache ein Wort, für das mehr als ein Ableitungsbaum existiert, so heißt diese Grammatik mehrdeutig. Anderenfalls heißt sie eindeutig.
Satz: Es ist nicht entscheidbar, ob eine gegebene kontextfreie Grammatik eindeutig ist.
Satz: Es gibt kontextfreie Sprachen, für die keine eindeutige Grammatik existiert.
Satz: Die kontextfreien Sprachen und die Sprachen, die von PDAs akzeptiert werden, sind dieselbe Sprachklasse.
Satz: Eine von einem DPDA akzeptierteSprache hat eine eindeutige Grammatik.
Vorgehensweise im Compilerbau: Eine Grammatik für die gewünschte Sprache definieren und schauen, ob sich daraus ein DPDA generieren lässt (automatisch).
-
einen Grammatiktypen, aus dem sich manuell oder automatisiert ein Programm zur deterministischen Syntaxanalyse (=Parser) erstellen lässt
-
einen Algorithmus zum Parsen von Programmen mit Hilfe einer solchen Grammatik
Wir verstehen unter Syntax eine Menge von Regeln, die die Struktur von Daten (z. B. Programmen) bestimmen.
Diese vorgegebene Syntax wird im Compilerbau mit einer kontextfreien Grammatik beschrieben und mit einem sogenannten Parser analysiert.
Heute: LL-Parsing, mit dem man eine Teilmenge der eindeutigen kontextfreien Grammatiken syntaktich analysieren kann.
Dabei wird der Ableitungsbaum von oben nach unten aufgebaut.
-
Bestimmung der syntaktischen Struktur eines Programms
-
aussagekräftige Fehlermeldungen, wenn ein Eingabeprogramm syntaktisch nicht korrekt ist
-
Erstellung des AST (abstrakter Syntaxbaum): Der Parse Tree ohne Symbole, die nach der Syntaxanalyse inhaltlich irrelevant sind (z. B. Semikolons, manche Schlüsselwörter)
-
die Symboltablelle(n) mit Informationen bzgl. Bezeichner (Variable, Funktionen und Methoden, Klassen, benutzerdefinierte Typen, Parameter, ...), aber auch die Gültigkeitsbereiche.
Welche Produktion nehmen?
Wir brauchen die "terminalen k-Anfänge" von Ableitungen von Nichtterminalen, um eindeutig die nächste zu benutzende Produktion festzulegen.
Def.: Wir definieren
$a \in T^\ast, |a| \leq k: {First}_k (a) = \lbrace a \rbrace$ $a \in T^\ast, |a| > k: {First}_k (a) = \lbrace v \in T^\ast \mid a = vw, |v| = k \rbrace$ $\alpha \in (N \cup T)^\ast \backslash T^\ast: {First}_k (\alpha) = \lbrace v \in T^\ast \mid \alpha \overset{\ast}{\Rightarrow} w,\text{mit}\ w \in T^\ast, First_k(w) = \lbrace v \rbrace \rbrace$
Def.: Bei einer kontextfreien Grammatik
Man schreibt
Def.: Eine kontextfreie Grammatik
und
mit
:::notes Hier entsteht ein Tafelbild. :::
Die von LL(k)-Grammatiken erzeugten Sprachen sind eine echte Teilmenge der deterministisch parsbaren Sprachen.
Die von LL(k)-Grammatiken erzeugten Sprachen sind eine echte Teilmenge der von LL(k+1)-Grammatiken erzeugten Sprachen.
Für eine kontextfreie Grammatik
In der Praxis reichen LL(1) - Grammatiken oft. Hier gibt es effiziente Parsergeneratoren (hier: ANTLR), deren Eingabe eine LL-Grammatik ist, und die als Ausgabe den Quellcode eines (effizienten) tabellengesteuerten Parsers generieren.
- eine LL(k)-Grammatik
- die
$First_k$ -Mengen der rechten Seiten aller Produktionsregeln - die
$Follow_k$ -Mengen aller Nichtterminale und der rechten Seiten aller Produktionsregeln - das Endezeichen
$\perp$ hinter dem Eingabewort
Def.: Wir definieren
:::notes Hier entsteht ein Tafelbild. :::
Eingabe: Eine Grammatik
Ausgabe: Eine Parsertabelle
Statt
:::notes Hier entsteht ein Tafelbild. :::
Rekursive Programmierung bedeutet, dass das Laufzeitsystem einen Stack benutzt. Diesen Stack kann man auch "selbst programmieren", d. h. einen PDA implementieren. Dabei wird ebenfalls die oben genannte Tabelle zur Bestimmung der nächsten anzuwendenden Produktion benutzt. Der Stack enthält die zu erwartenden Eingabezeichen, wenn immer eine Linksableitung gebildet wird. Diese Zeichen im Stack werden mit dem Input gematcht.
Eingabe: Eine Grammatik
Ausgabe: Wenn
:::notes Hier entsteht ein Tafelbild. :::
-
eventuelle Syntaxfehler mit Angabe der Fehlerart und des -Ortes
-
Format für die Weiterverarbeitung:
- Ableitungsbaum oder Syntaxbaum oder Parse Tree
- abstrakter Syntaxbaum (AST): Der Parse Tree ohne Symbole, die nach der Syntaxanalyse inhaltlich irrelevant sind (z. B. ;, Klammern, manche Schlüsselwörter,
$\ldots$ )
- Die Struktur von gängigen Programmiersprachen lässt sich nicht mit regulären Ausdrücken beschreiben und damit nicht mit DFAs akzeptieren.
- Das Automatenmodell der DFAs wird um einen endlosen Stack erweitert, das ergibt PDAs.
- Kontextfreie Grammatiken (CFGs) erweitern die regulären Grammatiken.
- Deterministisch parsebare Sprachen haben eine eindeutige kontextfreie Grammatik.
- Es ist nicht entscheidbar, ob eine gegebene kontextfreie Grammatik eindeutig ist.
- Syntaxanalyse wird mit deterministisch kontextfreien Grammatiken durchgeführt.
- Eine Teilmenge der dazu gehörigen Sprachen lässt sich top-down parsen.
- Ein effizienter LL(k)-Parser realisiert einen DPDA und kann automatisch aus einer LL(k)-Grammatik generiert werden.
- Der Parser liefert in der Regel einen abstrakten Syntaxbaum.
::: slides
Unless otherwise noted, this work is licensed under CC BY-SA 4.0. :::