Po co wydziela się analizę leksykalną? Wykład7,str1 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 czasuwartowięcgooptymalizować atorobisięinaczejniż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 Uproszczony schemat kompilacji Wykład7,str2 program źródłowy ciąg leksemów drzewo wywodu drzewo i tablice symboli analiza leksykalna analiza syntaktyczna analiza semantyczna KOMPILATOR generacja kodu dane do programu kod docelowy wyniki
Uproszczony schemat kompilacji Wykład7,str3 ciąg leksemów drzewo wywodu analiza syntaktyczna KOMPILATOR Różne metody analizy syntaktycznej Wykład7,str4 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
Koszty Wykład7,str5 Do każdej jednoznacznej gramatyki bezkontekstowej można zbudowaćparser,działającywczasieo(n 3 ),gdzienjestdł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) Rekursywny parser zstępujący Wykład7,str6 Analiza top-down Po jednej funkcji rekursywnej dla każdego nieterminala Funkcja dla nieterminalu X wywoływana jest po to, żeby skonstruować drzewo wywodu początkowych leksemów z wejścia, mające w korzeniu nieterminal X ; żeby móc zadecydować, której produkcji z nieterminalu X 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
Rekursywny parser zstępujący Wykład7,str7 Boolean B(drzewo* drz) { drzewo drz1; if(nowyleks( w )) if(b(&drz1)){ *drz=; return true; else { *drz=; return true; else return false; Boolean A(drzewo* drz); Boolean B(drzewo* drz); B ::=w B w w B B drz1 B w Rekursywny parser zstępujący Wykład7,str8 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; Boolean A(drzewo* drz); Boolean B(drzewo* drz); A ::=y A B z x y A x A A B drz1 drz2 z
Wykład7,str9 Przekształcenie gramatyki dla parsera zstępującego Zmiana kolejności produkcji i wyłączenie przed nawias : W ::= S S + W S - W S ::= C C * S C / S C ::= L ( W ) L ::=0 1 0 L 1 L W ::= S { + W - W λ S ::= C { * S / S λ C ::=( W ) L L ::=0 { L λ 1 { L λ Programowanie parsera zstępującego Wykład7,str10 W ::= S S + W S - W W ::= S { + W - W λ Booleanf W (drzewo*t) { if(f S (&t 1 )) if(nowyleks( +,&nleks) nowyleks( -,&nleks)) { if(f W (&t 2 )) {*t= W S W ; returntrue; t 1 nleks t2 else blad("po+lub-niema W "); else {*t= W S t 1 else return FALSE; ; returntrue; Uwaga: To jeszcze nie jest dokładnie to, co trzeba Patrz dalej
Parser zstępujący problemy Wykład7,str11 Lewostronna rekursja w gramatyce powoduje nieskończone obliczenie Przykład: innagramatyka: W ::= S W + S M BooleanfW (drzewo*t) { if(f S (&t 1 )) { *t= W S ; return TRUE; else t 1 if(f W (&t 1 )) { if(nowyleks( +,&nleks)) if(f S (&t 2 )) { *t= W W S ; returntrue; t 1 + t2 else blad("po + brakuje <S>"); else return FALSE; else return FALSE; Leczenie lewostronnej rekursji Wykład7,str12 Problem: produkcje gramatyczne postaci N ::= N (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 liczba ::= cyfra cyfra liczba 345=345=3 10 2 +45 ciąg cyfr grupowany do prawej(nie ma problemu) liczba ::= cyfra liczba cyfra ciąg cyfr grupowany do lewej(jest problem) 345=345=34 10+5
Leczenie lewostronnej rekursji Wykład7,str13 Wyjście: do gramatyk wprowadzić specjalną konstrukcję oznaczającą ciąggrupowanydolewej : Przykład: MŻeby wyrazić liczba to niepusty ciąg cyfr grupowany do lewej zamiast piszemy liczba ::= cyfra liczba cyfra liczba ::= cyfra cyfra liczba cyfra Leczenie lewostronnej rekursji Wykład7,str14 Wyjście: do gramatyk wprowadzić specjalną konstrukcję oznaczającą ciąggrupowanydolewej : Przykład: MŻeby wyrazić wyrażenie to niepusty ciąg składników porozdzielanych plusami i minusami grupowany do lewej zamiast wyrażenie ::= składnik wyrażenie + składnik wyrażenie składnik piszemy wyrażenie ::= składnik {{ + składnik wyrażenie składnik +
Leczenie lewostronnej rekursji Wykład7,str15 Gramatyka oryginalna z lewostronną rekursją: Gramatyka wyleczona ziteracją: W ::= S W + S S W - S S ::= C S * C C S / C C ::= L ( W ) L ::=0 1 L 0 L 1 W ::= S {{ + S S ::= C {{ / C C ::=( W ) L L ::= { 0 1 { 0 1