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 ∗