Nytt programmeringsspråk Mash

I flere år prøvde jeg meg på å utvikle mitt eget programmeringsspråk. Jeg ønsket å lage, etter min mening, et så enkelt, fullt funksjonelt og praktisk språk som mulig.

I denne artikkelen ønsker jeg å fremheve hovedstadiene i arbeidet mitt og til å begynne med beskrive det skapte konseptet av språket og dets første implementering, som jeg for tiden jobber med.

La meg si på forhånd at jeg skrev hele prosjektet i Free Pascal, fordi... programmer på den kan settes sammen for et stort antall plattformer, og selve kompilatoren produserer veldig optimaliserte binærfiler (jeg samler alle komponentene i prosjektet med O2-flagget).

Språk kjøretid

Først av alt er det verdt å snakke om den virtuelle maskinen som jeg måtte skrive for å kjøre fremtidige applikasjoner på språket mitt. Jeg bestemte meg for å implementere en stabelarkitektur, kanskje fordi det var den enkleste måten. Jeg fant ikke en eneste vanlig artikkel om hvordan man gjør dette på russisk, så etter å ha gjort meg kjent med det engelskspråklige materialet, satte jeg meg ned for å designe og skrive min egen sykkel. Deretter vil jeg presentere mine "avanserte" ideer og utviklinger i denne saken.

Stackimplementering

Åpenbart, på toppen av VM er stabelen. I min implementering fungerer det i blokker. I hovedsak er dette et enkelt utvalg av pekere og en variabel for å lagre indeksen på toppen av stabelen.
Når den er initialisert, opprettes en matrise med 256 elementer. Hvis flere pekere skyves inn på stabelen, øker størrelsen med de neste 256 elementene. Følgelig, når du fjerner elementer fra stabelen, justeres størrelsen.

VM-en bruker flere stabler:

  1. Hovedstabel.
  2. En stabel for lagring av returpunkter.
  3. Søppelsamlerstabel.
  4. Prøv/fang/blokker til slutt handlerstabel.

Konstanter og variabler

Denne er enkel. Konstanter håndteres i en egen liten kodebit og er tilgjengelig i fremtidige applikasjoner via statiske adresser. Variabler er en rekke pekere av en viss størrelse, tilgang til cellene utføres av indeks - dvs. statisk adresse. Variabler kan skyves til toppen av stabelen eller leses derfra. Egentlig fordi Mens variablene våre i hovedsak lagrer pekere til verdier i VM-minnet, domineres språket av å arbeide med implisitte pekere.

Søppelmann

I min VM er den halvautomatisk. De. utbygger bestemmer selv når han skal ringe søppelsamleren. Det fungerer ikke med en vanlig pekerteller, som i Python, Perl, Ruby, Lua, etc. Det implementeres gjennom et markørsystem. De. når en variabel er ment å bli tildelt en midlertidig verdi, legges en peker til denne verdien til søppelsamlerens stabel. I fremtiden går samleren raskt gjennom den allerede utarbeidede listen over pekere.

Håndtere prøve/fange/endelig blokker

Som i alle moderne språk er unntakshåndtering en viktig komponent. VM-kjernen er pakket inn i en try..catch-blokk, som kan gå tilbake til kodekjøring etter å ha fanget et unntak ved å skyve noe informasjon om det inn i stabelen. I applikasjonskode kan du definere prøve/fangst/endelig kodeblokker, spesifisere inngangspunkter ved fangst (unntaksbehandler) og til slutt/slutt (slutten av blokken).

Multithreading

Det støttes på VM-nivå. Det er enkelt og praktisk å bruke. Det fungerer uten et avbruddssystem, så koden bør kjøres i flere tråder flere ganger raskere.

Eksterne biblioteker for virtuelle datamaskiner

Det er ingen måte å klare seg uten dette. VM støtter import, på samme måte som den er implementert på andre språk. Du kan skrive en del av koden i Mash og en del av koden på morsmål, og deretter koble dem til ett.

Oversetter fra høynivå Mash-språk til bytekode for VM-er

Mellomspråk

For raskt å skrive en oversetter fra et komplekst språk til VM-kode, utviklet jeg først et mellomspråk. Resultatet ble et assembler-lignende forferdelig skue som det ikke er noe særlig vits i å vurdere her. Jeg vil bare si at på dette nivået behandler oversetteren de fleste konstanter og variabler, beregner deres statiske adresser og adressene til inngangspunkter.

Oversetterarkitektur

Jeg valgte ikke den beste arkitekturen for implementering. Oversetteren bygger ikke et kodetre, slik andre oversettere gjør. Han ser på begynnelsen av strukturen. De. hvis kodebiten som analyseres ser ut som "while :", så er det åpenbart at dette er en while-løkkekonstruksjon og må behandles som en while-løkkekonstruksjon. Noe som en kompleks brytersak.

Takket være denne arkitektoniske løsningen viste det seg at oversetteren ikke var veldig rask. Imidlertid har enkel modifikasjonen økt betydelig. Jeg la til de nødvendige strukturene raskere enn kaffen min kunne kjøle seg ned. Full OOP-støtte ble implementert på mindre enn en uke.

Kodeoptimalisering

Her kunne det selvfølgelig vært implementert bedre (og vil bli implementert, men senere, så fort man kommer i mål). Foreløpig vet optimalisereren bare hvordan den skal kutte ubrukt kode, konstanter og importer fra sammenstillingen. Dessuten erstattes flere konstanter med samme verdi med én. Det er alt.

Mash språk

Grunnleggende språkbegrep

Hovedideen var å utvikle et mest mulig funksjonelt og enkelt språk. Jeg synes at utbyggingen takler sin oppgave med et brak.

Kodeblokker, prosedyrer og funksjoner

Alle konstruksjoner i språket åpnes med kolon. : og lukkes av operatøren slutt.

Prosedyrer og funksjoner er deklarert som henholdsvis proc og func. Argumentene er oppført i parentes. Alt er som de fleste andre språk.

Operatør retur du kan returnere en verdi fra en funksjon, operator bryte lar deg gå ut av prosedyren/funksjonen (hvis den er utenfor løkkene).

Kodeeksempel:

...

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

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

Støttede design

  • Loops: for..end, mens..end, until..end
  • Betingelser: hvis...[else..]slutt, bytt..[sak..slutt..][else..]slutt
  • Metoder: proc ():... end, func ():... end
  • Etikett og gå til: :, hopp
  • Enum oppregninger og konstante matriser.

Variabler

Oversetteren kan bestemme dem automatisk, eller om utvikleren skriver var før de definerer dem.

Kodeeksempler:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Globale og lokale variabler støttes.

OOP

Vel, vi har kommet til det deiligste emnet. Mash støtter alle objektorienterte programmeringsparadigmer. De. klasser, arv, polymorfisme (inkludert dynamisk), dynamisk automatisk refleksjon og introspeksjon (full).

Uten videre er det bedre å bare gi kodeeksempler.

En enkel klasse og arbeid 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 gi ut: 30.

Arv og 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

Vil gi ut: 60.

Hva med dynamisk polymorfisme? Ja, dette er refleksjon!:

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 gi ut: 60.

La oss nå ta et øyeblikk til å introspisere for enkle verdier 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 gi ut: sant, sant.

Om oppdragsoperatører og eksplisitte pekere

Operatoren ?= brukes til å tilordne en variabel en peker til en verdi i minnet.
Operatoren = endrer en verdi i minnet ved å bruke en peker fra en variabel.
Og nå litt om eksplisitte pekepinner. Jeg la dem til språket slik at de eksisterer.
@ — ta en eksplisitt peker til en variabel.
? — få en variabel ved peker.
@= — tilordne en verdi til en variabel med en eksplisitt peker til den.

Kodeeksempel:

uses <bf>
uses <crt>

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

Vil gi ut: noen tall, 10, 11.

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

Kodeeksempel:

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 fortsetter å se og se på GraalVM & Truffle. Runtime-miljøet mitt har ikke en JIT-kompilator, så ytelsesmessig er det foreløpig kun konkurransedyktig med Python. Jeg håper at jeg vil være i stand til å implementere JIT-kompilering basert på GraalVM eller LLVM.

oppbevaringssted

Du kan leke med utviklingen og følge prosjektet selv.

Området
Repository på GitHub

Takk for at du leste til slutten hvis du gjorde det.

Kilde: www.habr.com

Legg til en kommentar