6. Sprachen, Compiler und Theorie Literatur (1/3) Bücher Michael L. Scott : „Programming Language Pragmatics”, MKP 2000, ISBN 1-55860-578-9 http://www.cs.rochester.edu/u/scott/pragmatics/ Uwe Schöning: „Theoretische Informatik kurzgefaßt“, 2001, ISBN 3827410991 Rechenberg & Pomberger: „Informatik-Handbuch“, Hanser Verlag, ISBN 3-446-21842-4 Drachenbuch Informatik II SS 2004 Teil 6: Sprachen, Compiler und Theorie 1 - Einführung und Übersicht Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman: „Compilers - Principles, Techniques and Tools“. Addison-Wesley 1988, ISBN 0-201-10088-6 Alfred V. Aho, Ravi Sethi, Jeffrey D. Ullman: „Compilerbau.“ Oldenbourg Verlag 1999, Teil 1: ISBN 3-486-25294-1, Teil 2: ISBN 3-48625266-6 Wikipedia: http://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques_and_Tools Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6.1-2 6. Sprachen, Compiler und Theorie Literatur (2/3) Literatur (3/3) Skripte Bücher Klassiker der Automatentheorie von Hopcroft/Ullman/Motwani Hopcroft, Motwani, Ullman : „Introduction to Automata Theory, Languages, and Computation“, 2001, http://www-db.stanford.edu/~ullman/ialc.html Hopcroft, Motwani, Ullman: „Einführung in die Automatentheorie, Formale Sprachen und Komplexitätstheorie“, Pearson Studium 2002, ISBN 3827370205 Asteroth, Baier: „Theoretische Informatik: Eine Einführung in Berechenbarkeit, Komplexität und formale Sprachen mit 101 Beispielen“, Pearson Studium 2002, ISBN 3-8273-7033-7 (insbesondere für Nebenfächler geeignet, da kaum Mathematikkenntnisse vorausgesetzt werden.) Compilerbau-Skript von Prof. Dr. Goltz, Universität Braunschweig http://www.cs.tu-bs.de/ips/ss04/cb/skript_cp.ps.gz Informatik-Skripte von Prof. Dr. Waack, Universität Göttingen http://www.num.math.uni-goettingen.de/waack/lehrmaterial/ Folien Informatik II - SS2003 Folien dienen als Grundlage und wurden übersetzt und ev. teilweise ergänzt. Es wird aber auch komplett neue Teile geben!!! http://user.informatik.uni-goettingen.de/~info2/SS2003/ Übersetzerbau I – Prof. Dr. Goos, Universität Karlsruhe http://www.info.uni-karlsruhe.de/lehre/2003WS/uebau1/ WWW: Wikipedia http://en.wikipedia.org/wiki/Compilers:_Principles,_Techniques_and_To ols http://de.wikipedia.org/wiki/Compiler Erfahrungsberichte von Studentenseite sind erwünscht Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-3 Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Weitere Quellen Einführung Katalog von Konstruktionswerkzeugen für Compiler http://www.first.gmd.de/cogent/catalog/ Inhalte Grundlegende Konzepte von Programmiersprachen Organisation von Compilern für moderne Programmiersprachen Einführung in die Theorie von formalen Sprachen und Automaten ANTLR, ANother Tool for Language Recognition: http:/www.antlr.org Konferenzen und Journale ACM Transactions on Programming Languages and Systems ACM SIGPLAN Conference on Programming Language Design and Implementation ACM SIGPLAN Conference on Programming Language Principles Als Grundlage dient das Buch “Programming Language Pragmatics” von Michael L. Smith Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-5 6. Sprachen, Compiler und Theorie Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-6 6. Sprachen, Compiler und Theorie Abstraktionen... Sprachen als Abstraktion Die menschliche Sprache ist ein Werkzeug für die Abstraktion von Gedanken Eliminiere Details welche unnötig zum Lösen eines speziellen Problems sind „Wenn es mir warm ist, dann schalte ich den Ventilator ein.“ Komplexität wird versteckt Eine einfache Absicht wird mitgeteilt, wobei aber die kognitiven und neurologischen Bedingungen, durch welche die Absicht aufkam, höchst wahrscheinlich für jeden zu komplex sind um sie zu Verstehen Die Bedeutung dieser Aussage ist dem Verständnis des Individuums welches es äußert und den Individuen die es hören überlassen Baue oft auf anderen auf Erlaubt das Lösen von zunehmend komplexeren Problemen (teile und herrsche, divide and conquer) Komplexität moderner Software ist ohne Beispiel (Präzedenzfall) Programmiersprachen sind ein Werkzeug zum Abstrahieren von Berechnungen Abstraktion ist ein grundlegender Bestandteil zum Handhaben von diesen komplexen Problemen if (temperatur() > 30.0) { schalte_ventilator_ein(); } Abstraktion Abstraktum Digitale Logik Computerarchitektur Assemblersprache Betriebssystem Computerkommunikation Transistoren Digitale Logik Maschinensprache Allokation von Ressourcen (Zeit, Speicher, etc.) (Physikalische) Netzwerke, Protokolle Beinhaltet eine komplexe aber konkrete Sequenz von Aktionen: Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-7 lese Thermostat; konvertiere den Ablesewert zu einer IEEE Fliesskomazahl nach der Celsiusskala; Vergleiche den Wert mit 30.0; wenn größer dann sende ein Signal an eine PCI Karte, welche ein Signal an ein Relais sendet, welches den Ventilator einschaltet Die Bedeutung dieses Ausdrucks ist festgelegt durch die formale Semantik der Programmiersprache und der Implementierung der Funktionen temperatur() und schalte_ventilator_ein(). Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Wie abstrahieren Programmiersprachen Berechnungen? (1/4) Biete eine Notation für den Ausdruck von Algorithmen welche 2. Verberge unterliegende (systemnahe) Details der Zielarchitektur Befehlsnamen der Assemblersprache, Registernamen, Argumentordnung, etc. Abbildung von Sprachelementen auf die Assemblersprache Arithmetische Ausdrücke, Bedingungen, Konventionen für Prozeduraufrufe, etc. wie der Algorithmus in einer Maschinensprache implementiert wird, wie Hilfsalgorithmen, z.B. Hash-Tabellen, Listen, implementiert werden, es dem Programmierer erlaubt seine eigene Abstraktion (Unterprogramme, Module, Bibliotheken, Klassen, etc.) zu bauen um die Weiterführung des Konzepts „Komplexitätsmanagement durch Schichtenbildung“ zu ermöglichen. if (a < b + 10) { do_1(); } else { do_2(); } RC SPA MI P .L1: .L2: add cmp bge call ba call … %l1,10,%l2 %l0,%l2 .L1; nop do_1; nop .L2; nop do_2; nop L1: L2: addi bge call b call … $t2,$t1,10 $t0,$t2,L1 do_1 L2 do_2 S Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-9 6. Sprachen, Compiler und Theorie Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Wie abstrahieren Programmiersprachen Berechnungen? (4/4) Biete Grundbefehle (primitives), Unterprogramme und Laufzeitunterstützung für übliche (lästige) Programmierpflichten Biete Merkmale welche eine besondere Art von Algorithmus oder Softwareentwicklung unterstützen (encourage) oder durchsetzen (enforce) Lesen und schreiben von Dateien Handhabung von Zeichenfolgen (Vergleiche, Erkennung von Teilzeichenfolge, etc.) Dynamische Allokation von Speicher (new, malloc, etc.) Rückgewinnung von unbenutztem Speicher (garbage collection) Sortieren etc. Strukturiertes Programmieren Unterprogramme Verschachtelte (Nested ???) Variablenbereiche ( scopes??) Schleifen Beschränkte Formen des „goto“ Befehls (statement??) Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-10 6. Sprachen, Compiler und Theorie Wie abstrahieren Programmiersprachen Berechnungen? (3/4) 3. SPARC (meistens) unabhängig von der Maschine ist auf welcher der Algorithmus ausgeführt wird, Fähigkeiten (features) auf höchster Ebene bietet und die Aufmerksamkeit des Programmierers mehr auf den Algorithmus fokussiert und weniger auf MIPS 1. Wie abstrahieren Programmiersprachen Berechnungen? (2/4) 6.1-11 Objekt-Orientierte Programmierung Klassen Vererbung Polymorphismus Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kategorien von Programmiersprachen Imperative Sprachen (1/2) Alle Sprachen fallen in eine der beiden folgenden Kategorien: Die von Neumann Sprachen Imperative Sprachen erfordern die schrittweise Beschreibung durch Programmierer wie ein Algorithmus seine Aufgabe erledigen soll. Analogie aus der realen Welt: „Ein Rezept ist eine Art von imperativem Programm, welches einem Koch sagt wie ein Gericht zuzubereiten ist.“ Deklarative Sprachen erlauben die Beschreibung durch Programmierer was ein Algorithmus erledigen soll ohne exakt zu beschreiben wie es getan werden soll. schließen Fortran, Pascal, Basic und C ein stellen eine Reflektion der von Neumann Computerarchitektur dar, auf welcher die Programme laufen Führen Befehle aus welche den Zustand des Programms (Variablen/Speicher) ändern Manchmal auch Berechnung durch Seiteneffekte genannt Analogie aus der realen Welt: „Das Pfandgesetz ist ein deklaratives Programm welches Einzelhändlern mitteilt das sie ein Recyclingprogramm für Einwegflaschen und Dosen des eigenen Sortiments aufstellen müssen, ohne exakt mitzuteilen wie dies zu erfolgen hat. Beispiel: Aufsummieren der ersten n Ganzzahlen in C for(sum=0,i=1;i<=n;i++) { sum += i; } Einführung Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-13 6. Sprachen, Compiler und Theorie Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie Imperative Sprachen (2/2) Deklarative Sprachen (1/2) Die objekt-orientierten Sprachen Die funktionalen Sprachen schließen Smalltalk, Eiffel, C++, Java und Sather ein Sind ähnlich der von Neumann Sprachen mit der Erweiterung von Objekten Objekte enthalten ihren eigenen internen Zustand (Klassenvariablen, member variables) und Funktionen welche auf diesem Zustand operieren (Methoden) Berechnung ist organisiert als Interaktion zwischen Objekten (ein Objekt ruft die Methoden eines anderen Objektes auf) Die meisten objekt-orientierten Sprachen bieten Konstrukte (facilities) basierend auf Objekten welche objekt-orientierte Programmierung fördern Kapselung (encapsulation), Vererbung (inheritance) und Polymorphismus (polymorphism) Wir werden uns darüber später genauer unterhalten schließen Lisp/Scheme, ML, Haskell (Gofer) ein Sind eine Reflektion von Church‘s Theorie der rekursiven Funktionen (lambda calculus) Berechnung werden ausgeführt als Rückgabewerte von Funktionen basierend auf der (möglicherweise rekursiven) evaluation von anderen Funktionen Mechanismus ist als Reduktion bekannt Keine Seiteneffekte Erlaubt gleichungsbasiertes Problemlösen (equational reasoning), einfachere formale Beweise von Programmkorrektheit, etc. Beispiel: Aufsummieren der ersten n Ganzzahlen in SML fun sum (n) = if n <= 1 then n else n + sum(n-1) Einführung Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-14 6.1-15 Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Deklarative Sprachen (2/2) Eine historische Perspektive: Maschinensprachen Die logischen Sprachen schließen Prolog, SQL und Microsoft Excel/OpenOffice OpenCalc ein Sind eine Reflektion von der Theorie der Aussagenlogik (propositional logic) Berechnung ist ein Versuch einen Wert zu finden welcher eine Menge von logischen Beziehungen erfüllt Die ersten Maschinen wurden direkt in einer Maschinensprache oder Maschinencode programmiert Langweilig, aber Maschinenzeit war teurer als Programmiererzeit Der meist verwendete Mechanismus um diesen Wert zu finden ist bekannt als Resolution (resolution) und Vereinheitlichung (unification) Beispiel: Aufsummieren der ersten n Ganzzahlen in Prolog sum(1,1). sum(N,S) :- N1 is N-1, sum(N1,S1), S is S1+N. MIPS Maschinencode für ein Programm zum Berechnen des GGT von zwei Ganzzahlen Einführung Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-17 6. Sprachen, Compiler und Theorie Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-18 6. Sprachen, Compiler und Theorie Eine historische Perspektive: Assemblersprachen (1/2) Eine historische Perspektive: Assemblersprachen (2/2) Programme wurden immer Komplexer Beispiel: zu schwierig, zeitintensiv und teuer um Programme in Maschinencode zu schreiben Assemblersprachen wurden entwickelt Für den Menschen lesbar ☺ Ursprünglich wurde eine eins-zu-eins Beziehung zwischen Instruktionen der Maschinensprache und Instruktionen der Assemblersprache bereitgestellt Schließlich wurden „makro“ Einrichtungen hinzugefügt um Softwareentwicklung durch anbieten von primitiven Formen von CodeWiederverwendung weiter zu beschleunigen Der Assembler war das Programm welches ein Assemblerprogramm in Maschinencode übersetzte mit welchem die Maschine laufen konnte Assembler Einführung Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-19 Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-20 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Eine historische Perspektive: höhere Sprachen (1/2) Eine historische Perspektive: höhere Sprachen (2/2) Programme wurden immer Komplexer Beispiel: es war zu schwierig, zeitintensiv und teuer um Programme in Assemblersprache zu schreiben es war zu schwierig von einer Maschine zu einer anderen zu wechseln, welche eine andere Assemblersprache hatte Es wurden höhere Programmiersprachen entwickelt Mitte der 1950er wurde Fortran entworfen und implementiert es erlaubte numerische Berechnungen in einer Form ähnlich von mathematischen Formeln auszudrücken Der Compiler war das Programm welches ein höheres Quellprogramm in ein Assemblerprogramm oder Maschinenprogramm übersetzte. Ursprünglich konnten gute Programmierer schnellere Assemblerprogramme schreiben als der Compiler Andere höhere Programmiersprachen folgen Fortran in den späten 50er und frühen 60er int gcd (int i, int j) { while (i != j) { if (i > j) i = i – j; else Compiler j = j – i; } printf(“%d\n”,i); } Lisp: erste funktionale Sprache, basierte auf der Theorie der rekursiven Funktionen Algol: erste block-strukturierte Sprache Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-21 6. Sprachen, Compiler und Theorie Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie Ausführung von Programmen höherer Sprachen Entwurf eines Compilers Kompilation Programm wird in Assemblersprache oder direkt in Maschinensprache übersetzt Kompilierte Programme können so erstellt werden, dass sie relativ schnell in der Ausführung sind Fortran, C, C++ Interpretation Programm wird von einem anderem Programm gelesen und Elemente der Quellsprache werden einzeln ausgeführt Ist langsamer als kompilierte Programme Interpreter sind (normalerweise) einfacher zu implementieren als Compiler, sind flexibler und können exzellent Fehlersuche (debugging) und Diagnose unterstützen Java, Pyhton, Perl, etc. Compiler sind gut untersuchte, aber auch sehr komplexe Programme Daher sollte man nicht davon ausgehen, dass Compiler immer fehlerfrei arbeiten!!! Die Komplexität wird durch die Aufteilung der Compilerarbeiten in unabhängige Abschnitte oder Phasen bewältigt Typischerweise analysiert eine Phase eine Repräsentation von einem Programm und übersetzt diese Repräsentation in eine andere, welche für die nächste Phase besser geeignet ist Das Design dieser Zwischenrepräsentationen eines Programms sind kritisch für die erfolgreiche Implementierung eines Compilers Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-22 6.1-23 Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-24 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Der Kompilationsprozess (-phasen) Lexikalische Analyse Scanner (lexikalische Analyse) Lese Programm und konvertiere Zeichenstrom in Marken (tokens). Parser (syntaktische Analyse) Lese Tokenstrom und generiere Parserbaum (parse tree). Semantische Analyse Traversiere Parserbaum, überprüfe nicht-syntaktische Regeln. Zwischencodegenerierung Traversiere Parserbaum noch mal, gebe Zwischencode aus. Optimierung Untersuche Zwischencode, versuche ihn zu verbessern. Zielcodegenerierung Übersetze Zwischencode in Assembler-/Maschinencode Optimierung Maschinenebene Untersuche Maschinencode, versuche ihn zu verbessern. Eine Programmdatei ist nur eine Sequenz von Zeichen Falsche Detailebene für eine Syntaxanalyse Die lexikalische Analyse gruppiert Zeichensequenzen in Tokens Tokens sind die kleinste „Bedeutungseinheit“ (units of meaning) im Kompilationsprozess und sind die Grundlage (foundation) fürs Parsen (Syntaxanalyse) Die Compilerkomponente zum Ausführen der lexikalischen Analyse ist der Scanner, welcher oftmals ausgehend von höheren Spezifikationen automatisch generiert wird Mehr über Scanner in der nächsten Vorlesung Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-25 6. Sprachen, Compiler und Theorie Syntaktische Analyse Ein GGT Programm in C Token int int int { i ) ( j = j j – } “%d\n” ) gcd i j while != { i ) i ; = i printf , ; Die lexikalische Analyse erzeugt einen Strom von Tokens Falsche Detailebene für die semantische Analyse und Codegenerierung Die Syntaxanalyse gruppiert eine Zeichenfolge von Tokens in Parserbäume, was durch die kontextfreie Grammatik gelenkt wird, die die Syntax der zu kompilierenden Sprache spezifiziert ( , ) ( j if > i – else j ; ( I } conditional -> if ( expr ) block else block Parserbäume repräsentieren die Phrasenstruktur eines Programmes und sind die Grundlage für die semantische Analyse und Codegenerierung Die Compilerkomponente zum Ausführen der syntaktischen Analyse ist der Parser, welcher oftmals ausgehend von höheren Spezifikationen automatisch generiert wird Mehr über kontextfreie Grammatiken und Parser später Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-26 6. Sprachen, Compiler und Theorie Beispiel für lexikalische Analyse int gcd (int i, int j) { while (i != j) { if (i > j) i = i – j; else j = j – i; } printf(“%d\n”,i); } Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-27 Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-28 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Beispiel Syntaxanalyse Semantische Analyse conditional Token if > i – else j ; ( j = j j – } i ) i ; = i ( expr id comp id i > j if block ) Bestimmt die Bedeutung eines Programms basierend auf der Repräsentation des Parserbaumes Setzt Regeln durch, welche nicht durch die Syntax der Programmiersprache verwaltet werden block else statement id = j statement id = expr i id op id i - j Konsistente Verwendung von Typen, z.B. expr int a; char s[10]; s = s + a; illegal! id op id j - i Jeder Bezeichner (identifier) muss vor der ersten Verwendung deklariert sein Unterprogrammaufrufe müssen die richtige Argumentanzahl und Argumenttyp haben etc. Bringt die Symboltabelle auf den aktuellen Stand, welche neben anderen Dingen den Typ von Variablen, deren Größe und den Gültigkeitsbereich in welchen die Variablen erklärt wurden notiert Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-29 6. Sprachen, Compiler und Theorie Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-30 6. Sprachen, Compiler und Theorie Zwischencodegenerierung Zielcodegenerierung Parserbäume sind die falsche Detailebene für die Optimierung und Zwischencodegenerierung Zwischencodegenerierung verwandelt den Parsebaum in eine Sequenz von Anweisungen (statements) der Zwischensprache welche die Semantik des Quellprogramms verkörpert Die Zwischensprache ist genauso Mächtig, aber einfacher, wie die höhere Sprache z.B. die Zwischensprache könnte nur einen Schleifentyp (goto) haben, wogegen die Quellsprache mehrere haben könnte (for, while, do, etc.) Das Endziel eines Compilerprozesses ist die Generierung eines Programms welches der Computer ausführen kann Dies ist die Aufgabe der Zielcodegenerierung Schritt 1: durchlaufe (traverse) die Symboltabelle, weise Variablen einen Platz im Speicher zu Schritt 2: durchlaufe (traverse) den Parsebaum oder Programm in der Zwischensprache um arithmetische Operationen, Vergleiche, Sprünge und Unterprogrammaufrufe auszugeben, sowie Lasten und Vorräte von Variablenreferenzen Eine einfache Zwischensprache macht es einfacher nachfolgende Compilerphasen zu implementieren Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-31 Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-32 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Optimierung Warum Programmiersprachen und Compiler studieren? Zwischencode und Zielcode ist typischerweise nicht so effizient wie er sein könnte Einschränkungen erlauben es dem Codegenerator sich auf die Codeerzeugung zu konzentrieren und nicht auf die Optimierung Ein Optimierer kann aufgerufen werden um die Qualität des Zwischencodes und/oder Zielcodes nach jeder dieser Phasen zu verbessern Die Compilerkomponente zur Verbesserung der Qualität des generierten Codes wird Optimierer (optimizer) genannt. Optimierer sind die kompliziertesten Teile eines Compilers Optimierungsalgorithmen sind oftmals sehr ausgefeilt, benötigen erheblich viel Speicher und Zeit für die Ausführung und erzeugen nur kleine Verbesserungen der Programmgröße und/oder Leistung der Laufzeit Zwei wichtige Optimierungen Registerzuteilung – entscheide welche Programmvariablen zu einem bestimmten Zeitpunkt der Programmausführung in Registern gehalten werden können Unbenutzten Code eliminieren – entferne Funktionen, Blöcke, etc., welche niemals vom Programm ausgeführt würden Nach Aussage von Michael Scott (siehe Literaturangabe) Verstehe schwer verständliche Spracheigenschaften Wähle zwischen alternativen Wegen um etwas auszudrücken Mache guten Gebrauch von Debuggern, Assemblern, Linkern und andere verwandte Werkzeuge Simuliere nützliche Eigenschaften (features) welche in einer Sprache fehlen Nach Aussage von Kevin Scott (vorheriger Dozent) Compiler sind große und komplexe Programme: studieren dieser Programme hilft dir „große Software“ besser zu verstehen Viele Programme enthalten „kleine Programmiersprachen“ Unix shells, Microsoft Office Anwendungen, etc. Es ist nützlich etwas über Sprachdesign und –implementierung zu wissen, so dass Sie kleine Sprachen in die eigene Software einbauen können Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-33 6. Sprachen, Compiler und Theorie Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-34 6. Sprachen, Compiler und Theorie Weitere Fragen zum Nachdenken Plan für nächste Vorlesungen Was macht eine Programmiersprache erfolgreicher als andere? Werden Programmiersprachen mit der Zeit besser? An welchen Eigenschaften (features) mangelt es deiner bevorzugten Sprache um Sie mächtiger, zuverlässiger, einfacher in der Verwendung zu machen? Die nächsten Vorlesungen (Kapitel 2 vom Buch) lexikalische Analyse syntaktische Analyse Automatentheorie und automatische Generierung von Scannern und Parsern Reguläre und kontextfreie Grammatiken Nachfolgende 5 Vorlesungen Namen, Geltungsbereiche und Binden (Kapitel 3) Kontrollfluss (Kapitel 6) Unterprogramme und Kontrolle über Abstraktion (Kapitel 8) Zusammenbauen eines lauffähigen Programms (Kapitel 9) Objekt-orientierte Programmierung (Kapitel 10) Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-35 Einführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.1-36 6. Sprachen, Compiler und Theorie Der Kompilationsprozess (-phasen) Informatik II SS 2004 Scanner (lexikalische Analyse) Lese Programm und konvertiere Zeichenstrom in Marken (tokens). Parser (syntaktische Analyse) Lese Tokenstrom und generiere Parserbaum (parse tree). Semantische Analyse Teil 6: Sprachen, Compiler und Theorie 2 – Lexikalische Analyse Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Traversiere Parserbaum, überprüfe nicht-syntaktische Regeln. Zwischencodegenerierung Traversiere Parserbaum noch mal, gebe Zwischencode aus. Optimierung Untersuche Zwischencode, versuche ihn zu verbessern. Zielcodegenerierung Übersetze Zwischencode in Assembler-/Maschinencode Optimierung Maschinenebene Untersuche Maschinencode, versuche ihn zu verbessern. Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6.2-2 6. Sprachen, Compiler und Theorie Lexikalische Analyse Beispiel für lexikalische Analyse Ein GGT Programm in C Die lexikalische Analyse gruppiert Zeichensequenzen in Tokens (Marken) bzw. Symbole Tokens sind die kleinste „Bedeutungseinheit“ (units of meaning) im Kompilationsprozess und sind die Grundlage (foundation) fürs Parsen (Syntaxanalyse) Die Compilerkomponente zum Ausführen der lexikalischen Analyse ist der Scanner, welcher oftmals ausgehend von höheren Spezifikationen automatisch generiert wird int gcd (int i, int j) { while (i != j) { if (i > j) i = i – j; else j = j – i; } printf(“%d\n”,i); } Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-3 Token int int int { i ) ( j = j j – } “%d\n” ) gcd i j while != { i ) i ; = i printf , ; ( , ) ( j if > i – else j ; ( I } Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie 2 Fragen Wie beschreiben wir die lexikalische Struktur? (1/2) Wie beschreiben wir die lexikalische Struktur einer Programmiersprache? 1. Versuch: Liste aller Tokens if else long int short char ; , : ( ) { } … Mit anderen Worten, was sind die Tokens (Symbole) Wie implementieren wir den Scanner nachdem wir wissen was die Tokens sind? Aber was ist mit den Konstanten (Ganzzahlen, Fliesskommazahlen, Zeichenketten)? Es können nicht alle aufgelistet werden, es gibt ~8 Milliarden 32-bit integer und floating-point Konstanten und eine unendliche Anzahl von Zeichenfolgenkonstanten Das gleiche Problem gilt für Bezeichner (Variablen, Funktionen und benutzerdefinierte Typnamen) Lösung: Wir brauchen einen Weg um kurz und prägnant Klassen von Tokens zu beschreiben, welche eine große Anzahl von verschiedenen Werten abdecken können Lexikalische Analyse Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-5 6. Sprachen, Compiler und Theorie Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-6 6. Sprachen, Compiler und Theorie Wie beschreiben wir die lexikalische Struktur? (2/2) Abkürzungen von regulären Ausdrücken 2. Versuch: Reguläre Ausdrücke Zeichenfolgen Muster (patterns) welche zum Auffinden von passendem Text verwendet werden können Werden mit folgenden Ausdrücken rekursiv ausgedrückt r=‘c1c2c3...cn‘ ist äquivalent zu r=c1.c2.c3.....cn Zeichenbereiche Ein Zeichen Der leeren Zeichenfolge ε Der Verkettung zweier regulärer Ausdrücke r=[c1-cn] ist äquivalent zu r=c1|c2|c3|...|cn für die aufeinander folgende Reihe von n Zeichen beginnend mit c1 und endend mit cn z.B. r=[a-d] ist äquivalent zu r=a|b|c|d r1.r2 ist der Wert von r1 gefolgt vom Wert von r2 Der Alternative zweier regulärer Ausdrücke Kleenesche Hülle + r1|r2 ist der Wert von r1 oder der Wert von r2 r+ ist ein oder mehrere Vorkommen des Wertes von r Der Kleenesche Hülle * (ode einfach Hülle oder Stern) Formal definiert als r+ = r.r* r* ist kein oder mehrere Vorkommen des Wertes von r1 Runde Klammern können zum Gruppieren von regulären Ausdrücken verwendet werden, um Zweideutigkeiten bei Kombinationen auszuschließen Das Symbol . steht für jeden Charakter außer „newline“ z.B. bedeutet r1.r2|r3 nun (r1.r2)|r3 oder r1.(r2|r3)??? Lexikalische Analyse Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-7 Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Lexikalische Analyse: Reguläre Ausdrücke bei der Arbeit Ein GGT Programm in C int gcd (int i, int j) { while (i != j) { if (i > j) i = i – j; else j = j – i; } printf(“%d\n”,i); } Reguläre Ausdrücke digit=[0-9] letter=[a-z]|[A-Z] punct=\|% INT=‘int’ WHILE=‘while’ IF=‘if’ ID=letter.(letter|digit)* LPAREN=( RPAREN=) COMMA=, SEMI=; LBRACE={ RBRACE=} EQ== MINUS=GT=> SC=“.(letter|digit|punct)*.” Ein genauerer Blick auf die lexikalische Analyse Wie behandelt der lexikalische Analysator Leerzeichen, Kommentare und Konflikte zwischen regulären Ausdrücken? Tokens INT INT INT LBRACE ID:i RPAREN LPAREN ID:j EQ ID:j ID:j MINUS RBRACE SC:“%d\n” RPAREN ID:gcd ID:i ID:j WHILE NEQ LBRACE ID:i RPAREN ID:i SEMI EQ ID:i ID:printf COMMA SEMI LPAREN COMMA RPAREN LPAREN ID:j IF GT ID:i MINUS ELSE ID:j SEMI LPAREN ID:i RBRACE Leerzeichen int gcd (int i, int j) { Kommentare einer Programmiersprache /* gcd */ int gcd (int i, int j) { Konflikte zwischen regulären Ausdrücken Gegeben: WHILE=‘while’ ID=letter.(letter|digit)* Beide reguläre Ausdrücke decken die Zeichenfolge „while“ ab. Welcher Ausdruck soll aber nun gewählt werden? Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-9 6. Sprachen, Compiler und Theorie Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie Handhabung von Leerzeichen Handhabung von Kommentaren (1/2) Leerzeichen können als Token durch folgende Regel erkannt werden Alternative 1: Präprozessoren Spezielles Programm welches eine Datei einliest, Kommentare entfernt, andere Operationen wie Makro-Expansion ausführt und eine Ausgabedatei schreibt, welche vom lexikalischen Analysator gelesen wird. Quellprogramme können auch Steueranweisungen enthalten, die nicht zur Sprache gehören, z.B. Makro-Anweisungen. Der lexikalische Analysator behandelt die Steueranweisungen und entfernt sie aus dem Tokenstrom. Präprozessor-Anweisungen in C und C++ WS=(\n|\r|\t|\s)* \n ist ein „escape“ Zeichen für „newline“ (neue Zeile) \r ist ein „escape“ Zeichen für „carriage return“ (Wagenrücklauf) \t ist ein „escape“ Zeichen für „tab“ (Tabulator) \s ist ein „escape“ Zeichen für „space“ (Leerzeichen) Das Leerzeichentoken WS ist normalerweise unwichtig für die Syntax einer Programmiersprache, weshalb es einfach vom Tokenstrom gelöscht werden kann z.B. #include und #define Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-10 6.2-11 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Handhabung von Kommentaren (2/2) Handhabung von Konflikten Gegeben sind zwei reguläre Ausdrücke r1 und r2, welche eine Teileingabe p=‘c1..ck‘ finden. Welche soll nun ausgewählt werden? Alternative 2: Kommentartoken In Abhängigkeit von der Komplexität von Kommentaren ist eine Beschreibung via regulärer Ausdrücke vielleicht möglich Alternative 1: Längster Fund Zeilenkommentare (single line comments) können mit regulären Ausdrücken gefunden werden SLC=‘//’.*.$ $ ist ein spezielles Symbol, welches das Ende einer Zeile bedeutet Nehme solange Eingabezeichen hinzu bis weder r1 noch r2 passen. Entferne ein Zeichen und entweder r1 oder r2 muss passen. Die Teileingabe p ist der längste Fund und wenn nur einer von r1 oder r2 passt, dann wähle ihn. Beispiel: r1=‘while’ r2=letter.(letter|digit)* Eingabe int while48; … Wenn p=‘while’, beide, r1 und r2 passen Wenn p=‘while48;’ weder r1 noch r2 passen Wenn p=‘while48’ nur r2 passt, wähle r2 aus Findet Texte wie // Dies ist ein Kommentar Einige Kommentare sind zu kompliziert um durch reguläre Ausdrücke gefunden zu werden Willkürlich verschachtelte Kommentare /* level 1 /* level 2 */ back to level 1 */ Wird Normalerweise vom Präprozessor behandelt Alternative 2: Regelpriorität Wenn der längste Fund immer noch in einem Konflikt endet, dann wähle den erste regulären Ausdruck aus der lexikalischen Definition der Sprache Lexikalische Analyse Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-13 6. Sprachen, Compiler und Theorie Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie Weitere spezielle Probleme Implementierung eines lexikalischen Analysators Reservierte Schlüsselwörter Wie übertragen wir reguläre Ausdrücke in einen lexikalischen Analysator? Schlüsselwörter dürfen nicht in Bezeichnern (Namen) verwendet werden Konvertiere reguläre Ausdrücke zu einem deterministischen endlichen (finite) Automaten (DFA) Groß-/Kleinschreibung intern nur eine Repräsentation verwenden, weshalb eine Anpassung notwendig ist Textende Das Textende muss dem Syntaxanalysator mitgeteilt werden, weshalb ein eind-of-text Symbol (eot) eingefügt werden muss Vorgriff (lookahead) um mehrere Zeichen Warum??? DFAs sind einfacher zu simulieren als reguläre Ausdrücke Schreibe ein Programm zum Simulieren eines DFAs Der DFA erkennt die Tokens im Eingabetext und wird der lexikalische Analysator Wenn ein Token erkannt wurde, dann kann eine benutzerdefinierte Aktion ausgeführt werden z.B. überprüfe, ob der Wert einer Ganzzahlkonstante in eine 32-bit integer passt gelesene aber nicht verwendete Zeichen müssen für nächsten Test berücksichtigt werden Lexikalische Fehler Die Verletzung der Syntax (z.B. falscher Wertebereich) wird gemeldet und trotzdem an den Syntaxanalysator weitergegeben Die Konvertierung von regulären Ausdrücken zu DFAs und das Schreiben eines Programms zum Simulieren des DFA kann entweder von Hand vorgenommen werden oder von einem anderen Programm, welches lexikalischer Analysegenerator genannt wird. Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-14 6.2-15 Regular Expressions + Actions Lexical Analyzer Generator Lexical analyzer source code High-level language compiler Lexical analyzer Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Endliche Automaten Ein Beispiel eines endlichen Automaten Formal, ein endlicher Automat M ist ein Quintupel M=(Q,Σ,q,F,δ), wobei Q ist eine endliche Menge von Symbolen genannt Zustände (states) Σ ist eine endliche Menge von Eingabesymbolen genannt Alphabet q ist der Startzustand F ist eine endliche Menge von finalen oder akzeptierenden Zuständen. F ist eine, möglicherweise leere, Teilmenge von Q. δ ist eine Übergangsfunktion L(M), oder die Sprache von M, ist die Menge von endlichen Zeichenketten von Symbolen aus dem Alphabet Σ welche vom Automaten M akzeptiert werden Q={q1,q2,q3,q4,q5} Σ={a,b} q=q1 F={q4} δ= {((q1,a),q2),((q1,b),q3), ((q2,a),q4),((q2,b),q2), ((q3,a),q4),((q3,b),q5), ((q4,a),q5),((q4,b),q5), ((q5,a),q5),((q5,b),q5)} b a q2 a,b a q1 q4 b Eingabe: a,b q5 a q3 b abba a Nicht akzeptiert! Welche Sprache akzeptiert M? (ab*a)|ba Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-17 6. Sprachen, Compiler und Theorie Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-18 6. Sprachen, Compiler und Theorie Zwei Arten von endlichen Automaten: Deterministisch und Nichtdeterministisch Ein Beispiel eines nichtdeterministischen Automaten (NFA) Deterministische endliche Automaten Die Übergangsfunktion ist formal definiert als δ:Q x Σ -> Q Ein Eingabesymbol und ein Zustand ergeben den einzigen nächsten Zustand Nichtdeterministische endliche Automaten (NFA) Q={q1,q2,q3,q4,q5} Σ={a,b} q=q1 F={q4} δ= {((q1,a),{q2,q3}),((q1,b),{q3}), ((q2,a),{q4}),((q2,b),{q2}), ((q3,a),{q4}),((q3,b),{q5}), ((q4,a),{q5}),((q4,b),{q5}), ((q5,a),{q5}),((q5,b),{q5})} Die Übergangsfunktion ist formal definiert als δ:Q x Σ -> φQ (Potenzmenge von Q) Ein Eingabesymbol und ein Zustand ergeben eine Menge von möglichen nächsten Zuständen. Die Menge kann auch leer sein. Abgesehen von den Übergangsfunktionen sind DFAs und NFAs gleich b a q2 q1 a,b Eingabe: a,b a q4 q3 a,b q5 a b abba a Nicht akzeptiert! Welche Sprache akzeptiert M? (ab*a)|ba Gleiche wie zuvor beim DFA… Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-19 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-20 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Eine interessante Sache über endliche Automaten Eine Nebenbemerkung zu endlichen Automaten Auch wenn es so aussieht als ob Nichtdeterminismus einem endlichen Automaten mehr Ausdruckskraft verleiht, sind NFAs und DFAs formal äquivalent Jeder NFA kann in einen DFA umgewandelt werden und ungekehrt Warum machen wir dann aber die Unterscheidung? Es ist einfacher reguläre Ausdrücke in NFAs umzuwandeln Es ist einfacher DFAs zu simulieren MESI cache coherence protocol Endliche Automaten sind auch für andere Dinge als lexikalische Analyse nützlich (Courtesy: John Morris, University of Western Australia) Die meisten Systeme, welche Transaktionen zwischen einer endlichen Anzahl von Zuständen vornehmen, können mit endlichen Automaten modelliert werden Beispiele Beschreibung, Simulation, Überprüfung und Implementierung von Protokollen Bauen von schnellen, zustandsbasierten Schaltungen (siehe Kapitel 2) Vending machine automata 25¢ 25 ¢ 0¢ 25 ¢ 50 ¢ 75¢ 25 ¢ 50 ¢ 50¢ 50 ¢ Lexikalische Analyse 6.2-21 6. Sprachen, Compiler und Theorie Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Beispiel: Reguläre Ausdrücke nach NFA Regulärer Ausdruck c є Alternative: r1|r2 Verkettung: r1.r2 r1 є r2 r1 є є r1: r1=a.b r2=c.d r3=r1|r2 r=r3* є Leere Zeichenkette: є a r=(‘ab’|’cd’)* Faktor: NFA Charakter: c є c r2: r d є є r3: є r2 є є є b r4: є є Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-22 6. Sprachen, Compiler und Theorie Bau des lexikalischen Analysators: Reguläre Ausdrücke nach NFA Kleenesche Hülle: r* vend 25 ¢ 50 ¢ Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 25 ¢ 6.2-23 є є a є b c є d a є b c є d a є b c є d є є є є є Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-24 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Theoretische Ergebnisse 1 Bau des lexikalischen Analysators: NFA nach DFA Definitionen Endliche Automaten akzeptieren oder erkennen Sprachen Reguläre Ausdrücke erzeugen Sprachen L(M) ist die akzeptierte Sprache vom endlichen Automaten M L(R) ist die erzeugte Sprache vom regulären Ausdruck R L(R) and L(M) sind Mengen von endlichen Zeichenketten von Symbolen der Alphabete ΣR und ΣM LR ist die Menge { L(r):alle reguläre Ausdrücke r } LN ist die Menge { L(n):alle nichtdeterministische endliche Automaten n } Regulärer Ausdruck LR ist eine Untermenge von LN Nichtdeterministische endliche Automaten akzeptieren alle von regulären Ausdrücken erzeugten Sprachen Warum? Wir haben gezeigt wie beliebige reguläre Ausdrücke zu einem NFA konvertiert werden können Beschreiben LR und LN die gleichen Mengen? Es stellt sich heraus, dass die Antwort ja ist Beweis durch zeigen das LN eine Untermenge von LR ist oder das jeder nichtdeterministische endliche Automat in einen regulären Ausdruck umgewandelt werden kann Siehe jedes gute theoretische Informatik Buch für Details: Introduction to Automata Theory, Languages, and Computation by Hopcroft and Ullman Introduction to the Theory of Computation by Michael Sipser Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 NFA r=(‘ab’|’cd’)* Konvertiere NFA nach DFA є unter Verwendung der є q3 Konstruktion von є q1 q2 Untermengen є q7 Beschrifte jeden DFA Zustand als die vom vorherigen Zustand in einem Schritt erreichbare DFA Menge von Zuständen a Wenn irgendein NFA {q1,q2,q3, Zustand in der Menge der erreichbaren Zustände ein q7,q12} Endzustand ist, dann ist der c ganze DFA Zustand ein Endzustand 6.2-25 6. Sprachen, Compiler und Theorie a c q4 q8 є є q5 q9 b d q6 q10 є q11 є q12 є a {q4,q5} {q8,q9} b {q2,q3,q6, q7,q11,q12} c a {q2,q3,q7, d q10,q11,q12} c Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-26 6. Sprachen, Compiler und Theorie Theoretische Ergebnisse 2 Einige Bemerkungen zu DFAs Definitionen Erinnerung, LN ist die Menge { L(n):alle nichtdeterministische endliche Automaten n } LD ist die Menge { L(d): alle deterministischen endlichen Automaten d } LN ist eine Untermenge von LD und LR ist eine Untermenge von LD Deterministische endliche Automaten akzeptieren alle Sprachen die auch von nichtdeterministischen endlichen Automaten akzeptiert werden Neben Transitivität, akzeptieren DFAs auch alle durch reguläre Ausdrücke generierte Sprachen Warum? Wir haben gezeigt wie jeder NFA zu einem DFA und jeder reguläre Ausdruck zu einem NFA konvertiert werden kann Ein DFA M gebaut unter Verwendung der Konstruktion von Untermengen kann nicht minimal sein Mit anderen Worten, es könnte einen Automaten M‘ geben wobei L(M)=L(M‘) und M‘ hat weniger Zustände als M Minimale DFAs sind besser geeignet für Implementierungszwecke Weniger Zustände benötigen weniger Speicher und führen generell zu schnelleren Simulationen Die meisten automatischen Werkzeuge zum Konvertieren von NFAs nach DFAs führen einen Optimierungsprozess aus um die Anzahl der DFA Zustände zu reduzieren Beschreiben LN und LD die gleichen Mengen? Es stellt sich heraus, dass die Antwort ja ist Beweis durch zeigen das LD eine Untermenge von LN ist oder das jeder DFA in einen NFA umgewandelt werden kann Noch mal, siehe jedes gute theoretische Informatik Buch für Details Das Finden eines minimalen DFAs ist ein sehr hartes Problem (auch NP-vollständig bezeichnet), weshalb Optimierer keinen minimalen DFA garantieren können Praktisch gesehen ist das Ok, obwohl weniger Zustände immer besser ist ☺ Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-27 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-28 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Bau des lexikalischen Analysators: DFA zu Code Bau des lexikalischen Analysators: Letzter Schritt a DFAs können effizient Simuliert werden indem ein tabellenbasierter Algorithmus verwendet wird void dfa (char *in) { s = in; state = start_state; while(1) { c = *s++; state = table[state][c]; if (final[state)]) { printf(“Accepted %s\n”,in); break; } } } b q2 a q3 c q1 a c d q4 q5 c Tabelle a b c d q1 q2 q6 q4 q6 q2 q6 q3 q6 q6 q3 q2 q6 q4 q6 q4 q6 q6 q6 q5 q5 q2 q6 q4 q6 q6 q6 q6 q6 q6 DFA Simulatorcode wird der Kern des lexikalischen Analysators Wenn der DFA in einem Endzustand ist Führe mit dem letzten, passenden regulären Ausdruck, entsprechend dem längsten Fund und/oder der Regelpriorität, die verbundene, benutzerdefinierte Aktion aus Merke aktuelle Stelle im Eingabestrom und gebe Token an Tokenkonsument (parser) weiter Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-29 6. Sprachen, Compiler und Theorie import java.io.IOException; %% %public %class Scanner %type void %eofval{ return; %eofval} %{ public static void main (String args []) { Scanner scanner = new Scanner(System.in); try { scanner.yylex(); } catch (IOException e) { System.err.println(e); } } %} comment space digit integer real IF THEN ELSE = = = = = = = = ("#".*) [\ \t\b\015]+ [0-9] {digit}+ ({digit}+"."{digit}*|{digit}*"."{digit}+) ("if") "then" else %% {space} break; } {comment} break; } {integer} break; { System.out.println("space"); { System.out.println("comment"); { System.out.println("Integer CONSTANT\t" + yytext()); 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 Die nächsten Vorlesungen (Kapitel 2 vom Buch) break; } {real} { System.out.println("REAL CONSTANT\t" + yytext()); { System.out.println("IF Token\t" + yytext()); { System.out.println("THEN Token\t" + yytext()); { System.out.println("ELSE Token\t" + yytext()); break; } {IF} break; } {THEN} break; } {ELSE} break; } \n { System.out.println("NL"); } { System.out.println("ADD"); break; } "-" { System.out.println("SUB"); break; } "*" { System.out.println("MUL"); break; } "/" { System.out.println("DIV"); break; } "%" syntaktische Analyse Automatentheorie und automatische Generierung Parsern Reguläre und kontextfreie Grammatiken Nachfolgende 5 Vorlesungen Namen, Geltungsbereiche und Binden (Kapitel 3) Kontrollfluss (Kapitel 6) Unterprogramme und Kontrolle über Abstraktion (Kapitel 8) Zusammenbauen eines lauffähigen Programms (Kapitel 9) Objekt-orientierte Programmierung (Kapitel 10) break; "+" Plan für nächste Vorlesungen { System.out.println("MOD"); break; } "(" { System.out.println("LPAR"); break; } ")" { System.out.println("RPAR"); break; } . { System.out.println("error" + "+" + yytext() + "+"); break; } Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-30 6. Sprachen, Compiler und Theorie Eine reale JLex lexikalische Spezifikation für einen Kalkulatorsprache 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-31 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-32 6. Sprachen, Compiler und Theorie Pumping Lemma für reguläre Sprachen (1/2) Informatik II SS 2004 Das Pumping Lemma ist eine Methode, um heraus zu finden, ob eine Sprache nicht regulär. Teil 6: Sprachen, Compiler und Theorie 2a – Lexikalische Analyse Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Pumping Lemma für reguläre Sprachen (2) Beweis Satz: Sei L eine reguläre Sprache. Dann gibt es eine Zahl (Konstante) n, derart dass alle Wörter (Zeichenreihen) w in L mit |w | ≥ n gilt, dass wir in w drei Wörter w = xyz zerlegen können, für die gilt: 6.2-34 Jede Zeichenreihe, deren Länge nicht kleiner ist als die Anzahl der Zustände, muss bewirken, dass ein Zustand zweimal durchlaufen wird (Schubfachschluss). |y | ≥ 1 (oder y ≠ ε) |xy | ≤ n, Für alle k ≥ 0 gilt, dass die Zeichenreihe xy kz auch in L enthalten ist. Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-35 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-36 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Beispiele Rückblick Sind diese Sprachen (nicht) regulär? w = anbn w= ab|c Die Anwendung des Pumping Lemmas ist ein kreativer Vorgang, da es kein „mechanisches“ Vorgehen für den Einsatz gibt. Reguläre Sprachen, reguläre Ausdrücke, (deterministische und nichtdeterministische) endliche Automaten Wichtige Algorithmen vergleiche Ableitungsregeln aus der Mathematik (Analysís) Konvertierung von regulären Ausdrücken zu nichtdeterministischen endlichen Automaten (NFA) (inklusive Beweise) Konvertierung von nichtdeterministischen endlichen Automaten zu deterministischen endlichen Automaten (DFA) Tabellenbasierte Simulation von DFAs Lexikalische Analyse und Scanner Verwenden reguläre Ausdrücke zur Definition der lexikalischen Struktur (Symbole/Token) einer Sprache Verwenden die Theorie der regulären Sprachen zur Erzeugung eines Scanners ausgehend von der Beschreibung der lexikalischen Struktur einer Programmiersprache anhand von regulären Ausdrücken Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-37 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Reguläre Ausdrücke (1/3) Reguläre Ausdrücke (2/3) Werden mit folgenden Ausdrücken rekursiv ausgedrückt: Ein Zeichen c aus dem Alphabet Σ, oder der leeren Zeichenfolge ε, oder der Verkettung zweier regulärer Ausdrücke, r1 . r2, oder der Alternative zweier regulärer Ausdrücke, r1 | r2, oder der Kleenesche Hülle * (ode einfach Hülle oder Stern), r1*. Zeichenfolgen r=‘c1c2c3...cn‘ = c1.c2.c3.....cn Zeichenbereiche Ein regulärer Ausdruck ist gedacht um Zeichenketten aus Zeichen aus einem Alphabet Σ zu erzeugen Die Menge aller durch einen regulären Ausdruck R erzeugte Zeichenketten wird die Sprache von R genannt und wird symbolisiert durch L(R) r=[c1-cn] = c1|c2|c3|...|cn Kleenesche Hülle + r+ = r.r* Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-38 6.2-39 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-40 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Reguläre Ausdrücke (3/3) Endliche Automaten (1/5) Reguläre Ausdrücke Erzeugt… ‘if’|’then’|’else’ Die Zeichenketten if, then, or else. a.(a|b|c)*.a Formal, ein endlicher Automat M ist ein Quintupel M=(Q,Σ,q0,F,δ), wobei Alle Zeichenketten mit a’s, b’s und c’s, welche mit einem a beginnen und enden. a.(a|b|c)*.(b|c).a Alle Zeichenketten mit a’s, b’s, und c’s, welche mit einem a beginnen und einem einzelnen a enden. (a|b|c)*.a.b.a.(a|b|c)* Alle Zeichenketten mit a’s, b’s und c’s, welche die Teilzeichenkette aba enthalten. (b|c)*.a.(b|c)*.a.(b|c)*.a.(b|c)* Alle Zeichenketten mit a’s, b’s und c’s, welche exakt drei a’s beinhalten. Q ist eine endliche Menge von Symbolen genannt Zustände (states) Σ ist eine endliche Menge von Eingabesymbolen genannt Alphabet q0 ist der Startzustand F ist eine endliche Menge von finalen oder akzeptierenden Zuständen. F ist eine, möglicherweise leere, Teilmenge von Q. δ ist eine Übergangsfunktion Ein endlicher Automat ist geeignet um Zeichenketten aus Zeichen aus dem Alphabet Σ zu akzeptieren L(M), oder die Sprache von M, ist die Menge von endlichen Zeichenketten von Symbolen aus dem Alphabet Σ welche vom Automaten M akzeptiert werden Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-41 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Endliche Automaten (2/5) Endliche Automaten (3/5) Deterministische endliche Automaten (DEA/DFA) Übergänge sind deterministisch 6.2-42 Übergang von einzelnem Zustand zu einzelnem Zustand δD:Q x Σ -> Q Übergangsfunktionen können als eine Tabelle oder Zustandsübergangsdiagramm geschrieben werden Tabelle Beispiel DFA Q={q1,q2,q3,q4,q5} Σ={a,b} q0=q1 F={q4} δ= q1 q2 q3 q4 q5 {((q1,a),q2),((q1,b),q3), ((q2,a),q4),((q2,b),q2), ((q3,a),q4),((q3,b),q5), ((q4,a),q5),((q4,b),q5), ((q5,a),q5),((q5,b),q5)} q2 q4 b q3 a,b q5 a b Lexikalische Analyse 6.2-43 a,b a q1 Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 b q3 q2 q5 q5 q5 b a Zustandsübergangsdiagramm a q2 q4 q4 q5 q5 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-44 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Endliche Automaten (4/5) Endliche Automaten (5/5) Nichtdeterministische endliche Automaten (NEA/NFA) Übergänge sind nichtdeterministisch Übergang von einzelnem Zustand zu einer Menge von möglichen Zuständen δD:Q x Σ -> P(Q) ( P(Q) Potenzmenge von Q ) Das Alphabet ist erweitert um Übergänge der leeren Zeichenkette є zu erlauben Tabelle Beispiel NFA Q={q1,q2,q3,q4,q5} Σ={a,b,є} q0=q1 F={q4} δ= { ((q1,є),{q2,q3}), ((q2,a),{q5}),((q2,b),{q2,q4}), ((q3,a),{q4}),((q3,b),{q5}), ((q4,a),{q5}),((q4,b),{q5}), ((q5,a),{q5}),((q5,b),{q5}) } a {q5} {q5} {q4} {q5} {q5} є {q2,q3} {q5} {q5} {q5} {q5} q1 q2 q3 q4 q5 a b q2 є a,b b a,b q4 q1 Zustandsübergangsdiagramm є 6.2-45 6. Sprachen, Compiler und Theorie b Wir können jeden regulären Ausdruck in einen NFA konvertieren und umgekehrt c Wir können jeden NFA in einen DFA konvertieren und umgekehrt r1 Alternative: r1|r2 Verkettung: r1.r2 є є r2 є є r1 є r2 є Kleenesche Hülle: r* Lexikalische Analyse 6.2-47 є Leere Zeichenkette: є Gegeben ist ein beliebiger NFA MN und DFA MD, mit L(MN) = L(MD) Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 NFA Charakter: c Gegeben ist beliebiger regulärer Ausdruck R und ein NFA MN, mit L(R)=L(MN) 6.2-46 Regulärer Ausdruck Es gibt einen regulären Ausdruck R so dass gilt L(R) = L(X), oder Es gibt einen DFA MD so dass gilt L(MD) = L(X), oder Es gibt einen NFA MN so dass gilt L(MN) = L(X) Die Sprachen der regulären Ausdrücke, DFA Sprachen und NFA Sprachen sind alle regulär Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Reguläre Ausdrücke nach NFA (1/2) Eine Sprache L(X) ist Regulär wenn: Lexikalische Analyse 6. Sprachen, Compiler und Theorie Reguläre Sprachen q5 a q3 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 b {q5} {q2,q4} {q5} {q5} {q5} є є r є Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-48 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Reguläre Ausdrücke nach NFA (2/2) NFAs nach DFAs (1/2) Definition: r=‘ab’|’cd’ Faktor: r1: r1=‘ab’ r2=‘cd’ r=r1.r2 a b r2: Gegeben NFA M=(Q,Σ,q,F,δ) und DFA MD=(QD,Σ,qD,FD,δD) c d d є є r3: b є c є a є b c є d є-FZ(s) ist die Menge aller Zustände, welche in s beinhaltet sind, plus aller von den Zuständen in s erreichbaren Zustände unter ausschließlicher Verwendung des є Überganges QD=P(Q), z.B., QD ist die Menge aller Untermengen von Q FD = {S:∀S ∈QD wobei S∩F ≠ {} } qD= є-FZ (q) δD({q1,q2,…,qk},a) = є-FZ(δ(q1,a)∪ δ(q2,a)∪… ∪(δ(qk,a)) є є Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-49 6. Sprachen, Compiler und Theorie NFAs nach DFAs (2/2) b NFA q2 є a,b q4 q1 є q3 a b a,b Schritt 1: Der Startzustand Schritt 2: Zustand {q1,q2,q3} q5 a,b a {q4,q5} a,b Schritt 3: Zustand {q4,q5} b {q2,q4,q5} b δD({q1,q2,q3},b) = є-FZ(δ(q1,b)∪δ(q2,b)∪δ(q3,b)) = є-FZ({q2,q4}∪{q2,q4}∪ {q5}) = {q2,q4,q5} {q5} a {q1,q2,q3} δD({q1,q2,q3},a) = є-FZ(δ(q1,a)∪δ(q2,a)∪δ(q3,a)) = є-FZ({q5}∪{q5}∪ {q4}) = {q4,q5} a b DFA qD= є-FZ({q1}) = {q1,q2,q3} … Schritt 4: Zustand {q2,q4,q5} … Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-51 Lexikalische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.2-50 6. Sprachen, Compiler und Theorie Der Kompilationsprozess (-phasen) Informatik II SS 2004 Scanner (lexikalische Analyse) Lese Programm und konvertiere Zeichenstrom in Marken (tokens). Parser (syntaktische Analyse) Lese Tokenstrom und generiere Parserbaum (parse tree). Semantische Analyse Teil 6: Sprachen, Compiler und Theorie 3 – Syntaktische Analyse Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Traversiere Parserbaum, überprüfe nicht-syntaktische Regeln. Zwischencodegenerierung Traversiere Parserbaum noch mal, gebe Zwischencode aus. Optimierung Untersuche Zwischencode, versuche ihn zu verbessern. Zielcodegenerierung Übersetze Zwischencode in Assembler-/Maschinencode Optimierung Maschinenebene Untersuche Maschinencode, versuche ihn zu verbessern. Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Lexikalische Analyse Der Kompilationsprozess (-phasen) Die lexikalische Analyse gruppiert Zeichensequenzen in Tokens (Marken) bzw. Symbole Tokens sind die kleinste „Bedeutungseinheit“ (units of meaning) im Kompilationsprozess und sind die Grundlage (foundation) fürs Parsen (Syntaxanalyse) Die Compilerkomponente zum Ausführen der lexikalischen Analyse ist der Scanner, welcher oftmals ausgehend von höheren Spezifikationen automatisch generiert wird Scanner (lexikalische Analyse) Lese Programm und konvertiere Zeichenstrom in Marken (tokens). Parser (syntaktische Analyse) Lese Tokenstrom und generiere Ableitungsbaum (parse tree). Semantische Analyse 6.3-3 Traversiere Parserbaum, überprüfe nicht-syntaktische Regeln. Zwischencodegenerierung Traversiere Parserbaum noch mal, gebe Zwischencode aus. Optimierung Untersuche Zwischencode, versuche ihn zu verbessern. Zielcodegenerierung Übersetze Zwischencode in Assembler-/Maschinencode Optimierung Maschinenebene Untersuche Maschinencode, versuche ihn zu verbessern. Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-2 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Syntaktische Analyse Die lexikalische Analyse erzeugt einen Strom von Symbolen (Tokens) Falsche Detailebene für die semantische Analyse und Codegenerierung Die Syntaxanalyse gruppiert eine Zeichenfolge von Tokens in Ableitungsbäume (Struktur-/Parser-/Syntaxbäume), was durch die kontextfreie Grammatik gelenkt wird, die die Syntax der zu kompilierenden Sprache spezifiziert Beispiel Syntaxanalyse conditional -> if ( expr ) block else block Ableitungsbäume repräsentieren die Phrasenstruktur eines Programms und sind die Grundlage für die semantische Analyse und Codegenerierung Die Compilerkomponente zum Ausführen der syntaktischen Analyse ist der Parser, welcher oftmals ausgehend von höheren Spezifikationen automatisch generiert wird conditional Token if > i – else j ; ( j = j j – } i ) i ; = i ( expr id comp id i > j if block ) statement statement 6.3-5 digit=[0-9] letter=[a-z] id=letter.(letter|digit)* Kann Identitäten von id durch Substitution entfernen: id=[0-9].([a-z]|[0-9])* id ist ein regulärer Ausdruck id j - i i id op id i - j 6.3-6 Eine kontextfreie Grammatik (KFG/CFG) ist eine rekursive Definition einer Sprache mit: Einem Alphabet Σ von Symbolen Eine Menge von Produktionen (oder Regeln) der Form Identitäten: digits=[0-9]+ sum=expr.’+’.expr expr=(‘(‘.sum.’)’) | digits Kann nicht Identitäten von expr durch Substitution entfernen: expr ist durch Rekursion definiert expr ist kein regulärer Ausdruck symbol -> symbol symbol … symbol Ein Startsymbol Eine Menge von nicht-terminalen Symbolen aus dem Alphabet Σ, welche auf der linken oder rechten Seite einer Produktionsregel erscheinen darf (convention: written in all capital letters) Eine Menge von terminalen Symbolen aus dem Alphabet Σ, welche nur auf der rechten Seite einer Produktionsregel erscheinen darf. (convention: written in all lower case letters) Die Menge aller von einer CFG G erzeugten Strings wird die Sprache von G genannt und wird symbolisiert durch L(G) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 op Kontextfreie Grammatiken (1/4) Warum können wir nicht reguläre Ausdrücke zum Beschreiben der Syntax einer Programmiersprache verwenden? Betrachte die folgenden Beschreibungen: Identitäten: id Syntaktische Analyse j expr expr 6. Sprachen, Compiler und Theorie Syntaxbeschreibung = = Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie id id Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 block else 6.3-7 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kontextfreie Grammatiken (2/4) Kontextfreie Grammatiken (3/4) Kurzschreibweisen: Alternativen s->a1..an|b1..bn|…|z1..zn = s->a1..an s->b1..bn … s->z1..zn Wenn eine CFG G zum Parsen von Programmiersprachen verwendet wird, dann gilt Beispiel: Kleenesche * Hülle L(G) ist die Menge von gültigen Quellprogrammen, und die terminalen Symbole sind die Tokens, welche vom Scanner zurückgeliefert werden Klammergrammatik expr -> LPAREN sum RPAREN expr -> INT sum -> expr PLUS expr s->s1* s->s1’ s1’->s1 s1’ s1’->є Terminale: {PLUS,LPAREN,RPAREN,INT} Nichtterminale: {sum,expr} Startsymbol: {expr} Σ = Terminale ∪ Nichtterminale Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-9 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kontextfreie Grammatiken (4/4) Ableitungen (1/4) Eine CFG G erzeugt Zeichenketten durch: Beginne mit dem Startsymbol Ersetze ein nichtterminales Symbol sk auf der rechten Seite mit der rechten Seite dieses Nichtterminals Grammatik: expr -> ( sum ) expr -> INT sum -> expr + expr s ⇒ s1 s2 … sn Gegeben: sk -> k1…km Dann: s ⇒ s1…sk…sn ⇒ s1…k1…km…sn Wiederhole bis nur noch Terminal auf der linken Seite sind Jeder Schritt in diesem Prozess wird Ableitung (derivation) genannt und jede Zeichenkette von Symbolen entlang dieses Weges wird Satzform genannt. Die abschließende Satzform, welche nur Terminalsymbole enthält, wird ein Satz (sentence) der Grammatik oder auch das Ergebnis (yield) des Ableitungsprozesses genannt Mögliche Ableitungen: expr ⇒ ( sum ) ⇒ ( expr + expr ) ⇒ ( INT + expr ) ⇒ (INT + ( sum ) ) ⇒ (INT + ( expr + expr ) ) ⇒ (INT + ( INT + expr ) ) ⇒ (INT + (INT + INT ) ) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-10 6.3-11 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Ableitungen (2/4) Rechtsseitige Ableitungen (rightmost derivations) Eine Ableitung (oder Herleitung) ist eine Operationenfolge von Ersetzungen, welche zeigen wie eine Zeichenkette von Terminalen (Tokens), ausgehend vom Startsymbol einer Grammatik, abgeleitet werden kann Unter der Annahme es gibt eine Produktion X -> y, eine einzelne Ersatzoperation oder ein Ableitungsschritt, dann können diese beschrieben werden durch αXβ⇒αγβ, für beliebige Zeichenketten von Grammatiksymbolen α, β und γ Kurzschreibweisen: Ersetze jeweils das äußerste rechte Nichtterminalsymbol in jedem Ableitungsschritt Wird manchmal auch die kanonische Ableitung genannt Linksseitige Ableitungen (leftmost derivations) Ableitungen (3/4) Ersetze jeweils das äußerste linke Nichtterminalsymbol in jedem Ableitungsschritt Siehe vorherige Folie Andere Ableitungsreihenfolgen sind möglich Die meisten Parser suchen nach entweder einer rechtsseitigen oder linksseitigen Ableitung α ⇒* β bedeutet β kann abgeleitet werden von α in 0 oder mehr Schritten α ⇒+ β bedeutet β kann abgeleitet werden von α in 1 oder mehr Schritten α ⇒n β bedeutet β kann abgeleitet werden von α in genau n Schritten Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-13 6. Sprachen, Compiler und Theorie Beispiel Ableitungen Linksseitige Ableitungen (leftmost derivations): Grammatik: expr -> ( sum ) | INT sum -> expr + expr Für jeden Ableitungsschritt αXβ⇒αγβ, muss X das äußerte linke Nichtterminal im String von Symbolen αXβ sein Wird verwendet in LL(k) bzw. top-down parsen Linksseitige Ableitung: expr ⇒ ( sum ) ⇒ ( expr + expr ) ⇒ ( INT + expr ) ⇒ (INT + ( sum ) ) ⇒ (INT + ( expr + expr ) ) ⇒ (INT + ( INT + expr ) ) ⇒ (INT + (INT + INT ) ) Rechtsseitige Ableitungen (rightmost derivations): Für jeden Ableitungsschritt αXβ⇒αγβ, muss X das äußerte rechte Nichtterminal im String von Symbolen αXβ sein Wird verwendet in LR(k) bzw. bottom-up parsen Wird manchmal auch die kanonische Ableitung genannt Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-14 6. Sprachen, Compiler und Theorie Ableitungen (4/4) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-15 Eingabe: (INT + (INT + INT)) Rechtsseitige Ableitung: expr ⇒ ( sum ) ⇒ ( expr + expr ) ⇒ ( expr + ( sum ) ) ⇒ (expr + ( expr + expr) ) ⇒ (expr + ( expr + INT ) ) ⇒ (expr + ( INT + INT ) ) ⇒ (INT + (INT + INT ) ) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Ableitungen und Ableitungsbäume (1/2) Ableitungen und Ableitungsbäume (2/2) Ein Ableitungsbaum ist eine graphische Repräsentation des Ableitungsprozesses expr expr ⇒ ( sum) ⇒ ( expr + expr ) expr Innere Knoten von Ableitungsbäumen entsprechen den Nichtterminalsymbolen der Grammatik (Produktionen auf der linken Seite) ⇒ ( INT + expr ) INT ⇒ (INT + ( sum ) ) Die meisten Parser konstruieren einen Ableitungsbaum während des Ableitungsprozesses für eine spätere Analyse ⇒ (INT + ( expr + expr ) ) expr ⇒ (INT + ( INT + expr ) ) INT ⇒ (INT + (INT + INT ) ) Blätter eines Ableitungsbaumes entsprechen den Terminalsymbolen (Token) der Grammatik ( sum ) + expr ( sum ) + expr Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-17 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie Mehrdeutigkeiten (2/2) Eine Grammatik gilt als Mehrdeutig, wenn ein Satz mit (mind.) zwei verschiedenen Ableitungsbäumen abgeleitet werden kann Beispiel – linksseitige versus rechtsseitige Ableitung: Berühmteres Beispiel – “dangling else” Grammatik: Programmfragment: if a then if b then s1 else s2 Kann interpretiert werden als: expression -> identifier | number | - expression | ( expression ) | expression operator expression operator -> + | - | * | / Eingabe: slope * x + intercept 1) if a then { if b then s1 else s2} 2) if a then { if b then s1 } else s2 Mehrdeutigkeit kann manchmal durch die Auswahl eines akzeptierenden Ableitungsbaumes aus mehreren gehandhabt werden Zum Beispiel, obige Interpretation #1 wird von den meisten Parsern für Sprachen die die „dangling else“ Mehrdeutigkeit haben ausgewählt Generell ist Mehrdeutigkeit jedoch ein Zeichen dafür, dass die Grammatik „schlecht“ spezifiziert wurde und umgeschrieben werden sollte um Mehrdeutigkeiten zu beseitigen Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-18 6. Sprachen, Compiler und Theorie Mehrdeutigkeiten (1/2) INT 6.3-19 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-20 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Grammatiken und Parser Kontextfreie Grammatiken erzeugen durch den Ableitungsprozess Strings (oder Sätze) Die kontextfreien Sprachen sind definiert durch Kellerautomat (1/4) LCF={L(G): Alle kontextfreien Grammatiken G} Mit anderen Worten: Die Menge aller Sprachen von allen kontextfreien Grammatiken Ein Parser für eine kontextfreie Grammatik erkennt Strings in der Grammatiksprache Parser können automatisch aus einer kontextfreien Grammatik generiert werden Parser zum Erkennen von allgemeinen kontextfreien Sprachen können langsam sein Parser, die nur eine Untermenge von kontextfreien Sprachen erkennen können, können so gestaltet werden, dass sie schneller sind Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-21 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kellerautomat (2/4) Kellerautomat (3/4) Kontextfreie Grammatiken können von Kellerautomaten (Push Down Automata, PDA) erkannt werden PDAs sind eine Erweiterung der endlichen Automaten um ein „einfaches“ Gedächtnis (Hilfsband) Eigenschaften eines Kellerautomaten: Das Hilfsband heißt auch Kellerstapel oder einfach Stapel (engl. stack ). Das Eingabeband kann sich nur in eine Richtung bewegen. Es existiert ein "Hilfsband", welches sich in beide Richtungen bewegen kann. Der Automat liest im ersten Schritt die jeweils erste Zelle beider Bänder. Als Reaktion des Automaten kann entweder das Hilfsband vorwärts bewegt und ein Zeichen in die nächste Zelle geschrieben werden oder das Symbol gelöscht und das Hilfsband eine Zelle zurück bewegt werden. Ein Element kann immer nur oben auf den Stapel gelegt (bzw. an das Ende des Bandes geschrieben) werden (= push ). Immer nur das oberste (letzte) Element kann wieder vom Stapel entfernt werden (= pop ). Die erste Zelle des Hilfsbandes enthält eine spezielle Kennzeichnung, um anzuzeigen, wann der Stapel leer ist. Ein Kellerautomat kann bei leerem Stapel nicht weiterarbeiten. Kellerautomaten arbeiten eigentlich nicht-deterministisch, nichtdeterministische Kellerautomaten sind aber in deterministische überführbar ε-Bewegungen sind erlaubt Eine Eingabe wird genau dann erlaubt, wenn es möglich ist, eine Konfiguration zu erreichen, bei der die gesamte Eingabe gelesen wurde und der Stapel leer ist. Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-22 6.3-23 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-24 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kellerautomat (4/4) PDA-Übergangsfunktionen Ein Kellerautomat (=pushdown automaton, PDA) ist ein Septupel P = {Q, Σ, Γ, δ, q0, Z0, F } mit: Q Σ Γ δ q0 Z0 F Die Ausgabe von δ besteht aus einer endlichen Menge von Paaren (p, γ), wobei p für den neuen Zustand und γ für die Zeichenreihe der Stacksymbole steht, die X auf dem oberen Ende des Stacks ersetzt. Wenn γ = ε, dann wird das oberste Stacksymbol wird gelöscht. (pop-Operation) Wenn γ = X, dann bleibt der Stack unverändert. Wenn γ = YZ, dann wird X durch Z ersetzt und Y zuoberst auf dem Stack abgelegt. (push-Operation) Zustandsmenge, |Q | < ∞ Eingabealphabet, |Σ| < ∞ Stackalphabet, |Γ| < ∞ Übergangsfunktion (ZustandsÜF) δ(q,a,X) mit q ∈ Q, a ∈ {Σ, ε}, X ∈ Γ Anfangszustand Startsymbol (für Stack) Endzustände, F ⊆ Q Da PDAs nicht-deterministisch arbeiten, kann die Ausgabe von δ eine Menge an Paaren ergeben, z.B. δ(q, a, X) = { (p, YZ), (r, ε) } Die Paare müssen dabei als Einheit betrachtet und behandelt werden. Wenn sich der PDA im Zustand q befindet, X das oberste Stacksymbol ist und die Eingabe a gelesen wird, kann in den Zustand p gewechselt und X durch YZ ersetzt werden, oder in den Zustand r gewechselt und X vom Stack entfernt werden. Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-25 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Beispiel PDA: Palindrome Schreibkonventionen für PDAs Formelle Beschreibung: P = ({q0,q1,q2},{0,1},{0,1,Z0}, δ, q0, Z0, {q2}) δ(q0, 0, Z0) = {(q0,0 Z0)} δ(q0, 1, Z0) = {(q0,1 Z0)} δ(q0, 0, 0) = {(q0,00)} δ(q0, 0, 1) = {(q0,01)} δ(q0, 1, 0) = {(q0,10)} δ(q0, 1, 1) = {(q0,11)} δ(q0, ε, Z0)= {(q1, Z0)} δ(q0, ε, 0) = {(q1, 0)} δ(q0, ε, 1) = {(q1, 1)} δ(q1, 0, 0) = {(q1, ε)} δ(q1, 1, 1) = {(q1, ε)} δ(q1, ε, Z0)= {(q2, Z0)} Æ lesen und push Æ lesen und push a, b, ... ∈ Σ p, q, ... ∈ Q w, z, ... = Zeichenreihen aus Σ (Terminale) X, Y, ... = Γ α, β, γ, ... = Zeichenreihen aus Γ (Nichtterminale) Æ Wechsel nach q1, ohne Stack zu verändern Æ lesen, vergleichen, pop Æ Z0 erreicht, akzeptiert Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-26 6.3-27 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-28 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Beschreibung der Konfiguration eines PDA (1/2) Beschreibung der Konfiguration eines PDA (2/2) Im Gegensatz zum endlichen Automaten, bei denen lediglich der Zustand (neben dem Eingabesymbol) für einen Übergang von Bedeutung ist, umfasst die Konfiguration eines PDA sowohl den Zustand als auch den Inhalt des Stacks. Die Konfiguration wird daher durch das Tripel (q, w, γ) dargestellt, wobei q w γ für den Zustand, für die verbleibende Eingabe, für den Inhalt des Stacks steht. Sei P = {Q, Σ, Γ, δ, q0, Z0, F } ein PDA. Angenommen, δ(q, a, X) enthält (p, α). Dann gilt für alle Zeichenreihen w aus Σ* und β aus Γ*: (q, aw, X β) ⊢ (p, w, αβ) D.h., der Automat kann vom Zustand q in den Zustand p übergehen, indem er das Symbol a (das ε sein kann) aus der Eingabe einliest und X auf dem Stack durch α ersetzt. (Die restliche Eingabe w und der restliche Inhalt des Stacks β beeinflussen die Aktion des PDA nicht!) (Das obere Ende des Stacks steht am linken Ende von γ.) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-29 6. Sprachen, Compiler und Theorie Eingabe wird von links-nach-rechts (1. L) abgearbeitet linksseitige Ableitung (2. L) “top down” oder “prädiktive” (voraussagende) Parser genannt Eingabe wird von links-nach-rechts (1. L) abgearbeitet rechtsseitige Ableitung (2. R) “bottom up” oder “schiebe-reduziere“ (shift-reduce) Parser genannt Sei P = {Q, Σ, Γ, δ, q0, Z0, F } ein PDA. Dann ist die Sprache N(P ), die von P durch Endzustand akzeptiert wird, {w | (q0, w, Z0) ⊢* (q, ε, ε) für einen beliebigen Zustand q. N(P) ist die Menge der Eingabezeichenreihen w, die P einlesen kann und bei der er gleichzeitig den Stack leeren kann. 6.3-31 “k” steht für die Anzahl von Symbolen (token) für die in der Eingabe vorausgeschaut werden muss um eine Entscheidung treffen zu können LL(k) – welche nächste Produktion auf der rechten Seite ist bei einer linksseitigen Ableitung zu wählen LR(k) – ob zu schieben oder reduzieren Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 LR(k) parsers Sei P = {Q, Σ, Γ, δ, q0, Z0, F } ein PDA. Dann ist die Sprache L(P ), die von P durch Endzustand akzeptiert wird, {w | (q0, w, Z0) ⊢*p (q, ε, β) für einen Zustand q in F und eine Stackzeichenreihe α. Akzeptanz durch leeren Stack LL(k) Parser Akzeptanz durch Endzustand Akzeptanz durch leeren Stack Zwar unterscheiden sich die Sprachen, die die jeweiligen PDAs akzeptieren, aber sie sind jeweils ineinander überführbar. Akzeptanz durch Endzustand Klassen von Grammatiken und Parsern Es gibt zwei Ansätze, wann ein PDA eine Eingabe akzeptiert: 6.3-30 6. Sprachen, Compiler und Theorie Akzeptanzzustände von PDAs Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-32 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Top-down versus Bottom-up Syntaxanalyse (2/2) Top-down versus Bottom-up Syntaxanalyse (1/2) Top-down oder LL-Syntaxanalyse Baue den Ableitungsbaum von der Wurzel aus bis hinunter zu den Blättern auf Berechne in jedem Schritt voraus welche Produktion zu verwenden ist um den aktuellen nichtterminalen Knoten des Ableitungsbaumes aufzuweiten (expand), indem die nächsten k Eingabesymbole betrachtet werden id_list -> id id_list_tail id_list_tail -> , id id_list_tail id_list_tail -> ; Bottom-up oder LR-Syntaxanalyse Grammatik: Beispiel Strings: A; A, B, C; Baue den Ableitungsbaum von den Blättern aus bis hinauf zu der Wurzel auf Ermittle in jedem Schritt, ob eine Kollektion von Ableitungsbaumknoten zu einem einzelnen Vorgängerknoten zusammengefasst werden kann oder nicht Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-33 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Syntaxanalyse durch rekursiven Abstieg (1/4) Syntaxanalyse durch rekursiven Abstieg (2/4) Rekursiver Abstieg ist ein Weg um LL (top-down) Parser zu implementieren Rekursiver Abstieg ist ein Weg um LL(1)-Parser zu implementieren: Es ist einfach von Hand zu schreiben Es wird kein Parsergenerator benötigt Jedes nichtterminale Symbol in der Grammatik hat einen Prozeduraufruf Erinnerung: LL(1)-Parser machen linksseitige Ableitungen, unter Verwendung von höchstens 1 Symbol in der Vorausschau, um zu entscheiden welche rechte Seite einer Produktion verwendet wird, um ein linksseitiges Nichtterminal in einer Satzform zu ersetzen. LL(1)-Parser Beispiel: Grammatikfragment: Wenn die Satzform “n1 … nk factor sm … sn” lautet, dann sollte die nächste Satzform folgende sein: Es muss im Stande sein die nächste, anzuwendende, linksseitige Ableitung zu bestimmen (predict), indem nur die nächsten k Symbole angeschaut werden 6.3-34 factor -> ( expr ) | [ sexpr ] “n1 … nk ( expr ) sm … sn” oder “n1 … nk [ sexpr ] sm … sn” k ist üblicherweise 1 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-35 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-36 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Syntaxanalyse durch rekursiven Abstieg (3/4) Syntaxanalyse durch rekursiven Abstieg (4/4) Grammatik für eine Kalkulatorsprache Ableitungsbaum für Beispieleingabe: read A read B sum := A + B write sum write sum / 2 Beispieleingabe: read A read B sum := A + B write sum write sum / 2 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-37 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie LL-Syntaxanalyse Probleme mit der LL-Syntaxanalyse (1/4) Finde zu einer Eingabe von Terminalsymbolen (tokens) passende Produktionen in einer Grammatik durch Herstellung von linksseitigen Ableitungen Linksrekursion Produktionen von der Form: Für eine gegebene Menge von Produktionen für ein Nichtterminal, X->y1|…|γn, und einen gegebenen, linksseitigen Ableitungsschritt αXβ ⇒ αγiβ, müssen wir im Stande sein zu bestimmen welches γi zu wählen ist indem nur die nächsten k Eingabesymbole angeschaut werden Anmerkung: Für eine gegebene Menge von linksseitigen Ableitungsschritten, ausgehend vom Startsymbol S ⇒ αXβ, wird der String von Symbolen α nur aus Terminalen bestehen und repräsentiert den passenden Eingabeabschnitt zu den bisherigen Grammatikproduktionen Wenn eine Grammatik linksrekursive Produktionen enthält, dann kann es dafür keinen LL Parser geben A -> Aα A -> β LL Parser würden in eine Endlosschleife eintreten, wenn versucht wird eine linksseitige Ableitung in solch einer Grammatik vorzunehmen Linksrekursion kann durch das Umschreiben der Grammatik ausgeschlossen werden A -> βA’ A’ -> αA’ | є Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-38 6.3-39 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-40 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Probleme mit der LL-Syntaxanalyse (2/4) Linksrekursion: Nicht formale Rechtfertigung Originalgrammatik: A -> Aα A -> β Ableitungen: Umgeschriebene Grammatik: A ⇒ Aα ⇒ Aαα ⇒ Aααα ⇒* βααα… Probleme mit der LL-Syntaxanalyse (3/4) Gemeinsame Präfixe A -> βA’ A’ -> αA’ | є Tritt auf wenn zwei verschiedene Produktionen mit der gleichen linken Seite mit den gleichen Symbolen anfangen A ⇒ βA’ ⇒ βαA’ ⇒ βααA’ ⇒* βααα… Linksrekursion: Beispiel Grammatik: Produktionen der Form: Ableitungen: id_list -> id_list_prefix ; id_list_prefix -> id_list_prefix , id | id A -> bα A -> bβ LL(1) Parser kann nicht entscheiden welche Regel auszuwählen ist, wenn A in einem linksseitigen Ableitungsschritt zu ersetzen ist, weil beide rechten Seiten mit dem gleichen Terminalsymbol anfangen Kann durch Faktorisierung ausgeschlossen werden: A -> bA’ A’ -> α | β Linksrekursion kann durch das Umschreiben der Grammatik ausgeschlossen werden id_list -> id id_list_tail id_list_tail -> , id id_list_tail | ; Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-41 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Probleme mit der LL-Syntaxanalyse (4/4) Bau eines Top-Down Parsers mit rekursivem Abstieg (1/2) Gemeinsame Präfixe Beispiel: stmt -> id := expr stmt -> id ( argument_list ) Für jedes Nichtterminal in einer Grammatik wird ein Unterprogramm erzeugt, welches einem einzelnen linksseitigen Ableitungsschritt entspricht, wenn es aufgerufen wird Gemeinsame Präfixe können durch das Umschreiben der Grammatik ausgeschlossen werden 6.3-42 Beispiel: factor -> ( expr ) factor -> [ sexpr ] void factor (void) { switch(next_token()) { stmt -> id stmt_list_tail stmt_list_tail -> expr | ( argument_list ) case ‘(‘: expr(); match(‘)’); break; case ‘[‘: Der Ausschluss von Linksrekursion und gemeinsame Präfixe garantiert nicht das eine Grammatik LL wird Wenn wir keinen LL Parser für eine Grammatik finden können, dann müssen wir einen mächtigere Technik verwenden sexpr(); match(‘]’); break; } Schwieriger Teil: z.B., LALR(1) – Grammatiken Herausbekommen welches Token den ‚case‘ Arm vom switch Befehl benennt Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-43 } Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-44 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Bau eines Top-Down Parsers mit rekursivem Abstieg (2/2) PREDICT-Mengen (Vorhersagemengen) PREDICT Mengen teilen uns mit, welche rechte Seite einer Produktion bei einer linken Ableitung auszuwählen ist, wenn mehrere zur Auswahl stehen PREDICT-Mengen dienen somit als Grundlage für Ableitungstabellen (Parse-Tabellen) bzw. sind eine andere Teildarstellungsform für die Tabellen Wird in Form von FIRST-, FOLLOW- und NULLABLE-Mengen definiert : Sei A ein Nichtterminal und α beliebig, dann gilt PREDICT(A->α) = FIRST(α) ∪ FOLLOW(A) wenn NULLABLE(α) PREDICT(A->α) = FIRST(α) wenn nicht NULLABLE(α) FIRST-Mengen Sei α eine beliebige Folge von Grammatiksymbolen (Terminale und Nichtterminale) FIRST(α) ist die Menge aller Terminalsymbolen a mit denen ein aus α abgeleiteter String beginnen kann: FIRST(α) ist { a: α ⇒* aβ} Gilt α ⇒* є, dann ist auch є in FIRST(α) NULLABLE-Mengen Sei X ein Nichtterminal NULLABLE(X) ist wahr wenn gilt X ⇒* є (X kann den leeren String ableiten) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-45 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Berechnung von FIRST-Mengen (1/2) Berechnung von FIRST-Mengen (2/2) Für alle Grammatiksymbole X wird FIRST(X) berechnet, indem die folgenden Regeln solange angewandt werden, bis zu keiner FIRSTMenge mehr ein neues Terminal oder є hinzukommt: 6.3-46 Folglich gilt: Elemente aus FIRST(Y1) gehören immer auch zu FIRST(X) Ist є nicht aus Y1 ableitbar (NICHT NULLABLE), dann brauch nichts mehr hinzugefügt werden 1. Wenn X ein Terminal ist, dann ist FIRST(X)={X} 2. Wenn X → ε eine Produktion ist, dann füge ε zu FIRST(X) hinzu Ist є aus Y1 ableitbar (NULLABLE), dann muss auch FIRST(Y2) zu FIRST(X) hinzugefügt werden Wenn X Nichtterminal und X → Y1Y2Y3 KYk nehme a zu FIRST(X) hinzu, falls Ist є aus Y2 ableitbar (NULLABLE), dann muss auch FIRST(Y3) zu FIRST(X) hinzugefügt werden usw. 3. eine Produktion ist, dann (a) a für irgendein i in FIRST(Yi) und (b) ein ε in allen FIRST(Y1), ..., FIRST(Yi-1) enthalten ist (Y1...Yi-1 sind alle NULLABLE) є wird nur zu FIRST(X) hinzugefügt, wenn es in allen Mengen FIRST(Y1), ... ,FIRST(Yk) enthalten ist Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-47 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-48 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie FOLLOW-Mengen Berechnung von FOLLOW-Mengen Sei A ein Nichtterminal FOLLOW(A) ist die Menge aller Terminalsymbole a, die in einer Satzform direkt rechts neben A stehen können ( sei S Startregel; α, β beliebig): Follow(A) wird für alle Nichtterminale A berechnet, indem die folgenden Regeln solange angewandt werden, bis keine FollowMenge mehr vergrößert werden kann: 1. Sei S das Startsymbol und $ die Endemarkierung, dann nehme $ in FOLLOW(S) auf FOLLOW(A) ist { a:S ⇒* αAaβ } Achtung: Zwischen A und a können während der Ableitung Symbole gestanden haben, die aber verschwunden sind, weil aus Ihnen є abgeleitet wurde! Gibt es eine Satzform, in der A das am weitesten rechts stehende Symbol ist, dann gehört auch $ (die Endemarkierung) zu FOLLOW(A) 2. 3. Wenn es eine Produktion A → αBβ gibt, dann wird jedes Element von FIRST(β ) mit Ausnahme von ε auch in FOLLOW(B) aufgenommen. Wenn es Produktionen A → αB oder A → αBβ gibt und FIRST(β ) enthält (d.h. β ⇒*ε ), dann gehört jedes Element von FOLLOW(A) auch zu FOLLOW(B) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-49 6. Sprachen, Compiler und Theorie S -> s$ B -> є A -> B S -> A B S B -> b A -> a Schritt 1: i=0 NULLABLE FIRST FOLLOW A False {a} {} B True {b, є} {} S False {s} {$} S A True {a,b,є} {b,s} B True {b, є} S False {s,a,b} True {a,b,є} {b,s,a} S -> A B S B -> b A -> a B True {b, є} {s,a,b} S False {s,a,b} {$} FOLLOW A True {a,b,є} {b} B True {b, є} {s} A->B {a,b,є,s} False {s,a} {$} A->a {a} B->є {a,b,s} PREDICT FIRST FOLLOW B->b {b, є} A True {a,b,є} {b,s,a} S->s$ {s,a} B True {s,a,b} {s} {b, є} S->ABS {$} S False {s,a,b} {$} {a,b,s} Syntaktische Analyse 6.3-51 FOLLOW A NULLABLE Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 FIRST A -> B FIRST Schritt 4: i=3 NULLABLE B -> є S -> s$ NULLABLE Schritt 3: i=2 FOLLOW 6.3-50 Beispiel für PREDICT-Mengen Schritt 2: i=1 FIRST Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie Beispiel für NULLABLE-, FIRST- und FOLLOW-Mengen NULLABLE ε PREDICT-Mengen zeigen uns welche Menge von look-ahead Symbolen die rechte Seite einer Produktion selektiert Diese Grammatik ist NICHT LL(1), da es duplizierte Symbole in den PREDICT-Mengen für alle drei Nichtterminale gibt Siehe Hervorhebungen (dick, rot) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-52 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie LL(k) Eigenschaften Ableitungsbäume und Parser mit rekursivem Abstieg (1/2) Satz: Jede kontextfreie Grammatik G ist genau dann LL(1), wenn für alle Alternativen A ⇒ α1| α2 | ... | αn gilt 1. 2. FIRST(α1), ..., First(αn) paarweise disjunkt, falls αi ⇒* є gilt, dann FIRST1(αj) ∩ FOLLOW1(A) = Ø für 1 ≤ j ≤ n, j ≠ i In Worten: Aus α1, α2 , ... und αn sind jeweils keine Strings ableitbar, wo zwei mit dem gleichen Nichtterminal anfangen Der leere String є kann nicht sowohl aus αi und αj für i ≠ j abgeleitet werden Falls αi ⇒* є gilt, dann beginnt kein aus αi ableitbarer String mit einem Terminal aus FOLLOW(A) Die Beispielparser auf die wir bisher geschaut haben sind nur Erkenner Sie bestimmen, ob eine Eingabe syntaktisch korrekt ist, aber bauen keinen Ableitungsbaum Wie konstruieren wir dann einen Ableitungsbaum? In Parser mit rekursivem Abstieg machen wir für jede nichtterminale Funktion: Konstruktion eines korrekten Ableitungsbaumknoten für sich selbst und Verbindungen zu seinen Kindern Geben den konstruierten Ableitungsbaumknoten an den Aufrufer zurück Satz: Sei G kontextfreie Grammatik, k ≥ 0. G ist genau dann LL(k), wenn gilt: Sind A ⇒ β, A ⇒ ζ verschiedene Produktionen, dann Firstk(βα) ∩ FIRSTk(ζα) = Ø für alle α, σ mit S ⇒* σ A α Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-53 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Ableitungsbäume und Parser mit rekursivem Abstieg (2/2) Beispiel: Jedes nichtterminale Unterprogramm konstruiert einen Ableitungsbaumknoten node *factor (void) { factor -> ( expr ) factor -> [ sexpr ] 6.3-54 Parsergeneratoren und Syntaxanalyse switch(next_token()) { Parsergeneratoren erzeugen ausgehend von der kontextfreien Grammatik einen Parser An Produktionen dürfen semantische Aktionen angehängt sein case ‘(‘: node = factor_node(expr()); match(‘)’); break; Wenn ein Parser eine Produktion erkannt hat, dann wird die semantische Aktion aufgerufen Wird hauptsächlich dazu verwendet einen Ableitungsbaum explizit zu konstruieren case ‘[‘: node = factor_node(sexpr()); Nicht alle Symbole werden zu einem Ableitungsbaumknoten Beispiele: ‘(‘, ‘)’, ‘[‘, ‘]’ Diese Art von Ableitungsbaum } wird „Abstrakter Syntaxbaum“ (abstract syntax tree, AST) genannt match(‘]’); break; } return node; Die Ausgabe eines Parsergenerators ist ein Programm in einer Hochsprache (z.B. C, C++, oder Java) welches einen Symbolstrom (token stream) von einem Lexer (für die lexikalische Analyse) entgegen nimmt und welches einen Ableitungsbaum für die nachfolgenden Compilerphasen produziert Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-55 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-56 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie ANTLR als Beispiel eines Parsergenerators Rückblick ANTLR Grammatikspezifikation (parser.g) lexikalischer Analysator (scanner.class) Eingabe 10+(27-5); (44-2)-(1-10); Kontextfreie Grammatiken (KFG) und Sprachen (KFS), Kellerautomat Wichtige Algorithmen: ANTLR java parser.class Syntaktische Analyse und Parser (java antlr.Tool parser.g) symbols.java parser.java Ausgabe =32 =51 Auflösung von Linksrekursion und gemeinsame Präfixe FIRST, FOLLOW, NULLABLE und PREDICT Mengen für kontextfreie Grammatiken Die Syntax einer Programmiersprache wird mit KFGs spezifiziert Konstruktion eines Parsers mit rekursivem Abstieg anhand von KFGs Automatische Generierung von Parsern anhand von KFGs Parser erzeugen Ableitungsbäume für die Analyse und weitere Verarbeitung in den nachfolgenden Compilerphasen javac parser.java Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-57 6. Sprachen, Compiler und Theorie Ausblick Namen, Bindungen und Gültigkeitsbereiche (Kapitel 3) Objektlebenszeit und Speichermanagement Kontrollfluss (Kapitel 6) Zusammenführung (Bau eines ausführbaren Programms) (Kapitel 9) Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-59 Syntaktische Analyse Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.3-58 6. Sprachen, Compiler und Theorie Namen, Bindungen und Gültigkeitsbereiche (1/3) Namen Informatik II Ein mnemonischer Zeichenname wird verwendet um irgendetwas anderes zu repräsentieren oder zu benennen (Mnemonic ist die Bezeichnung für Ausdrücke deren Bedeutung vorwiegend durch die verwendete Buchstabenfolge leicht behalten werden kann.) Normalerweise Bezeichner (identifier) SS 2004 Teil 6: Sprachen, Compiler und Theorie Ist essentiell für Abstraktion 4 – Namen, Bindungen und Gültigkeitsbereiche Erlaubt es Programmierern Werte zu bezeichnen damit die Notwendigkeit zur direkten Manipulation von Adressen, Registernamen, etc. vermieden wird Beispiel: Es ist nicht notwendig zu wissen ob die Variable foo im Register $t0 oder an der Speicherstelle 10000 gespeichert wird Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Erlaubt es Programmierern einen einfachen Namen für ein potenziell komplexes Programmstück stehen zu lassen Lehrstuhl für Telematik Institut für Informatik Beide Fälle verringern die konzeptuelle Komplexität Beispiel: foo = a*a + b*b + c*c + d*d; Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Namen, Bindungen und Gültigkeitsbereiche (2/3) Namen, Bindungen und Gültigkeitsbereiche (3/3) Bindungen Gültigkeitsbereiche (Scope) Ist eine Assoziation zwischen zwei Dingen Ein Variablenname zu einem Wert Ein Variablenname zu einer spezifischen Speicherstelle Ein Typ und seine Repräsentation oder Layout im Speicher Die Bindezeit ist die Zeit zu der eine solche Assoziation gemacht wird Ist der Textbereich eines Programms in welchem eine Bindung aktiv ist Java Beispiel: public void foo (int a) { Global Scope int b; while(a < n) { int c; Method Scope c = a + a; b = a * c; Block Scope a++; } } Verbessert die Abstraktion durch die Kontrolle über die Sichtbarkeit von Bindungen Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-2 6.4-3 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Bindezeitpunkte (1/8) Wir unterscheiden 7 Zeitpunkte für die Entscheidung über eine Bindung Zeitpunkt des Sprachentwurfs Entscheidungen welche vom Designer der Programmiersprache gemacht wurden Typische Beispiele: Sprache Bindezeitpunkte (2/8) Entwurf Implementierung (mit Compilern) Programm Programmierung (Schreiben des Programms) Kompilation Linken Laden Ausführen Binden von Kontrollstrukturen (Bedingungen, Schleifen, etc.) zu Ihrer abstrakten Bedeutung „Befehle in einem while Schleifenblock werden ausgeführt bis die Bedingung nicht mehr länger wahr ist“ Binden von primitiven Datentypnamen (int, float, char, etc.) zu Ihrer geforderten Repräsentation „Variablen vom Typ int beinhalten vorzeichenbehaftete Ganzzahlen“ „Variablen vom Typ int beinhalten vorzeichenbehaftete 32-bit Werte“ Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-5 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Bindezeitpunkte (3/8) Bindezeitpunkte (4/8) Zeitpunkt der Sprachimplementierung Zeitpunkt des Programmierens Entscheidungen welche vom Compiler bzw. vom Compilerprogrammierer gemacht wurden Mit anderen Worten, Angelegenheiten der Sprachimplementierung die nicht spezifisch während des Sprachentwurfs definiert wurden Binden von primitiven Datentypen zu deren Repräsentationsgenauigkeit (Anzahl der Bits) Entscheidungen des Programmierers Typische Beispiele: Typische Beispiele: „bytes sind 8-bits, shorts sind 16-bits, ints sind 32-bits, longs sind 64-bits“ Binden von Dateioperationen zur betriebssystemspezifischen Implementierung dieser Operationen Entscheidungen des Compilers Typische Beispiele: open() ist mit einem SYS_open Systemaufruf implementiert Binden eines Algorithmus zu den Befehlen der Programmiersprache mit denen der Algorithmus implementiert ist Binden von Namen zu Variablen, welche für einen Algorithmus erforderlich sind Binden von Datenstrukturen zu Sprachdatentypen Zeitpunkt der Kompilation Binden von höheren Konstrukten zu Maschinencode (Optimierung eingeschlossen) Binden von statisch definierten Datenstrukturen zu einem spezifischen Speicherlayout Binden von benutzerdefinierten Datentypen zu einem spezifischen Speicherlayout Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-6 6.4-7 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Bindezeitpunkte (5/8) Zeitpunkt des Verbindens (linken) Entscheidungen des Linkers Typische Beispiele: Bindezeitpunkte (6/8) Zeitpunkt der Programmausführung Linker binden Programmmodule zusammen Binden von Objekten (Unterprogramme und Daten) zu einem spezifischen Platz in einer ausführbaren Datei Binden von Namen, welche Objekte in anderen Modulen referenzieren, zu deren tatsächlichen Ortsreferenz Entscheidungen welche während der Programmausführung gemacht werden Typische Beispiele: Entscheidungen des Laders Typische Beispiele: Binden von konkreten Werten zu Programmvariablen Binden von Referenzen von dynamisch zugeteilten Objekten zu Speicheradressen Binden von Namen zu Objekten in Sprachen mit dynamischen Gültigkeitsbereichen Zeitpunkt des Ladens Lader holen ausführbare Dateien in den Speicher „a = a + 1;“ Binden von virtuellen Adressen in der ausführbaren Datei zu den physikalischen Speicheradressen In modernen Betriebssystemen nicht mehr wirklich notwendig, da das Betriebssystem virtuelle Adresse zu physikalischen Adressen bindet in dem es virtuellen Speicher (einschließlich der notwendiger Hardware) verwendet Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-9 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Bindezeitpunkte (7/8) Bindezeitpunkte (8/8) Ähnliche Bindeentscheidungen können zu mehreren Bindezeitpunkten durchgeführt werden Statisches Binden (static binding) Linkentscheidungen können zur Linkzeit, Ladezeit (ein Typ des dynamischen Linken) oder Laufzeit (ein anderer Typ des dynamischen Linkens) auftreten Optimierungsentscheidungen können zur Compilezeit, Linkzeit, Ladezeit und sogar zur Laufzeit (dynamische Optimierung) auftreten Dynamisches Binden (dynamic binding) Frühe Bindezeitpunkte Bezieht sich auf alle Bindeentscheidungen die zur Laufzeit gemacht werden Verbunden mit größerer Effizienz C bindet Variablennamen an die referenzierten Objekte zur Compilezeit Bezieht sich auf alle Bindeentscheidungen die vor der Laufzeit gemacht werden Bindeentscheidungen können zu verschiedenen Bindezeitpunkten in verschiedenen Sprachen getroffen werden Wenn wir in C sagen „foo=bar;“, dann wissen wir genau ob foo und bar global oder lokal sind oder nicht, von welchem Typ sie sind, ob ihre Typen für Zuweisungen kompatibel sind oder nicht, etc. Kompilierte Sprachen laufen typischerweise viel schneller, weil die meisten Bindeentscheidungen zur Compilezeit getroffen wurden Ist eine Bindeentscheidung aber erst einmal getroffen worden, dann verlieren wir auch einiges an Flexibilität Späte Bindezeitpunkte Verbunden mit größerer Flexibilität Interpretierte Sprachen erlauben es die meisten Bindeentscheidungen zur Laufzeit zu treffen, was eine größere Flexibilität erlaubt Wir können in Perl sagen „$foo=$bar;“, und wenn der Name $bar nicht schon an ein Objekt gebunden ist, dann wird eines erzeugt, und dann zu $foo zugewiesen Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Perl (und gilt eigentlich für alle interpretierten Sprachen) bindet Variablennamen an die referenzierten Objekte zur Laufzeit 6.4-10 6.4-11 Zum Beispiel, die meisten interpretierten Sprachen erlauben es einem Programm Fragmente eines anderen Programms dynamisch zu generieren und auszuführen Da Bindeentscheidungen zur Laufzeit getroffen werden, können interpretierte Sprachen langsam sein Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Zusammenfassung Namen und Bindungen Gültigkeitsbereiche Namen Ein mnemonischer Zeichenname wird verwendet um irgendetwas anderes zu repräsentieren oder zu benennen Beispiel: Variable foo kann die Speicherstelle 10000 referenzieren Textueller Bereich eines Programms in welchem eine Bindung aktiv ist Es gibt grundsätzlich zwei Varianten: Statische Gültigkeitsbereiche (static scopes) Bindungen Ist eine Assoziation zwischen zwei Dingen Bindezeit Bindungen zwischen Namen und Objekten hängen vom Programmfluss zur Laufzeit ab Nähere Ausführung Die Bindezeit ist die Zeit zu der die Entscheidung über eine solche Assoziation gemacht wird Dynamische Gültigkeitsbereiche (dynamic scopes) Ein Name und das was er referenziert Es kann zur Compilezeit genau festgestellt werden welcher Name welches Objekt an welchen Punkten im Programm referenziert Der Prozess durch den eine Menge von Bindungen aktiv wird wenn die Kontrolle in einen Gültigkeitsbereich eintritt Zum Beispiel die Allokation von Speicher um Objekte darin zu halten Beispiel: Ist der Wert von foo in einem Register oder im Speicher? (Entscheidung wird zur Compilezeit gemacht) Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-13 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Gültigkeitsbereiche Static scope: Verschachtelte Unterprogramme Referenzierende Umgebung (referencing environment) Die Menge von aktiven Bindungen zu einem gegebenen Zeitpunkt in der Programmausführung Wird durch die Regeln für Gültigkeitsbereiche einer Programmiersprache festgelegt Statische Gültigkeitsbereiche Bindungen zwischen Namen und Objekten könne zur Compilezeit festgestellt werden Einfache Varianten Komplexe Varianten 6.4-14 Frage: Welches Objekt wird von X im Funktionsblock von F1 referenziert? Regel über den nächsten Gültigkeitsbereich (closest nested scope ) Referenzen von Variablen referenzieren das Objekt im naheliegendsten Gültigkeitsbereich Frühe Versionen von BASIC hatten einen, globalen Gültigkeitsbereich Moderne Programmiersprachen erlauben verschachtelte Unterprogramme und Module weshalb kompliziertere Regeln für Gültigkeitsbereiche erforderlich sind Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-15 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Ein Problem welches nicht von verschachtelten Unterprogrammen behandelt wird (1/2) Ein Problem welches nicht von verschachtelten Unterprogrammen behandelt wird (2/2) Geheimnisprinzip (information hiding) für komplexe abstrakte Datentypen (ADT) Für einfache ADTs könnten Funktionen mit statischen lokalen Variablen funktionieren Siehe Beispiel auf nächster Folie: Die Variable name_nums behält seinen Wert über Aufrufe von gen_new_name bei Dies ist zu einfach für ADTs mit mehreren Funktionen die sich einen globalen Zustand teilen müssen Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-17 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Module Ein Modul Beispiel Ein Modul erlaubt es eine Sammlung von Objekten zu kapseln, so dass Objekte innerhalb eines Moduls sich gegenseitig sehen können Objekte innerhalb des Moduls nach außen nicht sichtbar sind, es sei denn sie werden explizit exportiert Objekte von außerhalb nach innen nicht sichtbar sind, es sei denn sie werden explizit importiert Abstraktion eines Stacks in Modula Wir exportieren die push und pop Funktionen Außerhalb des Moduls nicht sichtbar Top des Stackzeigers „top“ Stack array „s“ Module mit geschlossenem Gültigkeitsbereich 6.4-18 Betrifft Module in welche Namen explizit importiert werden müssen um innerhalb des Moduls sichtbar zu sein Module mit offenem Gültigkeitsbereich Betrifft Module für die es nicht explizit erforderlich ist Namen von außerhalb zu importieren um sichtbar zu sein Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-19 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-20 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Dynamische Gültigkeitsbereiche Bindungen zwischen Namen und Objekten hängen vom Programmfluss zur Laufzeit ab Statische vs. dynamische Gültigkeitsbereiche Reihenfolge in welcher Unterprogramme aufgerufen werden ist wichtig Die Regeln für dynamische Gültigkeitsbereiche sind normalerweise einfach Statischer Gültigkeitsbereich Die aktuelle Bindung zwischen Name und Objekt ist diejenige die während der Ausführung als letzte angetroffen wurde (sprich diejenige die am kürzlichsten gesetzt wurde) Dynamischer Gültigkeitsbereich Programm gibt 1 aus Programm gibt 1 oder 2 aus in Abhängigkeit des gelesenen Wertes in Zeile 8 Warum? Ist die Zuweisung zu „a“ in Zeile 3 eine Zuweisung zu dem globalen „a“ von Zeile 1 oder dem Lokalen „a“ von Zeile 5? Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-21 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Vorteile von dynamischen Gültigkeitsbereichen Ein Problem von dynamischen Gültigkeitsbereichen Was sind die Vorteile von dynamischen Gültigkeitsbereichen? 6.4-22 Problem: unvorhersagbare referenzierende Umgebungen Globale Variable max_score wird verwendet von scaled_score() max_score wird umdefiniert in foo() scaled_score() wird von foo() aufgerufen Ahhhhh Es erleichtert die Anpassung von Unterprogrammen Beispiel: begin --nested block print_base: integer := 16 print_integer(n) Die Variable print_base kontrolliert die Basis welche print_integer für die Ausgabe von Zahlen verwendet print_integer kann früh in einem globalen Gültigkeitsbereich mit einem Standardwert belegt werden und kann temporär in einem globalen Gültigkeitsbereich auf eine andere Basis überschrieben werden Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-23 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-24 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Vermischte Gültigkeitsbereiche (mixed scoping) Zusammenfassung für Gültigkeitsbereiche Perl unterstützt beide Arten, dynamische wie statische Gültigkeitsbereiche Dynamic Scoping Static Scoping $i = 1; $i = 1; sub f { sub f { local($i) = 2; my($i) = 2; return g(); return g(); } } sub g { return $i; } sub g { return $i; } print g(), f(); print g(), f(); Ausgabe: 1 2 Ausgabe: 1 1 Statische Gültigkeitsbereiche Wird von den meisten, kompilierten Hochsprachen verwendet Bindungen von Namen zu Variablen können zur Compilezeit festgestellt werden Effizient C, C++, Java, Modula, etc. Dynamische Gültigkeitsbereiche Wird von vielen interpretierten Sprachen verwendet Bindungen von Namen zu Variablen können eine Feststellung zur Laufzeit benötigen Flexibel Ursprüngliches LISP, APL, Snobol und Perl Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-25 6. Sprachen, Compiler und Theorie Ausblick Namen, Gültigkeitsbereiche und Bindungen (Kapitel 3) Speichermanagement und Implementierung Kontrollfluss (Kapitel 6) Zusammenführung (Bau eines ausführbaren Programms) (Kapitel 9) Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-27 Namen, Bindungen und Gültigkeitsbereiche Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.4-26 6. Sprachen, Compiler und Theorie Inhalt Lebensdauer von Objekten Speichermanagement Weiterführende Spracheigenschaften und Bindungen Informatik II SS 2004 Implementierung von statischen Gültigkeitsbereichen für verschachtelte Unterprogramme Implementierung von Unterprogrammreferenzen Teil 6: Sprachen, Compiler und Theorie 5 – Speichermanagement und Implementierung Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Objektlebensdauer (1/3) Objekt wird erzeugt Bindungen zum Objekt werden erzeugt Referenzen zu den Variablen, Unterprogrammen und Typen werden durch Bindungen gemacht Deaktivierung und Reaktivierung von temporär nicht verwendbaren Bindungen Vernichtung von Bindungen Vernichtung des Objekts Lebensdauer des Objekts Bar int b; b = b + 1; ... Deaktiviere Bindung von b zu Bar } Reaktiviere Bindung von b zu Bar } Beispiel: Zeit während der eine Java Referenz ein Objekt im Speicher referenziert Beispiel: Zeit während der ein Objekt im Speicher „lebt“ Anmerkung: Das durch foo() erzeugte Objekt Bar ist nicht unbedingt nach der Rückkehr aus dem Unterprogramm zerstört. Es kann nach der Rückkehr von foo() nicht länger referenziert werden, aber es wird wahrscheinlich erst bei der „garbage collection“ zu einem späteren Zeitpunkt in der Programmausführung zerstört werden. Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Bar b = new Bar(); while(1) { Binde Namen b an Objekt Bar Lebensdauer von Objekten: Zeit zwischen Erzeugung und Vernichtung eines Objekts Beispiel: public void foo (void) { Lebensdauer von Bindungen: Zeit zwischen Erzeugung und Vernichtung einer Bindung Objektlebensdauer (2/3) Schlüsselereignisse während der Lebensdauer eines Objektes 6.5-2 6.5-3 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Objektlebensdauer (3/3) Statische Allokation Die Lebenszeit eines Objekts korrespondiert mit einem von drei Hauptmechanismen der Speicherallokation Globale Variablen Konstante Variablen welche während der Programmausführung sich nicht ändern sollten Der Programmcode (Unterprogramme, etc.) Vom Compiler produzierte Informationen zum Debuggen Falls eine Sprache rekursive Unterprogrammaufrufe nicht untersützt, dann auch lokale Variablen, Unterprogrammargumente, Rückgabewerte, vom Compiler generierte temporäre Daten, etc. Objekte werden zur Compilezeit oder Laufzeit zu festen Speicherplätzen allokiert Stack Allokation Objekte werden zur Laufzeit wie benötigt allokiert, wobei eine last-in, first-out Ordnung gilt Was wird statisch allokiert? Statische Allokation Beispiel: Lokales Heap Allokation Objekte werden zur Laufzeit in einer beliebigen Reihenfolge allokiert und freigegeben Beispiel: dynamisch allokierte Objekte Beispiel: Statische Klassenvariablen Beispiel: “i=%d\n” ist konstant in printf(“i=%d\n”,i); Ausnahme: dynamisch verbundene Unterprogramme Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-5 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Beispiel: Statische Allokation von Lokalem Allokation über einen Stack (1/2) Keine Rekursion bedeutet das es zu einer Zeit nur eine aktive Instanz bzw. Aktivierung einer Instanz von jedem gegebenen Unterprogramm geben kann Daher können wir für eine einzelne, mögliche Aktivierung für jedes Unterprogramm eines Programms den Speicher statisch reservieren Fall eine Sprache Rekursion erlaubt, dann kann jedes Unterprogramm mehrere simultane Aktivierungen haben Beispiel: public int foo (int n) { int a, b, c; a = random(); b = random(); if (n > 0) { c = foo(n-1); } c = c * (a + b); } Wir können n Aktivierungen von foo() haben und jede benötigt Speicherplatz um die eigenen Kopien von a, b, c, Übergabeargument n und jeden temporären, vom Compiler generierten Wert zu speichern Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-6 6.5-7 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Allokation über einen Stack (2/2) Allokation über einen Heap Die Lösung für das Rekursionsproblem ist die Allokation über einen Stack (stack allocation) Wir wissen wie Code für Globales, Lokales, Konstanten, Temporäres, etc. allokiert wird Was bleibt übrig: Dynamisch allokierte Objekte Warum können diese nicht statisch allokiert werden? Warum können diese nicht auf einem Stack allokiert werden? „Push“ Stack um Platz für Lokales (locals) für Unterprogrammaufrufe zu reservieren Weil sie dynamisch erzeugt werden Ein Objekt, welches dynamisch von einem Unterprogramm erzeugt wurde, könnte die Aktivierung des Unterprogramms überleben „Pop“ Stack um Platz für Lokales (locals) bei der Rückkehr von einem Unterprogramm wieder freizugeben Heaps lösen dieses Problem Beispiel: Objekt wird einem Globalen zugewiesen oder von dem Unterprogramm zurückgegeben Ein Heap ist eine Speicherregion in welcher Speicherblöcke jederzeit willkürlich allokiert und wieder freigegeben werden können Wie sind Heaps implementiert? Wie handhaben wir Anfragen zur Allokation und Freigabe? Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-9 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Heap-Management (1/4) Heap-Management (2/4) Heap Heap Allokationsanforderung Allokationsanforderung Wir bekommen eine Allokationsanforderung für n Bytes vom Speicher Der Heap hat verwendete (dunkle) und freie (helle) Bereiche Wie wählen wir einen freien Bereich um eine Allokationsanforderung zu befriedigen? Einfache Antwort: Finde den ersten freien Bereich welcher groß genug ist der Allokation zu entsprechen (wird first fit genannt) Problem: interne Fragmentierung Wenn wir n Bytes anfordern und der erste, verfügbare freie bereich hat n+k Bytes, dann verschwenden wir k Bytes durch die Allokation im ersten Bereich Eine bessere Antwort (vielleicht): Finde den ersten Bereich, welcher von der Größe am nächsten zur Allokationsanforderung ist (wird best fit genannt) Problem: Zeitaufwendiger Muss alle freie Blöcke finden um den Besten zu finden Es kann immer noch interne Fragmentierung geben, aber hoffentlich weniger als bei first fit Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-10 6.5-11 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Heap-Management (3/4) Heap Heap Allokationsanforderung Allokationsanforderung Ein anderes Problem: externe Fragmentierung Wir bekommen eine Allokationsanforderung für n Bytes und der Heap hat mehr als n Bytes frei, aber Heap-Management (4/4) kein einzelner freier Bereich ist n Bytes groß oder größer Explizit: Wir haben genügend freien Speicher, aber wir können die Allokationsanforderung nicht befriedigen Der Programmierer muss dem Heap-Manager mitteilen dass ein Bereich nicht länger vom Programm verwendet wird Mögliche Lösungen: Wie handhaben wir die Freigabe? Automatisch: Vereinigung von Bereichen: wenn zwei benachbarte Bereiche mit j und k Bytes frei sind, dann vereinige diese zu einem einzelnen, freien Bereich mit j+k Bytes Ist eine Verbesserung, aber kann nicht alle externen Fragmentierungen eliminieren Beispiel: verwende in C free(p) um den Bereich freizugeben auf den p zeigt Das Laufzeitsystem bestimmt welche Objekte auf dem Heap lebendig (sprich immer noch an Namen gebunden sind) oder tot (sprich nicht länger zu irgendeinem Namen gebunden sind) sind und gibt die toten Objekte automatisch frei Wird Garbage Collection (Speicherbereinigung) genannt Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-13 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Speicherbereinigung (Garbage Collection) (1/2) Speicherbereinigung (Garbage Collection) (2/2) Warum verwenden wir Speicherbereinigung? Verhindert: Speicherlöcher (memory leak): Programmierer können die Freigabe von dynamisch allokiertem Speicher vergessen Dangling pointers: Programmierer können ein Objekt freigeben bevor alle Referenzen darauf zerstört sind Warum Speicherbereinigung nicht verwenden? Teuer: Festzustellen welche Objekte lebendig und welche tot sind kostet Zeit Schlecht für Echtzeitsysteme: Können normalerweise nicht garantieren wann die Speicherbereinigung laufen wird und wie lange es dauern wird Schwierig zu implementieren: Das Schreiben einer Speicherbereinigung ist schwierig und macht den Compiler und die Laufzeit einer Sprache komplizierter Sprachdesign: Einige Sprachen wurden nicht mit dem Gedanken an eine Speicherbereinigung entworfen, was es schwierig machte eine Speicherbereinigung zuzufügen Reduzieren den Programmieraufwand und resultiert in zuverlässigeren Programmen Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-14 6.5-15 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Weiterführende Spracheigenschaften und Bindungen Implementierung von statischen Gültigkeitsbereichen (1/2) Wie implementieren wir statische Gültigkeitsbereiche in Sprachen die verschachtelte Unterprogramme erlauben? Wie implementieren wir Referenzen auf Unterprogramme? Problem: void a (void) { int foo1; Von c(), wie referenzieren wir nichtlokale Variablen in a() und b()? void b (void) { int foo2; void c (void) { int foo3 = foo1 + foo2; } void d (void) { } } void e (void) { } } Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-17 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Implementierung von statischen Gültigkeitsbereichen (2/2) Lösung: statische Links Implementierung von Referenzen auf Unterprogramme - 1 Jede Aktivierung eines Unterprogramms speichert einen Zeiger zum nächsten, umgebenden Gültigkeitsbereich in seinem Stackrahmen c() bekommt seine eigenen Lokalen von seinem eigenen Stackrahmen c() bekommt die Nichtlokalen in b() durch die einmalige Dereferenzierung seines statischen Links c() bekommt die Nichtlokalen in a() durch die Dereferenzierung seines statischen Links zu b() und dann b()‘s statischen Link zu a() Große Frage: Wie wenden wir Gültigkeitsbereichsregeln in Sprachen an wo Unterprogramme als Wert übergeben werden können? Zwei Antworten: Flache Bindung (shallow binding): Referenzierende Umgebung wird sofort festgestellt bevor das referenzierte Unterprogramm aufgerufen wird Ist normalerweise der Standard in Sprachen mit dynamischen Gültigkeitsbereichen Tiefe Bindung (deep binding): Referenzierende Umgebung wird festgestellt wenn die Referenz auf das Unterprogramm erzeugt wird Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-18 6.5-19 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-20 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Tiefe vs. flache Bindung Flache Bindung (shallow) Implementierung von Referenzen auf Unterprogramme - 2 Nötig für die line_length Zuweisung in print_selected_records um das print_person Unterprogramm zu erreichen Nötig für die threshold Zuweisung im Hauptprogramm um das older_than Unterpogramm zu erreichen Ein Zeiger auf den Code des Unterprogramms und ein Zeiger auf die referenzierende Umgebung des Unterprogramms Warum benötigen wir einen Zeiger auf die referenzierende Umgebung? Tiefe Bindung (deep) Zum Abschluss Wir müssen einen Weg für das Unterprogramm haben mit welcher es Zugriff auf seine nichtlokalen, nichtglobalen Variablen hat Für Sprachen mit dynamischen Gültigkeitsbereichen schließt dies Variablen in den umschließenden, dynamisch verschachtelten Unterprogrammen ein (Unterprogramme die andere aufrufen) Für Sprachen mit statischen Gültigkeitsbereichen schließt dies Variablen in den umschließenden, statisch verschachtelten Unterprogrammen ein Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-21 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Zusammenfassung Ausblick Lebensdauer von Objekten Die Zeitperiode während der ein Name an ein Objekt (Variable, Unterprogramm, etc.) gebunden ist Namen, Gültigkeitsbereiche und Bindungen (Kapitel 3) Speichermanagement und Implementierung Kontrollstrukturen (Kapitel 6) Zusammenführung (Bau eines ausführbaren Programms) (Kapitel 9) Speichermanagement 6.5-22 Statische Allokation, Stacks und Heaps Weiterführende Spracheigenschaften und Bindungen Implementierung von statischen Gültigkeitsbereichen für verschachtelte Unterprogramme Implementierung von Unterprogrammreferenzen Links auf Stackrahmen ablegen Ein Zeiger auf den Code des Unterprogramms und ein Zeiger auf die referenzierende Umgebung des Unterprogramms (flache und tiefe Bindung) Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-23 Speichermanagement und Implementierung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.5-24 6. Sprachen, Compiler und Theorie Rückblick Informatik II Lebensdauer von Objekten Speichermanagement Weiterführende Spracheigenschaften und Bindungen SS 2004 Implementierung von statischen Gültigkeitsbereichen für verschachtelte Unterprogramme Implementierung von Unterprogrammreferenzen Teil 6: Sprachen, Compiler und Theorie 6 – Kontrollstrukturen Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Was fehlt uns noch? Kontrollfluss (control flow) Kontrollfluss Das Offensichtliche: Programme machen ihre Arbeit durch ausführen von Berechnungen Kontrollfluss (control flow) spezifiziert die Reihenfolge in welcher Berechnungen ausgeführt werden Sprachen stellen eine große Vielfalt von Kontrollflussmechanismen (control flow mechanism) bereit welche es dem Programmierer erlauben den Kontrollfluss zu spezifizieren Spezifikation der Reihenfolge in welcher Elemente einer höheren Programmiersprache ausgeführt werden Kontrollflussmechanismen Anweisungen (statements), Schleifen, Unterprogrammaufrufe, Rekursion, etc. Übersetzung zu ausführbarem Code Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-2 6.6-3 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kontrollfluss und Kontrollflussmechanismen Wichtige Fragen Iterationen (Wiederholungen) Führt eine Gruppe von Berechnungen mehrmals aus (repeatedly) Eine von sieben Kategorien von Kontrollflussmechanismen Wie implementieren Sprachen Iterationen? Schleifen: C, C++, Java for(i=0;i<N;i++) { do_something(i); Einige Sprachen verwenden andere Mechanismen um das gleiche Ziel zu erreichen Wie lauten die Kategorien von Kontrollflussmechanismen Welche Eigenschaften stellen Sprachen zur Verfügung um die Kontrollflussmechanismen einer gegebenen Kategorie zu implementieren? Wie übersetzen Compiler Kontrollflussmechanismen in ausführbaren Code? Rekursion: ML fun foo i N = } if i < N then do_something i Iteratoren: Icon, CLU foo i + 1 N every do_something(0 to N-1) foo 0 N Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-5 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Kategorien von Kontrollflussmechanismen Wähle, basierend auf einer Laufzeitbedingung, welche von mehreren Berechnungen ausgeführt werden soll Bestimmt für ein gegebenes Paar von Berechnungen welches zuerst ausgeführt wird Zwei Typen von Berechnungen für welche wir die Reihenfolge betrachten wollen Iteration (iteration) Bestimmt für ein gegebenes Paar von Berechnungen welches zuerst ausgeführt wird Auswahl (selection) Sequentialität (sequencing) Sequentialität (sequencing) Ausdrücke (expressions) Führt eine Gruppe von Berechnungen mehrmals aus Rekursion (recursion) Prozedurale Abstraktion (procedural abstraction) Berechnungen welche die Werte von Variablen ändern Beispiel: Erlaubt es Berechnungen „zur gleichen Zeit“ auszuführen Keine Festlegung (Nondeterminancy) Reihenfolge zwischen Berechnungen wird unspezifiziert gelassen zweistellige Ausdrücke: foo + bar Evaluieren wir zuerst die Unterausdrücke foo oder bar bevor wir die Addition ausführen? Zuweisungen (assignments) Nebenläufigkeit (concurrency) Erlaubt es einer Gruppe von Berechnungen zu bennen, möglicherweise zu parametrisieren und auszuführen wann immer der Name referenziert wird Berechnungen die einen Wert erzeugen Beispiel: Erlaubt es Berechnungen durch sich selbst zu definieren (Allows computations to be defined in terms of themselves) foo = bar + 1; bar = foo + 1; Welche von diesen beiden Zuweisungen evaluieren wir zuerst? Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-6 6.6-7 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Was genau ist eine Ausdruck (expression)? Evaluation von Ausdrücken und Seiteneffekte Berechnungen welche einen Wert erzeugen Evaluation von Ausdrücken kann zusätzlich zu den Werten auch Seiteneffekte erzeugen Beispiele: Variablenreferenzen Konstantenreferenzen 1, 2, 3, ‘a’, ‘b’, ‘c’, … Holt den Wert der Variablen aus dem Speicher Operatoren oder Funktionen angewandt auf eine Sammlung von Unterausdrücken Funktionsaufrufe foo(a,b+10) Arithmetische Operationen Ausdrücke ohne Seiteneffekte werden „referentially transparent” genannt foo + bar int foo (void) { a = 10; return a; } … foo() + 20 … Evaluation von „foo()+20“ ergibt den Wert 30 UND als einen Seiteneffekt die Zuweisung des Wertes 10 an die Globale a Solche Ausdrücke können als mathematische Objekte behandelt werden und entsprechend durchdacht Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-9 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Priorität und Assoziativität Priorität (Precedence) Zwei Wege um die Reihenfolge zu spezifizieren in welcher Unterausdrücke von komplexen Ausdrücken evaluiert werden Prioritätsregel Beispiel: Ähnlich dem mathematischen Konzept mit dem gleichen Namen Spezifiziert ob Operatoren von der gleichen Prioritätsgruppe zur Linken oder Rechten zuerst evaluieren -a * c Wie ist die Reihenfolge der Evaluierung? Spezifiziert wie Operationen gruppieren wenn Klammern abwesend sind Assoziativität (-a) * c oder –(a * c)??? In C, -, unäre Negierung (unary negation), hat höhere Priorität als * (Multiplikation), weshalb die erste Klammerung korrekt ist Prioritätsregeln variieren stark von Sprache zu Sprache Frage: Wie wird in C int i = 0; int *ip = &i; ++*ip++; ausgewertet? ++( ) auf den Wert von *ip++. Der Wert ist der Inhalt der Variablen, auf die ip verweist. Wenn ip abgerufen wurde, wird ip im nachhinein um eins erhöht (Zeigerarithmetik). Daher ist am Ende i = 1 und der Zeiger ip wurde auch um eins erhöht (und zeigt somit höchstwahrscheinlich ins Nirvana, da kein Bezug zu dieser Speicherstelle besteht) Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-10 6.6-11 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Assoziativität (Associativity) Die Assoziativität ist in Sprachen einheitlicher Elementare arithmetische Operatoren assoziieren von links nach rechts Operatoren werden von links nach rechts gruppiert und evaluiert Beispiel (Subtraktion): Einige arithmetische Operatoren assoziieren aber von rechts nach links Beispiel (Potenzieren): 9 – 3 – 2 evaluiert eher zu (9 – 3) -2 als 9 - (3 - 2) 4**3**2 evaluiert eher zu 4**(3**2) als (4**3)**2 In Sprachen die Zuweisungen in Ausdrücken erlauben, assoziieren Zuweisungen von rechts nach links Beispiel: a = b = a + c evaluiert zu a = (b = a + c) a + c wird berechnet und b zugewiesen, danach wird b zu a zugewiesen Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-13 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Arbeiten mit Prioritäten und Assoziativität Mehr zur Evaluierungsordnung von Ausdrücken (1/2) Regeln für Prioritäten und Assoziativität variieren stark von Sprache zu Sprache Prioritäten und Assoziativität können nicht immer eine Evaluierungsreihenfolge festlegen In Pascal: Beispiel: „if A < B und C < D then ...“ Könnte zu „if A < (B and C) < D then...“ evaluiert werden Upps! Leitfaden/Richtlinie: Wenn f(b) die Variablen c oder d modifiziert, dann hängt der Wert des ganzen Ausdruckes davon ab, ob f(b) zuerst ausgeführt wird oder nicht Verbesserung des Codes a – f(b) – c * d Mit Prioritäten ergibt sich a – f(b) – (c * d) und weiter mit Assoziativität (a – f(b)) - (c * d) Aber welches wird zuerst ausgeführt: a-f(b) oder (c*d)? Warum kümmert uns das? Seiteneffekte Im Zweifelsfall lieber Klammern verwenden, speziell wenn jemand oft zwischen verschiedenen Sprachen wechselt! Wir wollen (c*d) zu erst berechnen, so dass wir das berechnete Ergebnis nicht speichern und wiederherstellen müssen bevor und nachdem f(b) aufgerufen wird Nochmals: Im Zweifelsfall Klammern setzen! Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-14 6.6-15 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Mehr zur Evaluierungsordnung von Ausdrücken (2/2) Zuweisungen Boolesche Ausdrücke Ausdrücke die eine logische Operation ausführen und entweder zu wahr (true) oder falsch (false) evaluieren Beispiele: (a < b) && (c < d) Wenn (a < b) zu falsch evaluiert wird dann gibt es keinen Bedarf mehr (c <d ) auszuwerten Siehe Regel R1 aus Kapitel 2, Boolesche Algebra und Gatter Weisst den Wert, der durch den Ausdruck „ a +b“ berechnet wird, der Variablen c zu Zuweisungen sind der fundamentale Mechanismus um Seiteneffekte zu erzeugen Beispiel: Beispiel: c = a + b Die Evaluation von booleschen Ausdrücken kann durch die „short circuiting“ Technik optimiert werden a<b a && b || c oder a & b | c Berechnungen welche den Wert einer Variablen beeinflussen Werte die Variablen zugewiesen werden können zukünftige Berechnungen beeinflussen Zuweisungen sind unerlässlich für das imperative Programmiermodel Dies wird wichtig bei Code nach folgendem Muster: if (unwahrscheinliche Bedingung && teure Funktion() ) ... Wir wollen die Auswertung einer teuren Funktion() verhindern, wenn eine unwahrscheinliche Bedingung zu falsch ausgewertet wird Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-17 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Was genau ist eine Variable? (1/2) Was genau ist eine Variable? (2/2) Die Interpretation eines Variablennamens hängt von dem Kontext ab in welchem er auftritt Zwei Wege um eine Variable zu behandeln Wertemodell Beispiele: d=a Die Referenz zu der Variablen „a“ auf der „rechten Seite“ einer Zuweisung benötigt einen Wert Wird ein r-value-context genannt und das „a“ wird r-value genannt Variablen sind benannte Referenzen für Werte Nur die Interpretation als l-value ist möglich Die Referenz zu der Variablen „a“ auf der „linken Seite“ einer Zuweisung bezieht sich auf a‘s Speicherort Variablen sind benannte Container für Werte Beide Interpretationen als l-value und r-value von Variablen sind möglich Modell wird verwendet von Pascal, Ada, C, etc. Referenzmodell a=b+c 6.6-18 Variablen in einem r-value-context müssen „dereferenziert“ werden um einen Wert zu erzeugen Modell wird verwendet von Clu Wird ein l-value-context genannt und das „a“ wird l-value genannt Wertemodell Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-19 Referenzmodell Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-20 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Zuweisungsfolgen Zuweisungsfolgen (assignment sequencing ) ist in den meisten Sprachen einfach Zuweisungen werden in der Reihenfolge ausgeführt in der sie im Programmtext auftreten Wie initialisieren oder wie weisen wir initiale Werte Variablen zu? Verwende den Zuweisungsoperator um initiale Werte zur Laufzeit zuzuweisen Beispiel: Zuweisungsfolgen: Initialisierung (1/2) a = 10; b = a; Å Es wird zuerst die 10 dem a zugewiesen und danach wird a dem b zugewiesen Probleme: Ausnahmen Zuweisungsausdrücke Beispiel: a = b = a * c Erinnere dich an die Auswertereihenfolge bei Ausdrücken: In den meisten Programmiersprachen gilt die Assiziation von rechts nach links Ineffizienz: Wenn wir den initialen Wert einer Variablen zur Compilezeit kennen, dann kann der Compiler diesen Wert dem Speicher zuweisen ohne eine Initialisierung zur Laufzeit zu benötigen Programmierfehler: Wenn Variablen kein initialer Wert bei der Deklaration zugewiesen wird, dann könnte ein Programm eine Variable verwenden bevor sie irgendeinen (sinnvollen) Wert enthält Initialisierung Kombinationen mit dem Zuweisungsoperator Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-21 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Zuweisungsfolgen: Initialisierung (2/2) Zuweisungsfolgen: Kombinationen Verwende den Zuweisungsoperator um initiale Werte zur Laufzeit zuzuweisen Lösungen: Sprachen können einen Defaultwert für jede Deklaration eines eingebauten (built-in) Typs spezifizieren Ist fehleranfällig und schwierig zu schreiben, da Text wiederholt wird Kann zu ineffizientem kompilierten Code führen Lösungen: Kombinationen mit dem Zuweisungsoperator Beispiele: Mache es zu einem Laufzeitfehler, wenn eine Variable verwendet wird bevor ihr ein Wert zugewiesen wurde Mache es zu einem Compilerfehler wenn eine Variable verwendet werden könnte bevor ihr ein Wert zugewiesen wurde a = a + 1; b.c[3].d = b.c[3].d * e; Probleme: Statische Wertkontrollen Beispiele: Beispiel für C: static char s[] = “foo” Der Compiler kann den Elementen des Arrays s die Werte zur Compilezeit zuweisen, was uns 4 Zuweisungen zur Laufzeit erspart (eine für jedes Zeichen plus dem NULL (String-)Terminator) siehe auch C++ Konstruktorinitialisierung Dynamische Wertkontrollen Defaultwerte (Standardwerte/Ausgangswerte/...) Gibt es einen effizienteren Weg um komplexe Zuweisungen zu handhaben? Statische Initialisierung: a += 1; b.c[3].d *= e; Zuweisungen werden mit Operatoren für Ausdrücke kombiniert Vermeidet sich wiederholenden Code Erlaubt es dem Compiler auf einfacherem Wege effizienteren Code zu generieren Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-22 6.6-23 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-24 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Sequentialität: Zusammenfassung Ausdrücke Auswahl (selection) (1/4) Prioritäten und Assoziativität kontrollieren die Reihenfolge Es müssen Seiteneffekte bei Unterausdrücken beachtet werden Logische Ausdrücke können von der short-circuit Auswertung profitieren Wähle, basierend auf einer Laufzeitbedingung, welche von mehreren Berechnungen ausgeführt werden soll Am häufigsten Ausgeführt vom if..then..else Sprachkonstrukt Zuweisungen Beispiel: Die Folge des Auftretens im Programmtext bestimmt die Reihenfolge Zuweisungen zur Compilezeit erlauben die Vermeidung von Zuweisungen zur Laufzeit Zusammengesetzte Zuweisungsoperatoren kombinieren Ausdrucksauswertung und Zuweisung um effizienter sein zu können if ((a < b) && (c < d)) { do_something(); } Wenn beide bedingte Anweisungen „a<b“ und „c<d“ zu wahr ausgewertet werden, dann führe die Berechnung, welche durch das Unterprogramm do_something() referenziert wird, aus Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-25 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Auswahl (selection) (2/4) Auswahl (selection) (3/4) Short circuiting Angenommen wir haben Code der ähnlich wie dieser aussieht: j := … (* something complicated *) IF j = 1 THEN clause_A ELSIF j IN 2, 7 THEN clause_B ELSIF j in 3..5 THEN clause_C ELSE clause_D END Zur Erinnerung: Kann verwendet werden um unnötige Ausdrucksauswertungen zu verhinden Beispiel: if ( (a < b) && (c < d) ) { do_something(); } Wenn (a < b) falsch ist, dann kann do_something() nicht ausgeführt werden, egal was (c < d) ergibt Siehe Regel R1 aus Kapitel 2, Boolesche Algebra und Gatter Short circuiting wird Code generieren der die Auswertung von (c < d) genauso ausläßt (skipped) wie die Ausführung von do_something(), wenn (a < b) falsch ist Problem: Ist kompliziert zu schreiben Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-26 6.6-27 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-28 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Auswahl (selection) (4/4) Iteration Lösung: case/switch Befehle Beispiel: CASE … 1: | 2,7: | 3..5: ELSE END Führt eine Gruppe von Berechnungen mehrmals aus Sprachen bieten für Iterationen Schleifenkonstrukte an clause_A clause_B clause_C clause_D Die Schleife wird solange ausgeführt bis sich eine boolesche Bedingung ändert Anmerkung: Ist einfacher zu schreiben und zu verstehen Die Schleife wird für jeden Wert aus einer endlichen Menge (Aufzählung) ausgeführt Logikgesteuerte Schleifen Aufzählungsgesteuerte Schleifen Der imperative Stil der Programmierung favorisiert Schleifen gegenüber Rekursion Iteration und Rekursion können beide verwendet werden um eine Gruppe von Berechnungen wiederholt auszuführen Alle Arten von Schleifen können auf die beiden Konstrukte Selektion (if..then..end) und Sprung (goto) zurückgeführt werden. Wird oft in Zwischensprachen zur Codegenerierung verwendet. Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-29 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Aufzählungsgesteuerte Schleifen: Probleme (1/3) Aufzählungsgesteuerte Schleifen: Fragen (2/3) Beispiel von FORTRAN I, II und IV Allgemeine Form von Schleifen: do 10 i = 1, 10, 2 ... 10: continue FOR j := first TO last BY step DO … END Die Variable i wird die Indexvariable genannt und nimmt hier die Werte 1, 3, 5, 7, 9 an Die Statements zwischen der ersten und letzte Zeile werden der Schleifenrumpf (loop body) genannt und wird hier für jeden der fünf Werte von i einmal ausgeführt Probleme: Der Schleifenrumpf wird immer mindestens einmal ausgeführt Statements im Schleifenrumpf könnten i ändern und somit würde auch das Verhalten der Schleife verändert werden Goto Statements können in oder aus der Schleife springen Fragen die zu dieser Schleifenart zu stellen sind: 3. Kann j, first und/oder last im Schleifenrumpf verändert werden? Wenn ja, welchen Einfluss hat dies auf die Steuerung? Was passiert wenn first größer als last ist? Welchen Wert hat j wenn die Schleife beendet wurde? 4. Kann die Kontrolle von außerhalb in die Schleife hineinspringen? 1. 2. 6.6-30 Goto-Sprünge in die Schleife hinein, wobei i nicht vernünftig initialisiert wird, werden wahrscheinlich in einem Laufzeitfehler enden Die Schleife wird beendet wenn der Wert von i die obere Grenze überschreitet. Dies könnte einen (unnötigen) arithmetischen Überlauf veranlassen, wenn die obere Grenze in der Nähe des größten, zulässigen Wertes von i liegt Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-31 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-32 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Aufzählungsgesteuerte Schleifen: Antworten (3/3) Können der Schleifenindex oder die Grenzen verändert werden? Ist bei den meisten Sprachen verboten Algol 68, Pascal, Ada, Fortran 77 und 90, Modula-3 Was passiert wenn first größer als last ist? Iteratoren Die meisten Sprachen werten first und last aus bevor die Schleife ausgeführt wird Falls first größer als last ist, dann wird der Schleifenrumpf niemals ausgeführt Welchen Wert hat der Schleifenindex j am Ende der Schleife? Ist in einigen Sprachen nicht definiert, z.B. Fortran IV oder Pascal In den meisten Sprachen gilt der zuletzt definierte Wert Einige Sprachen machen die Indexvariable zu einer lokalen Variablen der Schleife, weswegen die Variable außerhalb des Gültigkeitsbereiches der Schleife nicht sichtbar ist Beispiel: “every write (1 + upto(‘ ‘, s))” in ICON schreibt jede Position in den String s welcher ein Leerzeichen folgt upto(‘ ‘,s) erzeugt jede Position in s gefolgt von einem Leerzeichen Nein, für die meisten Sprachen Viele Sprachen erlauben aber den Sprung von innen nach außen Viele Sprachen bieten Anweisungen (break; exit; last, etc,) an, die einen frühzeitige Abbruch der Schleife ohne expliziten Sprung erlauben Die “FOR j := first TO last BY step DO … END” Schleife kann auch geschrieben werden als “every j := first to last by step do { … }” in ICON Kann die Kontrolle von außerhalb in die Schleife hineinspringen? Alle bisher betrachteten Schleifen iterieren über eine arithmetische Folge (1,3,5,7) Aber wie iterieren wir über eine frei wählbare Menge von Objekten? Antwort: Iteratoren Iteratoren sind Ausdrücke die mehrere Werte generieren und welche Ihre enthaltende Ausdrücke und Statements bestimmen können um diese mehrmals auszuwerten oder auszuführen Ein anderes Beispiel: “write(10 + 1 to 20)” schreibt 10 + j für jedes j zwischen 1 und 20 1 bis 20 erzeugt die Folge 1 bis einschließlich 20 Siehe auch Standard Template Library (STL) von C++ Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-33 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Logikgesteuerte Schleifen Rekursion Beispiele: for(Vor-Anweisung; Bedingung; Nach-Anweisung ) Block Nachprüfende Schleife Mit anderen Worten, komplexe Berechnungen werden mit Begriffen von einfacheren Berechnungen ausgedrückt Ähnlich dem Prinzip der mathematischen Induktion Ist die bevorzugte Methode der wiederholenden Programmausführung in funktionalen Sprachen „repeat Anweisung until Bedingung“ „do Anweisung while Bedingung“ Diese Art von Schleife werden oft falsch verwendet und sollten daher vermieden werden (da mindest ein Durchlauf erfolgt!) Eigentlich ist es die meiste Zeit der einzigste Weg um Berechnungen in funktionalen Sprachen wiederholt auszuführen Warum? Prüfung in der Mitte einer Schleife Erlaubt es Berechnungen durch sich selbst zu definieren „while Bedingung do Anweisung“ Die C for-Schleife ist eine vorprüfende, logikgesteuerte Schleife und nicht eine aufzählungsgesteuerte Schleife! Vorprüfende Schleife „loop Anweisung when Bedingung exit Anweisung end“ Weil für die Alternative, nämlich Iteration (Schleifen), die Ausführung von Anweisungen mit Seiteneffekten (wie Zuweisungen) verbunden ist, was in rein funktionalen Sprachen verboten ist Dies ist nicht wirklich eine Einschränkung da Rekursion streng genommen genauso Mächtig ist wie Iteration und umgekehrt Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-34 6.6-35 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-36 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Rekursion und Iteration Verbesserte Durchführung von Rekursion Informell gesprochen, die Schleife “FOR j := first TO last BY step DO … END” wird durch Rekursion zu: fun loop j step last = if j <= last then … loop j + step step last fun loop j step last = if j <= last then … loop j + step step last Gegebene Schleife Der rekursive Aufruf der Schleife „loop“ ist die letzte ausgeführte Berechnung der Funktion „loop“ Geschickte Compiler werden Endrekursionen durch eine Schleife ersetzen Es gibt mit der rekursiven Emulation von Iteration aber ein Problem: sie ist langsam! Wird Endrekursion (tail recursion) genannt Argumente für den Aufruf der Endrekursion werden ausgewertet und in den entsprechenden Lokalen platziert und ein Sprung zurück zum Anfang der Funktion wird gemacht anstatt einen rekursiven Aufruf zu machen Dies kann zu einer signifikanten Steigerung der Leistung führen Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-37 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Evaluationskonzepte von Funktionsargumenten Ausblick Wie werden Argumente für Funktionen ausgewertet? Die Argumente einer Funktion werden ausgewertet bevor die Funktion die Steuerung erhält? Wird Applicative-Order Evaluation (eager evaluation, call-by-value) bezeichnet Ist der Standard in den meisten Sprachen Die Argumente einer Funktion werden nur dann ausgewertet, wenn sie auch benötigt werden? 6.6-38 Namen, Gültigkeitsbereiche und Bindungen (Kapitel 3) Speichermanagement und Implementierung Kontrollfluss (Kapitel 6) Zusammenführung (Bau eines ausführbaren Programms) (Kapitel 9) Wird Normal-Order Evaluation (lazy evaluation‚ call by need, delayed evaluation) bezeichnet Kann zu einer besseren Leistung führen wenn an eine Funktion übergebene Argumente manchmal nicht gebraucht werden Beispiel: void foo (int a, int b, int c) { if (a + b < N) { return c; } else { return a + b; } foo(a,b,expensive_function()) In einigen Fällen wird „c“ nicht benötigt, und der Wert von c könnte von einerteuer zu berechnenden Funktion bestimmt werden Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-39 Kontrollstrukturen Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.6-40 6. Sprachen, Compiler und Theorie Der Kompilationsprozess (-phasen) Informatik II Scanner (lexikalische Analyse) Lese Programm und konvertiere Zeichenstrom in Marken (tokens). Theorie: Reguläre Ausdrücke, endlicher Automat Parser (syntaktische Analyse) Lese Tokenstrom und generiere Ableitungsbaum (parse tree). Theorie: Kontextfreie Grammatiken, Kellerautomat SS 2004 Traversiere Parserbaum, überprüfe nicht-syntaktische Regeln. Semantische Analyse Teil 6: Sprachen, Compiler und Theorie Zwischencodegenerierung Traversiere Parserbaum noch mal, gebe Zwischencode aus. Optimierung Untersuche Zwischencode, versuche ihn zu verbessern. Zielcodegenerierung Übersetze Zwischencode in Assembler-/Maschinencode Optimierung Maschinenebene Untersuche Maschinencode, versuche ihn zu verbessern. 7 – Zusammenführung Prof. Dr. Dieter Hogrefe Dipl.-Inform. Michael Ebner Lehrstuhl für Telematik Institut für Informatik Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Die Organisation eines typischen Compilers Schreiben des Programms Frontend Führt Operationen aus welche von der zu kompilierenden Sprache abhängen und nicht von der Zielmaschine Backend Führt Operationen aus welche etwas Wissen über die Zielmaschine haben müssen Ein kleines Programm, geschrieben in Pascal, welches den größten gemeinsamen Teiler (ggT) von zwei Ganzzahlen berechnet program gcd (input, output); var i, j : integer; begin read(i,j); while i <> j do if i > j then i := i – j else j :- j – i; writeln(i) end. Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-2 6.7-3 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-4 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Vom Text des Quellcodes zu den Tokens Von den Tokens zum Ableitungsbaum Programmquelltext Programmquelltext program gcd (input, output); var i, j : integer; begin read(i,j); while i <> j do if i > j then i := i – j else j :- j – i; writeln(i) end. Tokens program gcd ( input , output ) ; var i , j : integer ; begin read ( i , j ) ; while i <> j do if i Ableitungsbaum und Symboltabelle program gcd (input, output); var i, j : integer; begin read(i,j); while i <> j do if i > j then i := i – j else j :- j – i; writeln(i) end. … Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-5 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Zwischencode Nicht optimierende Compiler Der Ableitungsbaum wird zu einem Kontrollflussgraphen konvertiert Die Knoten des Kontrollflussgraphen sind grundlegende Blöcke und enthalten eine pseudoAssemblersprache Die Kontrolle kann einen grundlegenden Block nur vom Anfang betreten und kann in nur am Ende wieder verlassen Der Kontrollflussgraph wird zum Zielcode der Zielmaschine konvertiert Der Zielcode ist eine andere pseudo-Assemblersprache Der Kontrollfluss wird ausführlich gemacht durch: Bezeichnen der Anfänge der grundlegenden Blöcke Konvertieren der Kontrollflusskanten zu Sprung- (branch), Aufruf(call) und Rückkehrinstruktionen Virtuelle Register werden durch reale Register ersetzt Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-6 6.7-7 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-8 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Zielcode Der Zielcode ist beinahe Assemblercode Vom Zielcode zum Assemblercode Normalerweise einfach: Der Kontrollfluss ist ausführlich Der Code referenziert nur reale Registernamen Anweisungen zum Speicherreservieren sind vorhanden r10 := r8 + r9 -> add $10, $8, $9 r10 := r8 + 0x12 -> addi $10, $8, 0x12 Manchmal auch zu einer Folge von Instruktionen erweitert: r14 := 0x12345abc -> lui $14, 0x1234 ori $14, 0x5abc Zielcode ist einfach zu Assemblercode zu übersetzen Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-9 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Binden (linking) Optimierende Compiler Beim Binden werden mehrere durch den Assembler erzeugte Objektdateien zu einer einzelnen, ausführbaren Datei kombiniert, welche durch ein Betriebssystem lauffähig ist Optimierung ist ein komplexer Prozess Verbessert die Qualität des generierten Codes auf Kosten von zusätzlicher Compilezeit Optimierer sind schwierig zu schreiben und einige Optimierungen verbessern das fertige Programm vielleicht nicht Praxistip: Die jeweilige Einstellung der Optimierungstiefe eines Compilers genau auf korrekte Funktion kontrollieren, da diese öfters fehleranfällig sind (lieber erstmal auf Optimierung verzichten und erst am Ende austesten!) Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-10 6.7-11 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-12 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Peephole-Optimierung Sehe dir den Zielcode an, wenige Instruktionen gleichzeitig, und versuche einfache Verbesserungen zu machen Versucht kurze, sub-optimale Folgen von Instruktionen zu finden und ersetzt diese Sequenzen mit einer „besseren“ Sequenz Sub-Optimale Sequenzen werden durch Muster (patterns) spezifiziert Peephole-Optimierungstypen (1/3) Redundante load/store Instruktionen beseitigen r2 i r3 r4 Meistens heuristische Methoden — Es gibt keinen Weg um zu überprüfen ob das Ersetzen eines sub-optimalen Musters tatsächlich das endgültige Programm verbessern wird := := := := r1 + 5 r2 i r3 x 3 wird zu r2 := r1 + 5 i := r2 r4 := r2 x 3 Konstantenfaltung (constant folding) r2 := 3 x 2 Einfach und ziemlich wirksam wird zu r2 := 6 Entfernung gemeinsamer Teilausdrücke (common subexpression elimination) r2 := r1 x r5 r2 := r2 + r3 r3 := r1 x r5 wird zu r4 := r1 x r5 r2 := r4 + r3 r3 := r4 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-13 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Peephole-Optimierungstypen (2/3) Algebraische Vereinfachung (strength reduction) r2 := 4 wird zu r3 := r1 + 4 und r2 := … r3 := r1 + 4 r2 := … r1 := r2 x 2 r1 := r2 / 2 wird zu r1 := r2 + r2 r1 := r2 >> 1 oder r1 := r2 << 1 und auch r2 := 4 r3 := r1 + r2 r3 := *r3 Peephole-Optimierungstypen (3/3) Fortpflanzung von Konstanten (constant propagation) r2 := 4 r3 := r1 + r2 r2 := … 6.7-14 wird zu r3 := r1 + 4 r3 := *r3 und r3 := *(r1+4) Fortpflanzung von Zuweisungen (copy propagation) r2 := r1 r3 := r1 + r2 r2 := 5 r2 := r1 wird zu r3 := r1 + r1 r2 := 5 und r3 := r1 + r1 r2 := 5 Beseitigung von unnötigen Instruktionen r1 := r1 + 0 r1 := r1 - 0 r1 := r1 * 1 wird zu Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-15 (wird komplett beseitigt) Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-16 6. Sprachen, Compiler und Theorie 6. Sprachen, Compiler und Theorie Komplexe Optimierungen Es ist für den Optimierer erforderlich den Datenfluss zwischen Registern und Speicher zu „verstehen“ Wird durch Datenflussanalyse (data flow analysis) bestimmt Beispiel für komplexe Optimierung Schleifeninvarianter Code (Loop Invariant Code Motion) Beispiel: Finde die Variablenmenge welche einen grundlegenden Block auf dem Weg zu einem anderen Block „durchfliesst“ und von diesem anderen Block verwendet wird (lebendige Variablen) Ist wichtig für Optimierungen welche datenverändernde Instruktionen einfügen, löschen oder bewegen Der ursprüngliche Programmdatenfluss kann nicht geändert werden! Bewege Berechnungen, dessen Werte während allen Schleifeniterationen gleich bleiben (invariant sind), aus der Schleife raus L1: r1 := *(sp + 100) r2 := *r1 … r3 := r2 < 100 if r3 goto L1 L0: r1 := *(sp + 100) L1: r2 := *r1 … r3 := r2 < 100 if r3 goto L1 Es ist für den Optimierer erforderlich die Struktur des Kontrollflussgraphen (control flow graph) zu „verstehen“ Wird durch Kontrollflussanalyse bestimmt Ist wichtig für die Leistungsoptimierung von Schleifen oder schleifenähnlichen Strukturen in dem Kontrollflussgraphen Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-17 6. Sprachen, Compiler und Theorie Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-18 6. Sprachen, Compiler und Theorie Was haben Sie in diesem Kapitel gelernt? Ausblick Compilerbau (Kapitel 6) Programmiersprachen sind Abstraktionsmechanismen, welche: ein Rahmenwerk zum Lösen von Problemen und erzeugen neuer Abstraktionen bieten Schirmen den Programmierer von niederen Detailebenen (low-level details) der Zielmaschine (Assemblersprache, Verbinden (linken), etc.) ab Compiler sind komplexe Programme welche eine höhere Programmiersprache in eine Assemblersprache umwandeln um danach ausgeführt zu werden Compilerprogrammierer handhaben die Komplexität des Kompilationsprozesses durch: Betriebssysteme (Kapitel 5) Maschinenorientierte Programmierung (Kapitel 4) von-Neumann-Rechner (Kapitel 3) aufteilen des Compilers in unterschiedliche Phasen verwenden, ausgehend von der Spezifikation, eine Theorie für den Bau von Compilerkomponenten Speicher Zahlen und logische Schaltungen (Kapitel 2) Kommunikation (Kapitel 7) von-Neumann-Rechner Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-19 Zusammenführung Dipl.-Inform. Michael Ebner, Prof. Dr. Dieter Hogrefe Informatik II - SS 2004 6.7-20