Méně známé skutečnosti o C a C++: Proč nemůže existovat LL(k) gramatika pro jazyk C

Od Petr Zemek, 2009-11-27

V tomto příspěvku naznačím důvod, proč nemůže pro jazyk C existovat LL(k) gramatika pro libovolné konečné k. Příspěvek si asi užijí jen ti, kteří mají určité základy z oblasti teorie formálních jazyků a překladačů, ale do žádných složitých detailů se pouštět nebudu.

LL gramatiky a parsery

Nejdříve pár ujasnění na úvod. Neformálně, LL(k) gramatika je taková gramatika, pro kterou lze vytvořit LL tabulku, která pro každý nonterminál (který bude na vrcholu zásobníku u LL parseru) a k terminálů (tokenů ze vstupu) obsahuje nejvýše jedno pravidlo, které se má aplikovat. Konstantě k se říká lookahead (bez překladu) a znamená počet tokenů, které jsou načteny ze vstupu k tomu, aby se mohl parser rozhodnout, které pravidlo použít. Obvykle se používají LL(1) gramatiky, kdy se rozhodujeme pouze podle jednoho aktuálního tokenu na vstupu (historicky především z důvodu nižší paměťové náročnosti; dnes už existují pokročilejší techniky, kdy lze uvažovat i větší k). Lze dokázat, že třída jazyků definovaná LL(k) gramatikami je vlastní podtřída třídy jazyků definovaná LL(k+1) gramatikami. Ale to jen tak na okraj.

Důvod

Důvodem je problém známý pod názvem "dangling else" (bez překladu). Obecně jde o to, že k fragmentu kódu

if a if b printf("b") else printf("!b")

je možné vygenerovat dva derivační stromy v závislosti na tom, ke kterému if příkazu se váže větev else. Tento problém je v jazyce C řešen tak, že se else větev váže na poslední otevřený if. To lze podchytit následující gramatikou:

selection_statement:
     IF '(' expression ')' statement
  |  IF '(' expression ')' statement ELSE statement
  ;

Tato gramatika ale není LL(1), protože pro nonterminál selection_statement a token IF máme dvě pravidla. Jako řešení by se nabízelo použít vytýkání (factorization), které ale nic nevyřeší, protože výsledná gramatika opět nebude LL(1). K tomu, abychom tento problém vyřešili, by bylo třeba načíst dostatečný počet tokenů ze vstupu, abychom se podle toho mohli rozhodnout, zda bude následovat else větev či ne a podle toho zvolit pravidlo (neboli zvolit dostatečně velké k). Po krátkém zamyšlení ale přijdete na to, že podmínka a tělo podmíněného příkazu může mít teoreticky neomezenou velikost, takže bychom potřebovali nekonečné k. Tato idea tedy ukazuje důvod, proč pro jazyk C nemůže existovat LL(k) gramatika pro konečné k.

Závěrečné poznámky

  • Výše uvedená gramatika je LR(1) (třída jazyků definovaná LR(1) gramatikami je striktně větší než třída obsahující všechny LL(k) jazyky pro libovolné k). LR parsery s tímto problém nemají.
  • Ryze technicky (formálně) nemůže pro jazyk C existovat žádná bezkontextová gramatika, protože se jedná o jazyk kontextový (nelze zachytit sémantiku a sémantické kontroly na úrovni bezkontextové gramatiky). Toto se ale v praxi neuvažuje, protože sémantická kontrola je řešena na úrovní parseru.
  • Výše zmíněný problém s konstrukcí if/else lze vyřešit hackem do vytvořené LL tabulky, což je ale ryze implementační řešení (naznačení pro zájemce).
Obsah tohoto pole je soukromý a nebude veřejně zobrazen.

Filtrované HTML (využíváno)

  • Povolené HTML značky: <a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <table>
  • Zvýraznění syntaxe kódu lze povolit přes následující značky: <code>, <blockcode>, <bash>, <c>, <cpp>, <haskell>, <html>, <java>, <javascript>, <latex>, <perl>, <php>, <python>, <ruby>, <rust>, <sql>, <text>, <vim>, <xml>, <yaml>.
  • Řádky a odstavce se zalomí automaticky.
  • Webové a e-mailové adresy jsou automaticky převedeny na odkazy.
CAPTCHA
8 + 2 =
Vyřešte tento jednoduchý matematický příklad a vložte výsledek. Např. pro 1+3 vložte 4.
Nějak se mi tady rozmohl spam, takže poprosím o ověření.