Nuovo linguaggio di programmazione Mash

Per diversi anni ho provato a sviluppare il mio linguaggio di programmazione. Volevo creare, secondo me, il linguaggio più semplice, pienamente funzionale e conveniente possibile.

In questo articolo voglio evidenziare le fasi principali del mio lavoro e, per cominciare, descrivere il concetto creato del linguaggio e la sua prima implementazione, su cui sto attualmente lavorando.

Premetto che ho scritto l'intero progetto in Free Pascal perché... i programmi su di esso possono essere assemblati per un numero enorme di piattaforme e il compilatore stesso produce binari molto ottimizzati (raccolgo tutti i componenti del progetto con il flag O2).

Durata della lingua

Innanzitutto vale la pena parlare della macchina virtuale che ho dovuto scrivere per eseguire le future applicazioni nella mia lingua. Ho deciso di implementare un'architettura stack, forse, perché era il modo più semplice. Non ho trovato un solo articolo normale su come farlo in russo, quindi dopo aver familiarizzato con il materiale in lingua inglese, mi sono seduto a progettare e scrivere la mia bicicletta. Successivamente presenterò le mie idee e sviluppi “avanzati” in questa materia.

Implementazione dello stack

Ovviamente, in cima alla VM c'è lo stack. Nella mia implementazione funziona a blocchi. Essenzialmente si tratta di un semplice array di puntatori e di una variabile per memorizzare l'indice della parte superiore dello stack.
Quando viene inizializzato, viene creato un array di 256 elementi. Se vengono inseriti più puntatori nello stack, la sua dimensione aumenta dei successivi 256 elementi. Di conseguenza, quando si rimuovono gli elementi dalla pila, la sua dimensione viene regolata.

La VM utilizza diversi stack:

  1. Pila principale.
  2. Uno stack per memorizzare i punti di ritorno.
  3. Pila del raccoglitore di rifiuti.
  4. Prova/cattura/finalmente blocca lo stack del gestore.

Costanti e variabili

Questo è semplice. Le costanti vengono gestite in una piccola porzione di codice separata e sono disponibili nelle applicazioni future tramite indirizzi statici. Le variabili sono un array di puntatori di una certa dimensione, l'accesso alle sue celle viene effettuato tramite indice, ad es. indirizzo statico. Le variabili possono essere spinte in cima allo stack o lette da lì. In realtà, perché Mentre le nostre variabili memorizzano essenzialmente puntatori a valori nella memoria della VM, il linguaggio è dominato dal lavoro con puntatori impliciti.

Netturbino

Nella mia VM è semiautomatico. Quelli. lo sviluppatore stesso decide quando chiamare il garbage collector. Non funziona utilizzando un normale contatore di puntatori, come in Python, Perl, Ruby, Lua, ecc. Viene implementato attraverso un sistema di marcatori. Quelli. quando si intende assegnare a una variabile un valore temporaneo, un puntatore a questo valore viene aggiunto allo stack del Garbage Collector. In futuro, il collezionista scorre rapidamente l'elenco di puntatori già preparato.

Gestire i blocchi try/catch/finally

Come in ogni linguaggio moderno, la gestione delle eccezioni è una componente importante. Il core della VM è racchiuso in un blocco try..catch, che può tornare all'esecuzione del codice dopo aver rilevato un'eccezione inserendo alcune informazioni sullo stack. Nel codice dell'applicazione è possibile definire blocchi di codice try/catch/finally, specificando i punti di ingresso in catch (gestore delle eccezioni) e last/end (fine del blocco).

Multithreading

È supportato a livello di VM. È semplice e comodo da usare. Funziona senza un sistema di interruzione, quindi il codice dovrebbe essere eseguito in più thread più volte più velocemente, rispettivamente.

Librerie esterne per VM

Non c'è modo di farne a meno. La VM supporta le importazioni, in modo simile a come viene implementata in altre lingue. Puoi scrivere parte del codice in Mash e parte del codice nelle lingue native, quindi collegarli in uno solo.

Traduttore dal linguaggio Mash di alto livello al bytecode per VM

Linguaggio intermedio

Per scrivere rapidamente un traduttore da un linguaggio complesso al codice VM, ho prima sviluppato un linguaggio intermedio. Il risultato è stato uno spettacolo terribile da assemblatore che non ha particolarmente senso considerare qui. Dirò solo che a questo livello il traduttore elabora la maggior parte delle costanti e delle variabili, calcola i loro indirizzi statici e gli indirizzi dei punti di ingresso.

Architettura del traduttore

Non ho scelto l'architettura migliore per l'implementazione. Il traduttore non costruisce un albero del codice, come fanno gli altri traduttori. Guarda l'inizio della struttura. Quelli. se il pezzo di codice da analizzare assomiglia a “ while <condizione>:”, allora è ovvio che si tratta di un costrutto di ciclo while e deve essere elaborato come un costrutto di ciclo while. Qualcosa come un complesso caso di commutazione.

Grazie a questa soluzione architetturale il traduttore si è rivelato poco veloce. Tuttavia, la facilità della sua modifica è aumentata in modo significativo. Ho aggiunto le strutture necessarie più velocemente di quanto il mio caffè potesse raffreddarsi. Il supporto completo della programmazione orientata agli oggetti è stato implementato in meno di una settimana.

Ottimizzazione del codice

Qui, ovviamente, avrebbe potuto essere implementato meglio (e lo sarà, ma più tardi, non appena ci si riuscirà). Finora, l'ottimizzatore sa solo come eliminare dall'assembly il codice, le costanti e le importazioni non utilizzate. Inoltre, più costanti con lo stesso valore vengono sostituite da una. È tutto.

Mash linguistico

Concetti base del linguaggio

L'idea principale era quella di sviluppare il linguaggio più funzionale e semplice possibile. Penso che lo sviluppo affronti il ​​suo compito con il botto.

Blocchi di codice, procedure e funzioni

Tutte le costruzioni nella lingua si aprono con i due punti. : e vengono chiusi dall'operatore fine.

Le procedure e le funzioni sono dichiarate rispettivamente come proc e func. Gli argomenti sono elencati tra parentesi. Tutto è come la maggior parte delle altre lingue.

Operatore ritorno puoi restituire un valore da una funzione, operatore rompere permette di uscire dalla procedura/funzione (se è fuori dai loop).

Esempio di codice:

...

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

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

Disegni supportati

  • Cicli: for..end, while..end, Until..end
  • Condizioni: if..[else..]end, switch..[case..end..][else..]end
  • Metodi: proc <nome>():... end, func <nome>():... end
  • Etichetta e vai a: <nome>:, salta <nome>
  • Enumerazioni Enum e matrici costanti.

variabili

Il traduttore può determinarli automaticamente oppure se lo sviluppatore scrive var prima di definirli.

Esempi di codice:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Sono supportate variabili globali e locali.

OLP

Bene, siamo arrivati ​​all’argomento più delizioso. Mash supporta tutti i paradigmi di programmazione orientata agli oggetti. Quelli. classi, ereditarietà, polimorfismo (compreso quello dinamico), riflessione automatica dinamica e introspezione (completo).

Senza ulteriori indugi, è meglio fornire solo esempi di codice.

Una classe semplice e lavorare con essa:

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

Uscita: 30.

Ereditarietà e polimorfismo:

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

Uscita: 60.

Che dire del polimorfismo dinamico? Sì, questa è una riflessione!:

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

Uscita: 60.

Ora prendiamoci un momento per analizzare valori e classi semplici:

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

Risulterà: vero, vero.

Informazioni sugli operatori di assegnamento e sui puntatori espliciti

L'operatore ?= viene utilizzato per assegnare a una variabile un puntatore a un valore in memoria.
L'operatore = modifica un valore in memoria utilizzando un puntatore da una variabile.
E ora un po' di indicazioni esplicite. Li ho aggiunti alla lingua in modo che esistano.
@<variabile>: prende un puntatore esplicito a una variabile.
?<variabile>: ottiene una variabile tramite puntatore.
@= — assegna un valore a una variabile tramite un puntatore esplicito ad essa.

Esempio di codice:

uses <bf>
uses <crt>

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

Verrà prodotto: un numero, 10, 11.

Prova...[cattura..][finalmente..]finisci

Esempio di codice:

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

Progetti per il futuro

Continuo a guardare e riguardare GraalVM e Truffle. Il mio ambiente runtime non dispone di un compilatore JIT, quindi in termini di prestazioni è attualmente competitivo solo con Python. Spero di essere in grado di implementare la compilazione JIT basata su GraalVM o LLVM.

deposito

Puoi giocare con gli sviluppi e seguire tu stesso il progetto.

sito web
Archivio su GitHub

Grazie per aver letto fino alla fine, se lo hai fatto.

Fonte: habr.com

Aggiungi un commento