kompilator

Transkrypt

kompilator
Wykład 7, str. 1
Po co wydziela się analizę leksykalną?
Włączenie analizy leksykalnej do analizy składniowej jest nietrudne; po co
więc jest wydzielona?
1. Analiza leksykalna jest prostsza niż składniowa
leksyka
syntaktyka
gramatyka prawoliniowa bezkontekstowa
akceptor
automat
maszyna stosowa
2. Analizator leksykalny czyta tekst, więc jego działanie zajmuje dużo
czasu. Warto więc go optymalizować — a to robi się inaczej niż w
składniowym.
3. Czytanie tekstu jest zależne od platformy, analiza składniowa jest niezależna. Rozdzielenie ich poprawia więc przenośność (portability ) kompilatorów.
Wykład 7, str. 2
Uproszczony schemat kompilacji
program
źródłowy
✕
analiza
leksykalna
ciąg
leksemów
✕
drzewo
wywodu
analiza
syntaktyczna
analiza
semantyczna
KOMPILATOR
generacja
kodu
✕
drzewo i
tablice
symboli
✯
kod
qqqqqqqqqq
qqqqqqqq
qqq
docelowy
qq qqq
qqq
qqq
qqq
qqq
qq
q
dane do
wyniki
programu
Wykład 7, str. 3
Uproszczony schemat kompilacji
ciąg
leksemów
✕
drzewo
wywodu
analiza
syntaktyczna
KOMPILATOR
Wykład 7, str. 4
Różne metody analizy syntaktycznej
Rozbiór słowa
— budowanie jego drzewa wywodu w danej gramatyce bezkontekstowej.
Synonimy: rozbiór = analiza syntaktyczna = parsing.
Parser
— program (procedura, podprogram) dokonujący rozbioru.
Budowy drzewa wywodu można dokonywać
• albo od strony korzenia (rozbiór zstępujący ),
• albo od strony liści (rozbiór wstępujący ).
Jest wiele metod rozbioru danego słowa; różnią się
• zakresem stosowalności (do jakich rodzajów gramatyk bezkontekstowych się nadają),
• oraz efektywnością (jak szybko działają i ile potrzebują pamięci).
Programy do automatycznego tworzenia parserów potrafią na ogół dobrać
optymalny (albo przynajmniej niezły) parser do danej gramatyki.
Wykład 7, str. 5
Koszty
• Do każdej jednoznacznej gramatyki bezkontekstowej można zbudować parser, działający w czasie O(n3 ), gdzie n jest długością ciągu
terminali na wejściu.
• Ta złożoność wynika z działania przez próbowanie i wycofywanie się
w przypadku złego dopasowania; drzewo wywodu jest wielokrotnie budowane i niszczone.
• Niezbyt bolesne ograniczenia na gramatyki umożliwiają istnienie parsera, działającego w czasie O(n), czyli znacznie szybciej.
• Wszystkie praktycznie stosowane parsery działają w czasie O(n).
Wykład 7, str. 6
Rekursywny parser zstępujący
• Analiza top-down.
• Po jednej funkcji rekursywnej dla każdego nieterminala. Funkcja dla
nieterminalu hXi
– wywoływana jest po to, żeby skonstruować drzewo wywodu początkowych leksemów z wejścia, mające w korzeniu nieterminal hXi;
– żeby móc zadecydować, której produkcji z nieterminalu hXi użyć,
jest wywoływana w sytuacji, w której znany (wczytany) jest już
pierwszy liść (leksem) tego drzewa; kończy działanie w sytuacji,
kiedy znany jest już pierwszy leksem spoza korony tego drzewa;
– może zasygnalizować błąd i program ją wywołujący stara się znaleźć inną produkcję do zastosowania.
Wykład 7, str. 7
Rekursywny parser zstępujący
Boolean
Boolean
A(drzewo* drz);
B(drzewo* drz);
hBi ::= whBi w
hBi
hBi
drz1
Boolean B(drzewo* drz) {
drzewo drz1;
if (nowyleks(’w’))
if (B(&drz1)){
*drz = ...;
return true;
}
else {
*drz = ...;
return true;
}
else return false;
}
w
hBi
w
Wykład 7, str. 8
Rekursywny parser zstępujący
Boolean
A(drzewo* drz);
Boolean
B(drzewo* drz);
hAi ::= yhAihBiz x
hAi
y
hAi
x
hAi
hBi
drz2
............................................................
.......
..........
.....
.......
.
.
.
.
....
...
...
...
.
...
.
.
...
.
.
...
...
..
..
..
..
.
..
.
drz1
Boolean A(drzewo* drz) {
drzewo drz1, drz2;
if (nowyleks(’y’))
if (A(&drz1))
if (B(&drz2))
if (nowyleks(’z’)) {
*drz = ...;
return true;
}
else blad("brakuje terminalu z");
else blad("brakuje nieterminalu <B>");
else blad("brakuje nieterminalu <A>");
else
if (nowyleks(’x’)) {
*drz = ...;
return true;
}
else return false;
}
z
Wykład 7, str. 9
Przekształcenie gramatyki dla parsera zstępującego
Zmiana kolejności produkcji i „wyłączenie przed nawias”:
n
o
hWi ::= hSi + hWi - hWi λ
hSi + hWi
hWi ::= hSi
hSi - hWi
hCi * hSi
hSi ::= hCi
hCi / hSi
hCi ::= hLi ( hWi )
hLi ::= 0 1 0 hLi 1 hLi
n
o
hSi ::= hCi * hSi / hSi λ
hCi ::= ( hWi ) hLi
n
o hLi ::= 0 hLi λ n
o
1 hLi λ
Wykład 7, str. 10
Programowanie parsera zstępującego
hWi ::= hSi + hWi - hWi λ
hWi ::= hSi hSi + hWi hSi - hWi
Boolean fW (drzewo* t) {
if (fS (&t1 ))
if (nowyleks(’+’,&nleks) || nowyleks(’-’,&nleks)) {
if (fW (&t2 )) { *t =
hWi
hSi
t1
else
hWi
hSi
;
t1
}
hWi
return TRUE; }
t2
blad("po + lub - nie ma hWi");
else { *t =
else
nleks
;
return FALSE;
ost d j.
e
j
nie z dale
e
z
c
atr
sz
o j e r z e ba . P
T
:
aga o, co t
w
t
U
nie
kład
return TRUE; }
Wykład 7, str. 11
Parser zstępujący — problemy
Lewostronna rekursja w gramatyce powoduje nieskończone obliczenie
Przykład: inna gramatyka:
hWi ::= hSi hWi+hSi
M
Boolean fW (drzewo* t) {
if (fS (&t1 )) {
hWi
*t =
;
return TRUE;
}
else
hSi
t1
if (fW (&t1 )) {
if (nowyleks(’+’,&nleks))
if (fS (&t2 )) {
hWi
*t =
hWi
;
return TRUE;
}
hSi
+
t1
t2
else blad("po + brakuje <S>");
else return FALSE;
else return FALSE;
}
Wykład 7, str. 12
Leczenie lewostronnej rekursji
Problem: produkcje gramatyczne postaci
hNi ::= . . . hNi . . . . . .
(czyli lewostronnie rekursywne) prowadzą do zapętlenia parsera zstępującego. Takie produkcje potrzebne są dla często spotykanej w składni języków
programowania konstrukcji „ciąg . . . grupowany do lewej”.
Przykład:
M
hliczbai ::= hcyfrai hcyfrai hliczbai
— ciąg cyfr grupowany do prawej (nie ma problemu)
345 = 3x45y = 3 · 102 + 45
hliczbai ::= hcyfrai hliczbai hcyfrai
— ciąg cyfr grupowany do lewej (jest problem)
345 = x34y5 = 34 · 10 + 5
Wykład 7, str. 13
Leczenie lewostronnej rekursji
Wyjście: do gramatyk wprowadzić specjalną konstrukcję oznaczającą
„ciąg . . . grupowany do lewej”:
Przykład:
M
Żeby wyrazić „liczba to niepusty ciąg cyfr grupowany do lewej” —
zamiast
hliczbai ::= hcyfrai hliczbai hcyfrai
piszemy
hliczbai ::= hcyfrai hcyfrai∗
liczba
✲ cyfra
✲
✛
Wykład 7, str. 14
Leczenie lewostronnej rekursji
Wyjście: do gramatyk wprowadzić specjalną konstrukcję oznaczającą
„ciąg . . . grupowany do lewej”:
Przykład:
M
Żeby wyrazić „wyrażenie to niepusty ciąg składników porozdzielanych plusami i minusami grupowany do lewej” —
hwyrażeniei + hskładniki
zamiast hwyrażeniei ::= hskładniki
hwyrażeniei − hskładniki
nn
o
o
piszemy hwyrażeniei ::= hskładniki + − hskładniki ∗
wyrażenie
✲ składnik
+ ✛
− ✛
✲
Wykład 7, str. 15
Leczenie lewostronnej rekursji
Gramatyka oryginalna
z lewostronną rekursją:
hWi ::= hSi
hSi
hSi ::= hCi
hCi
Gramatyka wyleczona
z iteracją:
hWi + hSi
hWi - hSi
hWi ::= hSi
nn
hSi * hCi
hSi / hCi
hSi ::= hCi
nn
hCi ::= hLi
( hWi )
hLi ::= 0
1
hLi 0 hLi 1
o o
+ − hSi ∗
o
o
∗ / hCi ∗
hCi ::= ( hWi ) hLi
n on o
hLi ::= 0 1 0 1 ∗

Podobne dokumenty