From f56a322328c238d5a2ada3a840d8aed18f9e7378 Mon Sep 17 00:00:00 2001 From: Richard Thier Date: Sun, 18 Aug 2024 00:48:04 +0200 Subject: [PATCH] more SLC descriptions --- BASED.md | 215 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 156 insertions(+), 59 deletions(-) diff --git a/BASED.md b/BASED.md index 7bc4c5e..c91d871 100644 --- a/BASED.md +++ b/BASED.md @@ -1219,7 +1219,7 @@ Ennek megfelelően valami hasonló lehetne: #: DECLARE_MIN_MAX_INT #inserts { - #: MIN_INT + #: MIN_INT // HIBÁS: Ezt nem lehet, insertálni deklarációt nem fog menni... #IF(#LT) { #DROP } [ @@ -1227,7 +1227,7 @@ Ennek megfelelően valami hasonló lehetne: #DROP ] # - #: MAX_INT + #: MAX_INT // HIBÁS: Ezt nem lehet, insertálni deklarációt nem fog menni... #IF(#GT) { #DROP } [ @@ -1238,6 +1238,12 @@ Ennek megfelelően valami hasonló lehetne: } # +Megj.: + + Ahogy fentebb írom is, ez a példa már NEM működik, mert az insert-stack temporális jellege miatt deklarációra + való mutatókat nem tudunk ezen a ponton kezelni! Tehát az insert-bufferbe ilyet nem írhatunk (error). + Viszont! Fontos lenne tudni írni a szótárba valami módon tehát ezt kihelyettesíthetővé tenni szerintem... + Vegyük észre, hogy itt a #insert mögötti blokkban szereplő dolgok mind az olvasó fej jelen pozíciója MÖGÉ kerülő nyers programszöveg - itt az olvasó fej már ugyebár beolvasta a `DECLARE_MIN_MAX_INT` szót, tehát az mögött állunk és olyan eredményt kapunk, mintha az adott dolgokat titkon oda gépelte volna valaki, szóval amint kész ezen szó @@ -1280,7 +1286,7 @@ A szimbólumtábla egy szimbóluma megnyitható "fájlszerűen", ezért a defin **Megj.: Talán ezzel kiváltható a #inserts kulcsszó használata! De egy plusz stack-el is...** -## A #write +## A `#write` működése? Ezzel a szimbólumtáblával manuálisan már sok mindent megoldhatunk és a #insert-el együtt ez powerful már, de a fordítóprogram-jellegű használathoz jó, ha szerepel itt "#write[0](..) {...}" szó is. Ez az inserts-hez @@ -1297,6 +1303,22 @@ A nyelvnek a köztes reprezentációja lényegében szintén emberileg olvashat adott menet közepén a fordítás. Természetesen ez akkor van, ha az alaprendszert fordítónak használjuk és nem egy interpreternek éppen. +## Az `#include` működése + + #include(wordset.tsc) + +Fontos, hogy az include-oláskor C-szerűen mennek a dolgok. Valószínűleg kell majd #ifdef-es guard, meg #define is. +Az include-oláshoz az úgynevezett session-storage-t használjuk és amikor ide jutunk, akkor a parzer / engine +szépen beolvassa a fájlt (hibajelzés, hogy ha nincs olyan) és ezt a kis szöveget kihelyettesíti úgy, hogy a zárójel +között vagy a pointer van - vagy arra szükség sincs és csak kerül be a session-be a cucc egyszerűen... plusz feldolgozzuk... + +## Kommentek + +A kommentek kezelését az engine végzi. Ha valaki programnyelvet akar vele definiálni, akkor meg kell adni a listát a +sorvégi komment karaktersorozatról, meg a többsoros kommentekéről (parancssori paraméter). Default-ként a C-s dolgok... + +Fontos: ezeket a parzoláskor egyszerűen skippeljük! + ## Template makró és egyéb példák Fordítandó (pl. JASS transpilerhez): @@ -1390,10 +1412,10 @@ Ebből legyen - útána meg már elvileg nem olyan nehéz: Egy adattagokat(változókat) is tartalmazó szó és példa a "lokális" változó-elérésre... - #: MY_WORD a b c - #a + #: MY_WORD @a; @b; @c; + @a #inc - #a(.) + @a(.) # Máshonnan elérni így lehet, namespace-elés szerűen: @@ -1430,12 +1452,12 @@ tehát alapból van egy rendszerezettség (például egy adott state-machine glo Ha nagyon ki akarjuk hagyni a namespace-elést, akkor a változó név helyett írjunk "típust" (ez csak név!) és üres szót neki: - #: MY_VAR int + #: MY_VAR @int; # Esetleg: - #: MY_VAR int # + #: MY_VAR @int; # Használat: @@ -1492,16 +1514,24 @@ Ha programnyelvet csinálunk: Lehetővé tesszük, hogy a meta-nyelv indításakor (pl. parancssori paraméterrel) a gyári szavak prefixje megváltoztatható legyen... Tehát ne #dup hanem mondjuk @dup legyen és a tieiddel ezt lekövetheted... - slc -prefix='#' my.app + slc -prefix='#' my.slc Ha nem akarunk prefix-et, akkor meg kellhet mondani, hogy mi a "záró jelző" karakter - ez alapból a prefix maga, de csak ahogy önmagában áll, tehát a fenti példában '#' (lásd a példáinkat) - üres prefix esetén alapból a pontosvessző karakter (forth-os szokás) - slc -ender='%' + slc -ender='%' hello.lang -Default: Üres prefix és pontosvessző ender. Ezzel forth-szerű interpretert kapunk! +Szükséges lehet még a 'kukacolás' átírása is valami másra, például így: + + slc -at='~~' hello.lang2 + +Default: Üres prefix és pontosvessző ender, kukac -at mellett. Ezzel forth-szerű interpretert kapunk! + +Az slc-nek továbbá szükséges lehet egy "prefix kód". Ez gyakorlatilag "auto-include"-olás: + + slc -prefixcode='based.slc' hello.basd Megj.: @@ -1510,13 +1540,43 @@ Megj.: prefixje is megfelelően kell változzon. Ennek az egyik egyszerű módja, ha "gyári szó deklaráláshoz" külön szó van, de igazából ezt a "külön szót" valószínűleg mi magunk is megint csak implementálhatjuk valamilyen insertálással! - Megoldás erre: #builtin: prefixelendő_szó +Megoldás erre: -## Névtelen szó + #builtin: prefixelendo + #swap + #dup + +Ezt követően hívható a szó prefixelt változata + + #push(5) + #push(4) + #prefixelendo + #intprint // 5 + #intprint // 5 + #intprint // 4 + +Megj.: A #builtin esetben mindig van elég byte hely majd a parzernek helyben átírni a szót a megfelelőre (whitespace-es törléssel) + +## Névtelen szó - mégsem lesz!!! Hasznos leíírni, hogy szó nélkül, csak zárójel esetén mi történjen... -Ez lehet hogy baromira elbonyolít mindent mondjuk... Talán csak API-t kéne adni erre ki... +Ez lehet hogy baromira elbonyolít mindent mondjuk... Talán inkább csak API-t kéne adni a zárojeles parzolás segítésére... + +## Hibakezelés: MISSING szó + + #MISSING:(...)[...]{...}...# + +Ha a runtime olyan szót talál, amit elvileg FUTÁSKOR odaérve nem definiáltak le, akkor ez a missing meghívódik. + +Igazából ezen a ponton hibakezelést írhatunk le! + +## Hibakezelés: Zárójelezés + +Az engine amikor interpretál, akkor megkapja az "indentáltság mértéke" karbantartott értékét! +Ez nagyon fontos, mert ezzel értelmes humán hibaüzeneteket tudunk kapni a zárójelezés elmaradásakor! + +Tehát alapvetően az engine maga ellenőríz zárójelezést, nem kell a programnyelv tervezőjének ezt magát megcsinálni! ## Pipeline és párhuzamosság @@ -1551,60 +1611,92 @@ szöveges interfésszel, hanem simán csatornákkal és egy DAG-ot leírva, azé ## Reprezentáció struct word { - int16_t len; // (*): A codelen helyett teljes rekord hossz (ender-el bezárólag), a codelen számítható: a vars hossza+len! - char name[]; // inline! Tehát nem egy pointer! - char vars[]; // inline! zero-terminált! Ebben simán lineárisan keresünk csak! Kevés változót használjanak felsorolva! - char data[]; // inline! Előbb a változók, aztán a kód - így írható primitív forth-szavakkal is ha kell! + char colon; uint8_t flags; // ':' + whitespace, but the whitespace becomes flags! + char name[]; // inline! Tehát nem egy pointer! Zero terminált a whitespace / zárójel felülírásával. + char vars[]; // inline! Maga a tárhely is itt lesz! Elfér 4 byte ' @i,' vagy ' @j' miatt! + char data[]; // inline! Előbb a változók, aztán a kód - így írható primitív forth-szavakkal is kódszó (pl. outputra). }; -Variációk: +A szavakat az "ender" string terminálja, a hosszt itt alapból nem tároljuk. Amikor a parzer látja +a szó defet, akkor a változókat (pl. C stack-en) megjegyzi és a szimbólumtáblába ezt a címet írja! +A változó helyén a programszövegben pont elfér 4 byte, tehát magában ott lesz! A szimbólumtábla rámutat, +a forth szó belében pedig ha látjuk a @-os kezelését, akkor szimbólumtábla nélküli írásra fordítható át, +tehát a lokális használat sokkal hatékonyabb tud lenni, mert közvetlen memória címzés történik! - * pozitív vagy nulla len: 4-byte-os változók (n db) és SLC kód szövegként. - * negatív len legfelső bit 1: A legfelső bitet nullázva és a címet egy nagy indexként véve megkapjuk a "complex_word" indexet +Ehhez arra is szükség van, hogy egy szónak maximum 0..255 db változója lehessen - ugyanis legalább egy karakter a név +és a @-al kezdődő szó, vagyis a -at='^^' esetén pl. azzal... Szóval úgy két byte a kódban a hely, de az első parzerbe +szükséges szóval csak a maradék byte-ot (a névből számmá alakítással) tudjuk az interpreterben fordítani! Ilyen névtér +nélküli kukacozás csak szódefinícióban lehetséges - ezért az engine tudhatja, hogy mindig át kell ezt írni és ha a +szót a definíciója szerint "lefuttatunk" interpretálva, akkor pedig azt, hogy mindig már az átírt alak áll ott (nem a név)! -Mint az egyértelmű, a szó deklaráció a kód szövegben legalább 4 byte-ot igénybe vesz: +Ha már THREADED formába került a szó, akkor lényegében egyfajta FORTH-os jit-elésen már átesett, de ez nem akkor +történik, amikor a szót a parzer meglátja és a szimbólumtáblába bekerül, hanem amikor ELŐSZÖR MEGHÍVÓDIK. +Ugyanis amikor először látja az engine a szót, lehet hogy nem fordítható, még pl. későbbi szóra hivatkozik! +Viszont ekkor nem is szükséges még "csinálni" vele semmit a szimbólumtáblába a kezdőcímének beírásán túl, meg +hasonló dolgokon túl! Amikor az első hívás történik, akkor megpróbáljuk, hogy mekkora lenne a méret az átírással - ehhez +ideiglenes tárhely sem szükséges, csak egy plusz számítási menet, amikor még NEM csináljuk meg, csak kiszámoljuk mibe +kerülne és mennyi ram kell hozzá - kifér-e majd a jelen dolog helyére! Ha igen, akkor átírjuk + használjuk a stack-et. + +Flag-ek: + + 0. offset_top + 1. offset_top + 2. offset_top + 3. offset_top + 4. offset_top + 5. offset_top + 6. TYPE0: a típus első bitje + 7. TYPE1: a típus második bitje (00 - szöveg és futtatatlan, 01 - szöveg és futtatott, 11 - THREADED és futtatott + +EDIT: + + Jelenleg úgy néz ki, hogy itt hét bitet is használhatnék - kérdés kéne-e vagy legyen még valami flag ott, mondjuk + egy továbbfejlesztési lehetőségként. Szerintem az utóbbi és akkor a data[] inline helyett a hossz után ptr lesz! + +Mi az a "threaded kód"? + + A "threaded kód" lényegében karaktersorozat helyett egy uint32/64_t-sorozat, ahol szavak helyett indexek vannak + az adott szavak definíciós pontjaira - DE! Viszont a zárójelek is el vannak kódolva. Alapvetően uint32_t, de + a felső bit ha beáll akkor uint64_t kell két egymást követőt értelmezni (alsó 32 bit jön előbb módon!) + + Ettől jellemzően tömörebb és hatékonyabb lesz az interpretálás hiszen nem kell szöveget parzolni, nincs szótár lookup! + Viszont megeshet, hogy "nincs elég hely" tárolni az adatokat a forrás szöveg helyén és nem optimalizálható olyankor... + + + Érdekes elképzelés lehet egy kifejezetten UTF8-szerű encoding is, hogy lehetőleg 8-16-32-64 bitet használjunk csak / szó! + Szerintem ez fontos lenne, hogy az optimalizációt többször hajthassuk végre és ne csak hosszú szavakat használó esetben! + +Mint az egyértelmű, a szó deklaráció a kód szövegben legalább 4 byte-ot igénybe vesz és minden változó-deklarálás is! * ':' -* whitespace -* egy karakter a névből (kötelező) -* lezáró karakter -* a szó tartalma se lehet amúgy üres és legalább egy whitespace és egy karakter nevű szó áll majd ott... +* Egy whitespace - ebből lesz a "flag" is! +* Minimum egy karakter a névből (kötelező) +* lezáró karakter majd a végén +* Bár a szó tartalma lehet üres - de jellemzően van ott valami, csak erre nem számíthatunk! -Emiatt ha a legfelső bit egyes volt a word-ben, akkor ott egy négybyte-os indexre cserélhetjük a dolgot, ami egy complexword index: +A szó deklarációnál a "FLAG" mezőben az "adat elérési offsetje" felső bitjei jelennek meg. +Ez fontos, mert így tudjuk tárolni a változók egészét, kihagyni ott a zéró termináltatást! +Mivel a szimbólumtábla miatt a "név" már számunkra nem szükséges, azt a minimum egy byte +nevet is használhatjuk tárolásra és az offset alsó byte-ja van benn ott! Ez jó így, mert +ezáltal 8+6 bit eltolást tudunk, ami elég sok változót és névhosszakat enged meg (~16kb) - struct complex_word { - int64_t len; // a szó végére mutat, tehát ahol az input folytatódik! - char *name; - - int64_t var_count; - char **var_names; // tömb: session storage-ba - int32_t var1, var2, var3, var4; // cache - - // Regular word definition code - int64_t main_len; - char *main_data; - - // (...) - int64_t parentheses_len; - char *parentheses_data; - - // [...] - int64_t squarebracket_len; - char *squarebracket_data; - - // {...} - int64_t brace_len; - char *brace_data; - }; - -Bármikor átalakítható egy szó egy "complexword"-re! +A másik esetben, a változóknál a @ + legalább egybetűs név + pontosvessző + whitespace/sorvége +miatt, ahogy fent említettük szintén hasonló betárolást végez az interpreter engine! Megjegyzés: - * Azért ilyennek választottam, mert a ": " miatt a forrás szöveget beolvasva pont marad ott 16 byte mindenképp! - * Lásd még a rust compilerről szóló youtube videó valahol fentebb az SLC-s blokk előttről!!! + * Azért ilyennek választottam, mert a ": " miatt a forrás szöveget beolvasva pont marad ott 16 bit mindenképp! + * Lásd még a ZIG compilerről szóló youtube videó valahol fentebb az SLC-s blokk előttről miért jó ez cache-ileg így! * Ugyanis: a számított mezők jobbak, mint nagyobb / másik memória terület szükséges volna csak a node-ok tárolgatására. +## Minden esetben threaded kód optimalizálás + +Most arra jutottam, hogy minden esetben csináljuk meg a threaded kódot. Annyi, hogy ha nem fér el a régi helyén, akkor úgy +hát tegyük be a session storage-ba alternatívaként és mivel ez mindig akkor történik, amikor első lefutás van, ezért ott +tudjuk, hogy milyen nevet / sőt target indexet tart jelenleg ez a szó fenn - ilyenkor az összes előfordulást megkeressük +és át fogjuk majd írni az újra és kész. Ez egy kicsit hosszadalmas is lehet, ha nagy a kód, de csak 1x történik és hát +jó ritkán történik majd csak meg... + ## Az engine-hez szükséges Szerintem ezeket inline-olható, function pointerré kéne "elkódolnom", akár úgy, hogy feles paraméterek vannak + szétifelés! @@ -1615,18 +1707,19 @@ Szerintem ezeket inline-olható, function pointerré kéne "elkódolnom", akár * Szimbólumtáblára MAP adatstruktúra (pl. kismap - név alapú lookupra + változó tároláshoz is [key alapján tudható melyik!]) * Complexword-ök "vektora" (mindig indexelés alapú! Nem pointerezős! * Session storage - egy charakter vektorhoz hasonló, de csak állandóan növő storage + session reset rá. +* IO accessor (fájlműveletek, pipe-ok stb.) ^^Ezzel a memória kialakítással a memory layout: - | engine kód | callstack | adatstack | szimbólumtábla -> ... <-complexwords|insert_stack-> ... <- sessionstorage | src | + | enginecode | callstack | datastack | symbolmap -> ... <-malloc_store|insert_stack-> ... <- session_storage | src | Igazából embedded környezetben, ilyen kismap-szerű (vagy még egyszerűbb) szimbólumtáblával tehát a legtöbb dolog vagy konstans, vagy csak két ponton van balról-jobbra és jobbról-balra folyton akár végtelenig (memhatár) növő rész! -A complexwords egy embedded megoldásban "mozoghat", tehát mivel csak indexálással érjük el és NEM pointeresen, ezért +A `malloc_store` egy embedded megoldásban "mozoghat", tehát mivel csak indexálással érjük el és NEM pointeresen, ezért ott memcpy-vel mozgatható, ha kezd elfogyni vagy a szimbólumtábla, vagy a session storage. -A jobbra-balra mozgó complexwords-ön túl még egy ilyen memóriát tudnánk csinálni, tehát egy cövek két oldalát is használva! +A jobbra-balra mozgó indexes elérésű malloc-ozgatón túl még egy ilyen memóriánk van, tehát egy cövek két oldalát is használva! Így került oda a "insert-stack", ami interpretereknek nagyon hasznos és a callstack-beli ID-tól függően (mélység szám) bizonyos szavak esetén az src-t kiegészíthetjük vele! Tehát "írhatunk az inputra" úgy, hogy épp egy feldolgozott szó mögé! Ez az insert-álás! @@ -1649,6 +1742,10 @@ Megj.: ## Egyéb implementációs szempontok -* Legyen "sima C" kompatibilitási okokból a fordító +* Legyen "sima C" kompatibilitási okokból a fordító amivel írom. * Legyen az "engine" a fentiek fényében független a környezetétől (függvény pointeres megoldás) * Lehetőleg könnyen self-host-olható legyen az egész történet (minimális számú szóból a többi implementálható) + +Egy jó kezdőszett FORTH szavakból: + + https://github.com/davidjade/MiniForth/blob/master/Words.c