Nový programovací jazyk Mash

Niekoľko rokov som si vyskúšal vývoj vlastného programovacieho jazyka. Chcel som vytvoriť, podľa môjho názoru, čo najjednoduchší, plne funkčný a pohodlný jazyk.

V tomto článku chcem vyzdvihnúť hlavné etapy mojej práce a na začiatok popísať vytvorený koncept jazyka a jeho prvú implementáciu, na ktorej práve pracujem.

Vopred mi dovoľte povedať, že som celý projekt napísal vo Free Pascal, pretože... programy na ňom sa dajú skompilovať pre obrovské množstvo platforiem a samotný kompilátor produkuje veľmi optimalizované binárne súbory (zhromažďujem všetky komponenty projektu s príznakom O2).

Jazykový runtime

V prvom rade stojí za to hovoriť o virtuálnom stroji, ktorý som musel napísať, aby som v mojom jazyku spúšťal budúce aplikácie. Rozhodol som sa implementovať architektúru zásobníka, možno preto, že to bol najjednoduchší spôsob. Nenašiel som jediný normálny článok o tom, ako to urobiť v ruštine, takže po oboznámení sa s materiálom v anglickom jazyku som sa posadil k navrhovaniu a písaniu vlastného bicykla. Ďalej predstavím svoje „pokročilé“ nápady a vývoj v tejto veci.

Implementácia zásobníka

Je zrejmé, že v hornej časti VM je zásobník. V mojej implementácii to funguje v blokoch. V podstate ide o jednoduché pole ukazovateľov a premennú na uloženie indexu vrcholu zásobníka.
Keď sa inicializuje, vytvorí sa pole 256 prvkov. Ak sa na zásobník zatlačí viac ukazovateľov, jeho veľkosť sa zväčší o ďalších 256 prvkov. V súlade s tým sa pri odstraňovaní prvkov zo stohu upraví jeho veľkosť.

VM používa niekoľko zásobníkov:

  1. Hlavný zásobník.
  2. Zásobník na ukladanie návratových bodov.
  3. Stoh zberača odpadu.
  4. Skúste/chytte/nakoniec zablokujte zásobník manipulátora.

Konštanty a premenné

Tento je jednoduchý. Konštanty sú spracované v samostatnom malom kúsku kódu a sú dostupné v budúcich aplikáciách prostredníctvom statických adries. Premenné sú pole ukazovateľov určitej veľkosti, prístup k jej bunkám sa uskutočňuje indexom - t.j. statická adresa. Premenné je možné posunúť na vrch zásobníka alebo odtiaľ čítať. Vlastne, pretože Zatiaľ čo naše premenné v podstate ukladajú ukazovatele na hodnoty v pamäti VM, v jazyku dominuje práca s implicitnými ukazovateľmi.

Smetiar

V mojom VM je to poloautomatické. Tie. sám developer rozhoduje o tom, kedy zavolať zberateľa odpadu. Nefunguje to pomocou bežného počítadla ukazovateľov, ako v Pythone, Perl, Ruby, Lua atď. Realizuje sa prostredníctvom značkovacieho systému. Tie. keď má byť premennej priradená dočasná hodnota, do zásobníka zberača odpadu sa pridá ukazovateľ na túto hodnotu. V budúcnosti zberateľ rýchlo prebehne už pripravený zoznam ukazovateľov.

Manipulácia s pokusmi/úlovkom/konečne blokuje

Ako v každom modernom jazyku je dôležitou súčasťou spracovanie výnimiek. Jadro VM je zabalené v bloku try..catch, ktorý sa môže vrátiť k vykonávaniu kódu po zachytení výnimky tým, že vloží nejaké informácie o nej do zásobníka. V kóde aplikácie môžete definovať bloky kódu try/catch/finally, špecifikujúce vstupné body na catch (obslužný program výnimky) a nakoniec/koniec (koniec bloku).

Multithreading

Je podporovaný na úrovni VM. Je to jednoduché a pohodlné na používanie. Funguje bez prerušovacieho systému, takže kód by sa mal vykonávať vo viacerých vláknach niekoľkonásobne rýchlejšie, resp.

Externé knižnice pre VM

Bez toho sa nedá zaobísť. VM podporuje import, podobne ako je implementovaný v iných jazykoch. Časť kódu môžete napísať v mashu a časť kódu v rodných jazykoch a potom ich prepojiť do jedného.

Prekladač z vysokoúrovňového jazyka Mash do bajtkódu pre virtuálne počítače

Stredný jazyk

Aby som rýchlo napísal prekladač zo zložitého jazyka do kódu VM, najprv som vyvinul prechodný jazyk. Výsledkom bola strašná podívaná podobná assemblerovi, o ktorej tu nemá zmysel uvažovať. Poviem len toľko, že na tejto úrovni prekladač spracováva väčšinu konštánt a premenných, vypočítava ich statické adresy a adresy vstupných bodov.

Architektúra prekladateľa

Nevybral som si najlepšiu architektúru na realizáciu. Prekladač nevytvára strom kódu, ako to robia iní prekladatelia. Pozerá sa na začiatok štruktúry. Tie. ak analyzovaný kus kódu vyzerá ako „while :“, potom je zrejmé, že ide o konštrukciu cyklu while a treba ho spracovať ako konštrukciu cyklu while. Niečo ako zložitá skriňa vypínača.

Vďaka tomuto architektonickému riešeniu sa ukázalo, že prekladateľ nie je príliš rýchly. Ľahkosť jeho modifikácie sa však výrazne zvýšila. Doplnil som potrebné štruktúry rýchlejšie, ako mi káva stihla vychladnúť. Plná podpora OOP bola implementovaná za menej ako týždeň.

Optimalizácia kódu

Tu sa to, samozrejme, dalo implementovať lepšie (a aj sa zrealizuje, ale neskôr, keď sa k tomu človek dostane). Optimalizátor zatiaľ vie len odrezať nepoužívaný kód, konštanty a importy zo zostavy. Tiež niekoľko konštánt s rovnakou hodnotou je nahradených jednou. To je všetko.

Jazyková kaša

Základný pojem jazyka

Hlavnou myšlienkou bolo vyvinúť čo najfunkčnejší a najjednoduchší jazyk. Myslím si, že vývoj sa so svojou úlohou vyrovná s prehľadom.

Kódové bloky, procedúry a funkcie

Všetky konštrukcie v jazyku sú otvorené dvojbodkou. : a sú uzavreté prevádzkovateľom koniec.

Procedúry a funkcie sú deklarované ako proc a func. Argumenty sú uvedené v zátvorkách. Všetko je ako vo väčšine iných jazykov.

Operátor návrat môžete vrátiť hodnotu z funkcie, operátora rozbiť umožňuje ukončiť procedúru/funkciu (ak je mimo slučiek).

Priklad kód:

...

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

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

Podporované návrhy

  • Slučky: na..koniec, kým..koniec, do..koniec
  • Podmienky: ak.[else..]koniec, prepnite.[prípad..koniec..][inak..]koniec
  • Metódy: proc ():... end, func ():... end
  • Označenie a názov: :, skok
  • Enum enumerácie a konštantné polia.

Premenné

Prekladateľ ich môže určiť automaticky, alebo ak vývojár pred ich definovaním napíše var.

Príklady kódu:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Podporované sú globálne a lokálne premenné.

PLO

No a dostali sme sa k najchutnejšej téme. Mash podporuje všetky objektovo orientované programovacie paradigmy. Tie. triedy, dedičnosť, polymorfizmus (vrátane dynamického), dynamická automatická reflexia a introspekcia (úplná).

Bez ďalších okolkov je lepšie uviesť príklady kódu.

Jednoduchá trieda a práca s ňou:

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.

Dedičnosť a polymorfizmus:

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 čo dynamický polymorfizmus? Áno, toto 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.

Teraz sa na chvíľu zamyslime nad jednoduchými hodnotami a triedami:

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

Výstup: pravda, pravda.

O operátoroch priradenia a explicitných ukazovateľoch

Operátor ?= sa používa na priradenie ukazovateľa premennej na hodnotu v pamäti.
Operátor = mení hodnotu v pamäti pomocou ukazovateľa z premennej.
A teraz trochu o explicitných ukazovateľoch. Pridal som ich do jazyka, aby existovali.
@ — použije explicitný ukazovateľ na premennú.
? — získajte premennú pomocou ukazovateľa.
@= — priraďte hodnotu premennej explicitným ukazovateľom na ňu.

Priklad 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: nejaké číslo, 10, 11.

Skúste.[chytiť..][konečne..]koniec

Priklad 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 budúcnosti

Stále pozerám a pozerám na GraalVM & Truffle. Moje runtime prostredie nemá JIT kompilátor, takže z hľadiska výkonu je momentálne konkurencieschopné len s Pythonom. Dúfam, že sa mi podarí implementovať kompiláciu JIT založenú na GraalVM alebo LLVM.

Úložisko

Môžete sa hrať s vývojom a sami sledovať projekt.

webové stránky
Úložisko na GitHub

Ďakujem, že ste to prečítali až do konca.

Zdroj: hab.com

Pridať komentár