Foar ferskate jierren haw ik myn hân besocht om myn eigen programmeartaal te ûntwikkeljen. Ik woe neffens my de meast ienfâldige, folslein funksjonele en handige taal mooglik meitsje.
Yn dit artikel wol ik de haadfazen fan myn wurk markearje en, om te begjinnen, it makke konsept fan de taal en de earste ymplemintaasje beskriuwe, dêr't ik no oan wurkje.
Lit my fan tefoaren sizze dat ik it hiele projekt yn Free Pascal skreaun haw, om't... programma's dêrop kinne wurde gearstald foar in grut oantal platfoarms, en de kompilator sels produseart heul optimalisearre binaries (ik sammelje alle komponinten fan it projekt mei de O2-flagge).
Taal runtime
Alderearst is it wurdich te praten oer de firtuele masine dy't ik skriuwe moast om takomstige applikaasjes yn myn taal út te fieren. Ik besleat miskien in stack-arsjitektuer út te fieren, om't it de maklikste manier wie. Ik fûn gjin inkeld normaal artikel oer hoe't ik dit dwaan yn it Russysk, dus nei't ik my mei it Ingelsktalige materiaal yn 'e kunde hân hie, siet ik myn eigen fyts te ûntwerpen en te skriuwen. Hjirnei sil ik myn "avansearre" ideeën en ûntwikkelingen yn dizze saak presintearje.
Stack ymplemintaasje
Fansels is oan 'e boppekant fan' e VM de stapel. Yn myn ymplemintaasje wurket it yn blokken. Yn essinsje is dit in ienfâldige array fan pointers en in fariabele om de yndeks fan 'e top fan' e stapel op te slaan.
As it is inisjalisearre, wurdt in array fan 256 eleminten makke. As mear oanwizers wurde skood op 'e steapel, syn grutte nimt ta mei de folgjende 256 eleminten. As gefolch, by it fuortheljen fan eleminten út 'e stapel, wurdt har grutte oanpast.
De VM brûkt ferskate stapels:
- Haadsteapel.
- In steapel foar it bewarjen fan werom punten.
- Garbage collector stack.
- Besykje / fange / úteinlik blokkearje handlerstack.
Konstinten en fariabelen
Dizze is ienfâldich. Konstinten wurde behannele yn in apart lyts stikje koade en binne beskikber yn takomstige applikaasjes fia statyske adressen. Fariabelen binne in array fan pointers fan in bepaalde grutte, tagong ta syn sellen wurdt útfierd troch yndeks - d.w.s. statyske adres. Fariabelen kinne wurde skood nei de top fan 'e steapel of lêzen fan dêr. Eins, om't Wylst ús fariabelen yn essinsje wizers nei wearden opslaan yn VM-ûnthâld, wurdt de taal dominearre troch te wurkjen mei ymplisite oanwizers.
Garbage samler
Yn myn VM is it semy-automatysk. Dy. de ûntwikkelder sels beslút wannear't de jiskefet belje. It wurket net mei in gewoane pointer-teller, lykas yn Python, Perl, Ruby, Lua, ensfh. It wurdt útfierd fia in markearring systeem. Dy. wannear in fariabele is bedoeld in wurde tawiisd in tydlike wearde, in oanwizer nei dizze wearde wurdt tafoege oan de garbage collector syn stack. Yn 'e takomst rint de samler gau troch de al taret list fan pointers.
Behannelje besykje / fange / úteinlik blokken
Lykas yn elke moderne taal is útsûnderingshanneling in wichtige komponint. De VM-kearn is ferpakt yn in try..catch-blok, dat kin weromkomme nei koade-útfiering nei it fangen fan in útsûndering troch wat ynformaasje deroer op 'e stapel te drukken. Yn applikaasje koade, kinne jo definiearje besykje / fange / úteinlik blokken fan koade, spesifisearje yngong punten by fangen (útsûndering handler) en úteinlik / ein (ein fan it blok).
Multithreading
It wurdt stipe op it VM-nivo. It is ienfâldich en handich te brûken. It wurket sûnder in ûnderbrekkingssysteem, dus de koade moat respektivelik ferskate kearen rapper wurde útfierd yn ferskate threads.
Eksterne biblioteken foar VM's
D'r is gjin manier om sûnder dit te dwaan. VM stipet ymporten, fergelykber mei hoe't it wurdt ymplementearre yn oare talen. Jo kinne in diel fan 'e koade skriuwe yn Mash en in diel fan' e koade yn memmetaal, en dan keppelje se yn ien.
Oersetter fan Mash-taal op heech nivo nei bytekoade foar VM's
Intermediate taal
Om fluch in oersetter út in komplekse taal yn VM-koade te skriuwen, ûntwikkele ik earst in tuskentaal. It resultaat wie in assembler-lykas ferskriklik spektakel dat d'r gjin spesjaal punt is om hjir te beskôgjen. Ik sil allinich sizze dat op dit nivo de oersetter de measte konstanten en fariabelen ferwurket, har statyske adressen en de adressen fan yngongspunten berekkent.
Oersetter arsjitektuer
Ik haw net de bêste arsjitektuer foar ymplemintaasje keazen. De oersetter bout gjin koadebeam, lykas oare oersetters dogge. Hy sjocht nei it begjin fan de struktuer. Dy. as it stik koade dat wurdt parseard liket op "wylst :", dan is it fanselssprekkend dat dit in while-loopkonstruksje is en moat wurde ferwurke as in while-loopkonstruksje. Iets as in komplekse switch-case.
Troch dizze arsjitektoanyske oplossing die bliken dat de oersetter net hiel fluch wie. Lykwols, it gemak fan syn wiziging is tanommen gâns. Ik tafoege de nedige struktueren flugger as myn kofje koe ôfkuolje. Folsleine OOP-stipe waard yn minder dan in wike ymplementearre.
Koade optimalisaasje
Hjir hie it fansels better útfierd wurde kinnen (en sil útfierd wurde, mar letter, sa gau as men der oan komt). Oant no wit de optimizer allinich hoe't net brûkte koade, konstanten en ymporten fan 'e gearkomste ôfsnien wurde. Ek wurde ferskate konstanten mei deselde wearde ferfongen troch ien. Da's alles.
Mash taal
Basis konsept fan taal
It haadgedachte wie om de meast funksjonele en ienfâldige taal mooglik te ûntwikkeljen. Ik tink dat de ûntwikkeling syn taak mei in knal omgiet.
Koade blokken, prosedueres en funksjes
Alle konstruksjes yn de taal wurde iepene mei in kolon. : en wurde sletten troch de operator ein.
Prosedueres en funksjes wurde respektivelik ferklearre as proc en func. De arguminten steane tusken heakjes. Alles is lykas de measte oare talen.
Operator werom kinne jo werom in wearde út in funksje, operator brekke kinne jo ferlitte de proseduere / funksje (as it is bûten de loops).
Foarbyld fan koade:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Stipe ûntwerpen
- Loops: foar..ein, wylst..ein, oant..ein
- Betingsten: as..[else..]ein, wikselje..[saak..ein..][else..]ein
- Metoaden: proc ():... ein, func ():... ein
- Label en gean nei: :, springe
- Enumeraasjes en konstante arrays.
Fariabelen
De oersetter kin bepale se automatysk, of as de ûntwikkelder skriuwt var foar it definiearjen se.
Koade foarbylden:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Globale en lokale fariabelen wurde stipe.
OOP
No, wy binne by it lekkerste ûnderwerp kommen. Mash stipet alle objekt-rjochte programmearring paradigma's. Dy. klassen, erfskip, polymorfisme (ynklusyf dynamyske), dynamyske automatyske refleksje en yntrospeksje (folslein).
Sûnder fierdere ado is it better om gewoan koadefoarbylden te jaan.
In ienfâldige klasse en dêrmei wurkje:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
proc main():
x ?= new MyClass(10, 20)
println(x->Summ())
x->Free()
end
Utfier: 30.
Erfskip en polymorfisme:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyNewClass(10, 20)
println(x->Summ())
x->Free()
end
Utfier: 60.
Hoe sit it mei dynamysk polymorfisme? Ja, dit is refleksje!:
uses <bf>
uses <crt>
class MyClass:
var a, b
proc Create, Free
func Summ
end
proc MyClass::Create(a, b):
$a = new(a)
$b = new(b)
end
proc MyClass::Free():
Free($a, $b)
$rem()
end
func MyClass::Summ():
return $a + $b
end
class MyNewClass(MyClass):
func Summ
end
func MyNewClass::Summ():
return ($a + $b) * 2
end
proc main():
x ?= new MyClass(10, 20)
x->Summ ?= MyNewClass::Summ
println(x->Summ())
x->Free()
end
Utfier: 60.
Litte wy no in momint nimme om yntrospektearje foar ienfâldige wearden en klassen:
uses <bf>
uses <crt>
class MyClass:
var a, b
end
proc main():
x ?= new MyClass
println(BoolToStr(x->type == MyClass))
x->rem()
println(BoolToStr(typeof(3.14) == typeReal))
end
Sil útfier: wier, wier.
Oer opdracht operators en eksplisite oanwizers
De operator ?= wurdt brûkt om in fariabele in oanwizer ta te jaan oan in wearde yn it ûnthâld.
De operator = feroaret in wearde yn it ûnthâld mei in oanwizer fan in fariabele.
En no in bytsje oer eksplisite oanwizings. Ik haw se oan de taal tafoege, sadat se bestean.
@ - nim in eksplisite oanwizer nei in fariabele.
? - krije in fariabele troch oanwizer.
@= - tawize in wearde oan in fariabele troch in eksplisite oanwizer derop.
Foarbyld fan koade:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
Sil útfiere: wat nûmer, 10, 11.
Besykje ..[catch..][finally..]ein
Foarbyld fan koade:
uses <bf>
uses <crt>
proc main():
println("Start")
try:
println("Trying to do something...")
a ?= 10 / 0
catch:
println(getError())
finally:
println("Finally")
end
println("End")
inputln()
end
Plannen foar de takomst
Ik bliuw sjen en sjoch nei GraalVM & Truffel. Myn runtime-omjouwing hat gjin JIT-kompiler, dus yn termen fan prestaasjes is it op it stuit allinich kompetitive mei Python. Ik hoopje dat ik JIT-kompilaasje kin ymplementearje basearre op GraalVM of LLVM.
repository
Jo kinne mei de ûntwikkelingen boartsje en it projekt sels folgje.
Tankewol foar it lêzen oant it ein as jo dien hawwe.
Boarne: www.habr.com