Implementace LL(1) překladů Ústav informatiky, FPF SU Opava sarka.vavreckova@fpf.slu.cz Poslední aktualizace: 6. ledna 2012
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) nebo alespoň všechny potřebné množiny. 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) nebo alespoň všechny potřebné množiny. 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) nebo alespoň všechny potřebné množiny. 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) nebo alespoň všechny potřebné množiny. 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) nebo alespoň všechny potřebné množiny. 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
Datové typy type TTypSymbolu = (S_ID, S_NUM, S_PLUS, S_MINUS, S_MUL, S_DIV, S_LPAR, S_RPAR, S_ENDOFFILE, S_NS, S_NA, S_NB, S_NC, S_ND, S_HASH); // terminály // neterminály TSymbol = record typ: TTypSymbolu; atrib: string; // identifikace (název) symbolu // atribut TZnak = record rad: string; // zpracovávaný řádek pozice: byte; // pozice posledního načteného znaku na řádku delka: byte; // délka tohoto řádku cislo: word; // číslo řádku
Proměnné var konec: boolean; // indikátor ukončení výpočtu, proveden accept znak: TZnak; // aktuální znak načtený ze zdroje pro funkci Lex() symbol: TSymbol; // aktuální symbol načtený z proměnné vstup vrchol_zas: TTypSymbolu; // symbol na vrcholu zásobníku zasobnik: TZasobnik; // zásobník, prvky jsou typu TTypSymbolu
expanze procedure expand(cislo_prav: integer); begin case cislo_prav of 1: begin // S AB Pridej_do_zasobniku(S_NB); Pridej_do_zasobniku(S_NA); 2: begin // A CD Pridej_do_zasobniku(S_ND); Pridej_do_zasobniku(S_NC); 3: begin // B +AB Pridej_do_zasobniku(S_NB); Pridej_do_zasobniku(S_NA); Pridej_do_zasobniku(S_PLUS);... // pro každé pravidlo gramatiky kromě epsilonových pravidel vystup(cislo_prav); // zápis čísla použitého pravidla na výstup
Ošetření chyb procedure error(const hlaska: string); // Chyba syntaxe; // vypíše číslo řádku, pozici na řádku a řetězec s daným hlášením begin Konec := true; writeln( Chyba při syntaktické analýze na řádku,znak.cislo,, sloupci,znak.pozice, :,hlaska);
Zpracování terminálů a akceptování procedure pop; begin if symbol.typ = vrchol_zas) then Lex // lexikální analyzátor načte další symbol else error( chybný symbol na vstupu - +VypisTyp(symbol.typ)); procedure accept; begin Konec := true;
Inicializace a ukončení procedure Init; begin... // inicializace vstupu a výstupu Vytvor_zasobnik; Pridej_do_zasobniku(S_HASH); // symbol konce zásobníku Pridej_do_zasobniku(S_NS); // startovací symbol gramatiky Lex; // načte symbol ze vstupu do sym Konec := false; procedure Done; begin Zlikviduj_zasobnik; // uvolní paměť zabranou zásobníkem... // uzavření vstupu a výstupu
Simulace práce s tabulkou Funkce Akce Pracuje takto: vyjme 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.
Simulace práce s tabulkou case řádek of první_řádek: case sloupec of první_sloupec: obsah_buňky_[1,1] druhý_sloupec: obsah_buňky_[1,2]... end else error(...); druhý_řádek: case sloupec of první_sloupec: obsah_buňky_[2,1] druhý_sloupec: obsah_buňky_[2,2]... end else error(...);... terminál: pop; dno_zásobníku: if konec_vstupu then accept else error(...);
Simulace práce s tabulkou procedure Akce; begin vrchol_zas := Vyjmi_ze_zasobniku; case vrchol_zas of S_NS: if (symbol.typ in [S_ID,S_NUM,S_LPAR] then expand(1) else error( chybný symbol na vstupu - +symbol.typ); S_NA: if (symbol.typ in [S_ID,S_NUM,S_LPAR] then expand(2) else error( chybný symbol na vstupu - +symbol.typ); S_NB: case symbol.typ of S_PLUS: expand(3); S_MINUS: expand(4); S_RPAR,S_ENDOFFILE: expand(5); else error( chybný symbol na vstupu - +symbol.typ);...
Simulace práce s tabulkou S_NC: case symbol.typ of S_ID: expand(7); S_NUM: expand(8); S_LPAR: expand(6); else error( chybný symbol na vstupu - +symbol.typ); S_ND: case symbol.typ of S_MUL: expand(9); S_DIV: expand(10); S_PLUS,S_MINUS,S_RPAR,S_ENDOFFILE: expand(11); else error( chybný symbol na vstupu - +symbol.typ); S_PLUS,S_MINUS,S_MUL,S_DIV,S_LPAR,S_RPAR,S_ID,S_NUM: pop; else error( chybný symbol na vstupu - +symbol.typ); S_HASH: if (symbol.typ = S_ENDOFFILE) then accept else error( chybný symbol na vstupu - +symbol.typ);
Hlavní funkce syntaktické analýzy procedure S_analyza; begin Init; while (not Konec) do Akce; Done;
Vlastnosti metody Výhody: nepoužíváme přímo rekurzi (netřeba řešit problém hloubky rekurze s prostorovou složitostí). Nevýhody: u překladů zahrnujících např. matematické výrazy se hůře implementuje sémantika, potřebujeme zásobník.
Vlastnosti metody Výhody: nepoužíváme přímo rekurzi (netřeba řešit problém hloubky rekurze s prostorovou složitostí). Nevýhody: u překladů zahrnujících např. matematické výrazy se hůře implementuje sémantika, potřebujeme zásobník.
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
Datové typy type TTypSymbolu = (S_ID, S_NUM, S_PLUS, S_MINUS, S_MUL, S_DIV, S_LPAR, S_RPAR, S_ENDOFFILE); // terminály TSymbol = record typ: TTypSymbolu; atrib: string; // identifikace (název) symbolu // atribut TZnak = record rad: string; // zpracovávaný řádek pozice: byte; // pozice posledního načteného znaku na řádku delka: byte; // délka tohoto řádku cislo: word; // číslo řádku
Proměnné var konec: boolean; // indikátor ukončení výpočtu, proveden accept znak: TZnak; // aktuální znak načtený ze zdroje pro funkci Lex() symbol: TSymbol; // aktuální symbol načtený z proměnné vstup
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; begin 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; begin Init; S; Done;
Inicializace a ukončení procedure Init; begin... // inicializace vstupu a výstupu Lex; // načte symbol ze vstupu do sym Konec := false; procedure Done; begin... // uzavření vstupu a výstupu
Ošetření chyb procedure error(const hlaska: string); // Chyba syntaxe; // vypíše číslo řádku, pozici na řádku a řetězec s daným hlášením begin Konec := true; writeln( Chyba při syntaktické analýze na řádku,znak.cislo,, sloupci,znak.pozice, :,hlaska);
Zpracování terminálů porovná zpracovávaný symbol (terminál z pravidla) se znakem na vstupu (musí souhlasit), načte další znak ze vstupu. procedure expect(terminal: TTypSymbolu); begin if symbol.typ = terminal then Lex else error( chybný symbol na vstupu - +VypisTyp(symbol.typ));
Funkce neterminálů Pro každou množinu pravidel se stejnou levou stranou: A α 1 α 2 α n procedure A; begin 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; begin 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; begin if symbol.typ in [S ID,S NUM,S LPAR] then begin A; B; end else error( chybný symbol na vstupu - +VypisTyp(symbol.typ));
Funkce neterminálů A CD procedure A; begin if symbol.typ in [S ID,S NUM,S LPAR] then begin C; D; end else error( chybný symbol na vstupu - +VypisTyp(symbol.typ));
Funkce neterminálů B +AB AB ε procedure B; begin case symbol.typ of S PLUS: begin expect(s PLUS); A; B; S MINUS: begin expect(s MINUS); A; B; S RPAR, S ENDOFFILE: ; else error( chybný symbol na vstupu - +VypisTyp(symbol.typ));
Funkce neterminálů C (S) i n procedure C; begin case symbol.typ of S LPAR: begin expect(s LPAR); S; expect(s RPAR); S ID: expect(s ID); S NUM: expect(s NUM); else error( chybný symbol na vstupu - +VypisTyp(symbol.typ));
Funkce neterminálů D CD /CD ε procedure D; begin case symbol.typ of S MUL: begin expect(s MUL); C; D; S DIV: begin expect(s DIV); C; D; S PLUS,S MINUS,S RPAR,S ENDOFFILE: ; else error( chybný symbol na vstupu - +VypisTyp(symbol.typ));
Vlastnosti metody Výhody: není nutné vytvářet rozkladovou tabulku, třebaže množiny signatur vytvořit musíme, nepotřebujeme vlastní zásobník, rekurze probíhá pouze přes vzájemné volání funkcí (procedur) s použitím systémového zásobníku, 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í.
Vlastnosti metody Výhody: není nutné vytvářet rozkladovou tabulku, třebaže množiny signatur vytvořit musíme, nepotřebujeme vlastní zásobník, rekurze probíhá pouze přes vzájemné volání funkcí (procedur) s použitím systémového zásobníku, 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í.