Ek het vir etlike jare my hand probeer om my eie programmeertaal te ontwikkel. Ek wou, na my mening, die eenvoudigste, ten volle funksionele en gerieflikste taal moontlik skep.
In hierdie artikel wil ek die hooffases van my werk uitlig en, om mee te begin, die geskepte konsep van die taal en die eerste implementering daarvan, waaraan ek tans werk, beskryf.
Laat ek vooraf sê dat ek die hele projek in Free Pascal geskryf het, want... programme daarop kan vir 'n groot aantal platforms saamgestel word, en die samesteller self produseer baie geoptimaliseerde binaries (ek versamel al die komponente van die projek met die O2-vlag).
Taal looptyd
Eerstens is dit die moeite werd om te praat oor die virtuele masjien wat ek moes skryf om toekomstige toepassings in my taal te laat loop. Ek het besluit om miskien 'n stapelargitektuur te implementeer, want dit was die maklikste manier. Ek het nie 'n enkele gewone artikel gekry oor hoe om dit in Russies te doen nie, so nadat ek myself vertroud gemaak het met die Engelstalige materiaal, het ek gaan sit om my eie fiets te ontwerp en skryf. Volgende sal ek my "gevorderde" idees en ontwikkelings in hierdie saak aanbied.
Stapel implementering
Dit is duidelik dat die stapel boaan die VM is. In my implementering werk dit in blokke. In wese is dit 'n eenvoudige reeks wysers en 'n veranderlike om die indeks van die bokant van die stapel te stoor.
Wanneer dit geïnisialiseer word, word 'n skikking van 256 elemente geskep. As meer wysers op die stapel gedruk word, neem die grootte daarvan toe met die volgende 256 elemente. Gevolglik, wanneer elemente uit die stapel verwyder word, word die grootte daarvan aangepas.
Die VM gebruik verskeie stapels:
- Hoofstapel.
- 'n Stapel vir die stoor van terugkeerpunte.
- Vullisverwyderaar stapel.
- Probeer/vang/blok hanteerderstapel uiteindelik.
Konstante en veranderlikes
Hierdie een is eenvoudig. Konstante word in 'n aparte klein stukkie kode hanteer en is beskikbaar in toekomstige toepassings via statiese adresse. Veranderlikes is 'n verskeidenheid wysers van 'n sekere grootte, toegang tot sy selle word uitgevoer deur indeks - d.w.s. statiese adres. Veranderlikes kan na die bokant van die stapel gedruk word of van daar af gelees word. Eintlik, want Terwyl ons veranderlikes in wese wysers na waardes in VM-geheue stoor, word die taal oorheers deur met implisiete wysers te werk.
Vullis versamelaar
In my VM is dit semi-outomaties. Dié. die ontwikkelaar besluit self wanneer om die vullisverwyderaar te bel. Dit werk nie met 'n gewone wyserteller, soos in Python, Perl, Ruby, Lua, ens. Dit word deur 'n merkerstelsel geïmplementeer. Dié. wanneer 'n veranderlike bedoel is om 'n tydelike waarde toegeken te word, word 'n wyser na hierdie waarde by die vullisverwyderaar se stapel gevoeg. In die toekoms gaan die versamelaar vinnig deur die reeds voorbereide lys wysers.
Hanteer probeer/vang/uiteindelik blokke
Soos in enige moderne taal, is uitsonderingshantering 'n belangrike komponent. Die VM-kern is toegedraai in 'n try..catch-blok, wat kan terugkeer na kode-uitvoering nadat 'n uitsondering opgevang is deur 'n bietjie inligting daaroor op die stapel te druk. In toepassingskode kan jy probeer/vang/uiteindelik blokke kode definieer, wat toegangspunte by vang (uitsonderingshanteerder) en uiteindelik/einde (einde van die blok) spesifiseer.
Multithreading
Dit word op VM-vlak ondersteun. Dit is eenvoudig en gerieflik om te gebruik. Dit werk sonder 'n onderbrekingstelsel, so die kode moet onderskeidelik verskeie kere vinniger in verskeie drade uitgevoer word.
Eksterne biblioteke vir VM'e
Daar is geen manier om hiersonder te doen nie. VM ondersteun invoere, soortgelyk aan hoe dit in ander tale geïmplementeer word. Jy kan 'n deel van die kode in Mash skryf en 'n deel van die kode in moedertaal, en dan in een koppel.
Vertaler van hoëvlak Mash-taal na greepkode vir VM's
Intermediêre taal
Om vinnig 'n vertaler uit 'n komplekse taal in VM-kode te skryf, het ek eers 'n intermediêre taal ontwikkel. Die resultaat was 'n samestelleragtige verskriklike skouspel wat daar geen spesifieke punt is om hier te oorweeg nie. Ek sal net sê dat die vertaler op hierdie vlak die meeste konstantes en veranderlikes verwerk, hul statiese adresse en die adresse van toegangspunte bereken.
Vertalerargitektuur
Ek het nie die beste argitektuur vir implementering gekies nie. Die vertaler bou nie 'n kodeboom, soos ander vertalers doen nie. Hy kyk na die begin van die struktuur. Dié. as die stuk kode wat ontleed word soos "while <condition>:" lyk, dan is dit duidelik dat dit 'n while lus konstruk is en as 'n while lus konstruk verwerk moet word. Iets soos 'n komplekse skakelaar.
Danksy hierdie argitektoniese oplossing het die vertaler geblyk nie baie vinnig te wees nie. Die gemak van die wysiging daarvan het egter aansienlik toegeneem. Ek het die nodige strukture vinniger bygevoeg as wat my koffie kon afkoel. Volle OOP-ondersteuning is binne minder as 'n week geïmplementeer.
Kode optimalisering
Hier kon dit natuurlik beter geïmplementeer gewees het (en sal geïmplementeer word, maar later, sodra mens daarby uitkom). Tot dusver weet die optimaliseerder net hoe om ongebruikte kode, konstantes en invoere van die samestelling af te sny. Ook word verskeie konstantes met dieselfde waarde deur een vervang. Dis al.
Mash taal
Basiese konsep van taal
Die hoofgedagte was om die mees funksionele en eenvoudige taal moontlik te ontwikkel. Ek dink die ontwikkeling hanteer sy taak met 'n knal.
Kodeblokke, prosedures en funksies
Alle konstruksies in die taal word met 'n dubbelpunt oopgemaak. : en word deur die operateur gesluit einde.
Prosedures en funksies word onderskeidelik as proc en func verklaar. Die argumente word tussen hakies gelys. Alles is soos die meeste ander tale.
Operator terugkeer jy kan 'n waarde van 'n funksie, operateur, terugstuur breek laat jou toe om die prosedure/funksie te verlaat (as dit buite die lusse is).
Kode voorbeeld:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Ondersteunde ontwerpe
- Lusse: vir..einde, terwyl..einde, tot..einde
- Voorwaardes: as..[else..]end, switch..[case..end..][else..]end.
- Metodes: proc <naam>():... end, func <naam>():... end
- Etiket en gaan na: <naam>:, spring <naam>
- Enum opsommings en konstante skikkings.
veranderlikes
Die vertaler kan hulle outomaties bepaal, of as die ontwikkelaar var skryf voordat dit gedefinieer word.
Kode voorbeelde:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Globale en plaaslike veranderlikes word ondersteun.
OOP
Wel, ons het by die lekkerste onderwerp gekom. Mash ondersteun alle objekgeoriënteerde programmeringsparadigmas. Dié. klasse, oorerwing, polimorfisme (insluitend dinamiek), dinamiese outomatiese refleksie en introspeksie (vol).
Sonder om verder te praat, is dit beter om net kodevoorbeelde te gee.
'n Eenvoudige klas en werk daarmee:
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
Sal uitset: 30.
Oorerwing en polimorfisme:
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
Sal uitset: 60.
Wat van dinamiese polimorfisme? Ja, dit is refleksie!:
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
Sal uitset: 60.
Kom ons neem nou 'n oomblik om introspekteer vir eenvoudige waardes en klasse:
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
Sal uitset: waar, waar.
Oor opdragoperateurs en eksplisiete aanwysings
Die ?= operateur word gebruik om 'n veranderlike 'n wyser na 'n waarde in die geheue toe te ken.
Die =-operateur verander 'n waarde in geheue deur 'n wyser van 'n veranderlike te gebruik.
En nou 'n bietjie oor eksplisiete aanwysings. Ek het hulle by die taal gevoeg sodat hulle bestaan.
@<veranderlike> — neem 'n eksplisiete wyser na 'n veranderlike.
?<veranderlike> — kry 'n veranderlike deur wyser.
@= — ken 'n waarde aan 'n veranderlike toe deur 'n eksplisiete wyser daarna.
Kode voorbeeld:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
Sal uitvoer: 'n getal, 10, 11.
Probeer..[vang..][uiteindelik..]end
Kode voorbeeld:
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
Planne vir die toekoms
Ek bly kyk en kyk na GraalVM & Truffle. My runtime-omgewing het nie 'n JIT-samesteller nie, so in terme van werkverrigting is dit tans net mededingend met Python. Ek hoop dat ek JIT-samestelling gebaseer op GraalVM of LLVM sal kan implementeer.
bewaarplek
Jy kan met die ontwikkelings speel en self die projek volg.
Dankie dat jy tot die einde gelees het as jy dit gedoen het.
Bron: will.com