Olen mitu aastat proovinud oma programmeerimiskeele arendamist. Soovisin luua vÔimalikult lihtsa, tÀieliku ja mugava keele.
Selles artiklis tahan tutvustada oma töö peamisi etappe ja alustada loodud keele kontseptsiooni ja selle esialgse rakenduse kirjeldamisest, mille kallal praegu töötan.
Kuna kirjutasin kogu projekti Free Pascalis, ĂŒtlen kohe, et sellel keeles saab programme kokku panna paljudele platvormidele ning kompilator loob ĂŒsna optimeeritud binaarfaile (kĂ”ik projekti komponendid kogun O2 lipuga).
Keelte tÀitmisring
Esmalt tasub rÀÀkida virtuaalsest masinast, mille pidin kirjutama oma keeles tulevaste rakenduste tĂ€itmiseks. Otsustasin rakendada stakialust arhitektuuri, kuna see oli kĂ”ige lihtsam. Ăhtegi korralikku artiklit selles osas vene keeles ei leidnud, nii et pĂ€rast ingliskeelsete materjalidega tutvumist asusin oma jalgratta projekteerimise ja kirjutamise kallale. Edasi toon oma "uuenduslikud" ideed ja arendused sellel alal.
Staki rakendamine
Ilmselt asub VM-i tipus virn. Minu teostuses töötab see blokkidena. Tegelikult on see lihtne pointerite massiiv ja muutuja, mis salvestab virna tipu indeksi.
Selle initsialiseerimisel luuakse 256 elemendiga massiiv. Kui virn lĂ€heb ĂŒle rohkemate pointerite, siis selle suurus suureneb jĂ€rgmise 256 elemendi vĂ”rra. Vastavalt, kui elemente virnast eemaldatakse, reguleeritakse selle suurust.
VM-is kasutatakse mitmeid virnu:
- Peamine virn.
- Tagasi pöördumispunktide virn.
- Kogumishalduri virn.
- Try/catch/finally plokkide töötleja virn.
Constantid ja muutujad
Selle osas on kĂ”ik lihtne. Constantid töödeldakse eraldi vĂ€ikese kooditĂŒkiga ja on tulevikus rakendustes kergesti saadaval staatiliste aadresside kaudu. Muutujad on teatud suurusega pointerite massiiv, mille elementide juurde pÀÀseb indeksi kaudu â s.t. staatilise aadressi kaudu. Muutujad saab paigutada virna tippu vĂ”i lugeda sealt. Tegelikult, kuna meie muutujad pĂ”himĂ”tteliselt hoiavad pointereid VM-i mĂ€lu vÀÀrtustele, siis keeles domineerib töötamine varjatud pointeritega.
KÔrvalvÀrvide kogu
Minu virtuaalses masinas on see poolautomaalne. See tĂ€hendab, et arendaja otsustab ise, millal tuleb koristustöötlust vĂ€lja kutsuda. See ei tööta nagu tavaliselt viidatud loenduri jĂ€rgi, nagu nĂ€iteks Pythonis, Perl'is, Ruby's, Lua's jne. See on rakendatud markerite sĂŒsteemi kaudu. See tĂ€hendab, et kui eeldatakse, et muutujale antakse ajutine vÀÀrtus â siis lisatakse viidatud vÀÀrtus koristustöötluse steki.
Try/catch/finally plokkide töötlemine
Nagu igas kaasaegses keeles, on erandite töötlemine selle oluline osa. Virtuaalse masina sĂŒda on ĂŒmbritsetud try..catch plokiga, mis vĂ”ib naasta koodi tĂ€itmise juurde pĂ€rast erandi pĂŒĂŒdmist, paigutades steki natuke teavet selle kohta. Rakenduskoodis saab mÀÀrata try/catch/finally koodiplokke, mĂ€rkides sissepÀÀsu punktid catch (erandi töötleja) ja finally/end (ploki lĂ”pp).
Mitme niidi olemasolu
Seda toetatakse virtuaalse masina tasemel. See on lihtne ja mugav kasutada. See töötab ilma katkestussĂŒsteemita, seega peab kood tööle olema mitmes niidis mitu korda kiiremini vastavalt.
Virtuaalse masina vÀlised raamatukogud
Seda on hĂ€davajalik. VM toetab imporde, sarnaselt sellele, kuidas see on rakendatud teistes keeltes. VĂ”ib kirjutada osa koodist Mashis ja osa koodist natiivsetes keeltes, seejĂ€rel siduda need ĂŒheks tervikuks.
TÔlkija kÔrgtasemest keele Mashi baidikoodiks VM jaoks
Vahekeel
Kiire tĂ”lkija koostamiseks keerulisest keelest VM koodiks arendasin ma esmalt vahekeele. Selle tulemusena sai sellest assembleerijapoodne hirmsa vĂ€limusega nĂ€htus, mida siin eriti pole mĂ”tet vaadata. Ătlen vaid, et sellel tasemel tĂ”lkija töötleb enamikku viimaseid, muutujaid, arvutab nende staatilisi aadresse ja sisenemispunktide aadresse.
TÔlkija arhitektuur
Valisin mitte kĂ”ige parema arhitektuuri rakendamiseks. TĂ”lkija ei ehita koodipuu, nagu peaksid teised tĂ”lkijad. Ta vaatab konstruktsiooni algust. St, kui analĂŒĂŒsitav koodijupp nĂ€eb vĂ€lja nagu «while :», siis on ilmne, et see on while-tsĂŒkli konstruktsioon ja seda tuleb töödelda kui while-tsĂŒklit. Midagi keerulist switch-case sarnast.
Sellise arhitektuurilise lahenduse tĂ”ttu ei ole tĂ”lkija eriti kiire. Kuid selle ĂŒmbertegemise lihtsus on kordades paranenud. Vajalikud konstruktsioonid lisasin kiiremini, kui suutsin oma kohvi jahtuda. TĂ€ielik OOP tugi saadi valmis vĂ€hem kui nĂ€dalaga.
Koodi optimeerimine
Siin oleks muidugi saanud paremini teha (ja tehakse ka, aga hiljem, kui jĂ”ud kĂ€es). Praegu suudab optimeerija ainult eemaldada kasutamata koodi, konstandid ja impordid kogumisest. Samuti asendatakse mitu konstandi, millel on sama vÀÀrtus, ĂŒhega. Sellega asi piirdub.
Mash keele
Keele pÔhikontseptsioon
PĂ”hieesmĂ€rk oli vĂ€lja arendada vĂ”imalikult funktsionaalne ja lihtne keel. Arvan, et arendus tĂ€idab oma ĂŒlesande suurepĂ€raselt.
Koodiblokid, protseduurid ja funktsioonid
KÔik konstruktsioonid keeles avatakse kolonni mÀrkiga : ja suletakse operaatoriga lÔpp.
Protseduure ja funktsioone deklareeritakse vastavalt proc ja func. Sulgudes loetletakse argumendid. KÔik nagu enamikus teistes keeltes.
Operaatori return abil saab funktsioonist vÀÀrtuse tagasi anda, operaator break lubab protseduurist/funktsioonist vÀljuda (kui see on vÀljaspool silmuseid).
KoodinÀide:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Toetatud konstruktsioonid
- TsĂŒklid: for..end, while..end, until..end
- Tingimused: if..[else..]end, switch..[case..end..][else..]end
- Meetodid: proc <nimi>():⊠end, func <nimi>():⊠end
- Label & goto: <nimi>:, jump <nimi>
- Enum, enum ja konstantide massiivid.
Muutujad
TÔlkija vÔib neid automaatselt mÀÀrata vÔi arendaja kirjutab var nende mÀÀratlemise ette.
KoodinÀidised:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Toetatakse globaalseid ja kohalikke muutujaid.
OOP
NĂŒĂŒd oleme jĂ”udnud kĂ”ige huvitavama teema juurde. Mash keeles toetatakse kĂ”iki objektorienteeritud programmeerimise paradigmasid. St. klassid, pĂ€randumine, polĂŒmorfism (sealhulgas dĂŒnaamiline), dĂŒnaamiline automaatne refleksioon ja introspektsioon (tĂ€ielik).
Ilma liigsete sÔnadeta, parim on anda koodinÀidised.
Lihtne klass ja töö selle kallal:
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
VĂ€ljastab: 30.
PĂ€rimine ja polĂŒmorfism:
kasutab
kasutab
klass MyClass:
muutuja a, b
prots Create, Free
funktsioon Summ
lÔpp
prots MyClass::Create(a, b):
$a = uus(a)
$b = uus(b)
lÔpp
prots MyClass::Free():
Vabasta($a, $b)
$rem()
lÔpp
funktsioon MyClass::Summ():
tagasta $a + $b
lÔpp
klass MyNewClass(MyClass):
funktsioon Summ
lÔpp
funktsioon MyNewClass::Summ():
tagasta ($a + $b) * 2
lÔpp
prots main():
x ?= uus MyNewClass(10, 20)
println(x->Summ())
x->Free()
lÔpp
VĂ€ljund: 60.
Aga mida öelda dĂŒnaamilise polĂŒmorfismi kohta? See on ju refleksioon!:
kasutab
kasutab
klass MyClass:
muutuja a, b
prots Create, Free
funktsioon Summ
lÔpp
prots MyClass::Create(a, b):
$a = uus(a)
$b = uus(b)
lÔpp
prots MyClass::Free():
Vabasta($a, $b)
$rem()
lÔpp
funktsioon MyClass::Summ():
tagasta $a + $b
lÔpp
klass MyNewClass(MyClass):
funktsioon Summ
lÔpp
funktsioon MyNewClass::Summ():
tagasta ($a + $b) * 2
lÔpp
prots main():
x ?= uus MyClass(10, 20)
x->Summ ?= MyNewClass::Summ
println(x->Summ())
x->Free()
lÔpp
VĂ€ljund: 60.
NĂŒĂŒd vĂ”tame hetke introspektsiooniks lihtsate vÀÀrtuste ja klasside jaoks:
kasutab
kasutab
klass MyClass:
muutuja a, b
lÔpp
prots main():
x ?= uus MyClass
println(BoolToStr(x->type == MyClass))
x->rem()
println(BoolToStr(typeof(3.14) == typeReal))
lÔpp
VĂ€ljund: true, true.
Kuna tegemist on mÀÀramisoperatsioonide ja selgete viidete teemaga
Operaator ?= mÀÀrab vÀÀrtuse aadressi muutujale.
Operaator = muudab aadressi vÀÀrtuse mÀlus muutuja kaudu.
Ja nĂŒĂŒd natuke selgetest viidetest. Lisasin need keelde, et need olemas oleksid.
@ â vĂ”tta selge viide muutujale.
?<пДŃĐ”ĐŒĐ”ĐœĐœĐ°Ń> â ĐżĐŸĐ»ŃŃĐžŃŃ ĐżĐ”ŃĐ”ĐŒĐ”ĐœĐœŃŃ ĐżĐŸ ŃĐșазаŃДлŃ.
@= â mÀÀrata muutujale vÀÀrtus selle otsese viitamise kaudu.
KoodinÀide:
kasutab
kasutab
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
VÀljund: mÔni number, 10, 11.
Proovi..[catch..][finally..]end
KoodinÀide:
kasutab
kasutab
proc main():
println("Algus")
proovi:
println("Proovin midagi teha...")
a ?= 10 / 0
catch:
println(getError())
finally:
println("LÔpuks")
end
println("LÔpp")
inputln()
end
Tulevikuplaanid
Vaatan pidevalt GraalVM-i & Truffle'i. Minu jooksutamisel puudub JIT kompilaator, seega vÔib selle jÔudlus praegu konkurentsi pakkuda vaid Pythoni osas. Loodan, et suudan JIT kompileerimise GraalVMi vÔi LLVM-i baasil ellu viia.
Repo
VÔite mÀngida nendega ja jÀlgida projekti ise.
AitÀh, et lugesite lÔpuni, kui te seda tegite.
Allikas: habr.com
