Vorlesungsskript Programmanalyse Prof. Dr. phil. Dr. rer. nat. habil. Michael Schenke Version vom: 15.05.2016 1 Inhaltsverzeichnis Inhaltsverzeichnis ................................................................................... 1 Vorwort ................................................................................................. 2 1 2 3 4 5 Einführung....................................................................................... 4 1.1 Allgemeines ............................................................................... 4 1.2 Syntax und Semantik .................................................................. 5 Semantik von Programmen ................................................................ 6 2.1 Einführung................................................................................. 6 2.2 Operationelle Semantik ............................................................... 7 2.3 Substitutionslemma .................................................................... 9 Kalküle.......................................................................................... 10 3.1 Einführung................................................................................ 10 3.2 Hoaresche Kalküle (HK).............................................................. 13 3.3 Vollständigkeit von PD und TD .................................................... 14 3.4 Beweisskizzen zur Korrektheit von PD .......................................... 16 Compilerbautechniken ..................................................................... 21 4.1 Parsingtechniken ....................................................................... 21 4.2 Vermeiden von Backtracking ....................................................... 26 4.3 Attributierte Grammatiken .......................................................... 27 4.4 Rekursiver Abstieg ..................................................................... 30 Praktikum ...................................................................................... 32 2 Vorwort Im Rahmen der Lehrveranstaltung Programmanalyse erlernen Sie Standardalgorithmen für Hoaresche Logik und erhalten eine knappe Einführung in die Welt des Compilerbaus. Die Korrektheitsbeweise stehen im theoretischen Teil im Vordergrund. Dabei vertiefen und erweitern Sie ihr Wissen in Themen wie Syntax und Semantik und erlernen völlig Neues, wie z. B. die Hoaresche Logik. Themen aus der Theoretischen Informatik finden ihre Anwendung, somit wird der Kreis zum Grundlagenstudium geschlossen. Im Grundlagenstudium erlerntes theoretisches Wissen wird hier praxisnah angewendet. Ein wesentlicher Bestandteil dieser Veranstaltung ist praktisch. Dafür wird ein elementares System für Korrektheitsbeweise implementiert. Es werden Theorie und Praxis verschmolzen. ! ACHTUNG Das Skript ist kein Ersatz zur Vorlesung. Es soll Sie lediglich unterstützen und es Ihnen ermöglichen, die Vorlesung aufmerksam zu verfolgen. 3 4 1 Einführung Dieses Kapitel gibt Ihnen eine Einleitung in die Programmanalyse. Sie wiederholen Ihr Wissen über Syntax und Semantik. 1.1 Allgemeines Die Programmanalyse beschäftigt sich mit der (möglichst automatisierten) Analyse des Verhaltens von Computerprogrammen. Eine besondere Rolle spielt dabei die Korrektheit eines Programmes. Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..1: Ein Programm wird als korrektes Programm bezeichnet, wenn es seine Spezifikation (Anforderungen) erfüllt. Diese Definition ist zunächst noch wenig aussagekräftig: Es fehlen noch formale Definitionen einer Spezifikation und des Erfüllungsbegriffs. Um diese Begriffe konkret zu fassen und eine solche Korrektheit nachzuweisen, verwenden wir später die Hoaresche Logik und ihre Kalküle. Ein Programm steht also immer im Zusammenhang mit einer Spezifikation. Aus Sicht des Programmes ist die Spezifikation ein formales Modell, nach dem es sich auszurichten gilt. Im allgemeinen wird dazu der Weg über die Semantik gegangen. Der Spezifikationssprache sei eine Semantik durch eine Funktion ⟦ . ⟧ gegeben, der Spezifikation S also der semantische Wert ⟦𝑆⟧; entsprechend gehöre zur Programmiersprache die Semantikfunktion 𝑀⟦ . ⟧, zum Programm P also der semantische Wert 𝑀⟦𝑃⟧. Der Definitionsbereich der semantischen Funktion ist jeweils die Menge der syntaktisch korrekt gebildeten Spezifikationen/Programme. Die Wertebereiche können von Sprache zu Sprache verschieden sein; normalerweise sind es Mengen oder Formeln. In Abhängigkeit von dieser Wahl muss es dann eine Implementierungsrelation geben, die aussagt, was ⟦ 𝑆⟧ mit 𝑀⟦𝑃⟧ zu tun haben muß, damit gesagt werden kann, P implementiere S. Beispielsweise könnte im Falle logischer Formeln die Implikation gefordert werden: 𝑀 ⟦ 𝑃⟧ → ⟦𝑆⟧. Programm Spezifikation [[S]] M[[P]] 5 Abbildung Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..1: Zusammenhang Spezifikation und Programm. 6 1.2 Syntax und Semantik Um ein besseres Verständnis zu entwickeln, betrachten wir zunächst folgende Grammatik: Prog → Zuweisung → Prog;Prog | SKIP | Zuweisung | IF bed THEN Prog { ELSE Prog } FI | WHILE bed DO Prog OD var = exp bed → element | bed && bed | bed || bed | !bed exp → var | zahl | exp op exp | ( exp ) element → exp vglop exp op → + | - | * | / vlgop → == | != | > | >= | < | <= Beispielgrammatik. Terminale sind blau eingefärbt. Unsere Grammatik bildet bereits eine kleine Sprache ab. Mithilfe dieser Sprache ist es uns möglich, Programme wie z. B. ein Fakultätsprogramm zu schreiben. Im wesentlichen hat jede Sprache zwei elementare Bestandteile. Einmal ist der Aufbau eines Satzes klar definiert. Es existieren Strukturregeln, die beschreiben, wie die Wörter zu Sätzen aneinandergereiht werden können. Des Weiteren steht hinter jedem Wort und jedem Satz eine Bedeutung. Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..2: Die Regeln für die formale Struktur (Satzbau), die durch die Grammatik der entsprechenden Sprache festgelegt sind, werden als Syntax einer Sprache bezeichnet. Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..3: Die Semantik steht für die Bedeutung eines Wortes bzw. Satzes. 7 2 Semantik von Programmen Hier erweitern und vertiefen Sie Ihr Wissen über die Semantik. 2.1 Einführung Wir bezeichnen 𝑀⟦𝑃⟧ als Semantik des Programms 𝑃. 𝑀⟦𝑃⟧(𝜎) = 𝜏 𝜎 – Variablenbelegung am Anfang 𝜏 – Variablenbelegung am Ende Σ - Menge aller Belegungen 𝑀⟦𝑃⟧: Σ ↪ Σ Die genaue Definition dieser Funktion stellt ein Problem dar. Es existieren hierzu zwei Hauptmethoden: denotationelle Semantik operationelle Semantik S P denot. [[S]] dichter an der Spezifikation dichter an der Maschinensprache (MS) MS operat. [[P]] [[MS]] Die Semantik der Sprachen könnte im Prinzip durch Induktive Konstruktion definiert werden. Eine Ausnahme bilden allerdings Programmschleifen. Ein Programm P ist syntaktisch aus kleinen Teilen aufgebaut, deren Semantik ist durch Induktion bekannt. Es wird versucht, die Semantik von P aus der Semantik der Programmteile zu berechnen. Dies ist (unter anderem) die Idee der denotationellen Semantik. Die mathematische Finesse der denotationellen Semantik ist beträchtlich. Aus diesem Grund beschäftigen wir uns nachfolgend intensiver mit der operationellen Semantik und lassen die denotationelle außen vor. 8 2.2 Operationelle Semantik Wir führen eine neue Dimension ein, die Abarbeitungsreihenfolge der einzelnen Schritte. Im Grunde handelt es sich dabei um die Einführung einer rudimentären Form der Zeit, die allerdings nicht weiter quantifiziert wird. Zu jedem Zeitpunkt wird der Prozess beschrieben durch eine Konfiguration. Das ist ein Paar (𝑃, 𝜎) mit: 𝑃 ist das noch abzuarbeitende Restprogramm. 𝜎 ist die zum gegebenen Zeitpunkt gültige Belegung der Variablen. Damit gilt: Am Anfang ist 𝑃 das gesamte Programm; Zustand. Am Ende ist das Programm abgearbeitet; die Endkonfiguration ist 𝜎 ist der am Anfang gültige (𝐸, 𝜏). Aus formalen Gründen wird hier die Einführung von E nötig. Dieses bezeichnet aus Sprachsicht das leere Wort, hier also das leere Programm. Für einen Beispielablauf (𝑃, 𝜎) → (𝑃1 , 𝜎1 ) → (𝑃2 , 𝜎2 ) → ⋯ → (𝑃𝑛 , 𝜎𝑛 ) Ergebnis 𝜎𝑛 , falls 𝑃𝑛 = E gilt. Das heißt, dann gilt 𝑀⟦𝑃⟧(𝜎) = 𝜎𝑛 . ! ist das Beispiel Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..1 – Fakultätsprogramm 𝑃≡ 𝑓𝑎𝑘 = 1; 𝑃1 Sei 𝜎 ∈ Σ (𝑃, 𝜎) → (𝑃1 , 𝜏) mit 𝜏 = 𝜎{𝑓𝑎𝑘⁄1} {𝑓𝑎𝑘⁄1} bezeichne hier logisch eine Substitution: Die Variable 𝑓𝑎𝑘 wird zu 1 gesetzt. 𝑃1 ist dann das Restprogramm. Das Problem ist dabei die Definition von →. → ist eine Relation auf Konfigurationen. Eine Berechnung des Programms ist ein Element von →∗ , also der reflexiven, transitiven Hülle von →. Die Konstruktion von → erfolgt induktiv! Dieser Ansatz, bei dem nicht das Programm sondern sein Ablauf mit einer induktiven Semantik versehen werden, heißt strukturelle operative Semantik. Es ist (𝑃, 𝜎) gegeben, dafür ist (𝑃, 𝜎) → (𝑃1 , 𝜏) zu bestimmen. Das wird durch Induktion über den Aufbau von P bewerkstelligt. Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..1: → ist folgendermaßen definiert: (SKIP, σ) → (Ε, σ) (var = exp, σ) → (E, σ{var⁄σ(exp)}) 9 (WHILE bed DO P OD, σ) → (P; WHILE bed DO P OD, σ), falls σ(bed) = true (WHILE bed DO P OD, σ) → (E, σ), falls σ(bed) = false (IF bed THEN P1 ELSE P2 FI, σ) → (P1 , σ), falls σ(bed) = true (IF bed THEN P1 ELSE P2 FI, σ) → (P2 , σ), falls σ(bed) = false wenn (P1 , σ) → (P1′ , σ′), dann (P1 ; P2 , σ) → (P1′ ; P2 , σ′) (E; P, σ) → (P, σ) Damit ist → definiert und folglich kann die Semantik insgesamt definiert werden. Das folgende Beispiel zeigt den Anfang bei der Abarbeitung des Fakultätsprogrammes. ! Beispiel Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..2 (fak = 1; P1 , σ) Ausgangskonfiguration (fak = 1, σ) → (E, σ{fak⁄1}) Der erste Schritt Da E das leere Programm ist gilt auch (fak = 1; P1 , σ) → (𝐸; 𝑃1 , 𝜎{𝑓𝑎𝑘 ⁄1}) → (𝑃1 , 𝜎{𝑓𝑎𝑘⁄1}) → ⋯ Hilfssatz: 1. Jede Konfiguration hat höchstens eine Nachfolgekonfiguration in →. Die Semantik ist also deterministisch. 2. Die einzigen Konfigurationen, die keine Nachfolge in → haben, sind von der Form (E, σ). Programme blockieren nicht. 3. Die maximalen Berechnungsfolgen sind entweder a. unendlich (Das Programm divergiert.) oder b. enden in (E, σ). (Das Programm terminiert im Zustand σ.) Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..2: Semantik der partiellen Korrektheit: 𝑀(⟦𝑃⟧)(𝜎) = 𝜏 gdw. (P, σ) →∗ (𝐸, 𝜏). Für jedes Programm P gilt also 𝑀⟦𝑃⟧: Σ ↪ Σ Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..3: 10 Semantik der totalen Korrektheit: 𝑀𝑡𝑜𝑡 (⟦𝑃⟧)(𝜎) = { 𝜏 gdw. (𝑃, 𝜎) →∗ (𝐸, 𝜏) ⊥ 𝑃 divergiert von 𝜎 aus Hinweis: 𝑀𝑡𝑜𝑡 (⟦𝑃⟧) ist eine totale Funktion: Σ → Σ ∪ {⊥} 𝑀𝑡𝑜𝑡 (⟦𝑃⟧)(⊥) = ⊥ Wenn gilt 𝑀𝑡𝑜𝑡 (⟦𝑃⟧)(𝜎) =⊥, dann gilt auch 𝑀𝑡𝑜𝑡 (⟦𝑃; 𝑅⟧)(𝜎) =⊥. Divergente Programme wirken wie Nullelemente bezüglich ;. 2.3 Substitutionslemma Eine Formel G gilt in einem Zustand gültig ist: 𝜎, wenn die mit 𝜎 substituierte Formel 𝜎 ⊨ 𝐺 gdw. ⊨ 𝐺{𝜎} ⊨ 𝐹 heißt: „F gilt unter einer leeren Menge von Voraussetzungen.“. Das heißt, ⊨ semantisches F ist allgemeingültig. Zeichen ⊢syntaktisches Zeichen Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..4: Die Erfüllungsmenge einer Formel F lautet: 𝛿(𝐹) = {𝜎| 𝜎 ⊨ 𝐹} Also ist ⊨ 𝐹 gdw. 𝐸(𝐹) = Σ. Oft ist das Ziel die Bestimmung der Allgemeingültigkeit einer Formel F, also von ⊨ 𝐹. Als Beispiel kann die Gültigkeit der Formel 𝑝 → (𝑞 → 𝑝) genommen werden. Diese kann bekanntlich z. B. durch eine Wahrheitstafel nachgewiesen werden. Die Komplexität einer Wahrheitstafel wächst jedoch exponentiell. Folglich versucht man einen anderen Weg: Statt mit der semantischen Methode „Wahrheitstafel“ wird schneller auf der syntaktischen Ebene gearbeitet. Zum Beispiel: 𝑝 → (𝑞 → 𝑝) ⇔ ¬𝑝 ∨ (𝑞 → 𝑝) ⇔ ¬𝑝 ∨ (𝑝 ∨ ¬𝑞) ⇔ (𝑝 ∨ ¬𝑝) ∨ ¬𝑞 ⇔ 𝑡𝑟𝑢𝑒 ∨ ¬𝑞 ⇔ 𝑡𝑟𝑢𝑒 Bemerkung: 1. Es wird rein formal syntaktisch geschlossen. 2. Es muß eine Menge von allgemeinen Gesetzen gefunden werden, die beschreiben, welche syntaktischen Manipulationen erlaubt sind (und welche nicht). 11 3. Bei allgemeinen Logiken ist der Weg zum „true“ schwierig zu finden. Bei praktischen Anwendungen muß natürlich dafür ein effizienter Weg gefunden werden. 12 3 Kalküle In diesem Kapitel erhalten Sie eine Einführung in die Welt der Kalküle. Sie beschäftigen sich im speziellen mit den Hoareschen Kalkülen. 3.1 Einführung Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..1: 1. Ein Kalkül ist eine Menge von Regeln 2. Eine Regel ist ein Tripel 𝑃𝑟ä 𝐶𝑜𝑛𝑑 𝐶𝑜𝑛𝑐 Prä (Prämisse): ist eine Menge von Formeln. Conc (Conclusion): ist eine Formel. Cond (Condition): Anwendungsbedingungen (Entscheidbar) Beispiele: 1. De Morgan ¬(𝐹 ∪ 𝐺) {} ¬𝐹 ∩ ¬𝐺 2. Ordnungsrelationen 𝑥 < 𝑦, 𝑦 < 𝑧 {} 𝑥<𝑧 3. SLD (aus Prolog) 𝐴1 , … , 𝐴𝑛 , 𝐵 ← 𝐵1 ∧ … ∧ 𝐵𝑚 {𝐴1 Θ = 𝐵Θ … } (𝐵1 , … , 𝐵𝑚 , 𝐴2 , … , 𝐴𝑛 )Θ 4. Jedes Axiomensystem ist ein Kalkül. Es stellt sich die Frage, wann ein gegebener Kalkül richtig ist. Es könnte auf zwei weisen falsch sein: Es könnte falsche Sätze ableiten: 𝐹→𝐺 𝐺→𝐹 wäre eine falsche Regel. Ein Kalkül, der eine solche Regel enthält, ist wertlos. Es könnte wahre Sätzen geben, die im Kalkül nicht abgeleitet werden können. 13 Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..2: Seien 𝐹1 , … , 𝐹𝑛 , 𝐺 Formeln; 𝑘 ein Kalkül. 1. 𝐺 ist ableitbar aus 𝐹1 , … , 𝐹𝑛 in 𝑘, wenn es in 𝑘 eine Regel gibt mit 𝐹1 , … , 𝐹𝑛 𝐺 und erfüllter NB, in Zeichen 𝐹1 , … , 𝐹𝑛 ⊢𝑘 𝐺 2. 𝐺 ist ableitbar aus einer Menge 𝑀 von Formeln, wenn es eine endliche Menge 𝑀′ ∈ 𝑀 gibt mit 𝑀′ ⊢𝑘 𝐺 3. Eine Menge 𝑁 von Formel ist aus einer Menge 𝑀 ableitbar, wenn für alle 𝐹 ∈ 𝑁 gilt: 𝑀 ⊢𝑘 𝐺 Damit ist ⊢𝑘 eine Relation auf der Menge aller Formelmengen. 4. Die Relation ⊢𝑘 wird erweitert, indem man zu ihrer reflexiven, transitiven Hülle übergeht. Die Arbeit mit einem Kalkül soll die Arbeit mit der Semantik ersetzen. Folglich stellt sich die Frage, was ⊨ und ⊢𝑘 miteinander zu tun haben. Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..3: Ein Kalkül 𝑘 heißt korrekt wenn gilt: 𝑀 ⊢𝑘 𝑁 ⇒ 𝑀 ⊨ 𝑁 Ein Kalkül 𝑘 heißt vollständig wenn gilt: 𝑀 ⊨ 𝑁 ⇒ 𝑀 ⊢𝑘 𝑁 Hilfssatz: Ein Kalkül ist korrekt gdw. jeder seiner Regeln korrekt ist. Beispiele für vollständige Kalküle: 1. Für Aussagelogik: a. Hilbert-Kalkül b. „Freistil-Kalküle“ 2. Für Prädikatenlogik: Prädikaten-Kalkül von Gödel 3. SLD-Kalkül für die Logik-Programmierung 4. Die Kalküle der Hoareschen Logik sind „relativ vollständig“ (s.u.). 14 Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..4: Sei 𝐻 eine Menge von Zuständen. Dann ist: 𝑀⟦𝑃⟧(𝐻) = {𝑀⟦𝑃⟧(ℎ)|ℎ ∈ 𝐻} Sei 𝐹 eine Formel, dann sei: 𝑀⟦𝑃⟧(𝐹) = 𝑀⟦𝑃⟧(𝛿(𝐹)) Hilfssatz: Sei 𝑃 ein Programm und 𝐹 eine Formel. Dann existiert eine Formel 𝐺 mit: 𝑀⟦𝑃⟧(𝐹) = 𝛿(𝐺) Dies alles gilt sinngemäß auch für 𝑀𝑡𝑜𝑡 . Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..5: Ein Hoaresches Tripel ist ein Tripel {𝐹} 𝑃 {𝐺} mit: 𝐹, 𝐺 sind Prädikatenlogische Formeln 𝑃 ist ein WHILE-Programm Definition Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..6: Ein Hoaresches Tripel heißt korrekt (gültig) wenn gilt: 𝑀⟦𝑃⟧(𝐹) ⊆ 𝛿(𝐺) Das Programm P erfüllt eine Spezifikation aus den Formeln 𝐹 und 𝐺 gdw. das Hoaresche Tripel {𝐹} 𝑃 {𝐺} gültig ist. Wir haben festgelegt was es bedeutet, dass {𝐹} 𝑃 {𝐺} gültig ist. Die Menge dieser Tripel bildet die Syntax der Hoareschen Logik. Das heißt: Wir kennen die Bedeutung von ⊨ {𝐹} 𝑃 {𝐺}. Jetzt gilt es, einen Kalkül 𝑘 (Hoarescher Kalkül) zu finden mit ⊢𝑘 {𝐹} 𝑃 {𝐺}. Dieser Kalkül muss korrekt und soll möglichst vollständig sein. 15 3.2 Hoaresche Kalküle (HK) Zunächst definieren wir die sechs grundlegenden Regeln der Hoareschen Logik. Dabei handelt es sich um einen Kalkül für die partielle Korrektheit deterministischer Programme (PD). SKIP {𝐹} 𝑆𝐾𝐼𝑃 {𝐹} Zuweisung {𝐹 {𝑥 ⁄𝑡}} 𝑥 = 𝑡 {𝐹} Konsequenzregel 𝐹1 → 𝐹, {𝐹} 𝑃 {𝐺}, 𝐺 → 𝐺1 {𝐹1 } 𝑃 {𝐺1 } Sequenzregel {𝐹} 𝑃1 {𝐺}, {𝐺} 𝑃2 {𝐻} {𝐹} 𝑃1 ; 𝑃2 {𝐻} Verzweigung {𝐹 ∧ 𝑏𝑒𝑑} 𝑃1 {𝐺} {𝐹 ∧ ¬𝑏𝑒𝑑} 𝑃2 {𝐺} {𝐹}𝐼𝐹 𝑏𝑒𝑑 𝑇𝐻𝐸𝑁 𝑃1 𝐸𝐿𝑆𝐸 𝑃2 {𝐺} Schleife {𝐼𝑛𝑣 ∧ 𝑏𝑒𝑑} 𝑃 {𝐼𝑛𝑣} {𝐼𝑛𝑣} 𝑊𝐻𝐼𝐿𝐸 𝑏𝑒𝑑 𝐷𝑂 𝑃 𝑂𝐷 {𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑} Die Korrektheit von PD ist bis auf die Zuweisungsregel anschaulich klar. Bemerkungen zu einigen Regeln: 1. Die Zuweisungsregel sieht auf den ersten Blick überraschend aus. Die Regel {𝐹} 𝑥 = 𝑡 {𝐹 {𝑥 ⁄𝑡}} scheint auf der Hand zu liegen. Sie ist aber falsch, wie man leicht durch eine Anwendung sieht. Dann würde nämlich gelten: {𝑥 = 3} 𝑥 = 𝑥 + 1 {𝑥 = 2}. Die Tatsache, daß wir bei der Zuweisungsregel von hinten nach vorne schließen hat erhebliche Konsequenzen für die Implementierung des Verifikationsverfahrens. Alle Algorithmen müssen von hinten durch das Programm laufen. 2. Die Konsequenzregel ist insofern ein Ärgernis, als sie uns zwingt, die gesamte Prädikatenlogik in die Hoaresche Logik hereinzuholen. 16 3. Für den Nachweis eines Tripels {𝐹} 𝑃1 ; 𝑃2 {𝐻} mittels der Sequenzregel, muß eine Zwischenbehauptung G gefunden werden. Das ist oft möglich, indem man auf die weiter unten beschriebene „schwächste Vorbedingung“ zurückgreift. 4. Auch die Schleifenregel hat ihre Tücken: Damit sie angewandt werden kann, muß eine Formel Inv, eine Invariante, gefunden werden. Leider gibt es beweisbar kein allgemeines Verfahren zum Finden einer Invariante. Es gibt Sonderfälle, wo dies geht; ansonsten ist eine Intervention durch den Nutzer unumgänglich. Der Kalkül TD: totale Korrektheit deterministischer Programme Betrachten wir als nächstes die totale Korrektheit deterministischer Programme (TD). Zunächst gelten hier ebenfalls die Regeln, der partiellen Korrektheit (Siehe Seite 13). Nur die Schleifenregel wird ersetzt durch 2. Schleifenregel {𝐼𝑛𝑣 ∧ 𝑏𝑒𝑑} 𝑃 {𝐼𝑛𝑣} {𝑧0 = 𝑡} 𝑃 {𝑧0 > 𝑡} 𝐼𝑛𝑣 → 𝑡 ∈ ℕ0 {𝐼𝑛𝑣} 𝑊𝐻𝐼𝐿𝐸 𝑏𝑒𝑑 𝐷𝑂 𝑃 𝑂𝐷 {𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑} wobei 𝑧0 eine Variable ist, die in 𝑃 nicht vorkommt. Bei 𝑡 handelt es sich um einen Term über den Programmvariablen. 3.3 Vollständigkeit von PD und TD Zur Vollständigkeit der beiden Kalküle kann folgendes Gesagt werden: ! Merksatz Error! Use the Home tab to apply Überschrift 1 to the text that you want to appear here..1 Die Kalküle der partiellen und totalen Korrektheit deterministischer Programme gilt die Vollständigkeit NICHT. PD ist „relativ vollständig bezüglich der Prädikatenlogik (PL)“. Das heißt, wenn wir ein Entscheidungsverfahren für die PL hätten, wäre PD vollständig. Da PL nicht entscheidbar ist, muss man sich bei der Implementierung anders helfen. 1. Möglichkeit: Abfrage an den Nutzer 2. Möglichkeit: Vertrauen auf Mathematica 17 TD ist noch nicht einmal relativ vollständig. Es gibt terminierende Programme, bei denen man die Terminierung mit TD selbst dann nicht nachweisen kann, wenn ein Entscheidungsverfahren für die PL vorausgesetzt wird. Was man zur Erzwingung der Terminierung braucht ist eine Ordnungsrelation mit der Artinschen Kettenbedingung: Unter jedem Element ist jede Kette endlich. Beispiel: Anordnung nach Gold-, Silber-, Bronzemedaillen (𝑔1 , 𝑠1 , 𝑏1 ) < (𝑔2 , 𝑠2 , 𝑏2 ) gdw. 𝑔1 < 𝑔2 oder 𝑔1 = 𝑔2 ∧ 𝑠1 < 𝑠2 oder 𝑔1 = 𝑔2 ∧ 𝑠1 = 𝑠2 ∧ 𝑏1 < 𝑏2 Das ist eine lineare Ordnung mit der Artinschen Kettenbedingung. 18 3.4 Beweisskizzen zur Korrektheit von PD Zuweisungsregel Zu zeigen: 𝑀⟦𝑥 = 𝑡⟧ (𝛿(𝐹{𝑥⁄𝑡})) ⊆ 𝛿(𝐹) Zum Beweis benutzen wir das aus der Logik bekannte Substitutionslemma: 𝑀(𝐹)(𝜎{𝑥⁄𝑡}) = 𝑀 (𝐹(𝑥⁄𝑡)) (𝜎) mit der Substitution {𝑥⁄𝑡} und der dadurch modifizierten Belegung 𝜎{𝑥⁄𝑡}(𝑦) = { 𝜎(𝑦) 𝜎(𝑡) 𝑥≠𝑦 𝑥=𝑦 Im Substitutionslemma werden eine syntaktische Veränderung (𝐹 → 𝐹{𝑥⁄𝑡}) und eine semantische Veränderung (𝜎 → 𝜎{𝑥⁄𝑡}) verknüpft. Sei 𝜎 ∈ 𝛿(𝐹{𝑥⁄𝑡}). Zu zeigen: (𝑥 = 𝑡, 𝜎) → (𝐸, 𝜏) für ein 𝜏 ∈ 𝛿(𝐹). Nach der Definition der →-Relation gilt: (𝑥 = 𝑡, 𝜎) → (𝐸, 𝜎 (𝑥⁄ )) = (𝐸, 𝜎(𝑥⁄𝑡)) 𝜎(𝑡) Wegen 𝜎 ∈ 𝛿(𝐹{𝑥⁄𝑡}) und wegen des Substitutionslemmas gilt: 1 = 𝑀(𝐹{𝑥⁄𝑡})(𝜎) = 𝑀(𝐹)(𝜎{𝑥⁄𝑡}) das heißt 𝜎{𝑥⁄𝑡} ∈ 𝛿(𝐹) ∎ Konsequenzregel Man sieht leicht die folgende Bemerkung ein („Monotonie der semantischen Abbildung“): Aus 𝑁1 ⊆ 𝑁2 folgt 𝑀⟦𝑃⟧(𝑁1 ) ⊆ 𝑀⟦𝑃⟧(𝑁2 ) Wenn jetzt die Vorbedingungen der Konsequenzregel gelten, 𝐹1 → 𝐹, {𝐹} 𝑃 {𝐺}, 𝐺 → 𝐺1 dann bedeutet das: 𝛿(𝐹1 ) ⊆ 𝛿(𝐹), 𝑀⟦𝑃⟧(𝛿(𝐹)) ⊆ 𝛿(𝐺), 19 𝛿(𝐺) ⊆ 𝛿(𝐺1 ) Also 𝑀⟦𝑃⟧(𝛿(𝐹1 )) ⊆ 𝑀⟦𝑃⟧(𝛿(𝐹)) ⊆ 𝛿(𝐺) ⊆ 𝛿(𝐺1 ) Folglich {𝐹1 } 𝑃 {𝐺1 } Sequenzregel Aus dem folgenden Hilfssatz folgt die Sequenzregel unmittelbar: 𝑀⟦𝑃1 ; 𝑃2 ⟧(𝜎) = 𝑀⟦𝑃2 ⟧(𝑀⟦𝑃1 ⟧(𝜎)) Beweis des Hilfssatzes: Aus der Regel der Semantik (𝑅1 , 𝜎) → (𝑅2 , 𝜏) (𝑅1 ; 𝑃, 𝜎) → (𝑅2 ; 𝑃, 𝜏) folgt durch Induktion über die Länge der Ableitung die stärkere Fassung, (𝑅1 , 𝜎) →∗ (𝑅2 , ) (𝑅1 ; 𝑃, 𝜎) →∗ (𝑅2 ; 𝑃, ) Die Voraussetzung besagt für den Fall 𝑅2 = E gerade, daß 𝑀⟦𝑅1 ⟧(𝜎) = gilt. Jetzt seien 𝑅1 = 𝑃1 und 𝑃 = 𝑃2 mit 𝑀⟦𝑃2 ⟧() = 𝜚 also (𝑃2 , ) →∗ (𝐸, 𝜚) dann gilt 𝑀⟦𝑃2 ⟧(𝑀⟦𝑃1 ⟧(𝜎)) = 𝜚 Aber es gilt auch (𝑃1 ; 𝑃2 , 𝜎) →∗ (𝐸; 𝑃2 , ) → (𝑃2 , ) →∗ (𝐸, 𝜚) und damit 𝑀⟦𝑃1 ; 𝑃2 ⟧(𝜎) = 𝜚 was den Hilfssatz beweist. Verzweigung Nach Definition ist 𝑀⟦𝐼𝐹 𝑏𝑒𝑑 𝑇𝐻𝐸𝑁 𝑃1 𝐸𝐿𝑆𝐸 𝑃2 𝐹𝐼⟧(𝜎) = { 𝑀⟦𝑃1 ⟧(𝜎) 𝜎 ⊨ 𝑏𝑒𝑑 𝑀⟦𝑃2 ⟧(𝜎) 𝜎 ⊭ 𝑏𝑒𝑑 Durch leicht Fallunterscheidung folgt für jede Menge 𝑁 von Belegungen: 𝑀⟦𝐼𝐹 𝑏𝑒𝑑 𝑇𝐻𝐸𝑁 𝑃1 𝐸𝐿𝑆𝐸 𝑃2 𝐹𝐼⟧(𝑁) = 𝑀⟦𝑃1 ⟧(𝑁 ∩ 𝛿(𝑏𝑒𝑑)) ∪ 𝑀⟦𝑃2 ⟧(𝑁 ∩ 𝛿(¬𝑏𝑒𝑑)) 20 Wir kommen jetzt zum Beweis der Verzweigungsregel: Die Voraussetzungen {𝐹 ∧ 𝑏𝑒𝑑} 𝑃1 {𝐺} {𝐹 ∧ ¬𝑏𝑒𝑑} 𝑃2 {𝐺} bedeuten 𝑀⟦𝑃1 ⟧(𝛿(𝐹 ∧ 𝑏𝑒𝑑)) ⊆ 𝛿(𝐺) 𝑀⟦𝑃2 ⟧(𝛿(𝐹 ∧ ¬𝑏𝑒𝑑)) ⊆ 𝛿(𝐺) Damit ist 𝑀⟦𝐼𝐹 𝑏𝑒𝑑 𝑇𝐻𝐸𝑁 𝑃1 𝐸𝐿𝑆𝐸 𝑃2 𝐹𝐼⟧(𝛿(𝐹)) = 𝑀⟦𝑃1 ⟧(𝛿(𝐹) ∩ 𝛿(𝑏𝑒𝑑)) ∪ 𝑀⟦𝑃2 ⟧(𝛿(𝐹) ∩ 𝛿(¬𝑏𝑒𝑑)) = 𝑀⟦𝑃1 ⟧(𝛿(𝐹 ∧ 𝑏𝑒𝑑)) ∪ 𝑀⟦𝑃2 ⟧(𝛿(𝐹 ∧ ¬𝑏𝑒𝑑)) ⊆ 𝛿(𝐺) Schleife (Beweisidee) Hilfssatz: Sei Ω ein Programm das total undefiniert ist, z. B. 𝑊𝐻𝐼𝐿𝐸 𝑡𝑟𝑢𝑒 𝐷𝑂 𝑆𝐾𝐼𝑃 𝑂𝐷 ≡ Ω Dann gilt: Für alle 𝜎: 𝑀⟦Ω⟧(𝜎) = ∅ Sei jetzt eine Abkürzung vereinbart: 𝑊𝐻𝐼𝐿𝐸 𝑏𝑒𝑑 𝐷𝑂 𝑃 𝑂𝐷 ≡ W Dann sei 𝑊0 = Ω 𝑊𝑖+1 = 𝐼𝐹 𝑏𝑒𝑑 𝑇𝐻𝐸𝑁 𝑃; 𝑊𝑖 𝐸𝐿𝑆𝐸 𝑆𝐾𝐼𝑃 𝐹𝐼 𝑊𝑖 ist das Progamm, das uns genau alle Ergebnisse von 𝑊 bis zum iten Schleifendurchlauf liefert und sonst nichts. Dann gilt: ∞ 𝑀⟦𝑊⟧(𝜎) = ⋃ 𝑀⟦𝑊𝑖 ⟧(𝜎) 𝑖=0 Beweis: ∎ Gemäß der Voraussetzung für die Schleifenregel gelte jetzt: 𝑀⟦𝑃⟧(𝜎(𝐼𝑛𝑣 ∧ 𝑏𝑒𝑑) ) ⊆ 𝛿(𝐼𝑛𝑣 ) Für die Konklusion der Schleifenregel ist zu zeigen: ∞ 𝑀⟦𝑊⟧(𝜎) = ⋃ 𝑀⟦𝑊𝑖 ⟧(𝛿(𝐼𝑛𝑣)) ⊆ 𝛿(𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑) 𝑖=0 21 Zum Beweis der Teilmengenbeziehung reicht es, zu zeigen: ∀ 𝑖: 𝑀⟦𝑊𝑖 ⟧(𝛿(𝐼𝑛𝑣)) ⊆ 𝛿(𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑) Das geschieht durch Induktion: 𝑖 = 0: 𝑖 → 𝑖 + 1: 𝑀⟦𝑊0 ⟧(𝛿(𝐼𝑛𝑣)) = ∅ ⊆ 𝛿(𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑) 𝑀⟦𝑊𝑖+1 ⟧(𝛿(𝐼𝑛𝑣)) = 𝑀⟦𝐼𝐹 𝑏𝑒𝑑 𝑇𝐻𝐸𝑁 𝑃; 𝑊𝑖 𝐸𝐿𝑆𝐸 𝑆𝐾𝐼𝑃 𝐹𝐼⟧(𝛿(𝐼𝑛𝑣)) = 𝑀⟦𝑃; 𝑊𝑖 ⟧(𝛿(𝐼𝑛𝑣) ∩ 𝛿(𝑏𝑒𝑑)) ∪ 𝑀⟦𝑆𝐾𝐼𝑃⟧(𝛿(𝐼𝑛𝑣) ∩ 𝛿(¬𝑏𝑒𝑑)) Der zweite Teil der Vereinigung ist in 𝛿(𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑) enthalten, also das, was wir haben wollen. Für den ersten gilt: 𝑀⟦𝑃; 𝑊𝑖 ⟧(𝛿(𝐼𝑛𝑣) ∩ 𝛿(𝑏𝑒𝑑)) = 𝑀⟦𝑊𝑖 ⟧(𝑀⟦𝑃⟧(𝛿(𝐼𝑛𝑣 ∧ 𝑏𝑒𝑑))) ⊆ 𝑀⟦𝑊𝑖 ⟧(𝛿(𝐼𝑛𝑣)) ⊆ 𝛿(𝐼𝑛𝑣 ∧ ¬𝑏𝑒𝑑) Begründungen für die drei Schritte der letzten Ungleichung: - Hilfssatz zur Sequenzregel, Voraussetzung für die Schleifenregel und Monotonie der semantischen Abbildung Induktionsvoraussetzung 22 Zum Beweis der Vollständigkeit der Beweissysteme PD und TD (soweit diese gegeben ist) müssen wir den Begriff der „schwächsten Vorbedingung“ klären. Zunächst müssen wir den Begriff der „schwächsten Vorbedingung“ klären. Es handelt sich um die Vorbedingung, die mindestens gelten muß, damit ein Programm 𝑃 bei Start mit dieser Vorbedingung in einen Zustand gelangen kann, in dem eine gegebene Formel 𝐹 gilt.Geschrieben: 𝑤𝑙𝑝(𝑃, 𝐹) Es soll also gelten { 𝑤𝑙𝑝(𝑃, 𝐹)} P {F} und es darf kein 𝜎 geben mit 𝜎 𝛿(𝑤𝑙𝑝(𝑃, 𝐹)). Der folgende Satz ist schwer zu beweisen. Er wird hier nur zitiert. Satz: Für all P und F existiert eine Prädikatenlogische Formel G mit 𝐺 ⇔ 𝑤𝑙𝑝(𝑃, 𝐹) Satz: {𝐹} 𝑃 {𝐺} ist genau dann korrekt, wenn gilt: 𝐹 → 𝑤𝑙𝑝(𝑃, 𝐺) Beweis: Konsequenzregel Wir wollen jetzt noch kurz sehen, wie die Zwischenbehauptung der Sequenzregel konstruiert werden kann. Es soll also das Tripel {𝐹} 𝑃1 ; 𝑃2 {𝐻} nachgewiesen werden. Damit die Sequenzregel angewandt werden kann muß man eine Zwischenformel G finden mit {𝐹} 𝑃1 {𝐺} und {𝐺} 𝑃2 {𝐻}. Man wähle dazu G = wlp(P2,H). Nach dem Satz von eben gilt {𝐺} 𝑃2 {𝐻} dann von selbst. Es bleibt also, wlp(P2,H) zu bestimmen und {𝐹} 𝑃1 {wlp(P2, H)} nachzuweisen. Beispiel: Zu zeigen sei {𝑥 = 𝑦} 𝑥 = 𝑥 + 1; 𝑦 = 𝑦 + 1 {𝑥 = 𝑦} Es für Korrektheitsbeweise immer eine gute Taktik, die Ein- und die Ausgabe in irgendeiner Form in Variablen zu speichern, damit man sie auch während des Korrektheitsbeweises noch in der Ausgangsform zur Verfügung hat. Wegen des Durchlaufes von hinten nach vorne (s. Bemerkung zur Zuweisungsregel) gilt das insbesondere für die Variablen in der Nachbedingung. Hier wird eine neue Variable z eingeführt und die Nachbedingung verändert sich zu 𝑥 = z 𝑦 = z. Das ist wegen der Konsequenzregel erlaubt. Mit der Zuweisungsregel berechnet sich die Vorbedingung zu wlp(𝑦 = 𝑦 + 1, 𝑥 = z 𝑦 = z) = (𝑥 = z 𝑦 + 1 = z) Ferner gilt wlp(𝑥 = 𝑥 + 1, 𝑥 = z 𝑦 + 1 = z) = (𝑥 + 1 = z 𝑦 + 1 = z) Damit ergibt sich folgende Beweisskizze {𝑥 = 𝑦} {𝑥 + 1 = z 𝑦 + 1 = z} 𝑥 = 𝑥 + 1 {𝑥 = z 𝑦 + 1 = z} 𝑦 = 𝑦 + 1 {𝑥 = z 𝑦 = 𝑧} {𝑥 = 𝑦} 23 4 Compilerbautechniken Ziel dieses Kapitels ist es, eine Einführung in grundlegende Vorgehensweisen und Techniken des Compilerbaus zu geben. 4.1 Parsingtechniken Ein Parser ist ein Programm, das für die Zerlegung und Umwandlung einer Angabe anhand einer gegebenen Grammatik verantwortlich ist. Für unsere Zwecke gilt, daß eine kontextfreie Grammatik vorhanden sein muß. Prog → WHILE bed DO Prog OD → … Prog → IF… … 1. Beobachtung Beim Aufbau eines Wahlmöglichkeiten. Ableitungsbaums hat man an manchen Stellen Wie geht man damit um? 1) „zufällige“ Wahl, Backtracking, 2) Ziel: Kosten minimieren (entstehen besonders beim Backtracking) 3) Techniken entwickeln, die die Wahl der Produktion regeln → Backtracking wird vermieden Beispiele für Parsingverfahren, die besser sind als simples Backtracking 1. mit Backtracking 2. ohne Backtracking zu 1.: Chart-Parsing Um Mehrfachanalysen eines Satzteiles zu vermeiden, hat man das ChartParsing entwickelt. Das Chart-Parsingverfahren zeichnet sich dadurch aus, daß es sich bereits analysierte Teile eines Satzes merkt. Definition: 1. Ein Chart ist eine endliche Folge von Items. 2. Ein Item ist eine Struktur 𝒏𝟏 𝒏𝟐 𝒘𝟏 → 𝒘𝟐 ∙ 𝒘𝟑 . Dabei sind 𝒏𝟏 𝒏𝟐 ∈ 𝑵𝟎 , 𝒏𝟏 ≤ 𝒏𝟐 , 𝒘𝟏 → 𝒘𝟐 𝒘𝟑 ∈ 𝑷. Die intuitive Bedeutung soll an einem Beispiel erläutert werden: Bei einer Analyse von „0 der 1 mann 2 sieht 3 die 4 frau 5“ entsteht an Beispiel einer Stelle des Algorithmus das Item 𝑛1 𝑛2 𝑤1 → 𝑤2 ∙ 𝑤3 mit 𝑛1 = 0, 𝑛2 = 2. Der Rest des Items habe die Form 𝑆 → 𝑁𝑃 ∙ 𝑉𝑃. Die Zahlen zeigen an, welche Teile 24 schon analysiert und bestimmt sind. Der Punkt trennt den schon analysierten und bestimmten Teil von der Vorhersagekomponente, dem noch spekulativen Rest. Hier würde das bedeuten, dass die von 0 bis 2 reichende Zeichenkette „ 0 der 1 mann “ bereits bestimmt ist. Und da sich links des Punktes das 2 Nichtterminale NP befindet, wissen wir auch, als was der Teil von 0 bis 2 analysiert worden ist: als Nominalphrase (NP). Zusätzlich wissen wir: Können wir den Rest als VP bestimmen (soweit die Spekulation), dann ist das ganze Konstrukt ein S, ein Satz. Der am weitesten verbreitete Chart-Parsing- Algorithmus ist der 1970 von Jay Earley vorgestellte Earley-Algorithmus. Er zeichnet sich besonders dadurch aus, dass er kein Backtracking nutzt. Es werden zeitgleich alle Alternativen verfolgt. Am Ende des Parsingvorgangs sind alle alternativen Syntaxanalysen in der Chart. Eingabe: Eine kontextfreie Grammatik G, ein Wort W der Länge n Ausgabe: ja, wenn W ℒ(G) nein sonst Es ist auch leicht, den Algorithmus so zu modifizieren, dass für den Fall W ℒ(G) ein Ableitungsbaum für W ausgegeben wird (oder sogar alle). Initialisierung: Zu Anfang stehen Items eines einzigen Typs in der Chart. Dies soll jetzt für die Zeichenkette „0 der 1 mann 2 sieht 3 die 4 frau 5“ erläutert werden: Diese soll als ein Satz (S) analysiert werden. Für das Nichtterminale S gibt es hier nur eine Regel S NP VP. Zu Anfang ist alles von der 0-ten bis zur 0-ten Stelle analysiert, also n1 = n2 = 0. Der Punkt steht anfangs ganz links, da noch nichts sicher analysiert und der weitere Verlauf noch Spekulation ist. Am Anfang gilt dort also S ∙ NP VP, und damit wird das Item 0 0 S ∙ NP VP in die Chart eingefügt. 0 Der 1 Mann NP 2 sieht 3 die 4 Frau 5 VP Im allgemeinen Fall müssen zum Start alle Items des Typs 0 0 V ∙ w in die Chart eingefügt werden. Dabei seien V das Startsymbol der Grammatik und V w alle Produktionen mit V als rechter Seite. Schritte: Im Wesentlichen besteht der Earley-Algorithmus aus drei Schritten, die immer in einer geeigneten Reihenfolge wiederholt werden: - Expand oder Predict, 25 - Scan, - Complete. Abbruchbedingung: Der Algorithmus terminiert, wenn ein Item 0 𝑛 𝑉 → 𝑤 ∙ aufgefunden wird. Alternativ kann auch nach allen Items des Typs 0 𝑛 𝑉 → 𝑤 ∙ gesucht werden. Dabei seien n die Länge des zu untersuchenden Wortes, V das Startsymbol der Grammatik und V w alle Produktionen mit V als rechter Seite. Diese Schritte des Algorithmus werden anhand eines Beispiels näher erklärt. Wir versuchen wieder, die Zeichenkette „der mann sieht die frau“ abzuleiten und zwar mittels einer Grammatik mit dem Startsymbol S und den Produktionsregeln 𝑆 → 𝑁𝑃 𝑉𝑃, 𝑁𝑃 → 𝐷𝑒𝑡 𝑁 , 𝑉𝑃 → 𝑉 | 𝑉 𝑁𝑃 , 𝑉 → 𝑖𝑠𝑠𝑡|𝑠𝑖𝑒ℎ𝑡 , 𝑁 → 𝑚𝑎𝑛𝑛|𝑓𝑟𝑎𝑢 , 𝐷𝑒𝑡 → 𝑑𝑒𝑟|𝑑𝑖𝑒. Das Ergebnis des Algorithmus ist die folgende Chart. An der Existenz des Items 30 ist zu sehen, dass die Ableitung erfolgreich war. Danach wird erläutert 1. welches die drei Schritte des Algorithmus genau sind, und 2. wie die Reihenfolge ihrer Anwendung gesteuert wird. ItemNr. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Bereich 0 0 0 0 0 0 1 1 1 0 0 2 2 2 2 0 0 0 0 1 1 1 1 2 2 2 2 2 2 2 Item Konstruiert durch S ∙ NP VP NP ∙ Det N Det ∙ der Det ∙ die Det der ∙ NP Det ∙ N N ∙ mann N ∙ frau N mann ∙ NP Det N ∙ S NP ∙ VP VP ∙ V VP ∙ V NP V ∙ isst V ∙ sieht Initial Expand 1 Expand 2 Expand 2 Scan 3 Complete 5 + 2 Expand 6 Expand 6 Scan 7 Complete 9+6 Complete 10+1 Expand 11 Expand 11 Expand 12+13 Expand 12+13 Verbleibender Text der mann sieht die frau der mann sieht die frau der mann sieht die frau der mann sieht die frau mann sieht die frau mann sieht die frau mann sieht die frau mann sieht die frau sieht die frau sieht die frau sieht die frau sieht die frau sieht die frau sieht die frau sieht die frau 26 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 2 2 2 0 3 3 3 3 3 4 4 4 3 2 0 V sieht ∙ VP V ∙ VP V ∙ NP S NP VP ∙ NP ∙ Det N Det ∙ der Det ∙ die Det die ∙ NP Det ∙ N N ∙ mann N ∙ frau N frau ∙ NP Det N ∙ VP V NP ∙ S NP VP ∙ 3 3 3 3 3 3 3 4 4 4 4 5 5 5 5 Scan 15 Complete 16+12 Complete 16+13 Complete 17+11 Expand 18 Expand 20 Expand 20 Scan 22 Complete 23+20 Expand 24 Expand 24 Scan 26 Complete 27+24 Complete 28+18 Complete 29+19 die frau die frau die frau die frau die frau die frau die frau frau frau frau frau ε ε ε ε Tabelle: Earley-Algorithmus Die Schritte des Verfahrens: Expand (Predict) Wir brauchen in der Chart ein Item der Form n1 n2 𝛼 → 𝛽 ∙ 𝑋 𝛾 mit 𝛼 ∈ 𝑁, 𝛽 ∈ (𝑁 ∪ 𝑇)∗ , 𝑥 ∈ ℕ, 𝛾 ∈ (𝑁 ∪ 𝑇)∗ und in P muss es eine Produktionsregel X δ geben. Dann können wir zum Chart ein Item der Form n2 n2 X ∙δ hinzufügen. expand vorhanden i j N → Seq ● N’ Seq’ neu konstruiert j j N’→ ● Seq’’ für alle möglichen Regeln Bsp: vorhanden 0 1 NP → Det ● N neu konstruiert 1 1 N → ● Mann 1 1 N → ● Frau 27 Wir brauchen in der Chart ein Item der Form n1 n2 𝛼 → 𝛽 ∙ 𝑡 𝛾 mit ni, α, β, γ wie oben und tT; und im zu analysierenden Satz, muss an Position n2+1 ein t stehen. Dann kann dem Chart ein Item der Form n1 n2+1 𝛼 → 𝛽 𝑡 ∙ 𝛾 hinzugefügt werden. Scan (Shift) scan vorhanden i j N → Seq ● t Seq’ Wenn im Eingabewort an Position j, j+1 das t steht, wird neu konstruiert: i j+1 N→ Seq t ● Seq’ Bsp.: vorhanden im Chart 0 0 Det → ● der im Wort an erster Stelle neu konstruiert der 0 1 Det → Complete (Reduce) der ● Wir brauchen ein Item der Form n1 n2 𝛼 → 𝛽 ∙ und ein Item der Form n3 n1 γ δ ∙ α ε mit γ N, β T, α N, δ, ε (N ∪ T)*. Dann kann ein Item der Form n3 n2 γ δ α ∙ε hinzugefügt werden. complete vorhanden i j N → Seq ● suche Partner der Form k i N’→ Seq’ ● N Seq’’ neu konstruiert k j N’→ Bsp: vorhanden 0 1 Det → Det ● Partner 0 0 NP → ● Det N Seq’ N ● Seq’’ 28 Ergebnis 0 1 NP → Det ● N Der Ablauf des Verfahrens wird wie folgt gesteuert: Es gibt im Verlauf immer ein aktuelles Item, das zur Produktion neuer Items benutzt wird, die dann hinten an die Chart gehängt werden. Das Verfahren endet, wenn alle Items schon zur Konstruktion neuer Items benutzt worden sind und kein neues aktuelles Item gefunden werden kann. Anfangs ist das aktuelle Item das Startitem. Für die Konstruktion neuer Items gibt es drei Möglichkeiten, abhängig von der Form des aktuellen Items. 1. ● vor Nichtterminalen: expand 2. ● vor Terminalen: scan 3. ● am Ende: complete Das heißt präziser: 1. Falls beim aktuellen Item hinter dem Punkt ein Nichtterminales n steht, also ein Item der Form n 1 n2 𝛼 → 𝛽 ∙ 𝑛 𝛾 vorliegt, werden Expand-Schritte durchgeführt: Für jede Produktionsregel der Form n δ wird ein Item der entsprechenden Form angehängt. 2. Falls beim aktuellen Item hinter dem Punkt ein Terminales t steht, also ein Item der Form n1 n2 𝛼 → 𝛽 ∙ 𝑡 𝛾 vorliegt, wird ein Scan-Schritt versucht. Findet sich an der Stelle n 2+1 im zu analysierenden Wort ebenfalls ein t, wird ein Item der entsprechenden Form angehängt. 3. Falls beim aktuellen Item hinter dem Punkt nichts mehr steht, also ein Item der Form n1 n2 𝛼 → 𝛽 ∙ vorliegt, wird ein Complete-Schritt versucht. Es werden in der Chart Items der Form n3 n1 w1 δ ∙ α w2 gesucht und im Erfolgsfall die entsprechenden Items angehängt. Übungsaufgabe: Wie muß der Algorithmus modifiziert werden, damit nicht nur eine ja/nein-Entscheidung als Ergebnis herauskommt sondern ein Ableitungsbaum? 4.2 Vermeiden von Backtracking Die Frage, wie der zweite der oben genannten Ansätze zu realisieren ist, nämlich die Frage, wie man Backtracking von vorneherein vermeidet, wird vordergründig beantwortet durch Definition: 29 Eine Grammatik heißt 𝑳𝑹(𝒏)- Grammatik, wenn bei der Analyse durch die Betrachtung der nächsten 𝑛 Token (Lookahead) entschieden werden kann, welche Produktionsregel zu wählen ist. Definition: Eine Sprache heißt 𝑳𝑹(𝒏)-Sprache, wenn es für sie eine 𝐿𝑅(𝑛)Grammatik gibt. Satz: Zu jeder 𝐿𝑅(𝑛)- Sprache L, existiert eine 𝐿𝑅(1)-Grammatik G mit L(G)=L. Die Klasse der 𝐿𝑅(1)-Sprachen ist genau die Klasse 𝐿(𝐷𝑃𝐷𝐴) Dabei steht DPDA für die deterministischen Kellerautomaten („deterministic pushdown automata“). Diese können genau die deterministisch kontextfreien Sprachen erkennen. Definition: Eine kontextfreie Grammatik heißt 𝑳𝑳(𝒏)- Grammatik für eine natürliche Zahl n, wenn jeder Ableitungsschritt eindeutig durch die nächsten n Symbole der Eingabe (Lookahead) bestimmt ist. Das bedeutet, die Frage, welches Nichtterminalsymbol mit welcher Regel als nächstes expandiert werden soll, kann eindeutig mit Hilfe der nächsten k Symbole der Eingabe bestimmt werden. Achtung: Im Gegensatz zu den LR-Sprachen geht es hier nicht um Analysesondern um Ableitungsschritte. Der Unterschied besteht sozusagen in der Richtung, in der die Ableitung gelesen wird. Generell gilt, je größer k gewählt wird, umso mächtiger wird die Sprachklasse. Das zeigt die folgende Inklusionskette, wo alle Inklusionen echt sind. Dabei steht DPDA für die deterministischen Kellerautomaten. Diese können genau die deterministisch kontextfreien Sprachen erkennen. Insgesamt bilden die LL(i) im Gegensatz zu den LR(i) eine echte Hierarchie. gibt es kontextfreie Sprachen, die für kein k von einer LL(k)-Grammatik erzeugt werden. 4.3 Attributierte Grammatiken Eine Attributgrammatik ist eine kontextfreie Grammatik, die um Attribute sowie Regeln und Bedingungen für diese Attribute erweitert ist. Angewandt wird das Konzept im Compilerbau, um die Einhaltung von Regeln zu überprüfen, die mit kontextfreien Grammatiken nicht formuliert werden können. Solche Regeln sind z. B. die, daß jede Variable deklariert sein muß und ihrem Datentyp entsprechend verwendet wird. 30 Ein Compiler überprüft die Einhaltung dieser Regeln während der semantischen Analyse. Dabei hat er nur die Informationen zur Verfügung, die im Syntaxbaum des Programms enthalten sind. Zusätzliche Informationen, die die semantische Analyse erleichtern, kann man als Attribute in den Syntaxbaum integrieren. Zum Beispiel kann der Typ eines Ausdrucks als Attribut an den entsprechenden Knoten im Syntaxbaum annotiert werden. Durch Attributregeln und -bedingungen können zusätzlich Abhängigkeiten von anderen Attributen (auch anderer Knoten im Syntaxbaum) angegeben werden. Die Arten der Abhängigkeit bestimmen den Durchlauf durch den Baum, sobald die Attribute ausgewertet werden sollen. Die Programmierung der betreffenden Teile des Compilers vereinfacht sich, wenn die Produktionen der Grammatik selbst mit entsprechenden Attributen versehen werden. Typische Attribute: Werte Typ Baum Hoare-Tripel Ausgabebefehle F-Strukturen in der Computerlinguistik Zum Beispiel: Syntaktisch Exp → Semantisch Exp1 op Exp2 Exp.Wert → Exp1.Wert op Exp2.Wert Exp 28 Exp 14 Exp 9 op Exp 5 Zahl 9 + Zahl 5 9 9 Attribut op Exp 2 * Zahl 2 2 2 5 5 Schon an diesem Punkt ist zu sehen, daß es im allgemeinen schwierig ist, die Reihen folge zu bestimmen, in der die Attribute ausgewertet werden, oft 31 sogar unmöglich (-> Zirkuläre Abhängigkeiten). Es werden je nach Form der Abhängigkeiten im Compilerbau zwei Klassen von Attributen betrachtet: - synthetische oder abgeleitete Attribute, „S-Attribute“ ererbte Attribute, „I-Attribute“, von „inherited“ Ein S-Attribut wird streng induktiv („bottom up“) ausgewertet. Der Attributwert zu einem Knoten bestimmt sich aus den Attributwerten zu seinen Kindern. Der Wert von arithmetischen Ausdrücken ist ein S-Attribut, wie das Beispiel eben zeigt. S-Attributgrammatiken sind Attributgrammatiken, die nur auf synthetischen Attributen arbeiten. So können sie direkt bei den Reduce-Schritten des ParseVorgang eines LR(k)-Parsers berechnet werden. Ein I-Attributwert zu einem Knoten K darf neben den Attributwerten zu den Kindern von K auch noch von den Attributwerten zu den linken Geschwistern von K abhängen. I-Attributgrammatiken können in einem Top-down-Durchgang von links nach rechts durch den abstrakten Syntaxbaum ausgewertet werden. Sie können für jede LL(k)-Grammatik ausgewertet werden. Wir werden zur Programmanalyse wie folgt vorgehen: - Um ein Hoare-Tripel {F} P {G} als korrekt zu beweisen, benutzen wir den Satz, daß es reicht, 𝐹 → 𝑤𝑙𝑝(𝑃, 𝐺) zu beweisen. Die Nachbedingung ist dann das einzige Attribut; 𝑤𝑙𝑝(𝑃, 𝐺) kann aus dem Knoten zu P und seinem Attribut {G} berechnet werden. Die Nachbedingung ist ein I-Attribut, jedoch mit einer Änderung: Alle Attribute müssen von rechts nach links ausgewertet werden. (Folge der Zuweisungsregel !). Das erfordert aber nur minimale Änderungen der Standardalgorithmen. 32 4.4 Rekursiver Abstieg Ziel: Konstruiere Ableitungsbaum rekursiv ohne Backtracking. Dabei gibt es Prozeduren der Form baum(NT), die einander wechselseitig aufrufen. Das Argument NT ist ein Nichtterminales, das angibt, als was zumindest der Anfangsteil der restlichen Zeichenkette analysiert werden soll. Start mit dem Prozeduraufruf baum(V). Dabei sei V das Startsymbol der Grammatik. Ablauf bei einem baum(NT): Wähle eine Produktionsregel für NT gemäß einer vor den rekursiven Aufrufen implementierten Funktion A (Auswahl). Form der gewählten Produktionsregel: NT → x1 … xn Bearbeite nacheinander x1,…,xn. Ist xi Nichtterminal, rufe baum(xi). Ist xi Terminal, bearbeite xi direkt, füge also das Terminal als neuen Knoten ein. Die Auswahlfunktion A hängt von zwei Argumenten ab: - dem Nichtterminalen NT, dem ersten noch unbearbeiteten token der Eingabezeichenkette. Die Frage ist, wann es eine solche Auswahlfunktion gibt, denn sie ist es, die den Nichtdeterminismus und damit das Backtracking verhindert. Antwort: Man braucht eine 𝐿𝑅(1)- Grammatik. Beispiel: zu konstruieren: baum(Schleife) per Rekursion existieren baum(bed) und baum(prog). Der dann entstehende Baum hat die Form 33 Schleife While baum(bed) DO baum(prog) OD 34 5 Praktikum Implementieren Sie ein Programm zur Prüfung der Korrektheit eingegebenen Programms anhand der Hoarsche Logik. eines Eingabe: Grammatik G, Hoaresches Tripel {F} P {H} Ausgabe: Attributierter Baum Verwenden Sie dafür eine Programmiersprache Ihrer Wahl. Es existiert eine Referenzimplementierung in Mathematica. Verwenden Sie die nachfolgende Grammatik: prog → zuweisung → alternative → schleife → exp → erest → term → trest → faktor → bed → disj → drest → elembed → SKIP | zuweisung | alternative | schleife | SKIP:prog | zuweisung:prog | alternative:prog | schleife:prog var = exp IF bed THEN prog ELSE prog FI | IF bed THEN prog WHILE bed DO prog OD term erest + term erest | - term erest | e faktor trest * factor trest | / factor trest | e zahl | var | (exp) disj brest elembed drest && elembd drest | e exp vlg exp Benutzen Sie zum Testen mindestens folgende 2 Programme: fak = WHILE DO fak = n = n OD 1; n > 1 fak * n; – 1; WHILE x != y DO IF x < y THEN z = x; x = y; y = z; FI; x = x – y; OD 35 Hinweise: ! Schreiben Sie einen „Tokeniser“ der das Eingabeprogramm in eine Zeichenfolge zerlegt. Schreiben Sie eine manuelle Regelzuweisung. Konstruiere I-Attributgrammatik G für WHILE-Programme mit der Nachbedingung als Attribut Entwickeln Sie einen Algorithmus, der aus dem Eingabeprogramm einen Baum macht. (Rekursiver Abstieg) Implementieren Sie einen Algorithmus, zur Ermittlung der schwächsten Vorbedingung (wlp). Wenn Sie den Baum in grafischer Form in Mathematica ausgeben wollen, brauchen Sie eine numerierte Pfeilliste. Bestimmung der Korrektheit von {F} P {H} ACHTUNG Es handelt sich um Ihre Lösung. Bei den Hinweisen handelt es sich lediglich um Vorschläge. Experimentieren Sie ruhig etwas und Hinterfragen Sie Lösungsansätze jederzeit.