A Factor programozási nyelv

Metaprogramozási lehetőségek

A Metaprogramozási lehetőségekről

A Factor programozási nyelv igen modern lehetőségekkel rendelkezik metaprogramok írásához, fordítási időben történő kiértékeléshez és a fordító bizonyos értelemben vett kiegészítéséhez/scripteléséhez. Mondhatni ez a nyelv egyik különleges erőssége, így érdemes neki egy egész fejezetet szentelni.

Alapvetően a következő lehetőségek állnak rendelkezésünkre:

Fordítási-idejű szavak

A fordítási idejű szavak, gyakorlatilag olyan szavak, melyek tokenizáláskor a fordító belsejében futtatják le a törzsükbe írt kódot. Ez gyakorlatilag a "Fordító scriptelését" jelenti tehát.

Lehetőségek/architektúra:

Mint azt már korábban leírtuk, egy factor program első közelítésben szóközökkel elválasztott tokenek sorozata (ahol a "..." is egy tokennek számít). Ez alapvetően tényleg igaz is, de igazából ha egy kicsit mélyebbre tekintünk a fordító működésébe, akkor látni fogjuk, hogy ettől akár teljes mértékben e is térhetünk. Ugyanez igaz a postfix alakú műveletvégzésre is, mert az itt ismertetett eszközökkel beágyazott nyelvek, infix kifejezések (lsd. az infix szótárt és az [infix szót) és hasonló érdekességek valósíthatóak meg.

Hogyan is valósítható ez meg egy olyan nyelvben, mely egymásután írt értékek és szavak együtteséből áll? Egy viszonylag elmés trükköt alkalmazva: A lényeg, hogy képesek vagyunk olyan szót deklarálni, mely nem akkor fog lefutni, amikor a program a megfelelő részhez ér a generált kódban, hanem amikor a fordító a forrásszövegben a megfelelő tokent éppen beolvasta!

Tehát gyakorlatilag a fordító szépen halad a forrás elemzésével sorról-sorra egyesével olvasgatva a tokeneket egészen odáig, amíg egy olyan tokenhez nem ér, mely fordítási idejű szónak van korábban már deklarálva. Ilyenkor ahelyett, hogy tovább építené az elemzési fát, a megfelelő kódrészletre ugrik és elkezdi végrehajtani az ott leírt kódot.

A fordítási-idejű szavainkat a SYNTAX: szóval deklarálhatjuk és a törzsét a ;-ig adhatjuk meg, akár egy hagyományos szó deklarációjkor. Nagyon fontos, hogy a fordítási idejű szavak forrását is hagyományos factor programként írhatjuk le, nem egy külön kis nyelv használható csak erre a célra. Ez azért is érdekes, mert így akár a fordítási szó deklarációjakor felhasználhatunk már meglévő, máshol definiált fordítási szavakat, tehát ha valamihez csináltunk már egy szintaktikus/szemantikus cukorkát, akkor azt akár a további ilyen deklarációinkhoz is felhasználhatjuk! Erre egyébként példát is láthatunk a mellékelt "imperative szótár"-példa implementációjának a while részében.

A fordítási idejű szavainknak ( syntaxtree -- syntaxtree' ) stack effect-el kell rendelkezniük. Ebből az is látszik, hogy az eddig elemzett szöveg valamilyen formában a jelenleg futó fordítási idejű szó rendelkezésére áll. Ez a szintaxisfa egy sorozat (vektor) formájában érhető el. Ez a vektor alapvetően szavakat és értékeket tartalmaz wrapper segítségével beágyazva (esetleg más, hasonló vektorokat) és mivel egy hagyományos sorozatról van szó, használhatjuk például a suffix! műveletet a módosítására. Ez azért hasznos, mert ezzel a módszerrel az elemzéskor futó fordítási szavunk olyan elemeket pakolhat rá a fordító által gyűjtögetett elemzőfának a végére "top level form"-ként, amik elvileg nem szerepeltek azon a helyen, így a kódban. Magyarul gyakorlatilag fordítási időben kigenerálhatunk tokeneket, melyek nincsenek eredetileg a kódban, de miután odahelyezzük őket pont a nekik megfelelő működést fogják nyújtani...

Mint azt említettük, használhatunk más fájlban definiált fordítási szót a fordítási szavaink törzsében, de fontos itt, hogy külön fájlban legyen a két dolog, különben nem fog működni. Van egy másik korlátozása is a rendszernek: bár tetszőleges értéket, szó wrappert, illetve kódidézetet helyezhetünk a veremre (ez utóbbit wrappelt szavakból álló vektorból a >quotation-al előállítva), sajnos itt már nem lehetünk olyan lazák, hogy használhassuk a saját SYNTAX:-al vagy más metaprogramozós módon deklarált dolgainkat. Ez utóbbi azért nem lehetséges, mert a fordító ekkor már pont abban a fázisban van, amikor ezeket kifejti (ugye pont ezért kezdett el futni a megfelelő fordítási szavunk) és ilyenkor már mit se számít, hogy az elemzési vektor végére ilyen szavakat akarunk irogatni - nincs aki kifejtse őket újra...

Egy viszonylag egyszerű, fordítási idejű hello world program:

SYNTAX: HELLO "Hello world" print ; HELLO ! Ez Kiirja: Hello world (de meg forditaskor, pl. a listenerbe)
Előreolvasás

A fentiek még nem sokat érnének, ha csupán annyi lenne a dolog, hogy egy szó segítségével manipulálhatnánk az eddig elvégzett elemzést és esetleg bizonyos kódot odagenerálhatnánk így a szavunk helyére. Ennek is már van bizonyos értelme, de a factor sokkal több lehetőséget nyújt a számunkra ennél. Ez a plussz előre definiált szavak egy olyan csoportja miatt létezik, melyeknek segítségével a fordítónak adhatunk utasításokat!

Az előző fejezetben említett suffix! nem tartozik közvetlenül ezek közé, az csupán egy sorozat végére helyez egy elemet. Itt olyan szavakra kell gondolni, amelyek segítségével egy tokent olvashatunk az input szövegből stringgé alakítva (scan), vagy akár több tokent stringek vektorjaként az adott stringig bezárólag (parse-tokens) és egyéb bonyolultabb szavakról, mint pl. parse-until, mely hagyományos faktor kódelemzést folytat egészen a megadott tokenig, ahol is kihasználhatjuk, hogy a parse-until más metaprogramozott szavakat is gond nélkül olvas, mert gyakorlatilag csak megkérjük a fordítot, hogy egy darabig (mondjuk egy ;-ig) csinálja azt, amit előttünk csinált volna. Ilyen és ehhez hasonló elemzési szavakból iszonyatosan sok található már meg a nyelvhez tartozó sztenderd szótárakban, így képtelenség őket itt számba venni. Mindenesetre a stringig bezárólag olvasó, stringet előállító szóval gyakorlatilag már mindent meg tudunk csinálni (persze bizonyos gyakorlati korlátok azért adódhatnak)

Az alapvető előre-olvasási műveletekről a programcsomag listenerjéből elérhető Factor handbook/The language/Parsing words alatti részekben érdemes olvasni.

Egyszerű fordítási szó, fordítási időben történő kiértékeléshez:

! dollar.factor a work/dollar alatt: USING: parser sequences ; IN: dollar SYNTAX: $[ parse-quotation ! Ez ;-ig parzolja a kodot es egy kodidezetet hoz letre call( -- value ) ! Ez meghivja a kodidezetet, ha az nem var parametert ! es csak egyetlen erteket allit elo a stack-en suffix! ! az eredmenyt (tehat ami eloallt az iment) az elemzesi ! fa vegere beirjuk, mint erteket ; ! A scratchpad-ban: USE: dollar 4 $[ 1 3000000 * 3.14 / ] + . ! A fenti sor a következő kóddal ekvivalens: ! 4 955414.0127388535 + .
Felhasználási lehetőségek

Könnyen látható, hogy a saját beágyazott nyelvünk képességeinek igazából semmi sem szabhat határt: adott egy turing-teljes nyelv (a factor maga), amivel a fordítót scriptelhetjük és adott egy turing-teljes nyelv (az elemzési fán gyűlő szavak listája), melyre a kódot alakítjuk. Nekünk csupán (a leglecsupaszított esetet tekintve) egy stringből kell dolgoznunk és az elemzési listába (nem kell fát építeni a szavakból) forth-stílussal a megfelelő programot megírnunk. Ha megvalósítjuk egy elemzési sóhoz az algoritmust, mely egy az elemzést záró egyértelmű szövegig tartó stringet ilyen módon elmezni képes, akkor elmondhatjuk, hogy implementáltunk egy beágyzottt DSL-t! Elvileg csak a rátermettségünkön és a kitartásunkon múlik, hogy milyen beágyazott nyelvet valósítunk meg, akár egy szigorúan típusos DSL is elképzelhető, ha kellően sokat macerálunk a dologgal!

Viszont ez a módszer nem csak az alap factor kódtól teljesen elkülönülő beágyazott nyelveket tesz lehetővé, hanem az alapnyelv elemeihez gördülékenyen illeszkdő szintaktikus és szemantikus cukorkák létrehozására is kényelmes eszköz. Ez utóbbi lehetőségre viszonylag sok példát láthatunk. Ilyen többek között a USING: ... ; is, mely a sok USE: szótár kiváltására alkalmas (ha megnézzük a kódját kiderül, hogy USE: -okra fordítja a kódunkat).

A fentieken kivul sajat felhasznalasi peldakkal is rendelkezunk is adhatunk. A factor egy olyan nyelv, amelyet nem igazan lehet, csak probalgatassal elsajatitatni, így nagyon fontos a példák adása az egyes lehetséges nyelvi eszközökhöz, különös tekintettel a fordítási szavak rendszerének a megértéséhez. Aki el akar mélyedni a factor nyelv ezen részében (vagy mert érdekli a nyelv, vagy mert esetleg ötletet szeretne meríteni más megoldásokhoz) annak erősen javasoljuk, hogy nézze meg a példaprogramok fejezet vonatkozó, metaprogramozási részét, melyben a Factor alapnyelvet kiegészítjük és felkészítjük imperatív programozási stílusra. Az említett példaprogramban megvalósítjuk az elágazást, a ciklust, a változók kezelését (értékadás+deklaráció), procedúra és függvényhívásokat. Tesszük mindezt egyrészt a factor alapnyelvbe beágyazott módon, de egy külső szótárhalmazként(az egyes elemek akár külön USE:-olhatók), másrészt olvasható szintaktikával és infix alakú kifejezésekkel.

Egy látványos példakód, mely az említett szótárainkat használja:

! work/factorial-test/factorial-test.factor: USING: imperative.if imperative.yield imperative.let imperative.invoke imperative.while io locals windows.types kernel math.parser ; IN: factorial-test :: factorial ( x -- facx ) drop f ; ! fuggveny elore deklaralasa :: factorial ( x -- facx ) ! rekurziv faktorialis fuggveny IF x = 0 THEN{ YIELD 1 ; } ELSE{ IF x = 1 THEN{ YIELD 1 ; } ELSE{ YIELD x* factorial(x-1) ; } ; } ; ; ! Listenerben: 5 factorial .

Amit fent látunk az mind-mind csupán a betöltött imperative.akármi szótáraknak köszönhető. A legtöbb modul viszonylag rövid (pl. az IF megvalósítása 50 körüli sor) és csak és kizárólag fordítási szavakat használ a szintaxis és a szemantika megadásához! A példaprogramként adott modulokhoz egy tesztállomány is tartozik, melyben a változók/értékadások és egyebek is bemutatásra kerülnek, így itt azokat nem emeltük ki külön példának. A példaprogram továbbá soronként és lépésenként el van látva kommentekkel, így igen hasznos forrás a fordítási szavak használatának a pontosabb megértéséhez.

Egy az előzőnél sokkal rövidebb, de szintén hasznos alkalmazás:

SYNTAX: SHOW_PARSE_TREE! dup ! duplikaljuk az elemzesi fat . ! es prettyprinteljuk ; 5 4 . . SHOW_PARSE_TREE! ! A listenerben az utasítás felett megjelenik, ! hogy "V{ 5 4 . . }", miközben 4 és 5 kiíródnak a futás eredményeképp alant. 5 '[ _ 1 + ] call . SHOW_PARSE_TREE! ! A listenerben az utasítás felett megjelenik, ! hogy "V{ 5 [ 1 + ] curry call . }" és a 6 eredmény alant. 5 SHOW_PARSE_TREE! '[ _ 1 + SHOW_PARSE_TREE! ] call SHOW_PARSE_TREE! . SHOW_PARSE_TREE! ! több sort is ír fent: ! V{ 5 } ! V{ _ 1 + } ! V{ 5 [ 1 + ] curry call } ! V{ 5 [ 1 + ] curry call . } ! és lent a futási eredményt: 6

Ez a kis szavacska nagyon hasznos segítség lehet akkor, ha épp érteni szeretnénk mi történik és miből mi alakul ki végül ténylegesen. Továbbá az előző példából az is látszik, hogy nem csak un. "top level formákkal" használható ez a szó, mert amikor a kódidézet belselyéből hívtuk, csak az ahhoz tartozó elemzési nódus gyermekeit mutatta meg...

Funktorok

A funktorok gyakorlatilag sablonok melyeket elsősorban szavak és tuple-k generikus előállítására használhatunk fel. Tehát paraméterezett kódgenerálásra használható szintaktikus cukorkáról van szó, mely ilyen esetekre megóv minket mindenféle bonyolultabb fordítási szavak által történő trükközéstől. A functorok a functors szótárban találhatóak meg.

Alakjuk

A funktorokat a FUNCTOR: szó vezeti be, melyet egy név, majd stack effekt deklaráció követ. A stack effect deklaráció a négyespont (::) -típusú szódeklarációhoz hasonlít. Itt is használhatjuk tehát a bemeneti paraméterek neveit lokális változóként majd a funktor törzsében.

A stack effect deklarációt valahány deklarációs sor követi, melyek újabb lokális változókötéseket hoznak létre. Ezeknek az alakja egy <lokális változónév><műveleti jelölő><deklarációs név> hármas. A lokális változó nevét azért kellmegadnunk, hogy a keletkező szóra már itt is hivatkozhassunk, a műveleti jelölő azt adja meg, hogy a hivatkozott szónak már léteznie kell (IS lehetőség), vagy most hozzuk létre majd (DEFINES és DEFINES-CLASS lehetőségek). A hármas legutolsó tagja egy név, ez lesz majd a keletkező szó neve. Ha ez nem lehetne paraméterezhető, akkor elég nagy bajban lennénk, ezért ez a rész egy un. interpolate-alak, melyben vegyíthetünk hagyományos szöveget lokális változókra történő hivatkozásokkal. Erre a lehetőségre egy példa: ${CLASS}-array.Tehát először megadjuk deklratív módon a keletkező szavainkat.

A fenti deklaratív leírást a a WHERE szó követi, amely után a létrehozott szavainkat írhatjuk be. Itt gyakorlatilag az implementációját adjuk meg paraméteresen annak, amit fent csak deklaráltunk...

! funtest.factor USING: functors kernel io lexer ; IN: funtest FUNCTOR: test1 ( NAME -- ) NAME DEFINES-CLASS ${NAME} ! NAME-el hivatkozhatunk arra ami a WHERE ! parameterben kapott nevu class-t definial TUPLE: NAME ; ! Itt használhatjuk a fenti classname-t ;FUNCTOR SYNTAX: TEST1: scan test1 ! Itt hivjuk meg a funktort forditasi idobe FUNCTOR: test2 ( NAME -- ) NAME IS ${NAME} ! NAME-el hivatkozhatunk arra ami a WHERE ! parameterben kapott nevu mar meglevo dolog TUPLE: NAME ; ! Itt használhatjuk a fenti classname-t ;FUNCTOR SYNTAX: TEST2: scan test2 ! Itt hivjuk meg a funktort forditasi idobe FUNCTOR: symsym ( PARAM1 PARAM2 -- ) GETTERSYM DEFINES ${PARAM1} ! GETTERSYM definialja a ${PARAM1} nevu dolgot GETTEDSYM IS ${PARAM2} ! GETTEDSYM pedig egy mar meglevo PARAM2 WHERE : GETTERSYM ( -- a ) GETTEDSYM ; ;FUNCTOR ! csinalunk egy fuggvenyt, ami visszaadja ! GETTEDSYM-et ha hivjak es a neve ${PARAM1} SYNTAX: SYMSYM scan scan symsym ; ! ez csak syntactic sugar mar... ! listenerben: USE: funtest SYMBOL: class2 class2 tuple-class? . ! azt irj, hogy f (mert csak szimbolum) TEST1: class1 class1 tuple-class? . ! Azt irja majd, hogy t TEST1: class2 class2 tuple-class? . ! Azt irja majd, hogy t (mert atalakul a szimbolum tuple-ve) SYMBOL: asdf ! ennek itt letezo dolognak kell lennie SYMSYM getasdf asdf ! hogy a SYMSYM-el hasznalhassuk getasdf . ! kiirja, hogy asdf, mert getasdf visszadja a megadott ! szimbolumot...

Mint azt megszokhattuk itt sem szovegszeru behelyettesitesrol van csupan szo, ami nem is meglepo, hiszen a funktorok forditasi szavakkal vannak megvalositva...

Makrók

Makrókkal kódidézeteket állíthatunk elő fordítási időben, melyek a makróhívás helyére kifejtve helyettesítődnek. A kódidézet előálltása paraméterezhető, így a makró fordítási időben ismert értékeket ehet meg a hívásakor a veremtetőről. Mindaz amit makrók segítségével megoldhatunk, az implementálható lenne kódidézetek futásidejű létrehozásával és call hívással, a makrók azonban akár jelentős sebességbeli gyorsítást eredményezhetnek a futásidejű kifejtődés miatt.

A fordítási időben történő futástól eltekintve a következő két kódsor ekvivalens:

MACRO: name ( params -- ) ..bar.. ; : name ( params -- ) ..bar.. call ;

Egy lehetséges felhasználási mód:

: ndrop1 ( n -- ) [ drop ] n*quot call ; ! Ennél a megoldásnál futásidőben jön létre a kódidézet ! hacsak ki nem optimalizálódik persze valamiért :-( MACRO: ndrop2 ( n -- ) ! Ennél a megoldásnál fordítási időben kifejtődik a törzs ;-) [ drop ] n*quot ! (n*quot egy kódidézet belsejét többszörözi meg konstansszor) ; ! "1 2 3 4 5 3 ndrop2 . ." ekvivalens "1 2 3 4 5 drop drop drop . ."

Egy makró akkor is kiértékelődik, ha olyan bemenetet adunk neki, amit a fordító nem tud fordítási időben kiszámítani. Ebben az utóbb említett esetben a fenti két megoldás esetén ugyanaz történne (tehát fordításkor nem ismert értékek esetén futásidőben jön létre a megfelelő kódidézet). A makrókról tehát összefoglalva elmondható, hogy gyakorlatilag olyan szavak, melyeknek a fordítási időben ismert paramétereik alapján kifejtésre kerülő kódidézet kerül a hívás helyére, míg a hagyományos, futásidejűséget megkövetelő paraméterektől függő kódrészük nem fejtődik ki.

Megj.: Makrók esetén is van a négyespontos definícióhoz (::) hasonló eszközünk. Itt ezt a MACRO:: szóval vezethetjük be, ha szeretnénk használni lokális változókat.

Az EBNF elemző

A peg szótárban lévő [EBNF és az EBNF: szavak által használhatjuk ezt a magasabb-szintű lehetőséget, melynek segítségével egy kicsit bővített EBNF szintaxissal megadott stringet elemezhetünk. Az elemzéssel alapesetben egy szintaxisfát kapunk eredményül, de az EBNF szabályok megadásakor adhatunk meg ad-hoc átírási szabályokat is, amivel a szintaxisfa adott pontjára tetszőleges elemet helyezhetünk el. Az átírási szabályokkal, az elemzés többször egymás után indításával és a fentiekben említett egyéb lehetőségek kombinációival viszonylag bonyolult beágyazott nyelvek készíthetőek. Ilyen beágyazott nyelvek például a peg.pl0 (PL\0 nyelvi elemző), a peg.javascript (beágyazott javascript elemző) és a smalltalk.parser (egy beágyazott smalltalk elemző).

Az elemző működése

Jelenleg egy PEG (Parsing Expression Grammar) típusú elemző modul van a Factorhoz.

A PEG-es elemzők az LR és LL elemzőkhöz képest több erőforrást használnak fel, tehát kevésbé hatékonyak. Az egyszerű implementációk általában exponenciális futásidőt eredményeznek az elemzendő szövegen, de a Factorban egy lineáris futású PEG-elemző kapott helyet, ami viszont cserébe kifejezetten memóriaigényes. Az előbbiek ellenére a PEG-es elemző egy igen erős eszköz beágyazott nyelvek leírására. Egyedül arra kell odafigyelnünk, hogy PEG segítségével nem lehet, csak egyértelmű nyelveket elemezni, mert a hagyományos környezetfüggetlen nyelvtanokhoz képest itt számít a leírt szabályok sorrendje. Ami korábban van, hamarabb illeszkedik.

A PEG nyelvtanokról/elemzőkről itt olvashatunk egy rövid összefoglalót:
http://en.wikipedia.org/wiki/Parsing_expression_grammar

Egy különösen fontos megjegyzés a példaprogramokhoz:
A megértés érdekében a példaprogramok bő-lére eresztett kommentekkel vannak ellátva, de sajnos mivel a kommentezési lehetőségek szintén csak fordítási idejű szavakkal vannak a factorban megvalósítva, itt igazából nem működnek. Tehát ha az itt látható kódokat futtatni is akarjuk (mondjuk a listenerben), akkor egyrészt meg kell sabadítanunk a példákat a kommentektől, másrészt a listener által felajánlott szótárakat USE:-olnunk is kell majd (ezt a kód bemásolására az interpreter felajálnja így nem macerás és ez a lépés egyéb korábbi példáknál is már egyébként is szükséges).

Alap felhasználás

Az EBNF: kulcsszót használhatjuk egy elemzőszó létrehozására. Az elemzőszó a vermen található stringet várja paraméterül és egy absztrakt szintaxisfát ad eredményül. Az elemzőszó neve az EBNF: után írt azonosító lesz míg a keletkező elemzési fa az EBNF; szóig bezárólag írt szabályok alapján áll elő.

Az elemzőszó megadása tehát a következő alakban írandó:

EBNF: <név> <szabályklóz1> <szabályklóz2> . . . <szabályklózn> ;EBNF

Ahol a szabályklózok a nemterminális nevéből, egy egyenlőségjelből, az illeszkedési mintából és egy opcionális akcióból állhatnak. A szintaxisfa egy Vektor adatstruktúrában épül, melybe a nemterminális pozíciók helyén további vektorok vannak beágyazva így alakítva ki a fastruktúrát.

Konkrét példák az egyszerű működésre:

EBNF: festerbester festeris = " "* "fester" " "* "is"? bester = " "* bester" " "* start = festeris? bester? ;EBNF "fester is bester" festerbester . ! Azt irja ki, hogy: ! V{ ! V{ V{ } "fester" V{ " " } "is" } ! V{ V{ " " } "bester" V{ } } ! } "fester is bester " festerbester . ! Azt irja ki, hogy: ! V{ ! V{ V{ } "fester" V{ " " " " " " " " } "is" } ! V{ V{ " " " " " " " " } "bester" V{ } } ! } ! (tehat a *-al megadott illesztesek mind bekerulnek rendesen) EBNF: exceptional elfogadottszo = "ExceptionaL" ;EBNF ! Lathatoan nem szukseges kiemelni az indulo "start" szabalyt, ! de az legyen az utolsokent megadott szabaly, kulonben nem lesz jo! "Exceptional" exceptional . ! A fenti kod exception-t dob: ! Peg parsing error at character position 0. ! Expected token 'ExceptionaL' "ExceptionaL" exceptional . ! Azt irja ki, hogy "ExceptionaL" (tehat egyetlen elemet mar nem foglal vektorba)
Szabályokhoz rendelt akciók

Mint azt fentebb említettük, a szabályokhoz adhatunk meg opcionális akciókat is. Ezt a szabályt követő => jel után írt factor programmal fejezhetjük ki, melyet [[ és ]] közé írhatunk be. Az így megadott kód a stack-en eléri az adott node-hoz tartozó elemzési fát (a fent látott vektoros formában) és módosíthatja azt tetszőleges elemre cserélve. Ilyen kódot a szintaxisfa tetszőleges pontjához rendelhetünk, tehát a fenjebb található elemeinkben a gyerek nódusokból szintetizálódó eredményre számíthatunk és azt dolgozhatjuk tovább új eredményt előállítva.

Egy konkrét felhasználási példa:

EBNF: parse-calc sign = "-" => [[ >string ]] ! Minusz eseten stringge alakitott elem kerul az adott nodusba wholepart = ([0-9])+ => [[ >string ]] ! Az egeszresz eseten is a string alakot hagyjuk a helyen ! (a zarojelekkel korulirt reszt a + egynel tobbszor ismetli, ! a * akarmennyiszer, tehat akar nullaszor is) floatpart = "." ([0-9])+ => [[ [ >string ] map concat ]] ! A pont majd valahany szam is stringge alakul ! (a vektoron megyunk map-el, majd osszekonkatenaljuk) number = sign? wholepart floatpart? => [[ [ f eq? not ] filter concat string>number ]] ! A szam nodus, mely egy opcionalis elojelbol, az egeszreszbol ! es az opcionalis tortreszbol all. ! Egy valodi szamma konvertalodik, miutan a vektorbol kifiltereztuk ! a false ertekeket (ha nincs az opcionalis helyen semmi f kerul oda) ! es egybe lett konkatenalva a sok string. A konverziot a szokasos ! string>number factor szoval vegezzuk el... add = "+" => [[ [ + ] ]] sub = "-" => [[ [ - ] ]] mul = "*" => [[ [ * ] ]] div = "/" => [[ [ / ] ]] ! Az egyes muveletek nodusait a hozzajuk tartozo tenyleges ! factor muveleteket tartalmazo kodidezetekke alakitjuk at ops = add|sub|mul|div ! A muvelet lehet osszeadas, kivonas, szorzas vagy osztas ! (tehat a szokasos alakban adhatunk meg alternativakat) spaces = " "* ! Ezt akar inline be is irhatjuk a kovetkezo sorba, ! de valamiert kulon szedtem a tetszoleges sok space-t expr = number spaces ops spaces number => [[ [ first ] [ 4 swap nth ] [ third ] tri '[ _ _ @ ] ]] ! A kifejezes egy szambol, opcionalisan nehany spacebol, ! egy muveleti jelbol, megint opcionalis space-kbol es meg egy szambol all ! Az igy keletkezett elemzesi vektorbol egy olyan kodot kepezunk, ! melyben a vektor elso elemet(number) betesszuk egy kodidezetbe, ! majd az otodik elemet is felhelyezzuk egy kodidezetbe ! (a 4 swap nth ezt csinalja, csak nullatol indexelünk), ! majd a vektor harmadik elemet - tehat a muveletet tartalmazo kodidezetet - ! is behelyezzuk meg egy kodidezetbe. ! a tri szoval ezt a harom kodidezetet vegrehajtjuk, ! tehat kifejtodnek es a vermen ket szam, meg az igazi (factor-os) ! operatort tartalmazo kodidezet lesz ! ebbol egy eloresutott kodidezettel (fried quotation, ! lsd korabban a megfelelo nyelvi elemeknel) eloallitjuk az outputot, ! mely tehat ket szamot fog tartalmazni (az alahuzashelyekre ! kerulnek a nevtelen fuggvenyunkben a parameterertekek balrol jobbra) ! es a ket szam mogott a kapott kodidezet kifejtese fog allni ! (a kukac ugyanaz, mint az _, csak ki is fejti a kodidezetet es nem csak ! ertekkent bekerul) ! tehat ha pl. az elso numberbol szintetizalt nodus [ 21 ] volt, ! a muveleti jelbol szintetizalt kodidezet [ [ + ] ], a masik szambol pedig [ 2 ], akkor ! 21 2 [ + ] '[ _ _ @ ] fog ott allni a tri vegrehajtasa utan ! es emiatt a parse-calc eredmenyekent [ 21 2 + ] fog eloallni. ! Azert mondhatjuk, hogy eredmenyekent, mert ez mar nincs hova ! szintetizalodjon es az utolso szabaly szintetizalta ezt a nodust. ;EBNF "-2 * -21" parse-calc ! Itt eloall a [ -21 -2 * ] kodidezet call ! Itt meghivasra kerul . ! Itt pedig kiirjuk a valaszt a kerdesre, hogy az elet, a vilagmindenseg, meg minden... "-2 # -21" parse-calc ! Ez peg parsing error kivetelt valt ki es meg azt is megmondja, hogy a masodik elem a hibas...

A fent látható kódot egy kis ügyeskedéssel tovább kombinálhajuk, ha vegyítjük egy kicsit a korábban említett fordítási idejű szavakkal:

SYNTAX: CALCULATE: ";" parse-tokens ! tokeneket olvasunk egeszen a ;-ig, egy sorozatnyi string keletkezik concat parse-calc suffix! ! a sorozatot egy stringge konkatenaljuk, majd a keletkezett stringet ! parzoljuk a fentivel es az elemzofa vegere helyezzuk \ call suffix! ! az elemzofa vegere irunk egy call hivast \ . suffix! ! es egy prettyprintet ; CALCULATE: 4.0/4 ; ! Azt irja ki, hogy 1.0

Az előző példa azért szerepel itt, hogy lássuk az ebnf-es elemzés miként segíthet pl. az elemzési szavaink esetén saját beágyazott nyelvek definiálására. Itt most viszonylag rövid teljes értékű beágyazást hajtottunk csak végre (CALCULATE: és ; között), de egy kis fantáziával akár egy [MYLANGUAGE ... MYLANGUAGE] jellegű, blokkszerű felhasználás is lehetségessé válik, amin belül egy akármilyen szemantikára forduló, tetszőleges szintaktikájú beágyazott programozási nyelvet is kidolgozhatunk!