která byla vytvořena scannerem. Příkladem tokenu ze vstupu je:
"is" > tKeyword
Syntaktický analyzátor pracující nad takovým vstupem může použít
například parser symbol
:
symbol([_>tWhitespace])
pro akceptování prázdného znaku. Stačilo pouze využít toho, že
symbol
je parametrizován řetězcem; dokonce lze rozklad provádět
v různých módech proměnných. Analogicky lze použít parser token
:
token([_>tDirective, _>tDelimiter])
pro přijmutí direktivy jazyka následované oddělovačem příkazů.
Výše uvedený způsob analýzy -- myšlen víceprůchodový rozklad, se používá zejména když jsou vytvářeny parsery za běhu, kdy nelze konzultovat kód pomocí něhož by bylo možné vytvářet nové módy (viz níže).
Pro tento účel zavedeme kombinátor chainPassage
. V prvním parametru
dostává scanner, jehož úkolem je vytvořit tokenizovaný vstupní
text nebo jej nějakým způsobem předpřipravit.
Jeho aplikací se získá vstup pro parser druhý, jehož seznam úspěšných rozkladů je nakonec vydán:
?- W :-> prelex chainPassage lexer | chainPassage strip | chainPassage synthPass | <@ doSemantic.V tomto příkladu jsme použili kombinátor
chainPassage
pro
zřetězení tří fází lexikální analýzy a jedné fáze analýzy
syntaktické. Pomocí mutátoru <@
lze nakonec provést
sémantickou analýzu syntaktického stromu vydaného tímto víceprůchodovým
parserem.
Použitelnost konstruktoru chainPassage
v případech, kdy získáváme
nebo dokonce konstruujeme parsery pro jednotlivé fáze za běhu je zřejmá.
Ještě si ukážeme generátor multiPass
, který ze seznamu parserů
zkonstruuje parser pro víceprůchodový rozklad. Předchozí úlohu by bylo
možné pomocí něj řešit takto:
?- multiPass([prelex,lexer,strip,synthPass],P), | W :-> P <@ doSemantic.S víceprůchodovým rozkladem se v literatuře věnované konstruktorům parserů ve funkcionálním programování můžeme setkat například ve Fokker [4] a Hutton [6]. Tento způsob řetězení fází syntaktické analýzy přenesený do našeho implementačního jazyka si však ne zcela zachovává míru efektivity, kterou měl právě ve funkcionálním světě, kde je využíván lazy charakter jazyka.
V knihovně konstruktorů parserů však můžeme tuto úlohu řešit jinak -- pomocí módů. Použijeme strukturu parseru, která se používá běžně třeba při konstrukci překladačů. Scanner zde nezpracovává celý vstup najednou, ale je volán syntaktickým analyzátorem za účelem získání jednoho tokenu -- lexikální analýza je spouštěna na žádost analýzy syntaktické. Zpracování vstupního textu má potom proudový charakter a pro zpracování vstupního textu postačuje jediný průchod.
Lexikální analyzátor se implementuje jako uživatelský mód, který je realizován parserem, jenž tokenizuje resp. transformuje vstupní text a vydává jej do vyšší vrstvy. Parser ve vyšší vrstvě pak pracuje s tokeny stejně, jako jsme si to ukázali v úvodu tohoto oddílu. Ze syntaktického stromu lze postradatelné tokeny, kterými jsou kupříkladu závorky, snadno odstranit. O tuto úlohu se můžou postarat třeba v případě prázdných znaků a komentářů již scannery a parseru tokeny tohoto typu vůbec nepředávat.
Tímto způsobem lze také velmi snadno implementovat kontextově závislou syntaktickou analýzu. Parser může v průběhu rozkladu snadno střídat módy dle aktuálního kontextu ve vstupním textu.
Analyzátoru vytvořenému pomocí módů tedy postačuje jediný průchod vstupním textem:
?- strip(lexer(prelex(file(Position,Handle)))) | :-> synthPass <@ doSemantic.V této části jsme si ukázali, jak s pomocí módů odstranit nutnost víceprůchodového rozkladu při použití konstruktorů parserů, který byl použit v ostatních pracích věnovaných tomuto tématu a nahradit jej jednoprůchodovým analyzátorem, v němž jednotlivé fáze průběžně spolupracují.
dvorka 2013-12-31