Překladače, přednáška č. 6 Ústav informatiky, FPF SU Opava sarka.vavreckova@fpf.slu.cz Poslední aktualizace: 30. října 2007
Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku popisující strukturu jazyka. 2 Vytvoříme podle ní překladový automat (rozkladovou tabulku). 3 Naprogramujeme: metodou přepisu rozkladové tabulky, metodou rekurzivního sestupu.
Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku popisující strukturu jazyka. 2 Vytvoříme podle ní překladový automat (rozkladovou tabulku). 3 Naprogramujeme: metodou přepisu rozkladové tabulky, metodou rekurzivního sestupu.
Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku popisující strukturu jazyka. 2 Vytvoříme podle ní překladový automat (rozkladovou tabulku). 3 Naprogramujeme: metodou přepisu rozkladové tabulky, metodou rekurzivního sestupu.
Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku popisující strukturu jazyka. 2 Vytvoříme podle ní překladový automat (rozkladovou tabulku). 3 Naprogramujeme: metodou přepisu rozkladové tabulky, metodou rekurzivního sestupu.
Postup Programujeme syntaktickou analýzu: 1 Navrhneme vhodnou LL(1) gramatiku popisující strukturu jazyka. 2 Vytvoříme podle ní překladový automat (rozkladovou tabulku). 3 Naprogramujeme: metodou přepisu rozkladové tabulky, metodou rekurzivního sestupu.
Přepis rozkladové tabulky Potřebujeme rozkladovou tabulku, zásobník na ukládání symbolů, proměnnou, ve které je uložen právě zpracovávaný symbol, funkci lex(), která nám vrátí další symbol, který extrahovala ze vstupního souboru (uloží do proměnné z předchozího bodu), proměnnou pro výstup (soubor, dynamická struktura apod.).
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), 2 zjistíme, o jaký symbol jde, přiřadíme terminál, 3 provedeme analýzu symbolu, zařadíme do derivačního stromu, při chybě nebo akceptování celého vstupu končíme výpočet, 4 pokud je hodnota symbolu potřebná (například identifikátor nebo číslo), uložíme ji, 5 návrat k prvnímu bodu.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), 2 zjistíme, o jaký symbol jde, přiřadíme terminál, 3 provedeme analýzu symbolu, zařadíme do derivačního stromu, při chybě nebo akceptování celého vstupu končíme výpočet, 4 pokud je hodnota symbolu potřebná (například identifikátor nebo číslo), uložíme ji, 5 návrat k prvnímu bodu.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), 2 zjistíme, o jaký symbol jde, přiřadíme terminál, 3 provedeme analýzu symbolu, zařadíme do derivačního stromu, při chybě nebo akceptování celého vstupu končíme výpočet, 4 pokud je hodnota symbolu potřebná (například identifikátor nebo číslo), uložíme ji, 5 návrat k prvnímu bodu.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), 2 zjistíme, o jaký symbol jde, přiřadíme terminál, 3 provedeme analýzu symbolu, zařadíme do derivačního stromu, při chybě nebo akceptování celého vstupu končíme výpočet, 4 pokud je hodnota symbolu potřebná (například identifikátor nebo číslo), uložíme ji, 5 návrat k prvnímu bodu.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), 2 zjistíme, o jaký symbol jde, přiřadíme terminál, 3 provedeme analýzu symbolu, zařadíme do derivačního stromu, při chybě nebo akceptování celého vstupu končíme výpočet, 4 pokud je hodnota symbolu potřebná (například identifikátor nebo číslo), uložíme ji, 5 návrat k prvnímu bodu.
Popis metody Budeme potřebovat tyto funkce: expand(číslo pravidla) uloží pravou stranu pravidla s daným číslem do zásobníku a na výstup přidá číslo pravidla, pop ověří shodnost symbolu na vstupu se symbolem vyjmutým ze zásobníku a načte další symbol ze vstupu, accept při konci vstupu a konci zásobníku ukončí výpočet programu, error ošetří chybu, která se vyskytla při překladu, Akce je hlavní řídicí funkce, v cyklu volá předchozí, zajišťuje pohyb v tabulce, Init je inicializační funkce (inicializuje zásobník, zajistí přednačtení prvního symbolu apod.), úklidová funkce je Done.
Ukážeme na příkladu: S AB A CD B +AB AB ε C (S) i n D CD /CD ε 1 2 3, 4, 5 6, 7, 8 9, 10, 11 Rozkladová tabulka i n + / ( ) $ S e1 e1 e1 A e2 e2 e2 B e3 e4 e5 e5 C e7 e8 e6 D e11 e11 e9 e10 e11 e11
Ukážeme na příkladu: S AB A CD B +AB AB ε C (S) i n D CD /CD ε 1 2 3, 4, 5 6, 7, 8 9, 10, 11 Rozkladová tabulka i n + / ( ) $ S e1 e1 e1 A e2 e2 e2 B e3 e4 e5 e5 C e7 e8 e6 D e11 e11 e9 e10 e11 e11
Proměnné vystup soub: string; (* název výstupního souboru *) vystup: text: (* soubor pro výstup programu *) konec: boolean; (* indikátor konce výpočtu, proveden accept*) vstupni sym: char; (* aktuální symbol načtený z prom. vstup *) vstupni atr: TAtribut; (* atribut právě načteného vstup. symbolu *) vrchol zasob:char; (* symbol na vrcholu zásobníku *) Dále předpokládáme funkce a datové struktury pro načtení znaku a práci se zásobníkem.
Proměnné vystup soub: string; (* název výstupního souboru *) vystup: text: (* soubor pro výstup programu *) konec: boolean; (* indikátor konce výpočtu, proveden accept*) vstupni sym: char; (* aktuální symbol načtený z prom. vstup *) vstupni atr: TAtribut; (* atribut právě načteného vstup. symbolu *) vrchol zasob:char; (* symbol na vrcholu zásobníku *) Dále předpokládáme funkce a datové struktury pro načtení znaku a práci se zásobníkem.
Hlavní funkce syntaktické analýzy Úkol: inicializovat výpočet, v cyklu volat funkci Akce pracující s tabulkou, ukončit výpočet. procedure S analyza; Init; while (not Konec) do Akce; Done;
Hlavní funkce syntaktické analýzy Úkol: inicializovat výpočet, v cyklu volat funkci Akce pracující s tabulkou, ukončit výpočet. procedure S analyza; Init; while (not Konec) do Akce; Done;
Init, Done procedure Init;... otevře vstupní soubor Vytvor zasobnik; Pridej do zasobniku( # ); Pridej do zasobniku( S ); pop; (* načte symbol ze vstupu do vstupni sym *) Konec := false; procedure Done; Zlikviduj zasobnik; (* uvolní paměť zásobníku *)... zavře soubory, atd.
Init, Done procedure Init;... otevře vstupní soubor Vytvor zasobnik; Pridej do zasobniku( # ); Pridej do zasobniku( S ); pop; (* načte symbol ze vstupu do vstupni sym *) Konec := false; procedure Done; Zlikviduj zasobnik; (* uvolní paměť zásobníku *)... zavře soubory, atd.
accept procedure accept; (* Jsme na konci vstupního souboru, syntaktická analýza správně ukončena. *) Konec := true;
pop porovná terminál ze zásobníku se vstupem (musí být stejné), posune se na vstupu (funkce Lex vrací další symbol ze vstupního souboru). procedure pop; if vstupni sym = vrchol zasob then Lex(vstupni sym, vstupni atr) else error;
pop porovná terminál ze zásobníku se vstupem (musí být stejné), posune se na vstupu (funkce Lex vrací další symbol ze vstupního souboru). procedure pop; if vstupni sym = vrchol zasob then Lex(vstupni sym, vstupni atr) else error;
expand procedure expand(cislo prav); case cislo prav of 1: (* S -> AB *) Pridej do zasobniku( B ); Pridej do zasobniku( A ); 2: (* A -> CD *) Pridej do zasobniku( D ); Pridej do zasobniku( C ); 3: (* B -> +AB *) Pridej do zasobniku( B ); Pridej do zasobniku( A ); Pridej do zasobniku( + ); 4: (* B -> -AB *) Pridej do zasobniku( B ); Pridej do zasobniku( A ); Pridej do zasobniku( - );... writeln(vystup, cislo prav);
error procedure error; Konec := true; writeln( Chyba při syntaktické analýze,... );... ošetření chyby
Akce Funkce Akce Pracuje takto: vyndá ze zásobníku jeden symbol, tím určí řádek tabulky a podle symbolu na vstupu určí sloupec tabulky, podle obsahu buňky na daném řádku a sloupci zavolá funkci expand, pop, accept nebo error (prázdná buňka znamená error), je volána v cyklu tak dlouho, dokud není konec zpracovávaného programu.
Akce procedure Akce; vrchol zasob := Vyjmi ze zasobniku; case vrchol zasob of... pro S, A B : case vstupni sym of (* řádek B *) + : expand(3); (* sloupec + *) - : expand(4); (* sloupec - *) ), $ : expand(5); (* sloupce ),$ *) else error; (* ostatní sloupce *)... pro B, C, D # : if (vstupni sym = $ ) then accept else error; else if (vrchol zasob in terminaly) then pop else error;
metody Výhody: nepoužíváme přímo rekurzi (netřeba řešit problém hloubky rekurze s prostorovou složitostí), lze využít rozkladovou tabulku. Nevýhody: hůře se implementuje sémantika.
metody Výhody: nepoužíváme přímo rekurzi (netřeba řešit problém hloubky rekurze s prostorovou složitostí), lze využít rozkladovou tabulku. Nevýhody: hůře se implementuje sémantika.
Rekurzívní sestup Potřebujeme LL(1) gramatiku (nemusíme dělat rozkladovou tabulku), množiny F IRST a F OLLOW, pro každé pravidlo A α vytvoříme množinu signatur F S(A, α) = F IRST (α F OLLOW (A)) proměnnou, ve které je uložen právě zpracovávaný symbol, funkci lex(), která nám vrátí další symbol, který extrahovala ze vstupního souboru (uloží do proměnné z předchozího bodu), proměnnou pro výstup (soubor, dynamická struktura apod.).
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Analýza probíhá takto: 1 zavoláme funkci Lex(), pak opět voláme pro každý symbol, 2 postupujeme přesně tak, jakobychom konstruovali derivační strom ručně : když jsme v uzlu ohodnoceném neterminálem, vytvoříme poduzly podle zvoleného pravidla, tentýž postup rekurzívně uplatníme na všechny poduzly (zleva doprava), které jsou ohodnoceny neterminály, u terminálních poduzlů pouze spustíme kontrolní porovnání podobně, jako bylo u předchozí metody pop. 3 rekurzívní volání probíhá zleva doprava a shora dolů, tedy i vstup je čten zleva doprava, 4 když skončí všechny rekurzivní výpočty pro jednotlivé větve a vstup je celý přečtený ($), akceptujeme vstup.
Popis metody Budeme potřebovat tyto funkce: Init, Done, expect ověří shodnost symbolu na vstupu se symbolem, který je parametrem této funkce, a načte další symbol ze vstupu, S, A, B,... pro každý neterminál vytvoříme stejně nazvanou funkci, tyto funkce se budou navzájem rekurzívně volat, error ošetří chybu, která se vyskytla při překladu.
Ukážeme na příkladu: S AB A CD B +AB AB ε C (S) i n D CD /CD ε 1 2 3, 4, 5 6, 7, 8 9, 10, 11
Proměnné vystup soub: string; (* název výstupního souboru *) vystup: text: (* soubor pro výstup programu *) vstupni sym: char; (* aktuální symbol načtený z prom. vstup *) vstupni atr: TAtribut; (* atribut právě načteného vstup. symbolu *) Dále předpokládáme funkce pro načtení znaku ze vstupu.
Proměnné vystup soub: string; (* název výstupního souboru *) vystup: text: (* soubor pro výstup programu *) vstupni sym: char; (* aktuální symbol načtený z prom. vstup *) vstupni atr: TAtribut; (* atribut právě načteného vstup. symbolu *) Dále předpokládáme funkce pro načtení znaku ze vstupu.
Hlavní funkce syntaktické analýzy Úkol: inicializovat výpočet, zavolat funkci S, dále je vše voláno rekurzí, ukončit výpočet. procedure S analyza; Init; S; Done;
Hlavní funkce syntaktické analýzy Úkol: inicializovat výpočet, zavolat funkci S, dále je vše voláno rekurzí, ukončit výpočet. procedure S analyza; Init; S; Done;
Init, Done procedure Init;... otevře vstupní soubor Lex(vstupni sym, vstupni atr); procedure Done;... zavře soubory, atd.
Init, Done procedure Init;... otevře vstupní soubor Lex(vstupni sym, vstupni atr); procedure Done;... zavře soubory, atd.
expect porovná zpracovávaný symbol (terminál z pravidla) se znakem na vstupu (musí souhlasit), načte další znak ze vstupu. procedure expect(term: char); if term = vstupni sym then Lex(vstupni sym, vstupni atr) else chyba;
expect porovná zpracovávaný symbol (terminál z pravidla) se znakem na vstupu (musí souhlasit), načte další znak ze vstupu. procedure expect(term: char); if term = vstupni sym then Lex(vstupni sym, vstupni atr) else chyba;
Funkce neterminálů Pro každou množinu pravidel se stejnou levou stranou: A α 1 α 2 α n procedure A; if vstupni sym in FS(A,α 1 ) then... postupně jsou ošetřeny symboly z řetězce α 1 else if vstupni sym in FS(A,α 2 ) then... postupně jsou ošetřeny symboly z řetězce α 2 else... ostatní pravidla else error;
Funkce neterminálů Pro každou množinu pravidel se stejnou levou stranou: A α 1 α 2 α n procedure A; if vstupni sym in FS(A,α 1 ) then... postupně jsou ošetřeny symboly z řetězce α 1 else if vstupni sym in FS(A,α 2 ) then... postupně jsou ošetřeny symboly z řetězce α 2 else... ostatní pravidla else error;
Funkce neterminálů S AB procedure S; if vstupni sym in ( i, n, ( ) then A; B; end else error;
Funkce neterminálů S AB procedure S; if vstupni sym in ( i, n, ( ) then A; B; end else error;
Funkce neterminálů A CD procedure A; if vstupni sym in ( i, n, ( ) then C; D; end else error;
Funkce neterminálů A CD procedure A; if vstupni sym in ( i, n, ( ) then C; D; end else error;
Funkce neterminálů B +AB AB ε procedure B; if vstupni sym = + then expect( + ); A; B; end else if vstupni sym = - then expect( - ); A; B; end else if vstupni sym in ( ),$) then ; else error;
Funkce neterminálů B +AB AB ε procedure B; if vstupni sym = + then expect( + ); A; B; end else if vstupni sym = - then expect( - ); A; B; end else if vstupni sym in ( ),$) then ; else error;
Funkce neterminálů C (S) i n procedure C; if vstupni sym = ( then expect( ( ); S; expect( ) ); end else if vstupni sym = i then expect( i ) else if vstupni sym = n then expect( n ) else error;
Funkce neterminálů C (S) i n procedure C; if vstupni sym = ( then expect( ( ); S; expect( ) ); end else if vstupni sym = i then expect( i ) else if vstupni sym = n then expect( n ) else error;
Funkce neterminálů D CD /CD ε procedure D; if vstupni sym = * then expect( * ); C; D; end else if vstupni sym = / then expect( - ); C; D; end else if vstupni sym in ( +, -, ),$) then ; else error;
Funkce neterminálů D CD /CD ε procedure D; if vstupni sym = * then expect( * ); C; D; end else if vstupni sym = / then expect( - ); C; D; end else if vstupni sym in ( +, -, ),$) then ; else error;
metody Výhody: není nutné vytvářet rozkladovou tabulku, třebaže množiny signatur vytvořit musíme, není problém s navázáním sémantické analýzy. Nevýhody: hloubka rekurze může za určitých okolností působit problémy s prostorovou složitostí.
metody Výhody: není nutné vytvářet rozkladovou tabulku, třebaže množiny signatur vytvořit musíme, není problém s navázáním sémantické analýzy. Nevýhody: hloubka rekurze může za určitých okolností působit problémy s prostorovou složitostí.