Mehrere Jahre lang habe ich versucht, meine eigene Programmiersprache zu entwickeln. Meiner Meinung nach wollte ich eine möglichst einfache, voll funktionsfähige und benutzerfreundliche Sprache schaffen.
In diesem Artikel möchte ich die Hauptphasen meiner Arbeit hervorheben und zunächst das erstellte Konzept der Sprache und ihre erste Implementierung beschreiben, an der ich derzeit arbeite.
Lassen Sie mich vorab sagen, dass ich das gesamte Projekt in Free Pascal geschrieben habe, weil... Programme darauf können für eine große Anzahl von Plattformen zusammengestellt werden, und der Compiler selbst erzeugt sehr optimierte Binärdateien (ich sammle alle Komponenten des Projekts mit der O2-Flagge).
Sprachlaufzeit
Zunächst lohnt es sich, über die virtuelle Maschine zu sprechen, die ich schreiben musste, um zukünftige Anwendungen in meiner Sprache auszuführen. Ich habe mich vielleicht für die Implementierung einer Stack-Architektur entschieden, weil das der einfachste Weg war. Ich habe keinen einzigen normalen Artikel darüber auf Russisch gefunden, also habe ich mich, nachdem ich mich mit dem englischsprachigen Material vertraut gemacht hatte, daran gemacht, mein eigenes Fahrrad zu entwerfen und zu schreiben. Als nächstes werde ich meine „fortgeschrittenen“ Ideen und Entwicklungen in dieser Angelegenheit vorstellen.
Stack-Implementierung
Offensichtlich befindet sich an der Spitze der VM der Stapel. In meiner Implementierung funktioniert es in Blöcken. Im Wesentlichen handelt es sich hierbei um ein einfaches Array aus Zeigern und einer Variablen zum Speichern des Indexes der obersten Ebene des Stapels.
Bei der Initialisierung wird ein Array mit 256 Elementen erstellt. Werden weitere Zeiger auf den Stapel geschoben, erhöht sich dessen Größe um die nächsten 256 Elemente. Dementsprechend wird beim Entfernen von Elementen aus dem Stapel dessen Größe angepasst.
Die VM verwendet mehrere Stacks:
- Hauptstapel.
- Ein Stapel zum Speichern von Rückgabepunkten.
- Müllsammelstapel.
- Try/catch/finally Block-Handler-Stack.
Konstanten und Variablen
Dieser ist einfach. Konstanten werden in einem separaten kleinen Codeabschnitt behandelt und stehen in zukünftigen Anwendungen über statische Adressen zur Verfügung. Variablen sind ein Array von Zeigern einer bestimmten Größe, der Zugriff auf seine Zellen erfolgt über den Index – d.h. Statische Adresse. Variablen können an die Spitze des Stapels verschoben oder von dort gelesen werden. Eigentlich, weil Während unsere Variablen im Wesentlichen Zeiger auf Werte im VM-Speicher speichern, dominiert in der Sprache die Arbeit mit impliziten Zeigern.
Müllsammler
In meiner VM ist es halbautomatisch. Diese. Der Entwickler selbst entscheidet, wann der Garbage Collector aufgerufen wird. Es funktioniert nicht mit einem regulären Zeigerzähler wie in Python, Perl, Ruby, Lua usw. Die Umsetzung erfolgt über ein Markersystem. Diese. Wenn einer Variablen ein temporärer Wert zugewiesen werden soll, wird dem Stapel des Garbage Collectors ein Zeiger auf diesen Wert hinzugefügt. Zukünftig geht der Collector schnell die bereits vorbereitete Liste der Zeiger durch.
Umgang mit try/catch/finally-Blöcken
Wie in jeder modernen Sprache ist die Ausnahmebehandlung ein wichtiger Bestandteil. Der VM-Kern ist in einen try..catch-Block eingeschlossen, der nach dem Abfangen einer Ausnahme zur Codeausführung zurückkehren kann, indem er einige Informationen darüber auf den Stapel schiebt. Im Anwendungscode können Sie Codeblöcke vom Typ „try/catch/finally“ definieren und dabei Einstiegspunkte bei „catch“ (Ausnahmehandler) und „finally/end“ (Ende des Blocks) angeben.
Multithreading
Es wird auf VM-Ebene unterstützt. Es ist einfach und bequem zu verwenden. Es funktioniert ohne Interrupt-System, sodass der Code in mehreren Threads jeweils um ein Vielfaches schneller ausgeführt werden sollte.
Externe Bibliotheken für VMs
Darauf kann man nicht verzichten. VM unterstützt Importe, ähnlich wie es in anderen Sprachen implementiert ist. Sie können einen Teil des Codes in Mash und einen Teil des Codes in Muttersprachen schreiben und sie dann zu einer einzigen verknüpfen.
Übersetzer von der höheren Mash-Sprache in Bytecode für VMs
Mittelstufe
Um schnell einen Übersetzer aus einer komplexen Sprache in VM-Code zu schreiben, habe ich zunächst eine Zwischensprache entwickelt. Das Ergebnis war ein Assembler-artiges Schreckensspektakel, das hier nicht näher betrachtet werden soll. Ich möchte nur sagen, dass der Übersetzer auf dieser Ebene die meisten Konstanten und Variablen verarbeitet, ihre statischen Adressen und die Adressen der Einstiegspunkte berechnet.
Übersetzerarchitektur
Ich habe nicht die beste Architektur für die Implementierung ausgewählt. Der Übersetzer erstellt keinen Codebaum, wie es andere Übersetzer tun. Er blickt auf den Anfang der Struktur. Diese. Wenn der zu analysierende Code wie „while <Bedingung>:“ aussieht, ist es offensichtlich, dass es sich um ein While-Schleifenkonstrukt handelt und als While-Schleifenkonstrukt verarbeitet werden muss. So etwas wie ein komplexes Schaltergehäuse.
Dank dieser architektonischen Lösung erwies sich der Übersetzer als nicht sehr schnell. Allerdings ist die Einfachheit seiner Modifikation deutlich gestiegen. Ich habe die nötigen Strukturen schneller hinzugefügt, als mein Kaffee abkühlen konnte. Die vollständige OOP-Unterstützung wurde in weniger als einer Woche implementiert.
Codeoptimierung
Hier hätte es natürlich besser umgesetzt werden können (und wird umgesetzt, aber später, sobald man dazu kommt). Bisher weiß der Optimierer nur, wie er ungenutzten Code, Konstanten und Importe aus der Assembly abschneidet. Außerdem werden mehrere Konstanten mit demselben Wert durch eine ersetzt. Das ist alles.
Mash-Sprache
Grundbegriff der Sprache
Die Hauptidee bestand darin, eine möglichst funktionale und einfache Sprache zu entwickeln. Ich denke, dass die Entwicklung ihre Aufgabe mit Bravour meistert.
Codeblöcke, Prozeduren und Funktionen
Alle Konstruktionen in der Sprache werden mit einem Doppelpunkt eröffnet. : und werden vom Betreiber geschlossen Ende.
Prozeduren und Funktionen werden als proc bzw. func deklariert. Die Argumente sind in Klammern aufgeführt. Alles ist wie in den meisten anderen Sprachen.
Operator Rückkehr Sie können einen Wert von einer Funktion oder einem Operator zurückgeben brechen ermöglicht Ihnen, die Prozedur/Funktion zu verlassen (wenn sie sich außerhalb der Schleifen befindet).
ример кода:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Unterstützte Designs
- Schleifen: for..end, while..end, Until..end
- Bedingungen: if..[else..]end, switch..[case..end..][else..]end
- Methoden: proc <name>():... end, func <name>():... end
- Label & gehe zu: <Name>:, springe zu <Name>
- Enum-Aufzählungen und konstante Arrays.
Variablen
Der Übersetzer kann sie automatisch ermitteln, oder wenn der Entwickler var schreibt, bevor er sie definiert.
Codebeispiele:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Es werden globale und lokale Variablen unterstützt.
OOP
Nun, wir sind beim köstlichsten Thema angelangt. Mash unterstützt alle objektorientierten Programmierparadigmen. Diese. Klassen, Vererbung, Polymorphismus (einschließlich dynamischer), dynamischer automatischer Reflexion und Selbstbeobachtung (vollständig).
Ohne weitere Umschweife ist es besser, nur Codebeispiele zu nennen.
Eine einfache Klasse und die Arbeit damit:
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
Wird ausgegeben: 30.
Vererbung und Polymorphismus:
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
Wird ausgegeben: 60.
Was ist mit dynamischem Polymorphismus? Ja, das ist Reflexion!:
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
Wird ausgegeben: 60.
Nehmen wir uns nun einen Moment Zeit, um nach einfachen Werten und Klassen zu suchen:
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
Gibt aus: wahr, wahr.
Über Zuweisungsoperatoren und explizite Zeiger
Der Operator ?= wird verwendet, um einer Variablen einen Zeiger auf einen Wert im Speicher zuzuweisen.
Der =-Operator ändert einen Wert im Speicher mithilfe eines Zeigers einer Variablen.
Und nun ein wenig zu expliziten Hinweisen. Ich habe sie der Sprache hinzugefügt, damit sie existieren.
@<Variable> – Nimm einen expliziten Zeiger auf eine Variable.
?<Variable> – eine Variable per Zeiger abrufen.
@= – Weisen Sie einer Variablen einen Wert zu, indem Sie einen expliziten Zeiger darauf setzen.
ример кода:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
Gibt aus: eine Zahl, 10, 11.
Versuchen Sie es..[fangen..][endlich..]Ende
ример кода:
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
Pläne für die Zukunft
Ich schaue immer wieder nach GraalVM & Truffle. Meine Laufzeitumgebung verfügt über keinen JIT-Compiler und ist daher leistungstechnisch derzeit nur mit Python konkurrenzfähig. Ich hoffe, dass ich die JIT-Kompilierung basierend auf GraalVM oder LLVM implementieren kann.
Repository
Sie können mit den Entwicklungen spielen und das Projekt selbst verfolgen.
Vielen Dank, dass Sie bis zum Ende gelesen haben.
Source: habr.com