Nový programovací jazyk Mash

Několik let jsem zkoušel vyvíjet svůj vlastní programovací jazyk. Chtěl jsem vytvořit, podle mého názoru, co nejjednodušší, plně funkční a pohodlný jazyk.

V tomto článku chci vyzdvihnout hlavní etapy mé práce a pro začátek popsat vytvořený koncept jazyka a jeho první implementaci, na které právě pracuji.

Předem říkám, že jsem celý projekt napsal ve Free Pascalu, protože... programy na něm lze sestavit pro obrovské množství platforem a samotný kompilátor produkuje velmi optimalizované binární soubory (sbírám všechny komponenty projektu s příznakem O2).

Jazykový běh

Za prvé, stojí za to mluvit o virtuálním stroji, který jsem musel napsat, abych spouštěl budoucí aplikace v mém jazyce. Rozhodl jsem se implementovat architekturu zásobníku, možná proto, že to byl nejjednodušší způsob. Nenašel jsem jediný normální článek o tom, jak to udělat v ruštině, takže poté, co jsem se seznámil s anglicky psaným materiálem, sedl jsem k navrhování a psaní vlastního kola. Dále představím své „pokročilé“ nápady a vývoj v této věci.

Implementace zásobníku

Je zřejmé, že na vrcholu virtuálního počítače je zásobník. V mé implementaci to funguje v blocích. V podstatě se jedná o jednoduché pole ukazatelů a proměnnou pro uložení indexu vrcholu zásobníku.
Po inicializaci se vytvoří pole 256 prvků. Pokud je na zásobník zatlačeno více ukazatelů, jeho velikost se zvětší o dalších 256 prvků. Podle toho se při vyjímání prvků ze stohu upraví jeho velikost.

VM používá několik zásobníků:

  1. Hlavní zásobník.
  2. Zásobník pro ukládání návratových bodů.
  3. Stoh sběrače odpadků.
  4. Zkuste/chytit/konečně zablokovat zásobník obslužného programu.

Konstanty a proměnné

Tento je jednoduchý. Konstanty jsou zpracovány v samostatném malém kousku kódu a jsou dostupné v budoucích aplikacích prostřednictvím statických adres. Proměnné jsou pole ukazatelů určité velikosti, přístup k jeho buňkám se provádí indexem - tzn. statická adresa. Proměnné lze přesunout na vrchol zásobníku nebo je odtud číst. Vlastně, protože Zatímco naše proměnné v podstatě ukládají ukazatele na hodnoty v paměti VM, jazyku dominuje práce s implicitními ukazateli.

Popelář

V mém VM je to poloautomatické. Tito. vývojář sám rozhodne, kdy zavolat popeláře. Nefunguje to pomocí běžného počítadla ukazatelů, jako v Pythonu, Perlu, Ruby, Lua atd. Je implementován prostřednictvím značkovacího systému. Tito. když má být proměnné přiřazena dočasná hodnota, přidá se ukazatel na tuto hodnotu do zásobníku odpadků. V budoucnu sběratel rychle projde již připravený seznam ukazatelů.

Manipulace s bloky pokus/chytit/konečně

Stejně jako v každém moderním jazyce je důležitou součástí zpracování výjimek. Jádro virtuálního počítače je zabaleno do bloku try..catch, který se může vrátit ke spuštění kódu po zachycení výjimky tím, že o ní vloží nějaké informace do zásobníku. V kódu aplikace můžete definovat bloky kódu try/catch/finally, specifikující vstupní body na catch (obslužný program výjimky) a nakonec/konec (konec bloku).

Vícevláknové zpracování

Je podporován na úrovni VM. Je to jednoduché a pohodlné použití. Funguje bez přerušovacího systému, takže kód by měl být vykonáván v několika vláknech několikrát rychleji, resp.

Externí knihovny pro virtuální počítače

Bez toho se nelze obejít. VM podporuje importy, podobně jako je implementován v jiných jazycích. Část kódu můžete napsat v Mash a část kódu v nativních jazycích a poté je propojit do jednoho.

Překladač z vysokoúrovňového jazyka Mash do bytecode pro virtuální počítače

Středně pokročilý jazyk

Abych rychle napsal překladač ze složitého jazyka do kódu VM, nejprve jsem vyvinul přechodný jazyk. Výsledkem byla strašlivá podívaná podobná assembleru, kterou zde nemá smysl uvažovat. Řeknu jen, že na této úrovni překladač zpracovává většinu konstant a proměnných, počítá jejich statické adresy a adresy vstupních bodů.

Architektura překladače

Nezvolil jsem nejlepší architekturu pro implementaci. Překladač nevytváří strom kódu, jako to dělají ostatní překladatelé. Podívá se na začátek struktury. Tito. pokud analyzovaný kus kódu vypadá jako „while <condition>:“, pak je zřejmé, že se jedná o konstrukci cyklu while a musí být zpracován jako konstrukce cyklu while. Něco jako složitá spínací skříň.

Díky tomuto architektonickému řešení se ukázalo, že překladač není příliš rychlý. Výrazně se však zvýšila snadnost jeho modifikace. Potřebné struktury jsem přidal rychleji, než mi káva stačila vychladnout. Plná podpora OOP byla implementována za méně než týden.

Optimalizace kódu

Tady to samozřejmě mohlo být implementováno lépe (a bude implementováno, ale později, jakmile se k tomu člověk dostane). Optimalizátor zatím umí pouze odříznout nepoužívaný kód, konstanty a importy ze sestavy. Také několik konstant se stejnou hodnotou je nahrazeno jednou. To je vše.

Mash jazyk

Základní pojem jazyka

Hlavní myšlenkou bylo vyvinout co nejfunkčnější a nejjednodušší jazyk. Myslím, že vývoj se se svým úkolem vyrovnává na jedničku.

Kódové bloky, procedury a funkce

Všechny konstrukce v jazyce se otevírají dvojtečkou. : a jsou uzavřeny provozovatelem konec.

Procedury a funkce jsou deklarovány jako proc a func. Argumenty jsou uvedeny v závorkách. Všechno je jako ve většině ostatních jazyků.

Operátor zpáteční můžete vrátit hodnotu z funkce, operátoru rozbít umožňuje opustit proceduru/funkci (pokud je mimo smyčky).

Ukázkový kód:

...

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

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

Podporované návrhy

  • Smyčky: pro..konec, zatímco..konec, do..konec
  • Podmínky: if.[else..]end, switch.[case..end..][else..]end
  • Metody: proc <name>():... end, func <name>():... end
  • Label & goto: <name>:, skok <name>
  • Výčty výčtů a konstantní pole.

Proměnné

Překladač je může určit automaticky, nebo pokud vývojář před jejich definováním napíše var.

Příklady kódu:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Podporovány jsou globální a lokální proměnné.

OOP

No, dostali jsme se k nejchutnějšímu tématu. Mash podporuje všechna objektově orientovaná programovací paradigmata. Tito. třídy, dědičnost, polymorfismus (včetně dynamického), dynamická automatická reflexe a introspekce (plná).

Bez dalších okolků je lepší uvést příklady kódu.

Jednoduchá třída a práce s ní:

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

Výstup: 30.

Dědičnost a polymorfismus:

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

Výstup: 60.

A co dynamický polymorfismus? Ano, to je odraz!:

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

Výstup: 60.

Nyní se na chvíli zadívejme na jednoduché hodnoty a třídy:

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

Bude výstup: pravda, pravda.

O operátorech přiřazení a explicitních ukazatelích

Operátor ?= se používá k přiřazení proměnné ukazatel na hodnotu v paměti.
Operátor = mění hodnotu v paměti pomocí ukazatele z proměnné.
A teď něco o explicitních ukazatelích. Přidal jsem je do jazyka, aby existovaly.
@<proměnná> — použije explicitní ukazatel na proměnnou.
?<proměnná> — získání proměnné pomocí ukazatele.
@= — přiřadit hodnotu proměnné pomocí explicitního ukazatele na ni.

Ukázkový kód:

uses <bf>
uses <crt>

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

Výstup: nějaké číslo, 10, 11.

Zkuste.[chytit..][konečně..]konec

Ukázkový kód:

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ány do budoucna

Pořád se dívám a dívám se na GraalVM & Truffle. Moje běhové prostředí nemá JIT kompilátor, takže z hlediska výkonu je momentálně konkurenceschopné pouze Pythonu. Doufám, že se mi podaří implementovat kompilaci JIT založenou na GraalVM nebo LLVM.

úložiště

Můžete si hrát s vývojem a sami sledovat projekt.

Site
Úložiště na GitHubu

Děkuji, že jste to dočetli až do konce.

Zdroj: www.habr.com

Přidat komentář