Egysorosok, amelyek segítségével a synben használt egyes típusokból kinyerhetjük a kifejezéseket
Az elképzelésemről röviden
A legutóbbi projektemben, amely az mplusfonts nevet viseli, és amely az azonos nevű TrueType-betűtípusnak egy no_std
grafikus könyvtárcsomaghoz készült adaptációja, volt egy olyan elképzelésem, hogy a #[strings::emit] segédattribútumot ne egy adott kifejezésre kelljen alkalmazni, hanem lehessen arra az utasításra is, amelyben a kifejezésnek az eredményét értékül adjuk egy változónak. Az volt az célom, hogy ezt általánosítva bármilyen kifejezésnek a szülője hordozhassa az attribútumot, mint ez az alábbi példában látható.
// Előtte
let bitmap_font =
mplus!;
// Utána
let bitmap_font = mplus!;
Ebben a példában a kifejezés szerepét az mplus! függvényszerű makrónak a hívása tölti be, és ez az a kifejezés, amelyen a #[strings] makrónak a feldolgozása során valamilyen műveletet végzünk majd.
A megvalósítás mikéntje
A syn crate (kódcsomag) egy olyan interfészhármast kínál, amellyel a szintaxisfát járhatjuk be és manipulálhatjuk:
- A fold modulban található függvények megszerzik a csomópont tulajdonjogát, és ezeknek ugyanilyen típusú értékkel is kell visszatérniük.
- A visit modulban található függvények kölcsönbe kapják meg a csomópontot, így ezek megvizsgálhatják annak értékét, de nem módosíthatják azt.
- A visit_mut modulban található függvények kölcsönbe kapják meg a csomópontot, de megkapják hozzá a lehetőségét annak is, hogy módosítsák a csomópont adatmezőit.
Ebben a harmadik modulban található az a trait (jellemvonás), amelyet a MacroVisitor implementál.
https://github.com/immersum/mplusfonts/blob/v0.2.0/macros/src/strings/visitor/mac.rs
Bár használhattam volna a modulból származó függvényeket is, amelyeket alapértelmezett esetben a traitnek a tagfüggvényei meghívnak, az elképzeléseimnek megfelelően típusonként egy-egy híváslánc gyűjti össze a segédattribútummal jelölt csomópontból indulva azokat a kifejezéseket, amelyek a részfát tekintve közvetlen leszármazottai a csomópontnak.
Miközben a kinyert kifejezések iterálása történik, amelynek a módját ez a blogbejegyzés tárgyalja, az eredményhalmazt igény szerint szűrhetjük.
for expr in exprs
Ami opcionális, az iterátorrá alakítható
A különböző típusok adatstruktúráját végignézve láthatjuk, hogy milyen változatos mezőtípusokban található kifejezés:
- Egyes adatmezők típusa egyszerűen kifejezés, másoké ugyanez, csak indirekcióval:
Box<Expr>
- Egyes adatmezők a számosságukat tekintve 1 eleműek, mások 0..1 eleműek, vagyis opcionálisak:
Option<Expr>
- Ugyanez indirekcióval együtt is megtalálható:
Option<Box<Expr>>
- Vannak az adatmezők között n eleműek is:
Punctuated<Expr, Comma>
- Ezek mellett komplex mezőtípusok is előfordulnak:
Option<(Eq, Expr)>
Az ilyen mezőkből tartalmaznak egyet vagy többet a számunkra érdekes típusok. A feladat legérdekesebb része annak a meghatározása volt, hogy milyen transzformációs logikával jutunk impl Iterator<Item = &mut Expr>
jellegű eredményre az egyes típusok esetében, ha egy-egy argumentumként kapott &mut node
a kiindulási pont.
Több módja is van például annak, hogy egy egyelemű iterátort létrehozzunk. Egy szeletliterál kódban mérve kompaktabb, mint egy függvényhívás, ha impl IntoIterator<Item = &mut Expr>
is elfogadott, mint ahogyan az egy for
ciklus esetében fennáll. Ezt akár két vagy többeleműre is kibővíthetjük. Az iter:once
függvénynek a meghívása másrészről viszont olvashatóbb.
// Megoldás #1 - Nem feltétlenül kell az .into_iter()
let exprs = .into_iter;
// Megoldás #2
let exprs = once;
Egy harmadik megoldás lehetne az, hogy a kifejezést opcionális típusba csomagoljuk: Some(expr).into_iter()
Ez azért működne, mert ha valami opcionális, akkor az egyúttal iterátorrá is alakítható. Azonban nemcsak iterátorrá, hanem szeletté is alakítható az opcionális érték, és egy for
ciklus esetében inkább ez javasolt, ellenkező esetben clippy figyelmeztetne minket - for_loops_over_fallibles.
Az attribútumokat hordozó és kifejezéseket tartalmazó típusok listája
1. Változókat és konstansokat reprezentáló típusok
A feldolgozás során leggyakrabban valamilyen értékadás kontextusában fogunk azzal találkozni, hogy az attribútumunkat használják, így elsőként azt nézzük meg, hogy ilyen esetben miként gyűjthetünk kifejezéseket.
1.1. Helyi változók memóriacím-kötése
- Local - Ez 0..1 db kezdeti értékadást tartalmaz, a kezdeti értékadás pedig 1 db kifejezést tartalmaz a
=
token után, illetve további 0..1 db kifejezést az opcionáliselse
kulcsszó után (a diverge kifejezést, amelynek neve külön irányba haladást jelent, és ez arra utal, hogy a vezérlés nem folytatódhat alet
kötés utáni utasításokkal).
1.2. Statikusok inicializációja
- ItemStatic - Ez 1 db konstans kifejezést tartalmaz a
=
token után.
1.3. Konstansok deklarációja
- ItemConst - Ez 1 db konstans kifejezést tartalmaz a
=
token után. - ImplItemConst - Ez 1 db konstans kifejezést tartalmaz a
=
token után (azimpl
blokkon belüli előfordulás esete). - TraitItemConst - Ez 0..1 db konstans kifejezést tartalmaz az opcionális
=
token után (a default konstans kifejezést, amelynek neve alapértelmezettet jelent, és ennek az értéknek a megadása egy trait, vagyis „vonás, jellemvonás” definiálásánál nem kötelező).
2. Típusdefiníciókat reprezentáló típusok
A Rust nyelvben konstans kifejezések néhány olyan helyen is előfordulhatnak, mint a szintaxisfának egy-egy összetettebb csomópontja.
2.1. Konstans paraméterek generikus típusokhoz
- ConstParam - Ez 0..1 konstans kifejezést tartalmaz az opcionális a
=
token után (a default konstans kifejezést, amelynek a megadása nem kötelező).
2.2. Enum (felsorolási típusok) definíciója
- ItemEnum - Ez n db variánst tartalmaz.
- Variant - Minden egyes variáns 0..1 db konstans kifejezést tartalmaz az opcionális
=
token után (a variáns diszkriminánsa, vagyis a megkülönböztető értéke, amely explicit módon is megadható).
3. Kifejezéseket reprezentáló típusok
A szintaxisfa csomópontjainak az összes többi, itt felsorolt típusa olyan, amely kifejezést reprezentál, és a típus adatmezői között vannak olyanok, amelyek típusa szintén kifejezést reprezentál.
3.1. Slice (tömbszeletek) and tuple (rendezett n-esek)
- ExprArray - Ez n db kifejezést tartalmaz a
[]
tokenek között (a tömbszelet elemeit). - ExprTuple - Ez n db kifejezést tartalmaz a
()
tokenek között (a rendezett n-es elemeit).
3.2. Struktúrák
- ExprStruct - Ez n db mezőnévből és hozzá tartozó értékből álló párost tartalmaz, illetve 0..1 db kifejezést az opcionális
..
token után (a bázisstruktúrát, amelynek rest mezőneve arra utal, hogy a többi mező értékét ez a struktúra szolgáltatja). - FieldValue - Minden egyes mezőnévből és hozzá tartozó értékből álló páros 1 db kifejezést tartalmaz a
:
token után.
3.3. If (elágazások) és while (elöltesztelő ciklusok)
- ExprIf - Ez 1 db kifejezést tartalmaz az
if
kulcsszó után (a cond kifejezést, amelynek kiértékelése után true vagy false értéket kapunk), amelyet először egy utasításblokk követ (a then_branch blokk, amely maga nem kifejezés), utána viszont tartalmaz további 0..1 db kifejezést az opcionáliselse
kulcsszó után (az else_branch kifejezést, amely lehet utasításblokk-kifejezés, de lehet ettől eltérő típusú kifejezés is). - ExprWhile - Ez 1 db kifejezést tartalmaz a
while
kulcsszó után (a cond kifejezést), amelyet egy utasításblokk követ (a body blokk, amely maga nem kifejezés). - ExprLet - Ez 1 db kifejezést tartalmaz a
=
token után, amelynek neve scrutinee, vagyis egy adott vizsgálat alanya, és ez arra utal, hogy az értéknek illeszkednie kell alet
kulcsszó után megadott mintára.
3.4. For loop (számláló ciklusok)
- ExprForLoop - Ez 1 db kifejezést tartalmaz az
in
kulcsszó után (egy olyan kifejezést, amely vagy maga egy iterátor, vagy pedig egy iterátorrá alakítható kifejezés).
3.5. Range (értékkészletek)
- ExprRange - Ez 0..1 db kifejezést tartalmaz a
..
vagy..=
token előtt (a start kifejezést), valamint 0..1 db kifejezést utána (az end kifejezést). - ExprRepeat - Ez 1 db kifejezést tartalmaz a
;
token előtt, valamint 1 db kifejezést utána.
3.6. Match (többirányú elágazások)
- ExprMatch - Ez 1 db kifejezést tartalmaz a
match
kulcsszó után, amelynek neve scrutinee, illetve n db kart, amelyek közül pontosan 1 db lesz végrehajtva. - Arm - Minden egyes
match
kar 0..1 db kifejezést tartalmaz az opcionálisif
kulcsszó után (a guard kifejezést, amelynek neve arra utal, hogy ez a feltétel védi a kart, akár egy „őr, pajzs”), illetve 1 db kifejezést a=>
token után (a body kifejezést, amely lehet utasításblokk-kifejezés, de lehet ettől eltérő típusú kifejezés is).
3.7. Break (ciklusmegszakítások) és return (visszatérések)
- ExprBreak - Ez 0..1 db kifejezést tartalmaz a
break
kulcsszó és az opcionális label után (ha feltüntetjük, akkor a hivatkozott címkével jelölt ciklus utáni utasításokkal folytatódik a vezérlés, egyéb esetben pedig az aktuális ciklus utániakkal). - ExprReturn - Ez 0..1 db kifejezést tartalmaz a
return
kulcsszó után. - ExprYield - Ez 0..1 db kifejezést tartalmaz a
yield
kulcsszó után.
3.8. Closure (lezárási típusok)
- ExprClosure - Ez 1 db kifejezést tartalmaz a
||
tokenek után (a body kifejezést, amely lehet utasításblokk-kifejezés, de lehet ettől eltérő típusú kifejezés is).
3.9. Cast (típuskonverziók)
- ExprCast - Ez 1 db kifejezést tartalmaz az
as
kulcsszó előtt.
3.10. Hivatkozási és memóriacím-lekérési műveletek
- ExprReference - Ez 1 db kifejezést tartalmaz a
&
token és az opcionálismut
kulcsszó után. - ExprRawAddr - Ez 1 db kifejezést tartalmaz.
3.11. Try (feltételes visszatérések) és await (művelet befejezésére várakozások)
- ExprTry - Ez 1 db kifejezést tartalmaz a
?
token előtt. - ExprAwait - Ez 1 db kifejezést tartalmaz a
.
token és azawait
kulcsszó előtt (abase
kifejezést).
3.12. Adatmezőhöz történő hozzáférések
- ExprField - Ez 1 db kifejezést tartalmaz a
.
token előtt (abase
kifejezést).
3.13. Függvényhívások és tagfüggvényhívások
- ExprCall - Ez 1 db kifejezést tartalmaz a
()
tokenek előtt (afunc
kifejezést), illetve n db kifejezést a()
tokenek között (a függvényhívás argumentumait). - ExprMethodCall - Ez 1 db kifejezést tartalmaz a
.
token és a metódusnév előtt (a receiver kifejezést, amely a metódusnak aself
argumentuma lesz), illetve n db kifejezést a()
tokenek között (a metódushívás argumentumait).
3.14. Indexelések
- ExprIndex - Ez 1 db kifejezést tartalmaz a
[]
tokenek előtt, illetve 1 db kifejezést a[]
tokenek között (az index kifejezést).
3.15. Unary (egyoperandusú) és binary (kétoperandusú) műveletek
- ExprUnary - Ez 1 db kifejezést tartalmaz az egyoperandusú operátor után.
- ExprBinary - Ez 1 db kifejezést tartalmaz a kétoperandusú operátor előtt (a left kifejezést), valamint 1 db kifejezést utána (a right kifejezést).
3.16. Assignment (értékadások)
- ExprAssign - Ez 1 db kifejezést tartalmaz a
=
token előtt, valamint 1 db kifejezést utána.
3.17. Zárójeles és zárójel nélküli csoportosítások
- ExprParen - Ez 1 db kifejezést tartalmaz a
()
tokenek között. - ExprGroup - Ez 1 db kifejezést tartalmaz.
3.18. Utasításblokkok, feltételes megszakítások, ciklusmagok stb.
Létezhetnek olyan csomópontok a szintaxisfában, amelyekre bár lehet attribútumot alkalmaznunk, mivel ezek utasításblokkot tartalmazó kifejezések (mint a then_branch blokk az if
kifejezések esetében), a közvetlen leszármazottai között nem találunk kifejezést.
- ExprBlock, ExprConst, ExprUnsafe, ExprTryBlock, ExprAsync, ExprLoop - Ezek 0 db kifejezést tartalmaznak.
3.19. Makróhívások
- ExprMacro - Ez részfákat tartalmaz, amelyek a hívásának bemeneteként szolgálnak, és a részfák tetszőleges csomópontokból állhatnak.
Mivel azonban ez a blogbejegyzés egy olyan esetet ír le, ahol a kinyert kifejezések közül eleve csak a makróhívásokat vesszük figyelembe a feldolgozás során, a makróhívásokban található kifejezések kinyerését nem részletezem ebben a bejegyzésben.