Nou llenguatge de programació Mash

Durant diversos anys vaig intentar desenvolupar el meu propi llenguatge de programació. Volia crear, al meu entendre, el llenguatge més senzill, totalment funcional i còmode possible.

En aquest article vull destacar les principals etapes del meu treball i, d'entrada, descriure el concepte creat de la llengua i la seva primera implantació, en què estic treballant actualment.

Permeteu-me dir per endavant que vaig escriure tot el projecte en Free Pascal, perquè... els programes que s'hi poden muntar per a un gran nombre de plataformes i el propi compilador produeix binaris molt optimitzats (recull tots els components del projecte amb la bandera O2).

Temps d'execució del llenguatge

En primer lloc, val la pena parlar de la màquina virtual que vaig haver d'escriure per executar futures aplicacions en el meu idioma. Vaig decidir implementar una arquitectura de pila, potser, perquè era la manera més fàcil. No vaig trobar un sol article normal sobre com fer-ho en rus, així que després de familiaritzar-me amb el material en anglès, em vaig asseure a dissenyar i escriure la meva pròpia bicicleta. A continuació, presentaré les meves idees i desenvolupaments "avançats" en aquesta matèria.

Implementació de la pila

Òbviament, a la part superior de la VM hi ha la pila. A la meva implementació funciona en blocs. Essencialment, es tracta d'una matriu senzilla de punters i una variable per emmagatzemar l'índex de la part superior de la pila.
Quan s'inicia, es crea una matriu de 256 elements. Si es col·loquen més punters a la pila, la seva mida augmenta en els 256 elements següents. En conseqüència, en treure elements de la pila, s'ajusta la seva mida.

La màquina virtual utilitza diverses piles:

  1. Pila principal.
  2. Una pila per emmagatzemar els punts de retorn.
  3. Pila de recollida d'escombraries.
  4. Prova/atrapa/per fi bloqueja la pila del controlador.

Constants i variables

Aquest és senzill. Les constants es gestionen en un petit fragment de codi separat i estan disponibles en aplicacions futures mitjançant adreces estàtiques. Les variables són una matriu de punters d'una mida determinada, l'accés a les seves cel·les es realitza mitjançant un índex, és a dir. adreça estàtica. Les variables es poden empènyer a la part superior de la pila o llegir-se des d'allà. De fet, perquè Tot i que les nostres variables essencialment emmagatzemen punters a valors a la memòria VM, el llenguatge està dominat pel treball amb punters implícits.

Abocador

A la meva VM és semiautomàtic. Aquells. el mateix desenvolupador decideix quan trucar al recol·lector d'escombraries. No funciona amb un comptador de punter normal, com en Python, Perl, Ruby, Lua, etc. S'implementa mitjançant un sistema de marcadors. Aquells. quan es pretén assignar un valor temporal a una variable, s'afegeix un punter a aquest valor a la pila del col·lector d'escombraries. En el futur, el col·leccionista repassa ràpidament la llista de punters ja preparada.

Maneig de blocs try/catch/finally

Com en qualsevol idioma modern, el maneig d'excepcions és un component important. El nucli de la VM està embolicat en un bloc try..catch, que pot tornar a l'execució de codi després d'haver detectat una excepció enviant informació sobre aquesta a la pila. Al codi de l'aplicació, podeu definir blocs de codi try/catch/finally, especificant els punts d'entrada a catch (controlador d'excepcions) i finalment/final (final del bloc).

Multithreading

S'admet a nivell de VM. És senzill i còmode d'utilitzar. Funciona sense un sistema d'interrupció, de manera que el codi s'ha d'executar en diversos fils diverses vegades més ràpid, respectivament.

Biblioteques externes per a màquines virtuals

No hi ha manera de prescindir d'això. VM admet importacions, de manera similar a com s'implementa en altres idiomes. Podeu escriure part del codi a Mash i part del codi en idiomes nadius, i després enllaçar-los en un de sol.

Traductor del llenguatge Mash d'alt nivell al bytecode per a màquines virtuals

Llenguatge intermedi

Per escriure ràpidament un traductor d'un llenguatge complex al codi de VM, primer vaig desenvolupar un llenguatge intermedi. El resultat va ser un espectacle terrible semblant a un muntador que no té cap sentit particular a considerar aquí. Només diré que en aquest nivell el traductor processa la majoria de constants i variables, calcula les seves adreces estàtiques i les adreces dels punts d'entrada.

Arquitectura del traductor

No vaig triar la millor arquitectura per a la implementació. El traductor no crea un arbre de codi, com ho fan altres traductors. Mira l'inici de l'estructura. Aquells. si el fragment de codi que s'analitza sembla "while <condition>:", aleshores és obvi que es tracta d'una construcció de bucle while i s'ha de processar com una construcció de bucle while. Una cosa així com un cas d'interruptor complex.

Gràcies a aquesta solució arquitectònica, el traductor va resultar ser poc ràpid. No obstant això, la facilitat de la seva modificació ha augmentat significativament. Vaig afegir les estructures necessàries més ràpidament del que el meu cafè podria refredar-se. El suport OOP complet es va implementar en menys d'una setmana.

Optimització del codi

Aquí, és clar, s'hauria pogut implementar millor (i s'implementarà, però més endavant, tan bon punt s'hi arribi). Fins ara, l'optimitzador només sap com tallar el codi no utilitzat, les constants i les importacions del conjunt. A més, diverses constants amb el mateix valor se substitueixen per una. Això és tot.

Llenguatge mash

Concepte bàsic de llenguatge

La idea principal era desenvolupar el llenguatge més funcional i senzill possible. Crec que el desenvolupament fa front a la seva tasca amb una explosió.

Blocs de codi, procediments i funcions

Totes les construccions de la llengua s'obren amb dos punts. : i són tancats per l'operador final.

Els procediments i les funcions es declaren com a proc i func, respectivament. Els arguments estan llistats entre parèntesis. Tot és com la majoria de les altres llengües.

Operador return podeu retornar un valor d'una funció, operador trencar permet sortir del procediment/funció (si està fora dels bucles).

Codi de mostra:

...

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

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

Dissenys suportats

  • Loops: for..end, while..end, fins...end
  • Condicions: if..[else..]end, switch..[case..end..][else..]end
  • Mètodes: proc <nom>():... final, func <nom>():... final
  • Etiqueta i va a: <nom>:, salta <nom>
  • Enumeracions i matrius constants.

Variables

El traductor pot determinar-los automàticament o si el desenvolupador escriu var abans de definir-los.

Exemples de codi:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

S'admeten variables globals i locals.

OOP

Bé, hem arribat al tema més deliciós. Mash admet tots els paradigmes de programació orientada a objectes. Aquells. classes, herència, polimorfisme (inclòs dinàmic), reflexió automàtica dinàmica i introspecció (complet).

Sense més preàmbuls, és millor donar exemples de codi.

Una classe senzilla i treballant amb ella:

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

Sortida: 30.

Herència i polimorfisme:

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

Sortida: 60.

Què passa amb el polimorfisme dinàmic? Sí, això és una reflexió!:

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

Sortida: 60.

Ara prenguem un moment per introspeccionar valors i classes simples:

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

Sortirà: cert, cert.

Sobre els operadors d'assignació i els punters explícits

L'operador ?= s'utilitza per assignar un punter a una variable a un valor a la memòria.
L'operador = canvia un valor a la memòria mitjançant un punter d'una variable.
I ara una mica sobre punters explícits. Els he afegit a la llengua perquè existeixin.
@<variable> — agafa un punter explícit a una variable.
?<variable> — obteniu una variable per punter.
@= — assigna un valor a una variable mitjançant un punter explícit.

Codi de mostra:

uses <bf>
uses <crt>

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

Sortirà: algun número, 10, 11.

Prova..[atrapar..][finalment..]acabar

Codi de mostra:

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

Plans per al futur

Continuo mirant i mirant GraalVM & Truffle. El meu entorn d'execució no té un compilador JIT, de manera que en termes de rendiment, actualment només és competitiu amb Python. Espero poder implementar la compilació JIT basada en GraalVM o LLVM.

repositori

Podeu jugar amb els desenvolupaments i seguir el projecte vosaltres mateixos.

Lloc
Repositori a GitHub

Gràcies per llegir fins al final si ho vas fer.

Font: www.habr.com

Afegeix comentari