Nytt programmeringsspråk Mash

Under flera år försökte jag mig på att utveckla mitt eget programmeringsspråk. Jag ville skapa, enligt min mening, ett så enkelt, fullt funktionellt och bekvämt språk som möjligt.

I den här artikeln vill jag lyfta fram huvudstadierna i mitt arbete och till att börja med beskriva det skapade konceptet för språket och dess första implementering, som jag för närvarande arbetar med.

Låt mig säga i förväg att jag skrev hela projektet i Free Pascal, eftersom... program på den kan monteras för ett stort antal plattformar, och kompilatorn själv producerar mycket optimerade binärfiler (jag samlar alla komponenter i projektet med O2-flaggan).

Språkkörningstid

Först och främst är det värt att prata om den virtuella maskinen som jag var tvungen att skriva för att köra framtida applikationer på mitt språk. Jag bestämde mig för att implementera en stack-arkitektur, kanske, eftersom det var det enklaste sättet. Jag hittade inte en enda normal artikel om hur man gör detta på ryska, så efter att ha bekantat mig med det engelskspråkiga materialet satte jag mig och designade och skrev min egen cykel. Därefter kommer jag att presentera mina "avancerade" idéer och utvecklingar i denna fråga.

Stackimplementering

Uppenbarligen, högst upp i den virtuella datorn är stacken. I min implementering fungerar det i block. I huvudsak är detta en enkel uppsättning pekare och en variabel för att lagra indexet för toppen av stacken.
När den initieras skapas en array med 256 element. Om fler pekare skjuts på stapeln, ökar dess storlek med nästa 256 element. Följaktligen, när element tas bort från stapeln, justeras dess storlek.

Den virtuella datorn använder flera stackar:

  1. Huvudstack.
  2. En stack för att lagra returpunkter.
  3. Sophämtningsstack.
  4. Försök/fånga/slutligen blockera hanterarstack.

Konstanter och variabler

Den här är enkel. Konstanter hanteras i en separat liten kodbit och är tillgängliga i framtida applikationer via statiska adresser. Variabler är en rad pekare av en viss storlek, åtkomst till dess celler utförs av index - d.v.s. statisk adress. Variabler kan skjutas till toppen av stapeln eller läsas därifrån. Egentligen, därför att Medan våra variabler i huvudsak lagrar pekare till värden i VM-minnet, domineras språket av att arbeta med implicita pekare.

Skräp samlare

I min virtuella dator är den halvautomatisk. De där. utvecklaren bestämmer själv när han ska ringa sophämtaren. Det fungerar inte med en vanlig pekarräknare, som i Python, Perl, Ruby, Lua, etc. Det implementeras genom ett markörsystem. De där. när en variabel är avsedd att tilldelas ett temporärt värde läggs en pekare till detta värde till sopsamlarens stack. I framtiden går samlaren snabbt igenom den redan förberedda listan med pekare.

Hantera försök/fånga/slutligen blockerar

Som i alla moderna språk är undantagshantering en viktig komponent. VM-kärnan är inlindad i ett try..catch-block, som kan återgå till kodexekvering efter att ha fångat ett undantag genom att skjuta upp lite information om det i stacken. I applikationskoden kan du definiera försök/fånga/slutligen kodblock, ange ingångspunkter vid catch (undantagshanterare) och slutligen/slut (slutet på blocket).

Multithreading

Det stöds på VM-nivå. Det är enkelt och bekvämt att använda. Det fungerar utan ett avbrottssystem, så koden bör exekveras i flera trådar flera gånger snabbare respektive.

Externa bibliotek för virtuella datorer

Det finns inget sätt att göra utan detta. VM stöder import, liknande hur det är implementerat på andra språk. Du kan skriva en del av koden i Mash och en del av koden på modersmål och sedan länka dem till ett.

Översättare från Mash-språk på hög nivå till bytekod för virtuella datorer

Mellanspråk

För att snabbt skriva en översättare från ett komplext språk till VM-kod utvecklade jag först ett mellanspråk. Resultatet blev ett assemblerliknande fruktansvärt skådespel som det inte är någon speciell poäng med att överväga här. Jag kommer bara att säga att på denna nivå bearbetar översättaren de flesta konstanter och variabler, beräknar deras statiska adresser och adresserna till ingångspunkter.

Översättararkitektur

Jag valde inte den bästa arkitekturen för implementering. Översättaren bygger inte ett kodträd, som andra översättare gör. Han tittar på början av strukturen. De där. om kodbiten som tolkas ser ut som "while <condition>:", så är det uppenbart att detta är en while-loopkonstruktion och måste bearbetas som en while-loopkonstruktion. Något som liknar ett komplext switch-case.

Tack vare denna arkitektoniska lösning visade sig översättaren inte vara särskilt snabb. Lättheten att modifiera den har dock ökat avsevärt. Jag lade till de nödvändiga strukturerna snabbare än mitt kaffe kunde svalna. Fullständigt OOP-stöd implementerades på mindre än en vecka.

Kodoptimering

Här kunde det förstås ha implementerats bättre (och kommer att genomföras, men senare, så fort man kommer runt). Än så länge vet optimeraren bara hur man skär bort oanvänd kod, konstanter och importer från monteringen. Dessutom ersätts flera konstanter med samma värde med en. Det är allt.

Mash språk

Grundläggande språkbegrepp

Huvudtanken var att utveckla ett så funktionellt och enkelt språk som möjligt. Jag tycker att utvecklingen klarar sin uppgift med råge.

Kodblock, procedurer och funktioner

Alla konstruktioner i språket öppnas med kolon. : och stängs av operatören änden.

Procedurer och funktioner deklareras som proc respektive func. Argumenten listas inom parentes. Allt är som de flesta andra språk.

Operatör avkastning du kan returnera ett värde från en funktion, operator bryta låter dig avsluta proceduren/funktionen (om den är utanför slingorna).

Exempelkod:

...

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

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

Designar som stöds

  • Slingor: för..slut, medan..slut, tills..slut
  • Villkor: om...[else..]slut, byt..[ärende..slut..][else..]slut
  • Metoder: proc <name>():... end, func <name>():... end
  • Etikett & gå till: <namn>:, hoppa <namn>
  • Räkna upp uppräkningar och konstantmatriser.

variabler

Översättaren kan fastställa dem automatiskt, eller om utvecklaren skriver var innan de definierar dem.

Kodexempel:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Globala och lokala variabler stöds.

OOP

Nåväl, vi har kommit till det läckraste ämnet. Mash stöder alla objektorienterade programmeringsparadigm. De där. klasser, arv, polymorfism (inklusive dynamisk), dynamisk automatisk reflektion och introspektion (fullständig).

Utan vidare är det bättre att bara ge kodexempel.

En enkel klass och att arbeta 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

Kommer ut: 30.

Arv och polymorfism:

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

Kommer ut: 60.

Hur är det med dynamisk polymorfism? Ja, det här är en reflektion!:

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

Kommer ut: 60.

Låt oss nu ta en stund för att introspektera efter enkla värden och 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

Kommer att skriva ut: sant, sant.

Om uppdragsoperatörer och explicita tips

Operatorn ?= används för att tilldela en variabel en pekare till ett värde i minnet.
Operatorn = ändrar ett värde i minnet med hjälp av en pekare från en variabel.
Och nu lite om tydliga tips. Jag lade till dem i språket så att de finns.
@<variabel> — ta en explicit pekare till en variabel.
?<variabel> — få en variabel med pekaren.
@= — tilldela ett värde till en variabel med en explicit pekare till den.

Exempelkod:

uses <bf>
uses <crt>

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

Kommer att mata ut: något nummer, 10, 11.

Prova..[fånga..][äntligen..]slut

Exempelkod:

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 för framtiden

Jag fortsätter titta och titta på GraalVM & Tryffel. Min runtime-miljö har ingen JIT-kompilator, så prestandamässigt är den för närvarande bara konkurrenskraftig med Python. Jag hoppas att jag kommer att kunna implementera JIT-kompilering baserad på GraalVM eller LLVM.

förvaret

Du kan leka med utvecklingen och själv följa projektet.

webbplats
Repository på GitHub

Tack för att du läste till slutet om du gjorde det.

Källa: will.com

Lägg en kommentar