Ciele
- Oboznámiť sa s transformáciami bezkontextových gramatík.
- Precvičiť si transformácie bezkontextových gramatík.
Úvod
Transformácia gramatiky predstavuje postup, pomocou ktorého získame z danej gramatiky ekvivalentnú gramatiku, čiže takú, ktorá generuje ten istý jazyk, ale zároveň spĺňa určité dodatočné požiadavky. Tieto transformácie sú v praxi veľmi dôležité, pretože umožňujú gramatiku zjednodušiť, odstrániť z nej problémové konštrukcie alebo ju prispôsobiť konkrétnej metóde syntaktickej analýzy, ako je napríklad rekurzívna syntaktická analýza zhora nadol.
V tejto kapitole sa oboznámime s rôznymi druhmi transformácií bezkontextových gramatík, keďže práve tie tvoria základ formálneho popisu syntaktickej štruktúry programovacích jazykov. Zároveň si tieto transformácie prakticky precvičíme a ukážeme, ako ich využiť pri implementácii syntaktického analyzátora a interpretátora pre jazyk aritmetických výrazov.
Postup
Krok 1
V praxi sa možeme stretnúť s gramatikami, ktoré obsahujú rôzne prvky, ktoré komplikujú ich spracovanie, na zákalde čoho nie je možné zostrojiť ich syntaktický analyzátor konkrétneho typu. Transformácie gramatík nám umožňujú tieto nedokonalosti odstrániť a previesť gramatiku do štandardizovanej ekvivalentnej formy, ktorá je vhodnejšia pre praktické použitie resp. dovoľuje zostrojenie syntaktického analyzátora konkrétneho typu.
Medzi najdôležitejšie transformácie bezkontextových gramatík patria tieto úpravy:
- odstránenie nadbytočných symbolov,
- odstránenie $\varepsilon$-pravidiel,
- odstránenie jednoduchých pravidiel a cyklov,
- odstránenie ľavej rekurzie a ľavá faktorizácia.
Následne, redukovaná bezkontextová gramatika je bezkontextová gramatika bez nadbytočných symbolov. Vlastná bezkontextová gramatika je bezkontextová gramatika bez cyklu, bez $\varepsilon$-pravidiel a bez nadbytočných symbolov.
Teraz sa na jednotlivé úpravy pozrieme bližšie.
Odstránenie nadbytočných symbolov gramatiky
Nedostupný symbol je taký symbol $\beta \in N \cup T$ (terminál alebo neterminál) gramatiky, ktorý sa neobjavuje v žiadnej vetnej forme. Neexistuje teda derivácia $ S \Rightarrow^* \alpha \beta \gamma $, kde $S$ je počiatočný neterminál a $ \alpha, \gamma \in (N \cup T)^* $.
Nadbytočný symbol je taký symbol $\beta \in N \cup T$ (terminál alebo neterminál) gramatiky, ktorý je nedostupný alebo ktorého výskyt v rámci vetnej formy spôsobí, že žiadnou deriváciou nie je možné túto vetnú formu transformovať na vetu. Neexistuje teda derivácia $ S \Rightarrow^* \alpha\beta\gamma \Rightarrow^* w $, kde $S$ je začiatočný symbol $\alpha,\gamma\in(N\cup T)^*$ a $ w \in T^*$.
Poznámka
Každý nedostupný symbol je nadbytočný, avšak opačné tvrdenie neplatí, pretože nie každý nadbytočný symbol je zároveň nedostupný.
Odstránenie nadbytočných symbolov z bezkontextovej gramatiky pozostáva z nasledujúcich krokov:
- Iteratívna konštrukcia množiny $N_T$ – neterminálych symbolov, ktoré môžu generovať terminálne reťazce.
- Odstránenie všetkých neterminálnych symbolov, ktoré nepatria do množiny $N_T$ – ak sa takýto symbol nachádza v niektorom pravidle, toto pravidlo z gramatiky odstránime.
- Iteratívna konštrukcia množiny dostupných symbolov $V_D$ – možno vykonať až po ukončení predchádzajúcich krokov.
- Odstránenie všetkých symbolov z gramatiky, ktoré nepatria do množiny $V_D$ – ak sa takýto symbol nachádza v pravidle, toto pravidlo z gramatiky odstránime.
Množinu všetkých neterminálnych symbolov, ktoré môžu generovať terminálne reťazce, určime pomocou nasledujúceho Algoritmu 1.
Algoritmus 1. Konštrukcia množiny neterminálnálnych sybolov, ktoré môžu generovať terminálne reťazce.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$.
Výstup: Množina neterminálov $N_T$, ktoré môžu generovať terminálne reťazce.
- $N_T\leftarrow\emptyset$;
- do
- $\acute{N}_T\leftarrow N_T$;
- $N_T\leftarrow\acute{N}_T \cup \{A\;|\;A\to\alpha\in P \;\land\;\alpha\in(\acute{N}_T\cup T)^{*}\}$;
- while $N_T\neq\acute{N}_T$
Množinu všetkých dostupných symbolov gramatiky určíme pomocou nasledujúceho Algoritmu 2.
Algoritmus 2. Konštrukcia množiny dostupných symbolov gramatiky.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$.
Výstup: Množina dostupných symbolov $V_D$ gramatiky $G$.
- $V_D\leftarrow\{S\}$;
- do
- $\acute{V}_D\leftarrow V_D$;
- $V_D\leftarrow\acute{V}_D \cup \{\beta\;|\;A\to\alpha\beta\gamma\in P \;\land\; \beta\in (N\cup T) \;\land\;A\in\acute{V}_D\;\land\;\alpha,\gamma\in(N\cup T)^{*}\};$
- while $V_D\neq\acute{V}_D$
Príklad
Daná je bezkontextová gramtika $G = (\{$S, A, B, C, D$\}, \{$a, b, c$\}$, $P$, S$)$, kde $P$ je množina pozostávajúca z nasledujúcich pravidiel:
| ${}$ |
|---|
S $\to$ AB | C |
A $\to$ aA | a |
B $\to$ bB |
C $\to$ c |
D $\to$ bc. |
Transformujte gramatiku $G$ na gramatiku $G'$, ktorá neobsahuje nadbytočné symboly a pre ktorú platí: $L(G) = L(G')$.
Riešenie
Najprv musíme pristúpiť k identifikácii a následnej eliminácii neterminálov, ktoré nedokážu generovať terminálne reťazce. Tie zistíme ako rozdiel množiny všetkých neterminálov $N$ a množiny $N_T$, ktorú získame ako výsledok aplikácie Algoritmu 1.
Po inicializácii $N_T$ prázdnou množinou sú hodnoty $N_T$ a $\acute{N}_T$ v jednotlivých iteráciách za ňou nasledujúceho logického cyklu v momente vyhonotenia jeho podmienky prehľadne prezentované v nasledujúcej tabuľke.
| Iterácia logického cyklu | Množina $N_T$ | Množina $\acute{N}_T$ | Podmienka cyklu $N_T\neq\acute{N}_T$ |
|---|---|---|---|
| 1. | $\{$A,C,D$\}$ |
$\emptyset$ | $\mathit{true}$ |
| 2. | $\{$A,C,D,S$\}$ |
$\{$A,C,D$\}$ |
$\mathit{true}$ |
| 3. | $\{$A,C,D,S$\}$ |
$\{$A,C,D,S$\}$ |
$\mathit{false}$ |
Keďže B$\not\in N_T$, neterminálny symbol B je nadbytočný a z gramatiky ho odstránime. Gramatika $G''$ (po prvej fáze transformácie) tak bude vyzerať nasledovne:
$G'' = (\{$S, A, C, D$\}$, $\{$a, b, c$\}$, $P''$, S$)$, kde $P''$ je množina pozostávajúca z nasledujúcich pravidiel:
| ${}$ |
|---|
S $\to$ C $\quad$(odstránili sme S $\to$ AB, pretože obsahuje B) |
A $\to$ aA | a |
C $\to$ c |
D $\to$ bc. |
Následne môžeme pristúpiť k identifikácii a eliminácii nedostupných sybolov gramatiky $G''.$ Tie ziskame ako rozdiel množiny všetkých symbolov $N\cup T$ a množiny $V_D$, ktorú získame ako výsledok aplikácie Algoritmu 2.
Po inicializácii $V_D$ množinou obsahujúcou začiatočný sybolom gramatiky S sú hodnoty $V_D$ a $\acute{V}_D$ v jednotlivých iteráciách za ňou nasledujúceho logického cyklu v momente vyhodnotenia jeho podmienky prehľadne prezentované v nasledujúcej tabuľke.
| Iterácia logického cyklu | Množina $V_D$ | Množina $\acute{V}_D$ | Podmienka cyklu $V_D\neq\acute{V}_D$ |
|---|---|---|---|
| 1. | $\{$S,C$\}$ |
$\{$S$\}$ |
$\mathit{true}$ |
| 2. | $\{$S,C,c$\}$ |
$\{$S,C$\}$ |
$\mathit{true}$ |
| 3. | $\{$S,C,c$\}$ |
$\{$S,C,c$\}$ |
$\mathit{false}$ |
Keďže symboly A, D, a, b $\notin V_D$, sú nadbytočné a z gramatiky ich môžeme odstrániť. Výsledná gramatika $G'$ bez nadbytočných symbolov bude vyzerať nasledovne:
$G' = (\{$S, C$\}$, $\{$c$\}$,$\{$S $\to$ C, C $\to$ c$\}$, S$)$.
Odstránenie $\boldsymbol\varepsilon$-pravidiel
Pravidlo sa nazýva $\boldsymbol\varepsilon$-pravidlo, ak je tvaru $A \rightarrow \varepsilon$, kde $A$ je ľubovoľný neterminál. Intuitívne nám toto pravidlo hovorí, že daný neterminál môže v priebehu derivácie kedykoľvek zmiznúť.
Gramatika sa nazýva gramatika bez $\boldsymbol\varepsilon$-pravidiel, ak jej množina pravidiel $P$ neobsahuje žiadne $\varepsilon$-pravidlo alebo v $P$ existuje jediné $\varepsilon$-pravidlo $S\to \varepsilon$, pričom začiatočný symbol gramatiky $S$ sa nevyskytuje na pravej strane žiadneho pravidla.
Odstránenie $\varepsilon$-pravidiel z bezkontextovej gramatiky pozostáva z nasledujúcich krokov:
- Iteratívna konštruukcia množiny $N_\varepsilon$ – určuje všetky neterminály, ktoré možno priamo či nepriamo prepísať na prázdny reťazec.
- Úprava pravidiel gramatiky na základe množiny $N_\varepsilon$.
Poznámka
$N_\varepsilon$ predstavuje množinu neterminalov, ktoré sa počas derivácie možu "stratiť".
Najprv predstavíme algoritmus konštrukcie množiny $N_\varepsilon$.
Algoritmus 3. Konštrukcia množiny neterminálov, ktoré môžu generovať $\varepsilon$.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$.
Výstup: Množina neterminálov $N_\varepsilon$, ktoré môžu generovať $\varepsilon$.
- $N_\varepsilon\leftarrow\emptyset$;
- do
- $\acute{N}_\varepsilon\leftarrow N_\varepsilon$;
- $N_\varepsilon\leftarrow\acute{N}_\varepsilon \cup \{A\;|\;A\to\alpha\in P \;\land\;\alpha\in\acute{N}_\varepsilon^{*}\}$;
- while $N_\varepsilon\neq\acute{N}_\varepsilon$
Postup úpravy prepisovacích pravidiel gramatiky na základe množiny $N_\varepsilon$ prezentuje nasledujúci Algoritmus 4.
Algoritmus 4. Úprava pravidiel gramatiky na základe množiny $N_\varepsilon$.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$ a množina neterminálov $N_\varepsilon$, ktoré môžu generovať $\varepsilon$.
Výstup: Bezkontextová gramatika $G'$ bez $\varepsilon$-pravidiel.
- $P'\leftarrow P$; kde $P'$ je nová množina prepisovacích pravidiel gramatiky $G'$
- for all $A \to \varepsilon \in P'$ do
- if $A\neq S$ then
- $P' \leftarrow P' - \{A \to \varepsilon\}$;
- end if
- if $A=S$ a $S$ sa vyskytuje na pravej strane nejakého pravidla z $P'$ then
- $P' \leftarrow P' - \{S \to \varepsilon\}$;
- $P' \leftarrow P' \cup \{S' \rightarrow S \mid \varepsilon\}$; kde $S'$ je nový počiatočný symbol gramatiky $G'$
- end if
- end for
- for all $A \to \beta \in P'$ do
- if $\beta = \alpha_0B_1\alpha_1B_2\ldots B_k\alpha_k,$ kde $\alpha_0,\ldots, \alpha_k \in ((N-N_\varepsilon)\cup T)^*$, $B_1,\ldots, B_k\in N_\varepsilon$ then
- $P'\leftarrow P'\cup\{A \to \alpha_0X_1\alpha_1X_2\ldots X_k\alpha_k \mid X_i=B_i \;\lor\; X_i=\varepsilon,\text{ pre }i=1,2,\ldots,k\};$
- end if
- if $A\neq S$ then
- $P' \leftarrow P' - \{A \to \varepsilon\}$;
- end if
- end for
Slovný opis Algoritmu 4.
- Z gramatiky najprv odstránime všetky $\varepsilon$-pravidlá, výnimkou je len pravidlo pre začiatočný symbol. Ak $S \in N_\varepsilon$, potom:
- pravidlo $S \rightarrow \varepsilon$ ponecháme, ak sa $S$ nevyskytuje na pravej strane žiadneho pravidla,
- v opačnom prípade pravidlo $S \rightarrow \varepsilon$ odstránime a do gramatiky pridáme nový začiatočný symbol $S'$ s pravidlami $S' \rightarrow S \mid \varepsilon$.
- Postupne prechádzame jednotlivé pravidlá gramatiky. Ak sa na pravej strane vyskytuje nejaký neterminál z množiny $N_\varepsilon$, teda pravidlo bude tvaru: $$A \to \alpha_0B_1\alpha_1B_2\ldots B_k\alpha_k,$$ kde $\alpha_0,\ldots, \alpha_k \in ((N-N_\varepsilon)\cup T)^*$, $B_1,\ldots, B_k\in N_\varepsilon$, potom toto pravidlo nahradíme sadou pravidiel tvaru $$A \to \alpha_0X_1\alpha_1X_2\ldots X_k\alpha_k,$$ kde $X_i=B_i$ alebo $X_i=\varepsilon$, pre $i\in\{1,2,\ldots,k\}$. Ak by pritom vzniklo pravidlo tvaru $A \to \varepsilon$ a $A\neq S$, pravidlo do gramatiky nepridáme.
Poznámka
Druhý bod vyjadruje skutočnosť, že v prípade, ak sa na pravej strane pravidla vyskytuje viac než jeden neterminál z množiny $N_\varepsilon$, je nutné pridať pravidlá, ktorých pravé strany predstavujú všetky možné kombinácie bez týchto neterminálov.
Príklad
Nech je daná bezkontextová gramatika $G = (\{$S, A, B$\}$, $\{$a, b$\}$, $P$, S$)$, kde $P$ obsahuje nasledujúce pravidlá:
| ${}$ |
|---|
S $\to$ AB |
A $\to$ aA | $\varepsilon$ |
B $\to$ bB | $\varepsilon$. |
Odstráňte z tejto gramatiky $\varepsilon$-pravidlá.
Riešenie
Najprv konštuujeme množinu $N_\varepsilon$ aplikáciou Algoritmu 3.
Po inicializácii $N_\varepsilon$ prázdnou množinou sú hodnoty $N_\varepsilon$ a $\acute{N}_\varepsilon$ v jednotlivých iteráciách za ňou nasledujúceho logického cyklu v momente vyhonotenia jeho podmienky prehľadne prezentované v nasledujúcej tabuľke.
| Iterácia logického cyklu | Množina $N_\varepsilon$ | Množina $\acute{N}_\varepsilon$ | Podmienka cyklu $N_\varepsilon\neq\acute{N}_\varepsilon$ |
|---|---|---|---|
| 1. | $\{$A,B$\}$ |
$\emptyset$ | $\mathit{true}$ |
| 2. | $\{$A,B,S$\}$ |
$\{$A,B$\}$ |
$\mathit{true}$ |
| 3. | $\{$A,B,S$\}$ |
$\{$A,B,S$\}$ |
$\mathit{false}$ |
Následne pristúpime k úprave pravidiel.
- Odstránime všetky $\varepsilon$-pravidlá, v tomto prípade:
A$\to$ $\varepsilon$ aB$\to$ $\varepsilon$. - Upravíme pravidlo
S$\to$ABna sadu pravidiel:S$\to$AB | A | B |$\varepsilon\quad$ (pretožeA,B$\in N_\varepsilon$). - Upravíme pravidlo
A$\to$aAna sadu pravidiel:A$\to$aA | a$\quad$(pretožeA$\in N_\varepsilon$). - Upravíme pravidlo
B$\to$bBna sadu pravidiel:B$\to$bB | b$\quad$(pretožeB$\in N_\varepsilon$).
Výsledná gramatika je $G' = (\{$S, A, B$\}$, $\{$a, b$\}$, $P'$, S$)$, kde $P'$ obsahuje nasledujúce pravidlá:
| ${}$ |
|---|
S $\to$ AB | A | B | $\varepsilon$ |
A $\to$ aA | a |
B $\to$ bB | b. |
Odstránenie jednoduchých pravidiel a cyklov
Jednoduché pravidlo je pravidlo tvaru $A \to B$, kde $A$ a $B$ sú ľubovoľné neterminály. Intuitívne nám toto pravidlo hovorí, že neterminál $A$ sa môže správať rovnako ako neterminál $B$.
Bezkontextová gramatika bez cyklu je taká bezkontextová gramatika, kde nie je pre žiadny neterminál možná derivácia $A \Rightarrow^+ A$. Platí, že ak je gramatika bez jednoduchých a $\varepsilon$-pravidiel, potom je bez cyklu (obrátene platiť nemusí).
Odstránenie jednoduchých pravidiel z bezkontextovej gramatiky pozostáva z nasledujúcich krokov:
- Iteratívna konštruukcia množiny $N_A$ – neterminálnych symbolov, ktoré je možné odvodiť z daného neterminálu $A$.
- Úprava pravidiel gramatiky na základe množiny $N_A$.
Najprv predstavíme algoritmus konštrukcie množiny $N_A$.
Algoritmus 5. Konštrukcia množiny neterminálov, ktoré je možné odvodiť z daného neterminálu $A$.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$ a neterminál $A\in N$.
Výstup: Množina neterminálov $N_A$, ktoré je možné odvodiť z daného neterminálu $A$.
- $N_A\leftarrow\{A\}$;
- do
- $\acute{N}_A\leftarrow N_A$;
- $N_A\leftarrow\acute{N}_A \cup \{C\;|\;B\to C\in P \;\land\;B\in\acute{N}_A\}$;
- while $N_A\neq\acute{N}_A$
Teraz sa bližšie pozrieme na postup úpravu prepisovacích pravidiel gramatiky na základe množiny $N_A$ vyjadrený nasledujúcim Algoritmom 6.
Algoritmus 6. Úprava pravidiel gramatiky na základe množín $N_A$, pre každý neterminál $A\in N$.
Vstup: Bezkontextová gramatika $G=(N,T,P,S)$ a množina neterminálov $N_A$, ktoré je možné odvodiť z daného neterminálu $A$ pre každý neterminál $A\in N$.
Výstup: Bezkontextová gramatika $G'=(N,T,P',S)$ bez jednoduchých pravidiel.
- $P'\leftarrow P$
- odstráň všetky jednoduché pravidlá z $P'$
- for all $N_A$, kde $A \in N$ do
- for all $B\in N_A$ do
- for all $B\to \alpha \in P$ do
- if $B\to \alpha$ nie je jednoduché pravidlo then
- $P'\leftarrow P'\cup\{A\to\alpha\}$
- end if
- end for
- end for
- end for
Príklad
Nech je daná gramtika $G = (\{$S, A, B, C, D$\}$, $\{$a, b, c, d$\}$, $P$, S$)$, kde $P$ obsahuje nasledujúce prepisovacie pravidlá:
| ${}$ |
|---|
S $\to$ A | B |
A $\to$ C | aA | bS |
B $\to$ D | cB | dS |
C $\to$ bC | a |
D $\to$ dD | c. |
Transformujte gramatiku $G$ na bezkontextovú gramatiku $G'$, ktorá neobsahuje jednoduché pravidlá a pre ktorú platí: $L(G) = L(G')$.
Riešenie
Najprv potrebujeme konštruovať množiny $N_A$ pre každý neterminálny symbol $A\in N$.
Konštrukcia $N_S:$
| Iterácia logického cyklu | Množina $N_S$ | Množina $\acute{N}_S$ | Podmienka cyklu $N_S\neq\acute{N}_S$ |
|---|---|---|---|
| 1. | $\{$S,A,B$\}$ |
$\{$S$\}$ |
$\mathit{true}$ |
| 2. | $\{$S,A,B,C,D$\}$ |
$\{$S,A,B$\}$ |
$\mathit{true}$ |
| 3. | $\{$S,A,B,C,D$\}$ |
$\{$S,A,B,C,D$\}$ |
$\mathit{false}$ |
Konštrukcia $N_A:$
| Iterácia logického cyklu | Množina $N_A$ | Množina $\acute{N}_A$ | Podmienka cyklu $N_A\neq\acute{N}_A$ |
|---|---|---|---|
| 1. | $\{$A,C$\}$ |
$\{$A$\}$ |
$\mathit{true}$ |
| 2. | $\{$A,C$\}$ |
$\{$A,C$\}$ |
$\mathit{false}$ |
Konštrukcia $N_B:$
| Iterácia logického cyklu | Množina $N_B$ | Množina $\acute{N}_B$ | Podmienka cyklu $N_B\neq\acute{N}_B$ |
|---|---|---|---|
| 1. | $\{$B,D$\}$ |
$\{$B$\}$ |
$\mathit{true}$ |
| 2. | $\{$B,D$\}$ |
$\{$B,D$\}$ |
$\mathit{false}$ |
Konštrukcia $N_C:$
| Iterácia logického cyklu | Množina $N_C$ | Množina $\acute{N}_C$ | Podmienka cyklu $N_C\neq\acute{N}_C$ |
|---|---|---|---|
| 1. | $\{$C$\}$ |
$\{$C$\}$ |
$\mathit{false}$ |
Konštrukcia $N_D:$
| Iterácia logického cyklu | Množina $N_D$ | Množina $\acute{N}_D$ | Podmienka cyklu $N_D\neq\acute{N}_D$ |
|---|---|---|---|
| 1. | $\{$D$\}$ |
$\{$D$\}$ |
$\mathit{false}$ |
Z gramatiky odstránime všetky jednoduché pravidla, v tomto prípade:
S$\to$A,S$\to$B,A$\to$CaB$\to$D.
Následne použitím príslušnej množiny upravíme pravidlá pre jednotlivé neterminály.
-
Pravidlá pre
Supravíme pomocou množiny $N_S=\{$S,A,B,C,D$\}$:S$\to$aA | bS | cB | dS | bC | a | dD | c. -
Pravidlá pre
Aupravíme pomocou množiny $N_A=\{$A,C$\}$:A$\to$aA | bS | bC | a. -
Pravidlá pre
Bupravíme pomocou množiny $N_B=\{$B,D$\}$:B$\to$cB | dS | dD | c. -
Pravidlá pre
Cupravíme pomocou množiny $N_C=\{$C$\}$:C$\to$bC | a. -
Pravidlá pre
Dupravíme pomocou množiny $N_D=\{$D$\}$:D$\to$dD | c.
Výsledná gramatika bez jednoduchých pravidiel vyzerá nasledovne $G' = (\{$S, A, B, C, D$\}$, $\{$a, b, c, d$\}$, $P'$, S$)$, kde $P$ je množina nasledujúcich prepisovacích pravidiel:
| ${}$ |
|---|
S $\to$ aA | bS | cB | dS | bC | a | dD | c |
A $\to$ aA | bS | bC | a |
B $\to$ cB | dS | dD | c |
C $\to$ bC | a |
D $\to$ dD | c. |
Odstránenie ľavej rekurzie a ľavá faktorizácia
Posledné dve transformačné metódy zohrávajú kľúčovú úlohu pri tvorbe deterministických syntaktických analyzátorov. Jednou z najčastejších prekážok použitia bezkontextovej gramatiky v deterministickej analýze zhora nadol je výskyt ľavej rekurzie. Tento jav spôsobuje, že parser môže uviaznuť v nekonečnom odvodení, čo znemožňuje vytvorenie efektívneho analyzátora. Odstránenie ľavej rekurzie je preto nevyhnutným krokom, ktorý musí predchádzať samotnej syntaktickej analýze.
Neterminálny symbol $A$ je rekurzívny zľava, ak existuje derivácia $A \Rightarrow^+ A\beta$, kde $\beta \in (N \cup T)^*$. Rozlišujeme dva typy ľavej rekurzie:
- Priama ľavá rekurzia – ak $A \Rightarrow^1 A\beta$. V množine pravidiel preto musí existovať prepisovacie pravidlo tvaru $A \to A\beta$ (tento typ ľavej rekurzie je viditeľný priamo v pravidlách pre daný neterminál, napríklad $A \to Aa$).
- Nepriama ľavá rekurzia – ak $A \Rightarrow^k A\beta$, kde $k>1$. Tento typ ľavej rekurzie nie je viditeľný priamo v pravidlách pre daný neterminál, napríklad $A \to Ba$, $B \to Ac$.
Ľavo rekurzívna gramatika je taká gramatika, ktorá má aspoň jeden ľavo rekurzívny neterminálny symbol.
Pravidlá, ktoré sú zdrojom priamej ľavej rekurzie, je možné z gramatiky odstrániť nasledujúcou transformáciou: Pravidlo
$$A \rightarrow A\alpha \mid \beta \quad $$ možno zameniť za sadu pravidiel: $$ \begin{align*} A &\rightarrow \beta A' \mid \beta\\ A' &\rightarrow \alpha A' \mid \alpha, \end{align*} $$ kde $\alpha, \beta \in (N \cup T)^*, A \in N$.
Pre viac alternatív $\alpha, \beta $ možno schému intuitívne rozšíriť nasledovne. Predidlo $$A \rightarrow A\alpha_1 \mid \ldots \mid A\alpha_n \mid \beta_1 \mid \ldots \mid \beta_m $$ možno zameniť za sadu pravidiel: $$ \begin{align*} A &\rightarrow \beta_1 A' \mid \ldots \mid \beta_m A' \mid \beta_1 \mid \ldots \mid \beta_m\\ A' &\rightarrow \alpha_1 A' \mid \ldots \mid \alpha_n A' \mid \alpha_1 \mid \ldots \mid \alpha_n, \end{align*} $$ kde $\alpha_i, \beta_i \in (N \cup T)^*, A \in N$.
Príklad
Ostráňte priamu ľavú rekurziu z bezkontextovej gramatiky $G=(\{$X,Y$\}$,$\{$a,b,c$\}$,$P$,A$)$, kde $P$ je množina nasledujúcich pravidiel:
| ${}$ |
|---|
X $\to$ Xab | cY |
Y $\to$ b. |
Riešenie
Najprv v pravidlách gramatiky $G$ identifikujeme vzory z vyššie uvedenej schémy:
- $A =$
X, - $\alpha =$
ab, - $\beta =$
cY.
Následne možno pristúpiť k odstráneniu ľavej rekurzie zavedením nového pravo rekurzívneho neterminálu X', a to nasledovne:
| ${}$ |
|---|
X $\to$ cYX'$\;\;$| cY |
X' $\to$ abX'$\;\;$| ab. |
Príklad
Transformujte gramatiku $G=(\{$E, T, F$\}$, $\{$+, *, (, )$\}$, $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
Gramatika obsahuje dva ľavo rekurzívne neterminály E a T. K odstráneniu ľavej rekurzie z prepisovacích pravidiel gramatiky preto pristúpime nahradením pravidiel
| ${}$ | ${}$ | ${}$ |
|---|---|---|
E $\to$ E+T | T, |
$\phantom{halohalohalo}$ | T $\to$ T*F | F |
dvojicami pravidiel:
| ${}$ | ${}$ | ${}$ |
|---|---|---|
E $\to$ TE'$\;\;$| T |
$\phantom{halohalohalo}$ | T $\to$ FT'$\;\;$| F, |
E' $\to$ +TE'$\;\;$| +T |
${}$ | T' $\to$ *FT'$\;\;$| *F, |
na základe čoho získame gramatiku $G'=(\{$E,E',T,T',F$\}$,$\{$+,*,(,)$\}$,$P'$,E$)$, kde $P'$ je množina nasledujúcich pravidiel:
| ${}$ |
|---|
E $\to$ TE'$\;\;$| T |
E' $\to$ +TE'$\;\;$| +T |
T $\to$ FT'$\;\;$| F |
T' $\to$ *FT'$\;\;$| *F |
F $\to$ cislo | (E). |
Ľavá faktorizácia, teda vyčlenenie spoločných predpôn pravých strán pravidiel s rovnakou ľavou stranou, je transformačný postup, ktorého cieľom je upraviť gramatiku do tvaru vhodného pre deterministickú syntaktickú analýzu zhora nadol. ktorá predstavuje nasledujúci princíp transformácie gramatiky. Princíp tejto transformácie možno formalizovať nasledovne. Pravidlá
${}$ $A \to \alpha X\beta_1$ $A \to \alpha Y \beta_2$
nahradíme sadou pravidiel:
${}$ $A \to \alpha B$ $B \to X \beta_1$ $B \to Y \beta_2$.
Príklad
Transformujte výslednú gramatiku z predchádzajúceho príkladu prostrednítvom ľavej faktorizácie.
Riešenie
Ľavou faktorizáciou výslednej gramatiky z predchádzajúceho príkladu získame gramatiku $G''=(\{$E,E',E'',T,T',T'',F$\}$,$\{$+,*,(,)$\}$,$P''$,E$)$, kde možina pravidiel $P''$ pozostáva z nasledujúcich pravidiel:
| ${}$ |
|---|
E $\to$ TE'' |
E'' $\to$ E'$\;\;$|$\;\;\varepsilon$ |
E' $\to$ +TE'' |
T $\to$ FT'' |
T'' $\to$ T'$\;\;$|$\;\;\varepsilon$ |
T' $\to$ *FT'' |
F $\to$ cislo | (E). |
Túto gramatiku možno ešte zjednodušiť. Keďže pre neterminál E' existuje len jediné prepisovacie pravidlo, všetky výskyty tohto neterminálu na pravej strane pravidiel možno nahradiť pravou stranou tohto pravidla teda vetnou formou + T E'' (obdobne postupujeme pri netreminálnom symbole T'). Výsledkom je gramatika $G^F=(\{$E,E'',T,T'',F$\}$,$\{$+,*,(,)$\}$,$P^F$,E$)$, kde $P^F$ je množina nasledujúcich pravidiel:
| ${}$ |
|---|
E $\to$ TE'' |
E'' $\to$ +TE''$\;\;$|$\;\;\varepsilon$ |
T $\to$ FT'' |
T'' $\to$ *FT''$\;\;$|$\;\;\varepsilon$ |
F $\to$ cislo | (E). |
Krok 2
Úloha 2.1
Transformujte bezkontextovú gramatiku $G$ na gramatiku $G'$, ktorá nebude obsahovať nadbytočné symboly a $L(G) = L(G')$.
Gramatika $G$ je daná nasledovne: $G = (\{$S, A, B, C, D$\}$, $\{$0,1$\}$, $P$, S$)$, kde $P$ je množina prepisovacích pravidiel:
| ${}$ |
|---|
S $\to$ 0S | 1D | $\varepsilon$ |
A $\to$ 0CB | 0AD |
B $\to$ 1B | 110 |
C $\to$ 1CC | 0A1B |
D $\to$ 11A | 0D00 | 1S | $\varepsilon$. |
Zobraziť riešenie
Odpoveď:
Úloha 2.2
Transformujte bezkontextovú gramatiku $G$ na gramatiku $G'$, ktorá nebude obsahovať $\varepsilon$-pravidlá a $L(G) = L(G')$. Gramatika $G$ je daná nasledovne: $G = (\{$S, A, B, C, D$\}$, $\{$0,1,2,3$\}$, $P$, S$)$, kde $P$ je množina prepisovacích pravidiel:
| ${}$ |
|---|
S $\to$ 0A0 | 0 |
A $\to$ BC | 2 | CCC |
B $\to$ 1C | 3D | $\varepsilon$ |
C $\to$ A3 | $\varepsilon$ |
D $\to$ A | 2. |
Zobraziť riešenie
Odpoveď:
Úloha 2.3
Transformujte bezkontextovú gramatiku $G$ na gramatiku $G'$, ktorá nebude obsahovať jednoduché pravidlá a $L(G) = L(G')$.
Gramatika $G$ je daná nasledovne: $G = (\{$S, A, B, C$\}$, $\{$a, b$\}$, $P$, S$)$, kde $P$ je množina prepisovacích pravidiel:
| ${}$ |
|---|
S $\to$ aBa | A |
A $\to$ aA | B |
B $\to$ bB | AB | C | $\varepsilon$ |
C $\to$ bA | b. |
Zobraziť riešenie
Odpoveď:
Krok 3
V tejto časti sa pozrieme na to, ako výskyt ľavej rekurzie ovplyvňuje praktickú použiteľnosť gramatiky pri tvorbe rekurzívneho syntaktického analyzátora. Na konkrétnom príklade ukážeme, prečo ľavá rekurzia spôsobuje problémy, ako sa odstraňuje a aký dopad má výsledná transformácia na štruktúru gramatiky, priebeh parsovania a následnú interpretáciu.
Aby sme lepšie porozumeli súvislostiam, nadviažeme na predchádzajúce cvičenia, kde sme podrobne opísali proces návrhu gramatiky jazyka pre implementáciu jeho syntaktického analyzátora a interpretera metódou rekurzívneho zostupu. Ukázali sme, že asociativitu operátorov možno vyjadriť už na syntaktickej úrovni špecifikácie jazyka t. j. v gramatike prostredníctvom rekurzie (ľavú asosiativitu ľavou rekurziou a pravú asociativitu pravou rekurziou). V skutočnosti sme však pre vyjadrenie ľavej asociativity operátorov v gramatike v EBNF a v jej následnej implementácii nepoužili ľavú rekurziu ale iteráciu (opakovanie):
| ${}$ |
|---|
E ::= T { "op" T } |
def E():
T()
# iterácia
while current_symbol in FIRST(op):
if current_symbol == op:
get_next_symbol()
else: error()
T()
na rozdiel od pravej asosiativity, ktorú sme vyjadrili a implementovali skutočnou pravou rekurziou:
| ${}$ |
|---|
E ::= T [ "op" E ] |
def E():
T()
if current_symbol in FIRST(op):
if current_symbol == op:
get_next_symbol()
else: error()
E() # rekurzívne volanie
Pozrime sa preto na dôvod, ktorý viedol k tejto zmenne. Ak vychádzame z vyjadrenia ľavej asociativity operátorov v rámci gramatiky v BNF:
| ${}$ |
|---|
<E> ::= <E> op <T> | <T> |
a transformujeme ju do EBNF tak, aby bola zachovaná ľavá rekurzia, získame gramatiku:
| ${}$ |
|---|
E ::= [ E "op" ] T. |
Problém však nastáva pri jej implementácii, keďže procedúra zodpovedajúca netreminálnemu symbolu E bude ako prvú procedúru v jej tele volať seba samú bez prvotného spracovania (redukcie) vstupu, čo vedie k problému nekonečnej rekurzie.
Úloha 3.1
Pozmente gramatiku aj implementáciu rekurzívneho syntaktického analyzátora jazyka aritmetických výrazov z predchádzajúceho cvičenia tak, aby ľavo asociatívne operátory skutočne využívali ľavú rekurziu a presvedče sa o zlyhaní tohto prístupu.
Úloha 3.2
Je možné problém ľavo rekurzívnej implementácie ľubovoľnej ľavo asociatívnej operácie na báze gramatiky v BNF vyriešiť doplňujúcou špecifikáciou precedencie pravidiel? Svoje tvrdenie detailne zdôvodnite.
Ľavú rekurziu však môžeme z gramatiky odstrániť tak, že ju nahradíme pravou rekurziou, čím zároveň eliminujeme potrebu využitia iteratívnych konštrukcií jazyka pri implementácii jeho syntaktického analyzátora metódou rekurzívneho zostupu. Takto navrhnutý analyzátor sa následne stáva plne rekurzívnym — celý proces spracovania vstupu je realizovaný výlučne pomocou volaní rekurzívnych funkcií, bez použitia cyklov.
Teraz pristúpime k návrhu plne rekurzívneho syntaktického analyzátora jazyka aritmetických výrazov. Vychádzať budeme z vyššie uvedenej tranformácie gramatiky tohto jazyka odstránením ľavej rekurzie. Výsledok tejto transtormácie prevedieme do EBNF, čím získame gramatiku $G=(\{$E,E',T,T',F$\}$,$\{$+,*,(,)$\}$,$P'$,E$)$, kde $P'$ je množina nasledujúcich pravidiel:
| ${}$ | ${}$ |
|---|---|
E |
::= T [E'] |
E' |
::= "+" T [E'] |
T |
::= F [T'] |
T' |
::= "*" F [T'] |
F |
::= cislo | "(" E ")". |
Úloha 3.3
Implementujte syntaktický analyzátor jazyka aritmetických výrazov metódou rekurzívneho zostupu na báze vyššie vyššie uvedenej gramatiky.
Nevýhodou tohto prístupu je však skutočnosť, že často vedie k strate prirodzenej väzby medzi syntaktickou štruktúrou a sémantickým významom výrazov, čo prezentuje aj nasledujúce porovnanie odvodzovacích stromov toho istého aritmetického výrazu.
1+2*3 pôvodnej gramatiky
1+2*3 transformovanej gramatiky
Samotný proces syntaxou riadenej interpretácia výrazov si následne vyžaduje modifikáciu, ktorej cieľom je zabezpečiť korektné priradenie významu jednotlivým výrazom. Nasledujúci obrázok ilustruje túto zmenu procesu itrerpretácie na konkrétnom príklade.
1+2*3 transformovanej gramatiky
Úloha 3.4
Transformujte syntaktický analyzátor z predchádzajúcej úlohy na interpretér. Inšpirujte sa schémou interpretácie konkrétneho aritmetického výrazu zachytenej na Obrázku 3.
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 zo siedmeho 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.