more SLC descriptions

This commit is contained in:
Richard Thier 2024-08-18 00:48:04 +02:00
parent 577ac45bb6
commit f56a322328

215
BASED.md
View File

@ -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<EOL>' 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