Pod článkem o Jisonu se rozvinula zajímavá diskuse, navážu proto na tématiku stručným vysvětlením rozdílu mezi abstraktní a frázovou syntaxí.
Nejprve je třeba ujasnit si terminologii, která je tu mírně zmatená. Frázové stromy se tak nazývají podle frázové gramatiky, která se definuje pro každý formální jazyk. V literatuře je najdete také pod názvem „konkrétní“ stromy (jako protiklad k abstraktním). Abstraktní syntaktické stromy (anglicky Abstract Syntax Tree, odtud často používaná zkratka AST) se odvozují z frázových a lze je přímo použít pro interpretaci jazyka (nebo kompilaci).
Parsingu zpravidla předchází lexikální analýza, jejímž výstupem je seznam tokenů (u programovacích jazyků bývá gramatika jednoznačná). Počet tokenů je stejný jako počet listů frázového stromu. Listy jsou ohodnoceny vstupními tokeny, ostatní vrcholy stromu jsou ohodnoceny neterminály gramatiky, na jejímž základě byl strom vytvořen.
Abstraktní strom obsahuje menší počet vrcholů, je roven nanejvýš počtu tokenů. Převod z frázového na abstraktní strom je plně automatický a není ovlivněn sémantikou jazyka. Každému frázovému stromu přitom odpovídá právě jeden abstraktní, opačně to ale samozřejmě neplatí.
S přihlédnutím k sémantice lze definovat ekvivalenci abstraktních stromů, např. vzhledem k vlastnostem operace sčítání jsou abstraktní stromy pro výrazy (a+b)+c a a+(b+c) ekvivalentní, pro mínus to už ale neplatí. Frázový strom těchto výrazů bude mít 10 vrcholů, abstraktní 5.
Při převodu na abstraktní strom se určuje hlava pravidla, což je většinou operátor nebo jméno funkce (u volání funkce). V příkladu výše by to byl operátor +.
V závislosti na sémantice se abstraktní stromy po parsingu ve fázi optimalizace upravují (některé podstromy např. nejsou potřebné). Interpretace abstraktního stromu je přímočará (fungují tak interprety Javascriptu), při kompilace se používá většinou mezikód, a to buď explicitní (např. bajtkód v .NETu či Javě), nebo implicitní (vnitřní reprezentace kódu, jako např. v LLVM).
Frázové stromy při implementaci interpretu nebo překladače v podstatě nikdy nevidíme, jsou jen jakousi imaginární datovou strukturou, již generuje gramatika, vlastní parser generuje přímo AST. Navíc často splývá syntax a sémantika. Pokud si chcete vyzkoušet, jak se netriviální gramatika implemetuje, doporučuji třeba ANTLR.
Mluví se jen o jazycích odvozených z Fotranu, hádám? Nebo jsou obecně programovací jazyky podmnožinou formálních jazyků (ve Forthu, Common Lisp a i třeba TeX umožňují měnit co se jak lexikálně zpracuje)? Co je frázový a abstraktní strom třeba u Forthu? Je abstraktním stromem u Common Lispu míněn načtený sexp? Ten zdá se závisí na sémantice - např. hash-dot.
Když matení jazyků, tak pořádně! :)
@1 Ono to ani u jazyku odvozenych od Fortranu neni tak jednoduche. Uz treba s takovym c++ mate problem. Obecne se tomu rika "context sensitive lexer".
Nekde na netu muzete najit vyjadreni od Stroudapa proc nemuze v emacsu nikdy spravne fungovat auto-indent pro c++. Treba abyste mohl poznat co znamena "a " tak si musite behem parsovani urzovat nejakou symbol table a musite vedet jestli je "a" sablona, typ anebo promenna. Pokud lexer do parseru vrati, ze "a" je identifier tak to nestaci.
U DSL jazyku kde se hledi na zpetnou kompatibilitu je nejcastejsi problem, ze slovo muze mit specialni(reserved) vyznam, ale zaroven ale muze byt identifikatorem.
@3: Mám na mysli třeba tohle v Common Lispu:
#.(compute-something-at-read-time) - ve fázi čtení (reader podle normy jazyka, odpovídá parseru podle terminologie blogu) výsledek závisí na tom, co aktuálně dělá funkce compute-something-at-read-time. Odpovídá příslušnému frázovému stromu pouze jeden abstraktní strom, když funkce vrací třeba aktuální čas?
Nebo, budu-li brutální,
(hello) ; -> (hello)
(eval-when (:compile-toplevel :load-toplevel)
(set-syntax-from-char #\l #\Space))
(hello)) ; -> (he o)
Jakou formální gramatikou bez včleněné sémantiky jazyka se popíše, kdy se l má brát jako mezera a kdy jako písmeno?
@3: "Forth bohužel moc neznám."
Chyba! (U nekoho, kdo se zabyva navrhem jazyku.) Vrele doporucuji http://sourceforge.net/projects/rpoku/files/jonesforth/
Zabere to 2 odpoledne precist a osviti to.
@15: Moje rypani se netyka AST samotneho, ale cesty od zdrojoveho textu k AST a jeji jednoznacnosti.
Dost mozna ze je problem v mem nechapani definic vyrazu (ktere zde explitne nevidim, a google na frazovy strom vcelku mlci).
Da se to ukazat co je co na dvou trivialnich prikladech v ramci stareho dobreho Ccka (s preprocesorem), treba se mi to pouzita termnilogie objasni?
Pokud je na vstupu 1+__LINE__, co je v tomto pripade frazovy strom (a zejmena obsahuje token __LINE__, nebo cislo radku?), a co je AST ke kompilaci? (u vyse uvadenych jazyku je rizeni preprocesoru soucasti jazyka, pokud je problem s tim ze by preprocesor a jazyk mely byt oddelene)
A pokud vstup nasledujici?
--
#define b +
a b c
--
@17 Definice AST přímo plyne z toho převodu. Ten se definuje tak, že ve frázovém stromě dojde ke kontrakci hran mezi vrcholem reprezentovaným hlavou aplikovaného pravidla a výsledným neterminálem. Po kontrakci všech takových hran dostaneme AST a z postupu přímo plyne, že různé frázové stromy mohou dát stejný AST (u jednoznačné gramatiky to je ovšem 1:1). Ono se to asi lépe vysvětluje na přirozeném jazyce, klasický příklad Chomského s neterminály S, N, V, NP, VP, Det má např. pravidlo VP -> V NP, u kterého je hlavou V. Proto ve frázovém stromě kontrahujeme hranu mezi V a VP (V je preterminál a ten by se měl ještě kontrahovat s odpovídajícím listem). Podobně u pravidla S -> NP VP je hlavou VP a kontrahuje se tedy hrana mezi S a VP. Nakonec tedy dostaneme strom, jehož vrcholy jsou ohodnoceny původními listy a hrany mezi nimi představují závislosti (např. u prog. jazyka ve výrazu sčítání závisí operandy na vrcholu ohodnoceném operátorem, jenž je hlavou příslušného pravidla). Celý tento proces je nezávislý na konkrétním jazyce, k dalším transformacím už dochází s přihlédnutím k sémantice.
Ok, asi vyjasneno - pokud je frazovym stromem mysleno neco uz takhle tesne pred AST, tak pak se jedna opravdu o trivialni operaci. Nepochopil jsem to nejspis proto, ze parsery ktere jsem videl snad ani frazovy strom v tomto smyslu explicitne netvori, pripadne je to jen jiny pohled na datovou strukturu reprezentujici AST. Pak opravdu asi plati vetsina veci v blogu, ale na druhou stranu cesta od vstupnich tokenu (a za vstupni token pro C pokladam v uvedenem pripade porad __LINE__) k nemu je potom obecne vyrazne zajimavejsi.
A pokud tedy prechazime k prirozenemu jazyku - tady mi zase tou vyrazne zajimavejsi casti parsovani prijdou viceznacnosti a jejich reseni at uz na urovni urceni typu literalu (time flies like an arrow) nebo na urovni "zavorkovani" - coz predpokladam v modelu vyse opet dela cast mezi tokenizaci a frazovym grafem (grafy?) zajimavejsi.
Za navrh nastudovat si teorii grafu a formalnich jazyku dekuji, ale kdysi jsem je uz zahledl, a jako mnoho jinych veci mi pripadaji jako sada nastroju, ze ktery je kazdy (napriklad context-free gramatiky) v nekterych pripadech uzitecny a v jinych mene, a je dobre vedet ktery pripad je ktery.
@20 Ono to není těsně, pokud si nakreslíte ony stromy pro různé vstupy, dost se liší. Zajímavé je probrat si případy, kdy více frázových stromů produkuje jeden AST. Rozhodně nejsou jiným pohledem na AST, není tam relace 1:1.
Explicitní frázové stromy se používají v nástrojích (SableCC, ANTLR...) při treewalkingu. Stejně tak v podstatě každý transpiler používá frázový strom. Explicitně jsou vždy při parsingu v paměti, buď někde na zásobníku (u zásobníkového automatu), nebo v rámci chartu (když se použije chart parser).
Cesta od seznamu tokenů z lex. analýzy k frázovému stromu je triviální, je to jen jeden krok a dělá jej za nás plně automaticky gramatika - nic mimo samotnou gramatiku nelze parametrizovat, modifikovat apod. To se dělá až s AST.
U přirozených jazyků syntax víceznačnosti neřeší. Buď je někde WSD (word sense disambiguation), nebo nějaký jazykový model nad sémantikou. Také tam není žádný AST, buď se parsuje do závislostních stromů (většinou ne přes bezprostřední složky, ale jde to), nebo se používá bezkontextová gramatika a k ní paralelní datová struktura (pak je výsledkem parsingu frázový strom a odpovídající struktura, která např. v LFG dost připomíná AST, akorát to není strom, protože může obsahovat cykly).
@23: Taky diky za odpoved a budu se muset zamyslet. Mozna na to koukam moc z pozice prakticke realizace veci ktere jsem videl a me zajimaji a ne z pohledu abstrakce.
Pokud mam frazovy strom jako strukturu ulozenou v pameti, tak veci jako kontrakce hran mi prijdou ze se daji provadet ciste zmenou pohledu na tu strukturu, ve stejnem smyslu jako jedna struktura muze byt videna jako seznam, strom, binarni strom, cons, mnozina, property list nebo jine veci - a tudiz reprezentaci frazoveho stromu se mi zda ze je i reprezentace AST, byt nemusi byt optimalni ani unikatni.
Trivialita presunu od __LINE__ k cislu ve frazovem stromu v ramci gramatiky mi stale neni zrejma.
Predpokladam ze ani u programovacich jazyku se netrva na AST coby stromu - minimalne DAG, v nekterych pripadech i cykly (byt to nemusi byt platny program, ale interpretovat a nekdy kompilovat muze jit).
@31 Existují i jiné definice AST - viz třeba 1. kapitola Practical Foundations for Programming Languages od Roberta Harpera (http://www.cs.cmu.edu/~rwh/plbook/book.pdf) - uvedená definice vůbec nezávisí na syntaxi jazyka, což je pro některé aplikace výhodnější.
@33 Ale vždyť ty definice jsou shodné, jen v té knize je zúžena na prog. jazyky. Jak jsem uvedl v článku, hlavou pravidla je zpravidla operátor (což je i volání funkce). Máte-li bezkontextovou gramatiku a postupujete, jak jsem popsal, dostanete přesně strom z knihy, kde kořenem podstromů je operátor a jeho syny argumenty (operandy). U listů se jejich ohodnocení v knize říká "variables", předpokládám, že jsou myšleny všechny atomické hodnoty.
Ta definice z knihy je snadnější na pochopení, ale neříká nic o vztahu mezi frázemi (a v takto definovaném stromě pochopitelně fráze explicitně zachyceny nejsou).
U Lispu je to krásně vidět, tam je operátor vždy první prvek seznamu, takže AST tam větví doprava (a u frázového stromu se vždy kontrahuje hrana k prvnímu synovi).
@35 Já myslím, že právě ta Harperova definice je obecnější:
1) Jednomu frázovému stromu mohu přiřadit libovolné množství AST
2) Sorty, jenž používám, nemusí být rekurzivně spočetné množiny
Ad 1) Např. jeden operátor v AST mohu použít pro více operátorů v konkrétní syntaxi - operátor implikace v AST mohu použít i pro operátor implikace a operátor obrácené implikace v konkrétní syntaxi. Nebo do AST stromu mohu přidat další uzly. Ten Harperův AST může více abstrahovat od syntaxe.
Ad 2) Reálná čísla.
@35 Ještě k těm proměnným. Nepochopil jsem, co přesně myslíte tím všechny atomické hodnoty.
Proměnnou Harper myslí pojmenované volné místo, které má nějakou sortu a lze tam substituovat jiný AST. Například jazyk pro sčítání reálných čísel bude mít nulární operátor pro každé reálné číslo a jeden binární operátor pro sčítání. Bude tam jediná sorta výraz. Zápisem plus(1, x) rozumím příslušný AST a mohu například za x substituovat AST 3.5. Tím dostanu AST plus(1, 3.5), a pokud tomu jazyku přiřadím standardní dynamiku tak to redukuji na AST 4.5.
"Ten Harperův AST může více abstrahovat od syntaxe." AST je Abstract SYNTAX Tree. Jak může syntax abstrahovat od syntaxe? Jeho definice se sice nevztahuje explicitně ke gramatice, ale to co říkáte Vy, např. o konverzi operátorů, je další fáze zpracování za syntaxí. Jednomu frázovému stromu nikdy nemůže odpovídat více AST. Jistě, můžeme se bavit o stromech (nebo DAG), jež vzniknou nějakou sémantickou konverzí z AST, asi je to zajímavější, není to jen suchá teorie jako formální syntax. Jako téma k další diskuzi navrhuji např. rozdíl mezi povrchovou (surface) a hloubkovou syntaxí.
@39 "Jednomu frázovému stromu nikdy nemůže odpovídat více AST." Podle té Harperovy definice může - nic mi nebrání zvolit si libovolné zobrazení, kterým převedu frázový strom na AST. Myslím, že nejsem sám, kdo to takhle chápe - viz třeba slajd 122 v následující prezentaci http://www.par.univie.ac.at/~mehofer/teach/CC/semantic.pdf
Tou podivnou větou jsem chtěl říct, že ta Vaše definice AST v podstatě vůbec neabstrahuje od konkrétní syntaxe na rozdíl od té Harperovy definice. U Harpera můžu měnit konkrétní syntax a nechat si AST, což podle mě odpovídá významu slova "abstraktní".
@1 Konkretne u Forthu by se ten strom obecne mohl menit s kazdym nove nadefinovanym slovem, takze to je z praktickeho hlediska nepouzitelne. Soucasne to je v praxi i zbytecne (postacuje rozsekat slova na zaklade bilych znaku a poslat je dat), teorie AST atd. se hodi spis na jazyky s nemennou syntaxi: Fortran->Algol->C(++)->Java atd.
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 36 200×
Přečteno 25 361×
Přečteno 23 795×
Přečteno 20 177×
Přečteno 17 874×