You are on page 1of 161

Compilerbau

Skriptum zur Vorlesung


Studiengang Informatik
Prof. Dr. Winfried Bantel
Sommersemester 2011
Stand 23. Marz 2011
Inhaltsverzeichnis
1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.1 Der Komplex Formale Sprachen - Automatentheorie - Compilerbau . . . . . . 13
1.2 Beispiele f ur Compilerbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
1.3 Begrisbildung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2 Die Programmiersprache PL/0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.1 Die Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.2 Programmbeispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.3

Ubungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
3 Wiederholung Automatentheorie und Formale Sprachen . . . . . . . . . . . . . 19
3.1 Formale Sprachen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1.2 Notationsformen von Grammatiken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.1.3 Notation in diesem Script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.1.4 Grammatiken und Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2 Endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2.1 Grundbegrie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
3.2.2 Modell eines erkennenden Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.2.3 Darstellungsformen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.2.4 Ein einf uhrendes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
3.2.5 Endliche Automaten und Formale Sprachen. . . . . . . . . . . . . . . . . . . . . . . 27
3.2.6 Grundalgorithmus eines Endlichen Automaten . . . . . . . . . . . . . . . . . . . . 27
3.3 Kellerautomaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.4

Ubungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
4 Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.1 UPN. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2 Scanner und Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.2.1 Zusammenspiel von Scanner und Parser . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4 Inhaltsverzeichnis
4.3 Primitive Scanner - zeichenbasiert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.4 Primitive Scanner - wortbasiert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.4.1 Prinzipien eines Scanners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.4.2 Implementierung von Scannern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.4.3 Fehlerbehandlung bei Scannern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.5 Bootstrapping - Das Henne-Ei-Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.6 Regulare Ausdr ucke. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.7 Regulare Ausdr ucke in C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
4.8

Ubungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
5 Endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.1 Grundbegrie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.2 Darstellungsformen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2.1 Tabellarische Darstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.2.2 Graphische Darstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.3 Algorithmus eines endlichen Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.4 Ein einf uhrendes Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.4.1 Deterministische endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.4.2 Nichtdeterministische endliche Automaten . . . . . . . . . . . . . . . . . . . . . . . . 43
5.5 Endliche Automaten mit Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.5.1 Moore-Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
5.5.2 Mealey-Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.6 Anwendungen von endlichen automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.6.1 Erkennen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.6.2 Suchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.6.3 Scannen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.7

Ubungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6 Keller-Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.2 Native Konstruktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6.3 LL-Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.3.1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.3.2 FIRST und FOLLOW . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.3.3 Programm-gesteuertes LL(1)-Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.3.4 LL-Parsing mit Stackverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
6.3.5 Elimination der Links-Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.3.6 Tabellen-gesteuertes LL-Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
6.4 LR-Parsing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
6.5 Operator-Prioritats-Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Inhaltsverzeichnis 5
7 Symboltabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.2 Methoden der Symboltabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
7.3 Aufbau eines Symboltabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
7.4 Fehlermoglichkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7.5 Namens-Suche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
7.6 Eine Symboltabelle auf Basis der C++-STL-Map . . . . . . . . . . . . . . . . . . . . . . . . 91
8 Zwischencode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8.1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8.2 AST f ur Terme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
8.3 AST f ur PL/0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
8.4 Neu - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
8.4.1 Die Knotenarten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
9 Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.1 Einf uhrung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.2 G ultigkeitsbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.3 Statische Speicherverwaltung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
9.4 Dynamische Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
9.4.1 Methoden im Hauptspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
9.5 Statische vs. Dynamische Speicherverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
10 Code-Optimierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.1 Optimierung der Kontrollstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.2 Optimierung von Spr ungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.2.1 Optimierung von NOPs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.2.2 Eliminierung unnotiger Spr unge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
10.3 Optimierung von Ausdr ucken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
10.4 Optimierung des Speicherzugris . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
11 Code-Erzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
11.1 Interpretierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
11.2 Assembler-Erzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
11.2.1 Der Emitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
11.2.2 Ausdr ucke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
11.2.3 if-Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
11.2.4 if-else-Statement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.3 Schleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.4 Variablenzugri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
11.5 Funktionsaufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
11.6 Spr unge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
6 Inhaltsverzeichnis
12 Generator-Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
12.1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
12.2 Generator-Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
12.3 Lex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
12.3.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
12.3.2 Grundaufbau einer Lex-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
12.3.3 Denitionsteil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
12.3.4 Regelteil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
12.3.5 Regulare Ausdr ucke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
12.3.6 Lex-Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
12.3.7 Lex-Funktionen und -Makros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
12.3.8 Startbedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
12.3.9 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
12.4 Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
12.4.1 Allgemeines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
12.4.2 Grundaufbau einer Yacc-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
12.4.3 Denitionsteil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128
12.4.4 Yacc-Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
12.5 Konikte in Yacc-Grammatiken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
12.5.1 Beispiele . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
A Der PL/0-Modellcompiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
A.1 Die Module des Compilers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
A.2 Quellcode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
B ASCII-Tabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157
C Abk urzungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
Abbildungsverzeichnis
1.1 Phasen eines Compilers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1 Hierarchie der Sprachen nach Chomsky . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.2 Syntaxbaum Die Katze schnurrt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.3 Syntaxbaum Der Hund jagt die Katze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
3.4 Automat zur Zahlenerkennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.1 Taschenrechner HP 12C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
4.2 Zusammenspiel zwischen Scanner und Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.1 Modell eines endlichen Automaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
5.2 Algorithmus eines endlichen Automaten - Stop bei Endezustand . . . . . . . . . . . . 40
5.3 Algorithmus eines endlichen Automaten - Stop bei Eingabeende . . . . . . . . . . . . 41
5.4 Modell Moore- und Mealey-Automat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
5.5 Einfache Textsuche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.6 Aloisius-Scanner als Mealey-Automat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
6.1 Endlicher Automat f ur L = a
n
b
n
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.2 Modell eines Kellerautomaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
6.3 Syntaxbaum bei linksrekursionsfreier Grammatik . . . . . . . . . . . . . . . . . . . . . . . . . 68
7.1 Aufbau einer Symboltabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
8.1 Compiler-Collection ohne Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
8.2 Compiler-Collection mit Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
8.3 Syntaxbaum f ur Term 1 + 2 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8.4 Datenstruktur f ur binaren Operator-Baum. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8.5 AST f ur PL/0-Programm ggt.pl0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
8.6 Zwischencode zur Verzweigung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
8.7 Zwischencode zur kopfgfesteuerten Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
9.1 Schachtelungsmoglichkeiten von Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
8 Abbildungsverzeichnis
9.2 Symboltabelle bei statischer Hauptspeicherverwaltung (Listing 9.1) . . . . . . . . . 107
9.3 Statischer Hauptspeicher g lokal zu f (Listing 9.1) . . . . . . . . . . . . . . . . . . . . . . . . 107
9.4 Leerer Hauptspeicher . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9.5 Hauptspeicher g lokal zu f (Listing 9.1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
9.6 Hauptspeicher g und f lokal zu main (Listing 9.2) . . . . . . . . . . . . . . . . . . . . . . . . . 109
9.7 Hauptspeicher f lokal zu g (Listing 9.3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
11.1 Ablaufplan Einfache Verzweigung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.2 Ablaufplan Verzweigung mit Alternative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
11.3 Ablaufplan kopfgesteuerte schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
12.1 Verarbeitung einer Lex-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
12.2 Verarbeitung von Yacc- und Lex-Datei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Tabellenverzeichnis
6.1 Parsing-Vorgang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.2 Parsing-Vorgang mit Code-Erzeugung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.3 LL(1)-Parsetabelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.4 Parstabelle zu tabellengesteuertem LL(1)-Parsen mit Rekursion . . . . . . . . . . . . 74
6.5 LR-Tabelle f ur Ausdr ucke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
6.6 LR-Parse-Vorgang 1+2*3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
9.1 Vor- und Nachteile von statischer und dynamischer Speicherverwaltung . . . . . 111
Listings
2.1 Rekursive Fakultat in PL/0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
2.2 Groter gemeinsamer Teiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
4.1 Ein zeichenbasierter Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
4.2 Scannen durch strtok-Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.3 Beispiel f ur Maximum-Match-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.4 Demo zur Benutzung von Regularen Ausdr ucken in C . . . . . . . . . . . . . . . . . . . . . 35
5.1 Zahlenerkennung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2 Aloisius-Scanner 1 (primitiv) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
5.3 Aloisius-Automat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4 BCD-Erkenner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
5.5 Textsuche - Einfachstversion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.6 Lola-Automat . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.7 Aloisius-Scanner 2 (schnell) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.8 Scanner f ur arithmetische Ausdr ucke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.9 Header-Datei zum Term-Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.10 Testprogramm f ur Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
6.1 Kellerautomat zur Berechnung arithmetischer Ausdr ucke . . . . . . . . . . . . . . . . . . 62
6.2 Programmgesteuerter LL(1)-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.3 LL(1)-Parser mit Stackverwaltung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.4 Tabellengesteuerter LL(1)-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
6.5 LR-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
6.6 Term-Grammatik in Yacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
6.7 Term-Grammatik in Yacc - Zusatzinformationen . . . . . . . . . . . . . . . . . . . . . . . . . 83
6.8 Operator-Prazedenz-Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
./programme/ast/termast.h . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
8.1 Denitionsdatei zum Operator-Baum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
8.2 Programm-Code zum Operator-Baum. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
8.3 Compiler zur Operator-Baum-Erstellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
9.1 g lokal zu f . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
12 Listings
9.2 f und g lokal zu main . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.3 f lokal zu g . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.1 Kekursives Potenzieren nach Lagrange . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
11.1 Codeerzeugung f ur Variablenzugri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
12.1 Lex-Programm zur Extraktion von C-Inline-Kommentaren . . . . . . . . . . . . . . . . . 126
12.2 PL/0-Zusweingen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
12.3 Term-Scanner mit Lex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
12.4 Ein Term-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
12.5 Ein Term-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
12.6 Ein Term-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
12.7 Ein Term-Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
A.1 Hauptprogramm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135
A.2 Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
A.3 Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138
A.4 AST Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
A.5 AST Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
A.6 Symboltabelle Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
A.7 Symboltabelle Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
A.8 Fehlermodul . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
A.9 Interpreter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
A.10 Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
1
Einf uhrung
Abbildung 1.1 zeigt die verschiedenen Phasen eines Compilers nach [ASU88].
Abb. 1.1. Phasen eines Compilers
Zielprogramm
Code-Erzeugung
Code-Optimierung
Zwischencode-Erzeugung
Semantische Analyse
Syntax-Analyse
Lexikalische Analyse
Quellprogramm

Symbol-
tabelle

`
`
`
`
`
`

Fehler-
behandlung
`
`
`
`
`
`

Compilerbau ist das schwierigste Fachgebiet in der Informatik, da extrem viel Theorie ver-
standen werden muss!
Compilerbau ist das einfachste Gebiet der Informatik, da nur hier Programme entwickelt
werden konnen, die zu einen Problem eine Losung automatisch programmieren!
1.1 Der Komplex Formale Sprachen - Automatentheorie -
Compilerbau
Denition 1.1. Abgrenzung der drei Teilgebiete
Formale Sprachen ist die Wissenschaft, Sprachen zu beschreiben.
Automatentheorie ist die Wissenschaft, die Syntax von Texten, die in einer formalen Sprache
geschrieben sind, zu uberpr ufen.
14 1 Einf uhrung
Compilerbau ist die Wissenschaft, Automaten zu codieren und um ein

Ubersetzungsmodul zu
erweitern.
1.2 Beispiele f ur Compilerbau
Internet-Benutzereingaben
Rechen-Programme
Syntax-Highlightning von Editoren
Verarbeitung von ini-Dateien
SQL-Interpreter
Verarbeitung von Eingabedaten
Web-Browser
XML-Verarbeitung
1.3 Begrisbildung
Denition 1.2 (Compiler). Ein Compiler ubersetzt ein in einer formalen Sprache ge-
schriebenes Programm in eine andere formale Sprache.
Denition 1.3 (Interpreter). Ein Interpreter f uhrt ein in einer formalen Sprache ge-
schriebenes Programm aus.
Mischformen sind moglich, popularstes Beispiel ist Java.
2
Die Programmiersprache PL/0
Als Programiersprache, welche sich konsequent durch die Beispiele zieht, verwenden wir
PL/0. Hierbei handelt es sich um ein Mini-Mini-Pascal, welches Wirth in [Wir86] vorschlagt.
2.1 Die Syntax
Grammatik
1
zeigt die Grammatik der Programmiersprache PL/0, wie Wirth sie in [Wir86]
einf uhrt.
Grammatik
1
: PL/0
program ::= block "."
block ::= [ "CONST" ident "=" number {"," ident "=" number} ";"]
[ "VAR" ident {"," ident} ";"]
{ "PROCEDURE" ident ";" block ";" } statement
statement ::= [ ident ":=" expression
| "CALL" ident
| "?" ident
| "!" expression
| "BEGIN" statement {";" statement } "END"
| "IF" condition "THEN" statement
| "WHILE" condition "DO" statement ]
condition ::= "ODD" expression
| expression ("="|"#"|"<"|"<="|">"|">=") expression
expression ::= [ "+"|"-"] term { ("+"|"-") term}
term ::= factor {("*"|"/") factor}
factor ::= ident
| number
| "(" expression ")"
Da die Ausdr ucke keine beliebigen Vorzeichen zulassen behalten wir uns vor, die Ausdr ucke
gema unserer Term-Grammatik zu implementieren, diese ist abwartskompatibel zu PL/0-
Ausdr ucken.
16 2 Die Programmiersprache PL/0
PL/0 bietet wichtige Grundlagen von Programmiersprachen, jedoch fehlen auch wichtige
Dinge:
Datentypen, PL/0 bietet nur int-Daten
Strings
Parameter und Funktionswerte f ur Funktionen
Felder
Strukturen
Zeiger
Dynamische Hauptspeicherallokierung
Am Ende der Vorlesung werden wir einen kompletten PL/0-Compiler bzw. -Interpreter
entwickelt haben.
2.2 Programmbeispiele
Listing 2.1. Rekursive Fakultat in PL/0 (pl-0/programme/fakultaet.pl0)
1 ( Rekursi ve Berechnung der Fakul t at )
2 VAR n , f ;
3 PROCEDURE f a kul t a e t ;
4 BEGIN
5 ! n ;
6 IF n > 0 THEN
7 BEGIN
8 DEBUG;
9 f := f n ;
10 n := n 1;
11 CALL f a kul t a e t ;
12 n := n + 1;
13 DEBUG;
14 END;
15 END;
16 BEGIN
17 ? n ;
18 f := 1;
19 DEBUG;
20 CALL f a kul t a e t ;
21 DEBUG;
22 ! f ;
23 END.
Listing 2.2. Groter gemeinsamer Teiler (pl-0/programme/ggt.pl0)
1 ( Berechnet GGT von zwei Zahl en )
2 VAR a , b , g ;
3 PROCEDURE ggt ;
4 BEGIN
5 WHILE a # b DO
6 BEGIN
7 IF a > b THEN a := a b ;
2.3

Ubungen 17
8 IF b > a THEN b := b a ;
9 END;
10 g := a ;
11 END;
12 BEGIN ( Hauptprogramm )
13 ? a ;
14 ? b ;
15 CALL ggt ;
16 ! g ;
17 END.
2.3

Ubungen

Ubung 2.1. Kleinstes gemeinsames Vielfaches


Algorithmus 1 berechnet das kleinste gemeinsame Vielfache (KGV) zweier ganzer Zahlen:
Algorithmus 1: Kleinstes gemeinsames Vielfaches
Eingabe a, b
x = a, y = b


x < 1 ODER y < 1?
Ja Nein
r = -1 x = y

x < y?
Ja Nein
x = x + a y = y + b
r = x

Ausgabe r
Codieren Sie das Struktogramm als PL/0-Programm.

Ubung 2.2. Teilersuche


Entwickeln Sie ein PL/0-Programm, das alle Teiler einer einzugebenden Zahl errechnet und
ausgibt.
3
Wiederholung Automatentheorie und Formale
Sprachen
Betrachtet man Abbildung 1.1 (Seite 13), so ndet man weit vorne die Phasen der lexikali-
schen sowie der syntaktischen Analyse. F ur diese zwei Phasen existieren Theorien, namlich
die der Formalen Sprachen und der Automaten. Aus diesem Grund soll hier nochmals kurz
eine Wiederholung durchgef uhrt werden.
3.1 Formale Sprachen
3.1.1 Einf uhrung
Die erste wissenschaftlich ernsthafte Auseinandersetzung mit Grammatik und Sprachen
stammt von dem Amerikaner Noam Chomsky. Er analysierte Grammatiken und teilte diese
in die sog. Chomsky-Klassen ein.
Die Theorie der Formalen Sprachen wie sie heute betrieben wird (und damit auch Automa-
tentheorie) basiert auf den Chomsky-Klassen.
Typ 3 Beim Chomsky-Typ 3 handelt es sich um die sog. regularen Sprachen
Typ 2 Beim Chomsky-Typ 1 handelt es sich um die sog. kontextfreien Sprachen.
Typ 1 Kontextsensitive Sprachen (f ur Informatik nur von untergeordneter Bedeutung)
Typ 0 Allgemeine Sprachen.
Eine Sprache eines Bestimmten Typs n ist immer auch eine Sprache des Typs (n+1), d.h.
die Sprachen eines Typs n stellen eine Teilmenge der Sprachen eines Typs (n+1) dar:
T
3
T
2
T
1
T
0
Menge aller Sprachen
Diese Mengenbeziehung ist in Abbildung 3.1 dargestellt.
F ur die Theoretische Informatik sind die Chomsky-Typen 2 und 3 interessant, da f ur die-
se zwei Klassen eziente Algorithmen entwickelt wurden, die die Sprache analysieren. Im
Gegensatz dazu sind f ur die Klassen Chomsky-0 und Chomsky-1 keine ezienten Algorith-
men bekannt. Die gangigen Programmiersprachen wie C Pascal, Delphi, C++ aber auch
arithmetische Ausdr ucke in der Mathematik sind Sprachen des Chomsky-Typs 2.
Eine Sprache besteht aus einem Alphabet, aus dem sich die Satze der Sprache bilden lassen.
Zusatzlich bestehen sog. Produktionsregeln, die die Bildung der Satze zeigen.
20 3 Wiederholung Automatentheorie und Formale Sprachen
Abb. 3.1. Hierarchie der Sprachen nach Chomsky
9
8
6
7
Alle Sprachen
9
8
6
7
Typ-0-Sprachen
9
8
6
7
Typ-1-Sprachen
9
8
6
7
Typ-2-Sprachen
9
8
6
7
Typ-3-
Sprachen
A =

A

, . . . ,

Alphabet f ur engl. Sprache


B =

0

Alphabet f ur Binarzahlen
C =

0

Alphabet f ur ganze Zahlen


D =

+

, . . . ,

Alphabet f ur ganze Zahlen mit Vorzeichen


E =

+

, . . . ,

Alphabet f ur Fixkommazahlen
F =

+

, . . . ,

Alphabet f ur Fliekommazahlen
G =

if

,

else

for

while

do

int

oat

, . . . Alphabet f ur einen C-Compiler


H =

zahl

Alphabet f ur arithmetische Ausdr ucke


Formal besteht eine Sprache aus einem 4-Tupel
Denition 3.1. T Die Menge der terminalen Symbole (engl. terminals)
N Die Menge der nicht-terminalen Symbole (engl. nonterminals)
P Die Menge der Produktionsregeln
S Dem Startsymbol N
Terminale Symbole sind die Symbole, die nicht mehr weiter zerlegt werden.
Nicht-terminale Symbole sind die Symbole, die durch eine Produktionsregel zerlegt wer-
den.
Als erstes Beispiel verwenden wir ein sehr einfaches Deutsch, dessen Satze folgendermaen
gebildet werden:
Ein Satz besteht aus einer Nominalphrase und einer Verbalphrase
Eine Nominalphrase besteht aus einem Nomen, vor dem evtl. ein Artikel steht.
Eine Verbalphrase besteht aus einem Verb, evtl. gefolgt von einer Nominalphrase.
Die terminalen Symbole sind jetzt V (f ur Verb), N (f ur Nomen) und A (f ur Artikel).
Die nicht-terminalen Symbole sind S Formell ist dies Grammatik in
2
dargestellt.
3.1 Formale Sprachen 21
Grammatik
2
: Einfach-Deutsch
T : {V, N, A}
N : {S, NP, VP}
P :
S NP VP
NP N
NP A N
VP V
VP V NP
S : S
Bei der terminalen Symbolen steht N steht f ur Nomen, A f ur Artikel, V f ur Verb.
Satze wie Die Katze schnurrt (Terminal-Folge A-N-V-A-n) oder Der Hund jagt die Katze
(Terminal-Folge A-N-V) lassen sich nach diesen Grammatikregeln bilden.
Eine ubersichtliche Darstellung f ur Satze ist der Syntaxbaum. Ausgehend vom Startsymbol
ganz oben werden alle angewendeten Produktionsregeln durch Verzweigungen nach unten
dargestellt. Abbildungen 3.2 und 3.3 zeigen die Syntaxbaume der obigen Beispielsatze.
Abb. 3.2. Syntaxbaum Die Katze schnurrt
A
(die)
N
(Katze)

NP
V
(schnurrt)
VP

S
Abb. 3.3. Syntaxbaum Der Hund jagt die Katze
A
(der)
N
(Hund)

NP
V
(jagt)
A
(die)
A
(Katze)

NP

VP

S
Satze wie Der Hund jagt die Katze auf einen Baum sind nach Grammatik
2
nicht korrekt.
Ziel der Theorie der formalen Sprachen und des Compilerbaus ist es, vorhandene Satze
anhand der Grammatikregeln zu analysieren und den Syntaxbaum aufzustellen.
3.1.2 Notationsformen von Grammatiken
Die von Chomsky vorgeschlagene Notationsform f ur Grammatiken war f ur die Informatik nur
bedingt geeignet. Im Zuge der Entwicklung der Programmiersprachen wurden geschicktere
Notationsformen entwickelt, die teilweise auch maschinell verarbeitbar waren.
22 3 Wiederholung Automatentheorie und Formale Sprachen
Backus-Naur-Form (BNF)
Die Backus-Naur-Form (
1
und
2
) ist eine der wichtigsten Beschreibungsformen f ur Program-
miersprachen. Sie basiert auf Chomskys Notationsform, verwendet aber unterschiedliche
Notation f ur terminale und nicht-terminale Symbole. Des weiteren werden zur Darstellung
nur Zeichen des ASCII-Zeichensatzes benotigt.
Linke und rechte Seite der Produktionen werden durch ::= getrennt
Nonterminals stehen in spitzen Klammern (<,>)
Alternativen werden durch | getrennt
Rekursion ist zulassig
Als Beispiel soll die IF-ELSE-Anweisung in Pascal in EBNF beschrieben werden, dargestellt
in Grammatik in
3
.
Grammatik
3
: IF-ELSE in Pascal
<Statement> ::= <Ifstatement> | <Assignment>
<Assigment> ::= <Variable> := <Expression>
<Ifstatement> ::= IF <Expression> THEN <Statement> <Elsepart>
<Elsepart> ::= | ELSE <Statement>
Man ndet in der Literatur haug Varianten der Ur-EBNF:
Als Produktionszeichen wird = statt ::= verwendet.
Die spitzen Klammern fehlen, daf ur stehen Terminals in Anf uhrungszeichen.
Ein Punkt kennzeichnet das Ende einer Regel.
Die einfache Deutsch-Grammatik
2
wird in BNF als
4
dargestellt.
Grammatik
4
: Einfach-Deutsch in EBNF
<S> ::= <NP> <VP>
<NP> ::= N | A N
<VP> ::= V | V <NP>
Die Backus-Naur-Form kann auch in sich selbst dargestellt werden, wie in Grammatik
5
gezeigt.
Grammatik
5
: Die Backus-Naur-Form
<S> ::= <NP> <VP>
<NP> ::= N | A N
<VP> ::= V | V <NP>
Erweiterte Backus-Naur-Form (EBNF)
In der erweiterten BNF (EBNF) In der erweiterten BNF (EBNF) gibt es einige zusatzliche
Moglichkeiten zur Beschreibung einer Sprache:
1
John Backus, amerikanischer Informatiker, *3.12.1924
2
Peter Naur, danischer Informatiker,*25. Oktober 1928
3.1 Formale Sprachen 23
Optionale Teile stehen in eckigen Klammern:
[<Elsepart>]
Geschweifte Klammern umschlieen beliebige Wiederholungen (auch Null!):
<Var> {,<Var>}
Setzen von Prioritaten durch runde Klammern:
(<A>|<B>) <C>
Die Grammatik der EBNF-Darstellung kann jetzt in EBNF angegeben werden: Grammatik

6
Grammatik
6
: EBNF
Start = syntax
syntax = {produktion} .
produktion = bezeichner = ausdruck.
ausdruck = term {| term} .
term = faktor {faktor} .
faktor = bezeichner | string | (ausdruck) | [ausdruck] | {ausdruck}.
bezeichner = buchstabe {buchstabe|zier} .
string = {buchstabe}.
buchstabe =A|B| . . . |Z.
zier =0|1| . . . |9.
Die einfache Deutsch-Grammatik
2
wird in der erweiterten Backus-Naur-Form als
7
dar-
gestellt.
Grammatik
7
: Einfach-Deutsch in EBNF
<S> ::= <NP> <VP>
<NP> ::= [A] N
<VP> ::= V [<NP>]
Syntaxdiagramme
In Syntaxdiagrammen werden Grammatiken grasch dargestellt. Terminalsymbole werden
durch einen Kreis oder ein Oval (oder ein Rechteck mit abgerundeten Ecken) dargestellt,
Nichtterminalsymbole durch Rechtecke. F ur die Moglichkeiten, eine Produktion einer in
EBNF notierten Grammatik darzustellen ergeben sich die folgenden Graphen:
Nicht-Terminal: Produktionsregeln (Nicht-Terminale) werden durch Rechtecke dargestellt:
Syntaxdiagramm 1: Nicht-Terminal A
A
Terminal: Terminale Symbole werden durch Ovale (abgerundete Rechtecke / Ellipsen / Krei-
se) dargestellt:
Syntaxdiagramm 2: Terminal-Symbol

Alternative: Produktionsregeln der Form A


1
|
2
|
3
| . . . |
n
24 3 Wiederholung Automatentheorie und Formale Sprachen
Syntaxdiagramm 3: Alternative

3
. . . . . .

Sequenz: Produktionsregeln der Form A


1

3
. . .
n
Syntaxdiagramm 4: Sequenz


1


2


3
. . .

n

Wiederholung: Produktionsregeln der Form A {}
Syntaxdiagramm 5: Wiederholung


Option: Produktionsregeln der Form A []
Syntaxdiagramm 6: Option

Regulare Ausdr ucke


F ur Sprachen des Chomsky-Typs 3 - den regularen Sprachen - hat sich in der Informatik
eine weitere Notationsform eingeb urgert, namlich die regularen Ausdr ucke. Diese sind jedoch
nicht in irgendeinem Sinne genormt, sondern von Programm zu Programm unterschiedlich.
Sie bestehen aus Zeichen und sog. Meta-Zeichen, die eine spezielle bedeutung haben. So stellt
das Zeichen * etwa eine Wiederholung dar, was in der EBN der -Klammerung entspricht,
das Zeichen ? stellt die Option dar. Die Meta-Zeichen des Scanner-Generators Lex sind in
Tabelle ?? (Seite ??) dargestellt.
Regulare Ausdr ucke konnen keine Typ-3-Sprachen beschreiben, also keine Klammerstruk-
turen.
Die einfache Deutsch-Grammatik
2
wird als regularere Ausdruck als
8
dargestellt.
Grammatik
8
: Einfach-Deutsch in EBNF
A?NV(A?N)?
Regulare Ausdr ucke bestehen nur aus einer Produktionsregel, das nichtterminale Symbol
welches durch diese Produktionsregel beschrieben wird ist immer das Startsymbol und kann
daher weggelassen werden. Auf der rechten Seite dieser einen Regel stehen nur terminale
Symbole.
3.2 Endliche Automaten 25
3.1.3 Notation in diesem Script
in diesem Script wird der besseren Lesbarkeit wegen fast durchgangig eine modizierte EBNF
verwendet:
Das Produktionszeichen ist .
Terminals stehen in -Zeichen und sind klein geschrieben.
Terminals sind gro geschrieben.
Die einfache Deutsch-Grammatik
2
wird in der Script-Notation als
9
dargestellt.
Grammatik
9
: Einfach-Deutsch in Script-Notation
S NP VP
NP [a] n
VP v [NP]
3.1.4 Grammatiken und Automaten
Es konnen zu einer Sprache mehrere Grammatiken angegeben werden, oder andersherum,
mehrere Grammatiken konnen ein- und dieselbe Sprache beschreiben. Daraus resultiert, da
Grammatiken ungeformt werden konnen, worauf hier jedoch nicht naher eingegangen werden
soll.
Denition 3.2 (Kontextfreie Grammatik). Eine Grammatik heiss kontextfrei (Chomsky-
Typ 2), wenn auf der linken Seite aller Produktionsregeln nur ein nicht-terminales Symbol
steht.
Denition 3.3 (Regulare Grammatik). Eine Grammatik heisst regular (Chomsky-Typ
3), wenn die Grammatik auf eine Produktionsregel reduziert werden kann und das Startsym-
bol nicht auf der rechten Seite dieser Produktionsregel steht.
Ein Beispiel f ur eine regulare Grammatik ist die Deutsch-Grammatik der Einleitung oder
die folgende Grammatik
10
die eine Fixkommazahl beschreibt.
Grammatik
10
: Fixkommazahl
ZAHL VK [NK]
VK INT
NK . INT
INT DIGIT DIGIT
DIGIT 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Ein Beispiel f ur eine kontextfreie Grammatik ist
11
die eine geklammerte ganze Zahlen
beschreibt.
Grammatik
11
: Geklammerte Zahl
SATZ INT | ( SATZ )
INT DIGIT {DIGIT}
DIGIT 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
3.2 Endliche Automaten
3.2.1 Grundbegrie
Ein endlicher Automat ist ein System, das interne Zustande abhangig von einer Eingabe
annimmt.
26 3 Wiederholung Automatentheorie und Formale Sprachen
Ein endlicher Automat A ist damit ein F unftupel A = (Z, E, , z
0
, F).
Z ist die Menge der Zustande in denen sich der Automat A benden kann.
E ist das Eingabealphabet des Automaten (die Menge der Eingabesymbole).
ist die

Ubergangsfunktion, die jeder Kombination aus Zustand und Eingabesymbol einen
Folgezustand zuordnet. Z E Z.
z
0
ist der Startzustand, in dem sich der Automat am Anfang bendet.
F ist die Menge der Endezustande (F Z). Kommt ein Automat in einen Endezustand so
hort er auf zu arbeiten.
3.2.2 Modell eines erkennenden Automaten
Eingabeband
Automat
`

3.2.3 Darstellungsformen
Tabellarische Darstellung
Die

Ubergangsfunktion kann als Tabelle dargestellt werden. Dabei werden die Zustande
meist als Zeilen und das Eingabealphabet als Spalten dargestellt.
F ur Endezustande gibt es keine Zeilen (da aus diesen Zustanden nicht mehr gewechselt
wird).
Die tabellarische Darstellung ist f ur uns Menschen weniger ubersichtlich, lasst sich jedoch
einfacher in ein Programm codieren.
Graphische Darstellung
Die

Ubergangsfunktion kann als

Ubergangsgraph (Diagramm) dargestellt werden. Dabei
werden die Zustande als Kreise (doppelte Linien f ur Endezustande) dargestellt. Die

Uber-
gangsfunktion wird durch Pfeile dargestellt, die Pfeile werden mit einem Zeichen aus dem
Eingabealphabet beschriftet.
Von Endezustanden f uhren keine Pfeile weg. Der Startzustand kann durch einen Initialpfeil
markiert werden.
Die graphische Darstellung ist f ur uns Menschen sehr ubersichtlich, lasst sich jedoch nicht
so einfach in ein Programm codieren.
3.2.4 Ein einf uhrendes Beispiel
Ein Automat soll erkennen, ob es sich bei seiner Eingabe um eine korrekte Fixkommazahl
handelt. Die regulare Grammatik, die uberpr uft werden soll, war
10
(Seite 25). Das Ein-
gabealphabet des Automaten besteht aus den Elementen Z (Zier), P (Dezimalpunkt,
E (Ende) und R (Rest):
3.2 Endliche Automaten 27
Abb. 3.4. Automat zur Zahlenerkennung
m
0
m
1
m
2
m
3
mj
4
m j
5

>
>
>
>
>
>
>
>
>.
`
`
-

`
`
-

Z
R,E,P
P Z
R R,E,P P,R
E E Z Z
Z = {0, 1, 2, 3, 4, 5}
E = {E, Z, P, R}
=
E Z P R
0 4 1 4 4
1 5 1 2 4
2 4 3 4 4
3 5 3 4 4
z0 = 0
F = {4, 5}
Satz 1. Zu jeder regularen Sprache (Chomsky-Typ-3) kann ein aquivalenter endlicher Au-
tomat konstruiert werden, der die Syntax dieser Sprache uberpr uft.
3.2.5 Endliche Automaten und Formale Sprachen
Satz 2. zu jedem endlichen Automaten kann eine regulare Grammatik entwickelt werden,
die die von diesem Automat akzeptierte Sprache beschreibt.
3.2.6 Grundalgorithmus eines Endlichen Automaten
Algorithmus 2: Grundalgorithmus 1 eines endlichen deterministischen Automa-
ten
zustand = z
0
solange zustand / F
lies Eingabezeichen
zustand = (zustand, Eingabezeichen)
28 3 Wiederholung Automatentheorie und Formale Sprachen
Algorithmus 3: Grundalgorithmus 2 eines endlichen deterministischen Automa-
ten
zustand = z
0
solange nicht am Eingabeende
lies Eingabezeichen
zustand = (zustand, Eingabezeichen)
3.3 Kellerautomaten
Satz 3. Zu jeder kontextfreien Sprache (Chomsky-Typ-2) kann ein aquivalenter Kellerauto-
mat konstruiert werden, der die Syntax dieser Sprache uberpr uft.
Satz 4. Zu jedem Kellerautomaten kann eine kontextfreien Grammatik (Chomsky-Typ-2)
entwickelt werden, die die von diesem Automat akzeptierte Sprache beschreibt.
3.4

Ubungen
Gegeben ist Grammatik
12
:
Grammatik
12
: Mathematischer Term
E T {(+ | -) T}
T F {(* | /) F}
F zahl | ( E ) | - F | + F
Dabei sind E, T und F Non-Terminals, zahl und die Zeichen +, -, *, /, ( und ) sind
Terminals.
12
ist eine Grammatik zur Beschreibung arithmetischer Terme.

Ubung 3.4. Grammatik-Art


Von welchem Chomsky-Typ ist
12
?

Ubung 3.5. Syntaxbaum


Erstellen Sie den Syntaxbaum f ur den Term (1 2).

Ubung 3.6. BNF-Umformung


Formen Sie
12
in BNF um.

Ubung 3.7. Syntaxdiagramme


Zeichnen Sie die Syntaxdiagramme f ur
12
.
4
Grundlagen
4.1 UPN
UPN steht f ur Umgekehrte Polnische Notation, im englischen Sprachgebrauch entspre-
chend RPN genannt. ein weiters Synonym ist Postx-Notation.
UPN hat f ur maschinelle Verarbeitung entscheidende Vorteile:
Es gibt Operatoren keinerlei Prioritatsregeln wie Punkt vor Strich.
Es gibt keine Prioritatsklammern.
Aus diesem Grund wurden und werden Prozessoren gerne als Register-Stack-Maschinen
entwickelt. Die Firma Hewlett-Packard baute lange Zeit Taschenrechner, die in UPN bedient
wurden, Abbildung 4.1 zeigt exemplarisch das Modell HP 12C. Wie zu erkennen ist, hat der
Taschenrechner weder eine Gleichheits- noch Klammertasten, daf ur aber die Enter-Taste die
zum Trennen der Eingabe dient.

Ubrigens verwenden die klassischen Taschenrechner keine
Abb. 4.1. Taschenrechner HP 12C
30 4 Grundlagen
reine Inx-Notation, sondern bei Nutzung von Funktionen ebenfalls die Postx-Notation.
Zur Berechnung des Terms

3
2
+ 4
2
ist die Wurzeltaste zuletzt zu dr ucken.
UPN wird folgendermaen abgearbeitet:
Zahlen (Operanden) werden auf den Stack gelegt.
Bei Operatoren wird je nach Wertigkeit des Operators (unar oder binar) die entspre-
chende Zahl von Operanden vom Stack geholt, entsprechend dem Operator miteinander
verkn upft und das Ergebnis wieder auf den Stack gelegt.
Bei Funktionen wird je nach Anzahl der Funktionsparameter die entsprechende Zahl von
Operanden vom Stack geholt, entsprechend der Funktion miteinander verkn upft und das
Ergebnis wieder auf den Stack gelegt.
Ausdr ucke m ussen f ur Register-Stack-Maschinen in Reverse-Notation (UPN) umgewandelt
werden:
Ausdruck Maschinencode
1 + 2 * 3
LOADC 1
LOADC 2
LOADC 3
MULT
ADD
(1 + 2) * (3 + 4)
LOADC 1
LOADC 2
ADD
LOADC 3
LOADC 4
ADD
MULT
Die Umsetzung kann durch den Syntaxbaum erfolgen.
1
2 3
/
/
`
`
*
/
/
`
`
+
Wird dieser Baum im Postorder-Verfahren Durchlaufen, so ergibt sich die Abfolge 1 2 3 * +,
was dem obigen Maschinencode entspricht.
1 2
/
/
`
`
+
3 4
/
/
`
`
+
/
/
`
`
*
Wird dieser Baum im Postorder-Verfahren Durchlaufen, so ergibt sich die Abfolge 1 2 + 3 4 + *,
was dem obigen Maschinencode entspricht.
4.2 Scanner und Parser 31
4.2 Scanner und Parser
Grammatik
13
ist eine Modikation von Grammatik
12
(Seite 28). Dabei wurde das ter-
minale Symbol zahl durch ein weiteres Non-Terminal ersetzt:
Grammatik
13
: Term-Grammatik 1
E T {(+-) T}
T F {(*/) F}
F Z ( E ) - F
Z (01...9){01...9}
Nun konnte ein Kellerautomat entwickelt werden, der die Syntax eines Eingabetextes uber-
pr uft, wobei der Eingabetext zeichenweise verarbeitet werden kann. in der Praxis ist dieses
Vorgehen aber aus verschiedenen Gr unden unpraktisch:
Die Anzahl der Non-Terminals ist unnotig hoch.
In Programmiersprachen entsteht ein Konikt zwischen Bezeichnern und Schl usselwortern
(siehe etwa die PL/0-Grammatik
1
.
Sog. whitespace-Zeichen m ussen uberlesen werden und w urden eine Grammatik unnotig
komplizierter machen.
Daher wird man diejenigen Regeln einer Grammatik, die f ur sich genommen vom Chomsky-
Typ 2 sind, aus der Grammatik herausnehmen und gesondert behandeln:
Die als Typ 2 darstellbaren Regeln werden uber die sog. lexikalische Analyse erkannt.
Die restlichen Regeln werden von der syntaktischen Analyse erkannt.
Die lexikalische Analyse ubernimmt der sog. Scanner, die syntaktische Analyse der Parser.
Denition 4.1 (Scanner). Scanner zerlegen eine Eingabe in ihre Bestandteile. Es erfolgt
keine

Uberpr ufung ob die gefundenen Teile Sinn ergeben. Diesen Vorgang nennt man auch
Lexikalische Analyse.
Denition 4.2 (Parser). Parser uberpr ufen eine Folge von Zeichen ihres Eingabealpha-
bets auf korrekte Reihenfolge. Diesen Vorgang nennt man auch Syntaktische Analyse oder
Grammatikalische Analyse.
Denition 4.3. Die Elemente einer Sprache, die ein Scanner ndet, nennt man Token.
4.2.1 Zusammenspiel von Scanner und Parser
Abb. 4.2. Zusammenspiel zwischen Scanner und Parser
Scanner
9
8
6
7
Scanner
9
8
6
7

Input

Token
32 4 Grundlagen
Scanner und Parser sollten zwecks logischer Trennung in zwei unterschiedlichen Funktionen
implementiert werden. Der R uckgabewert von Scanner zum Parser (das sog. Token) wird
von Datentyp int angelegt.
Der Scanner wird vom Parser mehrfach aufgerufen und liefert das jeweils nachjste Token
zur uck. Vom Modell her liest er einfach aus der Eingabe. Ist die Eingabe eine Datei (ein FILE-
Zeiger), so ubernimmt das Betriebssystem die Verwaltung des Lesezeigers. Wird dagegen aus
einem Speicherbereich gelesen, so muss uber scanner-interne statische Variablen die aktuelle
Leseposition gespeichert werden.
Als Beispiel soll die Token-Folge f ur den C-Text if (a > 2) betrachtet werden:
Token if
Token Klammer auf
Token Bezeichner, Wert = a
Token Operator >
Token Konstante, Wert = 2
Token Klammer zu
Bei vielen Grammatiken ist jedoch das Token als R uckgabe ungen ugend. Unter 4.2 wurde
diskutiert, die Grammatik einer formalen Sprache nicht bis ins letzte Zeichen zu formulieren,
sondern einzelne Regeln, die f ur sich genommen eine Typ-2-Grammatik darstellen, durch den
Scanner zu verarbeiten. daraus folgt aber, dass Scanner einer typischen Programmiersprache
wuie PL/0 oder C alle Bezeichner durch ein- und dasselbe Token an den Parser melden und
dieser damit keine Moglichkeit hat, den eigentlichen Namen des Bezeichners zu erfahren.
Genauso verhalt es sich mit Zahlen.
Aus diesem Grund geben Scanner in der Praxis nicht nur einen Tokenwert sondern zusatzlich
den gescannten Text oder Zahlenwerte oder ahnliches zur uck.
4.3 Primitive Scanner - zeichenbasiert
Haug machen Scanner nichts anderes, als verschiedene Zeichen zu einer Gruppe oder Klasse
zusammenzufassen, d.h. der Scanner liest immer genau ein Zeichen aus dem Eingabestrom.
Diese Scanner sind sehr einfach aufgebaut, da sie immer nur einen Vergleich eines einzel-
nen Zeichens machen m ussen. Betrachten wir noch einmal den Automat in Abbildung 3.4
(Seite 27), so erkennen wir, dass im gesamten Automaten die Ziern 0 bis 9 immer gleich
behandelt werden. W urde man nun alle Ziern im Eingabealphabet E angeben, so ware die
delta-Tabelle 13 Spalten breit. Setzt man aber einen zeichenbasierten Scanner ein, der alle
Ziern zu einem Token T E zusammenfasst, kommt die delta-Tabelle mit vier Spalten
aus. Ein Scanner f ur diesen Automat sollte also eines der Tokens Z (Zier), P (Punkt),
E (Ende) und R (Rest) zur uckliefern. Der Scanner selbst ist in Listing 4.1 codiert, wobei
das Hauptprogramm keinen Automaten darstellt, sondern lediglich den Aufruf des Scan-
ners demonstriert. In Zeile 28 des Programms wird lediglich ein Zur ucksetzen des Scanners
durchgef uhrt, in Zeile 31 erfolgt der eigentliche Aufruf des Scanners.
Listing 4.1. Ein zeichenbasierter Scanner (grundl-scanner-xkommazahl.c)
1 /
2 Scanner f ur Fixkommazahl
3 /
4 #i nc l ude <s t di o . h>
5
6 t ypedef enum { t ende , t z i f f e r , t punkt , t r e s t } token ;
4.4 Primitive Scanner - wortbasiert 33
7
8 token f i xkomma scanner ( char i nput , i nt reset ) {
9 s t a t i c char p ;
10 t oken t ;
11 i f ( r e s e t ) {
12 p = i nput 1;
13 ret urn t ende ; // Keine Verarbei t ung !
14 }
15 i f ((++p) >= 0 && p <= 9 ) t = t z i f f e r ;
16 e l s e i f (p == . | | p == , ) t = t punkt ;
17 e l s e i f (p == \0 | | p == \n ) t = t ende ;
18 e l s e t = t r e s t ;
19
20 ret urn t ;
21 }
22
23 i nt main( ) {
24 t oken t ;
25 char i nput [ 2 5 5 ] ;
26
27 whi l e ( p r i nt f ( Brauche Input : ) , f g e t s ( i nput , 254 , s t di n ) != NULL) {
28 f i xkomma scanner ( i nput , 1) ;
29 p r i nt f (TokenFol ge : ) ;
30 do
31 p r i nt f ( %d , t = ( i nt ) f i xkomma scanner ( i nput , 0) ) ;
32 whi l e ( t != t ende ) ;
33 p r i nt f (\n\n) ;
34 }
35 ret urn 0;
36 }
Zeichenbasierte Scanner lassen sich sehr ezient tabellengesteuert implementieren.
4.4 Primitive Scanner - wortbasiert
Hin und wieder m ussen Scanner entwickelt werden, die einen Text einfach in Worte zerlegen
m ussen, wobei diese Worte durch spezielle Zeichen getrennt sind. Die C-Standard-Bibliothek
string.h stellt hierzu die strtok-Funktion (abgek urzt f ur string-token) zur Verf ugung:
char s t r t ok ( char i nput , char de l i mi t e r ) ;
Um das erste Wort zu scannen wird der zu scannende String als Parameter ubergeben.
Zur uckgegeben wird ein Zeiger auf das Wort. Dies ist der Reset-Vorgang des Scanners.
In den folgenden Scanner-Aufrufen wird f ur den Eingabestring-Zeiger der NULL-Zeiger
ubergeben. Zur uckgegeben wird wieder ein Zeiger auf das gefundene Wort.
Kann nichts mehr gescannt werden, so wird der Nullzeiger zur uckgegeben.
Zu beachten ist, dass der Eingabestring verandert wird.
Listing 4.2 demonstriert, wie einfach ein wortbasierter Scanner mittels der strtok-Funktion
zu implementieren ist.
34 4 Grundlagen
Listing 4.2. Scannen durch strtok-Funktion (grundl-strtok.c)
1 /
2 Demonstri ert Scannen e i ne s St r i ngs durch di e Bi bl i ot heks
3 Funkti on s t r t ok
4 /
5 #i nc l ude <s t di o . h>
6 #i nc l ude <s t r i ng . h>
7
8 i nt main ( ) {
9 char t o s can [ ] = \tDas i s t \n ei n Test \ t zum scannen ;
10 char de l i mi t e r [ ] = \ t \n; // Leer , Tab und Zei l enumbruch
11 char word ;
12 i nt nr = 0;
13
14 p r i nt f (Zu scannender t e x t : %s \ n , t o s can ) ;
15 word = s t r t o k ( t o scan , de l i mi t e r ) ; // I ni t und e r s t e s Wort
16 whi l e ( word != NULL) {
17 p r i nt f (Wort %d : %s \ n , ++nr , word ) ;
18 word = s t r t o k (NULL, de l i mi t e r ) ; // f ol g e nde Woerter
19 }
20 r et ur n 0;
21 }
4.4.1 Prinzipien eines Scanners
Die Einfach-Scanner wortweise und zeichenweise kommen in der Praxis recht selten vor.
Es ware eine Zumutung, in Calle Sprachelemente durch Whitespace-Zeichen zu trennen.
Statt if(a>2) m usste if(a>2) codiert werden.
Durch das Weglassen der trennenden Whitespace-Zeichen kann es aber zu Mehrdeutigkeiten
beim Scannen f uhren. So kann der C-Text if_a vom Scanner entweder in die Token-Folge
Schl usselworzt if - Bezeichner a oder aber in die Token-Folge Bezeichner if a zerlegt
werden. Das Verhalten eines Scanners in einem solchen Fall ist Denitionssache:
Denition 4.4. Ein Scanner wahlt immer das Token, das den langsten Eingabetext abbildet.
Dies nennt man das Maximum-Match-Prinzip.
Als Beispiel f ur Maximum-Match dient der Ausdruck in Zeile 4 Listing 4.3.
Listing 4.3. Beispiel f ur Maximum-Match-Methode (grundl-maximum-match.c)
1 #i nc l ude <s t di o . h>
2 i nt main ( ) {
3 i nt a , b = 1 , c = 2;
4 a=b+++c ; // Achtung ! ! ! !
5 p r i nt f (a = %d , b = %d , c = %d\n , a , b , c ) ;
6 ret urn 0;
7 }
Der C-Scanner zerlegt den Ausdruck in Zeile 4 in die Token-Folge Bezeichner a - Operator =
- Bezeichner b - Operator ++ - Operator + - Bezeichner c. Aus dieser Token-Folge produziert
der Parser dann den folgenden Syntaxbaum:
4.7 Regulare Ausdr ucke in C 35
a
b
++
c

+
.
.
.

=
Das Maximum-Match-Problem trate auch in Zeile 6 auf, wenn das Leerzeichen nicht einge-
geben worden ware. In Diesem Fall w urde der Scanner statt der Token-Folge Schl usselwort
return - Int-Konstante 0 die Tokenfolge Bezeichner return0 liefern.
Scanner m ussen - wenn gefordert wird, moglichst keine Leerzeichen einzugeben - oft voraus-
schauend agieren. Beispielsweise wird ein Scanner im C-Ausdruck 1.2E3+4 bis zum Zeichen
+ davon ausgehen, eine Fliekomma-Zahl einzulesen. Das +-Zeichen produziert aber kei-
nen Syntax-Fehler, sondern beendet das Scannen der Fliekommazahl und wird - f ur den
nachsten Scan-Vorgang - in die Eingabe zur uckgestellt.
Denition 4.5. Arbeitet ein Scanner vorausschauend, so nennt man diese einen Look-
Ahead.
4.4.2 Implementierung von Scannern
Scanner bestehen meist aus einer Menge von regularen Ausdr ucken und werden damit am
besten als endliche Automaten codiert. Diese Technik sichert schnelle Scan-Vorgange, da die
Laufzeit proportional zur Eingabestromlange ist. Schlechte Scanner arbeiten mit einfachen
String-Vergleichen.
Da die -Tabellen der Automaten meist sehr gro sind, werden zur Codierung von Scannern
gerne Tools wie Lex eingesetzt.
Desweiteren uberlesen Scanner Whitespace-Zeichen, weshalb diese nicht in der Grammatik
des Parsers vorkommen.
4.4.3 Fehlerbehandlung bei Scannern
4.5 Bootstrapping - Das Henne-Ei-Problem
4.6 Regulare Ausdr ucke
4.7 Regulare Ausdr ucke in C
Listing 4.4. Demo zur Benutzung von Regularen Ausdr ucken in C (regex.c)
1 /
2 Demonstri er Regul are Ausdr ucke mit C
3 /
4 #i nc l ude <s t di o . h>
5 #i nc l ude <regex . h>
6 #i nc l ude <s t r i ng . h>
7
8 i nt main( i nt argc , char argv [ ] )
36 4 Grundlagen
9 {
10 char i nput [ 255] , rp [ 255] =[ 0 9] +(\\. [ 0 9] +)?$ , e r r o r t e x t [ 2 5 5 ] ;
11 r e g e x t r egexpr ;
12 i nt rc ;
13
14 p r i nt f ( Brauche Input : ) ;
15 f g e t s ( i nput , 254 , s t di n ) ;
16 i nput [ s t r l e n ( i nput ) 1] = \0 ;
17 p r i nt f ( Input : %s \nRegExp : %s \n , i nput , rp ) ;
18 i f ( ( rc = regcomp(&regexpr , rp , REGEXTENDED | REG NOSUB) ) != 0) {
19 r eger r or ( rc , &regexpr , e r r or t e x t , 254) ;
20 p r i nt f ( Problem beim Ausdruck %s : %s \n , rp , e r r o r t e x t ) ;
21 }
22 el se {
23 rc = r egexec(&regexpr , i nput , 0 , NULL, 0) ;
24 r eger r or ( rc , &regexpr , e r r or t e x t , 254) ;
25 p r i nt f ( Antwort : %d %s \n , rc , e r r o r t e x t ) ;
26 }
27 r e g f r e e (&r egexpr ) ;
28 r et ur n 0;
29 }
4.8

Ubungen

Ubung 4.6. Syntaxbaume


Setzen Sie die folgenden Mathematischen Terme in Syntaxbaume und UPN um, arbeiten
Sie anschlieend den UPN-Code ab.
a =
1
1
b
+
1
c
x
1
=
b +

b
2
4ac
2a

Ubung 4.7. Scanner


Spielen Sie f ur folgenden C-Code Scanner:
i nt main ( ) {
i nt a = 125 , b = 250;
whi l e ( a != b )
i f ( a > b )
a = b ;
e l s e
b = a ;
p r i nt f (%d , a ) ;
ret urn 0;
}

Ubung 4.8. Grammatik


Gegeben ist die folgende Sprache:
S ::= E {O E}
4.8

Ubungen 37
E ::= F {(A | N) F}
F ::= W | ( E )
O ::= (O | o) (R | r)
N ::= (N | n) (O | o) (T | t)
A ::= (A | a) (N | n) (D | d)
W ::= B {B}
B ::= A | a | B | b | ... | Z | z
Die Grammatik enthalt die Schl usselworter AND, OR und NOT, wobei Gro-Kleinschreibung
unbedeutend ist, bei der Regel W sind die drei Schl usselworter ausgenommen. Denieren
Sie, welche der Regeln von einem Scanner und welche von einem Parser verarbeitet werden
sollten.
5
Endliche Automaten
Wie eingangs erwahnt, sind formale Sprachen und Automaten eng miteinander verbunden.
Zu jeder regularen Grammatik kann ein endlicher Automat konstruiert werden, der diese
Grammatik erkennt. Anders herum kann zu jedem endlichen Automaten eine Grammatik
angegeben werden.
Wie man zu regularen Sprachen Automaten konstruiert und wie man zu Automaten regulare
Sprachen entwickelt soll hier nicht Gegenstand sondern Voraussetzung sein.
5.1 Grundbegrie
Ein endlicher Automat ist ein System, das interne Zustande abhangig von einer Eingabe
annimmt. Abbildung 5.1 zeigt das Modell eines Automaten.
Ein endlicher Automat A ist damit ein F unftupel A = (Z, E, , z
0
, F).
Z ist die Menge der Zustande in denen sich der Automat A benden kann.
E ist das Eingabealphabet des Automaten (die Menge der Eingabesymbole).
ist die

Ubergangsfunktion, die jeder Kombination aus Zustand und Eingabesymbol einen
Folgezustand zuordnet. Z E Z.
z
0
ist der Startzustand, in dem sich der Automat am Anfang bendet.
F ist die Menge der Endezustande (F Z). Kommt ein Automat in einen Endezustand so
hort er auf zu arbeiten.
Abb. 5.1. Modell eines endlichen Automaten
Eingabeband
Automat
`

40 5 Endliche Automaten
5.2 Darstellungsformen
Abbildung 3.4 auf Seite 27 zeigte bereits einen endlichen Automaten sowohl in grascher als
auch in tabellarischer Darstellungsform.
5.2.1 Tabellarische Darstellung
Die

Ubergangsfunktion kann als Tabelle dargestellt werden. Dabei werden die Zustande
meist als Zeilen und das Eingabealphabet als Spalten dargestellt.
F ur Endezustande gibt es keine Zeilen (da aus diesen Zustanden nicht mehr gewechselt
wird).
Die tabellarische Darstellung ist f ur uns Menschen weniger ubersichtlich, lasst sich jedoch
einfacher in ein Programm codieren.
5.2.2 Graphische Darstellung
Die

Ubergangsfunktion kann als

Ubergangsgraph (Diagramm) dargestellt werden. Dabei
werden die Zustande als Kreise (doppelte Linien f ur Endezustande) dargestellt. Die

Uber-
gangsfunktion wird durch Pfeile dargestellt, die Pfeile werden mit einem Zeichen aus dem
Eingabealphabet beschriftet.
Von Endezustanden f uhren keine Pfeile weg. Der Startzustand kann durch einen Initialpfeil
markiert werden.
Die graphische Darstellung ist f ur uns Menschen sehr ubersichtlich, lasst sich jedoch nicht
so einfach in ein Programm codieren.
5.3 Algorithmus eines endlichen Automaten
Endliche Automaten werden auf zwei verschiedene Arten codiert, die in den Abbildungen
5.2 und 5.3 dargestellt sind. Je nach Aufgabe - Erkennen, Suchen, Scannen, ... - ist mal die
eine und mal die andere Variante zu bevorzugen.
Abb. 5.2. Algorithmus eines endlichen Automaten - Stop bei Endezustand
Erkennender Automat
Gehe in Startzustand
Zustand kein Endezustand
Lies token von Scanner
Gehe in Folgezustand
(zustand,token)
-
-
-
-
-
-
-
-
-
-
-
-
-
--
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
Zustand ein Fehlerzustand?
Ja Nein
Ausgabe Fehlermeldung Ausgabe OK
5.4 Ein einf uhrendes Beispiel 41
Abb. 5.3. Algorithmus eines endlichen Automaten - Stop bei Eingabeende
Erkennender Automat
Gehe in Startzustand
Nicht am Eingabeende
Lies token von Scanner
Gehe in Folgezustand
(zustand,token)
-
-
-
-
-
-
-
-
-
-
-
-
-
--
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
Zustand ein Endezustand?
Ja Nein
Ausgabe OK Ausgabe Fehlermeldung
5.4 Ein einf uhrendes Beispiel
Der Automat zur Erkennung von Fixkommazahlen (Abbildung 3.4 Seite 27) soll gema
Abbildung 5.2 codiert werden. Der regulare Ausdruck f ur diesen Automaten lautet
[0-9]+("."[0-9]+)?.
Das folgende Programm hat - um die Unterschiede zu verdeutlichen - den endlichen Auto-
maten zweimal implementiert:
Tabellengesteuert In der Funktion automat t wird der Automat tabellengesteuert imple-
mentiert. Die -Funktion des Automaten wird in einem zweidimensionalen Feld gespei-
chert. Die Zeilen entsprechen den Zustanden, die Spalten den Tokens der lexikalischen
Analyse.
Programmgesteuert In der Funktion automat p wird der Automat programmgesteuert im-
plementiert. Dazu werden Mehrfachverzweigungen in zwei Ebenen ineinander geschach-
telt. Die auere Mehrfachverzweigung entspricht dem Zustand, die innere jeweils dem
Token der lexikalischen Analyse.
Listing 5.1. Zahlenerkennung (automaten-zahlenerkennung.c)
1 /
2 Erkennung von Fixkommazahlen
3 Regul arer Ausdruck :
4 [ 0 9] +(. [ 0 9] +)?
5 /
6 #i nc l ude <s t di o . h>
7
8 i nt scanner ( char , i nt ) ; // Prototypen
9 i nt automat t ( char ) ;
10 i nt automat p ( char ) ;
11
12 enum {E, Z, P, R} ; // Token , Ei ngabeal phabet
13
14 i nt main ( ) {
15 char i nput [ 2 5 5 ] ;
16
17 whi l e ( p r i nt f (\ nInput : ) , s canf (%s , i nput ) != EOF) {
18 p r i nt f (\nT:%s OK! , ( ! aut omat t ( i nput ))? : ni cht ) ;
19 p r i nt f (\nP:%s OK! , ( ! automat p ( i nput ))? : ni cht ) ;
42 5 Endliche Automaten
20 }
21 pr i nt f (\n ) ;
22 r et ur n 0;
23 }
24
25 i nt automat t ( char in) { // Tab e l l e ng e s t e ue r t e r Automat
26 // R uckgabe 0: ei ngabe OK; R uckgabe 1: Ei ngabe NICHT OK
27 i nt zust and = 0;
28 i nt d e l t a [ ] [ 4 ] = {/ 0 1 2 3 Token/
29 / Zustand 0/ {4 , 1 , 4 , 4 } ,
30 /Zustand 1/ { 5 , 1 , 2 , 4 } ,
31 /Zustand 2/ { 4 , 3 , 4 , 4 } ,
32 /Zustand 3/ { 5 , 3 , 4 , 4 } };
33
34 scanner ( in , 1 ) ;
35 while ( zustand <4)
36 zustand = de l t a [ zustand ] [ scanner ( in , 0 ) ] ;
37 r et ur n ( zustand == 4 ) ;
38 }
39
40 i nt automat p ( char in) { // Programmgest euert er Automat
41 // R uckgabe 0: ei ngabe OK; R uckgabe 1: Ei ngabe NICHT OK
42 i nt zust and = 0 , t oken ;
43
44 scanner ( in , 1) ; // ScannerReset
45 whi l e ( zust and <4) {
46 t oken = scanner ( in , 0) ;
47 s wi t ch ( zust and ) {
48 case / Zustand / 0: s wi t ch ( t oken ) {
49 case 0: zust and = 4; break ;
50 case 1: zust and = 1; break ;
51 case 2: zust and = 4; break ;
52 case 3: zust and = 4; break ;
53 }
54 break ;
55 case /Zustand/ 1: swi t ch ( token ) {
56 case 0: zust and = 5; break ;
57 case 1: zust and = 1; break ;
58 case 2: zust and = 2; break ;
59 case 3: zust and = 4; break ;
60 }
61 break ;
62 case /Zustand/ 2: swi t ch ( token ) {
63 case 0: zust and = 4; break ;
64 case 1: zust and = 3; break ;
65 case 2: zust and = 4; break ;
66 case 3: zust and = 4; break ;
67 }
68 break ;
69 case /Zustand/ 3: swi t ch ( token ) {
70 case 0: zust and = 5; break ;
71 case 1: zust and = 3; break ;
72 case 2: zust and = 4; break ;
73 case 3: zust and = 4; break ;
5.5 Endliche Automaten mit Ausgabe 43
74 }
75 break ;
76 }
77 }
78 r et ur n ( zustand == 4 ) ;
79 }
80
81 i nt scanner ( char in , i nt reset ) {
82 s t a t i c char p ;
83 char c ;
84 i f ( r e s e t ) { // p auf i n s et z e n
85 p = i n ;
86 ret urn E;
87 }
88 c = ( p++);
89 i f ( c >= 0 && c <= 9 ) r et ur n Z; // Zi f f e r
90 i f ( c == \0 ) r et ur n E; // Ende
91 i f ( c == . ) r et ur n P; // Dezi mal punkt
92 r et ur n R; // Rest
93 }
5.4.1 Deterministische endliche Automaten
Ein deterministischer endlicher Automat besitzt f ur jede Kombination aus Zustand und
Eingabealphabet genau einen Folgezustand. In diesem Skript werden bei den endlichen Au-
tomaten nur deterministische Automaten behandelt.
5.4.2 Nichtdeterministische endliche Automaten
Ein nichtdeterministischer endlicher Automat kann f ur eine Kombination aus Zustand und
Eingabealphabet mehrere Folgezustande besitzen. F ur die Implementierung von nichtdeter-
ministischen Automaten sind daher Backtracking-Algorithmen notwendig, was die Imple-
mentierung nicht gerade vereinfacht.
F ur jeden nichtdeterministischen Automaten kann ein aquivalenter deterministischer Auto-
mat konstruiert werden.
5.5 Endliche Automaten mit Ausgabe
Die bisher behandelten Automaten verarbeiten eine Eingabe und anschlieend wird durch
Auswertung der Endezustande eine Entscheidung getroen. Dieses Verhalten gen ugt mei-
stens, wenn eine Eingabe gema einer Typ-3-Grammatik gepr uft werden soll. Sollen aber
Text gescannt werden, so muss der gescannte Text zusatzlich ausgegeben werden. Damit
dies funktioniert, muss der Automat um eine Ausgabe erweitert werden.
5.5.1 Moore-Automaten
Ein Moore-Automat A ist ein Sechstupel A = (Z, E, , , z
0
, F). Auer der Ausgabefunktion
ist der Moore-Automat identisch mit einem erkennenden Automaten.
ist die Ausgabefunktion die jedem Zustand eine Ausgabe zuordnet. Z Ausgabealphabet.
44 5 Endliche Automaten
5.5.2 Mealey-Automaten
Ein Mealey-Automat A ist ein Sechstupel A = (Z, E, , , z
0
, F). Auer der Ausgabefunktion
ist der Mealey-Automat identisch mit einem erkennenden Automaten.
ist die Ausgabefunktion, die jeder Kombination aus Zustand und Eingabezeichen eine
Ausgabe zuordnet. Z E Ausgabealphabet.
Modell eines Moore- oder Mealey-Automaten
Abbildung 5.4 zeigt das Modell eines Moore- bzw Mealey- Automaten.
Abb. 5.4. Modell Moore- und Mealey-Automat
Eingabeband
Ausgabe
Automat
`


5.6 Anwendungen von endlichen automaten
5.6.1 Erkennen
Ein M unchner im Himmel (1)
Als der M unchener Dienstmann Alois in den Himmel kam, war er so betrunken, dass er
nicht einmal mehr frohlockend Halleluja rufen konnte. Daher wurde ihm das Halleluja
in Form eines erkennenden Automaten erklart:
5.6 Anwendungen von endlichen automaten 45
m
0
m
1
m
2
m
3
m
4
m
5
m
6
mj
8
mj
7


`

`
`
-

`
`
-

`
`
-

l
le
u
ja
E
F
le
lu
u
ja
E
F
ha
l
lu
u
ja
E
F
ha
l
u
ja
E
F
ha
l
le
lu
E
F
ha
l
le
lu
E
F
ha
l
le
lu
u
ja
F
E
ha le u
lu ja
ha l le lu u ja
Parsing-Tabelle:
0 1 2 3 4 5 6 7
E ha l le lu u ja F
0 7 1 7 7 4 7 7 7
1 7 1 2 7 7 7 7 7
2 7 7 7 3 7 7 7 7
3 7 7 7 3 4 7 7 7
4 7 7 7 7 7 5 6 7
5 7 7 7 7 7 5 6 7
6 8 7 7 7 7 7 7 7
Beispiele f ur korrektes Frohlocken sind etwa halleluja, halleleluja, luja oder aber
auch hahahalleleleleluuuuuuuuuuuja. Falsches Frohlocken ware etwa halllleluja oder
haluja.
Listing 5.2 implementiert einen Scanner f ur den Aloisius-Automaten. Er ist primitiv und
nicht laufzeitoptimiert mit Stringvergleichen aufgebaut. Wegen dem Maximum-Match-
Prinzip ist die Reihenfolge der Zeilen 14-16 wichtig, damit beim Aunden der Zeichenfolge
lu das Token lu und nicht das einfache l zur uckgegeben wird.
Listing 5.2. Aloisius-Scanner 1 (primitiv) (automaten-aloisius-scanner1.c)
1 /
2 Scanner 1 f ur Al oi s i us Automat
3 /
4 #i nc l ude <s t r i ng . h>
5
6 i nt a l o i s i us s c a nne r ( char in , i nt reset ) {
7 s t a t i c char p ;
8 enum { t oken end , t oken ha , t ok e n l , t ok e n l e , t oken l u ,
9 t oken u , t oken j a , t o k e n f e h l e r } token ;
10 i f ( reset ) {
11 p = i n ;
12 ret urn 0;
13 }
14 i f ( ! strncmp ( p , l e , 2) ) p+=2, token = t oke n l e ;
15 el se i f ( ! strncmp ( p , l u , 2) ) p+=2, token = t oken l u ;
16 el se i f ( ! strncmp ( p , l , 1) ) p+=1, token = t oke n l ;
17 el se i f ( ! strncmp ( p , ha , 2) ) p+=2, token = token ha ;
46 5 Endliche Automaten
18 el se i f ( ! strncmp ( p , u , 1) ) p+=1, token = token u ;
19 el se i f ( ! strncmp ( p , j a , 2) ) p+=2, token = t oke n j a ;
20 el se i f ( ! strncmp ( p , \0 , 1) ) token = token end ;
21 el se i f ( ! strncmp ( p , \n , 1) ) token = token end ;
22 el se r et ur n t o ke n f e hl e r ;
23 r et ur n token ;
24 }
Listing 5.3. Aloisius-Automat (automaten-aloisius.c)
1 /
2 Erkennender Automat f ur das Frohl ocken des M unchner
3 Dienstmanns Al oi s ( Engel Al o i s i us ) im Himmel
4 /
5 #i nc l ude <s t di o . h>
6 #i nc l ude <s t r i ng . h>
7 #i nc l ude automatena l o i s i us scanner1 . c
8 //#i nc l ude automatena l o i s i us scanner2 . c
9
10 i nt a l o i s i u s p a r s e r ( char in) {
11 s t a t i c i nt pt ab l e [ 7] [ 8] ={/0/ {8 , 1 , 7 , 7 , 4 , 7 , 7 , 7 } ,
12 /1/ { 7 , 1 , 2 , 7 , 7 , 7 , 7 , 7 } ,
13 /2/ { 7 , 7 , 7 , 3 , 7 , 7 , 7 , 7 } ,
14 /3/ { 7 , 7 , 7 , 3 , 4 , 7 , 7 , 7 } ,
15 /4/ { 7 , 7 , 7 , 7 , 7 , 5 , 6 , 7 } ,
16 /5/ { 7 , 7 , 7 , 7 , 7 , 5 , 6 , 7 } ,
17 /6/ { 8 , 7 , 7 , 7 , 7 , 7 , 7 , 7 } };
18 i nt zustand = 0 , token ;
19 a l o i s i us s c a nne r ( in , 1 ) ; // Reset
20
21 while ( zustand < 7) {
22 t oken=al oi s i us s c anne r ( in , 0 ) ;
23 // p r i nt f ( Zustand %d Token %d , zust and , t oken ) ;
24 zust and = pt ab l e [ zust and ] [ t oken ] ;
25 // p r i nt f ( > %d\n , zust and ) ;
26 }
27 r et ur n zustand == 7; // 7 i s t Fehl er zust and
28 }
29
30 i nt main ( ) {
31 char i nput [ 2 5 5 ] ;
32
33 whi l e ( p r i nt f ( Frohl ocken : ) , f g e t s ( i nput , 255 , s t di n )!= NULL)
34 p r i nt f (%sOK! \ n , ( a l o i s i u s p a r s e r ( i nput ))? Ni cht : ) ;
35
36 p r i nt f (\n) ;
37 ret urn 0;
38 }
Bildschirmausgabe:
5.6 Anwendungen von endlichen automaten 47
BCD-Erkenner
Ein Programm soll erkennen, ob ein eingegebener Text bestehend aus 0 und 1 eine korrekte
BCD-Folge darstellt:
BCD Dez.
0000 0
0001 1
0010 2
0011 3
0100 4
0101 5
0110 6
0111 7
1000 8
1001 9
Mogliche Fehler f ur eine Eingabe sind dabei falsche Zeichen (bspw. 0120), falsche BCD-
Codierung (bsp. 1011) und falsche Lange (bspw. 10010).
Ein erkennender Automat konnte folgendermaen konstruiert werden:
Diagramm Tabelle
m
0
m
1
m
2
m
3
m
4
m
5
m
6
mj
7
mj
8

0,1

0,1

0,1

0,1


F
>
>
>
>
>
1,E,F

E,F
`
1,E,F

E,F

E,F
>
>
>
>
>.
E,F
0 1 2 3
E 0 1 F
0 7 1 4 8
1 8 2 2 8
2 8 3 3 8
3 8 0 0 8
4 8 5 8 8
5 8 6 8 8
6 8 0 0 8
Das Eingabealphabet des Automaten ist Ende, 0, 1 und Fehler (falsche Zeichen).
Listing 5.4. BCD-Erkenner (automaten-bcd.c)
1 /
2 BCDErkenner vi a Automat
3 /
4 #i nc l ude <s t di o . h>
5
6 i nt bcd scanner ( char , i nt ) ;
7 i nt bcd par s er ( char ) ;
8
9 i nt main ( ) {
10 char i nput [ 2 5 5 ] ;
11
12 whi l e ( p r i nt f (BCDInput : ) , f g e t s ( i nput , 255 , s t di n )!= NULL)
13 p r i nt f (%sOK! \ n , ( bcd par s er ( i nput ))? Ni cht : ) ;
14
15 p r i nt f (\n) ;
16 ret urn 0;
48 5 Endliche Automaten
17 }
18
19 i nt bcd par s er ( char in) {
20 i nt zust and = 0;
21 s t a t i c i nt pt ab l e [ 7 ] [ 4 ] = { /0/ {7 , 1 , 4 , 8 } ,
22 /1/ { 8 , 2 , 2 , 8 } ,
23 /2/ { 8 , 3 , 3 , 8 } ,
24 /3/ { 8 , 0 , 0 , 8 } ,
25 /4/ { 8 , 5 , 8 , 8 } ,
26 /5/ { 8 , 6 , 8 , 8 } ,
27 /6/ { 8 , 0 , 0 , 8 } };
28 bcd scanner ( in , 1 ) ; // ScannerReset
29 while ( zustand < 7)
30 zustand = pt abl e [ zustand ] [ bcd scanner ( in , 0 ) ] ;
31 r et ur n zustand == 8;
32 }
33
34
35 i nt bcd scanner ( char in , i nt reset ) {
36 s t a t i c char p ;
37 enum { t oken end , t oken 0 , t oken 1 , t o k e n f e h l e r } ;
38 i f ( reset ) {
39 p = i n ;
40 ret urn 0;
41 }
42 swi t ch (p++) {
43 case 0 : ret urn t oken 0 ;
44 case 1 : ret urn t oken 1 ;
45 case \0 :
46 case \n : ret urn t oken end ;
47 d e f a u l t : ret urn t o k e n f e h l e r ;
48 }
49 }
Bildschirmausgabe:
5.6.2 Suchen
Wird in einem langen Text (z.B. einer Datei auf der Festplatte) nach einer Zeichenkette
gesucht, so ist die einfachste Suche der zeichenweise Vergleich des zu durchsuchenden Textes
mit dem Suchtext. Dies ist in Abbildung 5.5 dargestellt. Hier muss ein wichtiges Prinzip
der Suche angesprochen werden, namlich die uberlappenden Treer. Sucht man im Text
rororo nach der Zeichenfolge roro so ergibt sich nur ein Treer in den Stellen 1 bis 4.
Durch diesen Treer darf erst ab Stelle 5 weitergesucht werden und der theoretisch denkbare
Treer in den Stellen 3 bis 6 darf nicht erkannt werden.
An den zwei ineinander geschachtelten Schleifen erkennt man, dass das Laufzeitverhalten
des Algorithmus etwa proportional zu Lange(text) Lange(suche) ist.
Sollen mehrere Begrie gleichzeitig gesucht werden, so wird die Suche entsprechend noch
langsamer. Wenn aber nach einem regularen Ausdruck gesucht wird (so dass es evtl. unend-
lich viele Suchbegrie gibt) kann dieser Einfachalgorithmus keine Losung sein.
5.6 Anwendungen von endlichen automaten 49
Abb. 5.5. Einfache Textsuche
Einfache Textsuche
wichtige Variablen
text {Zu durchsuchender Text}
suche {Zu suchender Text}
i, j {Laufvariablen}
gefunden {Merker}
i=0;
i < Lange(text)
gefunden = 1
f ur jedes i von 0 bis Lange(suche)

text[i+j] = suche[j]?
Ja Nein
gefunden = 0


gefunden = 1?
Ja Nein
Ausgabe i
i = i + Lange(suche)
i = i + 1

Listing 5.5. Textsuche - Einfachstversion (automaten-textsuche.c)


1 /
2 Suche in einem Text nach einem f i xe n Subtext
3 Brachi al methode
4 /
5 #i nc l ude <s t di o . h>
6
7 i nt main ( ) {
8 char t e x t [ ] =
9 a l s l o l a di e l a o l a we l l e sah sang s i e ol ol a , l o l l o l a r o s s o s a l a t ;
10 char suche [ ] = l o l a ;
11 i nt suchl ang , t e x t l ang , i , j , gef unden , anz ;
12
13 s uchl ang = 0;
14 whi l e ( suche [ s uchl ang ] != \0 )
15 s uchl ang++;
16
17 t e x t l a ng = 0;
18 whi l e ( t e x t [ t e x t l a ng ] != \0 )
19 t e x t l a ng ++;
20
21 anz = t e x t l a ng s uchl ang + 1;
22 i = 0;
23 whi l e ( i < anz ) {
24 gef unden = 1;
25 f or ( j = 0; j < s uchl ang ; j ++)
26 i f ( t e x t [ i + j ] != suche [ j ] )
27 gef unden = 0;
28 i f ( gef unden ) {
29 p r i nt f ( Gefunden ab S t e l l e %d\n , i ) ;
30 i += s uchl ang ;
50 5 Endliche Automaten
31 }
32 el se
33 i ++;
34 }
35 }
Durch Konstruktion endlicher Automaten wird die Laufzeit der Suche proportional zur
Lange des zu durchsuchenden Textes unabhangig von Lange oder Komplexitat des zu su-
chenden Textes (regularen Ausdrucks)!
In einem Text soll der regulare Ausdruck "lol""l"*("o"|"a") gesucht werden. Dazu wird
ein Mealey-Automat konstruiert, der immer nach Erkennen des regularen Ausdrucks eine
Ausgabe macht:
m
0
m
1
m
2
m
3
mj
4

`
`
-

`
`
-

`
`
-

`
`
`
`
`

`
l/0 o/0 l/0
o/0,
a/0,
R/0
a/1,o/1,R/0
l/0
E/0 E/0
l/0 E/0 E/0
a/0,R/0
a/0,o/0,R/0
Alternativ die beiden Tabellen:
E l o a R
0 1 2 3 4
0 4 1 0 0 0
1 4 1 2 0 0
2 4 3 0 0 0
3 4 3 0 0 0
E l o a R
0 1 2 3 4
0 0 0 0 0 0
1 0 0 0 0 0
2 0 0 0 0 0
3 0 0 1 1 0
Verfolgen Sie den Automat anhand des Eingabetextes
als lola die laolawelle sah sang sie olala, lollorossosalat
Zeichen a l s l o l a d i e . . .
Zustand 0 0 1 0 0 1 2 3 0 0 0 0 0 . . .
Ausgabe 1 . . .
Listing 5.6. Lola-Automat (automaten-lola.c)
1 /
2 Automat sucht in einem Text den r e gul ar e n Ausdruck
3 l o l l ( o | a )
4 Bsp . l ol a , l o l l l a , l o l l o
5 /
6 #i nc l ude <s t di o . h>
7 i nt l o l a p a r s e r ( char in) {
8 i nt ct yp [ 256] = {
5.6 Anwendungen von endlichen automaten 51
9 0 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 0 , 4 , 4 , 0 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
10 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
11 4 , 3 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 1 , 4 , 4 , 2 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
12 4 , 3 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 1 , 4 , 4 , 2 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
13 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
14 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
15 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 ,
16 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 } ;
17 / ctyp [ \0 ]=ctyp [ \n ]=ctyp [ \ r ]=0
18 ctyp [ L ]=ctyp [ l ]=1
19 ctyp [ O ]=ctyp [ o ]=2
20 ctyp [ A ]=ctyp [ a ]=3
21 a l l e anderen I ndi z e s x : ctyp [ x ] = 4 /
22 i nt de l t a [ 4 ] [ 5 ] = {/ E l o a R /
23 /0/ {4 , 1 , 0 , 0 , 0} ,
24 /1/ {4 , 1 , 2 , 0 , 0} ,
25 /2/ {4 , 3 , 0 , 0 , 0} ,
26 /3/ {4 , 3 , 0 , 0 , 0} };
27 i nt lambda [ 4 ] [ 5 ] = {/ E l o a R /
28 /0/ {0 , 0 , 0 , 0 , 0} ,
29 /1/ {0 , 0 , 0 , 0 , 0} ,
30 /2/ {0 , 0 , 0 , 0 , 0} ,
31 /3/ {0 , 0 , 1 , 1 , 0} };
32 i nt zustand = 0 , pos = 1, token ;
33 while ( zustand != 4) {
34 t oken = ct yp [ ( i n + ++pos ) ] ;
35 // p r i nt f ( Zustand %d , Token %d , zust and , t oken ) ;
36 i f ( l ambda [ zust and ] [ t oken ] )
37 p r i nt f ( Tref f er , Ende an S t e l l e %d\n , pos ) ;
38 zust and = d e l t a [ zust and ] [ t oken ] ;
39 // p r i nt f (> wechs l e i n Zustand %d\n , zust and ) ;
40 }
41 }
42
43 i nt main ( ) {
44 char t e x t [ 256] =
45 a l s Lol a di e Laol awel l e sah sang s i e ol al a , Ll l o l l o r o s s o s a l a t ;
46
47 / p r i nt f (Geben Si e ei nen Text ei n : \ n) ;
48 f g e t s ( t ext , 255 , s t di n ) ; /
49 p r i nt f ( Durchsuche\n\%s \\n , t e x t ) ;
50 p r i nt f ( nach \ l o l \\ l \(\a \| \ o \) : \n\n) ;
51 l o l a p a r s e r ( t e x t ) ;
52 ret urn 1;
53 }
Innerhalb des Parsers wird eine Tabelle ctyp benutzt, die allen ASCII-Zeichen von 0. . . 255
einen der Werte 0. . . 4 (Eingabealphabet des Parsers) zuordnet:
ctyp[x] =

0 f ur x {\0, \n, \r}


1 f ur x {L, l}
2 f ur x {O, o}
3 f ur x {A, a}
4 sonst
52 5 Endliche Automaten
Beim Erstellen solcher Tabellen ist die ASCII-Tabelle (S. 157) hilfreich!
Bildschirmausgabe:
5.6.3 Scannen
Ein Frohlocken-Scanner
Der primitive Scanner des Aloisius-Automaten (Listing 5.2 Seite 45) vergleicht die Eingabe
mithilfe des C-Stringvergleichs. F ur langere Texte oder kompliziertere Textsuchen ware dies
sehr inezient. Es soll daher ein Mealey-Automat vorgestellt werden, der mit linearem
Laufzeitverhalten die Eingabe scannt.
Abb. 5.6. Aloisius-Scanner als Mealey-Automat
m
0
m
1
m
2
m
3
mj
4
mj
5

`
`
`
`
`
`
`
`
`
`
`

u/u, E/E, F/F, a/F, e/F


h/F
j/F
l/F
e/le, u/lu
h/l, a/l, l/l, j/l, E/l, F/l
a/ja, h/F, l/F, e/F, u/F, j/F, E/F, F/F
a/ha, h/F, l/F, e/F, u/F, j/F, E/F, F/F
Im Endezustand 5 muss ein Look-Ahead r uckgangig gemacht werden, im Endezustand 4
dagegen nicht! Die Ausgabe wird jeweils gemerkt und nach Erreichen eines Endezustands
wird die letzte gemerkte Ausgabe als Returnwert zur uckgegeben.
0 1 2 3 4 5 6 7
E h a l e u j F
0 4 2 4 3 4 4 1 4
1 4 4 4 4 4 4 4 4
2 4 4 4 4 4 4 4 4
3 5 5 5 5 4 4 5 5
0 1 2 3 4 5 6 7
E h a l e u j F
0 E F F u F
1 F F ja F F F F F
2 F F ha F F F F F
3 l l l l le lu l l
Text Code
Ende 0
ha 1
l 2
le 3
lu 4
u 5
ja 6
Fehler 7
Listing 5.7. Aloisius-Scanner 2 (schnell) (automaten-aloisius-scanner2.c)
1 /
5.6 Anwendungen von endlichen automaten 53
2 Scanner 1 f ur Al oi s i us Automat
3 Ausgabeal phabet :
4 Ende : 0
5 ha : 1
6 l : 2
7 l e : 3
8 l u : 4
9 u : 5
10 j a : 6
11 Fehl er : 7
12 /
13 #i nc l ude <s t r i ng . h>
14
15 i nt a l o i s i us s c a nne r ( char in , i nt reset ) {
16 s t a t i c char p ;
17 s t a t i c i nt d e l t a [ 4] [ 8] ={/ E h a l e u j F /
18 /0/ {4 , 2 , 4 , 3 , 4 , 4 , 1 , 4 } ,
19 /1/ { 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 } ,
20 /2/ { 4 , 4 , 4 , 4 , 4 , 4 , 4 , 4 } ,
21 /3/ { 5 , 5 , 5 , 5 , 4 , 4 , 5 , 5 } };
22 s t a t i c i nt lambda [ 4 ] [ 8 ] ={/ E h a l e u j F /
23 /0/ {0 , 7 , 7 , 7 , 7 , 5 , 7 , 7 } ,
24 /1/ { 7 , 7 , 6 , 7 , 7 , 7 , 7 , 7 } ,
25 /2/ { 7 , 7 , 1 , 7 , 7 , 7 , 7 , 7 } ,
26 /3/ { 2 , 2 , 2 , 2 , 3 , 4 , 1 , 1 } };
27 i nt chartyp [ 2 5 6 ] = {
28 0 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 0 , 7 , 7 , 0 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
29 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
30 7 , 2 , 7 , 7 , 7 , 4 , 7 , 7 , 1 , 7 , 6 , 7 , 3 , 7 , 7 , 2 , 7 , 7 , 7 , 7 , 7 , 5 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
31 7 , 2 , 7 , 7 , 7 , 4 , 7 , 7 , 1 , 7 , 6 , 7 , 3 , 7 , 7 , 2 , 7 , 7 , 7 , 7 , 7 , 5 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
32 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
33 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
34 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 ,
35 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 , 7 } ;
36 i nt zustand = 0 , token , r e t ur nval ;
37 i f ( reset ) {
38 p = i n ;
39 ret urn 0;
40 }
41 while ( zustand < 4) {
42 t oken=chart yp [ p++];
43 r e t ur nv al = l ambda [ zust and ] [ t oken ] ;
44 zust and = d e l t a [ zust and ] [ t oken ] ;
45 }
46 i f ( zustand == 5) // Lookahead r uckgangi g machen
47 p;
48 r et ur n r e t ur nval ;
49 }
54 5 Endliche Automaten
Internet-Suchmaschine
Syntaktische Analyse von mathematischen Ausdr ucken
Zur Analyse eines mathematischen Ausdrucks benotigen wir eine lexikalische Analyse, welche
uns folgendes Alphabet ausgibt:
Ende
+ (Addition oder positives Vorzeichen)
- (Subtraktion oder negatives Vorzeichen)
* (Multiplikation)
/ (Division)
( (

Onende Klammer)
) (Schlieende Klammer)
Zahl
Fehler
Der zustandige Automat muss dabei vorausschauend operieren (Look-Ahead). Er liest so-
lange ein, bis die Eingabe kein g ultiges Wort mehr ist. Danach muss das letzte gelesene
Zeichen in die Eingabe zur uckgestellt werden.
Als Endezeichen sollten die Zeichen \r, \n, und \0 genutzt werden.
Um f ur die lexikalische Analyse - die als DFA implementiert wird - nicht noch eine eigene
lexikalische Analyse zu schreiben, werden hier (ausnahmsweise) Scanner und Parser als eine
Einheit implementiert.
Eingabealphabet des Parsers:
E Ende
O Operator (+, -, *, /, ( und ))
Z Zier (0-9)
P Dezimalpunkt (.)
F Fehler (Falsche Zeichen)
Diagramm Tabelle
m
0
m
1
m
2
m
3
mj
4
mj
5
mj
6

`
`
-

`
`
-

Z
O,E
P Z
F,P F,P,O,E
E,O,F E,O,F,P Z Z
E O P Z F
0 4 4 6 1 6
1 5 5 2 1 5
2 6 6 6 3 6
3 5 5 5 3 5
5.6 Anwendungen von endlichen automaten 55
Endezustand 4 bedeutet dass ein Operator oder das Ende gefunden wurde.
Endezustand 5 bedeutet dass eine Fixkommazahl gefunden wurde. In diesem Fall wurde
aber ein Zeichen zuviel gelesen (Look-Ahead), welches quasi in die Eingabe zur uckgege-
ben werden muss.
Endezustand 6 bedeutet dass ein Fehler aufgetreten ist.
Listing 5.8. Scanner f ur arithmetische Ausdr ucke (term-scanner.c)
1 /
2 Le xi kal i s c he Anal yse f ur mathemati sche Ausdr ucke di e aus
3 Zahl en ( Fixkomma ) , Klammern und den Operatoren + /
4 bestehen .
5
6 Zum Reset des Scanners ei nen St r i ng a l s e r s t e n Parameter
7 ubergeben .
8 Zum Scannen ei nen NULLZe i ge r ubergeben .
9 /
10 #i nc l ude <s t dl i b . h>
11 #i nc l ude <s t r i ng . h>
12 #i nc l ude <mal l oc . h>
13 #i nc l ude termscanner . h
14
15 char yytext [ 1 0 0 ] ;
16 i nt yyl ex ( ) {
17 ret urn t erm scanner (NULL, y y t e x t ) ;
18 }
19
20 i nt term scanner ( char i nput , char text ) {
21
22 s t a t i c char myinput = NULL;
23 s t a t i c i nt d e l t a [ ] [ 6 ] = { / E O . Z W R /
24 /0/ {4 , 4 , 6 , 1 , 0 , 6 } ,
25 /1/ { 5 , 5 , 2 , 1 , 5 , 5 } ,
26 /2/ { 6 , 6 , 6 , 3 , 6 , 6 } ,
27 /3/ { 5 , 5 , 5 , 3 , 5 , 5 } };
28 s t a t i c i nt lambda [ ] [ 6 ] = { / E O . Z W R /
29 /0/ {1 , 1 , 1 , 1 , 0 , 1 } ,
30 /1/ { 0 , 0 , 1 , 1 , 0 , 0 } ,
31 /2/ { 0 , 1 , 1 , 1 , 1 , 1 } ,
32 /3/ { 0 , 0 , 0 , 1 , 0 , 0 } };
33 i nt zustand = 0 , r c ;
34 s t a t i c i nt pos , token , kl as s e nt ab [ 2 5 6 ] = {
35 0 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 0 , 5 , 5 , 0 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 ,
36 4 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 1 , 1 , 1 , 1 , 5 , 1 , 2 , 1 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 3 , 5 , 5 , 5 , 5 , 5 , 5 ,
37 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 ,
38 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 ,
39 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 ,
40 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 ,
41 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 ,
42 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 5 } ;
43 i f ( i nput != NULL) { // Reset
44 i f ( myinput != NULL)
45 f r e e ( myinput ) ;
46 myinput = ( char ) mal l oc ( s i z e o f ( char ) ( s t r l e n ( i nput ) + 1) ) ;
56 5 Endliche Automaten
47 i f ( myinput == NULL)
48 ret urn 1; // Kein Memory mehr
49 s t r c py ( myinput , i nput ) ;
50 pos = 0;
51 ret urn 0;
52 }
53 i f ( myinput == NULL) // Kein Reset oder kei n Haupt s pei cj er
54 r et ur n 2;
55 do {
56 t oken = k l as s e nt ab [ myinput [ pos ] ] ;
57 // r i n t f ( t oken=%d\n , t oken ) ;
58 i f ( l ambda [ zust and ] [ t oken ] )
59 t e x t++=myinput [ pos ] ;
60 zust and = d e l t a [ zust and ] [ t oken ] ;
61 pos++;
62 } while ( zustand < 4 ) ;
63 text = 0; // St r i nende s c hr e i be n
64 i f ( zustand == 5) { // Zahl gef unden
65 pos ; // Lookahead r uckgaangi g machen
66 rc = ZAHL;
67 }
68 el se i f ( zustand == 4) { // Operator oder Ende
69 s wi t ch (( t ext 1)) {
70 case + : rc = PLUS; break ;
71 case : rc = MINUS; ; break ;
72 case : rc = MAL; ; break ;
73 case / : rc = DIV; ; break ;
74 case ( : rc = KLA AUF; ; break ;
75 case ) : rc = KLA ZU; ; break ;
76 case \n :
77 case \0 : rc = END; ; break ;
78 }
79 }
80 el se
81 r c = FEHLER;
82 r et ur n r c ;
83 }
Listing 5.9. Header-Datei zum Term-Scanner (term-scanner.h)
1 #i f nde f TERM SCANNER
2 #de f i ne TERM SCANNER 1
3 // 0 1 2 3 4 5 6 7 8
4 enum {END, PLUS, MINUS, MAL, DIV, KLA AUF, KLA ZU, ZAHL, FEHLER} ;
5
6 i nt term scanner ( char i nput , char text ) ;
7
8 #e ndi f
Zum Test der lexikalischen Analyse dient folgendes Testprogramm:
Listing 5.10. Testprogramm f ur Scanner (term-scanner-test.c)
1 /
2 Testprogramm f ur MatheScanner
5.7

Ubungen 57
3 /
4 #i nc l ude <s t di o . h>
5 #i nc l ude termscanner . h
6
7 i nt main ( ) {
8 char t [ 255] , z [ 1 0 0 ] ;
9 i nt t oken ;
10
11 whi l e ( p r i nt f ( Input : ) , f g e t s ( t , 255 , s t di n ) != NULL) {
12 t erm scanner ( t , NULL) ;
13 whi l e ( ( t oken = t erm scanner (NULL, z ) ) != END)
14 p r i nt f (%d %s \ n , token , z ) ;
15 }
16 pr i nt f (\n ) ;
17 r et ur n 0;
18 }
Eine Syntaktische Analyse sowie ein Rechenprogramm, das diesen Scanner nutzt, wird im
Kapitel 6 vorgestellt werden.
5.7

Ubungen

Ubung 5.1. Fixkommazahl-Erkennung


Erstellen Sie einen Automaten, der Fixkommazahlen mit Vorzeichen und optionalem Vor-
kommateil akzeptiert (bspw. Akzeptieren von -2, +.5, 2.9, Ablehnen von 2.,
1.2+).

Ubung 5.2. Fliekommazahl-Erkennung


Erstellen Sie einen Automaten, der Fliekommazahlen mit Vorzeichen und optionalem Vor-
kommateil und optionalem 10er-Exponenten (ganzzahlig) akzeptiert (bspw. Akzeptieren von
-2E-4, +.5E1, 2.9, Ablehnen von E-2, 1.2E2.5).

Ubung 5.3. EBNF-Darstellung mit Scanner


Unter Nutzung eines Scanners kann die Grammatik der EBNF-Form einfacher geschrieben
werden:
Start = syntax
syntax = {produktion} .
produktion = bezeichner = ausdruck.
ausdruck = term {| term} .
term = faktor {faktor} .
faktor = bezeichner | string | (ausdruck) | [ausdruck] | {ausdruck}.
Er-
stellen Sie einen Scanner, der die Terminalsymbole
T = {(, ), [, ], {, }, =, ., |, bezeichner, string}
dieser Grammatik scannt. Testen Sie den Scanner mit der Grammatik
ausdruck = term {("+"|"-") term}.
term = faktor {("*"|"/") faktor}.
faktor = zahl | "(" ausdruck ")".
zahl = ziffer {ziffer}.
ziffer = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".
58 5 Endliche Automaten

Ubung 5.4. Sprachsteuerung


Das Kino Informatikon hat zwei Kinos. Im Kino A gibt es Platze zu 10 Euro, 14 Euro
und 20 Euro. Im Kino B gibt es Platze zu 10 Euro und 16 Euro.
Der Film Von Bits und Bytes lauft im Kino A um 14:00 Uhr, 18:00 Uhr und 22:00 Uhr
sowie im Kino B um 15:00 Uhr. Der Film Digitale Virologie lauft im Kino A um 20:00
Uhr und im Kino B um 18:00 Uhr.
Entwickeln Sie f ur ein telefonisches Spracherkennungssystem - das als Ausgabe die Ziern
0-9 sowie E (Ende) und F (Fehler) hat - einen Automaten, der die Buchung vornimmt.
Die Eingabe des Automaten soll uber Tastatur simuliert werden.

Ubung 5.5. Aloisius 3

Andern Sie den erkennenden Aloisius-Automaten in einen Moore-Automaten mit nur einem
Endezustand ab.

Ubung 5.6. Aloisius 4

Andern Sie Grammatik / Automat so ab, dass mehrere Halleluja mit ein oder mehreren
Ma Bier zwischen den einzelnen Hallelujas moglich sind.

Ubung 5.7. Aloisius 1


Erstellen Sie die Typ-3-Grammatik zum Frohlocken in EBNF

Ubung 5.8. Aloisius 2


Erstellen Sie die Typ-3-Grammatik zum Frohlocken als Syntax-Graphen

Ubung 5.9. Aloisius 5


Erstellen Sie einen erkennenden Automaten als Scanner f ur den Aloisius-Parser. Dieser Auto-
mat hat f ur jedes Zeichen des Parser-Eingabealphabets einen Endezustand. Durch geschickte
Wahl der Zustandsnummern kann aus dem Endezustand direkt das Zeichen des Ausgabeal-
phabets abgeleitet werden.

Ubung 5.10. BCD 1


Erstellen Sie die Grammatik f ur BCD-Zahlen in EBNF.

Ubung 5.11. BCD 2


Erstellen Sie die Grammatik f ur BCD-Zahlen als Syntax-Graph.
6
Keller-Automaten
6.1 Einf uhrung
Die Sprache L = a
n
b
n
produziert Satze , ab, aabb, aaabbb, . . .. Soll diese Sprache durch einen
endlichen Automaten erkannt werden, so m usste dieser Automat unendlich viele Zustande
haben, wie Abbildung g endlicher automat an bn zeigt.
Abb. 6.1. Endlicher Automat f ur L = a
n
b
n
m
0
mj
1
m
2
m
3

b
m
4
m
5

b
m
6
m
7


b
m
8
m
9

b
m
n-2
m
n-1
m
n
...
a
...
b

...
b
Kellerautomaten haben im Gegensatz zu den einfachen Automaten einen Speicher in Form
eines Kellers (Stacks). Auf dem Stack wird der Zustand des Automaten gespeichert. Dadurch
kann der Automat - wenn er in einen anderen Zustand geht - sich seine alten Zustande
merken. Abbildung 6.2 zeigt das Modell eines Kellerautomaten.
Ein Kellerautomat kennt zum Speichern die drei Aktionen
push Legt ein Element auf oben auf den Stack
pop Holt ein Element loschend oben vom Stack
tos Holt ein Element nichtloschend oben vom Stack
Abhangig vom anliegenden Eingabezeichen und dem obersten Stackelement kann nun tabel-
larisch das Verhalten des Automaten bestimmt werden.
END a b
END ACCEPT push(a);read() ERROR
a ERROR push(a);read() pop();read()
60 6 Keller-Automaten
Abb. 6.2. Modell eines Kellerautomaten
Eingabeband
Ausgabe
Kellerautomat
K
e
l
l
e
r
s
p
e
i
c
h
e
r
`

6.2 Native Konstruktion


Es soll der folgende Ausdruck ausgewertet werden:
1+2*3
Die Token-Folge des Scanners lautet damit ZAHL, PLUS, ZAHL, MAL, ZAHL, END.
Es werden zwei Kellerspeicher angelegt, einer f ur die Zahlen (Operanden), einer f ur die
Operatoren.
1. Als erstes wird die Zahl 1 eingelesen und auf dem Operandenkeller gespeichert:
Operanden
1
Operatoren
2. Jetzt wird der Operator + eingelesen und auf dem Operatorenkeller gespeichert:
Operanden
1
Operatoren
+
3. Jetzt wird die Zahl 2 eingelesen und auf dem Operandenkeller gespeichert:
Operanden
2
1
Operatoren
+
6.2 Native Konstruktion 61
4. Jetzt wird der Operator * eingelesen und auf dem Operatorenkeller gespeichert:
Operanden
1
Operatoren
*
+
5. Jetzt wird die Zahl 3 eingelesen und auf dem Operandenkeller gespeichert:
Operanden
3
2
1
Operatoren
*
+
6. Jetzt wird das Ende-Token eingelesen. Der Keller wird abgearbeitet indem jeweils die
zwei obersten Operanden gelesen werden, mit dem obersten Operator verkn upft werden
und das Ergebnis auf dem Operandenkeller gespeichert wird:
Operanden
6
1
Operatoren
+
Operanden
7
Operatoren
7. Am Ende ist der Operatorkeller leer und der Operandenkeller enthalt eine Zahl - das
Ergebnis 7 des Ausdrucks 1+2*3!
Wird also eine Zahl gefunden so wird sie (immer) auf dem Operatorkeller gespeichert. Wird
dagegen ein Operand gefunden so wird eine Aktion durchgef uhrt die abhangig von dem
gefundenen Operator und dem obersten im Operandenkeller gespeicherten Operator ist:
Token
E + - * / ( )
E X K K K K K F
+ D G G K K K D
- D G G K K K D
* D D D G G K D
/ D D D G G K D
( F K K K K K L
Dabei bedutet
X Exit, fertig
K Operator abkellern
G Kelleroperation durchf uhren, neuen Operator abkellern
D Kelleroperation durchf uhren (evtl. wiederholt)
L Ein Kellerelement loschen
F Fehler
62 6 Keller-Automaten
Listing 6.1. Kellerautomat zur Berechnung arithmetischer Ausdr ucke (keller-arith1.c)
1 /
2 Rechenprogramm f ur ar i t hme t i s c he Ausdr ucke
3 Kann Grundrechenarten mit PunktVorSt r i c h und Klammern
4 Kann KEINE Vorzei chen ! ! ! ! !
5 /
6 #i nc l ude <s t di o . h>
7 #i nc l ude automatenmathescanner . c
8
9 #de f i ne STACK HEIGHT 10
10
11 i nt kel l er aut omat ( char , doubl e ) ;
12 i nt k e l l e r r ( doubl e , doubl e , i nt , doubl e ) ;
13
14 i nt kel l er aut omat ( char i nput , doubl e z ) {
15 doubl e zk [STACK HEIGHT] ; // Zahl e nk e l l e r
16 i nt ok [ STACK HEIGHT] ; // Operandenkel l er
17 i nt oh = 1, zh = 1; // Operandenhohe , Zahl enhohe
18 doubl e z1 , z2 , z3 ;
19 i nt token , er r or = 0;
20 char akt i on , pt ab l e [ 6] [ 7] ={
21 / END + / ( ) /
22 / END / { X , K , K , K , K , K , F } ,
23 / + / { D , G , G , K , K , K , D } ,
24 / / { D , G , G , K , K , K , D } ,
25 / / { D , D , D , G , G , K , D } ,
26 / / / { D , D , D , G , G , K , D } ,
27 / ( / { F , K , K , K , K , K , L } };
28
29 mathescanner ( i nput , NULL) ; // ScannerReset
30 ok[++oh ] = END;
31 do {
32 t oken = mathescanner ( i nput , &z1 ) ;
33 i f ( t oken == ZAHL) // Zahl a b k e l l e r n
34 zk[++zh ] = z1 ;
35 e l s e i f ( t oken == FEHLER)
36 er r or = 3;
37 e l s e // Operand
38 do {
39 akt i on = pt ab l e [ ok [ oh ] ] [ t oken ] ;
40 s wi t ch ( akt i on ) {
41 case K : // Operator k e l l e r n
42 ok[++oh ] = t oken ;
43 break ;
44 case G : // Gl ei chr angi ge Operat i on
45 z2 = zk [ zh ];
46 z1 = zk [ zh ];
47 er r or = k e l l e r r ( z1 , z2 , ok [ oh], &z3 ) ;
48 i f ( ! er r or ) {
49 zk[++zh ] = z3 ; // Ergebni s auf St ack
50 ok[++oh ] = t oken ; // Neuer Operand
51 }
52 break ;
53 case D : // Ke l l e r abar bei t en
6.3 LL-Parsing 63
54 z2 = zk [ zh];
55 z1 = zk [ zh];
56 e r r or = k e l l e r r ( z1 , z2 , ok [ oh] , &z3 ) ;
57 i f ( ! e r r or )
58 zk[++zh ] = z3 ; // Ergebni s auf Stack
59 break ;
60 case L : // Ge ke l l e r t e Klammer l os chen
61 oh;
62 break ;
63 case F : // Ge ke l l e r t e Klammer l os chen
64 e r r or = 1;
65 break ;
66 }
67 } while ( ! e r r or && akt i on == D ) ;
68 } while ( ! e r r or && token != END) ;
69 i f ( zh != 0)
70 e r r or = 2;
71 i f ( ! e r r or )
72 z = zk [ 0 ] ;
73 r et ur n e r r or ;
74 }
75
76 i nt k e l l e r r ( doubl e z1 , doubl e z2 , i nt operator , doubl e z3 ) {
77 s wi t ch ( operat or ) {
78 case PLUS: z3 = z1 + z2 ; ret urn 0;
79 case MINUS: z3 = z1 z2 ; ret urn 0;
80 case MAL: z3 = z1 z2 ; ret urn 0;
81 case DIV: i f ( z2 == 0) ret urn 1;
82 z3 = z1 / z2 ; ret urn 0;
83 }
84 }
85
86 i nt main ( ) {
87 char t [ 2 5 6 ] ;
88 i nt r et code ;
89 doubl e z ;
90 whi l e ( p r i nt f (Rechnung : ) , f g e t s ( t , 255 , s t di n ) != NULL)
91 i f ( ( r et code = kel l er aut omat ( t , &z ) ) == 0)
92 p r i nt f ( Ergebni s : %l f \n\n , z ) ;
93 e l s e
94 p r i nt f ( Fehl er %d\n\n , r et code ) ;
95 p r i nt f (\n) ;
96 ret urn 0;
97 }
6.3 LL-Parsing
Im folgenden werden Parsing-Algorithmen entwickelt, die einen Syntaxbaum beginnend von
der Wurzel des Baumes entwickeln. Dies nennt man Top-Down-Parsing.
64 6 Keller-Automaten
6.3.1 Einf uhrung
Betrachten wir Grammatik
14
: Grammatik
14
: LL(1)-Grammatik
S A | B | C
A [D] E
B {F} G
C p | q
D r s | B
E {t | u} v
F [w] x
G y z
Nun wollen wir den Syntaxbaum f ur den Satz rstuuuv entwickeln. Warum geht das so
einfach?
FIRST(S) = FIRST(A) FIRST(B) FIRST(C) = {p, q, r, t, u, v, w, x, y}
FIRST(A) = FIRST(D) FIRST(E) = {r, t, u, v}
FIRST(B) = FIRST(F) FIRST(G) = {w, x, y}
FIRST(C) = {p, q}
FIRST(D) = {r}
FIRST(E) = {t, u, v}
FIRST(F) = {w, x}
FIRST(G) = {y}
FIRST(G) = {y}
FIRST(a) = {a} a ist Terminal!
FIRST(AB) = FIRST(A) falls A nicht nach ableitbar
FIRST(AB) = FIRST(A) FIRST(B) falls A nach ableitbar
FIRST({A}B) = FIRST(A) FIRST(B)
FIRST([A]B) = FIRST(A) FIRST(B)
FIRST(A|B) = FIRST(A) FIRST(B)
FIRST(S) = FIRST(A) FIRST(B) FIRST(C) = {p, q, r, t, u, v, w, x, y}
FIRST(A) = FIRST(D) FIRST(E) = {r, t, u, v}
FIRST(B) = FIRST(F) FIRST(G) = {w, x, y}
FIRST(C) = {p, q}
FIRST(D) = {r}
FIRST(E) = {t, u, v}
FIRST(F) = {w, x}
FIRST(G) = {y}
Es folgt die

Uberpr ufung der LL(1)-Bedingungen:
Regel 0: FIRST(A) FIRST(B) = {} FIRST(A) FIRST(C) = {} FIRST(B) FIRST(C) = {}
Regel 1: FIRST(D) FIRST(E) = {}
Regel 2: FIRST(F) FIRST(G) = {}
Eine kontextfreie Grammatik heit LL(1)-Grammatik, wenn zusatzlich zur Kontextfreiheit
die folgenden Bedingungen erf ullt sind:
F ur alle Produktionen mit Alternativen (A
1
|
2
| . . . |
n
) mu gelten
FIRST (
i
) FIRST (
j
) = {} f ur alle i , j mit i = j
F ur alle Produktionen die sich auf den Leerstring ableiten lassen (A ) mu gelten
FIRST (A) FOLLOW (A) = {}
Der Name LL(1) bedeutet Left-to-right-scanning, leftmost derivation, look-ahead 1 token.
6.3 LL-Parsing 65
6.3.2 FIRST und FOLLOW
Algorithmus 4: Berechnung der FIRST-Mengen
Berechnung der FIRST-Mengen
F ur jedes Terminal a
FIRST(a) := {a}
F ur jede Regel X Y
1
Y
2
. . . Y
k
i := 0
i := i+1
FIRST(X) := FIRST(X) (FIRST(Y
i
) {})
bis / FIRST(Y
i
)


FIRST(Y
i
) f ur jedes i?
Ja Nein
FIRST(X) := FIRST(X) {}
wiederhole solange

Anderungen
Algorithmus 5: Berechnung der FOLLOW-Mengen
Berechnung der FOLLOW-Mengen
FOLLOW(S) := {$}
F ur jedes A B
FOLLOW(B) := FOLLOW(B) (FIRST() {})

FIRST() =
Ja Nein
FOLLOW(B) := FOLLOW(B) FOLLOW(A)
wiederhole solange

Anderungen
Denition 6.1 (FIRST). Unter der Menge FIRST(A)eines Nicht-Terminals A versteht
man die Menge aller Terminal-Symbole , die zu Beginn eines Textes stehen konnen, der
aus A produziert wird.
Denition 6.2 (FOLLOW). Unter der Menge FOLLOW(A) eines Nicht-Terminals A
versteht man die Menge aller Terminal-Symbole , die unmittelbar nach einem Text stehen
konnen, der aus A produziert wird.
6.3.3 Programm-gesteuertes LL(1)-Parsing
Die einfachste moglichkeit, einen LL(1)-Parser zu entwickeln, ist die Methode des rekursiven
Abstiegs (engl. recursive descent). Siehe auch [Wir86], Seite 24., [Wir96], Seite 16,
[Sed92], S. 361.
Voraussetzung f ur einen Recursive-descent-Parser ist eine LL(1)-Grammatik. F ur eine solche
Grammatik kann ein Parser folgendermaen konstruiert werden:
1. Eine globale Variable token dient als Look-Ahead-Variable
2. Eine allgemeine Fehlerfunktion void error () wird im Fehlerfall aufgerufen.
66 6 Keller-Automaten
3. Es existiert eine Funktion void get token() die das jeweils nachste Token liest (Scanner-
Aufruf) und in der globalen Variablen token speichert.
4. f ur jeder Produktionsregel der Form A wird eine Funktion void f A() codiert.
5. Nichtterminale Symbole X werden dadurch verarbeitet, indem ihre Funktion void f X()
aufgerufen wird.
6. Terminale Symbole werden dadurch verarbeitet, indem das aktuelle Token mit dem
erwarteteten Token verglichen wird. Im Gleichheitsfall wird get token() aufgerufen, an-
dernfalls error();
7. Sequenzen werden durch sequentielle Codierung der Symbole abgearbeitet.
8. Alternativen werden durch mehrfache if-else-Verzweigungen oder durch switch-case-
Statements codiert. Eventuell wird error () aufgerufen.
9. Optionen werden durch ein einfaches if ohne else codiert.
10. Wiederholungen werden durch kopfgesteuerte Schleifen codiert.
Betrachten wir Grammatik
15
:
Grammatik
15
: Term-Grammatik in EBNF
E T {(+|-) T}
T F {(*|/) F}
F - F | ( E ) | id
Listing 6.2. Programmgesteuerter LL(1)-Parser (ll1-programm.c)
1 /
2 /
3 #i nc l ude <s t di o . h>
4 #i nc l ude mathescanner . h
5
6 voi d f e r r o r ( ) ;
7 voi d f ne xt t oke n ( ) ;
8 voi d f c a l c u l a t i o n ( ) ;
9 voi d f f a c t o r ( ) ;
10 voi d f e xpr e s s i o n ( ) ;
11 voi d f t er m ( ) ;
12
13 i nt token , e r r or ;
14 char text [ 2 5 5 ] ;
15
16 voi d f e r r o r ( ) {
17 er r or = 1;
18 }
19
20 voi d f ne xt t oke n ( ) {
21 t oken = mathescanner (NULL, t e x t ) ;
22 }
23
24 voi d f f a c t o r ( ) {
25 i f ( t oken == ZAHL)
26 f ne x t t o k e n ( ) ;
27 e l s e i f ( t oken == KLA AUF) {
28 f ne x t t o k e n ( ) ;
29 f e x p r e s s i o n ( ) ;
30 i f ( t oken == KLA ZU)
6.3 LL-Parsing 67
31 f ne x t t o k e n ( ) ;
32 e l s e
33 f e r r o r ( ) ;
34 }
35 el se f e r r o r ( ) ;
36 }
37
38 voi d f t er m ( ) {
39 f f a c t o r ( ) ;
40 whi l e ( t oken == MAL | | t oken == DIV) {
41 f ne x t t o k e n ( ) ;
42 f f a c t o r ( ) ;
43 }
44 }
45
46 voi d f e xpr e s s i o n ( ) {
47 f t er m ( ) ;
48 whi l e ( t oken == PLUS | | t oken == MINUS) {
49 f ne x t t o k e n ( ) ;
50 f t er m ( ) ;
51 }
52 }
53
54 voi d f c a l c u l a t i o n ( ) {
55 f e x p r e s s i o n ( ) ;
56 i f ( t oken == END)
57 f ne x t t o k e n ( ) ;
58 e l s e
59 f e r r o r ( ) ;
60 }
61
62 i nt main ( ) {
63 char i nput [ 2 5 5 ] ;
64 whi l e ( p r i nt f ( Input : ) , f g e t s ( i nput , 255 , s t di n )!= NULL) {
65 mathescanner ( i nput , NULL) ; // ScannerReset
66 t oken = mathescanner (NULL, t e x t ) ;
67 er r or = 0;
68 f c a l c u l a t i o n ( ) ;
69 i f ( ! er r or )
70 p r i nt f ( Syntax OK! \ n) ;
71 e l s e
72 p r i nt f ( Syntax ni cht OK! \ a\n) ;
73 }
74 r et ur n 0;
75 }
6.3.4 LL-Parsing mit Stackverwaltung
Gegeben ist linksrekursionsfreie Grammatik
16
.
68 6 Keller-Automaten
Grammatik
16
: Linksrekurionsfreie Term-Grammatik in BNF
S E $
E T E
E + T E |
T F T
T * F T |
F ( E ) | zahl
Abb. 6.3. Syntaxbaum bei linksrekursionsfreier Grammatik
zahl
1
F

`
`
T
+
zahl
2
F
*
zahl
3
F

T
>
>
>
>

`
`
T

'
'

E $

S
Der Parser verhalt sich nun folgendermaen:
Wenn auf TOS ein Terminal ist:
Wenn TOS = token dann losche TOS und lies weiter,
andernfalls FEHLER!
Andernfalls (TOS ist Non-Terminal)
Wenn in Parser-Tabelle(TOS,token) Regel eingetragen dann losche TOS und lege
Regel r uckwarts auf Stack
andernfalls FEHLER!
Wiederhole die ersten zwei Punkte bis Stack und Eingabe leer sind.
6.3 LL-Parsing 69
Algorithmus 6: LL-1-Algorithmus mit Stackverwaltung
Gegeben: PTABLE[TOS][token]
Lege Ende-Token auf Stack
Lege Startregel r uckwarts auf Stack
Lies token
error = 0
Eingabe nicht leer AND error = 0


TOS ist Terminal
Ja Nein

TOS = token?
Ja Nein
lies token error = 1

>
>
>
>
>
>
>
>
PTABLE[TOS][token]
gesetzt?
Ja Nein
losche TOS
Lege Regel
PTABLE[TOS][token]
r uckwarts auf Stack
error = 1

Voraussetzung f ur den Algorithmus ist eine linksrekursionsfreie Grammatik.


Anwendung von Algorithmus 4 zur Berechnung der FIRST-Mengen bzw. Algorithmus 5 zur
Berechnung der FOLLOW-Mengen auf Grammatik
16
ergibt
FIRST(E) = {(, id}
FIRST(E

) = {+, }
FIRST(T) = {(, id}
FIRST(T

) = {, }
FIRST(F) = {(, id}
FOLLOW(E) = {$, )}
FOLLOW(E

) = {$, )}
FOLLOW(T) = {+, $, )}
FOLLOW(T

) = {+, $, )}
FOLLOW(F) = {, +, $, )}
Tabelle 6.2 zeigt, wie tabellengesteuert Code ausgegeben werden kann. Terminale Symbole
werden beim Weiterlesen ausgegeben, zu nicht-terminalen Symbolen kann in der Grammatik
ein Ausgabetext hinterlegt werden, welcher dann ausgegeben wird, wenn das Symbol auf dem
Stack geloscht wird (Algorithmus 6). Die Grammatik mit Ausgabe ist in
17
dargestellt.
Grammatik
17
: Linksrekurionsfreie Term-Grammatik in BNF mit Ausgabe
S E $
E T E
E + T E
ADD
|
T F T
T * F T
MULT
|
F ( E ) | zahl
Die Konstruktion der Parse-Tabelle nach [ASU88], Seite 231 ist in Algorithmus 7 darge-
stellt.
70 6 Keller-Automaten
Tabelle 6.1. Parsing-Vorgang
Stapel Eingabe Aktion
$E 1 + 2 * 3 $ E TE

$E

T 1 + 2 * 3 $ T FT

$E

F 1 + 2 * 3 $ F zahl
$E

zahl 1 + 2 * 3 $
$E

+ 2 * 3 $ T


$E

+ 2 * 3 $ E

+TE

$E

T+ + 2 * 3 $
$E

T 2 * 3 $ T FT

$E

F 2 * 3 $ T zahl
$E

zahl 2 * 3 $
$E

* 3 $ T

FT

$E

F * 3 $
$E

F 3 $ F zahl
$E

zahl 3 $
$E

$ T


$E

$ E


$ $
Tabelle 6.2. Parsing-Vorgang mit Code-Erzeugung
Stapel Eingabe Aktion Ausgabe
$E 1 + 2 * 3 $ E TE

$E

T 1 + 2 * 3 $ T FT

$E

F 1 + 2 * 3 $ F zahl
$E

zahl 1 + 2 * 3 $ 1
$E

+ 2 * 3 $ T


$E

+ 2 * 3 $ E

+TE

$E

ADD
T+ + 2 * 3 $
$E

ADD
T 2 * 3 $ T FT

$E

ADD
T

F 2 * 3 $ T zahl
$E

ADD
T

zahl 2 * 3 $ 2
$E

ADD
T

* 3 $ T

FT

$E

ADD
T

MULT
F * 3 $
$E

ADD
T

MULT
F 3 $ F zahl
$E

ADD
T

MULT
zahl 3 $ 3
$E

ADD
T

MULT
$ T

MULT
$E

ADD
$ E

ADD
$ $
Algorithmus 7: LL(1)-Tabelle erstellen
F ur jeder Regel A der Grammatk
F ur jedes Terminal a FIRST(A)
Trage A in M[A,a] ein

FIRST()?
Ja Nein
F ur jedes Terminal b FOLLOW(A)
Trage A in M[A,b] ein

Beispiel Grammatik
18
in Tabelle 6.3
6.3 LL-Parsing 71
Grammatik
18
: Mathematischer Term linksrekursionsfrei
E T E
E + T E
E - T E
E
T F T
T * F T
T / F T
T
F ( E )
F id
Tabelle 6.3. LL(1)-Parsetabelle
Top of Token
Stack END + - * / ( ) ZAHL ERR
E T E T E
E + t e - t e
T F T F T
T * F T / F T
F ( E ) zahl
Listing 6.3 implementiert den hier erlauterten Parser.
Listing 6.3. LL(1)-Parser mit Stackverwaltung (ll1-stack.c)
1 /
2 Tabe l l e nge s t e ue r t e r r e k u r s i o n s f r e i e r LL(1)Parser
3

Ahnl i ch Grammatik 4. 11 aus Drache 1 , Se i t e 214:
4 Regel 0: S : : = E $
5 Regel 1: E : : = T E
6 Regel 2: E : : = + T E
7 Regel 3: E : : = T E
8 Regel 4: E : : = eps
9 Regel 5: T : : = F T
10 Regel 6: T : : = F T
11 Regel 7: T : : = / F T
12 Regel 8: T : : = eps
13 Regel 9: F : : = ( E )
14 Regel 10: F : : = zahl
15 /
16 #i nc l ude <s t di o . h>
17 #i nc l ude termscanner . h
18 #i nc l ude termscanner . c
19 #i nc l ude genprogs t ack . h
20 #i nc l ude genprogs t ack . c
21
22 enum {nt E = 256 , nt E2 , nt T , nt T2 , nt F} ; // NonTermi nal s
23 i nt n t o f f s e t = 256;
24
25 i nt par s e t abl e [ 5 ] [ 9 ] = {
26 / END + / ( ) z ahl err /
27 /E / { 1, 1, 1, 1, 1, 1 , 1, 1 , 1} ,
28 /E / { 4 , 2 , 3 , 1, 1, 1, 4 , 1, 1},
72 6 Keller-Automaten
29 /T / { 1, 1, 1, 1, 1, 5 , 1, 5 , 1},
30 /T / { 8 , 8 , 8 , 6 , 7 , 1, 8 , 1, 1} ,
31 /F / { 1, 1, 1,1, 1, 9 , 1, 10 , 1} ,
32 };
33
34 i nt g [ 1 1 ] [ 4 ] = { // Grammatik
35 /0/ { nt E , END, 1 } ,
36 /1/ { nt T , nt E2 , 1 } ,
37 /2/ { PLUS, nt T , nt E2 , 1 } ,
38 /3/ { MINUS, nt T , nt E2 , 1 } ,
39 /4/ {1 } ,
40 /5/ {nt F , nt T2 , 1 } ,
41 /6/ {MAL, nt F , nt T2 , 1 } ,
42 /7/ {DIV, nt F , nt T2 , 1 } ,
43 /8/ {1 } ,
44 /9/ {KLA AUF, nt E , KLA ZU, 1 } ,
45 /10/{ZAHL, 1 } ,
46 };
47
48 s t ack s ;
49
50 voi d r e g e l a uf s t a c k ( i nt r ) {
51 i nt i ;
52 i = 1;
53 p r i nt f ( r e g e l %d\n , r ) ;
54 whi l e ( i ++, g [ r ] [ i ] >=0);
55 f or ( i ; i >= 0; i )
56 s t ack pus h(&s , &(g [ r ] [ i ] ) ) ;
57 }
58
59 voi d i nt pr i nt ( i nt i ) { // f ur s t ac k pr i nt Funkti on
60 p r i nt f (%4d , i ) ;
61 }
62
63 i nt main ( ) {
64 char i nput [ ] = (1 2) 2 + 3;
65 char t e x t [ 2 5 5 ] ;
66 i nt token , er r or = 0 , r , t os ;
67
68 s t a c k i n i t (&s , s i z e o f ( i nt ) , 100 , i n t p r i n t ) ;
69 t erm scanner ( i nput , NULL) ; // ScannerI ni t
70 t oken = t erm scanner (NULL, t e x t ) ;
71
72 r e g e l a u f s t a c k ( 0) ; // St a r t r e g e l auf St ack
73 whi l e ( ! er r or && s t a c k h e i g h t (&s ) ) {
74 i f ( s t ack pop(&s , &t os ) , t os < n t o f f s e t ) // Terminal !
75 i f ( t os == t oken )
76 t oken = t erm scanner (NULL, t e x t ) ;
77 e l s e
78 er r or = 1;
79 e l s e // NonTerminal
80 i f ( ( r = p a r s e t a b l e [ t os n t o f f s e t ] [ t oken ] ) >= 0)
81 r e g e l a u f s t a c k ( r ) ;
82 e l s e
6.3 LL-Parsing 73
83 er r or = 1;
84 }
85 pr i nt f ( Parsee r ge bni s : %d\n , e r r or ) ;
86 s t a c k de l (&s ) ;
87 r et ur n 0;
88 }
6.3.5 Elimination der Links-Rekursion
Algorithmus 8: Elimination der Links-Rekursion
Gegeben: A A
1
|A
2
| . . . |A
m
|
1
|
2
| . . . |
n
(Kein
i
beginnt mit A)
Verfahren : Ersetze
A A
1
|A
2
| . . . |A
m
|
1
|
2
| . . . |
n
durch
A
1
A

|
2
A

| . . . |
n
A

und f uge hinzu


A


1
A

|
2
A

| . . . |
m
A

|
Siehe [ASU88], Seite 214.
6.3.6 Tabellen-gesteuertes LL-Parsing
Siehe auch [Wir96], Seite 22.
Am einfachsten basierend auf Syntaxgraphen der Regeln. F ur jede Pfeilspitze wird ein Vier-
tupel angelegt:
Art des Knotens (Terminal oder Non-Terminal),
Nr des entsprechenden Eintrags,
Alternative, falls kein Match,
Fortsetzung, falls Match.
Damit kann der Recursive-Descent-Parser unabhangig von der konkreten Grammatik imple-
mentiert werden:
Algorithmus 9: Tabellen-gesteuertes LL-Parsing
LL1-parser(knoten)
solange knoten g ultig

Terminal-Knoten und token = knoten?


Ja Nein
token = scanner()
knoten = nachfolger

Non-Terminal-Knoten und token


FIRST(knoten)?
Ja Nein
LL1-parser(knoten)
knoten = nachfolger
knoten = Nachste Alter-
native

74 6 Keller-Automaten
Als Nebeneekt der beschriebenen Datenstruktur f ur die Syntaxdiagramme erscheint die
Tatsache, dass die FIRST-Mengen einfach berechnet werden konnen. Listing 6.4 implemen-
tiert einen tabellengesteuerten LL(1)-Parser, der komplett unabhangig von der Grammatik
codiert ist.
Als Beispiel soll f ur die Term-Grammatik
15
(Seite 66) ein Parser entwickelt werden. Dazu
wird Syntax-Diagramm 7 entwickelt.
Syntaxdiagramm 7: Term-Grammatik
S:

E
0

$
1

E:

T
2

+
3

T
5

T:

F
6

/
8

F
9

F:

zahl
10

(
11

E
12

)
13

14

F
15

Tabelle 6.4. Parstabelle zu tabellengesteuertem LL(1)-Parsen mit Rekursion
Regel Knoten Typ Wert Alt. Nachf. FIRST
S 0 NT 2 e 1 {zahl,(,+,-}
1 T $ e x
E 2 NT 6 e 3 {zahl,(,+,-}
3 T + 4 5
4 T - x 5
5 NT 6 e 3
T 6 NT 10 e 7 {zahl,(,+,-}
7 T * 8 9
8 T / x 9
9 NT 10 e 7
F 10 T zahl 11 x {zahl,(,+,-}
11 T ( 14 12
12 NT 2 e 13
13 T ) e x
14 T - e 15
15 NT 10 e x
Listing 6.4. Tabellengesteuerter LL(1)-Parser (ll1-tabellarisch.c)
1 /
2 Tabe l l ar i s c h g e s t e ue r t e r LL(1)Parser
3 ToDo: Fi r s t Mengen aus Tabel l e kons t r ui e r e n !
4 /
6.3 LL-Parsing 75
5 #i nc l ude termscanner . c
6 #i nc l ude <s t di o . h>
7
8 i nt LL1 f i r s t ( node t abl e , i nt act node , i nt token ) {
9 i nt f i r s t s e t [ 256] = {0} , nt [ 2 5 6 ] = {0} , i , ok = 0;
10 do {
11 nt [ act node ] = 1;
12 whi l e ( act node >= 0) {
13 i f ( t a b l e [ act node ] . t ype == t er mi nal )
14 f i r s t s e t [ t a b l e [ act node ] . nr ] = 1;
15 e l s e i f ( t a b l e [ act node ] . t ype == nont ermi nal )
16 i f ( nt [ t a b l e [ act node ] . nr ] == 0)
17 nt [ t a b l e [ act node ] . nr ] = 2; // Noch abar bei t en !
18 act node = t a b l e [ act node ] . a l t ;
19 }
20 act node = 1; // Jet z noch unbear bei t et NTRegel n suchen
21 while (++act node < 256 && nt [ act node ] != 2 ) ;
22 } while ( act node < 256) ;
23 i f ( token >= 0) {
24 f or ( i = 0; i < 256; i ++)
25 i f ( f i r s t s e t [ i ] && i == t oken )
26 ok = 1;
27 }
28 el se
29 for ( i = 0; i < 256; i ++)
30 i f ( f i r s t s e t [ i ] )
31 pr i nt f ( %d , i ) ;
32 r et ur n ok ;
33 }
34
35 i nt LL1 parser ( node t abl e , i nt act node , i nt ( scanner ) ( ) ) {
36 i nt er r or = 0;
37 s t a t i c i nt t oken ;
38 s t a t i c char t e x t [ 1 0 0 ] ;
39 i f ( t a b l e == NULL) {
40 t oken = scanner ( t e x t ) ;
41 ret urn 0;
42 }
43 whi l e ( act node >= 0 && ! er r or ) {
44 // p r i nt f ( act node = %d , t oken = %d\n , act node , t oken ) ;
45 i f ( t a b l e [ act node ] . t ype == t er mi nal
46 && t a b l e [ act node ] . nr == t oken ) {
47 act node = t a b l e [ act node ] . next ;
48 t oken = scanner ( t e x t ) ;
49 }
50 e l s e i f ( t a b l e [ act node ] . t ype == nont ermi nal
51 && ( 1 | | LL1 f i r s t ( t ab l e , act node , t oken ) ) ) {
52 er r or = LL1 parser ( t ab l e , t a b l e [ act node ] . nr , scanner ) ;
53 act node = t a b l e [ act node ] . next ;
54 }
55 e l s e {
56 act node = t a b l e [ act node ] . a l t ;
57 }
58 }
76 6 Keller-Automaten
59 ret urn er r or | | ( act node == 1);
60 }
6.4 LR-Parsing
Im Gegensatz zum Top-Down-Parsing wird beim Bottom-Up-Parsing versucht, ausgehend
von den Blattern eines Syntaxbaums durch R uckwarts-Anwendung der Produktionsregeln
der Grammatik (durch Reduzieren), einen Eingabetext auf das Startsymbol zu reduzieren.
Handls sind dabei rechte Seiten einer Regel.

Ahnlich wie beim tabellengesteuerten LL(1)-
Parsing m ussen die Produktionsregeln daher einfach aufgebaut sein.
Betrachten wir Grammatik
19
(Beispiel aus [ASU88] Seite 267:
Grammatik
19
: Einfache Term-Grammatik in BNF
1) E E + T
2) E T
3) T T * F
4) T F
5) F ( E )
6) F zahl
Ein LR-Kellerautomat kennt vier Aktionen:
Reduziere das Handle am Stackende anhand einer Regel
Schiebe das nachste Eingabeelement auf den Stack
Akzeptiere die Eingabe
Fehler in der Eingabe
Stack Input Aktion
1 + 2 * 3 $ s
1 + 2 * 3 $ r6
F + 2 * 3 $ r4
T + 2 * 3 $ r2
E + 2 * 3 $ s
E + 2 * 3 $ s
E + 2 * 3 $ r6
E + F * 3 $ r4
E + T * 3 $ s (!!!)
E + T * 3 $ s
E + T * 3 $ r6
E + T * F $ r3
E + T $ r1
E $ a
Interessant ist der mit !!! markierte Zustand. Man hatte hier zwar das Handle T uber Regel
2 zu E reduzieren konnen, da aber ein *-Zeichen als Eingabe anliegt und das Handle aus
Regel 5 mit T* beginnt macht es sinn, auf dieses Handle zu setzen.
Die Entscheidung, welche Operation durchgef uhrt wird, hangt also einerseits von mehreren
Elementen am Ende des Stacks und andererseits von der Eingabe ab.
Zuerst soll der Syntaxbaum f ur den Ausdruck 1 + 2 3 erstellt werden:
6.4 LR-Parsing 77
1
j
+ 2 * 3
1 + 2 * 3
F
j
1 + 2 * 3
F
T
j
1 + 2 * 3
F
T
E
j
1 +
j
2 * 3
F
T
E
j
1 +
j
2
j
* 3
F
T
E
j
78 6 Keller-Automaten
1 +
j
2 * 3
F
T
E
j
F
j
1 +
j
2 * 3
F
T
E
j
F
T
j
1 +
j
2 *
j
3
F
T
E
j
F
T
j
1 +
j
2 *
j
3
j
F
T
E
j
F
T
j
1 +
j
2 *
j
3
F
T
E
j
F
T
j
F
j
1 +
j
2 * 3
F
T
E
j
F
T
F
T
`
`
`
`

j
6.4 LR-Parsing 79
1 + 2 * 3
F
T
E
F
T
F
T
`
`
`
`

j
1
F
T
E
+
2
F
T
*
3
F
.
.
.

T
.
.
.
.
.
.
.
.
.
.

E
Da nun die Suche nach dem optimalen Handle zeitaufwendig ist, werden Parse-Tabellen
entwickelt, die lediglich anhand des letzten Stack-Elements operieren konnen. Das Erstellen
dieser Tabellen, die f ur typische Programmiersprachen wie Pascal oder C mehrere hundert
Zeilen enthalten, ist in der Praxis zu aufwandig. Daher werden diese Tabellen von Compiler-
generatoren erstellt. F ur ein Verstandnis der Compilergeneratoren ist aber ein prinzipielles
Verstandnis dieses Verfahren notwendig.
Tabelle 6.5. LR-Tabelle f ur Ausdr ucke
Zustand Aktion Sprung
z + * ( ) $ E T F
0 s5 s4 1 2 3
1 s6 a
2 r2 s7 r2 r2
3 r4 r4 r4 r4
4 s5 s4 8 2 3
5 r6 r6 r6 r6
6 s5 s4 9 3
7 s5 s4 10
8 s6 s11
9 r1 s7 r1 r1
10 r3 r3 r3 r3
11 r5 r5 r5 r5
80 6 Keller-Automaten
Algorithmus 10: LR-Algorithmus
LR-Algorithmus
Gegeben: aktion- und sprung-Tabelle
stack-push(Zustand 0); token = scanner();
zustand = stack-tos()
a = aktion[zustand][token]

a = shift z2
Ja Nein
stack-push(token)
stack-push(z2)
token = scanner()

a = reduceA
Ja Nein
Losche 2|| Stackelemente
z2 = stack-tos()
stack-push(A)
stack-push(sprung[z2][A])

solange aktion = accept, aktion = error


Wendet man nun Algorithmus 10 auf Tabelle 6.4 und die Eingabe 1 +2 3 an so ergibt sich
der in Tabelle 6.6 dargestellte Parse-Vorgang:
Tabelle 6.6. LR-Parse-Vorgang 1+2*3
Stack Input Aktion
0 1 + 2 * 3 $ s5
0 z5 + 2 * 3 $ r6
0 F3 + 2 * 3 $ r4
0 T2 + 2 * 3 $ r2
0 E1 + 2 * 3 $ s6
0 E1 +6 2 * 3 $ s5
0 E1 +6 z5 * 3 $ r6
0 E1 +6 F3 * 3 $ r4
0 E1 +6 T9 * 3 $ s7
0 E1 +6 T9 *7 3 $ s5
0 E1 +6 T9 *7 z5 $ r6
0 E1 +6 T9 *7 F10 $ r3
0 E1 +6 T9 $ r1
0 E1 $ a
Listing 6.5. LR-Parser (keller-lr-parser.c)
1 /
2 LRParser
3 Zu Drache S 267
4 /
5 #i nc l ude termscanner . c
6 #i nc l ude genprogs t ack . c
7
8 char akt i on [ 1 2 ] [ 9 ] [ 4 ] = {
9 / $ + / ( ) z ahl err
10 / 0/ { er , er , er , er , er , s4 , er , s5 , er } ,
6.4 LR-Parsing 81
11 / 1/ {ac , s6 , s6 , er , er , er , er , er , er } ,
12 / 2/ {r2 , r2 , r2 , s7 , s7 , er , r2 , er , er } ,
13 / 3/ {r4 , r4 , r4 , r4 , r4 , er , r4 , er , er } ,
14 / 4/ {er , er , er , er , er , s4 , er , s5 , er } ,
15 / 5/ {r6 , r6 , r6 , r6 , r6 , er , r6 , er , er } ,
16 / 6/ {er , er , er , er , er , s4 , er , s5 , er } ,
17 / 7/ {er , er , er , er , er , s4 , er , s5 , er } ,
18 / 8/ {er , s6 , s6 , er , er , er , s11 , er , er } ,
19 / 9/ {r1 , r1 , r1 , s7 , s7 , er , r1 , er , er } ,
20 /10/ {r3 , r3 , r3 , r3 , r3 , er , r3 , er , er } ,
21 /11/ {r5 , r5 , r5 , r5 , r5 , er , r5 , er , er } };
22
23 i nt sprung [ 1 2 ] [ 3 ] = { / 0/ { 1 , 2 , 3} ,
24 / 1/ {1,1,1} ,
25 / 2/ {1,1,1} ,
26 / 3/ {1,1,1} ,
27 / 4/ { 8 , 2 , 3} ,
28 / 5/ {1,1,1} ,
29 / 6/ {1, 9 , 3} ,
30 / 7/ {1,1,10} ,
31 / 8/ {1,1,1} ,
32 / 9/ {1,1,1} ,
33 /10/ {1,1,1} ,
34 /11/ {1,1,1} };
35
36 enum {nt E , nt T , nt F} ;
37 s t r uc t { i nt nt ; i nt l ; } g [ 7 ] = {{1, 1} , // ni cht verwendet
38 /R1/ {nt E , 3} ,
39 /R2/ {nt E , 1} ,
40 /R3/ {nt T , 3} ,
41 /R4/ {nt T , 1} ,
42 /R5/ {nt F , 3} ,
43 /R6/ {nt F , 1} };
44
45 voi d i nt pr i nt ( voi d p) { // F ur StackProgramme
46 p r i nt f (%d , ( i nt ) p ) ;
47 }
48
49 i nt main ( ) {
50 i nt token , i , r , t os , a , dummy, z2 ;
51 char t o s can [ ] = 1 2 + 3 , t e x t [ 1 0 ] ;
52 s t ack s ;
53
54 s t a c k i n i t (&s , s i z e o f ( i nt ) , 100 , i n t p r i n t ) ;
55 dummy = 0 , s t ack pus h(&s , &dummy) ;
56
57 t erm scanner ( t o scan , NULL) ;
58 t oken = t erm scanner (NULL, t e x t ) ;
59
60 do {
61 s t a c k t o s (&s , &t os ) ;
62 p r i nt f ( t os = %2d t oken = %d akt i on = %s \n ,
63 t os , token , akt i on [ t os ] [ t oken ] ) ;
64 a = akt i on [ t os ] [ t oken ] [ 0 ] ;
82 6 Keller-Automaten
65 r = at oi ( akt i on [ t os ] [ t oken ] +1);
66 i f ( a == s ) { // Schi ebe
67 s t ack pus h(&s , &t oken ) ;
68 s t ack pus h(&s , &r ) ;
69 t oken = t erm scanner (NULL, t e x t ) ;
70 }
71 el se i f ( a == r ) { // Reduzi ere
72 f or ( i = 0; i < 2 g [ r ] . l ; i ++)
73 s t ack pop(&s , &dummy) ;
74 s t a c k t o s (&s , &z2 ) ;
75 s t ack pus h(&s , &(g [ r ] . nt ) ) ;
76 s t ack pus h(&s , &(sprung [ z2 ] [ g [ r ] . nt ] ) ) ;
77 }
78
79 } while ( a != e && a != a ) ;
80 pr i nt f ( Ergebni s : %c\n , a ) ;
81 r et ur n 0;
82 }
Erzeugung von LR-Parser-Tabellen
LR-Parser-Tabellen sind - zumindest f ur die klassischen Programmiersprachen - zu aufwandig
f ur eine handische Erstellung. F ur eine detaillierte Lekt ure empehlt sich [ASU88], Seiten
269.
In der Praxis werden diese Tabellen mittels eines Parser-Generators automatisch erstellt.
Der sicherlich bekannteste Parser-Generator ist YACC bzw. sein GNU-Derivat Bison (siehe
auch 12.4 ab Seite 127. Der PL/0-Parser des Modellcompilers besteht aus einer Tabelle mit
99 Zustanden!
Als ein Beispiel f ur die Generierung von Tabellen betrachten wir Listing 6.6, einer Imple-
mentierung unserer Term-Grammatik in Yacc.
Listing 6.6. Term-Grammatik in Yacc (lr-term.y)
1 %token zahl
2 %%
3 E: E + T { p r i nt f (+\n) ; }
4 | T
5 ;
6
7 T: T F
8 | F
9 ;
10
11 F: ( E )
12 | zahl
13 ;
14 %%
15 i nt yyl ex ( ) {
16 i nt c = get char ( ) ;
17 ret urn ( c != EOF) ? c : 0;
18 }
6.4 LR-Parsing 83
Man man Yacc so aufrufen, dass Zusatzinformationen wie Grammatik-Konikte aber auch
der produzierte Parser in einer gesonderten Datei beschrieben werden. F ur Listing 6.6 ist
diese Information in Listing 6.7 beschrieben:
Listing 6.7. Term-Grammatik in Yacc - Zusatzinformationen (lr-term.output)
1 Grammatik
2
3 0 $accept : E $end
4
5 1 E: E + T
6 2 | T
7
8 3 T: T F
9 4 | F
10
11 5 F: ( E )
12 6 | zahl
13
14
15 Termi nal e und di e Regeln , in denen s i e verwendet werden
16
17 $end ( 0) 0
18 ( ( 40) 5
19 ) ( 41) 5
20 ( 42) 3
21 + ( 43) 1
22 e r r or ( 256)
23 zahl ( 258) 6
24
25
26 Ni chtTermi nal und di e Regeln , in denen s i e verwendet werden
27
28 $accept ( 8)
29 auf der l i nke n Se i t e : 0
30 E ( 9)
31 auf der l i nke n Se i t e : 1 2 , auf der r echt en Se i t e : 0 1 5
32 T ( 10)
33 auf der l i nke n Se i t e : 3 4 , auf der r echt en Se i t e : 1 2 3
34 F ( 11)
35 auf der l i nke n Se i t e : 5 6 , auf der r echt en Se i t e : 3 4
36
37
38 Zustand 0
39
40 0 $accept : . E $end
41
42 zahl s c hi e be und gehe zu Zustand 1

A
1
4
ber
43 ( s c hi e be und gehe zu Zustand 2

A
1
4
ber
44
45 E gehe zu Zustand 3

A
1
4
ber
46 T gehe zu Zustand 4

A
1
4
ber
47 F gehe zu Zustand 5

A
1
4
ber
48
49
84 6 Keller-Automaten
50 Zustand 1
51
52 6 F: zahl .
53
54 $de f aul t r e duz i e r e mit Regel 6 (F)
55
56
57 Zustand 2
58
59 5 F: ( . E )
60
61 zahl s c hi e be und gehe zu Zustand 1

A
1
4
ber
62 ( s c hi e be und gehe zu Zustand 2

A
1
4
ber
63
64 E gehe zu Zustand 6

A
1
4
ber
65 T gehe zu Zustand 4

A
1
4
ber
66 F gehe zu Zustand 5

A
1
4
ber
67
68
69 Zustand 3
70
71 0 $accept : E . $end
72 1 E: E . + T
73
74 $end s c hi e be und gehe zu Zustand 7

A
1
4
ber
75 + s c hi e be und gehe zu Zustand 8

A
1
4
ber
76
77
78 Zustand 4
79
80 2 E: T .
81 3 T: T . F
82
83 s c hi e be und gehe zu Zustand 9

A
1
4
ber
84
85 $de f aul t r e duz i e r e mit Regel 2 (E)
86
87
88 Zustand 5
89
90 4 T: F .
91
92 $de f aul t r e duz i e r e mit Regel 4 (T)
93
94
95 Zustand 6
96
97 1 E: E . + T
98 5 F: ( E . )
99
100 + s c hi e be und gehe zu Zustand 8

A
1
4
ber
101 ) s c hi e be und gehe zu Zustand 10

A
1
4
ber
102
103
6.4 LR-Parsing 85
104 Zustand 7
105
106 0 $accept : E $end .
107
108 $de f aul t annehmen
109
110
111 Zustand 8
112
113 1 E: E + . T
114
115 zahl s c hi e be und gehe zu Zustand 1

A
1
4
ber
116 ( s c hi e be und gehe zu Zustand 2

A
1
4
ber
117
118 T gehe zu Zustand 11

A
1
4
ber
119 F gehe zu Zustand 5

A
1
4
ber
120
121
122 Zustand 9
123
124 3 T: T . F
125
126 zahl s c hi e be und gehe zu Zustand 1

A
1
4
ber
127 ( s c hi e be und gehe zu Zustand 2

A
1
4
ber
128
129 F gehe zu Zustand 12

A
1
4
ber
130
131
132 Zustand 10
133
134 5 F: ( E ) .
135
136 $de f aul t r e duz i e r e mit Regel 5 (F)
137
138
139 Zustand 11
140
141 1 E: E + T .
142 3 T: T . F
143
144 s c hi e be und gehe zu Zustand 9

A
1
4
ber
145
146 $de f aul t r e duz i e r e mit Regel 1 (E)
147
148
149 Zustand 12
150
151 3 T: T F .
152
153 $de f aul t r e duz i e r e mit Regel 3 (T)
Analysiert man die Informationen in der Datei, so ergibt sich die folgende Parser-Tabelle:
86 6 Keller-Automaten
Zustand Aktion Sprung
z + * ( ) $ E T F
0 s1 s2 3 4 5
1 r6 r6 r6 r6 r6 r6
2 s1 s2 6 4 5
3 s8 s7
4 r2 r2 s9 r2 r2 r2 2
5 r4 r4 r4 r4 r4 r4
6 s8 s10
7 a a a a a a
8 s1 s2 11 5
9 s1 s2 1 12
10 r5 r5 r5 r5 r5 r5
11 r1 r1 s9 r1 r1 r1
12 r3 r3 r3 r3 r3 r3
6.5 Operator-Prioritats-Analyse
a b Prioritat von a ist kleiner als Prioritat von b4
a
.
= b Prioritat von a ist gleich Prioritat von b4
a b Prioritat von a ist kleiner als Prioritat von b4
Algorithmus 11: Operator-Prazendenz-Algorithmus
Operator-Prazendenz-Algorithmus
stack-push(Zustand 0); token = scanner();
solange stack-tos() = END OR token =END


a b
Ja Nein
stack-push(token)
token = sacnner()

a token
Ja Nein
b = stack-pop()
bis stack-tos() b

Listing 6.8. Operator-Prazedenz-Analyse (operator-precedence.c)


1 /
2 OperatorPrecedence
3 /
4 #i nc l ude termscanner . h
5 #i nc l ude genprogs t ack . h
6 #i nc l ude <s t di o . h>
7 i nt pr i o r i t y [ 9 ] [ 9 ] = {
8
6.5 Operator-Prioritats-Analyse 87
9 //END, PLUS, MINUS, MAL, DIV, KLA AUF, KLA ZU, ZAHL, FEHLER} ;
10
11 / END / {2, 1, 1, 1, 1, 1, 2, 1, 2} ,
12 / PLUS / {+1, +1, +1, 1, 1, 1, +1, 1, 2} ,
13 / MINUS/ {+1, +1, +1, 1, 1, 1, +1, 1, 2} ,
14 / MAL / {+1, +1, +1, +1, +1, 1, +1, 1, 2} ,
15 / DIV / {+1, +1, +1, +1, +1, 1, +1, 1, 2} ,
16 / KLA AUF / {2, 1, 1, 1, 1, 1, 0 , 1, 2} ,
17 / KLA ZU / {+1, +1, +1, +1, +1, 2, +1, 2, 2} ,
18 / ZAHL / {+1, +1, +1, +1, +1, 2, +1, 2, 2} ,
19 / FEHLER / {2, 2, 2, 2, 2, 2, 2, 2, 2} };
20
21 t ypedef s t r uc t {
22 i nt t oken ;
23 char t e x t [ 2 0 ] ;
24 } s t ack el em ;
25
26 voi d t oke n pr i nt ( s t ack el em p) {
27 p r i nt f (%d (\%s \) , p>token , p>t e x t ) ;
28 }
29
30
31 i nt main ( ) {
32 char i nput [ ] = (1 + 2) 3 , t e x t [ 1 0 0 ] ;
33 s t ack s ;
34 s t ack el em next , t os , el em ;
35 i nt er r or ;
36
37 s t a c k i n i t (&s , s i z e o f ( s t ack el em ) , 15 , t ok e n pr i nt ) ;
38
39 t erm scanner ( i nput , NULL) ;
40 next . t oken = t erm scanner (NULL, next . t e x t ) ;
41
42 er r or = 0;
43 t os . t oken = 0 , t os . t e x t [ 0 ] = \0 , s t ack pus h(&s , &t os ) ;
44 // s t a c k p r i nt (&s ) ;
45 whi l e ( ! er r or && ( ( s t a c k t o s (&s , &t os ) , t os . t oken != END) ) | | next . t oken != END ) {
46 p r i nt f (TOS: %d (\%s \) , t os . token , t os . t e x t ) ;
47 p r i nt f ( i nput : %d (\%s \)\n , next . token , next . t e x t ) ;
48 i f ( p r i o r i t y [ t os . t oken ] [ next . t oken ] == 1 | | p r i o r i t y [ t os . t oken ] [ next . t oken ] == 0) {
49 s t ack pus h(&s , &next ) ;
50 next . t oken = t erm scanner (NULL, next . t e x t ) ;
51 p r i nt f (\ t push \n) ;
52 // s t a c k p r i nt (&s ) ;
53 }
54 el se i f ( pr i o r i t y [ t os . token ] [ next . token ] == +1) {
55 do {
56 s t ack pop(&s , &el em ) ;
57 p r i nt f (\ t pop %d (\%s \)\n , el em . token , el em . t e x t ) ;
58 } while ( s t ac k t os (&s , &t os ) , p r i o r i t y [ t os . token ] [ elem . token ] != 1);
59
60 }
61 el se
62 e r r or = 1;
88 6 Keller-Automaten
63 f f l u s h ( st dout ) ;
64
65 }
66
67 r et ur n 0;
68 }
7
Symboltabellen
Problem: Verwendung von Bezeichnern nicht uber Grammatik kontrollierbar.
Beispiel: ! a.
Grammatik, die Bezeichnernamen beinhaltet, ware zu komplex
Abhilfe: Symboltabelle
Ersetzt alle Bezeichner durch int-Werte
Wegen Rekursion zur Compilezeit aber noch keine Errechnung eines HS-PLatzes
moglich
F ur jeden Bezeichner ist ein Tripel zu verwalten:
Level-Delta im statischen Code
Oset, laufende Nr im jeweiligen Level
Typ des Bezeichners
Ersetzen in Programm 9.1 (Seite 104) alle Bezeichner durch Typ/Level/Oset
7.1 Einf uhrung
Wichtig bei der Diskussion der Symboltabelle sind Begrie wie
Namensbereiche von Bezeichnern (CT)
Lokale und globale Bezeichner
Lebenszeit eines Bezeichners (RT)
7.2 Methoden der Symboltabelle
Eine Symboltabelle hat prinzipiell die folgenden vier Methoden:
Einf ugen von neuen Bezeichnern: insert
Suchen von Bezeichnern: lookup
Betreten eines neuen Namensbereichs: level-up
Verlassen eines Namensbereichs: level-down
90 7 Symboltabellen
7.3 Aufbau eines Symboltabelle
Vorstellung: Die Symboltabelle ist eine zweidimensionale Tabelle, die in der einen Dimension
die Symbole und in der anderen Dimension die Namensbereiche verwaltet.
Praktisch wird man eine Symboltabelle so nicht codieren, da f ur alle Namensbereiche eine
feste feste maximale Zahl von Bezeichnern.
Die Tabelle beinhaltet Name und Typ eines Bezeichners. Die wichtigen Werte Level und
Oset ergeben sich aus der Position eines Bezeichners in der Symboltabelle.
Abb. 7.1. Aufbau einer Symboltabelle
4
3
2
1
0
Level * Ebene 0 Ebene 1 Ebene 2 Ebene 3
In Zeile 2 von Listing 9.1 werden die Variablen a und d lokal im Hauptprogramm deklariert,
in Zeile 4 kommt die Prozedur f dazu. Die Symboltabelle hat nun folgendes Aussehen:
4
3
2 f proc
1 d var
0 a var
Level * Ebene 0 Ebene 1 Ebene 2 Ebene 3
In Zeile 4 wird ein neuer Namensbereich betreten, in Zeile 5 die Variablen b und d deklariert
und in Zeile 7 die Prozedur g lokal zu f deklariert:
4
3
2 f proc g proc
2 d var d var
0 a var b var
Level Ebene 0 * Ebene 1 Ebene 2 Ebene 3
In Zeile 7 wird dann ein neuer Namensraum betreten und lokal die Variablen c und d
deklariert:
4
3
2 f proc g proc
1 d var d var d var
0 a var b var c var
Level Ebene 0 Ebene 1 * Ebene 2 Ebene 3
Bei den Variablenzugrien in den Zeilen 10 bis 14 ergibt sich nun f ur a das Bitupel (Level
2, Oset 0), f ur c das Bitupel (Level 0, Oset 0) und f ur d das Bitupel (Level 0, Oset 1).
Wird bei der Compilierung Zeile 17 erreicht, so siht die Symboltabelle wieder aus wie nach
Erreichen des Namensraums von Prozedur f. F ur a ergibt sich jetzt das Bitupel (Level 1,
Oset 0), f ur b das Bitupel (Level 0 Oset 0),f ur d ergibt sich (Level 0, Oset 1) und f ur g
(was ja eine Prozedur ist!) (Level 0, Oset 2).
7.6 Eine Symboltabelle auf Basis der C++-STL-Map 91
7.4 Fehlermoglichkeiten
Insert: Kein Speicherplatz mehr vorhanden
Bezeichnernamen im aktuellen Level bereits eingetragen
Lookup: Bezeichner nicht vorhanden
Bezeichner falscher Typ
Level-Up: Kein Speicherplatz mehr
Level-Down: Bereits in Level 0
7.5 Namens-Suche
Bei der Symboltabelle ist die schnelle Suche nach Strings notig. Bei kleinen Programmen,
die ubersetzt werden, ist ein einfacher String-Vergleich problemlos. Kritisch wird es aber,
wenn etwa mit einem C-Compiler ein ganzes Betriebssystem mit mehreren zehntausend
Zeilen Quellcode compiliert werden muss. In einem solchen Fall muss der Text-Suche in der
Symboltabelle besondere Bedeutung zugemessen werden.
Moglichkeiten zur Beschleunigung waren
Hash-Verfahren
Sortierte Index-Felder f ur binare Suche
Dynamische Generierung endlicher Automaten zur Indexierung
7.6 Eine Symboltabelle auf Basis der C++-STL-Map
Die Symboltabelle wird aus einem Array aus Maps (siehe C++-STL) aufgebaut. Das Array
bildet die verschiedenen Level der Symboltabelle ab. Die einzelnen Maps - die aus Key-Value-
Paaren aufgebaut sind - haben als Zugris-Key den Namen des Symbols und als Value das
Tupel {Eintragsart; Laufende Nr.}. Die jeweilige aktuelle Hohe der Spalten wird in einem
Array height gespeichert.

Uber die Maps - die intern uber B-Baume realisiert sind - sind
nun eziente String-Suchen moglich.
Listing A.7 zeigt die Klassendeklaration, Listing A.6 den Code der Klasse.
8
Zwischencode
Betrachten wir wieder einmal Abbildung 1.1 auf Seite 13. Im Frontend des Compilers ben-
den sich Scanner und Parser, im Backend die (noch zu behandelnde) Codeerzeugung.
Bei kleinen Compilern wird die Code-Generierung gerne direkt in den Parser integriert, dies
spart Arbeit und macht den gesamten Code des Compilers kompakt. Es gibt aber einige
gute Gr unde, warum der Parse-Vorgang (das Frontend) von der Code-Generierung (dem
Backend) getrennt werden sollte:
Soll nicht nur ein einzelner, isolierter Compiler erstellt werden, sondern eine ganze
Compiler-Collection wie etwa die GCC, so m ussten f ur n Quellsprachen und m Ziel-
systeme (n m) verschiedene Compiler erstellt werden. Abbildung 8.1 zeigt diesen Sach-
verhalt.
Wird statt der direkten Integration von Front- und Backend aber zwischen diesen Mo-
dulen eine sauber denierte und vor allem f ur alle Quellsprachen und Zielsysteme ein-
heitliche Schnittstelle verwendet, so reduziert sich der Aufwand auf die Erstellung von n
Frontends und m Backends. Dies wird in Abbildung 8.2 dargestellt.


Anderungen in der Vorgehensweise des Compilers ziehen viele Vreanderungen nach sich.
So ist es in der Regel nicht einfach moglich, einen Recursive-Descent-Parser durch einen
Bottom-Up-Parser zu ersetzen. Dies resultiert aus der Tatsache, dass die Grammatiken
meist f ur bestimmte Parse-Techniken angepasst werden m ussen. So haben wir bereits
verschiedene Grammtiken f ur arithmetische Terme kennengelernt, etwa Grammatik 12
(Seite 28) oder Grammatik 18 (Seite 71).
Aus diesen Gr unden wird die Codeerzeugung vom Parser getrennt und ein sog. Zwischencode
als Schnittstelle zwischen Front- und Backend eingesetzt.
8.1 Einf uhrung
Leider existiert f ur den Zwischencode nicht eine so schone Theorie wie die der Formalen Spra-
chen oder der Automatentheorie, diese Theorien hatten die lexikalische und die syntaktische
Analyse einfach werden lassen. Stattdessen muss eine allgemeine Schnittstelle gefunden wer-
den. Beim Betrachten von Abbildung 8.2 wird klar, dass diese Schnittstelle sowohl C- als
auch Fortran-, Pascal- und weitere Programme abbilden konnen muss.
In der Praxis ist Zwischencode haug
ein Zielsystem-unabhangiger Pseudo-Assembler-Code
f ur Register-Stack-Maschinen
f ur Zwei-Adress-Maschinen
94 8 Zwischencode
Abb. 8.1. Compiler-Collection ohne Schnittstelle
Fortran
Pascal
C
ARM
Interpreter
Motorola 68k
Intel x86

>
>
>
>
>
>
>
>
>
>
>
>

>
>
>
>
>
>
>
>
>
>
>
>
`
`
`
`
`
`
`
`
`
`
`
`
`
`
`
`
``

Abb. 8.2. Compiler-Collection mit Schnittstelle


Fortran
Pascal
C
ARM
Interpreter
Motorola 68k
Intel x86
9
8
6
7
Zwischen-
Code

`
`
`
`
`
`


.
.
.

`
`
`
`
`
`
`
`
`-
f ur Drei-Adress-Maschinen
ein Syntaxbaum
Syntax-Baume sind mit Sicherheit die beste, aber auch die schwierigste Variante von Zwi-
schencodes. Bei Syntax-Baumen ist es sehr wichtig, dass sie die Sprache abbilden und
nicht die Grammatik. Andernfalls ware ein Austausch des Parsers wie oben diskutiert nicht
moglich. Daraus resultiert die Namensgebung Abstrakter Syntaxbaum.
8.2 AST f ur Terme
Gewahlt wird nicht der Syntaxbaum der Grammatik, sondern der Rechenbaum (Operator-
baum).
Die Regeln zum Erstellen eines Syntaxbaums sind die folgenden:
8.2 AST f ur Terme 95
Es ist bzgl. Operatorprioritat und -assoziativitat der schwachste Operator zu suchen und
oben im Baum einzutragen. F ur jeden Operanden wird ein AST nach unten
1
gezeichnet.
F ur jeden Operanden wird
dieser am Ende des Asts eingetragen, falls der Operand eine Zahl ist,
ein Teilbaum gezeichnet, falls der Operand ein Teilausdruck ist.
Als Beispiel soll der Syntaxbaum f ur den Ausdruck 1 +2 3 erstellt werden: Der schwachste
Operator ist der +-Operator. Der linke Operand ist die Zahl 1, der rechte Operand der
Teilausdruck 2 3. Da dieser nur noch einen Operator enthalt ist die Erstellung des ent-
sprechenden Teilbaums trivial. Der gesamte Syntaxbaum ist in Abbildung 8.3 dargestellt.
Abb. 8.3. Syntaxbaum f ur Term 1 + 2 3
1
2 3

`
`

.
.
.
`
`
+
In diesen Syntaxbaumen sind keine Klammern mehr vorhanden. Diese werden uber die Form
des Syntaxbaums abgebildet.
Hat ein Compiler einen Syntaxbaum erstellt, so kann in einen zweiten Schritt dieser abge-
arbeitet werden.
Durchlauft man den Baum im Inorder-Verfahren unter Ber ucksichtigung der verschiede-
nen Operatoren so kann das Ergebnis des Terms errechnet werden.
Analog kann durch das Inorder-Verfahren auch Drei-Address-Code erzeugt werden.
Durchlauft man den Baum im Postorder-Verfahren so kann die UPN-Syntax ausgegeben
werden.
Da Operatoren zwei Operanden haben (mit der Ausnahme der unaren Vorzeichen, die nur
einen Operanden haben), bietet sich eine dynamische Datenstruktur f ur die Abbildung des
Baums an.
Abb. 8.4. Datenstruktur f ur binaren Operator-Baum

-
`
`
`
Text
l
r
t ypedef s t r uc t s node as t ;
t ypedef s t r uc t s node as t node ;
s t r uc t s node {
char t e x t [ 1 0 ] ;
as t l ;
as t r ;
} ;
1
In der Informatik stehen Baume immer auf dem Kopf!
96 8 Zwischencode
Im Text wird entweder der Zahlenwert oder aber der Operator als ASCII-Text gespeichert,
wobei f ur das unare Minus der Text CHS (Change-sign) eingesetzt wird. In den Blatt-
knoten sind l- und r-Zeiger auf NULL gesetzt, beim unaren Minus ist nur der l-Zeiger gesetzt.
Diese Datenstruktur ist als struct node in Listing 8.1 deniert. Der Code ndet sich in
Listing 8.2.
Listing 8.1. Denitionsdatei zum Operator-Baum (ast/term-ast.h)
1 /
2 HeaderDatei zum Syntaxbaum
3 /
4 #i f nde f TERM TREE H
5 #de f i ne TERM TREE H 1
6 t ypedef s t r uc t s node as t ;
7 t ypedef s t r uc t s node as t node ;
8 s t r uc t s node {
9 char t e x t [ 1 0 ] ;
10 as t l ;
11 as t r ;
12 } ;
13 // Prototypen
14 voi d t r e e out put ( as t , i nt ) ;
15 voi d t r e e f r e e ( as t ) ;
16 voi d t r e e c ode ( as t ) ;
17 doubl e t r e e r e s u l t ( as t ) ;
18 as t new node ( char , ast , as t ) ;
19
20 #e ndi f
Listing 8.2. Programm-Code zum Operator-Baum (ast/term-ast.c)
1 /
2 Syntaxbaum Bi bl i ot he k
3 /
4 #i nc l ude <s t di o . h>
5 #i nc l ude <s t dl i b . h>
6 #i nc l ude <s t r i ng . h>
7 #i nc l ude termas t . h
8
9 voi d t r e e out put ( as t p , i nt n) {
10 // BaumTraversi erung Fi rs t Order
11 i f ( p != NULL) {
12 p r i nt f (%s , +142n ) ;
13 p r i nt f (%s \n , p>t e x t ) ;
14 t r e e out put ( p>l , n+1);
15 t r e e out put ( p>r , n+1);
16 }
17 }
18
19 voi d t r e e c ode ( as t p) {
20 // BaumTraversi erung PostOrder i n UPN
21 i f ( p != NULL) {
22 t r e e c ode ( p>l ) ;
23 t r e e c ode ( p>r ) ;
24 p r i nt f (%s \n , p>t e x t ) ;
8.2 AST f ur Terme 97
25 }
26 }
27
28 doubl e t r e e r e s u l t ( as t p) {
29 doubl e erg ;
30 s wi t ch ( p>t e x t [ 0 ] ) {
31 case + : erg = t r e e r e s u l t ( p>l ) + t r e e r e s u l t ( p>r ) ; break ;
32 case : erg = t r e e r e s u l t ( p>l ) t r e e r e s u l t ( p>r ) ; break ;
33 case : erg = t r e e r e s u l t ( p>l ) t r e e r e s u l t ( p>r ) ; break ;
34 case / : erg = t r e e r e s u l t ( p>l ) / t r e e r e s u l t ( p>r ) ; break ;
35 case C : erg = t r e e r e s u l t ( p>l ) ; break ; // ChangeSi gn
36 d e f a u l t : erg = at of ( p>t e x t ) ; // Zahl
37 }
38 r et ur n erg ;
39 }
40
41 as t new node ( char t , as t l , as t r ) {
42 as t p = ( as t ) mal l oc ( s i z e o f ( as t node ) ) ;
43 i f ( p != NULL) {
44 p>l = l , p>r = r ;
45 s t r c py ( p>t ext , t ) ;
46 }
47 r et ur n p ;
48 }
49
50 voi d t r e e f r e e ( as t p) { // Baum l os chen
51 i f ( p != NULL) {
52 t r e e f r e e ( p>l ) ;
53 t r e e f r e e ( p>r ) ;
54 f r e e ( p ) ;
55 }
56 }
Ein Compiler, der in einem ersten Schritt den Operator-Baum erstellt und diesen anschlie-
end mehrfach verarbeiten lasst ndet sich in Listing ??.
Listing 8.3. Compiler zur Operator-Baum-Erstellung (ast/term-treegen.c)
1 /
2 RDParser f ur Terme
3 Baut bi naren OperatorSyntaxbaum aus TermAusdruck
4 /
5 #i nc l ude <s t di o . h>
6 #i nc l ude termas t . h
7 #i nc l ude termscanner . h
8
9 // Prototypen
10 voi d scanner ( ) ;
11 as t f s t a r t ( ) ;
12 as t f f a c t o r ( ) ;
13 as t f e xpr e s s i o n ( ) ;
14 as t f t er m ( ) ;
15
16 // Gl obal e Daten
17 char zahl [ 2 0 ] ;
98 8 Zwischencode
18 i nt token , e r r or ;
19
20 // Hauptprogramm
21 i nt main ( ) {
22 char t e x t [ 1 0 0 ] ;
23 as t s ;
24
25 whi l e ( p r i nt f ( Input : ) , f g e t s ( t ext , 255 , s t di n )!= NULL) {
26 t erm scanner ( t ext , NULL) ; // ScannerReset
27 s = f s t a r t ( ) ;
28 i f ( ! er r or ) {
29 p r i nt f ( Syntaxbaum: \ n) ;
30 t r e e out put ( s , 0) ;
31 p r i nt f (UPNCodeausgabe\n) ;
32 t r e e c ode ( s ) ;
33 p r i nt f ( Ergebni s : %l f \n , t r e e r e s u l t ( s ) ) ;
34 }
35 el se
36 pr i nt f ( Syntax ni cht OK, e r r or=%d\a\n , e r r or ) ;
37 t r e e f r e e ( s ) ;
38 }
39 r et ur n 0;
40 }
41
42 // Funkti onen
43
44 voi d scanner ( ) {
45 t oken = t erm scanner (NULL, z ahl ) ;
46 }
47
48 as t f f a c t o r ( ) {
49 as t p ;
50 i f ( t oken == PLUS) {
51 scanner ( ) ;
52 p = f f a c t o r ( ) ;
53 }
54 el se i f ( token == MINUS) {
55 scanner ( ) ;
56 p = new node (CHS , f f a c t o r ( ) , NULL) ;
57 }
58 el se i f ( token == ZAHL) {
59 p = new node ( zahl , NULL, NULL) ;
60 scanner ( ) ;
61 }
62 el se i f ( token == KLA AUF) {
63 scanner ( ) ;
64 p = f e x p r e s s i o n ( ) ;
65 i f ( t oken == KLA ZU)
66 scanner ( ) ;
67 e l s e
68 er r or = 1;
69 }
70 el se e r r or =2;
71 r et ur n p ;
8.3 AST f ur PL/0 99
72 }
73
74 as t f t er m ( ) {
75 as t p , p2 ;
76 char t x t ;
77 p = f f a c t o r ( ) ;
78 whi l e ( t oken == MAL | | t oken == DIV) {
79 t x t = ( t oken == MAL) ? : /;
80 scanner ( ) ;
81 p = new node ( t xt , p , f f a c t o r ( ) ) ;
82 }
83 r et ur n p ;
84 }
85
86 as t f e xpr e s s i o n ( ) {
87 as t p , p2 ;
88 i nt t oken2 ;
89 p = f t er m ( ) ;
90 whi l e ( t oken == PLUS | | t oken == MINUS) {
91 t oken2 = t oken ;
92 scanner ( ) ;
93 p = new node ( ( t oken2 == PLUS) ? + : , p , f t er m ( ) ) ;
94 }
95 r et ur n p ;
96 }
97
98 as t f s t a r t ( ) {
99 scanner ( ) , er r or = 0;
100 as t p = f e x p r e s s i o n ( ) ;
101 i f ( t oken != END)
102 er r or = 3;
103 ret urn p ;
104 }
8.3 AST f ur PL/0
Mehrere Knoten-Arten:
Block-Knoten
Zweiger auf Var-Knoten
Var-Knoten
Zweiger auf Var-Knoten
statement-Knoten
Zweiger auf Statement-Knoten
Expression-Knoten anaolg zu arithmetischen Termen
Zweiger auf Expression-Knoten
Abbildung 8.5 zeigt den Syntaxbaum f ur das GGT-Programm (Listing 2.2), Seite 16).
Der Syntaxbaum sollte bereits die Level-Oset-Werte der Symboltabelle enthalten, dann
wird die spatere Weiterverarbeitung einfacher.
100 8 Zwischencode
Abb. 8.5. AST f ur PL/0-Programm ggt.pl0
Block
ggt

var
a

var
b
var
g
block
ggt

stmt
while


expr
#
``
expr
id a
expr
id b
stmt
if


expr
>
``
expr
id a
expr
id b
stmt
a :=

expr
-
``
expr
id a
expr
id b
stmt
if


expr
>
``
expr
id b
expr
id a
stmt
a :=

expr
-
``
expr
id b
expr
id a
stmt
? a

stmt
? b

stmt
C ggt

stmt
! a

expr
id g
enum {cmd end , cmd wri te , cmd read , cmd assi gn , cmd i f , cmd whi l e } ;
s t r uc t as t node {
i nt t ype ; // Bef ehl
i nt s t l ; // Level der Symb ol t ab e l l e f ur as s i gn und read
i nt s t o ; // Of f s e t der Symb ol t ab e l l e f ur as s i gn und read
s t r uc t as t node next ; // Nachst er Bef ehl
s t r uc t as t node cond ; // Abhangi ge Ket t e f ur i f und whi l e
} ;
8.4 Neu -
8.4.1 Die Knotenarten
Befehlsknoten
Eingabe
Ausgabe
Funktionsaufruf
Bedingter Sprung
Unbedingter Sprung
Sprungziel
Ziel der Zuweisung
(Symboltabellen-Informationen, zwei Int)
Wert der Zuweisung
EXPR-Zeiger
8.4 Neu - 101
Nr der Funktion
(Symboltabellen-Informationen, zwei Int)
Name der Funktion
String
Ziel der Eingabe
(Symboltabellen-Informationen, zwei Int)
Wert der Zuweisung
EXPR-Zeiger
Sprungziel
(Eineindeutige int oder Label=
Wert der Bedingung
EXPR-Zeiger
Sprungziel
(Eineindeutige int oder Label)
Sprungziel
(Eineindeutige int oder Label)
Die Srtuktur der Knoten kann entweder als C-Struktur (Vereinigungsmenge aller Statement-
knoten) oder als C++-Klasse (per Vererbung) realisiert werden.
Code-Erzeugung
voi d c adr var ( i nt s t l , i nt sto , char name) {
i nt i ;
emi t ( s e t I31 , P0 [ 0 ] # Adr Var + s t r i ng (name) + : Sym + s t r i ng ( i t oa ( s t l ))+/+s t r i ng ( i t oa ( s t o ) ) ) ;
f or ( i = 0; i < s t l ; i ++)
emi t ( s e t I31 , P0[ I31 ] ) ;
emi t ( sub I31 , + s t r i ng ( i t oa ( s t o +2)));
}
orthand
Ausdruck-Knoten
Ausdruck-Knoten enthalten eine der drei Moglichkeiten:
Operator (innerer Knoten)
Konstante (Blattknoten)
Variable (Blattknoten)
Programmstruktur-Knoten
VAR a ;
BEGIN
? a ;
IF a < 0 THEN
102 8 Zwischencode
a := a ;
! a ;
END.
Abb. 8.6. Zwischencode zur Verzweigung
read
a 0/0

jmp f
JMP 0

.
.
.

assign
a 0/0

.
.
.

nop
JMP 0

write

.
.
.

NULL

start
VAR a ;
BEGIN
a := 1;
WHILE a <= 10 DO
BEGIN
! a ;
a := a + 1;
END;
END.
Abb. 8.7. Zwischencode zur kopfgfesteuerten Schleife
assign
a 0/0

.
.
.

nop
JMP 0

jmp f
JMP 1

.
.
.

write

.
.
.

assign
a 0/0

.
.
.

jump
JMP 0

nop
JMP 1

NULL

start
9
Speicherverwaltung
Bis jetzt ist unser Compiler in der Lage, ein Quellprogramm in einen AST abzubilden. Dieser
AST enthalt bereits die Informationen der Symboltabelle, in welchem Level und an welcher
Stelle die Variable zu suchen ist. Bevor nun die Codeerzeugung erfolgt, muss noch geklart
werden, wie die Variablen des Quellprogramms auf den Speicher des Zielsystems abgebildet
werden. Betrachten wir noch einmal Abbildung ?? (Seite ??), so wird schnell klar, dass hier
nur uber eine eindeutige Adresse ein Zugri auf den Speicher erfolgen kann.
9.1 Einf uhrung
Es gibt zwei Ansatze in der Speicherverwaltung. Je nachdem, ob die Adresse einer Variablen
zur Compiletime oder zur Runtime errechnet wird, spricht man von statischer und von
dynamischer Speicherverwaltung.
Moderne Programmiersprachen bieten meist beide Arten an. Wird in C eine Variable ohne
weitere Angabe deklariert (etwa int a;), so wird diese Variable dynamisch verwaltet. Teil-
weise erden diese Variablen auch automatische Variablen genannt [KR90]. Durch Angabe
des Schl usselworts static kann f ur die Variable aber auch eine statische Verwaltung gewahlt
werden (etwa static int b;).
Beide Varianten haben Vor- und Nachteile:
Da die Adresse von statischen Variablen zur CT errechnet werden kann ist der Varia-
blenzugri schneller.
Ein Rekursionsstack ist nur mit dynamischer Speicherverwaltung moglich. Im Falle von
Rekursion kommt eine Variable des Quellprogramms mehrfach im Hauptspeicher vor.
Da die Lebenszeit von Variablen zur RT unterschiedlicher Funktionen sich oft nicht
uberlappt, kommt dynamische Speicherverwaltung evtl. mit weniger Hauptspeicher aus.
Es muss also gut uberlegt werden, ob man eine Sprache mit statischer, dynamischer oder
beliebiger Speicherverwaltung entwickelt.
9.2 G ultigkeitsbereiche
Vereinbarungen:
f ruft g
Die Level seien L
f
und L
g
104 9 Speicherverwaltung
Variablen in
Hauptprogramm: a, d
f: b,d;
g: c,d;
Problem: wegen Rekursion kann Speicheradresse zur Compilezeit nicht erstellt werden
Losung: Im Hauptspeicher muss Dynamische Kette (Dynamic Link, kurz DL) als Stack
gef uhrt werden. Zusatzlich muss Statische Kette (Static Link, kurz SL) gef uhrt werden Neu-
es Problem: wie m ussen SL-Zeiger gesetzt werden?
Analyse:
Abb. 9.1. Schachtelungsmoglichkeiten von Funktionen
g lokal zu f
(Listing 9.1)
main
f
g
f und g lokal zu main
(Listing 9.2)
main
g
f
f lokal zu g
(Listing 9.3)
main
g
f
Listing 9.1. g lokal zu f (pl-0/programme/speicher-g-in-f.pl0)
1 ( g l o k a l zu f )
2 VAR a , d ;
3
4 PROCEDURE f ;
5 VAR b , d ;
6
7 PROCEDURE g ;
8 VAR c , d ;
9 BEGIN
10 a := 12;
11 b := 22;
12 c := 32;
13 d := 42;
14 DEBUG; ( Haupt spei cherausgabe )
15 ! a ;
16 ! b ;
17 ! c ;
18 ! d ;
19 END;
20
21 BEGIN
9.2 G ultigkeitsbereiche 105
22 a := 11;
23 b := 21;
24 d := 41;
25 DEBUG; ( Haupt spei cherausgabe )
26 CALL g ;
27 ! a ;
28 ! b ;
29 ! d ;
30 END;
31
32 BEGIN
33 a := 10;
34 d := 40;
35 DEBUG; ( Haupt spei cherausgabe )
36 CALL f ;
37 ! a ;
38 ! d ;
39 END.
Listing 9.2. f und g lokal zu main (pl-0/programme/speicher-f-g-in-main.pl0)
1 ( f und g g l o b a l )
2 VAR a , d ;
3 PROCEDURE g ;
4 VAR c , d ;
5 BEGIN
6 a := 12;
7 c := 32;
8 d := 42;
9 DEBUG; ( Haupt spei cher )
10 ! a ;
11 ! c ;
12 ! d ;
13 END;
14
15 PROCEDURE f ;
16 VAR b , d ;
17 BEGIN
18 a := 11;
19 b := 21;
20 d := 41;
21 DEBUG; ( Haupt spei cher )
22 CALL g ;
23 ! a ;
24 ! b ;
25 ! d ;
26 END;
27
28 BEGIN
29 a := 10;
30 d := 40;
31 DEBUG; ( Haupt spei cher )
32 CALL f ;
33 ! a ;
34 ! d ;
106 9 Speicherverwaltung
35 END.
Listing 9.3. f lokal zu g (pl-0/programme/speicher-f-in-g.pl0)
1 ( f l o k a l zu g r uf t g ! ! ! ! I ndi r e k t e Rekursi on ! ! ! )
2 VAR a , d ;
3
4 PROCEDURE g ;
5 VAR c , d ;
6
7 PROCEDURE f ;
8 VAR b , d ;
9
10
11 BEGIN
12 a := 11;
13 b := 21;
14 c := 31;
15 d := 41;
16 DEBUG; ( Haupt spei cher )
17 CALL g ;
18 END;
19
20
21 BEGIN
22 c := 32;
23 d := 42;
24 IF a = 10 THEN
25 BEGIN
26 DEBUG; ( Haupt spei cher )
27 CALL f ;
28 END;
29 IF a = 11 THEN
30 BEGIN
31 a := 12;
32 DEBUG; ( Haupt spei cher )
33 END;
34
35
36 END;
37
38 BEGIN
39 a := 10;
40 d := 40;
41 DEBUG; ( Haupt spei cher )
42 CALL g ;
43 ! a ;
44 ! d ;
45 END.
9.4 Dynamische Speicherverwaltung 107
9.3 Statische Speicherverwaltung
Eine Moglichkeit der Speicherzuweisung ist, die Adresse der Variablen bereits zur Compile-
time zu errechnen. Man spricht hier von statischer Speicherverwaltung.
F ur statische Speicherverwaltung sollte die Symboltabelle etwas modiziert werden. Ihr
grundsatzlicher Aufbau ist zwar immer noch wie inb Abbildung 7.1 (Seite 90) gezeigt, es
muss aber zusatzlich f ur die Variablen eine laufende Nummer mitgespeichert werden. Im
Zwischencode gen ugt es nun, f ur die Variablen anstelle von Level- und Oset einfach diese
laufende Nr zu speichern.
Abb. 9.2. Symboltabelle bei statischer Hauptspeicherverwaltung (Listing 9.1)
4
3
2 f proc g proc
1 d var 1 d var 3 d var 5
0 a var 0 b var 2 c var 4
Level Ebene 0 Ebene 1 * Ebene 2 Ebene 3
Abb. 9.3. Statischer Hauptspeicher g lokal zu f (Listing 9.1)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Alle Zeilen
12 main a
40 main d
22 f b
41 f d
32
g c
42 g d
9.4 Dynamische Speicherverwaltung
Im Hauptspeicher muss f ur R uckspr unge etc. ein Stack aufgebaut werden. Der G ultigkeits-
bereich der Variablen ist aber nicht identisch mit der Stack-Abfolge.
Daher muss im Hauptspeicher ein Zahlen-Tupel f ur jede Funktion gespeichert sein:
Zeiger zur Stackelement der aufrufenden Funktion (Dynamic Link)
Zeiger zur Stackelement der aus Sicht des namensbvereichs umgebenden Funktion (Static
Link)
108 9 Speicherverwaltung
Dieses Tupel wird zusammen mit den lokalen Variablen und weiteren Daten - etwa der R uck-
sprungadresse RS - im sog. Activation-Record (kurz AR) gespeichert. Je nach konkreter
Anwendung - Haupspeicher eines auszuf uhrenden Programms, Interpreter, Cross-Compiler
etc. m ussen im AR weitere Informationen gespeichert werden. Wenn z.B. ein Prozessor im
Assembler lediglich eine GOTO-Befehl kennt, aber keinen GOSUB, so muss die R uck-
sprungadresse zusatzlich im AR gespeichert werden.
Wir organisieren unseren Stack aufsteigend, am oberen Ende eines jeden AR ndet sich SL
und DL.
Abbildung 9.5 bezieht sich auf Listing 9.1.
Abbildung 9.6 bezieht sich auf Listing 9.2.
Abbildung 9.7 bezieht sich auf Listing 9.3.
Zum Programmstart ist der Hauptspeicher bei allen Programmen leer, wie in Abbildung 9.4
dargestellt. Beim Aufruf einer Funktion / Prozedur - und das gilt auch f ur das Hauptpro-
gramm - wird zuallererst ein neues Segment im Hauptspeicher angelegt. In Adresse 0 im
Hauptspeicher wird Top-of-Stack gespeichert, welches am Programmstart mit 0 initialisiert
wird (Abbildung 9.4).
Abb. 9.4. Leerer Hauptspeicher
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 0
0 TOS
Anhand von Level- und Oset aus der Symboltabelle kann nun uber die SL-Kette und
Oset der Speicherplatz eines jeden Bezeichners zur Laufzeit ermittelt werden.
Analyse der SL-Kette f ur den Zugri auf die Variable a innerhalb der Funktion g:
L
f
= L
g
1 (g lokal zu f):
Symboltabellen- = 2
Zweimal entlang SL-Kette gehen
L
f
= L
g
(f und g lokal zu main):
Symboltabellen- = 1
Einmal entlang SL-Kette gehen
L
f
= L
g
+ 1 (f lokal zu g):
9.4 Dynamische Speicherverwaltung 109
Abb. 9.5. Hauptspeicher g lokal zu f (Listing 9.1)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 35
4 TOS
40 main d
10 main a
0 main DL


0 main SL


0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 25
8 TOS
40 main d
11 main a
0 main DL


0 main SL

41 f d
21 f b
4 f DL


4 f SL

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 14
12 TOS
40 main d
12 main a
0 main DL


0 main SL

41 f d
22 f b
4 f DL


4 f SL

42 g d
32
g c
8 g DL


8 g SL

Abb. 9.6. Hauptspeicher g und f lokal zu main (Listing 9.2)


0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 31
4 TOS
40 main d
10 main a
0 main DL


0 main SL


0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 21
8 TOS
40 main d
11 main a
0 main DL


0 main SL

41 f d
21 f b
4 f DL


4 f SL

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 9
12 TOS
40 main d
12 main a
0 main DL


0 main SL

41 f d
21 f b
4 f DL


4 f SL

42 g d
32
g c
8 g DL


4 g SL

Symboltabellen- = 1
Einmal entlang SL-Kette gehen, unabhangig von Rekursionslevel
Analyse der SL-Kette f ur den Funktionsaufruf f ruft g auf:
L
f
= L
g
1 (g lokal zu f):
Symboltabellen- = 0
Nullmal entlang SL-Kette gehen
L
f
= L
g
(f und g lokal zu main):
Symboltabellen- = 1
Einmal entlang SL-Kette gehen
110 9 Speicherverwaltung
Abb. 9.7. Hauptspeicher f lokal zu g (Listing 9.3)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 41
4 TOS
40 main d
10 main a
0 main DL


0 main SL


0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 26
8 TOS
40 main d
10 main a
0 main DL


0 main SL

42 g d
32
g c
4 g DL


4 g SL

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 16
12 TOS
40 main d
11 main a
0 main DL


0 main SL

42 g d
31
g c
4 g DL


4 g SL

41 f d
21 f b
8 f DL


8 f SL

0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 32
16 TOS
40 main d
12 main a
0 main DL


0 main SL

42 g d
31
g c
4 g DL


4 g SL

41 f d
21 f b
8 f DL


8 f SL

42 g d
32
g c
12 g DL


4 g SL

L
f
= L
g
+ 1 (f lokal zu g):
Symboltabellen- = 2
Zwei entlang SL-Kette gehen, unabhangig von Rekursionslevel
Allgemein:
Es kann beliebig nach auen gesprungen werden, aber nur einen Ebene nach innen
L
f
= L
g
+, 1 < : L
f
L
g
+ 1 mal entlang SL-Kette gehen
9.4.1 Methoden im Hauptspeicher
Algorithmus 12: Berechnung der Adresse einer Variablen
int ram var adr(delta, nr)
Parameter
delta, nr {Informationen der Symboltabelle}
adr = RAM[0]
WDH delta mal
adr = RAM[adr]
adr = adr - AR-Groe
adr = adr - nr
adr

9.5 Statische vs. Dynamische Speicherverwaltung 111


Algorithmus 13: Anlegen eines neuen Hauptspeichersegments
void ram neusegment(n, delta)
Parameter
n {Anzahl der Variablen}
delta {Information der Symboltabelle}
RAM[RAM[0] + n + AR-Groe -1] = RAM[0] (DL)
adr = RAM[0]
WDH delta mal
adr = RAM[adr]
RAM[RAM[0] + n + AR-Groe] = adr (SL)
RAM[0] = RAM[0] + n + AR-Groe (TOS)
Algorithmus 14: Loschen eines Hauptspeichersegments
void ram loeschsegment()
RAM[0] = RAM[RAM[0]-1] (TOS)
9.5 Statische vs. Dynamische Speicherverwaltung
Tabelle 9.1. Vor- und Nachteile von statischer und dynamischer Speicherverwaltung
Pro Contra
Statisch
Einfach zu programmieren
Schnell zur Laufzeit
Rekursion schwierig
Verschwendung von Speicherplatz
Dynamisch
Eziente Speicherplatznutzung
Rekursion einfach
Schwierig zu programmieren
Langsam zur Laufzeit
10
Code-Optimierung
Die Optimierung der generierten Codes ist sehr stark von der Zielmaschine abhangig. Teil-
weise ist Intelligenz zur Optimierung notwendig.
Die Code-Optimierung ist in Teielen bereits im Zwischencode moglich (z.B. Vereinfachung
von Ausdr ucken), in Teilen aber auch erst nach der Code-Erzeugung. Optimierung im Zwi-
schencode ist damit von der Zielmaschine unabhangig.
10.1 Optimierung der Kontrollstrukturen
Invarianter Code in Schleifen
If-else-if Schachtelung statistisch wahlen
Mehrfachverzeigung statt if-else-id bei Vergleichen mit Konstanten
10.2 Optimierung von Spr ungen
10.2.1 Optimierung von NOPs
In vielen Ziel-Sprachen werden No-OperationStatements (kurz NOPs) als Sprungziele ver-
wendet. Diese NOPs sollten im Zuge der Optimierung entfernt werden.
NOP vor Optimierung NOP nach Optimierung
# I0 enthalt einen Wert, dessen
# Betrag berechnet werden soll.
lt I0, 0, BOTTOM
mul I0, -1
LBL: NOP # Nicht in Parrot!!!
print I0
# I0 enthalt einen Wert, dessen
# Betrag berechnet werden soll.
lt I0, 0, BOTTOM
mul I0, -1
LBL: print I0
10.2.2 Eliminierung unnotiger Spr unge
Verzweigungen Am Schleifenende haben unnotige Spr unge zur Folge.
114 10 Code-Optimierung
GGT vor Optimierung GGT nach Optimierung
# GGT-Berechnung von I0 und I1
TOP: eq I0, I1, END
lt I0, I1, L1
sub I0, I1
branch L2
L1: sub I1, I0
L2: branch TOP
END: print I0
# GGTBerechnung von I 0 und I 1
TOP: eq I0 , I1 , END
l t I0 , I1 , L1
sub I0 , I 1
branch TOP
L1 : sub I1 , I 0
branch TOP
END: pr i nt I 0
10.3 Optimierung von Ausdr ucken
Suche identischer Baume
Zwischenergebnisse abspeichern
Geschickte arithmetische Umformungen:
Quadratbildung oder Multiplikation?
Vermeidung doppelter Rekursion
a
b
=

1 f ur b = 0
(a
b
2
)
2
f ur b > 0, b gerade
a (a
b1
2
)
2
f ur b > 0, b ungerade
Listing 10.1. Kekursives Potenzieren nach Lagrange (ueb-lagrange.c)
1 #i nc l ude <s t di o . h>
2
3 i nt l agr ange pot enz ( i nt a , i nt b) {
4 i nt p ;
5 i f ( b == 0)
6 p = 1;
7 e l s e i f ( b % 2 == 0) // b > 0 und gerade
8 p = l agr ange pot enz ( a , b / 2) , p = p ;
9 e l s e // b > 0 und ungerade
10 p = l agr ange pot enz ( a , ( b 1) / 2) , p = ( a p ) ;
11 ret urn p ;
12 }
13
14 i nt main ( ) {
15 i nt bas i s , exponent ;
16 p r i nt f ( Gib Basi s und Exponent ei n : ) ;
17 s canf (%d%d , &bas i s , &exponent ) ;
18 p r i nt f (%d %d = %d\n , bas i s , exponent , l agr ange pot enz ( bas i s , exponent ) ) ;
19 ret urn 0;
20 }
10.4 Optimierung des Speicherzugris 115

v
0

Flugbahn
s
h
Die physikalischen Formeln lauten:
h =
(v
0
sin )
2
2g
s =
v
2
0
sin 2
g
Wird die Quadrierung uber die Potenzierung abgebildet so wird uber Logarithmen lang-
sam gerechnet.
Wird die Quadrierung uber Multiplizierung abgebildet so m ussen insgesamt drei Multi-
plikationen durchgef uhrt werden und sin() muss zweimal berechnet werden.
Die Berechnung von h geht am schnellsten uber eine temporare Variable:
temp := v0 * sin(alpha);
h = temp * temp / (2 * g);
Auf diese Weise werden nur zwei Multiplpikationen benotigt und sin() muss nur einmal
berechnet werden.
10.4 Optimierung des Speicherzugris
RAM / Register / HEAP / Stack
11
Code-Erzeugung
Am Ende einer Comilierungsphase muss der AST in irgendeiner Form in eine Ausgabe
gebracht werden. Dies geschieht in der sog. Code-Erzeugung (siehe auch Abbildung 1.1 Seite
g phasen compiler). Wir werden drei grundsatzlich verschiedene Arten der Code-Erzeugung
diskutieren:
Direkte Interpretierung des AST
Erzeugung von Assembler-Code
Erzeugung einer anderen Hochsprache
Nat urlich waren noch weitere Arten der Verarbeitung moglich, etwa Erzeugung von Cross-
ReferenzTabellen o.a.
11.1 Interpretierung
Sprachen die keinerlei Spr unge kennen, konnen direkt wahrend des Parse-Vorgangs inter-
pretiert werden. Entfernt man aus der PL/0-Grammatik (Grammatik
1
Seite 15) aus der
Block-Regel die Prozedur-Deklaration sowie die Befehle IF und WHILE so entsteht ein linear
ablaufendes Programm.
11.2 Assembler-Erzeugung
Der zu erzeugende Assembler wurde bereits in Kapitel ?? vorgestellt.
Soll Assembler- oder Maschinencode erzeuigt werden, so muss zwischen zwei grundsatzlich
verschiedenen Ziel-Sprachen unterschieden werden:
Sprachen mit symbolischen Sprungziel
LOADR 0
JMPLT LABEL A
READ
LABEL A NOP
WRITE
Sprachen mit numerischem Sprungziel
0000 LOADR 0
0001 JMPLT 0003
118 11 Code-Erzeugung
0002 READ
0003 NOP
0004 WRITE
Das Problem entsteht bei den Vorwarts-Spr ungen des JMPLT-Befehls. Wenn die Zielsprache
symbolische Sprungziele unterst utzt sind Vorwarts-Spr unge einfach zu generieren. Die Code-
Ausgabe erzeigt ein eindeutiges Sprungziel (ein sog. Label) - etwa durch Erhohen eines
globalen Zahlers - und erzeugt die zei Code-Zeilen mit Sprung und Sprungziel.
Wesentlich schwieriger wird dies bei numerischen Sprungzielen, also der konkreten Zeilen-
nummmer. Da die Zahl der Code-Zeilen, die ubersprungen werden sollen, nicht bekannt ist,
bleibt nur, die Sprunganweisung zweimal auszugeben. Zum ersten mal mit einem vorlau-
gen (falschen!!!!) Sprungziel. Erst wenn dann spater das Sprungziel bekannt ist, kann das
Sprungziel der ersten Ausgabe korrigiert werden.
R uckwarts-Spr unge sind einfacher zu generieren. Bei symbolischen Sprungzielen wird eben-
falls ein Label generiert und spater beim JUMP referenziert. M ussen Sprungziele numerisch
angegeben werden so hilft eine Merk-Variable.
11.2.1 Der Emitter
i nt emi t ( i nt nr , command code cmd, i nt arg , char comment ) ;
Ist die ubergebene Zeilen-Nummer _nr negativ, so wird an das Ende der Ausgabe angehangt
und die Nummer der ausgegebenen Zeile zur uckgegeben. Im anderen Fall - bei einer Zeilen-
Nummer 0 wird in genau diese Zeile ausgegeben. Damit kann bei numerischen Sprungzie-
len die spatere Korrektur eines Sprungs einfach erfolgen.
11.2.2 Ausdr ucke
Ausdr ucke werden wie schon behandelt (bei Register-Stack-Masachinen) in UPN umgesetzt.
11.2.3 if-Statement
Der Code if (<Ausdruck>) <Block> mu folgendermaen umgesetzt werden:
if a > 0 then
b := 1;
01 LOADV a
02 LOADC 0
03 CMPGT
04 JUMPZ 07
05 LOADC 1
06 STOREV 2
07 NOP
1 void f i f ( ) {
2 match( t i f , e i f r e q ) ; // Li es wei t er
3 f c o ndi t i o n ( ) ; // CodeAusgabe der Bedingung
4 match( t then , e t he n r e q ) ; // Li es wei t er
5 l nr 1 = emi t ( 1, cmd JMPZ, 0 , ) ; // Spr ungz i el f a l s c h ! !
6 f s t at e me nt ( ) ;
7 l nr 2 = emi t ( 1, cmd NOP, 0 , Spr ungzi el ) ;
8 emi t ( l nr1 , cmd JMPZ, l nr , ) ; // Spr ungz i el r i c h t i g
9 }
11.3 Schleifen 119
Abb. 11.1. Ablaufplan Einfache Verzweigung
Ablaufplan if
h
A

Ausdruck

`
`
`

`
`
`
Nein Ja
JUMPZ

Block
NOP

h
B
11.2.4 if-else-Statement
Abb. 11.2. Ablaufplan Verzweigung mit Alternative
Ablaufplan if-else
h
A

Ausdruck

`
`
`

`
`
`
Nein Ja
JUMPZ

if-Block
else-Block

NOP

h
B
11.3 Schleifen
Der Code while ( <Ausdruck> ) <Block> mu folgendermaen umgesetzt werden:
120 11 Code-Erzeugung
Abb. 11.3. Ablaufplan kopfgesteuerte schleife
Ablaufplan while-Statement
h
A

NOP

Ausdruck

`
`
`

`
`
`
Nein Ja
JUMPZ

Block
NOP

h
B
11.4 Variablenzugri
Wird dynamische Speicherverwaltung eingesetzt so ist der Variablenzugri relativ kompli-
ziert, da die Adresse einer Variablen erst zur Runtime errechnet werden kann. Je nachdem
wie kompliziert der Code ist und abhangig davon, wie einfach in der Zielsprache ein Unter-
programmaufruf ist bieten sich zwei Moglichkeiten an:
Der Code kann uber ein Unterprogramm in der Codeausgabe f ur jeden Variablenzugri
generiert werden
Der Code kann uber ein Unterprogramm in der Zielsprache einmalig generiert werden,
dieses Unterprogramm wird nun uber einen Funktionsaufruf in der Zielsprache aufgeru-
fen.
Im Beispiel-Assembler der Vorlesung ist der Funktionsaufruf mit zwei Parametern minde-
stens so kompliziert wie die direkte Code-Ausgabe, diese besteht minimal - abhangig von
der -Information der Symboltabelle - aus f unf Assemblerbefehlen. Listing 11.1 zeigt den
Zugri.
Listing 11.1. Codeerzeugung f ur Variablenzugri
1 voi d c adr var ( i nt s t l , i nt sto , char name) {
2 i nt i ;
3 char comment [ 2 5 5 ] ;
4 s p r i n t f ( comment , Adr Var %s : Sym %d/%d , name , s t l , s t o ) ;
5 emi t (1, cmd LOADR, 0 , comment ) ;
6 f or ( i = 0; i < s t l ; i ++)
7 emi t (1, cmd LOADS, 0 , ) ;
8 emi t (1, cmd LOADC, 2 , Of f s e t wegen SL/DL/RS) ; // Of f s e t anpassen
9 emi t (1, cmd SUB, 0 , ) ;
10 emi t (1, cmd LOADC, st o , SymtabOf f s e t ) ;
11 emi t (1, cmd SUB, 0 , ADR VAR) ;
11.6 Spr unge 121
12 }
Nach Abarbeitung des von Listing 11.1 erzeugten Codes liegt die Adresse der Variablen auf
dem Stack, danach kann mit LOADS der Werr der Variablen auf den Stack geladen werden
bzw. mit STORES der (vorher errechnete und damit unter der Adresse auf dem Stack liegende)
Wert gespeichert werden.
11.5 Funktionsaufruf
Der Speicherbedarf des Stack-Segments muss ermittelt werden
Neuer DL := Alter TOS
Neuer SL := Alter TOS und mal entlang der SL-Kette gehen
Evtl R ucksprung-Adresse auf Stack legen
TOS erhohen
11.6 Spr unge
Bei der Codeerzeugung m ussen Spr unge in die Ausgabe eingebaut werden. Nun muss man
unterscheiden, ob die Sprungziele wie in einem ublichen Assemler symbolisch sein konnen
oder ob bereits eine Zeilennummer als Sprungziel angegeben werden muss.
So konnte der Assemblercode f ur eine einfache Verzweigung
IF a < 0 THEN
a := -a;
analog Abbildung 11.1 folgfendermaen aussehen:
LOADV a
LOADC 0
CMTLT
JUMPZ LBL_1
LOADV a
CHS
STOREV a
LBL_1 NOP
Durch eine fortlaufende Numerierung der Labels (numerisch oder alphabetisch) ist die Co-
deerzeugung einfach zu bewerkstelligen.
void f_if() {
int label = get_next_label();
match(t_if);
f_expression();
printf(" JUMPZ LBL_%d\n", label);
match(t_then);
f_statement();
printf("LBL_%d NOP\n");
}
122 11 Code-Erzeugung
Die Codeausgabe f ur eine Verzweigung besteht also nur aus einem bedingten Sprung und
einem Sprungziel. Das Schachteln von Verzweigungen und Schleifen in der Quellsprache ist
unkritisch, da die Variable label in obigem Codefragment lokal ist.
Anders sieht es aus, wenn keine symbolischen Sprungziele erlaubt sind, sondern bereits
Zeilennummern als Sprungziel angegeben werden m ussen. Der Assemblercode sieht in dann
beispielhaft wie folgt aus:
0000 LOADV a
0001 LOADC 0
0002 CMTLT
0003 JUMPZ 0007
0004 LOADV a
0005 CHS
0006 STOREV a
0007 NOP
Hier entsteht nun das Problem, dass das Sprungziel des bedingten Sprungs zum Zeitpunkt
der Ausgabe noch gar nicht bekannt sein kann. Daher muss der Sprung erst einmal mit einem
falschen (da nicht bekannten) Sprungziel ausgegeben werden. Nachdem nun der Code des
Statements ausgegeben ist kann erst das Sprungziel ausgegeben werden und anschlieend
muss der Bedinge Sprung noch einmal ausgegeben werden. Man benotigt daher eine spezielle
Funktion f ur die Ausgabe, die sog. Emitter-Funktion die beispielhaft so aussehen konnte:
i nt emi t ( i nt nr , char txt , char arg ) {
const i nt l i ne wi dt h = 23; // Fr Windows anpassen
s t a t i c i nt nr = 1; // Nr der Aus gabez ei l e
i f ( nr < 0) // Neue Ze i l e
nr = ++nr ;
f s e e k ( o u t f i l e , l i ne wi dt h nr , SEEK SET) ;
f p r i n t f ( o u t f i l e , %04d %6s %10d\n , nr , t xt , arg ) ;
ret urn nr ;
}
Mit einer solchen Emitter-Funktion kann nun die Codeausgabe auch mit Zeilennummern
programmiert werden:
void f_if() {
int label het_next_label();
match(t_if);
f_expression();
lnr_1 = emit(-1, "JUMPZ", itoa(0)); // Sprungziel noch falsch!!!
match(t_then);
f_statement();
lnr_2 = emit(-1, "NOP", "");
emit(lnr_1, "JUMPZ", itoa(lnr_2)); // Sprungziel korrigiert!!!
}
12
Generator-Tools
12.1 Einleitung
12.2 Generator-Tools
[Her95] Lex und Yacc, Flex, Bison, JLex, ...
Abb. 12.1. Verarbeitung einer Lex-Datei
Lex-
Skript
C-Programm
lex.yy.c
Ausf uhrbares
Programm

Lex

C-compiler
Abb. 12.2. Verarbeitung von Yacc- und Lex-Datei
Lex-
Skript
C-Programm
lex.yy.c

Lex
Yacc-
Skript
C-Programm
y.tab.c

Yacc
Ausf uhrbares
Programm

C-compiler
89
124 12 Generator-Tools
12.3 Lex
12.3.1 Allgemeines
Entwickelt in den 1970ern von Lesk / Schmidt bei Bell [LS75]
Haug Varianten im Einsatz, etwa GNU-Lex oder JLex
12.3.2 Grundaufbau einer Lex-Datei
<Definitionen, C-Code>
%%
<RegExp-Aktions-Paare>
%%
<Zusatz-Code>
Zentraler Punkt ist der Mittelteil, der aus Regularen Ausdr ucken in Kombination mit Ak-
tionen (C-Code) besteht.
12.3.3 Denitionsteil
C-Code
%{
#include <stdio.h>
int i;
%}
Regulare Denitionen
DEC_INT [0-9]+
HEX_INT 0x[0-9A-F]
Startbedingungen
%start COMMENT
Zeichensatz-Tabellen
Tabellen-Groen
12.3.4 Regelteil
Besteht aus Regularen Ausdr ucken (Regeln) und Aktionen
Trit genau ein Regularer Ausdruck, so wird die entsprechende Aktion ausgef uhrt.
Treen mehrere Regeln, so wird die Aktion der Regel mit dem langsten Match ausgef uhrt.
Treen mehrere Regeln mit gleichlangem Match , so wird die Aktion der oberstenn Regel
ausgef uhrt.
Trit keine Regel, so wird die Eingabe einfach ausgegeben.
12.3 Lex 125
12.3.5 Regulare Ausdr ucke
Bestehen aus Zeichen, die in der Eingabe gesucht werden und
Meta-Zeichen mit speziellen Bedeutungen
Metazeichen
Zeichen Bedeutung Beispiel
\ ESC-Sequenz \n
\ metazeichen-Ausschaltung \\
^ Zeilenanfang ^#include
$ Zeilenende TEST$
. Beliebiges Zeichen T.
[] Zeichenklasse [0-9]
| Oder-Verkn upfung der|die
() Prioritats-Klammer D(er|ie|as)
* n-fache Wiederholnung, 0 n Too*r
+ n-fache Wiederholnung, 1 n To+r
? Option, n-fache Wiederholnung 0 n 1 Tore?
{} m-bisn-fache Wiederholnung To{1,10}r
{} Regulare Denition {DEC_INT}
/ Kontext-Operator Tor/ fur Deutschland
"" String-Operator "+"
<> Start-Bedingung <COMMENT>
12.3.6 Lex-Variablen
yytext Gescannter Text
yyin Eingabedatei
yyout Ausgabedatei
12.3.7 Lex-Funktionen und -Makros
yywrap Eingabeende-Funktion
input Input-Funktion
unput unput-Funktion
yyles unput-Funktion
yymode Weiterlese-Funktion
ESC-Sequenzen
12.3.8 Startbedingungen
Denition im Denitionsteil mit %start
Umschalten der Bedingungen mit Makro BEGIN
Regel mit Startbedingung ist nur aktiv, wenn mit BEGIN die jeweilige Startbedingung
eingeschlaten wurde
Regel ohne Startbedingung ist immer (!!!!!!) aktiv
126 12 Generator-Tools
Startbedingungen werden mit BEGIN 0 ausgeschalten
Beispiel: Inline-Kommentar in C:
Listing 12.1. Lex-Programm zur Extraktion von C-Inline-Kommentaren (lexyacc/inline-
comment.l)
1 %s t a r t COMMENT
2 %{
3 i nt l nr = 1;
4 %}
5 %%
6 // BEGIN COMMENT;
7 <COMMENT>\n BEGIN 0;
8 <COMMENT>[\n] pr i nt f ( Ze i l e %d : %s \n , l nr , yytext ) ;
9 . / do nothi ng /;
10 \n l nr ++;
11 %%
Der Kontext-Operator
Sucht den Regularen Ausdruck mit zwei Besonderheiten:
In yytext wird nur der Match bis vor dem Kontextoperator gespeichert
Das Weiterlesen erfolgt hinter dem Kontextoperator
Listing 12.2. PL/0-Zusweingen (lexyacc/pl0-var-assignment.l)
1 %{
2 / Sucht Zuweisungen an Vari abl en i n PL/0Programmen /
3 i nt l nr = 1 , nr = 0;
4 %}
5 ID [ AZaz ] [ AZaz 0 9]
6 WHT [ \ t \n\ f ]
7 %%
8 {ID}/{WHT}:= pr i nt f ( Ze i l e %d : %s \n , l nr , yytext ) ;
9 := nr++;
10 \n l nr ++;
11 . ;
12 %%
13 i nt yywrap ( ) {
14 p r i nt f (%d Zuweisungen gef unden ! \ n , nr ) ;
15 ret urn 1;
16 }
12.3.9 Beispiele
Ein Term-Scanner
Listing 12.3. Term-Scanner mit Lex (lexyacc/term-scanner.l)
1 %{
2 /
12.4 Yacc 127
3 LEXProgramm f ur Berechnung ar i t hme t i s c he r Ausdr ucke
4 Erkennt FixkommaZahl en , Grundrechenarten und
5 runde Klammern
6 /
7 //#i nc l ude termscanner . h
8 #i nc l ude <s t r i ng . h>
9 //enum {PLUS = 1 , MINUS, MAL, DIV, KLA AUF, KLA ZU, ZAHL, FEHLER} ;
10
11 %}
12 %%
13 + r et ur n PLUS;
14 r et ur n MINUS;
15 r et ur n MAL;
16 / r et ur n DIV;
17 ( r et ur n KLA AUF;
18 ) r et ur n KLA ZU;
19 [ 0 9] +(. [ 0 9] +)? { s t r c py ( y y l v a l . t , y y t e x t ) ; ret urn ZAHL; }
20 [ \ t \n ] / do nothi ng /;
21 . r et ur n FEHLER;
22 %%
23
24 i nt term scanner ( char i nput , char text ) {
25 // Adapt i er t S c h n i t t s t e l l e f ur AutomatenBe i s pi e l e
26 i nt rc ;
27 i f ( i nput != NULL) { // Reset
28 y y s c an s t r i ng ( i nput ) ;
29 ret urn 0;
30 }
31 r c = yyl ex ( ) ;
32 s t r cpy ( text , yytext ) ;
33 r et ur n r c ;
34 }
35
36 i nt yywrap ( ) {
37 ret urn 1;
38 }
39
40
41 // i nt main ( ) {
42 // i nt t oken ;
43 //
44 // whi l e ( ( t oken = yyl e x ( ) ) != 0)
45 // p r i nt f (%d %s \ n , token , y y t e x t ) ;
46 // ret urn 0;
47 // }
Ein PL/0-Scanner
12.4 Yacc
12.4.1 Allgemeines
Entwickelt in den 1970ern von Johnson bei Bell
128 12 Generator-Tools
Yet another COmpiler-Compiler
Haug Varianten im Einsatz, etwa GNU-Bison oder JYacc
Yacc produziert einen LALR-Parser
Yacc Setzt eine Scanner-Funktoin mit dem Prototypen int yylex(); voraus
12.4.2 Grundaufbau einer Yacc-Datei
<Definitionen, C-Code>
%%
Grammatik-Aktions-Tupel
%%
<Zusatz-Code>
Zentraler Punkt ist der Mittelteil, der aus einer Grammatik in einer BNF-ahnlichen Notation
sowie zugehorigen Aktionen besteht.
12.4.3 Denitionsteil
C-Code
%{
#include <stdio.h>
int i;
%}
Token-Denitionen Yacc akteptiert prinzipiell char-Konstanten als Token. Andere Token
werden mittels %token angelegt:
%token t_plus t_mult
Die hinter %token angegeben Tokens werden mit int-Werten ab 255 aufsteigend belegt.
Das Ende-Token word von Yacc mit dem numerischen Wert 0 deniert.
Datentypen: Haug haben Scanner-Resultate oder Grammatik-Resultate ein Ergebnis.
Da oft verschiedene Datentypen gehandelt werden m ussen, bietet sich die union als Da-
tentyp an. Yacc-Direktive ist %union.
%union {
int _int;
double _double;
char text[20];
}
Startregel %start
Operator-Prioritat und -Assoziativitat, sofern nicht aus Grammatik ersichtlich
%left t_plus
%right t_power
Datentypen der Token (Scanner-Resultate)
%token<_int> t_int
%token<_double> t_double
%token<_text> t_identifier
12.5 Konikte in Yacc-Grammatiken 129
12.4.4 Yacc-Variablen
yylval
12.5 Konikte in Yacc-Grammatiken
shift-reduce-Konikte
reduce-reduce-Konikte
12.5.1 Beispiele
Ein Term-Parser
Listing 12.4. Ein Term-Parser (lexyacc/term-parser.y)
1 %{
2 /
3 Parser f ur ar i t hme t i s c he Ausdr ucke
4 /
5 #i nc l ude <s t d i o . h>
6 %}
7 %token ZAHL KLA AUF KLA ZU PLUS MINUS MAL DIV END FEHLER
8 %uni on {char t [ 1 0 0 ] ; }
9 %%
10 e xpr e s s i on : term
11 | e xpr e s s i on PLUS term
12 | e xpr e s s i on MINUS term
13 ;
14
15 term : f a c t o r
16 | term MAL f a c t o r
17 | term DIV f a c t o r
18 ;
19
20 f a c t o r : ZAHL
21 | KLA AUF e xpr e s s i on KLA ZU
22 | PLUS f a c t o r
23 | MINUS f a c t o r
24 ;
25 %%
26 #i nc l ude l e x . yy . c
27
28 i nt yyer r or ( char s ) {
29 p r i nt f (%s \n , s ) ;
30
31 }
32
33 i nt main ( ) {
34 i nt rc ;
35 rc = yyparse ( ) ;
36 i f ( rc == 0)
130 12 Generator-Tools
37 p r i nt f ( Syntax OK\n) ;
38 e l s e
39 p r i nt f ( Syntax ni cht OK\n) ;
40 ret urn rc ;
41 }
Ein Term-Compiler
Listing 12.5. Ein Term-Parser (lexyacc/term-compiler.y)
1 %{
2 /
3 compi l er f ur ar i t hme t i s c he Ausdr ucke : Term > UPN
4 /
5 #i nc l ude <s t d i o . h>
6 #i nc l ude <s t d l i b . h>
7 %}
8 %uni on {char t [ 1 0 0 ] ; }
9 %token KLA AUF KLA ZU PLUS MINUS MAL DIV END FEHLER
10 %token<t> ZAHL
11 %%
12 e xpr e s s i on : term
13 | e xpr e s s i on PLUS term { p r i nt f (+\n) ; }
14 | e xpr e s s i on MINUS term { p r i nt f (\n) ; }
15 ;
16
17 term : f a c t o r
18 | term MAL f a c t o r { p r i nt f (\n) ; }
19 | term DIV f a c t o r { p r i nt f (/\n) ; }
20 ;
21
22 f a c t o r : ZAHL { p r i nt f (%s \n , $1 ) ; }
23 | KLA AUF e xpr e s s i on KLA ZU
24 | PLUS f a c t o r
25 | MINUS f a c t o r { p r i nt f (CHS\n) ; }
26 ;
27 %%
28 #i nc l ude l e x . yy . c
29
30 i nt yyer r or ( char s ) {
31 p r i nt f (%s \n , s ) ;
32
33 }
34
35 i nt main ( ) {
36 i nt rc ;
37 rc = yyparse ( ) ;
38 i f ( rc == 0)
39 p r i nt f ( Syntax OK\n) ;
40 e l s e
41 p r i nt f ( Syntax ni cht OK\n) ;
42 ret urn rc ;
43 }
12.5 Konikte in Yacc-Grammatiken 131
Ein Term-Rechner
Listing 12.6. Ein Term-Parser (lexyacc/term-rechner.y)
1 %{
2 /
3 I nt e r pr e t e r f ur ar i t hme t i s c he Ausdr ucke
4 /
5 #i nc l ude <s t d i o . h>
6 #i nc l ude <s t d l i b . h>
7 %}
8 %uni on {char t [ 1 0 0 ] ; doubl e x ; }
9 %token KLA AUF KLA ZU PLUS MINUS MAL DIV END FEHLER
10 %token<t> ZAHL
11 %type<x> f a c t o r term e xpr e s s i on
12 %%
13 i nput : e xpr e s s i on { p r i nt f (Erg : %l f \n , $1 ) ; }
14 ;
15
16 e xpr e s s i on : term {$$ = $1 ; }
17 | e xpr e s s i on PLUS term {$$ = $1 + $3 ; }
18 | e xpr e s s i on MINUS term {$$ = $1 $3 ; }
19 ;
20
21 term : f a c t o r {$$ = $1 ; }
22 | term MAL f a c t o r {$$ = $1 $3 ; }
23 | term DIV f a c t o r {$$ = $1 / $3 ; }
24 ;
25
26 f a c t o r : ZAHL {$$ = at of ( $1 ) ; }
27 | KLA AUF e xpr e s s i on KLA ZU {$$ = $2 ; }
28 | PLUS f a c t o r {$$ = $2 ; }
29 | MINUS f a c t o r {$$ = $2 ; }
30 ;
31 %%
32 #i nc l ude l e x . yy . c
33
34 i nt yyer r or ( char s ) {
35 p r i nt f (%s \n , s ) ;
36
37 }
38
39 i nt main ( ) {
40 i nt rc ;
41 rc = yyparse ( ) ;
42 i f ( rc == 0)
43 p r i nt f ( Syntax OK\n) ;
44 e l s e
45 p r i nt f ( Syntax ni cht OK\n) ;
46 ret urn rc ;
47 }
Ein Term-AST-Generator
132 12 Generator-Tools
Listing 12.7. Ein Term-Parser (lexyacc/term-ast-gen.y)
1 %{
2 /
3 Kons t r ui er t AST f ur ar i t hme t i s c he Ausdr ucke ( OperatorBaum)
4 /
5 #i nc l ude <s t d i o . h>
6 #i nc l ude <s t d l i b . h>
7 #i nc l ude termas t . h
8 #i nc l ude termas t . c
9 s t r uc t node t r e e ;
10 %}
11 %uni on {char t [ 1 0 0 ] ; s t r uc t node p ; }
12 %token KLA AUF KLA ZU PLUS MINUS MAL DIV END FEHLER
13 %token<t> ZAHL
14 %type<p> f a c t o r term e xpr e s s i on
15 %%
16 i nput : e xpr e s s i on { t r e e = $1 ; }
17 ;
18
19 e xpr e s s i on : term {$$ = $1 ; }
20 | e xpr e s s i on PLUS term {$$ = new node (+, $1 , $3 ) ; }
21 | e xpr e s s i on MINUS term {$$ = new node(, $1 , $3 ) ; }
22 ;
23
24 term : f a c t o r {$$ = $1 ; }
25 | term MAL f a c t o r {$$ = new node ( , $1 , $3 ) ; }
26 | term DIV f a c t o r {$$ = new node (/ , $1 , $3 ) ; }
27 ;
28
29 f a c t o r : ZAHL {$$ = new node ( $1 , NULL, NULL) ; }
30 | KLA AUF e xpr e s s i on KLA ZU {$$ = $2 ; }
31 | PLUS f a c t o r {$$ = $2 ; }
32 | MINUS f a c t o r {$$ = new node (CHS , $2 , NULL) ; }
33 ;
34 %%
35 #i nc l ude l e x . yy . c
36
37 i nt yyer r or ( char s ) {
38 p r i nt f (%s \n , s ) ;
39
40 }
41
42 i nt main ( ) {
43 i nt rc ;
44 rc = yyparse ( ) ;
45 i f ( rc == 0) {
46 p r i nt f ( Syntax OK\n) ;
47 p r i nt f (Baum: \ n) ; t r e e out put ( t ree , 0 ) ;
48 p r i nt f (UPN: \ n) ; t r e e c ode ( t r e e ) ;
49 p r i nt f ( Ergebni s : %l f \n , t r e e r e s u l t ( t r e e ) ) ;
50 }
51 el se
52 pr i nt f ( Syntax ni cht OK\n ) ;
53 t r e e f r e e ( t r e e ) ;
12.5 Konikte in Yacc-Grammatiken 133
54 r et ur n r c ;
55 }
Die Programme benotigen zur Compilierung noch die Programme f ur den Term-AST, Listing
?? (Seite ??) und ?? (Seite ??).
A
Der PL/0-Modellcompiler
In diesem Kapitel wird ein PL/0-Modellcompiler vorgestellt, der alle in der Vorlesung behan-
delten Prinzipien umsetzt. Der Compiler kann aber nicht nur PL/0 in Assembler ubersetzten,
er kann den Programmcode auch drekt interpretieren. Desweiteren ist ein Assembler nach-
geschaltet, der den assemblercode in einen binaren Maschinencode ubersetzt, der wiederum
von einem weiteren Modlu, dem

UProzessor-Emulator, abgearbeitet wird.
A.1 Die Module des Compilers
Scanner
Parser
AST als Zwischencode
Symboltabelle
Code-Erzeugung
Interpretierung
A.2 Quellcode
Listing A.1. Hauptprogramm (pl-0/pl0.c)
1 /
2 PL/0Compi l er
3
4 Auf r uf opt i onen
5 c Compi l er
6 i I nt e r pr e t e r
7 e e mul i e r e r
8 a as s e mbl i e r e r
9
10 Datei en
11 pl 0 . c Steuerporgramm
12 pl 0compi l er . c Compi l er
13 pl 0i nt e r pr e t e r . c I nt e r pr e t e r
14 pl 0as t . c Abstrakter Syntaxbaum
15 pl 0symtabneu . c Symbol t abel l e
16 pl 0ypar s er . y YACCParser
136 A Der PL/0-Modellcompiler
17 pl 0scanner . l LEXScanner
18
19

Ubersetzung :
20 l e x pl 0scanner . l
21 yacc d pl 0ypar s er . y
22 g++ pl 0 . c pl 0i nt e r pr e t e r . c pl 0compi l er . cpp l e x . yy . c y . tab . c pl 0as t . c pl 0symtab . cpp
23 /
24
25 #i nc l ude <s t di o . h>
26 #i nc l ude <s t r i ng . h>
27
28 #i nc l ude pl 0i nt e r pr e t e r . h
29 #i nc l ude pl 0compi l er . h
30
31 i nt yyparse ( ) ;
32 char f i l ename [ 1 0 0 ] ;
33
34 i nt main( i nt argc , char argv [ ] ) {
35 char act i on ;
36 i nt er r or ;
37 ext er n FILE yyi n ;
38
39 i f ( argc < 3) {
40 f p r i n t f ( s t der r , Auf ruf Fehl er ! \n) ;
41 f p r i n t f ( s t der r , Auf ruf : pl 0 (c|i |a|e ) f i l ename \n) ;
42 f p r i n t f ( s t der r , f i l ename ohne Erwei t erung ! \ n) ;
43 ret urn 1;
44 }
45 ac t i on = argv [ 1 ] [ 1 ] ;
46 s p r i n t f ( f i l ename , %s . pl 0 , argv [ 2 ] ) ;
47 i f ( ( yyi n = f open ( f i l ename , r ) ) == NULL) {
48 f p r i n t f ( s t der r , Ei ngabedat ei %s konnt e ni cht g e o f f ne t werden ! \ n , f i l ename ) ;
49 ret urn 1;
50 }
51 s p r i n t f ( f i l ename , %s , argv [ 2 ] ) ;
52
53 // pr i nt f ( St ar t e Parser . . . \ n ) ;
54 pr i nt f ( St ar t e Parser f ur %s . . . , f i l ename ) ;
55 e r r or = yyparse ( ) ;
56 pr i nt f ( ParserErgebni s : %d\n , e r r or ) ;
57 i f ( e r r or )
58 pr i nt f ( Fehl er %d\n , e r r or ) ;
59 el se {
60 p r i nt f ( Syntax OK! \ n\n) ;
61 s wi t ch ( act i on ) {
62 case c : compi l er ( ) ; break ;
63 case i : i nt e r p r e t e r ( ) ; break ;
64 case a : a s t a s t wr i t e ( ) ; break ;
65 }
66 }
67 r et ur n e r r or ;
68 }
A.2 Quellcode 137
Listing A.2. Scanner (pl-0/pl0-scanner.l)
1 %{
2 /
3 LexScanner f ur PL/0
4 ( c ) FH Aal en I nf ormat i k Compil erbau Prof . Bant el
5 /
6 #i nc l ude pl 0as t . h
7 #i nc l ude y . t ab . h
8 i nt comm l evel = 0;
9 %}
10 %s t a r t COMMENT
11 %%
12 <COMMENT>[ ( ) ] ;
13 <COMMENT>( | ) | ( ) ;
14 <COMMENT>) i f (comm l evel ==0) BEGIN 0;
15 <COMMENT>( comm l evel ++;
16 ( comm l evel ++; BEGIN COMMENT; // Kommentar
17 CONST ret urn t c ons t ;
18 VAR ret urn t v ar ;
19 PROCEDURE ret urn t pr oc ;
20 CALL ret urn t c a l l ;
21 BEGIN ret urn t b e g i n ;
22 END ret urn t end ;
23 IF ret urn t i f ;
24 THEN ret urn t t he n ;
25 WHILE ret urn t wh i l e ;
26 DO ret urn t do ;
27 DEBUG ret urn t debug ;
28 ODD ret urn t odd ;
29 = ret urn t e q ;
30 . ret urn t punkt ;
31 , ret urn t komma ;
32 ; ret urn t s emi k ;
33 := ret urn t a s s i g n ;
34 ? ret urn t r e ad ;
35 ! ret urn t wr i t e ;
36 # ret urn t ne ;
37 < ret urn t l t ;
38 <= ret urn t l e ;
39 > ret urn t g t ;
40 >= ret urn t g e ;
41 + ret urn t p l u s ;
42 ret urn t mi nus ;
43 ret urn t mul t ;
44 / ret urn t d i v ;
45 ( ret urn t b r a o ;
46 ) ret urn t b r a c ;
47 [ AZaz ] [ AZaz 0 9] s t r ncpy ( y y l v a l . t e x t , yyt ext , 8) ; y y l v a l . t e x t [ 8 ] = \0 ; ret urn t i d e nt ;
48 [09]+ y y l v a l . i nt = at oi ( y y t e x t ) ; ret urn t number ;
49 [ \ t \ f \r \n] ; // Whi tespace
50 . ret urn t e r r o r ;
51 %%
52 i nt yywrap (){
53 p r i nt f (End of i nput reached ! \ n) ;
138 A Der PL/0-Modellcompiler
54 ret urn 1; // Ni cht wei t er scannen !
55 }
Listing A.3. Parser (pl-0/pl0-y-parser.y)
1 %{
2 /
3 PL/0YACCParser
4 Er s t e l l t AST
5 /
6 #i nc l ude <i ost ream>
7 usi ng namespace s t d ;
8
9 ext er n char y y t e x t ;
10 #i nc l ude pl 0as t . h
11 #i nc l ude pl 0er r or . h
12 #i nc l ude pl 0symtab . hpp
13 #i nc l ude <s t r i ng . h>
14 #i ncl ude <s t d i o . h>
15 char l s = 0;
16 symtab s t ;
17 a s t b l o c k as t ;
18 s t a t i c i nt l a b e l = 1;
19 i nt yyl e x ( ) ;
20 i nt yyerror ( const char ) ;
21 ext er n i nt yyl i neno ;
22
23 %}
24 %uni on { // R uckgabetypen
25 i nt i nt ; // f r ganze Zahl en
26 char t e x t [ 1 0 ] ; // f r Bezei chner
27 a s t b l o c k b l o c k ;
28 as t s t mt s t mt ;
29 as t e x pr expr ;
30 a s t i d i d ;
31 s t r uc t { as t s t mt s t a r t ; as t s t mt end ; } l i s t ;
32 }
33
34 %token t e o f t punkt t c ons t t e q t komma
35 %token t s emi k t var t pr oc t a s s i g n t c a l l t be gi n t end
36 %token t r e ad t wr i t e t i f t t hen t whi l e t do t debug
37 %token t odd t ne t l t t l e t g t t ge
38 %token t pl us t mi nus t mul t t di v t br a o t br a c
39 %token t e r r o r t c hs
40 %token < t ext > t i de nt
41 %token < i nt > t number
42
43 %type < bl ock> bl ock p r o c l i s t procedure
44 %type < i d> c ons t de c l c o n s t l i s t c ons t i d
45 %type < i nt > v a r l i s t var de c l
46 %type < expr > f a c t o r e xpr e s s i on c ondi t i on term
47 %type < stmt> assi gnement pr o c c a l l read write debug
48 %type < l i s t > i f while s t a t e me nt l i s t statement
49
50 %%
A.2 Quellcode 139
51 program: bl ock t punkt { as t = $1 ; }
52 ;
53
54 bl ock : { s t . l e v e l u p ( ) ; } c ons t de c l var de c l p r o c l i s t statement
55 {$$ = ne w as t b l oc k ( $2 , $3 , $4 , $5 . s t a r t ) ; s t . l evel down ( ) ; }
56 ;
57 c ons t de c l : // {$$ = NULL; }
58 | t c ons t c o n s t l i s t t s emi k {$$ = $2 ; }
59 ;
60
61 c o n s t l i s t : c ons t i d t e q t number {$$ = $1 ; $$>v al = $3 ; }
62 | c ons t i d t e q t number t komma c o n s t l i s t {$$ = $1 ; $$>v al = $3 ; $$>next = $5 ; }
63 ;
64
65 c ons t i d : t i de nt {$$ = new as t i d ( $1 ) ; s t . i ns e r t ( $$>name , s t c o ns t ) ; / s ymt ab i ns er t (&st , $$>name , s t c ons t , &($$>s t o ) ) ; / }
66 ;
67
68 var de c l : // {$$ = 0; }
69 | t var v a r l i s t t s emi k {$$ = $2 ; }
70 ;
71
72 v a r l i s t : t i de nt {$$ = 1; s t . i ns e r t ( $1 , s t v a r ) ; }
73 | t i de nt t komma v a r l i s t {$$ = 1 + $3 ; s t . i ns e r t ( $1 , s t v a r ) ; }
74 ;
75
76 p r o c l i s t : // {$$ = NULL; }
77 | procedure t s emi k p r o c l i s t {$$ = $1 , $$>next = $3 ; }
78 ;
79
80 procedure : t pr oc
81 t i de nt { s t . i ns e r t ( $2 , s t pr oc ) ; }
82 t s emi k
83 bl ock {$$ = $5 ; s t r c py ( $$>name , $2 ) ; / $$>s t o = $< i nt >3;/}
84 ;
85
86 statement : // {$$ . s t a r t = $$ . end = NULL; }
87 | assi gnement {$$ . s t a r t = $$ . end = $1 ; }
88 | pr o c c a l l {$$ . s t a r t = $$ . end = $1 ; }
89 | read {$$ . s t a r t = $$ . end = $1 ; }
90 | write {$$ . s t a r t = $$ . end = $1 ; }
91 | i f {$$ = $1 ; }
92 | while {$$ = $1 ; }
93 | debug {$$ . s t a r t = $$ . end = $1 ; }
94 | t be gi n s t a t e me nt l i s t t end {$$ = $2 ; }
95 ;
96
97 assi gnement : t i de nt t a s s i g n e xpr e s s i on { i nt s t l , s t o ;
98 s t . l ookup ( $1 , s t var , s t l , s t o ) ;
99 $$ = new as t s t mt ( s t mt as s i g n ) ;
100 $$>expr = $3 ;
101 s t r c py ( $$>i d , $1 ) ;
102 $$>s t l = s t l , $$>s t o = s t o ; }
103 ;
104 pr o c c a l l : t c a l l t i de nt { i nt s t l , s t o ;
140 A Der PL/0-Modellcompiler
105 s t . l ookup ( $2 , s t pr oc , s t l , s t o ) ;
106 $$ = new as t s t mt ( s t mt c a l l ) ;
107 s t r c py ( $$>i d , $2 ) ;
108 $$>s t l = s t l , $$>s t o = s t o ; }
109 ;
110 read : t r e ad t i de nt { i nt s t l , s t o ;
111 s t . l ookup ( $2 , s t var , s t l , s t o ) ;
112 $$ = new as t s t mt ( s t mt read ) ; s t r c py ( $$>i d , $2 ) ;
113 $$>s t l = s t l , $$>s t o = s t o ; }
114 ;
115 write : t wr i t e e xpr e s s i on {$$ = new as t s t mt ( s t mt wr i t e ) ; $$>expr = $2 ; }
116 ;
117 debug : t debug {$$ = new as t s t mt ( st mt debug ) ; }
118 ;
119 i f : t i f c ondi t i on t t hen statement
120 {$$ . s t a r t = new as t s t mt ( s t mt j ump f al s e ) ;
121 s p r i n t f ( $$ . s t ar t >i d , JMP %d , ++l a b e l ) ;
122 $$ . s t ar t >expr = $2 ;
123 $$ . s t ar t >next = $4 . s t a r t ;
124 $$ . end = new as t s t mt ( st mt nop ) ;
125 s t r c py ( $$ . end>i d , $$ . s t ar t >i d ) ;
126 $$ . s t ar t >jump = $$ . end ;
127 $4 . end>next = $$ . end ; }
128 ;
129 while : t whi l e c ondi t i on t do statement
130 { as t s t mt n1 = new as t s t mt ( st mt nop ) ,
131 n2 = new as t s t mt ( s t mt j ump f al s e ) ,
132 n3 = new as t s t mt ( stmt j ump ) ,
133 n4 = new as t s t mt ( st mt nop ) ;
134 s p r i n t f ( n1>i d , JMP %d , ++l a b e l ) ;
135 s t r c py ( n3>i d , n1>i d ) ;
136 s p r i n t f ( n2>i d , JMP %d , ++l a b e l ) ;
137 s t r c py ( n4>i d , n2>i d ) ;
138 n1>next = n2 ;
139 n2>jump = n4 , n2>next = $4 . s t a r t ;
140 n2>expr = $2 ;
141 $4 . end>next = n3 ;
142 n3>next = n4 , n3>jump = n1 ;
143 $$ . s t a r t = n1 , $$ . end = n4 ;
144 }
145 ;
146
147 s t a t e me nt l i s t : statement {$$ = $1 ; }
148 | statement t s emi k s t a t e me nt l i s t {$$ = $1 ; i f ( $3 . s t a r t != NULL) $$ . end = $3 . end , ( $1 . end)>next= $3 . s t a r t ; }
149 // | s t a t e me nt l i s t t s emi k statement {$$ = $1 ; i f ( $3 . s t a r t != NULL) $$ . end = $3 . end , ( $1 . end)>next= $3 . s t a r t ; }
150 ;
151
152 c ondi t i on : t odd e xpr e s s i on {$$ = new as t expr ( t odd , ODD , $2 ,
NULL) ; }
153 | e xpr e s s i on t e q e xpr e s s i on {$$ = new as t expr ( t eq , =, $1 , $3 ) ; }
154 | e xpr e s s i on t ne e xpr e s s i on {$$ = new as t expr ( t ne , #, $1 , $3 ) ; }
155 | e xpr e s s i on t g t e xpr e s s i on {$$ = new as t expr ( t g t , >, $1 , $3 ) ; }
156 | e xpr e s s i on t ge e xpr e s s i on {$$ = new as t expr ( t ge , >=, $1 , $3 ) ; }
157 | e xpr e s s i on t l t e xpr e s s i on {$$ = new as t expr ( t l t , <, $1 , $3 ) ; }
A.2 Quellcode 141
158 | e xpr e s s i on t l e e xpr e s s i on {$$ = new as t expr ( t l e , <=, $1 , $3 ) ; }
159 ;
160
161 e xpr e s s i on : term {$$ = $1 ; }
162 | e xpr e s s i on t pl us term {$$ = new as t expr ( t pl us ,
163 ( const char ) +,
164 $1 ,
165 $3 ) ; }
166 | e xpr e s s i on t mi nus term {$$ = new as t expr ( t mi nus , , $1 , $3 ) ; }
167 ;
168 term : f a c t o r {$$ = $1 ; }
169 | term t mul t f a c t o r {$$ = new as t expr ( t mul t , , $1 , $3 ) ; }
170 | term t di v f a c t o r {$$ = new as t expr ( t di v , / , $1 , $3 ) ; }
171 ;
172
173 f a c t o r : t i de nt { i nt s t l , s t o ;
174 $$ = new as t expr ( t i de nt , $1 , NULL, NULL) ;
175 s t . l ookup ( $1 , s t v a r | s t c ons t , s t l , s t o ) ;
176 $$>s t l = s t l , $$>s t o = s t o ; }
177 | t number {$$ = new as t expr ( t number , , NULL, NULL) ; s p r i n t f ( $$>t ext ,%d , $1 ) ; }
178 | t br a o e xpr e s s i on t br a c {$$ = $2 ; }
179 | t pl us f a c t o r {$$ = $2 ; }
180 | t mi nus f a c t o r {$$ = new as t expr ( t chs , CHS , $2 , NULL) ; }
181 ;
182
183
184
185 %%
186
187 i nt yyer r or ( const char t ) {
188 p r i nt f ( Error Line %d : %s (%s ) \ n , yyl i neno , t , y y t e x t ) ;
189 ret urn 0;
190 }
Listing A.4. AST Header (pl-0/pl0-ast.h)
1 #i f nde f PL0 AST H
2 #de f i ne PL0 AST H 1
3
4 t ypedef s t r uc t s a s t bl o c k as t bl oc k ;
5 t ypedef s t r uc t s as t s t mt as t s t mt ;
6 t ypedef s t r uc t s a s t e xpr as t e xpr ;
7 t ypedef s t r uc t s a s t i d a s t i d ;
8
9 s t r uc t s a s t i d {
10 char name [ 2 0 ] ;
11 i nt s t o ;
12 i nt v al ;
13 s t r uc t s a s t i d next ;
14 } ;
15
16 s t r uc t s a s t bl o c k {
17 char name [ 2 0 ] ;
18 // i nt s t o ;
19 i nt n var ; // Anzahl Vari abl en f ?r HS
142 A Der PL/0-Modellcompiler
20 s t r uc t s a s t b l o c k sub ; // Unterprogramme
21 s t r uc t s a s t b l o c k next ; // Nhst er Bl ock im s e l b e n Berei ch
22 s t r uc t s a s t s t mt st mt ; // Be f e h l s l i s t e
23 } ;
24
25 enum { s t mt as s i gn , s t mt c al l , st mt read ,
26 s t mt wr i t e , st mt debug , s t mt j ump f al s e , stmt nop , stmt j ump} ;
27 const char as t s t mt t e xt [ ] [ 1 0 ] = { as s i gn , c a l l , read , wr i t e , debug , j mp f , nop , jump} ;
28
29 s t r uc t s as t s t mt {
30 i nt t ype ;
31 char i d [ 2 0 ] ;
32 i nt s t o ;
33 i nt s t l ;
34 s t r uc t s a s t e x p r expr ;
35 s t r uc t s a s t s t mt next ;
36 // s t r uc t s a s t s t mt sub ;
37 s t r uc t s a s t s t mt jump ;
38 } ;
39
40 s t r uc t s a s t e xpr {
41 i nt t oken ;
42 char t e x t [ 1 0 ] ;
43 i nt v al ;
44 i nt s t o ;
45 i nt s t l ;
46 s t r uc t s a s t e x p r l ;
47 s t r uc t s a s t e x p r r ;
48 } ;
49
50 as t s t mt new ast stmt ( i nt ) ;
51 // as t bl oc k new as t bl ock ( char ) ;
52
53 as t bl oc k new as t bl ock ( s t r uc t s a s t i d con , i nt varcount ,
54 s t r uc t s a s t bl o c k sub , s t r uc t s as t s t mt stmt ) ;
55
56
57 as t e xpr new as t expr ( i nt , const char , as t e xpr , as t e xpr ) ;
58 a s t i d new as t i d ( char ) ;
59 voi d a s t e xpr wr i t e ( as t e xpr ) ;
60 voi d as t s t mt wr i t e ( as t s t mt , i nt ) ;
61 voi d a s t bl o c k wr i t e ( as t bl oc k , i nt ) ;
62 voi d a s t a s t wr i t e ( ) ;
63
64 #e ndi f
Listing A.5. AST Code (pl-0/pl0-ast.c)
1
2 /
3 PL0 Abstrakter Syntaxbaum
4 Funkti onen zum ei nf ahcen e r s t e l l e n der voi r e Knotenarten
5 sowi e Ausgabe des kompl etten Baums auf den Bi l dschi r m
6 /
7
A.2 Quellcode 143
8 #i nc l ude <s t di o . h>
9 #i nc l ude <s t dl i b . h>
10 #i nc l ude <s t r i ng . h>
11 #i nc l ude pl 0as t . h
12 #i nc l ude y . tab . h
13 //#i nc l ude <mal l oc . h>
14 //#i nc l ude pl 0compi l er . tab . h
15 ext er n as t bl oc k as t ;
16
17 as t e xpr new as t expr ( i nt token , const char t , as t e xpr l , as t e xpr r ) {
18 as t e x pr e ;
19 e = ( as t e x pr ) mal l oc ( s i z e o f ( as t e x pr ) ) ;
20 i f ( e != NULL) {
21 e>t oken = t oken ;
22 s t r c py ( e>t ext , t ) ;
23 e>l = l ;
24 e>r = r ;
25 }
26 r et ur n e ;
27 }
28
29 a s t i d new as t i d ( char t ) {
30 a s t i d i d ;
31 i d = ( a s t i d ) mal l oc ( s i z e o f ( a s t i d ) ) ;
32 s t r c py ( i d>name , t ) ;
33 i d>next = NULL;
34 ret urn i d ;
35 }
36
37 as t s t mt new ast stmt ( i nt type) {
38 as t s t mt s ;
39 s = ( as t s t mt ) mal l oc ( s i z e o f ( as t s t mt ) ) ;
40 i f ( s != NULL) {
41 s t r c py ( s>i d , ) ;
42 s>t ype = t ype ;
43 // s>sub = NULL;
44 s>next = NULL;
45 s>jump = NULL;
46 s>expr = NULL;
47 }
48 r et ur n s ;
49 }
50
51 // as t bl oc k new as t bl ock ( char t ) {
52 a s t b l o c k ne w as t b l oc k ( s t r uc t s a s t i d con , i nt varcount ,
53 s t r uc t s a s t b l o c k sub , s t r uc t s a s t s t mt st mt )
54 {
55 a s t b l o c k p = ( a s t b l o c k ) mal l oc ( s i z e o f ( a s t b l o c k ) ) ;
56 i f ( p != NULL) {
57 s t r c py ( p>name , ) ;
58 // p>con = NULL;
59 p>n var = varcount ;
60 p>sub = sub ;
61 p>next = NULL;
144 A Der PL/0-Modellcompiler
62 p>st mt = st mt ;
63 }
64 r et ur n p ;
65 }
66
67 voi d a s t e xpr wr i t e ( as t e xpr s ) {
68 i f ( s != NULL) {
69 a s t e x p r wr i t e ( s>l ) ;
70 a s t e x p r wr i t e ( s>r ) ;
71 i f ( s>t oken == t i d e nt )
72 p r i nt f ( ( var %s : l%d o%d ) , s>t ext , s>s t l , s>s t o ) ;
73 e l s e
74 p r i nt f ( %s , s>t e x t ) ;
75 }
76 }
77
78 voi d as t s t mt wr i t e ( as t s t mt s , i nt l e v e l ) {
79 s t a t i c char l e e r s t r [ 255] =
;
80 char l = l e e r s t r + s t r l e n ( l e e r s t r ) l e v e l ;
81 // p r i nt f ( st mt : ) ;
82 whi l e ( s != NULL) {
83 p r i nt f (%s%l u st mt %d (%s ) : , l , ( unsi gned l ong i nt ) s , s>t ype , a s t s t mt t e x t [ s>t ype ] ) ;
84 p r i nt f ( i d=%s s t l=%d s t o=%d jmp= Expr : , s>i d , s>s t l , s>st o , ( unsi gned l ong i nt ) s>jump ) ;
85 a s t e x p r wr i t e ( s>expr ) ;
86 p r i nt f (\n) ;
87 s = s>next ;
88 }
89 }
90
91 char s t r t o l a t e x ( char in) {
92 s t a t i c char l a t e x = NULL;
93 char out ;
94 i f ( l a t e x != NULL) f r e e ( l a t e x ) ;
95 l a t e x = ( char ) mal l oc (2 s t r l e n ( i n ) ) ;
96 out = l a t e x ;
97 do {
98 i f ( i n == )
99 out++ = \\ ;
100 out++ = i n ;
101 } while ( i n++ != \0 ) ;
102 ret urn l a t e x ;
103 }
104
105 voi d a s t s t mt l a t e x ( as t s t mt s , i nt l e v e l ) {
106 i nt i = 0;
107 i nt von [ 10] , nach [ 10] , n jmp = 1;
108 FILE f =f open (dummy. t ex , w) ;
109 f p r i n t f ( f , \\ document cl ass { a r t i c l e }\n\\ begi n {document }\n\\ uni tl ength8mm\n\\ begi n { pi c t ur e }(14 , 4)\n) ;
110 whi l e ( s != NULL) {
111 f p r i n t f ( f , \\ put(%d , 1) {\\ f ramebox ( 1. 4 , 1) {\\ begi n {mi ni page }{. 8cm}\\ f o o t no t e s i z e { , i 2) ;
112 s wi t ch ( s>t ype ) {
113 case s t mt as s i g n : f p r i n t f ( f , as s i gn\\\\%s %d/%d , s t r t o l a t e x ( s>i d ) , s>s t l , s>s t o ) ;
114 break ;
A.2 Quellcode 145
115 case s t mt read : f p r i n t f ( f , read\\\\%s %d/%d , s t r t o l a t e x ( s>i d ) , s>s t l , s>s t o ) ;
116 break ;
117 case s t mt wr i t e : f p r i n t f ( f , wr i t e ) ;
118 break ;
119 case s t mt c a l l : f p r i n t f ( f , c a l l \\\\%s %d/%d , s>i d , s>s t l , s>s t o ) ;
120 break ;
121 case s t mt j ump f al s e : f p r i n t f ( f , jmp\\ f \\\\%s , s t r t o l a t e x ( s>i d ) ) ;
122 n jmp++, von [ at oi ( s>i d +4)] = i ;
123 // p r i nt f ( Sprung %s \n , s>i d ) ;
124 break ;
125 case stmt j ump : f p r i n t f ( f , jump\\\\%s , s t r t o l a t e x ( s>i d ) ) ;
126 n jmp++, von [ at oi ( s>i d +4)] = i ;
127 // p r i nt f ( Sprung %s \n , s>i d ) ;
128 break ;
129 case st mt nop : f p r i n t f ( f , nop\\\\%s , s t r t o l a t e x ( s>i d ) ) ;
130 nach [ at oi ( s>i d +4)] = i ;
131 break ;
132 case st mt debug : f p r i n t f ( f , debug ) ;
133 break ;
134 d e f a u l t : f p r i n t f ( f , unkown\\\\%d , s>t ype ) ;
135 break ;
136 }
137 f p r i n t f ( f , }\\end{mi ni page }}}\n) ;
138 i f ( s>expr != NULL) {
139 f p r i n t f ( f , \\ put (%.1 l f , 1) {\\ vect or (0 , 1){. 5}}\n , i 2. 0+. 2) ;
140 f p r i n t f ( f , \\ put (%.1 l f , . 4) {\\ makebox (0 , 0){\\ vdot s }}\n , i 2. 0+. 2) ;
141 }
142 f p r i n t f ( f , \\ put(%l f , 1. 5) {\\ vect or ( 1 , 0) {. 6}}\n , i 2. 0+1. 4) ;
143 s = s>next , i ++;
144 }
145 f p r i n t f ( f , \\ put(%l f , 1. 25) {\\ makebox (0 , 0){\\ f o o t no t e s i z e {NULL}}}\n , i 2. 0) ;
146 f or ( i = 0; i <= n jmp ; i ++) {
147 // p r i nt f ( von %d nach %d\n , von [ i ] , nach [ i ] ) ;
148 f p r i n t f ( f , \\ put (%.1 l f , 2) {\\ l i ne (0 , 1){%. 1 l f }}\n , von [ i ] 2. 0+. 7 , i . 5+. 5) ;
149 f p r i n t f ( f , \\ put (%.1 l f ,%.1 l f ){\\ vect or (0, 1){%.1 l f }}\n , nach [ i ] 2. 0+. 7 , i . 5+2. 5 , i . 5+. 5) ;
150 f p r i n t f ( f , \\ put (%.1 l f ,%.1 l f ){\\ l i ne (%d , 0){%. 1 l f }}\n , von [ i ] 2. 0+. 7 , i . 5+2. 5 , ( nach [ i ] > von [ i ] ) ? 1 : 1, abs ( nach [ i ] von [ i ] ) 2 . 0 ) ;
151
152 }
153 f p r i n t f ( f , \\ put ( . 3 , 4) {\\ vect or (0 , 1){2}\\makebox ( 1 , . 5) { s t a r t }}\n) ;
154 f p r i n t f ( f , \\end{ pi c t ur e }\n\\end{document }\n) ;
155 f c l o s e ( f ) ;
156 }
157
158 voi d a s t b l o c k wr i t e ( a s t b l o c k p , i nt l e v e l ) {
159 a s t b l o c k proc ;
160 s t a t i c char l e e r s t r [ 255] =
;
161 char l = l e e r s t r + s t r l e n ( l e e r s t r ) l e v e l ;
162 a s t i d i d ;
163
164 p r i nt f (\n%sBl ock \%s \ %l u \n , l , p>name , ( unsi gned l ong i nt ) p ) ;
165 / i d = p>con ;
166 p r i nt f (%sKonstanten : , l ) ;
167 whi l e ( i d != NULL) {
146 A Der PL/0-Modellcompiler
168 p r i nt f ( \%s\=%d o%d , i d>name , i d>val , i d>s t o ) ;
169 i d = i d>next ;
170 }
171 p r i nt f (\n) ; /
172 // i d = p>var ;
173 p r i nt f (%s %d Vari abl en\n , l , p>n var ) ;
174 / whi l e ( i d != NULL) {
175 p r i nt f ( \%s \ o%d , i d>name , i d>s t o ) ;
176 i d = i d>next ;
177 }
178 p r i nt f (\n) ; /
179 proc = p>sub ;
180 whi l e ( proc != NULL) {
181 a s t b l o c k wr i t e ( proc , l e v e l +1);
182 proc = proc>next ;
183 }
184 a s t s t mt wr i t e ( p>stmt , l e v e l ) ;
185 a s t s t mt l a t e x ( p>stmt , l e v e l ) ;
186 p r i nt f (\n) ;
187 }
188
189
190 voi d a s t a s t wr i t e ( ) {
191 a s t b l o c k wr i t e ( ast , 0) ;
192 }
Listing A.6. Symboltabelle Code (pl-0/pl0-symtab.cpp)
1 #i nc l ude pl 0symtab . hpp
2 #i nc l ude <map>
3 #i nc l ude <s t r i ng >
4 #i nc l ude <i ostream>
5 us i ng namespace std ;
6
7 symtab entry : : symtab entry ( i nt type , i nt nr ) {
8 t ype = t ype , nr = nr ;
9 }
10
11 symtab : : symtab ( ) {
12 l e v e l = 1;
13 }
14
15 voi d symtab : : l e ve l up ( ) {
16 l e v e l ++;
17 }
18
19 voi d symtab : : l evel down ( ) {
20 cont ent [ l e v e l ]. c l e ar ( ) ;
21 }
22
23 i nt symtab : : i ns e r t ( s t r i ng name , i nt typ ) {
24 i nt n = cont ent [ l e v e l ] . s i z e ( ) , r = 1;
25 i f ( cont ent [ l e v e l ] . f i nd (name) == cont ent [ l e v e l ] . end ( ) ) {
26 cont ent [ l e v e l ] [ name ] = symt ab ent ry ( typ , n ) ;
A.2 Quellcode 147
27 cout << Symtabi ns e r t << name << : << cont ent [ l e v e l ] [ name ] . t ype << / <<
cont ent [ l e v e l ] [ name ] . nr << endl ;
28 r = 0;
29 }
30 r et ur n r ;
31 }
32
33 i nt symtab : : l ookup ( s t r i ng name , i nt type , i nt & l , i nt & o ) {
34 i nt i = l e v e l +1, rc ;
35 l = o = 1;
36 whi l e (i >= 0 && cont ent [ i ] . f i nd (name) == cont ent [ i ] . end ( ) ) ;
37 cout << Symtabl ookup << name << : << ( l e v e l i ) << / << cont ent [ i ] [ name ] . nr << endl ;
38 i f ( i >= 0)
39 i f ( cont ent [ i ] [ name ] . t ype | t ype )
40 l = l e v e l i , o = cont ent [ i ] [ name ] . nr , rc = 0;
41 e l s e
42 rc = 1; // Fal s cher Typ
43 e l s e
44 rc = 2; // Ni cht gef unden
45 ret urn rc ;
46 }
47
48 voi d symtab : : pr i nt ( ) {
49 map<s t r i ng , symt ab ent ry> : : i t e r a t o r pos ;
50 cout << Akt . Level : << l e v e l << endl ;
51 f or ( i nt i = 0; i <= l e v e l ; i ++) {
52 cout << Level << i << : Hohe << cont ent [ i ] . s i z e ( ) << endl ;
53 pos = cont ent [ i ] . begi n ( ) ;
54 f or ( pos = cont ent [ i ] . begi n ( ) ; pos != cont ent [ i ] . end ( ) ; ++pos ){
55 cout << Key : << ( pos ) . f i r s t << ( pos ) . second . nr << ( pos ) . second . t ype << endl ;
56 }
57 }
58 }
Listing A.7. Symboltabelle Header (pl-0/pl0-symtab.hpp)
1 #i nc l ude <map>
2 #i nc l ude <s t r i ng >
3 us i ng namespace std ;
4
5 enum { s t v a r = 1 , s t c o ns t = 2 , s t pr oc = 4} ;
6
7 c l a s s symtab entry{
8 pub l i c :
9 symt ab ent ry ( i nt = 0 , i nt = 0) ;
10 i nt t ype ;
11 i nt nr ;
12 // i nt v al ;
13 } ;
14
15 c l a s s symtab {
16 pub l i c :
17 symtab ( ) ;
18 voi d l e v e l u p ( ) ;
19 voi d l evel down ( ) ;
148 A Der PL/0-Modellcompiler
20 i nt i ns e r t ( s t r i ng name , i nt t yp ) ;
21 i nt l ookup ( s t r i ng name , i nt t ype , i nt & l , i nt & o ) ;
22 voi d pr i nt ( ) ;
23 pr i v at e :
24 map<s t r i ng , symt ab ent ry> cont ent [ 1 0 ] ;
25 i nt l e v e l ;
26 } ;
Listing A.8. Fehlermodul (pl-0/pl0-error.h)
1 #i f nde f PL0 ERROR H
2 #de f i ne PL0 ERROR H 1
3
4 enum {
5 e no er r = 0 ,
6 e dot r e q = 1 ,
7 e i d r e q = 2 ,
8 e e q r e q = 3 ,
9 e number req = 4 ,
10 e s emi kol on r eq = 5 ,
11 e a s s i g n r e q = 6 ,
12 e c a l l r e q = 7 ,
13 e i f r e q = 8 ,
14 e t he n r e q = 9 ,
15 e wh i l e r e q = 10 ,
16 e do r eq = 11 ,
17 e wr i t e r e q = 12 ,
18 e r e ad r e q = 13 ,
19 e comp op req = 14 ,
20 e b r c r e q = 15 ,
21 e b e g i n r e q = 16 ,
22 e end r eq = 17
23 } ;
24
25 char ge t xt [ 2 0 ] [ 3 0 ] = {
26 / e no er r / Kein Fehl er ,
27 / e dot r eq , / \. \ er war t et ,
28 / e i d r e q , / Bezei chner er war t et ,
29 / e eq r eq , / \=\ er war t et ,
30 / e number req , / Zahl er war t et ,
31 / e s emi kol on r eq / \; \ er war t et ,
32 / e a s s i g n r e q / \:=\ er war t et ,
33 / e c a l l r e q / CALL er war t et ,
34 / e i f r e q / IF er war t et ,
35 / e t he n r e q / THEN er war t et ,
36 / e wh i l e r e q / WHILE er war t et ,
37 / e do r eq / DO er war t et ,
38 / e wr i t e r e q / \! \ er war t et ,
39 / e r e ad r e q / \?\ er war t et ,
40 / e comp op req / Ver g l e i c hs ope r at or er war t et ,
41 / e b r c r e q / \(\ er war t et
42 / e b e g i n r e q / BEGIN er war t et ,
43 / e end r eq / END erwwart et
44 } ;
45 #e ndi f
A.2 Quellcode 149
Listing A.9. Interpreter (pl-0/pl0-interpreter.c)
1 /
2 Der zei t ni cht l a uf f a hi g ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
3
4
5 PL/0I nt e r pr e t e r
6 I n t e r p r e t i e r t AST
7 Dazu werden di e Symbol tabel l enEi nt r age im AST auf ei nen
8 RAM abge bi l de t . Im RAM wi rd neben den Daten das s i nd
9 i nt Werte f ur di e PL/0Vari abl en und BlockZe i ge r f ur
10 di e Unterprogramme di e St at i c LinkKette SL und
11 DynamicLinkKette DL gehal t en
12 /
13
14 #i nc l ude pl 0i nt e r pr e t e r . h
15 #i nc l ude pl 0as t . h
16 #i nc l ude y . tab . h
17 #i nc l ude <s t di o . h>
18 #i nc l ude <i ostream>
19 us i ng namespace std ;
20 #i nc l ude <s t dl i b . h>
21
22 // Gl obal e Vari abl en
23 ram entry ram[ 1 0 0 ] ;
24 ext er n char f i l ename [ 1 0 0 ] ;
25 ext er n FILE as m f i l e , b i n f i l e , yyi n ;
26 i nt yyparse ( ) ;
27 ext er n i nt l nr , cnr ;
28 ext er n as t bl oc k as t ;
29
30 // Hauptprogramm
31
32
33 i nt i nt e r pr e t e r ( ) {
34 p r i nt f ( I nt e r pe r at at i on : \ n) ;
35 r am i ni t ( ) ;
36 i t p b l o c k ( ast , 0) ;
37 ret urn 0;
38 }
39
40 // Unterprogramme
41
42
43
44 i nt ram get adr ( i nt l e ve l , i nt o f f s e t ) {
45 i nt p = ram [ 0 ] . i nt , i ;
46 f or ( i = 0; i < l e v e l ; i ++) p = ram[ p ] . i nt ;
47 ret urn p 2 o f f s e t ;
48 }
49
50 voi d ram pri nt ( ) {
51 i nt i ;
150 A Der PL/0-Modellcompiler
52 p r i nt f(\n) ;
53 f or ( i = ram [ 0 ] . i nt ; i >= 0; i )
54 p r i nt f (%2d : %10d\n , i , ram[ i ] . i nt ) ;
55 p r i nt f(\n) ;
56 }
57
58 voi d r am i ni t ( ) {
59 ram [ 0 ] . i nt = 2; // TOS
60 ram [ 2 ] . i nt = 1; // SL
61 ram [ 1 ] . i nt = 1; // DL
62 }
63
64 i nt r am l evel up ( i nt n , i nt s l l e ng t h ) {
65 i nt i , p=ram [ 0 ] . i nt ;
66 // p r i nt f ( Level up : s l l e n g t h = %d\n , s l l e n g t h ) ;
67 f or ( i = 0; i < s l l e n g t h ; i ++)
68 p = ram[ p ] . i nt ;
69 ram[ ram [ 0 ] . i nt + n + 2 ] . i nt = p ; // SLLink s et z e n
70 ram[ ram [ 0 ] . i nt + n + 1] = ram [ 0 ] ; // DLLink s et z e n
71 ram [ 0 ] . i nt += (n + 2) ;
72 // p r i nt f ( Neuer raml e v e l : TOS=%d DL=%d , SL=5d\n , ram [ 0 ] . i nt , ram[ ram [ 0 ] . i nt 1] . i nt , ram[ ram [ 0 ] . i nt ] . i nt ) ;
73 // ram pri nt ( ) ;
74 ret urn 0;
75 }
76
77 i nt ram l evel down ( ) {
78 ram[ 0 ] = ram[ ram [ 0 ] . i nt 1 ] ;
79 ret urn (ram [ 0 ] . i nt < 0) ;
80 }
81
82 ram entry ram get ( i nt l e ve l , i nt o f f s e t ) {
83 // p r i nt f (ram get l=%d o=%d adr=%d : %d\n , l e v e l , o f f s e t , ram get adr ( l e v e l , o f f s e t ) , ram[ ram get adr ( l e v e l , o f f s e t ) ] . i nt ) ;
84 ret urn ram[ ram get adr ( l e v e l , o f f s e t ) ] ;
85 }
86
87 voi d ram set ( i nt l e ve l , i nt o f f s e t , ram entry val ) {
88 ram[ ram get adr ( l e v e l , o f f s e t ) ] = v al ;
89 // p r i nt f (ram s e t l=%d o=%d adr=%d : %d\n , l e v e l , o f f s e t , ram get adr ( l e v e l , o f f s e t ) , ram[ ram get adr ( l e v e l , o f f s e t ) ] . i nt ) ;
90 }
91
92
93 voi d i t p bl o c k ( as t bl oc k p , i nt s l d e l t a ) {
94 a s t b l o c k proc ;
95 a s t i d i d ;
96
97 // p r i nt f ( I nt e r p r e t i e r e Bl ock \%s \\n , p>name ) ;
98 i nt ramsi ze = 0;
99 i d = NULL; //p>var ; // Ri cht en ! ! ! RAMSi z e k unf t i g ui n St r ukt ur !
100 ram ent ry r ;
101 whi l e ( i d != NULL) {
102 // p r i nt f ( Var %s , i d>name ) ;
103 i d = i d>next ;
104 ramsi ze++;
105 }
A.2 Quellcode 151
106
107 proc = p>sub ;
108 while ( proc != NULL) {
109 // p r i nt f ( Proc %s : %d\n , proc>name , ( i nt ) proc ) ;
110 proc = proc>next ;
111 ramsi ze++;
112 }
113 // pr i nt f (\n%d Spei cher el ement e \n , ramsi ze ) ;
114 r am l evel up ( ramsi ze , s l d e l t a ) ;
115 proc = p>sub ;
116 while ( proc != NULL) {
117 r . b l oc k = proc ;
118 // ram set (0 , proc>st o , r ) ;
119 proc = proc>next ;
120 }
121 f f l u s h ( st dout ) ;
122 i t p s t mt ( p>stmt ) ;
123 ram l evel down ( ) ;
124 }
125
126 i nt i t p e xpr ( as t e xpr e ) {
127 i nt rc = 0;
128 i f ( e != NULL) {
129 s wi t ch ( e>t oken ) {
130 case t odd :
131 case t p l u s : rc = i t p e x p r ( e>l ) + i t p e x p r ( e>r ) ; break ;
132 case t mi nus : rc = ( e>r != NULL) ? i t p e x p r ( e>l ) i t p e x p r ( e>r ) : i t p e x p r ( e>l ) ; break ;
133 case t mul t : rc = i t p e x p r ( e>l ) i t p e x p r ( e>r ) ; break ;
134 case t d i v : rc = i t p e x p r ( e>l ) / i t p e x p r ( e>r ) ; break ;
135 case t g t : rc = i t p e x p r ( e>l ) > i t p e x p r ( e>r ) ; break ;
136 case t g e : rc = i t p e x p r ( e>l ) >= i t p e x p r ( e>r ) ; break ;
137 case t l t : rc = i t p e x p r ( e>l ) < i t p e x p r ( e>r ) ; break ;
138 case t l e : rc = i t p e x p r ( e>l ) <= i t p e x p r ( e>r ) ; break ;
139 case t e q : rc = i t p e x p r ( e>l ) == i t p e x p r ( e>r ) ; break ;
140 case t ne : rc = i t p e x p r ( e>l ) != i t p e x p r ( e>r ) ; break ;
141 case t c h s : rc = i t p e x p r ( e>l ) ; break ;
142 case t i d e nt : rc = ram get ( e>s t l , e>s t o ) . i nt ; break ;
143 case t number : rc = at oi ( e>t e x t ) ; break ;
144 d e f a u l t : p r i nt f ( expr unbek . t oken %d\n , e>t oken ) ;
145 }
146 }
147 r et ur n r c ;
148 }
149
150
151 voi d i t p s t mt ( as t s t mt stmt ) {
152 ram ent ry r ;
153 whi l e ( st mt != NULL) {
154 s wi t ch ( stmt>t ype ) {
155 case s t mt as s i g n : r . i nt = i t p e x p r ( stmt>expr ) ;
156 ram set ( stmt>s t l , stmt>st o , r ) ;
157 break ;
158 case s t mt read : p r i nt f ( Ei ngabe %s : , stmt>i d ) ;
159 s canf (%d , &(r . i nt ) ) ;
152 A Der PL/0-Modellcompiler
160 ram set ( stmt>s t l , stmt>st o , r ) ;
161 break ;
162 case s t mt wr i t e : p r i nt f ( Ausgabe : %d\n , i t p e x p r ( stmt>expr ) ) ;
163 break ;
164 / case s t mt whi l e : whi l e ( i t p e x p r ( stmt>expr ) ) i t p s t mt ( stmt>sub ) ;
165 break ;
166 case s t mt i f : i f ( i t p e x p r ( stmt>expr ) ) i t p s t mt ( stmt>sub ) ;
167 break ; /
168 case s t mt c a l l : // p r i nt f ( Proc %s : \ n , stmt>i d ) ;
169 r = ram get ( stmt>s t l , stmt>s t o ) ;
170 // p r i nt f ( Level %d Of f s e t %d : %d\n , stmt>s t l , stmt>st o , ( i nt ) r . b l oc k ) ;
171 i t p b l o c k ( r . bl ock , stmt>s t l ) ;
172 break ;
173 }
174 stmt = stmt>next ;
175 }
176 }
Listing A.10. Compiler (pl-0/pl0-compiler.cpp)
1 /
2 CompilerModul
3 Gibt ParrotAssembl er aus
4
5 Fi xe Re gi s t e r :
6
7 I 31 : Adresse e i ne r Vari abl en
8 I30 , P30 , S30 : Ei ngabe von Vari abl en
9 I 29 : SymtabDel ta bei Funkt i ons auf r uf
10 I 0 . . . : Expr essi on
11 /
12 #i nc l ude <i ostream>
13 #i nc l ude <iomanip>
14 #i nc l ude <f stream>
15 #i nc l ude <s t r i ng . h>
16 us i ng namespace std ;
17 #i nc l ude pl 0as t . h
18 #i nc l ude y . tab . h
19
20 ext er n char f i l ename [ 1 0 0 ] ;
21 ext er n i nt l nr , cnr ;
22 ext er n as t bl oc k as t ;
23
24 // Prototypen
25 voi d emi t ( i nt , s t r i ng , s t r i ng , s t r i ng , s t r i ng , s t r i ng = ) ;
26 voi d emi t ( s t r i ng ) ;
27 voi d c bl oc k ( as t bl oc k , i nt ) ;
28 voi d c stmt ( as t s t mt , i nt l e v e l ) ;
29 voi d c expr ( as t e xpr , char ) ;
30 voi d c e xpr r ( as t e xpr , char ) ;
31 i nt e xpr l bl nr ;
32
33 // Gl obal e Vari abl en
34 s t a t i c i nt l abel ;
35 of st ream a s m f i l e ;
A.2 Quellcode 153
36
37
38 char i t oa ( i nt zahl ) {
39 s t a t i c char t e x t [ 1 0 0 ] ;
40 s p r i n t f ( t ext , %d , z ahl ) ;
41 ret urn t e x t ;
42 }
43
44 // Hauptprogramm
45
46 i nt compi l er ( ) {
47 char f [ 1 0 0 ] ;
48 s p r i n t f ( f , %s . pasm , f i l ename ) ;
49 as m f i l e . open( f , i os : : out | i os : : t runc ) ;
50 i f ( ! as m f i l e ) {
51 f p r i n t f ( s t der r , Ausgabedat ei %s konnt e ni cht g e o f f ne t werden ! \ n , f i l ename ) ;
52 ret urn 1;
53 }
54
55 pr i nt f ( Compi l i erung %s : \ n , f ) ;
56 l abel = 1;
57
58 emi t ( new P0 , Array # Fel d f ur Hauptspei cher ) ;
59 emi t ( set P0 , 100 # mit Groe 100) ;
60 emi t ( set I0 , 0 # TOS s et z en ) ;
61 emi t ( set P0 [ 0 ] , I 0 ) ;
62 emi t ( branch s t a r t ) ;
63 emi t ( ram pri nt : set I0 , P0 [ 0 ] ) ;
64 emi t ( pr i nt \\\n\) ;
65 emi t ( pr i nt \RAM\\n\) ;
66 emi t ( ram top : pr i nt I 0 ) ;
67 emi t ( pr i nt \ \) ;
68 emi t ( set I1 , P0 [ I 0 ] ) ;
69 emi t ( pr i nt I 1 ) ;
70 emi t ( pr i nt \\\n\) ;
71 emi t ( sub I0 , 1) ;
72 emi t ( ge I0 , 0 , ram top ) ;
73 emi t ( pr i nt \\\n\) ;
74 emi t ( r e t ) ;
75 emi t ( s t a r t : ) ;
76 emi t ( set I29 , 0 # SymtabDel ta 0 f ur main ) ;
77 c bl oc k ( ast , 0 ) ; // Hauptprogramm ab Ze i l e 1
78 emi t ( end ) ;
79 a s m f i l e . c l o s e ( ) ;
80 r et ur n 0;
81 }
82
83 // Unterprogramme
84
85 voi d c adr var ( i nt s t l , i nt sto , char name) {
86 i nt i ;
87 emi t ( s e t I31 , P0 [ 0 ] # Adr Var + s t r i ng (name) + : Sym + s t r i ng ( i t oa ( s t l ))+/+s t r i ng ( i t oa ( s t o ) ) ) ;
88 f or ( i = 0; i < s t l ; i ++)
89 emi t ( s e t I31 , P0[ I31 ] ) ;
154 A Der PL/0-Modellcompiler
90 emi t ( sub I31 , + s t r i ng ( i t oa ( s t o +2)));
91 }
92
93 voi d c bl oc k ( as t bl oc k p , i nt l e v e l ) {
94 a s t b l o c k proc ;
95 a s t i d i d ;
96 i nt anz ram ;
97 s t r i ng l b1 , l b 2 ;
98 emi t (LBL PROC + s t r i ng ( p>name) + : ) ;
99
100 // Je t z t wi rd AR ang e l e g t
101 / i d = p>var ;
102 anz ram = 0;
103 whi l e ( i d != NULL) { // Vari abl en zahl en
104 anz ram++;
105 i d = i d>next ;
106 }
107 anz ram += 2; // wegen SL, DL und RS/
108 anz ram = p>n var + 2;
109 emi t ( set I0 , P0 [ 0 ] # a l t e r TOS ) ;
110 emi t ( add I1 , I0 , +s t r i ng ( i t oa ( anz ram))+
#neuer TOS ) ;
111 emi t ( set P0 [ 0 ] , I 1 ) ;
112 emi t ( set I2 , I 0 # SL ) ;
113 l b1 = LBL + s t r i ng ( i t oa(++l abel ) ) , l b2 = LBL + s t r i ng ( i t oa(++l abel ) ) ;
114 emi t ( l b1 + : l e I29 , 0 , + l b2 ) ;
115 emi t ( set I2 , P0 [ I 2 ] # SLKette ) ;
116 emi t ( sub I29 , 1) ;
117 emi t ( branch + l b1 ) ;
118 emi t ( l b2 + : set P0 [ I 1 ] , I 2 #SL s et ze n ) ;
119 emi t ( sub I1 , 1 # Adresse DL ) ;
120 emi t ( set P0 [ I 1 ] , I 0 # Adresse DL ) ;
121 proc = p>sub ;
122 i f ( proc != NULL) {
123 l b 1 = LBL + s t r i ng ( i t oa(++l a b e l ) ) ;
124 emi t ( branch + l b 1 + # Prozeduren ubers pri ngen ) ;
125 whi l e ( proc != NULL) {
126 c b l oc k ( proc , l e v e l + 1) ;
127 proc = proc>next ;
128 }
129 emi t ( l b1 + : # Unterprogramme Ende ) ;
130 }
131 emi t(#Je t z t Programmcode von PROC + s t r i ng ( p>name ) ) ;
132 c stmt ( p>stmt , l e v e l ) ;
133 emi t ( set I0 , P0 [ 0 ] #Stacksegment l os chen ; TOSZe i ge r l aden ) ;
134 emi t ( sub I0 , 1 # DLAdresse ) ;
135 emi t ( set I0 , P0 [ I 0 ] #Stacksegment l os chen ; TOSZe i ge r l aden ) ;
136 emi t ( set P0 [ 0 ] , I 0 ) ;
137 i f ( l e v e l > 0)
138 emi t ( r e t # Funkti on Ende ) ;
139 }
140
141 voi d c expr ( as t e xpr p , char r e s ul t ) {
142 e x p r l b l nr = 0;
A.2 Quellcode 155
143 c e x pr r ( p , r e s u l t ) ;
144 }
145
146 voi d c e xpr r ( as t e xpr p , char r e s ul t ) {
147 s t r i ng t x t ;
148 char l op [ 10] = , r op [ 10] =;
149 i f ( p>l == NULL) {// Bl at t
150 i f ( p>t oken == t i d e nt ) {
151 c adr var ( p>s t l , p>st o , p>t e x t ) ;
152 s p r i n t f ( r e s ul t , I%d , e x p r l b l nr ++);
153 emi t ( s e t + s t r i ng ( r e s u l t ) + , P0[ I31 ] ) ;
154 }
155 el se // Konstante
156 {
157 s t r c py ( r e s ul t , p>t e x t ) ;
158 }
159 }
160 el se {
161 s wi t ch ( p>t e x t [ 0 ] ) {
162 case + : t x t = add ; break ;
163 case : t x t = sub ; break ;
164 case : t x t = mul ; break ;
165 case / : t x t = di v ; break ;
166 case C : t x t = mul ; s t r c py ( r op , 1); break ;
167 d e f a u l t : t x t = cmp;
168 }
169 c e xpr r ( p>l , l op ) ;
170 i f ( p>r != NULL) c e xpr r ( p>r , r op ) ;
171 s p r i n t f ( r e s ul t , I%d , e xpr l bl nr ++);
172 emi t ( 1, txt , r e s ul t , l op , r op , p>text ) ;
173 }
174 }
175
176 voi d c stmt ( as t s t mt stmt , i nt l e v e l ) {
177 char erg [ 2 0 ] ;
178 s t r i ng op ;
179 whi l e ( st mt != NULL) {
180 s wi t ch ( stmt>t ype ) {
181 case s t mt as s i g n : c expr ( stmt>expr , erg ) ;
182 c adr var ( stmt>s t l , stmt>st o , stmt>i d ) ;
183 emi t ( s e t P0[ I31 ] , + s t r i ng ( erg ) + # + s t r i ng ( stmt>i d ) ) ;
184 break ;
185 case s t mt read : emi t ( pr i nt \ Ei ngabe : \) ;
186 emi t ( g e t s t d i n P30 ) ;
187 emi t ( r e adl i ne S30 , P30 ) ;
188 emi t ( s e t I30 , S30 ) ;
189 c adr var ( stmt>s t l , stmt>st o , stmt>i d ) ;
190 emi t ( s e t P0[ I31 ] , I30
# + s t r i ng ( stmt>i d ) ) ;
191 break ;
192 case s t mt wr i t e : c expr ( stmt>expr , erg ) ;
193 emi t ( pr i nt + s t r i ng ( erg ) ) ;
194 emi t ( pr i nt \\\n\) ;
195 break ;
156 A Der PL/0-Modellcompiler
196 case s t mt j ump f al s e : c expr ( stmt>expr , erg ) ;
197 s wi t ch ( stmt>expr>t oken ) {
198 case t l t : op = ge ; break ;
199 case t l e : op = gt ; break ;
200 case t g t : op = l e ; break ;
201 case t g e : op = l t ; break ;
202 case t e q : op = ne ; break ;
203 case t ne : op = eq ; break ;
204 }
205 emi t ( + op + + s t r i ng ( erg ) + , 0 , + s t r i ng ( stmt>i d ) ) ;
206 break ;
207 case stmt jump : emi t ( branch + s t r i ng ( stmt>i d ) ) ;
208 break ;
209 case stmt nop : emi t ( s t r i ng ( stmt>i d ) + : # NOP ) ;
210 break ;
211 case s t mt c a l l : emi t ( set I29 , + s t r i ng ( i t oa ( stmt>s t l ) ) +
#SymtabDel ta ! ) ;
212 emi t ( bsr LBL PROC +
s t r i ng ( stmt>i d ) ) ;
213 break ;
214 case stmt debug : emi t ( bsr ram pri nt ) ;
215 break ;
216 de f aul t : emi t(#ERROR: Unkown opcode ) ;
217 }
218 stmt = stmt>next ;
219 }
220 }
221
222 // i nt emi t ( char label , char command, char argument , char comment ) {
223 voi d emi t ( i nt l ab e l , s t r i ng command, s t r i ng dest , s t r i ng arg1 , s t r i ng arg2 , s t r i ng comment ) {
224 i f ( l a b e l >= 0)
225 as m f i l e << LBL << l a b e l << : \ t ;
226 e l s e
227 as m f i l e << ;
228 as m f i l e << command << \ t ;
229 i f ( des t != )
230 as m f i l e << des t ;
231 i f ( arg1 != )
232 as m f i l e << , \ t << arg1 ;
233 i f ( arg2 != )
234 as m f i l e << , \ t<< arg2 ;
235 i f ( comment != )
236 as m f i l e << \ t#<< comment ;
237 as m f i l e << endl ;
238 }
239 voi d emi t ( s t r i ng command) {
240 as m f i l e << command << endl ;
241 }
B
ASCII-Tabelle
dez 0 16 32 48 64 80 96 112
hex 0 10 20 30 40 50 60 70
0 0 \0 0 @ P p
1 1 ! 1 A Q a q
2 2 " 2 B R b r
3 3 # 3 C S c s
4 4 $ 4 D T d t
5 5 % 5 E U e u
6 6 & 6 F V f v
7 7 \a 7 G W g w
8 8 \b ( 8 H X h x
9 9 \t ) 9 I Y i y
10 A \n * : J Z j z
11 B + ; K [ k {
12 C \f , < L \ l |
13 D \r - = M ] m }
14 E . > N ^ n ~
15 F / ? O _ o del
Die ASCII-Codes 0 bis 31 (dez) stellen Steuerzeichen dar. In der Tabelle sind nur die Steu-
erzeichen eingetragen, f ur die es in C Escapesequenzen gibt. stellt das Leerzeichen dar.
Die Codes von 128-255 (dez) sind nicht standardisiert, und von Betriebssystem zu Betriebs-
system unterschiedlich belegt. So konnen Programme, die deutsche Umlaute verwenden,
auf einem anderen Bestriebssystem z.B. falsche Anzeigen verursachen. In Zeiten der 7-bit-
Datenverarbeitung musste - da es keine Codes > 127 gab - im ASCII-Code einige Zeichen
durch die deutschen Umlaute ersetzt werden. So gibt es eine deutsche Variante des 7-bit-
ASCII-Codes.
Umlaut Unix / WindowsDOS / WIN-Konsole ASCII (D)

A 196/C4 142/8E 91/5B

O 214/D6 153/99 92/5C

U 220/DC 154/9A 93/5D


a 228/E4 132/84 123/7B
o 246/F6 148/94 124/7C
u 252/FC 129/81 125/7D
223/DF 225/E1 126/7E
C
Abk urzungen
CT Compile-time
RT Compile-time
SL Static Link
DL Dynamic Link
AR Activation Record
Literaturverzeichnis
[ASU88] Alfred Aho, Ravi Sehti, and Jerey D. Ullman. Compilerbau - Band 1. Addison-Wesley,
Bonn, 1988. ISBN 3-89319-150-X.
[Her95] Helmut Herold. Lex und Yacc: Lexikalische und syntaktische Analyse. Addison-Wesley,
Bonn, 2. edition, 1995.
[KR90] Brian W. Kernighan and Dennis M. Ritchie. Programmieren in C. Hanser, M unchen, 2.
edition, 1990. ISBN 3-446-15497-3.
[LS75] M. E. Lesk and E. Schmidt. Lex - a lexical analyzer generator. Computing Science Technical
Report No. 39, October, 1975.
[Sed92] Robert Sedgewick. Algorithmen. Addison-Wesley, 1992.
[Wir86] Niklaus Wirth. Compilerbau. Teubner, Stuttgart, vierte edition, 1986.
[Wir96] Niklaus Wirth. Grundlagen und Techniken des Compilerbaus. Addison-Wesley, Bonn, 1996.