Christian Wagenknecht | Michael Hielscher Formale Sprachen, abstrakte Automaten und Compiler
Christian Wagenknecht | ...
285 downloads
1359 Views
3MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Christian Wagenknecht | Michael Hielscher Formale Sprachen, abstrakte Automaten und Compiler
Christian Wagenknecht | Michael Hielscher
Formale Sprachen, abstrakte Automaten und Compiler Lehr- und Arbeitsbuch für Grundstudium und Fortbildung STUDIUM
Bibliografische Information der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über abrufbar.
Prof. Dr. rer. nat. Christian Wagenknecht Geboren 1959 in Löbau/Sachsen. Studium der Mathematik und Physik (Lehramt) an der PH Dresden (heute in TU Dresden), Diplom in Informatik 1981. Lehrtätigkeit an Mittelschule und Gymnasium. Ab 1983 Aspirantur an der PH Dresden, Promotion in Informatik 1987. Wissenschaftlicher Mitarbeiter an der PH Dresden bis 1991 und anschließend an der PH Ludwigsburg. Seit 1993 Professor für Informatik an der Hochschule Zittau/Görlitz. Arbeitsgebiete: Theoretische Informatik, Programmierparadigmen, Entwicklung von Web-Applikationen und XML-basierter CMS; einschl. fach- und mediendidaktischer Aspekte, E-Learning Michael Hielscher Geboren 1982 in Zittau/Sachsen. Studium der Informatik an der Hochschule Zittau/Görlitz, Diplom 2006, Master of Science 2008. Lehrtätigkeit an der Hochschule Zittau/Görlitz in theoretischer Informatik und Netzwerkprogrammierung. Mitarbeit in diversen Projekten und Workshops zur Lehrerfortbildung. Seit Oktober 2008 wissenschaftlicher Mitarbeiter an der PH Bern/Schweiz. Arbeitsgebiete: Entwicklung von Desktop-/Web-Applikationen und Computerspielen, E-Learning und Web 2.0
1. Auflage 2009 Alle Rechte vorbehalten © Vieweg +Teubner | GWV Fachverlage GmbH, Wiesbaden 2009 Lektorat: Ulrich Sandten | Kerstin Hoffmann Vieweg+Teubner ist Teil der Fachverlagsgruppe Springer Science+Business Media. www.viewegteubner.de Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Jede Verwertung außerhalb der engen Grenzen des Urheberrechtsgesetzes ist ohne Zustimmung des Verlags unzulässig und strafbar. Das gilt insbesondere für Vervielfältigungen, Übersetzungen, Mikroverfilmungen und die Einspeicherung und Verarbeitung in elektronischen Systemen. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Umschlaggestaltung: KünkelLopka Medienentwicklung, Heidelberg Druck und buchbinderische Verarbeitung: STRAUSS GMBH, Mörlenbach Gedruckt auf säurefreiem und chlorfrei gebleichtem Papier. Printed in Germany ISBN 978-3-8348-0624-6
Lernen und (erst recht) Studieren erfordern Aktivität. Die beste Vorlesung bleibt wirkungslos, wenn sie als „Kinoeffekt“ verpufft. Schnell sieht man ein, dass praktische Prozesse, die man beherrschen muss, eingeübt werden müssen. Abstrakte Denktechniken scheinen da pflegeleichter zu sein. Das Gegenteil ist der Fall! Die wirkliche Verinnerlichung abstrakter Inhalte erfordert höchste geistige Aktivität. Deshalb haben wir dieses Arbeitsbuch zur theoretischen Informatik geschrieben. Benutzen Sie es! Verinnerlichen Sie die anfangs unappetitlichen Happen. Sie werden zunehmend spüren, dass abstrakte Denktechniken Freude bereiten können. Dies gilt auch für verwandte Denkaktivitäten außerhalb der theoretischen Informatik. Die Reise lohnt sich also. Aber: SIE sind der Kapitän. Wir reichen Ihnen lediglich Karte und Kompass. . . . Der Text basiert auf Vorlesungen zur theoretischen Informatik, die der erste Autor seit 1993 im Fachbereich Informatik an der Hochschule Zittau/Görlitz wiederholt gehalten hat. Sie wurden ständig modifiziert, verbessert und durch Begleitmaterial im Web ergänzt. Es wird lediglich Schulwissen aus der Mathematik vorausgesetzt. Kernstück des Arbeitsbuches ist eine Lernumgebung, die wir seit 2004 entwickelt, erprobt, verbessert und erweitert haben. Sie heißt AtoCC (from automaton to compiler construction), , und bietet die Möglichkeit, Grundlagen der Theorie formaler Sprachen und Automaten mit Bezug auf sehr praxisbezogene Anwendungen im automatisierten Übersetzerbau (compiler compiler/construction) am Computer einzuüben. Andere Teilgebiete der theoretischen Informatik, wie Berechenbarkeitstheorie und Komplexitätstheorie, werden hier nicht betrachtet. Der Dank der Autoren gilt allen Kollegen und Studierenden, die in irgendeiner Form – durch individuelle Beiträge, gezielte Erprobungen und vielfältige Hinweise – direkt oder indirekt an der Entstehung dieses Materials beteiligt waren. Die kritischen Bemerkungen haben maßgeblich dazu beigetragen, dass der Text in den fast 15 Jahren seiner Entstehung zahlreiche didaktische Erfahrungen aufnehmen konnte. Die trotz großer Sorgfalt nicht auszuschließenden Fehler bzw. Ungeschicklichkeiten sind allein den Autoren anzulasten. Bitte schreiben Sie uns eine E-Mail mit Ihren Anmerkungen. Dafür sind wir sehr dankbar!
Vervielfältigungen beliebiger Teile dieses Textes oder deren Weitergabe sind nicht gestattet. © 2009 by Christian Wagenknecht and Michael Hielscher
Den Professoren Immo O. Kerner und Herbert Löthe in großer Dankbarkeit gewidmet
Inhaltsverzeichnis 1
Einleitung 1.1 Theoretische Informatik und ihre Anwendungen . . . . . . . . . . 1.2 AtoCC - unsere Lernumgebung . . . . . . . . . . . . . . . . . . .
2
Struktur von Programmen 2.1 Sprache, Syntax, Semantik und Pragmatik 2.2 Konkrete Syntax . . . . . . . . . . . . . 2.3 Abstrakte Syntax . . . . . . . . . . . . . 2.4 Syntaxanalyse . . . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
5 5 7 12 14
Grundbegriffe 3.1 Alphabet und Zeichen . . . . . . 3.2 Wort, Wortlänge und Verkettung 3.3 Wortmenge . . . . . . . . . . . 3.4 Sprache . . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
17 17 19 21 25
Definition unendlicher Mengen 4.1 Muster und formale Grammatiken . . . . . . . . 4.2 Ableitung und definierte Sprache . . . . . . . . . 4.3 Nichtdeterminismus des Ableitungsprozesses . . 4.4 Mehrdeutigkeit kontextfreier Grammatiken . . . 4.5 C HOMSKY-Hierarchie . . . . . . . . . . . . . . 4.6 ε-Sonderregelungen . . . . . . . . . . . . . . . . 4.7 Das Wortproblem . . . . . . . . . . . . . . . . . 4.8 Recognizer und Parser für kontextfreie Sprachen
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
27 27 30 32 35 36 38 41 44
Sprachübersetzer 5.1 Compiler und Interpreter . . . . . . . . . 5.2 Die Sprache eines Zeichenroboters . . . . 5.3 Modellierung von Übersetzungsprozessen 5.4 Scanner und Parser . . . . . . . . . . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
47 47 48 49 56
Endliche Automaten und reguläre Sprachen 6.1 Aufbau abstrakter Akzeptoren . . . . . . . . . . 6.2 Deterministischer Endlicher Automat (DEA, EA) 6.3 Endlicher Automat und reguläre Grammatik . . . 6.4 Nichtdeterministischer endlicher Automat (NEA)
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
61 61 63 67 70
3
4
5
6
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
. . . .
1 1 4
XI
Inhaltsverzeichnis
6.5 6.6 6.7 6.8 6.9
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
76 80 89 96 99
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
. . . . . . .
105 105 107 107 108 114 118 120
Kellerautomaten und kontextfreie Sprachen 8.1 Grenzen endlicher Automaten . . . . . . . . . . . 8.2 Nichtdeterministischer Kellerautomat (NKA) . . . 8.3 Äquivalenz von NKA und kontextfreier Grammatik 8.4 Parsing kontextfreier Sprachen . . . . . . . . . . . 8.5 Deterministischer Kellerautomat (DKA) . . . . . . 8.6 Deterministisch kontextfreie Sprachen . . . . . . . 8.7 Parsergeneratoren für dkfS . . . . . . . . . . . . . 8.8 Optimierung kontextfreier Grammatiken . . . . . . 8.9 C HOMSKY-Normalform . . . . . . . . . . . . . . 8.10 Das Pumping Lemma für kontextfreie Sprachen . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
127 127 128 135 142 146 148 151 153 156 157
LL(k)-Sprachen 9.1 Deterministische Top-down-Syntaxanalyse . 9.2 Begriff . . . . . . . . . . . . . . . . . . . . 9.3 LL(1)-Forderungen . . . . . . . . . . . . . 9.4 Top-down-Parser für LL(1)-Grammatiken . 9.5 Methode des Rekursiven Abstiegs . . . . . 9.6 Grammatiktransformationen . . . . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
161 161 162 164 169 171 180
10 LR(k)-Sprachen 10.1 Begriff . . . . . . . . . . . . . . . . . . . . 10.2 Deterministische Bottom-up-Syntaxanalyse 10.3 Tabellengesteuerte LR(k)-Syntaxanalyse . . 10.4 Automatisierte Parsergenerierung . . . . . . 10.5 Compiler . . . . . . . . . . . . . . . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
187 187 187 191 195 198
7
8
9
Konstruktion eines äquiv. DEA aus einem NEA Minimalautomaten . . . . . . . . . . . . . . . NEA mit ε-Übergängen . . . . . . . . . . . . . Das Pumping Lemma für reguläre Sprachen . . Endliche Maschinen . . . . . . . . . . . . . . .
Reguläre Ausdrücke 7.1 Definition . . . . . . . . . . . . . . . . . . . 7.2 Klammersparregeln . . . . . . . . . . . . . . 7.3 Äquivalente reguläre Ausdrücke . . . . . . . 7.4 Reguläre Ausdrücke und endliche Automaten 7.5 Reguläre Ausdrücke in der Praxis . . . . . . 7.6 Anwendungsgebiete für reguläre Ausdrücke . 7.7 Reguläre Ausdrücke in Scannergeneratoren .
. . . . . . .
11 Sprachübersetzerprojekt 207 11.1 Motivation und Anwendungskontext . . . . . . . . . . . . . . . . 207
Inhaltsverzeichnis
IXI
11.2 Die Notensprache ML . . . . . . . . . . . . . . . . . . . . . . . . 208 11.3 Entwicklung eines ML-Interpreter . . . . . . . . . . . . . . . . . 210 11.4 Entwicklung eines ML → SV G-Compilers . . . . . . . . . . . . . 214 12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen 12.1 Grenzen von Kellerautomaten . . . . . . . . . . . . . . 12.2 Die T URING-Maschine (TM) . . . . . . . . . . . . . . . 12.3 Die Arbeitsweise einer DTM . . . . . . . . . . . . . . . 12.4 Alternative TM-Definitionen . . . . . . . . . . . . . . . 12.5 Die DTM als Akzeptor . . . . . . . . . . . . . . . . . . 12.6 DTM, NTM, LBTM und Sprachklassen . . . . . . . . . 12.7 TM in Komplexitäts- und Berechenbarkeitstheorie . . . . 12.8 TM zur Berechnung von Funktionen . . . . . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
. . . . . . . .
221 221 221 223 225 226 228 231 232
Literaturverzeichnis
237
Sachverzeichnis
241
1 Einleitung 1.1 Theoretische Informatik und ihre Anwendungen Zur Weihnachtszeit sind Flughäfen oft überfüllt und restlos ausgelastet. Jeder möchte rasch nach Hause in den Kreis der Familie. Weihnachten 2004 ging dieser Wunsch für ca. 30000 Reisende nicht in Erfüllung: Eine US-amerikanische Fluggesellschaft musste über 1100 Flüge streichen. Grund war ein Softwarefehler. Ein enormer Image-Schaden für diese Firma war die Folge. Der Zwischenfall führte dazu, dass in den Medien über die Nachteile von Automatisierung und Sicherheit im Informationszeitalter debattiert wurde. Wie war es aber zu dieser Katastrophe gekommen? Ein Mitarbeiter trug in ein Formular (vermutlich ein typisches Web-Formular) einen Wert ein, der dort nicht hingehörte. Dies führte zu einer Kettenreaktion, die sich sogar noch auf eine weitere bekannte Fluglinie auswirkte, da die Server der beiden Fluggesellschaften von ein und der selben Firma betrieben wurden. Es gab kein Sicherheitssystem für einen solchen Fall. Mit sehr geringem Aufwand hätten die Entwickler des Systems einen solchen Fehler ausschließen können, nämlich durch eine Eingabevalidierung. Hierfür sind Kenntnisse über formale Sprachen und abstrakte Automaten – ein Teilgebiet der theoretischen Informatik – notwendig. Zulässige Eingaben werden dabei (durch formale Grammatiken oder abstrakte Automaten bestimmten Typs) exakt beschrieben, unzulässige werden ausgefiltert bzw. abgewiesen. Auch leistungsfähige Text-Editoren erfordern Theorie-Wissen, das sich nicht selten über Begrifflichkeiten darstellt. Sie arbeiten nämlich mit regulären Ausdrücken und damit nicht nur Zeichen-, sondern Struktur-orientiert. Der Begriff „regulärer Ausdruck“ wird hier mit der Erwartung benutzt, dass der Nutzer das zugrunde liegende theoretische Konzept kennt. Theoriewissen ist also eine wichtige Voraussetzung für erfolgreiche praktische Tätigkeit auf hohem Niveau - ganz im Sinne des von dem deutsch-amerikanischen (Sozial-)Psychologen Kurt Lewin1 (1890-1947) stammenden Ausspruchs "There is nothing as practical as a good theory". Theoretische Informatik ist heute mehr denn je eine Voraussetzung, um die Herausforderungen moderner ITAnwendungen bewältigen zu können. Die (immer noch) junge Wissenschaft „Informatik“ entwickelt sich mit rasan1 Oft
wird dieses Zitat auch Albert Einstein zugeschrieben.
Eingabevalidierung formale Sprachen und abstrakte Automaten reguläre Ausdrücke
2
1 Einleitung
ter Geschwindigkeit. Äußerlich betrifft dies vor allem den technologischen Fortschritt. Für jedermann sichtbar und oftmals spürbar haben sich viele Lebensbereiche durch den wachsenden IT-Einsatz fundamental verändert. Daten stehen unabhängig von Ort und Zeit zu akzeptablen Kosten zur Verfügung. Kommunikationsprozesse via Chat, Telefon, E-Mail, Web, Blog, Wiki usw. verlaufen synchron bzw. asynchron, oft „auf mehreren Kanälen“ gleichzeitig. Tiefgreifende Anwendungen der Informatik in Ingenieurdisziplinen, wie etwa dem Fahrzeugbau, führen zur Veränderung traditioneller Berufsfelder. zunehmende Abstraktion
Fragt man nach dem Kern dieser Innovationen, der allen Ausprägungen gemein ist, stößt man zwangsläufig auf die zunehmende Abstraktion. Das Absehen von nicht-charakteristischen Aspekten des Betrachtungsgegenstandes (Dinge und Prozesse) ermöglicht den Übergang zu etwas Allgemeinerem und in gewissem Sinne Einfacherem. Wenn man sich nicht mehr um jede Einzelheit selbst kümmern muss, vereinfacht sich der Umgang damit. Noch bequemer wird es, wenn man den notwendigen Umgang mit dem Abstraktum an Dritte übertragen kann. Letzteres erfordert jedoch zusätzlich, den Arbeitsauftrag so zu spezifizieren, dass er von Dritten verstanden und (mit deren Fähigkeiten) umgesetzt werden kann.
deskriptives Arbeiten
Spätestens an dieser Stelle wird klar, dass deskriptives Arbeiten auf hoher Abstraktionsstufe für den Menschen wesentlich anspruchsvoller ist, als konkretoperationale Tätigkeiten auszuführen. Das Profil des Informatikers wandelt sich vom Problemlöser zum Manager, der die gewünschten Ergebnisse auf abstrakter Ebene beschreibt und einen Lösungsprozess veranlasst. Die auf Ausführungsebene erforderlichen Operationen, wie etwa das Sortieren von Listen oder Suchprozesse, sind längst zum Systembestandteil geworden. Sie stehen zur Verfügung und treten uns mit ihren Eigenschaften, wie etwa einer durch den Nutzer zu interpretierenden Effizienzangabe, also einem Qualitätsmerkmal, entgegen.
Komplexitätstheorie Effizienz praktisch unlösbar
Die Komplexitätstheorie, ein weiteres Teilgebiet der theoretischen Informatik, befasst sich mit algorithmischer Effizienz (Ressourcen: Zeit und Speicher) und lotet objektiv die praktischen Grenzen algorithmischen Problemlösens aus. Leider sind gerade die aus wirtschaftlicher Sicht relevantesten Algorithmen, etwa in der Logistik, bestenfalls für „Baby-Anwendungen“ brauchbar. In echtem Anwendungskontext sind sie unbrauchbar. Man versucht dann (bisher meist erfolglos) effizientere Algorithmen zu finden bzw. geeignete Näherungsverfahren einzusetzen. Die Klassifizierung der praktisch schwersten Probleme wurde inzwischen so weit getrieben, dass aus der Angabe einer effizienten Lösung irgendeines Problems aus der schwersten Klasse, effiziente Lösungen für sämtliche (prinzipiell lösbare) Probleme resultieren. Die Suche nach einer solchen „Schlüssellösung“ verlief bisher ohne Erfolg.
Berechenbarkeitstheorie absolut unlösbar
Auch die Berechenbarkeitstheorie gehört zur theoretischen Informatik. Sie vermittelt die Einsicht, dass es (sogar unendlich viele) formal definierbare Probleme gibt, die absolut unlösbar sind. Der Umgang mit unendlichen Mengen verschiedenster Art ist Gegenstand dieses Gebiets. Abstraktes Denken wird hier an Gegenständen
1.1 Theoretische Informatik und ihre Anwendungen
3
herausgebildet, die in der Praxis eher selten vorkommen, wohl aber in anderem Kontext angewandt werden. Wir befassen uns im Folgenden mit der Theorie der formalen Sprachen und der Automatentheorie. Die entsprechenden Inhalte geben uns das Rüstzeug, um Sprachen – meist unendliche Mengen von Wörtern bzw. Sätzen – beschreiben zu können. Wie bei Programmiersprachen spielt hier die Syntaxanalyse eine besondere Rolle. Deshalb ist es auch sinnvoll, einen kleinen Ausflug auf das Gebiet der praktischen Informatik zu wagen, um zu sehen, wie man das Wissen zur automatisierten Compiler-Konstruktion erfolgreich einsetzen kann. Im Gegensatz zur Situation bei Programmiersprachen wird die Semantik sprachlicher Ausdrücke in der theoretischen Informatik nicht betrachtet. Ende der 70er Jahre gehörten Compilergeneratoren (compiler compiler), die man häufig zur Herstellung von Übersetzern (Compilern) für Fachsprachen benutzt, zum Arbeitsgebiet einer geringen Zahl von Spezialisten. Dies lag vor allem daran, dass Compilergeneratoren ausschließlich auf Großrechnern zur Verfügung standen und sich auf diese Weise dem Tagesgeschäft entzogen. Seit der Verbreitung von Linux-Betriebssystem-Distribution für Heim- und Personalcomputer gehören Compilergeneratoren, wie etwa yacc (yet another compiler compiler) in Verbindung mit lex (lexical analysis), zum Softwarebasispaket und können für die verbreiteten Betriebssysteme aus dem Internet beschafft werden. Mit wenigen Handgriffen steht damit ein Werkzeug zur täglichen unmittelbaren Verfügung, das wesentlich abstraktere Anforderungen an den Informatiker stellt, als es die „Compilerprogrammierung vom leeren Blatt“ vermag. Die Sprache, für die der Compiler hergestellt werden soll, muss geeignet beschrieben werden. Die eigentliche Arbeit erledigt dann das Generator-Programm. So soll es sein! Ohne Kenntnisse aus der Theorie der formalen Sprachen und der Automatentheorie sind solche Werkzeuge nicht zielführend nutzbar. Abstrakte Automaten finden außerdem vielfältige Anwendungen zur Modellierung von Zustandsübergangsprozessen in diversen Bereichen. So kann man beispielsweise Computerspiele (adventure games) mit Automaten bestimmten Typs modellieren. Auch die Robotik, Neuroinformatik und Künstliche Intelligenz verwenden Automatenmodelle. Denkt man an Getränkeautomaten oder ähnliches, werden hingegen Automaten benötigt, die eine Ausgabe2 liefern. Auch zur Modellierung des Interaktionsverhaltens bei modernen Web-Anwendungen können Automaten (mit gewissen Einschränkungen) verwendet werden. Bei allen Anwendungen der theoretischen Informatik, die wir auch in einigen der folgenden Kapitel ganz hoch halten wollen, bleibt es ein Wissengebiet, das der Mathematik sehr nahe steht. Insofern ist es auch notwendig, abstrakten Denktechniken anwendungsfrei zu folgen und die mathematische Fachsprache gepflegt einzusetzen. 2 ggf.
zur Geldrückgabe, nicht zur Kaffeeausgabe
Theorie der formalen Sprachen, Automatentheorie automatisierte CompilerKonstruktion Compilergenerator
yacc lex
Beschreibung der Zielsprache
Abstrakte Automaten Computerspiele
Automaten mit Ausgabe; WebAnwendungen
4
1 Einleitung
1.2 AtoCC - unsere Lernumgebung AtoCC
AtoCC steht für „vom Automaten zur Compilerkonstruktion“ (from automaton to compiler construction). Seit 2004 arbeiten wir intensiv an dieser Software, die nicht nur für Windows-, sondern auch für Linux- und Mac-Betriebssysteme zur Verfügung steht und kostenlos aus dem Internet bezogen werden kann. Die Adresse lautet . Neben der Software und den in diesem Buch an diversen Stellen verwendeten Arbeitsdateien finden sich hier zahlreiche didaktische Materialien und Tutorials, auch Videos. Die Installation der Software geschieht vollautomatisch in wenigen Sekunden3 . Bei Internetzugang stehen eine Online-update- und eine Online-Hilfefunktion zur Verfügung.
Lernende
Während der Name AtoCC mit o.g. Langform den ursprünglichen Themenkreis reflektiert, hat sich dessen Funktionsumfang inzwischen auf den Bereich der formalen Sprachen ausgeweitet. Es ist nun möglich, die für das Gebiet der formalen Sprachen und abstrakten Automaten typische Themenbreite und ausgewählte Inhalte des Compilerbaus mit AtoCC zu behandeln. Damit steht eine konsistente Lernumgebung zur Verfügung, die den Lernenden (Studierende der Informatik aller Hochschulformen im Grundlagenstudium, Studierende von InformatikAnwendungsfächern, Lehrpersonen und Schüler der gymnasialen Oberstufe) bei intuitivem Bedienkontext in aufeinander abgestimmten Modulen agieren lassen. Auch in der Hand des Lehrenden kann AtoCC wertvolle Unterstützung bei der Wissensvermittlung und bei der Herstellung von Lehrmaterialien bzw. einschlägiger Publikationen leisten. Immer wenn in diesem Buch das folgende Symbol am Rand auftaucht, handelt es sich um eine Computerübung, die unter Verwendung von AtoCC durchgeführt werden soll.
AtoCC wurde auf nationalen und internationen Tagungen sowie in teilweise mehrtägigen Workshops für Lehrpersonen vorgestellt und in mehrjährigen Erprobungen mit Studierenden kontinuierlich überarbeitet. Die Beurteilung des Resultats überlassen wir Ihnen. 3 Das
ist keines der üblichen hohlen Versprechen!
2 Struktur von Programmen 2.1 Sprache, Syntax, Semantik und Pragmatik Stößt man auf das Wort „Sprache“, wie im Titel dieses Buches, so denkt man wohl zuerst an die „natürliche Sprache“, d.h. an unsere Muttersprache oder eine Fremdsprache. Eine solche Sprache benutzen wir täglich. Wir verfügen über einen mehr oder weniger breiten Wortschatz und bilden Sätze durch Aneinanderreihung von Worten. Für den Satzbau gibt es Regeln, die durch die Grammatik der entsprechenden Sprache definiert sind. Man nennt das die Syntax einer Sprache. Im Allgemeinen wenden wir diese Regeln intuitiv an. Wenigstens bei Fremdsprachen kann das allerdings schon mal schief gehen: Dann fördert die Satzanalyse Regelverstöße ans Tageslicht. Die Kommunikation, zu deren Zweck wir die natürliche Sprache im Allgemeinen einsetzen, wird durch einen fehlerhafte Satzbau gestört. Sätze mit geringen Syntaxfehlern können für den Kommunikationspartner dennoch verständlich bleiben. Wovon hängt das ab? Wenn es um die Verständlichkeit geht, kommt die Semantik ins Spiel. Sie steht für die Bedeutung eines Wortes bzw. Satzes. Sie kann unter anderem von Stimmungen, sozialen Komponenten, kulturellen Einflüssen oder weltanschaulichen Positionen abhängen. Die Semantik des „gesprochenen Wortes“ kann nachhaltig durch Mimik und Gestik beeinflusst werden. Die moderne Neurobiologie untersucht u.a. die Beeinflussung des Gehirns durch Stimmmodulation: Wenn Sie jemanden mit dem Satz „Ich liebe Dich.“ beschimpfen, wird der Empfänger starke Zweifel am eigentlichen Bedeutungsgehalt dieser Aussage haben. Was man mit Sprache bei Menschen bewirken kann, ist ein Untersuchungsgegenstand der Pragmatik.
Regeln Grammatik Syntax Satzanalyse
Semantik
Pragmatik
Selbst bei syntaktisch korrektem Gebrauch einer natürlichen Sprache können semantische Mehrdeutigkeiten auftreten. Es gibt Personen, die diese Eigenschaft gezielt einsetzen, um etwas „durch die Blume“ zu sagen oder um liebevolle sprachliche Seitenhiebe auszuteilen: „Verena kaufte noch eine Vase. Sie war wieder einmal blau.“ Worauf bezieht sich der zweite Satz?
Semantische Mehrdeutigkeiten
Syntax (bzw. Syntaktik), Semantik und Pragmatik sind (überlappende) Teilbereiche der Semiotik, die als die allgemeine Lehre von den Zeichen, Zeichensystemen und Zeichenprozessen gilt. Die Linguistik ist die Wissenschaft, die sich mit Sprache beschäftigt.
Semiotik Linguistik
6
Programme
Kommunikation zwischen Programmen Protokoll
formale Sprache
2 Struktur von Programmen
Auch für die Informatik ist der Sprachbegriff fundamental: Es gibt eine riesige Anzahl von Programmier- und Fachsprachen, in denen Programme im Allgemeinen als Texte geschrieben werden, deren Abarbeitung einen vorgegebenen Zweck erfüllen oder eben fehlerhaft sind. Bei genauerem Hinsehen stellt man fest, dass Programme eine weitere sprachbezogene Aufgabe wahrnehmen können: Sie verarbeiten Eingaben zu Ausgaben und reichen letztere ggf. an einen oder mehrere Empfänger weiter. Im Allgemeinen kommen sie weder mit semantischen noch mit syntaktischen Mehrdeutigkeiten zurecht. Es geht aber nicht nur um Programmiersprachen und Übersetzungen entsprechender Texte in lauffähige Programme, sondern eben auch um die Kommunikation zwischen Programmen. In der Tat können sich zwei Programme „unterhalten“: Der Browser „spricht“ mit dem Webserver, indem er z.B. ein -Dokument anfordert (request). Der Server antwortet (response) entsprechend. Dabei wird eine sehr einfache Sprache verwendet, die man als ein Protokoll bezeichnet. Auf höherem Niveau kommunizieren Systeme mit XML-Sprachen. Auf diese Weise lässt sich sogar die traditionelle Softwarelandschaft zu einer Service-orientierten Architektur (SOA) umgestalten, was im Moment ein sehr populäres Thema ist. Sprache ist also ein tragender Begriff in der Informatik. Die oben herausgestellten Aspekte (Syntax, Semantik, Pragmatik) von Sprache sind Gegenstand verschiedener Teilgebiete der Informatik. Während sich die praktische Informatik (Compilerbau) mit Programmiersprachen (sowie deren Übersetzung und Interpretation) beschäftigt, beschränkt sich die theoretische Informatik auf die Untersuchung formaler Sprachen. Programmiersprachen sind künstliche, also keine natürlichen, Sprachen. Das ist klar. Aber worin besteht der Unterschied zwischen Programmiersprachen und formalen Sprachen? Die folgende „Gleichung“ bringt es auf den Punkt: Programmiersprache = Syntax + Semantik + Pragmatik. Die Theorie der formalen Sprachen befasst sich also nur mit der Syntax und stellt Fragen zur Syntaxanalyse für Sprachen und Sprachklassen, um festzustellen, ob zur Erzeugung eines vorgelegten Satzes (hier Wort genannt) ausschließlich die grammatikalischen Regeln der betrachteten Sprache angewandt wurden. Sprachübersetzung (Compilerbau) greift einige Ergebnisse der theoretischen Informatik auf und thematisiert zusätzlich Semantik, Pragmatik und die Effizienz der Analyseverfahren. Damit kein falscher Eindruck entsteht: Auch zur Definition der Semantik von Programmiersprachen wird in der praktischen Informatik sehr viel Mathematik eingesetzt. Didaktischer Hinweis 2.1 Damit haben wir ein intuitives Verständnis für die Bedeutung des Sprachbegriffs und seiner Hauptaspekte geschaffen. Alltagserfahrungen im Umgang mit natürlichen Sprachen wurden begrenzt auf künstliche Sprachen übertragen und den entsprechenden Teilgebieten der Informatik zugeordnet. Der Untersuchungsgegenstand der Theorie der formalen Sprachen wurde herausgearbeitet.
2.2 Konkrete Syntax
7
2.2 Konkrete Syntax In der Sprachwissenschaft nennt man die Lehre von der Verknüpfung von Zeichen Syntaktik. So kann man beispielsweise die Syntaktik von Verkehrszeichen, wie in Abbildung 2.1, betrachten.
Syntaktik
Abbildung 2.1: Verkehrszeichen: Basissymbole zur Komposition
Unter Beachtung der gewünschten Semantik werden komplexere Verkehrszeichen aus Basissymbolen zusammengesetzt. Beim Entwurf eines neuen Verkehrszeichens hat der Konstrukteur verschiedene Möglichkeiten für die Komposition ausgewählter Basissymbole, um damit die gewünschte Semantik syntaktisch zu repräsentieren. Dann kommt die Wirkung ins Spiel, die das neue Zeichen auf die Verkehrsteilnehmer hat, also die Pragmatik.
Komposition
Während bei Verkehrszeichen grafische Basiselemente in zulässiger und zweckorientierter Weise zusammengefügt werden, verwenden Programmiersprachen im Allgemeinen textuelle Repräsentationen. Der Entwickler einer neuen Programmiersprache hat also neben den syntaktischen und semantischen auch pragmatische Aspekte zu berücksichtigen. Betrachten wir beispielsweise eine Zuweisung, wie sie in imperativen Programmiersprachen typischerweise anzutreffen ist. Eine Zuweisung ist eine Anweisung, mit der eine Variable eine Wertbindung erfährt. Es entsteht ein Name-Wert-Paar. In imperativen Sprachen gibt es dafür einen Operator, den man meist als , oder ← notiert (Syntax). Mit ← wird der Wert von inkrementiert (Semantik). In JavaScript verwendet man dafür , z.B.: . Damit wird der alte Wert der angegebenen Variable verändert (mutiert). Vergleicht man ← und miteinander, so stellt man fest, dass sich die konkrete Syntax in beiden Fällen – trotz übereinstimmender Semantik – deutlich unterscheidet. Welche Version sollte man bevorzugen? Die konkrete Syntax, also das Design, einer Programmiersprache ist demnach eine Frage der Pragmatik. Dabei können viele Gesichtspunkte eine Rolle spielen. Beispielsweise ist das Gleichheitszeichen in Zuweisungen umstritten, weil es mit dem Vergleichsoperator „ist gleich“ verwechselt werden könnte. Für dieses Relationszeichen hat sich deshalb verbreitet.
Zuweisung
konkrete Syntax Sprachdesign
8
2 Struktur von Programmen
Beispiel 2.1 Im Folgenden betrachten wir ein Programm der Sprache MiniJavaScript. Es stellt eine Version des bekannten Nim-Spiels dar, das gegen den Computer gespielt werden kann.
MiniJavaScript Nim-Spiel
In diversen Quellen finden sich verschiedene Spielregeln für das Nim-Spiel. Wir legen die folgenden Verabredungen zugrunde: Zu Beginn des Spiels sind 20 Streichhölzchen vorhanden. Zwei Spieler nehmen abwechselnd ein, zwei oder drei Hölzchen weg. Derjenige Spieler, der den letzten Zug macht, also die letzten Streichhölzer wegnimmt, gewinnt.
2.2 Konkrete Syntax
9
Der Hauptzyklus wird solange durchlaufen, bis der Streichhölzchen-Pool vollständig aufgebraucht ist, oder der menschliche Spieler eine ungültige Anzahl von Hölzern gezogen hat. Die -Bedingungen am Ende des Programmtextes bilden sozusagen die Intelligenz des Computerprogramms als Gegenspieler. Da es in unserem MiniJavaScript kein gibt, behilft sich das Programm durch Zuweisung von an die Variable nach einem Halbzug des Computers, um so ein weiteres Ziehen eines Hölzchens zu verhindern. Da sich MiniJavaScript sehr stark am echten JavaScript orientiert, können wir MiniJavaScript-Programme in einem Browser (wie Internet Explorer oder FireFox) abarbeiten lassen. Da jedoch die zwei Prozeduren write und writeln in JavaScript nicht definiert sind, müssen wir diese als Hilfsfunktionen extra bereitstellen:
Hinweise zur Programmausführung
Die folgenden Ausgaben im Browser protokollieren ein konkretes Spiel.
Browser
10
2 Struktur von Programmen
Das Nim-Spiel ist aus der Sicht der Spieltheorie durchaus reizvoll, da es eine vollständige Analyse der Gewinnsituationen ermöglicht. Uns interessiert hier jedoch lediglich die Syntax des MiniJavaScript-Programms. Didaktischer Hinweis 2.2 Es sollte lediglich gezeigt werden, dass mit dieser sehr einfachen Sprache ein Programm für eine durchaus anspruchsvolle Aufgabe geschrieben werden kann.
konkrete Syntax von MiniJavaScript
Die konkrete Syntax von MiniJavaScript wird durch folgende Regeln bestimmt. Die für die Interpretation durch den Browser notwendigen -Tags bleiben unberücksichtigt. Programm → Be f ehle → Be f ehl
Kondition
Ausdruck
Be f ehle Be f ehl | Be f ehl ; Be f ehle
→ Variable = Ausdruck |
Variable = prompt (
|
write ( Ausdruck )
|
write ( Zeichenkette )
|
writeln ( Ausdruck )
|
writeln ( Zeichenkette )
|
if ( Kondition ) { Be f ehle }
|
while ( Kondition ) { Be f ehle }
,
→ Ausdruck == Ausdruck |
Ausdruck < Ausdruck
|
Ausdruck > Ausdruck
→ Ausdruck + Ausdruck |
Ausdruck - Ausdruck
|
Variable
|
Zahl
| Variable → Zahl
→
Zi f f er
→
Zeichenkette →
- Zahl A | B | C Zi f f er | Zi f f er Zahl 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
)
2.2 Konkrete Syntax
11
Die Form, wie wir diese Regeln aufgeschrieben haben, bedarf einiger Erklärung. Eingerahmte Zeichen sind genau die, die tatsächlich im Programm vorkommen können. Gleichgültig, ob sie über eine normale oder über eine „Spezialtastatur“ (mit write, prompt usw.) eingegeben werden. Wir nennen sie Terminale. Wörter in Normalschrift sind grammatikalische Begriffe, die uns in die Lage versetzen, die Regeln überhaupt formulieren zu können. Prinzipiell sind diese Begriffe frei wählbar, anstelle von „Ausdruck“ könnten wir an jeder vorkommenden Stelle ebenso „XY11K19“ schreiben, was aus nahe liegenden Gründen nicht geschieht. Grammatikalische Begriffe heißen Nichtterminale. Der Rechtspfeil → trennt das auf der linken Seite einer Regel oder Produktion stehende Nichtterminal1 von ihrer rechten. Man liest den Pfeil als „definiert“, „ist definiert als“ oder „steht für“, z.B. „Ausdruck definiert eine Zahl“. In englischsprachiger Literatur nimmt man einfach nur „is“. Der Alternativ-Strich | verringert den Notationsaufwand für den Fall, dass es für ein und dasselbe Nichtterminal mehrere Regeln gibt. X → Y und X → Z dürfen also zu X → Y | Z zusammengefasst werden. | liest man als „oder“.
Terminale
Nichtterminale Regeln
Alternative Regeln
Alle-ASCII-Zeichen-außer- steht für die Menge aller zulässigen ASCII-Zeichen, also wenigstens alle Ziffern, Buchstaben und Sonderzeichen, die über die Tastatur eingetippt werden können. Dies ist wesentlich kürzer, als wenn wir die gesamte Liste als alternative Regeln hier angeben müssten. Nur das Zeichen ist nicht erlaubt. Die in dieser Form vorgenommene Definition der konkreten Syntax einer Sprache ist selbst eine Sprache, genauer: eine Metasprache. Die oben erklärten Zeichen | und → sind Bestandteile dieser Sprache, die von BACKUS2 eingeführt wurde und Backus-Naur3 -Form, kurz: BNF, heißt. „Form“ ist das englische Wort für Formular oder Formalismus.
Metasprache Backus-NaurForm
Die BNF wurde für Sprachen eingeführt, deren grammatikalische Regeln auf sämtlichen linken Seiten aus jeweils genau einem Nichtterminal bestehen. Obwohl die BNF nicht darauf beschränkt ist, werden wir uns verstärkt dem Studium dieser Grammatikklasse zuwenden. Um rekursive Regeln der Form X → a | aX verkürzt darstellen zu können, wurde die BNF durch die Metazeichen ∗ und + ergänzt. Man spricht dann von der Erweiterten Backus-Naur-Form, kurz: EBNF. a+ .
Für X → a | aX schreibt man dann X → Um die Folgen von Terminalen und/oder Nichtterminalen, auf die sich ∗ bzw. + beziehen, auszuweisen, sind ggf. 1 Bei
Regeln dieser Art bestehen sämtliche linke Regelseiten aus genau einem Nichtterminal.
2 John Warner Backus (geb. 3. Dezember 1924 in Philadelphia; gest. 17. März 2007 in Ashland, Oregon) 3 Peter
Naur (geb. 25. Oktober 1928 in Frederiksberg bei Kopenhagen)
EBNF
12
2 Struktur von Programmen
geschweifte Klammern zu setzen. Dies würde bei unserer Sprache MiniJavaScript zu einer Kollission mit den zum Sprachumfang gehörenden geschweiften Klammern führen, die als Terminale der Sprache nicht als metasprachliche Symbole verwendet werden dürfen. Manchmal werden anstelle von ∗ geschweifte und anstelle von + eckige Klammern verwendet. Weitere Varianten sind anzutreffen. Auch optionale Parameter in Programmaufrufen, wie in , werden in dieser Form angegeben. Übung 2.1 Eine alternative Darstellungsform zur BNF sind Syntaxdiagramme. Sie sind grafikorientiert und damit sehr anschaulich. Suchen Sie im Internet nach entsprechenden Quellen und machen Sie sich sachkundig. Übung 2.2 Interpretieren Sie das MiniJavaScript-Programm aus Beispiel 2.1 in einem Browser mit JavaScript-Unterstützung und spielen Sie das Nim-Spiel gegen den Computer. Editieren Sie anschließend den Programmtext und verursachen Sie einen Syntaxfehler. Sie werden dann feststellen, das Ihnen der Browser (genauer: der JavaScript-Interpreter) keinerlei Fehlerhinweis liefert und lediglich eine leere Seite anzeigt. Justieren Sie ggf. Ihre persönliche Einstellung zu Fehlermeldungen in der Programmierung.
2.3 Abstrakte Syntax
abstrakte Syntax
Abstrakter Syntaxbaum (AST)
Während die konkrete Syntax im Allgemeinen auf eine Mensch-orientierte Darstellung von Sätzen der betreffenden Sprache abzielt, verkörpert die abstrakte Syntax lediglich den Extrakt, d.h. die (unbedingt notwendigen) Bestandteile des Satzes unter Beachtung der Satzstruktur. Von den eher äußerlichen Eigenschaften wird hier also abstrahiert. Ein syntaktisch korrekter Satz einer Sprache, z.B. unser MiniJavaScript-Programm in Beispiel 2.1 auf S. 8, wird in Form eines abstrakten Syntaxbaumes, kurz: AST, von engl. abstract syntax tree, dargestellt. Dies kann in einer Datenstruktur geschehen, die für den Menschen völlig unlesbar ist. Wie erhält man für einen syntaktisch korrekten Satz einen zugehörigen AST? Jeder Regel der konkreten Syntax wird genau ein Teilbaum zugeordnet: Für jedes Nichtterminal X in X → α wird dabei genau ein Knotentyp definiert. Dessen Kindknoten entsprechen den in α vorkommenden Nichtterminalen. Typischerweise werden Zahl, Variablenname usw. als Terminale angesehen, zu deren Speicherung im AST die betreffende Zahl bzw. der vorgefundene Bezeichner als Zusatzinformation gehören. Für Zahl wird dies in Abbildung 2.2 durch Rechtecke gekennzeichnet. Andererseits können bestimmte Elemente der konkreten Syntax, wie Klammern, das Semikolon als Anweisungstrennzeichen und das Zuweisungszeichen, weggelassen werden, da sie für die abstrakte Syntax keine Rolle spielen.
2.3 Abstrakte Syntax
13
Beispiel 2.2 Wir betrachten die abstrakte Syntax für Ausdruck in einen Ausschnitt aus der Regelmenge für MiniJavaScript unter der Voraussetzung, dass das vorkommende Nichtterminal Zahl alle Zahlwörter für die natürlichen Zahlen beschreibt Ausdruck
→
Ausdruck + Ausdruck
|
Ausdruck - Ausdruck
|
Zahl
konkrete Syntax
abstrakte Syntax
Ausdruck → Ausdruck + Ausdruck
Ausdruck → Ausdruck - Ausdruck
Ausdruck → Zahl
(a) Differenz aus Zahl und Summe 3 − (4 + 5)
(b) Summe aus Differenz und Zahl (3 − 4) + 5
Abbildung 2.2: Zwei verschiedene Bäume (AST) für
Wie in Abbildung 2.2 zu sehen ist, ergeben sich für den (syntaktisch korrekten) Ausdruck 3 - 4 + 5 sogar zwei verschiedene zugehörige AST. Die Knotentypen sind Ausdruck und Zahl.
14
Operatorbaum
Compiler
Quell- und Zielsprache syntaktische Mehrdeutigkeit
2 Struktur von Programmen
Man nennt einen solchen Baum für arithmetische Ausdrücke auch Operatorbaum. Operatorbäume werden in der funktionalen Programmierung für entsprechende Ausdrücke und bei Datenbanken zur Darstellung von Anfragen verwendet. Ein AST ist der wichtigste Anknüpfungspunkt des Übersetzungsprozesses eines syntaktisch korrekten Satzes aus einer (formalen) Sprache in eine andere. Hier käme dann allerdings noch die Erzeugung eines Zielcodes hinzu. Dieser Prozess wird typischerweise von einem Compiler übernommen. Die entsprechenden Konzepte werden an verschiedenen Stellen in den folgenden Kapiteln dieses Buches behandelt. Zwei oder mehrere verschiedene AST für ein und denselben Ausdruck (Satz) sind für einen Compiler im Allgemeinen unbrauchbar. Für ein und denselben Satz der Quellsprache entstehen (auf völlig korrektem Weg) verschiedene Sätze der Zielsprache. Eine solche syntaktische Mehrdeutigkeit sollte vermieden werden. Dies kann nur durch Veränderungen der Regelmenge geschehen. Didaktischer Hinweis 2.3 Im Gegensatz zur traditionellen Behandlungsreihenfolge haben wir den AST vor dem Ableitungsbaum (s. nächster Abschnitt) thematisiert, obwohl ein AST die syntaktische Korrektheit und damit die Existenz einer Ableitung voraussetzt. Die hier gewählte Reihenfolge macht es möglich, an dieser Stelle den Strukturaspekt gegenüber dem eher prozedural angelegten Analyseprozess heraus zu heben.
2.4 Syntaxanalyse
Syntaxanalyse Parsing
Eine in gewisser Hinsicht zuverlässige Übersetzung bzw. Verarbeitung eines Satzes einer Sprache erfordert eine vorausgehende Satzanalyse. Wie bereits erwähnt wurde, beschränkt sich die theoretische Informatik dabei auf die Syntaxanalyse. Bei einem Sprachübersetzer (Compiler) bildet die Syntaxanalyse – das Parsing – die erste Hauptphase, die im Erfolgsfall mit dem Aufbau eines dem untersuchten Satz entsprechenden AST endet. Anhand der in Beispiel 2.2 bereits herausgegriffenen syntaktischen Einheit Ausdruck wollen wir uns mit dem grundsätzlichen Vorgang der Syntaxanalyse vertraut machen. Dabei werden viele Fragen offen bleiben, deren Klärung den folgenden Kapiteln vorbehalten ist. Wieder wählen wir 3 - 4 + 5 als zu analysierenden Beispielsatz. Die Vorgehensweise ist intuitiv: Beim Nichtterminal Ausdruck beginnend wenden wir eine Regel an, die dieses Nichtterminal erklärt. Das ist beispielsweise die zweite Regel: Ausdruck → Ausdruck - Ausdruck. Das Start-Nichtterminal, also Ausdruck, wird nun durch die komplette rechte Regelseite, also Ausdruck - Ausdruck ersetzt. Dadurch ergeben sich zwei zu ersetzende Nichtterminale, nämlich Ausdruck
2.4 Syntaxanalyse
15
und Ausdruck. Die Ersetzung der noch vorhandenen Nichtterminale durch je eine zugehörige rechte Seite einer passenden Regel wird solange fortgesetzt, bis nur noch Terminalsymbole vorkommen. Für syntaktisch fehlerhafte Ausdrücke gibt es keine derartige Ersetzungkette. Protokolliert man diesen Prozess für unser Beispiel, so entsteht ein Ableitungsbaum (Parsebaum), wie in Abbildung 2.3. Wie wir bereits festgestellt hatten, gibt es zwei AST für diesen Beispielausdruck. Jetzt können wir die Ursache dafür angeben: Es gibt zwei Möglichkeiten 3 4 + 5 ab- bzw. herzuleiten. Der Baum in Abbildung 2.4 zeigt diese zweite Variante.
Abbildung 2.3: Ableitungsbaum für den Ausdruck
Abbildung 2.4: Noch ein Ableitungsbaum für den Ausdruck Übung 2.3 Erzeugen Sie diese Bäume auf Papier. Was stellen Sie hinsichtlich der Regelauswahl fest, wenn es in einem Schritt mehr als eine anwendbare Regel gibt?
Ersetzung
Ableitungsbaum
16
2 Struktur von Programmen
Mehrdeutige Sprachen sind für die Verarbeitung im Compiler prinzipiell unbrauchbar. Wir werden uns noch ansehen, wie man solche Mehrdeutigkeiten zu beheben versucht.
Satz Wort
Abschließend wollen wir auf einen wichtigen begrifflichen Unterschied hinweisen, von dem wir auch schon ein wenig Gebrauch gemacht haben: Während man im Compilerbau (wie bei natürlichen Sprachen) von Wörtern und Sätzen einer Sprache spricht, verwendet die Theorie der formalen Sprachen generell den Wortbegriff. Dies gilt unabhängig von der Länge eines Wortes, also auch dann, wenn sein Aufschrieb mehrere Seiten beansprucht. Um eine Strukturvorstellung aufzubauen, haben wir bis hierher den Satzbegriff bevorzugt. Dies ist auch für Programmtexte sehr wichtig, da sie im Allgemeinen als eine Folge von Zeichen entstehen, die über die (normale) Tastatur eingegeben werden.
3 Grundbegriffe 3.1 Alphabet und Zeichen Formale Sprachen enthalten Wörter1 . Wörter bestehen aus Zeichen. Sämtliche Zeichen eines Wortes stammen aus genau einem Alphabet. Damit haben wir bereits drei wichtige Grundbegriffe der Theorie der formalen Sprachen angesprochen. Es folgen die zugehörigen Definitionen und einige Überlegungen zum konstruktiven Umgang mit Zeichen, Wörtern und Sprachen. Definition 3.1 Ein Alphabet ist eine beliebige endliche, nichtleere Menge. Die Elemente dieser Menge heißen Zeichen. Beispiel 3.1 Die folgenden Mengen sind Alphabete. Auf einige von ihnen werden wir uns im Folgetext mehrfach beziehen. A1 = { , , , . . . , } A2 = { , , , , , , } A3 = { begin , end , for , while , do , repeat , until } A4 = {, , }
A5 = {
}
A6 = {
}
1 Wenigstens im Deutschen besitzt das Wort „Wort“ zwei Pluralformen, nämlich „Wörter“ und „Worte“.
Wörter stehen ohne Bezug auf ihren Zusammenhang. Man kann also mit vielen Wörtern nichts sagen. Hingegen spricht man von Worten, wenn damit Gedanken oder Gefühle ausgedrückt werden. Beispiele: die groß geschriebenen Wörter; Gottes Wort, geflügelte Worte, Er sprach ein paar andachtsvolle Worte. In der Theoretischen Informatik spielt die Bedeutung von Wortfolgen keine Rolle. Folglich benutzen wir den Plural „Wörter“.
formale Sprachen Wörter Zeichen Alphabet
18
3 Grundbegriffe
A7 = {
}
Man beachte insbesondere bei A3 und A7 , dass es sich um „atomare“ Zeichen handelt, die nicht etwa in einzelne Tastaturzeichen zerfallen. Man kann sie sich als beschriftete Kärtchen vorstellen. Didaktischer Hinweis 3.1 Terminale (wie in Abschnitt 2.2) und Alphabetzeichen (s.o.) haben wir mit einem Rahmen umgeben bzw. durch eine Typewriter-Schrift gekennzeichnet. Dies werden wir in den folgenden Kapiteln nicht konsequent durchhalten, weil es die Darstellung nicht in jedem Falle übersichtlicher macht. Es ist aber sehr wichtig, die entsprechenden Symbole (Alphabetzeichen und Terminale) als atomare Zeichen zu verstehen. Computerübung 3.1 An dieser Stelle verwenden Sie die Lernumgebung AtoCC zum ersten Mal. Öffnen Sie die Grammatik-Editor-Komponente kfG Edit und wählen Sie im linken Bereich „Grundbegriffe“. Anschließend verwenden Sie den Tab (Reiter) „Sprache“. Zur Definition der Alphabete A1 (s. Abbildung 3.1), A2 und A3 geben Sie die entsprechenden Zeichen über die Tastatur ein. Sie können den atomaren Charakter „längerer“ Alphabetzeichen (wie in A3 ) durch einfache Hochkommas – je eines am Anfang und eines am Ende des Symbols – hervorheben. Auf diese Weise sind auch Zeichen(!), die ein Komma enthalten, darstellbar. So würde man das Symbol 3,14 in der Form angeben. Ohne Hochkommas ergäbe sich 3 , 14 .
Abbildung 3.1: Grammatik-Editor-Komponente von AtoCC: Grundbegriffe, Sprache
alternative AlphabetDefinition
In mancher Literatur wird ein Alphabet auch als eine geordnete Menge von Zeichen, also als ein Singletupel, definiert. Das hat Vorteile für die Sortierung von Wörtern, wie wir in Abschnitt 3.3 sehen werden. Im Allgemeinen ist es jedoch nicht erforderlich, eine Ordnungsrelation als definierende Eigenschaft eines Alphabets anzusehen. Deshalb definieren wir Alphabete als Mengen, s. Definition 3.1.
3.2 Wort, Wortlänge und Verkettung
19
3.2 Wort, Wortlänge und Verkettung Definition 3.2 Irgendeine (auch leere) Zeichenkette ist ein Wort. Man sagt: Eine Zeichenkette „w ist ein Wort über dem Alphabet A“, wenn sämtliche Zeichen von w aus A stammen. ist ε. Das Symbol für das leere Wort
Didaktischer Hinweis 3.2 Die Aussage „ε ist ein Name für das leere Wort “ kann durch eine Analogie untermauert werden: „0/ ist ein Name für die leere Menge {}.“
Beispiel 3.2 , , s. Abbildung 3.2, und sind Wörter über A1 = { , , , ..., }, denn sie enthalten ausschließlich Zeichen aus A1 . Dabei spielt es keine Rolle, wie oft ist kein Wort über A1 , weil ein ausgewähltes Zeichen im Wort verwendet wird. es mit ’ ’ ein Zeichen enthält, das nicht zu A1 gehört.
Abbildung 3.2: Wort über A1 aus Beispiel 3.1
Eine vorgegebene Zeichenkette z = z1 z2 . . . zn ist genau dann ein Wort über dem Alphabet A, wenn für jedes einzelne Zeichen zi gilt: zi ∈ A. Damit ist auch klar, dass die Zeichenkette ein Wort – das leere Wort – über (jedem) Alphabet A darstellt. ε enthält kein einziges unzulässiges Zeichen.
leere Wort
Ist das Wort über A1 = { , , , ..., }, s. Abbildung 3.2, eigentlich auch ein Wort über A3 = { , , , , , , }? Nein, denn wir wissen, dass es ein Wort über A1 ist und folglich aus den Zeichen , , , , und besteht. Diese Zeichen gibt es in A3 nicht. Andererseits ist die aus den beiden Zeichen und bestehende Zeichenkette sehr wohl ein Wort über A3 , s. Abbildung 3.3. Ein optischer Unterschied ist – bis auf die Darstellung in AtoCC – nicht erkennbar. Bei der Analyse muss also stets das zugrunde liegende Alphabet mit angegeben werden. Wörter werden in Anführungszeichen eingeschlossen. Dies entspricht unseren Notationsgewohnheiten für Zeichenketten. Wenn keine Verwechslungsmöglichkeit
Notation von Wörtern
20
3 Grundbegriffe
Abbildung 3.3: Wort über A3 aus Beispiel 3.1
besteht, können die Anführungszeichen entfallen: oder beschreiben also ein und dasselbe Wort über A1 . und stehen jedoch für zwei völlig verschiedene Dinge, nämlich das Zeichen bzw. das „einzeichige“ Wort . Didaktischer Hinweis 3.3 Leider sind hier immer wieder Fehler zu beobachten: Weder das leere noch irgendein anderes Wort dürfen in einem Alphabet vorkommen.
Wortlänge
Wörter haben unterschiedliche Längen. Zur Definition des Begriffs der Wortlänge gibt es zwei ernstzunehmende Vorschläge: Entweder man nimmt die Anzahl der paarweise verschiedenen Zeichen eines Wortes, gleichgültig wie oft diese Zeichen darin vorkommen, oder man zählt jedes (auch mehrfach vorkommendes) Zeichen extra. Man hat sich für die zweite Variante entschieden. Definition 3.3 Die Länge eines Wortes w, kurz: |w|, ist bestimmt durch die Anzahl aller Zeichen, die das Wort enthält. Dabei werden Mehrfachvorkommen entsprechend ihres Auftretens mehrfach gezählt. Beispiel 3.3 | = 7, s. Abbildung 3.4, nicht etwa 3. |ε| = 0. |
Abbildung 3.4: Länge eines Wortes über A1 aus Beispiel 3.1
Wortbildung
Wir wenden uns nun der Wortbildung zu. Um ein Wort über einem gegebenen
3.3 Wortmenge
21
Alphabet zu bilden, wendet man eine sehr einfache Operation an: die Verkettung oder Konkatenation. Gern schreibt man dafür das Symbol ◦. Um beispielsweise das Wort über dem Alphabet { , , } zu bilden, geht man folgendermaßen vor: Man wählt zunächst das Zeichen aus dem Alphabet und verkettet es mit dem Zeichen , d.h. ◦ = . Durch die Verkettung zweier Zeichen entsteht ein Wort.
Verkettung
Computerübung 3.2 Verwenden Sie die Grammatik-Editor-Komponente (Grundbegriffe, Sprache), um das Alphabet A = { , , } zu definieren. Bilden Sie anschließend ein beliebiges Wort der Länge 4. Hierzu klicken Sie einfach auf das jeweils auszuwählende Alphabetzeichen. Diese werden im Programm in abgerundetem Rechteck mit gelbem Hintergrund dargestellt.
Als nächstes erwarten wir, dass die Verkettungsoperation auf ein Wort (erster Operand) und ein Zeichen (zweiter Operand) anwendbar ist: ◦ = . Wieder entsteht ein Wort. Dies ist auch der Fall, wenn zwei Wörter über einem bestimmten Alphabet miteinander verkettet werden: ◦ = .
3.3 Wortmenge Definition 3.4 Die Menge aller Wörter über A nennt man die Wortmenge A∗ . Das leere Wort ε gehört zu jeder Wortmenge. Beispiel 3.4 , . . .}, wie in Abbildung 3.5 dargestellt, die WortSei A = { }. Dann ist A∗ = {ε, , , wird in A∗ gefunden, s. menge der „Bierdeckelnotation“. Das gebildete Beispielwort blauer Streifen in Abbildung 3.5.
Wie viele Elemente besitzt A∗ für ein beliebiges Alphabet A mit |A| = n Elementen? A∗ ist stets eine unendliche Menge: Für jedes Wort aus A∗ kann man ein weiteres Wort durch Verkettung bilden. Wie wir in Beispiel 3.4 gesehen haben, reicht schon ein Alphabet mit nur einem einzigen Zeichen aus, um unendlich viele Wörter darüber zu erzeugen. Innerhalb von A∗ hat ◦ die Eigenschaft, durch Anwendung auf je zwei beliebige Wörter aus A∗ ein Wort zu erzeugen, das (ganz sicher) zu A∗ gehört. Man sagt, A∗ ist bezüglich ◦ abgeschlossen. Das Verknüpfungsgebilde (A∗ , ◦) hat einige (leicht beweisbare) Eigenschaften. Einige davon sind Gegenstand der folgenden Übung. Übung 3.1 Bearbeiten Sie die folgenden Aufgaben in der angegebenen Reihenfolge. (a) Beweisen Sie die folgenden Eigenschaften der Operation „Verkettung“ in der Menge aller Wörter über einem gegebenen Alphabet A. Für u, v ∈ A∗ gelten: |u ◦ v| = |u| + |v| und |un | = | u ◦ u ◦. . . ◦ u | = n · |u|, mit n ∈ N. n mal
unendliche Menge
(A∗ , ◦)
22
3 Grundbegriffe
(b) Argumentieren Sie für oder gegen die Gültigkeit der folgenden Eigenschaften der Verkettung: • Abgeschlossenheit, d.h. für alle x, y ∈ A∗ gilt x ◦ y = xy ∈ A∗ . • Assoziativität, d.h. für alle x, y, z ∈ A∗ gilt (x ◦ y) ◦ z = x ◦ (y ◦ z) = xyz. • Kommutativität, d.h. für alle x, y ∈ A∗ gilt x ◦ y = y ◦ x. • Neutrales Element, d.h. es gibt ein e ∈ A∗ mit e ◦ x = x ◦ e = x. (c) Bilden Sie vier Wörter, die zu A∗ gehören, und geben Sie zwei Wörter an, die nicht zu A∗ gehören. Dabei ist A = { , , , , , , }. (d) Ermitteln Sie die Anzahl der Wörter über einem Alphabet mit n Zeichen, deren Länge k nicht überschreitet.
Abbildung 3.5: Wortmenge über A aus Beispiel 3.4
Die Bildung von Wörtern über einem einelementige Alphabet, wie in Beispiel 3.4, ist sehr einfach. Umfasst das Alphabet mehrere Elemente, wie etwa A = { , , }, sollte man die Wortmenge systematisch notieren, um den Überblick nicht zu verlieren. Beispielsweise kann man die Wörter in Gruppen aufsteigender Wortlänge anordnen. Beispiel 3.5 ε , , , , , A = { , , }. A∗ = { A0
A1
,
,
,
,
,
,
,
,
, . . .}
A2
Die gesamte Wortmenge über A setzt sich dann aus den Wörtern dieser unendlich
3.3 Wortmenge
23
vielen disjunkten Teilmengen Ai zusammen: A∗ =
Ai .
i∈N
Die natürliche Zahl i kann beliebig groß gewählt werden. Man nennt solche Mengen potentiell unendlich. Manchmal verwendet man auch A+ = A∗ \{ε}.
unendlich viele endliche und disjunkte Teilmengen
Zu beachten ist, dass A0 , A1 , A2 , . . . , Ai , . . . endliche Mengen sind. Sie besitzen jeweils |A|i Elemente. Bei der Anordnung der Elemente von A∗ in Beispiel 3.5 sind wir sogar einen Schritt weiter gegangen: Neben der Gruppierung nach der Wortlänge wurden die Wörter der einzelnen Gruppen zusätzlich lexikografisch2 angeordnet. Die Reihenfolge des Aufschriebs der Alphabetelemente bestimmt deren aufsteigende Sortierung bezüglich der betrachteten Ordnungsrelation ≺ . Für Beispiel 3.5 bedeutet das:
≺
lex
lex
≺ . Fasst man beides zusammen, so ergibt sich die längenlexikografi-
≺
lex
lex
sche Ordnung ∗
∗
≺ : A × A → {true, f alse} mit
llo
⎧ wenn |w1 | < |w2 | ⎪ ⎨ true, w1 ≺ w2 , wenn |w1 | = |w2 | w1 ≺ w2 = lex ⎪ llo ⎩ f alse, sonst In Beispiel 3.4 hatten wir bereits vermutet, dass eine Wortmenge stets eine abzählbar unendliche Menge ist. Was ist darunter zu verstehen? Gibt es noch andere „Arten“ von Unendlichkeit? Definition 3.5 Zwei Mengen M1 und M2 heißen gleichmächtig, wenn es eine bijektive Abbildung von M1 auf M2 gibt. Ist eine der beiden Mengen die der natürlichen Zahlen N, so ist M eine abzählbar unendliche Menge, kurz: „M ist abzählbar“.
Abbildung 3.6 veranschaulicht die geforderte Abbildung. Didaktischer Hinweis 3.4 Auf die Existenz überabzählbar unendlicher Mengen sollte unbedingt hingewiesen werden. So ist die Menge der natürlichen Zahlen N eine abzählbar unendliche Menge, während die Menge der reellen Zahlen R nicht abzählbar, kurz: überabzählbar unendlich, ist. Es gibt also (sehr viel) mehr reelle Zahlen als natürliche.
längenlexikografische Ordnung ≺
llo
abzählbar unendliche Menge
24
3 Grundbegriffe
0
m1
1
2 3
m2 m 0 m3 m4 ...
4 ... N
M
Abbildung 3.6: Bijektive Abbildung f : N → M
Übung 3.2 Wiederholen Sie die Begriffe injektiv, surjektiv und bijektiv, und drücken Sie diese Eigenschaften mit Mengenbeziehungen aus.
In Abbildung 3.6 wurden die Elemente von M mit m0 , m1 , m2 , . . . benannt. O.B.d.A.3 kann man eine bijektive Abbildung f : N → M definieren, für die f (i) = mi gilt, wobei i ∈ N. Dies legt die Idee nahe, die Elemente aus M einfach durchzunummerieren: M = { f (0), f (1), f (2), . . .}. Das Durchnummerieren wird durch eine Sortierung der Elemente erreicht und genau dies leistet die Sortierung nach der Ordnungsrelation ≺ . Damit ist der Beweis des folgenden Satzes llo
gedanklich ausreichend vorbereitet. Satz 3.1 Die Menge A∗ aller Wörter über A ist abzählbar unendlich. Beweis Die erforderliche bijektive Abbildung von N aus A∗ ergibt sich aus der Sortierung der Ele mente von A∗ nach der längenlexikografischen Ordnungsrelation ≺ . llo
Beispiel 3.6 Wir bestimmen f (86) in der längenlexikografisch geordneten Wortmenge über { , , , }, beginnend mit f (0): |A| = 4; |A0 | = 1, |A1 | = 4, |A2 | = 16, |A3 | = 64; |A0 | + |A1 | + |A2 | + . |A3 | = 85. Wir suchen also das erste Wort aus A4 . Das ist Computerübung 3.3 Überprüfen Sie das Ergebnis aus Beispiel 3.6 mit AtoCC (Grammatik-Editor, Grundbegriffe, Sprache). Der blaue Streifen zur Markierung eines ausgewählten Wortes ist zusätzlich mit einer Nummer versehen. Sie entspricht der Ordnungszahl des jeweiligen Elements in der Wortmenge.
2 Dies
entspricht der alphabetischen Sortierung wie in einem Lexikon oder im Duden. Beschränkung der Allgemeinheit
3 Ohne
3.4 Sprache
25
3.4 Sprache Definition 3.6 Sei A ein Alphabet. Jede Teilmenge L ⊆ A∗ heißt Sprache über A. (L erinnert an das englische Wort language für Sprache.)
Eine Sprache besteht also aus Wörtern. Die Definition lässt zwei extreme Sprachen zu: L1 = 0/ und L2 = A∗ . Sowohl die leere Menge als auch die Allsprache sind wenig interessant: Dies sind gerade die Sprachen, die kein einziges Wort enthalten bzw. keinerlei Anforderungen an den Aufbau ihrer Wörter stellen. Sprachen sind entweder endliche oder (typischerweise) unendliche4 Mengen. In der Mathematik wird für unendliche Mengen die bekannte Pünktchen-Notation, wie bei M = { , , , , , . . .}, verwendet. Klar, die Pünktchen deuten an, dass es sich um eine Menge mit unendlich vielen Elementen handelt. Aber wie lauten die nächsten fünf Elemente von M? Wir müssen erkennen, dass sich diese deskriptive Repräsentationsform als unbrauchbar erweist, wenn wir ganz bestimmte Sprachen festlegen wollen. Computerübung 3.4 Legen Sie eine Sprache L ⊂ A∗ für A = { , , , } fest, indem Sie bestimmte Elemente aus A∗ (durch Doppelklick) auswählen. Soll L eine unendliche Menge sein, müssen auch die Pünktchen übernommen werden. Diese stehen für die in A∗ zum Zeitpunkt der Auswahl nicht angezeigten Elemente. Didaktischer Hinweis 3.5 Auch mit AtoCC können die Einschränkungen, die sich aus der Pünktchen-Notation ergeben, nicht behoben werden. Die in A∗ nicht angezeigten (unendlich vielen) Elemente können lediglich übernommen werden. Oft möchte man das nicht, sondern nur jedes zweite Element oder alle Elemente, die ein bestimmtes Zeichen enthalten. Dann wünscht man sich so etwas wie ein Filter, das man auf die Elemente dieser Teilmenge anwenden kann. Formale Grammatiken, die wir im nächsten Kapitel behandeln, können das Gewünschte leisten. Übung 3.3 A1 = {0, 1, 2, 3, . . . , 9} L1 = {a1 a2 . . . an | ai ∈ A1 , für 1 ≤ i ≤ n, n ∈ N; a1 = 0, falls n ≥ 2 gilt.} Welche Sprache beschreibt L1 ? Übung 3.4 L sei die Sprache über A = { }, die aus einer primzahligen Anzahl von ’s bestehen. Geben Sie eine mathematische Notation dafür an.
die Grundmenge A∗ eine abzählbar unendliche Menge ist, kann eine beliebige Teilmenge davon höchstens abzählbar unendlich sein.
4 Da
PünktchenNotation
4 Definition unendlicher Mengen 4.1 Muster und formale Grammatiken In Abschnitt 3.4 haben wir erfahren, dass eine Sprache L eine endliche oder im Allgemeinen eine abzählbar unendliche Teilmenge der Wortmenge A∗ über einem bestimmten Alphabet A ist. Die Elemente jeder Sprache sind Wörter aus A∗ . Zur Definition einer bestimmten Sprache findet also ein Auswahlprozess statt.
Auswahlprozess
A∗
Konkrete Wörter aus einzeln herauszugreifen ist zwar unproblematisch, führt jedoch nur zu endlichen Sprachen. Zur Bestimmung unendlicher Sprachen braucht man so etwas wie einen „Auswahlprozessor“, der auf jedes Wort aus A∗ angewandt wird und genau die Wörter in L aufnimmt, die die entsprechende Auswahlbedingung (i.S.v. „Aufnahmebedingung“) erfüllen. Abbildung 4.1 illustriert das Prinzip. Programmiersprachen, die mit streams1 arbeiten können, sind in der Lage, Sprachen nach dieser Vorstellung (als streams) zu implementieren.
..., w4, w3, w2, w1, w0
w erfüllt Bedingung?
w0, w1, w3, w4, w7, ...
A*
L Abbildung 4.1: Auswahlprozess für L ⊆ A∗
Die Auswahlbedingung charakterisiert die Sprache L über A vollständig. Wie könnte eine solche Bedingung formuliert werden? Die schon in Abschnitt 3.4 genannte Idee eines Filters stellt sich als eine Art Schablone oder Muster dar, wie wir dies von Editoren her kennen, s. Abbildung 4.2. Unter Verwendung von Schablonen dieser Art gestaltet sich der Auswahlprozess als Pattern matching (Mustervergleich): Das zu beurteilende Wort w aus A∗ wird mit dem Muster verglichen und wenn es passt in L aufgenommen, ansonsten verworfen. Das Verfahren ist sehr bequem. Leider ist es nicht mächtig genug, um beliebige Sprachen bzw. Sprachklassen zu beschreiben. In Kapitel 7 untersuchen wir die Leistungsfähigkeit solcher Muster. 1 Der
Datentyp stream (Strom) ermöglicht die Arbeit mit potentiell unendlichen Mengen, wie etwa Zahlenfolgen. Eine wichtige Voraussetzung dafür ist, dass die Programmiersprache über das Konzept der verzögerten Evaluation verfügt. Dies gilt für eine Reihe funktionsorientierter Sprachen.
Schablone Muster pattern matching
28
4 Definition unendlicher Mengen
Abbildung 4.2: Muster (reguläre Ausdrücke) in Texteditoren
Regeln
Also suchen wir nach einem weiteren Auswahlprozessor und stoßen auf die Beschreibung einer Sprache durch Regeln, wie in Abschnitt 2.2. Regeln sind (der wichtigste) Bestandteil einer formalen Grammatik. Definition 4.1 Eine (formale) Grammatik G ist ein 4-Tupel G = (N, T, P, s) mit folgenden Eigenschaften: N . . . Menge der Nichtterminale (grammatikalische Variablen) T . . . Menge der Terminale (Alphabetzeichen) N, T sind nichtleere, endliche und disjunkte Mengen, d.h. N ∩ T = 0. / P . . . endliche Menge von Regeln oder Produktionen P = {α → β | α ∈ (N ∪ T )∗ \T ∗ und β ∈ (N ∪ T )∗ } s . . . Start- oder Satz- oder Spitzensymbol, wobei s ∈ N.
Satzform
Im Grammatik-Kontext tritt die Terminalmenge T an die Stelle des Alphabetbegriffs. Die Menge N ∪ T wird häufig als Vokabular V bezeichnet. (N ∪ T )∗ bezeichnet die Menge aller Zeichenketten über dem Vokabular. Dies sind „gemischte Zeichenketten“, die aus beliebig vielen Nichtterminalen und Terminalen in beliebiger Reihenfolge bestehen. Man nennt sie Satzformen2 .
α →β
In der Grammatik-Definition (4.1) wird eine sehr freizügige Regelgestalt α → β
Vokabular
2 Manchmal wird zusätzlich gefordert, dass eine Satzform aus dem Spitzensymbol gemäß Definition 4.3
ableitbar ist. Unsere Definition sieht das nicht vor.
4.1 Muster und formale Grammatiken
29
erlaubt. Auf der rechten Seite von Produktionen (β ) dürfen beliebige Satzformen stehen. Links (α) sind lediglich Folgen von Terminalen ausgeschlossen. Beispiel 4.1 zeigt eine Grammatik mit uneingeschränkter Regelgestalt. Zur Notation haben wir uns an der auf Seite 11 eingeführten BNF orientiert. Beispiel 4.1 G = (N, T, P, s), mit N = {S, A, B} T = { , , } P = {S → AS | S ; S → ; AS → S ; S → } s = S ist eine formale Grammatik i.S.v. Definition 4.1.
Wie in den Regeln der Sprache MiniJavaScript auf S. 11 besitzen die Regeln der Grammatik in Beispiel 4.2 eine besondere Eigenschaft: Sämtliche linken Regelseiten bestehen aus genau einem Nichtterminal. Grammatiken dieser Bauart nennt man kontextfreie Grammatiken, kurz: kfG. Eine fundierte Einordnung dieses Grammatikklasse erfolgt erst in Abschnitt 4.5. Da dieser Begriff jedoch im Folgenden sehr oft benötigt wird, führen wir ihn an dieser Stelle bereits ein. Beispiel 4.2 Wir betrachten die folgende sehr einfache Grammatik: G = (N, T, P, s), mit N = {Number, Digit} T = { , , , , , , , , , } P = {Number → Digit | DigitNumber Digit → | | | | | | | | | } s = Number
Die Unterstützung durch AtoCC ist auf kfG (in BNF, nicht EBNF) beschränkt.
Abbildung 4.3: Regelmenge einer Grammatik für Zahlwörter Computerübung 4.1 Wir verwenden wieder die Grammatik-Editor-Komponente von AtoCC (s. Abbildung 4.3), diesmal allerdings den Grammatik-Tab. Das freie Feld mit der vorbereiteten Zeile 1 erwartet die Regeln der Grammatik. Eigentlich werden diese über die Tastatur eingegeben, für
kontextfreie Grammatik kfG
30
4 Definition unendlicher Mengen
die Grammatik aus Beispiel 4.2 ist aber schon alles vorbereitet: Wählen Sie das kleine Icon rechts neben „Einfügen“. Die entsprechenden Regeln werden eingefügt. An der Notation der Regeln sind allerdings die Pünktchen für die Zahlzeichen bis zu bemängeln. Dies wird durch Umformatierung behoben. Probieren Sie beide Icons neben „Format“ aus und interpretieren Sie die Reaktionen. Im rechten Bereich wird die zugehörige Symbolliste angezeigt. Terminale werden wie Alphabetzeichen dargestellt.
Startsymbol
Computerübung 4.2 Verwenden Sie den Tab „Definition“, um die vollständige Definition der Grammatik wie in Beispiel 4.2 zur Anzeige zu bringen. Sie erkennen, dass die in der Folge der Regeln zuerst angegebene eine besondere Rolle spielt: Das darin links stehende Symbol nimmt AtoCC als das Startsymbol3 der Grammatik. Bis auf diese Vereinbarung zur impliziten Definition des Startsymbols ist die Reihenfolge des Regel-Aufschriebs ohne Belang. Computerübung 4.3 Definieren Sie die Grammatik aus Beispiel 2.2 auf S. 13 in AtoCC (s. Abbildung 4.4). Geben Sie eine Regel für das Startsymbol (hier: Ausdruck) stets als erste ein. Ergänzen Sie die Regeln für Zahl 4 . Bei der Eingabe der Regeln sollten Sie die Symbolliste im Auge haben: Terminale werden implizit definiert und erscheinen in der bekannten Form. Es gilt: Alle Symbole, die nicht auf der linken Seite (mindestens) einer Regel stehen, sind Terminale, ansonsten sind es Nichtterminale. Während der Eingabe kann es zu Umentscheidungen kommen. Innerhalb einer Satzform können die Symbole durch beliebig viele Leerzeichen untereinander getrennt werden. Solche Trenn-Leerzeichen haben keine weitere Bedeutung. Zur Kennzeichnung von Terminalen, insbesondere auch solchen, die ein oder mehrere Leerzeichen enthalten, können einfache Hochkommas verwendet werden. Das Hochkomma selbst darf in keinem Terminal vorkommen.
Abbildung 4.4: Regelmenge einer Grammatik für einfache Ausdrücke
4.2 Ableitung und definierte Sprache Die intuitive Anwendung grammatikalischer Regeln bei der Syntaxanalyse eines Wortes wurde in Abschnitt 2.4 vorgestellt. Definition 4.3 formalisiert die gesammelten Erfahrungen. 3 Auf
die Rolle des Startsymbols kommen wir in Abschnitt 4.2 zu sprechen. von Digit, wie in Abbildung 4.4 zu sehen, hätte sich natürlich auch Ziffer angeboten. Die Wahl der Nichtterminale obliegt unserer freien Entscheidung. Soll ein verwendetes Nichtterminal umbenannt werden, muss dies konsequent an allen Stellen geschehen, wo es auftritt.
4 Anstelle
4.2 Ableitung und definierte Sprache
31 ∗
Wir beginnen mit der Einführung zweier Relationen ⇒G und ⇒G .
∗
⇒ G ⇒G
Definition 4.2 Mit u ∈ (N ∪T )∗ \T ∗ und v ∈ (N ∪T )∗ stehen u und v in Relation ⇒G , d.h. u ⇒G v, und man sagt: „u geht unter G unmittelbar über in v“, falls u und v die folgenden Formen besitzen: u = xyz, v = xy z, x, z ∈ (N ∪ T )∗ und (y → y ) ∈ P. ∗ Für d ⇒G e ⇒G . . . ⇒G f schreibt man verkürzend d ⇒G f . Es ist üblich, das an den Doppelpfeil angehängte G wegzulassen und auch die Sprechweise etwas zu entschärfen: „u geht unmittelbar über in v“.
α steht in Relation ⇒ mit β , wenn es in α (mindestens) eine Teilzeichenkette gibt, die mit der linken Seite (mindestens) einer Regel aus P übereinstimmt. ∗
Unter Verwendung von ⇒ bzw. ⇒ kann nun der Auswahlprozess für jedes Wort w aus A∗ gemäß G als Ableitung5 exakt beschrieben werden. Definition 4.3 Eine Folge von Satzformen (α0 , α1 , . . . , αn ), mit α0 = s, αn = w, w ∈ T ∗ und s ⇒ α1 ⇒ α2 ⇒ . . . ⇒ w, heißt Ableitung von w.
Wörter, die zu der von G definierten Sprache L(G) gehören, müssen vom Startsymbol aus ableitbar sein. Definition 4.4 ∗ Die durch G definiertea Sprache L(G) ist L(G) = {w | w ∈ T ∗ und s ⇒G w}. a Oft
wird dies auch „erzeugte Sprache“ oder „erzeugbare Sprache“ genannt.
Computerübung 4.4 Verwenden Sie die Grammatik für die Sprache der Zahlwörter aus Beispiel 4.2 und leiten ab. Ein Wort kann eingegeben werden, wenn Sie den Tab „Ableitung“ ausSie das Wort wählen. Das Ergebnis ist in Abbildung 4.5 zu sehen. Die im rechten Bereich protokollierte Zahl, Digit, ) schreibt man in Ableitung (Zahl, DigitZahl, Zahl, DigitZahl, der Form: Zahl ⇒ Digit ⇒ . Zahl ⇒ Digit Zahl ⇒ Zahl ⇒ Digit Zahl ⇒ Computerübung 4.5 der SpraErzeugen Sie die beiden Ableitungsbäume aus Abschnitt 2.4 für das Wort che einfacher arithmetischer Ausdrücke unter Verwendung der mehrdeutigen Grammatik aus Beispiel 2.2. AtoCC unterstützt Sie bei der Auswahl aus mehreren Ableitungsbäumen. 5 Manchmal
wird anstelle von Ableitung der Begriff „Herleitung“ verwendet und man spricht von einer „generativen Grammatik“.
Ableitung
32
4 Definition unendlicher Mengen
Abbildung 4.5: Ableitung des Zahlwortes
4.3 Nichtdeterminismus des Ableitungsprozesses Wie bereits erwähnt, beschränken wir uns zunächst auf kfG.
Linksableitung
In Computerübung 4.4 wurde AtoCC verwendet, um für ein Zahlwort, wie , den zugehörigen Ableitungsbaum zu erzeugen. Das Ergebnis in Abbildung 4.5 zeigt den Baum, der unter Verwendung einer Standardeinstellung von AtoCC entstand. Das kleine Symbol mit dem „L“ weist darauf hin, dass die so genannte Linksableitung ausgeführt wurde. Die Verabredung lautet: In jeder „abzuleitenden“ Satzform wird das am weitesten links stehende Nichtterminal im nächsten Schritt (durch eine entsprechende rechte Regelseite) ersetzt. Das Wort entsteht also von links her. Computerübung 4.6 Schalten Sie auf Rechtsableitung um und leiten Sie
Rechtsableitung
erneut ab.
Wir erkennen, dass bei der Rechtsableitung (Zahl, . . . , ), d.h. Zahl ⇒ Digit Zahl ⇒ Digit Digit Zahl ⇒ Digit Digit Digit ⇒ Digit Digit ⇒ Digit ⇒ die gleichen Regeln verwendet wurden, wie bei der Linksableitung, allerdings in anderer Reihenfolge. Es entsteht der gleiche Baum, wie in Abbildung 4.5. Ob das allgemeingültig ist? Im Schrittbetrieb kann man erkennen, dass die optisch identischen Bäume dynamisch verschieden sind, d.h. unterschiedliche „Entstehungsgeschichten“ haben. Bei der Rechtsableitung entsteht das Zahlwort von rechts her. Computerübung 4.7 Führen Sie den Animationsschieber ganz nach links an den Anfang der Ableitung und starten Sie dann den Schrittbetrieb (sowohl für Links- als auch für Rechtsableitung). Dabei kann man jederzeit den Pause-Knopf drücken.
Diese heile „Links/Rechts-Ableitungswelt“ verdeckt einige sehr wichtige Fragen: 1. Welches Nichtterminal wird im nächsten Schritt ersetzt: Das am weitesten links/rechts stehende oder gibt es weitere Möglichkeiten?
4.3 Nichtdeterminismus des Ableitungsprozesses
33
2. Welche Regel sollte zur Substitution des in einer Satzform ausgewählten Nichtterminals verwendet werden, wenn es mehrere Regeln für dieses Nichtterminal gibt? Die erste Frage ist in der Tat trivial: Ein Blick auf den Ableitungsbaum in Abbildung 4.5 macht rasch klar, dass sich durch Änderung der Ersetzungsreihenfolge der betreffenden Nichtterminale keine neuen Substitutionsmöglichkeiten ergeben. Lässt man die anzuwendenden Regeln unverändert, ergibt sich bei kfG stets das gleiche Ergebnis. Ganz anders sieht es bei nicht-kfGs, wie in Beispiel 4.1 auf S. 29, aus. Dort kann es durchaus zu neuen Konstellationen für Satzformen kommen, auf die dann linke Regelseiten der Grammatik passen, die bei einem veränderten Ableitungsprozess nicht anwendbar wären. Für kfG ist es also sinnvoll, generell nur Linksableitungen zu verwenden. Definition 4.5 Eine Ableitung für ein Wort w gemäß einer kfG heißt Linksableitung genau dann, wenn es für jedes i = 0, 1, . . . , n − 1 eine Produktion Yi → Qi derart gibt, dass γi = αiYi βi , γi+1 = αi Qi βi , wobei X = γ0 ⇒ γ1 ⇒ . . . ⇒ γn = w, s = X, n ≥ 1, βi ∈ (N ∪ T )∗ gelten und αi ∈ T ∗ . Satz 4.1 Bei kontextfreien Sprachen, kurz: kfS, gibt es zu jedem Ableitungsbaum genau eine Linksableitung. Beweis Als Beweis gelte eine Argumentation, die sich aus den vorangegangenen Bemerkungen ergibt. Ein strenger Beweis benötigt eine formale Definition des Begriffes Ableitungsbaum, worauf wir hier jedoch verzichten wollen. Beispiel 4.3 Gegeben sei die Grammatik G = (N, T, P, s), mit s = S, T = {a, b, c, −}, N = {A, S} und 1
2
3
4
5
P = {S → A, S → S − A, A → a, A → b, A → c}. Mit Hilfe der Ziffern über den Pfeilen der fünf Produktionen werden wir uns im Folgenden auf die jeweilige Regel beziehen. Wegen S ⇒ S − A ⇒ S − A − A ⇒ A − A − A ⇒ a − A − A ⇒ a − b − A ⇒ a − b − b gilt a − b − b ∈ L(G). Die gewählte Ableitung ist (S, S − A, S − A − A, A − A − A, a − A − A, a − b − A, a − b − b), die zugehörige Folge der Regelanwendungen lautet (2, 2, 1, 3, 4, 4). Ebenso hätte man S ⇒ S − A ⇒ S − b ⇒ S − A − b ⇒ A−A −b ⇒ A −b −A ⇒ a−b−b nehmen können, was der Folge (2, 4, 2, 1, 4, 3) entspricht. Rechtsableitung hätte noch (2, 4, 2, 4, 1, 3) hinzugefügt. Diese drei Ableitungen unterscheiden sich lediglich durch die Reihenfolge ihrer Elemente. Insfern ist es sinnvoll, für sie alle einen Repräsentanten, nämlich (2, 2, 1, 3, 4, 4), auszuwählen. (Natürlich beschreibt nicht jede Permutation dieser Elemente eine mögliche Ableitung.)
Auf diese Weise kann man jedem Ableitungsbaum genau ein Wort aus L(G) zuordnen. Ob diese Zuordnung auch umgekehrt besteht, hängt von der Antwort auf die zweite Frage ab.
Linksableitung
34
4 Definition unendlicher Mengen
Computerübung 4.8 Zur Klärung der zweiten Frage kehren wir zur Grammatik in Beispiel 2.2 auf S. 13f zurück. Stellen Sie in AtoCC „Linksableitung“ ein und erzeugen Sie nochmals den Baum . Anschließend führen Sie die Ableitung um vier Schritte zurück. Hierfür klicken für Sie das entsprechende Symbol in der AtoCC-Grammatik-Editor-Komponente wiederholt an. Im nächsten Schritt ist das Nichtterminal Zahl zu ersetzen. Wählen Sie nun die nächste anzuwendende Produktionsregel manuell – wieder mit dem entsprechenden Knopf. Tool tips sind dabei behilflich. Nach der „Methode des scharfen Hinsehens“ ist klar, dass nur die Regel Zahl → Digit zielführend ist. Wir wählen jedoch Zahl → Digit Zahl und sind fortan durch Linksableitung zu erzeugen. nicht mehr in der Lage, das Zielwort
Abbildung 4.6: Ableitung des Zahlwortes
Sackgasse
Backtracking
Earley
Abbildung 4.6 zeigt den Einstieg in eine Sackgasse: Der Ableitungsprozess befindet sich am Ende einer solchen, wenn der Ableitungsprozess nicht fortgesetzt werden kann, d.h. wenn auf die aktuelle Satzform keine Regel anwendbar ist. Das Vorliegen einer Sackgasse bedeutet im Allgemeinen nicht, dass das Eingabewort nicht akzeptiert wird. Wir müssen vielmehr die jeweils zuletzt vorgenommene Ersetzungsentscheidung revidieren und – falls möglich – eine alternative Ersetzung durchführen. Dann sieht man weiter. Dieses Zurückkehren an den letzten Entscheidungspunkt nennt man Backtracking. Erst wenn alle Möglichkeiten ausgeschöpft sind und sich darunter keine Ableitung für das zu analysierende Wort w ergab, ist nachgewiesen, dass w nicht zur Sprache gehört. Wir stellen also fest, dass der Ableitungsprozess – trotz verabredeter Linksableitung – bei spontaner Auswahl aus einer Menge prinzipiell anwendbarer Regeln in Sackgassen geraten kann. Diese Irrwege werden durch Backtracking korrigiert, was natürlich sehr aufwendig ist. Statt dieser Versuch-Irrtum-Strategie wäre es besser, die als nächste erfolgreich anzuwendende Regel zu berechnen. Der in AtoCC eingebaute modifizierte Earley-Algorithmus verfügt über diese „Voraussagefähigkeit“. Damit kann zusammenfassend festgestellt werden, dass man zwar jedem Ableitungsbaum genau eine Linksableitung zuordnen kann, aber nicht jede Linksablei-
4.4 Mehrdeutigkeit kontextfreier Grammatiken
35
tung für ein bestimmtes Wort besitzt genau einen Ableitungsbaum. Mit anderen Worten: Es gibt kfG mit der Eigenschaft, dass es für manche Wörter der zugehörigen Sprache mehrere strukturell verschiedene Ableitungsbäume gibt.
4.4 Mehrdeutigkeit kontextfreier Grammatiken Wie wir in Beispiel 2.2 (S. 13) bereits gesehen haben, besitzt das Wort wenigstens zwei voneinander verschiedene Ableitungsbäume, s. Abbildung 2.2. Solche Grammatiken heißen mehrdeutig.
mehrdeutige Grammatik
Definition 4.6 Eine kfG ist mehrdeutig, wenn es ein Wort in L(G) gibt, das (mindestens) zwei Linksableitungen besitzt. Andernfalls ist die Grammatik eindeutig. Eine kfS L ist eindeutig, wenn es (mindestens) eine eindeutige kfG G, mit L = L(G), gibt. Ansonsten ist L (inhärent) mehrdeutig. Beispiel 4.4 Gegeben sei die Grammatik G = (N, T, P, s), mit s = S, T = { , , }, N = {A, B, S} und 1
2
3
4
P = {S → B, S → A , A →
, B→
}. 1
4
gibt es zwei Linksableitungen: S ⇒ B ⇒
Für das Wort
S
a
3
S
B
b
2
und S ⇒ A ⇒
c
A
c
a
b
Abbildung 4.7: Ableitungsbäume für das Wort
Dieses Beispiel sollte uns an die enge Verbindung von AST (bei prinzipiell erfolgreicher Syntaxanalyse) und Semantik erinnern, wie sie uns im Gebiet des Compilerbaus entgegentritt. Mehrdeutige Grammatiken sind grundsätzlich unerwünscht bzw. unbrauchbar. Leider gibt es keinen Universalalgorithmus, der für eine beliebige kontextfreie Sprache entscheidet, ob sie mehrdeutig ist. Im Einzelfall gelingt es aber, solche Mehrdeutigkeiten durch Angabe einer äquivalenten Grammatik zu eliminieren. Definition 4.7 Zwei Grammatiken G1 und G2 heißen äquivalent, geschrieben: G1 ∼ = G2 , wenn die zugehörigen erzeugbaren Sprachen übereinstimmen, d.h. wenn L(G1 ) = L(G2 ).
äquivalente Grammatik
36
inhärent mehrdeutig
4 Definition unendlicher Mengen
Allerdings kann nicht zu jeder mehrdeutigen kfG eine äquivalente eindeutige angegeben werden. Gelingt dies nicht, spricht man von inhärenter Mehrdeutigkeit. Beispielsweise ist L = {
i j k
| i = j oder j = k} eine inhärent mehrdeutige kfS.
Computerübung 4.9 Untersuchen Sie die folgende Grammatik auf Mehrdeutigkeit. G = {N, T, P, s}, mit s = X, T = { , }, N = {X} und P = {X → | | XX}.
dangling else
Computerübung 4.10 Ein typisches Beispiel einer mehrdeutigen kfG ist das sog. „dangling else“-Problem bei Pascal-ähnlichen Sprachen, s. auch Abschnitt 9.6. , , , , , }, G = {N, T, P, s}, mit s = statement, T = { , expression statement | N = {statement, expression} und P = {statement → expression statement statement | | ; expression → | } Experimentieren Sie mit verschiedenen Eingabewörtern. Die Frage ist: Zu welchem der beigehört das in dem Ausdruck ? Finden Sie den eine Möglichkeit, um durch möglichst geringfügige Modifikationen von Regeln in P das zu beheben. „lose, baumelnde“ Computerübung 4.11 Gegeben sei folgende Grammatik: G = ({E, T, F}, { , , , , }, P, E), und P = {E → T | E T, T → F | T F, F → | E }. Stellen Sie fest, ob zu der durch G definierten Sprache L(G) gehören. Geben Sie – falls möglich – zugehörige Ableitungsbäume an. Stellen Sie fest, ob G eine mehrdeutige Grammatik ist. Wenn man bei E, T bzw. F an expression, term bzw. factor denkt, wird klar, dass diese Grammatik die Sprache der korrekt geklammerten arithmetischen Ausdrücke beschreibt. Erzeugen Sie weitere Wörter aus L(G).
4.5 C HOMSKY-Hierarchie Bisher haben wir den Begriff „kontextfreie Grammatik“ verwendet ohne präzise zu wissen, was er bedeutet und wie es zu dieser Bezeichnung kam. Die in folgender Tabelle zusammengestellte Klassifikation formaler Grammatiken gibt Auskunft. Hier findet man kfG und auch deren Definition. C HOMSKYHierarchie
C HOMSKY6 hat etwa 1956 die formalen Grammatiken in 4 Klassen (Typen) eingeteilt und damit hierarchische Sprachklassen beschrieben. Auf den ersten Blick sieht das recht willkürlich aus. Mit zunehmender Beschäftigung mit dem Gegenstand wird deutlich, wie fundamental diese Typisierung für die Theorie der formalen Sprachen und die Automatentheorie ist. Der klassifizierende Unterschied zwischen diesen Typen liegt in der Regelgestalt. Wir gehen von der allgemeinsten7 Form, wie sie bereits in der GrammatikDefinition 4.1 angegeben wurde, aus: 6 Noam
Chomsky, geb. 1928 in der Literatur hierfür verwendeten Definitionen differieren ein wenig. In allen Fällen werden aber die unten angegebenen Sprachklassen beschrieben.
7 Die
4.5 C HOMSKY-Hierarchie
37
α → β | α ∈ (N ∪ T )∗ \T ∗ und β ∈ (N ∪ T )∗ . Typ 0 1
2 3
Bezeichnung Regelgestalt unbeschränkt keine Einschränkung kontextsensitiv wie Typ 0 und zusätzlich: längenmonotone Regeln, d.h. |α| ≤ |β |; Ausnahme: s → ε darf vorkommen, wenn s in keiner Regel auf der rechten Seite steht kontextfrei wie Typ 1 und zusätzlich: α ∈ N; Ausnahme: Regeln der Form α → ε sind erlaubt regulär wie Typ 2 und zusätzlich: Neben Regeln der Form α → x gibt es entweder nur linkslineare, d.h. α → Ax, oder nur rechtslineare, d.h. α → xA, mit x ∈ T und A∈N
Die zugehörigen Sprachklassen heißen – wie die Grammatiktypen – unbeschränkt (uS), kontextsensitiv (ksS), kontextfrei (kfS) und regulär (rS). Die von C HOMSKY definierten Sprachklassen bilden eine Hierarchie:
L L
Typ 3
LTyp 2
L Typ 1
Typ 0
Abbildung 4.8: C HOMSKY-Hierarchie für Sprachklassen
Für die in Abbildung 4.8 dargestellten Sprachklassen gilt: LTyp 3 ⊂ LTyp 2 ⊂ LTyp 1 ⊂ LTyp 0 . Man beachte, dass die Elemente dieser Mengen Sprachen, also selbst Mengen, sind. Deshalb haben wir anstelle von L ein stilisiertes L verwendet. Übung 4.1 Ergänzen Sie die Klasse der endlichen Sprachen in Abbildung 4.8.
Sprachklassen
38
unbeschränkt
4 Definition unendlicher Mengen
Eine uS nennt man auch Phrasenstrukturgrammatik oder Semi-Thue8 -System.
kontextfrei
Die Bezeichnungen „kontextfrei“ vs. „kontextsensitiv“ unterstützt folgende Vorstellung: Ein Nichtterminal A einer kfG kann in sämtlichen Satzformen u1 Av1 , u2 Av2 und u3 Av3 durch β ersetzt werden, wenn die betrachtete Grammatik über eine Regel A → β verfügt. Die Substitution geschieht offenbar ungeachtet des Kontextes, also kontextfrei.
kontextsensitiv
Die Begriffsbildung ksG bezieht sich also auf eine bestimmte Darstellungsform (Normalform) einer Typ-1-Grammatik und ist auch nur in diesem Zusammenhang verständlich. Ferner ist es leicht, eine allgemeingültige Transformation anzugeben, die eine kfG in eine äquivalente ksG, mit eben dieser Regelgestalt, überführt. Bei kontextsensitiven, oder kontextabhängigen Ersetzungen, ist die Substitution von A nur dann möglich, wenn A in einer ganz bestimmten Umgebung (Kontext) steht, z.B. unmittelbar nach u1 und unmittelbar vor v1 . u1 Av1 ⇒ u2 β v2 gilt dann und nur dann, wenn die Regel u1 Av1 → u2 β v2 existiert. Beispiel 4.5 Ein (in einschlägiger Literatur reichlich strapaziertes) Beispiel einer ksG, die nicht kontextfrei ist, ist G = ({B,C, S}, { , , }, P, S) mit P = {S → SBC | BC, CB → BC, B → , B → , C → , C → }. Die zugehörige Sprache ist L(G) = { n n n | n ∈ N, n ≥ 1}. Beispiel 4.6 G = ({A, B}, { , , }, P, A) mit P = {A → AB | B , B → B , B → Die zugehörige Sprache ist ebenfalls L(G) = { n n n | n ∈ N, n ≥ 1}. Beispiel 4.7 G = ({S, A,C}, { , , }, P, S) mit P = {S → A | }. Die zugehörige Sprache ist ebenfalls L(G) = {
, B→
}.
, A → A C | C, C → C, C → | n ∈ N, n ≥ 1}.
n n n
Damit haben wir schon drei (echt) verschiedene Grammatiken, die ein und dieselbe Sprache definieren. Diese Grammatiken sind also äquivalent.
4.6 ε-Sonderregelungen
Längenmonotonie
Etwas problematisch ist die Verwendung sog. ε-Regeln der Form: α → ε in kontextsensitiven- (hier nur für α = s), kontextfreien- und regulären Grammatiken. Sie verstoßen gegen die Längenmonotonie, die für ksG und damit auch kfG sowie rG gefordert ist, s. Abschnitt 4.5. Diese fordert für alle Regeln in P, dass die rechte Seite (β ) mindestens genauso lang ist, wie die linke (α), also |α| ≤ |β |. Wegen |α| > 0 aber |β | = |ε| = 0 trifft dies bei α → ε nicht zu. Dadurch wäre es auch nicht möglich, das leere Wort abzuleiten, d.h. ε ∈ L(G), denn dafür ist s → ε definitiv erforderlich. Der folgende Satz rechtfertigt die o.g. Ausnahme in der Regelgestalt. 8 Norwegischer
Mathematiker A. T HUE, 1863-1922
4.6 ε-Sonderregelungen
39
Satz 4.2 Gegeben ist eine ε-freie ksG G = (N, T, P, s). Gesucht ist eine äquivalente ksG G = (N , T , P , s ) mit L(G ) = L(G) ∪ {ε}. Beweis Die erforderliche Grammatiktransformation für kfG und analog für ksG geschieht folgendermaßen9 :
ε ∈ L(GTyp1,2 )
1. Wähle ein noch nicht in N vorhandenes Nichtterminal s als Spitzensymbol von G . 2. Ergänze die Regeln s → s und s → ε. Aus G entsteht die Grammatik G = (N ∪ {s }, T, P ∪ {s → s | ε}, s ). Will man ε ∈ L(G) auch für reguläre Grammatiken erzielen, müssen wir die stark eingeschränkte Regelgestalt sorgfältig beachten. Eine Regel der Form s → s, wie oben verwendet, ist für Typ-3-Grammatiken nicht erlaubt. Folgende Transformation ist zielführend:
ε ∈ L(GTyp3 )
1. Wähle ein noch nicht in N1 vorhandenes Nichtterminal s als Spitzensymbol von G2 . 2. Ergänze für alle Regeln s → β in P1 die Regeln s → β sowie s → ε. Wir heben hervor, dass durch diese Transformation der jeweilige Typ der Grammatik unverändert bleibt. Beispiel 4.8 Durch simples Ergänzen der Regel s → ε gelingt die gewünschte Transformation im Allgemeinen nicht, wie dieses Beispiel zeigt. G2 wurde aus G1 durch pures Hinzufügen der Regel K → ε gewonnen. G1 = ({K, H}, {i, j, l, r}, {K → iH j, H → lK | r}, K). G2 = ({K, H}, {i, j, l, r}, {K → iH j | ε, H → lK | r}, K). Offensichtlich gehört das Wort il j, wegen K ⇒ iH j ⇒ ilK j ⇒ il j, zu L(G2 ) jedoch nicht zu L(G1 ). Dies entspricht nicht unserer Absicht. G2 erfüllt die geforderte Eigenschaft nicht! Die Lösung unseres obigen Beispiels ist daher G = ({K, H, Q}, {i, j, l, r}, {Q → K | ε, K → iH j, H → lK | r}, Q). Das Wort il j gehört weder zu L(G1 ) noch zu L(G ).
Nun wenden wir uns dem „umgekehrten“ Problem zu: Wie kann man für eine kfG ε-Freiheit herstellen? KfG mit ε-freien Regeln werden in Sätzen der folgenden Kapitel öfters vorausgesetzt. Dann wird immer wieder auf den folgenden Satz und das im Beweis angegebene Transformationsverfahren verwiesen. Satz 4.3 Zu jeder kfG G = (N, T, P, s) mit ε-Regeln der Form A → β , mit A ∈ N und β ∈ (N ∪ T )∗ , gibt es eine äquivalente kfG G = (N , T , P , s ) ohne ε-Regeln (ggf. bis auf s → ε).
9 Ein
alternativer Vorschlag lautet: 1. Benenne das Spitzensymbol s in allen Regeln in P um zu s , wobei s ∈ N. 2. Ergänze die beiden Regeln s → s | ε. In diesem Falle wird das Spitzensymbol von G in G beibehalten, d.h. s = s.
Achtung: So geht es nicht!
40
Herstellung ε-freier Regeln
4 Definition unendlicher Mengen
Beweis Die Hauptidee dieses (konstruktiven) Beweises besteht darin, ein Verfahren anzugeben, das alle möglichen ε-Ersetzungen in den betreffenden Produktionen „per Hand“ ausführt, sodass sich die ε-Regeln erübrigen. Hierfür müssen zunächst alle Nichtterminale Ai ∈ N bestimmt werden, die in beliebig vielen Schritten zu ε abgeleitet werden können: Beginne mit Nε = {Ai }, mit Ai → ε in P. Ergänze im nächsten Schritt A in Nε , wenn A → A1 A2 . . . Ak in P, wobei k ≥ 1, Ai ∈ N und für alle Ai (1 ≤ i ≤ k) gilt Ai ∈ Nε . Das Verfahren stoppt, wenn sich im nächsten Schritt keine weitere Veränderung in Nε ergibt. Da N endlich ist, terminiert das Verfahren. Anschließend entfernen wir alle Regeln der Gestalt Ai → ε aus P. Für jede Regel B → β Ai γ ∗ in P, mit Ai ⇒ ε, d.h. Ai ∈ Nε , ergänzen wir B → β γ. β und γ sind Satzformen, von denen höchstens eine das leere Wort bezeichnet. Die ursprünglichen Regeln B → β Ai γ in P bleiben erhalten.
Damit ist nun gezeigt, dass die Verwendung von ε-Regeln in kfG eher kosmetische Wirkung hat.
Simultanableitung
Beispiel 4.9 G1 = ({X, B, K}, {a, c}, {X → aB, B → cB | K, K → a | ε}, X). Die zu G1 äquivalente Grammatik G1 ohne ε-Regeln ergibt sich nach der oben (im Beweis) beschriebenen Transformation: G1 = (N , T , P , s ), mit N = N = {X, B, K}, T = T = {a, c}, P = {X → aB | a, B → cB | c | K, K → a}, s = s = X. Didaktischer Hinweis 4.1 Es gibt noch ein anderes Verfahren, um für eine kfG mit ε-Regeln die Menge Nε zu bestimmen. Hierzu bilden wir für jedes Nichtterminal alle möglichen Folgesatzformen, die sich durch Anwendung aller passenden Regeln auf die jeweilige Satzform ergeben. Das Verfahren verhält sich also wie ein Simultan-Schachspieler, der gleichzeitig mehrere Partien gegen verschiedene Gegner spielt. Auf unseren Fall angewandt bedeutet das: Für jede Satzform werden alle möglichena Regeln angewandt. In Anlehnung an das Schachspiel kann man das beschriebene Ableitungsverfahren als Simultanableitung bezeichnen. In der modernen Terminologie der Informatik spricht man wohl besser von einem Brute-force-Ansatz. Schon nach wenigen Schritten können gigantische Mengen entstehen und es wird schwierig, ja sogar unmöglich, diesen Prozess für große Schrittanzahlen praktisch auszuführen. Die Simultanableitungen beginnen beim zu untersuchenden Nichtterminal A, genauer: mit ∗ {A}. Wie weit müssen sie fortgesetzt werden, um entscheiden zu können, ob A ⇒ ε gilt oder nicht? Da bei Simultanableitungen in jedem Schritt alle in einer Satzform enthaltenen Nichtterminale durch die jeweiligen rechten Regelseiten ersetzt werden, ergibt sich der längste mögliche „Weg zu ε“, wenn es für N = {A1 , A2 , . . . , An } die Ableitung A = A1 ⇒ A2 ⇒ . . . ⇒ An ⇒ ε gibt. Es sind also höchstens n = |N| Simultanableitungsschritte nötig. Damit ist gesichert, dass das Verfahren stets terminiert. Wegen N = {X, B, K} reichen in Beispiel 4.9 |N| = 3 Simultanableitungsschritte aus, um die zu ε ableitbaren Nichtterminale zu bestimmen. a Es
ist also durchaus möglich, dass sich aus einer Satzform mehrere Folgesatzformen ableiten lassen.
4.7 Das Wortproblem
41
Übung 4.2 Die nachfolgende Grammatik für mathematische Klammerausdrücke enthält ε-Regeln. Formen Sie die Grammatik G so um, dass eine ε-freie Grammatik G entsteht. G = (N, T, P, s), mit N = {S, E, T, Zahl, Ziffer} T = { , , , , , , , , , , , , } P = { S → E | Zahl E→ST T→ S|ε Zahl → Ziffer WeitereZiffern WeitereZiffern → Ziffer WeitereZiffern | ε Ziffer → | | | | | | | | | } s = S Computerübung 4.12 Die durch die in Übung 4.2 erstellte ε-freie Grammatik G beschriebene Sprache L(G ) soll um das leere Wort ε zu L(G ) erweitert werden. gehört nicht zur Sprache L(G ). Sollte es Testen Sie Ihr Ergebnis in kfGEdit. Das Wort mit Ihrer Grammatik ableitbar sein, dann überprüfen Sie erneut, ob Sie das Verfahren zum Hinzufügen von ε richtig angewandt haben. Übung 4.3 Transformieren Sie die im Folgenden gegebene kfG in eine ε-freie. G = (N, T, P, s) mit N = {variable, buchstabe, name, zeichen}, T = { }, P = { variable → buchstabe name, name → buchstabe name | zeichen name | ε buchstabe → | | . . . | | | | . . . | , zeichen → | | . . . | | | | }, s = variable.
4.7 Das Wortproblem In Abschnitt 4.3 haben wir kfG betrachtet und die Frage aufgeworfen, ob das dort beschriebene Entscheidungsverfahren (Linksableitung und ggf. Backtracking bei Sackgassen) allgemeingültig ist, also ob es auch – sagen wir vorsichtig – für ksG gilt. Da auf der linken Regelseite ksG im Allgemeinen mehrere Symbole stehen, kann es schon mal keinen Ableitungsbaum geben. Gibt es überhaupt eine Möglichkeit, die Frage w ∈ L(G) für ksS und uS zu entscheiden? Das Wesen eines Entscheidungsproblems besteht darin, dass der dieses Problem bearbeitende Prozess nach endlicher Zeit stoppt und im Ergebnis genau einen
Entscheidungsproblem
42
4 Definition unendlicher Mengen
Wahrheitswert (true oder f alse) liefert. In unserem Falle erhält das gesuchte Entscheidungsverfahren ein Wort w aus T ∗ und eine Grammatik G der betrachteten Klasse als Eingabe. Genau dies sind die beiden Eingaben, die wir kfGEdit (in AtoCC) gegeben haben, um zu entscheiden, ob ∈ L(G) oder ∈ L(G), s. ?
Abbildung 4.5 auf S. 32. kfG Edit entscheidet also die Frage: w ∈ L(G). Wir nennen dies das Wortproblem für kfG. Abbildung 4.9 zeigt den Sachverhalt.
w ∈ T∗
G
P : w ∈ L(G)?
true/ f alse Abbildung 4.9: Wortproblem
Definition 4.8 Das (allgemeine) Wortproblem besteht aus der Frage nach der Existenz eines allgemeingültigen Entscheidungsverfahrens, das für jedes beliebige Wort w und jede beliebige Grammatik G in endlicher Zeit feststellt, ob w ∈ L(G) oder w ∈ L(G).
Wortproblem Um in endlich vielen Schritten festzustellen, ob ein Wort w ∈ T ∗ zur Sprache L(G) gehört oder nicht, ist zu prüfen, ob w aus dem Spitzensymbol s der betrachteten Grammatik G über beliebig viele Schritte abgeleitet werden kann. Mittels Simultanableitung, wie im didaktischen Hinweis 4.1 auf S. 40 ausführlich beschrieben, werden aus jeder Satzform einer Satzformenmenge Si alle möglichen Folgesatzformen erzeugt. Diese bilden zusammen10 mit Si die Menge Si+1 , bis auf folgende Ausnahmen: • Wenn die betrachtete Satzform ausschließlich aus Terminalen besteht und mit w übereinstimmt, antwortet das Entscheidungsverfahren mit true und wird beendet. • Wenn die betrachtete Satzform ausschließlich aus Terminalen besteht, jedoch nicht mit w übereinstimmt, wird diese Satzform nicht in Si+1 übernommen. Sie wäre eine (für die Ableitung von w) aussichtslose Kandidatin. • Wenn die betrachtete Satzform eine Länge besitzt, die größer als n = |w| ist, wird diese Satzform nicht in Si+1 übernommen. Aufgrund der für ksG 10 Das
Archivieren früherer Satzformen ist notwendig, um ggf. Probleme bei zyklischen Ableitungen ∗ der Form α ⇒ α auszuschließen.
4.7 Das Wortproblem
43
geforderten Längenmonotonie wäre sie eine (für die Ableitung von w) aussichtslose Kandidatin. Das Verfahren beginnt mit S0 = {s}, wobei s das Spitzensymbol der betrachteten Grammatik bezeichnet, und endet • entweder wenn w ∈ Sk , dann ist die Ausgabe des Algorithmus’ true, s.o., • oder wenn Sk+1 = Sk , d.h., es sind keine neuen Satzformen ableitbar. Dann lautet die Ausgabe des Algorithmus’ f alse. Da es in (N ∪ T )∗ nur endlich viele Satzformen gibt, deren Länge höchstens gleich n = |w| ist, muss dieses Verfahren auch für w ∈ L(G) terminieren, denn es gibt ein solches k. Man beachte, dass das k keinesfalls durch n beschränkt ist. k wird im Allgemeinen sogar sehr groß sein: Betrachtet man nämlich die Anzahl der Satzformen11 mit Längen kleiner oder gleich n, die über (N ∪ T )∗ mit |N ∪ T | = m gebildet werden können, so ergibt sich n
∑ mj =
j=0
mn+1 − 1 , m ≥ 2. m−1
Hierin steht n im Exponenten, was auf gigantisches Wachstum hinweist. Aus theoretischer Sicht ist jedoch alles klar: Das Verfahren ist effektiv, d.h., es ist algorithmisch beschrieben, und die einzige Frage ist, für welche Grammatik-Typen es terminiert (d.h. nach endlich vielen Schritten stoppt). Beispiel 4.10 Wir verwenden die Grammatik G = ({A, B}, { , , }, P, A) mit P = {A → AB | B , B → B , B → , B → } aus Beispiel 4.6 mit L(G) = {an bn cn | n ∈ N, n ≥ 1}. , |w| = 6. Die Folge der Satzformenmengen erDas zu untersuchende Wort ist w = gibt sich wie folgt: S0 = {A} S1 = {A, aABc, aBc} (((, aaBcBc, }, da |aaABcBc| = 7 > |w| und abc = w aaABcBc abc S2 = {A, aABc, aBc, aBc, ( S3 = {A, aABc, aBc, aaBcBc, aabcBc, aaBBcc}, S4 = {A, aABc, aBc, aaBcBc, aabcBc, aaBBcc, aabBcc}, S5 = {A, aABc, aBc, aaBcBc, aabcBc, aaBBcc, aabBcc, aabbcc} und Stopp, da aabbcc ∈ S5 . Übung 4.4 Zeigen Sie, dass das Wort acb nicht in L(G), mit G = ({A, B}, { , , }, P, A) mit P = {A → AB | B , B → B , B → , B → }, enthalten ist.
Für C HOMSKY-Typ-0-Grammatiken versagt dieses Verfahren. Aufgrund der fehlenden Längenmonotonie ist es grundsätzlich möglich, aus Satzformen mit einer Länge größer als |w| das Wort w abzuleiten. Dadurch kann die Menge der bildbaren Satzformen, aus denen das vorgegebene Wort abgeleitet werden kann, nicht beschränkt werden. 11 Das
heißt natürlich nicht zwangsweise, dass alle diese Satzformen in den Si -Mengen auch wirklich vorkommen müssen.
44
4 Definition unendlicher Mengen
Beispiel 4.11 Wir betrachten die folgende unbeschränkte Grammatik G = (N, T, P, s) mit N = {A, B,C}, T = { , }, P = { A → BC | A, B → CB CBaC → } s = A. gehört offensichtlich zur Sprache L(G), wegen A ⇒ A ⇒ BC ⇒ Das Wort CB C ⇒ aba. Es hat die Länge 3. Die zu dessen Ableitung verwendeten Satzformen BC (Länge 4 > 3) und CB C (Länge 6 > 3) wären von dem oben beschriebenen Verfahren als aussichtslose Kandidatinnen verworfen worden. Folglich hätte das Ergebnis nicht ∈ L(G) lauten können.
Zusammenfassend stellen wir fest, dass die Längenmonotonie der Grammatik das entscheidende Kriterium für das Terminieren des beschriebenen Verfahrens ist. Satz 4.4 Das Wortproblem ist für Typ-1,2,3-Sprachen entscheidbar, jedoch nicht für Sprachen vom Typ 0. Beweis Argumentativ, s.o.
4.8 Recognizer und Parser für kontextfreie Sprachen XML
DTD XML Schema
Ursprünglich zur Dokumentbeschreibung konzipierte XML-Sprachen gewinnen bei der Datenrepräsentation zunehmend an Bedeutung. Der XML-basierte Datenaustausch zwischen Applikationen, sog. B2B-Applikationen (business to business), hat sich zu einem wichtigen Konzept entwickelt und findet in verschiedenen Technologien Anwendung. Vereinfacht gesagt, handelt es sich um „selbstgemachte“ Sprache vom (X)HTML-Typ. XML steht für eXtensile Markup Language. XML-Dokumente sind die Wörter einer XML-Sprache. Zur Auszeichnung werden Tags verwendet, wie man sie von (X)HTML her kennt. Allerdings entfällt die Beschränkung auf eine vorgegebene Tag-Liste. Die Kreation einer neuen XMLSprache bedeutet gerade, neue, der Anwendung der Sprache angepasste Tags, inkl. deren grammatikalischen Gebrauch, festzulegen. Die zur Definition der entsprechenden Tag-Strukturen (Inhaltsmodelle) einer solchen XML-Sprache erforderliche kfG wird in Gestalt einer DTD (document type definition) oder als XML Schema angegeben. Die Grammatikdefinition mittels DTD geschieht zwar nicht in BNF-Gestalt, sie kann aber in eine solche Darstellungsform transformiert werden. Eine genauere Betrachtung zeigt, dass die mit DTDs definierbare Sprachklasse
4.8 Recognizer und Parser für kontextfreie Sprachen
45
eine Teilmenge der kfS bildet, die Menge aller regulären Sprachen jedoch nicht vollständig umfasst. Tag-Strukturen müssen stets vollständig sein in dem Sinne, dass es zu jedem öffnenden Tag einen zugehörigenden schließenden gibt. Wie bei Klammerungen folgt der schließende dem öffnenden. Verschachtelungen nach dem Vorbild der Matrjoschka-Puppen (fälschlicherweise manchmal als Babuschka-Puppen „zitiert“) sind möglich. Verschränkungen, bei denen Tag-Klammerungen ineinander greifen, sind nicht erlaubt. Grob gesagt gilt: XML-Dokumente, die diese Eigenschaften besitzen, nennt man wohlgeformt – als mehr oder weniger zufriedenstellende Übersetzung von well-formed. Ist ein wohlgeformtes XML-Dokument darüber hinaus syntaktisch korrekt bezüglich der zugehörigen DTD, so nennt man es gültig (valid). Das Besondere an XML aus der Sicht der theoretischen Informatik ist nun, dass hier ein Wort einer Sprache zusammen mit deren Definition „vertrieben“ werden kann. Ein XML-Parser, nimmt also zur Syntaxanalyse nicht nur das Wort selbst als Eingabe, sondern zusätzlich die „mitgelieferte“ (oder vorher verabredete und bei Sender und Empfänger vorliegende) kfG (DTD). Dies entspricht genau dem in Abbildung 4.9 (S. 42) eingeführten Verarbeitungsprinzip mit dem kleinen Unterschied, dass nicht ein für kfG allgemeingültiges Verfahren P zum Einsatz kommt, sondern ein effizient arbeitender XML-Parser wie beispielsweise Xerces, s. Abbildung 4.10. XML−Dokument
XML-Parser
DTD
XML−Parser
true / false
Abbildung 4.10: XML-Parser
In B2B-Anwendungen werden XML-Dokumente im Allgemeinen nicht nur auf Gültigkeit geprüft, d.h. einem Erkennungsverfahren durch einen Recognizer unterzogen, sondern weiterverarbeitet. Deshalb ist ein Parser im Einsatz, der bei syntaktischer Korrektheit zusätzlich einen AST, s. Kapitel 2, erzeugt. Die „Hintergrundarbeit“ eines Parsers (Analysator, der eine abstrakte Repräsentation des Eingabewortes aufbaut) wird in Abbildung 4.10 nicht deutlich. Dort ist eher ein Recognizer (Erkenner, der nur Ja/Nein sagt) erkennbar. Obwohl der begriffliche Unterschied zwischen Recognizer und Parser praktische Relevanz besitzt, verwenden wir hier meist den Parser-Begriff. Natürlich ist es möglich, für eine konkrete Nicht-XML-Sprache einen zugehörigen Parser herzustellen, der die Syntaxprüfung für ein beliebiges Eingabewort durch-
Recognizer
46
4 Definition unendlicher Mengen
führt. Genau dies ist auch ein kleiner Teil dessen, was wir in den Folgekapiteln für unsere Zeichenroboter-Sprache aus Abschnitt 5.2 erarbeiten werden. Hierfür könnte man ein Programm verwenden, das wie P in Abbildung 4.9 (S. 42) arbeitet. In der Praxis wäre das allerdings inakzeptabel, da die Syntaxprüfung für die meisten der relevanten Sprachen viel zu ineffizent wäre. Deshalb sucht man ein effizientes Programm PG , s. Abbildung 4.11, das als einzige Eingabe das zu analysierende Wort nimmt. In PG ist die konkrete Grammatik G bereits „eingebaut“.
w ∈ T∗ PG : w ∈ L(G)?
true/ f alse Abbildung 4.11: Parser PG für G
Parsergenerator Compiler
Programme, wie PG , werden nur für sehr einfache Grammatiken zu Lehrzwecken per Hand geschrieben. Im Allgemeinen verwendet man dafür Parsergenerator, s. Abbildung 4.12. Parser werden in Compilern zur maschinellen Übersetzung von Sprachen verwendet.
G PG -Generator PG Abbildung 4.12: PG -Parsergenerator
Mit Compilern befassen wir uns im nächsten Kapitel.
5 Sprachübersetzer 5.1 Compiler und Interpreter Programme, die Programme einer Quellsprache in zugehörige Programme einer Zielsprache überführen, nennt man Compiler. Historisch hat sich der Bedarf an Compilern aus der Verwendung von Hochsprachen ergeben: Statt Anwendungsprogramme aus maschinennahen Instruktionen aufzubauen, verwendet man menschenfreundlichere Sprachelemente, um sie zu einem Programm in syntaktisch korrekter Weise zusammenzufügen. Damit diese Programme von einer „primitiven Zielmaschine“, genauer: einem Prozessor und dem Betriebssystem, verarbeitet werden können, ist eine entsprechende Übersetzung notwendig. Compiler sind derartige Übersetzungsprogramme. Interpreter gehen anders vor als Compiler. Das vorgelegte Programm wird nicht vollständig übersetzt, sondern „portionsweise“ analysiert, in eine zugehörige Folge von Prozessorinstruktionen übertragen und ausgeführt. Programmiersprachen, wie LOGO und BASIC, besitzen Interpreter, ebenso Skriptsprachen (Sprachen für kleinere Einsatzbereiche), auch in Form von Kommandozeilen-Interpretern (z.B. Windows PowerShell).
Compiler
Interpreter
Übung 5.1 Setzen Sie sich aufgrund der beiden beschriebenen Verarbeitungskonzepte mit dem Argument auseinander, dass Programme in Interpretersprachen üblicherweise langsamer laufen als semantisch äquivalente Programme in Compilersprachen. Schließen Sie ZyklusStrukturen in Ihre Betrachtung ein.
Ein vollständiges Programmiersystem kommt nicht ohne Interpretation durch den Prozessor aus. Gleichgültig wie viele Übersetzungsschritte durch Anwendung diverser Compiler auf den Quelltext bzw. erzeugte Programme in Zwischensprachen stattgefunden haben, muss das Programm in Zielsprache (Maschinencode) schließlich doch interpretiert werden. Während Interpreter den Programmentwurf bequemer gestalten, liefern Compiler zur Laufzeit performanten Zielcode. Das Streben nach Vereinigung der Vorteile beider Systeme führte zu Mischformen: Compiler-Interpreter-Systeme (Interpreter für den Entwurf, Compiler für die Ausführung), inkrementelle Compiler, die jeweils nur die editierten Programmteile übersetzen, Zwischencode-Interpreter, die ein (vor)compiliertes Programm in einem Zwischencode (z.B. Bytecode) interpretieren und damit eine virtuelle Maschine repräsentieren, und Just-in-time-
Mischformen
48
5 Sprachübersetzer
Compiler, die eine Übersetzung in Maschinencode zur Laufzeit (also gerade noch „rechtzeitig“) vornehmen. Die folgenden Betrachtungen sind auf Compiler und Interpreter in der klassischen Form beschränkt.
5.2 Die Sprache eines Zeichenroboters Didaktischer Hinweis 5.1 In Spezialkursen zum Compilerbau greift man den historischen Zugang auf und wählt als Zielsprache den Maschinencode (MC) einer konkreten Rechnerarchitektur bzw. des jeweiligen Betriebssystems. Nicht nur aus didaktischen Gründen, sondern auch den vielfältigen Anwendungszusammenhängen entsprechend, verwenden wir eine Hochsprache als Zielsprache. (Compiler mit dieser Eigenschaft werden auch Transcompiler genannt.) Für eine belastbare Motivation der Lernenden ist es von Bedeutung, dass die Interpretationen von Zielsprachen-Programmen zu grafischen oder akustischen Wirkungen führen.
Zeichenroboter
Unsere Quellsprache ZR ist die Sprache eines imaginären Zeichenroboters. Wir haben die Vorstellung, dass unser Roboter einen kleinen Zeichenstift besitzt. Dieser Stift ist so angebracht, dass der Roboter bei seinen Vorwärtsbewegungen eine Spur (als Stiftstrich) hinterlässt. n bedeutet, dass sich der Roboter n Schritte vorwärts bewegt. n lässt ihn eine Rechtsdrehung um n Grad vollführen. n bestimmt die Strichstärke n des Stiftes bis zur nächsten Veränderung. f legt die Farbe (rot, blau, grün, gelb, schwarz) fest. n
. . . führt dazu, dass die mit Pünktchen symbolisierte Anweisungsfolge n-mal hintereinander ausgeführt wird (Wiederholung).
Damit lassen sich ZR-Programme formulieren, wie beispielsweise , dessen Interpretation als „Blume“ in Abbildung 5.1 dargestellt ist. Übung 5.2 Interpretieren Sie jedes der folgenden ZR-Programme.
Übung 5.3 Geben Sie ZR-Programme an, die die ersten drei in Abbildung 5.1 dargestellten Figuren beschreiben.
5.3 Modellierung von Übersetzungsprozessen
49
Abbildung 5.1: ZR-Figuren
Auf der Suche nach einem geeigneten Verarbeitungsprogramm für ZR-Programme stoßen wir auf die Programmiersprache LOGO. Sie besitzt einen TurtleGeometrie-Anteil (T G). Eine Turtle ist ebenfalls ein Zeichenroboter, den man sich als eine Art Schildkröte mit einem Stift am Bauch vorstellen kann. Es wäre also durchaus möglich, eine ZR → T G-Übersetzung anzustreben, um den Vorteil der in LOGO eingebauten T G-Interpretation auszunutzen. Bei grafischen Darstellungen, die durch Sprachen beschrieben werden, denken wir auch an SV G, PostScript (PS) und PDF. Letztere hat den Vorteil, dass z.B. mit dem Adobe Reader ein weit verbreiteter Interpreter (Renderer) kostenlos zur Verfügung steht. Nachteilig ist, dass das Format für den Menschen nicht lesbar ist.
Turtle
SVG PS PDF
5.3 Modellierung von Übersetzungsprozessen Wir wählen ZR als Quell- und PDF als Zielsprache und betrachten im Folgenden den Übersetzungsprozess. Zur Modellierung verwenden wir T-Diagramme1 . Der Name dieser Diagramme rührt von ihrer Grundform her.
T-Diagramm
Neben den in Abbildung 5.2 angegebenen Grundbausteinen, gibt es noch Ein/Ausgabeelemente, die wie „Öhrchen“ links/rechts an ein Programm geheftet werden können. Auf diese Weise ergibt sich eine grafische Form, die zwar auch wie ein T aussieht, jedoch nicht mit der namensgebenden Figur für einen Compiler verwechselt werden darf. AtoCC stellt mit TDiag einen T-Diagramm-Editor zur Verfügung, mit dessen Hilfe wir Diagramme für Übersetzungsprozesse entwerfen und anschließend ausführen. Beispiel 5.1 Abbildung 5.3 enthält ein Diagramm für den ZR → PDF-Übersetzungsprozess, wie wir ihn weiter oben beschrieben haben: ZR-Compiler und PDF-Interpreter. 1 T-Diagramme
gehen auf McKeeman, Horning und Wortman zurück, die diese grafische Darstellungsform zur Beschreibung von Übersetzungsprozessen 1971 einführten. Sie wurden von Kerner (1990) und anderen aufgegriffen. Watt und Brown (2000) verwenden hierfür Tombstone-Darstellungen (tombstone = Grabstein), die auch ein bisschen wie Bäckermützen aussehen.
TDiag
50
5 Sprachübersetzer
P
S
S
M
S1
S2
S3
M
Programm P implementiert in S
Interpreter für Sprache S läuft auf M
Compiler Quellsprache: S1, Zielsprache: S2 implementiert in S3
Abbildung 5.2: T-Diagrammdarstellung für Compiler
Abbildung 5.3: ZR → PDF-Übersetzer mit PDF-Interpreter
Jedes einzelne Element des Diagramms wird in TDiag (AtoCC) durch Angabe der entsprechenden Eigenschaften spezifiziert. Hierzu gehören die Quell- (S1) bzw. Zielsprache (S2), die Sprache (S3), in der der betreffende Compiler geschrieben ist, der Name der Datei, die den betrachteten Compiler als Programm enthält, und die ggf. virtuelle Maschine, auf der er läuft. Eine Sprache, die für einen Diagramm-Baustein benötigt wird, in der Angebotsliste jedoch nicht vorkommt, kann hinzugefügt und anschließend frei ausgewählt werden. Sind alle Diagramm-Bausteine vollständig spezifiziert, kann das Diagramm „ausgeführt“ werden. Unter Beachtung der angegebenen Reihenfolge gilt dies auch für mehrere Diagramme. Computerübung 5.1 in einem neuen Ordner und beschäftigen Sie sich mit dem Entpacken Sie die Datei . Machen Sie sich auf diese Weise mit TDiag vorbereiteten Diagramm in und scheuen Sie sich nicht einen vertraut. Modifizieren Sie das ZR-Programm in
5.3 Modellierung von Übersetzungsprozessen
51
, Syntaxfehler einzubauen. Verwenden Sie weitere ZR-Programme ( und eigene). Hinweis: Damit das T-Diagramm abgearbeitet werden kann, ist es notwendig, dass Sie Ghostview (Postscript) auf Ihrem Computer installiert haben. Die erforderliche (kostenlose) Software finden Sie zusammen mit weiteren Handreichungen auf der AtoCC-Website.
Steht ein Compiler beispielsweise nur unter Unix zur Verfügung, soll aber auf einem Windows-Rechner verwendet werden, so muss (z.B. mittels ) eine Unix-API unter Windows emuliert werden. Um diesen Sachverhalt im Diagramm darzustellen, hat man quasi zwei Möglichkeiten: Entweder man abstrahiert von dieser Emulation und tut so, als würde alles unter Windows laufen, oder man fügt einen Unix-Interpreter ein, der tatsächlich unter Windows läuft. Im ersten Fall sprechen wir von einer virtuellen Unix-Maschine.
virtuelle Maschine
In Computerübung 5.1 wurde ein ZR-PDF-Compiler verwendet. Wie ist er entstanden? Schaut man in den Quelltext eines beliebigen PDF-Dokuments, so entsteht wohl kaum das Bedürfnis, einen Übersetzer per Hand zu beschreiben. Mit PS (PostScript) gibt es jedoch eine Alternative: PS ist eine vollständige, stapelorientierte Programmiersprache, die vor allem in der Druckindustrie eingesetzt wird. Wie der Name vermuten lässt, folgen die Operatoren den Operanden, daher „post“. Übung 5.4 Nutzen Sie das Arbeitsblatt in sich mit PS (PostScript) vertraut zu machen.
und Internet-Beiträge, um
Unter Verwendung eines vorhandenen PS-PDF-Compilers in kann ZR über PS in PDF umgewandelt werden. Auf diese Weise ergibt sich eine zweistufige Übersetzung (multi-stage translation), nämlich von ZR in PS (1. Stufe) und schließlich von PS in PDF (2. Stufe), s. Abbildung 5.4. Beispiel 5.2 Abbildung 5.4 zeigt die zweistufige Übersetzung eines ZR-Programms in ein PDFProgramm über PS.
Abbildung 5.4: Zweistufige ZR → PS → PDF-Übersetzung
zweistufige Übersetzung
52
5 Sprachübersetzer
Computerübung 5.2 Editieren Sie das in Beispiel 5.2 dargestellte T-Diagramm TDiag, spezifizieren Sie die einaus. zelnen Bausteine und führen Sie es für Computerübung 5.3 Eine Ghostview-Installation liefert neben einem PS-Compiler einen PS-Interpreter, den man zur grafischen Darstellung von ZR-Programmen verwenden kann. Passen Sie das Diagramm in der Lösung von Computerübung 5.2 entsprechend an. Computerübung 5.4 Entwickeln Sie ein T-Diagramm für die Übersetzung und anschließende Ausführung eines Java-Programms. Schreiben Sie zum Experimentieren ein einfaches HelloWorldProgramm. Computerübung 5.5 Modellieren Sie die vollständige Übersetzung eines LATEX-Programms in PDF mit TDiag.
Abschließend wollen wir noch darauf hinweisen, dass Compiler auch auf Compiler angewandt werden können, um neue Compiler herzustellen. D.h., dass ebenso wie alle anderen Programme auch Compiler und natürlich auch Interpreter compiliert werden können. Beispiel 5.3 Um einen S → M-Compiler (in M) herzustellen, beginnen wir beispielsweise mit einem CProgramm für S → M. Dieses kann mit Hilfe eines allgemein verfügbaren C-Compilers in das gewünschte Zielprogramm compiliert werden, s. Abbildung 5.5.
Abbildung 5.5: Ziel: S → M-Compiler in M Natürlich ist es auch möglich, den S → M-Compiler selbst in S zu schreiben. Dann kann dieser Compiler mit dem (gerade hergestellten) S → M-Compiler in M übersetzt werden, s. Abbildung 5.6. Es entsteht ebenso ein S → M-Compiler für die Maschine M, was für Effizienzvergleiche der beiden gleichartigen Compiler von Interesse sein kann. Nun können wir den in S geschriebenen S → M-Compiler verwenden, um Spracherweiterungen von S zu S per Hand in S zu programmieren, s. Abbildung 5.7. Nach einer entsprechenden Compilation erhalten wir einen S → M-Compiler in M.
5.3 Modellierung von Übersetzungsprozessen
53
Abbildung 5.6: S → M-Compiler in S
Abbildung 5.7: S → M-Compiler in S bzw. in M
Das Verfahren kann man weiter fortsetzen. Da es in jedem Schritt auf Compiler zurückgreift, die im vorhergehenden Schritt quasi mit „aufgestockten Bordmitteln“ erzeugt wurden, nennt man das Verfahren Bootstrapping. Der Begriff wird auch gern in Beziehung zu Baron Karl Friedrich Hieronymus Freiherr von Münchhausen (Lügenbaron, 1720-1797) gebracht, der sich (samt Pferd) am eigenen Zopf aus dem Sumpf gezogen haben soll.
Man sollte jedoch immer gut Sicherungskopien von älteren Versionen seines Compilers aufheben, wie eine Geschichte aus der OpenSource-Gemeinde erzählt: Bei der Entwicklung eines Compiler hatte jemand im Schritt von S zu S’ einen Trojaner in den Compiler eingebaut, diesen in eine binäre Version übersetzt und anschließend den Trojaner wieder aus dem Quelltext entfernt (sodass niemand ihn im Code vorfinden konnte). Der binäre Compiler wurde nun von der Gemeinde weiterverwendet, um die nächste Generation zu erzeugen. Jedem mit diesem Compiler neu erzeugten Compiler wurde binär ebenfalls der Trojaner angehängt. Jedes Programm, welches mit einem solchen Compiler übersetzt wurde, trug auch den Trojaner in sich. Er wurde erst mehrere Versionen später festgestellt und konnte nur mit großer Mühe wieder entfernt werden.
Bootstrapping
Trojaner
Beispiel 5.4 Hier ist noch eine ganz ähnliche Geschichte, die man auf der entsprechenden ACM-Seite im Web nachlesen kann: Ken Thompson made this very clear during his 1983 Turing Award lecture to the ACM, in which he revealed a shocking, and subtle, software subversion tech-
Ken Thompson
54
5 Sprachübersetzer
nique that’s still illustrative seventeen years later. Thompson modified the UNIX C compiler to recognize when the login program was being compiled, and to insert a back door in the resulting binary code such that it would allow him to login as any user using a ”magic” password. Anyone reviewing the compiler source code could have found the back door, except that Thompson then modified the compiler so that whenever it compiled itself, it would insert both the code that inserts the login back door, as well as code that modifies the compiler. With this new binary he removed the modifications he had made and recompiled again. He now had a trojaned compiler and clean source code. Anyone using his compiler to compile either the login program, or the compiler, would propagate his back doors. The reason his attack worked is because the compiler has a bootstrapping problem. You need a compiler to compile the compiler. You must obtain a binary copy of the compiler before you can use it to translate the compiler source code into a binary. There was no guarantee that the binary compiler you were using was really related to the source code of the same. Most applications do not have this bootstrapping problem. But how many users of open source software compile all of their applications from source? Übung 5.5 Bootstrap-Verfahren gibt es nicht nur im Compilerbau. Finden Sie heraus, wie eine Primzahlprüfung mit Bootstrapping durchgeführt werden kann.
Cross-Compiler
eingebettete Systeme Spielkonsole
Cross-Compiler sind Compiler, die auf einer bestimmten Maschine laufen, und Programmcode für eine andere Plattform generieren. Auf diese Weise können beispielsweise Programme für verschiedene Handys produziert werden, ohne dass dem Hersteller dieser Programme das entsprechende Handy jemals vorlag. CrossCompiler spielen heute eine wichtige Rolle für eingebettete Systeme (embedded systems), die selbst über keine (üppigen) Softwareentwicklungsumgebungen verfügen. Ähnlich verhält es sich mit Spielkonsolen. Beispiel 5.5 Gewünscht sei ein C++-Compiler für das IBM-Unix-Betriebssystem Alpha, wie in Abbildung 5.8. Wir schreiben den C++→ Al pha-Compiler natürlich nicht im Alpha-
Abbildung 5.8: C++→ Al pha-Compiler, lauffähig auf Alpha Maschinencode, sondern in C++ auf unserem Linux-Laptop mit x86-Befehlssatz. Dann nehmen wir den unter Linux laufenden C++-Compiler und erzeugen einen C++→ Al phaCompiler. Wie aus Abbildung 5.9 hervorgeht, hat dies noch einen Haken: Der erzeugte Compiler läuft zwar auf unserem Linux-Laptop, jedoch nicht auf der Alpha. Also nehmen wir den gerade hergestellten Compiler und wenden ihn wieder auf den in C++ geschriebenen C++→ Al pha-Compiler an, s. Abbildung 5.10. Damit erhalten wir den gewünschten
5.3 Modellierung von Übersetzungsprozessen
55
Abbildung 5.9: C++→ Al pha-Compiler lauffähig auf x86 (z.B. Linux-Laptop)
Abbildung 5.10: C++→ Al pha-Compiler lauffähig auf Alpha
Compiler. Wie Abbildung 5.11 zeigt, kann dieser nun auf der Alpha zur Übersetzung von C++-Programmen verwendet werden.
Abbildung 5.11: C++→ Al pha-Compiler übersetzt C++-Programme auf der Alpha
56
5 Sprachübersetzer
Computerübung 5.6 Modellieren Sie die in Beispiel 5.5 erläuterte Cross-Compilation mit TDiag.
5.4 Scanner und Parser Die oben eingeführte zweistufige Übersetzung lässt noch offen, wie der ZR → PSCompiler entstanden ist. Natürlich müssen Quell- und Zielsprache beschreiben werden, bevor konkrete Übersetzungsregeln formuliert werden können. Da wir uns zunächst mit der Analysephase des Compilers beschäftigen, beschränken wir uns vorerst auf die Definition der Quellsprache und geben in Abbildung 5.12 eine entsprechende Grammatik an. (N, T, P, s) {Programm, Anweisungen, Anweisung, Farbwert, Zahl, Ziffern, Ziffer, ErsteZiffer} T = { , , , , , , , , , , , , , , , , , , , , , } P = {Programm → Anweisungen Anweisungen → Anweisung Anweisungen | ε Anweisung → Zahl Anweisungen | Farbwert | Zahl | Zahl | Zahl Farbwert → | | | | Zahl → ErsteZiffer Ziffern Ziffern → Ziffer Ziffern | ε Ziffer → | | | | | | | | | ErsteZiffer → | | | | | | | | } s = Programm G N
= =
Abbildung 5.12: kfG für ZR Computerübung 5.7 Geben Sie den Ableitungsbaum des Wortes Grammatik in Abbildung 5.12, mit kfgEdit an.
aus ZR, gemäß der
5.4 Scanner und Parser
57
Die einzelnen Phasen eines Compilers sind 1. die lexikalischer Analyse (Scanner) 2. die syntaktische Analyse (Parser) 3. die Zwischencodegenerierung (AST) 4. die semantische Analyse 5. die Code-Optimierung 6. die Speicherplatzvergabe 7. die Zielcodegenerierung Die ersten drei Phasen gelten analog auch für Interpreter, bezogen auf QuellcodeAbschnitte. Didaktischer Hinweis 5.2 In der Tat gibt es noch einige in der Liste unerwähnte Aktionen: So wird beispielsweise eine Symboltabelle eingerichtet, die je nach Möglichkeit Typinformationen usw. aufnimmt. Bei streng typisierten Sprachen ist diese Tabelle auch eine Voraussetzung für das type checking (semantische Analyse) und scoping. Da „echte“ Programmiersprachen im Allgemeinen kontextsensitiv sind, jedoch durch kontextfreie Grammatiken beschrieben werden, sind zusätzlich Prüfungen von Kontextbedingungen erforderlich.
Vermutlich überrascht es Sie, dass nur die letzte Phase, nämlich die Zielcodegenerierung maschinenabhängig ist. Dass die gesamte Compilerentwicklung, also alle hier angegebenen Phasen zielmaschinenfern auf einem beliebigen Computer ausgeführt werden können, haben wir schon in Beispiel 5.5 und Computerübung 5.6 ausgeführt (Cross-Compiler).
Cross-Compiler
Wir sehen uns nun die ersten beiden Phasen etwas genau an. Bisher haben wir nur das Parsing zur Syntaxanalyse genannt, jedoch noch nichts darüber gesagt, wie ein Parser konzeptionell arbeitet.
Abbildung 5.13: Scanner und Parser in einem Compiler
Abbildung 5.13 zeigt eine Art Pipeline: dem Scanning folgt das Parsing. Dabei erwartet der Parser, dass der Scanner eine hilfreiche Vorarbeit leistet, die sich in
Scanner
58
Tokenliste
5 Sprachübersetzer
Form einer Tokenliste, auch Tokenstrom genannt, darstellt, s. auch Abbildung 5.14.
Abbildung 5.14: Scanner erzeugt Tokenstrom
Token
Lexem
Token sind also syntaktische Elementarbausteine, die man manchmal auch Morpheme nennt. Aus dem als Zeichenstrom empfangenen Wort (Satz) greift der Scanner (to scan = abtasten) Elementarbausteine (Token) heraus und hinterlässt sie als Folgen von Tokenklasse-Lexem-Paaren. Ein Lexem ist eine zur betrachteten Tokenklasse gehörende Zeichenkette als Teil des Analysewortes. Typische Tokenklassen sind: Schlüsselwort (für reservierte Befehlsworte), Bezeichner (für Variablennamen), Zahl, Zuweisungsoperator (für ), linkeKlammer, rechteKlammer und Begrenzer (für ). In den Klammern wurden nur einige Beispiele genannt. Für das Festlegen von Tokenklassen gibt es keine Definition oder Vorschrift, es ist eher eine Erfahrungsentscheidung. Typischerweise werden die (regulären) Typ-3-Anteile der Grammatik als Tokenklassen herausgegriffen. Beispiel 5.6 könnte Aus dem Eingabe-Zeichenstrom sich (mit Bezug auf eine entsprechende Grammatik) beispielsweise der Tokenstrom
ergeben.
Wie man in Beispiel 5.6 sehen kann, gibt es pro Tokenklassen unterschiedlich viele passende Lexeme. Im Extremfall gibt es nur eins, wie bei , und . Didaktischer Hinweis 5.3 Hier bietet sich eine Analogie zur Objekt-orientierten Programmierung an: Die Lexeme bilden die Instanzen der entsprechenden Token-Klasse.
reduzierte Grammatik
Als Nächstes käme also nun der Parser zum Einsatz. Durch die Bereitstellung des Tokenstroms braucht er sich nicht mehr um die Analyse der Lexeme zu kümmern, die dem jeweiligen Token zugeordnet wurden. Der Parser versteht sie als Terminale einer reduzierten Grammatik, die für ZR als G in Abbildung 5.15 angegeben wird.
5.4 Scanner und Parser
G N T P
= = = =
s
=
59
(N , T , P , s ) {Programm, Anweisungen, Anweisung} { , , , , , , , , {Programm → Anweisungen Anweisungen → Anweisung Anweisungen | ε Anweisung → Anweisungen | | | | } Programm
}
Abbildung 5.15: Reduzierte kfG für ZR
Übung 5.6 Geben Sie die in G gegenüber G verwendeten Tokenklassen und sämtliche jeweils passende Lexeme an. Didaktischer Hinweis 5.4 Wie weiter oben bereits mitgeteilt wurde, darf man sich die Zusammenarbeit zwischen Scanner und Parser als einen Prozess mit zwei aufeinander folgenden Durchläufen vorstellen: Zuerst tastet der Scanner den Eingabetext (Zeichenstrom) vollständig ab und danach analysiert der Parser den vom Scanner hinterlassenen Tokenstrom. Obwohl es heute sehr ausgeklügelte Hand-in-Hand-arbeitende und inkrementelle Verarbeitungstechniken gibt, ist diese phasenbezogene Sicht konzeptionell und didaktisch sehr zu empfehlen. Die Analysephase wird auch das Frontend und der Rest (ab einschl. semantischer Analyse) das Backend eines Compilers genannt.
Die Arbeitsweise von Recognizern und Parsern kann sich stark an den Ableitungsprozess bei formalen Grammatiken anlehnen. Von einem Spitzensymbol aus wird das zu analysierende Wort hergeleitet. Man spricht von einer Top-down-Analyse. Ebenso gibt es Parser, die in entgegengesetzter Richtung arbeiten und das Eingabewort schrittweise zu einem Satzsymbol reduzieren. Letzteres nennt man Bottomup-Analyse. Für beide Analysetechniken werden wir noch typische Verfahren kennenlernen. In der Praxis ist es häufig so, dass der Scanner vom Parser bei Bedarf aufgerufen wird. Wenn der Parser an der aktuellen Analysestelle ein Zahlwort erwartet, dann beauftragt er den Scanner, ihm das nächste Token zu generieren und zu überreichen. Falls es ein Token der Klasse ist, wird das zugehörige Lexem, z.B.
Top-downAnalyse Bottom-upAnalyse scan on demand
60
5 Sprachübersetzer
, zum Aufbau eines zugehörigen AST benutzt. Kann der Scanner den Parserwunsch nicht erfüllen, entsteht ein Syntaxfehler. Gleichgültig, wann die Tokenerkennung durch den Scanner stattfindet, erfordert sie ein Programm, das der jeweils definierten syntaktischen Einheit entspricht und wie folgt arbeitet: 1. Nimm einen bestimmten Bearbeitungszustand z0 ein. 2. Lies das nächste Zeichen a und nimm in Abhängigkeit von a einen (nicht notwendigerweise anderen) Zustand zi ein. 3. Stopp, falls die Zeichenfolge vollständig abgetastet wurde, ansonsten gehe nach 2. Zustandsautomat
Dies ist eine informelle Beschreibung der Arbeit abstrakter Zustandsautomaten, die wir endliche Automaten nennen und im nächsten Kapitel thematisieren werden. Ein Scanner ergibt sich dann als Komposition endlicher Automaten mit verschiedenen Anfangszeichen. Beispiel 5.7 Die Arbeitsweise eines solchen Automaten kann durch das folgende kleines Programm für das Schlüsselwort var illustriert werden.
6 Endliche Automaten und reguläre Sprachen 6.1 Aufbau abstrakter Akzeptoren Aus dem vorhergehenden Kapitel haben wir die Motivation mitgenommen, einfache (Sub-)Sprachen mit (einfachen) Automaten zu beschreiben. Abstrakte Automaten dienen – ebenso wie formale Grammatiken – zur Definition formaler Sprachen, indem sie vorgegebene Wörter als Elemente einer bestimmten Sprache akzeptieren oder ablehnen. Daher werden sie auch Akzeptoren genannt.
Abstrakte Automaten Akzeptor
Im Gegensatz zu Grammatiken, mit deren Hilfe bestimmte Wörter aus dem Spitzensymbol abgeleitet werden, steht bei Akzeptoren der Analyseaspekt im Vordergrund: Das zu untersuchende Eingabewort wird zeichenweise abgetastet und taktweise verarbeitet, bis schließlich ein definiertes Ende1 erreicht ist. Dann leuchtet – bildlich gesprochen – entweder eine grüne oder eine rote Lampe. Didaktischer Hinweis 6.1 Aufgrund der Modellierung mit Zuständen stehen Automaten den Programmen des imperativen und objektorientierten Paradigmas sehr nahe. Obwohl Akzeptoren nichts anderes bewirken, wie formale Grammatiken, fällt dem Programmierfreudigen der gedankliche Umgang mit Zustandsmaschinen wesentlich leichter. Dennoch handelt es sich in beiden Fällen um mentale Modelle, deren Zweck nicht darin besteht, in konkrete Produkte überführt zu werden.
Wir unterschieden verschiedene Automatentypen. Jeden einzelnen Automatentyp ordnen wir einem entsprechenden C HOMSKY-Sprachtyp zu. Alle Automatenmodelle2 haben folgenden Aufbau gemeinsam: Sie bestehen aus einem potenziell unendlichen Band, das in aufeinanderfolgende Felder oder Zellen gegliedert ist. Dies wird in Abbildung 6.1 dargestellt. Die vielen Fragezeichen in dieser Abbildung lassen erkennen, was für den jeweiligen Automatentyp zu spezifizieren ist. In jedes Feld kann genau ein Zeichen des entsprechenden Alphabets geschrieben werden. Eine Kommunikationseinheit kann mit einem Kopf auf jeweils genau ein 1 Ob
wirklich jeder Automat für jedes Eingabewort ein definiertes Ende erreicht, muss noch geklärt werden. Für endliche Automaten gelingt dies in jedem Fall. 2 Abstrakte Automaten werden als Modelle beschrieben. Dies unterstützt unsere Vorstellungskraft, darf aber deren abstrakten Charakter nicht verschleiern.
Automatentyp Aufbau
Kommunikationseinheit Kopf
62
6 Endliche Automaten und reguläre Sprachen potenziell unendliches Band a
a
b
a
Eingabewort
Felder / Zellen ? read / write
? move
? ggf. Zusatzspeicher ? read / write
Kommunikations− einheit
? move #
Abbildung 6.1: Automatenmodell: allgemeiner Aufbau
Feld dieses Bandes zugreifen und den entsprechenden Feldeintrag lesen, ohne ihn dadurch zu entfernen, und auswerten. Je nach Automatentyp kann auch etwas geschrieben werden: entweder in die aktuelle Zelle oder in einen externen Zusatzspeicher. Zustand
Arbeitsbeginn
Arbeitsende
Dabei arbeitet die Kommunikationseinheit schritt- oder taktweise. Zu Beginn eines jeden Arbeitstaktes befindet sie sich in einem bestimmten Zustand. Danach geht sie in einen Folgezustand, der sich nicht unbedingt von dem vorhergehenden unterscheidet, über. Am Ende eines Arbeitstaktes erfolgt eine Kopfbewegung zu dem als nächstes zu inspizierenden Feld. Die beschriebene Arbeitsweise wird in einer Überführungsfunktion definiert. Jeder Automat besitzt genau einen Startzustand. Dies ist der Zustand, in dem sich der Automat zu Beginn seiner Arbeit befindet. Je nach Automatentyp sind gewisse Vorarbeiten3 zu erledigen, bevor die abstrakte Maschine die Arbeit aufnimmt. Beispielsweise muss das vom Akzeptor zu analysierende Wort vor Arbeitsbeginn auf das Band geschrieben werden. Im Falle eines Zusatzspeichers ist auch dort ein vorgegebenes Zeichen in einem bestimmten Feld einzutragen. Vereinbarungsgemäß steht der Kopf zu Beginn auf dem ersten Zeichen dieses Wortes. Wir erwarten, dass der betrachtete Automat am Arbeitsende signalisiert, ob das Wort zur Sprache gehört oder nicht. Dies ist es, was einen Akzeptor ausmacht. Die hier gegebene Beschreibung der Arbeitsweise abstrakter Automaten als Akzeptor wird im Folgenden für die einzelnen Typen präzisiert.
3 Dies
sind Aktionen, die von außen – durch Menschen – vorgenommen werden und nicht zum Aufgabenspektrum des Automaten gehören.
6.2 Deterministischer Endlicher Automat (DEA, EA)
63
6.2 Deterministischer Endlicher Automat (DEA, EA) Endliche Automaten, definieren reguläre Sprachen. Wie wir zeigen werden, stellen sie eine gleichberechtigte Alternative zu regulären Grammatiken dar.
Endlicher Automat
Definition 6.1 Ein DEAa (auch EA) ist ein Quintupel M = (Q, Σ, δ , q0 , E), mit Q . . . endliche Menge der Zustände, Σ . . . Eingabealphabet, Q ∩ Σ = 0, / δ . . . Überführungsfunktion (totale Funktion), Q × Σ → Q, q0 . . . Startzustand, q0 ∈ Q, und E . . . endliche (nichtleere) Menge der Endzustände, E ⊆ Q. a DEA steht – wie in der Abschnittsüberschrift angegeben – für deterministischer endlicher Automat. In
der Tat gibt es auch ein nichtdeterministisches Pendent. Die Hintergründe dieser Begriffswahl werden weiter unten erläutert.
Das Band von DEA-Modellen besitzt die Eigenschaft, dass die in den einzelnen Zellen eingetragenen Zeichen nur gelesen werden können. In der heutigen Sprechweise würde man dies wohl read-only-tape nennen. Ein DEA verfügt also über einen Lesekopf, so wie ein Audio- oder Video-Player. In jedem Takt wird 1. das aktuelle4 Zeichen
gelesen,
2. ein Folgezustand gemäß δ eingenommen und 3. der Kopf um genau ein Feld nach rechts verschoben.
a
a
b
a
read move δ control q
i
Abbildung 6.2: Aufbau eines DEA
Die Überführungsfunktion δ bestimmt den jeweiligen Folgezustand. Es handelt sich um eine totale Funktion, d.h. δ (qi , ) muss für alle qi ∈ Q und ∈ Σ existieren. 4 Das
„aktuelle Zeichen“ ist – genauer gesagt – genau das Zeichen, das sich in dem Feld befindet, auf/über dem sich der Kopf des Automaten gerade befindet.
Lesekopf
64
6 Endliche Automaten und reguläre Sprachen
Für das Modell bedeutet dies, dass es niemals eine ungewisse Situation gibt, in der der Automat nicht „weiß“, wie es weiter gehen soll. Eine zugehörige Tabelle für diese zweistellige Funktion muss also stets vollständig ausgefüllt sein. δ q0 q1 .. .
a0 ... ... .. .
qm
...
a1 qr
... ...
an qi
a
qj
···
Abbildung 6.3: Überführungsfunktion eines DEA (links: Tabelle, rechts: Graph)
Endzustand Akzeptanzverhalten
Für δ gibt es eine sehr anschauliche grafische Darstellung, die in Abbildung 6.3 angedeutet wurde: Der Automat befindet sich zunächst in Zustand qi , liest das Zeichen und wechselt in den Zustand q j . Dass der Kopf anschließend um genau ein Feld nach rechts bewegt wird, geht aus δ nicht5 hervor. Falls q j – wie im Beispiel – ein Endzustand ist, wird dies durch zwei konzentrische Kreise dargestellt. Das Akzeptanzverhalten des Automaten lässt sich so beschreiben: Ein DEA stoppt, wenn das Eingabewort vollständig6 abgetastet wurde und verharrt in dem dann aktuellen Zustand qk . Falls qk ein Endzustand ist, wird das betrachtete Eingabewort von diesem DEA akzeptiert, ansonsten wird es abgewiesen. Da δ total ist, kann es kein (qi , )-Paar ohne definierten Folgezustand geben. Beispiel 6.1 Wir betrachten die Sprache aller Wörter aus und , die entweder mit beliebig vielen s beginnen und mit genau einem enden oder mit genau einem beginnen und beliebig vielen s enden. „Beliebig viele“ schließt auch den Fall ein, dass das betreffende Zeichen überhaupt nicht vorkommt: L = {w | w = n oder w = n , n ≥ 0}. Wir definieren nun einen DEA M als Akzeptor genau für die Sprache L. M = ({q0 , q1 , q2 , q3 , q4 , q5 , q6 }, { , }, δ , q0 , {q1 , q2 , q4 , q6 }) mit a, b
Start
q0
b
a
5 Dies
a, b
b
a
a
q5
a, b
q6
b
q2
q1
a
q3
b
q4
wird mit der erweiterten Überführungsfunktion ausgedrückt, die wir weiter unten betrachten. der Kopf der Kommunikationseinheit befindet sich auf dem ersten leeren Feld rechts neben dem Eingabewort.
6 D.h.,
6.2 Deterministischer Endlicher Automat (DEA, EA)
65
Der Zustand q5 in Beispiel 6.1 wirkt wie eine Falle (engl.: trap state): Wird dieser Zustand im Verlauf der Analyse eingenommen, gibt es keine Chance, diesen Zustand wieder zu verlassen. Da er selbst kein Endzustand ist, endet die Analyse mit der Abweisung des Eingabewortes. Solche Fallen-Zustände sind notwendig, da δ eine totale Funktion ist. Man kann unliebsame oder verbotene Übergänge also nicht einfach weglassen.
„Falle“
Die Analyse eines Eingabewortes w kann durch eine Folge von Konfigurationen protokolliert werden. Eine Konfiguration entspricht einem „Schnappschuss“, einer Momentaufnahme der Arbeit des Automaten, und umfasst den jeweils aktuellen Zustand qi zusammen mit dem aktuellen Restwort rk .
Konfiguration
(qi1 , rk1 ) (qi2 , rk2 ) (qi3 , rk3 ) . . . (qi|w|+1 , ε). Konfigurationen werden auch gern in Tabellenform protokolliert. Zustand q0 q2 q3 q3 q3 q4
AutoEdit
Restwort
(q0 , (q3 ,
) (q2 , ) (q3 , ) (q3 , ) (q4 , ε)
)
ε
Abbildung 6.4: Konfigurationenfolge (links: Tabelle, rechts oben: mit AutoEdit)
Didaktischer Hinweis 6.2 Der Entwurf von Automatenmodellen mit der AtoCC-Komponente AutoEdit ist ein Prozess, der von AutoEdit in vielfältiger Weise (lernertypadäquat usw.) unterstützt wird. Wie dies im Einzelnen geschieht und warum das so ist, kann hier nicht ausgeführt werden. Die AtoCC-Website hält auch hierzu ausführliches Material bereit. Computerübung 6.1 Verwenden Sie AutoEdit, s. Abbildung 6.5, aus AtoCC und überprüfen Sie die oben angegebene Konfigurationenfolge.
Die Abarbeitung endet in q4 . Da q4 ein Endzustand ist, wird das Wort vom Automaten akzeptiert und gehört somit zu der Sprache, die der Automat beschreibt. Allgemein gilt für die von einem DEA M akzeptierte (erkannte, beschriebene, definierte) Sprache L(M): L(M) = {w | w ∈ Σ∗ und (q0 , w) ∗ (qe , ε) und qe ∈ E}. Das Symbol ∗ steht für „Konfigurationenfolge beliebiger Länge“.
von M akzeptierte Sprache L(M)
66
6 Endliche Automaten und reguläre Sprachen
Abbildung 6.5: AutoEdit
erweiterte Überführungsfunktion
Weiter oben hatten wir bereits darauf hingewiesen, dass die Überführungsfunktion eines Automaten dessen Arbeitsweise nicht vollständig beschreibt. Es müssen also noch mathematische Beschreibungen der Kopfbewegung (um genau ein Feld nach rechts in jedem Takt) und des Arbeitsendes (nach Erreichen des Wortendes) nachgetragen werden. Dies geschieht mit der erweiterten Überführungsfunktion δˆ : Q × Σ∗ → Q. Sie ist also eine Abstraktion der beschriebenen Arbeitsweise des DEA-Modells. δˆ wird rekursiv definiert, wobei a für ein einzelnes Zeichen und w für das jeweilige Restwort (also das ursprüngliche Wort ohne das erste Zeichen) stehen. δˆ (q, ε) = q δˆ (q, aw) = δˆ (δ (q, a), w) Die erste Zeile beschreibt das Anhalten das Automaten für w = ε. In der zweiten Zeile wird der allgemeine Fall betrachtet, dass nämlich die Bearbeitung (δˆ ) von aw nach einem Zustandswechsel von q zu δ (q, a) mit dem Restwort w fortgesetzt wird. Unter Verwendung von δˆ kann die von einem DEA akzeptierte Sprache wie folgt beschrieben werden: L(M) = {w | w ∈ Σ∗ und δˆ (q0 , w) = qe und qe ∈ E}. Computerübung 6.2 Starten Sie den DEA aus Beispiel 6.1 mit dem Wort koll mit der oben angegebenen Konfigurationenfolge.
und vergleichen Sie das Proto-
Beispiel 6.2 greift unsere Zeichenroboter-Sprache aus Abschnitt 5.2 wieder auf. Für die beiden „Minisprachen“ (Zahlen und Farbwerte) sollen entsprechende DEAs angegeben werden, die später in den Scanner einfließen.
6.3 Endlicher Automat und reguläre Grammatik
67
Beispiel 6.2 Der DEA M = ({q0 , q1 , q2 }, { , , , , , , , , , }, δ , q0 , {q1 }) mit folgendem δ 0, 1,2,3,4,5,6,7,8,9
Start
q0
1, 2,3,4,5,6,7,8,9
0
q1
0, 1,2,3,4,5,6,7,8,9
q2
beschreibt die Sprache der Zahlwörter ohne Null und Vornullen. Dabei handelt es sich um die gleiche Sprache, wie die, die von folgender Grammatik G beschrieben wird. G = (N, T, P, s) N = {Zahl, Ziffern, Ziffer, ErsteZiffer} T = { , , , , , , , , , } P = {Zahl → ErsteZiffer Ziffern Ziffern → Ziffer Ziffern | ε Ziffer → | | | | | | | | | ErsteZiffer → | | | | | | | | } s = Zahl Übung 6.1 Geben Sie die Überführungsfunktion von M in Beispiel 6.2 als Tabelle an. Computerübung 6.3 Definieren Sie einen DEA für die „Sprache der Farbwerte“ als Untersprache der Zeichenrobotersprache aus Abschnitt 5.2. Übung 6.2 Weisen Sie nach, dass G in Beispiel 6.2 eine reguläre Grammatik ist.
6.3 Endlicher Automat und reguläre Grammatik Aus Beispiel 6.2 ergibt sich eine interessante Frage: Gelingt es, zu einer gegebenen regulären Grammatik G einen äquivalenten DEA M, mit L(M) = L(G), aus G automatisch zu gewinnen, d.h. durch Anwendung eines Konstruktionsverfahrens (ohne Intelligenzeinsatz) zu erzeugen? Falls dies möglich ist kommt hinzu: Gelingt dies auch umgekehrt? Die Antworten gibt Satz 6.1. Satz 6.1 Zu jeder regulären Grammatik G gibt es einen äquivalenten DEA M und umgekehrt.
68
6 Endliche Automaten und reguläre Sprachen
Beweis Dies zu beweisen, bedeutet zwei Teile zu behandeln, nämlich 1. konstruiere aus G einen äquivalenten DEA M (Teil 1) und 2. konstruiere aus M eine äquivalente C HOMSKY-Typ-3-Grammatik G (Teil 2). Beide Beweisteile sind konstruktiv. Didaktischer Hinweis 6.3 Beweise dieser Art sind in diesem Text immer wieder anzutreffen, sodass es sich lohnt, über eine geeignete Strategie nachzudenken. Die Empfehlung lautet: Ausschroten der Vorgaben inkl. aller Begriffsdefinitionen. „Ausschroten“ bedeutet so viel wie „trage alles zusammen, was sich aus den Vorgaben herausholen lässt.“ Erst danach beginnt man, eine Beweiskette zu der gesuchten Seite hin aufzubauen. Häufig wird dieser zweite Schritt zu früh angegangen, sodass Beweisideen im Keim ersticken.
Konstruiere G aus M
Wir beginnen mit Teil 2: Gegeben ist M = (Q, Σ, δ , q0 , E), gesucht ist G = (N, T, P, s), mit L(G) = L(M). Offensichtlich gelten N = Q, T = Σ und s = q0 . Die Produktionen in P ergeben sich aus δ und E: Für jedes δ (qi , a) = q j nehmen wir die Regel qi → aq j in P auf, wobei qi , q j ∈ N und a ∈ T . Falls q j ein Endzustand ist, kommt zusätzlich qi → a hinzu. Falls q0 ∈ E, d.h. ε ∈ L(M), wird zusätzlich q0 → ε in P aufgenommen. Auf diese Weise wird schließlich eine zugehörige äquivalente Grammatik7 aufgebaut und es gilt für alle w = a1 a2 . . . an ∈ Σ∗ : w ∈ L(M) gdw.8 es eine Konfigurationenfolge (q0 , a1 a2 . . . an ) (qi1 , a2 . . . an ) . . . (qik , ε) gibt, wobei qik ∈ E, gdw. q0 ⇒ a1 qi1 ⇒ a1 a2 qi2 ⇒ . . . ⇒ a1 a2 . . . an gdw. w ∈ L(G). Damit ist der Beweis des Teil 2 beendet. Mit Teil 1 befassen wir uns weiter unten. Beispiel 6.3 Wir konstruieren nach dem angegebenen Verfahren eine reguläre Grammatik G für den in Beispiel 6.2 angegebenen DEA zur Beschreibung der Sprache der Zahlwörter ohne Null und Vornullen. Das Ergebnis ist G = (N, T, P, s) = ({q0 , q1 , q2 }, { , , , , , , , , , }, P, q0 ) mit P={ q0 → q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | q0 → q2 q1 → q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | } q2 → q2 | q2 | q2 | q2 | q2 | q2 | q2 | q2 | q2 | q2 }
Optimierung der Grammatik
Nun sieht man allerdings, dass die entstandene Grammatik mit q2 ein Nichtterminal enthält, für das es ausschließlich rekursive Regeln gibt. Im Ableitungsprozess kann es also niemals verschwinden. In M entspricht q2 gerade der „Falle“. Sämtliche Regeln mit q2 werden deshalb entfernt. Dies hat jedoch Konsequenzen für die Regeln, die q2 auf der rechten Regelseite enthalten. In unserem Beispiel ist das nur q0 , sodass wir genau die eine betroffene Regel ebenfalls entfernen. Optimierungsschritte dieser Art bieten sich an, um die resultierende Grammatik von unnötigem Ballast zu befreien. Übrig bleibt Grammatik G . 7 Nachträgliche
Optimierungen bzw. Umstrukturierungen sind denkbar.
8 „gdw.“ steht für „genau dann, wenn“ (Äquivalenz). Manchmal schreibt man auch „gd“ und „w“, wenn
es dem Satzbau zuträglich ist.
6.3 Endlicher Automat und reguläre Grammatik
69
G = (N , T , P , s ) = ({q0 , q1 }, { , , , , , , , , , }, P, q0 ) mit P = { q0 → q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | q1 → q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | | q1 | } Computerübung 6.4 Vergleichen Sie die in Beispiel 6.3 angegebene Lösung mit dem zugehörigen AutoEditExport. Computerübung 6.5 Gegeben ist ein DEA mit M = ({q0 , q1 , q2 }, { , }, δ , q0 , {q2 }) und δ q0 q1 q2 q1 q1 q2 q2 q1 q2 Verwenden Sie das beschriebene Verfahren, um eine äquivalente reguläre Grammatik für die Sprache L(M) herzustellen. Überprüfen Sie anschließend Ihr Ergebnis mit dem von AutoEdit. Computerübung 6.6 Gegeben ist ein DEA M = ({q0 , q1 , q2 , q3 }, { , }, δ , q0 , {q0 , q1 , q2 }), mit folgender Überführungsfunktion δ . a
a, b a
Start
q0
b
q1
b
q2
b
q3
a
1. Geben Sie δ als Tabelle an. Vergleichen Sie Ihre Lösung mit der von AutoEdit gelieferten Tabelle. 2. Stellen Sie L(M) fest. 3. Konstruieren Sie eine reguläre Grammatik G mit L(G) = L(M) und vergleichen Sie wieder mit der AutoEdit-Lösung. Computerübung 6.7 Verwenden Sie AutoEdit, um den im Folgenden definierten Automaten M zu editieren. }, δ , q0 , {q2 }), M = ({q0 , q1 , q2 , q3 }, { , , , , , , , , , , δ q0 q3 q3 q3 q3 q3 q3 q3 q3 q3 q3 q1 q1 q2 q2 q2 q2 q2 q2 q2 q2 q2 q2 q3 q2 q2 q2 q2 q2 q2 q2 q2 q2 q2 q2 q3 q3 q3 q3 q3 q3 q3 q3 q3 q3 q3 q3 q3 Simulieren Sie das Automatenverhalten für das Eingabewort . Anschließend verwenden Sie die Export-Grammatik-Funktion. Die Grammatik wird daraufhin in kfGEdit ab und interpretieren Sie den Ableigeöffnet. Leiten Sie hier nun das Wort tungsbaum.
70
Konstruiere M aus G
6 Endliche Automaten und reguläre Sprachen
Wir setzen nun den Beweis fort, indem wir uns Teil 1 vornehmen: Gegeben ist eine reguläre Grammatik G, gesucht ist ein äquivalenter DEA M. Beweis Ein erster Konstruktionsversuch lässt Schwierigkeiten erkennen, denn Regeln, wie q2 → aq1 | aq3 | . . . führen strikt zu der in Abbildung 6.6 dargestellten Situation, was aber der
q1 a q2 a q3
Abbildung 6.6: Verstoß gegen die DEA-Definition Definition eines DEA widerspricht. Welcher Folgezustand sollte von einem DEA, der sich in Zustand q2 befindet, nach dem Einlesen des Zeichens eingenommen werden: q1 oder q3 ? Wir kommen also nicht recht weiter und unterbrechen an dieser Stelle den Beweis zu Teil 1 des Satzes. Erst in Abschnitt 6.5 nehmen wir die Arbeit wieder auf. Dann verfügen wir über die erforderlichen Inhalte, die in Abschnitt 6.4 bereit gestellt werden.
6.4 Nichtdeterministischer endlicher Automat (NEA)
R ABIN S COTT
Der Versuch, aus einer beliebigen regulären Grammatik einen äquivalenten DEA zu konstruieren, führt uns zu den nichtdeterministischen endlichen Automaten (NEA). Dieser Automatentyp wurde von R ABIN9 und S COTT10 im April 1959 eingeführt. 1976 erhielten Sie dafür den T URING-Award11 – eine der höchsten Auszeichnungen in der Informatik. Abbildung 6.6 illustriert das Problem, wonach der Automat aus einem bestimmten Zustand, dort q2 , mit ein und demselben Alphabetzeichen, dort a, in mehrere Folgezustände, q1 und q3 , übergehen kann. Eine Funktion kann so etwas nicht de9 Miachel
O. Rabin wurde 1931 in Wroclaw (Breslau) geboren. Von der dortigen Universität erhielt er 2007 einen Ehrendoktortitel. Seit 1981 ist er als Professor an der Harvard Universität (USA) tätig. 10 Dana S. Scott wurde 1932 geboren und arbeitete bis zu seiner Emeritierung im Jahre 2003 als Professor an der Carnegie Mellon University in Pittsburgh (USA) 11 In der Begründung heißt es: For their joint paper ’Finite Automata and Their Decision Problem’, which introduced the idea of nondeterministic machines, which has proved to be an enormously valuable concept. Their (Scott & Rabin) classic paper has been a continuous source of inspiration for subsequent work in this field.
6.4 Nichtdeterministischer endlicher Automat (NEA)
71
finieren, denn für jedes Argument wird jeweils genau ein Funktionswert erwartet, nicht zwei oder noch mehr. Ein kleiner Trick hilft uns weiter: Die sich ergebenden Folgezustände werden zu einer Menge zusammengefasst. Diese eine Menge bildet dann den jeweiligen Funktionswert. Dann gibt es beispielsweise für das Argument (q2 , a) genau einen Funktionswert von δ , nämlich {q1 , q3 }, denn δ (q2 , a) = {q1 , q3 }. ... .. . ... .. .
δ .. . q2 .. .
a .. . {q1 , q3 } .. .
... .. . ... .. .
Im Tabellenkörper stehen also Mengen, genauer: Teilmengen von Q. Die Menge aller Teilmengen von Q heißt Potenzmenge von Q, kurz: ℘(Q).
Potenzmenge
Beispiel 6.4 Die Potenzmenge von M = {a, b, c} ist ℘(M) = {0, / {a}, {b}, {c}, {a, b}, {a, c}, {b, c}, {a, b, c}}.
Für eine endliche Menge M besitzt ℘(M) genau 2|M| Elemente. In Beispiel 6.4 sind das gerade 2|M| = 23 = 8. Damit ist der Funktionscharakter gerettet. Bevor wir uns der Frage zuwenden, wie dieses neue Automatenmodell arbeitet, benötigen wir eine NEA-Definition. Definition 6.2 Ein nichtdeterministischer endlicher Automat, kurz: NEA, wird durch ein Quintupel M = (Q, Σ, δ , q0 , E) definiert, wobei bis auf δ die Bedeutungen der Symbole aus der Definition des DEA übernommen werden. Die Überführungsfunktion eines NEA ist wie folgt definiert: δ : Q × Σ → ℘(Q).
NEA Selbstverständlich kann auch die leere Menge 0/ ∈ ℘(Q) als Funktionswert von δ vorkommen, also δ (qi , a) = {} = 0. / Dies wird als verbotener Übergang interpretiert, denn es gibt keinen einzigen möglichen Folgezustand. Wir stellen nun die Frage nach der Arbeitsweise des NEA-Modells. Was bedeutet so etwas wie δ (q2 , a) = {q1 , q3 } im aktuellen Arbeitstakt? Welchen Folgezustand soll der NEA als Akzeptor einnehmen? Es gibt drei Interpretationsmuster, die wir im Folgenden mit den Stichworten Ambiguous, Cloning und Backtracking versehen: Man stelle sich vor, es gäbe ein Orakel (oder eine gute Fee), die die ?
Antwort auf die Frage w ∈ L(M) kennt. Das Orakel weiß ganz genau, in
verbotener Übergang NEA als Akzeptor
72
6 Endliche Automaten und reguläre Sprachen
welchen Folgezustand der NEA im nächsten Schritt übergehen muss, um erfolgreich zu sein – vorausgesetzt, dass es ein erfolgreiches Ende (Akzeptanz des Wortes) gibt. Das Orakel kann man sich als Ambiguous-Operator, wie im Aufrufbeispiel q1 q3 für δ (q2 , a) = {q1 , q3 }, vorstellen und auch tatsächlich implementieren. Falls nur q3 zur Akzeptanz des Eingabewortes führt, wird es ganz sicher q3 und nicht etwa q1 auswählen, d.h. q1 q3 =q3 . Für jeden Folgezustand (q1 und q3 ) wird je ein neuer Automat durch Klonen gebildet. Ein so entstandener Automat ist identisch zum ursprünglichen Automaten. Geklonte Automaten werden im entsprechenden Startzustand (q1 und q3 ) auf den zum Zeitpunkt ihrer Entstehung aktuellen Bandinhalt angesetzt. Sie arbeiten zeitlich parallel. Im Folgenden kann es zu weiteren Klonierungen der entsprechenden Automaten kommen. Das Eingabewort wird aktzeptiert, wenn es (mindestens) einen Automaten gibt, der nach vollständigem Abtasten des Eingabewortes in einem Endzustand stoppt. Dann kann die Arbeit der anderen geklonten Automaten eingestellt werden. Der betrachtete Automat nimmt im aktuellen Arbeitstakt zunächst einen der möglichen Folgezustände (q1 ) ein und wird auf den aktuellen Bandinhalt angesetzt. Dies läuft solange weiter, bis das Wort entweder akzeptiert wurde oder die Analyse in einer Sackgasse steckt. Im Falle einer Sackgasse wird die Arbeit des Automaten an der letzten möglichen Entscheidungsstelle für einen noch nicht verwendeten Folgezustand (q3 ) fortgesetzt. Die alte Konfiguration (Bandinhalt und Kopfposition) wird rekonstruiert. Stehen keine weiteren Folgezustände zu Verfügung, so muss noch weiter zurückgegangen werden. (Die Abarbeitung kann sehr gut als Baum dargestellt werden, dessen Knoten die eingenommenen Zustände bilden und dessen Aufbau mit maximalem Vortrieb in die Tiefe (statt in die Breite) stattfindet.) verschiedene Modelle und zugehörige Abstraktionsniveaus für Nichtdeterminismus
Alle Modellvorstellungen haben gemein, dass ein nicht zur Sprache gehörendes Eingabewort erst dann abgelehnt werden kann, wenn sämtliche Möglichkeiten ausgereizt wurden, bzw. das Orakel mit seinem Latein am Ende ist. Beim Cloning sind das alle geklonten Automaten, die im zeitlichen Nebeneinander werkeln. Keiner von ihnen kommt zum Ziel. Bei der Backtracking-Vorstellung müssen auch wirklich alle Verzweigungen (im Zurück- und wieder Vorwärtsgehen) ausgeschöpft worden sein, bevor man sicher sein kann, dass das Wort nicht akzeptiert wird. Die Orakelvorstellung kann sehr leicht auf das Cloning oder auch auf systematische Suche (Tiefe zuerst; Backtracking) zurückgeführt werden. Ambiguous12 bietet die höchste Abstraktion bei der Beschreibung des Konzepts des Nichtdeterminismus. Danach folgen Cloning und Backtracking in dieser Reihenfolge. Wir be12 Intern,
also bei der Implementierung des gesetzt.
-Operators, wird aber letztlich doch Backtracking ein-
6.4 Nichtdeterministischer endlicher Automat (NEA)
73
tonen hier die Cloning-Vorstellung. In AutoEdit ist dies fest verankert, das Backtracking ist in den entsprechenden Simulationen durch eine sequentielle Anordnung der geklonten Automaten erkennbar.
AutoEdit verwendet Cloning.
Formal wird die Arbeitsweise des NEA-Modells durch die erweiterten Überführungsfunktion δˆ : ℘(Q) × Σ∗ → ℘(Q) ausgedrückt. δˆ (Q , ε) = Q , für alle Q ⊆ Q δˆ (Q , aw) = (δˆ (δ (q, a), w)) q∈Q
Didaktischer Hinweis 6.4 Gegenüber der erweiterten Überführungsfunktion bei einem DEA wird hier mit den entsprechenden Mengen gearbeitet.
Damit kann die von einem NEA M akzeptierte Sprache L(M) angegeben werden: L(M) = {w | w ∈ Σ∗ und δˆ ({q0 }, w) ∩ E = 0}. / NEAs haben den Vorteil, dass sie reguläre Sprachen im Allgemeinen kompakter definieren als entsprechende DEAs. Zum direkten Vergleich mit dem in Beispiel 6.1 entwickelten DEA für die Sprache L = {w | w = n oder w = n , n ≥ 0} wird in Beispiel 6.5 ein äquivalenter NEA angegeben. Beispiel 6.5 NEA M = ({q0 , q1 , q2 , q3 , q4 }, { , }, δ , q0 , {q2 , q3 , q4 }) für die Sprache L = {w | w = n oder w = n , n ≥ 0} a
Start
q0
a
b
a
q1
b
q3
b
q2
δ q0 q1 q2 q3 q4
{q1 , q4 } {q1 } {} {} {}
{q3 } {q2 } {} {} {q4 }
q4
Simuliert man die Arbeitsweise des NEA in Beispiel 6.5 für das Eingabewort mit AutoEdit (Modus: automatische Simulation), so erkennt man zwei Automaten M0 und M1 . Zum Cloning kommt es bereits im ersten Schritt.
akzeptierte Sprache
74
mehrdeutig
6 Endliche Automaten und reguläre Sprachen
Obwohl M0 in Beispiel 6.5 nach zwei Schritten erfolgreich war, wird trotzdem die Arbeit von M1 simuliert. AutoEdit stellt also die Arbeit aller geklonten Automaten vollständig dar. Dies geschieht aus gutem Grund: Akzeptiert nämlich mehr als ein Automat das Eingabewort, so ist die zugrunde liegende Grammatik mehrdeutig. Für sind zwei Automaten erfolgreich. Computerübung 6.8 Schalten Sie für Beispiel 6.5 im Simulationsmodus auf Einzelschrittbetrieb um, beobachten Sie den Graphendurchlauf (im oberen Teilbereich dargestellt) und interpretieren Sie den Analyseprozess mit der Backtracking-Vorstellung. Computerübung 6.9 als Eingabewort und simulieren Sie den Analyseprozess. Hier ist der geWählen Sie wird abgewiesen, da nicht ein einziger klonte Automat M1 erfolgreich. Das Wort Automat in einem Endzustand stoppt.
Achtung: falsch!
Computerübung 6.10 Geben Sie einen NEA an, der die Menge aller Wörter über { , } definiert, wobei jedes und – in dieser Reihenfolge, d.h. die -Troika steht vor (links Wort dieser Sprache von) der -Troika – enthält. Zur Unterstützung Ihrer Überlegungen geben wir hier einen fehlerhaften Lösungsvorschlag an: M = ({q0 , q1 , q2 , q3 , q4 , q5 , q6 }, { , }, δ , q0 , {q6 }), mit a, b
Start
q0
a, b
a, b
a
q1
a
q2
a
q3
b
q4
b
Wie aus der folgenden Abbildung hervorgeht, wird das Wort obwohl es nicht zur Sprache gehört.
Konstruiere NEA M aus G
q5
b
q6
akzeptiert,
Ausgehend von einer regulären ε-freien13 Grammatik G ist es recht einfach, einen äquivalenten NEA M aufzubauen. Wir geben das allgemeine Konstruktionsprinzip an. Gegeben: reguläre Grammatik G = (N, T, P, s) ohne ε-Regeln Gesucht: NEA M = (Q, Σ, δ , q0 , E), mit L(M) = L(G) entsprechende Transformation zur Eliminierung von Regeln der Form X → ε wurde – sogar für kfG – auf S. 40 vorgeführt.
13 Die
6.4 Nichtdeterministischer endlicher Automat (NEA)
75
Klar sind Σ = T und q0 = s. Bilde Q = N ∪ {qx }, mit qx ∈ N, und δ wie folgt: Falls A → aB in P existiert, so füge δ (A, a) 14 B in δ hinzu. Falls A → a in P existiert, so füge δ (A, a) qx in δ hinzu. Für die Menge der Endzustände E des NEA M gilt {q0 , qx }, wenn (s → ε) ∈ P E= {qx }, sonst Damit ist die Konstruktion von M aus G vollständig beschreiben. Auf einen formalen Beweis der Äquivalenz wird hier verzichtet. Anzumerken ist allerdings, dass wir bei der oben vorgestellten NEA-Konstruktion von regulären Grammatiken ausgegangen sind, die (neben Regeln der Form X → a) ausschließlich rechtslineare Regeln besitzen. Ist das Transformationsverfahren für Grammatiken mit linkslineare Regeln unbrauchbar? Didaktischer Hinweis 6.5 Linkslineare reguläre Grammatiken sind ohnehin recht „unhandlich“: Versucht man für ein vorgegebenes Wort eine Ableitung zu finden, so entpuppt sich diese Regelgestalt als Linksrekursion. Man „sieht“ also nicht auf das nächste zu erzeugende Terminalzeichen, sondern muss sich vorerst damit begnügen, der wiederholten Anwendung der ausgewählten Regel X → Xa zu folgen. Dadurch wird in jedem Schritt das rechts von diesem Nichtterminal X stehende Terminal a einmal in das Wort „hineingepumpt“. Wie erkennt man, wann man damit aufhören muss? Computerübung 6.11 Verwenden Sie kfG Edit mit manueller Regelauswahl, um das Wort abaabb aus L(G), mit G = ({S, K}, {a, b}, {S → Sb | Kb | Sa, K → a}, S) zu analysieren. Die gesuchte Ableitung ist S ⇒ Sb ⇒ Sbb ⇒ Sabb ⇒ Saabb ⇒ Kbaabb ⇒ abaabb. Haben Sie sie durch „scharfes Hinschauen“ rasch gefunden? Man muss immer nach rechts zum jeweiligen Restwort blicken, wenn man Sackgassen vermeiden will.
Erfreulicherweise gelingt die Transformation regulärer ε-freie Grammatiken mit linkslinearen Regeln in äquivalente rechtslineare immer. Dies geschieht in zwei Schritten: 1. Transformation aller15 Regeln in linkslineare: Regeln der Form X → Ya bleiben unverändert. Regeln der Form X → a werden ersetzt durch X → Ha und H → ε. H ist hierbei ein neues Nichtterminal. Achtung: Man braucht nur genau dieses eine neue Nichtterminal H, nicht etwa für jeder Regel eines. Schreibweise liest man als „enthält“. δ (A, a) B bedeutet daher, dass B zur Menge(!) δ (A, a) gehört. 15 Die Definition regulärer Grammatiken schließt eine Mischform rechts- und linkslinearer Regeln aus. 14 Die
rechtslineare Regeln
76
6 Endliche Automaten und reguläre Sprachen
2. Transformation der vorbereiteten Regeln in rechtslineare: Wähle H als neues Spitzensymbol und streiche die Regel H → ε. Ergänze s → ε, wobei s das Spitzensymbol der linkslinearen Grammatik ist. Ersetze jede Regel der Form X → Ya durch Y → aX. Falls ε ∈ L(G), muss H → ε wieder hinzugefügt werden. Beispiel 6.6 Gegeben sei die linkslineare reguläre Grammatik G = ({A, B, S}, {a, b, c}, P, S) mit P = {S → Bc | Ac; A → a | Aa; B → b | Bb}. Hieraus entsteht im ersten Schritt: G = ({A, B, H, S}, {a, b, c}, P , S) mit P = {S → Bc | Ac; A → Ha | Aa; B → Hb | Bb; H → ε}. Der zweite Schritt liefert: G = ({A, B, H, S}, {a, b, c}, P , H) mit P = {H → aA | bB; A → cS | aA; B → cS | bB; S → ε}. Computerübung 6.12 Der Grammatik-Editor kfG Edit von AtoCC gibt uns die Möglichkeit, das in Beispiel 6.6 entwickelte Ergebnis zu überprüfen. In Abbildung 6.7 erkennt man sowohl die drei Transformationsmöglichkeiten (ε-Freiheit herstellen, ε in L hinzufügen und linkslineare Regeln in rechtslineare transformieren) als auch auch das Resultat für die Beispielgrammatik.
Abbildung 6.7: Transformationen für reguläre Grammatiken
6.5 Konstruktion eines äquiv. DEA aus einem NEA G → NEA → DEA(→ G)
Der Versuch zu zeigen, dass es zu jeder regulären Grammatik einen äquivalenten DEA gibt, führte uns zunächst zu einem NEA, s. Abbildung 6.6 auf Seite 70, der zur Beschreibung von Sprachen offenbar gute Dienste leistet. Die eigentliche Beweisaufgabe ist damit jedoch nicht gelöst. Es bleibt zu zeigen, dass zu jedem NEA ein äquivalenter DEA konstruiert werden kann. Gelingt dies, so haben wir unser ursprüngliches Konstruktionsziel über den „Umweg“ NEA erreicht. Dies
6.5 Konstruktion eines äquiv. DEA aus einem NEA
77
bedeutet dann außerdem, dass die Leistungsfähigkeit von DEAs mit der von NEAs zur Definition regulärer Sprachen übereinstimmen. Beweis Die Beweisidee liegt darin, den oben angewandten Trick rückgängig zu machen, indem jedem Element (also jeder Menge) aus ℘(Q) genau ein Zustand zi zugeordnet wird. Dann kann die Überführungsfunktion δ des gesuchten DEA M aus δ des betrachteten NEA M = (Q, Σ, δ , q0 , E) nach der Vorschrift δ (z, a) =
δ (q, a)
q∈z
konstruiert werden. Die anderen Bestandteile von M = (Q , Σ , δ , z0 , E ) ergeben sich aus Q
=
℘(Q) nach Umbenennung zu zi , mit 0 ≤ i < 2|Q| ,
z0
=
z0 (= {q0 }) und
=
{R | R ⊆ Q und R ∩ E = 0} / nach Umbenennung zu zi .
E
Die sehr knappe Darstellung diese (konstruktiven) Beweises verlangt nach einem Beispiel. Beispiel 6.7 Konstruktion eines äquivalenten DEA M = (Q , Σ , δ , z0 , E ) aus einem NEA M = ({q0 , q1 , q2 }, { , }, δ , q0 , {q2 }), mit δ q0 q1 q2
0, 1
Start
q0
0
q1
0
q2
{q0 , q1 } {q2 } {}
{q0 } {} {}
℘(Q) = {{q0 }, {q1 }, {q2 }, {q0 , q1 }, {q0 , q2 }, {q1 , q2 }, {q0 , q1 , q2 }, 0/ } z0
z1
z2
z3
z4
z5
z6
z7
Die Reihenfolge der Elemente (Teilmengen) in ℘(Q) spielt bei deren Umbenennung keine Rolle. Zur Illustration wurde absichtliche eine andere Anordnung als in Beispiel 6.4 auf S. 71 gewählt.
z.B. δ (z3 , 0)
=
δ ({q0 , q1 }, 0) = δ (q0 , 0) ∪ δ (q1 , 0)
=
{q0 , q1 } ∪ {q2 } = {q0 , q1 , q2 }
=
z6
Die vollständige Tabelle für δ (links) kann anschließend vereinfacht werden (rechts). Hierfür sind Zustände, zu denen es keinen Übergang gibt (wie z5 ) oder von denen kein Übergang wegführt (wie z7 ), zu streichen. Streichungen können neue Streichungen hervorrufen.
78
6 Endliche Automaten und reguläre Sprachen
δ z0 z1 z2 z3 z4 z5 z6 z7
0 z3 z2 z7 z6 z3 z2 z6 z7
δ z0 z3 z6
1 z0 z7 z7 z0 z0 z7 z0 z7
0 z3 z6 z6
1 z0 z0 z0 1
0 0
Start
z0
1
z3
0
z6
1
Wir erhalten den DEA M = (Q , { , }, δ , z0 , E ) mit Q
=
{z0 , z3 , z6 }
z0
=
z0 , denn q0 ist der Anfangszustand von M.
=
{z6 }, denn q2 ist der einzige Endzustand von M.
E
Bei Interesse können die Zustände zi zu geeigneten q j umbenannt werden. Offensichtlich gilt w ∈ L(M) genau dann, wenn w ∈ L(M ). Computerübung 6.13 Verwenden Sie AutoEdit zum Editieren des in Beispiel 6.7 gegebenen NEA. Lassen Sie diesen in einen äquivalenten DEA konvertieren und vergleichen Sie das Resultat mit dem in diesem Beispiel konstruierten. Lassen Sie schließlich von AutoEdit eine äquivalente Grammatik generieren und exportieren Sie diese in den Grammatikeditor kfGEdit. Didaktischer Hinweis 6.6 Bei |Q| ≥ 4 und damit |℘(Q)| ≥ 24 kann die Tabelle auch nach Bedarf, also unvollständig ausgefüllt werden. D.h., der jeweils als nächstes zu berechnende Zustandsübergang ist ein solcher, der im Ergebnis einer vorhergehenden Berechnung entstand. Auf diese Weise kann man ggf. die Berechnung der recht umfangreichen Tabelle vermeiden. Im Beispiel hätte 0
0
0
man zuerst δ (z0 , 1), danach δ (z3 , 1) und schließlich δ (z6 , 1) berechnet. Übung 6.3 Gegeben sei die reguläre Grammatik G = ({A, B, S}, { , }, P, S), mit P = {S → A | B, A → A | ε, B → B | }. (a) Zeigen Sie, dass G mehrdeutig ist. (b) Stellen Sie ε-Freiheit her. (c) Konstruieren Sie einen äquivalenten NEA M. (d) Konstruieren Sie aus M einen äquivalenten DEA. (e) Überführen Sie den erzeugten DEA in einen äquivalenten NEA und vergleichen Sie diesen mit M. (f) Versuchen Sie, eine äquivalente reguläre Grammatik G zu finden, die eindeutig ist.
6.5 Konstruktion eines äquiv. DEA aus einem NEA
79
Fazit: Typ-3-Grammatiken, DEA und NEA sind äquivalente Beschreibungsmittel regulärer Sprachen. Alle möglichen Transformationen zwischen diesen sind in
rG
DEA
NEA
Abbildung 6.8: Transformationskette (fett): rG → NEA → DEA → rG
Abbildung 6.8 dargestellt. Computerübung 6.14 Gegeben ist ein NEA M mit M = ({q0 , q1 , q2 , q3 }, {a, b}, δ , q0 , {q1 , q3 }), / δ (q1 , a) = 0, / δ (q1 , b) = 0, / δ (q0 , a) = {q1 , q2 }, δ (q0 , b) = 0, / δ (q2 , b) = {q3 }, δ (q3 , a) = {q2 }, δ (q3 , b) = 0. / δ (q2 , a) = 0, 1. Geben Sie δ als Graph an. 2. Stellen Sie L(M) fest. 3. Konstruieren Sie einen äquivalenten DEA für L(M). Verwenden Sie AutoEdit. Computerübung 6.15 Gegeben ist ein NEA M = (Q, Σ, δ , q0 , E) mit Q = {q0 , q1 , q2 , q3 , q4 }, Σ = {a, b}, E = {q2 , q4 } und δ ist bestimmt durch folgende Tafel: δ q0 q1 q2 q3 q4
a {q0 , q3 } {} {q2 } {q4 } {q4 }
b {q0 , q1 } {q2 } {q2 } {} {q4 }
1. Geben Sie δ als Graph an. 2. Stellen Sie fest, ob der Automat die Eingabe abaab akzeptiert, und geben Sie im positiven Fall eine zugehörige Konfigurationenfolge an. AutoEdit unterstützt Sie dabei. 3. Welche Sprache akzeptiert der Automat? Computerübung 6.16 Gegeben ist die Sprache S, die aus allen Wörtern der Form an mit n ≥ 0 besteht, die durch 3 oder durch 4 (oder durch beide) teilbar ist. Geben Sie einen NEA für S an. Setzen Sie AutoEdit ein.
80
6 Endliche Automaten und reguläre Sprachen
Computerübung 6.17 Geben Sie einen DEA für die Menge der ganzen Zahlen an. Konstruieren Sie daraus eine zugehörige reguläre Grammatik, aus der Sie in einem dritten Schritt einen entsprechenden NEA konstruieren. Schließlich entwickeln Sie aus diesem NEA einen DEA und vergleichen ihn mit Ihrer Lösung am Anfang. Verwenden Sie anschließend AutoEdit, um mit Ihren Lösungen zu vergleichen. Computerübung 6.18 Gegeben sei ein Automat M = (Q, Σ, δ , S, {R}) mit folgender Überführungsfunktion: 1, a
Start
S
a
R
1, ?
? B
1, ?,a
1. Geben Sie die vollständige Definition von M an und stellen Sie den Automatentyp fest. 2. Entscheiden Sie, ob M die Wörter aa11a, aaa1a und 1a1a1a1a akzeptiert zuerst durch Handrechnung und anschließend mittels AutoEdit. 3. Interpretieren Sie die Symbole a als Zeichen für einen beliebigen Buchstaben, 1 als Zeichen für ein beliebiges Sonderzeichen und ? als Zeichen für ein verbotenes Zeichen. Wo findet die Sprache bei dieser Interpretation Verwendung? 4. Wenden Sie das Verfahren zur Entwicklung der zu M gehörenden regulären Grammatik G an und vergleichen Sie Ihr Ergebnis mit der Grammatik, die von AutoEdit zum Export angeboten wird. Leiten Sie einige Beispielwörter ab. 5. Konvertieren Sie M in einen N und vergleichen Sie mit dem AutoEdit-Resultat (Konvertierungsoption).
6.6 Minimalautomaten
Minimalautomat
Für eine gegebene reguläre Sprache L kann also stets ein zugehöriger DEA angegeben werden. Wie wir aus den Lösungen der Übungsaufgaben wissen, gibt es im Allgemeinen sogar mehrere DEAs, die L definieren. Diese äquivalenten DEAs unterscheiden sich in der Anzahl der Zustände, die sie besitzen. Von besonderem Interesse sind die Minimalautomaten, also diejenigen DEAs mit der geringsten Zustandsanzahl.
6.6 Minimalautomaten
81
Wir werden feststellen, dass es für jede reguläre Sprache L genau einen MinimalDEA gibt, der L akzeptiert. Automaten, die sich nur in den Zustandsnamen von dem Minimal-DEA unterscheiden (Isomorphie), werden dabei nicht berücksichtigt. Der folgende Satz liefert nicht nur ein notwendiges und hinreichendes Kriterium für reguläre Sprachen, sondern auch die theoretische Grundlage für ein Konstruktionsverfahren eines Minimal-DEA. Satz 6.2 Eine Sprache L ist genau dann regulär, wenn der Indexa von RL endlich ist. (Satz von M YHILL b und N ERODE ) Dabei ist RL eine zweistellige Relation in der Wortmenge Σ∗ , mit L ⊆ Σ∗ . Für beliebige Zeichenketten x, y ∈ Σ∗ gilt xRL y, wenn für alle Zeichenketten z ∈ Σ∗ gilt: xz ∈ L ⇔ yz ∈ L. a Unter
dem Index einer Äquivalenzrelation R in M versteht man die Anzahl der durch R definierten Äquivalenzklassen in M.
b John
R. Myhill, gest. 1987; Anil Nerode, geb. 1932
Beweis Teil 1: Wenn L regulär ist, dann ist der Index von RL endlich. Teil 2: Wenn der Index von RL endlich ist, dann ist L regulär. Der Beweis für beide Teile wird im nachfolgenden Text erbracht. Aus Gründen der Übersichtlichkeit wird dies jedoch nicht in der entsprechenden Weise formatiert. Lediglich das jeweilige Ende eines Beweisteils wird angemerkt.
Es ist zu beachten, dass die N ERODE-Relation RL in Σ∗ definiert ist. Sie ist also nicht etwa auf L beschränkt und kann ebenso auf alle Zeichenketten x und y angewandt werden, die keine Wörter aus L sind.
y
L x
Σ∗
xz ¯
yz ¯
x¯ y¯
Abbildung 6.9: N ERODE-Relation
xRL y gilt genau dann, wenn xz und yz für alle z ∈ Σ∗ entweder beide in L oder beide nicht in L, also in Σ∗ \ L, liegen. Da z = ε erlaubt ist, muss auch x ∈ L ⇔
Satz von M YHILL und N ERODE
RL
82
6 Endliche Automaten und reguläre Sprachen
y ∈ L gelten, um xRL y zu erfüllen. In Abbildung 6.9 sind zwei Wörter x¯ und y¯ eingezeichnet, die nicht in Relation RL stehen, denn es gibt (offensichtlich) ein z ∈ Σ∗ , sodass xz ¯ ∈ L und yz ¯ ∈ L. Übung 6.4 Weisen Sie nach, dass RL eine (rechtsinvariante16 ) Äquivalenzrelation ist.
Äquivalenzrelation Äquivalenzklassen
Eine Äquivalenzrelation in Σ∗ erzeugt eine vollständige Zerlegung von Σ∗ in paarweise disjunkte Teilmengen. Diese nennt man Äquivalenzklassen [x]RL = {w | w ∈ Σ∗ und wRL x}. Didaktischer Hinweis 6.7 Die Zahlentheorie beschäftigt sich mit den ganzen Zahlen. Als klassisches Beispiel zur Einführung von Äquivalenzelationen werden gern Zahlenkongruenzen b ≡ a mod n verwendet. Dabei betrachtet man den Rest r der Ganzzahldivision von a durch n. Offensichtlich können nur die Reste r = 0, 1, 2, . . . , n − 1 auftreten. Die betrachtete Äquivalenzrelation zerlegt die Grundmenge, also die ganzen Zahlen, in paarweise disjunkte Teilmengen, sog. Restklassen. Die Restklasse a mod n ist eine Menge, die aus genau den ganzen Zahlen besteht, die bei Division durch n den gleichen Rest lassen wie a. Jede ganze Zahl befindet sich in genau einer Restklasse. Da n verschiedene Reste auftreten können, gibt es genau n Restklassen. Es ist üblich, sie mit dem jeweiligen Rest r zu benennen, z.B [2]3 . Man wählt also jeweils einen geeigneten Repräsentanten als Restklassennamen [a]n := {b | b ≡ a mod n}. Beispiel 6.8 Wir betrachten die Menge der Wörter über {0, 1}, deren vorletztes Zeichen 0 ist: L = {w | w = u0v und u ∈ {0, 1}∗ und v ∈ {0, 1}} = {00, 01, 000, 100, 001, 101, 1000, . . .}. Dann wird Σ∗ durch RL in folgende vier Äquivalenzklassen zerlegt: [11]RL = {ε, 1, 11, 011, 111, . . .}, denn x und y liegen beide außerhalb von L, sodass jeder Suffix z die L-Mitgliedschaft beider Wörter xz und yz übereinstimmend festlegt. Wählt man z = ε, so liegen xz und yz (also jeweils beide) nicht in L. [01]RL = {01, 001, 101, 0001, . . .}, denn x und y liegen beide in L. Auch hier bestimmt der jeweils ausgewählte Suffix z die L-Mitgliedschaft für xz und yz übereinstimmend: Wählt man z = ε, so liegen xz und yz (also jeweils beide) in L. Aufgrund der erläuterten Zugehörigkeiten der Wörter in [11]RL und [01]RL für z = ε ist sofort klar, dass [11]RL und [01]RL disjunkte Mengen sind. Nun betrachten wir die Wörter der Restklasse [10]RL = {0, 10, 110, 0010, . . .}. Sie bringen das Potenzial mit, durch Anhängen genau eines Zeichens z = 0 oder 1, zu L zu gehören. Das ist weder bei [11]RL noch bei [01]RL der Fall. Für z = ε ergeben sich stets Wörter, die nicht zu L gehören. [00]RL = {00, 000, 100, 0000, . . .} fasst die Wörter zusammen, die zu L gehören (z = ε) und zusätzlich auf 0 enden, sodass durch Anhängen genau eines Zeichens aus {0, 1} je ein Wort aus L erzeugt wird. 16 Eine
Äquivalenzrelation R heißt rechtsinvariant, wenn xRy ⇒ ∀z ∈ Σ∗ : xzRyz
6.6 Minimalautomaten
83
Die gewählten Repräsentanten der Äquivalenzklassen, d.h. den Zeichenketten in den eckigen Klammern, entsprechen im Allgemeinen den beiden Zeichen, auf die Wörter der betrachteten Klasse enden. An den Wörtern ε, 1 ∈ [11]RL und 0 ∈ [10]RL wird deutlich, dass dies nicht dogmatisch gilt.
Da Σ∗ eine unendliche Menge ist, kann es im Extremfall unendlich viele Äquivalenzklassen geben. Es gilt also entweder Σ∗ Σ
∗
= [w1 ]RL ∪ [w2 ]RL ∪ . . . ∪ [wn ]RL oder = [w1 ]RL ∪ [w2 ]RL ∪ . . . ,
wobei [wi ]RL = {w | wRL wi }. Nach dem Vorbild von RL kann man auch für DEA eine Äquivalenzrelation RM wie folgt definieren. Definition 6.3 Sei M = (Q, Σ, δ , q0 , E) ein DEA, der genau L akzeptiert. Dann sind die Zeichenketten u, v ∈ Σ∗ ununterscheidbar durch M, d.h. uRM v, wenn gilt: δˆ (q0 , u) = δˆ (q0 , v) = qi mit qi ∈ Q. Dabei spielt es keine Rolle, ob δˆ (q0 , u) ein Endzustand ist oder nicht.
Die Definition von RM geschieht natürlich mit Blick auf unser Ziel, ein Verfahren zur Gewinnung des jeweiligen Minimalautomaten zu finden. RM als Äquivalenzrelation sorgt schon mal dafür, dass jeweils ununterscheidbare Zeichenketten in einer Äquivalenzklasse zusammengefasst werden. Wir orientieren uns an RL und betrachten Zeichenketten der Form uz und vz für beliebige z ∈ Σ∗ . Hat der Arbeitsprozess von M beginnend in q0 für je zwei unterschiedliche Zeichenketten u und v den gleichen Zustand qi erreicht, ergibt sich für die Restzeichenkette z zwangsläufig ein identischer Verlauf, s. Abbildung 6.10. Für z = ε stoppt M in qi . u Start z
q0
qi
qn
v
Abbildung 6.10: xRM y
u und v können nur dann in der Relation RM stehen können, wenn sie entweder beide von M akzeptiert oder beide von M nicht akzeptiert werden.
RM
84
6 Endliche Automaten und reguläre Sprachen
Übung 6.5 Weisen Sie nach, dass RM eine Äquivalenzrelation in Σ∗ ist.
Aus xRM y folgt xRL y.
Damit liefert auch RM eine Zerlegung von Σ∗ in paarweise disjunkte Teilmengen. Interessant ist, dass für jedes beliebige x ∈ Σ∗ die zugehörige Äquivalenzklasse von RM in der entsprechenden Äquivalenzklasse von RL enthalten ist. Satz 6.3 Für alle x ∈ Σ∗ gilt: [x]RM ⇒ [x]RL .
Man sagt, RM sei eine Verfeinerung von RL . Wie viele Elemente in der jeweiligen Äquivalenzklasse enthalten sind, spielt dabei keine Rolle. Beweis Zu zeigen: Wenn xRM y, d.h. δˆ (q0 , x) = δˆ (q0 , y), dann xRL y, d.h. xz ∈ L ⇔ yz ∈ L. xz ∈ L
⇔ ⇔
δˆ (q0 , xz) ∈ E δˆ (δˆ (q0 , x), z) ∈ E
⇔
δˆ (δˆ (q0 , y), z) ∈ E, wegen xRM y ⇔ δˆ (q0 , x) = δˆ (q0 , y) δˆ (q0 , yz) ∈ E
⇔
yz ∈ L
⇔
Aufgrund der Definition von RM ist klar, dass RM höchstens so viele Äquivalenzklassen besitzt, wie M Zustände hat. M stoppt für u und v mit uRM v in genau einem Zustand qi . Jeder Zustand qi , der – wegen δˆ (q0 , u) = δˆ (q0 , v) = qi – vom Anfangszustand q0 aus erreichbar ist, repräsentiert eine Äquivalenzklasse. [x]RM ist also die Menge aller Wörter w, die M im gleichen Zustand qi stoppen lassen wie x. Von daher kann man für [x]RM auch [qi ]RM schreiben. Da die Zustandsmenge eines DEA für L endlich ist, ist der Index von RM endlich und – wegen [x]RM ⇒ [x]RL für alle x ∈ Σ∗ – ist auch der Index von RL endlich. Ende des Beweisteils 1
Damit ist der erste Teil des Satzes 6.2 bewiesen: Wenn L regulär ist, so ist der Index von RL endlich.
6.6 Minimalautomaten
85
Beispiel 6.9 M = ({q0 , q1 , q2 , q3 , q4 }, { , }, δ , q0 , {q2 }). Start
q0
b
q4
a
q1
a, b
q2
a
b
a
q3
b
a, b
Man findet leicht heraus, dass L(M) = {w | w = aab2n oder w = abb2n , n ≥ 0}. Die Relation RM für diesen DEA zerlegt Σ∗ in 5 Äquivalenzklassen. In diesem Beispiel kann jeder Zustand vom Anfangszustand q0 aus erreicht werden. Die Zeichenketten, die von q0 zu qi „führen“, notieren wir als Elemente von [qi ]RM mit i = 0, 1, 2, 3, 4. [q0 ]RM [q1 ]RM [q2 ]RM [q3 ]RM [q4 ]RM
= {ε} = {a} = {w | w = aabr oder w = abbr , r gerade} = {w | w = aabr oder w = abbr , r ungerade} enthält alle restlichen Wörter
Wir geben nun die Äquivalenzklassen von RL für L = {w | w = aab2n oder w = abb2n , n ≥ 0} an. Dabei helfen uns die folgenden Überlegungen. • Es gilt aaRL ab, denn für ein beliebiges w ∈ Σ∗ gilt: aaw ∈ L und abw ∈ L gdw. w = abb2n , n ≥ 0. • Es gilt bRL ba, da bw ∈ L und baw ∈ L für alle w ∈ Σ∗ . • Hingegen gilt ¬(aRL ab), da z.B. abb ∈ L aber abbb ∈ L Wir erhalten die folgenden Äquivalenzklassen von RL , sind uns aber zunächst nicht ganz sicher, ob wir wirklich alle Klassen gefunden haben. Ein Vergleich mit den Äquivalenzklassen von RM signalisiert Vollständigkeit, d.h. [ε]RL ∪ [a]RL ∪ [aa]RL ∪ [aab]RL ∪ [b]RL = Σ∗ mit = [q0 ]RM = {ε} [ε]RL = [q1 ]RM = {a} [a]RL [aa]RL = [q2 ]RM = {w | w = aabr oder w = abbr , r gerade} [aab]RL = [q3 ]RM = {w | w = aabr oder w = abbr , r ungerade} = [q4 ]RM [b]RL sodass wir in diesem Beispiel nun sämtliche Äquivalenzklassen gefunden haben. Wegen [x]RM ⇒ [x]RL für alle x ∈ Σ∗ hätten wir RL auch einfach aus RM konstruieren können.
Wir beweisen nun die Rückrichtung des Satzes: Wenn der Index von RL endlich ist, dann ist die Sprache L regulär.
Beginn des Beweisteil 2
86
6 Endliche Automaten und reguläre Sprachen
Dann gibt es die Zeichenketten x1 , x2 , . . . , xk , mit Σ∗ = [x1 ]RL ∪ [x2 ]RL ∪ . . . ∪ [xk ]RL . Definiere einen DEA M = (Q, Σ, δ , q0 , E), mit Q = {[x1 ]RL , [x2 ]RL , . . . , [xk ]RL }, δ ([xi ]RL , a) = [xi a]RL , q0
= [ε] und
E
= {[xi ]RL | xi ∈ L}
Nun zeigen wir, dass ein Wort w genau dann zu L gehört, wenn es von einem so konstruierten DEA M akzeptiert wird. δˆ (q0 , w) ∈ E ⇔ δˆ ([ε]RL , w) ∈ E
w ∈ L(M) ⇔
⇔
[w]RL ∈ E
⇔ w∈L Ende des Beweisteil 2 Regularität einer Sprache
Da DEA reguläre Sprachen definieren, ist L regulär. Wir verwenden nun den Satz von M YHILL und N ERODE, um zu zeigen, dass eine gegebene Sprache regulär bzw. nicht-regulär ist. Beispiel 6.10 Die Sprache L = {an bn | n ≥ 1} ist nicht-regulär. Man erkennt, dass die Zeichenketten ak b, mit k ≥ 1, (paarweise) unterschiedlichen Äquivalenzklassen angehören müssen. Für ein beliebig ausgewähltes r ≥ 1 gibt es nur eine einzige Zeichenkette z = br−1 , sodass ar bz ∈ L. Für alle anderen Zeichenketten ak b, mit k = r, gilt ak bz ∈ L. Folglich gibt es unendlich viele Äquivalenzklassen von RL : [ab]RL = L [a2 b]RL = {a2 b, a3 b2 , a4 b3 , . . .} [a3 b]RL = {a3 b, a4 b2 , a5 b3 , . . .} .. .. . . [ak b]RL = {ak+i−1 bi | i ≥ 1} .. .. . . L ist nicht-regulär. Eine genaue Betrachtung der Äquivalenzklassenstruktur ist nicht nötig. Didaktischer Hinweis 6.8 Falsch wäre [ε]RL , [a]RL = {a, a2 b, a3 b2 , . . .}, [aa]RL , . . ., denn z.B. a2 bRL a ⇔ a2 bab2 ∈ L aber aab2 ∈ L. Übung 6.6 Zeigen Sie mit Hilfe des Satzes von M YHILL und N ERODE, dass die Sprache L = {w | w ∈ {0, 1}∗ und w endet mit 00} regulär ist.
6.6 Minimalautomaten
87
Aus dem Beweis des Satzes von M YHILL und N ERODE kann man erkennen, dass der Minimal-DEA für eine reguläre Sprache L gerade der Äquivalenzklassenautomat ist. Bis auf Umbenennung der Zustände gibt es für jede reguläre Sprache genau einen Minimalautomat. Das Auffinden des RL -Äquivalenzklassenautomat für eine Sprache L kann schwierig sein. Es erfordert schon etwas Übung, um keinen Fehler zu machen. Etwas besser geht es via RM . Aber dafür müssten wir den Minimal-DEA bereits kennen. Hier hilft uns ein Verfahren, das aus einem bereits entwickelten DEA für L den Minimal-DEA konstruiert. Klar ist, dass ein DEA nicht minimal sein kann, wenn es zwei Zustände z und z gibt, sodass für alle x ∈ Σ∗ gilt δˆ (z, x) ∈ E ⇔ δˆ (z , x) ∈ E. Solche Zustandpaare können zu jeweils einem Zustand verschmelzen. Das von H OPCROFT und U LLMAN angegebene Verfahren hinterlässt alle verschmelzbaren Zustandspaare. Es arbeitet sehr schnell (mit quadratischem Zeitaufwand). Im Folgenden wird das Verfahren vorgestellt und an einem Beispiel angewandt. Beispiel 6.11 DEA M = ({1, 2, 3, 4, 5, 6}, { , }, δ , 1, {2, 5, 6}). a
a, b b
Start
1
a
2
a
4
b
a
b
3
b
5
b
6
a
Die Idee des Verfahrens besteht darin, schrittweise alle die Zustandspaare auszufiltern, die nicht verschmelzbar sind. Dafür ist es notwendig, dass alle möglichen Zustandspaare gebildet werden. Genau genommen sind es Zustandszweiermengen, denn die Reihenfolge von z und z in (z, z ) spielt keine Rolle. Hieraus erklärt sich die Vorbelegung bestimmter Felder in der weiter unten angegebenen Matrix mit einem Ausrufezeichen, was wir als ‚verboten‘ ansehen können. Interessiert man sich für die Belegung eines verbotenen Feldes (z, z ), so muss man bei (z , z) nachsehen. Zustandspaare, die nicht verschmelzbar sind, werden (mit x) markiert. Die am Ende unmarkierten Paare sind verschmelzbar. Für alle Paare (z, z) gilt dies naturgemäß.
Minimal-DEA
88
Schritt 1
6 Endliche Automaten und reguläre Sprachen
Schritt 1: Wir stellen als erstes sämtliche Zustandspaare in einer Matrix zusammen.
1 2 3 4 5 6
Schritt 2
1
2
3
4
5
! ! ! ! !
! ! ! !
! ! !
! !
!
Schritt 2: Markiere nun alle Paare (z, z ), wenn genau einer der beiden Zustände, also entweder z oder z , ein Endzustand ist. 1 1 2 3 4 5 6
Schritt 3
6
! ! ! ! !
2 x ! ! ! !
3
4
x
x
! ! !
! !
5 x
6 x
x x
x x
!
Schritt 3: Für jedes noch unmarkierte Paar (z, z ) teste, ob für wenigstens ein a ∈ Σ gilt: (δ (z, a), δ (z , a)) ist bereits markiert. Wenn ja, dann markiere (z, z ). Beispiel für das Paar (1, 4): δ (1, a) = 2, δ (4, a) = 2 Das Paar (2, 2) ist unmarkiert. Also müssen noch δ (1, b) = 3, δ (4, b) = 6 gebildet werden. Das Paar (3, 6) = (6, 3) ist bereits markiert. Folglich muss das Paar (1, 4) markiert werden.
Schritt 4
Schritt 4: Wiederhole Schritt 3 solange, bis sich keine Veränderung der Matrixeinträge ergibt. 1 1 2 3 4 5 6
Schritt 5
! ! ! ! !
2 x ! ! ! !
3 x x ! ! !
4 x x x ! !
5 x x x
6 x x x x x
!
Schritt 5: Alle jetzt noch unmarkierten Zustandspaare sind verschmelzbar.
6.7 NEA mit ε-Übergängen
89
Im Beispiel sind dies die Zustände 2 und 5. Dies ergibt folgenden Minimal-DEA. a
a, b b
Start
1
a
b
b
2/5
a
4
b
6
a
3
Computerübung 6.19 Verwenden Sie AutoEdit, um die Lösung des mitgeführten Beispiels zu überprüfen. Übung 6.7 Der (nicht minimale) DEA M = ({q0 , q1 , q2 , q3 , q4 }, { , }, δ , q0 , {q4 }) mit 0, 1
Start
q0
0
q1
0
q4
1 0
1
q2
1
0
q3
1
akzeptiert genau die Sprache L(M) = {x | x ∈ { , }∗ und in x kommt der zugehörige Minimalautomat.
vor.} Gesucht ist
Computerübung 6.20 Verwenden Sie AutoEdit, um die Lösung von Übungsaufgabe 6.7 zu überprüfen.
6.7 NEA mit ε-Übergängen DEA-Definitionen verlangen die Angabe einer totalen Überführungsfunktion. Dies ist mitunter sehr aufwendig, denn alle „verbotenen“ Eingaben müssen vollständig aufgeführt werden. Ein äquivalenter NEA kommt meist mit weniger Zuständen aus; der zugehörige Zustandsgraph ist dann übersichtlicher. Im Folgenden werden wir sehen, dass man die Überführungsfunktion noch weiter verschlanken kann. Beispiel 6.12 Wir geben zuerst einen DEA für die Sprache der ganzen Zahlen ohne Vornullen an: M = ({q0 , q1 , q2 , q3 , q4 }, { , , , , , , , , , , , }, δ , q0 , {q1 , q4 }) an, δ s. Abbildung 6.11. Der Akzeptor M = ({q0 , q1 , q3 , q2 }, { , , , , , , , , , , , }, δ , q0 , {q1 , q3 }) mit der in Abbildung 6.12 gezeigten Überführungsfunktion ist ein entsprechender NEA für die gleiche Sprache. Er kommt mit nur vier Zuständen aus.
90
6 Endliche Automaten und reguläre Sprachen
+, -,0,1,2,3,4,5,6,7,8,9
Start
q0
0
q1
+, -
1, 2,3,4,5,6,7,8,9
q3
1, 2,3,4,5,6,7,8,9
+, -,0,1,2,3,4,5,6,7,8,9
+, -,0
q2
+, -
q4
0, 1,2,3,4,5,6,7,8,9
Abbildung 6.11: DEA für die Sprache der ganzen Zahlen ohne Vornullen
Start
q0
0
q1
+, -
1, 2,3,4,5,6,7,8,9
q2
1, 2,3,4,5,6,7,8,9
q3
0, 1,2,3,4,5,6,7,8,9
Abbildung 6.12: NEA für die Sprache der ganzen Zahlen ohne Vornullen
spontaner Übergang
In diesem NEA gibt es für die Zeichen , . . . , Übergänge sowohl von q0 nach q3 als auch von q2 nach q3 . Durch Einführung eines sog. ε-Übergangs von q0 zu q2 können diese Übergänge zusammengefasst werden. Man spricht von einem spontanen Übergang. Das bedeutet, dass der Zustandswechsel ohne Verbrauch des nächsten Zeichens des zu analysierenden Wortes erfolgt. Wir erhalten M = ({q0 , q1 , q3 , q2 }, { , , , , , , , , , , , }, δ , q0 , {q1 , q3 }) mit der in Abbildung 6.13 gezeigten Überführungsfunktion.
Start
q0
0
q1
1, 2,3,4,5,6,7,8,9
q3
+, -, ε
q2
0, 1,2,3,4,5,6,7,8,9
Abbildung 6.13: NEAε für die Sprache der ganzen Zahlen ohne Vornullen
6.7 NEA mit ε-Übergängen
91
Der so entstandene Automat hat zwar ebenso viele Zustände wie obiger NEA ohne εÜbergänge, aber er ist noch besser lesbar: 1. 0 ist eine ganze Zahl. 2. Eine vorzeichenbehaftete Ziffernfolge ohne führender Null ist eine ganze Zahl. 3. Eine vorzeichenlose Ziffernfolge ohne führender Null ist eine ganze Zahl. Computerübung 6.21 Verwenden Sie AutoEdit, um die in Beispiel 6.12 beschriebenen Transformationen nachzuvollziehen.
Der Gebrauch von NEA mit ε-Übergängen, kurz: NEAε , zur Beschreibung regulärer Sprachen ist recht bequem. Momentan haben wir allerdings noch keine Berechtigung, NEAε s zu verwenden. Zuvor muss die Äquivalenz von NEAε und NEA nachgewiesen werden.
Äquivalenz von NEAε und NEA
Satz 6.4 Zu jedem NEA ohne ε-Übergänge gibt es einen äquivalenten NEAε und umgekehrt. Beweis Teil 1: Es ist zu zeigen, dass jedem NEA ohne ε-Übergänge ein äquivalenter NEAε zuordenbar ist. Dies ist trivial: Hierfür erweitert man δNEA um Übergänge der Form δ (qi , ε) qi . Teil 2: Für die Gegenrichtung ist zu zeigen, dass zu jedem beliebigen NEAε ein äquivalenter NEA ohne ε-Übergänge angegeben werden kann. Dieser Beweisteil ist jedoch nicht so offensichtlich, sodass wir uns im folgenden Text ausführlicher damit beschäftigen. Das Ende dieses Beweisteils ist entsprechend markiert.
Für die Überführungsfunktion eines NEAε gilt: δ : Q × (Σ ∪ {ε}) → ℘(Q). Um ε-Übergänge zu eliminieren, müssen für jeden Zustand sämtliche Folgezustände, die ohne Eingabezeichenverbrauch erreichbar sind, ermittelt werden. Man nennt dies die ε-Hülle des Zustands q. Definition 6.4 Die ε-Hülle eines Zustands q ist wie folgt definiert: ε-Hülle(q) = {p ∈ Q | p ist von q erreichbar ohne das aktuelle Eingabezeichen zu verbrauchen.}
ε-Hülle(q) q ist sinnvoll, denn jeder Zustand ist natürlich von sich selbst aus ohne Eingabezeichenverbrauch erreichbar. Damit ist klar, dass die ε-Hülle(q) mindestens ein Element und höchstens |Q| Elemente enthält. Natürlich kann es auch mehrere „hintereinander geschaltete“ ε-Übergänge geben. Ein Zustand p ist von q mit dem aktuellen Eingabezeichen a erreichbar, wenn 1. δ (q, a) p („klassischer“ Fall) q
a
p
oder wenn es einen oder mehrere Zustände r gibt, mit
ε-Hülle(q)
92
6 Endliche Automaten und reguläre Sprachen
2. δ (q, ε) r, mit r = q, und δ (r, a) p q
ε
r
a
p
oder 3. δ (q, a) r, mit r = q, und δ (r, ε) p q
ε-Hülle einer Menge von Zuständen
a
r
ε
p
.
Die letzten beiden Fällen besagen, dass der eigentliche „a-Übergang“ sowohl nach als auch vor einem spontanen Übergang stattfinden kann. Hierbei müssen alle möglichen „Zwischenzustände“ r betrachtet werden. Im Übrigen kann es auch von r aus weitere spontane Übergänge geben. Die Fälle 2 und 3 sind diesbezüglich eingeschränkt. Eine allgemeingültige Beschreibung erfordert die Ausweitung der Definition der ε-Hülle auf eine Menge von Zuständen. Definition 6.5 Die ε-Hülle einer Menge von Zuständen {qi1 , qi2 , . . . qim } ist die Vereinigungsmenge der ε-Hüllen der Einzelzustände, d.h. ε-Hülle(0) /
=
ε-Hülle({qi1 , qi2 , . . . qim })
=
0/ m
ε-Hülle(qik ).
k=1
Unter Hinzunahme der folgenden Definition sind wir in der Lage, die oben genannten drei Fälle ohne Einschränkung und kompakt zu beschreiben sowie eine Berechnungsvorschrift für die Überführungsfunktion δ des zu konstruierenden äquivalenten NEA anzugeben. Definition 6.6 Für q ∈ Q und a ∈ Σ definieren wir d(q, a) = {p ∈ Q | Es gibt einen (direkten) Übergang von q nach p mit a}. Verallgemeinert gilt für eine Zustandsmenge {qi1 , qi2 , . . . qim } und a ∈ Σ d({qi1 , qi2 , . . . qim }, a) =
m
d(qik , a).
k=1
Dann haben die folgenden Ausdrücke die jeweils angegebenen Bedeutungen: d(ε
(q), a) ist die Menge aller von q direkt oder nach vorausgegangenen spontanen Übergängen erreichbarer Zustände. Dies entspricht den Fällen 1 und 2 (verallgemeinert) in obiger Dreierliste.
6.7 NEA mit ε-Übergängen
ε
93
(d(q, a)) ist die Menge aller von q direkt erreichbarer Zustände, inklusive sich anschließender spontaner Übergänge. Dies entspricht gerade dem Fall 3 (verallgemeinert) in obiger Liste.
Dann ergibt sich für die gesuchte Überführungsfunktion δ des zugehörigen NEA δ (q, a) = ε-Hülle (d(ε-Hülle(q), a)) . ε vorher
mit a
ε nachher
Die restlichen Bestandteile des gesuchten NEA M sind Q
= Q
= Σ
Σ
q0
E
= q0 = E ∪ {q | ε-Hülle(q) ∩ E = 0} /
Da wir bei der Konstruktion von M aus M lediglich die spontanen Übergänge eliminiert haben, hat sich am Akzeptanzverhalten des Automaten nichts geändert, womit der geforderte Beweis erbracht wurde. Fast man alles zusammen, so haben wir nachgewiesen, dass es für reguläre Sprachen eine weitere gleichwertige Beschreibungstechnik, nämlich NEAε , gibt. Satz 6.5 NEA u. NEAε beschreiben reguläre Sprachen. Beweis argumentativ; als Übungsaufgabe
An folgendem Beispiel wird die Berechnung von δ aus δ vorgeführt. Beispiel 6.13 Gegeben sei M = ({q0 , q1 , q2 , q3 , q4 , q5 }, { , }, δ , q0 , {q2 }), mit δ q0 q1 q2 q3 q4 q5
{q3 } {q4 } {} {} {} {}
{} {q2 } {} {q4 } {} {}
ε {q1 } {} {} {q1 } {q5 } {}
Start
q0
ε
q1
a
ε
a
q3
b
q4
Die von M akzeptierte Sprache ist nicht sehr aufregend: L(M) = { ,
b
q2
ε
q5
}. Wir führen die
Ende des Beweisteils 2
94
6 Endliche Automaten und reguläre Sprachen
Berechnung von δ (q0 , ) vor. δ (q0 , )
=
ε-Hülle(d(ε-Hülle(q0 ), ))
=
ε-Hülle(d({q0 , q1 }), )
=
ε-Hülle({q3 , q4 })
=
{q1 , q3 , q4 , q5 }
Es empfiehlt sich, die Teilergebnisse in eine Tabelle aufzunehmen, um die Nacheinanderausführung obiger Funktionen angemessen verwalten zu können. q q0 q0 q1 q1 q2 q2 q3 q3 q4 q4 q5 q5
a
ε-Hülle(q) {q0 , q1 } {q0 , q1 } {q1 } {q1 } {q2 } {q2 } {q1 , q3 } {q1 , q3 } {q4 , q5 } {q4 , q5 } {q5 } {q5 }
d(Spalte 3,a) {q3 , q4 } {q2 } {q4 } {q2 } 0/ 0/ {q4 } {q2 , q4 } 0/ 0/ 0/ 0/
ε-Hülle(Spalte 4)= δ (q, a) {q1 , q3 , q4 , q5 } {q2 } {q4 , q5 } {q2 } 0/ 0/ {q4 , q5 } {q2 , q4 , q5 } 0/ 0/ 0/ 0/
E = {q2 } kann man aus der dritten Spalte, also aus den Werten für ε-Hülle(q), direkt ablesen: Die einzige Teilmenge in dieser Spalte, die den Endzustand von M, nämlich q2 , enthält, ist {q2 }. Nun können wir den gesuchten NEA M ohne ε-Übergänge vollständig angeben: M = ({q0 , q1 , q2 , q3 , q4 , q5 }, { , }, δ , q0 , {q2 }), mit b
Start
q0
a
q1 a
a
b
b
a
q2
a
a q3
a, b
q4
q5
a, b
Schaut man etwas genauer auf den Graph der Überführungsfunktion δ , fallen Zustände auf, von denen keinen Pfeile abgehen. Für den Endzustand q2 ist das in Ordnung. Die Zustände q4 und q5 und mit ihnen alle zuführenden Übergänge können jedoch entfallen. Es entsteht die folgende reduzierte Überführungsfunktion:
6.7 NEA mit ε-Übergängen
Start
95
q0
b a a
q2 b
q1
b
q3
Auch diese Funktion kann vereinfacht werden: Offensichtlich gibt es für und nur für dieses Wort zwei gleichartige Wege von q0 zu q2 – einen über q1 und einen über q3 . Die beiden Wege können zu einem verschmolzen werden. Dies ergibt dann: Start
q0
b a
q2 b
q1
Computerübung 6.22 Verwenden Sie AutoEdit zur Definition des NEAε M aus Beispiel 6.13. Konvertieren Sie M in einen äquivalenten NEA ohne spontane Übergänge M . Verwenden Sie AutoEdit ebenfalls für diese Konvertierung und vergleichen Sie anschließend das Ergebnis mit dem im Beispiel angegebenen. Übung 6.8 Konstruieren Sie für den folgenden NEAε M = ({S, S1 , A, H, B, S2 }, { , }, δ , S, {A, H}), mit b
Start
S
a
S1
ε
b
A
b
ε
S2
a a H
B
einen äquivalenten NEA ohne ε-Übergänge per Handrechnung. Computerübung 6.23 Vergleichen Sie Ihr Ergebnis in Übungsaufgabe 6.8 mit der von AutoEdit durchgeführten Konvertierung. Welche(s) der Wörter ab und abb akzeptiert der Automat?
Schlussbetrachtung: Zur Beschreibung regulärer Sprachen sind NEAs mit spontanen Übergängen nicht nur geeignet, sondern für den Entwurf besonders zu empfehlen. Für uns Menschen ist dies das übersichtlichste Entwurfswerkzeug, wenn man bereit ist, das Konzept des Nichtdeterminismus anzunehmen. Ist man an praktischen Umsetzungen, also an lauffähigen Parsern bzw. Scannern, interessiert,
96
6 Endliche Automaten und reguläre Sprachen
sind sie jedoch aus Effizienzgründen die schlechteste Wahl. Hierfür wählt man Minimal-DEAs. Im vorangehenden Text haben wir die Sicherheit geschaffen, jeden beliebigen NEAε über die Stufen NEA und DEA in einen Minimal-DEA transformieren zu können. Und mit AutoEdit haben wir auch die reale Möglichkeit, dies bequem zu erledigen.
6.8 Das Pumping Lemma für reguläre Sprachen Einer vorgegebenen kontextfreien Sprache sieht man im allgemeinen nicht an, ob sie regulär ist, d.h. mit einer regulären Grammatik, einem DEA oder einem NEA (mit und ohne ε-Übergängen) definiert werden kann. Von daher wäre ein dementsprechendes Entscheidungswerkzeug wünschenswert. Wenn man sich darunter ein Entscheidungsverfahren vorstellt, das für jede Sprache L entweder mit „Ja“ antwortet, wenn L regulär ist, und anderenfalls mit „Nein“, wird man enttäuscht sein. Ein solches Verfahren gibt es leider nicht. (Auch auf dem Satz von N ERODE und M YHILL lässt sich kein praktikables Verfahren aufbauen, das in jedem Falle zu einer Entscheidung führt.) Pumping Lemma Beweis der Nichtregularität einer Sprache
Auf den ersten Blick ist es sicher eine Überraschung, dass ein Verfahren namens Pumping Lemma existiert, mit dem die Nichtregularität einer Sprache nachgewiesen werden kann. Leider gelingt ein Nachweis nicht für jede nicht-reguläre Sprache. Dennoch ist das Pumping Lemma für reguläre Sprachen das diesbezüglich leistungsfähigste bekannte Entscheidungswerkzeug. Es ist auch unter dem Namen Iterationssatz, Schleifenlemma oder uvw-Theorem bekannt. Didaktischer Hinweis 6.9 Anstelle einen Satz an den Anfang zu stellen und dessen Beweis anzuschließen, geben wir eine Herleitung an. Diese mündet in die Formulierung des Pumping Lemmas für reguläre Sprachen und liefert rückblickend den Beweis. Die Motivation für das Pumping Lemma wäre viel geringer, wenn wir in traditionelle Weise diesen Satz vorgeben und dessen Beweis anschließen würden.
Herleitung des Satzes
Herleitung: Wir dürfen davon ausgehen, dass es für jede reguläre Sprache L (mindestens) einen DEA gibt, der genau L akzeptiert. Ein solcher Automat hat endlich viele, sagen wir n, mit n > 0, Zustände. Wir betrachten ein Eingabewort z = a1 a2 a3 . . . an . . . am der Länge m ≥ n, wobei keineswegs gefordert ist, dass alle in z vorkommenden Alphabetzeichen ai paarweise verschieden sind. Der DEA startet in q0 und berechnet als ersten Folgezustand δ (q0 , a1 ) = q1 . Anschließend geht er in den Zustand δ (q1 , a2 ) = q2 über usw.17 Schließlich liest gehen wir davon aus, dass die Zustände gerade so benannt sind, dass δ in der hier verwendeten Form definiert werden kann.
17 O.B.d.A.
6.8 Das Pumping Lemma für reguläre Sprachen
97
er an und nimmt Zustand qn ein. Bis dahin lautet die zugehörige Zustandsfolge (q0 , q1 , q2 , . . . , qn ). Danach folgt noch das Restwort an+1 an+2 . . . am . Da der Automat – nach Voraussetzung – genau n Zustände besitzt, muss es in der Folge18 (q0 , q1 , q2 , . . . , qn ) mindestens einen Zustand geben, der mehrfach vorkommt. Bezieht man das Restwort ein, so gilt das ebenso. Es reicht aber völlig aus, den Wortanfang der Länge n zu betrachten, denn bei dessen Analyse findet mindestens eine solche „Zustandswiederkehr“ zwangsläufig statt. Es gibt also (mindestens) ein j und ein k, mit 0 ≤ j < k ≤ n, sodass q j = qk . Dann ergibt sich folgendes Bild. q0
a1 . . . a j
q j = qk
ak+1 . . . am
qm
a j+1 . . . ak Abbildung 6.14: Beweisskizze für das Pumping Lemma regulärer Sprachen
Der Teil des Wortes, der den schon einmal eingenommenen Zustand q j wiederbringt, also a j+1 . . . ak , wurde in Abbildung 6.14 als Schleife dargestellt. Die Länge dieses Teilworts ist mindestens gleich 1, denn j < k. Wenn qm ∈ E, dann gehört z zu L. Das Teilwort a j+1 . . . ak hat darauf keinen Einfluss. Es spielt keine Rolle, ob der Zustand q j mehr als einmal vorkommt, wenn er wenigstens einmal eingenommen wird. Folglich kann die Schleife ebenso entfallen wie beliebig oft durchlaufen werden. Dies bedeutet anschaulich, dass das Teilwort a j+1 . . . ak aus z herausgeschnitten oder beliebig oft „hineingepumpt“ werden kann, was wohl zur Namensgebung des Satzes geführt hat. D.h., wenn z = a1 a2 a3 . . . an . . . am ∈ L, dann gilt für beliebiges i ≥ 0: a1 a2 . . . a j (a j+1 . . . ak )i ak+1 . . . an . . . am ∈ L. u
v
w
Satz 6.6 Wenn L eine reguläre Sprache ist, dann existiert eine natürliche Konstante n > 0, sodass sich jedes Wort z aus L mit |z| ≥ n in der Form z = uvw schreiben lässt und für alle natürlichen i ≥ 0 gilt uvi w ∈ L, mit |v| ≥ 1 und |uv| ≤ n. (Die Konstante n ist nicht größer als die Zahl der Zustände eines DEA für L.) Beweis s. Herleitung oben 18 Die
Folge enthält n + 1 Zustände.
98
6 Endliche Automaten und reguläre Sprachen
Beispiel 6.14 Für eine reguläre Sprache, wie L = {am | m ≥ 0} = {ε} ∪ {am | m > 0}, kann man sich nun leicht davon überzeugen, dass es ein n gibt, sodass sich die in Satz 6.6 geforderte Zerlegung für jedes Wort aus {am | m > 0} vornehmen lässt: Schnell stellt man fest, dass n = 1 das Gewünschte leistet. Jedes Wort z = ak , mit k ≥ n = 1, lässt sich dann zerlegen in z = uvw, mit u = ε, v = a und w = ak−1 . Nun kann man sich leicht davon überzeugen, dass die drei geforderten Bedingungen erfüllt sind: |v| = |a| = 1 ≥ 1 (OK), |uv| = 1 ≥ n = 1 (OK), uvi w = ai ak−1 = ak+i−1 = am ∈ L (OK).
Natürlich wäre es wünschenswert, wenn wir das Pumping Lemma nicht nur bestätigend, sondern auch zum Beweis der Regularität einer Sprache einsetzen könnten. Kann man Satz 6.6 einfach umkehren, d.h. aus der Existenz eines n mit den geforderten Eigenschaften (B), folgern, dass die Sprache regulär ist (A)? Nein, das dürfen wir nicht: Mit dem Pumping Lemma gilt A ⇒ B und damit ist B eine notwendige, keinesfalls hinreichende Bedingung für A. Beispiel 6.15 Die Sprache L = {ah b j ck | h = 0 oder j = k} ist nicht-regulär, erfüllt aber trotzdem die Forderungen aus dem Pumping Lemma. Für n = 1 gibt es für die Zerlegung z = uvw, mit u = ε, jedes Wortes z ∈ L folgende Fälle: Fall 1: v = a, für h = 0, w = ar b j c j Fall 2: v = b, für h = 0, j = 0, w = br ck Fall 3: v = c, für h = j = 0, w = cr Die drei Bedingungen sind erfüllt: |v| = 1 ≥ 1 (OK), |uv| = 1 ≥ n = 1 (OK), uvi w = ai w = ai ar b j c j = as b j c j ∈ L, also für Fall 1 OK. uvi w = bi w = bi br ck = bq ck ∈ L, also für Fall 2 OK. uvi w = ci w = ci cr = c p ∈ L, also für Fall 3 OK. Zum Nachweis der Nichtregularität dieser Sprache muss man auf den Satz von Nerode und Myhill (Satz 6.2, s. Seite 81) zurückgreifen.
Wie Beispiel 6.15 zeigt, kann Satz 6.6 nicht einfach umgekehrt werden. Aber was kann man mit so einem Satz anfangen, wenn das eigentliche Untersuchungsziel als zutreffend vorausgesetzt wird? Wenn A, dann B. Wenn nicht B, dann nicht A.
Aus dem Satz „Wenn L regulär ist, dann existiert eine Konstante n, sodass ...“ (A ⇒ B) kann man nicht die Gültigkeit von „Wenn eine Konstante n existiert, sodass ..., dann ist L regulär“ folgern. Korrekt ist jedoch: „Wenn NICHT existiert eine Konstante n, sodass ..., dann ist L NICHT regulär“. (¬B ⇒ ¬A) Damit ist die Begründung für den bereits am Anfang dieses Abschnitts beschriebenen „merkwürdigen“ Gebrauch des „Entscheidungswerkzeugs“ gegeben. Leider gibt es nicht-reguläre Sprachen, deren Nichtregularität mit dem Pumping Lemma nicht ohne weiteres nachgewiesen werden kann. Dann bleibt wiederum nur der Satz von Nerode und Myhill (Satz 6.2), um zu zeigen, dass es unendlich viele Äquivalenzklassen gibt.
6.9 Endliche Maschinen
99
Übung 6.9 Weisen Sie die Korrektheit dieser Folgerung mit den Mitteln der Aussagenlogik nach.
Beweistechnisch gesehen, ergeben sich zwei Möglichkeiten, um die Nichtregularität einer Sprache L mit dem Pumping Lemma nachzuweisen: Entweder man zeigt ¬B, d.h., dass es ein solches n nicht gibt, woraus sich dann mittels ¬B ⇒ ¬A die gewünschte Aussage ¬A ergibt. Oder man konstruiert aus der Annahme, L sei regulär, mittels A ⇒ B einen Widerspruch, wodurch das Gegenteil der Annahme Gültigkeit erlangt. Letzteres ist ein indirekter Beweis, s. Beispiel 6.16. Die Kernaktivität läuft für diese beiden Beweisstrategien auf das Gleiche hinaus. Beispiel 6.16 Es ist zu zeigen, dass L = {ak bk | k ≥ 1} nicht-regulär ist. Annahme: L ist regulär. Dann existiert lt. Pumping Lemma ein wie in Satz 6.6 gefordertes 2n
. . b. Es entsteht aus ak bk für k = n, mit n ≥ 1, n. Wir betrachten das Wort z = a . . a b . . n
n
und gehört daher zu L. Wenn wir das n-lange Anfangsstück von z in der Form uv darstellen, ergibt sich wegen |uv| ≤ n zwingend, dass v, wegen |v| ≥ 1, aus mindestens einem a besteht. Dann gilt für das spezielle Wort uv0 w = an−|v| bn ∈ L. Damit haben wir den Widerspruch, denn das jedes Wort der Form uvi w sollte zu L gehören. Die Annahme ist also falsch. L ist folglich nicht-regulär. Didaktischer Hinweis 6.10 Im Beweis muss von einem beliebigen und damit unbekannten n ausgegangen werden. Es wäre absolut falsch, einfach ein festes n, z.B. n = 4, zu wählen und damit die Zerlegung von z vorzunehmen. Dieser Versuch wird aber sehr gern unternommen. Übung 6.10 2 Gegeben sei die Sprache L = {0i | i > 0}. Welche Sprache wird mit L beschrieben? Zeigen Sie mit dem Pumping Lemma, dass L nicht-regulär ist. Übung 6.11 Entscheiden (und begründen) Sie, welche der folgenden Sprachen regulär sind und welche n nicht. L1 = {ai b2i | i ≥ 1}, L2 = {(ab)i | i ≥ 1}, L3 = {a2n | n ≥ 1}, L4 = {a2 | n ≥ 0}, n m n+m | n, m ≥ 1} L5 = {a b a
6.9 Endliche Maschinen Endliche Automaten haben einen festen Platz in der theoretischen Informatik. Aber auch dann, wenn man ihre Praxisrelevanz beurteilt, schneiden sie gut ab. Als Akzeptoren für Sprachen können sie zur Modellierung von Prozessen mit sequentiellen Zustandswechseln (Robotersteuerung, Web-Applikationen etc.) ebenso Verwendung finden, wie zur Definition und Analyse zulässiger Eingaben in GUIs.
indirekter Beweis
100
Grenzen von Akzeptoren
DEA + Ausgabe = endliche Maschine
6 Endliche Automaten und reguläre Sprachen
Denkt man an reale Automaten, die als Getränke- und Fahrkartenautomaten unser gesellschaftliches Lebensbild prägen, so gibt es jedoch Aspekte, die mit Akzeptoren nicht modelliert werden können: Zu diesen Defiziten gehört die Ausgabe von Daten nach jedem Zustandswechsel. Solche Ausgaben sind nützlich, z.B. zur Zwischeninformation des menschlichen Nutzers über den noch zu zahlenden Restbetrag nach Einwurf einer Münze mit einem bestimmten Wert. Offensichtlich ist es sinnvoll, DEAs um eine Ausgabe zu erweitern. DEAs mit Ausgabe nennt man endliche Maschinen. Abstrakte Maschinen-Modelle dieser Art sind M EALY19 - und M OORE20 -Maschinen. Wie DEAs besitzen sie eine Überführungsfunktion, jedoch keine Endzustandsmenge. Zusätzlich verfügen sie über eine Ausgabefunktion. Obwohl endliche Maschinen (im Unterschied zu DEAs) keine Akzeptoren sind, können sie so beschaffen sein, dass die Akzeptanz eines Eingabewortes (z.B. Folge eingeworfener Münzen) an der Gestalt des erzeugten Ausgabewortes festgestellt werden kann: So könnte die Ausgabe beispielsweise mit enden, um anzuzeigen, dass das anfangs auf dem Eingabeband stehende „Münz-Wort“ (Getränkepreis) akzeptiert wird.
M OOREMaschine
Falls das in jedem Schritt auszugebende Zeichen ausschließlich von dem jeweils aktuellen Zustand der Maschine abhängt, d.h. der zugehörige Übergang keine Rolle spielt, handelt es sich um eine sog. M OORE-Maschine. Beispielsweise können die Zustände einer M OORE-Maschine, die einen Fahrkartenautomaten modelliert, den jeweils noch zu zahlenden Restbetrag repräsentieren. Die Ausgabefunktion einer M OORE-Maschine nimmt also nur den aktuellen Zustand als Argument.
M EALYMaschine
Bei M EALY-Maschinen ist das ein wenig anders: Die noch zu zahlenden bzw. die Rückgabebeträge (Ausgabe) werden zusammen mit dem nächsten möglichen Eingabe-Münzwert an den Kanten des entsprechenden Übergangsgraphen vermerkt. Jede Kante des Übergangsgraphen einer M EALY-Maschine ist also sowohl mit dem jeweiligen Eingabezeichen als auch dem zugehörigen Ausgabezeichen markiert.
Kaffee-Automat
Die M EALY-Maschine, deren Zustandsübergänge in Abbildung 6.15 dargestellt sind, treten in ähnlicher Form bei einem Kaffee-Automaten auf. Eine Tasse Kaffee kostet 1,50 e. Der Automat akzeptiert lediglich 50-cent- und 1-e-Münzen. Die Ausgabefunktion ist hierbei eine zweistellige Funktion, die neben dem aktuellen Zustand ein Zeichen des Eingabealphabets nimmt und das zugehörige Ausgabezeichen zurückgibt. Zum Eingabewort (oder etwas übersichtlicher: ) erzeugt die M EALY-Maschine, deren Überführungsfunktion in Abbildung 6.15 grafisch dargestellt ist, folgende Ausgabe: 19 George
H. Mealy, 1955 Forrest Moore (1925-2003) war einer der Mitbegründer der Automatentheorie und Erfinder des nach ihm benannten M OORE-Automaten (1956). Von 1966 bis 1985 lehrte er als Professor für Mathematik und Informatik an der University of Wisconsin.
20 Edward
6.9 Endliche Maschinen
101
q1
100 / Bitte Kaffee entnehmen.\n
50 / noch zu zahlen: 1 Euro\n
Start
q0
50 / noch zu zahlen: 50 cent\n
100 / noch zu zahlen: 50 cent\n
q2
50 / Bitte Kaffee entnehmen.\n
q4
100 / Geldrückgabe: 1 Euro\n 50 / Geldrückgabe: 50 cent\n
100 / Bitte Kaffee und 50 cent entnehmen.\n
q3
100 / Geldrückgabe: 1 Euro\n 50 / Geldrückgabe: 50 cent\n
Abbildung 6.15: M EALY-Maschine zur Modellierung eines Kaffeeautomaten
Computerübung 6.24 Verwenden Sie AutoEdit zum Editieren der Maschine aus Abbildung 6.15 und simulieren Sie die Verarbeitung selbstgewählter Eingabewörter. Geben Sie die zugehörige Konfigurationenfolge an. Um eine zeilenweise formatierte Ausgabe zu erhalten, müssen die Zeichen enden. des Ausgabealphabets mit Zeilenwechselzeichen Definition 6.7 Eine M EALY-Maschine ist ein Sechstupel M = (Q, Σ, Δ, δ , λ , q0 ), wobei Q, Σ, δ und q0 die von DEAs bekannten Bedeutungen haben. Δ ist das Ausgabealphabet und λ ist eine totale Funktion der Form λ : Q × Σ → Δ.
Eine endliche Maschine startet mit einem Eingabewort aus Σ∗ auf dem Band und stoppt, wenn dieses Wort vollständig eingelesen wurde. Dies geschieht zeichenweise und wird von entsprechenden Zustandswechseln begleitet. Dieses Verhalten entspricht genau dem eines DEAs.
Start Stopp
Die Ausgabe, die für jedes Eingabewort von einer endlichen Maschine erzeugt wird, ergibt sich durch Verkettung sämtlicher Einzelausgaben vom Start bis zum Stopp.
Ausgabe
102
6 Endliche Automaten und reguläre Sprachen
Im Übergangsgraphen für M EALY-Maschinen werden die Kanten in der Form a/b markiert, wobei a ∈ Σ und b ∈ Δ∗ . Eine M EALY-Maschine berechnet genau eine Funktion f : Σ∗ → Δ∗ , wobei f (x) = ∗
y genau dann, wenn (q0 , x, ε) (qi , ε, y) und qi ist irgendein Zustand aus Q. y ist die von der Maschine produzierte Ausgabe. Beispiel 6.17 Die M EALY-Maschine mit M1 = ({q0 , q1 , q2 }, { , }, { , }, δ , λ , q0 ) liefert beispielsweise das Ausgabewort . zur Eingabe von a/1
Start
q0
b/0
q1
a/1 b/0
b/0
q2
a/0
Abbildung 6.16: M EALY-Maschine zur Berechnung einer Funktion f : Σ∗ → Δ∗ Computerübung 6.25 Geben Sie die vollständige Definition von M1 in Beispiel 6.17 an. Notieren Sie hierzu für δ und λ die zugehörigen Tabellen. Vergleichen Sie Ihre Lösung mit der Angabe im AutoEditExport. Computerübung 6.26 Erweitern Sie die Definition der in Abbildung 6.15 angegebenen M EALY-Maschine für den Kaffee-Automaten so, dass auch 2-e-Münzen als zulässiges Zahlungsmittel akzeptiert werden. Vergleichen Sie Ihre Lösung mit der in Abbildung 6.17.
Akzeptoren
Mit geeigneten Ausgabefunktionen können M EALY-Maschinen die Akzeptanzanalyse endlicher Automaten simulieren. Beispiel 6.18 Wir betrachten die Sprache L = {w | w = v oder w = v , v ∈ { , }∗ }. Es ist die Menge aller Wörter, die aus beliebig vielen en und en bestehen und mit mindestens zwei gleichen Zeichen enden. }, δ , λ , q0 ), s. Eine entsprechende M EALY-Maschine M2 = ({q0 , q1 , q2 }, { , }, { , -Wort ausgeben, das genau dann mit endet, Abbildung 6.18, soll jeweils ein wenn das Eingabewort zu der gegebenen Sprache gehört. Hier wird also die Akzeptanzanalyse eines Automaten durch eine bestimmte Form des Ausgabewortes einer M EALYMaschine simuliert.
6.9 Endliche Maschinen
103
q1
100 / Bitte Kaffee entnehmen.\n
50 / noch zu zahlen: 1 Euro\n
200 / Bitte Kaffee und 1 Euro entnehmen.\n Start
q0
50 / noch zu zahlen: 50 cent\n
100 / noch zu zahlen: 50 cent\n
q2 100 / Geldrueckgabe: 1 Euro\n 200 / Geldrueckgabe: 2 Euro\n 50 / Geldrueckgabe: 50 cent\n
50 / Bitte Kaffee entnehmen.\n
q4
q3
100 / Bitte Kaffee und 50 cent entnehmen.\n 200 / Bitte Kaffee und 1,50 Euro entnehmen.\n
100 / Geldrueckgabe: 1 Euro\n 200 / Geldrueckgabe: 2 Euro\n 50 / Geldrueckgabe: 50 cent\n
200 / Bitte Kaffee und 50 cent entnehmen.\n
Abbildung 6.17: 1.50-e-Kaffee-Automat akzeptiert nur 0.50-e-, 1-e-, 2-e-Münzen 1 / yes
Start
q0
1 / no
q1
1 / no
0 / no
0 / no q2
0 / yes
Abbildung 6.18: M EALY-Maschine für L = {w | w = v
oder w = v
Eine Simulation mit AutoEdit liefert für das Eingabewort .
, v ∈ { , }∗ }
die Ausgabe
Man kann nachweisen, dass ein DEA für die Sprache aus obigem Beispiel mindestens fünf Zustände benötigt. Computerübung 6.27 Geben Sie einen DEA für die Sprache L = {w | w = v
oder w = v
, v ∈ { , }∗ } an.
Definition 6.8 Eine M OORE-Maschine ist ein Sechstupel M = (Q, Σ, Δ, δ , λ , q0 ), wobei Q, Σ, δ und q0 die von DEAs bekannten Bedeutungen haben. Δ ist das Ausgabealphabet und λ ist eine totale Funktion der Form λ : Q → Δ.
Eine M OORE-Maschine berechnet genau eine Funktion f : Σ∗ → Δ∗ , wobei f (x) = ∗
y genau dann, wenn (q0 , x, λ (q0 )) (qi , ε, y) und qi ist irgendein Zustand aus Q.
104
6 Endliche Automaten und reguläre Sprachen
Beispiel 6.19 M3 = ({q0 , q1 , q2 }, { , }, { , }, δ , λ , q0 ) ist eine M OORE-Maschine, die für je ein Eingabewort aus { , }∗ ein entsprechendes Wort aus {no, yes}∗ ◦ {yes} ausgibt, wenn das verarbeitete Eingabewort mit zwei gleichen Zeichen endet. Eine Simulation mit AutoEdit die Ausgabe . liefert für das Eingabewort q1 no
0
0 Start
q0 no
q3 yes
0
q4 yes
1
1 0
1 0
1 q2 no
1
Abbildung 6.19: M OORE-Maschine für L = {w | w = v
oder w = v
, v ∈ { , }∗ }
Computerübung 6.28 Geben Sie die vollständige Definition von M3 an. Notieren Sie für δ und λ die zugehörigen Tabellen. Vergleichen Sie Ihre Lösung mit der Angabe im AutoEdit-Export.
endliche Automaten und endliche Maschinen
zellularer Automat Petri-Netz
Endliche Maschinen haben die gleiche Leistungsfähigkeit wie endliche Automaten. Außerdem kann man zeigen, dass M EALY- und M OORE-Maschinen äquivalent sind, d.h. zu jeder M OORE-Maschine existiert eine äquivalente M EALYMaschine und umgekehrt. Die zugehörigen Beweise sind konstruktiver Natur: Sie liefern die erforderliche Vorschrift zur Konstruktion der herzustellenden Maschine aus der jeweils gegebenen. Außerdem gilt, dass M EALY-Maschinen im Allgemeinen mit weniger Zuständen auskommen als ihre M OORE-Maschinen-Pendents. Endliche Automaten und endliche Maschinen modellieren sequentielle Zustandsübergänge und eignen sich deshalb zur Beschreibung digitaler Schaltungen. Durch Komposition können sie auch zur Modellierung paralleler Übergänge verwendet werden. Dies tritt beispielsweise bei zellularen Automaten und Petri-Netzen auf. Sinnvolle Anwendungen endlicher Maschinen sind Software- und Systementwicklung, Workflow-Management, elektronischer Handel und Web Services.
7 Reguläre Ausdrücke 7.1 Definition Wir wissen, dass reguläre Sprachen mit C HOMSKY-Typ-3-Grammatiken definiert werden können. Außerdem haben wir gezeigt, dass sich DEA und NEA in gleicher Weise dafür eignen. Reguläre Ausdrücke stellen eine weitere Beschreibungsmöglichkeit regulärer Sprachen dar. Reguläre Ausdrücke sind sehr kompakt und von daher in der Praxis sehr verbreitet. Wir beginnen mit der Definition regulärer Mengen. Die Idee besteht darin, zu deren Definition nur ganz wenige Basismengen und insgesamt nur drei Mengenoperationen zuzulassen. Durch deren wiederholte und kombinierte Anwendung kann jede beliebige reguläre Menge (Sprache) erzeugt werden und etwas anderes als reguläre Mengen kann auf diesem Wege nicht entstehen. Die einzigen Basismengen sind: 0/ {ε} {a}
∗
reguläre Mengen
Die leere Menge, die also kein einziges Wort enthält, nicht einmal das leere Wort. Die Menge, die nur das leere Wort ε enthält. Alle Einermengen, die nur jeweils genau ein einzeichiges Wort, das aus dem zugehörigen Alphabetzeichen besteht, enthalten.
Die so definierten Basismengen sind reguläre Mengen. Durch Anwendung der im Folgenden angegebenen Operationen – und nur durch diese – entstehen weitere reguläre Mengen. ∪ ◦
regulärer Ausdruck
Vereinigung: Konkatenation: K LEENE-Stern:
Operationen
A ∪ B = {w | w ∈ A oder w ∈ B} A ◦ B = {w = w1 w2 | w1 ∈ A und w2 ∈ B} A∗ = A0 ∪ A1 ∪ . . . ∪ Ai ∪ . . .
Auf die jeweils erzeugten Resultatmengen dürfen diese Operationen erneut angewandt werden, so dass wiederum reguläre Mengen über dem zugrunde liegenden Alphabet entstehen. Die Klasse der regulären Mengen wird nur durch die auf die angegebene Weise erzeugten Mengen gebildet. Alle anderen Mengen sind nicht regulär. Die binäre Operation Verkettung (Konkatenation) wurde zunächst für Zeichen eingeführt und später auf Wörter und schließlich auf Mengen (Sprachen) ausgeweitet. Das Operationszeichen ◦ wird vereinbarungsgemäß gerne weggelassen.
Ausschlussregel
106
7 Reguläre Ausdrücke
Beispiel 7.1 Man unterscheide Konkatenation und Vereinigung für zwei Mengen: { } ◦ { } = { }{ } = { } aber { } ∪ { } = { , }. Beispiel 7.2 Der K LEENE1 -Stern2 wurde im Zusammenhang mit dem Begriff der Wortmenge einge, , , , , . . .}. führt: { , }∗ = {ε, , , , , , ,
Mit Hilfe von regulären Mengen kann man reguläre Sprachen definieren. Beispielsweise beschreibt der Ausdruck ((({ }(({ } ∪ { })∗ ))({ }{ }))(({ } ∪ { })∗ )) Mustervorstellung Notation
reguläre Ausdrücke
die Menge aller Wörter über { , }, die mit beginnen und zwei s unmittelbar nebeneinander enthalten. Das kürzestes Wort, das auf dieses Muster (Pattern) passt, ist . Die Schreibweise des obigen Ausdrucks ist sehr unübersichtlich. Neben der Häufung an runden Klammern fällt auf, dass eine kompaktere Notation durch das Weglassen der geschweiften Klammern, die die Mengen anzeigen, erzielt werden könnte. Hierzu ist es nötig, geeignete Symbole für die jeweiligen Einermengen einzuführen. Diese müssen sich wiederum von den entsprechenden Mengenelementen unterscheiden. Hierfür verwendet man Fettdruck (gelegentlich auch Unterstreichungen) und spricht von einem regulären Ausdruck (engl.: regular expression, kurz: RegExp oder Regex). Definition 7.1 Reguläre Ausdrücke über einem Alphabet Σ sind wie folgt induktiv definiert. 1. 0, / ε und a, für jedes ∈ Σ, sind reguläre Ausdrücke über Σ und bezeichnen die Mengen 0, / {ε} bzw. { }. 2. Seien u und v reguläre Ausdrücke über Σ und L(u) bzw. L(v) die zugehörigen Sprachen. Dann sind auch a) (u + v) = L(u) ∪ L(v), b) (u · v) = L(u) ◦ L(v) und c) (u∗ ) = L(u)∗ sowie (v∗ ) = L(v)∗ reguläre Ausdrücke über Σ. 3. Nur die mit diesen Regeln erzeugten Ausdrücke sind reguläre Ausdrücke über Σ.
Obwohl in obiger Definition nicht ausgewiesen, werden die Notationen x+ = xx∗ und xn = x . . x zur Abkürzung gerne benutzt. . n−mal
1 sprich: 2 Dieser
klini; Stephan Kleene: 1909-1998 Stern-Operator wird auch K LEENEsche Hülle oder auch endlicher Abschluss genannt.
7.2 Klammersparregeln
107
Beispiel 7.3 Die oben betrachtete Sprache ((({a}(({a} ∪ {b})∗ ))({b}{b}))(({a} ∪ {b})∗ )) kann mit folgendem regulären Ausdruck (((a · (a + b)∗ ) · (b · b)) · (a + b)∗ ) definiert werden. Es stören lediglich die vielen runden Klammern.
7.2 Klammersparregeln Ähnlich wie in der Programmiersprache Scheme (Lisp) dienen runde Klammern in regulären Ausdrücken dazu, die Auswertungsreihenfolge der entsprechenden Operationen festzulegen. Man kann sie vermeiden, wenn man bereit ist, bestimmte Prioritätsfestlegungen zu akzeptieren. Die Prioritätenfolge lautet (beginnend mit der höchsten): ∗ , ·, +. Da Vereinigung und Konkatenation assoziative Operationen sind, dürfen die betreffenden Klammern einfach weggelassen werden.
Prioritätenfolge
Beispiel 7.4 Der obige Beispielausdruck kann dann noch kompakter als a(a + b)∗ bb(a + b)∗ geschrieben werden. Bezogen auf den Schreibaufwand ist dies die kürzeste Form zur Definition regulärer Sprachen.
Die Wahl der Operationssymbole ∗ , · und + für reguläre Ausdrücke suggeriert eine Verwandschaft mit dem Potenzieren, Multiplizieren und Addieren natürlicher Zahlen. Hier gilt die gleiche Prioritätenfolge. Es gibt aber auch Unterschiede, wie wir in Abschnitt 7.3 sehen werden.
7.3 Äquivalente reguläre Ausdrücke Es ist sofort einzusehen, dass es im Allgemeinen mehr als einen regulären Ausdruck zur Definition einer bestimmten Sprache gibt. Ähnlich wie bei äquivalenten Termumformungen dürfen folgende Gleichheiten benutzt werden. u, v und w seien reguläre Ausdrücke. 1. 0u / = u0/ = 0/ – mit Erinnerung an die Multiplikation mit 0. Sehr wichtig! 2. εu = uε = u – mit Erinnerung an die Multiplikation mit 1. 3. 0/ ∗ = ε 4. ε ∗ = ε
Umformungsregeln
108
7 Reguläre Ausdrücke
5. u + v = v + u – Erinnerung an die Kommutativität der Addition. 6. u + 0/ = u – Erinnerung an die Addition von 0. 7. u + u = u – verstößt gegen die Analogie. 8. (u∗ )∗ = u∗ 9. u(v + w) = uv + uw – mit Erinnerung an die Distributivität. 10. (uv)∗ u = u(vu)∗ 11. (u + v)∗ = (u∗ + v∗ )∗ Übung 7.1 Weisen Sie einige der hier angegebenen Gleichungen nach. Übung 7.2 Gegeben sind L1 = {
, } und L2 = {
,
}. Ermitteln Sie L1 L2 und {
,
}∗ .
Übung 7.3 Welche Sprache L wird durch den regulären Ausdruck c∗ (b + ac∗ )∗ über dem Alphabet { , , } beschrieben? und zu L gehören. Zeigen Sie außerdem, dass Übung 7.4 Zeigen Sie in (1 + 10)i durch vollständige Induktion über i, dass die durch (1 + 10)∗ definierte Sprache keine Wörter mit zwei aufeinander folgenden Nullen enthält. Übung 7.5 Geben Sie einen regulären Ausdruck an, der die folgende Sprache beschreibt: L = {w ∈ { , }∗ | w beginnt oder endet mit }.
7.4 Reguläre Ausdrücke und endliche Automaten Wir wollen nun zeigen, dass reguläre Ausdrücke genau die gleiche Sprachklasse definieren, wie endliche Automaten, nämlich reguläre Sprachen. Etwas formaler regelt dies der folgende Satz von K LEENE. Satz 7.1 Eine Sprache L wird von einem DEA M genau dann akzeptiert, wenn L durch einen regulären Ausdruck R über dem zu M gehörenden Eingabealphabet beschrieben werden kann. Beweis Der Beweis erfolgt durch - Konstruktion von M aus R (1. Teil) und durch - Konstruktion von R aus M (2. Teil),
7.4 Reguläre Ausdrücke und endliche Automaten
109
wobei für beide Teile L(M) = L(R) = L gelten muss. Bei der geforderten Konstruktion eines äquivalenten DEA aus einem gegebenen regulären Ausdruck machen wir uns LNEAε ≡ LDEA zunutze. Es ist nämlich viel einfacher, einen zu R äquivalenten NEAε zu konstruieren als einen dementsprechenden DEA. Ein solcher NEAε für R besitzt genau einen Endzustand. Dies ist keine Einschränkung gegenüber der allgemeinen NEA-Definition, was wir im Folgenden zeigen werden. Satz 7.2 Zu jedem NEAε M gibt es einen äquivalenten NEAε M mit genau einem Endzustand q f . Beweis Man konstruiert M aus M, indem man für alle Endzustände qi von M spontane Übergänge δ (qi , ε) q f in M ergänzt. Es gilt E = {q f }. Beispiel 7.5 Gegeben sei der NEAε M = ({q0 , q1 , q2 , q3 }, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +, −}, δ , q0 , {q1 , q3 }) mit dem in Abbildung 7.1(a) dargestellten δ . Durch Anwendung des im Beweis formulierten Verfahrens erhält man einen zu M äquivalenten Automaten M = ({q0 , q1 , q2 , q3 }, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, +, −}, δ , q0 , {q f }) mit dem in Abbildung 7.1(b) dargestellten δ . Start
q0
0
q1
Start
q0
0
q1
ε qf +, -
1, 2,3,4,5,6,7,8,9
+, -
1, 2,3,4,5,6,7,8,9
ε q2
1, 2,3,4,5,6,7,8,9
q3
q2
1, 2,3,4,5,6,7,8,9
0, 1,2,3,4,5,6,7,8,9
(a) Beispiel-NEAε mit zwei Endzuständen
q3
0, 1,2,3,4,5,6,7,8,9
(b) zugeh. NEAε mit genau einem Endzustand
Abbildung 7.1: Transformation eines NEAε in einen NEAε mit genau einem Endzustand Wir beginnen mit dem Beweis von Teil 1 des Satzes 7.1, also der Transformation eines regulären Ausdrucks in einen äquivalenten NEA. In den folgenden Abbildungen sollen s an Startzustand und f an Finalzustand erinnern.
R NEAε
Der reguläre Ausdruck 0/ beschreibt die leere Menge. Es gibt einen Anfangs- und einen Endzustand, aber es findet kein einziger Übergang statt. Da das Alphabet nach Definition nicht leer sein darf, geben wir hier das Zeichen vor.
0/
Start
s
f
M = ({s, f }, { }, δ , s, { f }), L(M) = 0/ Für den regulären Ausdruck ε entsteht ein Automat, der nur das leere Wort akzeptiert.
ε
110
7 Reguläre Ausdrücke
Start
ε
s
f
M = ({s, f }, { }, δ , s, { f }), L(M) = {ε}
a
Für alle Alphabetzeichen
ergibt sich jeweils der zu a gehörende NEA.
Start
s
a
f
M = ({s, f }, { }, δ , s, { f }), L(M) = { }
a+b
Um den zu a+b gehörenden NEA zu erzeugen, brauchen wir zwei neue Zustände s (Anfangszustand) und f (Endzustand des zu konstruierenden NEAε ). Dann folgen spontane Übergänge von s zu den Anfangszuständen sa und sb bzw. von den Endzuständen fa und fb der „parallelgeschalteten“ Automaten zu f . sa
a
fa
ε Start
ε
s
f
ε
ε sb
b
fb
M = ({s, sa , sb , fa , fb , f }, { , }, δ , s, { f }), L(M) = { , }
a∗
Nun konstruieren wir einen NEA für a∗ . ε
sa
a
fa
ε
ε Start
ε
s
M = ({s, sa , fa , f }, { }, δ , s, { f }), L(M) = {ε, ,
a·b
,
s
ε
sa
a
fa
ε
M = ({sa , fa , sb , fb }, { , }, δ , sa , { fb }), L(M) = {
RegExp Edit
, . . .}
Ein NEA für a · b = a b = ab ergibt sich durch „Verkettung“ der zugehörigen Automaten in der angegebenen Reihenfolge. Start
Ende des Beweisteils 1
f
sb
b
fb
ε
f
}
Mit der Angabe dieses allgemein gültigen Verfahrens zur Konstruktion eines äquivalenten NEAε M aus einem gegebenen regulären Ausdruck R ist der erste Teil des Beweises beendet. Bevor wir uns der Gegenrichtung zuwenden, wollen wir eine weitere AtoCC-Komponente einführen: RegExp Edit. Abbildung 7.2 zeigt nicht nur, wie sich RegExp Edit optisch präsentiert, sondern enthält beispielbezogene Inhalte.
7.4 Reguläre Ausdrücke und endliche Automaten
111
Abbildung 7.2: AtoCC-Komponente RegExp Edit Didaktischer Hinweis 7.1 In Abbildung 7.2 ist ein zum regulären Ausdruck a äquivalenter NEAε dargestellt. Im Unterschied zu der weiter oben angegebenen Überführungsfunktion des Automaten für a sind zwei zusätzliche Zustände, ein Startzustand und ein Finalzustand, hinzugefügt worden. Dies ist aus technischen Gründen in RegExp Edit so eingebaut worden.
Didaktischer Hinweis 7.2 Beginnen Sie bei der Eingabe eines regulären Ausdrucks in RegExp Edit stets mit der Definition des zugrunde liegenden Alphabets. Computerübung 7.1 Geben Sie den regulären Ausdruck a∗ in RegExp Edit ein und vergleichen Sie die Überführungsfunktion des zugehörigen NEAε mit der auf Seite 110. Nutzen Sie zur Überprüfung manueller Umformungen die in RegExp Edit eingebaute Vergleichsmöglichkeit für zwei reguläre Ausdrücke. Schon relativ einfache reguläre Ausdrücke können durch Anwendung des im Beweisteil 1 vorgestellten Konstruktionsverfahrens zu recht unübersichtlichen Überführungsfunktionen führen. Nachträgliche Optimierungen sind wünschenswert. Computerübung 7.2 Wählen Sie den Tab „Minimierte NEA-Graph“, um für den aktuellen Ausdruck einen kompakten NEAε zu bekommen. Der erzeugte NEA kann außerdem in Auto Edit oder als Grammatik konvertiert in kfG Edit übertragen werden. Probieren Sie dies alles aus.
112
7 Reguläre Ausdrücke
Minimierung
Die Herstellung eines möglichst kurzen3 regulären Ausdrucks für einen von RegExp Edit erzeugten NEAε macht von einem Teil unserer Theorie-Kenntnisse Gebrauch: Der NEAε wird in einen äquivalenten NEA überführt, danach in einen äquivalenten DEA. Der DEA wird minimiert. Für den letzten Schritt, nämlich um aus einem DEA einen regulären Ausdruck zu erzeugen, benötigen wir noch die Legitimation durch den nun folgenden Beweisteil 2.
Beginn des Beweisteils 2 DEA R
Wir beweisen Teil 2, indem wir aus einem gegebenen DEA M = ({q1 , q2 , . . . , qn }, Σ, δ , q1 , E) einen äquivalenten regulären Ausdruck R konstruieren. Die Umbenennung der Zustände in der angegebenen Form ändert nichts an Sprache, die durch M akzeptiert wird, und kann für jeden DEA vorgenommen werden. Wir definieren Mengen Rk (i, j)
=
{w ∈ Σ∗ | δˆ (qi , w) = q j , wobei für alle eingenommenen Zwischenzustände qz gilt: z ≤ k}
induktiv über k = 0, 1, . . . , n, mit i, j = 1, 2, . . . , n. Rk (i, j) fasst alle Wörter aus der jeweils zugrunde liegenden Wortmenge zusammen, die M aus qi in q j wechseln lassen. Dabei können Zwischenzustände eingenommen werden, deren Indices nicht größer als k sind. k = 0 bedeutet, dass es keine solchen Zwischenzustände gibt. Dann gilt4 ⎧ ⎪ wenn i = j; ⎨ {a | δ (qi , a) = q j }, R0 (i, j) = ⎪ ⎩ {a | δ (q , a) = q } ∪ {ε}, wenn i = j. i j Abbildung 7.3 hilf uns, eine sinnvolle Definition für Rk+1 (i, j) zu finden. Es gibt praktisch zwei verschiedene „Wegarten“ von qi zu q j , nämlich die unter potenzieller Verwendung eines Zwischenzustands qz , mit z ≤ k, oder die Wegart, die qk+1 einbezieht. Außerdem kann es noch qk+1 -Schleifen geben, also Wege, die qk+1 als Start- und Zielknoten ggf. sogar mehrfach verwenden. Folgender Ausdruck fast das zusammen: Rk+1 (i, j) = Rk (i, j) ∪ Rk (i, k + 1) ◦ Rk (k + 1, k + 1)∗ ◦ Rk (k + 1, j). Es spielt im Grunde keine Rolle, ob man die so definierten Mengen als reguläre Ausdrücke notiert oder die mathematische Mengenschreibweise verwendet und nur die gesuchte Menge als regulären Ausdruck angibt. Der gesuchte reguläre Ausdruck ist
R= i,
Ende des Beweises
Rn (1, i).
mit qi ∈E
3 Die
Minimalisierung regulärer Ausdrücke ist sehr aufwendig. Bisher ist dafür nur ein Algorithmus bekannt, der zur Lösung exponentielle Zeit benötigt (NP-vollständiges Problem). Wir begnügen uns daher mit einer Transformationskette, die brauchbare Näherungslösungen liefert. 4 Die im Folgenden angegebenen Mengen enthalten Wörter der Länge 1. Um die Notation übersichtlich zu halten, werden lediglich die wortbildenden Alphabetzeichen angegeben.
7.4 Reguläre Ausdrücke und endliche Automaten
113
Rk (i, j) qi
qj
Rk (i, k + 1) Rk (k + 1, j) qk+1
Rk (k + 1, k + 1)∗
Abbildung 7.3: Konstruktion eines äquivalenten regExp aus einem DEA
Beispiel 7.6 Zur Demonstration der im Beweis (Teil 2) beschriebenen DEA-Transformation verwenden wir einen DEA M = ({q1 , q2 }, { , }, δ , q1 , {q2 }) mit nur zwei5 Zuständen und der folgenden Überführungsfunktion. 1 0, 1 Start
q1
q2 0
Gesucht ist der Wert von R = R2 (1, 2). Wir imitieren eine rekursive Berechnung:
5 Wir
R2 (1, 2)
=
R1 (1, 2) ∪ R1 (1, 2) ◦ R1 (2, 2)∗ ◦ R1 (2, 2)
R1 (1, 2)
=
R0 (1, 2) ∪ R0 (1, 1) ◦ R0 (1, 1)∗ ◦ R0 (1, 2)
R0 (1, 2)
=
{0, 1}
R0 (1, 1)
=
{ε}
R1 (1, 2)
=
{0, 1} ∪ {ε} ◦ {ε}∗ ◦ {0, 1} = {0, 1}
R1 (2, 2)
=
R0 (2, 2) ∪ R0 (2, 1) ◦ R0 (1, 1)∗ ◦ R0 (1, 2)
R0 (2, 2)
=
{ε, 1}
R0 (2, 1)
=
{0}
R1 (2, 2)
=
{ε, 1} ∪ {0} ◦ {ε}∗ ◦ {0, 1} = {ε, 1, 00, 01}
R2 (1, 2)
=
{0, 1} ∪ {0, 1} ◦ {ε, 1, 00, 01}∗ ◦ {ε, 1, 00, 01}
=
0 + 1 + (0 + 1) · (ε + 1 + 00 + 01)∗ · (ε + 1 + 00 + 01)
räumen ein, dass eine solche Handrechnung für größere n recht aufwendig sein kann.
114
7 Reguläre Ausdrücke
Computerübung 7.3 Vergleichen Sie das Ergebnis aus Beispiel 7.6 mit dem zugehörigen AutoEdit-RegExpExport. Versuchen Sie den Ausdruck zu vereinfachen. Computerübung 7.4 Gegeben sei die Sprache L = {w | w = aw mit w ∈ {a, b}∗ }. Entwerfen Sie einen DEA M für L und entwickeln Sie daraus einen äquivalenten regulären Ausdruck. Wenden Sie dabei das in obigem Beweis (Teil 2) vorgestellte Verfahren6 an. Setzen Sie AtoCC geeignet ein. Computerübung 7.5 Prüfen Sie durch Umformung, ob die folgenden regulären Ausdrücke äquivalent sind: (a(b+(ab+ac))∗ )∗ a und a((b∗ +(a(b+c))∗ )∗ a)∗ . Verwenden Sie RegExp Edit, um Ihr Ergebnis zu verifizieren. Übung 7.6 Geben Sie einen NEA (mit möglichst wenigen Zuständen) für folgende Sprache an: (0 + 1)∗ (000 + 111)(0 + 1)∗ . Übung 7.7 Konstruieren Sie einen endlichen Automaten für 10 + (0 + 11)0∗ 1. Übung 7.8 Gegeben sei die Sprache L mit L = {w | w ∈ { , } und w endet auf
und w enthält nicht
}.
(a) Geben Sie einen regulären Ausdruck an, der L definiert. Nennen Sie mindestens drei Wörter aus L. (b) Konstruieren Sie einen DEA für L. (c) Geben Sie für L eine Typ-3-Grammatik an.
7.5 Reguläre Ausdrücke in der Praxis Texteditoren
Regulärer Ausdrücke werden in der Praxis sehr gern angewandt, wenn Texte, also Zeichenketten, im Spiel sind. Dann gibt es mindestens zwei Anwendungsbereiche: Suchen (und Ersetzen) oder Ausfiltern von Teilzeichenketten, die einem bestimmten Muster gehorchen, s. Abbildung 4.2 (S. 28), und Verifikation kompletter Zeichenketten, wie sie bei Nutzereingaben in Formulare gemeinhin vorkommen. Bei Kommandozeileninterpreter diverser Betriebssysteme sind es Eingaben, die Joker oder wildcards für verschieden lange Teilwörter enthalten. Dies wird beispielsweise eingesetzt, wenn Dateien mit einer bestimmten Namenserweiterung (nach dem Punkt) ausgefiltert und auflistet werden sollen. Die Windows PowerShell ist moderner Ausdruck dieses Konzepts. 6 Der
Rechenaufwand ist durchaus verkraftbar. Die Autoren haben es auch selbst auf sich genommen.
7.5 Reguläre Ausdrücke in der Praxis
115
Übung 7.9 Wiederholen Sie einige reguläre Muster, die in Verbindung mit dem UNIX-Systemsehr gerne verwendet werden. kommando
Auch im Simulationsfenster von RegExp Edit können reguläre Ausdrucke für Such- und Ersetzungsprozesse eingesetzt werden. Man gibt einen beliebigen Text (Zeichenkette) ein und einen Ausdruck vor. Durch farbige Hervorhebungen werden die Stellen im Text markiert, auf die das Muster passt. Bei sich „überlappenden Mustern“, wie etwa ab und (ab)∗ , wird die jeweils längste passende Zeichenkette ausgewählt. Obwohl dies optisch nicht erkennbar ist, denn Farbkennzeichnung ist in keiner Weise strukturiert, hat dies Auswirkungen bei der Ersetzung.
längste passende Zeichenkette
Computerübung 7.6 Entwerfen Sie einen (sehr einfachen) regulären Ausdruck, der die Sprache der Vokale in der deutschen Sprache beschreibt. Diese Sprache ist endlich und umfasst nur fünf Zeichen. Geben Sie diesen Ausdruck ein und schreiben Sie in das Simulationsfenster von RegExp Edit . Beobachten Sie die farbig gedie Zeichenfolge: kennzeichneten Fundstellen von Wörtern der Vokal-Sprache. Ersetzen Sie anschließend die gefundenen Vokale jeweils mit und lesen Sie das entstandene Wort ihrem Nachbarn vor. Gegen Sie schließlich zu komplexeren Übungsausdrücken über. Computerübung 7.7 Geben Sie sich im Simulationsfenster einen Text vor, der diverse Termine, wie , , usw., enthält. Das jeweilige Datum soll zwischen 2000 und 2008 liegen, wobei vereinfachend ein Monat generell mit 31 Tagen angenommen wird. Entwerfen Sie einen regulären Ausdruck, der diese Datumsangaben erkennt und ersetzen Sie anschließend sämtliche Fundstellen mit 24.12.2008. Hinweis: Zur Anzeige des zum regulären Ausdruck gehörenden NEA müssen Sie etwas Geduld aufbringen. Es entsteht ein recht komplexer NEA mit über 230 Zuständen. Für die Lösung der eigentlichen Aufgabe ist die Darstellung des Automaten jedoch nicht notwendig. Exportieren Sie den NEA in AutoEdit und führen Sie entsprechende Simulationen durch.
Zu den klassischen Anwendungen in Editoren, wie vi und emacs, sind modernere Einsatzfelder, wie etwa cgi-Technik in der Web-Programmierung, hinzugekommen. In Skriptsprachen, wie JavaScript, Perl, Tcl, awk oder Python und Groovy, sind reguläre Ausdrücke ebenso integriert, wie in höheren Programmiersprachen, wie z.B. Delphi, Java oder Visual C++. In allen Anwendungsfeldern werden reguläre Ausdrücke zur Definition von Mustern benutzt, nach denen ein bestimmter Text durchsucht wird. Man kann beschreiben, was geschehen soll, wenn ein Teil einer Zeichenkette oder ein Textstück auf ein solches Muster passt. Man nennt dies pattern matching. Neben deterministischen Pattern-Matching-Verfahren finden auch nichtdeterministische Verwendung. Aus der Sicht der theoretischen Informatik ist dies keineswegs verwunderlich, denn sie bieten prinzipiell die gleiche Leistungsfähigkeit und können im Einzelfall effizienter arbeiten.
pattern matching
116
Rückwärtsreferenz
7 Reguläre Ausdrücke
Moderne Entwicklungen haben zu einer – aus praktischer Sicht naheliegenden – Erweiterung derartiger matcher geführt, die man unter dem Stichwort Rückwärtsreferenz findet. Dabei werden die in einem Teilausdruck enthaltenen Variablen beim Abgleich an das passenden Teilwort gebunden. Wenn dieser Prozess von links nach rechts abläuft, müssen dann alle weiter rechts stehenden Vorkommen dieser Variablen mit dem an diese Variable gebundenen Teilwort matchen. Auf diese Weise können syntaktisch komplexer strukturierte Wörter verarbeitet werden. Zu beachten ist, dass der Bereich regulärer Mengen dadurch im Allgemeinen überschritten wird. Übung 7.10 Machen Sie sich unter Verwendung entsprechender Informationsangebote (man, -?) mit der Rückwärtsreferenz-Technik in Perl und egrep (Weiterentwicklung von grep – global regular expression print) vertraut.
Perl, egrep
Praxis-relevante Syntax regulärer Ausdrücke POSIX VCC
statt +
Wie Tabelle 7.1 auf Seite 117 zeigt, gibt es eine ganze Reihe zusätzlicher Metazeichen, wie , , , , , , , , , , , , \ und , die in einer solchen Praxisrelevanten Syntax regulärer Ausdrücke verwendet werden können. Leider unterliegt diese Syntaxdefinition keiner Standardisierung, sodass die einzelnen Implementierungen (in verschiedenen Programmiersprachen) teilweise stark differieren. Indem wir uns am POSIX7 -Standard orientieren, beschränken wir uns daher auf einen typischen Kern, s. Tabelle 7.1, der auch für VCC (Visual Compiler Construction) – das Werkzeug zur Compilerentwicklung aus AtoCC – gilt. Auf Rückwärtsreferenzen werden wir dabei gänzlich verzichten. Ein markanter Unterschied zwischen der wissenschaftlichen Notation regulärer Ausdrücke und der in der Praxis üblichen ist die Verwendung des Metazeichens , wie in für . Da die Eingabe + in Programmtexten so nicht möglich ist, schriebt man . Dies ist zwar vernünftig, hat aber zur Folge, dass die ursprüngliche Bedeutung des Zeichens + als „oder“, wie in Definition 7.1 auf Seite 106 festgelegt, nun durch ein anderes Symbol getragen werden muss. Hierfür hat man mit | ein Symbol gewählt, welches wir auch in der BNF zur Grammatiknotation für alternative Regeln verwenden. Anstatt α β müssen wir also α β schreiben8 . Zusätzlich wurden eine Reihe von Makrozeichen-Sequenzen (bestehend aus zwei Metazeichen) vereinbart, um problematische ASCII-Zeichen, wie Zeilenumbrüche, in einen regulären Ausdruck aufnehmen zu können. Tabelle 7.2 zeigt die Details.
7 POSIX
steht für „Portable Operating System Interface“ und ist eine Spezifikation der Benutzer- und Software-Schnittstelle des Betriebssystems. Die aktuelle Version stammt aus dem Jahr 2004. Auch Kommandozeileninterpreter sind darin erfasst. Die Standard-POSIX-Shell ist die aus UNIX bekannte Korn-Shell. Hilfsprogramme, wie awk, vi oder echo, sind ebenfalls Teil des POSIX-Standards. 8 α und β stehen hier für beliebige reguläre Ausdrücke.
7.5 Reguläre Ausdrücke in der Praxis
Metazeichen . a* a+ a? {m,n} {n} (ab)
A|B
[ABC]
[a-zA-Z]
[^A]
Bedeutung . . . steht für ein beliebiges Zeichen (außer \n und \r - siehe weiter unten). . . . steht für 0 bis beliebig viele Vorkommen von a. . . . steht für 1 bis beliebig viele Vorkommen von a. . . . steht für ein optionales Zeichen. Im Beispiel darf a auftreten oder ab auch nicht. (gleichbedeutend mit a{0,1}) . . . steht für m bis n Vorkommen des Zeichens oder Metazeichens. . . . steht für n Vorkommen des Zeichens oder Metazeichens. . . . Klammern erlauben Teilausdrücke zu einer Einheit zusammenzufassen um anschließend Operationen wie +, * oder | für den gesamten Teilausdruck anzuwenden. . . . steht für ein Zeichen A ODER B (kann auch auf komplexe geklammerte Teilausdrücke angewendet werden z.B.: (a|b)|[0-9]+ ). Eckige Klammern beschreiben eine Auswahl (Zeichenklasse). Dieses Beispiel steht also für genau ein Zeichen A,B oder C. (die Reihenfolge in einer Zeichenklasse spielt dabei keine Rolle) Der Bindestrich innerhalb eckiger Klammern bestimmt einen Bereich. Daher steht dieses Beispiel für genau ein Zeichen von a bis z oder A bis Z. Das Dach (^) negiert die Auswahl. Daher wird hier ein beliebiges Zeichen außer A beschrieben. Wird gern verwendet um verbotene Zeichen in einem Ausdruck zu beschreiben.
Tabelle 7.1: Praxis-relevante Syntax regulärer Ausdrücke: Metazeichen
Metazeichen \n \r \t \s \d \w
Bedeutung ein Zeilenumbruch ein Wagenrücklauf (unter Windows steht \r\n für einen Zeilenumbruch) ein Tabulatorzeichen ein einzelnes Leerzeichen der reguläre Teilausdruck [0-9] ein Buchstabe, eine Ziffer oder der Unterstrich [a-zA-Z_0-9]
Tabelle 7.2: Praxis-relevante Syntax regulärer Ausdrücke: Metazeichen-Sequenzen
117
118
7 Reguläre Ausdrücke
Beispiel 7.7 Im Folgenden geben wir einige Beispiele für syntaktisch korrekte reguläre Ausdrücke und die damit jeweils beschriebene Sprache an. ... trifft 3 beliebige Zeichen (außer \r und \n) .{1,5} trifft 1,2,3,4 oder 5 beliebige Zeichen (außer \r und \n) [0-9]+ trifft eine beliebig(mindestens ein Zeichen) lange Ziffernfolge wie etwa: 058237435 oder aber auch 0 a*b trifft beliebig viele a (oder keins) gefolgt von genau einem b (a*)|(b*) trifft beliebig viele a ODER beliebig viele b oder auch das leere Wort! \d+,\d\d trifft eine Fließkommazahl der Form: 234,32 oder 0,98 identisch mit [0-9]+,[0-9][0-9] [1-9][0-9]* trifft eine beliebig lange Ziffernfolge aber ohne vorangestellte Nullen. [\r\t\n\s] trifft ein typisches Whitespace [1-5]0 trifft genau die Ziffernfolgen 10, 20, 30, 40 oder 50.
Möchte man das Tastaturzeichen mit einem regulären Ausdruck beschreiben, muss das Symbol \ (Backslash) vor das Metazeichen gesetzt werden, um dessen Bedeutung (Option) aufzuheben: also \ . Wollen wir beispielsweise den arithmetischen Ausdruck (234 + 23)∗ 4 mit einem regulären Ausdruck erkennen, so eignet sich der folgende dafür: \ \ \ \ Didaktischer Hinweis 7.3 zu formulieren, was jedoch Man könnte auf die Idee kommen, einen Ausdruck wie muss ausschließlich aus Zeichen bestehen. Ein nicht erlaubt ist. Eine Zeichenklasse . gültiger Ausdruck wäre hingegen
7.6 Anwendungsgebiete für reguläre Ausdrücke Eingabevalidierung
In der Praxis finden sich viele Anwendungsgebiete für reguläre Ausdrücke. Ein typisches Beispiel ist die Eingabevalidierung bei graphischen Bedienoberflächen sowohl in Webanwendungen als auch in Desktopanwendungen. Meldet man sich etwa bei einem Internet-Portal an, wird meist eine gültige Emailadresse gefordert. Macht man hier falsche Eingaben liefert die Webanwendung einen Hinweis wie in Abbildung 7.4 auf Seite 119. Die Webanwendung muss also überprüfen, ob eine gültige Emailadresse eingegeben wurde oder nicht. Für diese Überprüfung können wir einen regulären Ausdruck verwenden, was uns viele Zeilen Programmcode erspart. Dieser ist zwar komplex und für das ungeübte Auge schwierig leserbar jedoch sehr kompakt und leistungsfähig: \ \ \
\ \
\
7.6 Anwendungsgebiete für reguläre Ausdrücke
119
Abbildung 7.4: Anmeldung eines neuen Benutzers bei Wikipedia.de
Dabei steht der erste Teil bis zum Zeichen für eine Folge von Zeichen, die in URL’s im Allgemeinen erlaubt sind. Nach dem folgen mögliche Subdomains, der Domainname und eine zwei- bis vierstellige Landeskennung, wie . Übung 7.11 Entwickeln Sie eine Grammatik für die Sprache der Emailadressen.
Im Kapitel 5 haben wir XML und die Verwendung von XML Schemas angesprochen. Auch hier steht die Validierung von Daten im Mittelpunkt, nämlich von XML-Daten. Mit Hilfe von regulären Ausdrücken können ganz bestimmte Einschränkungen für Attribute definiert werden. Beispiel 7.8 Eine Postleitzahl beginnt mit einem Länderzeichen gefolgt von einem Minus und fünf Zif. Abbildung 7.5 zeigt ein entsprechendes XML-Schema, das diese Sprache fern: definiert. In Abbildung 7.6 wird ein XML-Schema angegeben, das Passwort als Typ beschreibt. Der abgeleitet und zusätzlich durch einen (oder neue Datentyp ist vom Basistyp sogar mehrere) reguläre Ausdrücke eingeschränkt: Ein Passwort ist demzufolge eine mindestens fünf Zeichen lange Zeichenkette, die wenigstens eine Ziffer und einen Buchstaben enthält. Nur wenn alle xsd:pattern das Eingabewort matchen ist die Eingabe zulässig.
XML
120
7 Reguläre Ausdrücke
Abbildung 7.5: Schema für Postleitzahl
Abbildung 7.6: Schema für Passwort
7.7 Reguläre Ausdrücke in Scannergeneratoren Zeichenroboter
Wir kehren nun zu unserer Zeichenroboter-Beispielsprache ZR aus Kapitel 5 zurück. Didaktischer Hinweis 7.4 Wir nehmen hier sehr starken Bezug auf die Inhalte aus Abschnitt 5.4. Insofern ist es ratsam, sich den Text noch einmal genau anzusehen.
reduzierte Grammatik für ZR
Kellerautomat
Scannergenerator Parsergenerator
In Abbildung 5.12 auf Seite 56 findet man eine formale Grammatik für ZR. Mit Bezug auf die Aufgabenteilung von Scanner und Parser wurde dort bereits festgestellt, dass die regulären Teilgrammatiken einer Sprache im Scanner zusammengefasst werden. Dann werden Nichtterminale der Ausgangsgrammatik zu Terminalen der reduzierten Grammatik G für ZR, s. Abbildung 5.15 (S. 59), die wir hier noch einmal zitieren. Nachdem wir uns mit endlichen Automaten beschäftigt haben, ist auch klar, dass ein Scanner eine Komposition diverser DEAs ist, während ein Parser (für die reduzierte Grammatik) durch sog. Kellerautomaten repräsentiert wird. Kellerautomaten beschreiben kfS. Wir werden sie im nächsten Kapitel ausführlich behandeln. Kellerautomaten können schon für kleine Grammatikausschnitte recht komplex sein. Einen Parser (mit Scanner) zu schreiben, erfordert also den Einsatz von Werkzeugen, die das sehr gut können. Man nennt sie Scanner- bzw. Parsergeneratoren.
7.7 Reguläre Ausdrücke in Scannergeneratoren
G N T P
= = = =
s
=
121
(N , T , P , s ) {Programm, Anweisungen, Anweisung} { , , , , , , , , {Programm → Anweisungen Anweisungen → Anweisung Anweisungen | ε Anweisung → Anweisungen | | | | } Programm
}
Abbildung 7.7: Reduzierte kfG für ZR (identisch mit Abbildung 5.15)
AtoCC stellt mit der Komponente VCC – für Visual Compiler Construction – einen Scanner-, Parser- und Compiler-Generator zur Verfügung, der Sprachbeschreibungen mittels Grammatiken in visuell editierbarer Syntaxdiagrammform verarbeitet und im Erfolgsfall zugehörigen Zielcode generiert – ein Generator für einen kompletten Compiler eben. An dieser Stelle interessieren wir uns allerdings nur für die Scanner-Herstellung, s. Abbildung 7.8. Generatoren dieser Art nehmen die Beschreibung der Quellsprache auf und erzeugen ein zugehöriges Analyseprogramm für diese Sprache. Bei erfolgreicher Analyse hinterlässt ein Scanner eine Tokenliste und ein Parser einen abstrakten Syntaxbaum (AST). Wir beginnen mit der Scanner-Beschreibung für die regulären Sub-Sprachen, indem wir für jedes Terminal der reduzierten Grammatik G aus Abbildung 7.7 eine Tokenklasse angeben. In G sind Farbwert und Zahl Terminale; Ziffern, Ziffer und ErsteZiffer, wie in den folgende Produktionen, auf die wir uns stützen, entfallen. Farbwert Zahl Ziffern Ziffer ErsteZiffer
→ → → → →
| | ErsteZiffer Ziffern Ziffer Ziffern | ε | | | | | | | | | | | |
|
| |
|
| |
|
Zur Definition der Token (Terminale der „Parser-Grammatik“) Farbwert und Zahl werden wir reguläre Ausdrücke (in Praxisnotation) verwenden. Für Farbwert ist das trivial: , wobei zu beachten ist, dass die Zeichen | von keinem Leerzeichen umgeben sind. Der zur Beschreibung von Zahl gewählte reguläre Ausdruck sorgt für die Unterdrückung von Vornullen.
VCC
122
7 Reguläre Ausdrücke
Abbildung 7.8: Scanner-Beschreibung für ZR
ScannerBeschreibung Tokenklasse
In der Scanner-Beschreibung muss jedes Eingabezeichen einer Tokenklasse zugeordnet werden, sonst wird der daraus erzeugte Scanner die Eingabe nicht akzeptieren. Leerzeichen, Zeilenumbrüche, Tabs oder ähnliche Zeichen spielen in vielen Eingabetexten aber nur eine kosmetische Rolle. Für die spätere Verarbeitung können wir diese ignorieren. Zu ignorierende Zeichen werden in VCC mit dem speziellen Token definiert. Alle Zeichen, die dieser Tokenklasse angehören, werden nicht in die Tokenliste aufgenommen. Computerübung 7.8 Nehmen Sie die Scanner-Definition gemäß Abbildung 7.9 in VCC vor. Testen Sie diverse Zahlwörter in der Scanner-Simulation (Scanner ausführen), sodass Sie eine Ausgabe wie in Abbildung 7.10 erhalten. Unterscheiden Sie gedanklich klar zwischen ScannerBeschreibung/Definition, Scanner-Generator und Scanner.
ScannerDefinition
Wie Abbildung 7.9 zeigt, verwenden wir bei der Scanner-Definition in VCC eine Praxis-orientierte Notation für reguläre Ausdrücke. Scanner sind verknüpfte DEAs. Zum „Einstieg“ in den jeweiligen „DEA-Strang“ ist es notwendig, dass sich die Initialzeichen unterscheiden. Dies tritt häufig in Konflikt mit den praktischen Anforderungen: Man denke beispielsweise an als Schlüsselwort und oder als in etablierten Programmiersprachen durchaus zulässige Variablennamen. Dies führt zu Überlappungen.
Token-Präfix
Gemeinsame Token-Präfixe, d.h. auf mehrere Tokendefinitionen passende LexemAnfänge, werden nach folgenden Regeln verarbeitet: 1. Das längste passende Anfangsteilwort bestimmt die Tokenklasse. Dabei spielt es keine Rolle, an welcher Stelle die betrachtete Tokenklasse definiert wurde.
7.7 Reguläre Ausdrücke in Scannergeneratoren
123
Abbildung 7.10: Scanner-Simulation für ein Zahlwort
Abbildung 7.9: Scanner-Beschreibung für ZR
2. Passt ein und dasselbe Lexem zu mehreren Tokenklassen, so wird die (von oben nach unten) zuerst definierte genommen. Abbildung 7.11 illustriert die Arbeit mit sich überlappenden Tokendefinitionen für und . Die Scanner-Animation verdeutlicht, dass das längste Anfangsstück der Zeichenkette ist, das auf ein „Token-Muster“ passt. Es wird als Bezeichner ( ) erkannt und es gibt auch nur diese Möglichkeit. Anders ist das bei . Hier gibt es zwei Möglichkeiten, nämlich und . Letzteres wird genommen, da die betreffende Tokendefinition vor der von steht, wie man der in Abbildung 7.11 rechts oben hineinkopierten Tokendefinitionsliste entnimmt. Es wird also Regel 2 angewandt. Für das Restwort ergeben sich wiederum zwei passende Token, nämlich für und dem zur Analyse verbleibenden Restwort oder als . Hier wird der Bezeichner ( ) erkannt, da das gesamte Restwort auf die Tokendefinition von passt. Es greift Regel 1.
124
7 Reguläre Ausdrücke
Abbildung 7.11: Gemeinsame Token-Präfixe
Computerübung 7.9 an erster Stelle in der Definitionsliste Ergänzen Sie die Tokendefinition für Name . Verschieben Sie diese Definition ganz nach und analysieren Sie das Wort unten und analysieren Sie das gleiche Wort erneut. Computerübung 7.10 mit Führen Sie einen Scanner-Simulation für Wörter wie VCC durch. Wenn Sie alle genannten Wörter in das Eingabefenster schreiben (mit/ohne Zeilenumbruch) wird die zugehörige Tokenliste ausgegeben. Verwenden Sie auch die Scannerund . Animation. Studieren Sie syntaktische Fehler, wie bei Computerübung 7.11 zur Beschreibung unserer BluUm Wörter, wie me in eine Tokenfolge zu überführen, sind weitere Tokendefinitionen erforderlich. Hinweis: Die eckigen Klammern werden mit einem vorangestellten (Backslash) markiert, also für die öffnende Klammer. Computerübung 7.12 Geben Sie einen (recht komplexen) regulären Ausdruck für die Sprache der E-MailAdressen in VCC ein und analysieren Sie einige Beispielwörter. Computerübung 7.13 Eine Textdatei soll Messwerte einer Anlage enthalten. Diese habe etwa folgende Form:
7.7 Reguläre Ausdrücke in Scannergeneratoren
125
... Ein Eintrag beginnt stets mit einer Zeilennummer gefolgt von einem Doppelpunkt. Es folgt ein Komponentenname der mindestens mit einem Buchstaben beginnt. Anschließend folgen beliebig viele Messwerte in der angegebenen Form. Entwickeln Sie für dieses Dateiformat einen Scanner mit VCC. Finden Sie geeignete Tokenklassen und testen Sie Ihren Scanner anhand von Eingabebeispielen. Computerübung 7.14 Ein Mitarbeiter möchte wissen, welche Bauteile Messwerte geliefert haben. Dazu soll die Textdatei (aus Aufgabe 7.13) nach Komponentennamen durchsucht werden. Diese sollen nacheinander auf der Konsole ausgegeben werden. Entwickeln Sie ein Javaprogramm, welches dies mit Hilfe von regulären Ausdrücken leistet (Java bietet hier entsprechende Klassen für RegExp an - suchen Sie diese heraus und machen Sie sich mit ihnen vertraut). Computerübung 7.15 Ein Buchhaltungssytem soll Daten aus einer externen Datei einlesen. Es handelt sich um eine einfache Textdatei, die Preisangaben enthält. Beispiel:
... Entwickeln Sie eine reguläre Grammatik für einen korrekten Eingabewert (eine Preisangabe) aus dieser Datei. Erstellen Sie aus dieser reguläre Grammatik einen NEA nach dem bekannten Verfahren. Diese Datei wurde von einer Sekretärin per Hand verfasst und kann Schreibfehler enthalten. Folgende Eingaben sind fehlerhaft:
... Natürlich ist es unmöglich solche Tippfehler automatisch zu korrigieren. Jedoch könnte ein Programm diese potenziellen Fehler anzeigen, damit sie die Sekretärin bereinigen kann. Entwickeln Sie mit VCC einen Scanner und definieren Sie Token für korrekte und inkorrekte Eingabewerte. Testen Sie den Scanner mit verschiedenen Eingabetexten. Schreiben Sie ein Java-Programm welches die fehlerhaften Werte auf der Konsole ausgibt. für reguläre Ausdrücke. Verwenden Sie dabei die Bibliothek
8 Kellerautomaten und kontextfreie Sprachen 8.1 Grenzen endlicher Automaten Endliche Automaten reichen zur Beschreibung beliebiger kontextfreier Sprachen nicht aus. Dies machen folgende Beispiele klar. Beispiel 8.1 L1 = {w | w ∈ { , }∗ und w enthält ebenso viele Zeichen Beispiel 8.2 Sprache der Palindrome über { , }, d.h. L2 = {ε, , ,
,
wie } ,
,
,
,
, . . .}
Beispiel 8.3 L3 : Sprache der geklammerten arithmetischen Ausdrücke
Woran liegt es, dass endliche Automaten nicht in der Lage sind, die Sprache L1 in Beispiel 8.1 zu akzeptieren? Offensichtlich liegt es daran, dass die Anzahl n der ’s bzw. ’s nicht vorgegeben ist. n wird erst durch das konkrete Eingabewort, wie etwa - hier gilt n = 6, bestimmt. Eine naheliegende Idee zur Erweiterung endlicher Automaten mit Ziel, deren diesbezügliche „Defizite auszubügeln“, ist so eine Art „Merkzettel“, auf dem wir jedes gelesene Zeichen durch einen Merker, z. B. |, notieren. Jedes gelesene führt dann zur Streichung eines Merkstriches. Das Eingabewort gehört zu L, wenn der Merkzettel am Ende leer ist. Leider hat die Sache einen Haken: Was geschieht, wenn das Eingabewort im Anfangsstück mehr ’s als ’s enthält? Dann haben wir noch gar keinen (nun zu streichenden) Merker gesetzt. Anstelle eines neutralen Merkstriches ist es offenbar notwendig, Eingabezeichen-spezifische Merker, z.B. I für und I für , zu verwenden. Wird ein Zeichen gelesen, das sich vom jeweils vorhergehenden Zeichen unterscheidet, wird der zugehörige Merker entfernt. Ansonstens wird der Eingabezeichen-Merker auf einen als Stapel organisierten Merkzettel gelegt. Abbildung 8.1 unterstreicht die anschauliche Vorstellung von einem Stapel (auch Keller genannt) und illustriert die Wirkung der Operationen push und pop. Die beiden Operationen push und pop beziehen sich stets auf das oberste Kellerzeichen, das top of stack (tos). Die Ausführung von push( , ) legt das Zeichen auf den Stapel, sodass fortan das neue tos ist. Durch pop( ) wird das zum
Merkzettel
Stapel Keller push pop top of stack
128
8 Kellerautomaten und kontextfreie Sprachen
Abbildung 8.1: Stapel (stack) und Operationen push und pop
Zeitpunkt der Ausführung der Operation gültige tos vom Stapel entfernt und kann für eine bestimmte Weiterverarbeitung herangezogen werden. Der Einsatz eines Stapels ist auch zur Beschreibung der Sprache L2 aus Beispiel 8.2 unumgänglich. Bei L2 ist nicht nur die Anzahl der Zeichen und von Bedeutung, sondern zusätzlich deren Reihenfolge. Offensichtlich muss der gelesene Teil v eines Eingabewortes w = vv zeichenweise auf den Stapel gelegt - man sagt: gekellert - werden, bis die Wortmitte erreicht ist. Ist das letzte Zeichen von v das oberste Kellerzeichen (top of stack), so wird geprüft, ob das folgende Zeichen, also das erste Zeichen von v , mit dem aktuellen top of stack übereinstimmt. Wenn das der Fall ist, wird der Kellerinhalt dabei verbrauchend gelesen (pop) usw. Falls v das Umkehrwort von v ist, so gehört w zur betrachteten Sprache. Dies gilt analog auch für Palindrome, deren Symmetrieachsen durch ein Zeichen (statt zwischen zwei aufeinanderfolgenden Zeichen) verlaufen. Dann gelten w = vxv , wobei v, v ∈ {a, b}∗ und x ∈ {a, b}. Allerdings bleibt noch die Frage, wie die Wortmitte erkannt wird.
8.2 Nichtdeterministischer Kellerautomat (NKA) Aus dieser anschaulichen Betrachtung der Erweiterung endlicher Automaten durch ein „Gedächtnis“, das im Stapelbetrieb beschrieben und gelöscht wird, kann eine Definition des neuen Automatentyps abgeleitet werden. Definition 8.1 Ein nichtdeterministischer Kellerautomat, kurz: NKA oder KA, (engl.: NPDA = nondeterministic pushdowna automaton) ist durch ein 7-Tupel M = (Q, Σ, Γ, δ , q0 , k0 , E) definiert. Die verwendeten Symbole haben folgende Bedeutungen: Q ... endliche Menge der Zustände Σ ... Eingabealphabet Γ ... Kelleralphabet δ ... partielle Überführungsfunktion q0 ... Anfangszustand, q0 ∈ Q k0 ... Kellervorbelegungszeichen, k0 ∈ Γ E ... Menge von Endzuständen, E ⊆ Q a ‚Pushdown‘
bedeutet so viel wie ‚nach unten drücken (winden)‘, nicht etwa ‚umschubsen‘.
8.2 Nichtdeterministischer Kellerautomat (NKA)
129
Die Definition bedarf noch einiger Erläuterung und einer Ergänzung. Die Überführungsfunktion δ bestimmt die Arbeitsweise eines NKA-Modells, dessen Aufbau in Abbildung 8.2 dargestellt ist.
a
a
b
a
read move
δ
pop y multiple push
control
x q
i
$
Abbildung 8.2: Aufbau eines Kellerautomaten-Modells
Die Arbeitsweise des Modells kann man folgendermaßen beschreiben: Ein NKA wechselt von einem Zustand qi in einen Zustand q j , wenn k das oberste Kellerzeichen (top of stack) und das aktuelle Eingabezeichen sind. Achtung: Das oberste Kellerzeichen wird stets verbrauchend gelesen, was der Operation pop für Kellerspeicher entspricht. Anschließend wird ein Kellerwort k1 k2 . . . kr auf den Stapel geschrieben. Hier findet die Operation push r-mal Anwendung. In Abbildung 8.2 wurde dies als multiple push bezeichnet. Wichtig ist die Reihenfolge. Wie in Abbildung 8.3 dargestellt, verabreden wir: Nach dem Schreiben von k1 k2 . . . kr auf den Stapel ist k1 (und nicht etwa kr ) das neue top of stack, darunter liegt k2 usw.
Abbildung 8.3: Operationen pop und multiple push angewandt auf eine Zeichenkette
pop push multiple push
130
8 Kellerautomaten und kontextfreie Sprachen
Das in einem Schritt zu kellernde Wort darf auch leer sein. In diesem Fall schrumpft der Kellerinhalt. Der beschriebene Vorgang wird in der Überführungsfunktion durch δ (qi , a, k) (q j , k1 k2 . . . kr ) ausgedrückt. spontane Übergänge
Außerdem sind spontane Übergänge erlaubt, z.B. δ (qi , ε, k) (q j , k1 k2 . . . kr ). Auch in diesem Fall wird das oberste Kellerzeichen verbrauchend gelesen. Die Überführungsfunktion von NKA hat damit folgende allgemeine Gestalt: δ : Q × (Σ ∪ {ε}) × Γ → ℘endlich (Q × Γ∗ ),
℘endlich
Crash
Akzeptanz
wobei ℘endlich (X) die Menge aller endlichen Teilmengen einer Menge X bezeichnet. Dies wird weiter unten – nach einem Beispiel – noch einmal aufgegriffen und begründet. Außerdem ist δ eine partielle Funktion. Gibt es für ein Tripel aus Q×(Σ∪{ε})×Γ keinen Funktionswert, so stoppt der Automat durch Crash. Ein bis zu einem Crash nicht vollständig eingelesenes Eingabewort, wird abgewiesen. Der Analyseprozess eines auf das Eingabeband geschriebenen Wortes beginnt mit dem Lesekopf auf dessen erstem Zeichen. Der Keller enthält nur das Kellervorbelegungszeichen als aktuelles top of stack. Der Automat stoppt, wenn das Wort vollständig gelesen wurde. Ggf. finden dann noch spontane Übergänge statt. Ein NKA akzeptiert das Eingabewort w genau dann, wenn 1. w vollständig abgetastet wurde und 2. der dann eingenommene Zustand ein Endzustand ist. Der Kellerinhalt spielt in diesem Fall keine Rolle.
Konfiguration Konfigurationenfolge
Die von einem NKA vorgenommene Wortanalyse wird durch die Folge der Konfigurationen beschrieben. Eine Konfiguration eines NKA ist ein Tripel aus Q × Σ∗ × Γ∗ . Eine entsprechende Liste von Konfigurationen, die im Allgemeinen zur Protokollierung eines Analyseprozesses verwendet wird, heißt Konfigurationenfolge. Didaktischer Hinweis 8.1 Eine tabellarische Darstellung der Konfigurationenfolge wird nachdrücklich angeraten.
Die von einen NKA definierte (oder akzeptierte oder erkannte) Sprache ist ∗
L(M) = {w ∈ Σ∗ | (q0 , w, k0 ) (qe , ε, K), mit qe ∈ E, K ∈ Γ∗ }.
8.2 Nichtdeterministischer Kellerautomat (NKA)
131
Beispiel 8.4 Der NKA M1 = ({q0 , q1 }, { , }, { , , }, δ , q0 , , {q1 }) mit folgendem δ δ (q0 , , )
(q0 , ε)
δ (q0 , , )
(q0 ,
)
δ (q0 , , )
(q0 ,
)
δ (q0 , , )
(q0 , ε)
δ (q0 , , )
(q0 ,
)
δ (q0 , , )
(q0 ,
)
δ (q0 , ε, )
(q1 , ε)
(b,a): ε (0,a):a0 (a,a):aa (a,b): ε (0,b):b0 (b,b):bb
Start
q0
(0, ε ): ε
q1
akzeptiert genau die in Beispiel 8.1 angegebene Sprache L1 . Die grundlegende Idee zur Konstruktion vom M1 wurden dort bereits vorgetragen. Im Gegensatz zu endlichen Automaten kann die dreistellige Überführungsfunktion eines NKA nicht als Tabelle aufgeschrieben werden. Ein Zustandsgraph des NKA, der eine grafische Darstellung für δ enthält, ist oben bereits angegeben.
Die Markierungen der Kanten des Zustandsgraphen eines NKAs, wie z.B. in Beispiel 8.4, haben folgenden Aufbau: . Damit soll die wichtige Rolle des jeweils obersten Kellerzeichens hervorgehoben werden. Selbst bei einem spontanen Übergang wird es verbrauchend gelesen. Didaktischer Hinweis 8.2 Die Erarbeitung der Zustandsübergänge eines NKA führt gedanklich zwingend zuerst zu dem jeweils beteiligten top of stack. Im Gegensatz zu dem aktuellen Eingabezeichen ist das top of stack zusammen mit dem Zustand, von dem der Übergang ausgeht, stets beteiligt. Es gilt der einfache Grundsatz: Das Ding, an das man zuerst denkt, muss man auch zuerst notieren können. Deshalb steht das top of stack in den Paaren, die wir links des Doppelpunktes an den Kanten des Graphen verwenden, an erster Stelle. Da es gegenüber der Funktionsschreibweise δ (Zustand, Eingabezeichen, oberstes Kellerzeichen) abweicht, ist dieser besondere Hinweis wichtig. Uns wäre auch dort eine veränderte Reihenfolge, nämlich δ (Zustand, oberstes Kellerzeichen, Eingabezeichen) lieber, was aber zu Abweichungen gegenüber etablierter Literatur führen und das Lernen somit erschweren würde. Computerübung 8.1 und protokollieren Sie Simulieren Sie mittels AutoEdit M1 angesetzt auf das Wort den Analyseprozess als Tabelle. Vergleichen Sie Ihre Lösung mit folgender Konfigurationenfolge.
Transitionen
132
8 Kellerautomaten und kontextfreie Sprachen
Zustand q0 q0 q0 q0 q0 q0 q0 q1
Eingabezeichen bbabaa babaa abaa baa aa a ε ε
Keller (links: top of stack) 0 b0 bb0 b0 bb0 b0 0 ε
Machen Sie sich die Wirkung des Nichtdeterminismus klar. Verwenden Sie auch Wörter, die nicht zu L1 gehören.
In der Tabelle aus Computerübung 8.1 ist ein wichtiger Unterschied zwischen endlichen Automaten und Kellerautomaten deutlich erkennbar: Die zu leistende Berechnung der jeweiligen Folgekonfiguration erfordert nämlich nicht nur das oberste Kellerzeichen, wie das die Überführungsfunktion suggeriert. Zusätzlich muss der gesamte Kellerinhalt mitgeführt werden.
Kellerautomat vs. endlicher Automat
Obwohl ein NKA nur endlich viele Zustände qi besitzt, gibt es abzählbar unendlich viele Paare (qi , y j ), denn es sind abzählbar unendlich viele Kellerwörter y j ∈ Γ∗ bildbar. Die Potenzmenge ℘(Q × Γ∗ ) ist sogar überabzählbar unendlich. Streicht man alle unendlichen Mengen in ℘(Q × Γ∗ ), so bleiben abzählbar unendlich viele Elemente übrig. Diese Menge ist gerade der Wertebereich von δ . Es ist also nicht die Endlichkeit der Zustandsmenge, die einen Automat als endlich charakterisiert, sondern die Endlichkeit des Wertebereiches von δ . NKA sind keine endlichen Automaten. (Die Überführungsfunktionen aller Automatenmodelle besitzen grundsätzlich endliche Argumentmengen.) Computerübung 8.2 Geben Sie einen NKA für die Sprache der Palindrome über { , }, s. L2 in Beispiel 8.2, an.
6-TupelNKA-Def.
Zur NKA-Definition 8.1 gibt es eine alternative, gleichwertige NKA-Definition als 6-Tupel – ohne Endzustandsmenge: M = (Q, Σ, Γ, δ , q0 , k0 ). Dieses Automatenmodell akzeptiert ein Eingabewort, wenn es vollständig gelesen wurde, der Automat also stoppt, und der Keller leer ist. Man sagt: Der Automat „akzeptiert per leerem Keller“. Die Tatsache, dass die beiden alternativen NKA-Definitionen äquivalent sind, werden wir in Satz 8.1 beweisen. AutoEdit ist auf die 7-Tupel-Definition beschränkt. Insofern ist die Angabe eines entsprechenden Transformationsverfahrens NKA6 NKA7 nicht nur von theoretischem, sondern auch von praktischem Interesse, nämlich um 6-Tupel-NKAs für die Arbeit mit AutoEdit zu erschließen. Der Beweisteil 2 von Satz 8.1 beschreibt eine solche allgemeingültige Transformation.
8.2 Nichtdeterministischer Kellerautomat (NKA)
133
Didaktischer Hinweis 8.3 Die Einführung von NKA in der 7-Tupel-Definition hat didaktische Vorteile, die sich aus der Nähe zu endlichen Automaten ergeben. Die Endzustandsmenge hat dort prinzipiell die gleiche Aufgabe wie bei 7-Tupel-Kellerautomaten. Andererseits wird die 6-Tupel-Definition in einigen Beweisen sehr vorteilhaft verwendet. Insofern ist es notwendig, dass wir uns mit beiden Definitionen befassen.
Satz 8.1 Zu jedem 7-Tupel-NKA M = (Q , Σ , Γ , δ , q0 , k0 , E ) gibt es einen äquivalenten 6-TupelNKA M = (Q, Σ, Γ, δ , q0 , k0 ) und umgekehrt. Die beiden Definitionen sind äquivalent. Beweis Teil 1: Zu jedem NKA M = (Q , Σ , Γ , δ , q0 , k0 , E ) gibt es einen äquivalenten NKA M = (Q, Σ, Γ, δ , q0 , k0 ) mit L = L(M ) = L(M).
NKA7 NKA6
Die Beweisaufgabe besteht in der Konstruktion von M aus M , so dass M die Abarbeitung von M für ein beliebiges Eingabewort simuliert. Ganz einfach die Endzustände zu eliminieren ist nicht die Lösung. Man muss nämlich beachten, dass M einen (beliebigen) Endzustand einnehmen kann, um entweder die Akzeptanz des Analysewortes anzuzeigen oder mit dessen Verarbeitung fortzufahren. Diese Überlegung führt unter Ausnutzung des Nichtdeterminismus dazu, dass wir bei der Konstruktion von M aus M für alle Endzustände qei ∈ E und sämtliche Kellerzeichen A ∈ Γ spontane Übergänge zu einem neuen Zustand qe ∈ Q hinzufügen, d.h. δ (qei , ε, A) (qe , ε). In qe wird der (ggf. nichtleere) Keller von M geleert, d.h. δ (qe , ε, A) (qe , ε) für alle A ∈ Γ hinzufügen. Wenn M nach vollständigem Lesen des Eingabewortes w seinen Keller leert ohne einen Endzustand einzunehmen, wird w bekanntlich nicht akzeptiert. Bei der Konstruktion von M muss darauf geachtet werden, dass dies nicht etwa zur Akzeptanz von w führt. Man erreicht dies durch ein neues Kellervorbelegungszeichen k0 ∈ Γ. Zu Beginn wird es unter das Vorbelegungszeichen k0 ∈ Γ „geschoben“: δ (q0 , ε, k0 ) = {(q0 , k0 k0 )}. Hierzu benötigt M einen neuen Anfangszustand q0 . Alle anderen Funktionswerte für δ werden von δ übernommen. So ergibt sich M = (Q ∪ {q0 , qe }, Σ , Γ ∪ {k0 }, δ , q0 , k0 ), mit q0 , qe ∈ Q und k0 ∈ Γ . Teil 2: Zu jedem NKA M = (Q, Σ, Γ, δ , q0 , k0 ) gibt es einen NKA M = (Q , Σ , Γ , δ , q0 , k0 , E ) mit L = L(M ) = L(M). Wir konstruieren also M aus M: Wähle Q = Q ∪ {q0 , qe }, mit q0 = qe und q0 , qe ∈ Q, Σ = Σ, Γ = Γ ∪ {k0 }, E = {qe }. M wird so konstruiert, dass er M simuliert: δ (qi , a, A) = δ (qi , a, A) für alle qi ∈ Q, a ∈ Σ, A ∈ Γ. Für alle Zustände qi ∈ Q ergänzen wir δ (qi , ε, k0 ) (qe , ε), um dafür zu sorgen, dass es genau dann einen spontanen Übergang zum Endzustand qe von M gibt, wenn M mit leerem Keller stoppt. Genau dann ist nämlich k0 das oberste Kellerzeichen.
NKA6 NKA7
134
8 Kellerautomaten und kontextfreie Sprachen
Schließlich müssen wir noch dafür sorgen, dass vor dem Start der Simulation von M dessen Kellervorbelegungszeichen k0 auf den nur mit k0 belegten Keller gelegt wird ohne k0 dabei zu entfernen: δ (q0 , ε, k0 ) = {(q0 , k0 k0 )}. Beispiel 8.5 Wir wenden die Transformation aus dem Beweisteil 1 auf M1 aus Beispiel 8.4 an. Die gesuchte 6-Tupeldefinition lautet: M1 = ({q0 , q1 , q2 , q3 }, { , }, { , , , }, δ , q2 , ) mit
Für das Wort
δ (q2 , ε, )
=
{(q0 ,
δ (q0 , , )
(q0 , ε)
δ (q0 , , )
(q0 ,
)
δ (q0 , , )
(q0 ,
)
δ (q0 , , )
(q0 , ε)
δ (q0 , , )
(q0 ,
)
δ (q0 , , )
(q0 ,
)
δ (q0 , ε, )
(q1 , ε)
δ (q1 , ε, )
(q3 , ε)
δ (q1 , ε, )
(q3 , ε)
δ (q1 , ε, )
(q3 , ε)
δ (q1 , ε, )
(q3 , ε)
δ (q3 , ε, )
(q3 , ε)
δ (q3 , ε, )
(q3 , ε)
δ (q3 , ε, )
(q3 , ε)
δ (q3 , ε, )
(q3 , ε)
)}
ergibt sich dann folgende Konfigurationenfolge: Zustand q2 q0 q0 q0 q0 q0 q0 q0 q1 q3
Eingabezeichen
Keller (links: top of stack)
ε ε ε
ε
Das Eingabewort wird also auch von M1 akzeptiert, denn der Keller ist am Ende des Analyseprozesses leer.
Die im Beweis beschriebene Transformation NKA6 NKA7 wird in Beispiel 8.6 und ganz am Ende von Beispiel 8.7 angewandt.
8.3 Äquivalenz von NKA und kontextfreier Grammatik
135
Übung 8.1 Zeigen Sie, dass man eine weitere gleichwertige Definition für NKA durch Aufnahme einer Menge von Startzuständen formulieren könnte.
8.3 Äquivalenz von NKA und kontextfreier Grammatik Bisher haben wir NKA als Beschreibungsmittel für kontextfreie Sprachen (kfS) kennengelernt und beispielbezogen angewandt. Wir wissen aber noch nicht, ob dies für jede kfS gelingt, oder ob es sogar kontextsensitive Sprache gibt, die mit NKA beschrieben werden können. Der folgende Satz beantwortet diese Fragen. Satz 8.2 Eine Sprache L ist kontextfrei genau dann, wenn L von einem NKA akzeptiert wird.
Damit steht fest, dass die Menge der durch NKA akzeptierten Sprachen und die Menge der kfS identisch sind: Lk f S = LNKA .
Lk f S = LNKA
Vor uns steht der Beweis dieses Satzes. Dieser ist konstruktiv und liefert als Nebenprodukte ein Verfahren zur Erzeugung eines äquivalenten NKA aus einer gegebenen kfG (Teil 1) und eines zur Gewinnung einer äquivalenten kfG aus einem gegebenen NKA (Teil 2). Wir verwenden die alternative NKA-Definition als 6Tupel ohne Endzustandsmenge, s. S. 132. Beweis Teil 1: Konstruiere einen äquivalenten NKA M = (Q, Σ, Γ, δ , q0 , k0 ) aus einer (beliebig) vorgegebenen kfG G = (N, T, P, s). Die Idee besteht darin, den Ableitungsprozess durch die Arbeitsweise des NKA zu simulieren. Hierfür braucht man nur die jeweils rechte Regelseite als Kellerwort auf den Stapel zu legen. Da vereinbarungsgemäß das erste Zeichen des jeweiligen Kellerwortes das neue top of stack ist, wird praktisch eine Linksableitung simuliert. Ist das top of stack ein Nichtterminal, wird eine der passenden Regeln angewandt, d.h. dieses Nichtterminal wird durch eine zugehörige rechte Regelseite (im Keller) ersetzt. Wenn ein Terminalzeichen ganz oben auf dem Stapel liegt, muss es mit dem aktuellen Zeichen das Eingabewortes übereinstimmen. Es wird dann einfach vom Keller entfernt. Anderenfalls wird das Wort abgewiesen. Die Simulation der Ableitung beginnt mit dem Spitzensymbol der Grammatik als Kellervorbelegungszeichen und besteht ausschließlich aus Stapelarbeit im Zustand q0 . Der so konstruierte NKA benötigt also nur einen einzigen Zustand. Die weiteren Bestandteile des NKA ergeben sich unmittelbar: M = ({q0 }, T, N ∪ T, δ , q0 , s). Für δ gilt: 1. Für jede Regel A → α ∈ P, mit α ∈ (N ∪ T )∗ und A ∈ N, setze δ (q0 , ε, A) (q0 , α). Mit anderen Worten: Die Substitution eines Nichtterminals erfolgt ohne Verbrauch des aktuellen Zeichens auf dem Eingabeband. Die rechte Regelseite wird gekellert.
kfG NKA
Linksableitung mit Keller simulieren
136
8 Kellerautomaten und kontextfreie Sprachen
2. Für alle Terminale a ∈ T setze δ (q0 , a, a) (q0 , ε). Das top of stack wird verbrauchend gelesen und muss mit dem aktuellen Zeichen auf dem Eingabeband übereinstimmen.
w ∈ L(G) ⇔ w ∈ L(M)
Für alle Wörter w ∈ T ∗ gilt w ∈ L(G) genau dann, wenn • es eine Ableitung der Form s ⇒ . . . ⇒ w in G gibt; ∗
• es eine Konfigurationenfolge der Form (q0 , w, k0 ) (q0 , ε, ε) gibt, d. h. wenn w ∈ L(M).
Ein formaler Beweis verwendet für diesen letzten Teil vollständige Induktion.
Beispiel 8.6 Gegeben sei G = ({S}, {a, b}, {S → ε | aSa | bSb}, S) für die Sprache der Palindrome über {a, b} mit geradzahliger Länge. Der zu G äquivalente NKA M = ({q0 , q1 , q2 }, { , }, { , , , }, δ , q0 , , {q2 }) hat folgende Überführungsfunktion. (S, ε ): ε (S, ε ):aSa (S, ε ):bSb (a,a): ε (b,b): ε
Start
q0
(k, ε ):Sk
q1
(k, ε ): ε
q2
Es ist zu beachten, dass zur Konstruktion dieses NKA nach der 7-Tupel-Definition 8.1 das Verfahren aus dem Beweis zu Satz 8.1 zusätzlich angewandt wurde. Die Konfigurationenfolge zur Akzeptanz des Wortes baab zeigt nur den erfolgreichen (sackgassenfreien) Weg. Er umfasst neun Schritte. Zustand q0 q1 q1 q1 q1 q1 q1 q1 q1 q2
Eingabe
Keller
ε ε
ε
Computerübung 8.3 Verwenden Sie AutoEdit, um Beispiel 8.6 zu bearbeiten.
8.3 Äquivalenz von NKA und kontextfreier Grammatik
137
Computerübung 8.4 Geben Sie für die Sprache LA der korrekt geklammerten arithmetischen Ausdrücke einen äquivalenten NKA an. LA sei durch folgende Grammatik G = ({E, T, F}, { , , , , }, P, E) definiert. Die Produktionen in P sind E
→
T | E T,
T
→
F | T F,
F
→
| E .
Beweis Teil 2: Konstruiere eine äquivalente kfG G = (N, T, P, s) aus einem gegebenen NKA M = (Q, Σ, Γ, δ , q0 , k0 ). Auch hier verwenden wir die 6-Tupel-Definition.
NKA kfG
Welcher Zusammenhang besteht zwischen den beiden verschiedenen „Welten“: Grammatik ∗ und Automat? Ein Wort v kann genau dann von X abgeleitet1 werden, d.h. X ⇒ v, wenn gilt ∗
(qi , v, k) (q j , ε, ε). Dabei spielt es keine Rolle, wie viele Schritte die zugehörige Konfigurationenfolge umfasst. Doch wo steckt das X? Nichtterminale (Grammatikwelt) müssen aus den Konfigurationsübergängen (Automatenwelt) gewonnen werden. Sie werden aus genau drei Informationen gespeist: • dem alten Zustand qi , • dem obersten Kellerzeichen (top of stack) k und • dem neuen Zustand q j . Von daher ist es sinnvoll, diese Bestandteile im Namen der zu bildenden Nichtterminale zu verankern. Wir schreiben die entsprechenden Tripel aus Q × Γ × Q in eckigen Klammern, um eben diese Nichtterminal-Namen2 von den Konfigurationen des Automaten zu unterscheiden.
Notation für Nichtterminale [qi , kr , q j ]
∗
Ein Wort w gehört zu L(M) genau dann, wenn (q0 , w, k0 ) (qi , ε, ε). In der Grammatik wird ∗ dies durch die Regel [q0 , k0 , qi ] ⇒ w ausgedrückt. Werfen wir nun einen Blick auf die Überführungsfunktion eines NKA. Die allgemeine Gestalt von δ ist δ (q, a, A)
(q , B1 . . . Bk ), mit k ≥ 0 und a ∈ Σ oder a = ε.
(8.1)
Damit wir uns nicht allen Fällen mit k ≥ 0 zuwenden müssen, nehmen wir eine Normierung vor, so dass k ≤ 2 gilt. Hierfür zeigen wir, dass jede Gestalt von δ in die zugehörige normierte Form transformiert werden kann. 1 Dabei
ist X nicht notwendigerweise das Spitzensymbol der Grammatik. wie [qi , kr , q j ], repräsentieren also nichts anderes als ein Nichtterminal. Mag sein, dass die Namensgebung etwas merkwürdig ausschaut. Auf keinen Fall darf man beim Gebrauch dieser Nichtterminale in der zu konstruierenden Grammatik die Namenselemente „auseinandernehmen“. Es handelt sich also um atomare Gebilde, wie A, B oder X.
2 Darstellungen,
Normierung: k≤2
138
8 Kellerautomaten und kontextfreie Sprachen
Zur Umformung werden weitere Zustände, q1 , q2 , . . . , qk−2 , eingeführt. Aus (8.1) wird eine Folge von „δ -Regeln“: δ (q, a, A)
(q1 , Bk−1 Bk )
δ (q1 , ε, Bk−1 )
(q2 , Bk−2 Bk−1 )
δ (q2 , ε, Bk−2 )
.. .
(q3 , Bk−3 Bk−2 )
δ (qk−2 , ε, B2 )
(q , B1 B2 )
Aufgrund der vorgenommenen Normierung für δ können bezüglich der Konfigurations∗
übergänge (q, aβ , Aγ) (q , β , γ), mit a ∈ Σ, β ∈ Σ∗ , A ∈ Γ und γ ∈ Γ∗ , nur die folgenden drei Fälle auftreten, nämlich für k = 0 (1. Fall), k = 1 (2. Fall) und k = 2 (3. Fall). 1. Fall: reines Entkellern bei δ (q, a, A) (q , ε) Zustand q q
Eingabe aβ β
Keller Aγ γ
In der „Welt der Grammatiken“ bedeutet das [q, A, q ] → a. 2. Fall: genau ein Kellerzeichen bei δ (q, a, A) (q1 , B) Zustand q q1 .. . q
Eingabe aβ
Keller Aγ Bγ
β
γ
In der „Welt der Grammatiken“ bedeutet das [q, A, q ] → a[q1 , B, q ]. [q, A, q ] und [q1 , B, q ] sind Nichtterminale in G für alle q ∈ Q. 3. Fall: Kellerwort der Länge 2 bei δ (q, a, A) (q1 , BC) Zustand q q1 .. . q2 .. . q
Eingabe aβ
Keller Aγ BCγ Cγ
β
γ
8.3 Äquivalenz von NKA und kontextfreier Grammatik
139
In der „Welt der Grammatiken“ bedeutet das [q, A, q ] → a[q1 , B, q2 ][q2 ,C, q ]. [q, A, q ], [q1 , B, q2 ] und [q2 ,C, q ] sind Nichtterminale in G für alle q , q2 ∈ Q. Diese intuitiven Vorüberlegungen genügen, um sich klar zu machen, wie man die Menge der Produktionen von G bestimmt. Für a ∈ (Σ ∪ {ε}) ergibt sich P
=
{s → [q0 , k0 , q] | für alle q ∈ Q}
∪
{[q, A, q ] → a | δ (q, a, A) (q , ε)}
∪
{[q, A, q ] → a[q1 , B, q ] | δ (q, a, A) (q1 , B), für alle q ∈ Q}
∪
{[q, A, q ] → a[q1 , B, q2 ][q2 ,C, q ] | δ (q, a, A) (q1 , BC), für alle q , q2 ∈ Q}
Mit vollständiger Induktion kann man nun für jede Zeichenkette w ∈ Σ∗ zeigen, dass L(M) = L(G).
w ∈ L(M) ⇔ w ∈ L(G)
Beispiel 8.7 Wir wenden das Konstruktionsverfahren aus diesem Beweisteil zur Erzeugung einer äquivalenten kfG G aus dem NKA M = ({q0 , q1 }, {0, 1}, {X,Y }, δ , q0 ,Y ) mit folgendem δ an. δ (q0 , 0,Y )
(q0 , XY )
δ (q0 , 0, X)
(q0 , XX)
δ (q0 , 1, X)
(q1 , ε)
δ (q1 , 1, X)
(q1 , ε)
δ (q1 , ε, X)
(q1 , ε)
δ (q1 , ε,Y )
(q1 , ε)
Da in der Überführungsfunktion δ ausschließlich Kellerwörter vorkommen, deren Länge höchstens 2 beträgt, liegt die gewünschte Normierung bereits vor. Wir können also gleich mit der eigentlichen Transformation beginnen. Als erstes ergeben sich die Produktionen für das Spitzensymbol: s → [q0 ,Y, q0 ] | [q0 ,Y, q1 ].
Spitzensymbol
Nun folgen die Konstruktionen nach den 3 betrachteten Fällen. Für Fall 1 erhalten wir: [q0 , X, q1 ]
→
1
[q1 , X, q1 ]
→
1
[q1 , X, q1 ]
→
ε
[q1 ,Y, q1 ]
→
ε
Da in δ kein einziger Funktionswert ein Kellerwort der Länge 1 enthält, entfällt Fall 2. Im Fall 3 stellen wir (zuerst für δ (q0 , 0,Y ) (q0 , XY )) das für alle Zustandskombinationen auszufüllende Muster voran: [q0 ,Y, ] → 0[q0 , X, ♦][♦,Y, ]
Fall 3
140
8 Kellerautomaten und kontextfreie Sprachen
Die darin enthaltenen Symbole ♦ und sind so durch Zustände zu belegen, dass gleiche Symbole gleiche Zustände repräsentieren. Andererseits ist jedoch nicht ausgeschlossen, dass verschieden symbolisierte Zuständsbelegungen übereinstimmen. [q0 ,Y, q0 ]
→
0[q0 , X, q0 ][q0 ,Y, q0 ]
[q0 ,Y, q0 ]
→
0[q0 , X, q1 ][q1 ,Y, q0 ]
[q0 ,Y, q1 ]
→
0[q0 , X, q0 ][q0 ,Y, q1 ]
[q0 ,Y, q1 ]
→
0[q0 , X, q1 ][q1 ,Y, q1 ]
Analog gehen wir nun für δ (q0 , 0, X) (q0 , XX) vor. [q0 , X, ]
→
0[q0 , X, ♦][♦, X, ]
[q0 , X, q0 ]
→
0[q0 , X, q0 ][q0 ,Y, q0 ]
[q0 , X, q0 ]
→
0[q0 , X, q1 ][q1 ,Y, q0 ]
[q0 , X, q1 ]
→
0[q0 , X, q0 ][q0 ,Y, q1 ]
[q0 , X, q1 ]
→
0[q0 , X, q1 ][q1 ,Y, q1 ]
Wir erhalten 14 Regeln. Diese Regelmenge kann allerdings noch deutlich optimiert werden: Als erstes entfernen wir jede Regel, auf deren rechter Seite (mindestens) ein Nichtterminal vorkommt, für das es keine Regel gibt. Von den verbleibenden Produktionen werden die rekursiven Regeln, wie X → αXβ , für solche Nichtterminale gestrichen, für die es ausschließlich rekursive gibt. Durch diese zweite Gruppe gestrichener Regeln kann es durchaus sein, dass der erste Optimierungsschritt wiederholt werden muss. In unserem Beispiel bleiben nur noch die folgenden Nichtterminale übrig. Wir benennen sie um, damit sich praktikablere Namen ergeben: [q0 , X, q1 ] := A, [q1 , X, q1 ] := B, und [q0 ,Y, q1 ] := C. Damit erhalten wir folgende Grammatik G = ({A, B,C}, {0, 1}, P,C) mit P = {A → 1 | 0AB, B → 1 | ε, C → 0A}. Je nach Wunsch kann G nun in eine ε-freie kfG transformiert werden. Offensichtlich wird mit G die Sprache L = L(G) = {
n n
| n ≥ 1} beschrieben.
Übung 8.2 Arbeiten Sie das Beispiel 8.7 komplett und gewissenhaft durch. Computerübung 8.5 Wenden Sie die in AutoEdit eingebaute automatische Transformation von NKA (7-TupelDefinition) in äquivalente kfG auf den in Beispiel 8.7 angegebenen NKA an. Führen Sie im Anschluss entsprechende Optimierungen durch: Umbenennung der Nichtterminale, Streichen unnötiger Regeln. Vergleichen Sie die auf diese Weise erzeugte Grammatik mit der aus Beispiel 8.7. Computerübung 8.6 Verwenden Sie kfG Edit und AutoEdit, um mittels G bzw. M aus Beispiel 8.7 Ableitungen bzw. Konfigurationenfolgen für jeweils ein bestimmtes Eingabewort zu erzeugen. Hierzu benötigen Sie eine 7-Tupel-Definition für den betrachteten NKA:
8.3 Äquivalenz von NKA und kontextfreier Grammatik
141
M = ({q0 , q1 , q2 , q3 }, {0, 1}, {X,Y, Z}, δ , q2 , Z, {q3 }) mit δ (q2 , ε, Z) = {(q0 ,Y Z)}, δ (q0 , 0,Y ) (q0 , XY ), δ (q0 , 0, X) (q0 , XX), δ (q0 , 1, X) (q1 , ε), δ (q1 , 1, X) (q1 , ε), δ (q1 , ε, X) (q1 , ε), δ (q1 , ε,Y ) (q1 , ε), δ (q0 , ε, Z) (q3 , ε), δ (q1 , ε, Z) (q3 , ε). Die Simulation der Anwendung von M auf das Eingabewort Ableitung dieses Wortes mittels G zeigt Abbildung 8.5.
zeigt Abbildung 8.4. Die
Abbildung 8.4: Akzeptanz des Wortes
durch den NKA M
Abbildung 8.5: Ableitung des Wortes
mit Hilfe der kfG G
142
8 Kellerautomaten und kontextfreie Sprachen
8.4 Parsing kontextfreier Sprachen Wir wissen nun, dass kfS durch kfG (C HOMSKY-Typ 2) und NKA beschrieben werden können. Für praktische Anwendungen sind Parser auf der Basis von NKA jedoch unbrauchbar, da sie im Allgemeinen mit exponentieller Zeitkomplexität3 arbeiten. Effizientes kfS-Parsing
Es gibt zwei verschiedene Wege, diesem Problem zu begegnen: 1. Suche nach effizienten Parsing-Verfahren für allgemeine kfS und 2. Definition „brauchbarer“ Untermengen der Menge aller kfS, für die es sehr effiziente Parsing-Verfahren gibt.
allgemeine kfS
Für beide Arbeitsrichtungen gibt es interessante Resultate. Während wir uns weiter unten mit Sprachfamilien befassen werden, die in der Menge aller kfS echt enthalten sind, beleuchten wir hier Variante 1. D.h., wir fragen nach effizienten Parsing-Verfahren für ganz allgemeine kfS, also auch für mehrdeutige Grammatiken, die sogar ε- und linksrekursive Regeln besitzen können. Im Übrigen hat die aufgeworfene Fragestellung inzwischen eine historische Dimension: Insbesondere (Computer-)Linguisten haben sich schon vor mehr als 50 Jahren damit beschäftigt. Sie haben es naturgemäß mit eher „unbequemen“ Sprachen zu tun, da sie eine gewisse Nähe zu den natürlichen Sprachen anstreben.
Parsing in AtoCC
Auch bei der Enwicklung von AtoCC trat dieses Problem auf: Schließlich sollte in kfG Edit jede kfS – ohne vorherige Transformation (falls überhaupt möglich) – verarbeitet werden können. Triviale Verfahren, die bei linksrekursiven Regeln zur Endlosarbeit des Computers führen, sind da genauso ungeeignet wie die oben beschriebenen ineffizienten.
dynamisches Programmieren
Effiziente Verfahren können durch die Methode des dynamischen Programmierens entstehen. Grob gesagt, wird dabei das eigentliche Problem dadurch gelöst, dass (sämtliche) gleichartige Teilprobleme (beginnend mit den kleinsten) gelöst werden, von denen oft nur einige benötigt werden, um aus deren Lösung die Lösung des Ausgangsproblems zu gewinnen. Meist kann man die Teilprobleme mit den zugehörigen Lösungen in Form einer n × m-Tabelle darstellen. Zum Ausfüllen der gesamten Tabelle (mit den Einzellösungen) benötigt der Berechnungsprozess eine Zeit in O(nm). Braucht es zur Berechnung jedes dieser Tabelleneinträge beispielsweise einen linearen Zeitaufwand (in O(p)), so ergibt sich insgesamt ein kubischer Aufwand: O(nm) · O(p) = O(nmp) = O(r3 ), mit r = max(m, n, p). Der Einsatz dynamischen Programmierens garantiert allerdings nicht in jedem Fall eine Reduktion der Aufwandsordnung von exponentiell zu polynomial, z.B. ku3 Zur
Angabe des zeitlichen Berechnungsaufwandes kann eine exponentielle Funktion T der Form y = T (n) = kn (k > 1, n ist die Problemgröße, hier die Länge des Eingabewortes) in gewissem Sinn nicht „unterboten“ werden. Man sagt: „T liegt in O(kn ).“ Genaueres erfährt man im Lehrgebiet über Effizienz von Algorithmen und Komplexität.
8.4 Parsing kontextfreier Sprachen
143
bisch. Neben den Erfolgsfällen gibt es sogar Probleme, die sich dieser Methode vollständig entziehen, oder sog. NP-vollständige Probleme, wie das Rucksackproblem, die trotz erfolgreicher Anwendung des dynamischen Programmierens (pseudopolynomialer Aufwand) ihren Charakter als praktisch unlösbare Aufgabe (exponentieller Aufwand) nicht verlieren.
Rucksackproblem
Mit der Erfindung sog. Chart-Parser für allgemeine kfG durch M ARTIN K AY (1996) gelingt ein Effizienzgewinn durch dynamisches Programmieren. Charts (Tabellen) bezeichnen hier eine geeignete Datenstruktur, nicht etwa eine ParsingStrategie. Die Parsing-Strategie kann gewissermaßen „unter der Haube“ in gewissen Grenzen frei gewählt werden. Eine solches Vorgehen wurde schon viel früher (1970) von dem Psychologen JAY E ARLEY vorgeschlagen.
Chart-Parser
Der Earley-Parser ergibt sich aus dem Chart-Parser-Konzept durch Wahl einer bestimmten Strategie. Im Allgemeinen arbeitet er mit O(n3 ), wobei n die Länge des Eingabewortes ist. Für eindeutige Grammatiken liegt sein Zeitaufwand sogar in O(n2 ) und für eine in einem Folgekapitel behandelte Klasse der LR(k)-Sprachen ist er linear, also in O(n).
Earley-Parser
Auch sehr aktuelle Publikationen befassen sich mit effizienten Chart-Parsern, für deren Implementierung funktionale Programmiersprachen mit verzögerter Evaluation von großem Vorteil sind. Zur Beschreibung des Arbeitsprinzips eines Chart-Parsers für eine gegebene reduzierte kfG G = (N, T, P, s) gehen wir davon aus, dass für das zu analysierende Eingabewort (Satz) eine Tokenfolge vorliegt, die im Ergebnis der lexikalischen Analyse entstand. Die Positionen vor, zwischen und nach den Token werden durchnummeriert:
Arbeitsweise eines Chart-Parsers
0 t1 1 t2 . . . n-1 tn n . Die Positionsmarkierungen sind notwendig, um die jeweils aktuelle ParserSituation beschreiben zu können. Dies geschieht durch Tripel (i, j, A → α.β ) mit natürlichen Zahlen 0 ≤ i ≤ n und i ≤ j ≤ n sowie A ∈ N und α, β ∈ (N ∪ T )∗ . Ein Chart ist eine endliche Menge von Tripeln dieser Art.
(i, j, A → α.β ) Chart
Anschaulich bedeutet i die Position, ab der ein bestimmtes Satzglied A erwartet wird. j ist die Position, bis zu der α erkannt worden ist. Der Punkt zwischen dem geschlossenen Teil (α) und dem offenen Teil (β ) trennt die beiden Teile und steht vor der als nächstes zu erwartenden Kategorie (am Beginn von β ). (2, 6, X → K R . R) bedeutet also, dass X ab Satzposition 2 erwartet wird. Die Analyse von X ist bis zur Position 6 vorangeschritten (geschlossener Teil K R). Ab Position 6 wird R erwartet. Zur Initialisierung des Parse-Prozesses nimmt man S als neues Nichtterminal, das in N bisher nicht vorkommt, hinzu, und schreibt (0, 0, S → .s) in den Chart. Zu beachten ist der Punkt vor dem Spitzensymbol s der Grammatik G. Noch nichts ist getan und erwartet wird s.
Initialisierung des ParseProzesses
144
Akzeptanzverhalten
ParserOperationen
8 Kellerautomaten und kontextfreie Sprachen
Ein Satz w1 w2 . . . wn bzw. in der vorbereiteten Tokengestalt t1 t2 . . . tn wird genau dann akzeptiert, wenn (0, n, S → s.) im Chart steht. Der Punkt steht nun ganz am Ende, d.h. s wurde (komplett) erkannt. Der Satz wurde von Position 0 bis n gelesen, analysiert und syntaktisch akzeptiert. Findet sich dieses Tripel am Ende nicht im Chart, wird der Satz abgewiesen, da er nicht zu L(G) gehört. Insgesamt drei Parser-Operationen sorgen für Einträge im Chart: Wenn das als nächstes erwartete Satzglied ein passendes Token ist, dann lies es und schiebe die Arbeitsposition (j) um eins weiter. Formal bedeutet das: Wenn sich (i, j, A → α.t j β ) im Chart befindet und t j ist die Tokenklasse des als nächstes einzulesenden Tokens t j , dann füge (i, j + 1, A → αt j .β ) im Chart hinzu, falls dieses Tripel nicht schon vorkommt. Für das als nächstes erwartete Nichtterminal können sämtliche Erwartungen durch die entsprechenden Regeln für dieses Nichtterminal vorhergesagt werden. Formal bedeutet das (für eine Top-Down-Strategie4 ): Wenn sich (i, j, A → α.Bβ ) im Chart befindet, dann füge für jede Regel B → γ der Grammatik G das Tripel ( j, j, B → .γ) im Chart hinzu, falls dieses Tripel nicht schon vorkommt5 . Der Completer stellt die Fundamentalregel des Chart-Parsings dar. Sie gilt, ebenso wie die scan-Operation, unabhängig von der gewählten Strategie: Wenn sich (i, j, A → α.Bβ ) und ( j, k, B → γ.) im Chart befinden, dann füge (i, k, A → αB.β ) im Chart hinzu, falls dieses Tripel dort nicht schon vorkommt. Die anschlauliche Erklärung dieser Operation übernimmt Abbildung 8.6. Zu beachten sind die Positionen der Punkte in den Regeln. A → α.Bβ
A → α.Bβ B → γ.
i
j
B → γ. k
i
j
k
A → αB.β
Abbildung 8.6: Fundamentale Regel (completer) eines Chart-Parsers
Beispiel 8.8 Gegeben G = ({S,U, X,W }, {v, k, r, y, z}, P, S), mit P = {S → U X (1), S → S W (2), X → v U (3), U → U W (4), U → y k (5), U → z (6), W → r U (7)}6 . Das Eingabewort ist . 4 An
dieser Stelle findet die Strategiewahl statt, z.B. Top-Down, Bottom-up, Earley, Stolcke. diesen Zusatz wird verhindert, dass linksrekursive Regeln den Chart aufblähen. 6 In Klammern wurden Marken angefügt, auf die wir uns im Parse-Protokoll beziehen wollen. 5 Durch
8.4 Parsing kontextfreier Sprachen
145
, k→ , r→ , Der Scanner verwendet folgende Regeln v → und z → | . Die Tokenklassen sind also v, k, r, y und z. In der reduziery→ ten Grammatik G finden sie sich als Terminale wieder. Die Tokenfolge für das Eingabewort )(v . )(z . )(r . )(y . )(k . ). Wie der ist (z . Chart für das Parsing dieses Satzes in Tabelle 8.1 zeigt, wird das Eingabewort akzeptiert.
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
(0, 0, S → .S) (0, 0, S → .U X) (0, 0, S → .S W ) (0, 0,U → .U W ) (0, 0,U → .y k) (0, 0,U → .z) (0, 1,U → z.) (0, 1, S → U.X) (0, 1,U → U.W ) (1, 1, X → .v U) (1, 1,W → .r U) (1, 2, X → v.U) (2, 2,U → .U W ) (2, 2,U → .y k) (2, 2,U → .z) (2, 3,U → z.) (2, 3,U → U.W ) (3, 3,W → .r U) (3, 4,W → r.U) (4, 4,U → .U W ) (4, 4,U → .y k) (4, 4,U → .z) (4, 5,U → y.k) (4, 6,U → y k.) (4, 6,U → U.W ) (3, 6,W → r U.) (2, 6,U → U W.) (1, 6, X → v U.) (0, 6, S → U X.) (0, 6, S → S.)
Initialisierung P1,1 P1,2 P2,4 P2,5 P2,6 S6 C2,7 C4,7 P8,3 P9,7 S10 P12,4 P12,5 P12,6 S15 C13,16 P17,7 S18 P19,4 P19,5 P19,6 S21 S22 C20,24 C19,24 C17,26 C12,27 C8,29 C1,29
(t1 )
(t2 )
(t3 )
(t4 )
(t5 ) (t6 )
Akzeptiert!
Tabelle 8.1: Chart-Parsing
Die in Spalte 3 von Tabelle 8.1 verwendeten Symbole haben folgende Bedeutungen: Pn, m . . . predict des Tripels in Zeile n mit Regel m Sn . . . scan des Tripels in Zeile n Cn, m . . . Kombination der Tripel in den Zeilen n und m
146
8 Kellerautomaten und kontextfreie Sprachen
Durch die zu wählenden Strategien und durch Vorausschautechnik kann die Zahl unnötig berechneter Tripel reduziert werden. Das vorgestellte Verfahren kann prinzipiell mit jeder höheren Programmiersprache implementiert werden. Besonders zu empfehlen ist allerdings die Verwendung von Prolog. Hierfür gibt es in der Literatur mehrere interessante Fundstellen. Computerübung 8.7 ∈ L(G) mit G = ({X}, { , }, {X → X | Verwenden Sie kfG Edit, um für das Wort }, X) einen Ableitungsbaum zu erzeugen. Beachten Sie dabei, dass kfG Edit mit der linksrekursiven Regel offenbar prima klarkommt. Übung 8.3 Wenden Sie das Chart-Parsing mit Top-Down-Strategie, analog zu Beispiel 8.8, auf G = an. Vergleichen Sie anschließend ({X}, { , }, {X → X | }, X) und das Eingabewort Ihre Lösung mit der folgenden. Initialisierung 1 (0, 0, S → .X) 2 (0, 0, X → .X ) P1,1 3 (0, 0, X → . ) P2,2 4 (0, 1, X → .) S3 5 (0, 1, X → X. ) C2,4 6 (0, 1, S → X.) C1,4 7 (0, 2, X → X .) S5 8 (0, 2, X → X. ) C2,7 9 (0, 3, X → X .) S8 10 (0, 3, S → X.) C1,9 Akzeptiert!
8.5 Deterministischer Kellerautomat (DKA) In Analogie zu endlichen Automaten gibt es auch bei Kellerautomaten den deterministische Typ, also den deterministischen Kellerautomaten, kurz: DKA. Spricht man nur vom Kellerautomaten schlechthin, so ist der NKA gemeint. Definition 8.2 Ein DKA ist wie ein NKA definiert. Allerdings gibt es drei Abweichungen: 1. δ : Q × (Σ ∪ {ε}) × Γ → Q × Γ∗ , anstelle von ℘endlich (Q × Γ∗ ) auf der rechten Seite bei NKA. Die Funktionswerte sind also Paare und keine Mengen. 2. Wenn δ (q, ε, A) definiert ist, dann ist für alle a ∈ Σ der Funktionswert δ (q, a, A) undefiniert oder umgekehrt. 3. Es gibt eine Menge von Endzuständen E ⊆ Q, d.h. es wird immer die NKADefinitionsform als 7-Tupel M = (Q, Σ, Γ, δ , q0 , k0 , E) zugrunde gelegt. Die bei NKA zulässige 6-Tupel-Definition darf für DKA nicht verwendet werden.
Der in Abbildung 8.7 links angegebene Graph-Ausschnitt zeigt einen Verstoß gegen Punkt 1 der Definition 8.2, der auf der rechten Seite verstößt gegen Punkt 2.
8.5 Deterministischer Kellerautomat (DKA)
147
Das Verbot solcher Konstellationen ist notwendig, um eine deterministische Arbeitsweise zu garantieren.
qj qj
(A,a):... (A,a): ...
qi qi
(A,a):...
(A, ε ): ...
qm
qm
Abbildung 8.7: Verstöße gegen DKA-Definition
Die von einem DKA akzeptierte Sprache ist ∗
L(M) = {w ∈ Σ∗ | (q0 , w, k0 ) (qe , ε, K), K ∈ Γ∗ , qe ∈ E}. Ein DKA stoppt, wenn das Eingabewort vollständig abgetastet wurde und kein weiterer spontaner Zustandsübergang vorhanden ist. Falls das Eingabewort komplett gelesen wurde und von dem dann aktuellen Zustand aus ein spontaner Übergang möglich ist, wird dieser in jedem Fall ausgeführt. Beispiel 8.9 Für die kfS L = {an bn | n ≥ 1} kann ein DKA wie folgt angegeben werden. MDKA = ({s0 , s1 , s2 }, { , }, {a, $}, δ , s0 , $, {s2 }) mit folgendem δ : δ (s0 , a, $)
=
(s0 , a$)
δ (s0 , a, a)
=
(s0 , aa)
δ (s0 , b, a)
=
(s1 , ε)
δ (s1 , b, a)
=
(s1 , ε)
δ (s1 , ε, $)
=
(s2 , $)
Abbildung 8.8 zeigt den zugehörigen Graph. ($,a):a$ (a,a):aa
Start
s0
(a,b): ε
(a,b): ε
s1
($, ε ):$
Abbildung 8.8: Graph der Überführungsfunktion des DKA aus Beispiel 8.9
s2
148
8 Kellerautomaten und kontextfreie Sprachen
Die mit AutoEdit durchgeführte Simulation der Akzeptanz des Eingabewortes ist erfolgreich.
(s0 , , $) (s0 , , a$) (s0 , , aa$) (s1 , , a$) (s1 , ε, $) (s2 , ε, $) ist die zugehörige Konfigurationenfolge. Sie kann auch als Tabelle dargestellt werden. Zustand Eingabe Keller s0 $ s0 a$ s0 aa$ s1 a$ s1 ε $ s2 ε $ Computerübung 8.8 Verwenden Sie AutoEdit, um dies nachzuvollziehen.
8.6 Deterministisch kontextfreie Sprachen LDEA = LNEA
Die Frage nach der Leistungsfähigkeit des Beschreibungsmittels DKA drängt sich förmlich auf. Erinnern wir uns an die Situation bei endlichen Automaten: LDEA = LNEA .
LDKA ⊆ LNKA
DEAs und NEAs sind gleichermaßen leistungsfähig im Hinblick auf die Beschreibungskraft regulärer Sprachen. Eine Analogie für kfG und kfS gibt es jedoch nicht. Hier gilt LDKA ⊆ LNKA . Für manche kfS kann sehr wohl ein NKA, jedoch kein entsprechender DKA angegeben werden. In Beispiel 8.10 wird eine solche Sprache betrachtet. Sie unterscheidet sich nur geringfügig von der Spache in Beispiel 8.11, für die es einen zugehörigen DKA gibt. Beispiel 8.10 Für die Sprache der Palindrome L = {w ∈ {a, b}∗ | w = umkehr(w)}
8.6 Deterministisch kontextfreie Sprachen
149
gibt es keinen DKA, der genau L akzeptiert. Ein DKA verfügt über keine Mittel, um festzustellen, ob die Wortmitte erreicht ist. Genau dies ist aber die Voraussetzung, um den Vorgang des Kellerns zu beenden und auf Entkellern umzuschalten. Ein NKA für L ist M = ({q0 , q1 , q2 }, { , }, {$, a, b}, δ , q0 , $, {q2 }), mit folgender Überführungsfunktion δ . δ (q0 , a, $)
=
{(q0 , a$), (q1 , $)}
δ (q0 , ε, $)
=
{(q1 , $)}
δ (q0 , b, $)
=
{(q0 , b$), (q1 , $)}
δ (q0 , ε, a)
=
{(q1 , a)}
δ (q0 , a, a)
=
{(q0 , aa), (q1 , a)}
δ (q0 , ε, b)
=
{(q1 , b)}
δ (q0 , b, a)
=
{(q0 , ba), (q1 , a)}
δ (q1 , a, a)
=
{(q1 , ε)}
δ (q0 , a, b)
=
{(q0 , ab), (q1 , b)}
δ (q1 , b, b)
=
{(q1 , ε)}
δ (q0 , b, b)
=
{(q0 , bb), (q1 , b)}
δ (q1 , ε, $)
=
{(q2 , $)}
Zum direkten Vergleich ergänzen wir den Übergangsgraph für diesen NKA. ($,a):a$ (a,a):aa (b,a):ab ($,b):b$ (a,b):ba (b,b):bb
Start
($, ε ):$ (a, ε ):a (b, ε ):b ($,a):$ (a,a):a (b,a):b ($,b):$ (a,b):a (b,b):b
q0
(a,a): ε (b,b): ε ($, ε ):$
q1
q2
Abbildung 8.9: Graph der Überführungsfunktion des NKA aus Beispiel 8.10 Computerübung 8.9 Bearbeiten Sie den NKA aus Beispiel 8.10 mit AutoEdit und führen Sie entsprechende Simulationen durch. Beispiel 8.11 Wir betrachten die Sprache der Palindrome mit markierter Wortmitte: L = {w ∈ {a, b, c}∗ | w = vcv und v = umkehr(v), mit v ∈ {a, b}∗ }. M = ({q0 , q1 , q2 }, { , , }, { , , }, δ , q0 , , {q2 }) mit δ (q0 , , )
=
(q0 ,
)
δ (q0 , , )
=
(q1 , )
δ (q0 , , )
=
(q0 ,
)
δ (q0 , , )
=
(q1 , )
δ (q0 , , )
=
(q0 ,
)
δ (q0 , , )
=
(q1 , )
δ (q0 , , )
=
(q0 ,
)
δ (q1 , , )
=
(q1 , ε)
δ (q0 , , )
=
(q0 ,
)
δ (q1 , , )
=
(q1 , ε)
δ (q0 , , )
=
(q0 ,
)
δ (q1 , ε, )
=
(q2 , ε)
150
8 Kellerautomaten und kontextfreie Sprachen
ist ein DKA für L. Ergänzend geben wir die Überführungsfunktion grafisch an: ($,a):a$ (a,a):aa (b,a):ab ($,b):b$ (a,b):ba (b,b):bb
Start
q0
(a,a): ε (b,b): ε ($,c):$ (a,c):a (b,c):b
q1
($, ε ): ε
q2
Abbildung 8.10: Graph der Überführungsfunktion des DKA aus Beispiel 8.11
Computerübung 8.10 Bearbeiten Sie den DKA aus Beispiel 8.11 mit AutoEdit und führen Sie entsprechende Simulationen durch.
dkfS
Die Sprachen, die durch DKA beschrieben werden können, heißen deterministisch kontextfreie Sprachen, kurz: dkfS. Sie bilden die für die Programmiersprachen wichtigste Sprachklasse und stimmen mit den sog. LR(k)-Sprachen überein, s. Abbildung 8.11. Diese werden im Gebiet der Sprachübersetzer (Compilerbau) ausführlich thematisiert.
LNKA
LDKA = Ldk f S = LLR(k)
Abbildung 8.11: Darstellung relevanter Sprachklassen: kfS
8.7 Parsergeneratoren für dkfS
151
8.7 Parsergeneratoren für dkfS An dieser Stelle greifen wir unsere Zeichenrobotersprache aus Abbildung 5.12 wieder auf, um unserem Ziel, einen ZR→PS-Compiler herzustellen, einen Schritt näher zu kommen. Es ist klar, dass NKA zur Beschreibung kfS, die den Kern von Programmiersprachen bilden, ungeeignet sind. Schon bei kleineren Programmen würde die Analyse viel zu viel Zeit beanspruchen. Backtracking ist eine sehr zeitaufwendige Strategie. Also konzentrieren wir uns auf DKAs und verwenden einen DKA exemplarisch zur Beschreibung eines kleinen Ausschnitts unserer ZR-Sprache. Beispiel 8.12 Die folgenden Regeln beschreiben eine simple Subsprache von ZR, s. Abbildung 5.12 auf Seite 56. Anweisungen
→
Anweisung
→
|
Anweisung Anweisungen ε Anweisungen
| Die Terminale sind in der gewohnten Form notiert. ist also hier wirklich ein verwendbares Zeichen und nicht etwa ein Platzhalter für eine Zahl. Anweisungen ist das Startsymbol. Im Folgenden geben wir einen DKA für diese Sprache7 an. M = ({q0 , q1 , q2 , q3 , q4 , q5 , q6 , q7 }, { , , ,
,
}, { , }, δ , q0 , , {q6 }) mit (+,WH):+ (+,]): ε (+,n):+
q1
($,n):$ (+,n):+
q2
($,[):+$ (+,[):++
q3
(+,VW):+
q4
q5 (+,VW):+
(+,WH):+
(+,]): ε ($, ε ):$
($,WH):$
($,WH):$
Start
q0
($,VW):$
q7
($,n):$
q6
($,VW):$
M akzeptiert das Wort nach 36 Schritten. 7 Schaut
man etwas genauer hin, erkennt man, dass der angegegene DKA das leere Wort – obwohl lt. Sprachdefinition zulässig – nicht akzeptiert. Diese Ausnahme nehmen wir in Kauf, da zur formalen Korrektur ein NKA erforderlich wäre.
152
Parsergeneratoren
8 Kellerautomaten und kontextfreie Sprachen
Hätten Sie diesen DKA auch angeben können? Können Sie sich vorstellen, einen DKA für die zu Java gehörende kfS zu entwerfen? Wohl kaum, denn es ist eine sehr komplexe Aufgabe, für deren Lösung hilfreiche Werkzeuge entwickelt wurde. Gemeint sind Parsergeneratoren, denen wir die Definition einer Sprache übergeben. Wie es der Name vermuten lässt, erzeugt ein Parsergenerator daraus einen Parser für die übergebene Sprachdefinition. Diese Prinzip kennen wir bereits vom Scannergenerator, s. Abschnitt 7.7 ab Seite 120. Dort haben wir darauf hingewiesen, dass Parser und Scanner zusammenarbeiten. Darauf wollen wir jetzt zurückgreifen, um unseren Parser dementsprechend zu strukturieren. Als (menschenwürdige) Beschreibungsmittel für kontextfreie Sprachen (Parser) verwenden wir kontextfreie Grammatiken und für reguläre Sprachen reguläre Ausdrücke (Scanner). Mit VCC aus AtoCC steht uns ein komfortabler Scanner- und Parsergenerator zur Verfügung. Beispiel 8.13 Wir laden als erstes die Grammatikdefinition von ZR (s. Abbildung 5.12) in kfG Edit und entfernen genau die Regeln, um die sich der Scanner kümmern wird. Das sind die Regeln für Zahl, Ziffern, Ziffer, ErsteZiffer und Farbwert, gemäß der reduzierten Grammatik für die Sprache ZR in Abbildung 5.15 auf Seite 59. Dann exportieren wir diese kfG in VCC via Knopfdruck: „Export Compiler“. VCC wird anschließend mit der beim Export erzeugten Datei geöffnet. Die Parserdefinition gibt die Regeln der reduzierten kfG grafisch wieder. Farbwert und Zahl werden bereits als Token angesehen, müssen aber noch definiert werden. Dies geschieht in der Scannerdefinition. Für Zahl geben wir den regulären Ausdruck ein. Für Farbwert schreiben wir . Als Sprache, in der der Compiler entwickelt werden soll, wählen wir Java, hinzu kommt der Top-down-Parsertyp LL(1) und dann starten wir die automatisierte Compiler-Generierung. Einen eventueller Hinweis, sog. S-Attribute betreffend, ignorieren wir. Im Erfolgsfall ist die Ausschrift sehr knapp:
Zum Aufruf des Parsers und Anwendung auf den Inhalt der Datei Eingabefenster:
öffnet sich ein
8.8 Optimierung kontextfreier Grammatiken
153
Wir können den Parser von VCC in einen NKA umwandeln lassen. Dieser NKA akzepin 19 Schritten. An der Suche aller tiert das Eingabewort Akzeptanz-Wege sind 26 NKA-Clones beteiligt. Die oben erwähnte praktische Unbrauchbarkeit von NKAs zum Parsen kfS wird damit eindrucksvoll bestätigt.
Besonders interessant sind die Bildschirminformationen bei Syntaxfehlern: Befindet sich beispielsweise ein fehlerhaftes Terminal im Eingabetext, wird dies vom Scanner festgestellt. Die Meldung lautet dann etwa . Ein Verstoß gegen die Regeln des Satzbaus quittiert hingegen der Parser. Diese Beobachtung führt natürlich zu neuen Begehrlichkeiten: Wir wünschen uns eine Systemantwort, die nicht nur den Fehlerstatus und ggf. die Fehlerart erfasst, sondern auch einen Hinweis zur Korrektur (also mindestens zur Fehlerposition) ausgibt. Ein mit VCC erzeugter Compiler gibt bei „No matching token“ auch das Restwort ab der Fehlerposition aus. Im Allgemeinen ist dies allerdings eine sehr schwierige Aufgabe. Bei komplexeren Sprachen kommt es beispielsweise vor, dass die Position, an der der Fehler erkannt wird, nicht die Fehler-verursachende Stelle ist. Da das sog. error recovery kompliziert ist, muss man sich oft mit nichtssagenden Fehlermeldungen, wie , oder kryptischen Ausschriften (z.B. Java-Fehlerprotokolle) begnügen. Computerübung 8.11 Verwenden Sie AtoCC, um die in Beispiel 8.13 beschriebenen Aktionen nachzuvollziehen. Studieren Sie vor allem auch die Folgen von Syntaxfehlern.
Damit sind wir unserem ZR→PS-Compiler wieder ein Stück näher gekommen. Für syntaktisch korrekte ZR-Wörter fehlt nun noch die Zielcodeerzeugung. Dies ist ein Gegenstand in Abschnitt 10.5.
8.8 Optimierung kontextfreier Grammatiken Der Entwurf einer kfG für eine Sprache mit echter Anwendungsrelevanz ist ein aufwendiger Prozess. Um ihn zu beherrschen, verwenden wir aussagefähige Nichtterminale (speaking symbols) in Regeln, die die jeweile Teilstruktur für den menschlichen Leser leicht verständlich beschreiben. Dies fördert auch die Wartbarkeit, d.h., wir können die Rolle, die die betrachtete Regel spielt, rasch reproduzieren, um Erweiterungen oder ggf. Korrekturen anzubringen. Was für die Lesbarkeit und die Beherrschung der Komplexität des Entwurfsprozesse zuträglich ist, kann für den Prozess der Syntaxanalyse unnötigen Ballast
Syntaxfehler
154
8 Kellerautomaten und kontextfreie Sprachen
bedeuten. Unnötige Nichtterminale, die wir aus o.g. Gründen gern etwas üppig verwendet haben, führen aus technischer Sicht zu vermeidbaren Zuständen und zeitaufwendigen Übergängen, wenn man an das praktische Parsing denkt. Außerdem lässt manche Ableitung erkennen, dass die eine oder andere Regel nach einem kleinen Eingriff ganz entfallen kann. Entfernung unnützer Nichtterminale Kettenregeln
Im Folgenden wird gezeigt, wie man Grammatik-Optimierungen in Bezug auf unnütze Nichtterminale und Kettenregeln algorithmisch durchführt. Unnütze Nichtterminale sind solche, die nichts zur Erzeugung der Sprache L(G) beitragen. Um sie herauszufiltern, fragen wir umgekehrt nach Nichtterminalen, die unverzichtbar sind: 1. Auf das Spitzensymbol s kann natürlich nicht verzichtet werden, da es Aus∗ gangspunkt jeder Ableitung ist: s ⇒ w. 2. Für alle X ∈ N mit X = s gilt: a) X darf nicht gestrichen werden, wenn es mindestens ein Wort w ∈ T + ∗ gibt, mit X ⇒ w ∗
b) und wenn s ⇒ αXβ , wobei α, β ∈ (N ∪T )∗ , d.h. wenn X in wenigstens einer vom Spitzensymbol aus erreichbaren Satzform vorkommt. Die nach der zweiten Forderung unverzichtbaren Nichtterminale werden iterativ „eingesammelt“. Zuerst wenden wir uns der Forderung 2a zu: M1 Mi+1
= {X | X ∈ N = Mi ∪ {X | X ∈ N
α ∈ T +}
(X → α) ∈ P, (X → α) ∈ P,
α ∈ (Mi ∪ T )+ }, i ≥ 1.
Die Bildung der Mengen Mi wird bei Menge Mn beendet, wenn sich Mn nicht von Mn+1 unterscheidet, d.h. wenn Mn = Mn+1 = . . . = Mn+k . n ist also der kleinste Index mit dieser Eigenschaft. Aus Mi ⊆ N für alle 1 ≤ i ≤ n folgt, dass dieser Prozess in jedem Fall terminiert. Mn enthält sämtliche Nichtterminale aus N, die zu einem Wort w ∈ T + führen. Die ursprüngliche Grammatik G = (N, T, P, s), mit L(G) = 0, / wird reduziert zu G = (N , T , P , s ) = (Mn , T, {X → α | (X → α) ∈ P X ∈ Mn }, s). Nun ist Forderung 2b an der Reihe: In P werden nun alle Regeln X → α gestrichen, die auf der rechten Seite „tote Nichtterminale“ Y , mit Y ∈ (N \ Mn ), enthalten, für die also Y ∈ α gilt. Wieder werden die unverzichtbaren Nichtterminale iterativ „eingesammelt“: M1 Mi+1
= {s} = Mi ∪ {X | X ∈ N
(Z → αXβ ) ∈ P , ∗
α, β ∈ (N ∪ T ) , Z ∈ Mi },
i ≥ 1.
Mn mit dem kleinsten n mit Eigenschaft Mn = Mn+1 enthält alle Nichtterminale, die
8.8 Optimierung kontextfreier Grammatiken
155
von s = s aus erreichbar sind. Die gesuchte, gegenüber G reduzierte Grammatik ist G = (N , T , P , s ) = (Mn , T, {X → α | (X → α) ∈ P X ∈ Mn }, s). Beispiel 8.14 Gegeben ist G = ({A, B,C, D, E, F}, {a, b}, P, A), mit P = {A → aBb | aA | D, D → E, E → ab, B → Fa, C → abba}. Gesucht ist die zugehörige reduzierte Grammatik G . Diese wird in den beiden oben beschriebenen Schritten hergestellt: M1 = {E,C}, M2 = {E,C, D}, M3 = {E,C, D, A} = M4 , n = 3. G = (N , T , P , s ) = ({A,C, D, E}, {a, b}, P , A), mit P = {A → aA | D, D → E, E → ab, C → abba}. Da B ∈ N , entfällt auch Regel A → aBb. M1 = {A}, M2 = {A, D}, M3 = {A, D, E} = M4 , n = 3. G = (N , T , P , s ) = ({A, D, E}, {a, b}, {A → aA | D, D → E, E → ab}, A).
In Beispiel 8.14 ist gut zu erkennen, dass die resultierende Grammatik G unnütze Kettenregeln A → D, D → E enthält. G kann also weiter reduziert werden. Die Lösung ist in diesem Minibeispiel sofort zu sehen: A → E. Das im Folgenden beschriebene Verfahren zur Eliminierung von Kettenregeln entfernt im ersten Schritt Zyklen der Art X1 → X2 , X2 → X3 , . . . , Xn−1 → Xn , Xn → X1 . Hierfür werden die betreffenden Nichtterminale X2 , X3 , . . . , Xn−1 , Xn durch X1 ersetzt. Anschließend werden alle Regeln X1 → X1 entfernt. Für die Beseitigung der dann noch vorhandenen Kettenregeln ist die Reihenfolge wichtig, in der die betreffenden Regeln gestrichen werden. Hierfür werden die Nichtterminale der Grammatik umbenannt und durchnummeriert: X1 , X2 , X3 , . . . , Xk , sodass aus Xi → X j stets i < j folgt. Nun verfährt man nach folgendem Verfahren: 1: for i = k − 1, . . . , 1 do 2: for j = i + 1, i, . . . , k do 3: if (Xi → X j ) ∈ P then 4: Entferne die Regel Xi → X j . 5: for all (X j → α) ∈ P do 6: Füge Xi → α hinzu. 7: end for 8: end if 9: end for 10: end for Beispiel 8.15 Für die in Beispiel 8.14 hergestellte Grammatik G schreiben wir P = {X1 → aX1 | ˆ X2 =D, ˆ X3 =E; ˆ k = 3. X2 , X2 → X3 , X3 → ab}, wobei X1 =A, Da (X2 → X3 ) ∈ P, wegen i = 2, j = 3, ist Regel X2 → X3 zu streichen und X2 → ab wird hinzugefügt, da X3 → ab existiert. Da (X1 → X2 ) ∈ P, wegen i = 1, j = 2, ist Regel X1 → X2 zu streichen und X1 → ab wird hinzugefügt, da X2 → ab existiert. Für i = 1 und j = 3 ergibt sich keine Veränderung. Das Ergebnis ist G = ({X1 , X2 , X3 }, {a, b}, {X1 → aX1 | ab, X2 → ab, X3 → ab}, X1 ). Die verwendeten Nichtterminale können wieder in die ursprünglichen umgeschrieben werden:
unnütze Kettenregeln
156
8 Kellerautomaten und kontextfreie Sprachen
G = ({A, D, E}, {a, b}, {A → aA | ab, D → ab, E → ab}, A). Offensichtlich enthält G unnütze Nichtterminale. Diese können mit dem weiter oben beschriebenen Verfahren eliminiert werden. Schließlich erhalten wir: A → aA | ab.
8.9 C HOMSKY-Normalform Grammatiktransformationen der in Abschnitt 8.8 beschriebenen Form werden nicht nur zur Reduktion eingesetzt, sondern können auch zur Herstellung von Regelstrukturen verwendet werden, die in gewisser Hinsicht vorteilhaft sind. Definition 8.3 Eine kfG G = (N, T, P, s) ist in C HOMSKY-Normalform, kurz: CNF, wenn jede Regel aus P entweder die Form X → a oder X → Y Z, mit a ∈ T und X,Y, Z ∈ N, besitzt.
Falls es in der betrachteten kfG ε-Regeln gibt, kann mit dem Verfahren aus dem Beweis von Satz 4.3 ab Seite 39 eine Transformation in eine äquivalente kfG ohne ε-Regeln durchgeführt werden. Sollte ε zur Sprache L(G) gehören, wird ein neues Spitzensymbol s mit s → ε | s eingeführt und nur die Sub-Grammatik mit s als Spitzensymbol hinsichtlich der geforderten Regelgestalt beurteilt und ggf. transformiert. CNF
CYK
Die speziellen Regelgestalten einer CNF sind in Definition 8.3 angegeben. Diese gewährleisten, dass Ableitungsbäume stets Binärbäume sind, da das jeweils linke bzw. rechte Nichtterminal Wurzel des zugehörigen Ableitungsteilbaums ist. Eine Reihe theoretischer Untersuchungen können auf Basis der CNF eleganter oder überhaupt geführt werden, da sich Schlussfolgerungen über Binärbäumen durch deren kalkulierbare Tiefe usw. anbieten. Außerdem kann für kfG in CNF ein sehr effizientes Parse-Verfahren angegeben werden: der Cocke-Younger-Kasami-Algorithmus (CYK). Die Grundlage für die Suche nach diesem Verfahren bildet folgender Satz. Satz 8.3 Zu jeder kfG G, mit ε ∈ L(G), gibt es eine äquivalente kfG G in C HOMSKY-Normalform. Beweis Der Beweis ist konstruktiv und zeigt, wie G aus G algorithmisch gewonnen wird. 1. Entfernen von Kettenregeln und unnützen Nichtterminalen, soweit vorhanden, nach den in Abschnitt 8.8 beschriebenen Verfahren 2. Ersetze jede Regel der Form X → αaβ , a ∈ T , α, β ∈ N ∗ , |αβ | ≥ 1, durch X → αXa β . Xa ist ein neues Nichtterminal. Ergänze die Regel Xa → a.
8.10 Das Pumping Lemma für kontextfreie Sprachen
157
3. Ersetze alle Regeln der Form X → Y1Y2 . . .Yn , n ≥ 3, durch X
→
Y1 Z1
Z1
→
Y2 Z2
Z2
→ .. .
Y3 Z3
Zn−2
→
Yn−1Yn
wobei Zi (1 ≤ i ≤ n − 2) neue (paarweise verschiedene) Nichtterminale sind. Beispiel 8.16 Wir betrachten die kfG G = ({S, A, B}, {a, b}, P, s), mit P = {S → aSa | aa | A, A → bA | B, B → b}. 1. Schritt: Kettenregeln eliminieren ˆ X2 =A, ˆ X3 =B. ˆ In {X1 → aX1 a | aa | X2 , X2 → bX2 | X3 , X3 → b} Wir substituieren X1 =S, streichen wir die Regel X2 → X3 und ergänzen X2 → b. Dies führt zu {X1 → aX1 a | aa | X2 , X2 → bX2 | b, X3 → b}. Aus dieser Menge entfernen wir nun X1 → X2 und ergänzen X1 → bX2 | b. Das Ergebnis ist: {X1 → aX1 a | aa | bX2 | b, X2 → bX2 | b, X3 → b}. Durch Beseitigung unnützer Nichtterminale erhält man sogar {X1 → aX1 a | aa | bX2 | b, X2 → bX2 | b}. 2. Schritt: Neue Nichtterminale für Terminale ergänzen X1 → Xa X1 Xa | Xa Xa | Xb X2 | b, X2 → Xb X2 | b, Xa → a, Xb → b} 3. Schritt: Reduzierung rechter Regelseiten Nur die Regel X1 → Xa X1 Xa hat eine rechte Seite der Länge 3. Nach Substitution erhält man X1 → Xa Z1 | Xa Xa | Xb X2 | b, Z1 → X1 Xa , X2 → Xb X2 | b, Xa → a, Xb → b} Das Ergebnis (nach Rücksubstitution der Nichtterminale) lautet: G = ({S, Xa , Xb , A, Z1 }, {a, b}, {S → Xa Z1 | Xa Xa | Xb A | b, Z1 → SXa , A → Xb A | b, Xa → a, Xb → b}, S). Computerübung 8.12 Führen Sie die in Beispiel 8.16 betrachtete Transformation mit AutoEdit durch. Benennen Sie die Nichtterminale so um, dass sie ggf. mit denen aus dem Beispiel übereinstimmen. Übung 8.4 Schlagen Sie das CYK-Verfahren nach und machen Sie sich mit dynamischem Programmieren vertraut. Wenden Sie die Überlegungen an, die wir im Zusammenhang mit der CNF entwickelt haben. Wenden Sie den Algorithmus auf ein selbst gewähltes Beispiel an und begründen Sie, dass die Zeitkomplexität des Verfahrens in O(n3 ) liegt, wobei n die Länge des Eingabewortes ist. Stellen Sie Ihre Ausarbeitungen in einem Vortrag dar.
8.10 Das Pumping Lemma für kontextfreie Sprachen In Abschnitt 6.6 haben wir bereits das Pumping Lemmas für reguläre Sprachen behandelt. Einen entsprechenden Satz gibt es auch für kfS. Der zugehörige Be-
158
8 Kellerautomaten und kontextfreie Sprachen
weis macht direkten Gebrauch von der im vorangehenden Abschnitt betrachteten C HOMSKY-Normalform kfS. CNF
Wir wissen, dass jede kfS durch eine kfG in C HOMSKY-Normalform definiert werden kann. Aufgrund der Beschränkung auf die einzigen beiden Regelformen X → a und X → Y Z, mit a ∈ T und X,Y, Z ∈ N, hat jeder CNF-Ableitungsbaum stets die in Abbildung 8.12 dargestellte Gestalt. Es handelt sich um einen Binärbaum, dessen Wurzel mit dem Spitzensymbol s der betrachteten CFN-Grammatik beschriftet ist. Man sagt, dass sich die Wurzel auf Baum-Niveau 0 befindet. Dieses und die folgenden Niveaus bis einschließlich Nummer m + 1 sind in Abbildung 8.12 angegeben. Die Blätter des Baumes bestehen aus Terminalsymbolen der Grammatik. Sie liegen auf Niveau m + 1. Da diese Terminal-Blätter durch Anwendungen der Regeln X → a, mit X ∈ N und a ∈ T erzeugt werden, stimmt die Anzahl der Blätter mit der Anzahl der Knoten des unmittelbar vorhergehenden Niveaus m überein. Die Anwendung der Regeln vom Typ X → Y Z, mit X,Y, Z ∈ N, führt auf dem jeweiligen Niveau hingegen zur Verdopplung der Knotenanzahl gegenüber dem vorhergehenen. CNF−Ableitungsbaum
...
a1
a2
...
Niveau
Knotenanzahl
0 1
1 2
m−1 m
m−1 2 m 2
m+1
2
m
=n
a n−1 a n
Abbildung 8.12: Struktur eines Ableitungbaumes für ein Wort einer kfG in CNF
Für ein beliebiges Wort z = a1 a2 . . . an ∈ L(G) der Länge n = 2m besitzt ein CNFAbleitungsbaum m + 2 Niveaus (von 0 bis m + 1) und 2m Terminalknoten, die von links nach rechts verkettet z ergeben. maximale Pfadlänge
Sämtliche inneren Knoten des Baumes sind mit Nichtterminal-Symbolen markiert. Die maximale Länge eines Pfades von der Wurzel s zu einem beliebigen TerminalBlatt ist m + 1: Jeder Schritt von Knoten zu Knoten vollzieht sich im Einklang mit der jeweiligen Niveaunummer des Baumes beginnend bei 0. Ein solcher Pfad enthält (einschließlich des Wurzelkonotens s) m + 2 Knoten (0, 1, 2, . . . , m + 1), wovon m + 1 auf Nichtterminale entfallen. Wählt man nun ein z ∈ L(G) mit |z| = 2m gerade so, dass m = |N|, d.h. m ist gerade die Anzahl der Nichtterminale der CNF-Grammatik G für L, so gibt es
8.10 Das Pumping Lemma für kontextfreie Sprachen
159
im Ableitungsbaum für z mindestens einen Pfad von der Wurzel s zu ai , der über mindestens m + 1 Nichtterminal-Knoten führt. Da es aber nur m Nichtterminale in G gibt, muss es mindestens ein Nichtterminal A geben, das auf diesem Pfad mehrfach (also mindestens doppelt) vorkommt. Den beschriebenen Sachverhalt illustriert Abbildung 8.13. s
A
A
u
v
w
x
y
z
Abbildung 8.13: Doppelung eines Nichtterminals A im CNF-Ableitungsbaum
Da A mit einer Regel der Form A → BC weiter abgeleitet wird, gilt vx = ε, was das Gleiche ist wie |vx| ≥ 1. Aus der weiter oben angestellten Betrachtung des CNFAbleitungsbaumes wird auch klar, dass das aus A abgeleitete Teilwort vwx nicht länger sein kann als z selbst, d.h. |vwx| ≤ 2m . Diese beiden Eigenschaften werden sich in Satz 8.4 wiederfinden. Analog zum Pumping Lemma für reguläre Sprachen wird die Pumping-Eigenschaft intuitiv augenscheinlich. Abbildung 8.14 unterstützt die Beobachtung. Während im linken Teil von Abbildung 8.14 das Teilwörter v und x aus z „herausgeschnitten“ wurden, sind sie im rechten einmal mehr „hineingepumpt“ worden. In beiden Fällen entsteht ein Wort, das zu L(G) gehört, denn der Teilbaum mit Wurzel A kann entfernt oder beliebig oft eingehangen werden, ohne am Erfolg der ∗ Ableitung s ⇒ z etwas zu ändern. Nun sind wir gut vorbereitet, um die Formulierung des Pumping Lemmas für kfS in Satz 8.4 verstehen zu können. Satz 8.4 Sei L eine kontextfreie Sprache. Dann existiert eine Konstante n, sodass sich jedes Wort z aus L mit |z| ≥ n in der Form z = uvwxy, mit u, v, w, x, y ∈ T ∗ , schreiben lässt und für alle i ≥ 0 gilt uvi wxi y ∈ L, mit |vx| ≥ 1 (vx = ε) und |vwx| ≤ n. Beweis Fall 1: L sei eine endliche kfS. Wähle n größer als die Länge des längsten Wortes in L. Dann
160
8 Kellerautomaten und kontextfreie Sprachen
s
s
A
A
w A
y
u
u
v
y
x A
v u w y = u v 0w x0y
w
x
u v 2w x2y
Abbildung 8.14: Struktur eines Ableitungbaumes für ein Wort einer kfG in CNF
gibt es kein einziges Wort z ∈ L mit |z| ≥ n, d.h. die Menge aller z, für die die Eigenschaften des Pumping Lemmas gelten sollen, betrifft eine leere Menge. Da man in einer leeren Menge kein einziges Element antrifft, das die Eigenschaften nicht besitzt, ist die Gültigkeit des Satzes für Fall 1 bewiesen. Fall 2: L = L(G) ist eine unendliche kfS. G = (N, T, P, s) ist eine kfG in CNF, wobei |N| = m. O.B.d.A. sei ε ∈ L. Dann gelten die obigen Überlegungen, die wir zur Herleitung des Satzes angestellt haben. Man spricht auch von einem sog. „Schubfachschluss“.
L = {an bn cn | n ≥ 0}
Beispiel 8.17 Die Anwendung des Pumping Lemmas für kfS wollen wir für eine typische Referenzsprache vorführen. Es handelt sich um die Sprache L = {an bn cn | n ≥ 0}, die eine (echt) kontextsensitive Sprache ist. Obwohl nach C HOMSKY-Hierarchie grundsätzlich möglich, handelt es sich nicht um eine kfS. Genau das soll mit dem Pumping Lemma gezeigt werden. Über die Beweistechnik hatten wir uns bereits im Zusammenhang mit dem Pumping Lemma für reguläre Sprachen ausführlich Gedanken gemacht, s. S. 97ff. Angenommen L ist eine kfS. Dann existiert ein n mit den im Pumping Lemma angegebenen Eigenschaften. Wähle k > n3 mit k ∈ N: Mit z = ak bk ck = uvwxy ∈ L, wobei |z| ≥ n, gilt lt. Pumping Lemma auch uvi wxi y ∈ L, für i ≥ 0. Man sieht, das v (und ebenso x) aus genau einem Buchstaben – also entweder aus a oder b oder c – bestehen muss, da ansonsten ein Hineinpumpen von z.B. ab zu einem unerlaubten Mix, wie beispielsweise ababab = (ab)3 als Teilwort, führen würde. Ein Wort, das eine solche Zeichenkette enthält, verstößt gegen die Form an bn cn und gehört deshalb nicht zu L. Aber auch wenn v aus genau einem Zeichen besteht, führt dessen wiederholte Verwendung dazu, dass die Anzahlen der verwendeten as, bs und cs nicht übereinstimmen. Im Widerspruch zur Aussage des Pumping Lemmas liegen also nicht alle diese Wörter uvi wxi y in L und die Negation der Annahme gilt: L ist nicht kontextfrei.
9 LL(k)-Sprachen 9.1 Deterministische Top-down-Syntaxanalyse Aus der Kapitel 8 wissen wir, dass •
-Typ-2-Grammatiken und NKAs gleichberechtigte Beschreibungsmittel für die Klasse der kfS sind, und
• die sog. deterministisch-kontextfreien Sprachen (dkfS), die durch DKAs beschrieben werden, eine Untermenge der kontextfreien bilden.
dkfS
Top-down-Verfahren zur Syntaxanalyse kontextfreier Sprachen beginnen mit dem Spitzensymbol und erzeugen das Analysewort vereinbarungsgemäß durch Linksableitungen. Wir wissen, dass dieser Prozess durchaus in Sackgassen führen kann. Der danach durch Backtracking verursachte Zeitaufwand ist für praktisches Parsing inakzeptabel. Von daher ist es sehr sinnvoll, nach Sprachklassen zu suchen, für die das Parsing Backtrack-frei stattfindet. DkfS (als Untermenge der kfS) bilden eine solche Klasse.
Top-downVerfahren
Deterministisches Top-down-Parsing bedeutet, dass die in jedem Ableitungsschritt anzuwendende Regel eindeutig bestimmbar ist. Jede Regelauswahl erfolgt also „irrtumsfrei“, d.h. ohne Sackgassen, ohne nachträgliche Korrektur – also ohne Backtracking. Die im jeweils nächsten Ableitungsschritt anzuwendende Produktion ist treffsicher vorhersagbar. Hieraus ergibt sich auch die Bezeichnung prädiktive Syntaxanalyse, engl.: to predict = vorhersagen.
Deterministisches Top-downParsing
Um dies zu erreichen ist es erforderlich, dass in jeder aktuellen Analysesituation auf ein gewisses Anfangsstück des noch zu analysierenden Restwortes vorausgeschaut wird. Man spricht daher auch von lookahead-Verfahren. Die Anzahl k solcher Vorausschauzeichen, genauer: Vorausschau-Token, kann sich dabei ganz unterschiedlich auf den Erfolg und die Effizienz der Syntaxanalyse auswirken. Prädiktive Parser für LL(k)-Sprachen, s. Abschnitt 9.2, sind sehr leistungsfähig und es gibt sehr transparente Verfahren zu deren Umsetzung. Bei aller Leistungsfähigkeit prädiktiver Parser darf nicht der Eindruck entstehen, LL(k)-Sprachen und dkfS seien identisch. Wir werden sehen, dass LL(k)Sprachen lediglich eine Teilmenge der dkfS repräsentieren, sodass unsere Suche nach Sprachklassen mit effizienten Parsing-Algorithmen im nächsten Kapitel weitergeht. In Abbildung 9.1 sind die erwähnten Mengen dargestellt. Das stilisierte L symbolisiert jeweils eine Sprachklasse, also eine Menge von Sprachen.
prädiktive Syntaxanalyse
Vorausschauzeichen
LLL(k) ⊂ Ldk f S
162
9 LL(k)-Sprachen
Lk f S Ldk f S LLL(k)
Abbildung 9.1: kfS und zwei relevante Teilmengen
9.2 Begriff Die im Namen LL(k) verwendeten Abkürzungen haben folgende Bedeutung: L
• Das erste (ganz links stehende) L steht für „Analyse des Eingabewortes von links nach rechts“. Wie bei DKAs definiert, arbeitet sich der Lesekopf konsequent von links nach rechts voran. Es gibt nicht etwa einen Rückgriff auf frühere Zeichen, etwa durch Zurückfahren (Linksbewegung) des Kopfes auf dem Eingabeband.
L
• Das zweite L steht für „Linksableitung des Analysewortes“. Das am weitesten links stehende Nichtterminal wird zuerst ersetzt. Das Analysewort wird vom Spitzensymbol her erzeugt.
k
• Die in Klammern stehende Zahl k gibt die Anzahl der Vorausschauzeichen (genauer: Vorausschautoken) auf das noch nicht analysierte Restwort (die Resttokenfolge) an, sodass eine irrtumsfreie (Sackgassen ausschließende) Entscheidung des jeweils nächsten Ableitungsschrittes gewährleistet ist. Grundsätzlich steht für die in jedem Schritt zu treffende RegelauswahlEntscheidung auch die Information über das bereits analysierte Teilwort zur Verfügung, auch wenn es vielleicht etwas unbequem ist, diese Information einzubeziehen. Erfreulicherweise lassen sich die meisten syntaktischen Konstruktionen in Programmiersprachen auch ohne diesen Beitrag deterministisch analysieren. Dies führt zu folgender begrifflicher Unterscheidung.
9.2 Begriff
163
Definition 9.1 LL(k)-Grammatiken, die nur mit der Vorausschau auf k Folgezeichen auskommen, nennt man stark-LL(k)-Sprachen.
Diesen Sprachen gilt unser besonderes Interesse. Ohne Beweis nehmen wir folgende Tatsachen auf: 1. Nicht jede LL(k)-Sprache ist auch stark-LL(k). Aber man kann zu jeder LL(k)-Grammatik eine äquivalente stark-LL(k)-Grammatik angeben. Das ist sehr erfreulich, versetzt es uns doch in die Lage, uns im Folgenden nur noch mit stark-LL(k)-Sprachen befassen, selbst dann, wenn dies nicht ausdrücklich betont wird und nur von LL(k)-Sprachen die Rede ist. 2. Für eine gegebene kfS ist allerdings nicht allgemein entscheidbar, ob sie durch eine LL(k)-Grammatik definiert werden kann. Die Herstellung eines effizienten Parsers bleibt also zu einem gewissen Teil „Handarbeit“. 3. Die Frage, ob eine gegebene kfS für ein festes k vom LL(k)-Typ ist, kann hingegen allgemein entschieden, d.h. für jede beliebige kfS entweder mit „ja“ oder mit „nein“ beantwortet werden.
Ldk f S LLL(3) LLL(2) LLL(1)
Abbildung 9.2: Hierarchie der LL(k)-Sprachen
...
164
9 LL(k)-Sprachen
Natürlich sind wir bestrebt, die Anzahl k (k ≥ 1) der Vorausschauzeichen möglichst klein zu halten. Dies hat jedoch Konsequenzen für die Leistungsfähigkeit des jeweiligen Parsing-Verfahrens: Wie in Abbildung 9.2 dargestellt, bilden die LL(k)-Sprachen mit aufsteigendem k eine Hierarchie, d.h. LLL(1) ⊂ LLL(2) ⊂ . . . ⊂ LLL(i) ⊂ LLL(i+1) ⊂ . . . ⊂ Ldk f S . LL(1)-Sprachen LL(1)-Parser
Erfreulicherweise ist sogar die (beliebte) Klasse der LL(1)-Sprachen mächtig genug, um in der Praxis bestehen zu können. Beispielsweise können PascalProgramm von einem LL(1)-Parser analysiert werden.
9.3 LL(1)-Forderungen Welche Bedingungen muss eine kfS erfüllen, um LL(1)-Sprache zu sein? Beispiel 9.1 Wir betrachten die Sprache D = L(G) = {xn y, xn z | n ≥ 0}, mit G = (N, T, P, s), wobei N = {A, B, S}, T = {x, y, z}, s = S und P = {S → A | B, A → xA | y, B → xB | z}. D ist keine LL(1)-Sprache. Warum? Versucht man beispielsweise das offensichtlich zu D gehörende Wort xxz aus S durch Verwendung genau eines Vorausschauzeichens abzuleiten, kann die Analyse in eine Sackgasse geraten. Schon für den ersten Ableitungsschritt gibt es zwei Möglichkeiten. Die Vorausschau auf x lässt sowohl S → A als auch S → B zu. Ein Syntaxdiagramm für S (Abbildung 9.3), in dem alle Nichtterminale außer S substituiert wurden, lässt die Unbestimmtheit der Regelauswahl unmittelbar erkennen. Man spricht von
x ? S
- y -
- z 6 x
Abbildung 9.3: Syntaxdiagramm für S aus Beispiel 9.1
nichtdetermin. Graphensystem
einem nichtdeterministischen Graphensystem. Für welchen Weg soll man sich entscheiden, wenn das erste Zeichen eines Wortes x ist? Für den oberen oder unteren Weg?
9.3 LL(1)-Forderungen
165
Aus Beispiel 9.1 lernen wir, dass die sog. Zielsymbolmengen alternativer Regeln keine gemeinsamen Elemente besitzen dürfen. Nur so kann man erreichen, dass durch Vorausschau genau eines Zeichens die Auswahl der entsprechenden Regel ohne Korrektur stattfinden kann. Genau dies besagt Definition 9.2.
LL(1)-Ford. 1
Definition 9.2 Eine Grammatik G erfüllt die LL(1)-Forderung 1, wenn für jedes Nichtterminal X von G, mit X → α1 | α2 | . . . | αn und αi ∈ (N ∪ T )∗ , gilt FIRST (αi ) ∩ FIRST (α j ) = 0/
für alle
i = j.
Anschaulich ist FIRST (α) die Menge aller Terminalzeichen, die bei allen möglichen Ableitungen von α an der jeweils ersten Stelle der entstehenden Satzformen auftreten können.
FIRST
Definition 9.3 Für eine Satzform α gilt ∗
FIRST (α) := {t | t ∈ T, α ⇒ tβ , β ∈ (N ∪ T )∗ }. ∗
Falls α ⇒ ε, gilt zusätzlich FIRST (α) ε. (Lies als „enthält“.)
Aus dieser Definition kann man noch kein Verfahren für die Konstruktion der FIRST -Mengen erkennen. In der Literatur gibt es verschiedene iterativ arbeitende Algorithmen für FIRST (α). Man erkennt sie an der Formulierung: „Beenden Sie dieses Verfahren, wenn sich an der/den FIRST -Menge/n nichts mehr verändert, d.h. wenn keine neuen Elemente hinzukommen“. Didaktischer Hinweis 9.1 Die im Folgenden angegebene rekursive Definition mag auf den ersten Blick etwas kompliziert anmuten, hat aber den großen Vorteil, dass sie mit einer höheren Programmiersprache unmittelbar umgesetzt werden kann. Da bei rekursiven Definitionen die Beschreibung des Resultats und nicht die des Berechnungsprozesses im Vordergrund steht, ist sie didaktisch wertvoll, da sie zur nochmaligen Auseinandersetzung mit dem eigentlichen Inhalt auffordert.
Verfahren zur Ermittlung von FIRST
166
9 LL(k)-Sprachen
Definition 9.4 Rekursive Definition der FIRST -Mengen ⎧ {}, ⎪ ⎪ ⎪ ⎪ ⎪ {a}, ⎪ ⎪ ⎪ ⎪ {ε}, ⎪ ⎪ ⎨ n FIRST (βi ), FIRST (α) = ⎪ i=1 ⎪ ⎪ ⎪ ⎪ FIRST (Y1 ), ⎪ ⎪ ⎪ ⎪ FIRST (Y1 ) ∪ FIRST (Y2 . . .Yn ), α ⎪ ⎪ ⎩ (FIRST (Y1 ) ∪ FIRST (Y2 . . .Yn )) \ {ε},
α ist leer α = aγ α =ε α = X und X → β1 | β2 | . . . | βn α = Y1Y2 . . .Yn , ε ∈ FIRST (Y1 ) = Y1Y2 . . .Yn , ∀i : ε ∈ FIRST (Yi ) α = Y1Y2 . . .Yn
wobei a ∈ T , X ∈ N, βi , γ ∈ (N ∪ T )∗ und Yi ∈ (N ∪ T )+ . Beispiel 9.2 Es ist zu prüfen, ob die kfG G = (N, T, P, s) mit N = {K, S, E}, T = { , , , }, P = {K → S | ε, S → S | E, E → | E} und s = K die LL(1)-Forderung erfüllt. Wir berechnen zunächst die FIRST -Mengen für die Satzformen, die die rechten Regelseiten des jeweils betrachteten Nichtterminals bilden und prüfen anschließend deren paarweise Disjunktheit. Für K: FIRST (S) = { , , }, FIRST (ε) = {ε}, FIRST (S) ∩ FIRST (ε) = 0. / / Für S: FIRST ( S ) = { }, FIRST (E) = { , }, FIRST ( S ) ∩ FIRST (E) = 0. / Für E: FIRST ( ) = { }, FIRST ( E) = { }, FIRST ( ) ∩ FIRST ( E) = 0. Die Untersuchung zeigt, dass G die LL(1)-Forderung 1 erfüllt. Computerübung 9.1 Verwenden Sie kfG Edit, um für die Grammatik aus Beispiel 9.2 zu prüfen, ob sie die LL(1)-Forderung 1 erfüllt. Definieren Sie zuerst die Grammatik in der bekannten Weise. Anschließend wählen Sie den Tab „LL(1) Forderungen“ und schließlich „Forderung 1 prüfen“. Gehen Sie dann den Inhalt des rechten Fensters genau durch. Ignorieren Sie zunächst sämtliche Hinweise auf eine Forderung 2. Übung 9.1 Untersuchen Sie, ob die Grammatik G, mit G = ({X}, { }, {X → X | }, X), die LL(1)Forderung 1 erfüllt. Was stellen Sie fest? Verallgemeinern Sie Ihre Beobachtung.
Grammatik mit linksrekursiven Regeln
Aus der Lösung von Übungsaufgabe 9.1 wissen wir, dass kfG mit wenigstens einer linksrekursiven Regel nicht vom LL(1)-Typ sind. Deshalb werden wir uns in Abschnitt 9.6 überlegen, wie man Linksrekursionen beseitigen kann.
LL(1)-Ford. 2
Wie das folgende Beispiel zeigt, reicht Forderung 9.2 nicht aus, um die LL(1)Eigenschaft einer beliebigen kfG sicherzustellen. Beispiel 9.3 G2 = ({A, S}, { }, {S → A , A → ε | A}, S)
9.3 LL(1)-Forderungen
167
Obwohl G2 in Beispiel 9.3 die FIRST -Mengenforderung erfüllt, ist sie offensichtlich keine LL(1)-Grammatik. Worin liegt die Ursache? Übung 9.2 . Was stellen Sie bezüglich irrtumsfreier Vorhersage der jeAnalysieren Sie das Wort weils folgenden anzuwendenden Produktion bei Vorausschau genau eines Zeichens fest? Unterstützen Sie Ihre Argumentation durch ein Syntaxdiagramm für S.
Die Anwendung der Produktionen S → A und A → A führen dazu, dass in einer aus S abgeleiteten Satzform vor und nach dem Nichtterminal A das Terminalsymbol stehen kann. Da in G2 für das Nichtterminal A die Regel A → ε existiert, ist nicht klar, ob das in einer Satzform durch S → A oder A → A erzeugt wurde. Dies führt zu folgender Erkenntnis: ∗
Nur bei ε-Ableitungen, d.h. X ⇒ ε, müssen die Initialsymbolmenge FIRST (X) und die Folgesymbolmenge FOLLOW (X) des betreffenden Nichtterminals X disjunkt sein. Definition 9.5 Eine Grammatik G erfüllt die LL(1)-Forderung 2, wenn für jedes Nichtterminal X, mit ∗
X ⇒ ε, gilt
FIRST (X) ∩ FOLLOW (X) = 0. /
Die Menge FOLLOW (X) enthält also alle Terminale, die einem Nichtterminal X in jeder aus s ableitbaren Satzform unmittelbar folgen können. „Unmittelbar“ bedeutet, dass ein solches Terminal nur dann in FOLLOW (X) aufgenommen wird, wenn es direkt rechts neben X steht. Definition 9.6 ∗ FOLLOW (X) := {t | t ∈ T, s ⇒ αXtβ , mit α, β ∈ (N ∪ T )∗ , X ∈ N}
Didaktischer Hinweis 9.2 Während die FIRST -Mengen-Bildung für Forderung 1 stets für Satzformena gebildet werden, geschieht die FOLLOW -Mengen-Bildung für (ausgewählte) Nichtterminale. Dies wird immer wieder gern verwechselt. a Eine
Satzform kann im Speziellen natürlich aus einem einzigen Nichtterminal bestehten.
Die Forderungen 1 und 2 stellen quasi die definierenden Eigenschaften für LL(1)Grammatiken dar. Eine LL(1)-Grammatik kann daher niemals linksrekursiv oder mehrdeutig sein. Definition 9.7 Wir nennen eine kfG genau dann eine LL(1)-Grammatik, wenn sie die beiden Forderungen gemäß den Definitionen 9.2 und 9.5 erfüllt.
FOLLOW
168
Berechnung der FOLLOW Menge
9 LL(k)-Sprachen
Zur Ermittlung von FOLLOW (X) für ein Nichtterminal X mit o.g. Eigenschaft geht man folgendermaßen vor: Für jede in P enthaltene Regel, in der X auf der rechten Seite vorkommt, d.h. A → αXβ , wobei α, β ∈ (N ∪ T )∗ und A = X, ... 1. ... wird FIRST (β ) – ohne ε – in FOLLOW (X) aufgenommen. Es ist offensichtlich, dass die Terminale, die auf X folgen, genau die sind, die bei Ableitung von β ganz vorn stehen. Diese Terminale bilden gerade die FIRST -Menge von β . ∗
2. ... und wenn β ⇒ ε, d.h. FIRST (β ) ε, wird (zusätzlich) jedes Zeichen aus FOLLOW (A) in FOLLOW (X) aufgenommen. Wenn es möglich ist, dass β „verschwindet“, endet A mit X und alle Terminale, die in einer Satzform dem Nichtterminal A folgen, folgen nach Substitution von A auch X. Definition 9.8 Für sämtliche Regelna der Form A → α1 Xβ1 |α2 Xβ2 | . . . |αn Xβn mit αi , βi ∈ (N ∪ T )∗ ist FOLLOW (X) wie folgt definiert: ⎧ n n ⎪ ⎪ FIRST (βi ), falls ε ∈ FIRST (βi ) ⎨ i=1 i=1 FOLLOW (X) = n ⎪ ⎪ ⎩ ( FIRST (βi ) \ {ε}) ∪ FOLLOW (A), sonst i=1
a Wir
gehen davon aus, dass die jeweils betrachtete Grammatik ausschließlich echte Regeln besitzt, also solche, deren Nichtterminal auf der linken Seite mindestens einmal in irgendwelcher rechten Regelseite vorkommen.
Beispiel 9.4 | C, C → Um festzustellen, ob G = ({S, X, C, B}, { , }, {S → X , X → B ε, B → }, S) eine LL(1)-Grammatik ist oder nicht, nehmen wir uns das Nichttermi∗ nal X vor: Da X ⇒ ε bilden wir die FIRST (X) = {d} und FOLLOW (X) = {d}. Wegen FIRST (X) ∩ FOLLOW (X) = {d} = 0/ ist die LL(1)-Forderung 2 verletzt. G ist also keine LL(1)-Grammatik. Computerübung 9.2 Verwenden Sie kfG Edit, um Ihr Ergebnis im Beispiel zu verifizieren.
Für kfG, die keine LL(1)-Grammatiken sind, kann man versuchen, durch Transformation die gewünschte Form herzustellen. Es ist also nicht aussichtslos, obgleich diese Transformation nicht für jede kfG möglich ist.
9.4 Top-down-Parser für LL(1)-Grammatiken
169
Übung 9.3 Gegeben sei die Grammatik G = (N, T, P, s) mit N
=
{E, X, T, Y, F}
T
=
{ , , , , }
P
=
{E → T X X→ TX | ε T →FY Y→ FY | ε F→ E
s
=
|
}
E.
Stellen Sie fest, ob G eine LL(1)-Grammatik ist oder nicht. Computerübung 9.3 Verwenden Sie kfG Edit, um Ihr Ergebnis der vorangehenden Übungsaufgabe zu überprüfen. Computerübung 9.4 Stellen Sie zuerst durch Rechnung und kontrollierend mit kfG Edit fest, ob G = (N, T, P, s) mit N
=
{E, T, F}
T
=
{ , , , , }
P
=
{E → E T →T F→ E
s
=
T | T F | F |
}
E
LL(1)-Grammatik ist oder nicht.
9.4 Top-down-Parser für LL(1)-Grammatiken Wir haben nun alles vorbereitet, um einen deterministisch arbeitenden Top-downParser für LL(1)-Sprachen entwickeln zu können. Ein solcher Parser arbeitet zwar auf der Grundlage eines entsprechenden NKAs für die betrachtete LL(1)-Sprache, doch dort wo es mehrere alternative Übergänge X → α1 | α2 | . . . | αn gibt, wird X → αi , mit FIRST (αi ) v und v ist das Vorausschauzeichen, ausgewählt. Ein NKA für eine LL(1)-Sprache kann also dazu gebracht werden, deterministisch zu arbeiten. Dies steigert die Effizienz des Parsings natürlich beträchtlich. Das Verfahren lässt sich folgendermaßen beschreiben: Das in jedem Arbeitstakt aktuelle oberste Kellerzeichen (top of stack, kurz: tos) entspricht entweder einem Terminal (Token) oder einem Nichtterminal der Grammatik.
Deterministische LL(1)-ParsingStrategie
170
9 LL(k)-Sprachen
tos = Terminal
Handelt es sich um ein Terminal, muss es mit dem aktuellen Zeichen des Eingabewortes w übereinstimmen. Anderenfalls endet die Analyse mit w ∈ L(G).
tos = Nichtterminal
Ist es ein Nichtterminal X, so müssen zunächst die Mengen FIRST (α1 ), FIRST (α2 ), . . . , FIRST (αi ), i ≤ n, berechnet werden, bis wir auf eine Menge FIRST (αi ) stoßen, die das aktuelle Vorausschauzeichen (Token) enthält. Durch die LL(1)-Forderung 1 ist ausgeschlossen, dass sich ein (auf die Untersuchung der Übergänge für ein bestimmtes Nichtterminal bezogenes) Token in zwei oder mehreren FIRST -Mengen befindet. Gehört das betrachtete Token zu keiner dieser FIRST -Mengen, endet die Analyse von w mit dem Ergebnis w ∈ L(G).
vorgezogene FIRST -MengenBerechnung
Wenn wir diese Vorschrift strikt befolgen, erhalten wir einen LL(1)-Parser, der jedoch sehr ineffizient arbeitet, denn er wird zahlreiche FIRST -Mengen mehrfach berechnen. Es ist also sinnvoll, einmal berechnete FIRST s zu speichern, damit die Resultate später wiederverwendet werden können. Diese Methode nennt man Memoizing. Sie sichert, dass während der Analyse nur die wirklich erforderlichen FIRST -Mengen genau einmal berechnet werden.
Memoizing
Diese mit Memoizing erzielte Effizienzverbesserung lässt sich überbieten, wenn wir alle FIRST -Mengen vor der Parsezeit (einmalig) berechnen. Damit wird die Analyse von diesen Berechnungen völlig befreit, da sämtliche Ergebnisse (in einer Tabelle) vorliegen und bei Bedarf direkt zugegriffen werden. Beispiel 9.5 Wir nehmen uns eine LL(1)-Grammatik G = ({S, R}, { , , , }, {S → | S R ; R → | S R }, S) vor, die zur Beschreibung einfacher Ausdrücke in der Sprache Lisp bzw. Scheme verwendet werden kann. Mit kfG Edit kann man rasch feststellen, dass beispielsweise ein Wort der Sprache L(G) ist. Zur Vorbereitung des Parsings berechnen wir für S: FIRST ( ) = { }, FIRST (
) = { } und
für R: FIRST ( ) = { }, FIRST (
) = { }.
Dies hat Auswirkungen auf die zu verwendenden Übergänge für δ (q1 , ε, ) und δ (q1 , ε, ) in dem äquivalenten NKAε M = ({q0 , q1 , q2 }, { , , , }, { , , , , , , }, δ , q0 , , {q2 }), δ (q0 , ε, )
=
{(q1 ,
)}
δ (q1 , ε, )
=
{(q2 , ε)}
δ (q1 , , )
=
{(q1 , ε)}
δ (q1 , , )
=
{(q1 , ε)}
δ (q1 , , )
=
{(q1 , ε)}
δ (q1 , , )
=
{(q1 , ε)}
δ (q1 , ε, )
=
{(q1 ,
δ (q1 , ε, )
=
(q1 , ), (q1 ,
), (q1 , )} )}
ergibt sich die in Tabelle 9.1 wiedergegebene KonfiFür das Eingabewort gurationenfolge. In Spalte 3 wird das jeweilige Vorausschauzeichen angegeben, das den im
9.5 Methode des Rekursiven Abstiegs
171
aktuellen Schritt verwendeten Übergang eindeutig bestimmt.
Zustand q0 q1 q1 q1 q1 q1 q1 .. . q1 q1 q2
Eingabe
.. . ε ε
Vorausschau
.. .
Keller
.. .
Übergang δ (q0 , ε, ) = (q1 , ) δ (q1 , ε, ) = (q1 , ) δ (q1 , , ) = (q1 , ε) δ (q1 , ε, ) = (q1 , ) δ (q1 , , ) = (q1 , ε) δ (q1 , ε, ) = (q1 , ) δ (q1 , , ) = (q1 , ε) .. .
ε
δ (q1 , , ) = (q1 , ε) δ (q1 , ε, $) = (q2 , ε) Akzeptiert!
Tabelle 9.1: NKA-basiertes LL(1)-Parsing
In den Zeilen 2 und 4 sieht man, dass das Vorausschauzeichen den Übergang δ (q1 , ε, ) = ) verlangt. (q1 , Computerübung 9.5 Definieren Sie G aus Beispiel 9.5 in kfG Edit und betrachten Sie den Ableitungsbaum für . Exportieren Sie den äquivalenten NKA in AutoEdit und betrachten Sie . Greifen Sie die Folge heraus, die zur Akdie Konfigurationenfolge für zeptanz des Wortes führt. Diese sollte exakt mit der in Tabelle 9.1 angedeuteten Konfigumit Vorausschau genau eines Zeichens übereinstimmen. rationenfolge für Vervollständigen Sie diese. Hinweis: Bei der Bestimmung der Anzahl (29) der Arbeitsschritte bis zur Akzeptanz des Wortes beachten Sie bitte, dass die Zählung in AutoEdit bei 0 beginnt.
9.5 Methode des Rekursiven Abstiegs Anstelle die Syntaxanalyse von LL(1)-Sprachen durch einen NKA mit Lookaheadbedingter deterministischer Arbeitsweise zu bewerkstelligen, gibt es eine einfache Methode, das Parsing-Verfahren direkt aus der Grammatik abzuleiten. Dies führt zu der Methode des rekursiven Abstiegs (recursive descent). Die Grundidee besteht darin, zu jedem Nichtterminal eine parameterfreie (nullstellige) Prozedur (Methode) anzugeben, deren Definition den Regeln für dieses Nichtterminal entspricht: Für jedes Nichtterminal auf der rechten Regelseite entsteht dann der zugehörige Prozeduraufruf. Im Verlauf der Analyse können sich auf diese Weise (direkt und indirekt) rekursive Aufrufe ergeben. Dies setzt voraus, dass zur Programmierung dieses Verfahrens eine Sprache eingesetzt wird, die
recursive descent
Nichtterminal
172
9 LL(k)-Sprachen
Rekursion unterstützt. AtoCC bietet hierfür die Sprachen Delphi, Java, C# und Scheme an. Terminal
Für ein Terminal gibt es keinen Regelaufruf. In diesem Falle muss das betreffende Terminalsymbol mit dem ersten Zeichen das Eingabewortes übereinstimmen. Trifft dies zu, so wird es an aktueller Position im Eingabepuffer entfernt, anderenfalls muss die Analyse mit einer Fehlermeldung beendet werden. Der Analyseprozess beginnt mit dem Aufruf der zum Spitzensymbol gehörenden Prozedur. In einem solchen Fehlerfall ist zu beachten, dass es sich um einen Sofortabbruch handelt, der nicht etwa die bisher stattgefundene rekursive Abarbeitung rückkehrend bedient. Fortgeschrittene Fehlerbehandlung (error recovery) ist einem Spezialkurs über Compilerbau vorbehalten.
Produktionen
Parsergenerator
Beispielsprache PL/0
Mehrere Produktionen für ein und dasselbe Nichtterminal erscheinen als alternative Fälle in den zugehörigen Prozeduren bzw. Methoden. Das jeweils aktuelle Vorausschauzeichen bestimmt, welcher dieser Fälle ausgewählt wird. Aufgrund der LL(1)-Eigenschaften ist gesichert, dass in jedem Schritt genau eine der angegebenen Alternativen zutrifft. Analog zum „NKA mit Vorausschau“ ist zu beachten, dass es sich bei der Vorausschau nicht um verbrauchendes Lesen handelt, so dass es dadurch zu keiner Veränderung des Wortes im „Eingabepuffer“ kommt. Die Methode des rekursiven Abstiegs ist für LL(1)-Sprache ein etabliertes Verfahren. Da die Regeln in der oben beschriebenen sehr transparenten Art und Weise in Prozeduren, die die Syntaxanalyse konkretisieren, überführt werden, lohnt sich ein Parsergenerator. Dieser nimmt die Grammatik der betrachteten LL(1)Sprache und generiert den zugehörigen Rekursiven-Abstiegs-Parser. Für die praktische Realisierung verwenden wir VCC. Von N IKLAUS W IRTH übernehmen wir zunächst eine formale Grammatik für die Sprache PL/0. Die großbuchstabigen Wörter sind die Nichtterminale. Die Terminale dieser Grammatik sind die Symbole . Weiterhin gehören alle Ziffernsymbole ( . . . ) und die Kleinbuchstaben ( . . . ) zur Menge der Terminale. Die übrigen Zeichen, die zur Angabe der Produktionen in P dienen, sind gängige Metazeichen der EBNF: Die mit einer geschweiften Klammer gruppierten Satzglieder dürfen beliebig oft wiederholt oder auch weggelassen werden. Eckige Klammern markieren Satzform-Fragmente, die genau einmal an dieser Stelle auftreten oder vollständig fehlen.
EBNF
Die Regelmenge P ist wie folgt definiert.
9.5 Methode des Rekursiven Abstiegs
173
Das folgende PL/0-Programmbeispiel wurde ebenfalls von W IRTH übernommen. Wir gehen davon aus, dass es der Inhalt der Datei ist.
PL/0Programmbeispiel
174
9 LL(k)-Sprachen
Leerzeichen zwischen je zwei Terminalen können zur Erhöhung der Lesbarkeit eingefügt werden. Sie sind ohne syntaktische Relevanz. EBNFBNF
Da VCC eine Grammatik grundsätzlich in BNF erwartet, eliminieren wir die o.g. EBNF-Metazeichen durch entsprechende Transformationen. Wir illustrieren dies an der Regel . Die BNF benötigt zusätzliche Nichtterminale. Dies sind hier , und . Mit Blick auf VCC schreiben wir bereits für ε.
9.5 Methode des Rekursiven Abstiegs
175
In der gesamten Grammatik können wir die vier ursprünglich vorhandenen Nichtterminale, nämlich und sowie und , zusammen mit den Regeln in denen sie vorkommen, entfernen. Da sie vom Scanner erkannt werden, versteht sie der Parser als Terminale. Es entsteht die folgende Regelmenge der nun reduzierten Grammatik in BNF.
Computerübung 9.6 Definieren Sie diese reduzierte Grammatik (in BNF) in kfG Edit und überprüfen Sie die LL(1)-Forderungen.
Greduziert in BNF
176
automatisierte Generierung eines LL(1)-Parsers mit VCC
9 LL(k)-Sprachen
Wir verwenden die in kfG Edit definierte Grammatik aus der vorangehenden Computerübung und rufen mit „Exportieren Compiler“ das Modul VCC mit der Beschreibung des zu generierenden Parsers (sogar Compilers1 ) auf. Dabei werden 32 Tokenklassen definiert, wobei die Tokenklassen und eine Definition durch reguläre Ausdrücke (in Praxisnotation) erfordern: bzw. . Diese beiden Tokenklassen verschieben wir nun ganz ans Ende der Tokenklassenliste. Diese Verschiebung ist notwendig, um der Erkennung von Schlüsselwörtern, wie (Token ), Vorrang gegenüber Bezeichnern (Token ) zu geben. Um die Tokenklassennamen eine bessere Lesbarkeit zu verleihen, sind entsprechende Umbenennungen empfehlenswert. Computerübung 9.7 . Beobachten Sie Wählen Sie die „Scanner-Simulation“ und laden Sie die Datei den oben angedeuteten Umgang des Scanners mit Lexemen (Teile des Quelltextes), die zu mehreren Tokenklassen passen oder sich überlappen, und mit Wildcards, z.B. \ \ . Durch nochmalige Auswahl von „Scanner-Animation“ beenden Sie das zeichenweise Abtasten des Quelltextes und können sich mit „Scanner ausführen“ das in Abbildung 9.4 dargestellte Gesamtergebnis der Arbeit des Scanners kennenlernen.
Abbildung 9.4: Ergebnis des Scannens des Quelltextes in
1 In
der Tat wird sogar ein Compiler erzeugt, den wir hier jedoch noch nicht benötigen und erst weiter unten thematisieren. Der Compiler umfasst einen Parser und genau den brauchen wir hier.
9.5 Methode des Rekursiven Abstiegs
177
Werfen Sie einen Blick auf die Parser-Definition: Hier werden die Regeln grafisch dargestellt. Für die Parser-Generierung ist an dieser Stelle nichts zu tun. Wenn es später um die Compilerentwicklung geht, werden wir hier ansetzen. Vor der Compilergenerierung sind der Parsertyp (LL(1)-Parser) und die Sprache (Java), in der das Programm anschließend vorliegen soll, auszuwählen. Die Meldung, wonach 49 bislang noch nicht definierte S-Attribute automatisch erstellt wurden, ignorieren wir. SAttribute werden für Compiler, die neben der Analysephase eine Zielcodesynthese vorneh, oder men, benötigt. Ein Parser liefert ja lediglich eine Fehlermeldung, wie er produziert keine Ausgabe, wenn der analysierte Satz syntaktisch korrekt ist. Der fertige Parser ist ein Java-Programm und liegt in Datei wir nur noch den Parser mit dem Text in der Datei den Verzeichnis lautet der Aufruf
vor. Nun brauchen aufzurufen. Im entsprechen-
. -Parameter z.B. im Fehlerfall verwendet werden, falls man sich Zusätzlich kann der für die bereits analysierte Tokenfolge im Sinne eines Parse-Protokolls interessiert. Computerübung 9.8 Betrachten Sie die folgende Grammatik: G = ({E, T, F}, { , , , , , , }, P, E) mit P = {E → T {[ | ]T }∗ , T → F{[∗ | ]F}∗ , F → E | }. Stellen Sie fest, ob G eine LL(1)-Sprache definiert. Zu diesem Zweck transformieren Sie die in EBNF angegebene Grammatik in BNF nach folgenden Regeln: • Transformiere A → E ∗ zu X → E und A → XA | ε. • Transformiere A → E + zu X → E und A → XA | X. • Transformiere A → E? zu A → E | ε. • Transformiere A → [E1 | E2 ] zu A → E1 | E2 . Verwenden Sie den von VCC generierten LL(1)-Parser, um festzustellen, ob das Wort zu L(G) gehört.
Der von VCC erzeugte Parser hält in jeder „Nichtterminal-Prozedur“ eine Fallauswahl für alle möglichen Vorausschauzeichen/token bereit, die die jeweils anzuwendende Regel vorhalten. In folgendem Code-Schnipsel des im Beispiel erzeugten Java-Programm finden wir neben der parameterlosen Methode für das Nichtterminal PROGRAM die „Reaktionen“ auf alle an dieser Stelle erlaubten Token. Die Produktion finden wir hier in der Form
wieder. Wenn für das aktuelle Nichtterminal (top of stack) das aktuelle Vorausschauzeichen mit einem der elf angegebenen Token übereinstimmt, wird diese Regel angewandt.
Parsergenerierung mit VCC
178
tabellengesteuerte Analyse
9 LL(k)-Sprachen
Man kann die Progammstruktur in diesem Punkt entspannen, wenn man die Regel aus einer vorbereiteten Tabelle entnimmt. Die Idee, die aus der FIRST Mengenbetrachtung gewonnenen Regelanwendungen vor Beginn des Parseprozesses in einer Tabelle zu speichern, hatten wir schon weiter oben entwickelt. Man spricht dann von tabellengesteuerter Analyse. Sämtliche Nichtterminale bzw. alle Terminale der betrachteten Grammatik bilden die Zeilen bzw. Spalten einer Tabelle, in deren Körper die jeweils anzuwendenden Regeln eingetragen werden. Zu den Terminalen wird ein Abschlusszeichen für den Eingabepuffer – entspricht dem Kellervorbelegungszeichen – hinzugefügt. Bei LL(1)-Sprachen darf in jedem Tabellenfeld höchstens eine Regel stehen. Alle leeren Tabellenfelder bedeuten „Fehler“. Mit dem Paar (aktuelles Nichtterminal = oberstes Kellerzeichen X, Vorausschauzeichen a) konsultiert das Programm die Tabelle, wobei in jedem Schritt genau einer der folgenden Fälle eintreten kann. 1. Wenn X = a = $, dann ist die Syntaxanalyse erfolreich beendet. 2. Wenn X = a = $, dann entferne X vom Stapel und setze Vorausschauzeiger auf das nächste Zeichen des Eingabewortes. 3. Wenn X (im Gegensatz zu den ersten beiden Fällen) ein Nichtterminal ist, dann betrachte Tabelle[X, a]. Ist dieses Feld leer, wird die Analyse mit Fehlermeldung abgebrochen. Steht jedoch die Regel X → UVW in Tabelle[X, a], dann substituiere X auf dem Stapel durch UVW , wobei U das neue oberste Kellerzeichen ist. Bei X → ε wird lediglich entkellert. Die Position des Vorausschauzeigers wird nicht verändert. Beispiel 9.6 G = ({E, E , T, T , F}, { , , , , }, P, E), mit P = {E → T E , E → T E , E → ε, T → F T , T → F T , T → ε, F → , F → E }
9.5 Methode des Rekursiven Abstiegs
179 input
N E E’ T T’ F
E → T E’
E → T E’ E’ → T E’
T → F T’
E’ → ε
E’ → ε
T’ → ε
T’ → ε
T→ F T’ T’ → ε
T’ → F T’
F→
F→ E
Didaktischer Hinweis 9.3 In der Tabelle in Beispiel 9.6 erkennt man eine gewisse Redundanz: Die Einträge in den Tabellenfeldern bestehen aus vollständigen Regeln. Dabei könnten sämtliche linken Regelseiten weggelassen werden, denn sie stimmen mit dem ganz links in der jeweiligen Zeile angegebenen Nichtterminal überein. In der Praxis wird von dieser verkürzten Schreibweise natürlich Gebrauch gemacht, auch bei entsprechenden Implementierungen.
Das tabellengesteuerte Parsing ist natürlich nicht an die Methode des rekursiven Abstiegs gebunden, sondern prinzipiell bei der Implementierung prädiktiver Parser zu empfehlen. Der Analyseprozess wird durch den jeweils aktuellen Stapelinhalt, das entsprechende Restwort im Eingabepuffer und die anzuwendende Produktion protokolliert. Für obige Beispielgrammatik und das zu analysierende Wort lautet die erste Zeile wie folgt: Keller E$
Eingabewort
Regel E→ T E’
Übung 9.4 Setzen Sie die Tabelle fort, indem Sie in jedem Schritt das oberste Kellerzeichen, das erste Zeichen der aktuellen Eingabe und die in Beispiel 9.6 angegebene Parsetabelle benutzen.
Wir haben noch nicht formalisiert, wie LL(1)-Parsetabellen entstehen. Das Konstruktionsprinzip ist unter Rückgriff auf FIRST und FOLLOW sofort verständlich: Für jede Regel A → α in P gilt bzw. gelten: ∗
• Wenn α ⇒ aβ , d.h. a ∈ FIRST (α), schreibe A → α in die Felder Tabelle[A, a], für alle diese Terminale a. ∗
• Wenn α ⇒ ε, d.h. ε ∈ FIRST (α), dann betrachte FOLLOW (A), wobei $ ∈ FOLLOW (A)2 gelten kann. Schreibe A → α in die Felder Tabelle[A, b], mit b ∈ FOLLOW (A). • Sämtliche noch freien Felder der Zeile für A bleiben leer. Sollten sich nach dieser Vorschrift mehrere Regeleinträge in (wenigstens) ein Tabellenfeld erforderlich machen, so ist die betrachtete Grammatik nicht vom LL(1)Typ. 2 Dies
bedeutet, dass das Eingabeabschlusszeichen wie ein Terminal behandelt wird.
Konstruktion von LL(1)Parsetabellen
180
9 LL(k)-Sprachen
Übung 9.5 Konstruieren Sie die LL(1)-Parsetabelle aus dem vorherigen Beispiel nach dieser Vorschrift. Übung 9.6 Für die in Beispiel 9.5 verwendete Grammatik soll eine tabellengesteuerte Syntaxanalyse durchgeführt werden. Bereiten Sie die LL(1)-Parsetabelle vor und für das Wort protokollieren Sie den Analyseprozess.
9.6 Grammatiktransformationen Wir haben gesehen, dass Rekursive-Abstiegs-Parser ein leicht zu programmierendes effizientes Syntaxanalyseverfahren darstellen. Ihre Anwendung setzt LL(1)Grammatiken voraus. Ist eine betrachtete kfG nicht LL(1)-Grammatik, weil sie gegen eine oder beide Forderungen verstößt, ist noch nicht alles verloren. Man kann dann versuchen, eine äquivalente LL(1)-Grammatik zu finden. Dafür gibt es verschiedene Transformationen, wie beispielsweise die Herstellung der ε-Freiheit, die wir bereits in Satz 4.3 und dessen Beweis kennengelernt haben. Im Folgenden behandeln wir drei typische Transformationen, deren Anwendungen im Allgemeinen gute Erfolgsaussichten haben. • die Beseitigung linksrekursiver Produktionen • die Beseitigung von Mehrdeutigkeit • die Beseitigung gemeinsamer Präfixe durch Linksfaktorisierung zyklenfrei, ohne ε-Regeln
LL(1)-Sprachen können niemals linksrekursive Regeln enthalten. Besitzt eine zyklenfreie3 kfG G ohne ε-Regeln4 wenigstens eine linksrekursive Produktion, muss sie eliminiert werden. Die Frage, ob die gewünschte Transformation immer möglich ist, beantwortet der folgende Satz 9.1 positiv. Auch die Herstellung einer εund zyklenfreien kfG ist immer möglich. Der zugehörige Beweis bietet ein Transformationsverfahren. Satz 9.1 Zu jeder kfG G gibt es eine äquivalente kfG G ohne Linksrekursivität.
direkte Linksrekursivität
Beweis Am einfachsten können direkt-linksrekursive Regeln der Form
+
bedeutet, dass es für kein einziges Nichtterminal X von G eine Ableitung der Form X ⇒ X gibt. 4 Eine kfG nennt man ε-frei, wenn sie keine Regel der Form X → ε besitzt, bis auf den Fall, dass X das Startsymbol der Grammatik ist. Falls s → ε in P existiert, darf s auf keiner rechten Seite einer beliebigen Regel in P vorkommen. 3 Zyklenfrei
9.6 Grammatiktransformationen
181 X → Xα | β
eliminiert werden. Dazu ersetzt man die Produktionen mit dem Nichtterminal X auf der linken Seite X → Xα1 | Xα1 | Xα2 | . . . | Xαm | β1 | β2 | . . . | βn , wobei kein βi mit X beginnt, durch X → β1 X | β2 X | . . . | βn X und X → α1 X | α2 X | . . . | αm X | ε. (Wird fortgesetzt!)
Beispiel 9.7 G = ({A, S}, {a, b, c, d}, {S → Aa | b, A → Ac | Sd | ε}, S) Zuerst muss ε-Freiheit hergestellt werden. Dies führt zu G = ({A, S}, {a, b, c, d}, {S → Aa | b | a, A → Ac | Sd | c}, S). Danach beseitigen wir die Linksrekursion in der Regel A → Ac, was schließlich G = ({A, A , S}, {a, b, c, d}, {S → Aa | b | a, A → SdA | cA , A → cA | ε}, S) ergibt. Nun kann wieder ε-Freiheit hergestellt werden. G = ({A, A , S}, {a, b, c, d}, {S → Aa | b | a, A → SdA | Sd | cA | c, A → cA | c}, S). Die Transformation der urspünglichen Grammatik ist damit allerdings noch nicht getan, denn es gibt indirekte Linksrekursivität. Diese lässt sich im Allgemeinen nicht durch „scharfes Hinsehen“ aufspüren. In unserem Beispiel ist dies jedoch möglich: S ⇒ Aa ⇒ Sda. Beweis (Fortsetzung des Beweises von Satz 9.1) Der folgende Algorithmus transformiert jede zyklusfreie kfG ohne ε-Produktionen in eine äquivalente linksrekursionsfreie kfG. Eine Vorbehandlung durch Beseitigung direkter Linksrekursivität ist nicht erforderlich. 1: Bezeichne die Nichtterminale der Grammatik mit X1 , X2 , . . . , Xn . 2: for i = 1, . . . , n do 3: for j = 1, . . . , i − 1 do 4: // Zyklus mindestens einmal (für j = 1) durchlaufen, auch wenn i = 1. 5: Ersetze alle Regeln der Form Xi → X j γ durch Xi → δ1 γ | δ2 γ | . . . | δk γ, 6: wobei X j → δ1 | δ2 | . . . | δk alle aktuellen Regeln in P sind. 7: Beseitige direkte Linksrekursivität. 8: end for 9: end for Beispiel 9.8 Wir greifen die kfS aus dem vorangehenden Beispiel wieder auf und beginnen mit der zyklen- und ε-freien kfG G = ({A, S}, {a, b, c, d}, {S → Aa | b | a, A → Ac | Sd | c}, S). Nun erfolgt die Umbenennung der Nichtterminale: S wird X1 und A wird X2 . Dadurch entstehen die Regeln: X1 → X2 a | b | a, X2 → X2 c | X1 d | c. Wir wenden nun den Algorithmus aus obigem Beweis an. Für i = 1 suchen wir vergebens nach einer Regel der Form X1 → X1 γ. Also nehmen wir uns den einzigen verbleibenden
indirekte Linksrekursivität
182
9 LL(k)-Sprachen
Fall, nämlich mit i = 2 und j = 1, vor: Wir müssen X2 → X1 d durch X2 → X2 ad | bd | ad ersetzen. Dies ergibt insgesamt X1 → X2 a | a | b | a und X2 → X2 c | X2 ad | bd | ad | c. Wir beseitigen die beiden direkt-linksrekursiven Regeln für X2 nach der im Beweisanfang angegeben Transformation und erhalten insgesamt X1 → X2 a | b | a X2 → bdX3 | adX3 | cX3 X3 → cX3 | adX3 | ε Nach optionaler Herstellung der ε-Freiheit erhält man schließlich die gewünschte Grammatik G = ({X1 , X2 , X3 }, {a, b, c, d}, P, X1 ), mit folgender Regelmenge P: X1 → X2 a | b | a X2 → bdX3 | adX3 | bd | ad | cX3 | c X3 → cX3 | adX3 | c | ad Computerübung 9.9 Gegeben ist G = (N, T, P, s), N = {E, T, F}, T = {a, +, ∗, (, )}, s = E, mit P = {E → E + T | T, T → T ∗ F | F, F → (E) | a}. Analysieren Sie das Wort a + a ∗ a mittels kfG Edit. Bewerten Sie die entsprechenden Ableitungsbäume. Prüfen Sie, ob G eine LL(1)Grammatik ist, und begründen Sie Ihre Entscheidung. Geben Sie eine zu G äquivalente Grammatik G ohne Linksrekursionen an. Weisen Sie die LL(1)-Eigenschaft von G nach.
dangling-elseProblem
Wie das bereits in Computerübung 4.10 auf Seite 36 angesprochene dangling-elseProblem zeigt, ist die Beseitigung der Linksrekursivität noch keine Garantie dafür, dass die betrachtete kfG LL(1)-Grammatik ist. Das Problem des dangling else, „loses“ else, ist jedem Programmierer, der mit imperativen Sprachen umgeht, bestens bekannt: Bezogen auf die folgende Methode in Java lässt es sich auf folgende Frage zurückführen: Zu welchem Zweig gehört das ?
Für
, wie im Programmtext angegeben, lautet die Ausgabe , für erhalten wir liefert die Methode keine Ausgabe.
und für
Damit ist klar, dass das Schlüsselwort in verschachtelten Alternativen zum unmittelbar vorhergehenden gehört. Soweit unser Experiment. Eine Bestätigung erhalten wir, wenn wir die Formatierungshilfe einer geeigneten IDE in Anspruch nehmen:
9.6 Grammatiktransformationen
183
Soll der -Teil in dieser Methode zum äußeren dies durch veränderte Klammerung erzwingen:
Für
-
gehören, muss man
erhalten wir nun die (fehlerhafte) Ausschrift: .
Für uns stellt sich nun die Frage, ob man die Zugehörigkeit von syntaktisch beschreiben kann. Der erste Versuch scheitert, denn die im Folgenden angegebene Grammatik G ist mehrdeutig. G = ({S}, { , , , , }, {S → Man denke bei und bei an
an .
, bei
SS | , S → S | ε}, S).
an Bedingung, bei
an
, bei
an Anweisung
Wie die beiden möglichen Ableitungsbäume für das Eingabewort in Abbildung 9.5 zeigen, lässt G genau die oben diskutierte Zugehörigkeitsentscheidung für offen. Eine mehrdeutige Grammatik kann nicht LL(1)-Grammatik sein. Wir müssen daher versuchen, Mehrdeutigkeit durch Transformation zu beseitigen. Dies führt zu weiteren zu G äquivalenten Grammatiken für die -Konstruktion. Der erste Entwurf ist G1 , mit S→
S |
S S | .
Mehrdeutigkeit
184
9 LL(k)-Sprachen
Abbildung 9.5: Mehrdeutige Grammatik für verschachtelte if-then-else-Anweisungen
Dies ist leider nicht zielführend, denn G1 ist ebenfalls mehrdeutig. Computerübung 9.10 ∗ Zeigen Sie die Mehrdeutigkeit von G1 für S ⇒ Ableitungsbäume mit kfG Edit.
Beseitigung von Mehrdeutigkeit
In der Tat kann die Mehrdeutigkeit von G1 durch eine einfache Umformung unter Verwendung eines weiteren Nichtterminals beseitigt werden. Es entsteht G2 , mit S S
→ →
S | S S S |
Computerübung 9.11 Verwenden Sie wieder kfG Edit, um das Wort Ableitungsbaum.
inhärent mehrdeutigen Grammatiken
. Erzeugen Sie entsprechende
abzuleiten. Es entsteht genau ein
Gelingt eine solche Transformation immer? Die Antwort ist leider: Nein. Bei sog. inhärent mehrdeutigen Grammatiken haben wir keine Chance, eine äquivalente LL(1)-Grammatik zu erhalten und es gibt auch keine5 . Trotz des Erfolgs, die Mehrdeutigkeit in unserem Beispiel zu beheben, ist noch keine LL(1)-Grammatik entstanden. Computerübung 9.12 Verwenden Sie kfG Edit, um zu zeigen, dass G2 die LL(1)-Forderung 1 verletzt.
Wie man den Transformationsprozess weitertreiben muss, um schließlich doch eine äquivalente LL(1)-Grammatik herstellen zu können, besprechen wir anhand der Grammatik G3 , die man durch eine kleine Veränderung aus G2 gewinnt. S M S
→ M | → S | → M |
S S
genauerer Betrachtung stellt sich heraus, dass für S → SS | a, S → S | ε der Doppeleintrag → ε, S → S im Feld [S , ] der Parsetabelle durch Streichen von S → ε problemlos behoben werden kann. Algorithmisch ist jedoch nicht allgemein entscheidbar, für welche Sprache eine solche „Reparaturmaßnahme“ von Mehrfacheinträgen möglich ist.
5 Bei
S
9.6 Grammatiktransformationen
185
In den beiden Produktionen für M ist der Verstoß gegen die erste LL(1)-Forderung offensichtlich. Dagegen hilft Linksfaktorisierung. Hierbei wird der längste gemeinsame Anfangsteil von Satzformen der rechten Regelseiten für das betrachtete Nichtterminal wie ein gemeinsamer Faktor „ausgeklammert“. Die Restsatzformen werden zur Bildung neuer Regeln unter Verwendung eines neuen Nichtterminals benutzt: Aus X → αβ1 | αβ2 werden die Regeln
X → αX und X → β1 | β2
gebildet. Dabei ist für βi ∈ (N ∪ T )∗ durchaus βi = ε zulässig. Beispiel 9.9 Die Regeln F → P
F | P werden zu F → PF und F →
F | ε transformiert.
Übung 9.7 Die Grammatik G4 = ({S, M, X, S }, { , , , , }, P, S) besitzt folgende Regelmenge P. S M X S
→ → → →
M | S X S | ε M |
Leiten Sie das Wort aus S für G4 durch eine Top-down-Analyse mit einem Vorausschauzeichen ab und protokollieren Sie die Konfigurationenfolge in einer Tabelle. Sie werden feststellen, dass G4 mehrdeutig ist. Außerdem verstößt G4 gegen LL(1)Forderung 2.
Linksfaktorisierung
10 LR(k)-Sprachen 10.1 Begriff Die im Namen LR(k) verwendeten Abkürzungen haben folgende Bedeutungen: • Das erste (ganz links stehende) L steht für „Analyse des Eingabewortes von links nach rechts“. Wie bei DKAs definiert, arbeitet sich der Lesekopf konsequent von links nach rechts voran. Es gibt nicht etwa einen Rückgriff auf frühere Zeichen, etwa durch Zurückfahren (Linksbewegung) des Kopfes auf dem Eingabeband.
L
• Das an zweiter Position stehende R steht für „Rechtsableitung des Analysewortes“. Wäre es ein Top-Down-Verfahren1 , das beim Spitzensymbol beginnt, dann würde das am weitesten rechts stehende Nichtterminal zuerst ersetzt. Das Analysewort würde von rechts her entstehen. Da es sich aber um eine Bottom-up-Analyse handelt, muss man das Ganze „auf den Kopf stellen“, wie in Abschnitt 10.2 beschrieben.
R
• Die in Klammern stehende Zahl k gibt die Anzahl der Vorausschauzeichen (genauer: Vorausschautoken) auf das noch nicht analysierte Restwort (die Resttokenfolge) an, sodass eine irrtumsfreie (Sackgassen ausschließende) Entscheidung des jeweils nächsten Analyseschrittes gewährleistet ist. k = 1 ist der Vorgabewert, d.h. LR steht für LR(1).
k
10.2 Deterministische Bottom-up-Syntaxanalyse LR(k)-Sprachen stellen die umfassendste Klasse deterministisch analysierbarer kontextfreier Sprachen dar. Aus der Theorie formaler Sprachen ist bekannt, dass genau diese Klasse durch deterministische Kellerautomaten beschrieben wird. Da es sich um ein Bottom-up-Verfahren handelt, wird die jeweils betrachtete Satzform, die einer rechten Regelseite entspricht, durch das zugehörige Nichtterminal auf der linken Seite dieser Regel ersetzt. Beispiel 10.1 Wir wollen versuchen, das Wort 1 Die
für die Grammatik (N, { , , , , }, P, E) mit
Namensgebung folgt der Top-Down-Analyse-Vorstellung. Eigentlich handelt es nämlich um eine Linksreduktion.
Bottom-upVerfahren
188
10 LR(k)-Sprachen
P = {E → E T, E → T, T → T F, T → F, F → E , F → } zum Spitzensymbol E zu reduzieren. Computerübung 10.1 Verwenden Sie kfG Edit, um für die kfG aus Beispiel 10.1 einen NKA zu erzeugen. Sie werden feststellen, dass AutoEdit selbst für Wörter geringer Länge die Simulation der Analyse abbricht. Der Prozess wäre viel zu zeitaufwendig. Übung 10.1 mit Papier und Bleistift „bottom up“ (von links Analysieren Sie das Wort her zum Spitzensymbol reduzierend) unter Verwendung genau eines Vorausschauzeichens. Welche Erfahrung machen Sie dabei? Was stellen Sie fest? Übung 10.2 Analysieren Sie das im Beispiel gegebene Wort „top down“ unter Verwendung genau eines Vorausschauzeichens. Notieren Sie die Ableitung. Vergleichen Sie die beiden Methoden. Was stellen Sie fest?
Unter Verwendung von kfG Edit erhält man die folgende Rechtsableitung für das Wort und die Grammatik aus Beispiel 10.1: E ⇒E T ⇒E T F ⇒E T E ⇒E T E T ⇒ E T E F ⇒E T E ⇒E T T ⇒ E T F ⇒E T ⇒E F ⇒E ⇒ T ⇒F ⇒
Abbildung 10.1: Rechtsableitung für
Wir kehren nun die Reihenfolge der Rechtsableitung um und notieren die einzelnen Satzformen als Konfigurationenfolge eines DKA, der soetwas wie eine Linksreduktion (vom Wort zum Spitzensymbol hin) simuliert. Als Aktion vermerken wir shift, reduce bzw. accepted mit folgenden Bedeutungen:
10.2 Deterministische Bottom-up-Syntaxanalyse
189
shift bedeutet, dass das nächste Token aus dem Eingabepuffer (Restwort) entfernt und auf den Stapel gelegt wird.
shift
reduce: X → β bedeutet, dass der Stapelinhalt gemäß Regel X → β reduziert wird. Die rechte Regelseite β stimmt genau mit dem obersten Stapel(teil)wort überein. Genau dieser Stapelinhalt wird durch die linke Regelseite, also X, ersetzt.
reduce
accepted steht ganz am Ende, wenn das Startsymbol der Grammatik als einziges Zeichen im Keller steht und der Puffer für das Eingabewort leer ist. Um das Ende des Eingabewortes zu kennzeichnen, verwenden wir ein Dollarzeichen $.
accepted
tos
Stapel ↓
F T E E E E F E T E T E T E T E T F E T T E T E E T E E T E E T E F E T E T E T E E T E E T F E T E
Restwort
Aktion shift reduce: F → reduce: T → F reduce: E → T shift shift reduce: F → reduce: T → F shift shift shift reduce: F → reduce: T → F reduce: E → T shift shift reduce: F → reduce: T → F reduce: E → E + T shift reduce: F → E reduce: T → T F reduce: E → E T accepted
Tabelle 10.1: Erfolgreiche LR-Analyse des Wortes
Die Angabe der Aktionen in Tabelle 10.1 ist nicht in jedem Schritt eindeutig. Beispielsweise wäre in der durch drei Ausrufezeichen gekennzeichneten Zeile 9 anstelle der gewählten shift-Aktion ein reduce mit Regel E → T prinzipiell möglich.
shift oder reduce?
190
10 LR(k)-Sprachen
Hätten wir uns jedoch für diese Reduktion entschieden, wäre die Analyse in eine Sackgasse geraten. Wie man der Konfigurationenfolge in Tabelle 10.1 entnimmt, ist F → a die (von unten gezählte) 6. angewandte Regel. Damit dies möglich ist, müssen vorher drei shift-Aktionen stattfinden. Wir halten also fest, dass wir ohne Zuhilfenahme der vorbereiteten Rechtsableitung nicht in der Lage gewesen wären, im betrachteten Beispiel eine sackgassenfreie Bottom-up-Syntaxanalyse durchzuführen.
Handle
Für eine deterministische Bottom-up-Analyse ist es notwendig, die als nächstes stattfindende Aktion (reduce, shift) irrtumsfrei zu bestimmen: Entweder das nächste Zeichen des restlichen Eingabewortes wird auf den Stapel gelegt (shift) oder die Satzform β am oberen Stapelrand – das sog. Handle (Henkel oder Ansatz)2 wird reduziert (reduce). Bezeichnet β eine Satzform (als Teilzeichenkette der aktuell betrachteten Satzform) und γ ein entsprechendes Restwort, so gilt: Eine Reduktion αβ γ ⇐ αXγ, ∗ mittels Regel X → β , findet statt, wenn αXγ ⇐ s gilt. Dies entspricht der Empfehlung „Reduktion genau dann, wenn die grundsätzlich erfolgreiche Analyse dadurch nicht in eine Sackgasse führt.“ Aus praktischer Sicht bringt das nichts. Aber wir können damit den Begriff der LR(k)-Sprachen definieren. Definition 10.1 Eine kfG ist LR(k)-Grammatik, wenn für jede Satzform αβ γ in einer Rechtsableitung, mit γ ∈ T ∗ , β ∈ (N ∪ T )+ und α ∈ (N ∪ T )∗ , das Handle β durch Vorausschau auf die ersten k Zeichen von γ eindeutig identifiziert werden kann. Es gilt k ≥ 0.
Welche Informationen stehen bei LR(k)-Sprachen überhaupt zur Verfügung, um ein Handle zu bestimmen? • Die Vorausschau auf die ersten k Zeichen des noch unverbrauchten Eingabewortes und • der gesamte Stapelinhalt. Vorausschauzeichen
In Bezug auf die Anzahl k der Vorausschauzeichen reicht es aus, wenn wir uns auf k = 0 und k = 1 konzentrieren. Dies sichert der folgende Satz. Satz 10.1 Zu jeder LR(k)-Grammatik, mit k > 1, gibt es eine äquivalente LR(1)-Grammatik. Beweis s. Literatur
Stapelinhalt
Die Auswertung (Lesen, Verarbeiten) des (gesamten) Stapelinhalts in jedem 2 In
mancher Literatur wird auch die gesamte zugehörige Regel, X → β , als Handle bezeichnet.
10.3 Tabellengesteuerte LR(k)-Syntaxanalyse
191
Schritt würde die Arbeitsgeschwindigkeit eines LR-Parsers unakzeptabel absinken lassen. Deshalb besteht der Trick darin, bestimmte Konfigurationen durch Zustände (eines potentiellen DEA3 ) auszudrücken und im Wechsel mit den Satzformen (Terminale, Nichtterminale) auf den Stapel zu legen. Diese Zustände werden konzeptionell gern mit 0, 1, 2, . . . , n angegeben. Die im Vorab ermittelte Steuerung (Aktion und Folgezustandsbestimmung) wird in Tabellenform angegeben.
10.3 Tabellengesteuerte LR(k)-Syntaxanalyse Zur Illustration verwenden wir im Folgenden wiederum die kfG aus Beispiel 10.1 und zitieren hier zur besseren Lesbarkeit die Regeln dieser Grammatik. (1)
E
→ E T,
(2)
E
→
T,
(3)
T
→ T F,
(4)
T
→ F,
(5)
F
→
(6)
F
→
E ,
action Z 0 1 2 3 4 5 6 7 8 9 10 11
s
goto
s r4
r6
r6
s
r2 r4
accept r2 r4
r6
r6
s
s s
s s s r1 r3 r5
s r3 r5
4
T 2
F 3
5
4
8
2
3
5 5
4 4
9
3 10
5
s s r2 r4
E 1
s r1 r3 r5
6 7
6 r1 r3 r5
11 7
Tabelle 10.2: LR-Parsetabelle für die Grammatik aus Beispiel 10.1
An dieser Stelle wollen wir die LR-Parsetabelle als gegeben ansehen und uns mit deren Verwendung im Analyseprozess vertraut machen. 3 Man
kann sich das als Handle-basierten DEA vorstellen.
LR-Parsetabelle
192
action goto
10 LR(k)-Sprachen
Eine LR-Parsetabelle, wie etwa in Tabelle 10.2, besteht aus zwei Teilen: einem action-Teil und einem goto-Teil. Die Tabelleneinträge haben folgende Bedeutungen: • Das Zeichen
im Kopf der Tabelle ist das Eingabeabschlusszeichen4 .
• s steht für shi f t: Schiebe das aktuelle Eingabezeichen auf den Stapel. • ri steht für reduce mit Regel i. • accept steht für „Eingabewort akzeptiert“. • Leere Felder bedeuten Fehler. shift-reduceSyntaxanalyse
Die Analyse verwendet einen Stapel, auf dem sowohl Zustandsnamen (hier: 0, 1, . . . , n) als auch Vokabularzeichen, d.h. Zeichen aus N ∪ T , liegen. Für die Bearbeitung des Wortes ergeben sich die in Abbildung 10.2 gezeigten Kellerinhalte (von links nach rechts).
Abbildung 10.2: Ausschnitt aus einer Parsetabelle eines shift-reduce-Parsers
Zu Beginn bildet der Zustandsname 0 das top of stack. Als Erstes wird die actionFunktion bemüht, um aus dem aktuellen Zustand und dem nächsten Eingabezeichen eine der o.g. drei Aktionen (shift, reduce, accept) durchzuführen. Im Falle von shift wird das jeweils nächste Zeichen des Eingabewortes auf den Stapel geschoben. Die bei ri auszuführende Reduktion mit Regel i bewirkt, dass der der rechten Regelseite von ri entsprechende Stapelinhalt, inkl. Zustandsnamen, durch das Nichtterminal auf der linken Seite von ri ersetzt wird. Anschließend kommt die goto-Funktion zum Einsatz. Sie nimmt (ebenfalls) den aktuellen Zustand und das oberste Vokabularzeichen, um den neuen Zustand zu bestimmen. Der Name dieses Zustandes wird gekellert und bildet das neue top of stack. Damit ist ein Arbeitstakt beendet. 4 Das
Eingabeabschlusszeichen markiert das Ende des Eingabewortes und soll den Abschluss des Einlesevorgangs erkennbar machen. Selbstverständlich könnte dies auch anders, etwa durch das Ende einer Liste, bewirkt werden. Deshalb wird es in mancher Literatur auch nicht explizit verwendet. Wir verwenden das Zeichen unter Hinweis auf die Forderung, dass es mit keinem Terminalsymbol übereinstimmt.
10.3 Tabellengesteuerte LR(k)-Syntaxanalyse
193
Didaktischer Hinweis 10.1 Die klassische Form der Parsetabelle weicht von der hier beschriebenen etwas ab. Anstelle der s(hift)-Einträge im action-Teil finden sich si -Angaben. Diese bedeuten, dass nach der entsprechenden shift-Aktion Zustand i gekellert wird. Auf diese Weise spart man die Spalten für Terminale im goto-Teil ein. Wir machen davon jedoch keinen Gebrauch, um die Bestimmung der jeweiligen Aktion von der Berechnung des jeweiligen goto-Zustands konzeptionell zu entkoppeln.
Die Analyse stoppt erfolgreich, wenn der Zustand accept erreicht ist, bzw. mit einem Fehler, wenn sie auf ein leeres Feld in der Tabelle trifft. Das Wort wird akzeptiert. Übung 10.3 Arbeiten Sie den Analyseprozess auf Papier gründlich durch.
Wie auch in Abbildung 10.3 dargestellt, können LR(1)-Sprachen niemals mehrdeutig sein.
LkfS Leindeutige kfS LLR(1) = LdkfS LLALR(k) LLL(1) LSLR(1) LLR(0)
Abbildung 10.3: LR(1)-Sprachen mit relevanten Teilmengen
194
10 LR(k)-Sprachen
Didaktischer Hinweis 10.2 In der Praxis gibt es durchaus einige Gründe, von mehrdeutigen Grammatiken auszugehen und gewisse Maßnahmen zur Schadensbegrenzung hinzuzufügen. Wir sehen Mehrdeutigkeit hier eher als schädlich für die Sprachverarbeitung und trachten nach deren Vermeidung.
shift/reduceKonflikt reduce/reduceKonflikt
Konstruktion von LR-Parsetabellen
Wie Abbildung 10.3 zeigt, ist nicht jede eindeutige kfG LR(1)-Grammatik. Bei Nicht-LR(1)-Grammatiken treten shift/reduce- und reduce/reduce-Konflikte auf: Für den betrachteten Schritt ist sowohl eine shift- als auch eine reduce-Operation möglich oder für ein reduce kommen mindestens zwei Regeln infrage. Eine typische „Reparaturmaßnahme“ für solche kfG besteht in der Praxis darin, die Assoziativität von Operationen zu beschreiben. Manche Parsergeneratoren, wie etwa der Yacc (s. Abschnitt 10.4), bieten sprachliche Möglichkeiten, um dies auszudrücken. Wie VCC mit Nicht-LR-Grammatiken umgeht, wird ebenfalls in Abschnitt 10.4 erwähnt. Nachdem nun klar ist, wie LR-Parsetabellen eingesetzt werden, betrachten wir deren Konstruktion. Dabei gehen wir nicht ins Detail. Grundsätzlich gibt es drei Haupt-Konstruktionsverfahren. Sie führen zu sog. LR(1)-, SLR(1)- und LALR(1)Sprachen. SLR(1)-Sprachen sind die einfachsten. Die SLR-Methode hat die geringste Reichweite (Anzahl der Grammatiken, für die sie arbeiten), ist aber am einfachsten zu implementieren. Der Buchstabe S im Namen steht für „simple“. Eine allgemeine LR(k)-Parsetabelle, mit k = 0 bzw. k = 1, entsteht in zwei Schritten: 1. Erzeugung aller Zustände, die den aktuellen Stand der Analyse – die Stapelbelegung – repräsentieren 2. Konstruktion des zugehörigen Überführungsgraphen (DEA) – Repräsentation als Tabelle Je nach Anzahl der Vorausschauzeichen, die für den ersten bzw. zweiten Arbeitsschritt herangezogen werden, unterscheidet man folgende Sprachklassen:
Sprachklasse LR(0) SLR(1) LR(1) SLR(1)-Sprachen
Anzahl der Vorausschauzeichen für die . . . Erzeugung der Zustände Erzeugung der Tabelle 0 0 0 1 1 1
Wie in Abbildung 10.3 dargestellt, liegen die SLR(1)-Sprachen gerade zwischen den in obiger Tabelle genannten Sprachklassen. SLR(1)-Grammatiken beschreiben fast alle Konstrukte in Programmiersprachen und beanspruchen im Vergleich zu LR(1)-Sprachen wesentlich kleinere Parsetabellen. Für reine LR(1)-Sprachen
10.4 Automatisierte Parsergenerierung
195
muss man mit Tabellen rechnen, deren Konstruktion im Allgemeinen per Hand unzumutbar ist. Für Sprachen, die über den Status von Übungsbeispielen hinausgehen, kann ein zugehöriger DEA durchaus mehrere Hundert Zustände besitzen. Handarbeit ist da nicht möglich. Wir brauchen ein Werkzeug, das die DEAGenerierung leistet. Da dieser Erzeugungsprozess algorithmisch5 beschreibbar ist, gibt es solche Werkzeuge tatsächlich. In Abschnitt 10.4 befassen wir uns einführend mit der automatisierten LR-Parsergenerierung. Die Suche nach einer Möglichkeit Parsetabellen zu verkleinern und dennoch nahezu über den Beschreibungsumfang von LR(1)-Grammatiken zu verfügen, führte zu LALR(1)-Grammatiken. Nach der Erzeugung der LR(1)-Zustandsmenge werden zusammenfassbare Zustände verschmolzen. Dadurch entstehen auch kleinere Tabellen. Die Bezeichnung LALR(1) steht für LookAhead-LR(1). Für die meisten Praxisanforderungen stellen sie den besten Kompromiss dar und sind auch Gegenstand von Parsergeneratoren. Aus dem Gebiet des Compilerbaus übernehmen wir für (schnelles) tabellengesteuertes Parsing in Abhängigkeit der Vorausschauzeichenanzahl k folgende Befunde mit praktischer Relevanz: LR(0)-Sprachen sind einfach zu analysieren aber nicht mächtig genug, um alle gängigen Konstrukte in (imperativen) Programmiersprachen zu beschreiben. LR(1)-parsing, kurz: LR-Parsing, ist die leistungsfähigste und in der Praxis am häufigsten benutzte Analysetechnik. LR(k)-parsing, k > 1, ist praktisch wegen Satz 10.1 (S. 190) ohne Interesse. Interessant ist vielmehr der Zusammenhang zwischen LR(k)- und LL(k)-Sprachen:
Satz 10.2 Jede LL(k)-Sprache ist auch LR(k)-Sprache. Beweis s. Literatur
Die Umkehrung von Satz 10.2 gilt nicht, s. Abbildungen 10.3 und 9.2 (s. Seite 163). Die Grammatik in Beispiel 10.1 ist eine LR(1)-, jedoch keine LL(1)Grammatik.
10.4 Automatisierte Parsergenerierung Für die meisten der praxisrelevanten Sprachen kommen LALR(1)-Parser zum Einsatz. Wie bereits erwähnt, werden sie nicht per Hand geschrieben, sondern mit ent5 Dies
ist Gegenstand eines Kurses zum Compilerbau und wird hier nicht thematisiert.
LALR(1)Sprachen LookaheadLR(1)
196
Yacc Lex
10 LR(k)-Sprachen
sprechenden Parsergeneratoren automatisch erzeugt. Ein traditioneller CompilerGenerator ist Yacc yet another compiler compiler6 . Scanner, mit denen der mittels Yacc erzeugte Parser kooperiert, werden mit dem Scannergenerator Lex erzeugt. Lex und Yacc sind ursprünglich in C geschriebene Unix-Programme, die seit langem als Bestandteil von Linux-Distributionen zur Verfügung stehen. Die verbesserten Versionen Flex und Bison sind auch für Windows-Betriebssysteme aus dem Web downloadbar. Es gibt eine Reihe von Problemen der angewandten Informatik, die sich auf die Übersetzung einer Sprache in eine andere zurückführen lassen. Insofern ist es wünschenswert, eine leicht handhabbare „Compiler-Werkstatt“ zur Hand zu haben, die die Umsetzung der konzeptionell vorüberlegten Prozesse ermöglicht, ohne einen längeren Einarbeitungsaufwand in technische Bedienungsdetails abzuverlangen. Didaktischer Hinweis 10.3 Aus der Erfahrung in der Arbeit mit Studierenden wissen wir, dass es bei der Verwendung von Yacc immer wieder technische Probleme gibt, die fern der eigentlichen Aufgabenstellung zeitaufwendige Lösungen erfordern. Flex und Yacc sind also klassische Werkzeuge für Informatiker, die viel Erfahrung im Umgang mit diesen konkreten Programmen mitbringen bzw. aufbauen wollen.
VCC
In jüngerer Vergangenheit wurden alternative Compilergeneratoren, wie etwa VCC entwickelt, die sich durch Benutzerfreundlichkeit in o.g. Sinne auszeichnen. VCC (Visual Compiler Construction) bietet LL(1)- und LALR(1)-Parsing, ermöglicht sogar eine grafische Darstellung grammatikalischer Regeln, ist in AtoCC eingebettet und dadurch in der Lage, mit den anderen AtoCC-Komponenten zusammenzuarbeiten.
Konfliktbehandlung VCC behandelt shift-reduce-Konflikte durch Bevorzugung von shift und reducereduce-Konflikte durch Verwendung der zuerst aufgeschiebenen Produktion. Ein Eingriff von außen ist also dadurch möglich und ggf. nötig, dass die Reihenfolge der Regelangaben verändert wird. Noch besser ist es, mehrdeutige Grammatiken zu vermeiden.
LALR(1)-Parser für PL/0 mit VCC
In Abschnitt 9.5 ab Seite 171 haben wir das Thema „Parsergenerator“ bereits angesprochen und VCC benutzt, um einen LL(1)-Parser für PL/0 herzustellen. Hierzu wurde eine reduzierte Grammatik der zu analysierenden Sprache (PL/0) in BNF an VCC übergeben und ein entsprechender LL(1)-Parser in einer wählbaren Sprache (Java) erzeugt. Die LALR(1)-Parsergenerierung geschieht analog, wobei eben anstelle des LL(1)-Typs LALR(1) gewählt wird.
6 Für
Compilergeneratoren wird auch der Begriff Compiler Compiler verwendet.
10.4 Automatisierte Parsergenerierung
197
Beispiel 10.2 Wir verwenden VCC zur Erzeugung eines LALR(1)-Parsers für die Grammatik aus Abschnitt 10.3. (1)
E
→
E
(2)
E
→
T,
T,
(3)
T
→
T
(4)
T
→
F,
(5)
F
→
(6)
F
→
F, E ,
Der Parserdefinition in Abbildung 10.4 liegen die folgenden sechs Tokendefinitionen zufür , für , für , für , für und für \ \ \ \ . grunde: , Auf eine Scanneranwendung auf bzw. -animation für ein Eingabewort, wie etwas sollte man nicht verzichten.
Abbildung 10.4: Parserdefinition für LALR(1)-Parsergenerierung mit VCC
198
10 LR(k)-Sprachen
Im Ausgabe-Log von VCC wird die Parsetabelle, wie in Abbildung 10.5, angegeben.
Abbildung 10.5: Ausschnitt aus einer Parsetabelle eines LALR(1)-Parsers Aus dieser Darstellung ist eine Tabelle in gewohnter Gestalt leicht zu gewinnen. Uns kommt es hier lediglich darauf an, die konzeptionelle Nähe des Generators zu der angedeuteten DEA-Konstruktion zu illustrieren. Computerübung 10.2 Wir betrachten eine Grammatik für die Sprache einfacher arithmetischer Ausdrücke: G = (N, T, P, s), N = {E, T, F}, T = {+, −, ∗, /, num}, s = E. P = {E → E + T | E − T | T, T → T ∗F | T /F | F, F → num} num steht für eine beliebige ganze Zahl. Verwenden Sie VCC zur automatisierten Generierung eines LALR(1)-Parsers für diese L(G). Der Parser soll als Javaals Analysebeispiel, ebenso Programm vorliegen. Verwenden Sie das Eingabewort ∈ L(G). wie Computerübung 10.3 Verwenden Sie VCC zur Erzeugung eines LALR(1)-Parsers für PL/0 aus Abschnitt 9.5 ab Seite 171 und testen Sie diesen Parser für syntaktisch korrekte und fehlerhafte PL/0Programme.
10.5 Compiler ZR
An dieser Stelle greifen wir wieder unser Beispiel auf, das uns an mehreren Stellen dieses Buches immer wieder begegnete: die Zeichenroboter-Sprache ZR, die wir in Abschnitt 5.2 ab Seite 48 eingeführt haben. Gemäß des im T-Diagramm in Abbildung 5.4 auf Seite 51 modellierten Übersetzungsschemas soll ein korrektes ZR-Programm in ein entsprechendes Postscript-Programm und schließlich in pdf übertragen werden. In Abschnitt 8.7 ab Seite 151 wurde bereits ein LL(1)-Parser für ZR mittels VCC erzeugt. Hier geht es nun darum, die Analyse um eine Zielcodegenerierung zu erweitern und einen entsprechenden LALR-Parsergenerator zu einem LALR-Compilergenerator auszubauen.
10.5 Compiler
199
In Abschnitt 5.4, ab Seite 56, wurden die Phasen eines Compilers genannt. Grundsätzlich besteht ein vollständiger Compiler aus einem Analyseteil (Parsing eines gegebenen Quellcodes) und einem Syntheseteil (Zielcodegenerierung). Hier wenden wir uns nun dem Syntheseteil zu und befassen uns mit syntaxgesteuerter Übersetzung. Die Idee besteht darin, den Produktionen, die bei einer LR-Analyse zur Anwendung kommen, semantische Regeln zur Seite zu stellen. Auf diese Weise wird der während der Syntaxanalyse erzeugte Parsebaum bewertet, d.h. mit Attributen versehen (annotiert). Der Wert eines solchen Attributs wird durch die bei der Konstruktion des Parsers angegebene semantische Regel bestimmt. Die Regeln können beliebige Berechnungsprozesse beschreiben und auch Seiteneffekte (Veränderung der Werte globaler Variablen) bewirken. Wir sind natürlich daran interessiert, jeweils zugehörige Fragmente des Zielcodes zu produzieren.
syntaxgesteuerte Übersetzung semantische Regeln Attribute
Man unterscheidet synthetisierte und ererbte Attribute. Der Wert eines synthetisierten Attributs, kurz: S-Attribut, ergibt sich aus den Attributwerten bestimmter Nachfolger-Knoten des Parsebaums durch Anwendung der zugehörigen semantischen Regel(n). Die Bewertung ererbter Attribute greift auf Vorgänger- und Geschwisterknoten zu. Wir befassen uns hier nur mit S-Attributen7 . VCC verwendet sowohl für die Generierung von LL(1)- als auch LALR(1)-Parsergenerierung SAttribute.
synthetisierte Attribute ererbte Attribute S-Attribute
Die Berechnungsreihenfolge von S-Attributen wird bei LR-Syntaxanalyse wie folgt bestimmt: Immer, wenn die Analyse den entsprechenden Knoten des Parsebaums erreicht, wird die entsprechende Regel aktiviert. Die Bottom-upAnalysemethode sorgt dafür, dass die Berechnung der S-Attribute von den Blättern des Parsebaums her aufsteigend erfolgt. Das kommt uns sehr entgegen, denn beim Top-down-Abstieg von der Wurzel zu den Blättern würden die Werte erforderlicher Operanden im Allgemeinen noch nicht vorliegen.
Berechnungsreihenfolge
Semantische Regeln haben folgenden Aufbau: Die zu deren Definition verwendeten Symbole der Gestalt n bezeichnen den Wert des n-ten Vokabularzeichens (Nichtterminale und Terminale) in der betreffenden Regel. Die Zählung beginnt mit dem ersten Vokabularzeichen auf der rechten Seite der betrachteten Produktion. Dem Nichtterminal auf der linken Regelseite wird das Zeichen zugeordnet.
Aufbau der Regeln
Beispiel 10.3 Jeder Produktion der Grammatik aus Beispiel 10.1 auf Seite 187 ordnen wir eine semantische Regel zu, die im Zusammenspiel bewirken, dass erfolgreich analysierte Ausdrücke (Wörter der Quellsprache) unverändert ausgegeben werden. Da der Parser als JavaProgramm hergestellt werden soll, wird in den semantischen Regeln Java-Code verwendet.
7 Der
Verzicht auf ererbte Attribute bedeutet lediglich einen Verzicht auf Bequemlichkeit, jedoch keine Einschränkung: Man kann jedes Programm so umschreiben, dass ererbte Attribute eliminiert werden, sodass man mit S-Attributen auskommt.
200
10 LR(k)-Sprachen
Produktion S→E E →E T E →T T →T F T →F F→ E F→
semantische Regel
Wie man der Tabelle entnimmt, wurde in der Grammatik eine Produktion für ein neues Spitzensymbol S hinzugefügt. Dies ist immer dann erforderlich, wenn es für das eigentliche Startsymbol (hier: E) mehr als eine grammatikalische Regel gibt. auf. Sie wird benöAußerdem tritt in der ersten Zeile die vordefinierte Variable tigt, um die Ausgabe des Zielcodes in eine Datei oder auf die Konsole zu leiten – je nach ist in VCC für die AusgaAufrufmuster des Compilers: mit/ohne Zieldateinamen. besprachen Java, Delphi und C# vordefiniert und bietet nur eine einzige Methode, nämlich , für die Ausgabe einer Zeichenkette. Verwendet man Scheme, so , und , in die Zieldatei werden prinzipiell alle Ausgabebefehle, wie umgeleitet.
Computerübung 10.4 Setzen Sie das Beispiel 10.3 mit VCC um und analysieren Sie Wörter wie bereitstellen. nacheinander in der Datei
, die Sie
Computerübung 10.5 Modifizieren Sie die in Beispiel 10.3 angegebenen semantischen Regeln so, dass die Folge der anwandten Regeln in die Ausgabedatei geschrieben wird. Vergleichen Sie diese Folge mit der Aktionenfolge in Tabelle 10.1 auf Seite 189. für das Eingabewort
Taschenrechner
Ein beliebtes Beispiel ist das eines Taschenrechners für einfache mathematische Ausdrücke. Als Einstiegsbeispiel eingesetzt verwirrt es etwas: Schließlich geht es um Compiler, die Wörter aus der einen Sprache in Wörter einer anderen Sprache übersetzen. Erwartet man als Rückgabe eine Zahl, die sich durch Evaluation des betrachteten Ausdrucks ergibt, so will man illustrieren, dass die semantischen Regeln eben auch dafür verwendbar sind. Die Idee, syntaxgesteuerte SemantikDispatcher zu konstruieren, ist sehr anregend. Beispiel 10.4 Wir betrachten Sie Grammatik G = (N, T, P, s) = ({S, E}, { , , }, {S → E, E → E E | E E | a}, S). Das hier als Terminal angegebene Zeichen steht für eine beliebige natürliche . Zahl, d.h. Die folgende Tabelle zeigt die Java-Codierung der zugehörigen semantischen Regeln.
10.5 Compiler
Produktion S→E E →E E
201
semantische Regel
E →E E E →a Wie in diesem Beispiel ist es gelegentlich hilfreich, einige Hilfsvariablen einzuführen. Variablendeklarationen sollten dabei als globaler Quelltext angegeben werden. Den entsprechenden Eingabebereich erreicht man durch Klick mit der rechten Maustaste im Fenster mit den Syntaxregeln für die Parserdefinition, allerdings ohne dabei eine solche Regel zu treffen. In unserem Beispiel lautet der Java-Eintrag: , so wie das in Abbildung 10.6 zu sehen ist.
Abbildung 10.6: Syntaxregeln für den Parser und globale Variablen Mit der kleinen Lupe in der VCC-Bedienungsleiste kann man sich sämtliche semantische Regeln anzeigen lassen und ggf. editieren. Der globale Quellcode steht immer ganz unten. G ist eine mehrdeutige Grammatik, s. auch Beispiel 2.2 auf Seite 13. Dies äußert sich bekanntlich darin, dass es für Ausdrücke (Wörter), wie beispielsweise , zwei verschiedene Ableitungsbäume gibt. Für unsere Ausdrucksbewertung nach dem Vorbild eines Taschenrechners ergibt sich daraus ein sichtbares Folgeproblem: Wir müssen schlichtweg feststellen, dass der für den exemplarisch betrachteten Ausdruck 2 ∗ 3 + 4 ermittelte Wert 14 (nach gängigen Rechenregeln) falsch ist. Offensichtlich wurde stattdessen 2 · (3 + 4) = 14 berechnet. Damit wurde das Prinzip „Punktrechnung geht vor Strichrechnung.“ verletzt. Wie bringt man dem Programm dieses Prinzip bei? In Parser-Generatoren, wie Yacc, geschieht dies durch bestimmte Zusätze, die – wie oben angemerkt – Mehrdeutigkeiten beheben. Demgegenüber versuchen wir eine eindeutige kfG für diese Sprache anzugeben. Eine solche Grammatik kann aus der in Beispiel 10.1 unter Verzicht auf die Klammern und einiger Überlegung gewonnen werden.
globaler Quelltext
202
10 LR(k)-Sprachen
Didaktischer Hinweis 10.4 Bei Generatoren, wie Yacc, ist für jeden Operator anzugeben, ob er links-, rechts- oder nicht assoziativ ist. Es gibt eine sehr einprägsame Definition der Links/Rechtsassoziativität: Wird ein Operand a von zwei gleichen Operatoren eingeschlossen, so gehört a entweder zum linken oder zum rechten Teilausdruck. Im ersten Fall ist der betrachtete Operator links- im zweiten rechtsassoziativ. Während die Addition und Substraktion linksassoziativ sind, ist das Potenzieren eine rechtsassoziative Operation.
Nun endlich wollen wir den ZR→PDF-Compiler vollenden. Als erstes wiederholen wir die Grammatikdefinition der Quellsprache ZR (Abbildung 5.12 auf Seite 56) und konzentrieren uns auf die reduzierte Grammatik gemäß Beispiel 8.13 ab Seite 152 (Abbildung 5.15 auf Seite 59). Zur Beschreibung des von VCC zu erzeugenden Scanners werden die in Abbildung 10.7 dargestellten Token definiert.
Abbildung 10.7: Tokendefinition für den ZR → PS-Compilergenerator
Postscript
Im Gegensatz zu diesem Referenzbeispiel werden wir hier jedoch einen LALR(1)Parser erzeugen. Vorher müssen noch die semantischen Regeln hinzugefügt werden. Da diese Regeln die passenden Zielcodefragmente produzieren sollen, ist eine gewisse Kenntnis der Zielsprache, also Postscript (PS), erforderlich. Didaktischer Hinweis 10.5 Um die Sprache Postscript bekannt zu machen, reicht ein Arbeitsblatt im Umfang von einer Seite aus. Es sollte die für ZR relevanten Sprachelemente im Verbund vorstellen und kommentieren. Weitere Informationsquellen bietet das Web.
Postscript ist eine Programmiersprache. Es gibt einen Interpreter, der die in PSProgrammen enthaltenen Befehle in grafische Darstellungen umsetzt. Wie der Na-
10.5 Compiler
203
me andeutet, sind die Befehle so aufgebaut, dass die Operatoren, wie oder , den Operanden, wie etwa die x-y-Koordinaten eines Punktes, nachgestellt sind. Ein solcher Befehl lautet beispielsweise: . Dessen Interpretation setzt den imaginären Zeichenstift auf die Position (120, 200). Intern wird dies durch Stack-Arbeit umgesetzt. Nutzerdefinierte Prozeduren werden mit dem Kommando festgelegt und können fortan mit dem gewählten Namen und ggf. entsprechenden konkreten Parametern verwendet werden. In der semantischen Regel für die erste Produktion machen wir davon Gebrauch. Programm → Anweisungen:
Anweisungen → Anweisung Anweisungen: Anweisungen → ε: Für die folgende Produktionsregel zeigt Abbildung 10.8 exemplarisch, wie der Zielcode in VCC beschrieben wird. Anweisung →
Anweisungen
Anweisung →
Anweisung → Anweisung → Anweisung →
:
:
: : :
Damit ist der ZR → PS-Compiler vollständig beschrieben und kann nun von VCC
204
10 LR(k)-Sprachen
Abbildung 10.8: Zielcode-Beschreibung in VCC für eine ausgewählte Regel
mit den beiden Einstellungen „LALR(1)-Parser“ und „Java-Programm“ erzeugt werden. Abschließend kehren wir zum T-Diagramm, welches wir in Kapitel 5 ab Seite 47 entwickelt haben, zurück und verknüpfen den entstandenen ZR → PS-Compiler mit dem entsprechenden Baustein im Diagramm. Da wir für die Sprache, in der unser Compiler vorliegt, Java Bytecode definiert haben, bietet uns TDiag bei der Dateiauswahl nur -Dateien an (siehe Abbildung 10.9). Java erzeugt mehrere -Dateien für einen Compiler. Wir wählen die Hauptdatei, etwas , aus dem Projektverzeichnis. Der durch das T-Diagramm beschriebene Prozess kann nun auf Knopfdruck ausgeführt werden. Computerübung 10.6 Verwenden Sie VCC und erzeugen Sie den ZR → PS-Compiler der beschriebenen Weise. Vervollständigen Sie das entsprechende T-Diagramm und starten Sie die Übersetzung eines ) und Interpretation des im zweiten Schritt erzeugten pdfZR-Programms (z.B. Textes.
Wir haben nun eine Compilerdefinition in VCC und ein lauffähiges T-Diagramm in TDiag. Es bietet sich an, die Sprache ZR durch neue Token und Syntaxregeln in VCC zu erweitern. Für das Zeichnen komplexer Figuren ist ein Sprungbefehl von Vorteil, um mehrfaches Zurückfahren einzusparen. Wir haben bereits im PostScript-Kopf einen passenden Befehl definiert und verwendet: . Computerübung 10.7 Erweitern Sie den ZR → PS-Compiler so, dass er einen neuen Befehl der Form „versteht“ und entsprechend verarbeitet. Hierfür benötigen Sie zwei neue Tokenklasund das Komma, und eine neue rechte Regelseite für Anweisung. sen, für Computerübung 10.8 PostScript bietet eine Vielzahl an einfachen Befehlen, um unseren Zeichenroboter mit weiteren Funktionen auszurüsten. Experimentieren Sie unter Zuhilfenahme entsprechender Literatur zu PostScript.
10.5 Compiler
205
Abbildung 10.9: Dateiauswahl für den Compilerbaustein in TDiag
Didaktischer Hinweis 10.6 An dieser Stelle sollten die Hauptinhalte der vorherigen Kapitel zu Grammatiken, Automaten und regulären Ausdrücken mit Bezug auf deren Anwendung im ZR-Projekt systematisiert werden. Computerübung 10.9 Entwerfen Sie einen „Farbübersetzer“, der einen Fließtext nimmt, und alle Vorkommen der Wörter blau, rot, grün, gelb, schwarz und weiß durch einen Tag der Form ersetzt. Alle anderen Zeichen sollen wieder direkt ausgegeben werden. Hierfür ist eine entsprechende Tokenklasse nötig. Um tatsächlich ein beliebiges ASCII-Zeichen (inklusive Zeilenumbrüchen und Sonderzeichen) mit einem regulären Aus. druck zu beschreiben, kann etwa folgender Ausdruck verwendet werden:
11 Sprachübersetzerprojekt 11.1 Motivation und Anwendungskontext In diesem Kapitel soll der gesamten Entwicklungsprozess sowohl eines Interpreter als auch eines Compilers mit AtoCC anhand eines kleinen Projekts durchlaufen werden. Zur Motivation haben wir einen (hoffentlich) interessanten Anwendungsbezug ausgewählt: Es geht um Klingeltöne von Mobiltelefonen, die quasi jeder (vor allem der jüngeren Generation) aus dem Alltag kennt. Diese Melodien basieren in der Regel auf einer sehr einfachen Notensprache, die mit Hilfe eingebauter Editoren verwendet wird, um mit dem Handy eigene Lieder zu komponieren. Obwohl es je nach Handy-Hersteller kleinere Unterschiede im Detail gibt, sind Notensprache und Editor gängiger Mobiltelefone nahezu gleich. Abbildung 11.1 zeigt einen solchen typischen Editor und lässt den grundsätzlichen Aufbau der Notensprache bereits erahnen.
Abbildung 11.1: Bildschirmfoto eines Klingeltoneditors eines Mobiltelefons
Um schon mal einen Vorgeschmack von einem der praktischen Projektergebnisse zu bekommen, hören wir uns zwei Melodien an: und . Die gewählten Dateinamen lassen erkennen, um welche Lieder es sich handelt. Didaktischer Hinweis 11.1 Sollte der ML-Interpreter zwar sichtlich arbeiten aber keine Töne erzeugen, liegt dies häufig -Datei im Systempfad. Man kann das bereinigen, indem an einer fehlenden ins ML-Interpreter Verzeichnis kopiert wird. die Datei . Ggf. hilft ein Blick auf
Notensprache
208
Interpreter
ML
11 Sprachübersetzerprojekt
Obwohl VCC in erster Linie ein Werkzeug zur automatisierten Compilergenerierung ist, können auch Interpreter damit hergestellt werden. Bei einem Interpreter löst jede Instruktion des Quellcodes eine bestimmte Aktion aus. Diese Aktionen werden in gewohnter Weise mit semantischen Regeln beschrieben. Eine Zielcode-Datei wird dabei jedoch nicht erzeugt. Deshalb dürfen die Regen auch kein enthalten. Das Projekt gliedert sich in zwei Teile. Wir beginnen mit der Definition der Notensprache ML (ML = Music Language) und wollen einen Interpreter für ML entwickeln, der tatsächlich monofone (d.h. einkanalige) Melodien abspielen kann. Im zweiten Teil soll die gleiche Sprache verwendet werden, um eine Compiler herzustellen. Dieser Compiler wird für die jeweils betrachtete Melodie ein (vereinfachtes) Notenblatt schreiben. Natürlich sollen Notenblätter grafisch ansprechend dargestellt werden. Kenntnisse aus der Notentheorie werden nicht vorausgesetzt. Möchte man die in diesem Projekt entwickelten Werkzeuge tatsächlich für praktische KlingeltonKompositionen verwenden, sind solche Kenntnisse erwartungsgemäß nötig.
11.2 Die Notensprache ML Die Quellsprache ML ist eine textbasierte Notensprache. Eine Note wird dabei in der Form notiert, was einer Achtel-C-Note in der ersten Oktave entspricht. Auch Pausen sollen in ML möglich sein. Da die Oktave für Pausen keine Rolle spielt, notieren wir diese einfach als P-16 für eine Sechzehntel-Pause. Auf halbe Noten, Punktierungen usw. wollen wir hier der Einfachheit halber verzichten. Beispiel 11.1
ist ein konkretes Notenprogramm also ein Wort der . Sprache ML. Es handelt sich um den Inhalt der Datei Beispiel 11.2 Abbildung 11.2 zeigt die mittels kfG Edit hergestellte Ableitung eines ML-Wortes. Computerübung 11.1 , um mittels kfG Edit die zugehörige AbVerwenden Sie das Wort aus Datei leitung anzugeben. Modifizieren Sie das Wort anschließend ein wenig, so dass syntaktische Fehler entstehen.
11.2 Die Notensprache ML
209
Didaktischer Hinweis 11.2 Betrachtet man die Sprache L(GML ) genauer, erkennt man, dass diese auch durch eine reguläre Grammatik definiert werden könnte. Man kann ML zu einer echt kfS ausbauen, wenn man beispielsweise verschachtelte Wiederholungen hinzufügt, wie etwa in . In diesem Projekt wollen wir aber darauf verzichten, um es auch für Musiklaien möglichst einfach und nachvollziehbar zu halten.
Abbildung 11.2: Ableitungsbaum für
Ausgehend von diesem Beispiel definieren wir eine kontextfreie Grammatik für ML. GML = (N, T, P, s) mit: N
= {Song, Notes, Note, Key, KeyName, Octave, Duration, Pause}
T
= { , , , , , , , , , , , ,
P
= {Song → Notes
,
, , , }
Notes → Note | Note Notes Note → Pause
Duration | Key
Duration
Key → KeyName Octave KeyName → Octave → Duration → Pause → } s = Song
| |
|
|
| |
|
|
|
| |
|
|
|
210
11 Sprachübersetzerprojekt
11.3 Entwicklung eines ML-Interpreter Das Vorhaben gliedert sich in folgende Schritte: 1. T-Diagramm für den Interpreter entwerfen 2. Scanner und Parser definieren 3. Semantische Regeln für den Parser angeben 4. ML-Interpreter generieren lassen 5. Interpreter mit Hilfe des T-Diagramms testen Wir beginnen mit der Modellierung des Übersetzungskonzepts für den zu entwickelnden ML-Interpreter. Hierfür verwenden wir wieder ein T-Diagramm und die entsprechende AtoCC-Komponente TDiag. Wie in Abbildung 11.3 zu sehen ist, kommt ein Java-Bytecode-Interpreter zum Einsatz. Die semantischen Regeln des MC-Interpreters werden in Java geschrieben. Die Compilation erzeugt einen in BC geschriebenen ML-Interpreter. Wohl wissend, dass wir ebenso eine andere Programmiersprache hätten einsetzen können, haben wir hier Java gewählt, da wird dadurch die in Java vorhandene Tonerzeugung unmittelbar verwenden können.
Abbildung 11.3: T-Diagramm für den ML-Interpreter
Damit ist die Modellierung der Sprachverarbeitungsprozesse abgeschlossen. Mittels TDiag konnten wir sie transparent und für den Test vorbereitend repräsentieren. Es folgen die Scanner- und Parserdefinition für den ML-Interpreter. Selbstverständlich greifen wir auf die Grammatikdefinition aus Abschnitt 11.2 zurück und exportieren die kfG für ML mittels kfG Edit als Compiler-Beschreibung. Daraufhin wird VCC mit der erzeugten Datei geöffnet. Eine Nachbearbeitung der angelegten Token-Definitionen ist unnötig.
11.3 Entwicklung eines ML-Interpreter
Computerübung 11.2 Lediglich die Tokenklasse
sollten Sie zu
211
umbenennen.
Computerübung 11.3 Simulieren Sie den Scanner für einige ML-Programmen und begutachten Sie die Tokenfolge wie etwa in Abbildung 11.4.
Abbildung 11.4: Scannersimulation für ML
Da wir mit der Programmiersprache Java arbeiten wollen, wählen wir auf der Seite Sprache Java aus.
11.3.1 Semantischen Regeln für den ML-Interpreter Die eigentliche Tonerzeugung muss in den semantischen Regeln erfolgen. Wir müssen also dafür sorgen, dass beim Anwenden der Regeln Note → Pause Duration | Key Duration jeweils eine Note gespielt bzw. eine Pause eingelegt wird. Java stellt uns mit dem Paket ein geeignetes Werkzeug zur Verfügung. Um auf die Funktionen komfortabel zugreifen zu können, legen wir uns eine Hilfsfunktionen im Globalen Quellcode der Parserdefinition unseres VCC Projekts an. Diese Methode spielt sowohl Noten als auch Pausen (Key = ) mit dem Standardinstrument Klavier ab.
212
11 Sprachübersetzerprojekt
Didaktischer Hinweis 11.3 Die Methode kann vom Lehrenden vorgegeben werden. Damit entfällt Implementierungsarbeit. Außerdem entspricht dies dem in der Softwareentwicklung vorherrschenden Prinzip, Bibliotheken und Hilfsfunktionen zu verwenden. Man wählt sie auf der Basis von Signaturen, Komponenten via Interface, aus und abstrahiert von der konkreten Implementation.
Für die Regel Note → Key Duration können wir nun unsere Hilfsfunktion aufrufen. Die konkreten Werte für die Tonhöhe und die Tonlänge ergeben sich aus den Platzhaltern $1 (etwa oder ) bzw. $3 (etwa 4, 8 oder 16). Da $n-Variablen den Datentyp besitzen, können wir in Java ein Reihe von Methoden für deren Verarbeitung einsetzen:
11.3 Entwicklung eines ML-Interpreter
213
Für die Produktion Note → Pause Duration ergibt sich eine ähnliche semantische Regel, wobei wir hier den festen Wert für die angeschlagene Taste verwenden:
Damit haben wir bereits alle relevanten Regeln beschrieben. VCC wird uns noch einige ausstehende triviale semantische Regeln automatisch generieren. Nach Auswahl des Parsertyps LALR(1) erzeugt VCC nach Klick auf Compiler generieren den fertigen ML-Interpreter. Im Ergebnis erhalten wir die weiter oben verwendete Eingabekonsole, um den Interpreter anzuwenden. Computerübung 11.4 Entwickeln Sie einige ML-Notenprogramme als txt-Dateien für diesen Zweck. Beginnen Sie zunächst damit, vorhandene Programme zu modifizieren. Computerübung 11.5 Kehren Sie zum T-Diagramm in T-Diag zurück und verbinden Sie die einzelnen Bausteine mit den entwickelten Dateien. Um relative Pfade nutzen zu können, sollte das T-Diagramm
214
11 Sprachübersetzerprojekt
im gleichen Verzeichnis wie der ML-Interpreter liegen. Für den Interpreterbaustein wäh1 , für das ML-Notenprogramm eine geeignete len Sie die Datei txt-Datei die Sie zuvor angelegt haben. Für den Java-Bytecode-Interpreter sollten alle Einstellungen bereits durch T-Diag getroffen worden sein. Die Ausführung des T-Diagramm bestätigt, dass unser Entwurf den Anforderungen entspricht.
11.4 Entwicklung eines ML → SV G-Compilers Nun wenden wir uns der Herstellung einfacher Notenblätter zu. Hier ist es sinnvoll, einen Compiler vorzusehen, der ein Notenprogramm nimmt und das zugehörige „Blatt“ (Datei) erzeugt. Anschließend muss sich ein Programm (Interpreter) um die grafische Darstellung des Notenblattes kümmern. Die Entwicklungsschritte ähneln denen, die wir weiter oben für die MLInterpreter-Herstellung verwendet haben: 1. T-Diagramm für den Compiler entwerfen 2. Scanner und Parser definieren 3. Semantische Regeln für den Parser angeben 4. ML → SV G-Compiler generieren lassen 5. Compiler mit Hilfe des T-Diagramms testen
SVG
Für den ML → SV G-Compiler beginnen wir wieder mit einem T-Diagramm. Der Compiler verarbeitet ein ML-Programm zu einem SV G-Text, der beispielsweise von Firefox ohne Zusatzmaßnahmen interpretiert werden. Im Browser wird dann das Notenblatt angezeigt. SV G steht für scalable vector graphics und ist eine der wichtigsten XML-Sprachen für grafische Darstellungen. Grundsätzlich wäre es auch möglich, nach dem Vorbild der Zeichenrobotersprache auch hier Postscript und pdf zu verwenden. Abbildung 11.5 modelliert das Übersetzungskonzept. Die Analyse der Zielsprache SV G ergibt, dass es sich dabei um eine sehr umfangreiche Grafik-Beschreibungssprache handelt. Deshalb konzentrieren wir uns auf einige ausgewählte Sprachelemente. Für das Notenblatt geben wir zunächst ein Grundgerüst in SV G vor. Es beinhaltet die typischen XML-Header-Informationen, die für ein SV G-Dokument notwendig sind. Darüber hinaus legt es ein gelbes Rechteck mit 5 schwarzen Notenlinien an. Diese befinden sich in den Y -Positionen 30, 40, 50, 60 und 70. Die Notenlinien werden für unsere späteren Zeichenanweisungen als Referenzpunkte dienen. Die Note hat damit den Y -Wert 80 und die Note hat Y = 65. Je Tonhöhe ergibt sich so ein Abstand von 5 Einheiten auf der Y -Achse.
1 Die
Vorgabe eines Dateinamens ist notwendig, damit das T-Diagramm ausgeführt werden kann.
11.4 Entwicklung eines ML → SV G-Compilers
215
Abbildung 11.5: T-Diagramm für den ML → SV G-Compiler
Didaktischer Hinweis 11.4 Wir haben uns hier auf eine einzige sehr lange Notenzeile (1200 Einheiten) beschränkt. Es ist durchaus möglich, ein vollständiges Notenblatt mit Notenschlüssel, Taktstrichen usw. vorzugeben. Dies würde aber zwangsläufig zu einem üppigen SV G-Grundgerüst führen, was wir in einem Einführungsprojekt-Beispiel unbedingt vermeiden wollen.
Eine Note zeichnen wir als einfache Ellipse. Die zugehörige SV G-Anweisung ist:
Dabei handelt es sich um eine -Note, was leicht an zu erkennen ist. legt die Position der Note auf der Notenzeile fest. Mit jeder gezeichneten Note muss dieser Wert ansteigen. und definieren die Größe der Ellipse und bleiben
216
11 Sprachübersetzerprojekt
konstant. Das Aussehen der Noten unterscheidet sich leicht in Abhängigkeit ihres Werts (aus der Notentheorie des Musikunterichts bekannt). Für ganze und halbe Noten können wir eine kleinere weiße Ellipse in die Notenmitte zeichnen, damit der bekannte Hohlraum-Effekt entsteht:
Für viertel, achtel, sechzehntel und zweiunddreißigstel Noten benötigen wir einen Notenhals. Diesen können wir durch eine einfach Line (etwa 30 Einheiten lang) darstellen. Da dieser ans rechte Ende der Ellipse gezeichnet wird ergibt sich = = + :
Zusätzlich brauchen wir für achtel, sechzehntel und zweiunddreißigstel Noten noch ein Fähnchen am Notenhals. Hierfür verwenden wir einen . Die Startposition (20, 40) muss für die jeweilige Note angepasst werden, die restlichen Parameter bleiben konstant:
Didaktischer Hinweis 11.5 Die Sprache SV G im Detail zu beschreiben und zu erlernen ist für dieses Projekt keinesfalls erforderlich. Die drei beschriebenen Sprachbefehle genügen für die Darstellung. Sie werden in einschlägiger Literatur im Detail erläutert und können bei Bedarf nachgeschlagen werden.
Eine Grammatik für die Sprache ML haben wir ja bereits in kfG-Edit vorliegen. Durch die Regel Song → Notes können wir garantieren, dass dieses Regel bei einer beliebigen Ableitung genau einmal (und die damit verbundene semantische Regel genau als letzte) angewendet wird. Wieder exportieren wir GML nach VCC. Der Tokennamen für das Minussymbol sollte wieder in der Scannerdefinition umbenannt werden. Unter Sprache wählen wir erneut Java und LALR(1) als Parsertyp aus. Nun wenden wir uns den semantischen Regeln auf der Seite Parserdefinition zu. Bei Anwendung der Regel Song → Notes (einem Knoten des späteren Parse-
11.4 Entwicklung eines ML → SV G-Compilers
217
baums bei dessen Besuch am Ende des rekursiven Aufstiegs) wollen wir das SV GGrundgerüst ausgeben.
Didaktischer Hinweis 11.6 , und können auch durch geeignete gloDie verwendeten Anweisungen im ML-Interpreter ausgedrückt werbale parametrisierte Hilfsmethoden wie den. Dies beugt Schreibfehlern vor und ermöglicht es von der Sprache SV G zu abstrahieren.
Zu beachten sind hier die Escape-Zeichen für Zeichenketten in Java und die verwendeten Zeilenumbrüche. Wichtig ist hier außerdem der Platzhalter $1 der nach den Anweisungen für die 5 Notenlinien in die Zeichenkette eingefügt wird. Nach der Regel S → Notes enthält $1 also das berechnete S-Attribut von Notes. Wie für den ML-Interpreter müssen wir uns nur noch den Regeln Note → Key Duration und Note → Pause Duration zuwenden. Alle anderen semantischen Regeln, die wir benötigen, werden später von VCC automatisch generiert. Da jede Note, die wir ausgeben, um einen bestimmten Wert weiter rechts ausgegeben werden muss, benötigen wir globale Hilfsvariablen:
218
11 Sprachübersetzerprojekt
Didaktischer Hinweis 11.7 Die semantische Regel für Note → Key Duration sollte schrittweise erarbeitet werden. Die entstehenden Zwischenergebnisse können stets mit Firefox begutachtet werden.
Der nachfolgender Quellcode zeigt ein mögliches Endergebnis:
Für die die Regel Note → Pause Duration würde sich nun ein sehr ähnliches Ergebnis bezüglich der Postionsauswertung ergeben. Da Pausen recht komplexe Symbole in der Notenschrift aufweisen, wird hier von deren Betrachtung abgesehen und nur die Positionsmarke um die Länge einer Note vorgerückt, um eine Pause anzuzeigen.
11.4 Entwicklung eines ML → SV G-Compilers
219
Jetzt können wir den fertigen ML → SV G-Compiler von VCC auf Knopfdruck generieren lassen. Als Dateiname wählen wir diese Benennung ist für derartige Übersetzungen typisch und lässt sich vielerorts wiederfinden (zum Beispiel: ). Wir können nun wieder zu unserem T-Diagramm in T-Diag zurückkehren und die einzelnen Bausteinen mit entsprechenden Dateinamen verknüpfen. Für die Eingabedatei wird wieder eine txt-Datei gewählt, die bereits für den ML-Interpreter angelegt wurden. Da das Ausgabeprogramm in SV G erst bei der Abarbeitung des Prozesses entstehen wird, geben wir hier einen beliebigen Dateinamen wie als Dateinamen an. Der Compiler wird mit der Datei verknüpft. Für den SV G-Interpreter wählen wir die Startdatei von Firefox aus. Diese findet sich meist in . Die erfolgreiche Übersetzung wird uns im Firefox wie in Abbildung 11.6 dargestellt.
Abbildung 11.6: Erzeugtes Notenblatt in Firefox
Damit haben wir in diesem Kapitel nutzbare Interpreter und Compiler für eine selbst gewählte Sprache ML entwickelt. Es lassen sich viele weitere Beispiele im Bereich Grafik und Akustik finden, die auf ähnliche Weise aufbereitet werden können. Während der Entwicklung lohnt sich stets der Blick zurück auf die Welt der Automaten und formalen Sprachen. AtoCC bietet vielfältige Transformationsmöglichkeiten, die dies ohne zusätzlichen Arbeitsaufwand ermöglichen und so immer wieder den Zusammenhang der vermittelten Inhalte aus der Theoretischen Informatik erkennen lassen.
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen 12.1 Grenzen von Kellerautomaten Aus vorangehenden Kapiteln wissen wir, dass kfS mit NKA definiert werden. Für ksS (kontextsensitive Sprachen) und C HOMSKY-Typ-0-Sprachen reichen NKA nicht aus. D.h., es gibt beispielsweise keinen NKA, der genau alle Wörter der Sprache L = {an bn cn | n ≥ 1}, akzeptiert, s. Beispiel 8.17 auf Seite 160. Man kann nun versuchen, neue Automatenmodelle zu kreieren, die gerade die Typ1- und Typ-0-Sprachen erkennen. Einfach ist diese Aufgabe nicht. Vielleicht entwerfen Sie einen erweiteren NKA-Typ für L, der mit zwei Stapeln arbeitet: Zuerst werden alle a’s gekellert. Das erste b eröffnet den zweiten Stapel. Jedes c führt dazu, dass das jeweils oberste a bzw. b vom 1. bzw. 2. Stapel entfernt wird, bis schließlich beide Stapel leer sind. Dieses „neue Modell“ ist allerdings für die Sprache L = {ss | s ∈ {a, b}∗ } nicht geeignet. Anstelle der beiden Stapel wäre hier vermutlich eine Warteschlange (queue) angebracht. Dies wäre für die erste Sprache jedoch unbrauchbar.
12.2 Die T URING-Maschine (TM) Alan Mathison T URING1 ersann 1936 ein Automatenmodell, indem er statt des Kellers nur einen sequentiellen Speicher (wie bei EA) vorsah. Dieses Eingabeband ist (in der bekannten Weise) in unendlich viele aufeinanderfolgende Felder gegliedert. Jeder Feldinhalt kann gelesen oder überschrieben werden. Zu der aus anderen Modellen bekannten Lesefunktion des Kopfes tritt eine Schreibfunktion hinzu. Jedes Feld des Eingabebandes enthält genau ein Zeichen.
Alan T URING
Der Lese/Schreib-Kopf darf entweder um genau ein Feld nach links (L) oder nach
Lese/SchreibKopf
1 Alan
M.T URING: geb. 23.06.1912 in London, gest. 07.06.1954 in Wilmslow
222
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
rechts (R) bewegt werden. Alternativ darf er im entsprechenden Arbeitstakt auch seine aktuelle Position beibehalten, d.h. es findet keine Kopfbewegung statt (N). $
$
$
$
$
N
L
$
$
$
$
$
$
$
R
δ q 0 Abbildung 12.1: Aufbau einer TM
Berechenbarkeitstheorie
Schon während seiner Zeit bei Alonzo C HURCH (1903-1995) an der Princeton University (USA), wo er 1938 promovierte, erarbeitete T URING theoretische Grundlagen für automatische Rechenmaschinen. Die Ergebnisse dieser Überlegungen führten zu herausragenden Beiträgen zur Berechenbarkeitstheorie (theory of computation; computability). Damit war er (zusammen mit C HURCH) einer der ersten, die sich mit den Möglichkeiten bzw. absoluten Grenzen maschinellen Rechnens auseinandersetzten. Es ist zu beachten, dass es Mitte/Ende der dreißiger Jahre noch keinen Computer als physischen Gegenstand gab2 . Bei seinen Überlegungen ließ sich T URING von dem Vorhaben leiten, den geistigen Prozess des Rechnens mit Papier und Bleistift und dessen Leistungsfähigkeit („computing power“) abstrakt zu beschreiben. Heute finden Bleistiftrechnungen zwar kaum noch statt, aber man darf ersatzweise getrost an die Arbeit von Computerprogrammen denken.
(potentiell) unendliches Band endliche Zustandsmenge
Das Papier oder eben den Speicher zur Aufnahme der zu verarbeitenden Daten abstrahiert T URING zu einem (potentiell) unendlichen Band mit Zellen für jedes einzelne Zeichen. Wenn ein Mensch eine Berechnung ausführt, so nimmt sein Denken bestimmte Zustände, sog. „states of mind“, ein. Die Abstraktion mündet in eine endliche Zustandsmenge. Es wird angenommen, dass die Zustände nacheinander eingenommen werden und dass während des Berechnungsprozesses keine Interaktion des „menschlichen Rechners“ mit der Umwelt stattfindet3 .
Lese- und Schreibkopf
Das Schreiben und Wegradieren von Zeichen wird durch einen Lese/Schreib-Kopf abstrakt beschrieben. 2 Mit
der Entwicklung des Z3 im Jahre 1941 gilt Konrad Z USE (geb. 22.06.1910 in Berlin, Abitur in Hoyerswerda; gest. 18.12.1995 in Hünfeld bei Fulda) als Erfinder des Computers. 3 Beide Annahmen sind anfechtbar und Gegenstand gewisser wissenschaftlicher Debatten darüber, ob das Modell der T URING-Maschine wirklich alle Aspekte beliebiger Berechnungsprozesse erfasst.
12.3 Die Arbeitsweise einer DTM
223
Man kann sich leicht überlegen, dass die Ideen aus Abschnitt 12.1 von der Aufnahme zusätzlicher Stapel bzw. Warteschlangen in NKAs durch das T URINGMaschinenmodell mit erfasst werden. Hierfür ist es allerdings notwendig, die Arbeitsweise von TM zu kennen. Diese wird in Abschnitt 12.3 erläutert. Da die ksS von geringem praktischen Interesse sind, konzentrieren wir unsere Betrachtung auf TM, von denen wir wissen (s. Satz 12.1 auf S. 229), dass sie für die C HOMSKY-Typ-0-Sprachen „zuständig“ sind. Außerdem reicht es aus, wenn wir uns nur um deterministische TM, kurz: DTM, kümmern. Wie bei EA stellt sich nämlich heraus, dass NTM und DTM in ihrer Leistungsfähigkeit als Akzeptoren gleichwertig sind. Die folgende Definition präzisiert das Automatenmodell. Definition 12.1 Eine T URING-Maschine (TM) ist ein 7-Tupel M = (Q, Σ, Γ, δ , q0 , $, E). Q . . . endliche Menge von Zuständen Σ . . . Eingabealphabet Γ . . . Bandalphabet, wobei Σ ⊆ Γ\{$} δ . . . partielle Überführungsfunktion: Q × Γ → Q × Γ × {L, N, R}. Für eine nichtdeterministische TM – kurz: NTM – gilt: Q × Γ → ℘(Q × Γ × {L, N, R}). q0 . . . Anfangszustand, q0 ∈ Q $ . . . Bandvorbelegungszeichen, kurz: Blankzeichen, mit $ ∈ Γ \ Σ E . . . endliche Menge von Endzuständen, mit E ⊆ Q
12.3 Die Arbeitsweise einer DTM Jedes einzelne Feld des potentiell unendliche Band einer DTM enthält zunächst ein Blank- oder Vorbelegungszeichen. In Abbildung 12.2 ist das das Zeichen $. Vorbereitend wird das Eingabewort w, das ausschließlich aus Zeichen des Eingabealphabets Σ besteht, auf das Band geschrieben. Die Vorbelegungszeichen in den entsprechenden Feldern werden dadurch verdrängt. Der Lese/Schreib-Kopf befindet sich über dem Feld mit dem ersten Zeichen von w und die Maschine nimmt den Anfangszustand q0 ein. Wir nennen dies eine Anfangskonfiguration und schreiben dafür q0 w.
Blankzeichen Eingabealphabet Anfangskonfiguration
Es ist zu beachten, dass die Initialisierung der betrachteten DTM mit einem Eingabewort von außen (durch den handelnden Menschen) geschieht. Damit handelt es sich um einen externen Vorgang, der nicht zur Arbeitsweise der DTM gehört. Die partielle Überführungsfunktion δ δ : Q × Γ → Q × Γ × {L, N, R} ist das Herzstück einer DTM. Mit ihr wird die Aktion der Maschine in jedem
partielle Überführungsfunktion
224
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
$
$
$
h
e
N
L
l
l
o
$
$
$
$
R
δ q 0
Abbildung 12.2: TM mit Eingabewort
Arbeitstakt festgelegt. Diese Festlegung geschieht in Abhängigkeit - vom Zustand qi (qi ∈ Q), den die DTM gerade eingenommen hat, - und vom Zeichen k (k ∈ Γ), das sich in dem Feld der aktuellen Lese/SchreibKopf-Position befindet. δ (qi , k) = (q j , m, b), mit qi , q j ∈ Q, k, m ∈ Γ und b ∈ {L, N, R}, falls der Funktionswert (q j , m, b) überhaupt existiert, bestimmt die nächste Aktion der Maschine: 1. Die DTM stoppt, wenn δ (qi , k) nicht definiert ist. Man sagt auch: „Die Maschine stoppt per crash.“
Arbeitstakt
2. Die DTM geht aus qi in einen (vom aktuellen nicht unbedingt verschiedenen) Zustand q j über, schreibt das entsprechende Zeichen m in das aktuelle Feld und bewegt anschließend ggf. den Kopf um eine Position nach links bzw. rechts, gemäß b ∈ {L, N, R}. Wird eine DTM in der oben beschriebenen Anfangskonfiguration für ein bestimmtes Eingabewort gestartet, so wird in jedem einzelnen Arbeitstakt die entsprechende Aktion ausgeführt. 1. Dies wird fortgeführt, bis die DTM entweder (per crash) stoppt. 2. Anderenfalls läuft die DTM endlos weiter. Für den zweiten Fall gibt es drei Realisierungsmöglichkeiten: (a) unendlicher Rechtslauf, (b) unendlicher Linkslauf, (c) Endloszyklus, wobei die zum Zyklus gehörende „Taktlänge“ keine Rolle spielt. Beispiel 12.1 Wir betrachten die folgende sehr einfache DTM M1 = ({q0 , q1 , q2 }, {a, b, c}, {$, a, b, c, x}, δ , q0 , $, {q2 }), mit
12.4 Alternative TM-Definitionen δ q0 q1 q2
$ (q1 , $, N) -
a (q1 , x, R) (q1 , x, R) -
b -
225
c (q2 , x, L) -
x -
Die Überführungsfunktion kann auch als Graph dargestellt werden: $:$,N a:x,R
Start
q0
a:x,R
q1
c:x,L
q2
Für das Eingabewort aac ergibt sich folgende Verarbeitung: q0 aac xq1 ac xxq1 c xq2 xx.
Zur Protokollierung dieser Arbeitstaktsequenz haben wir die zugehörige Folge der Konfigurationen αqi β , mit α ∈ Γ∗ , qi ∈ Q und β ∈ Γ+ , beginnend mit der Anfangskonfiguration q0 w notiert. α bzw. β bestimmt den Bandinhalt links bzw. rechts von der Kopfposition, wobei der Feldinhalt der aktuellen Kopfposition das erste Zeichen von β bildet. Definition 12.2 Eine Konfiguration einer TM ist ein Wort aus Γ∗ ◦ Q ◦ Γ+ . Ein Konfigurationsübergang ⊆ (Γ∗ ◦ Q ◦ Γ+ ) × (Γ∗ ◦ Q ◦ Γ+ ) ist eine binäre Relation mit der oben kommentierten Definition. ∗ Eine Folge von Konfigurationsübergängen k1 k2 . . . kn kann verkürzt durch k1 kn angegeben werden.
Übung 12.1 Geben Sie nacheinander die Folgen der Konfigurationsübergänge für die Eingabewörter aacaaba, abaaca und aaa in der DTM aus Beispiel 12.1 an. Kommentieren Sie die Befunde.
12.4 Alternative TM-Definitionen Schaut man in die Fachliteratur, so finden sich zahlreiche alternative Definitionen für die TM. Man kann zeigen, dass sie (in Bezug auf die Beschreibung berechenbarer Probleme bzw. Akzeptanz von Sprachen und Sprachklassen) äquivalent sind. D.h., grundsätzlich ist es gleichgültig, welche Definition wir verwenden. Spezielle Vorlieben für die eine oder andere Definitionsvariante ergeben sich aus dem vorgesehenen Verwendungszweck, etwa für Beweise bestimmter Sätze. Die geschickte Definitionsauswahl kann die Arbeit dabei enorm erleichtern.
Konfiguration
226
Alternative äquivalente TM-Definitionen
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
Die folgende unvollständige Liste trägt lediglich Informationscharakter. • Reduziert man in Definition 12.1 die möglichen Kopfbewegungen auf links und rechts, also {L, R}, ergibt sich eine gleichwertige TM-Definition. Das Verweilen des Kopfes auf der alten Position kann durch schreibneutrales Zurückführen des Kopfes mit L bzw. R erreicht werden. • δ : (Q \ E) × Γ → Q × Γ × {L, R}. Der Ausschluss von Endzuständen im ersten Argument von δ macht Sinn, wenn zusätzlich gefordert wird, dass die Maschine bei Erreichen eines Endzustandes grundsätzlich stoppt. • Forderung: δ : Q × Γ → Q × Γ × {L, N, R} ist total. Hieraus folgt für den Stopp einer TM: (qe , k) → (qe , k, N), mit qe ∈ E und k ∈ Γ. Man spricht in diesem Falle von einem dynamischen Stopp. • Es gibt mehrere Spuren auf einem Band, die mit einem oder mit mehreren separaten Köpfen verwaltet werden. Dies ist gleichbedeutend mit der Vorstellung von der Mehrband-TM.
Linear beschränkte TM
• Das Band wird links beschränkt, so dass sich der Kopf nicht über das Feld des ersten Eingabewort-Zeichens zu Beginn der Arbeit hinausbewegen kann. Dies gilt aber nur für die Linksbewegung. Hinweis: Diese Definitionsvariante darf nicht mit der linear beschränkten TM (LBTM oder LBA), deren Kopfbewegung nur innerhalb des durch das Eingabewort belegten Bandbereiches erlaubt ist, verwechselt werden. Die (nichtdeterministischen) LBTM beschreiben genau die Menge der C HOMSKY-Typ-1-Sprachen.
12.5 Die DTM als Akzeptor w ∈ L(M)
w ∈ L(M)
Analog zu den bereits behandelten Automatenmodellen dienen auch DTM als Akzeptoren für Sprachen: Ein Wort w aus Σ∗ wird akzeptiert, wenn die auf w angesetzte DTM in endlich vielen Schritten in einem Endzustand stoppt. Dabei ist es völlig gleichgültig, was auf dem Band steht, wo sich der Lese-/Schreibkopf befindet und ob das Wort überhaupt ganz oder teilweise gelesen wurde. Aus der Lösung von Übungsaufgabe 12.1 (S. 225) geht hervor, dass es DTM gibt, die lediglich das erste Zeichen eines Eingabewortes lesen, um das (gesamte) Wort zu akzeptierten. Dies steht in strengem Gegensatz zu endlichen und Kellerautomaten, die das gesamte Eingabewort stets vollständig abtasten. Hält die Maschine nicht oder in einem Nicht-Endzustand, so wird das Eingabewort nicht akzeptiert. Unter Verwendung der oben eingeführten Begriffe Konfiguration und Konfigurationsübergang (s. Definition 12.2) kann die von einer DTM akzeptierte Sprache recht kompakt definiert werden.
12.5 Die DTM als Akzeptor
227
Definition 12.3 Sei M = (Q, Σ, Γ, δ , q0 , $, E) eine DTM. Dann heißt L(M) die von M akzeptierte Sprache, mit ∗
L(M) = {w ∈ Σ∗ | q0 w αqe β , mit α, β ∈ Γ∗ und qe ∈ E}. Übung 12.2 Überlegen Sie, warum eine DTM prinzipiell mit einem einzigen Endzustand qe auskommt. Welcher Vorteil ergibt sich ggf., wenn man mehrere Endzustände zulässt? Beispiel 12.2 Gesucht ist eine TM M2 für die Sprache L(M2 ) = {an bn cn | n ≥ 1}. Lösungsidee: Bei jedem Rechtslauf des Kopfes – beginnend mit dem ersten Zeichen des Eingabewortes – wird das jeweils zuerst gefundene a durch d, das jeweils zuerst gefundene b durch d und das jeweils zuerst gefundene c durch d ersetzt. Anschließend fährt der Kopf über eventuell vorhandene c’s bis an’s Wortende, um dabei sicherzustellen, dass keine von c verschiedenen Zeichen folgen. Danach wird der Kopf auf das am weitesten links stehende d zurückgeführt. (Die linke Begrenzung erkennt der Kopf durch Erreichen des Bandvorbelegungszeichens, so dass eine unbegrenzte Linksbewegung ausgeschlossen ist.) Am Ende stehen (neben den Vorbelegungszeichen) nur noch d’s auf dem Band. In diesem Falle wird das Eingabewort akzeptiert. Vollständige Definition einer DTM M2 für L(M2 ) = {an bn cn | n ≥ 1}: M2 = ({q1 , q6 , q2 , q3 , q4 , q5 }, {a, b, c}, {−, a, b, c, d}, δ , q1 , −, {q6 }) mit a:a,R d:d,R d:d,R
q2
b:b,R d:d,R
b:d,R
c:c,R
q3
a:d,R Start
q1
-:-,N
q6
c:d,R -:-,R
q5
-:-,L
q4
a:a,L b:b,L c:c,L d:d,L
L = {an bn cn | n ≥ 1}
228
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
Computerübung 12.1 Simulieren Sie M2 für mehrere Wörter über {a, b, c}, die zu L(M2 ) = {an bn cn | n ≥ 1} gehören, und auch für solche, die nicht zu L gehören. Computerübung 12.2 M3 = ({q0 , q1 , q2 , q3 , q4 , q5 , q6 }, {a, b, c}, {$, a, b, c, X,Y, Z}, δ , q0 , $, {q6 }), mit Y:Y,R a:a,R
Start
q0
X:X,R
q5
a:X,R
q1
Z:Z,R b:b,R
b:Y,R
q2
c:Z,R
c:c,L
q3
$:$,L
q6
$:$,R
Y:Y,L Z:Z,L a:a,L b:b,L
q4
X:X,L Y:Y,L Z:Z,L
wobei L(M3 ) = L(M2 ), ist eine alternative DTM für {an bn cn | n ≥ 1}. Beschäftigen Sie sich mit M3 und ergründen Sie die zugrunde liegende Konstruktionsidee.
12.6 DTM, NTM, LBTM und Sprachklassen Wie wir bereits festgestellt haben, gibt es drei verschiedene Arbeitsergebnisse von Akzeptor-DTM: 1. Die DTM stoppt und der erreichte Zustand ist ein Endzustand. 2. Die DTM stoppt und der erreichte Zustand ist kein Endzustand. 3. Die DTM stoppt nicht. (Unendlicher Rechtslauf, Linkslauf bzw. Endloszyklus) Definition 12.4 Eine Sprache L heißt rekursiv aufzählbar (semientscheidbar), wenn es eine DTM M gibt, mit L = L(M).
Die Elemente solcher Sprachen können algorithmisch effektiv erzeugt, aufgelistet oder eben im anschaulichen Sinne aufgezählt werden. Dies wird in der Berechenbarkeitstheorie genauer untersucht.
12.6 DTM, NTM, LBTM und Sprachklassen
229
Satz 12.1 Die Menge Lre = {L(M) | M ist eine DTM} aller rekursiv aufzählbaren Sprachen stimmt genau mit der Menge aller C HOMSKY-Typ-0-Sprachen überein. Beweis Einen entsprechenden Beweis findet man in der Literatur.
„re“ steht für „recursive enumerable“. Dabei handelt es sich um einen Begriff, der sich historisch etabliert hat. Dies gilt auch für den weiter unten verwendeten Begriff der „rekursiven Sprache“.
recursive enumerable
DTM, die für alle möglichen Eingabewörter w über dem zugehörigen Alphabet (akzeptierend: w ∈ L(M) oder ablehnend: w ∈ L(M)) stoppen, nennt man auch stets anhaltende DTM. Die DTM M2 aus Beispiel 12.2 besitzt diese Eigenschaft, M1 aus Beispiel 12.1 jedoch nicht. Wie Übungsaufgabe 12.1 erkennen lässt, stoppt M1 nicht, wenn M1 beispielsweise auf das Eingabewort a angewandt wird.
stets anhaltende DTM
Definition 12.5 Eine Sprache L heißt rekursiv (entscheidbar), wenn es eine stets anhaltende DTM M gibt, mit L = L(M).
Man kann zeigen, dass die Menge aller rekursiven (algorithmisch erkennbaren) Sprachen Lr eine Untermenge der Menge der rekursiv aufzählbaren Sprachen Lre ist, die Klasse der ksS jedoch vollständig enthält. LTyp 1 ⊂ Lr ⊂ Lre . Für die Feststellung, ob eine rekursiv aufzählbare Sprache entscheidbar ist oder nicht, gibt es kein allgemein anwendbares Verfahren. In der Berechenbarkeitstheorie wird gezeigt, dass es keinen Algorithmus geben kann, der diese Prüfung durchführt. Die Klasse der C HOMSKY-Typ-1-Sprachen wird durch linear beschränkte TM, kurz: LBTM, beschrieben, d.h. LLBT M = LTyp 1 . Bei LBTM handelt es sich dabei um eine eingeschränkte Form nichtdeterministischer4 TM (NTM), deren Kopf nur innerhalb des Bandbereiches, der zu Beginn der Abarbeitung durch das Eingabewort belegt wird, agieren, d.h. sich bewegen und schreiben, darf.
4 Es
ist bisher nicht bekannt, ob linear beschränkte DTM dafür ebenso ausreichen würden, d.h. ob LDLBT M = L(N)LBT M gilt oder nicht.
LBTM
230
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
Beispiel 12.3 Die weiter oben behandelte Sprache L = {an bn cn | n ≥ 1} ist eine Typ-1-Sprache. Sie kann durch eine ksG G = ({A, B, S}, {a, b, c}, P, S) mit P = {S
→
abc | aAbc
Ab
→
bA
Ac
→
Bbcc
bB
→
Bb
aB
→
aa | aaA}
definiert werden. Rückblickend stellen wir fest, dass die in Beispiel 12.2 angegebene DTM M2 den Eingabewort-Bandbereich nicht verlässt und folglich leicht zu einer LBTM umkonstruiert werden könnte.
Da die Arbeitsweise von NTMs mit DTMs simuliert werden kann, gilt zusammenfassend: LNT M = LDT M = LTyp 0 = Lre . Abbildung 12.3 fasst die relevanten Sprachklassen zusammen. - Überabzählbar unendliche Mengen Bsp.: Menge aller Wortmengen über einem Alphabet - Abzählbare Mengen Bsp.: Menge der wahren arithmetischen Formeln - rekursiv aufzählbare Spr. = DTM/NTM-Spr. = Chomsky-Typ-0-Spr. Bsp.: Menge aller Algorithmen, Halteproblem - rekursive Sprachen = Sprachen der stets stoppenden TM - ksS = LBTM-Sprachen = Chomsky-Typ-1-Sprachen Bsp.: L = {an bn cn | n ∈ N} - kfS = NKA-Sprachen = Chomsky-Typ-2-Sprachen Bsp.: L = {an bn | n ∈ N} - dkfS = DKA-Sprachen Bsp.: Palindrome mit markierter Wortmitte - rS = regExp-Sprachen = Chomsky-Typ-3-Sprachen Bsp.: Zahlwörter - endliche Sprachen (alle Elemente angebbar)
Abbildung 12.3: Hierarchie der relevanten Sprachklassen (ohne LR- und LL-Sprachen)
12.7 TM in Komplexitäts- und Berechenbarkeitstheorie
231
12.7 TM in Komplexitäts- und Berechenbarkeitstheorie In der Komplexitätstheorie werden TM (DTM und NTM) als Modelle verwendet, um den Aufwand algorithmischer Berechnungen abstrakt, d.h. z.B. Prozessorunabhängig, zu definieren. Algorithmische Operationen werden (wenigstens gedanklich) durch die atomaren TM-Instruktionen ausgedrückt. Ein Auswandsmaß ergibt sich dann aus der Anzahl der zur Ausführung der Operationen jeweils erforderlichen TM-Schritte.
Komplexitätstheorie
Während sich die Komplexitätstheorie mit der Effizienz von Algorithmen beschäftigt, gehören Fragen nach der Entscheidbarkeit von Sprachen und Lösbarkeit von Problemen in die Berechenbarkeitstheorie. TM haben dabei eine fundamentale Bedeutung, da sie zur Präzisierung der dort verwendeten Begriffe dienen.
Effizienz von Algorithmen Berechenbarkeitstheorie
Betrachtet man den auf dem Band nach dem Stopp einer TM hinterlassenen Inhalt in einem verabredeten Bereich als Funktionswert, so kann man TM zur Berechnung n-stelliger Wortfunktionen (Σ∗ )n → Γ∗ (s. Abschnitt 12.8) einsetzen. Der Begriff der Berechenbarkeit wird durch die T URING-Berechenbarkeit präzisiert. Dies macht die eigentliche Leistung T URINGs deutlich und hebt ihn auf die Stufe der bedeutendsten Informatiker überhaupt. Um das Wortproblem für Sprachklassen zu bearbeiten, bedarf es einer TM, die in der Lage ist, die Arbeitsweise jeder anderen TM (der betrachteten Klasse) zu simulieren. Eine TM mit dieser Eigenschaft nennt man universelle TM, kurz: UTM. Wohlgemerkt: Dabei handelt es sich um definitionstreue TM (s. Definition 12.1), nicht etwa um einen neuen Typ. Auf dem Band erwartet eine UTM U zwei Eingabewörter, zwischen denen genau ein Blankzeichen $ steht:
T URINGBerechenbarkeit
UTM
cod(M)$w. M ist die TM, deren Verhalten von U simuliert werden soll. cod(M) ist das zu M gehörende Wort. Die Überführungsfunktion von U wird also so konstruiert, dass sie die Überführungsfunktion δM von M, die sich innerhalb des Wortes cod(M) befindet, abtastet und interpretiert. U(cod(M), w) liefert dann das gleiche Resultat, wie M(w), und zwar für alle w ∈ Σ∗ . Das beschriebene Verhalten der UTM ist dem Prinzip der bis heute aktuellen VON N EUMANN-Rechner verwandt. Da die Angabe einer UTM angesichts der doch sehr elementaren TM-Instruktionen überaus aufwendig ist, wollen wir hier darauf verzichten. Grundsätzlich ist es aber möglich, bei Vorgabe einer (beliebigen) Kodierungsvorschrift cod für M eine solche UTM Ucod anzugeben. Bei der Definition der Überführungsfunktion einer UTM muss die zugrunde liegende Kodierung von M berücksichtigt werden.
Arbeitsweise einer UTM
VON -
N EUMANNRechner
232
Effizienz von Algorithmen zur Lösung der Wortprobleme
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
Für Typ-0-Sprachen ist das Wortproblem bekanntlich nicht entscheidbar. Der für die Entscheidung von Typ-1-Sprachen erforderliche exponentielle Aufwand, der durch die Erzeugung aller Satzformen (Simultanableitungen) bis zur Länge des Eingabewortes verursacht wird, ist für die Praxis uninteressant. Während für kfS der sog. CYK5 -Algorithmus das Wortproblem mit einer Zeiteffizienz in O(n3 ) löst, geschieht dies für deterministisch kfS – ebenso wie für reguläre Sprachen – mit linearem Aufwand, d.h. in O(n).
12.8 TM zur Berechnung von Funktionen partielle Wortfunktionen
TM berechnen partielle Wortfunktionen der Form f : (Σ∗ )n → Γ∗ . Dass es sich im Allgemeinen um partielle Funktionen handelt, folgt aus der Arbeitsweise einer TM, die ggf. für bestimmte Argumente nicht anhält. Partielle nstellige Wortfunktionen sind die allgemeinste Funktionsklasse überhaupt. Sie umfassen die totalen Funktionen als eine echte Teilmenge. Die n Argumente w1 , w2 , . . . , wn , mit wi ∈ Σ∗ , werden durch jeweils genau ein Blankzeichen $ untereinander getrennt auf das Band geschrieben. Für einen Bandeintrag der Form w1 $w2 $ . . . $wn
Funktionswert
berechnet die darauf angesetzte TM M den Funktionswert M(w1 , w2 , . . . , wn ), falls dieser existiert. Nach dem Stopp von M wird das zugehörige Ausgabewort vom Band abgelesen: Der Lese/Schreib-Kopf steht auf dem ersten Zeichen dieses Wortes, das sich von links nach rechts bis zum Feldinhalt unmittelbar vor dem ersten auftretenden Blankzeichen erstreckt. Ebenso, wie das Aufbringen von Eingabeworten (Argumenten), ist das Ablesen und Dekodieren des Ausgabewortes (Funktionswertes) ein externer Vorgang, der nicht zur TM-Definition gehört. Beispiel 12.4 Nachfolger-Maschine: Eine Nachfolger-Maschine ist eine TM, die zu einer in unärer Darstellung auf das Band geschriebenen Zahl deren Nachfolger berechnet und diesen in unärer Darstellung auf dem Band hinterlässt. Eine Lösungsidee besteht darin, den Lese/Schreib-Kopf schrittweise nach rechts zu verschieben, wobei sämtliche Vorkommen des Eingabealphabetzeichens I reproduziert werden. In das (von links her) erste Feld, mit einem Blankzeichen $ wird I geschrieben. Danach fährt der Kopf an die Anfangsposition zurück und bleibt dort stehen.
5 C OOKE ,
YOUNGER , K ASAMI
12.8 TM zur Berechnung von Funktionen
233
M = ({q0 , q1 , q2 }, {I}, {$, I}, δ , q0 , $, {q2 }) mit I:I,R
Start
I:I,L
q0
$:I,L
q1
$:$,R
q2
Eine alternative Lösungsidee ist überraschend kurz: Der Kopf geht einfach einen Schritt nach links, schreibt dort I und bleibt auf dieser Position stehen. M = ({q0 , q1 }, {I}, {$, I}, δ , q0 , $, {q1 }) mit I:I,L
Start
q0
$:I,N
q1
NachfolgerMaschine Übung 12.3 Erproben Sie beide Lösungen. Verwenden Sie auch „Extrem-Eingaben“: das leere Wort und I.
Übung 12.4 Geben Sie eine DTM mit Σ = {I} an, die den Vorgänger einer beliebigen natürlichen Zahl n (n > 0) berechnet. Die Zahlen (Eingabe, Ergebnis) sollen unär als Worte über {I} dargestellt werden.
Beispiel 12.5 Busy beaver: Ein fleißiger Biber (busy beaver) ist eine DTM M = ({q0 , q1 , . . . , qn−1 , qn }, {0}, {0, 1}, δ , q0 , 0, {qn }), die auf einem mit Nullen (0) vorbelegten Eingabeband nach endlich vielen Schritten stoppt und eine maximale Anzahl von Einsen (1) hinterlässt. (DTM, die nicht halten, können durchaus mehr Einsen schreiben.) Dabei sollen alle Einsen gezählt werden, auch wenn sie nicht zu einer zusammenhängenden Kette gehören, also durch Nullen unterbrochen wurden. Außerdem spielt die Position des Kopfes nach dem Halt keine Rolle. Für n = 1 ist die Lösung einfach zu sehen: Der Biber produziert genau eine Eins auf dem Band und „legt sich wieder schlafen“.
busy beaver n=1
M = ({q0 , q1 }, {0}, {0, 1}, δ , q0 , 0, {q1 }) mit Start
q0
0:1,N
q1
Auch für n = 2 kann die Lösung leicht verifiziert werden. M = ({q0 , q1 , q2 }, {0}, {0, 1}, δ , q0 , 0, {q2 }) mit
n=2
234
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
0:1,R 1:1,L Start
q0
q1
1:1,N
q2
0:1,L
Nach sechs Schritten hat der Biber 4 Einsen produziert. Übung 12.5 Simulieren Sie die fleißigen Biber. Verwenden Sie die o.g. Definitionen und lassen Sie die Eingabe leer.
R ADO-Funktion
Für n > 0 entsteht auf diese Weise die R ADOsche6 Funktion n → E(n), wobei E(n) die maximale Einsen-Anzahl ist.
n 1 2 3 4 5 6 .. . 12
E(n) 1 4 6 13 501(?) 1915(?) 2075(?) .. . 40964 ··
4096·
6 · 4096
Bemerkungen
(1962: Tibor Rado, Shen Lin) (1973: Bruno Weimann) (1983: Schult) (1984: Uhing) (Schult, mit spezieller Hardware)
rechnerisch: Die Punkte stehen für insgesamt 162 mal 4096.
Manche Funktionswerte in der Tabelle sind mit einem Fragezeichen markiert: Man vermutet, dass die bisher gefundene größtmögliche Anzahl von Einsen doch noch übertroffen werden kann. Übung 12.6 Schlagen Sie die R ADOsche Funktion nach. Vermutlich werden Sie weitere und auch von der obigen Tabelle leicht abweichende Angaben finden. Übung 12.7 Versuchen Sie selbst, bessere Biber zu bauen. Das ist allerdings ziemlich schwierig.
Beim Experimentieren mit busy-beaver-TM gelingt es nicht immer, die eigentliche Aufgabenstellung zu erfüllen. Mitunter entstehen dabei recht „merkwürdige Biber“, wie etwa die folgende, spaßigerweise als „Wissenschaftsbiber“ bezeichnete DTM. 6 Tibor
R ADO: ungarischer Mathematiker: geb.: 02.06.1895 in Budapest; gest. 12.12.1965 in New Smyrna Beach/Florida
12.8 TM zur Berechnung von Funktionen
235
Ein Wissenschaftsbiber ist unheimlich fleißig und strampelt sich ab, aber produziert meist nicht viel: Die folgende TM macht 187 Schritte, rückt dabei 8 Felder vor, aber schreibt keine einzige Eins. M = ({z0 , z1 , z3 , z4 , z5 , z6 }, {0}, {0, 1}, δ , z0 , 0, {z3 })) mit 1:0,L
Start
z0
0:0,R
0:1,L
z1
1:0,N
z3
0:0,R
1:1,L z4
0:1,R
z5
z6 0:1,R
1:0,L
1:1,R
Fragestellungen „rund um“ diese Funktion sind vor allem bei InformatikOlympiaden beliebt. In der Berechenbarkeitstheorie lernt man, dass die (wohldefinierte) R ADO-Funktion nicht berechenbar ist. Übung 12.8 Entwickeln Sie eine DTM, die zu zwei in unärer Darstellung („Bierdeckelnotation“) auf das Band geschriebenen Wörtern für die Zahlen a und b deren Summe a + b berechnet, indem sie das jeweilige Resultatwort in unärer Darstellung auf dem Band hinterlässt. Entwerfen Sie zunächst einen Plan auf Konzeptpapier!
TM für zusammengesetzte Funktionen erhält man durch Kopplung der zugehörigen einfacheren TM. Dies hier einzuüben, wäre jedoch eine „brotlose Kunst“. Übung 12.9 Entwickeln Sie eine DTM mit Σ = {a, b, c}, die als Bandlöschmaschine arbeitet. Eine Bandlöschmaschine entfernt ein Eingabewort, indem es die Felder mit dem Blankzeichen beschreibt. Übung 12.10 Konstruieren Sie eine DTM, die die Sprache L = {w | w ∈ {a, b}∗ und w = a2n |n ≥ 0} akzeptiert.
Kopplung von TM
236
12 T URING-Maschine (TM) und C HOMSKY-Typ-0/1-Sprachen
Übung 12.11 Geben Sie für jeder der folgenden Sprachen über {a, b} eine zugehörige DTM an. (a) aba∗ b (b) {w | w hat eine gerade Länge.} (c) {an bm | n ≥ 1 und m = n} (d) {w | w enthält gleich viele a’s und b’s.} Übung 12.12 Eine natürliche Zahl ist entweder gerade oder ungerade. Finden Sie eine DTM, die die Paritätsfunktion für natürliche Zahlen n (n ≥ 0) berechnet.
0, n ist gerade f (n) = 1, n ist ungerade
Literaturverzeichnis [Aho88] A HO , A. V., S ETHI -R. U LLMAN J. D.: Compilers, Principles, Techniques, and Tools. Addison Wesley, Reading, 1988. [Aho99] A HO , A. V., S ETHI -R. U LLMAN J. D.: Compilerbau Teil 1. Oldenbourg Verlag, München, Wien, 2. durchges. Aufl., 1999. [Alb90]
A LBERT, J., OTTMANN T H .: Automaten, Sprachen und Maschinen für Anwender. BI-Wissenschaftsverlag, Mannheim, Wien, Zürich, 1990.
[And06] A NDERSON , J. A.: Automata Theory with Modern Applications. Cambridge University Press, Cambridge, 2006. [Ast03]
A STEROTH , A., BAIER C.: Theoretische Informatik. Pearson Studium, München, 2003.
[Bac75]
BACHMANN , P.: Grundlagen der Compilertechnik. BSG B.G. Teubner Verlagsgesellschaft, Leipzig, 1975.
[Bro89]
B ROOKSHEAR , J.G.: Formal Languages, Automata, and Complexity. The Benjamin/Cummings Publishing Company, Inc., Redwood City, 1989.
[Buc84] B UCHER , W., M AURER H.: Theoretische Grundlagen der Programmiersprachen: Automaten und Sprachen. BI-Wissenschaftsverlag, Mannheim, Wien, Zürich, 1984. [Coh97] C OHEN , D. I. A.: Introduction to Computer Theory. John Wiley & Sons, Inc., New York, 2nd ed., 1997. [Eir00]
E IRUND , H., M ÜLLER B.-S CHREIBER G.: Formale Beschreibungsverfahren der Informatik. B.G. Teubner Verlag, Stuttgart, Leipzig, Wiesbaden, 2000.
[Flo96]
F LOYD , R., B EIGEL R.: Die Sprache der Maschinen. Internat. Thomson Publishing, Bonn, Albany, Attenkirchen, 1996.
[Grä07]
G RÄTZER , G.: More Math Into LATEX. Springer Verlag, New York, 4th Auflage, 2007.
[Güt99]
G ÜTIG , R. H., E RWIG -M.: Übersetzerbau: Techniken, Werkzeuge, Anwendungen. Springer-Verlag, Berlin, Heidelberg, New York, 1999.
[Gum06] G UMM , H., S OMMER M.: Einführung in die Informatik. Oldenbourg Verlag, München, Wien, 7., vollst. überarb. Aufl., 2006. [Hed07] H EDTSTÜCK , U.: Einführung in die Theoretische Informatik. Oldenbourg Verlag, München, 4. Aufl., 2007. [Hop90] H OPCROFT, J. E., U LLMAN -J.: Einführung in die Automatentheorie, formale
238
Literaturverzeichnis
Sprachen und Komplexitätstheorie. Addison-Wesley Verlag, Bonn, Reading u.a., 2. Aufl., 1990. [Hop01] H OPCROFT, J. E., M OTWANI -R. U LLMAN J. D.: Introduction to Automata Theory, Languages, and Computation. Addison Wesley, Boston, 2nd ed., 2001. [Hro01]
H ROMKOVI Cˇ , J.: Algorithmische Konzepte der Informatik. B.G. Teubner Verlag, Stuttgart, Leipzig, Wiesbaden, 2001.
[Hro07]
H ROMKOVI Cˇ , J.: Theoretische Informatik. Teubner Verlag, Wiesbaden, 3., erw. u. überarb. Aufl., 2007.
[Job92]
J OBST, F.: Compilerbau: Von der Quelle zum professionellen Assemblertext. Hanser Verlag, München, Wien, 1992.
[Kel95]
K ELLEY, D.: Automata and Formal Languages. Prentice-Hall, Inc., Englewood Cliffs, 1995.
[Ker90]
K ERNER , I.: Informatik. Deutscher Verlag der Wissenschaften, Berlin, 1990.
[Kop88] KOPP, H.: Compilerbau: Grundlagen, Methoden, Werkzeuge. Hanser Verlag, München, Wien, 1988. [Law04] L AWSON , M. V.: Finite Automata. Chapman & Hall/CRC, Boca Raton, 2004. [Loe87]
L OEPER , H., JÄKEL H.-J. OTTER W.: Compiler und Interpreter für höhere Programmiersprachen. Akademie-Verlag, Berlin, 1987.
[Mar91] M ARTIN , J. C.: Introduction to Languages and the Theory of Computation. McGraw-Hill, Inc., New York u.a., 1991. [Mod88] M ODROW, E.: Automaten, Schaltwerke, Sprachen. 1988.
Dümmler Verlag, Bonn,
[MW71] M C K EEMAN , W. M., H ORNING -J. J. und D. B. W ORTMAN: A Compiler Generator. Prentice-Hall, Englewood Cliffs, NJ, 1971. [Par02]
PARKES , A. P.: Introduction to Languages, Machines and Logic. SpringerVerlag, London, Berlin, Heidelberg, 2002.
[Rod06] RODGER , S., F INLEY T. W.: JFLAP: An Interactive Formal Languages and Automation Package. Jones and Bartlett Publishers, Inc., Sudbury, 2006. [San92]
S ANDER , P., S TUCKY W. H ERSCHEL R.: Automaten, Sprachen, Berechenbarkeit. B.G. Teubner Verlag, Stuttgart, 1992.
[Sch91]
S CHREINER , A. T., F RIEDMAN G.: Compiler bauen mit UNIX: Eine Einführung. Hanser Verlag, München, Wien, 2. Aufl., 1991.
[Sch92a] S CHMITT, F. J.: Praxis des Compilerbaus. Hanser Verlag, München, Wien, 1992. [Sch92b] S CHÖNING , U.: Theoretische Informatik kurz gefasst. BI-Wiss.-Verlag, Mannheim, Leipzig, Wien, Zürich, 1992. [Sch02]
S CHÖNING , U.: Ideen der Informatik: Grundlegende Modelle und Konzepte. Oldenbourg Verlag, München, Wien, 2002.
Literaturverzeichnis
239
[Sip97]
S IPSER , M.: Introduction to the Theory of Computation. PWS Publishing Company, Boston, 1997.
[Soc06]
S OCHER , R.: Theoretische Grundlagen der Informatik. Hanser Verlag, München, 3., akt. u. erw. Aufl., 2006.
[Ste88]
S TETTER , F.: Grundbegriffe der Theoretischen Informatik. Springer-Verlag, Berlin, Heidelberg, New York, 1988.
[Sud97]
S UDKAMP, T H . A.: Languages and Machines. Addison Wesley Longman, Inc., Reading, 2nd ed., 1997.
[Vos06]
VOSSEN , G., W ITT K.-U.: Grundkurs Theoretische Informatik. Friedr. Vieweg & Sohn Verlag, Wiesbaden, 4., verb. Aufl., 2006.
[Wag94] WAGNER , K.: Einführung in die Theoretische Informatik. Springer-Verlag, Berlin, Heidelberg, New York, 1994. [Wat00] WATT, D., B ROWN D.: Programming Language Processors in Java: Compilers and Interpreters. Prentice Hall, Harlow Essex, 2000. [Weg96] W EGENER , I.: Kompendium Theoretische Informatik - eine Ideensammlung. B.G. Teubner Verlag, Stuttgart, 1996. [Weg05] W EGENER , I.: Theoretische Informatik: Eine algorithmenorientierte Einführung. B.G. Teubner Verlag, Wiesbaden, 3., überarb. Aufl., 2005. [Wil97]
W ILHELM , R., M AURER D.: Übersetzerbau: Theorie, Konstruktion, Generierung. Springer-Verlag, Berlin u.a., 2., überarb. u. erw. Aufl., 1997.
[Win02] W INTER , R.: Theoretische Informatik. Oldenbourg Verlag, München, Wien, 2002. [Wir86]
W IRTH , N.: Compilerbau. B.G. Teubner Verlag, Stuttgart, 4., durchges. Aufl., 1986.
[Wät94] WÄTJEN , D.: Theoretische Informatik. Oldenbourg Verlag, München, 1994.
Sachverzeichnis Ableitungsbaum, 15 Abstraktion, 2 action, 192 Akzeptanzverhalten EA, 64 Akzeptor, 61 Alphabet, 17 AST, 12 AtoCC, 4 Attribute, 199 ererbte, 199 synthetisierte, 199 Ausdruck regulärer, 1, 105, 106 Automat abstrakter, 3, 61 endlicher, 63 mit Ausgabe, 3 nichtdet. endlicher, 71 zellularer, 104 Backtracking, 34 Backus-Naur-Form, 11 Bandlöschmaschine, 235 Berechenbarkeitstheorie, 2, 222 Bootstrapping, 53 Bottom-up-Analyse, 59, 187 Brute force, 40 busy beaver, 233 Chart, 143 Chart-Parser, 143 Chomsky, Noam, 36 Chomsky-Hierarchie, 36 CNF, 156 Compiler, 3, 14, 46, 47 inkrementelle, 47 Compilerbau, 6 Compilergenerator, 3 Cross-Compiler, 54, 57
CYK, 156 dangling-else-Problem, 36, 182 DEA, 63 DTD, 44 dynamisches Programmieren, 142 Ealey-Algorithmus, 34 Earley-Parser, 143 EBNF, 11 Effizienz, 2, 231 Eingabevalidierung, 1, 118 Endzustand, 64 Entscheidungsproblem, 41 ε-Hülle, 91 ε-freie Regeln, 40 Falle, 65 FIRST, 165 FOLLOW, 167 goto, 192 Grammatik, 5, 28 äquivalente, 35 kontextfreie, 29, 37 kontextsensitive, 37 mehrdeutige, 35 reduzierte, 58 reguläre, 37 unbeschränkte, 37 Grammatiktransformationen, 153 Handle, 190 inhärent mehrdeutig, 36 Interpreter, 47, 208 JIT-Compiler, 48 Keller, 127 Kellerautomat, 120
242
Kettenregeln, 154, 155 kfG, 29 Komplexitätstheorie, 2, 231 Komposition, 7 Konfigurationenfolge, 65, 130 Konkatenation, 21 Kopplung von Turingmaschinen, 235 Längenmonotonie, 38 LALR(1)-Sprachen, 195 LALR-Sprachen, 194 Lesekopf, 63 Lex, 196 lex, 3 Lexem, 58 Linguistik, 5 Linksableitung, 32 Linksfaktorisierung, 185 LL(1)-Forderung 2, 167 LL(1)-Forderung 1, 165 LL(1)-Parser, 164 LL(1)-Sprachen, 164 LOGO, 49 lookahead, 161 Lookahead-LR(1)-Sprachen, 195 LR(k)-Sprachen, 187 LR(1)-Sprachen, 194 LR-Parsetabelle, 191 Maschine endliche, 100 virtuelle, 47, 51 M EALY, 100 mehrdeutig, 35 Mehrdeutigkeit syntaktische, 14 Mehrdeutigkeit, semantische, 5 Memoizing, 170 Menge überabzählbar unendlich, 24 abzählbar unendlich, 23 MiniJavaScript, 8 Minimalautomat, 80 M OORE, 100 Nachfolgermaschine, 233 NEA, 71 Nichtterminale, 11, 28
Sachverzeichnis
unnütze, 154 Notensprache, 207 Operatorbaum, 14 Ordnung längenlexikografische, 23 Parsergenerator, 46, 120, 172 Parsergeneratoren, 152 Parsing, 14 Pattern matching, 27, 115 Petri-Netz, 104 Phrasenstrukturgrammatik, 38 PL/0, 172 pop, 127 POSIX, 116 Postscript, 49, 202 Potenzmenge, 71 Pragmatik, 5 Produktion, 11 Produktionen, 28 Programme, 6 Protokoll, 6 Pumping Lemma, 96 für kfS, 157 push, 127 Quellsprache, 14, 47 Rückwärtsreferenz, 116 Rechtsableitung, 32 Recognizer, 45 reduce, 188 reduce/reduce-Konflikt, 194 Regel, 11 Regeln, 28 semantische, 199 RegExp Edit, 110 rekursiv aufzählbar, 228 rekursiver Abstieg, 171 Rucksackproblem, 143 S-Attribute, 199 Sackgasse, 34 Satz von K LEENE, 108 Satzform, 28 Satzsymbol, 28 Scanner, 57
243
Scanner-Beschreibung, 122 Scanner-Simulation, 122 Scannergenerator, 120 Semantik, 5 Semi-Thue-System, 38 Semiotik, 5 shift, 188 shift/reduce-Konflikt, 194 Simultanableitung, 40 SLR(1)-Sprachen, 194 SLR-Sprachen, 194 Spielkonsole, 54 Spitzensymbol, 28 Sprachdesign, 7 Sprache, 25 akzeptierte, 65 deterministisch-kontextfrei, 161 formale, 6, 17 kontextfreie, 33 rekursiv aufzählbar, 229 Sprachklassen, 37 Stapel, 127 stark-LL(k)-Sprache, 163 Startsymbol, 28 SVG, 49, 214 Syntaktik, 7 Syntax, 5 abstrakte, 12 konkrete, 7 Syntaxanalyse, 3, 14 prädiktive, 161 Syntaxbaum abstrakter, 12 Systeme eingebettete, 54 T-Diagramm, 49 tabellengesteuerte Analyse, 178 TDiag, 49 Terminale, 11, 28 Token, 58 Token-Präfix, 122 Tokenklasse, 122 Tokenliste, 58 top of stack, 127 Top-down-Analyse, 59, 162 Top-down-Syntaxanalyse, 161
Transcompiler, 48 Translation multi-stage, 51 trap state, 65 Trojaner, 53 Turing, 221 Turing-Berechenbarkeit, 231 Turingmaschine, 221 linear beschränkte, 226 universelle, 231 Turtle, 49 Überführungsfunktion, 63 erweiterte, 66, 73 Übergang spontaner, 90, 130 Übersetzung syntaxgesteuerte, 199 unlösbar absolut, 2 praktisch, 2 VCC, 121, 196 Verkettung, 21 von-Neumann-Rechner, 231 Vorausschau, 161 Vorausschauzeichen, 190 Wort, 16, 17, 19 leeres, 19 Wortbildung, 20 Wortlänge, 20 Wortmenge, 21 Wortproblem, 42 XML, 44, 119 XML-Parser, 45 Yacc, 196 yacc, 3 Zeichen, 17 Zeichenroboter, 48 Zielsprache, 14, 47 Zustand, 62 Zustandsautomat, 60 Zuweisung, 7