Nuwe programmeertaal Mash

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:

  1. Hoofstapel.
  2. 'n Stapel vir die stoor van terugkeerpunte.
  3. Vullisverwyderaar stapel.
  4. 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.

Site
Bewaarplek op GitHub

Dankie dat jy tot die einde gelees het as jy dit gedoen het.

Bron: will.com

Voeg 'n opmerking