Nieuwe programmeertaal Mash

Een aantal jaren heb ik geprobeerd mijn eigen programmeertaal te ontwikkelen. Ik wilde, naar mijn mening, de meest eenvoudige, volledig functionele en handige taal creëren die mogelijk is.

In dit artikel wil ik de belangrijkste fasen van mijn werk belichten en, om te beginnen, het gecreëerde concept van de taal en de eerste implementatie ervan beschrijven, waar ik momenteel aan werk.

Laat ik vooraf zeggen dat ik het hele project in Free Pascal heb geschreven, omdat... programma's erop kunnen voor een groot aantal platforms worden samengesteld, en de compiler zelf produceert zeer geoptimaliseerde binaire bestanden (ik verzamel alle componenten van het project met de O2-vlag).

Taallooptijd

Allereerst is het de moeite waard om te praten over de virtuele machine die ik moest schrijven om toekomstige applicaties in mijn taal uit te voeren. Ik besloot misschien een stapelarchitectuur te implementeren, omdat dit de gemakkelijkste manier was. Ik vond geen enkel normaal artikel over hoe je dit in het Russisch moest doen, dus nadat ik mezelf vertrouwd had gemaakt met het Engelstalige materiaal, ging ik aan de slag met het ontwerpen en schrijven van mijn eigen fiets. Vervolgens zal ik mijn “geavanceerde” ideeën en ontwikkelingen op dit gebied presenteren.

Stack-implementatie

Het is duidelijk dat bovenaan de VM de stapel staat. In mijn implementatie werkt het in blokken. In wezen is dit een eenvoudige reeks pointers en een variabele om de index van de bovenkant van de stapel op te slaan.
Wanneer het wordt geïnitialiseerd, wordt een array van 256 elementen gemaakt. Als er meer wijzers op de stapel worden geplaatst, wordt de grootte ervan groter met de volgende 256 elementen. Dienovereenkomstig wordt bij het verwijderen van elementen uit de stapel de grootte ervan aangepast.

De VM gebruikt verschillende stapels:

  1. Hoofdstapel.
  2. Een stapel voor het opslaan van retourpunten.
  3. Vuilnisverzamelaar stapel.
  4. Probeer/vang/eindelijk de handlerstapel te blokkeren.

Constanten en variabelen

Deze is eenvoudig. Constanten worden in een apart stukje code verwerkt en zijn in toekomstige toepassingen beschikbaar via statische adressen. Variabelen zijn een reeks verwijzingen van een bepaalde grootte, de toegang tot de cellen wordt uitgevoerd door index - d.w.z. statisch adres. Variabelen kunnen naar de bovenkant van de stapel worden geduwd of van daaruit worden gelezen. Eigenlijk omdat Terwijl onze variabelen in wezen pointers naar waarden opslaan in het VM-geheugen, wordt de taal gedomineerd door het werken met impliciete pointers.

Vuilnisman

In mijn VM is het semi-automatisch. Die. de ontwikkelaar beslist zelf wanneer hij de vuilnisman moet bellen. Het werkt niet met een gewone pointerteller, zoals in Python, Perl, Ruby, Lua, enz. Het wordt geïmplementeerd via een markersysteem. Die. wanneer het de bedoeling is dat aan een variabele een tijdelijke waarde wordt toegewezen, wordt een verwijzing naar deze waarde toegevoegd aan de stapel van de garbage collector. In de toekomst doorloopt de verzamelaar snel de reeds opgestelde lijst met aanwijzingen.

Omgaan met try/catch/finally-blokken

Zoals in elke moderne taal is de afhandeling van uitzonderingen een belangrijk onderdeel. De VM-kern is verpakt in een try..catch-blok, dat kan terugkeren naar de uitvoering van de code nadat een uitzondering is onderschept door wat informatie erover naar de stapel te duwen. In applicatiecode kunt u try/catch/finally-codeblokken definiëren, waarbij u toegangspunten specificeert bij catch (uitzonderingshandler) en final/end (einde van het blok).

Multithreading

Het wordt ondersteund op VM-niveau. Het is eenvoudig en handig in gebruik. Het werkt zonder een interruptsysteem, dus de code moet respectievelijk meerdere malen sneller in verschillende threads worden uitgevoerd.

Externe bibliotheken voor VM's

Er is geen manier om zonder dit te doen. VM ondersteunt import, vergelijkbaar met hoe het in andere talen is geïmplementeerd. U kunt een deel van de code in Mash schrijven en een deel van de code in de moedertaal, en deze vervolgens tot één taal koppelen.

Vertaler van Mash-taal op hoog niveau naar bytecode voor VM's

Tussenliggende taal

Om snel een vertaler van een complexe taal naar VM-code te schrijven, heb ik eerst een tussentaal ontwikkeld. Het resultaat was een assembler-achtig verschrikkelijk schouwspel dat hier geen bijzondere betekenis heeft. Ik wil alleen zeggen dat de vertaler op dit niveau de meeste constanten en variabelen verwerkt, hun statische adressen en de adressen van toegangspunten berekent.

Vertaler architectuur

Ik heb niet de beste architectuur voor implementatie gekozen. De vertaler bouwt geen codeboom, zoals andere vertalers doen. Hij kijkt naar het begin van de structuur. Die. als het stuk code dat wordt geparseerd er uitziet als “while <condition>:”, dan is het duidelijk dat dit een while-lusconstructie is en moet worden verwerkt als een while-lusconstructie. Zoiets als een complexe schakelkast.

Dankzij deze architecturale oplossing bleek de vertaler niet erg snel te zijn. Het gemak van de wijziging is echter aanzienlijk toegenomen. Ik heb sneller de nodige structuren toegevoegd dan mijn koffie kon afkoelen. Volledige OOP-ondersteuning werd in minder dan een week geïmplementeerd.

Code-optimalisatie

Hier had het natuurlijk beter geïmplementeerd kunnen worden (en zal ook geïmplementeerd worden, maar later, zodra men er aan toe komt). Tot nu toe weet de optimizer alleen ongebruikte code, constanten en import uit de assemblage af te snijden. Ook worden meerdere constanten met dezelfde waarde vervangen door één. Dat is alles.

Mash-taal

Basisconcept van taal

Het belangrijkste idee was om een ​​zo functioneel en eenvoudig mogelijke taal te ontwikkelen. Ik denk dat de ontwikkeling zijn taak met een knal vervult.

Codeblokken, procedures en functies

Alle constructies in de taal worden geopend met een dubbele punt. : en worden door de exploitant gesloten einde.

Procedures en functies worden respectievelijk gedeclareerd als proc en func. De argumenten staan ​​tussen haakjes. Alles is zoals de meeste andere talen.

Exploitant terugkeer u kunt een waarde retourneren van een functie, operator breken Hiermee kunt u de procedure/functie verlaten (als deze zich buiten de lussen bevindt).

Voorbeeldcode:

...

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

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

Ondersteunde ontwerpen

  • Lussen: for..end, while..end, until..end
  • Voorwaarden: if..[else..]end, switch..[case..end..][else..]end
  • Methoden: proc <naam>():... end, func <naam>():... end
  • Label & ga naar: <naam>:, spring <naam>
  • Opsommingen en constante arrays.

variabelen

De vertaler kan ze automatisch bepalen, of als de ontwikkelaar var schrijft voordat hij ze definieert.

Codevoorbeelden:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Globale en lokale variabelen worden ondersteund.

OOP

Nou, we zijn bij het heerlijkste onderwerp aangekomen. Mash ondersteunt alle objectgeoriënteerde programmeerparadigma's. Die. klassen, overerving, polymorfisme (inclusief dynamisch), dynamische automatische reflectie en introspectie (volledig).

Zonder verder oponthoud is het beter om alleen codevoorbeelden te geven.

Een eenvoudige klasse en ermee werken:

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

Zal opleveren: 30.

Overerving en 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

Zal opleveren: 60.

Hoe zit het met dynamisch polymorfisme? Ja, dit is reflectie!:

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

Zal opleveren: 60.

Laten we nu even de tijd nemen om naar eenvoudige waarden en klassen te kijken:

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

Zal resulteren in: waar, waar.

Over toewijzingsoperatoren en expliciete verwijzingen

De operator ?= wordt gebruikt om aan een variabele een pointer toe te wijzen naar een waarde in het geheugen.
De operator = verandert een waarde in het geheugen met behulp van een aanwijzer van een variabele.
En nu iets over expliciete aanwijzingen. Ik heb ze aan de taal toegevoegd zodat ze bestaan.
@<variabele> — neem een ​​expliciete verwijzing naar een variabele.
?<variabele> — haal een variabele op via de aanwijzer.
@= — wijs een waarde toe aan een variabele door er expliciet naar te verwijzen.

Voorbeeldcode:

uses <bf>
uses <crt>

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

Zal uitvoeren: een getal, 10, 11.

Probeer..[vang..][eindelijk..]einde

Voorbeeldcode:

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

Plannen voor de toekomst

Ik blijf kijken en kijken naar GraalVM & Truffle. Mijn runtime-omgeving beschikt niet over een JIT-compiler, dus qua prestaties is deze momenteel alleen concurrerend met Python. Ik hoop dat ik JIT-compilatie op basis van GraalVM of LLVM kan implementeren.

opslagplaats

Je kunt spelen met de ontwikkelingen en zelf het project volgen.

Plaats
Opslagplaats op GitHub

Bedankt voor het lezen tot het einde, als je dat hebt gedaan.

Bron: www.habr.com

Voeg een reactie