Martin Schneider, Folien von Prof. H.-P. Gumm

Slides:



Advertisements
Ähnliche Präsentationen
Vorlesung Compilertechnik Sommersemester 2008
Advertisements

Vorlesung Compilertechnik Sommersemester 2008
Forschungszentrum caesar
Gliederung 1. Grundlagen der Bottom-Up-Syntaxanalyse
Suche in Texten (Stringsuche )
8. Formale Sprachen und Grammatiken
10. Grundlagen imperativer Programmiersprachen
Imperative Programmierung
Grammatiken, Definitionen
Kapitel 4 Syntaktische Analyse: LR Parsing.
Parser generieren Yet Another Compiler – Compiler YACC.
FH-Hof Extensible Markup Language Richard Göbel. FH-Hof Extensible Markup Language XML XML ist universeller Ansatz für die Strukturierung von Zeichenketten.
Sortierverfahren Richard Göbel.
FH-Hof Grammatiken Richard Göbel. FH-Hof Begriffe Eine Grammatik definiert die Struktur (Syntax) einer Zeichenkette Eine Grammatik definiert nicht die.
Java: Dynamische Datentypen
Parser - Verfahren: Rekursiver Abstieg
Sortierverfahren Richard Göbel.
CFGs und Kellerautomaten
REKURSION + ITERATION. Bemerkung: Die in den folgenden Folien angegebenen "Herleitungen" sind keine exakten Beweise, sondern Plausibilitätsbetrachtungen.
Strukturen. In einer Struktur kann eine beliebige Anzahl von Komponenten (Daten) mit unterschiedlichen Datentypen (im Gegensatz zu Feldern) zusammengefaßt.
Dynamischer Speicher. In einer Funktion wird z.B. mit der Deklaration int i; Speicher auf dem sogenannten Stack reserviert. Wenn die Funktion verlassen.
Algorithmentheorie 04 –Hashing
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (05 – Reguläre Ausdrücke) Prof. Dr. Th. Ottmann.
WS Prof. Dr. Th. Ottmann Algorithmentheorie 09 - Suche in Texten Suffix –Tree –Konstruktion Ukkonen Algorithmus.
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (06 – Reduktion endlicher Automaten) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (04 – Automaten mit ε-Transitionen) Prof. Dr. Th. Ottmann.
M a r c – o l i v e r p a h l Informatik II – Kapitel 18 Übersetzung Zusammenfassung des Kapitel 18 Küchlin, Weber, Vorversion Einführung in die Informatik,
Prof. Dr. rer.nat. Ralph Großmann Fakultät Informatik / Mathematik Sommersemester 2012 Internet-Technologien XML-basierte Techniken Teil Metasprache der.
Parsing regulärer Ausdrücke
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
Beispiele für Ausdrucksalgebren
Agenda Motivation Formale Sprachen Compiler Compilerentwicklung
Zusammenfassung Vorwoche
High Performance = Innovative Computer Systems + Efficient Algorithms Friedhelm Meyer auf der Heide 1 HEINZ NIXDORF INSTITUT Universität Paderborn Algorithmen.
Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage:
Von der Sprache zum Programm
SLR(1)-Parser Basiert auf LR(0)-Item-Mengen, wie LR(0)-Parser. Zusätzlich wird für Reduktionen bei Follow(X) als Vorschau- menge benutzt. LR(1)-Parser.
LL(1) Grammatiken Simple LL(1) ohne ε-Regeln verschiedene Produktionen, so gilt LL(1) ohne ε-Regeln verschiedene Produktionen, so gilt LL(1) mit ε-Regeln.
Programmiersprachen II Integration verschiedener Datenstrukturen
Formale Sprachen und Automaten
Einführung in die Programmiersprache C 3.Tag Institut für Mathematische Optimierung - Technische Universität Braunschweig.
Chaos und Fraktale M. Bostelmann Michael Bostelmann.
Syntaxanalyse Bottom-Up und LR(0)
BIT – Schaßan – WS 02/03 Basisinformationstechnologie HK-Medien Teil 1, 11.Sitzung WS 02/03.
Präsentation C Tutorium von Daniel J. Nowak Folie 1 C Tutorium.
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Formale Sprachen Grammatiken und die Chomsky-Hierarchie
Informatik II Grundlagen der Programmierung Programmieren in C Programmstrukturen / Kontrollstrukturen Hochschule Fulda – FB ET Sommersemester 2014.
Automaten, formale Sprachen und Berechenbarkeit II SoSe 2004 Prof. W. Brauer Teil 1: Wiederholung (Vor allem Folien von Priv.-Doz. Dr. Kindler vom WS 2001/02.
Städtisches Gymnasium Beverungen Friedel Berlage
Dieser nicht Fehler finden Algorithmus enthält einfach einen gravierenden welcher zu ist.
MODULA-2.
Beispiele: KFG 2.Teil Beispiel 1: Sei G eine Grammatik mit den folgenden Regeln: S  Ac | Bd A  aAb | ab B  aBbb | abb Definieren Sie.
Noam CHOMSKY, Sheila GREIBACH
BMEVIEEA100 Grundlagen der Programmierung
Automaten, formale Sprachen und Berechenbarkeit II SoSe 2004 Prof. W. Brauer Teil 3: Potenzreihen und kontextfreie Sprachen (Vgl. Buch von A. Salomaa)
1 Albert-Ludwigs-Universität Freiburg Rechnernetze und Telematik Prof. Dr. Christian Schindelhauer Informatik III Christian Schindelhauer Wintersemester.
Informatik Formale Sprachen 1.2 Grammatiken formaler Sprachen
Agenda Motivation und Einordnung Syntaxgerichtete Übersetzung
Der Hund jagt die Katze. Theoretische Informatik Satz S P O
7. Formale Sprachen und Grammatiken
Inhalt Einordnung und Funktion der lexikalische Analyse Grundlagen
Semantische Analyse und attributierte Grammatiken
Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar?
 Sortigkeit oder Arität
 Am Ende der letzten Stunde hatten wir über die Grenzen unserer Automaten-Modell gesprochen. Dr. Lars Ettelt2  Tipp: Parkhaus.  Einfahrt erst wenn.
Tutorium Software-Engineering SS14 Florian Manghofer.
Lookup, accept reduce 1 LR(0) Parser Beispielblatt Grammatik 1. Beginne mit State J 0 und Trage das erste Item aus Produktion 0 ein. Der Punkt des Items.
Tabellengesteuerte Verfahren
Gliederung 0. Motivation und Einordnung 1. Endliche Automaten
 Präsentation transkript:

Martin Schneider, 25.05.2000 Folien von Prof. H.-P. Gumm Parsen Martin Schneider, 25.05.2000 Folien von Prof. H.-P. Gumm

Der zweistufige Aufbau von Sprachen Richtige Sprachen ( Deutsch, Englisch, Prolog, Pascal, ... ) lassen sich in zwei Stufen beschreiben : 1. Stufe Aus einem Zeichenalphabet (etwa  = {a, b, ... ,z, A,B, ...,Z} wird der lexikalische Anteil der Sprache aufgebaut, die Menge aller Wörter der Sprache. Dies ist eine Teilmenge von *, also eine Sprache. Worte, die eine gleichartige Rolle spielen, wie etwa Bezeichner oder Integerzahlen werden zu Klassen zusammengefaßt. Jede solche Klasse wird durch einen Stellvertreter (token) repräsentiert. PASCAL-token sind z.B. : Identifier, IntegerConstant, RealConstant, BoolConstant, ... IF, THEN, :=, ... Die Klasse jedes Tokens wird durch einen regulären Ausdruck beschrieben, ist also selber eine reguläre Sprache

Der zweistufige Aufbau von Sprachen 2. Stufe Aus einem Alphabet der Token (etwa  = {id, intConst, addop, assign, IF, THEN, VAR, INTEGER, ...} wird der syntaktische Anteil der Sprache aufgebaut, die Menge aller möglichen Sätze der Sprache. Dies ist eine Teilmenge von *, also eine Sprache über . Letztere Sprache ist i.A. nicht mehr regulär, man benutzt zu ihrer Beschreibung entweder “kontextfreie Grammatiken”, BNF (Backus-Naur-Form) oder Syntaxdiagramme. Beispiel in Pascal : VarDecl ::= VAR id (, id)* colon Type Type ::= INTEGER, BOOLEAN, RECORD Fields END Fields ::= . . .

Sprachen und Grammatiken Eine Grammatik ist ein mathematisches Hilfsmittel, um Sprachen zu beschreiben. Eine Grammatik ist eine Menge von Regeln mit deren Hilfe Worte (Sätze) der Sprache konstruiert werden können. Erzeugen Mit Hilfe der Grammatik kann man Sätze einer Sprache erzeugen. Die zu der Grammatik G gehörende Sprache, L(G) , besteht aus allen Sätzen die man mit der Grammatik erzeugen kann. Parsen Ist ein Satz gegeben, so möchte man evtl. feststellen , ob dieser Satz zu der Sprache L(G) gehört oder nicht. Diese Aufgabe nennt man “Parsen”. Dabei wird auch die Struktur des Satzes erkannt.

Syntax - Semantik Eine Grammatik beschreibt die Struktur ( Syntax ) von Sätzen, nicht aber deren Bedeutung (Semantik). Obwohl die Bedeutung eines Satzes auch von seiner Struktur abhängt, kann man leicht syntaktisch richtige Sätze bilden, die bedeutungslos sind. Syntaktisch falscher Satz gestohlen der hat Gans Fuchs die. Syntaktisch richtiger, semantisch falscher Satz der Gans hat die Fuchs gestohlen. Syntaktisch und semantisch richtiger Satz der Fuchs hat die Gans gestohlen.

Syntax - Semantik in PASCAL Test Program. i for 1 do n ; to n+1 := n Syntaktisch falsches Programm Program Test ; Var i : Real; Begin For i := 1 To n Do i := n+1 End . Syntaktisch richtiges, semantisch falsches Programm Program Test ; Var i,n : Integer; Begin For i := 1 To n Do n := i+1 End . Syntaktisch und semantisch richtiges Programm

if x < 0 then y := y+1 else Inc(z) Parsen Parsen ist der Prozeß, ein gegebenes Wort w  L(G) in seine grammatikalischen Bestandteile zu zerlegen und einen Herleitungsbaum zu finden. Beispiel: if x < 0 then y := y+1 else Inc(z) if Bexpr then Expr RelOp id num Stmt else := + ( ) x < y 1 Inc z if x < then y := y + 1 else Inc ( z )

Bottom Up Parsing Beim Parsen wird der zu analysierende Satz von links nach rechts gelesen und gleichzeitig analysiert. Dabei wird der Syntaxbaum von den Blättern zur Wurzel (bottom-up) aufgebaut. Bexpr Stmt Expr Expr Expr id num num id id x > y := y + 1 if then else Inc ( z ) Lesepointer

if then y := y + 1 else Inc ( z ) Top Down Parsing Beim top down parsing beginnt man an der Wurzel des Baumes und baut ihn von oben nach unten auf. Dabei gibt das jeweils am weitesten rechts stehende Blatt an, was man im Input zu sehen wünscht. Stmt Bexpr Stmt Expr Expr id num if then y := y + 1 else Inc ( z ) x >

Shift-Reduce Parser Shift-Reduce Parser sind "bottom-up parser", d.h. sie bauen den Syntaxbaum von den Blättern her auf. Die Aktionen sind : Shift Lese ein weiteres Zeichen aus dem Input Die rechte Seite einer Produktion wurde im Input erkannt. Ersetze sie durch die linke Seite. Reduce Der Input kann dabei eine beliebige Satzform sein.

Shift-Reduce - Parsing Der gegenwärtige String besteht aus der Satzform if Bexpr then id := Expr das nächste Token (lookahead) ist ein else. Bexpr Shift Lese nächstes Token Expr Expr Expr Reduce Ersetze id := Expr durch Stmt. id num num id id if then else ??? ? ? ? x > y := y + 1 Lesepointer Wegen des Lookahead Tokens “else” ist ein Reduce angebracht. Wäre das lookahead ein `+` , so müßte geshiftet werden.

Shift-Reduce - Parsing Der gegenwärtige String besteht aus der Satzform if Bexpr then Stmt das nächste Token (lookahead) ist ein else. Bexpr Stmt Shift Lese nächstes Token Expr Expr Expr Reduce Ersetze if Bexpr then Stmt durch Stmt . id num num id id if then else ??? ? ? ? x > y := y + 1 Lesepointer Wegen des Lookahead Tokens “else” ist ein Shift angebracht. Wäre das lookahead ein `;` oder ein `end` , so müßte geshiftet werden.

Shift/Reduce Parsing Grammatik Wir parsen den Input “IF id THEN id := num + id eof“ 1 2 3 4 5 6 7 8 S  Stmt eof Stmt  if Expr then Stmt | if Expr then Stmt else Stmt | id := Expr Expr  Expr + Term | Term Term  num | id | ( Expr ) Erkannt : lookahead Aktion IF id shift IF id THEN red(6) IF Term THEN red(5) IF Expr THEN shift IF Expr THEN id shift IF Expr THEN id := shift IF Expr THEN id := num shift IF Expr THEN id := num + red(6) IF Expr THEN id := Term + red(5) IF Expr THEN id := Expr + shift IF Expr THEN id := Expr + id shift IF Expr THEN id := Expr + id eof red(7) IF Expr THEN id := Expr + Term eof red(4) IF Expr THEN id := Expr eof red(3) IF Expr THEN Stmt eof red(1) Stmt eof red(0) S eof accept

Zustände Ein Shift/Reduce Parser kann i.a. nicht allein anhand des lookahead tokens entscheiden, was als nächstes zu tun ist. Zu diesem Zweck muß man noch Zustände einführen, die besagen, was der Parser gerade zu erkennen versucht. Anhand dieser Grammatik 4 5 Expr  Expr + Term | Term und allein aus der Tatsache, daß zuletzt ein Term gesehen wurde und das lookahead “+” ist, ist nicht ersichtlich, ob gemäß der Regel 4 oder der Regel 5 reduziert werden muß : Erkannt : lookahead Aktion ... THEN id := Term + red(5) . . . ... THEN id := Expr + Term + red(4) Eine Reduktion mit Regel 5 ( red(5) ) würde im zweiten Falle in eine Sackgasse führen !!!

LR(0)- items *) Zur korrekten Entscheidungsfindung des Parsers sind Zustände notwendig. Ein Zustand drückt aus, - Welches Nonterminal der Parser gerade erkennen will - Mit welcher Regel er dies versucht und - Wieviel von der rechten Seite der Regel er bereits erkannt hat. Sei A   eine Produktion. Ein item ist eine Produktion zusammen mit einer “Position” in . Diese Position wird durch einen Punkt markiert. Beispiel: Aus der Produktion Expr  Expr + Term gewinnt man folgende items mit den Bedeutungen : Bei dem Versuch ein Expr zu finden ... ... erwarte ein Expr + Term ... Expr gesehen, erwarte + Term ... Expr + gesehen, erwarte Term ... Expr + Term gesehen Expr   Expr + Term Expr  Expr  + Term Expr  Expr +  Term Expr  Expr + Term  *) LR - steht für Left-Right, gemeint ist die Abarbeitung des Inputs von Links nach rechts

Der Automat einer Grammatik Ein item drückt den jeweiligen Zustand des Parsers beim Erkennen eines Nonterminals aus. Mit dem Lesen des nächsten Tokens geht der Parser in den nächsten Zustand über. Es gibt zwei mögliche Übergänge : + im Input Expr  Expr  + Term Expr  Expr + Term  Term  num  Expr  Expr + Term Term   id  Term   ( Expr ) Dies definiert einen nichtdeterministischen Automaten: N(G).

N(G) : Automat zu einer Grammatik G Gegeben eine Grammatik G=(V,T,P,S), mit Startsymbol S, dann definieren wir einen NFA mit den items als Zustandsmenge und den Terminalen und Nonterminalen der Grammatik als Alphabet : S = Menge aller items  = V  T s0 =S T = { X  X P }  (YX, ) = {X | XP } (Yu, u) = {Yu } für u V  T Start  Term Term  Term  Factor | Factor Factor  id | ( Term ) Für die Grammatik ergibt sich : ST T T  F ST TTF TTF TTF TTF ) T TF F( T ) F( T ) F( T) F( T ) ( F Alle unbeschrifteten Pfeile sind -Transitionen Fid TF Fid  id

Vom NFA zum DFA T T  F ) T ( F Alle unbeschrifteten Pfeile ST T T  F ST TTF TTF TTF TTF ) T TF F( T ) F(T ) F( T) F( T ) ( F Alle unbeschrifteten Pfeile sind -Transitionen TF Fid Fid  id Aus dem NFA gewinnen wir die Zustände des DFA, den wir D(G) nennen wollen: A = { ST, TTF, TF, F( T ) , Fid } B = { ST, TTF } C = { TF} D = { F( T), TTF, TF, F(T), Fid } E = { Fid  } F = { } G = {TTF, F( T ), Fid } H = { F( T), TTF } J = { TTF } K = { F( T) } T  F Der DFA D(G) ist : A B G J ( ( F  id T ) D H K C F id ( alle übrigen Pfeile auf F id F E

Goto In der Compilerliteratur nennt man die Tabelle des Automaten D(G) auch “Goto”-Tabelle. T  F A B G J ( ( F  id T ) D H K C F ( id alle übrigen Pfeile auf F id F E Goto-Tabelle : (Die leeren Positionen entsprechen Fehlerzuständen) Goto id ( ) * T F A E D B C B G D E D H C G E D J H K G

Die Notwendigkeit eines Stacks Angenommen der Parser ist in einem Zustand (mit dem item) er muß nun also in einen Zustand springen, der das item enthält. Nach Abarbeitung dieses items kommt er in den Zustand : Nun muß er sich aber erinnern, daß er zurückspringen muß in den Zustand Offensichtlich muß bei jedem Sprung der alte Zustand auf einem Stack aufbewahrt werden und bei der Beendigung des items zurückgesprungen werden, verbunden mit einem POP des Stacks. Term  ( Expr ) Expr  Expr + Term Expr  Expr + Term  Term  ( Expr )

shift items / reduce items Ein shift item hat die Form A u  , mit u  T. shift push Das shift item drückt aus, daß wir als nächstes ein u erwarten können. Sind wir in einem Zustand Y = { ... , A u ... } und ist das lookahead tatsächlich u , so wird es konsumiert und der nächste Zustand Z = Goto(Y,u) = { ... , A u ... } auf den Stack gepusht. Ein reduce item hat die Form A . reduce pop Es drückt aus, daß wir gerade erfolgreich ein A erkannt haben. Auf dem Stack sollte gefälligst ein Zustand Y = { ... , X A ... } liegen , darüber |  | weitere Zustände. Wir poppen also |  | viele Zustände und ersetzen sie durch Goto(Y,A) = { ... , X A ... }.

Der Stack beim Parsen von ( id  id ) F id E J  ) G G G T K id F T E C H H H H H H ( T F D D D D D D D D D C B A A A A A A A A A A A A A ( id  id ) eof A = { ST, TTF, TF, F( T ) , Fid } B = { ST, TTF } C = { TF} D = { F( T), TTF, TF, F(T), Fid } E = { Fid  } F = { } G = {TTF, F( T ), Fid } H = { F( T), TTF } J = { TTF } K = { F( T) } Die Shift-Aktionen entsprechen einer Traversierung des DFA(G)

Shift-Zustände - Reduce Zustände Ein reduce-Zustand ist ein Zustand, der ein reduce item enthält. Ein shift-Zustand ist ein Zustand, der ein shift item enthält. Ein accept-Zustand ist ein reduce-Zustand der Form Start  A = {ST, TTF, TF, F( T ) , Fid } B = { ST, TT F } C = { TF} D = { F( T), TTF, TF, F(T), Fid } E = { Fid  } F = { } G = {TTF, F( T ), Fid } H = { F( T), TTF } J = { TTF } K = { F( T) } Beispiel : Für die Zustände des vorigen Automaten hat man Shift Zustände : A, B, D, G, H Reduce Zustände : B, C, E, J, K Accept Zustand: B Überlegen Sie sich : Falls L(X)  für alle X  V, dann enthält jeder nichtleere Zustand ein shift item oder ein reduce item. Der Zustand, der der leeren Menge von items entspricht, kann immer als Fehlerzustand interpretiert werden.

Shift-Reduce Konflikte Enthält ein Zustand Z sowohl ein shift item als auch ein reduce item, so muß evtl. das lookahead entscheiden, was zu tun ist : Z = { ... , A u  , ... ,B  , ... } lookahead = u  shift, lookahead  Follow(B)  reduce Falls u Follow(B) gibt es einen Konflikt ! Man sagt dann: Z hat einen Shift-Reduce Konflikt für u. Pragmatik: Meistens entscheidet man sich bei einem Shift-Reduce Konflikt zugunsten eines shift.

Reduce-Reduce Konflikte Ein Reduce Konflikt kommt zustande, wenn ein Zustand mehrere Reduce items mit nicht disjunkten Follow-Mengen enthält : Z = { ... , A  , ... ,B  , ... } lookahead  Follow(A)  reduce mit A  lookahead  Follow(B)  reduce mit B  Falls u Follow(A) Follow(B) , gibt es einen Konflikt ! Man sagt dann: Z hat einen Reduce-Reduce Konflikt für u. Pragmatik: Reduce-Reduce Konflikte sind unangenehm. Notfalls muß man die Grammatik ändern.

Syntaxfehler ‘*’ or end-of-file expected Ein Syntaxfehler im Input ist beim LR-Parsen immer zum frühestmöglichen Zeitpunkt erkennbar : Angenommen der Parser ist in Zustand Z und das lookahead ist a. Ein Syntaxfehler liegt vor, wenn Z kein shift item A a  enthält und für alle reduce items B in Z gilt : aFollow(B) Beispiel : A = { AT, TTF, TF, F( T ) , Fid } B = { AT, TT F } In Zustand A gibt es den möglichen Syntaxfehler : id or ‘(‘ expected In Zustand B gibt es den möglichen Syntaxfehler : ‘*’ or end-of-file expected

Präzedenz und Assoziativität Sowohl Präzedenz, als auch durch Assoziativität bedingte Mehrdeutigkeit der Grammatik führt zu Shift-Reduce-Konflikten. erzeugt u.a. die Zustände S  E E  E + E | E  E | ( E ) | id E  E + E  E  E + E E  E  E I8 E  E  E  E  E + E E  E  E I7 shift-reduce Konflikt für + und für  . shift-reduce Konflikt für + und für  . Wie sind diese Konflikte zu lösen ? I7 E  E + E  E  E + E E  E  E für  shift ( garantiert Präzedenz von * über  ) für +  reduce ( bewirkt Links-Klammerung von + ) I8 E  E  E  E  E + E E  E  E für  reduce (bewirkt Links-Klammerung von ) für +  reduce (garantiert Präzedenz von * über )

Die Reduktion mit der Startregel, A T wird mit Accept bezeichnet. Parsertabelle In einer Tabelle kann die Aktion des Parsers festgehalten werden. Links steht immer der Zustand und oben ein Terminal oder ein Nonterminal a Q P bedeutet: im Zustand Q bei lookahead a shift und gehe in Zustand P Ein Eintrag a Q r k bedeutet: im Zustand Q bei lookahead a reduce mit Grammatikregel k Ein Eintrag Für ein Reduce mit der Regel A  ist nur interessant : |  | - weil soviele Stackelemente gepoppt werden A - weil anschließend mit Goto(top(stack) ,A) geshiftet wird. Die Reduktion mit der Startregel, A T wird mit Accept bezeichnet.

Parsertabelle Die komplette Information über die Grammatik steckt in der Parsertabelle: (0) A  Term (1) Term  Term  Factor (2) | Factor (3) Factor  id (4) | ( Term ) Grammatik Action id ( ) * T F eof A E D B C B G Accept C r 2 r 2 r 2 D E D H C E r 3 r 3 r 3 F G E D J H K G J r 1 r 1 r 1 K r 4 r 4 r 4 Parser tabelle

LR(0) Parsing ( id  id ) eof  F id E J ) G G G T K id F T E C H H H B A A A A A A A A A A A A A ( id  id ) eof id ( ) * T F eof A E D B C B G Accept C r 2 r 2 r 2 D E D H C E r 3 r 3 r 3 F G E D J H K G J r 1 r 1 r 1 K r 4 r 4 r 4 (0) A  T (1) T  T  F (2) | F (3) F  id (4) | ( T )

yacc Yacc ist ein Parser Generator. Aus einer kontextfreien Grammatik erzeugt er automatisch einen LALR(1) Parser (mächtiger als LR(0)). Der Parser ist ein ausführbares Programm und liegt als C-Funktion yyparse() vor. Typischerweise arbeitet yacc mit mit lex zusammen. lex erkennt die Token, während yacc für die darauf aufbauende Grammatik verantwortlich ist. # yacc Input File für Baby Deutsch %start Satz %Token ARTIKEL NAME HAUPTWORT AUX VERB PUNKT %% Satz : Subjekt Praedikat Objekt PUNKT | Subjekt Praedikat PUNKT ; Subjekt : ARTIKEL HAUPTWORT | NAME Objekt : Subjekt ; Praedikat : AUX VERB ; | VERB | error{“Verb erwartet} Startsymbol Für diese Token ist lex verantwortlich Aktionen werden wie Terminalsymbole behandelt

Das Duo : lex und yacc yacc erzeugt die Funktion yyparse(), lex die Funktion yylex(). yylex() liefert die Nummer des Tokens. Der String aus dem dieses besteht ist immer in der (globalen) Variablen yytext vorhanden. main() 0 falls gültig 1 sonst verlange nächstes token yyparse() Token Nummer, 0 falls eof lese nächstes Zeichen yylex() Input yytext Fig. nach: T.Mason, D. Brown: lex & yacc O’Reilly & Associates, Inc.

Beispiel: Ein Compiler für Expressions Wir wollen einen Compiler bauen, der algebraische Ausdrücke in Postfix-Notation verwandelt. Wir gehen aus von der einfachen Grammatik Expr  Expr + Expr | Expr - Expr | Expr * Expr | Expr / Expr | ( Expr ) | id | num Eine Umwandlung, z.B. zur Beseitigung der Linksrekursion, oder zur Erzwingung der Präzedenzen oder der Linksassoziativität ist nicht nötig. Als erstes spezifizieren wir die Token durch ein lex file .

Das lex file Das lex-file infix_postfix.l . Wir benutzen die Konvertierungsfunktion “sscanf” der Sprache C, um den (aus Ziffern bestehenden) String “yytext” in den entsprechenden Zahlenwert zu verwandeln. Diesen speichern wir in der globalen Variablen “yylval”. Die Integerkonstanten PLUS,MINUS,TIMES,QUOT werden wir in dem zugehörigen yacc-file spezifizieren. letter [a-zA-Z] digit [0-9] %% [ \t]+ ; “+” { return(PLUS) } “-” { return(MINUS) } “*” { return(TIMES) } “/” { return(QUOT) } {digit}+ { sscanf(yytext,”%d”, &yylval); return(NUM)} {letter}({letter}|{digit})* {return(ID)}

Das von lex erzeugte C-Programm wird hier eingefügt. Das yacc file In dem yacc file werden zunächst die token ID, NUM und PLUS, MINUS, TIMES, QUOT deklariert. Die letzteren werden als links-assoziative Operatoren spezifiziert, was den Parser zur richtigen Auflösung der entstehenden shift-reduce Konflikte veranlaßt. Die Reihenfolge (zunächst PLUS, MINUS, danach TIMES, QUOT) bewirkt die gewünschte Präzedenz. Über yylval und yytext kann auf Attribute der Token NUM und ID zugegriffen werden. %token ID, NUM %left PLUS, MINUS %left TIMES, QUOT %start expr %% expr : expr PLUS expr { printf(“add “); } | expr MINUS expr { printf(“sub “); } | expr TIMES expr { printf(“mult “); } | expr QUOT expr { printf(“div “); } | NUM { printf("%d ",yylval);} | ID { printf("%s ",yytext);} ; #include "lex.yy.c" int main(){ printf(“Bitte geben Sie einen Ausdruck ein :\n”); yyparse(); } Das von lex erzeugte C-Programm wird hier eingefügt.

Das fertige C-Programm Aus dem lex file infix_postfix.l erzeugt lex ein C-File lex.yy.c. Das Kommando lautet hier : lex infix_postfix.l Über zusätzliche Optionen (-v, ..) können auch diagnostische Informationen hinzugefügt werden. Aus dem yacc file infix.y erzeugt yacc ein C-File y.tab.c. Das Kommando hierzu ist : yacc infix.y Der C-Compiler erzeugt aus y.tab.c das ausführbare Programm. Hierzu werden beim Linken gewisse Bibliotheksroutinen für lex (-ll) und für yacc (-ly) benötigt. Wenn das fertige Programm in2post heißen soll, lautet das Kommando : cc -o in2post y.tab.c -ll -ly

Das Zusammenspiel von lex & yacc Spezifikation *.y yacc Spezifikation lex yacc lex.yy.c y.tab.c *.c yylex( ) yyparse( ) Zusätzl. C-Routinen UNIX Bibliotheken cc fertiges Programm Fig. nach: T.Mason, D. Brown: lex & yacc O’Reilly & Associates, Inc.

Der Aufbau eines Syntaxprüfers Die Arbeitsweise eines Syntaxprüfers geschieht in 2 Phasen. Diese Phasen können in ihrem Ablauf zeitlich verschachtelt sein. Der Scanner zerlegt das inputfile anhand regulärer Definitionen in eine Reihe von Token. Phase 1: Scannen File of char: b e t r a g : = b e t r a g * ( 1 + z i n s ) Scanner File of token: id assign id * ( intConst + id )

Der Aufbau eines Syntaxprüfers Phase 2: Parsen DerParser versucht die Reihe der Token zu einem Herleitungsbaum zu gruppieren File of token: Parse Tree : id assign assign id id * * Parser id ( ( ) intConst + + id id intConst )

Symboltabelle Ein Compiler besteht aus einem Syntaxprüfer und einer weiteren Stufe, dem Codeerzeuger. Um aus einem Syntaxbaum Code erzeugen zu können, muß aber die Identität einiger Token bekannt sein. Etwa, welche Bezeichner identisch sind, welchen Wert eine intConst hat, etc. Der Scanner legt diese Information in einer Symboltabelle ab und reicht dem Parser nicht nur die Token, sondern auch Zeiger in diese Tabelle weiter. b e t r a g : = * ( 1 + z i n s ) nl betrag zins x test Real Integer Boolean Name Typ 17F4 17F8 201C C011 Sp.Platz Scanner id  assign id  * ( intConst 1 + id  )

Symboltabelle Gewisse Token im Syntaxbaum haben einen Link in die Symboltabelle betrag zins x test Real Integer Boolean Name Typ 17F4 17F8 201C C011 Sp.Platz Parse Tree : assign * id  id  ( ) + intConst 1 id 

Stackprozessor Aus einem Programmtext gilt es, Code für eine einfache Maschine (einen Prozessor) zu erzeugen. Ein einfaches Maschinenmodell ist ein Stackprozessor. Wir denken uns eine Maschine, die im wesentlichen aus einem Stack besteht und damit folgende Operationen ausführen kann : PUSH <num> LOAD <hex> STORE <hex> ADD MULT Lege den Zahlenwert <num> auf dem Stack ab Lege den Inhalt von Adresse <hex> auf dem Stack ab Speichere den Top des Stacks an Adresse <hex>. Der Stack wird dabei gepopped. Ersetze das oberste Element des Stacks durch die Summe der beiden obersten Ersetze das oberste Element des Stacks durch das Produkt der beiden obersten

Code-Erzeugung Aus dem Syntaxbaum kann leicht Code erzeugt werden, etwa für einen Stackprozessor : betrag zins x test Real Integer Boolean Name Typ 17F4 17F8 201C C011 Sp.Platz Code für einen Stackprozessor Parse Tree : LOAD 17F4 PUSH 1 LOAD 17f8 ADD MULT STORE 17F4 assign * id  id  ( ) + intConst 1 id 

Code Erzeugung aus dem Parser Die Ausgabe des Codes kann als semantische Aktion vom Parser veranlaßt werden, die erweiterte Syntax würde etwa lauten : %{ int* loc }% expr : expr + expr { printf("ADD \n"); } | expr * expr { printf("MULT\n"); } | intConst { printf("PUSH "); printf("%d\n",yylval);} | ID { printf("LOAD "); printf(lookup(yytext));} ; stmt : ID { loc=lookup(yytext);} ASSIGN expr { printf("STORE "); printf(&loc); } | ... etc. ... %% #include lex.yy.c ...