Vorlesung Compilertechnik Sommersemester 2009

Slides:



Advertisements
Ähnliche Präsentationen
Vorlesung Compilertechnik Sommersemester 2008
Advertisements

Vorlesung Compilertechnik Sommersemester 2008
Christian Schindelhauer
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
WS Prof. Dr. Th. Ottmann Algorithmentheorie 09 - Suche in Texten KMP, BM.
Institut für Informatik Abt. Intelligente Systeme
8. Formale Sprachen und Grammatiken
10. Grundlagen imperativer Programmiersprachen
Modelle und Methoden der Linearen und Nichtlinearen Optimierung (Ausgewählte Methoden und Fallstudien) U N I V E R S I T Ä T H A M B U R G November 2011.
Technische Universität Dortmund
Imperative Programmierung
Grammatiken, Definitionen
Christian Schindelhauer
Kapitel 4 Syntaktische Analyse: LR Parsing.
Parser generieren Yet Another Compiler – Compiler YACC.
Java: Objektorientierte Programmierung
FH-Hof Grammatiken Richard Göbel. FH-Hof Begriffe Eine Grammatik definiert die Struktur (Syntax) einer Zeichenkette Eine Grammatik definiert nicht die.
Parser für CH3-Sprachen
Suche in Texten: Suffix-Bäume
WS Algorithmentheorie 02 - Polynomprodukt und Fast Fourier Transformation Prof. Dr. Th. Ottmann.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Tobias Lauer.
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (02 – Endliche Automaten) Prof. Dr. Th. Ottmann.
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 (12 – Kellerautomaten, PDA) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (27 – Kürzeste Wege) Prof. Th. Ottmann.
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (04 – Automaten mit ε-Transitionen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (03 – Nichtdeterminierte endliche Automaten) Prof. Dr. Th. Ottmann.
WS Prof. Dr. Th. Ottmann Algorithmentheorie 09 - Suche in Texten Suffix - Bäume.
Vorlesung Informatik 3 Einführung in die Theoretische Informatik (17 –Turingmaschinen) Prof. Dr. Th. Ottmann.
Grundkurs Theoretische Informatik, Folie 2.1 © 2006 G. Vossen,K.-U. Witt Grundkurs Theoretische Informatik Kapitel 2 Gottfried Vossen Kurt-Ulrich Witt.
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Kapitel 9 Claudio Moraga; Gisbert Dittrich FBI Unido
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
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
PKJ 2005/1 Stefan Dissmann Rückblick auf 2005 Was zuletzt in 2005 vorgestellt wurde: Klassen mit Attributen, Methoden und Konstruktoren Referenzen auf.
PKJ 2005/1 Stefan Dissmann Zusammenfassung Bisher im Kurs erarbeitete Konzepte(1): Umgang mit einfachen Datentypen Umgang mit Feldern Umgang mit Referenzen.
Zusammenfassung Vorwoche
High Performance = Innovative Computer Systems + Efficient Algorithms Friedhelm Meyer auf der Heide 1 HEINZ NIXDORF INSTITUT Universität Paderborn Algorithmen.
Christian Schindelhauer
Christian Schindelhauer
20:00.
Grenzen der Regularität
Javakurs FSS 2012 Lehrstuhl Stuckenschmidt
Christian Schindelhauer Wintersemester 2006/07 8. Vorlesung
Syntaxanalyse Bottom-Up und LR(0)
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Vorlesung Mai 2000 Konstruktion des Voronoi-Diagramms II
Formale Sprachen Reguläre Sprachen Rudolf FREUND, Marian KOGLER.
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
1 (C)2006, Hermann Knoll, HTW Chur, FHO Quadratische Reste Definitionen: Quadratischer Rest Quadratwurzel Anwendungen.
Schutzvermerk nach DIN 34 beachten 20/05/14 Seite 1 Grundlagen XSoft Lösung :Logische Grundschaltung IEC-Grundlagen und logische Verknüpfungen.
1 Albert-Ludwigs-Universität Freiburg Rechnernetze und Telematik Prof. Dr. Christian Schindelhauer Informatik III Christian Schindelhauer Wintersemester.
1 Albert-Ludwigs-Universität Freiburg Rechnernetze und Telematik Prof. Dr. Christian Schindelhauer Informatik III Christian Schindelhauer Wintersemester.
Christian Schindelhauer Wintersemester 2006/07 3. Vorlesung
Arne Vater Wintersemester 2006/ Vorlesung
Christian Schindelhauer Wintersemester 2006/07 2. Vorlesung
1 Albert-Ludwigs-Universität Freiburg Rechnernetze und Telematik Prof. Dr. Christian Schindelhauer Informatik III Christian Schindelhauer Wintersemester.
Mensch – Maschine - Kommunikation
Wintersemester 2005 / Vorlesung
7. Formale Sprachen und Grammatiken
1 Medienpädagogischer Forschungsverbund Südwest KIM-Studie 2014 Landesanstalt für Kommunikation Baden-Württemberg (LFK) Landeszentrale für Medien und Kommunikation.
Inhalt Einordnung und Funktion der lexikalische Analyse Grundlagen
Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar?
 Sortigkeit oder Arität
 Präsentation transkript:

Vorlesung Compilertechnik Sommersemester 2009 Lexikalische Analyse M. Schölzel

Aufgabe des Scanners Grundlage ist eine endliche Menge von regulären Sprachen (Morphemarten). Strukturierung der Zeichen des Quelltextes zu Morphemen, wobei jedes Morphem zu (genau) einer regulären Sprache gehören muss. f o r i d = t o 1 d o \n j : = 6 * i d \n o d \n Scanner, fasst Zeichen zu Morphemen zusammen … for id = to 10 do \n Schlüsselworte j := 6 * id \n Bezeichner od \n Operatoren … und reicht Token an den Parser weiter, wobei beim Parsing nur die Morphemart beachtet wird. Integerliterale (Schlüsselwort,for),(Bezeichner,id),(Operator,=),(Integerliteral,0), (Schlüsselwort,to), (Integerliteral,10),… Trenner

Gestaltung der Morphemarten Problem in diesem Beispiel: Schlüsselworte sind nicht mehr für den Parser unterscheidbar! Besser: Jedes Schlüsselwort bildet eine eigene Morphemart, die nur dieses Schlüsselwort als Element enthält. Allgemein: Eine Morphemart sollte nur solche Morpheme enthalten, die während der syntaktischen Analyse wie ein Terminalsymbol behandelt werden können und deren spezifische Bedeutung nicht relevant für die Erzeugung des Syntaxbaums ist. while … do if … then … … od fi

Typische Morphemarten Bezeichner Integerliterale Gleitkommaliterale Zeichenkettenliterale Relationale Operatoren Eine Morphemart für jeden arithmetischen/logischen/bitweisen Operator Eine Morphemart für jedes Punktierungssymbol (Klammern, Semikolon, Komma,…) Kommentare (werden oft vom Scanner nicht an den Parser weitergereicht) Trenner (werden im Allgemeinen nicht an den Parser weitergereicht) …

Präzisierung der Begrifflichkeiten Eine Morphemart ist eine reguläre Wortmenge. Ein Morphem (auch Lexem) ist ein Element einer Morphemart. Ein Morphem entspricht damit einer im Quelltext durch den Scanner erkannten Zeichenkette. Ein Token ist ein Zweitupel bestehend aus einer Morphemart und einer dem erkannten Morphem zugeordneten Bedeutung. Der Scanner liefert an den Parser Token, von denen der Parser während der Analysephase nur die Morphemart nutzt. In späteren Phasen wird auch die Bedeutung benötigt. Die Morphemarten entsprechen damit den Terminalsymbolen der Grammatik, auf der der Parser basiert.

Vereinbarungen zur BNF-Schreibweise Unterteilung in kontextfreie und lexikalische Syntax durch: Metasymbole beginnen mit einem Großbuchstaben und sind nicht kursiv. Terminalsymbole werden dargestellt durch in Gänsefüßchen eingeschlossene Zeichen/Zeichenketten oder ein Zeichen, das kein Großbuchstabe ist, falls dieses Zeichen das einzige Morphem seiner Art ist oder kursiv gesetzte Zeichenketten, falls diese Zeichenkette eine Morphemart mit mehr als einem Morphem repräsentiert. Beispiel: Kontextfreie Syntax: REG ::= ALT ALT ::= KON | KON "|" ALT KON ::= SYM | SYM . KON SYM ::= Bezeichner |  | ( REG ) | ( REG )* Lexikalische Syntax: Bezeichner := *

Einbettung des Scanners in den Compiler Der Scanner erhält den gesamten Quelltext als Eingabe und stellt die Funktion nextMor bereit, bei deren Aufruf: ein Präfix der noch nicht verarbeiteten Eingabe hinsichtlich seiner Morphemart klassifiziert und die Morphemart sowie die erkannte Zeichenkette (oder deren Bedeutung) an den Parser in Form eines Tokens übergeben werden, die Eingabe um den erkannten Präfix gekürzt wird, so das beim nächsten Aufruf von nextMor diese Zeichen nicht mehr betrachtet werden, der Scanner einen Fehler erzeugt, wenn kein Präfix einer Morphemart zugeordnet werden kann Aufruf von nextMor Scanner Parser Quell-text Token Rückgabe eines Tokens Frontend

Abgrenzung Scanner und Parser Ohne Scanner müsste der Parser die Verarbeitung aller Zeichen übernehmen: Mit Scanner kann der Parser mit Morphemarten als Terminalsymbole arbeiten: Atom ::= Number | Ident | ... Ident ::= Letter | Ident Letter Number ::= Digit | Digit Number Letter ::= "a" | ... | "z" | "A" | ... | "Z" Digit ::= "0" | ... | "9" Kontextfreie Syntax Atom ::= Number | Ident | ... Lexikalische Syntax: Number := {0,1,2,3,4,5,6,7,8,9}+ Ident := {a,…,z,A,…,Z}+

Gründe für separate Implementierung des Scanners Vereinfachung des Parsers durch Verlagerung spezifischer Aufgaben in den Scanner (z.B. Elimination von Trennern und Kommentaren) Erhöhung der Verarbeitungsgeschwindigkeit durch spezialisierte Techniken im Scanner. Kapselung von I/O-Operationen im Scanner. Dadurch Erhöhung der Portabilität des Compilers Grundsätzlich könnte aber auch der Parser die Aufgabe des Scanners übernehmen

Theoretisches Modell für den Scanner Endlicher Automat A = (Z, , z, F, ) mit: Z – endliche Zustandsmenge  – endliches Eingabealphabet z  Z – Startzustand F  Z – Menge von Endzuständen   Z    Z – Überführungsrelation Situation (p,)  Z  * gekennzeichnet durch: p – aktueller Zustand  – anliegende Eingabe (wird von links nach rechts gelesen) Situationsübergänge:  := {((p,a), (q,)) | a   und   * und (p,a,q)  } oder kurz: (p,a)  (q,) Akzeptierte Sprache: L(A) = { | (z,) * (q,) und q  F} Dieses Modell dient nur der Spezifikation der Morphemarten. Zur Verwendung bei der Implementierung des Scanners so oft nicht eingesetzt, da dort auf "einfache Weise" entschieden werden muss, wann ein Morphem endet.

Determinisierung eines endlichen Automaten Ein endlicher Automat ist deterministisch, falls für alle a   und z  Z gilt, dass aus (z,a,q)   und (z,a,p)   folgt, dass q = p. Gegeben ist ein nichtdeterministischer Automat A = (ZA,,zA,FA,A) Ein deterministischer Automat D = (ZD,,{zA},FD,D) mit L(D) = L(A) wird konstruiert durch die bzgl. Mengeninklusion kleinsten Mengen ZD und D für die gilt ZD = ZD  {{zA}}  {{p | (r,a,p)  D und r  q}| a   und q  ZD} D = D  {(q,a,p) | q,p  ZD und a   und (z,a,z')  A, wobei z  q und z'  p} Außerdem: FD := {q | q  ZD und q  FA  }

Determinisierung eines Beispielautomaten 1 a 3 a b b b a a a b 2 b 4 b b

Erkennung der Morpheme mit einem endlichen Automaten Spezifikation der endlichen Automaten A1,…,An zur Erkennung der Morphemarten L(A1),…,L(An) Der Scanner der Quellsprache Q muss dann folgende Sprache erkennen: L = {w0w1…wk | ij: 0  i  k  1  j  n und wi  L(Aj)} Konstruktion des Automaten A = (Z, , z, F, ) mit L(A) = L aus den Automaten Ai = (Zi, , zi, Fi, i) : Z ist Vereinigung der disjunkten Mengen Z1,…,Zn z ist neuer Startzustand F := F1  …  Fn  := 1  …  n  {(z,a,z') | i: (zi,a,z')  i}  {(z,a,z') | ij : z  Fi und (zj,a,z')  j} Simulation des Laufs von A bei gegebener Eingabe w Problem: Erkennung der wi in w

Determinismus vs. Nichtdeterminismus Zwei Möglichkeiten zur Simulation eines endlichen Automaten A: Konstruktion eines deterministischen endlichen Automaten A' zu A. Verarbeitung der Eingabe damit in Echtzeit möglich. Simulation des nichtdeterministischen Automaten A. Verarbeitung der Eingabe durch gleichzeitige Simulation aller möglichen Pfade. Als akzeptierender Lauf eines Automaten A = (Z, , z, F, ) bei Eingabe w = a0…an wird eine Folge von Zuständen z0,z1,…,z|w| bezeichnet, für die gilt: z0 = z ist Startzustand z|w|  F Für alle 0  i  |w|-1: (zi,ai,zi+1)  

Simulation eines deterministischen Automaten Speichern des aktuellen Zustands in einer Variablen z Der Startzustand sei 0 Nächstes Eingabesymbol wird durch nextChar() abgerufen (Rückgabe = 0, falls kein weiteres Zeichen existiert) Folgezustand ist durch nächstes Eingabesymbol eindeutig bestimmt: Lauf := 0 z := 0 while(c = nextChar()) do if p: (z,c,p)   then z := p else Error Lauf := Lauf  z od If z  F then Akzeptieren else Verwerfen

Simulation eines nichtdeterministischen Automaten Verwendung zweier Stapelspeicher Abwechselnd speichern Stapel 1 und Stapel 2 die möglichen Zustände, in denen sich der nichtdeterminsitische Automat befinden kann: s1 : stack s2 : stack Lauf := {0} s1.push(0) while(c = nextChar()) do nz :=  while(s1 nicht leer) do p := s1.pop Für alle q  Z für die ein (p,c,q)   existiert do s2.push(q) nz := nz  {q} od Lauf := Lauf  nz Tausche Inhalte von s1 und s2 If s1 enthält einen Endzustand then Akzeptieren else Verwerfen

Algorithmus zur Rekonstruktion der Morphemarten Es sei Lauf = z0…zm die Folge der Stapelinhalte eines simulierten Laufs bei der Eingabe w1…wm (wi  ) des Automaten der aus den Einzelautomaten Ai = (,Zi,si,Fi,i) konstruiert wurde: morEnde := m morArt := k und mor := z, wobei z  zm und z  Fk for i := m downto 1 do if((k'z': z'  zi-1 und z'  Fk' und (sk,wi,mor)  k) oder (i=1 und (sk,wi,mor)  k)) then wi…wmorEnde ist erkanntes Morphem morEnde := i-1 morArt := k' mor := z else Es sei z'  zi-1 und z'  Zk und (z',wi,z)k mor := z' fi od

Motivation der Modifikationen des Automatenmodells Das Automatenmodell für den Scanner muss von einer Zeichenkette einen Präfix als Morphem erkennen. Es muss also entschieden werden, wann das Ende eines Morphems erreicht ist: Für Morphemarten mit explizitem Abschluss einfach; Ende wird am letzten zum Morphem gehörenden Zeichen erkannt, z.B. Zeichenkettenliterale in Gänsefüßchen. Für Morphemarten mit implizitem Abschluss schwieriger; Ende wird erst erkannt wenn Zeichen gesehen wurden, die auf das Morphem folgen. Deshalb Einschränkung auf den Fall: Ende ist erkennbar, wenn das nächste, nicht zum Morphem gehörende Zeichen gesehen wurde. Vereinheitlichung: Morphemarten mit explizitem Abschluss werden wie Morphemarten mit implizitem Abschluss behandelt. Konsequenzen: Der modifizierte Automat arbeitet mit einem „Look-Ahead“-Symbol. Beim Übergang in einen akzeptierenden Endzustand darf das anliegende Look-Ahead-Symbol nicht gelesen werden. Um am Ende der Eingabe ein Morphem mit implizitem Abschluss zu erkennen, wird an die Eingabe ein spezielles Endesymbol  angehangen, das die Morphemart {} bildet. Der Startzustand ist niemals ein Endzustand, weil in der Praxis die Erkennung des leeren Wortes zu einer Endlosschleife im Scanner führen würde.

Modifizierter deterministischer endlicher Automat für eine Morphemart Wir betrachten reduzierte deterministische endliche Automaten, d.h. jeder Zustand ist vom Startzustand aus erreichbar und von jedem Nicht-Endzustand ist ein Endzustand erreichbar. Zulässige Situationsübergänge: (p,a)  (q,), falls q  F und (p,a,q)   (p,a)  (q,a), falls q  F und (p,a,q)   Die akzeptierten Morpheme eines Automaten sind dann { |   (  {})+: (z,) * (q,) und q  F} Grundsätzlich kann die lexikalische Analyse des gesamten Textes durch einen einzigen Automaten zur Erkennung aller Morphemarten oder mehrere Automaten, von denen jeder genau eine Morphemart erkennt, durchgeführt werden. Grundlage für ein rein mechanisches Vorgehen bei der Konstruktion eines Scanners ist jedoch meistens die separate Konstruktion der Automaten für jede Morphemart.

Transformation in modifizierten Automaten Ein deterministischer endlicher Automat A = (ZA,,zA,FA,A) kann in einen modifizierten Automaten M = (ZM,  {},zA,FM,M) transformiert werden durch: ZM := ZA  {e}, wobei e  ZA M := A  {(q,a,e) | q  FA und p  ZA: (q,a,p)  A} FM := {e} M ist wieder deterministisch. Da beim Übergang in den Endzustand das bis dahin erkannte Morphem  nicht mit dem Look-Ahead-Symbol a fortgesetzt werden kann, ist a nicht Präfix eines Wortes der Sprache L(A).

Beispiel: Relationale Operatoren An eine Kante dürfen auch mehrere Zeichen geschrieben werden Alphabet sei X mit der Menge aller druckbaren ASCII-Zeichen und dem Ende-Symbol . < = < = 1 2 1 2 x > > = 3 = 3 x x - {=,>} > > 4 x 5 5 x - {=} x = 6 7 6 7 =

Beispiel: Bezeichner {a,…,z,A,…,Z,0…,9} {a,…,z,A,…,Z} 1 1 {a,…,z,A,…,Z,0…,9} {a,…,z,A,…,Z} 1 2 X - {a,…,z,A,…,Z,0…,9}

Beispiel: Schlüsselworte und Trenner f X i f 1 2 3 1 2 t h e n t h e n X 1 2 3 4 1 2 3 4 5 e l s e e l s e X 1 2 3 4 1 2 3 4 5 {Blank, Tab, Newline}  1 {Blank, Tab, Newline} 1  X 1 2 {Blank, Tab, Newline} {Blank, Tab, Newline} 1 2 X - {Blank Tab, Newline}

Beispiel: Gleitkommazahlen {0,…,9} {0,…,9} {0,…,9} {0,…,9} {0,…,9} {0,…,9} . E {+,-} 1 2 3 4 5 6 {0,…,9} E {0,…,9} 7 X - {0,…,9,E} 8 X - {0,…,9} X - {0,…,9} {0,…,9} {0,…,9} {0,…,9} {0,…,9} {0,…,9} . {+,-} 1 2 3 4 5 6 {0,…,9} E E {0,…,9} {0,…,9} 7

Eigenschaften der Transformation in modifizierten Automaten Es sei M ein modifizierter Automat, der aus dem deterministischen Automaten A erzeugt wurde. Falls   L(M), dann ist auch   L(A). Falls   L(A), dann auch   L(M) (in jedem Fall für die Fortsetzung ) Aber: Nicht für jede Fortsetzung wird ein Morphem   L(A) erkannt Zum Beispiel: Modifizierter Automat für {-,<,-->} läuft bei Eingabe ---> in Sackgasse Ähnliche Fälle treten in realen Programmiersprachen auf; Beispiel aus C: a = b+++++c; Scanner benötigt Trenner für korrekte Erkennung der Morpheme + und ++ Schwierigkeit: Modifizierter Automat muss des Ende eines Morphems erkennen Mit einem Lookahead von 1 kann der transformierte Automat diese Entscheidung nicht immer treffen und kann in eine Sackgasse laufen. Lösung, die manchmal hilft: Zerlegung einer Morphemart in mehrere Morphemarten; man erhält mehrere Automaten Generelles Problem: Zerlegung der Eingabe in eine Folge von Morphemen durch Simulation eines Automaten je Morphemart

Zerlegung der Eingabe in Morpheme Variante 1 Die einzelnen Automaten werden sequentiell mit derselben Eingabe simuliert Beim ersten akzeptierenden Automaten wird die Simulation beendet und das erkannte Morphem an den Parser übergeben. Problem: Reihenfolge kann wichtig sein: z.B. wenn der Präfix eines Bezeichners ein Schlüsselwort ist Variante 2 Die einzelnen Automaten werden parallel simuliert Der Automat, der die längste Eingabe akzeptiert hat, legt das Token für den Parser fest Variante 3 Aus den einzelnen Automaten A1,…,An wird ein Automat konstruiert, der die Sprache L(A1)  …  L(An) akzeptiert und ein Wort nur dann als Morphem akzeptiert, wenn dieses nicht echter Präfix eines anderen akzeptierten Wortes ist. Dadurch wird es beispielsweise möglich --> auch dann zu erkennen, wenn auch – erkannt werden soll.

Variante 1 (Sequentielle Simulation) Es seien A1,…,An die modifizierten deterministischen Automaten für die Morphemarten M1,…,Mn. Die Funktion nextMor ist dann definiert als: Der nächste Aufruf der Funktion nextMor durch den Parser erfolgt dann mit der Zeichenkette , wobei  = .

Variante 2 (Parallele Simulation) Es seien A1,…,An die modifizierten deterministischen Automaten für die Morphemarten M1,…,Mn. Die Funktion nextMor ist dann definiert als: Der nächste Aufruf der Funktion nextMor durch den Parser erfolgt dann mit der Zeichenkette , wobei  = .

Variante 3 (voll-parallele Simulation) Es seien Ai = (Zi, , zi, Fi, i) die deterministischen (unmodifizierten) Automaten für die Morphemarten Mi mit 1  i  n. Es wird ein neuer Automat A = (Z, , z, F, ) konstruiert: Z := Z1  …  Zn  {z}  := 1  …  n  {(z,a,p) | ia: (zi,a,p)  i} F := F1  …  Fn A in einen deterministischen Automaten D umgewandelt

Variante 3 (Fortsetzung) Für jeden Zustand q von D gilt: i: |q  Zi|  1. Ein Lauf von D ist also eine parallele Simulation der Automaten Ai. Ein Präfix  eines Wortes  wird als Morphem der Art i akzeptiert, wenn D durch Lesen von  in den Zustand q gelangt und q  Fi   und von q kein akzeptierender Zustand mit einem beliebigen nichtleeren Präfix von  erreichbar ist. Insbesondere für die Realisierung des letzten Punktes muss in der Implementierung des Automaten D oft mehr von der Eingabe gelesen werden, als zum akzeptierten Morphem gehört. D.h. es werden solange Eingabezeichen verarbeitet, wie ein Situationsübergang möglich ist und der letzte akzeptierende Zustand gespeichert. Ein Fehler tritt auf, wenn kein Situationsübergang mehr möglich ist und bis dahin auch kein akzeptierender Zustand erreicht wurde.

Implementierung eines Scanners Manuell Entweder syntaxgebunden nach Variante 1 oder 2: Kodierung der det. modifizierten Automaten als Quelltext einer Programmiersprache nur für sehr kleine Scanner geeignet Modifikation des Verhaltens sehr schwierig oder syntaxgesteuert nach Variante 1 oder 2: Det. modifizierte Automaten werden durch eine Tabelle gesteuert Nur für kleine Scanner geeignet Einfache Modifikation des Verhaltens oder individuell an die Morpheme angepasst Werkzeugunterstütz durch einen Scannergenerator Spezifikation der Morphemarten und Morpheme (z.B. durch reguläre Ausdrücke) Exemplarisch betrachten wir Flex (arbeitet nach Variante 3)

Syntaxgebundene manuelle Implementierung int isRelOp(int pos) { int state = 0; // Startzustand while(1) { switch(state) { case 0: switch(currChar(pos)) { case '<': state = 1; pos = moveNext(pos); break; case '=': state = 5; pos = moveNext(pos); break; case '>': state = 6; pos = moveNext(pos); break; default : return 0; }; break; case 1: switch(currChar(pos)) { case '=' : state = 2; pos = moveNext(pos); break; case '>' : state = 3; pos = moveNext(pos); break; default : state = 4; break; case 2: state = 4; break; case 3: state = 4; break; ... case 4: return pos; } 1 2 < = 3 > x - {=,>} x 4 5 6 7 x - {=} Vollständiger Quelltext Ausführbares Programm

Steuertabelle für einen Beispielautomaten Modifizierter det. endlicher Automat für die Morphemart RelOp Erkennung der Morpheme {<=, <>, <, =, >=, >} < = 1 2 x > = 3 < = > X-{<,=,>} 1 5 6 Error 4 2 3 Accept 7 x x - {=,>} > 4 x 5 x - {=} x 6 7 =

Syntaxgesteuerte manuelle Implementierung #define Err -1 #define Acc -2 int transTableRelOp[][4] = {{1 ,5 ,6 ,Err}, // Zustand 0 {4 ,2 ,3 ,4 }, // Zustand 1 {4 ,4 ,4 ,4 }, // Zustand 2 {4 ,4 ,4 ,4 }, // Zustand 3 {Acc,Acc,Acc,Acc}, // Zustand 4 {4 ,4 ,4 ,4 }, // Zustand 5 {4 ,7 ,4 ,4 }, // Zustand 6 {4 ,4 ,4 ,4 } // Zustand 7 }; int mapChar(char c) { switch(char) { case '<': return 0; case '=': return 1; case '>': return 2; default : return 3; } int isRelOp(int pos) { int state = 0; // Startzustand while(state != Err && transTableRelOp[state][0] != Acc) { state = transTableRelOp[state][mapRelOp(currChar(pos))]; if(state != Err && transTableRelOp[state][0] != Acc) pos = moveNext(pos); } if(state == Err) return 0; //Fehler return pos; Vollständiger Quelltext Ausführbares Programm

Verarbeitung der Eingabe (sequentiell) Für jede Tokenart T1 bis Tn existiert eine Funktionen der Form int isT1(int pos); ...; int isTn(int pos); Verarbeitung der Eingabe durch: char* inputBuff; int pos; nextMor(){ TToken t; int nextPos; if(nextPos = isSep(pos)) pos = nextPos; if(nextPos = isT1(pos)) { t.type = T1; cpy(&t.val,&inputBuff[pos],nextPos-pos); return t; } ... ... if(nextPos = isTn(pos)) { t.type = Tn; cpy(&t.val,&inputBuff[pos],nextPos-pos); pos = nextPos; return t; } exit(1); // Fehler Vollständiger Quelltext Ausführbares Programm

Verarbeitung der Eingabe (parallel) Für jede Tokenart T1 bis Tn existiert eine Funktionen der Form int isT1(int pos); ...; int isTn(int pos); Verarbeitung der Eingabe durch: char* inputBuff; int pos; nextMor(){ TToken t; int nextPos; int maxPos = 0; if(nextPos = isT1(pos) && nextPos > maxPos) { t.type = T1; cpy(t.val,&inputBuff[pos],nextPos-pos); maxPos = nextPos } ... if(nextPos = isTn(pos) && nextPos > maxPos) { t.type = Tn; maxPos = nextPos; if(maxPos == 0) exit(1); // Fehler pos = nextPos; return t; Vollständiger Quelltext Ausführbares Programm

Lex Generierung des C-Quelltextes eines Scanners. Spezifiziert werden die Morphemarten durch reguläre Ausdrücke. Jede Morphemart kann mit einer Aktion in Form von C-Quelltext annotiert werden, die bei der Erkennung eines Morphems dieser Art ausgeführt wird. Lex- Quelltext name.l Lex- Aufruf: lex name.l Scanner als C-Datei: Lex.yy.c C-Compiler Aufruf: gcc lex.yy.c Ausführ-bare Datei: a.exe

Struktur eines Lex-Quelltextes Deklarationen %% Regeln C-Funktionen Deklarationssektion Deklaration von Bezeichnern des C-Programms Deklaration von Morphemarten Deklaration von regulären Ausdrücken Regel-Sektion Besteht aus einer Folge von: Muster {Aktion} Muster definiert eine Morphemart durch einen regulären Ausdruck Aktion ist C-Quelltext, der ausgeführt wird, wenn der Scanner ein Morphem der entsprechenden Art erkannt hat C-Funktionssektion Deklaration von C-Funktionen, die in den generierten Quelltext kopiert werden

Ablauf der Generierung einer C-Datei … … %% Muster 1 {Aktion 1} Muster n {Aktion n} … … NFA für Muster 1 … … DFA … NFA für Muster n Steuer- tabelle des DFA Simulator für DFA Scanner als C-Datei: Lex.yy.c Anfang Look-Ahead Eingabepuffer

Reguläre Ausdrücke Reguläre Ausdrücke Jedes Zeichen a   ist ein regulärer Ausdruck für die Sprache {a}. Wenn  und  reguläre Ausdrücke für die Sprachen L() und L() sind, dann ist:  |  ein regulärer Ausdruck für die Sprache L()  L()    ein regulärer Ausdruck für die Sprache L()  L() * ein regulärer Ausdruck für die Sprache L()* Außerdem erlauben wir die Klammerung eines regulären Ausdrucks Wir legen fest, dass in regulären Ausdrücken der Operator * die höchste,  die zweithöchste und  die niedrigste Priorität hat Wir verzichten auf den regulären Ausdruck für die leere Sprache

Beispiel für reguläre Ausdrücke Es sei: Digit := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 Digits := (Digit)  (Digit)* Letter := a | … | z | A | … | Z Die Morpheme sind dann definiert als: Bezeichner := (Letter)  (Letter)* FNumber := Digits  ((.  Digits) | (((.  Digits  E)| E)  ((+ | -)  Digits | Digits))) RelOp := < | > | = | (<  =) | (>  =) | (<  >)  := EOF Sep := Blank | Tab | CR If := i  f Then := t  h  e  n Else := e  l  s  e

Transformation eines regulären Ausdrucks in einen Automaten Das Prinzip sollte aus der Theoretischen Informatik bekannt sein. Wir wiederholen nur die Transformation ohne Korrektheitsbeweis. Wir definieren eine Funktion RegToAut als:

Automaten zur Erkennung eines Zeichens Automat für den regulären Ausdruck a mit a  : ({0,1},,0,{1},{(0,a,1)}) a 1

Vereinigung zweier Automaten (Z, , z, F, ) := Union((Z, , z, F, ),(Z, , z, F, )) ist definiert als: Zustandsmengen Z und Z disjunkt machen, danach folgende Mengen erzeugen: Z := Z  Z  {z}, wobei z  Z  Z  :=     {(z,a,p) | (z,a,p)   oder (z,a,p)  } F := F  F  {z | z  F oder z  F} a a 1 1 1 a a 4 4 L = {a} b b b 2 3 3 a b 1 b L = {a,b} c L = {b} c c 1 5 6 L = {c} L = {a,b,c}

Kleene-Abschluss (Z, , z, F, ) := Kleene((Z,,z,F,)) ist definiert als: Z := Z  :=   {(p,a,q) | p  F und (z,a,q)  } F := F  {z} a 1 a b a 1 a b 3 b c a b 3 c c b c 6 6 L = {a,b,c} c L = {a,b,c}*

Verkettung zweier Automaten (Z, , z, F, ) := Concat((Z, , z, F, ),(Z, , z, F, )) ist definiert als: Zustandsmengen Z und Z disjunkt machen, danach folgende Mengen erzeugen: Z := Z  Z  :=     {(p,a,q) | p  F und (z,a,q)  } F := F  {p | p  F und z  F} a a a 1 1 b a a b a c b a 3 b b 3 b c a c c c b b 6 c 6 L = {a,b,c} c c L = {a,b,c}  {a,b,c}* L = {a,b,c}*

Automatenmodell in Lex Erzeugung eines Automaten für alle n Morphemarten entsprechend Variante 3 durch: Union(A1,Union(A2,Union(…,Union(An-1,An)…)) Von Union konstruierte Automaten sind im Allgemeinen nichtdeterministisch Transformation in deterministischen Automaten D Simulation von D entsprechend Variante 3 Damit ergeben sich Prioritäten wie folgt: Scanner erkennt Morpheme maximaler Länge Gibt es mehrere Morpheme maximaler Länge, dann wird das erkannt, dessen Muster zuerst spezifiziert wurde

Wichtige Operatoren zur Bildung Regulärer Ausdrücke in Flex Matched mit . jedem Zeichen \n Newline Exp1Exp2 Exp1 gefolgt von Exp2 Exp* Kleene-Abschluss von Exp Exp+ einer nicht leeren Folge von Ausdrücken, die mit Exp matchen Exp?  oder Exp ^ Beginn einer neuen Zeile $ Ende einer Zeile Exp1 | Exp2 Exp1 oder Exp2 "text" mit der Zeichenkette text [] einem der Zeichen zwischen den eckigen Klammern oder jedem Zeichen, das nicht zwischen den eckigen Klammern steht, wenn das erste Zeichen ^ ist. Bereichsangaben sind mit – möglich. () Klammerung \x dem Zeichen x. Falls das Zeichen x eine besondere Bedeutung hat, wird diese dadurch aufgehoben

Wichtige Funktionen und Variablen in Flex Wirkung int yylex(void) Entspricht nextMor; Rückgabewert ist die Morphemart char* yytext Zeiger auf das erkannte Morphem int yylen Länge des erkannten Morphems YYSTYPE yylval Bedeutung des erkannten Morphems int yywrap(void) Rückgabe 0: Scannen fortsetzen; Neue Datei sollte geöffnet werden Rückgabe 1: Scannen beenden FILE* yyin Eingabestrom des Scanners typedef union YYSTYPE Typdefinition für Morphembedeutungen

Einbinden eines Flex-Scanners in ein Projekt Eingabe öffnen und yyin zuweisen. Aufruf der Funktion yylex, um das nächste Token anzufordern. Rückgabe von yylex ist die Morphemart (entspricht dem Wert in der return-Anweisung der Aktion des matchenden Musters) Bedeutung des Morphems ist in yylval gespeichert (muss ebenfalls durch die Aktion veranlasst werden). Auf die textuelle Darstellung kann mittels yytext zugegriffen werden (wird vom Scanner bereitgestellt).

Beispiel: Lex-Datei [Quelltext] [Ausführbare Datei] digit [0-9] digits {digit}+ letter [a-zA-Z] %% [\t \n]+ {/*Keine Aktion, dadurch werden Morpheme dieser Art überlesen*/}; {digits}((\.{digits})|(((\.{digits}E)|E)((\+|\-){digit}|{digit}){digit}*)) {return 1;} if {return 1;} then {return 1;} else {return 1;} {letter}({digit}|{letter})* {return 1;} "<" {return 1;} "<=" {return 1;} "=" {return 1;} "<>" {return 1;} ">" {return 1;} ">=" {return 1;} . {return 0; /*Zur Erkennung von Fehlern bei der Spezifikation der Ausdrücke*/} #include <stdio.h> int main(int argc, char* argv[]) { while(yylex()) printf("Erkannt: %s\n", yytext); } int yywrap() return 1; [Quelltext] [Ausführbare Datei]

Möglichkeiten/Grenzen von Flex Eingangsbeispiel {-, -->, <} [Quelltext] [Ausführbare Datei] Die in flex zu Grunde liegende Semantik der Simulation von Automaten kann unerwünschte Effekte verursachen: Kommentare Falsch: "(*".*"*)" Richtig: "{*"([^*\n]*\*+)([^}*\n][^*\n]*\*+)*"}" [Quelltext] [Ausführbare Datei] Morphemart {aa, aaa} [Quelltext] [Ausführbare Datei]

Ende der lexikalischen Analyse Weiter zur syntaktischen Analyse