The SLC / metalanguage got a lot of updates

This commit is contained in:
Richard Thier 2024-08-17 20:31:16 +02:00
parent 61794943fb
commit f779f514b8

702
BASED.md
View File

@ -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! 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 ### 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ű, 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... - 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 ## 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... 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 # 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! 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! 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! - 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! - 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 ## 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 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ő. 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 int64_t var_count;
begin char **var_names;
const func T: min (in T: x, in T: y) is return x < y ? x : y; int32_t variables;
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, // Regular word definition code
azaz fordításkor futtatják ezt a procedúrát (nálunk "szó" lenne), akkor ugye deklarálódnak a dolgok. 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 int64_t squarebracket_len;
// code that uses compiler primitives to add / build the word char *squarebracket_data;
#end
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, int64_t brace_len;
nem csak a definícióját (függvény-szerűen, threaded-code szerűen) végrehajtja, hanem az alatta adott szavak úgy char *brace_data;
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.
Rendelkezésünkre áll egy(esetleg kettő?) forth-szerű stack, erre a #push stb. szavakkal lehet hivatkoznunk. Bármikor átalakítható egy szó egy "complexword"-re!
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. Megjegyzés:
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: * 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 Szerintem ezeket inline-olható, function pointerré kéne "elkódolnom", akár úgy, hogy feles paraméterek vannak + szétifelés!
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: * 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) Alternatíva:
- 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! | engine kód | callstack | szimbólumtábla -> ... <-complexwords|adatstack-> ... <- sessionstorage | src |
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: 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) A complexwords egy embedded megoldásban "mozoghat", tehát mivel csak indexálással érjük el és NEM pointeresen, ezért
- Nem tartozhat több (..) egy adott szóhoz (se a másik eseteknél)! Tehát mindegyik blokkféleségből max egy van! ott memcpy-vel mozgatható, ha kezd elfogyni vagy a szimbólumtábla, vagy a session storage.
- 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 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... Megj.:
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: 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
#: DECLARE_MIN_MAX_INT 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
#inserts { 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!
#: 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é...