Nouveau langage de programmation Mash

Pendant plusieurs années, j'ai essayé de développer mon langage de programmation. Je voulais créer à mon avis le langage le plus simple, entièrement fonctionnel et pratique.

Dans cet article, je veux souligner les principales étapes de mon travail et commencer par décrire le concept créé du langage et sa première implémentation sur laquelle je travaille actuellement.

Je dirai à l'avance que j'ai écrit tout le projet en Free Pascal, car Les programmes peuvent être assemblés pour un grand nombre de plates-formes, et le compilateur lui-même produit des binaires très optimisés (je collecte tous les composants du projet avec le drapeau O2).

Exécution de la langue


La première chose à parler est une machine virtuelle que j'ai dû écrire pour exécuter les futures applications dans ma langue. J'ai décidé d'implémenter une architecture de pile, peut-être, car c'était le moyen le plus simple. Je n'ai pas trouvé un seul article normal sur la façon de le faire en russe, donc après avoir lu le matériel en anglais, je me suis assis pour concevoir et écrire mon vélo. Je donnerai en outre mes idées et développements «avancés» dans ce domaine.

Implémentation de la pile


De toute évidence, la machine virtuelle est en tête de la pile. Dans mon implémentation, cela fonctionne par blocs. En substance, il s'agit d'un simple tableau de pointeurs et d'une variable pour stocker l'index du haut de la pile.
Lorsqu'il est initialisé, un tableau de 256 éléments est créé. Si plusieurs pointeurs sont jetés sur la pile, sa taille est augmentée des 256 éléments suivants. En conséquence, lors du retrait d'articles de la pile, sa taille est réglable.

Une machine virtuelle utilise plusieurs piles:

  1. La pile principale.
  2. Pile pour stocker des points de retour.
  3. Pile de collecteur d'ordures.
  4. Le gestionnaire de pile des blocs try / catch / finally.

Constantes et variables


Avec ça, tout est simple. Les constantes sont traitées dans un petit morceau de code séparé et seront disponibles dans des applications futures à des adresses statiques. Les variables sont un tableau de pointeurs d'une certaine taille, l'accès à ses cellules s'effectue par index - c'est-à-dire adresse statique. Les variables peuvent être placées en haut de la pile ou lues à partir de là. En fait, parce que nos variables stockent essentiellement des pointeurs vers des valeurs dans la mémoire de la VM, puis travaillent avec des pointeurs implicites prédomine dans le langage.

Collecteur d'ordures


Dans ma VM, c'est semi-automatique. C'est-à-dire le développeur décide quand appeler le garbage collector. Il ne fonctionne pas selon le compteur de pointeurs habituel, comme dans le même Python, Perl, Ruby, Lua, etc. Il est mis en œuvre via un système de marqueurs. C'est-à-dire lorsqu'il est entendu qu'une valeur temporaire est affectée à une variable, un pointeur vers cette valeur est ajouté à la pile du garbage collector. À l'avenir, le collecteur parcourt rapidement une liste de pointeurs déjà préparée.

Gestion des blocs try / catch / finally


Comme dans tout langage moderne, la gestion des exceptions en est un élément important. Le noyau de la machine virtuelle est enveloppé dans un bloc try..catch, qui peut retourner à l'exécution du code après avoir intercepté l'exception en mettant un peu d'informations à ce sujet sur la pile. Dans le code d'application, vous pouvez spécifier des blocs de code try / catch / finally, indiquant les points d'entrée à rattraper (gestionnaire d'exceptions) et enfin / end (fin de bloc).

Multithreading


Il est pris en charge au niveau de la machine virtuelle. Il est simple et pratique à utiliser. Il fonctionne sans système d'interruption, donc le code doit s'exécuter dans plusieurs threads plusieurs fois plus rapidement, respectivement.

Bibliothèques externes pour VM


Il n'y a aucun moyen de s'en passer. VM prend en charge les importations, tout comme il est implémenté dans d'autres langues. Vous pouvez écrire une partie du code dans Mash et une partie du code dans les langues natives, puis les lier ensemble.

Traducteur de Mash de langue de haut niveau en bytecode pour VM


Langue intermédiaire


Pour écrire rapidement un traducteur d'un langage complexe en code VM, j'ai d'abord développé un langage intermédiaire. Il s'est avéré un spectacle effrayant comme un assembleur, qu'il n'y a pas de sens particulier à considérer ici. Je peux seulement dire qu'à ce niveau le traducteur traite la plupart des constantes, variables, calcule leurs adresses statiques et adresses de points d'entrée.

Architecture du traducteur


Je n'ai pas choisi la meilleure architecture pour l'implémentation. Le traducteur ne construit pas d'arborescence de code, comme il sied aux autres traducteurs. Il regarde le début de la construction. C'est-à-dire si le morceau de code analysé ressemble à "while <condition>:", alors il est évident qu'il s'agit d'une construction while de la boucle et qu'elle doit être traitée comme la construction de la boucle while. Quelque chose comme un boîtier de commutation compliqué.

Grâce à une telle solution architecturale, le traducteur n'a pas été très rapide. Cependant, la simplicité de son raffinement a considérablement augmenté. J'ai ajouté les designs nécessaires plus rapidement que mon café ne pouvait refroidir. La prise en charge complète de la POO a été mise en œuvre en moins d'une semaine.

Optimisation du code


Ici, bien sûr, cela pourrait être mieux réalisé (et cela se fera, mais plus tard, lorsque les mains atteindront). Jusqu'à présent, l'optimiseur ne sait que couper le code inutilisé, les constantes et les importations de l'assembly. De plus, plusieurs constantes de même valeur sont remplacées par une. C’est tout.

Langue de la purée


Le concept de base du langage


L'idée principale était de développer le langage le plus fonctionnel et le plus simple. Je crois que le développement fait face à sa tâche avec un bang.

Blocs de code, procédures et fonctions


Toutes les constructions du langage sont ouvertes avec deux points : et fermées avec l'opérateur final .

Les procédures et fonctions sont déclarées respectivement proc et func. Les arguments sont répertoriés entre parenthèses. Tout comme la plupart des autres langues.

L' instruction return peut renvoyer une valeur à partir d'une fonction, l'instruction break vous permet de quitter une procédure / fonction (si elle est en dehors des boucles).

Exemple de code:

...

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

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


  • : for..end, while..end, until..end
  • : if..[else..]end, switch..[case..end..][else..]end
  • : proc <>():… end, func <>():… end
  • Label & goto: <>:, jump <>
  • Enum .


, var .

:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

.


. Mash - . .. , , ( .. ), ().

, .

:

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

: 30.

:

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

: 60.

? !:

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

: 60.

:

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

: true, true.


?= .
= .
. .
@<> — .
?<> — .
@= — .

:

uses <bf>
uses <crt>

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

: - , 10, 11.

Try..[catch..][finally..]end


:

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


GraalVM & Truffle. JIT , . , JIT GraalVM LLVM.


.


GitHub

, , .

Source: https://habr.com/ru/post/fr434966/


All Articles