Parser und Compiler ■ Sie wissen, wie ein Compiler und Parser funktioniert ■ Sie können eine Übergangstabelle entwickeln ■ Sie können einen endlichen Automaten zur Überprüfung eines Satzes erstellen Der Übersetzungsvorgang School of Engineering © K. Rege, ZHAW 2 von 33 Schritte wärend der Übersetzung Zeichenstrom v a l = 1 0 * va l + i Lexikalische Analyse (Scanning) Token-,Symbol strom 1 (ident) "val" 3 (assign) - 2 (number) 10 4 (times) - 1 (ident) "val" 5 (plus) - 1 (ident) "i" Tokennummer Tokenwert Syntaxanalyse (Parsing) Statement Syntaxbaum Expression Term ident = number * ident + ident School of Engineering © K. Rege, ZHAW 3 von 33 Schritte wärend der Übersetzung Statement Syntaxbaum Expression Term ident = number * ident + ident Semantische Analyse (z.B.Typprüfung, ...) Zwischensprache Syntaxbaum, Symbolliste, ... Optimierung Statement Syntaxbaum Codeerzeugung Expression Term Maschinencode School of Engineering ldc.i4.s 10 ldloc.1 ident = number mul * ident + ident ... © K. Rege, ZHAW 4 von 33 Mehrpass-Compiler Phasen sind eigene Programme, die nacheinander ablaufen Scanner Zeichen sem. Analyse Parser Token Baum ... Code ■ Jede Phase liest von einer Datei und schreibt ihre Ausgabe auf eine neue Datei Wann ist/war das notwendig? ■ wenn der Hauptspeicher zu klein ist (heute irrelevant) ■ wenn die Sprache sehr komplex ist ■ wenn einfache Portierbarkeit gewünscht ist School of Engineering © K. Rege, ZHAW 5 von 33 Einpass-Compiler Die einzelnen Phasen arbeiten verzahnt scan token parse token check token generate code for token n eof? j Während das Quellprogramm gelesen wird, wird bereits das Zielprogramm (Code) erzeugt. School of Engineering © K. Rege, ZHAW 6 von 33 Heute oft Zweipass-Compiler Front End Back End Scanning Parsing Sem. Analyse Codeerzeugung Zwischensprache oder Datenstruktur (Baum) sprachabhängig maschinenabhängig Java C# Pascal Pentium PowerPC SPARC beliebig kombinierbar n*m n*mversus versusn+m n+m Vorteile ■ bessere Portierbarkeit ■ Kombination beliebiger Front Ends mit beliebigen Back Ends möglich ■ Zwischensprache ist einfacher optimierbar als Quellsprache School of Engineering Nachteile ■ etwas langsamer ■ mehr Speicherverbrauch © K. Rege, ZHAW 7 von 33 Compiler versus Interpreter Compiler übersetzt in Maschinencode Scanner Parser ... Codegenerator Maschinencode Quellcode Interpreter Lader führt Quellprogramm "direkt" aus Scanner ■ Anweisungen in einer Schleife laufen jedesmal erneut durch Scanner und Parser Parser Interpretation Quellcode Auch Interpretation von Zwischencode möglich ... Compiler ... Quellcode School of Engineering VM Zwischencode (z.B. Common Intermediate Language (CIL, ByteCode)) © K. Rege, ZHAW ■ Quellcode wird in den Code einer virtuellen Maschine (VM) übersetzt ■ VM interpretiert den Code; simuliert physische Maschine: langsam (~*10) 8 von 33 Just In Time Compilation (JIT) Codegenerator Zwischencode all in one incremental hot-spot School of Engineering ■ Code für virtuelle Maschine wird zur Ladezeit in den Code für physische Maschine übersetzt. ■ -> Ausgeführt wird Code für physische Maschine: schnell Lader Maschinencode 00110 1001 00110 1001 00110 1001 ■ Gesammter Code wird beim Laden übersetzt. Nachteil: Verzögerung von Programmstart ■ Code wird vor der ersten Ausführung übersetzt (meist auf Granulariät von Methoden) ■ Code wird interpretiert. Die Teile, die besonders häufig durchlaufen werden, werden in Maschinencode übersetzt. ■ Programm startet sofort und läuft sich "warm" © K. Rege, ZHAW 9 von 33 Sprachen, Automaten, Übergangstabellen School of Engineering © K. Rege, ZHAW 10 von 33 Natürliche Sprache Satz = Artikel Substantiv Verb Adverb Artikel = der | die | das Substantiv = Katze | Hund Verb = isst | schläft | bellt Adverb = viel | laut ■ Beispiele gültiger Sätze ■ ■ ■ die Katze schläft viel der Hund isst viel der Hund bellt laut ■ aber ■ ■ Syntax ist korrekt der Katze schläft gut die Katze bellt laut School of Engineering Syntax ist zwar korrekt, aber zusätzliche Regeln: "subst. = f" -> die" "macht keinen Sinn", Semantik stimmt nicht Satz muss verstanden werden, u.U. schwierig: Kontextwissen (Mehrdeutig, Zynismus, Witze). © K. Rege, ZHAW 11 von 33 Begriffe und Definitionen ■ natürliche Sprachen ■ ■ ■ ■ ■ bestehen aus Worten die Menge aller Worte wird als Vokabular bezeichnet haben Regeln, wie aus Worten gültige Sätze erzeugt werden können Regeln sind in einer Grammatik (nach Duden) festgelegt haben eine Bedeutung: was verstehen wir unter dem Satz ■ Programmiersprachen (formale Sprachen) ■ ■ ■ ■ ■ bestehen aus vordefinierten Symbolen: class, void, int, double, {, },.. die Menge alle Symbole wird als Vokabular bezeichnet haben Syntax, die beschreibt, wie aus Symbolen korrekte Programme erstellt werden können: Sätze der Sprache, oder Literale Regeln werden durch Compiler überprüft haben eine Bedeutung (Semantik): ausführbaren Code School of Engineering © K. Rege, ZHAW 12 von 33 Künstliche Sprache AB ■ Vokabular: die Buchstaben a und b ■ Sprache: {a}{b} beliebig beliebigoft oft ■ Beispiele von Sätzen/Literalen der Sprache ■ a, aa, aaa, aaaa, b, bb, bbb, bbbb, ab, aab, aabb, abb, aaab, abbb, aaabb, … ■ Vokabular ist endlich aber Menge der Sätze ist unendlich ■ Frage: welche der folgenden Sätze gehören zur Sprache AB ■ ■ ■ ■ abbbbbbbbb aaaaaabbbb aaaaabaaaa bbbbbbbbbb School of Engineering © K. Rege, ZHAW 13 von 33 Beschreibung Syntax von Programmiersprachen ■ Sprache zur Beschreibung der Syntax von Programmiersprachen ■ Aufzählung aller Sätze der Sprache ■ ℑ ::= a,b,ab,aab,abb, aaab, ….a*b* ■ regulärer Ausdruck: "Bildungsregeln" ■ ■ ℑ ::= {a}{b} ℑ = a*b* EBNF Regex Syntax EBNF Regex beliebig oft 0..inf. {} * Gruppierung () () Optional [] ? Alternative | | Definition ::= = beliebig oft =1 1..inf Spezialzeichen, z.B. ?,|,+ School of Engineering + "" "" oder mit vorangestelltem \ z.b. \? © K. Rege, ZHAW 14 von 33 Grammatiken ■ Reguläre Ausdrücke werden schnell unübersichtlich ■ a (b* | c?) d ■ Einführen von sog. Nichtterminalsymbolen ■ ■ Benannte Platzhalter (Variablen) ähnlich wie in Programmiersprachen ■ A = b* | c ■ B = a A d ■ Mengen von regulären Ausdrücken werden reguläre Grammatiken bezeichnet School of Engineering © K. Rege, ZHAW 15 von 33 Grammatiken ■ eine reguläre Grammatik besteht aus einer oder mehreren regulären Ausdrücken ■ Kleinbuchstabe = a|b|c|d … |z ■ Grossbuchstabe = A|B|C|D…|Z ■ Ziffern = 0|1|2|3|4|5|6|7|8|9 ■ Buchstabe = Kleinbuchstabe | Grossbuchstabe ■ Bezeichner = Buchstabe | _ | $ (Buchstabe | Ziffer | _ |$)* ■ Die einzelnen Zeilen werden als Produktionen bezeichnet. ■ Ein Ausdruck kann einem Platzhalter, d.h. Nichtterminal-Symbol, zugewiesen werden, welches selber wieder in Ausdrücken verwendet werden darf. ■ Alle andern Symbole werden entsprechend als Terminal-Symbole bezeichnet ■ Bei regulären Grammatiken können alle Platzhalter (NT-Symbole) wieder durch ihre regulären Ausdrücke ersetzt werden. School of Engineering © K. Rege, ZHAW 16 von 33 Umformungen, Beschreibung einer Sprache ■ Grammatiken können umgeformt werden ■ ■ a* (b | d) c* = (a*b | a* d) c* = a*b c* | a*d c* oder P = a*bc* ■ C = c* ■ A = a* b C ■ P = (a A) | (b C) ■ Die Symbole, mit denen Sätze/Literale beginnen können, werden als Startsymbole bezeichnet. ■ a und b sind Startsymbole von a*bc* ■ Leeres Symbol: ε ■ es kann u.U. Sinn machen, das leere Symbol einzuführen: a (b | c | ε)+ e ■ Eine Sprache ist syntaktisch definiert durch: <T,N,S,P> ■ ■ ■ ■ T: Menge der Terminal-Symbole N: Menge der Nichtterminal-Symbole S: Menge der Startsymbole ∈ T P: Menge der Produktionen School of Engineering © K. Rege, ZHAW 17 von 33 Endliche Automaten ■ anhand der Grammatik lässt sich ein endlicher Automat konstruieren, der überprüft, ob ein Satz zu dieser Sprache gehört Bei Beijedem jedemÜbergang Übergangwird wirdein ein Zeichen Zeichenvom vomEingabestrom Eingabestrom gelesen gelesen ■ bsp: a* b c* a 11 Startzustand a 22 ε : leeres Symbol c b 33 c ε 44 ε b 55 Endzustand ■ Die Symbole mittels denen der Automat gestartet wird, werden als Startsymbole bezeichnet: a b ■ Wird der (ein) Endzustand erreicht, dann gehört der Satz zur Sprache ■ Man sagt: der Automat erkennt/akzeptiert die Sprache, wenn die Symbolfolge ihn in den Endzustand überführt ■ bei jedem Zustandsübergang wird dabei ein Symbol vom Eingabestrom gelesen; Ausnahme ε School of Engineering © K. Rege, ZHAW 18 von 33 Übung ■ zeichnen Sie den endlichen Automaten für folgende Grammatik auf ■ a? ( b | c ) d* ■ Zeichnen Sie Start- und Endzustand ein ■ welches sind die Startsymbole ■ überprüfen Sie ihren Automaten mit folgenden Sätzen ■ a b d d d, a c d d d, b d, b c, a b c d School of Engineering © K. Rege, ZHAW 19 von 33 Übergangstabelle ■ Der Automat lässt sich auch übersichtlich in einer sog. Übergangstabelle beschreiben: ■ ■ Zustand (S: Start; E: Endzustand; N: Fehlerzustand) Übergänge Zustand ■ 0 1 2 3 4 5 a 0 2 2 0 0 5 b 0 3 3 0 0 5 c 0 0 0 4 4 5 ε 0 N 0 S 0 5 5 5 E Fehler Fehler Start Start End End Zweck: es lässt sich einfach ein Programm schreiben, das anhand dieser Tabelle prüft, ob ein Satz zu einer Sprache gehört. School of Engineering © K. Rege, ZHAW 20 von 33 Methode getSym und Variable sym ■ Die Parser-Methoden werden vereinfacht, wenn die Hilfsmethode getSym verwendet wird, die das nächste Symbol von der Eingabe liest. ■ das aktuelle Symbol wird in einer Instanz-Variablen sym zwischengespeichert; ■ Zahlen: eine zusätzliche Variable symValue nötig, in der der Wert gespeichert wird ■ Die Methode die einen Buchstabenstrom in einen Symbolstrom umwandelt wird als Scanner bezeichnet. String input = "2+6"; Falls Fallsmehrere mehrereZiffern Ziffern eine while Schleife eine while Schleife final char NUMBER = 'N'; final char EOT = '\0'; int pos = 0; char sym; int symValue; //Zahlen mit nur einer Ziffer static char getSym() { if (pos < input.length()) { sym = input.charAt(pos++); } else sym = EOT; if (sym >= '0' && sym <='9') { symValue = input.charAt(pos++)-'0'; return NUMBER; } return sym; } School of Engineering © K. Rege, ZHAW 21 von 33 Programm zum Prüfen von Bezeichner int[][] transition = {{0,0,0,0},{2,0,2,0},{2,2,2,3},{3,3,3,3}}; int START = 1, END = 3, ERR = 0, EOT = 3; String input = "$Hallo"; 0 1 2 3 int pos = 0; int sym; int getSym() { Buchstabe 0 2 2 3 Ziffer _ oder 0 0 2 3 $ 0 2 2 3 ε 0N 0S 3 3E if (pos < input.length()) { char c = input.toUpperCase().charAt(pos++); if (c >= 'A' && c <= 'Z') return 0; // Buchstabe else if (c >= '0' && c <= '9') return 1; // Zahl else if (c == '_' || c == '$') return 2; // Sonderzeichen } return EOT; } boolean isIdentifier() { int state = START; do { sym = getSym(); state = transition[state][sym]; } while (sym != EOT); return state == END; } School of Engineering © K. Rege, ZHAW 22 von 33 Übung ■ Zeichnen Sie die Übergangstabelle für die Grammatik a? ( b | c ) d* auf a b c d N S 0 1 2 3 4 5 6 7 School of Engineering ε E © K. Rege, ZHAW 23 von 33 Parser School of Engineering © K. Rege, ZHAW 24 von 33 Der Parser ■ Beispiele einfacher mathematischer Ausdrücke ■ 3 + 4 oder 3 - 2 ■ Methode getSym liest nächstes Zeichen/Token vom Eingabestrom ■ Wert in symValue ■ Parser Programm char sym = getSym(); Fehler Fehlerfalls fallsnicht nichtkorrektes korrektes Symbol Symbol Fehler Fehlerfalls fallsnicht nichtkorrektes korrektes Symbol Symbol int operand1 = symValue; char operator = getSym(); char sym = getSym(); Fehler Fehlerfalls fallsnicht nichtkorrektes korrektes Symbol Symbol int operand2 = symValue; if (operator == '+') return operand1 + operand2; else if (operator == '-') return operant1 - operand2; School of Engineering © K. Rege, ZHAW 25 von 33 Der Parser ■ Beliebige einfache mathematischer Ausdrücke ■ 3 + 3 oder 3 + 2 - 3 oder 3 - 3 + 2 ■ obige mathematische Ausdrücke werden durch folgende Grammatik beschrieben: ■ ■ Ausdruck = Term (("+" | "-") Term)* Term = Zahl ■ für jede Produktion wird nun eine entsprechende Methode geschrieben: void ausdruck() { term(); while (sym == '+' || sym == '-') { 3-3+2 sym = getSym(); term(); } } getSym() void term() { NUMBER "-" NUMBER "+" NUMBER if (sym = NUMBER) { sym = getSym(); // lese Zahl 3 zahl = symValue; 3 2 } } ■ für Nichtterminal-Symbole wird einfach die entsprechende Methode aufgerufen School of Engineering © K. Rege, ZHAW 26 von 33 Regeln zur (top-down) Parser-Erstellung Grammatik Parser Sequenz: a b c if (sym == 'a') sym = getSym(); else Error if (sym == 'b') sym = getSym(); else Error if (sym == 'c') sym = getSym(); else Error Alternative: a | b | c if (sym == 'a' || sym == 'b' || sym == 'c') sym =getSym(); else Error Option: a? c if (sym == 'a') sym = getSym(); if (sym == 'c') sym = getSym();else Error Repetition a*b while (sym = 'a') {sym =getSym();} if (sym == 'b') sym =getSym(); else Error Nichtterminal-Symbol B=Ab A=aaa School of Engineering B: if (sym == 'a') A; Aufruf Aufrufder derMethode MethodeAA if (sym == 'b') sym = getSym(); else Error A: sym = getSym(); sym = getSym(); sym = getSym(); © K. Rege, ZHAW 27 von 33 Die vollständige Grammatik ■ Beispiele mathematischer Ausdrücke ■ 3 + 3 + 3 oder 3 + 3*2 + 2 oder 3 * (2 + 3) ■ Grammatik dazu (nicht mehr regulär, aber für Parser kein Problem) ■ ■ ■ Ausdruck = Term (("+" | "-") Term)* Term = Faktor (("*" | "/") Faktor)* Faktor = Zahl | "(" Ausdruck ")" Nicht Nichtmehr mehrregulär regulär wegen wegenRekursion Rekursion ■ der Parser dazu static void factor() { if (sym == '(') { sym = getSym(); ausdruck(); sym = getSym(); // ')' } else if (sym == NUMBER) { sym = getSym(); // Zahl } static void ausdruck() { term(); while (sym == '+' || sym == '-') { sym = getSym(); term(); } } static void term() { factor(); while (sym == '*' || sym == '/') { sym = getSym(); factor(); } } School of Engineering } public static void main(String[] s) { sym = getSym(); // lesen des 1. Symbols ausdruck(); } © K. Rege, ZHAW 28 von 33 Berechnung des Ausdruckes - Infix Variante ■ in den einzelnen Parser-Methoden kann direkt der Wert berechnet werden ■ es wird der Code zur Berechnung ergänzt und das Resultat wird zurückgegeben. double factor() throws Exception{ double val = 0; if (sym == '(') { sym = getSym(); val = ausdruck(); sym = getSym(); // ')' } else if (sym == NUMBER) { val = symValue; sym = getSym(); } return val; } School of Engineering double term() throws Exception{ double val = factor(); while (sym == '*' || sym == '/') { char s = sym; sym = getSym(); double v = factor(); if (s == '*') val *= v; else val /= v; } return val; } double ausdruck() throws Exception { double val = term(); while (sym == '+' || sym == '-') { char s = sym; sym = getSym(); double v =term(); if (s == '+') val += v; else val -= v; } return val; } © K. Rege, ZHAW 29 von 33 Berechnung des Ausdruckes - Stack Variante Logik kann direkt in Parser hineinkodiert werden static int stack[] = new int[10]; static int sp = 0; + einfach, effizient static void push(int val) { stack[sp++]=val; } + Stackrechner sehr einfach - Vermischung von Syntax und Semantik static int pop() { return stack[--sp]; } bei Compiler: statt auszuwerten wird Code erzeugt void factor() throws Exception{ if (Scanner.sym == '(') { Scanner.getSym(); expr(); Scanner.getSym(); } else if (Scanner.sym == 'N') { push(Scanner.symValue); Scanner.getSym(); } else Scanner.error("illegal Symbol"); } School of Engineering © K. Rege, ZHAW static void expr() throws Exception { term(); while (Scanner.sym == '+' || Scanner.sym == '-') { int op = Scanner.sym; Scanner.getSym(); term(); if (op == '+') push(pop()+pop()); else push(-pop()+pop()); } } 30 von 33 Berechnung des Ausdruckes - Baum Variante ■ Während Parsing wird Datenstruktur erstellt ■ Bsp : ■ public class Item { int val; int kind; Token token; Item left, right; } * aus dem Ausdruck 2 * 5* (3 + 4) wird folgender P-Baum erstellt 2 ■ innerer Knoten: Operatoren * 5 + ■ Blätter: Operanden 3 Item factor() throws Exception { Item item = null; if (Scanner.sym == '(') { Scanner.getSym(); item = expr(); Scanner.getSym(); } else if (Scanner.sym == 'N') { item = new Item(Scanner.symValue); Scanner.getSym(); } else Scanner.error("illegal Symbol"); return item; } School of Engineering 4 Item term() throws Exception { Item item = factor(); while (Scanner.sym == '*' || Scanner.sym == '/') { Item i = new Item(Scanner.sym); Scanner.getSym(); i.left = item; i.right = factor(); item = i; } return item; } © K. Rege, ZHAW 31 von 33 … Berechnung des Ausdruckes - Baum Variante ■ Der Parse-Baum kann durch eine rekursive eval-Methode ausgewertet werden ■ Klammerung wurde schon beim Aufbau des Baumes berücksichtigt int eval(Item item) { int val = 0; if (item.kind == Token.NUMBER) else if (item.kind == '+') val else if (item.kind == '-') val else if (item.kind == '*') val else if (item.kind == '/') val return val; } val = item.val; = eval(item.left)+eval(item.right); = eval(item.left)-eval(item.right); = eval(item.left)*eval(item.right); = eval(item.left)/eval(item.right); ■ Vorteile: ■ ■ ■ Trennung zwischen Syntaxanalyse und Berechnung/Codeerzeugung Ausdruck muss nur einmal übersetzt werden und kann dann mehrmals ausgewertet werden Es kann im Ausdruck zuvor nach mehrfach vorkommenden Teilen gesucht werden -> Optimierungen School of Engineering © K. Rege, ZHAW 32 von 33 Zusammenfassung ■ Übersetzungsvorgang ■ Sprachen, Automaten, Übergangstabellen ■ Parser ■ Arithmetische Ausdrücke School of Engineering © K. Rege, ZHAW 33 von 33