Več let sem se preizkušal v razvoju lastnega programskega jezika. Po mojem mnenju sem želel ustvariti najbolj preprost, popolnoma funkcionalen in priročen jezik.
V tem članku želim izpostaviti glavne faze svojega dela in za začetek opisati ustvarjen koncept jezika in njegovo prvo implementacijo, na kateri trenutno delam.
Že vnaprej naj povem, da sem celoten projekt napisal v Free Pascalu, ker... programi na njem se lahko prevedejo za ogromno platform, sam prevajalnik pa izdeluje zelo optimizirane binarne datoteke (vse komponente projekta zbiram z oznako O2).
Jezikovno izvajanje
Najprej je vredno govoriti o virtualnem stroju, ki sem ga moral napisati za izvajanje prihodnjih aplikacij v svojem jeziku. Morda sem se odločil implementirati arhitekturo skladov, ker je bil to najlažji način. Nisem našel niti enega normalnega članka o tem, kako to storiti v ruščini, zato sem se po seznanitvi z gradivom v angleškem jeziku lotil oblikovanja in pisanja lastnega kolesa. Nato bom predstavil svoje "napredne" ideje in razvoj v tej zadevi.
Implementacija sklada
Očitno je na vrhu VM sklad. V moji izvedbi deluje v blokih. V bistvu je to preprosto polje kazalcev in spremenljivka za shranjevanje indeksa vrha sklada.
Ko je inicializiran, se ustvari niz 256 elementov. Če je na sklad potisnenih več kazalcev, se njegova velikost poveča za naslednjih 256 elementov. V skladu s tem se pri odstranjevanju elementov iz sklada prilagodi njegova velikost.
VM uporablja več skladov:
- Glavni sklad.
- Sklad za shranjevanje povratnih točk.
- Zbiralnik smeti.
- Sklad obravnave poskusi/ulovi/končno blokiraj.
Konstante in spremenljivke
Ta je preprosta. Konstante se obravnavajo v ločenem majhnem delu kode in so na voljo v prihodnjih aplikacijah prek statičnih naslovov. Spremenljivke so niz kazalcev določene velikosti, dostop do njegovih celic se izvaja z indeksom - tj. statični naslov. Spremenljivke je mogoče potisniti na vrh sklada ali prebrati od tam. Pravzaprav, ker Medtem ko naše spremenljivke v bistvu shranjujejo kazalce na vrednosti v pomnilnik VM, v jeziku prevladuje delo z implicitnimi kazalci.
Zbiralec smeti
V mojem VM je polavtomatsko. Tisti. razvijalec se sam odloči, kdaj bo poklical pobiralca smeti. Ne deluje z običajnim števcem kazalcev, kot v Pythonu, Perlu, Rubyju, Lui itd. Izvaja se preko sistema markerjev. Tisti. ko naj bi spremenljivki dodelili začasno vrednost, je kazalec na to vrednost dodan v sklad zbiralnika smeti. V nadaljevanju zbiralec hitro preleti že pripravljen seznam kazalcev.
Ravnanje z bloki poskusi/ulovi/končno
Kot v vsakem sodobnem jeziku je obravnava izjem pomembna komponenta. Jedro VM je ovito v blok try..catch, ki se lahko vrne k izvajanju kode, potem ko ujame izjemo, tako da potisne nekaj informacij o njej na sklad. V kodi aplikacije lahko definirate bloke kode try/catch/finally, pri čemer določite vstopne točke pri catch (obravnavalnik izjem) in finally/end (konec bloka).
Večnitnost
Podprt je na ravni VM. Je preprost in priročen za uporabo. Deluje brez prekinitvenega sistema, zato je treba kodo izvajati v več nitih nekajkrat hitreje oz.
Zunanje knjižnice za VM
Brez tega ne gre. VM podpira uvoz, podobno kot je implementiran v drugih jezikih. Lahko napišete del kode v Mash in del kode v maternih jezikih, nato pa ju povežete v eno.
Prevajalnik iz visokonivojskega jezika Mash v bajtno kodo za VM
Vmesni jezik
Za hitro pisanje prevajalnika iz kompleksnega jezika v kodo VM sem najprej razvil vmesni jezik. Rezultat je bil monterju podoben grozen spektakel, ki ga tukaj nima posebnega smisla obravnavati. Rekel bom le, da na tej ravni prevajalnik obdela večino konstant in spremenljivk, izračuna njihove statične naslove in naslove vstopnih točk.
Arhitektura prevajalnika
Nisem izbral najboljše arhitekture za izvedbo. Prevajalec ne gradi kodnega drevesa, kot to počnejo drugi prevajalci. Pogleda na začetek strukture. Tisti. če je del kode, ki se razčlenjuje, videti kot "while :", potem je očitno, da je to konstrukcija zanke while in jo je treba obdelati kot konstrukcijo zanke while. Nekaj podobnega zapletenemu stikalnemu ohišju.
Zahvaljujoč tej arhitekturni rešitvi se je izkazalo, da prevajalec ni zelo hiter. Vendar se je enostavnost njegovega spreminjanja znatno povečala. Dodal sem potrebne strukture hitreje, kot se je moja kava ohladila. Popolna OOP podpora je bila implementirana v manj kot enem tednu.
Optimizacija kode
Tu bi se seveda dalo bolje implementirati (in se bo izvajalo, a kasneje, ko bo komu šlo na pamet). Optimizator zaenkrat zna le izrezati neuporabljeno kodo, konstante in uvoze iz sklopa. Prav tako se več konstant z isto vrednostjo nadomesti z eno. To je vse.
Language Mash
Osnovni koncept jezika
Glavna ideja je bila razviti čim bolj funkcionalen in preprost jezik. Mislim, da se razvoj uspešno spopada s svojo nalogo.
Kodni bloki, postopki in funkcije
Vse konstrukcije v jeziku se odprejo z dvopičjem. : in jih operater zapre konec.
Postopki in funkcije so deklarirane kot proc oziroma func. Argumenti so navedeni v oklepajih. Vse je kot večina drugih jezikov.
Operater vrnitev lahko vrnete vrednost iz funkcije, operatorja odmor omogoča izhod iz procedure/funkcije (če je zunaj zank).
Vzorčna koda:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Podprti modeli
- Zanke: for..end, while..end, until..end
- Pogoji: if.[else..]end, switch..[case..end..][else..]end
- Metode: proc ():... end, func ():... end
- Oznaka & goto: :, jump
- Enum naštevanja in konstantna polja.
Spremenljivke
Prevajalec jih lahko določi samodejno ali če razvijalec napiše var, preden jih definira.
Primeri kod:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Podprte so globalne in lokalne spremenljivke.
OOP
Pa smo prišli do najbolj slastne teme. Mash podpira vse paradigme objektno usmerjenega programiranja. Tisti. razredi, dedovanje, polimorfizem (vključno z dinamičnim), dinamična avtomatska refleksija in introspekcija (polna).
Brez nadaljnjega odlašanja je bolje, da navedete samo primere kode.
Preprost razred in delo z njim:
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
Izhod: 30.
Dedovanje in polimorfizem:
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
Izhod: 60.
Kaj pa dinamični polimorfizem? Ja, to je refleksija!:
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
Izhod: 60.
Zdaj pa si vzemimo trenutek in se poglobimo v preproste vrednosti in razrede:
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
Izhod bo: res, res.
O operatorjih dodeljevanja in eksplicitnih kazalcih
Operator ?= se uporablja za dodelitev spremenljivki kazalca na vrednost v pomnilniku.
Operator = spremeni vrednost v pomnilniku z uporabo kazalca iz spremenljivke.
In zdaj malo o eksplicitnih kazalcih. Dodal sem jih v jezik, da obstajajo.
@ — sprejme eksplicitni kazalec na spremenljivko.
? — pridobi spremenljivko s kazalcem.
@= — dodelite vrednost spremenljivki z eksplicitnim kazalcem nanjo.
Vzorčna koda:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
Izpis bo: neko število, 10, 11.
Poskusi.[catch..][finally..]end
Vzorčna koda:
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
Načrti za prihodnost
Kar naprej gledam in gledam GraalVM & Truffle. Moje izvajalno okolje nima prevajalnika JIT, tako da je glede zmogljivosti trenutno konkurenčno samo Pythonu. Upam, da mi bo uspelo implementirati prevajanje JIT na osnovi GraalVM ali LLVM.
repozitorij
Lahko se igrate z razvojem in sami spremljate projekt.
Hvala, ker ste prebrali do konca, če ste.
Vir: www.habr.com