Nova programlingvo Mash

Dum pluraj jaroj mi provis mian manon pri disvolvi mian propran programlingvon. Mi volis krei, laŭ mi, la plej simplan, plene funkcian kaj oportunan lingvon eble.

En ĉi tiu artikolo mi volas reliefigi la ĉefajn etapoj de mia laboro kaj, por komenci, priskribi la kreitan koncepton de la lingvo kaj ĝian unuan efektivigon, pri kiuj mi nun laboras.

Mi anticipe diru, ke mi verkis la tutan projekton en Free Pascal, ĉar... programoj sur ĝi povas esti kunvenitaj por grandega nombro da platformoj, kaj la kompililo mem produktas tre optimumigitajn binarojn (mi kolektas ĉiujn komponantojn de la projekto kun la O2-flago).

Lingva rultempo

Antaŭ ĉio, indas paroli pri la virtuala maŝino, kiun mi devis skribi por ruli estontajn aplikojn en mia lingvo. Mi decidis efektivigi stakan arkitekturon, eble, ĉar ĝi estis la plej facila maniero. Mi ne trovis eĉ unu normalan artikolon pri kiel fari tion en la rusa, do post konatiĝo kun la anglalingva materialo, mi sidiĝis por desegni kaj verki mian propran biciklon. Poste mi prezentos miajn "altnivelajn" ideojn kaj evoluojn en tiu ĉi afero.

Stak efektivigo

Evidente, ĉe la supro de la VM estas la stako. En mia efektivigo ĝi funkcias en blokoj. Esence ĉi tio estas simpla tabelo de montriloj kaj variablo por stoki la indekson de la supro de la stako.
Kiam ĝi estas pravigita, tabelo de 256 elementoj estas kreita. Se pli da montriloj estas puŝitaj sur la stakon, ĝia grandeco pliiĝas je la sekvaj 256 elementoj. Sekve, kiam oni forigas elementojn el la stako, ĝia grandeco estas ĝustigita.

La VM uzas plurajn stakojn:

  1. Ĉefa stako.
  2. Stako por stoki revenpunktojn.
  3. Rubokolektisto stako.
  4. Provu/kapti/fine bloki prizorgan stakon.

Konstantoj kaj Variabloj

Ĉi tiu estas simpla. Konstantoj estas traktataj en aparta malgranda peco de kodo kaj estas haveblaj en estontaj aplikoj per senmovaj adresoj. Variabloj estas tabelo da montriloj de certa grandeco, aliro al ĝiaj ĉeloj estas farita per indekso - t.e. statika adreso. Variabloj povas esti puŝitaj al la supro de la stako aŭ legi de tie. Efektive, ĉar Dum niaj variabloj esence stokas montrilojn al valoroj en VM-memoro, la lingvo regas laborante kun implicaj montriloj.

Rubokolektisto

En mia VM ĝi estas duonaŭtomata. Tiuj. la programisto mem decidas kiam voki la rubkolektiston. Ĝi ne funkcias uzante regulan montrilon, kiel en Python, Perl, Ruby, Lua, ktp. Ĝi estas efektivigita per markilo-sistemo. Tiuj. kiam variablo estas celita esti asignita provizora valoro, montrilo al tiu valoro estas aldonita al la stako de la rubkolektisto. En la estonteco, la kolektanto rapide trakuras la jam pretan liston de montriloj.

Pritrakti try/catch/fine blokojn

Kiel en ĉiu moderna lingvo, esceptotraktado estas grava komponento. La VM-kerno estas envolvita en try..catch-bloko, kiu povas reveni al koda ekzekuto post kapti escepton puŝante iujn informojn pri ĝi sur la stakon. En aplika kodo, vi povas difini try/catch/finally blokojn de kodo, specifante enirpunktojn ĉe catch (esceptotraktilo) kaj fine/end (fino de la bloko).

Multfadenado

Ĝi estas subtenata ĉe la VM-nivelo. Ĝi estas simpla kaj oportuna uzi. Ĝi funkcias sen interrompa sistemo, do la kodo devus esti ekzekutita en pluraj fadenoj plurajn fojojn pli rapide, respektive.

Eksteraj bibliotekoj por VMs

Ne estas maniero fari sen ĉi tio. VM subtenas importadojn, simile al kiel ĝi estas efektivigita en aliaj lingvoj. Vi povas skribi parton de la kodo en Mash kaj parton de la kodo en gepatraj lingvoj, poste ligi ilin en unu.

Tradukisto de altnivela Mash-lingvo al bajtokodo por VM-oj

Meza lingvo

Por rapide skribi tradukilon el kompleksa lingvo en VM-kodon, mi unue evoluigis mezan lingvon. La rezulto estis asemble-simila terura spektaklo, kiun ĉi tie ne estas aparta signifo pripensi. Mi nur diros, ke je ĉi tiu nivelo la tradukisto prilaboras plej multajn konstantojn kaj variablojn, kalkulas iliajn senmovajn adresojn kaj la adresojn de enirpunktoj.

Tradukisto-arkitekturo

Mi ne elektis la plej bonan arkitekturon por efektivigo. La tradukisto ne konstruas kodan arbon, kiel faras aliaj tradukistoj. Li rigardas la komencon de la strukturo. Tiuj. se la peco de kodo analizita aspektas kiel "dum <kondiĉo>:", tiam estas evidente, ke ĉi tio estas konstruo de buklo while kaj devas esti prilaborita kiel konstruo de while buklo. Io kiel kompleksa ŝaltilo-kazo.

Dank' al ĉi tiu arkitektura solvo, la tradukisto montriĝis ne tre rapida. Tamen, la facileco de ĝia modifo signife pliiĝis. Mi aldonis la necesajn strukturojn pli rapide ol mia kafo povus malvarmiĝi. Plena OOP-subteno estis efektivigita en malpli ol semajno.

Optimumigo de kodo

Ĉi tie, kompreneble, ĝi povus esti efektivigita pli bone (kaj estos efektivigita, sed poste, tuj kiam oni atingos ĝin). Ĝis nun, la optimumigilo nur scias kiel fortranĉi neuzatan kodon, konstantojn kaj importadojn de la aro. Ankaŭ, pluraj konstantoj kun la sama valoro estas anstataŭigitaj per unu. Tio estas ĉio.

Lingvo Mash

Baza koncepto de lingvo

La ĉefa ideo estis evoluigi la plej funkcian kaj simplan lingvon ebla. Mi pensas, ke la evoluo traktas sian taskon per bruego.

Kodblokoj, proceduroj kaj funkcioj

Ĉiuj konstruoj en la lingvo estas malfermitaj per dupunkto. : kaj estas fermitaj de la funkciigisto fino.

Proceduroj kaj funkcioj estas deklaritaj kiel proc kaj func, respektive. La argumentoj estas listigitaj en krampoj. Ĉio estas kiel plej multaj aliaj lingvoj.

Operatoro reveno vi povas redoni valoron de funkcio, operatoro paŭzo permesas vin eliri la proceduron/funkcion (se ĝi estas ekster la bukloj).

Ekzempla kodo:

...

func summ(a, b):
  return a + b
end

proc main():
  println(summ(inputln(), inputln()))
end

Subtenataj Dezajnoj

  • Bukloj: por..fino, dum..fino, ĝis..fino
  • Kondiĉoj: se..[alie..]fino, ŝanĝi..[kazo..fino..][alie..]fino
  • Metodoj: proc <nomo>():... fino, func <nomo>():... fino
  • Etikedu kaj iru: <nomo>:, saltu <nomon>
  • Enumeradoj kaj konstantaj tabeloj.

Variabloj

La tradukisto povas determini ilin aŭtomate, aŭ se la programisto skribas var antaŭ ol difini ilin.

Ekzemploj de kodo:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Tutmondaj kaj lokaj variabloj estas subtenataj.

OOP

Nu, ni venis al la plej bongusta temo. Mash subtenas ĉiujn objekt-orientitajn programajn paradigmojn. Tiuj. klasoj, heredo, polimorfismo (inkluzive de dinamika), dinamika aŭtomata reflektado kaj introspekto (plena).

Sen plia antaŭparolo, estas pli bone doni nur kodajn ekzemplojn.

Simpla klaso kaj laborado kun ĝi:

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

Eligos: 30.

Heredo kaj polimorfismo:

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

Eligos: 60.

Kio pri dinamika polimorfismo? Jes, ĉi tio estas pripenso!:

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

Eligos: 60.

Nun ni prenu momenton por introspekti simplajn valorojn kaj klasojn:

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

Eligos: vera, vera.

Pri asigno operatoroj kaj eksplicitaj montriloj

La ?= operatoro estas uzata por atribui variablon montrilon al valoro en memoro.
La = operatoro ŝanĝas valoron en memoro uzante montrilon de variablo.
Kaj nun iomete pri eksplicitaj indikiloj. Mi aldonis ilin al la lingvo por ke ili ekzistu.
@<variable> — prenu eksplicitan montrilon al variablo.
?<variable> — akiri variablon per montrilo.
@= — atribui valoron al variablo per eksplicita montrilo al ĝi.

Ekzempla kodo:

uses <bf>
uses <crt>

proc main():
  var a = 10, b
  b ?= @a
  PrintLn(b)
  b ?= ?b
  PrintLn(b)
  b++
  PrintLn(a)
  InputLn()
end

Eligos: iun nombron, 10, 11.

Provu..[kapti..][fine..]fino

Ekzempla kodo:

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

Planoj por la estonteco

Mi daŭre rigardas kaj rigardas GraalVM & Truffle. Mia rultempa medio ne havas JIT-kompililon, do laŭ rendimento ĝi estas nuntempe nur konkurenciva kun Python. Mi esperas, ke mi povos efektivigi JIT-kompilon bazitan sur GraalVM aŭ LLVM.

deponejo

Vi povas ludi kun la evoluoj kaj sekvi la projekton mem.

retpaĝaro
Deponejo sur GitHub

Dankon pro legi ĝis la fino, se vi faris.

fonto: www.habr.com

Aldoni komenton