Ciele
- Definovať zásobníkový automat ako ekvivalentný spôsob určenia bezkontextového jazyka.
- Uviesť základné pojmy analýzy bezkontextových gramatík. Množiny $\mathit{FIRST},$ $\mathit{FOLLOW},$ $\mathit{PREDICT}$ a definovať $LL(1)$ gramatiku.
- Predstaviť princíp nerekurzívnej syntaktickej analýzy zhora nadol postavenej na zásobníkovom automate.
- Implementovať syntaktický analyzátor jednoduchého jazyka aritmetických výrazov na báze zásobníkového automatu.
Úvod
V predchádzajúcich cičeniach sme venovali základom bezkontextových gramatík a metódam syntaktickej analýzy. V tomto cvičení si rozšírime vedomosti o zásobníkové automaty, ktoré predstavujú formálny model schopný rozpoznať všetky bezkontextové jazyky. Zásobníkové automaty preto v tomto zmysle predsatvujú plnohodnotnou alternatívou k bezkontextovým gramatikám pri definovaní jazykov tohto typu.
Následne sa zameriame na kľúčové pojmy súvisiace s analýzou bezkontextových gramatík, predovšetkým na množiny $\mathit{FIRST}$, $\mathit{FOLLOW}$ a definíciu $LL(1)$ gramatiky, ktoré zohrávajú kľúčovú úlohu pri tvorbe deterministických parsovacích algoritmov.
Osobitnú pozornosť budeme venovať princípom nerekurzívnej syntaktickej analýzy zhora nadol. Táto metóda využíva zásobník na riadenie parsovacieho procesu namiesto rekurzívnych volaní, čo z nej robí efektívnu a praktickú metódu pre implementáciu syntaktických analyzátorov. V závere kapitoly si ukážeme implementáciu jednoduchého nerekurzívneho syntaktického analyzátora zhora nadol, ktorý bude schopný spracovať vstupný reťazec podľa LL(1) gramatiky.
Postup
Krok 1
Zásobníkové automaty (ZA) zohrávajú pri akceptácii bezkontextových jazykov rovnakú úlohu ako konečné automaty pri akceptácii regulárnych jazykov. To znamená, že každý jazyk, pre ktorý existuje bezkontextová gramatika, ktorá ho generuje, je akceptovateľný nejakým ZA a naopak, každý jazyk akceptovaný nejakým ZA je zároveň generovateľný nejakou bezkontextovou gramatikou.
Schému ZA prezentuje nasledujúci Obrázok 1.
Zásobníkový automat (angl. pushdown automata) je usporiadana sedmica: $$M=(Q,T,\Gamma,\delta,q_0,Z_0,F),$$ kde:
- $Q$ je konečná množina stavov automatu,
- $T$ je konečná množina prípustných vstupných symbolov (alebo vstupná abeceda) automatu,
- $\Gamma$ je konečná množina zásobníkových symbolov (tzv. zásobníková abeceda),
- $\delta$ je prechodová funkcia zásobníkového automatu, $\delta: Q \times T_\varepsilon\times\Gamma_\varepsilon \to 2^{Q\times\Gamma^*}$,
- $q_0$ je počiatočný alebo iniciálny stav automatu, $q_0 \in Q$,
- $Z_0$ je zásobníkový symbol nachádzajajúci sa na začiatku každého výpočtu na dne zásobníka, $Z_0 \in \Gamma$,
- $F$ je množina akceptujúcich (alebo koncových) stavov automatu, $F\subseteq Q$.
Poznámka
Na základe vyššie uvedenej definície je zrejmé, že ZA predstavuje rozšírenie NKA o pamäťový prvok – zásobník.
Poznámka
Nad zásobníkom možno vykonávať dve základné operácie, a to:
- $\mathit{POP}()$, ktorá odstráni jeden symbol z vrchola zásobníka a zároveň ho poskytne ako návratovú hodnotu,
- $\mathit{PUSH}(s)$, ktorá postupne pridá na vrchol zásobníka symboly slova $s\in \Gamma^*$ (zľava doprava).
Všimnime si, že špecifikácia prechodovej funkcie $\delta$ zodpovedá nedeterministickému ZA. Uprednostnenie tohto variantu vyplýva z jeho univerzálnosti a schopnosti popísať taktiež deterministický variant ZA jednoduchým obmedzením kodomény jeho prechodovej funkcie $\delta$.
Konfigurácia zásobníkového automatu $M=(Q,T,\Gamma,\delta,q_0,Z_0,F)$ je usporiadaná trojica $(q, w, s)\in Q\times T^*\times \Gamma^*$, kde $q$ je aktuálny stav automatu, $w$ je ešte nespracovaná časť vstupu a $s$ je obsah zásobníka.
- Začiatočná konfigurácia automatu $M$ je konfigurácia $ (q_0, w, Z_0) $, kde $w$ je celý vstupný reťazec.
- Koncová (alebo akceptujúca) konfigurácia automatu $M$ je konfigurácia $ (q, \varepsilon, s) $ kde $ q \in F $ a $s \in \Gamma^*$.
Na množine konfigurácií $Q \times T^* \times \Gamma^* $ definujeme binárnu reláciu prechodu (alebo krok výpočtu) ZA $\vdash\;\subseteq (Q \times T^* \times \Gamma^*)^2$ nasledovne. Nech $q, p \in Q, w \in T^*, x \in T_\varepsilon, Z \in \Gamma, s,t \in \Gamma^*$. Potom
$(q, xw, Zs) \vdash (p, w, ts)$ vtedy a len vtedy, keď $(p,t) \in \delta(q, x, Z).$
Postupnosť (sekvencia) konfigurácií $ (q_0, w_0, s_0), (q_1, w_1, s_1), \ldots, (q_k, w_k, s_k) $, pre $ k \in \mathbb{N}$ takých, že $$ (q_0, w_0, s_0) \vdash (q_1, w_1, s_1) \vdash \dots \vdash (q_k, w_k, s_k), $$ sa nazýva výpočet ZA $M$ z $(q_0, w_0, s_0)$ do $(q_k, w_k, s_k)$ a vyjadrovať ho budeme v tvare $$ (q_0, w_0, s_0) \vdash^k (q_k, w_k, s_k). $$
Poznámka
Číslo $k$ predstavuje počet krokov výpočtu.
Pri zásobníkových automatoch existujú dva spôsoby akceptácie reťazcov. Slovo môže byť akceptované:
- akceptačným stavom – automat prečíta celý vstup a ukončí výpočet v akceptačnom stave, obsah zásobníka je v tomto prípade irelevantný, alebo
- prázdnym zásobníkom – automat prečíta celý vstup a ukončí výpočet s prázdnym zásobníkom, stav je v tomto prípade irelevantný.
Oba spôsoby akceptácie slov sú ekvivalentné. Vždy sa dá automat pracujúci jedným spôsobom previesť na automat pracujúci druhým spôsobom a naopak.
Jazyk $L(M)$ rozpoznávaný (akceptovaný) zásobníkovým automatom $M$ je teda množina:
$L(M) = \{ w \mid (q_0, w, Z_0) \vdash^* (q, \varepsilon, s) \land w \in T^*,$ $s \in \Gamma^* \land (q \in F \lor s=\varepsilon) \}.$
Príklad
Navrhnite zásobníkový automat $M$ akceptujúci jazyk $L = \{ a^{n}b^{n}~|~n \geq 1\}$, $n \in \mathbb{N}$. Na automate $M$ vykonajte výpočty nad slovami $aabb$, $aab$.
Riešenie
V 4. cvičení sme dokázali, že tento jazyk nie je regulárny, preto neexistuje KSA, ktorý by ho akceptoval. Problém spočíva v tom, že pri postupnom čítaní reťazca musíme zabezpečiť taktiež počítanie výskytov jeho jednotlivých symbolov. Keďže jediným kvázi-pamäťovým prvkom KSA je jeho vnútorny stav, túto informáciu nemáme ako zaznamenať. V prípade ZA však máme k dispozícii skutočnú pamäťovú jednotku – zásobník, nad ktorým možeme vykonávať základné operácie $\mathit{PUSH}$ a $\mathit{POP}$. Uveďme teda princíp fungovania ZA akceptujúceho jazyk $L$.
- Vždy keď zo vstupu čítame symbol $a$ na vrchol zásobníka sa pridá nejaký (konkrétny) symbol zásobníkovej abecedy.
- Po prečítaní prvého symbolu $b$ zo vstupu automat zmení svoj stav a vykoná operáciu $\mathit{POP}$ nad zásobníkom.
- Po prečítaní každého ďalšieho symbolu $b$ zo vstupu automat vykoná operáciu $\mathit{POP}$ nad zásobníkom.
- Ak po prečítaní celého reťazca zo vstupu v zásobníku ostane len počiatočný zásobníkový symbol, automat zmeni svoj stav na akceptujúci.
Výsledný automat má tvar $M=(\{q_0,q_1,q_2\},\{a,b\},\{a,Z_0\},\delta,q_0,Z_0,\{q_2\})$, kde prechodová funkcia $\delta$ je určená nasledujúcim diagramom.
Výpočet nad slovom $aabb$:
$(q_0,aabb,$$\phantom{a}Z_0$$)\vdash(q_0,abb,$$\phantom{a}aZ_0$$)\vdash(q_0,bb,$$\phantom{a}aaZ_0$$)\vdash(q_1,b,$$\phantom{a}aZ_0$$)\vdash(q_1,\varepsilon,$$\phantom{a}Z_0$$)\vdash(q_2,\varepsilon,$$\phantom{a}Z_0$$)\nvdash$
Slovo bolo akceptované akceptačným stavom.
Výpočet nad slovom $aab$:
$(q_0,aab,$$\phantom{a}Z_0$$)\vdash(q_0,ab,$$\phantom{a}aZ_0$$)\vdash(q_0,b,$$\phantom{a}aaZ_0$$)\vdash(q_1,\varepsilon,$$\phantom{a}aZ_0$$)\nvdash$
Slovo nebolo akceptované, keďže výpočet neskončil v konečnom stave ani s prázdnym zásobníkom.
Poznámka
Označenie prechodu $a,b/s$ v diagrame znamená, že zo vstupu sa prečíta symbol $a\in T$, na vrchole zásobníka sa musí nachádzať symbol $b\in \Gamma$, ktorý sa zo zásobníka vyberie ($\mathit{POP}$) a následne sa do zásobníka vloží ($\mathit{PUSH}$) postupnosť symbolov zásobníka $s\in\Gamma^*$
Poznámka
Na vyjadrenie obsahu zásobníka využívame riadkovu notáciu, v rámci ktorej je vrchol zásobníka najviac vľavo. Pre jednoznačnosť však pridáme do notácie taktiež grafické vyznačenie, napríklad $\phantom{a}aZ_0$ reprezentuje zásobník, na vrchole ktorého je zásobníkový symbol $a$ a na dne symbol $Z_0$.
Príklad
Transformujte ZA $M$ z predchádajúcého príkladu na ZA $M'$, ktorý bude akceptovať slová prázdnym zásobníkom. Vykonajte na ňom výpočty nad slovami $aabb$, $aab$.
Riešenie
$M=(\{q_0,q_1\},\{a,b\},\{a,Z_0\},\delta,q_0,Z_0,\emptyset)$, kde prechodová funkcia $\delta$ je určená nasledujúcim diagramom.
Výpočet nad slovom $aabb$:
$(q_0,aabb,$$\phantom{a}Z_0$$)\vdash(q_0,abb,$$\phantom{a}a$$)\vdash(q_0,bb,$$\phantom{a}aa$$)\vdash(q_1,b,$$\phantom{a}a$$)\vdash(q_1,\varepsilon,\varepsilon)\nvdash$
Slovo bolo akceptované prázdnym zásobníkom.
Výpočet nad slovom $aab$:
$(q_0,aab,$$\phantom{a}Z_0$$)\vdash(q_0,ab,$$\phantom{a}a$$)\vdash(q_0,b,$$\phantom{a}aa$$)\vdash(q_1,\varepsilon,$$\phantom{a}a$$)\nvdash$
Slovo nebolo akceptované, keďže výpočet neskončil s prázdnym zásobníkom ani v konečnom stave.
Úloha 1.1
Navrhnite zásobníkový automat $M$ akceptujúci jazyk $L = \{ a^{n}b^{2n}~|~n \in \mathbb{N} \;\land\;n \geq 1\}$.
Úloha 1.2
Navrhnite zásobníkový automat $M$ akceptujúci jazyk $L = \{ ww^{R}~|~w \in \{0,1\}^{*}\}$.
Krok 2
Základnou požiadavkou na v praxi použiteľný syntaktický analyzátor je jeho schopnosť deterministicky spracovať vstupné reťazce s lineárnou časovou zložitosťou. V kontexte syntaktickej analýzy zhora nadol to znamená, že v každom kroku (najľavejšieho) odvodenia je možné jednoznačne (deterministicky) určiť, ktoré pravidlo gramatiky je potrebné aplikovať. Takéto sytaktické analyzátory potom označujeme ako prediktívne. Aby sme mohli rozhodnúť o tom, či je daná gramatika deterministická potrebujeme ju najprv analyzovavať, a to prostredníctvom konštukcie nasledujúcich analytických množín bezkontextových gramatík.
$\mathit{FIRST}(\alpha)$ je množina takých terminálnych symbolov, ktoré môžu stáť na začiatku slova odvodeného z vetnej formy $\alpha$. Ak z danej vetnej formy $\alpha$ možno odvodiť prazdne slovo, $\varepsilon$ taktiež patrí do tejto množiny.
$\mathit{FIRST}(\alpha)=\{a \mid \alpha \Rightarrow^{*} a\beta \;\land$ $a\in T \;\land\; \beta\in (N\cup T)^{*}\}\cup\{\varepsilon\ \mathit{ak}\ \alpha \Rightarrow^{*} \varepsilon\}$
Algoritmus 1. konštrukcia množiny $\mathit{FIRST}(\alpha)$ pre $\alpha \in (N \cup T)^{*}$.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$ a vetná forma $\alpha\in(N \cup T)^{*}$.
Výstup: Množina $\mathit{FIRST}(\alpha)$.
$\mathit{FIRST}(\alpha):$
- ak $\alpha=\varepsilon$ potom vráť $\{\varepsilon\}$;
- ak $\alpha=a\beta$, kde $a\in T\;\land\;\beta\in(N\cup T)^{*}$ potom vráť $\{a\}$;
- ak $\alpha=A\beta$, kde $A\in N\;\land\;\beta\in(N\cup T)^{*}$ potom
- $\mathit{FST}_A \leftarrow \emptyset$;
- pre všetky $A\to\gamma \in P$ vykonaj
- ak $A$ nie je prefixom $\gamma$ potom
- $\mathit{FST}_A \leftarrow \mathit{FST}_A \cup \mathit{FIRST}(\gamma)$;
- koniec ak
- koniec pre
- ak $\varepsilon\in \mathit{FST}_A$ potom
- $\mathit{FST}_A\leftarrow (\mathit{FST}_A-\{\varepsilon\})\cup\mathit{FIRST}(\beta)$;
- koniec ak
- vráť $\mathit{FST}_A$;
- koniec ak
Množina $\mathit{FOLLOW}(A)$ je množina všetkých terminálnych symbolov, ktoré môžu stáť bezprostredne za neterminálom A v nejakej vetnej forme (reťazci odvoditeľnom zo začiatočného symbolu gramatiky). Ak neterminál A môže stáť na konci niektorej vetnej formy, symbol $ patrí taktiež do tejto množiny.
Poznámka
$ je špeciálny symbol signalizujúci koniec reťazca. Nie je súčasťou samotného reťazca, ale používa sa na označenie jeho konca. V tomto zmysle sa preto podobá na ukončovací znak \0
v jazyku C.
$\mathit{FOLLOW}(A)=\{a \mid S \Rightarrow^{+} \beta_{1} Aa\beta_{2} \;\land\; a\in T\;\land$ $\beta_{1},\beta_{2}\in(N \cup T)^{*}\}\cup$
$\{$$$\ \mathit{ak}\ S \Rightarrow^{*} \beta A \;\land\; \beta\in(N \cup T)^{*}\}$
Algoritmus 2. konštrukcia množiny $\mathit{FOLLOW}(A)$ pre všetky $A \in N$.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$ a množina neterminálnych symbol $N$.
Výstup: Množiny $\mathit{FOLLOW}(A)$ pre všetky $A \in N$.
- pre všetky $A \in N$ vykonaj
- $\mathit{FLW}_A\leftarrow \emptyset$;
- koniec pre
- $\mathit{FLW}_S\leftarrow\{$$$\};$
- opakuj
- pre všetky $A \in N$ vykonaj
- pre všetky $B\to\alpha A\beta \in P$, kde $\alpha,\beta\in (N\cup T)^{*}$ vykonaj
- $\mathit{FLW}_A \leftarrow \mathit{FLW}_A \cup (\mathit{FIRST}(\beta)-\{\varepsilon\});$
- ak $\varepsilon \in \mathit{FIRST}(\beta)$ potom
- $\mathit{FLW}_A\leftarrow \mathit{FLW}_A \cup \mathit{FLW}_B;$
- koniec ak
- koniec pre
- koniec pre
- pokiaľ nenastala zmena niektorej z množín $\mathit{FLW}_A$ pre všetky $A \in N$.
- pre všetky $A \in N$, $\mathit{FOLLOW}(A)=\mathit{FLW}_A$
Aby sme zistili, či gramatika povoľuje konštrukciu prediktívneho syntaktického analyzátora, je nutné konštruovať množiny $\mathit{PREDICT}(A\to\alpha)$ pre každé pravidlo danej gramatiky a následne konštruovať tzv. rozkladovú tabuľku $RT$ (z angl. parse table). Táto tabuľka na základe aktuálne odvodzovaného (expandovaného) netermináleho symbolu $A$ a terminálneho symbolu zo vstupu $t$ špecifikuje, ktoré z pravidiel gramatiky možno aplikovať, ide teda o zobrazenie:
$RT: N\times (T\cup\{$$$\})\to 2^P$.
Rozkladová tabuľka $RT$ je spravidla parciálne (neúplné) zobrazenie. V prípade, že vstupný symbol neurčuje použitie žiadneho pravidla pre expanziu neterminálu $A$, signalizuje to syntaktickú chybu.
Najprv teda definujme množinu $\mathit{PREDICT}(A\to\alpha)$, ktorá predstavuje množinu všetkých takých terminálnych symbolov (prípadne symbolu $), ktoré pokiaľ sa nachádzajú na vstupe, signalizujú možnosť použiť na expanziu neterminálu $A$ pravidlo $A\to\alpha$. Formálne túto množinu definujeme nasledovne:
$$ \mathit{PREDICT}(A\to\alpha)=\left\{\begin{array}{ll} \mathit{FIRST}(\alpha), & \text{ak } \varepsilon \notin \mathit{FIRST}(\alpha),\\ (\mathit{FIRST}(\alpha)-\{\varepsilon\})\cup \mathit{FOLLOW}(A), & \text{ak } \varepsilon \in \mathit{FIRST}(\alpha). \end{array}\color{transparent}\right\} $$
Rozkladovú tabuľku potom možno konštruovať pomocou nasledujúceho pravidla:
Ak platí, že rozkladová tabuľka danej gramatiky jazyka neobsahuje konflikty, teda pre jeden neterminálny symbol $A$ a jeden terminálny symbol $t$ poskytuje nanajvýš jedno pravidlo, túto gramatiku označujeme ako $LL(1)$ gramatiku. Formálna špecifikácia zobrazenia RT v prípade $LL(1)$ gramatík má preto tvar:
$RT: N\times (T\cup\{$$$\}) \rightharpoonup P.$
Na základe takejto gramatiky možno zostaviť deterministický (prediktívny) syntaktický analyzátor, ktorého princíp fungovania predstavíme v ďalšom kroku.
Príklad
Daná je gramatika $G = (\{$E
,F
,T
$\}$,$\{$+
,*
,(
,)
,cislo
$\}$,$P$,E
$)$, kde $P$ je množina nasledujúcich pravidiel:
${}$ |
---|
E $\to$ E + T | T |
T $\to$ T * F | F |
F $\to$ cislo | ( E ) . |
Riešenie
Aby sme odpovedali na uvedenú otázku, potrebujeme zostrojiť rozkladovú tabuľku gramatiky $G$. To si v prvom kroku vyžaduje konštrukciu množín $\mathit{FIRST}(A)$ pre každý neterminál $A\in N$.
Konštrukcia množiny $\mathit{FIRST}($E
$)$:
Úroveň vnorenia | Vstupná vetná forma $\alpha$ | $\beta$ | $\mathit{FST}_A\phantom{halo}$ | Iterácia cyklu$\phantom{halohalohaloha}$ | $\gamma$ | Opis |
---|---|---|---|---|---|---|
1. | E |
$\varepsilon$ | $\emptyset$ | 1. | E + T |
Vstupná vetná forma $\alpha$ sa začína neterminálom, prvé dva kroky preto preskočíme. Keďže E je prefixom E + T , postúpime na ďalšiu alternatívu pravej strany pravidla pre neterminál E . |
${}$ | ${}$ | ${}$ | $\emptyset$ | 2. | T |
Keďže E nie je prefixom T , vykonáme rekurzívne volanie $\mathit{FIRST}($T $)$. |
2. | T |
$\varepsilon$ | $\emptyset$ | 1. | T * F |
Vstupná vetná forma $\alpha$ sa začína neterminálom, prvé dva kroky preto preskočíme. Keďže T je prefixom T * F , postúpime na ďalšiu alternatívu pravej strany pravidla pre neterminál T . |
${}$ | ${}$ | ${}$ | $\emptyset$ | 2. | F |
Keďže T nie je prefixom F , vykonáme rekurzívne volanie $\mathit{FIRST}($F $)$. |
3. | F |
$\varepsilon$ | $\emptyset$ | 1. | cislo |
Vstupná vetná forma $\alpha$ sa začína neterminálom, prvé dva kroky teda preskočíme. Keďže F nie je prefixom cislo , vykonáme rekurzívne volanie $\mathit{FIRST}($cislo $)$. |
4. | cislo |
$\varepsilon$ | ${}$ | vráti $\{$cislo $\}$ |
${}$ | Keďže cislo je neterminál, volanie vracia množinu $\{$cislo $\}.$ |
3. | F |
$\varepsilon$ | $\{$cislo $\}$ |
2. | (E) |
Návrat z rekurzívneho volania. Keďže F nie je prefixom (E) , vykonáme rekurzívne volanie $\mathit{FIRST}($(E) $)$. |
4. | (E) |
E) |
${}$ | vráti $\{$( $\}$ |
${}$ | Keďže ( je neterminál, volanie vracia množinu $\{$( $\}$. |
3. | F |
$\varepsilon$ | $\{$cislo ,( $\}$ |
vráti $\{$cislo , ( $\}$ |
${}$ | Návrat z rekurzívneho volania. |
2. | T |
$\varepsilon$ | $\{$cislo ,( $\}$ |
vráti $\{$cislo , ( $\}$ |
${}$ | Návrat z rekurzívneho volania. |
1. | E |
$\varepsilon$ | $\{$cislo ,( $\}$ |
vráti $\{$cislo , ( $\}$ |
${}$ | Návrat z rekurzívneho volania a poskytnutie výsledku. |
Na základe vyššie uvedeného výpočtu je zrejmé, že:
- $\mathit{FIRST}($
E
$)=\{$cislo
,(
$\}$, - $\mathit{FIRST}($
T
$)=\{$cislo
,(
$\}$ a - $\mathit{FIRST}($
F
$)=\{$cislo
,(
$\}$.
Následne pristúpime ku konštrukcii množín $\mathit{FOLLOW}(A)$ pre každý neterminál $A\in N$. Najprv inicializujeme všetky pomocné premenné $\mathit{FLW_A}$ pre každý neterminál $A\in N$ práznou množinou a do množiny $\mathit{FLW_E}$ pridáme symbol $, keďže neterminálny symbol E
je počiatočným neterminálom. Následne pristúpime k vykonaniu logického cyklu, a to nasledovne.
Iterácia vonkajšieho logického cyklu | Iterácia stredného cyklu | $A$ | Iterácia vnútorného cyklu | Pravidlo $B\to \alpha A\textcolor{red}{\beta}\phantom{\varepsilon}$ | $\mathit{FIRST}(\textcolor{red}{\beta})$ | $\mathit{FLW_E}\phantom{\varepsilon}$ | $\mathit{FLW_T}\phantom{\varepsilon\varepsilon}$ | $\mathit{FLW_F}\phantom{\varepsilon\varepsilon}$ |
---|---|---|---|---|---|---|---|---|
1. | 1. | E |
1. | E $\to$ E + T |
$\{$+ $\}$ |
$\{$$,+ $\}$ |
$\emptyset$ | $\emptyset$ |
${}$ | ${}$ | ${}$ | 2. | F $\to$ ( E ) |
$\{$) $\}$ |
$\{$$,+ ,) $\}$ |
${}$ | ${}$ |
${}$ | 2. | T |
1. | E $\to$ E + T $\textcolor{red}{\varepsilon}$ |
$\{\varepsilon\}$ | ${}$ | $\{$$,+ ,) $\}$ |
${}$ |
${}$ | ${}$ | ${}$ | 2. | E $\to$ T $\textcolor{red}{\varepsilon}$ |
$\{\varepsilon\}$ | ${}$ | $\{$$,+ ,) $\}$ |
${}$ |
${}$ | ${}$ | ${}$ | 3. | T $\to$ T * F |
$\{$* $\}$ |
${}$ | $\{$$,+ ,) ,* $\}$ |
${}$ |
${}$ | 3. | F |
1. | T $\to$ T * F $\textcolor{red}{\varepsilon}$ |
$\{\varepsilon\}$ | ${}$ | ${}$ | $\{$$,+ ,) ,* $\}$ |
${}$ | ${}$ | ${}$ | 2. | T $\to$ F $\textcolor{red}{\varepsilon}$ |
$\{\varepsilon\}$ | ${}$ | ${}$ | $\{$$,+ ,) ,* $\}$ |
Keďže po prvej iterácii vonkajšieho logického cyklu došlo k zmene obsahu všetkých troch pomocných množín $\mathit{FLW_E}, \mathit{FLW_T} a \mathit{FLW_F}$, mali by sme pristúpiť k druhej iterácii tohto cyklu. V rámci nej však už nedôjde k žiadnej zmene obsahu týchto pomocných množín a z tohto dôvodu ju neuvádzame.
Na základe vyššie uvedeného výpočtu je zrejmé, že:
- $\mathit{FOLLOW}($
E
$)=\{$$,+
,)
$\}$, - $\mathit{FOLLOW}($
T
$)=\{$$,+
,)
,*
$\}$ a - $\mathit{FOLLOW}($
F
$)=\{$$,+
,)
,*
$\}$.
Následne pristúpime ku konštrukcii množín $\mathit{PREDICT}(A\to\alpha)$ pre každé pravidlo $A\to\alpha\in P$.
- $\mathit{PREDICT}($
E
$\to$E + T
$)$
$=\mathit{FIRST}($
E + T
$)=\{$cislo
,(
$\}$, keďže $\mathit{FIRST}($E + T
$)$ neobsahuje $\varepsilon$.
- $\mathit{PREDICT}($
E
$\to$T
$)$
$=\mathit{FIRST}($
T
$)=\{$cislo
,(
$\}$, keďže $\mathit{FIRST}($T
$)$ neobsahuje $\varepsilon$.
- $\mathit{PREDICT}($
T
$\to$T * F
$)$
$=\mathit{FIRST}($
T * F
$)=\{$cislo
,(
$\}$, keďže $\mathit{FIRST}($T * F
$)$ neobsahuje $\varepsilon$.
- $\mathit{PREDICT}($
T
$\to$F
$)$
$=\mathit{FIRST}($
F
$)=\{$cislo
,(
$\}$, keďže $\mathit{FIRST}($F
$)$ neobsahuje $\varepsilon$.
- $\mathit{PREDICT}($
cislo
$)$
$=\mathit{FIRST}($
cislo
$)=\{$cislo
$\}$, keďže $\mathit{FIRST}($cislo
$)$ neobsahuje $\varepsilon$.
- $\mathit{PREDICT}($
( E )
$)$
$=\mathit{FIRST}($
( E )
$)=\{$(
$\}$, keďže $\mathit{FIRST}($( E )
$)$ neobsahuje $\varepsilon$.
Na základe vyššie uvedeného teraz možno pristúpiť k zostrojeniu rozkladovej nasledujúcej tabuľky.
$RT$ | + |
* |
( |
) |
cislo |
$ |
---|---|---|---|---|---|---|
E |
${}$ | ${}$ | $\textcolor{red}{1,2}$ | ${}$ | $\textcolor{red}{1,2}$ | |
T |
${}$ | ${}$ | $\textcolor{red}{3,4}$ | ${}$ | $\textcolor{red}{3,4}$ | |
F |
${}$ | ${}$ | $\textcolor{green}{6}$ | ${}$ | $\textcolor{green}{5}$ |
Keďže rozkladová tabuľa obsahuje konflikty (zvýraznené červenou farbou), daná gramatika nie je $LL(1)$ gramatikou.
Konflikty v rozkladovej tabuľke pre gramatiku z vyššie uvedeného príkladu sú spôsobené ľavo rekurzívnymi prepisovacími pravidlami. Gramatika s ľavo rekurzívnymi neterminálnymi symbolmi preto nikdy nebude $LL(1)$ gramatikou.
Ďalším zdrojom nedeterminizmu v gramatike je prítomnosť viacerých pravidiel pre neterminálny symbol $A,$ ktorých pravé strany začínajú rovnakou vetnou formou $\alpha$. Keďže princípy eliminácie oboch týchto problémov sme vysvetlili a precvičili na predchádzajúcom cvičení, v nasledujúcom príklade sa už len presvedčíme, že odstránením týchto zdrojov nedeterminizmu možno gramatiku determinizovať.
Príklad
Overte, či transformovaná gramatika jazyka aritmetických výrazov $G^T=(\{$E
,E
'',T
,T
'',F
$\}$,$\{$+
,*
,(
,)
$\}$,$P^T$,E
$)$, kde $P^T$ je množina nasledujúcich pravidiel:
${}$ |
---|
E $\to$ T E '' |
E '' $\to$ + T E ''$\;\;$| $\;\;\varepsilon$ |
T $\to$ F T '' |
T '' $\to$ * F T ''$\;\;$| $\;\;\varepsilon$ |
F $\to$ cislo | ( E ) |
je $LL(1)$ gramatikou.
Riešenie
Zostavme množiny $\mathit{FIRST}(A)$ a $\mathit{FOLLOW}(A)$, pre všetky neterminály $A \in N^T$ a množiny $\mathit{PREDICT}(A\to\alpha)$ pre všetky pravidlá $A\to\alpha \in P^T$.
${}$ | ${}$ | ${}$ |
---|---|---|
$\mathit{FIRST}($E $)=\{$cislo ,( $\}$ |
${}$ | $\mathit{FOLLOW}($E $)=\{$$,) $\}$ |
$\mathit{FIRST}($E ''$)=\{$+ ,$\varepsilon\}$ |
${}$ | $\mathit{FOLLOW}($E ''$)=\{$$, ) $\}$ |
$\mathit{FIRST}($T $)=\{$cislo ,( $\}$ |
${}$ | $\mathit{FOLLOW}($T $)=\{$$,) ,+ $\}$ |
$\mathit{FIRST}($T ''$)=\{$* ,$\varepsilon\}$ |
${}$ | $\mathit{FOLLOW}($T ''$)=\{$$,) ,+ $\}$ |
$\mathit{FIRST}($F $)=\{$cislo ,( $\}$ |
${}$ | $\mathit{FOLLOW}($F $)=\{$$,) ,* ,+ $\}$ |
${}$ |
---|
$\mathit{PREDICT}($E $\to$ T E ''$)=\{$cislo ,( $\}$ |
$\mathit{PREDICT}($E ''$\to$ + T E ''$)=\{$+ $\}$ |
$\mathit{PREDICT}($E ''$\to \varepsilon)=\{$$,) $\}$ |
$\mathit{PREDICT}($T $\to$ F T ''$)=\{$cislo ,( $\}$ |
$\mathit{PREDICT}($T ''$\to$ * F T ''$)=\{$* $\}$ |
$\mathit{PREDICT}($T ''$\to \varepsilon)=\{$$,) ,+ $\}$ |
$\mathit{PREDICT}($F $\to$ cislo $)=\{$cislo $\}$ |
$\mathit{PREDICT}($F $\to$ ( E ) $)=\{$( $\}$ |
Následne možno zostaviť rozkladovú tabuľku.
$RT$ | + |
* |
( |
) |
cislo |
$ |
---|---|---|---|---|---|---|
E |
${}$ | ${}$ | $\textcolor{green}{1}$ | ${}$ | $\textcolor{green}{1}$ | ${}$ |
E '' |
$\textcolor{green}{2}$ | ${}$ | ${}$ | $\textcolor{green}{3}$ | ${}$ | $\textcolor{green}{3}$ |
T |
${}$ | ${}$ | $\textcolor{green}{4}$ | ${}$ | $\textcolor{green}{4}$ | ${}$ |
T '' |
$\textcolor{green}{6}$ | $\textcolor{green}{5}$ | ${}$ | $\textcolor{green}{6}$ | ${}$ | $\textcolor{green}{6}$ |
F |
${}$ | ${}$ | $\textcolor{green}{8}$ | ${}$ | $\textcolor{green}{7}$ | ${}$ |
Gramatika $G^T$ je $LL(1)$ gramatikou, to znamená, že na základe danej gramatiky možno zostaviť jednoduchý prediktívny nerekurzívny syntaktický analyzátor.
Úloha 2.1
Daná je nasledujúca gramatika:
${}$ |
---|
$(1)$ S $\to$ A |
$(2)$ A $\to$ xAx |
$(3)$ A $\to$ C |
$(4)$ B $\to$ yBy |
$(5)$ B $\to$ C |
$(6)$ C $\to$ zBz |
$(7)$ C $\to$ wAw |
$(8)$ C $\to$ $\varepsilon$ |
a) Určte množiny $N$, $T$.
b) Skonštruujte deriváciu reťazca xzzx
začínajúc v S
. Vyznačte, ktoré odvodzovacie pravidlá ste použili a nakreslite odvodzovací strom podľa Vašej derivácie.
c) Nájdite množiny $\mathit{FIRST}$ a $\mathit{FOLLOW}$ pre každý neterminálny symbol gramatiky.
d) Pre každé pravidlo gramatiky skonštruujte množinu $\mathit{PREDICT}$.
e) Zostavte rozkladovú tabuľku pre danú gramatiku. Zistite, či daná gramatika je $LL(1)$ gramatikou. Svoju odpoveď odôvodnite.
f) Ukážte kroky, ktoré by váš analyzátor realizoval na analýzu vstupu xzyyzx
.
Riešenie
a) $T = \{$x
, y
, z
, w
$\}$, $N=\{$ S
, A
, B
, C
$\}$.
b)
${}$ | ${}$ |
---|---|
$\phantom{\overset{(2)}{\Rightarrow}}$S |
$\overset{(1)}{\Rightarrow}$ A |
${}$ | $\overset{(2)}{\Rightarrow}$ xAx |
${}$ | $\overset{(3)}{\Rightarrow}$ xCx |
${}$ | $\overset{(6)}{\Rightarrow}$ xzBzx |
${}$ | $\overset{(5)}{\Rightarrow}$ xzCzx |
${}$ | $\overset{(8)}{\Rightarrow}$ xzzx |
Strom odvodenia vyplýva priamo z derivácie.
xzzx
c)
${}$ | ${}$ | ${}$ |
---|---|---|
$\mathit{FIRST}($S $) = \{$ x , z , w $\varepsilon\}$ |
${}$ | $\mathit{FOLLOW}($S $) = \{$$$\}$ |
$\mathit{FIRST}($A $) = \{$ x , z , w , $\varepsilon\}$ |
${}$ | $\mathit{FOLLOW}($A $) = \{$$, x , w $\}$ |
$\mathit{FIRST}($B $) = \{$ y , z , w , $\varepsilon\}$ |
${}$ | $\mathit{FOLLOW}($B $) = \{$ y , z $\}$ |
$\mathit{FIRST}($C $) = \{$ z , w , $\varepsilon\}$ |
${}$ | $\mathit{FOLLOW}($C $) = \{$$, x , y , z , w $\}$ |
d)
${}$ |
---|
$\mathit{PREDICT}(1) = \{$ x , z , w , $$\}$ |
$\mathit{PREDICT}(2) = \{$ x $\}$ |
$\mathit{PREDICT}(3) = \{$ z , w , x , $$\}$ |
$\mathit{PREDICT}(4) = \{$ y $\}$ |
$\mathit{PREDICT}(5) = \{$ z , w , y $\}$ |
$\mathit{PREDICT}(6) = \{$ z $\}$ |
$\mathit{PREDICT}(7) = \{$ w $\}$ |
$\mathit{PREDICT}(8) = \{$$ , x , y , z , w $\}$ |
e)
$RT$ | x |
y |
z |
w |
$ |
---|---|---|---|---|---|
S |
$\textcolor{green}{1}$ | $\textcolor{green}{1}$ | $\textcolor{green}{1}$ | $\textcolor{green}{1}$ | |
A |
$\textcolor{red}{2, 3}$ | $\textcolor{green}{3}$ | $\textcolor{green}{3}$ | $\textcolor{green}{3}$ | |
B |
$\textcolor{red}{4, 5}$ | $\textcolor{green}{5}$ | $\textcolor{green}{5}$ | ||
C |
$\textcolor{green}{8}$ | $\textcolor{green}{8}$ | $\textcolor{red}{6, 8}$ | $\textcolor{red}{7, 8}$ | $\textcolor{green}{8}$ |
Uvedená gramatika nie je $LL(1)$ gramatikou, pretože v rozkladovej tabuľke sú konflikty.
f) Analyzátor by nemohol analyzovať daný reťazec, pretože v tabuľke analýzy sú konflikty.
Úloha 2.2
Daná je nasledujúca gramatika:
${}$ |
---|
$(1)$ S $\to$ AB |
$(2)$ A $\to$ xA |
$(3)$ A $\to$ B |
$(4)$ B $\to$ yzB |
$(5)$ B $\to$ z |
a) Určte množiny $N$, $T$.
b) Skonštruujte strom odvodenia pre vstupný reťazec xyzzz
c) Nájdite množiny $\mathit{FIRST}$ a $\mathit{FOLLOW}$ pre každý neterminálny symbol gramatiky.
d) Pre každé pravidlo gramatiky skonštruujte množinu $\mathit{PREDICT}$.
e) Zostavte rozkladovú tabuľku pre danú gramatiku. Zistite, či daná gramatika je $LL(1)$ gramatikou. Svoju odpoveď odôvodnite.
f) Ak do danej gramatiky pridáme pravidlo A
$\to$ $\varepsilon$, zmení sa jej klasifikácia? Svoju odpoveď odôvodnite.
Riešenie
a) $T = \{$ x
, y
, z
, $\}$, $N=\{$ S
, A
, B
$\}$.
b)
xyzzz
c)
${}$ | ${}$ | ${}$ |
---|---|---|
$\mathit{FIRST}($S $) = \{$ x , y , z $\}$ |
${}$ | $ \mathit{FOLLOW}($S $) = \{$$$\}$ |
$\mathit{FIRST}($A $) = \{$ x , y , z $\}$ |
${}$ | $ \mathit{FOLLOW}($A $) = \{$ y , z $\}$ |
$\mathit{FIRST}($B $) = \{$ y , z $\}$ |
${}$ | $ \mathit{FOLLOW}($B $) = \{$ y , z , $$\}$ |
d)
${}$ |
---|
$\mathit{PREDICT}(1) = \{$ x , y , z $\}$ |
$\mathit{PREDICT}(2) = \{$ x $\}$ |
$\mathit{PREDICT}(3) = \{$ y , z $\}$ |
$\mathit{PREDICT}(4) = \{$ y $\}$ |
$\mathit{PREDICT}(5) = \{$ z $\}$ |
e)
$RT$ | x |
y |
z |
$ |
---|---|---|---|---|
S |
$\textcolor{green}{1}$ | $\textcolor{green}{1}$ | $\textcolor{green}{1}$ | |
A |
$\textcolor{green}{2}$ | $\textcolor{green}{3}$ | $\textcolor{green}{3}$ | |
B |
$\textcolor{green}{4}$ | $\textcolor{green}{5}$ |
Uvedená gramatika je $LL(1)$ gramatikou, pretože v rozkladovej tabuľke nie sú konflikty.
f) Označme nové pravidlo $A \rightarrow \varepsilon$ poradovým číslom $(6)$. Množiny $\mathit{FIRST}$ a $\mathit{FOLLOW}$ sa zmenia nasledovne:
${}$ | ${}$ | ${}$ |
---|---|---|
$\mathit{FIRST}($S $) = \{$ x , y , z $\}$ |
${}$ | $ \mathit{FOLLOW}($S $) = \{$$$\}$ |
$\mathit{FIRST}($A $) = \{$ x , y , z , $\varepsilon \}$ |
${}$ | $ \mathit{FOLLOW}($A $) = \{$ y , z $\}$ |
$\mathit{FIRST}($B $) = \{$ y , z $\}$ |
${}$ | $ \mathit{FOLLOW}($B $) = \{$ y , z , $$\}$ |
Pozn. Žiadna z množín $\mathit{FOLLOW}$ sa nezmenila.
Množiny $\mathit{PREDICT}$
${}$ |
---|
$\mathit{PREDICT}(1) = \{$ x , y , z $\}$ |
$\mathit{PREDICT}(2) = \{$ x $\}$ |
$\mathit{PREDICT}(3) = \{$ y , z $\}$ |
$\mathit{PREDICT}(4) = \{$ y $\}$ |
$\mathit{PREDICT}(5) = \{$ z $\}$ |
$\mathit{PREDICT}(6) = \{$ y , z $\}$ |
Pozn. Množina $\mathit{PREDICT}(6)$ je rovnaká ako $\mathit{FOLLOW}(A)$.
Ak by sme zostavili rozkladovú tabuľku pre túto novú gramatiku, dostaneme:
$RT$ | x |
y |
z |
$ |
---|---|---|---|---|
S |
$\textcolor{green}{1}$ | $\textcolor{green}{1}$ | $\textcolor{green}{1}$ | |
A |
$\textcolor{green}{2}$ | $\textcolor{red}{3,6}$ | $\textcolor{red}{3,6}$ | |
B |
$\textcolor{green}{4}$ | $\textcolor{green}{5}$ |
Gramatika teda nie je $LL(1)$ gramatikou.
Krok 3
Teoretický model nerekurzívneho sytaktického analyzátora jazyka generovaného $LL(1)$ gramatikou je zásobníkový automat, ktorý namiesto rekuzívneho volania procedúr (pozri cvičenie 6.) používa zásobník pre ukladanie terminálnych a neterminálnych symbolov, resp. jednotlivých vetných foriem odvodenia. Zásobníkový symbol nachádzajajúci sa na dne jeho zásobníka je $.
Schéma nerekurzívneho syntaktického analyzátora je uvedená na Obrázku 4. Tento syntaktický analyzátor
- číta symboly vstupného reťazca,
- má prístup k vrcholu zásobníka,
- realizuje prediktívnu syntaktickú analýzu na základe obsahu rozkladovej tabuľky $RT$ a
- na výstupe produkuje odvodenie (pre jednoduchosť možno uvažovať o pravidlách použitých pri najľavejšom odvodení).
Samotný princíp nerekuzívnej prediktívnej syntaktickej analýzy definujeme prostredníctvom nasledujúceho algoritmu.
Algoritmus 3. Algoritmus nerekurzívnej prediktívnej syntaktickej analýzy.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$, k nej zostrojená rozkladová tabuľka syntaktického analyzátora $LL(1)$, vstupný reťazec (slovo) $w$.
Výstup: Ak $w\in L(G)$ výstupom je najľavejšie odvodenie reťazca $w$, v opačnom prípade je výstupom informácia o chybe.
- $\mathit{PUSH}(S)$;
- $t\leftarrow$ prvý symbol $w$;
- pokiaľ zásobnik nie je prázdny vykonaj
- $X\leftarrow \mathit{POP}()$;
- ak $X\in N$ potom
- ak $RT[X,t]=X\to\alpha$ potom
- $\mathit{PUSH}(\alpha^R)$;
- výstup $X\to\alpha$;
- v opačnom prípade
- výstup $\mathit{SYNTAX\ ERROR}$;
- koniec ak
- v opačnom prípade
- ak $X\in T\;\land\;X=t$ potom
- $t\leftarrow$ nasledujúci symbol $w$;
- v opačnom prípade
- výstup $\mathit{SYNTAX\ ERROR}$;
- koniec ak
- koniec ak
- koniec pokiaľ
Príklad
Vykonajte nerekurzívnu syntaktickú analýzu jazykového výrazu 3 * ( 2 + 1 )
na báze gramatiky $G^T$ konštruovanej v predchádzajúcom príklade.
Riešenie
Iterácia | Vstup | Zásobník $\phantom{aaaaa}$ | Výstup |
---|---|---|---|
1. | 3 * ( 2 + 1 ) |
E |
$(1)$ |
2. | 3 * ( 2 + 1 ) |
T E'' |
$(4)$ |
3. | 3 * ( 2 + 1 ) |
F T'' E'' |
$(7)$ |
4. | 3 * ( 2 + 1 ) |
3 T'' E'' |
|
5. | * ( 2 + 1 ) |
T'' E'' |
$(5)$ |
6. | * ( 2 + 1 ) |
* F T'' E'' |
|
7. | ( 2 + 1 ) |
F T'' E'' |
$(8)$ |
8. | ( 2 + 1 ) |
( E ) T'' E'' |
|
9. | 2 + 1 ) |
E ) T'' E'' |
$(1)$ |
10. | 2 + 1 ) |
T E'' ) T'' E'' |
$(4)$ |
11. | 2 + 1 ) |
F T'' E'' ) T'' E'' |
$(7)$ |
12. | 2 + 1 ) |
2 T'' E'' ) T'' E'' |
|
13. | + 1 ) |
T'' E'' ) T'' E'' |
$(6)$ |
14. | + 1 ) |
E'' ) T'' E'' |
$(2)$ |
15. | + 1 ) |
+ T E'' ) T'' E'' |
|
16. | 1 ) |
T E'' ) T'' E'' |
$(4)$ |
17. | 1 ) |
F T'' E'' ) T'' E'' |
$(7)$ |
18. | 1 ) |
1 T'' E'' ) T'' E'' |
|
19. | ) |
T'' E'' ) T'' E'' |
$(6)$ |
20. | ) |
E'' ) T'' E'' |
$(3)$ |
21. | ) |
) T'' E'' |
|
22. | $\varepsilon$ | T'' E'' |
$(6)$ |
23. | $\varepsilon$ | E'' |
$(3)$ |
24. | $\varepsilon$ | $\varepsilon$ |
Príklad
Nakreslite diagram ZA z predchádzajúceho príkladu. Z koľkých stavov a koľkých prechodov bude pozostávať?
Riešenie
Keďže všetky výpočty výsledného ZA sú realizované výlučne na báze zásobníka tento automat obsahuje len len jeden jediný stav s počtom prechodov 13.
Úloha 3.1
Analyticky určte počet prechodov jednostavového ZA vykonavajuceho akceptáciu ľubovoľného jazyka definovaného $LL(1)$ gramatikou prázdnym zásobníkom.
Krok 4
V tomto kroku pristúpime ku konštrukcii nerekurzívneho syntaktického analyzátora jazyka aritmetických výrazov, ktorý je definovaný transformovanou $LL(1)$ gramatikou $G^T$, prezentovanou v predchádzajúcom kroku. Samotný analyzátor bude realizovaný na báze zásobníkového automatu, ktorého zásobník bude v priebehu analýzy slúžiť na uchovávanie terminálnych a neterminálnych symbolov gramatiky. Z tohto dôvodu je nevyhnutné, aby boli tieto symboly reprezentované jednoznačným a programovo spracovateľným spôsobom. Keďže terminálne symboly možno reprezentovať prostredníctvom enumeračného typu TokenType
, pre zabezpečenie analogickej reprezentácie neterminálnych symbolov zavedieme vlastný enumeračný typ Nonterminal
.
# Definícia enumeračného typu neterminálov
class Nonterminal(Enum):
E = auto()
E_PRIME = auto()
T = auto()
T_PRIME = auto()
F = auto()
Inicializácia syntaktického analyzátora: Do internej premennej syntaktického analyzátora sa uloží referencia na zvolený lexikálny analyzátor.
class Parser:
def __init__(self, scanner):
self.scanner = scanner # Lexikálny analyzátor na získavanie tokenov
self.current_token = None # Aktuálne rozpoznaný token zo vstupu
self.stack = None # Zásobník
Súčasťou inicializácie je taktiež definícia gramatiky a rozkladovej tabuľky syntaktického analyzátora. Obe sú uložené v dátovej štruktúre typu slovník, ktorá predstavuje kolekciu dvojíc kľúč–hodnota. V prípade gramatiky je kľúčom číselný identifikátor pravidla a hodnotou jeho ďalej štrukturovaná reprezentácia. Rozkladová tabuľka využíva ako kľúč kombináciu neterminálneho a vstupného symbolu a ako hodnotu číslo pravidla, ktoré je treba aplikovať. Takto navrhnutá reprezentácia umožňuje efektívne a jednoznačné vyhľadávanie pravidiel počas analýzy vstupného reťazca.
self.rules = {
1: {"left_side": Nonterminal.E, "right_side": [Nonterminal.T,Nonterminal.E_PRIME]}, # E -> T E'
2: {"left_side": Nonterminal.E_PRIME, "right_side": [TokenType.PLUS,Nonterminal.T,Nonterminal.E_PRIME]}, # E' -> + T E'
3: {"left_side": Nonterminal.E_PRIME, "right_side": []}, # E' -> ε
4: {"left_side": Nonterminal.T, "right_side": [Nonterminal.F,Nonterminal.T_PRIME]}, # T -> F T'
5: {"left_side": Nonterminal.T_PRIME, "right_side": [TokenType.MULTIPLY,Nonterminal.F,Nonterminal.T_PRIME]}, # T' -> * F T'
6: {"left_side": Nonterminal.T_PRIME, "right_side": []}, # T' -> ε
7: {"left_side": Nonterminal.F, "right_side": [TokenType.NUM_CONST]}, # F -> cislo
8: {"left_side": Nonterminal.F, "right_side": [TokenType.LPAREN,Nonterminal.E,TokenType.RPAREN]}, # F -> ( E )
}
self.parsing_table = {}
# Riadky rozkladovej tabuľky
# Riadok E
self.parsing_table[(Nonterminal.E, TokenType.LPAREN)] = 1
self.parsing_table[(Nonterminal.E, TokenType.NUM_CONST)] = 1
# Riadok E'
self.parsing_table[(Nonterminal.E_PRIME, TokenType.PLUS)] = 2
self.parsing_table[(Nonterminal.E_PRIME, TokenType.RPAREN)] = 3
self.parsing_table[(Nonterminal.E_PRIME, TokenType.EOF)] = 3
# Riadok T
self.parsing_table[(Nonterminal.T, TokenType.LPAREN)] = 4
self.parsing_table[(Nonterminal.T, TokenType.NUM_CONST)] = 4
# Riadok T'
self.parsing_table[(Nonterminal.T_PRIME, TokenType.MULTIPLY)] = 5
self.parsing_table[(Nonterminal.T_PRIME, TokenType.PLUS)] = 6
self.parsing_table[(Nonterminal.T_PRIME, TokenType.RPAREN)] = 6
self.parsing_table[(Nonterminal.T_PRIME, TokenType.EOF)] = 6
# Riadok F
self.parsing_table[(Nonterminal.F, TokenType.NUM_CONST)] = 7
self.parsing_table[(Nonterminal.F, TokenType.LPAREN)] = 8
Metóda parse
: Na začiatku analýzy sa inicializuje zásobník, na vrchol zásobníka sa vloží počiatočný neterminálny symbol E
a načíta sa prvý token zo vstupného reťazca.
def parse(self):
self.stack=[TokenType.EOF]
self.stack.append(Nonterminal.E)
self.current_token = self.scanner.get_next_token()
Hlavný cyklus analýzy: Postupne spracováva symboly z vrchola zásobníka. Ak je na vrchole neterminálny symbol, použije rozkladovú tabuľku na vyhľadanie príslušného pravidla, ktorým neterminál expanduje (vloží do zásobníka symboly pravej strany tohto pravidla v opačnom poradí). Ak je na vrchole terminál, porovná sa s aktuálnym tokenom zo vstupu; ak sa zhodujú, parser načíta ďalší token. V prípade nezrovnalosti ohlási syntaktickú chybu. Proces pokračuje, kým nie je obsah zásobníka úplne spracovaný.
while self.stack:
# Vrchol zásobníka
X = self.stack.pop()
# Ak je X neterminálny symbol
if isinstance(X, Nonterminal):
# Ak existuje pravidlo v rozkladovej tabuľke pre kombináciu (neterminál, token)
if (X, self.current_token.type) in self.parsing_table:
# Získame číslo pravidla a pravú stranu pravidla
rule_number = self.parsing_table[(X, self.current_token.type)]
right_side = self.rules[rule_number]["right_side"]
# Postupné vloženie symbolov pravej strany pravidla do zásobníka v opačnom poradí
for symbol in reversed(right_side):
self.stack.append(symbol)
print(f"({rule_number})",end='=>' if len(self.stack)>1 else '\n')
else:
# Syntaktická chyba vo vstupnom reťazci
raise SyntaxError("Syntaktická chyba")
# Ak je X terminál alebo koncový znak a zhoduje sa s aktuálnym tokenom zo vstupu
elif isinstance(X,TokenType) and X==self.current_token.type:
# Posun na ďalší token
self.current_token = self.scanner.get_next_token()
else:
# Syntaktická chyba vo vstupnom reťazci
raise SyntaxError("Syntaktická chyba")
Úloha 4.1
Upravte implementáciu syntaktického analyzátora tak, aby na výstupe negeneroval iba postupnosť aplikovaných pravidiel, ale aj úplné postupné odvodenie vstupného slova.
Úloha 4.2
Doplňte zdrojový kód syntaktického analyzátora tak, aby počas parsovania výrazu konštruoval taktiež odvodzovací strom výrazu, ktorého grafická reprezentácia sa zobrazí po úspešnom ukončení syntaktickej analýzy. Pre vykreslenie stromu môžete v jazyku Python použiť napríklad externú knižnicu PrettyPrintTree.
Doplňujúce materialy
Pomocný nastroj pre náplň tohto cvičenia nájdete na tejto stránke prístupnej len z TUKE siete.
Dotazník na ohodnotenie učiva
Vážení študenti,
týmto by sme Vás chceli požiadať o poskytnutie spätnej väzby k formátu a obsahu materiálov na cvičenia vo forme vyplnenie nasledujúceho dotazníka .
Tento dotazník slúži na ohodnotenie vašich skúseností s pripravenými materiálmi z ôsmeho cvičenia. Na vylepšovaní materiálov na základe vašej spätnej väzby aktívne pracujeme, a to s cieľom zvýšiť kvalitu, zrozumiteľnosť a prínosnosť výučby.