Nyt programmeringssprog Mash

I flere år forsøgte jeg mig med at udvikle mit eget programmeringssprog. Jeg ønskede efter min mening at skabe det mest enkle, fuldt funktionelle og bekvemme sprog som muligt.

I denne artikel vil jeg fremhæve hovedstadierne i mit arbejde og til at begynde med beskrive det skabte sprogbegreb og dets første implementering, som jeg i øjeblikket arbejder på.

Lad mig sige på forhånd, at jeg skrev hele projektet i Free Pascal, fordi... programmer på det kan samles til et stort antal platforme, og selve compileren producerer meget optimerede binære filer (jeg samler alle komponenterne i projektet med O2-flaget).

Sprog køretid

Først og fremmest er det værd at tale om den virtuelle maskine, som jeg skulle skrive for at køre fremtidige applikationer på mit sprog. Jeg besluttede mig for at implementere en stakarkitektur, måske fordi det var den nemmeste måde. Jeg fandt ikke en eneste normal artikel om, hvordan man gør dette på russisk, så efter at have sat mig ind i det engelsksprogede materiale, satte jeg mig til at designe og skrive min egen cykel. Dernæst vil jeg præsentere mine "avancerede" ideer og udviklinger i denne sag.

Stakimplementering

Det er klart, at toppen af ​​VM'en er stakken. I min implementering fungerer det i blokke. I bund og grund er dette en simpel række af pointere og en variabel til at gemme indekset for toppen af ​​stakken.
Når det initialiseres, oprettes et array med 256 elementer. Hvis flere pointere skubbes ind på stakken, øges dens størrelse med de næste 256 elementer. Følgelig, når elementer fjernes fra stakken, justeres dens størrelse.

VM'en bruger flere stakke:

  1. Hovedstak.
  2. En stak til opbevaring af returpunkter.
  3. Affaldsopsamlerstabel.
  4. Prøv/fang/blokér endelig handlerstak.

Konstanter og variabler

Denne er enkel. Konstanter håndteres i et separat lille stykke kode og er tilgængelige i fremtidige applikationer via statiske adresser. Variabler er en række pointere af en vis størrelse, adgang til dens celler udføres af indeks - dvs. statisk adresse. Variabler kan skubbes til toppen af ​​stakken eller læses derfra. Faktisk fordi Mens vores variabler i det væsentlige gemmer pointere til værdier i VM-hukommelsen, er sproget domineret af at arbejde med implicitte pointere.

Skraldemand

I min VM er den semi-automatisk. De der. udvikleren bestemmer selv, hvornår han skal ringe til skraldemanden. Det virker ikke ved at bruge en almindelig pointertæller, som i Python, Perl, Ruby, Lua osv. Det implementeres gennem et markørsystem. De der. når en variabel er beregnet til at blive tildelt en midlertidig værdi, tilføjes en pointer til denne værdi til skraldeopsamlerens stak. I fremtiden løber samleren hurtigt igennem den allerede udarbejdede liste over pointer.

Håndtering af try/catch/finally blocks

Som i ethvert moderne sprog er undtagelseshåndtering en vigtig komponent. VM-kernen er pakket ind i en try..catch-blok, som kan vende tilbage til kodeudførelse efter at have fanget en undtagelse ved at skubbe nogle oplysninger om den ind på stakken. I applikationskode kan du definere prøve/fangst/endelig kodeblokke, der angiver indgangspunkter ved fangst (undtagelsesbehandler) og endelig/slut (slutningen af ​​blokken).

Multithreading

Det understøttes på VM-niveau. Det er enkelt og bekvemt at bruge. Det fungerer uden et afbrydelsessystem, så koden bør udføres i flere tråde flere gange hurtigere hhv.

Eksterne biblioteker til VM'er

Der er ingen måde at undvære dette. VM understøtter import, svarende til hvordan det er implementeret på andre sprog. Du kan skrive en del af koden i Mash og en del af koden på modersmål og derefter linke dem til et.

Oversætter fra Mash-sprog på højt niveau til bytekode til VM'er

Mellemsprog

For hurtigt at skrive en oversætter fra et komplekst sprog til VM-kode, udviklede jeg først et mellemsprog. Resultatet var et assembler-agtigt forfærdeligt skue, som der ikke er nogen særlig mening i at overveje her. Jeg vil kun sige, at på dette niveau behandler oversætteren de fleste konstanter og variabler, beregner deres statiske adresser og adresserne på indgangspunkter.

Oversætter arkitektur

Jeg valgte ikke den bedste arkitektur til implementering. Oversætteren bygger ikke et kodetræ, som andre oversættere gør. Han ser på begyndelsen af ​​strukturen. De der. hvis det stykke kode, der parses, ser ud som "while :", så er det indlysende, at dette er en while loop konstruktion og skal behandles som en while loop konstruktion. Noget som en kompleks switch-case.

Takket være denne arkitektoniske løsning viste oversætteren sig ikke at være særlig hurtig. Imidlertid er dets lette modifikation steget betydeligt. Jeg tilføjede de nødvendige strukturer hurtigere, end min kaffe kunne køle ned. Fuld OOP-support blev implementeret på mindre end en uge.

Kode optimering

Her kunne det selvfølgelig have været implementeret bedre (og vil blive implementeret, men senere, så snart man kommer omkring det). Indtil videre ved optimizeren kun, hvordan man afskærer ubrugt kode, konstanter og importer fra samlingen. Også flere konstanter med samme værdi erstattes af én. Det er alt.

Sprog Mash

Grundlæggende sprogbegreb

Hovedideen var at udvikle det mest funktionelle og enkle sprog som muligt. Jeg synes, at udviklingen klarer sin opgave med et brag.

Kodeblokke, procedurer og funktioner

Alle konstruktioner i sproget åbnes med et kolon. : og lukkes af operatøren ende.

Procedurer og funktioner erklæres som henholdsvis proc og func. Argumenterne er angivet i parentes. Alt er som de fleste andre sprog.

Operatør afkast du kan returnere en værdi fra en funktion, operator bryde giver dig mulighed for at afslutte proceduren/funktionen (hvis den er uden for sløjferne).

Eksempelkode:

...

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

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

Understøttede designs

  • Loops: for..end, mens..end, indtil..end
  • Betingelser: hvis..[else..]ende, skifte..[sag..ende..][else..]ende
  • Metoder: proc ():... end, func ():... end
  • Etiket & gå til: :, hop
  • Enum opregninger og konstante arrays.

Variabler

Oversætteren kan bestemme dem automatisk, eller hvis udvikleren skriver var før de definerer dem.

Kode eksempler:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Globale og lokale variabler understøttes.

OOP

Nå, så er vi kommet til det lækreste emne. Mash understøtter alle objektorienterede programmeringsparadigmer. De der. klasser, arv, polymorfi (herunder dynamisk), dynamisk automatisk refleksion og introspektion (fuld).

Uden videre er det bedre bare at give kodeeksempler.

En simpel klasse og arbejde med den:

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

Vil output: 30.

Arv og polymorfi:

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

Vil output: 60.

Hvad med dynamisk polymorfi? Ja, det er refleksion!:

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

Vil output: 60.

Lad os nu tage et øjeblik til at introspektere for simple værdier og klasser:

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

Vil udskrive: sandt, sandt.

Om opgaveoperatører og eksplicitte pointer

Operatoren ?= bruges til at tildele en variabel en pointer til en værdi i hukommelsen.
Operatoren = ændrer en værdi i hukommelsen ved hjælp af en pointer fra en variabel.
Og nu lidt om eksplicitte pointer. Jeg føjede dem til sproget, så de eksisterer.
@ — tag en eksplicit pointer til en variabel.
? — få en variabel ved hjælp af pointer.
@= — tildel en værdi til en variabel med en eksplicit pointer til den.

Eksempelkode:

uses <bf>
uses <crt>

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

Vil udskrive: et eller andet tal, 10, 11.

Prøv..[fangst..][endelig..]afslut

Eksempelkode:

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

Planer for fremtiden

Jeg bliver ved med at kigge og kigge på GraalVM & Truffle. Mit runtime-miljø har ikke en JIT-compiler, så ydelsesmæssigt er det i øjeblikket kun konkurrencedygtigt med Python. Jeg håber, at jeg vil være i stand til at implementere JIT-kompilering baseret på GraalVM eller LLVM.

depot

Du kan selv lege med udviklingen og følge med i projektet.

Site
Repository på GitHub

Tak fordi du læste til slutningen, hvis du gjorde det.

Kilde: www.habr.com

Tilføj en kommentar