Parser und Compiler

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