Úvod do formálnych gramatík.

Ciele

  1. Oboznámiť sa s definíciami základných pojmov z oblasti formálnych gramatík.
  2. Definovať Backusova-Naurova forma (BNF) a rozšírenú Backusova-Naurova forma (EBNF) špecifikácie formálnych gramatík.
  3. Zostrojiť formálnu gramatiku podľa špecifikácie jazyka.
  4. Oboznámiť so špecifikáciou priority a asociativity operácií na úrovni syntaktického modelu jazyka.

Úvod

V tomto cvičení sa oboznámime s pojmom formálna gramatika, ktorý predstavuje presný a formálne definovaný nástroj na opis syntaktickej štruktúry formálnych jazykov. Uvedieme dva základné notačné štandardy zápisu bezkontextových gramatík, a to Backus-Naurovu formu (BNF) a rozšírenú Backus-Naurovu formu (EBNF).

Taktiež sa budeme venovať návrhu gramatiky na základe zadanej špecifikácie jazyka. Tento proces zahŕňa najmä vhodnú voľbu tvaru gramatických pravidiel a úrovní ich vnorenia tak, aby bolo zabezpečené jednoznačné spracovanie výrazov s operátormi rôznej priority a asociativity. Ukážeme si, ako možno pomocou gramatiky vyjadriť prioritu a asociativitu operácií v jazyku, čo je nevyhnutné napríklad pri spracovaní aritmetických výrazov.

Zvládnutie týchto aspektov je dôležité nielen pre správnu formálnu definíciu jazyka, ale aj pre praktické aplikácie, ako sú syntaktická analýza zdrojového kódu či návrh efektívnych parserov.

Postup

Krok 1

Formálne gramatiky predstavujú poslednú z pokročilých metód špecifikácie jazyka, ktorou sa budeme v rámci tohto predmetu zaoberať. Ide o tzv. generatívny spôsob špecifikácie jazyka, autorom ktorého je americký lingvista Noam Chomsky. Teraz pristúpime k definícii formálnej gramatiky a základných pojmov s ňou spojených.


Formálna gramatika je usporiadaná štvorica $G=(N,T,P,S)$, kde:

  • $N$ je konečná neprázdna množina neterminálnych symbolov (neterminálov),
  • $T$ je konečná množina terminálnych symbolov (terminálov), pričom $N \cap T = \emptyset$,
  • $S$ je začiatočný alebo štartovací symbol gramatiky, pričom $S \in N$,
  • $P$ je množina (prepisovacích) pravidiel, ktorá je konečnou podmnožinou množiny $(N \cup T)^* N (N \cup T)^* \times (N \cup T)^*$.

Neterminálne symboly sú pomocné symboly používané v pravidlách gramatiky. Tieto symboly sú postupne nahrádzané (substituované) počas procesu zvaného derivácia alebo odvodzovanie. Štandardne sa označujú veľkými písmenami latinskej abecedy, napríklad A, B, C, alebo sú uzavreté v lomených zátvorkách, napríklad <program>, <term>, <boolen-term> (BNF štandard).

Terminálne symboly, taktiež označované ako lexémy, tvoria abecedu výsledného jazyka. Zvyčajne sa zapisujú malými písmenami latinskej abecedy, napr. a, b, c, alebo sa uvádzajú v úvodzovkách, napríklad "while","if","+" (EBNF štandard).

Prepisovacie pravidlá definujú, ako môžu byť reťazce pozostávajúce z terminálnych a neterminálnych symbolov prepisané na iné reťazce. Tieto reťazce, teda prvky množiny $(N \cup T)^*$, budeme vo všeobecnosti označovať písmenami gréckej abaecedy.

Poznámka

Výraz $(N \cup T)^* N (N \cup T)^* \times (N \cup T)^*$ definuje množinu usporiadaných dvojíc $(\alpha,\beta)$, kde:

  • pravá strana pravidla $\alpha$ je reťazec z množiny $(N \cup T)^* N (N \cup T)^*$, ktorý obsahuje aspoň jeden neterminálny symbol,
  • ľavá strana pravidla $\beta$ je ľubovoľný reťazec z množiny $(N \cup T)^*$ (môže byť aj prázdny) zložený z terminálov a neterminálov.

Na množine vetných foriem $(N \cup T)^*$ definujeme binárnu reláciu krok odvodenia (alebo reláciu derivácie) $\Rightarrow\subseteq(N \cup T)^+\times(N \cup T)^*$ nasledujúcim spôsobom:

$\alpha \beta \gamma \Rightarrow \alpha \delta \gamma\ $ vtedy a len vtedy, keď v $P$ existuje pravidlo $\beta \rightarrow \delta$, pričom $\alpha, \gamma, \delta \in (N \cup T)^*$ a $\beta \in (N \cup T)^* N (N \cup T)^*$.

Poznámka

Vyššie uvedenú formálnu definíciu binárnej relácie kroku odvodenia možno interpretovať nasledovne – dva reťazce terminálnych a neterminálnych symbolov sú v relácii derivácie práve vtedy, keď v množine prepisovacích pravidiel $P$ existuje pravidlo tvaru $\beta \rightarrow \delta$ také, že $\beta$ je súčasťou prvého reťazca a $\delta$ je súčasťou druhého reťazca.

Nech $\alpha_i \in (N \cup T)^* \text{ pre } i = 0,1,\ldots,k $. Potom hovoríme, že reťazec $ \alpha_k $ sa dá odvodiť z reťazca $ \alpha_0 $ (na $k$ krokov), ak platí, že $$ \alpha_0 \Rightarrow \alpha_1 \Rightarrow \dots \Rightarrow \alpha_k .$$

Postupnosť reťazcov $ \alpha_0 \Rightarrow \alpha_1 \Rightarrow \dots \Rightarrow \alpha_k $ sa nazýva odvodenie (alebo derivácia) reťazca $ \alpha_k $ z reťazca $ \alpha_0 $. Vyjadrovať ju budeme vo forme mocniny relácie $ \Rightarrow $ v tvare $ \alpha_0 \Rightarrow^k \alpha_k $, kde číslo $k$ predstavuje dĺžku odvodenia.

V odvodení pritom vystupujú dva typy reťazcov, ktoré sa od seba líšia mierou úplnosti odvodenia:

  • Slovo alebo tiež veta jazyka $w$ je reťazec zložený výlučne z terminálnych symbolov, $w\in T^*$ odvoditeľný zo začiatočného symbolu gramatiky $S$. Jazyk $L(G)$ generovaný formálnou gramatikou $G$ je množina všetkých takýchto slov, pre ktoré platí: $$L(G)=\{w \mid S \Rightarrow^* w \land w\in T^*\}.$$
  • Vetná forma $\alpha$ je reťazec $\alpha \in (N \cup T)^*$ zložený z terminálnych aj neterminálnych symbolov, ktorý je odvoditeľný zo začiatočného symbolu $S$.

Poznámka

Uvedomme si, že každé slovo je zároveň vetnou formou, no nie každá vetná forma je slovom.


Na základe tvaru prepisovacích pravidiel je možné definovať hierarchiu tried gramatík a jazykov, ktorú označujeme pojmom Chomskeho hierarchia (CHH).

Nech $\alpha$ patrí množine reťazcov terminálnych a neterminálnych symbolov obsahujúcich aspoň jeden neterminálny symbol $(N \cup T)^* N (N \cup T)^*$; $\alpha_1, \alpha_2, \beta$ patria množine reťazcov terminálnych a neterminálnych symbolov $(N \cup T)^*$; $A,B$ patria množine neterminálnych symbolov $N$ a $b$ patrí množine terminálnych symbolov $T$. Na základe tvaru prepisovacích pravidiel potom rozlišujeme jednotlivé triedy formálnych gramatík a jazykov preyentované v nasledujúcej tabuľke.

Typ jazyka Typ gramatiky Názov typu gramatiky Tvar pravidiel
rekurzívne vyčísliteľné $G_0$ frázová gramatika $ \alpha \to \beta $
kontextové $G_1$ kontextová gramatika $ \alpha_1 A \alpha_2 \to \alpha_1 \beta \alpha_2 $, $\beta \neq \varepsilon$
bezkontextové $G_2$ bezkontextová gramatika $ A \to \beta $
regulárne $G_3$ regulárna gramatika $ A \to bB $, $ A \to b $

Upozornenie

Všimnime si, že podmienka $\beta \neq \varepsilon$ v prípade kontextových gramatíka a prípustné tvary pravidiel bezkontextových gramatík vylučujú možnosť generovať prázdny reťazec ako platné slovo jazyka. Výnimku však predstavuje situácia, keď je neterminálny symbol $A$ zároveň začiatočným symbolom gramatiky. To umožňuje generovanie prázdneho reťazca $\varepsilon$ avšak len priamou deriváciou zo začiatočného symbolu.

Medzi jazykmi generovanými jednotlivými typmi gramatík platí nasledujúci vzťah: $$ L(G_3) \subseteq L(G_2) \subseteq L(G_1) \subseteq L(G_0) .$$

Vizualizácia Chomskeho hierarchie jazykov
Obr. 1: Vizualizácia Chomskeho hierarchie jazykov

Poznámka

Všimnime si, že čím je gramatika na nižšej úrovni v Chomskeho hierarchii, tým viac obmedzení je kladených na jej prepisovacie pravidlá. Tieto obmedzenia majú iteratívny (kumulatívny) charakter v smere zhora nadol. Ľubovoľný typ gramatiky preto spĺňa aj všetky obmedzenia kladené na gramatiky na vyšších úrovniach v Chomskeho hierarchii, napríklad všetky regulárne gramatiky sú taktiež bezkontextové, kontextové a frázové.

Upozornenie

V prípade klasifikácie gramatík a jazykov do jednej z tried Chomskeho hierarchie budeme vždy uvažovať najnižšie položenú triedu, do ktorej možno gramatiku resp. jazyk klasifikovať.


Lineárnu deriváciu slova je možné zapísať taktiež pomocou orientovanej stormovej štruktúry, ktorá sa nazýva strom odvodenia (derivačný strom) (z angl. parse tree) a pre ktorú platia nasledujúce pravidlá:

  • každý vrchol stromu je ohodnotený symbolom z množiny $ N\cup T_\varepsilon$,
  • koreň stromu je ohodnotený začiatočným symbolom $S$,
  • ak má nejaký vrchol stromu potomkov, potom je ohodnotený symbolom z množiny $N$,
  • ak $X_1,X_2,\dots,X_k$​ sú ohodnotenia priamych potomkov vrcholu $A$, potom v $P$ musí existovať pravidlo $ A \to X_1,X_2,\dots,X_k $,
  • listy stromu sú ohodnotené symbolmi z množiny $T_\varepsilon$,
  • slovo $w \in T^*$ sa dá získať zreťazením ohodnotení listov stromu zľava doprava.

Okrem stromu odvodenia poznáme aj strom abstraktnej syntaxe (AST) (z angl. abstract syntax tree), ktorý predstavuje štruktúrovanú reprezentáciu syntaxe programovacieho jazyka alebo výrazu, zachytávajúcu podstatnú sémantickú štruktúru bez zachovania všetkých syntaktických detailov. Pre štruktúru AST platia nasledujúce pravidlá:

  • každý vrchol stromu reprezentuje operáciu, príkaz alebo konštrukciu jazyka,
  • koreň stromu reprezentuje hlavnú operáciu alebo konštrukciu programu,
  • vnútorné uzly predstavujú operácie alebo štruktúry (napríklad +, *, if, while),
  • listy stromu sú tvorené operandmi – terminálnymi symbolmi (napríklad číselné literály, identifikátory premenných),
  • vyjadruje hierarchiu operácií a prioritné vzťahy medzi operáciami,
  • neobsahuje neterminálne symboly ani pomocné syntaktické prvky (zátvorky, bodkočiarky, atď.),
  • je jazykovo špecifický a závisí od sémantiky cieľového jazyka.

Príklad

Uvažujme o gramatike $G=(\{$E,T,F$\},\{$+,*,id,(,)$\}, P,$E$)$ kde množinu $P$ tvoria nasledujúce prepisovacie pravidlá:

${}$ ${}$
($1$) E $\to$ E + T
($2$) E $\to$ T
($3$) T $\to$ T ∗ F
($4$) T $\to$ F
($5$) F $\to$ (E)
($6$) F $\to$ id.

Klasifikujete gramatiku $G$ do jednej z tried CHH. Odvoďte slovo id * (id + id) a zostrojte strom odvodenia a AST tohto slova.

Riešenie

Ako je možné vidieť, gramatika $G$ patrí do triedy bezkontextových gramatík. Vysvetlenie: Keďže všetky prepisovacie pravidlá majú na ľavej strane len jeden neterminál, do úvahy prichádzajú len bezkontextový a regulárny typ gramatiky. Napríklad pravidlo $5.$ však nespĺňa obmedzenia pre pravidlá regulárnej gramatiky a daná gramatika je preto bezkontextová.

Teraz pristúpme k odvodeniu slova. Každé odvodenie slova začína zo štartovacieho symbolu, v tomto prípade ide o neterminálny symbol E.

${}$ ${}$
$\phantom{\overset{(2)}{\Rightarrow}}$E $\overset{(2)}{\Rightarrow}$ T
${}$ $\overset{(3)}{\Rightarrow}$ T * F
${}$ $\overset{(4)}{\Rightarrow}$ F * F
${}$ $\overset{(6)}{\Rightarrow}$ id * F
${}$ $\overset{(5)}{\Rightarrow}$ id * (E)
${}$ $\overset{(1)}{\Rightarrow}$ id * (E + T)
${}$ $\overset{(2)}{\Rightarrow}$ id * (T + T)
${}$ $\overset{(4)}{\Rightarrow}$ id * (F + T)
${}$ $\overset{(6)}{\Rightarrow}$ id * (id + T)
${}$ $\overset{(4)}{\Rightarrow}$ id * (id + F)
${}$ $\overset{(6)}{\Rightarrow}$ id * (id + id)

Podľa tohto postupu odvodenia vieme zostrojiť derivačný strom.

Strom odvodenia slova id * (id + id)
Obr. 2: Strom odvodenia slova id * (id + id)

V tomto príklade môžeme pozorovať nasledujúce vlastnosti stromu odvodenia:

  • koreň stromu odvodenia je tvorený začiatočným symbolom gramatiky,
  • listy sú tvorené terminálnymi symbolmi,
  • aplikácia vybraného pravidla na konkrétny neterminál sa prejaví tým, že symboly na pravej strane použitého pravidla tvoria priamych potomkov prepísaného neterminálu (ľavej strany pravidla).

Abstraktný syntaktický strom je zjednodušená verzia derivačného stromu, ktorá odstraňuje neterminálne symboly a zachováva iba štruktúru výrazu.

Abstraktný syntaktický strom slova id * (id + id)
Obr. 3: Abstraktný syntaktický strom slova id * (id + id)

V porovnaní so stromom odvodenia môžeme pozorovať nasledujúce vlastnosti AST:

  • koreň stromu reprezentuje hlavnú operáciu alebo konštrukciu programu,
  • listy sú tvorené operandmi (numerály alebo premenné),
  • vnútorné uzly reprezentujú operácie alebo štruktúry (napr. +, *, if, while),
  • je jazykovo špecifický a závisí na sémantike cieľového jazyka (napr. priorita operátorov už nie je explicitne vyjadrená).

Krok 2

Backusova-Naurova forma (BNF) predstavuje kompaktnejší spôsob zápisu pravidiel bezkontextových gramatík v nasledujúcom tvare:

<neterminal> ::= vetna-forma-1 $\;|\;\ldots \;|\;$ vetna-forma-n.


Autorom pôvodnej formy je John Backus (Obrázok 4 vľavo), ktorý ju prvýkrát použil na opis syntaxe programovacieho jazyka ALGOL 60. Následne ju Peter Naur (Obrázok 4 vpravo) rozšíril a zdokonalil, čím získala dnešnú podobu.

John Backus a Peter Naur
Obr. 4: John Backus a Peter Naur

BNF má nasledujúce vlastnosti.

  • Neterminálne symboly, v BNF označované ako metajazykové premenné alebo taktiež syntaktické triedy/kategórie sú uzavreté v lomených zátvorkách, napr. <expr>.
  • Terminálne symboly sú zapísané ako samostatné znaky alebo reťazce, napr. + alebo 123.
  • Ľavá a pravá časť pravidla je oddelená symbolom ::= (alternatíva k symbolu $\to$).
  • Pravá strana pravidla môže predstavovať taktiež prázdny reťazec $\varepsilon$.
  • Symbol | označuje alternatívu, pomocou ktorej zoskupujeme pravidlá s rovnakou ľavou stranou.

BNF je teda formálny spôsob zápisu syntaxe programovacích a iných formálnych jazykov.

Príklad

Zapíšte prepisovacie pravidlá gramatiky z predchádzajúceho príkladu v BNF.

Riešenie

Využitím alternatívy | zoskupíme pravidlá s identickou ľavou stranou, neterminály uzatvoríme do lomených zátvoriek a namiesto symbolu $\to$ použijeme symbol ::=.

${}$ ${}$
<E> ::= <E> + <T> | <T>
<T> ::= <T> * <F> | <F>
<F> ::= id | (<E>)

Príklad

Zapíšte gramatiku pre zápis ľubovolného celého čísla v BNF.

Riešenie

${}$ ${}$
<cele-cislo> ::= <znamienko> <cislo>
<cislo> ::= <cifra> <cislo> | $\varepsilon$
<znamienko> ::= - | $\varepsilon$
<cifra> ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Keďže BNF využíva symboly <, >, |, ::= vo svojej vlastnej syntaxi, zabraňuje tak použitiu týchto znakov v syntaxi jazykov v nej definovaných. Rozšírená Backusova-Naurova forma (EBNF) sa vysporadúva s týmto problémom striktným uzavretím všetkých terminálov do úvodzoviek ("..." alebo '...'), na základe čoho možno lomené zátvorky pre neterminály vynechať. EBNF naviac implementuje aj štandardnú notáciu využívanú pri regulárnych výrazoch umožňujúcu ešte kompaktnejší zápis prepisovacích pravidiel:

  • pre voliteľnosť reťazca symbolov $\alpha$ využívame zápis [$\alpha$],
  • pre nula alebo viac opakovaní reťazca symbolov $\alpha$ využívame zápis {$\alpha$},
  • pre zoskupenie reťazcov symbolov $\alpha, \beta$ využívame zápis ($\alpha\beta$).

Príklad

Transformujte prepisovacie pravidlá gramatík z predchádzajúcich príkladov do EBNF.

Riešenie

  • Jazyk aritmetických výrazov s využitím voliteľnosti (vľavo) a s využitím opakovania (vpravo):
${}$ ${}$ ${}$ ${}$ ${}$
E ::= [E "+"] T $\quad\quad\quad\quad$ E ::= T {"+" T}
T ::= [T "*"] F ${}$ T ::= F {"*" F}
F ::= id | "(" E ")", ${}$ F ::= id | "(" E ")".

  • Jazyk celých čísel:
${}$ ${}$
cele-cislo ::= ["-"] cifra {cifra}
cifra ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".

Poznámka

EBNF je široko používaná pri definícii syntaxe programovacích jazykov a je štandardizovaná v ISO/IEC 14977. Existujú rôzne varianty EBNF s miernymi odchýlkami v notácii, základné princípy však zostávajú rovnaké.

Teraz sa bližšie pozrieme na postup ako transformovať (rozvinúť) gramatiku z kompaktnejšej EBNF do BNF. Tento postup rozvinutia gramatiky bude zohrávať kľučovú úlohu pri potlačení (eliminovaní) využitia iteračných konštrukcií jazyka (ako while) pri implementácii syntaxou riadeného interpretátora.

  • Odstránenie voliteľnosti – nech A $\in N$; $R_1,R,R_2$ sú regulárne výrazy nad $N\cup T$ a nech
${}$ ${}$
A ::= $R_1$ [ $R$ ] $R_2$

je pravidlo v EBNF. Pri transformácii tohto pravidla do BNF máme niekoľko možností. Jenou z nich je nahradiť voliteľný výraz [$R$] novým neternimálom X a zaviesť preň nasledujúce nerekurzívne pravidlá:

${}$ ${}$
<A> ::= $R_1$ <X> $R_2$
<X> ::= $R$ | $\varepsilon$,

Druhou množnosťou je pridanie novej alternatívy pravidla, ktorá vznikne vynechaním voliteľnosti [$R$] z už existujúcej alternatívy a odstránenie označenia voliteľnosti nasledujúcim spôsobom:

${}$ ${}$
<A> ::= $R_1$ $R$ $R_2$ | $R_1R_2$.

  • Odstránenie Kleeneho uzáveru – nech A $\in N$; $R_1,R,R_2$ sú regulárne výrazy nad $N\cup T$ a nech
${}$ ${}$
A ::= $R_1$ { $R$ } $R_2$

je pravidlo v EBNF. Pri transformácii tohto pravidla do BNF je Kleeneho uzáver { $R$ } potrebné nahradiť novým neterminálom X a vytvoriť jemu zodpovedajúce ľavo alebo pravo rekurzívne pravidlo:

${}$ ${}$ ${}$ ${}$ ${}$
<A> ::= $R_1$ <X> $R_2$ $\quad\quad\quad\quad$ <A> ::= $R_1$ <X> $R_2$
<X> ::= <X> $R$ | $\varepsilon$, ${}$ <X> ::= $R$ <X> | $\varepsilon$.

  • Odstránenie zoskupenia – nech A $\in N$; a nech $R_1,R,R_2$ sú regulárne výrazy nad $N\cup T$. Uvažujme pravidlo v EBNF v tvare
${}$ ${}$
A ::= $R_1$ ( $R$ ) $R_2$

Pri transformácii tohto pravidla do BNF zoskupenie ( $R$ ) nahradíme novým neternimálnym symbolom X spolu s jemu zodpovedajúcim pravidlom:

${}$ ${}$
<A> ::= $R_1$ <X> $R_2$
<X> ::= $R$

Príklad

Transformujte nasledujúce pravidlá gramatiky v EBNF do BNF:

${}$ ${}$
A ::= B {+ B [C]}.

Riešenie

Najprv odstráníme opakovanie zavedením nového rekurzívneho neterminálu X:

${}$ ${}$
<A> ::= <B> <X>
<X> ::= <X> + <B> [<C>] | $\varepsilon$.

Následne odstránime voliteľnosť zavedením nového nerekurzívneho neterminálu C-opt:

${}$ ${}$
<A> ::= <B> <X>
<X> ::= <X> + <B> <C-opt> | $\varepsilon$
<C-opt> ::= <C> | $\varepsilon$.

Úloha 2.1

V predchádzajúcich príkladoch sme zostojili gramatiku pre zápis ľubovolného celého čísla v BNF aj EBNF. Tieto gramatiky však generujú zápisy celých čísel s nepodstatnými nulami na začiatku (napríklad $00012$,$-001$ ) a taktiež záporné nuly ($-0$). Upravte tieto gramatiky (v BNF aj EBNF) tak aby ste odstánili tieto nedostatky.

Riešenie

Upravená gramatika v BNF:

${}$ ${}$
<cele-cislo> ::= 0 | <znamienko> <cifra-bez-0> <numeral>
<numeral> ::= <numeral> <cifra> | $\varepsilon$
<znamienko> ::= - | $\varepsilon$
<cifra> ::= 0 | <cifra-bez-0>
<cifra-bez-0> ::= 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9.

Upravená gramatika v EBNF:

${}$ ${}$
cele-cislo ::= "0" | ["-"] cifra-bez-0 {cifra}
cifra ::= "0" | cifra-bez-0
cifra-bez-0 ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9".

Úloha 2.2

Zostrojte gramatiku pre jazyk prepisovacích pravidiel v BNF a EBNF.

Riešenie

  • Syntax jazyka prepisovacích pravidiel v BNF: $G_{\mathit{BNF}}=(\{$bnf, pravidlo, prava-strana, postupnost, symbol, terminal, neterminal$\}, \{$<, >, ::=, |,$\varepsilon\}, P,$bnf$)$, kde $P$ je množina pozostávajúca z nasledujúcich pravidiel v EBNF:
${}$ ${}$
bnf ::= pravidlo {pravidlo}
pravidlo ::= "<" neterminal ">" "::=" prava-strana
prava-strana ::= postupnost {"|" postupnost}
postupnost ::= "$\varepsilon$" | symbol {symbol}
symbol ::= terminal | "<" neterminal ">".
  • Syntax jazyka prepisovacích pravidiel v EBNF: $G_{\mathit{EBNF}}=(\{$ebnf, pravidlo, prava-strana, regular, alternativa, postupnost, element, terminal, neterminal$\}, \{$::=, |, ", (, ), [, ], {, },$\varepsilon\}, P, $ebnf$)$, kde $P$ je množina pozostávajúca z nasledujúcich pravidiel v EBNF:

Upozornenie

V tomto prípade však musíme pristúpiť k uzatváraniu terminálnych symbolov do apostrofov, keďže úvodzovky predstavujú terminálny symbol.

${}$ ${}$
ebnf ::= pravidlo {pravidlo}
pravidlo ::= neterminal '::=' prava-strana
prava-strana ::= regular {'|' regular}
regular ::= '$\varepsilon$' | alternativa
alternativa ::= postupnost {'|' postupnost}
postupnost ::= element {element}
element ::= '"' terminal '"' | neterminal | '(' alternativa ')' |
$\phantom{::=}\ \ $ '[' alternativa ']' | '{' alternativa '}'.

Krok 3

V tomto kroku pristúpime ku konštrukcii formálnej gramatiky jazyka.

Príklad

Navrhnite gramatiku jazyka $L= \{a^nb^m\;|\; n,m \in \mathbb{N}_0\}$.

Riešenie

Jazyk $L$ predstavuje množinu všetkých reťazcov, ktoré sa skladajú z ľubovoľného počtu symbolov a (vrátane nuly) nasledovaných ľubovoľným počtom symbolov b (vrátane nuly).

Formálna gramatika pre tento jazyk je definovaná nasledovne, $G = (\{$S, A, B$\}, \{$a, b$\}, P, $S$)$, kde $P$ je nasledujúca⁠⁠⁠⁠⁠⁠ množina prepisovacích pravidiel v BNF:

${}$ ${}$
<S> ::= <A><B>
<A> ::= <A>a | $\varepsilon$
<B> ::= <B>b | $\varepsilon$.

Prepisovacie pravidlá v EBNF:

${}$ ${}$
S ::= {"a"}{"b"}.

Niekedy nemáme presnú špecifikáciu jazyka, ale máme k dispozícii len fragment jazyka. Na základe týchto fragmetov sa následne snažíme odvodiť gramatiku.

Príklad

Máme nasledujúce fragmenty jednoduchého jazyka na popis farieb:

  • červená farba,
  • modrá farba,
  • zelená farba,
  • svetločervená farba,
  • tmavomodrá farba.
    Na základe týchto fragmentov navrhnite gramatiku, ktorá bude generovať podobné výrazy.

Riešenie

  1. Identifikujeme vzory – pozrieme sa na štruktúru každého fragmentu:
  • všetky slová končia reťazcom farba,
  • pred reťazcom farba je vždy nejaká špecifikácia farby (červená, modrá, atď.),
  • niektoré špecifikácie farby majú predponu (svetlo-, tmavo-).
  1. Pomenujeme vzory pomocou neterminálov:
  • <farebny-vyraz> – celý výraz,
  • <odtien> – predpona ako svetlo- alebo tmavo- (môže byť prázdna),
  • <farba> – základná farba (červená, zelená, modrá).
  1. Vytvoríme prepisovacie pravidlá
  • v BNF zápise:
${}$ ${}$
<farebny-vyraz> ::= <odtien-farby> farba
<odtien_farby> ::= <odtien> <farba>
<odtien> ::= svetlo | tmavo | $\varepsilon$
<farba> ::= červená | zelena | ... | modrá,

  • v EBNF zápise:
${}$ ${}$
farebny-vyraz ::= odtien-farby "farba"
odtien_farby ::= [odtien] farba
odtien ::= "svetlo" | "tmavo"
farba ::= "červená" | "zelená" | ... | "modrá".

Úloha 3.1

Daná je gramatika:.

S $\to$ A B
A $\to$ a A | ε
B $\to$ b B | ε

Pre túto gramatiku:

  1. napíšte všetky kroky odvodenia reťazca "aabb",
  2. zostrojte strom odvodenia reťazca "aabb",
  3. určte, či reťazec "baba" patrí do jazyka generovaného touto gramatikou.

Úloha 3.2

Vytvorte gramatiku pre jazyk všetkých reťazcov nad abecedou $\{a, b\}$, ktoré obsahujú párny počet symbolov $a$.

Úloha 3.3

Vytvorte gramatiku pre jazyk všetkých reťazcov nad abecedou $\{a, b, c\}$, kde každý symbol $c$ je obklopený symbolmi $a$

Krok 4

Pri návrhu programovacích jazykov a vyhodnocovaní výrazov zohrávajú dôležitú úlohu dva základné koncepty, ktoré určujú spôsob, akým sa výrazy analyzujú a vyhodnocujú. Sú to

  • priorita a
  • asociativita operátorov.

Tieto vlastnosti možno vyjadriť už na úrovni syntaktického modelu jazyka (gramatiky),, čo vysvetlíme podrobnejšie v nasledujúcom kroku.

Špecifikácia priority operátorov

Priorita operátorov určuje, ktoré operácie majú prednosť pri vyhodnocovaní výrazov. Štandardne má napríklad operácia násobenia vyššiu prioritu ako operácia sčítania. Preto sa výraz 1+2*3 vyhodnotí na hodnotu $7$ a nie $9$. Pri syntaktickej špecifikácii priority operátorov v gramatike sa budeme riadiť nasledujúcimi pravidlami:

  1. Pre každú úroveň priority operácii definujeme samostatný neterminálny symbol.
  2. Pravidlá sa pre jednotlivé úrovne (neterminálne symboly) zreťazia tak, že smerom dovnútra (vnáranie k operandom) sa zvyšuje priorita.

Demonštrujme tento postup na nasledujúcom príklade gramatiky aritmetických výrazov.

Nech $G$ je gramatika $(\{$E, T, F$\},\{$+, *, (, )$\}$,$P$,E$)$, kde množina prepisovacích pravidiel v EBNF $P$ je daná nasledovne:

${}$ ${}$
E ::= T {"+" T}
T ::= F {"*" F}
F ::= cislo | "(" E ")".

Táto gramatika určuje nasledujúce úrovne priority:
  1. najvyššia úroveň priority: zátvorky (spracovávané v F),
  2. stredná úroveň priority: násobenie (spracovávané v T),
  3. najnižšia úroveň priority: sčítanie (spracovávané v E).

Porovnajme vplyv tvaru gramatiky na prioritu operátorov prostredníctvom zostrojenia stromov odvodenia pre ten istý syntaktický zápis aritmetického výrazu 1+2*3.

Prepisovacie pravidlá gramatiky $G_1$ so štandardnou prioritou operátorov

${}$ ${}$
E ::= T {"+" T}
T ::= F {"*" F}
F ::= cislo | "(" E ")"

Prepisovacie pravidlá gramatiky $G_2$ so zamenenou prioritou operátorov

${}$ ${}$
T ::= E {"*" E}
E ::= F {"+" F}
F ::= cislo | "(" T ")"

Strom odvodenia výrazu podľa gramatiky $G_1$
Obr. 5: Strom odvodenia výrazu podľa gramatiky $G_1$

Strom odvodenia výrazu podľa gramatiky $G_2$
Obr. 6: Strom odvodenia výrazu podľa gramatiky $G_2$

Špecifikácia asociativity operátorov

Asociativita operátorov zohráva dôležitú úlohu v prípadoch, keď sa v sekvencii vyskytne viacero operátorov s rovnakou prioritou. Určuje totiž, v akom poradí sa tieto operácie budú vyhodnocovať. Napríklad v aritmetických výrazoch:

  • Ľavá asociativita: výraz 5−3−2 sa vyhodnotí ako (5−3)−2=0.
  • Pravá asociativita: výraz 2^2^3 sa vyhodnotí ako 2^(2^3)=256. Doplníme, že ľavá asociativita by nebola pri umocňovaní užitočná – rovnaký výsledok sa dá dosiahnúť vďaka pravidlám pre mocniny pomocou súčinu exponentov (2^2)^3=2^(2*3).
  • Neasociatívne operátory: operátory porovnania.

Pri implementácii syntaktického analyzátora pomocou rekurzívnych funkcií sa asociativita vyjadruje tvarom pravidiel gramatiky, a to nasledovne:

Asociativita Tvar pravidla v EBNF Tvar pravidla v BNF
ľavá E ::= T { "op" T } <E> ::= <E> op <T> | <T>
pravá E ::= T [ "op" E ] <E> ::= <T> op <E> | <T>
neasociatívnosť E ::= T [ "op" T ] <E> ::= <T> op <T> | <T>

Poznámka

Všimnime si, že asociativita operátorov je vyjadrená rekurziou (rekurzívnym pravidlom). Ľavo asociatívne operátory sú definované prostredníctvom ľavo rekurzívnych pravidiel a pravo asociatívne operátory prostredníctvom pravo rekurzívnych pravidiel.

Porovnajme vplyv tvaru gramatiky na asociativitu operátorov prostredníctvom zostrojenia stromov odvodenia pre ten istý syntaktický zápis aritmetického výrazu 2^2^3.

Prepisovacie pravidlá gramatiky $G_1$ s ľavo asociatívnou operáciou umocňovania

${}$ ${}$
E ::= T {"^" T}
T ::= cislo | "(" E ")"

Prepisovacie pravidlá gramatiky $G_2$ s pravo asociatívnou operáciou umocňovania

${}$ ${}$
E ::= T ["^" E]
T ::= cislo | "(" E ")"

Strom odvodenia výrazu podľa gramatiky $G_1$
Obr. 7: Strom odvodenia výrazu podľa gramatiky $G_1$

Strom odvodenia výrazu podľa gramatiky $G_2$
Obr. 8: Strom odvodenia výrazu podľa gramatiky $G_2$

Príklad

Konštruujte gramatiku jazyka boolovských výrazov bez premenných, ktorá zohľadňuje štandardné priority a asociativity jednotlivých operátorov.

Riešenie

Najskôr špecifikujeme abecedu tohto jazyka, ktorá pozostáva z nasledujúcich terminálnych symbolov:

  • true, false – pravdivotné konštanty,
  • ! – unárna logická operácia negácie s najvyššou prioritou,
  • /\, \/, => – binárne logické operácie konjunkcie, disjunkcie a implikácie s postupne narastajúcou prioritou,
  • ( a ) – pomocné symboly.

Každý z operátorov jazyka boolovských výrazov predstavuje samostatnú úroveň priority, preto bude výsledná gramatika $G$ obsahovať päť neterminálnych symbolov. Upozorníme taktiež na skutočnosť, že operácia implikácie je štandardne považovaná za pravo asociatívnu, zatiaľ čo operácie konjunkcie a disjunkcie sú asociatívne. To znamená, že ich asociativita môže byť v gramatike vyjadrená ľubovoľne (ľavostranná alebo pravostranná) a nebude to mať žiadny vplyv na ich význam, teda ich výslednú pravdivostnú hodnotu.

$G=(\{$I,D,K,N,C$\},\{$true,false,!,/\,\/,=>,(,)$\},P,$I$)$, kde $P$ je nasledujúca množina pravidiel:

${}$ ${}$
I ::= D ["=>" I]
D ::= K {"\/" K}
K ::= N {"/\" N}
N ::= ["!"] C
C ::= "true" | "false" | "(" I ")".

Úloha 4.1

Ako sa zmení jazyk definovaný gramatikou z predchádzajúceho príkladu, ak pristúpime k úprave pravidla pre neterminálny symbol N na tvar N ::= "!" N | C ? V akom (množinovo-teoretickom) vzťahu sú tieto jazyky?

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 piateho 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.