Syntaxanalyse Ausgangspunkt und Ziel



Ähnliche Dokumente
Von der Grammatik zum AST

Shift Reduce Parser (Bottom up Parser) Historie Grundbegriffe Tabellengesteuerter LR(1) Parser Konstruktion der Elementmengen Tabellenkonstruktion

Shift Reduce Parser (Bottom up Parser) Historie Grundbegriffe Tabellengesteuerter LR(1) Parser Konstruktion der Elementmengen Tabellenkonstruktion

Kapitel 5: Syntax-Analyse

7. Syntax: Grammatiken, EBNF

Alphabet, formale Sprache

Vorlesung Programmieren

Software Entwicklung 1. Fallstudie: Arithmetische Ausdrücke. Rekursive Klassen. Überblick. Annette Bieniusa / Arnd Poetzsch-Heffter

Software Entwicklung 1

LR-Parsing. Präsentation vom 19. Dez Adriana Kosior, Sandra Pyka & Michael Weidauer. Automatische Syntaxanalyse (Parsing) Wintersemester 12/13

Äquivalente Grammatiken / attributierte Grammatik

Fachseminar Compilerbau

Tagesprogramm

1. Der Begriff Informatik 2. Syntax und Semantik von Programmiersprachen - 1 -

1. Der Begriff Informatik 2. Syntax und Semantik von Programmiersprachen - 1 -

Syntax von Programmiersprachen

Sprachen sind durch folgenden Aufbau gekennzeichnet:

Syntax von Programmiersprachen

Inhalt Kapitel 11: Formale Syntax und Semantik

Kontextfreie Sprachen. Automaten und Formale Sprachen alias Theoretische Informatik. Sommersemester Kontextfreie Sprachen

Compilerbau für die Common Language Run-Time

Einführung - Parser. Was ist ein Parser?

2.2 Syntax, Semantik und Simulation

Syntax von Programmiersprachen

Kapitel 2. Methoden zur Beschreibung von Syntax

5. Syntaxanalyse und der Parser-Generator yacc. 5.5 Aufsteigende Analyse. Absteigende Analyse versus aufsteigende Analyse. 5.5 Aufsteigende Analyse

5. Die syntaktische Analyse

LR-Parser, Shift-Reduce-Verfahren

9.4 Grundlagen des Compilerbaus

Compilerbau Syntaxanalyse 68. LR(1)-Syntaxanalyse

2.1 Grundlagen: Kontextfreie Grammatiken

Grammatik Prüfung möglich, ob eine Zeichenfolge zur Sprache gehört oder nicht

Fragenkatalog 2. Abgabegespräch Formale Modellierung 3 Reguläre Sprachen

Bottom-Up Analyse. im Einzelnen

15. Rekursion. Rekursive Funktionen, Korrektheit, Terminierung, Aufrufstapel, Bau eines Taschenrechners, BNF, Parsen

Der Grid-Occam-Compiler. Syntaxanalyse

1 Grammar Engineering. 2 Abstrakte Syntax als abstrakte Algebra. 3 LL(1)-Parser. 4 LR Parser. 5 Fehlerbehandlung. 6 Earley Parser

Automaten und formale Sprachen Klausurvorbereitung

Kellerautomat (1/4) Kellerautomat (2/4) Kellerautomat (3/4) Kellerautomat (4/4)

Einführung in die Programmierung für NF. Übung

Compiler: Parser. Prof. Dr. Oliver Braun. Fakultät für Informatik und Mathematik Hochschule München. Letzte Änderung:

Compilerbau. Bachelor-Programm. im SoSe Prof. Dr. Joachim Fischer Dr. Klaus Ahrens Dipl.-Inf. Ingmar Eveslage.

17. Rekursion 2. Bau eines Taschenrechners, Ströme, Formale Grammatiken, Extended Backus Naur Form (EBNF), Parsen von Ausdrücken

Kapitel IV Formale Sprachen und Grammatiken

Begriffe (Wiederholung)

Programmierkurs Java

Formale Sprachen. Inhaltsverzeichnis. M. Jakob. 10. Dezember Allgemeine Einführung. Aufbau formaler Sprachen

Kapitel 5: Syntaxdiagramme und Grammatikregeln

LL(k)-Analyse. (y) folgt α = β. (x) = start k. (=l> ist ein Linksableitungsschritt)

Einführung in die Programmiertechnik

Was bisher geschah Chomsky-Hierarchie für Sprachen: L 0 Menge aller durch (beliebige) Grammatiken beschriebenen Sprachen L 1 Menge aller monotonen

Syntax von Softwaresprachen

Compilerbau. Bachelor-Programm. im SoSe Prof. Dr. Joachim Fischer Dr. Klaus Ahrens Dr. Andreas Kunert Dipl.-Inf.

Anwendung von Kontextfreien Grammatiken

Einführung in die Programmierung für NF. Rückgabewerte, EBNF, Fallunterscheidung, Schleifen

4.4 Imperative Algorithmen Prozeduren

Kapitel 4. Kontrollstrukturen

Elementare Konzepte von

Ein Fragment von Pascal

Programmiersprache 1 (C++) Prof. Dr. Stefan Enderle NTA Isny

Java für Anfänger Teil 2: Java-Syntax. Programmierkurs Manfred Jackel

Kapitel 2: Methoden zur Beschreibung von Syntax

The ACCENT Compiler Compiler. A compiler compiler for the entire class of context-free languages

Kontextfreie Grammatiken. Kontextfreie Grammatiken 1 / 45

6 Modellierung von Strukturen 6.1 Kontextfreie Grammatiken

Sprachanalyse. Fachseminar WS 08/09 Dozent: Prof. Dr. Helmut Weber Referentin: Nadia Douiri

Theoretische Informatik I

Software Entwicklung 2. Übersetzerbau 2

Kapitel 2. Methoden zur Beschreibung von Syntax

Formale Sprachen und Grammatiken

Übersicht: Inhalt und Ziele. Kapitel 2: Methoden zur Beschreibung von Syntax. Aufbau von Programmen. Syntax von Programmiersprachen

Kapitel 4. Kontrollstrukturen

3 Syntax von Programmiersprachen

Syntax von Programmiersprachen

Einführung in die Programmierung I. 2.0 Einfache Java Programme. Thomas R. Gross. Department Informatik ETH Zürich

3 Syntax von Programmiersprachen

Kapitel 4: Syntaxdiagramme und Grammatikregeln

Java für Anfänger Teil 2: Java-Syntax. Programmierkurs Manfred Jackel

Kontextfreie Grammatiken. Kontextfreie Grammatiken 1 / 48

Programmieren in Java

6 Kontextfreie Grammatiken

Aufgabe 1 Basiswissen zur Vorlesung (8 Punkte)

...imbeispiel: Die Konkatenation der Blätter des Ableitungsbaums t bezeichnen wir auch mit yield(t). liefert die Konkatenation: name int + int.

Interpreter - Gliederung

Definition von LR(k)-Grammatiken

Der Parsergenerator BTRACC2

Algorithmen und Datenstrukturen

Grundbegriffe der Informatik

Literatur Reguläre Ausdrücke

2.6 Deterministisches Top-Down-Parsen

Übungs- und Praktikumsaufgaben zur Systemprogrammierung Dipl.-Ing. H. Büchter (Lehrbeauftragter) FH-Dortmund WS 2001/2002 / SS 2002

7. Formale Sprachen und Grammatiken

Objektorientierte Programmierung. Kapitel 3: Syntaxdiagramme und Grammatikregeln

11. Rekursion, Komplexität von Algorithmen

Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 18/19. Syntax. Dr. Philipp Wendler

Transkript:

Syntaxanalyse Ausgangspunkt und Ziel Ausgangspunkt: Kontextfreie Grammatik Im Normalfall BNF, manchmal EBNF BNF = Backus-Naur-Form = Produktionsregeln EBNF = erweiterte BNF (+ reguläre Ausdrücke) Prüfung der Korrektheit der Eingabe Darstellung der semantischen Bedeutung der Eingabe in anderer Form (Baumstruktur, Datenbank, anderes Datenformat) Erkennung der Semantik soll durch Syntaxanalyse unterstützt werden.

Syntaxanalyse Einschränkungen Kontextfreie Grammatik Eindeutigkeit der Grammatik: es gibt genau einen Ableitungsbaum (es gibt unterschiedliche Ableitungen, je nachdem in welcher Reihenfolge man vorgeht. Bei eindeutiger Grammatik ist aber bei jeder Ableitung eines Nichtterminals immer genau eine Regel anwendbar.) Theorie verlangt die Existenz einer Ableitung. Compilerbau verlangt, dass die Ableitung effizient gefunden wird. (möglichst kein Backtracking) Es sollte leicht möglich sein, die Übersetzung an die Syntaxanalyse anzuhängen. Je nach verwendeter Methode gibt es mehr oder weniger große Einschränkungen in der Formulierung der Grammatik

Syntaxanalyse Top Down Verfahren Ausgangspunkt: Eingabe, Startsymbol Zu jedem Zeitpunkt steht eine Satzform zu weiteren Ableitung an. Die Eingabe wird von links nach rechts verarbeit. L Die Satzform wird von links nach rechts verarbeitet. LL Bei Entscheidungen werden die k nächsten Eingabesymbole betrachtet (hier nicht 1) LL(k) Terminalsymbole müssen mit der Eingabe übereinstimmen. (match) Nichtterminale werden durch ihre Ableitung ersetzt. Bei der Auswahl der passenden Regel werden die k-ersten Anfangssymbole der rechten Seiten einer Regel benutzt (hier nur das 1. Symbol) Wenn die rechte Seite einer Regel leer ist, entscheidet das Folgesymbol der linken Seite (Erklärung kommt noch).

LL(1)-Verfahren Programmierte Lösung: rekursiver Abstieg match() vergleicht Terminalsymbole und steuert die Eingabe Nichtterminale werden durch Prozeduren implementiert. Entscheidungen werden über Lookahead-Symbol getroffen. Ableitungen werden durch Prozeduraufrufe realisiert. Folge von Symbolen = Anweisungsfolge. Tabellengesteuerte Verfahren Stack speichert die noch zu verarbeitenden Symbole Das Top-Stack Symbol wird verarbeitet. Ein Terminalsymbol wird mit der Eingabe gematcht. Ein Nichtterminalsymbol wird durch die rechte Seite einer Regel ersetzt. Über die Auswahl der Regel entscheidet eine Tabelle.

Beispiel für LL(1)-Verfahren 1) E T R Terminalsymbole: { z, +, (, ) + $ 2) T z Nonterminalsymbole: { E, T, R 3) T ( E ) Startsymbol: E 4) R + T R 5) R z + ( ) $ E 1-1 - - T 2-3 - - Spalten: Eingabesymbole + $ = Ende Zeilen: Nichtterminalsymbole Schnittpunkte: anzuwendende Regel R - 4-5 5

Syntaxerkennung nach LL(1) while true: if isdollar(tos): return isdollar(lookahead) else if isterminal(tos): match(tos) or Synaxerror else:// isnonterminal(tos) nt = pop() push(apply(nt, lookahead)) match(symbol): if symbol == input[i]: i=i+1 else: Syntaxerror apply(ntsym, tsym): nr = tab[ntsym, tsym] if nr == -: Syntaxerror else: return umgedrehte rechte Seite von Regel nr Eingabe Stack Bemerkung z + ( z + z ) $ E $ Regel 1 z + ( z + z ) $ T R $ Regel 2 z + ( z + z ) $ z R $ match + ( z + z ) $ R $ Regel 4 + ( z + z ) $ + T R $ match ( z + z ) $ T R $ Regel 3 ( z + z ) $ ( E ) R $ match z + z ) $ E ) R $ Regel 1 z + z ) $ T R ) R $ Regel 2 z + z ) $ z R ) R $ match + z ) $ R ) R $ Regel 4 + z ) $ + T R ) R $ match z ) $ T R ) R $ Regel 2 z ) $ z R ) R $ match ) $ R ) R $ Regel 5 ) $ ) R $ match $ R $ Regel 5 $ $ accept

LL(1) Tabelle Die Tabelle steuert die Entscheidung welche Regel bei der Ableitung eines Nichterminalsymbols zu verwenden ist. Die Entscheidung hängt von dem nächsten Eingabesymbol ab (lookahead-symbol) Notwendige Bedingungen: Eine Regelanwendung kann nur erfolgreich sein, wenn das lookahead-symbol gleich einem der möglichen Anfangsterminale der rechten Seite ist. (First-Symbole) Wenn die rechte Seite einer Regel leer ist, kann die Regelanwendung nur dann erfolgreich sein, wenn das lookahead-symbol gleich einem der Terminalsymbole ist, die bei einer gültigen Ableitung hinter dem Symbol der linken Seite stehen können. (Follow-Symbole) Die Tabellenkonstruktion führt nur dann zu einem brauchbaren Verfahren, wenn in jeder Zelle höchstens eine Regelnummer steht. (LL(1)-Bedingung)

Ermittlung der Firstmenge Die Funktion first(folge) ordnet einer Folge von (beliebigen) Symbolen eine Menge von Terminalsymbolen zu. In der Ergebnismenge kann auch das leere Symbol (ε) enthalten sein. Ein Nichtterminalsymbol N habe mehrere Regeln der Form N R i. T steht für ein Terminalsymbol. α steht für eine Folge von Terminal und Nichtterminalsymbolen (kann auch leer sein).. Eine Symbolfolge heißt nullable, wenn sie die Firstmenge ε enthält. Für die Ermittlung der Firstmenge gelten die folgenden Regeln: first(tα) = {T first(ε) first(n) = {ε = U i first(r i ), wobei R i eine rechte Seite von N-Regeln ist. first(nα) = first(n) \ ε first(α), first(n), nullable(n) sonst

Ermittlung der Followmenge Die Funktion follow(n) ordnet einem Nichtterminalsymbol N eine Menge von Terminalsymbolen zu. In der Ergebnismenge kann auch das Endesymbol ($) gehören (aber nicht das leere Symbol ε). Ein Nichtterminalsymbol N habe mehrere Regeln der Form N R i. T steht für ein Terminalsymbol. α und β stehen für eine Folge von Terminal und Nichtterminalsymbolen. Für die Ermittlung der Followmenge von N gelten die folgenden Regeln: Wenn N das Startsymbol ist, so gilt $ in follow(n) Wenn N auf der rechten Seite einer Regel der Form α N β gefolgt von der Symbolfolge β steht, so gilt: first(β) \ ε in follow(n). Wenn X αn oder X αnβ mit nullable(β), so gilt follow(x) follow(n).

LL(1)-Tabellenkonstruktion Die Tabelle enthält für jedes Nichtterminalsymbol der Grammatik eine Zeile (in der ersten Zeile steht das Startsymbol) und für jedes Terminalsymbol und für das Endezeichen $ eine Spalte. Der Inhalt einer Zeile ergibt sich aus den Regeln für das entsprechende Nichtterminalsymbol. Das Nichtterminalsymbol habe mehrere (mit i numerierte) Regeln N -> R i. Für jedes Terminalsymbol T (nicht für ε) aus first(r i ) wird der Verweis auf die Regel i in der Spalte des Terminalsymbols T eingetragen. Gilt ε in first(r i ), so wird die Regel i in die Spalte der Symbole aus follow(r i ) eingetragen. Freibleibende Tabellenfelder bezeichnen Syntaxfehler. Wird nach diesen Regeln ein Tabellenfeld doppelt besetzt, so ist die Tabellenkonstruktion fehlgeschlagen. Die Grammatiken, bei denen die LL(1)-Konstruktion funktioniert, nennt man LL(1)- Grammatiken.

LL(1)-Bedingungen Eine Grammatik ist in LL(1), wenn sie den folgenden Bedingungen für jedes Nichtterminalsymbol N genügt: Die Firstmengen verschiedener Regeln eines Nichterminalsymbols N sind untereinander disjunkt. Enthält die Firstmenge einer rechten Seite eines Nichterminalsymbols N das leere Symbol ε, so ist follow(n) mit den Firstmengen aller rechten Seiten von N disjunkt.

Beispiel non LL(1) - Grammatik 1) E E + T Terminalsymbole: { z, +, (, ) und $ 2) E T 3) T z Nonterminalsymbole: { E, T 4) T ( E ) Startsymbol: E z + ( ) $ E 1/2-1/2 - - T 3-4 - - für E: für T first(e+t) = { z, ( first(z) = { z first(t) = { z, ( first( ( E ) ) = { (

Grammatik für Scheme Eine Scheme Ausdruck ist entweder ein Atom oder eine Liste. Eine Liste ist eine eingeklammerte Folge von Scheme Ausdrücken. Ein Atom ist ein Wort oder eine Zahl. Beispiele: (+ alfa 4), (define (a x) (+ x 1)), 345, alfa Schreiben Sie formal die Grammatik auf. Konstruieren Sie eine LL(1)-Tabelle. Führen Sie die Syntaxanalyse für ein Beispiel durch. Frage am Rande: wie sind in Scheme Wörter definiert?

JavaCC InstructionNode ifinstruction() : { // Deklaration Node block; Node cond; IfInstructionNode node; { // Syntax ( <IF> cond=expression() <COLON> block=block() { node = new IfInstructionNode(cond, block); ( <ELSEIF> log=expression() <COLON> block=block()) { node.addelseifbranch(cond, block); )* ( <ELSE> <COLON> block=block()) { node.addelsebranch(block); )? ) { return node;

Rekursiver Abstieg eine LL(1) Variante Tabellengesteuerte Methoden legen die Verwendung von Parsergeneratoren nahe. Das gibt es auch im Fall von LL(1): javacc Tabellengesteuerte Verfahren benötigen in jedem Fall eine Kopplung zur Programmiersprache. Zusätzlich haben Tabellengesteuerte Verfahren häufig die Möglichkeit, die Nachteile von LL(1) durch besondere Eingriffsmöglichkeiten in den Parserablauf (z.b. gezielte Vergrößerung des Lookahead) abzumildern. Ein sinnvolle Alternative ist die durchgängige Programmierung des Parsers nach der Methode des rekursiven Abstiegs.

Rekursiver Abstieg - Vorbereitung Als Scannerschnittstelle verwenden wir die Methoden interface LL1Scanner { /** * Liefert das aktuelle Lookahead-Symbol */ int lookahead() /** * Matched ein Symbol und liest das nächste Symbol von der * Eingabe. * @param zu prüfendes Terminalsymbol * @throws SyntaxException wenn das Symbol nicht mit dem * Lookahead-Symbol übereinstimmt */ void match(symbol) Terminale werden durch match(sym) dargestellt. Nichterminale werden durch Parsermethoden repräsentiert. Ableitungen werden zu Methodenaufrufen. Entscheidungen werden durch Vergleich mit lookahead() getroffen.

Rekursiver Abstieg - Methodik Terminalsymbole sind durch Konstanten kodiert. Für jedes Nichtterminal schreiben wir eine eigene Methode. Der Auswahl der richtigen Regel erfolgt durch Vergleich mit lookahead(). Die Folge der Symbole der rechten Seite einer Regel ergibt eine Folge von Anweisungen. Für jedes Terminalsymbol rufen wir match(tsym) auf. Für jedes Nichtterminalsymbol rufen wir dessen Methode auf.

Rekursiver Abstieg - Beispiel void expression() { term(); termrest(); // expression -> term termrest void term() { if (lookahead() == ZAHL) // term -> zahl match(zahl); else if (lookahead() == KLAUF) { // term -> ( expression ) match(klauf); expression(); match(klzu); else throw new SyntaxException(); void termrest() { if (lookahead() == PLUS) { // termrest -> + term termrest match(plus); term(); termrest(); if (lookahead()==ende lookahead()==klzu) // termrest -> /* nichts */ else throw new SyntaxException(); (anstelle von if kann man auch switch verwenden)

Rekursiver Abstieg Bemerkungen Rekursiver Abstieg lässt sich schematisch aus der Syntaxbeschreibung herleiten. Rekursiver Abstieg funktioniert genau dann, wenn die Syntax LL(1) ist. Rekursiver Abstieg lässt sich leicht durch programmierte Aktionen für die Übersetzung erweitern. Rekursiver Abstieg in der dargestellten Form nutzt nicht alle Kontrollstrukturen (Wiederholung durch Rekursion) Rekursiver Abstieg lässt sich besser bei einer in EBNF formulierten Grammatik einsetzen (die Nachteile von LL(1) werden dabei etwas ausgeglichen) Rekursiver Abstieg lässt programmierte Optimierungen zu.

EBNF EBNF = BNF + reguläre Ausdrücke [... ] für 0-1 mal, manchmal auch (... )? {... für 0-n mal, manchmal auch (... )* (... ) für Schachtelung für Alternative Die Umsetzungsregeln ergeben sich daraus, dass man jede EBNF formal in eine BNF umwandeln kann. Vereinfacht: [... ] in eine Fallunterscheidung, {... in eine Iteration. Eine Besonderheit ist, dass man jetzt genau überlegen muss, von welchen Bedingungen Fallunterscheidungen und Wiederholungen abhängen.

EBNF-Grammatik für Ausdrücke expression term ( + term )* term z ( expression ) void expression() { term(); while (lookahead() == PLUS) { // { + term match(plus); term(); void term() { if (lookahead() == ZAHL) match(zahl); else if (lookahead() == KLAUF) { match(klauf); expression(); match(klzu); else throw new SyntaxException();

Unwandlung von EBNF in BNF Abschließend soll kurz dargestellt werden, dass EBNF letztlich nur eine abgekürzte BNF ist. EBNF Formeln können nämlich einfach umgefwandelt werden: ( Ausdruck ) wird zu Klammer ::= Ausdruck { Ausdruck wird zu Wiederholung ::= ; Wiederholung ::= Wiederholung Ausdruck ; // oder: Ausdruck Wiederholung [ Ausdruck ] wird zu Option ::= ; Option ::= Ausdruck ;