Nije programmeartaal Mash

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:

  1. Haadsteapel.
  2. In steapel foar it bewarjen fan werom punten.
  3. Garbage collector stack.
  4. 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.

webside
Repository op GitHub

Tankewol foar it lêzen oant it ein as jo dien hawwe.

Boarne: www.habr.com

Add a comment