Der Scanner führt die lexikalische Analyse des Programms durch Er sammelt (scanned) Zeichen für Zeichen und baut logisch zusammengehörige Zeichenketten (Tokens) aus diesen Zeichen Zur formalen Beschreibung der Tokens, die erkannt werden können, werden reguläre Ausdrücke verwendet Die Tokens gibt der Scanner an den Parser in Form von speziellen Symbolen Die lexikalische Analyse zerlegt das Programm if (x == 0) wert = wert * 2; // blub blub blub else wert = 0; // bla bla bla in: IF SYM BOL ID OP NUM SYM BOL ID OP ID OP NUM SYM BOL ELSE ID OP NUM SYM BOL If ( X == 0 ) wert = wert * 2 ; else wert = 0 ;
Der Scanner muss also die Regeln (reguläre Sprache) kennen, nach denen die Tokens und Schlüsselwörter gebaut werden Hierzu wird ein Zustandsautomat verwendet, der jedes potenziell vorkommende Wort erkennt (akzeptiert) Ein Automat kann unterschiedliche Zustände annehmen Ein Automat geht in Abhängigkeit von gelesenen Eingaben von einem Zustand in einen anderen Zustand über Jeder Zustand repräsentiert eine Zusammenfassung der bisher gelesenen Zeichen Zustandsautomaten werden häufig als Zustandsübergangsdiagramme grafisch repräsentiert
Ein Automat für das Wort haha! h a! >> 1 2 3 4 Der Automat beginnt im Anfangszustand (1); der Lese-Zeiger wird auf das erste Zeichen der Zeichenkette gesetzt Das aktuelle Zeichen wird gelesen und der Lese-Zeiger um 1 inkrementiert Der Automat nimmt den Zustandsübergang, der mit dem gelesenen Zeichen übereinstimmt Er wiederholt die Lese/Übergang-Operation bis er im Endzustand (4) ist h
Bei Eingabe der Zeichenkette haha! h a! >> 1 2 3 4 h
Bei Eingabe der Zeichenkette haha! h a! >> 1 2 3 4 h
Bei Eingabe der Zeichenkette haha! h a! >> 1 2 3 4 h
Bei Eingabe der Zeichenkette haha! h a! >> 1 2 3 4 h
Bei Eingabe der Zeichenkette haha! h a! >> 1 2 3 4 h
Bei Eingabe der Zeichenkette haha! h a! >> 1 2 3 4 h
Wenn der Automat die Eingabe vollständig gelesen hat, gibt es zwei Möglichkeiten: 1) der aktuelle Zustand ist ein Endzustand der Automat hat die Eingabe akzeptiert 2) der aktuelle Zustand ist kein Endzustand der Automat hat die Eingabe nicht akzeptiert Der Automat kann auch in einem beliebigen Zustand halten, weil kein Übergang zum gelesenen Zeichen passt die Eingabe wird ebenfalls nicht akzeptiert
Der (endliche) Automat ist ein Akzeptor für die regulären Ausdrücke ha(ha)*! Die Menge aller Zeichenketten, die von einem Endlichen Automaten A akzeptiert werden, heißt Sprache des Automaten ( L( A ) ). h a! >> 1 2 3 4 h
Bestandteile eines endlichen Automaten h a! >> 1 2 3 4 h 1 3 2 4 h a! 1 2 2 3 3 2 3 4 >> 1 4 Zustände Alphabet Übergänge Anfangszustand Endzustände
Ein (Deterministischer) Endlicher Automat ist ein FŸünf-Tupel < Q,, δ, q 0, F > Endliche, nicht leere Menge von Zuständen Q Eingabealphabet Übergangsfunktion δ (Q ) Q Startzustand q 0 Menge von EndzustŠänden F
Automat zur Erkennung von Schlüsselwörtern und Bezeichner letter symbol blank >> 1 2 3 letter digit Vorsicht: da sowohl ein Leerzeichen als auch ein Zeichen eines neuen Tokens gelesen werden kann, muss der Lese-Zeiger um 1 zurück gesetzt werden
Automat zur Erkennung von Gleitkommazahlen (ohne Dezimalpunkt) digit `e `E + - digit >> 1 2 3 4 5 symbol blank 6 digit digit digit Vorsicht: da sowohl ein Leerzeichen als auch ein Zeichen eines neuen Tokens gelesen werden kann, muss der Lese-Zeiger um 1 zurück gesetzt werden
Konstruktion eines Scanners basierend auf den Übergangsdiagrammen Lesen von Zeichen Auswahl des entsprechenden Übergangs Behandlung für nicht erkannte Zeichenfolgen Zurücksetzen des Lese-Zeigers Auswahl des entsprechenden Übergangs in einem Java-Programm mit dem switch-statement:
Das switch-statement in Java switch (expr) { case const 0 : ss 0 ; ( break; )? case const 1 : ss 1 ; ( break; )? case const k-1 : ss k-1 ; ( break; )? ( default: ss k )? } expr sollte von einem Basistyp sein const i sind Konstanten des jeweiligen Basistyps ss i sind alternative Statement-Folgen
Das switch-statement in Java switch (expr) { case const 0 : ss 0 ; ( break; )? case const 1 : ss 1 ; ( break; )? case const k-1 : ss k-1 ; ( break; )? ( default: ss k )? } default beschreibt den Fall, bei dem keiner der Konstanten zutrifft Fehlt ein break-statement wird mit der Statement-Folge der nächsten Alternative fortgefahren
static boolean scan (char[] programmcode) { int state = S0, term = 0; while ((state!= FAIL) &&!term) { switch (state) { case S0: switch (nextchar(programmcode)) { case 1 : case 2 : case 3 : /*... */ case 9 : state = S1; break; default: state = FAIL; }...
... case S1: switch (nextchar(programmcode)) { case 0 : case 1 : /*... */ case 9 : state = S1; break; case e : case E : state = S2; break; } default: state = FAIL; /*... */ case S2: /*... */
... case S5: switch (nextchar(programmcode)) { case 0 : case 1 : /*... */ case 9 : state = S5; break; } default: term = 1; } /* end switch */ } /* end while */ rewind(programmcode) /* zurücksetzen des Lese-Zeigers */ return term; } /* end scan */
Der Teil der Grammatik, der zur Definition der Symbole der Programmiersprache dient, besitzt reguläre Ableitungen, während der Rest (kompliziertere) kontextfreie Ableitungen besitzt. Es werden die einfacheren Ableitungen durch einen endlichen Automaten erkennen zu lassen, während der (nicht-reguläre) Rest durch eine komplizierteren Automaten erkannt werden muss
Zusammenfassung Die lexikalische Analyse ist ein typisches Anwendungsbeispiel für die Automatentheorie Welche Muster erkannt werden sollen, wird durch reguläre Ausdrücke spezifiziert Mit dem Automat gibt man an, wie ein Muster (ein regulärer Ausdruck) erkannt werden soll Automaten zur Erkennung von regulären Ausdrücken können automatisch konstruiert und simuliert (in ein Programm überführt) werden
Das Unix-Programm lex ist ein Werkzeug für die lexikalische Analyse: lex akzeptiert eine Menge von Mustern, die erweiterte reguläre Ausdrücke und Definitionen darstellen Hieraus produziert lex ein durch Tabellen gesteuertes Programm, das in der Lage ist, Folgen von Zeichen zu erkennen, die auf die Muster passen. Zusätzlich können Aktionen angegeben werden, die bei Erkennung eines Musters ausgeführt werden sollen