From f779f514b818ec48eafac58643fdffb21be5691a Mon Sep 17 00:00:00 2001 From: Richard Thier Date: Sat, 17 Aug 2024 20:31:16 +0200 Subject: [PATCH] The SLC / metalanguage got a lot of updates --- BASED.md | 702 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 560 insertions(+), 142 deletions(-) diff --git a/BASED.md b/BASED.md index f2605bd..2356f8d 100644 --- a/BASED.md +++ b/BASED.md @@ -285,6 +285,22 @@ a pointerre ki kell írni? A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk! +## Típusos indexek + +Egy pointer a modern gépen 64 bit, sokszor tömörebben tudsz tárolni "referencia-szerűséget" ha indexet tárolsz rá! + + struct A { uint32_t a, b, c; }; // 3*4=12 bytes with 4 byte alignment + struct B { child *a, *b, *c; }; // 3*8=24 bytes with 8 byte alignment + + struct C { A aa; uint32_t x; }; // 4*4=16 bytes with 4 byte alignment + struct D { B bb; uint32_t x; }; // 4*8=32 bytes with 8 byte alignment (4 wasted bytes) + +Viszont! A pointerek - mint itt is látszik - típusosak, de az indexek NEM! Viszont miért ne lehetnének? + + child@uint32_t index; // Például így + +A syntax-on lehetne még variálni... + ### NonOwningPointer - smartpointer Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű, @@ -1001,6 +1017,10 @@ A C/C++ kimenettel az a nehéz, hogy egyeztetni kell a szemantikáinkat - de miv - Természetesen érdemes elgondolkozni, hogy eleve adjunk-e build-system-et, vagy ne. Ez egy érdekes kérdés. Ha nem adunk, akkor a modul lookup stb. is bonyolódhat pl. de könnyebb beilleszteni a BASED programokat meglévő build rendszerbe amikor interop van (pl. makefile) és ugye kevesebb effort is megcsinálni. De inkább arra hajlok, hogy mi magunk, a fordítóval build-eljünk. Ez mondjuk még a "strukturált..." megvalósításától is függ, hogy miként is lenne jobb... Szóval valamennyire nyitott kérdés is... +## LSP + +- Sajnos egyetértek JonBlow-val és fasságnak tartom... De ha valaki akar, próbáljon írni egyet oké... + ## Csomagkezelés Nem igazán támogatom, bár ha véletlen sikeres nyelv volna, nehéz lenne megoldani, hogy mások ne csináljanak hozzá. De a legjobb az volna, ha first-class támogatás lenne a manuális csomag / modul kezelésnek, a sima bemásolgatósnak... @@ -1011,6 +1031,14 @@ Linux-first, onnan aki szeretné implementálja át portolással más OS-re szer # A fordítóprogram szerkezete +## Linkek: + +Infix -> lengyelforma + + https://brilliant.org/wiki/shunting-yard-algorithm/ + +## Leírás + Nem a tipikus rekurzív leszállós, vagy LALR jellegű fordítást csináljuk, hanem a korábbi ötletem mentén történne! Ez egy FORTH-szerű nyelv (stack alapú), ami viszont kiegészül hierarchikussággal és zárójelezéssel! @@ -1022,7 +1050,473 @@ konvenció szerint # jellel kezdjük: tehát például #include, vagy #if, esetl - Ha a fordításhoz akarunk új szavakat hozzáadni, azt konvenció szerint *.mag fájlokkal tesszük meg! - Az ilyen fájlok lényegében DSL-t írnak így le nekünk alapvetően és #include-al hozandók be! -- Alapvetően #passes(...) {...} leíróval írható le, hogy milyen meneteket futtatunk, mely *.mag fájlokkal! +- Alapvetően #passes(...) {...} leíróval írható le, hogy milyen meneteket futtatunk, mely *.mag (.slc?) fájlokkal! + +## Compiler implementációs tippek-trükkök + +Érdekes gondolatok (zig fordító implementáció): + + https://www.youtube.com/watch?v=IroPQ150F6c + +De szerintem mi a köztes adatot meghagyjuk string formában (ez sajnos valszeg több ramot eszik, mint kellene) + +Megj.: + + Igazából a bináris formát is támogatjuk, ha az SLC tud primitíveket bináris írás/olvasásra (és kell tudni legalább írásra!) + + + + + + + +# SLC: Strukturált formában tárolt programszöveg (tm) fordítás + +Az SLC jelentése kettős: SuckLessCode és SuckLessCompiler. Egy általános fordító / interpreter / transpiler / metaprogramozás tool. +Alapjában véve egy forth-szerű, interpretált általános célú nyelv önmaga is, de speciális nyelvi elemekkel compiler íráshoz! + +Az címet nehezebb érteni, ha nem ismerjük a kontextust: Van ez az ötletem, ami forth-szerű, de faszerkezetben strukturált "compiler", ahol a forth szavaknak lehetnek "gyerekei" és ilyen blokkjai. A (...) blokk is csak egy block, mint a [..], vagy akár a {...}. Ugyanúgy forth szavakat definiálunk és meg tudjuk mondani, hogy a compiler az adott szóhoz érve mit csináljon. Tehát konkrétan scriptelni tudjuk a fordítót. Most nem mennék bele ebbe, de pár kiegészítéssel (papíron ez részletesen is megvan) kb. az összes "értelmes" nyelvet lehet így "parzolni". A fordításban csak egymásba pipe-olva állítjuk elő a következő és következő változatokat több pass-ban - és az utolsó pass ugye mondjuk fizikai fájlba ír, vagy interpreter esetén mondjuk végrehajt. Ezzel debuggolni is tök könnyű a "compiler/interpreter"-t vagy nézni hol mit optimalizál. + +Viszont ha ezt megcsinálom, akkor ezt az itt leírt nyelvet, ezzel implementálva alapból van metaprogramozásunk - méghozzá mindennél erősebb. De ugye nem feltétlenül kényelmes is... Pl. a jai, vagy a zig comptime lehet hogy sokkal kényelemsebb. + +Megj.: A "néhány kiegészítés / részlet" azok olyasmik mint hogy nyilvántartjuk az indentálást (mind a hibajelzések, mind az ilyen python-szerű fos nyelvek miatt), kicsit trükközni kell a <..> jellegű dolgoknál (pl. generics, template), vagy épp hogy a "fordító scriptelése" azt is jelenti, hogy tudunk előre-olvasni vagy akár kézzel karakterenként olvasni és úgy mozogni az "input szöveg nyelén" ahogy Csörnyei mondaná Magyarosan a "handle"-t... Tehát ezzel tényleg nagyon testre szabható mi történik. Alapból stack-ed is van, mint a forth-nak, meg mellé vannak a fordító adatszerkezetei is (bár esetleg azokat csak akkor éred el, ha speciális jellegű szót definiálsz). De a gyakori state-machine használat miatt változói és adattagjai is vannak a szavaknak, továbbá zárójelezésük! + +## Fordítási időben / interpretálási időben futó szavak és példák + + #: int + process_varname + declare_int_var + # + + int alma; + +Macerásabb: + + struct MyAlma { + }; + + var MyAlma alma; // ??? var ??? több menetessel megoldható, hogy ne kelljen var, mert a típusok + +Mivel a szóvégek nem mindenhol "jó" helyen vannak, ezek átírandók: + + set x=1+ a(1, b(3, 4)); + +Ilyenre: + + set x = 1 + a(1 b(3 4)); + +Ha egy programnyelvnek saját "zárójelezése" van, ami esetleg még kontextusfüggő is, akkor ilyesmit lehet: + + #: PROCESS_LOOPS + #read + #if(TOP=="L") { + // .. O O P S... + } + # + + #: LOOP + WRITE('loops{') + PROCESS_LOOPS // -> {... + count + PROCESS_ENDLOOPS // -> }... + count + #read_until('E'); + # + + LOOP + ... + ENDLOOP + +## Deklaráció fordítási időben + +Ezt a turbobuf-szerű dolgot lehet aztán úgy is használni, ahogy a Seed7 programnyelv deklarál típustól függően +például egy min/max függvényt! Erről videót itt lehet látni: + + https://youtu.be/9m8gdgbAIrE?t=580 + +Náluk ez így néz ki: + + const proc: DECLARE_MIN_MAX(in type: T) is func + begin + const func T: min (in T: x, in T: y) is return x < y ? x : y; + const func T: max (in T: x, in T: y) is return x >= y ? x : y; + end func; + ... + DECLARE_MIN_MAX(integer); + DECLARE_MIN_MAX(MyType); + +Tehát náluk a "deklaráció" és definíció ugyanúgy kód, mint a függvény egyéb más törzsei, ám ha top-levelként, +azaz fordításkor futtatják ezt a procedúrát (nálunk "szó" lenne), akkor ugye deklarálódnak a dolgok. + +No mármost, nálunk ez ehhez hasonlatosan néz ki: + + #: DECLARE_MIN_MAX + ( #readword ) // runs first + // code that uses compiler primitives to add / build the word + # + +Azonban ez a forma nem éppen a legkényelmesebb. Ugyanis alapvetően a #: segítségével definiálunk egy "szót" a forth +nyelvhez hasonlatosan, ám nálunk a szavaknak lehet hierarchiája és így amikor a "fordító" meglátja az adott szót, +nem csak a definícióját (függvény-szerűen, threaded-code szerűen) végrehajtja, hanem az alatta adott szavak úgy +futnak le, hogy lehetnek (...) és [...] és {...} részei is (egészen a #-ig). Az adott szakaszban csak a belső +hierarchiát látó kódot írhatjuk, tehát a fenti példában a #readword egy egész szót olvas (mint magunk is), de azt +a (...) követő részéből a szónak - ha nincs ilyen, akkor semmi sem történik. A zárójeleken kívüli résznél lévő +kód pedig a külső, a szó megjelenésének első betűjén álló olvasó (és író) fejjel fut. + +Rendelkezésünkre áll egy(esetleg kettő?) forth-szerű stack, erre a #push stb. szavakkal lehet hivatkoznunk. +Itt a push a nyelv struktúrája miatt használható "42" helyett "#push(42)" - ez azért kell, hogy a valódi prognyelv +immediate-jei normálisan használhatók maradjanak, ugyanis nem tűnik jónak, ha alapesetben minden szám itt a stack +tetejére csak felmenne, ha egy szó helyett egy számot látunk épp... + +Egy minimál forth(-szerű) szószett áll rendelkezésre: #swap, #drop, #iadd, #imul, fadd(?), fmul(?), stb. +De nem ilyen 32 / 10 szavas forth-ból érdemes kiindulni (bár lehetne), hanem valami praktikusból! + +Lásd például innen jó sok dolog: + + http://www.murphywong.net/hello/simple.htm + +Emellett olyan szavak is rendelkezésre állnak, amelyek a "fordítót scriptelik". Ilyen például a #readword, ami +a stackre olvassa a "talált szót" és mozgatja az input nyelét. A stack-en alapvetően uint32 méretű szám lehet! +Természetesen rendelkezésre áll még a #readchar, #readline, #readuntil is, továbbá a #write(...) is! + +Régi forth-os esetekre lásd hasonlóért: + + https://www.forth.com/starting-forth/11-forth-compiler-defining-words/ + +## A #readword használata a következő: + +- A szó string belseje olvasható karakterenként: 42 #wordchar, #wordchar[42], 1 #wordchar(41) +- Ehhez természetesen a stack-re kerül a szó TÖVE (zárójelek nélkül) és hossza is (ebben a sorrendben)! +- Szerintem nem árt egy #strcmp('for') jellegű dolog, amivel a word szó része olvasható... 0-1-et tesz a stackre! +- Jó lehet még: #strprefix('starts-with-text'), esetleg #strsuffix(...) is ami 0 vagy N ad (N db megfelelő char!) +- Ez a kialakítás pazarlónak tűnhet a 32 bites stack miatt, de így nem lesz ram szivárgás a rendszerben! +- Egyébként a reprezentációt nem kötöttem ki ezzel - szóval lehet 4 karaktert tárolni egy szóban itt ám! +- Ennek megfelelően kell #popword utasítás, ami leszedi az egész szót a stack-ről ha nem érdekel már! +- Szükség lehet például még #dupword utasításra is - ezek végül is "string kezelő" rutinok... +- Az olvasó fej a szó (nem-zárójeles része) mögé mozog. + +A #readword(raw) a zárójelezéssel együtt az egészet olvassa be, míg #readword(full) ugyanez, de wspace*->space! +Szükséges még a #skipword és #skipword(full) szavak, melyek spórolósabbak, mint olvasni és drop-olni... + +Vegyük észre, hogy a zárójeleket ezzel alapesetben nem kezeltük: + +- #has(), #has[], #has{} - ezek kiadják, a szót követőe nyélen állva, ahhoz tartozik-e adott szekció (0 vagy 1) +- Nem tartozhat több (..) egy adott szóhoz (se a másik eseteknél)! Tehát mindegyik blokkféleségből max egy van! +- De a fordító scriptelős feldolgozásnál ott viszont állhat többször és a sorrendiséget fejezi ki csupán! +- #enter(), #enter[], #enter{} - ezekkel bemegyünk olyan módba, hol az adott blokk vége eof-ként értődik +- #exit - ez lép eggyel vissza - de az adott enterrel megkezdett dolog VÉGE mögé állunk (feldolgoztuk) +- FONTOS: Az enter-exit lényegében automatikus, amikor a definícióban a megfelelő helyre írjuk a kódunkat, tehát + a zárójelek belsejébe írtuk azt a részt, amikor a zárójelen belüli dolgokat dolgozzuk fel... + +Viszont azt is vegyük észre, hogy a szó tartalmazhat whitespace-t, ha teljességében olvassuk be + +## Insert-álás (az input stream-en) + +Factor programnyelven láttam olyat, hogy a bejövő szó-folyam aktuális pontjánra "írhatok" is akár... +Ezt valahogy talán jó lenne támogatni, de lehet hogy felesleges. Ezzel nem csak a #write outputra írása lenne, +hanem jelenleg ahol tart a végrehajtás, azt a programszöveget tudnánk módosítani. A fordításhoz szerintem ez +nem kifejezetten ad plusz funkcionalitást, de az interpreterként működéshez viszont igen... + +Ennek megfelelően valami hasonló lehetne: + + #: DECLARE_MIN_MAX_INT + #inserts { + #: MIN_INT + #IF(#LT) { + #DROP + } [ + #SWAP + #DROP + ] + # + #: MAX_INT + #IF(#GT) { + #DROP + } [ + #SWAP + #DROP + ] + # + } + # + +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ó +futtatása, a rendszer a #: deklarációs szót fogja az inputján látni. Ezzel az input kiegészítésre került! + +A #inserts-hez tartozhatnak paraméterek: + + 42 21 #inserts (N K) { #push($N) #writechar #push($K) #push(2) #imul #writechar } + +Természetesen ez sokkal érdekesebb, ha nem így magában áll, hanem szó deklarációban, de a lényeg, +hogy a zárójelek között felsorolhatok dolgokat, ami a stack-ről értéket kap. A $$ a $ escape-elése csupán... + +Természetesen itt kellhet típust adni: + + 'some text word' 21 #inserts (word(W) float(F)) { .... } + +Az inserts továbbá rendelkezhez output átirányítós paraméterrel: + + #inserts [0] { ... } + +^^Ez például a 0.-dik kimenetre / pipe-ra ír, szerintem erre külön kéne saját szó: + + #outputs { ... } + 42 21 #errors (N K) { ... } + +Az insert-hez szükséges: + +- Egy plusz verem / adatszerkezet, ahova az kerül amivel kiegészítettem a jelen pozíciót, ha beljebb vagyok, nyélmozog+másol! +- A többit igazából meg kell tudjuk írni a nyelv meglévő elemeivel, de perf okból valszeg jó ha beépítetten jön? + +## Szimbólumtábla + +Rendelkezésre áll továbbá a "szimbólumtábla" is, ami egy map adatszerkezet és szónévből egy definícióra mutat +illetve annak tulajdonságainak elérésére, illetve új bejegyzések beírására hasznos szavaink is lesznek... + +Szükség lehet ezen a ponton fullword(W) jellegű paraméterre is, ami a zárójelekkel együtt értett teljes szót +jelképezi, de szerintem alapjában a word, mint típus, lényegében "string" típus ugye a hossz + tárolás mód.. + +A szimbólumtábla egy szimbóluma megnyitható "fájlszerűen", ezért a definícióját write paranccsal megírhatjuk! + +**Megj.: Talán ezzel kiváltható a #inserts kulcsszó használata! De egy plusz stack-el is...** + +## A #write + +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 +hasonló módon, de külső fájlba (vagy standard kimenetre, error-ra) tud írkálni. + +Természetesen ennek megfelelően kell még #fopen[0](out.txt) jellegű dolog is, ami a nullás fájlt nyitja (mondjuk +4-8 ilyen lehessen) és #fclose[0] szó is. Kell még stdout[0] és stderr[1] jellegű dolog is... + +Igazából a "fordító" úgy fordít, hogy sok menetben, folyamatosan át-transzformálja a programszöveget egy másik +programszöveggé, melynek elején van egy #include, ami behozza az adott új "makrókat" és azokkal az új menet mit +fog pontosan is csinálni már... Ennek megfelelően például az ifeket hamar ugrásokra tudjuk fordítani stb. + +A nyelvnek a köztes reprezentációja lényegében szintén emberileg olvasható szöveges kód és meg is állítható egy +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. + +## Template makró és egyéb példák + +Fordítandó (pl. JASS transpilerhez): + + if a > 1 then + set b = 2 + elseif a < -1 then + set b = 3 + else + set b = 0 + endif + +Jó lenne (bnf-szerű): + + $if = if $expr then $code (elseif $expr then $code)* (else $code)? endif + +De imperatívan így írható ez le (expression gyárilag jöhet(?) és stack segítségével lengyelformára hozás, shunting yard algó): + + #: expr + #expression(+ - / *) [0 1 2 3] + #writeexpr + # + +Azért code reuse lehetséges: + + #: if + #progress_until("then") { + #expr + } + # + +Az első menet után "standardabb" formába írva vannak a dolgok és deklaratív leírás adható már: + + // PASS #2 + + if (expr(3)[a 1 >]) + { + set b = 2 + } + [ + if (expr(3)[3 a -1 <]) + { + set b = 3 + } + [ + set b = 0 + ] + ] + + // Alapvetően #: esetén a () utáni részbe írt az imperatívan fut le + // Itt viszont paraméter a template-ezéshez - mindegy a sorrend? + // Ez már nem kell C-s szó legyen, csak egy #insert-re forduljon! + ##: if($con){$then}[$else] + condition($con) // ebx és eax között hasonlításra felépít - stack-re vissza adja milyen jump + asm(cmp eax,ebx) + jumptype(else) // jnz / jg / jl stack alapján.... + loclabel(then:) + $then + loclabel(else:) + $else + # + +Bonyolultabb példa (bogus példa - "bement-e az if ágba az alapján az if true-false ad vissza mint expression pl") +de valamiért külön match-elni akarom + + ##: if(if($con) {$value1} [$value2]) {$then} [$else] + +^^lehet hogy ezt azért nem kéne, mert fadiffelést okoz... + +másik példa (Jass nyelvről újra): + + set a = 1 + LOOP + set a = (a + 1) + EXITWHEN a == 42 + ENDLOOP + +Ebből legyen - útána meg már elvileg nem olyan nehéz: + + set a = 1 + LOOP { + set(a) {(a + 1)} + EXITWHEN(a == 42) + } + +## A makró nyelven a FORTH-szerű szavaknak változói / adattagjai is lehetnek (nem csak kódja)! + +- a szóhoz tartozó olvasása a stack-re tehát csak a neve, illetve a kukacos neve +- írása meg a zárójelbe tett dolgot írja bele - kivéve hogy ha ott "pont" áll akkor a stack tetejét írja bele és csőváz +- csak 4 byte-os int van és csőváz... Ha valaki mást akar megoldja "handle"-kkel majd meg float-ra lesz gyakorlatilag reinterpret cast-olgatás vagyis a művelet határozza meg, hogy float-ként dolgozol-e a négy byte-al, de ez talán nem baj, ha extension, mert a fordítóba nem nagyon kellenek float-ok szerintem..... + +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 + #inc + #a(.) + # + +Máshonnan elérni így lehet, namespace-elés szerűen: + + MYWORD@a(41) + MYWORD + MYWORD@a + #intprint + +Ha nem szerepel több szó a definíciós első sorban az enter előtt / zárójelek előtt, akkor csak kódot tartalmazó "funkcionális" szó: + + #: #just_code + #dup + #inc + #swap + # + + #push(12) + #just_code + #intprint // 12 + #intprint // 13 + +Vagy: + + #: #just_code ( + #dup + #inc + #swap + ) + # + +Globális változót ezt követően úgy definiálhatunk, hogy namespace-eléssel valami értelmes "namespace"-be tesszük egy szó alá, +tehát alapból van egy rendszerezettség (például egy adott state-machine globális változói stb.) + +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 + # + +Esetleg: + + #: MY_VAR int # + +Használat: + + #push(41) + MYVAR@int(.) + + MYVAR@int + #inc + #print // 42 + +Ja nem mondtam, de az érték megadása a zárójelek közt működik, a kiolvasás meg anélkül. Ha pont szerepel, a stackről kap értéket! + +Megj.: + + A zárójelek közti rész NEM expression... csak simán egy szám (esetleg ugye 'a' 'abcd' módon 0..4 karakter!) + +Megj.: + + Igazából kicsit fura, hogy a forth-ban nem találták ezt ki a változókra... jó ötletnek néz ki interpretálásnál... + +Megj.: + + Az adattagok arra is "jók", hogy a menetek egymásnak kommunikáljanak vele és így kvázi "blokkokra ható" dolgokat írhatunk. + Ennek akkor van jeletősége, ha a "blokkot" előtte lévő, korábban azt megelőző dolgok "változtatják" jelentésében, mert akkor + létrehozhatunk egy számozott blokk_42 szót a kimeneten (definícióval), míg a blokk helyén "hívhatjuk" és ott elérhető a + korábban beállított változó anélkül, hogy nagy káosz keletkezne (de ez csak egy példa, nem kell így csinálni!) + +## Típusok a meta-nyelven + +Alapból a változók és a stack 32bites (előjeles) int típusú - ez kb. mindent jól leír és a legtöbb architektúrán gyors is. + +Nem igazán vannak, de a forth-szavakba elkódolható a típus (assembly-hez hasonlóan). + +Például string-kezelő dolgok kellenek inputon! Az input nyelét olvasva pl. karakter kerülhet a stack-re (int ascii), +de hasonlóan karakterláncot is "építhetünk" (vagy olvashatunk) a stack-en (-re), olyankor minden elem max 4 karakter és van a +stack tetején egy hossz jelző! Ahhoz, hogy könnyen építhessük a stack mélyebb része a szó eleje és a tetejéhez közel a vége! +Ez fontos, mert így minimális stack műveletet eredményez ha "stringbuilderezgetünk"! Ami egy transpiler tech-nek elég fontos... + +Szerintem kellenek ugyan ilyen szavak arra is, hogy bináris adat stream-et olvassunk és írjunk. Ugyanez oldja meg ezt is! + +## Naming convention ('#'-jelek itt mindenfele...) + +Ha az slc-t csak magában használjuk (interpreterként - default működés): + + Akkor nincsenek a gyári szavaknak prefixei, ellenben meg kell adni a "záró" szót (ami pontosvessző defaultként). + +Ha programnyelvet csinálunk: + + Alapből minden, nem a célnyelvhez tartozó szó '#'-al kezdődjön. A "gyári" forth szavak így jönnek tehát, de amiket csinálsz is + jó ha ilyenek - kivéve amikor a nyelvet, amit transpile-olsz szeretnéd már definiálni, vagy ha eleve nem nyelvet csinálsz + hanem a meta-nyelven magán "nyersen" programozol... tehát ha interpretált programnyelvnek használod a meta-nyelvet magát... + + +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 + +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='%' + +Default: Üres prefix és pontosvessző ender. Ezzel forth-szerű interpretert kapunk! + +Megj.: + + Trükkösködést jelenthet, ha adott fordító implementációban a "gyári" szavak egy részét SLC kódként implementáljuk, + például helytakarékossági okokból egy embedded környezetben. Ilyenkor ugyanis az így implementált "gyári" szavak + 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ó + +## Névtelen szó + +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... ## Pipeline és párhuzamosság @@ -1054,172 +1548,96 @@ kiegészítettünk potencionálisan egy jó kis aszinkronitással. Ennek a megfelelője talán meglehetne a BASED programnyelven is egyébként! Esetleg ott nem stdout/err meg ilyen szöveges interfésszel, hanem simán csatornákkal és egy DAG-ot leírva, azért sok minden kifejezhető. -## Strukturált formában tárolt programszöveg (tm) fordítás +## Reprezentáció -Az első pontot nehezebb érteni, ha nem ismerjük a kontextust: Van ez az ötletem, ami forth-szerű, de faszerkezetben strukturált "compiler", ahol a forth szavaknak lehetnek "gyerekei" és ilyen blokkjai. A (...) blokk is csak egy block, mint a [..], vagy akár a {...}. Ugyanúgy forth szavakat definiálunk és meg tudjuk mondani, hogy a compiler az adott szóhoz érve mit csináljon. Tehát konkrétan scriptelni tudjuk a fordítót. Most nem mennék bele ebbe, de pár kiegészítéssel (papíron ez részletesen is megvan) kb. az összes "értelmes" nyelvet lehet így "parzolni". A fordításban csak egymásba pipe-olva állítjuk elő a következő és következő változatokat több pass-ban - és az utolsó pass ugye mondjuk fizikai fájlba ír, vagy interpreter esetén mondjuk végrehajt. Ezzel debuggolni is tök könnyű a "compiler/interpreter"-t vagy nézni hol mit optimalizál. + 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! + }; -Viszont ha ezt megcsinálom, akkor ezt az itt leírt nyelvet, ezzel implementálva alapból van metaprogramozásunk - méghozzá mindennél erősebb. De ugye nem feltétlenül kényelmes is... Pl. a jai, vagy a zig comptime lehet hogy sokkal kényelemsebb. +Variációk: -Megj.: A "néhány kiegészítés / részlet" azok olyasmik mint hogy nyilvántartjuk az indentálást (mind a hibajelzések, mind az ilyen python-szerű fos nyelvek miatt), kicsit trükközni kell a <..> jellegű dolgoknál (pl. generics, template), vagy épp hogy a "fordító scriptelése" azt is jelenti, hogy tudunk előre-olvasni vagy akár kézzel karakterenként olvasni és úgy mozogni az "input szöveg nyelén" ahogy Csörnyei mondaná Magyarosan a "handle"-t... Tehát ezzel tényleg nagyon testre szabható mi történik. Alapból stack-ed is van, mint a forth-nak, meg mellé vannak a fordító adatszerkezetei is (bár esetleg azokat csak akkor éred el, ha speciális jellegű szót definiálsz). + * 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 -### Fordítási időben / interpretálási időben futó szavak +Mint az egyértelmű, a szó deklaráció a kód szövegben legalább 4 byte-ot igénybe vesz: -Ezt a turbobuf-szerű dolgot lehet aztán úgy is használni, ahogy a Seed7 programnyelv deklarál típustól függően -például egy min/max függvényt! Erről videót itt lehet látni: +* ':' +* 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... - https://youtu.be/9m8gdgbAIrE?t=580 +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: -Náluk ez így néz ki: + struct complex_word { + int64_t len; // a szó végére mutat, tehát ahol az input folytatódik! + char *name; - const proc: DECLARE_MIN_MAX(in type: T) is func - begin - const func T: min (in T: x, in T: y) is return x < y ? x : y; - const func T: max (in T: x, in T: y) is return x >= y ? x : y; - end func; - ... - DECLARE_MIN_MAX(integer); - DECLARE_MIN_MAX(MyType); + int64_t var_count; + char **var_names; + int32_t variables; -Tehát náluk a "deklaráció" és definíció ugyanúgy kód, mint a függvény egyéb más törzsei, ám ha top-levelként, -azaz fordításkor futtatják ezt a procedúrát (nálunk "szó" lenne), akkor ugye deklarálódnak a dolgok. + // Regular word definition code + int64_t main_len; + char *main_data; -No mármost, nálunk ez ehhez hasonlatosan néz ki: + // (...) + int64_t parentheses_len; + char *parentheses_data; - #: DECLARE_MIN_MAX - ( #readword ) // runs first - // code that uses compiler primitives to add / build the word - #end + // [...] + int64_t squarebracket_len; + char *squarebracket_data; -Azonban ez a forma nem éppen a legkényelmesebb. Ugyanis alapvetően a #: segítségével definiálunk egy "szót" a forth -nyelvhez hasonlatosan, ám nálunk a szavaknak lehet hierarchiája és így amikor a "fordító" meglátja az adott szót, -nem csak a definícióját (függvény-szerűen, threaded-code szerűen) végrehajtja, hanem az alatta adott szavak úgy -futnak le, hogy lehetnek (...) és [...] és {...} részei is (egészen a #end-ig). Az adott szakaszban csak a belső -hierarchiát látó kódot írhatjuk, tehát a fenti példában a #readword egy egész szót olvas (mint magunk is), de azt -a (...) követő részéből a szónak - ha nincs ilyen, akkor semmi sem történik. A zárójeleken kívüli résznél lévő -kód pedig a külső, a szó megjelenésének első betűjén álló olvasó (és író) fejjel fut. + // {...} + int64_t brace_len; + char *brace_data; + }; -Rendelkezésünkre áll egy(esetleg kettő?) forth-szerű stack, erre a #push stb. szavakkal lehet hivatkoznunk. -Itt a push a nyelv struktúrája miatt használható "42" helyett "#push(42)" - ez azért kell, hogy a valódi prognyelv -immediate-jei normálisan használhatók maradjanak, ugyanis nem tűnik jónak, ha alapesetben minden szám itt a stack -tetejére csak felmenne, ha egy szó helyett egy számot látunk épp... +Bármikor átalakítható egy szó egy "complexword"-re! -Egy minimál forth(-szerű) szószett áll rendelkezésre: #swap, #drop, #iadd, #imul, fadd(?), fmul(?), stb. -De nem ilyen 32 / 10 szavas forth-ból érdemes kiindulni (bár lehetne), hanem valami praktikusból! +Megjegyzés: -Lásd például innen jó sok dolog: + * 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!!! + * 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. - http://www.murphywong.net/hello/simple.htm +## Az engine-hez szükséges -Emellett olyan szavak is rendelkezésre állnak, amelyek a "fordítót scriptelik". Ilyen például a #readword, ami -a stackre olvassa a "talált szót" és mozgatja az input nyelét. A stack-en alapvetően uint32 méretű szám lehet! -Természetesen rendelkezésre áll még a #readchar, #readline, #readuntil is, továbbá a #write(...) is! +Szerintem ezeket inline-olható, function pointerré kéne "elkódolnom", akár úgy, hogy feles paraméterek vannak + szétifelés! -Régi forth-os esetekre lásd hasonlóért: +* Engine kódja +* Callstack - előre adott méret (pl. parancssori paraméter, vagy embeddednél beállított) +* Adatstack, a műveleteivel - előre adott méret (pl. parancssori paraméter, vagy embeddednél beállított) +* 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á. - https://www.forth.com/starting-forth/11-forth-compiler-defining-words/ +^^Ezzel a memória kialakítással a memory layout: -A #readword használata a következő: + | engine kód | callstack | adatstack | szimbólumtábla -> ... <-complexwords|-> ... <- sessionstorage | src | -- A szó string belseje olvasható karakterenként: 42 #wordchar, #wordchar[42], 1 #wordchar(41) -- Ehhez természetesen a stack-re kerül a szó TÖVE (zárójelek nélkül) és hossza is (ebben a sorrendben)! -- Szerintem nem árt egy #strcmp('for') jellegű dolog, amivel a word szó része olvasható... 0-1-et tesz a stackre! -- Jó lehet még: #strprefix('starts-with-text'), esetleg #strsuffix(...) is ami 0 vagy N ad (N db megfelelő char!) -- Ez a kialakítás pazarlónak tűnhet a 32 bites stack miatt, de így nem lesz ram szivárgás a rendszerben! -- Egyébként a reprezentációt nem kötöttem ki ezzel - szóval lehet 4 karaktert tárolni egy szóban itt ám! -- Ennek megfelelően kell #popword utasítás, ami leszedi az egész szót a stack-ről ha nem érdekel már! -- Szükség lehet például még #dupword utasításra is - ezek végül is "string kezelő" rutinok... -- Az olvasó fej a szó (nem-zárójeles része) mögé mozog. +Alternatíva: -A #readword(raw) a zárójelezéssel együtt az egészet olvassa be, míg #readword(full) ugyanez, de wspace*->space! -Szükséges még a #skipword és #skipword(full) szavak, melyek spórolósabbak, mint olvasni és drop-olni... + | engine kód | callstack | szimbólumtábla -> ... <-complexwords|adatstack-> ... <- sessionstorage | src | -Vegyük észre, hogy a zárójeleket ezzel alapesetben nem kezeltük: +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! -- #has(), #has[], #has{} - ezek kiadják, a szót követőe nyélen állva, ahhoz tartozik-e adott szekció (0 vagy 1) -- Nem tartozhat több (..) egy adott szóhoz (se a másik eseteknél)! Tehát mindegyik blokkféleségből max egy van! -- De a fordító scriptelős feldolgozásnál ott viszont állhat többször és a sorrendiséget fejezi ki csupán! -- #enter(), #enter[], #enter{} - ezekkel bemegyünk olyan módba, hol az adott blokk vége eof-ként értődik -- #exit - ez lép eggyel vissza - de az adott enterrel megkezdett dolog VÉGE mögé állunk (feldolgoztuk) -- FONTOS: Az enter-exit lényegében automatikus, amikor a definícióban a megfelelő helyre írjuk a kódunkat, tehát - a zárójelek belsejébe írtuk azt a részt, amikor a zárójelen belüli dolgokat dolgozzuk fel... +A complexwords 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. -Viszont azt is vegyük észre, hogy a szó tartalmazhat whitespace-t, ha teljességében olvassuk be +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! +Odakerülhet tehát például még egy másik stack is ha szeretnénk (pl. memhatárig növelhető adat stack beállítás / paraméter?) -### Insert-álás az input stream-en +Ennél bonyolultabb memory management nem szükséges - tehát nem kell "malloc-implementáció" hozzá embeddedben! -Factor programnyelven láttam olyat, hogy a bejövő szó-folyam aktuális pontjánra "írhatok" is akár... -Ezt valahogy talán jó lenne támogatni, de lehet hogy felesleges. Ezzel nem csak a #write outputra írása lenne, -hanem jelenleg ahol tart a végrehajtás, azt a programszöveget tudnánk módosítani. A fordításhoz szerintem ez -nem kifejezetten ad plusz funkcionalitást, de az interpreterként működéshez viszont igen... +Megj.: -Ennek megfelelően valami hasonló lehetne: - - #: DECLARE_MIN_MAX_INT - #inserts { - #: MIN_INT - #IF(#LT) [ - #DROP - ] { - #SWAP - #DROP - } - #end - #: MAX_INT - #IF(#GT) [ - #DROP - ] { - #SWAP - #DROP - } - #end - } - #end - -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ó -futtatása, a rendszer a #: deklarációs szót fogja az inputján látni. Ezzel az input kiegészítésre került! - -A #inserts-hez tartozhatnak paraméterek: - - 42 21 #inserts (N K) { #push($N) #writechar #push($K) #push(2) #imul #writechar } - -Természetesen ez sokkal érdekesebb, ha nem így magában áll, hanem szó deklarációban, de a lényeg, -hogy a zárójelek között felsorolhatok dolgokat, ami a stack-ről értéket kap. A $$ a $ escape-elése csupán... - -Természetesen itt kellhet típust adni: - - 'some text word' 21 #inserts (word(W) float(F)) { .... } - -### Szimbólumtábla - -Rendelkezésre áll továbbá a "szimbólumtábla" is, ami egy map adatszerkezet és szónévből egy definícióra mutat -illetve annak tulajdonságainak elérésére, illetve új bejegyzések beírására hasznos szavaink is lesznek... - -Szükség lehet ezen a ponton fullword(W) jellegű paraméterre is, ami a zárójelekkel együtt értett teljes szót -jelképezi, de szerintem alapjában a word, mint típus, lényegében "string" típus ugye a hossz + tárolás mód.. - -A szimbólumtábla egy szimbóluma megnyitható "fájlszerűen", ezért a definícióját write paranccsal megírhatjuk! - -**Megj.: Szerintem ezzel kiváltható a #inserts kulcsszó használata!!!** - -### A #write - -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 -hasonló módon, de külső fájlba (vagy standard kimenetre, error-ra) tud írkálni. - -Természetesen ennek megfelelően kell még #fopen[0](out.txt) jellegű dolog is, ami a nullás fájlt nyitja (mondjuk -4-8 ilyen lehessen) és #fclose[0] szó is. Kell még stdout[0] és stderr[1] jellegű dolog is... - -Igazából a "fordító" úgy fordít, hogy sok menetben, folyamatosan át-transzformálja a programszöveget egy másik -programszöveggé, melynek elején van egy #include, ami behozza az adott új "makrókat" és azokkal az új menet mit -fog pontosan is csinálni már... Ennek megfelelően például az ifeket hamar ugrásokra tudjuk fordítani stb. - -A nyelvnek a köztes reprezentációja lényegében szintén emberileg olvasható szöveges kód és meg is állítható egy -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. - -## LSP - -- Sajnos egyetértek JonBlow-val és fasságnak tartom... De ha valaki akar, próbáljon írni egyet oké... + A session storage alapvetően arra kell, hogy stringeket építsünk. Tudom... Erre a stack is alkalmas, de kellhet az olvasott + inputról (src) valahogy a complex szó átírásnál, meg az outputra írásokhoz előkészítésnél valami ilyen tárhely, ami azért + perzisztensebb, mint a sima stack! Az src természetesen épp függ attól, amely fájlt olvasunk, szóval a session kezdetén az + egész session storage..src végig törlődhet majd. Egy embedded (vagy pl. retró számítógép) memóriáját ezzel kihasználjuk!