Új programozási nyelv a Mash

Több éven át próbálgattam a saját programozási nyelvem fejlesztését. Véleményem szerint a lehető legegyszerűbb, legteljesebben működőképes és kényelmesebb nyelvet akartam létrehozni.

Ebben a cikkben szeretném kiemelni munkám főbb állomásait, és mindenekelőtt ismertetni a nyelv megalkotott koncepcióját és annak első megvalósítását, amelyen jelenleg is dolgozom.

Előre hadd mondjam el, hogy az egész projektet Free Pascalban írtam, mert... a rajta lévő programok rengeteg platformra összeállíthatók, és maga a fordító is nagyon optimalizált binárisokat produkál (a projekt összes komponensét az O2 jelzővel gyűjtöm össze).

Nyelvi futásidő

Először is érdemes beszélni arról a virtuális gépről, amelyet meg kellett írnom, hogy a jövőbeni alkalmazásokat az én nyelvemen fusson. Talán azért döntöttem úgy, hogy egy verem architektúrát valósítok meg, mert ez volt a legegyszerűbb módja. Egyetlen normális cikket sem találtam arról, hogyan kell ezt csinálni oroszul, így miután megismerkedtem az angol nyelvű anyagokkal, leültem saját kerékpárom tervezésére és megírására. Legközelebb bemutatom az ezzel kapcsolatos „haladó” elképzeléseimet és fejlesztéseimet.

Stack megvalósítás

Nyilvánvalóan a virtuális gép tetején található a verem. Az én megvalósításomban blokkokban működik. Lényegében ez egy egyszerű mutató tömb és egy változó a verem tetejének indexének tárolására.
Az inicializáláskor egy 256 elemből álló tömb jön létre. Ha több mutatót tolunk a veremre, a mérete a következő 256 elemmel nő. Ennek megfelelően, amikor az elemeket eltávolítja a kötegből, a mérete be van állítva.

A virtuális gép több stacket használ:

  1. Fő verem.
  2. Verem a visszatérési pontok tárolására.
  3. Szemétgyűjtő kazal.
  4. Próbáld ki/elkapni/végül blokkolni a kezelő veremét.

Állandók és Változók

Ez egyszerű. A konstansokat egy külön kis kódrészletben kezelik, és statikus címeken keresztül elérhetők a jövőbeni alkalmazásokban. A változók egy bizonyos méretű mutatók tömbje, a celláihoz való hozzáférést index - azaz pl. statikus cím. A változók a verem tetejére tolhatók, vagy onnan olvashatók. Valójában azért Míg a változóink alapvetően a virtuális gép memóriájában tárolják az értékekre mutató mutatókat, a nyelvet az implicit mutatók használata uralja.

Szemetes

Az én virtuális gépemben félautomata. Azok. a fejlesztő maga dönti el, mikor hívja a szemétszállítót. Nem működik normál mutatószámlálóval, mint például a Python, Perl, Ruby, Lua stb. Marker rendszeren keresztül valósul meg. Azok. Ha egy változóhoz ideiglenes értéket kívánnak rendelni, akkor az erre az értékre mutató mutató hozzáadódik a szemétgyűjtő vereméhez. A jövőben a gyűjtő gyorsan átfutja a már elkészített mutatók listáját.

A try/catch/végre blokkok kezelése

Mint minden modern nyelvben, a kivételkezelés is fontos összetevő. A virtuálisgép magja egy try..catch blokkba van csomagolva, amely egy kivétel elkapása után visszatérhet a kódvégrehajtáshoz úgy, hogy a verembe helyez néhány információt. Az alkalmazáskódban megadhatja a try/catch/finlyly kódblokkokat, megadva a belépési pontokat a fogásnál (kivételkezelő) és végül/végén (a blokk vége).

Többszálú

VM szinten támogatott. Használata egyszerű és kényelmes. Megszakítási rendszer nélkül működik, így a kódot több szálban többször gyorsabban kell végrehajtani, ill.

Külső könyvtárak virtuális gépekhez

Enélkül nincs mód. A virtuális gép támogatja az importálást, hasonlóan a többi nyelven való megvalósításhoz. Leírhatja a kód egy részét Mash-ban, egy részét pedig anyanyelven, majd összekapcsolhatja őket egybe.

Fordító magas szintű Mash nyelvről bájtkódra virtuális gépekhez

Középfokú nyelv

Ahhoz, hogy egy bonyolult nyelvből gyorsan VM-kódba írjak fordítót, először kifejlesztettem egy köztes nyelvet. Az eredmény egy assembler-szerű iszonyatos látvány volt, amelyet itt semmi különösebb észbevételnek nincs értelme. Csak annyit mondok, hogy ezen a szinten a fordító feldolgozza a legtöbb állandót és változót, kiszámítja a statikus címeiket és a belépési pontok címét.

Fordító architektúra

Nem a legjobb architektúrát választottam a megvalósításhoz. A fordító nem épít fel kódfát, ahogy más fordítók teszik. A szerkezet elejére néz. Azok. ha az elemezni kívánt kódrészlet így néz ki, mint „while :”, akkor nyilvánvaló, hogy ez egy while ciklus, és while ciklus konstrukcióként kell feldolgozni. Valami olyan, mint egy összetett kapcsolótok.

Ennek az építészeti megoldásnak köszönhetően a fordító nem volt túl gyors. A módosítás egyszerűsége azonban jelentősen megnőtt. Gyorsabban tettem hozzá a szükséges szerkezeteket, mint ahogy a kávém kihűlhetett volna. A teljes OOP támogatás kevesebb mint egy hét alatt megtörtént.

Kód optimalizálás

Itt persze jobban is meg lehetett volna valósítani (és meg fog valósulni, de később, amint ráér az ember). Az optimalizáló eddig csak azt tudja, hogyan vágja le a nem használt kódot, konstansokat és importálást az összeállításból. Ezenkívül több azonos értékű állandót eggyel helyettesítünk. Ez minden.

Mash nyelv

A nyelv alapfogalma

A fő gondolat az volt, hogy a lehető legfunkcionálisabb és legegyszerűbb nyelvet dolgozzuk ki. Úgy gondolom, hogy a fejlesztés nagy lendülettel megbirkózik a feladatával.

Kódblokkok, eljárások és függvények

A nyelv összes konstrukciója kettősponttal van megnyitva. : és az üzemeltető zárja be végén.

Az eljárások és a funkciók proc és func néven vannak deklarálva. Az érvek zárójelben vannak felsorolva. Minden olyan, mint a legtöbb más nyelvben.

Operátor visszatérés függvényből, operátorból adhat vissza értéket szünet lehetővé teszi az eljárásból/funkcióból való kilépést (ha a hurkon kívül van).

Minta kód:

...

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

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

Támogatott tervek

  • Hurok: for..end, while..end, amíg...vége
  • Feltételek: ha..[else..]end, switch..[case..end..][else..]end
  • Módszerek: proc ():... end, func ():... end
  • Label & goto: :, ugrás
  • Felsorolások és konstans tömbök.

Változók

A fordító automatikusan meg tudja határozni őket, vagy ha a fejlesztő a definíció előtt írja a var parancsot.

Kódpéldák:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

A globális és helyi változók támogatottak.

Hopp

Nos, elérkeztünk a legfinomabb témához. A Mash támogatja az összes objektum-orientált programozási paradigmát. Azok. osztályok, öröklődés, polimorfizmus (beleértve a dinamikusat is), dinamikus automatikus reflexió és introspekció (teljes).

Minden további nélkül jobb, ha csak kódpéldákat adunk meg.

Egy egyszerű osztály és a vele való munka:

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

Kimenet: 30.

Öröklődés és polimorfizmus:

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

Kimenet: 60.

Mi a helyzet a dinamikus polimorfizmussal? Igen, ez tükröződés!:

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

Kimenet: 60.

Most szánjunk egy pillanatot az egyszerű értékek és osztályok önvizsgálatára:

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

Kimenet: igaz, igaz.

A hozzárendelési operátorokról és az explicit mutatókról

Az ?= operátor arra szolgál, hogy egy változót mutatóként rendeljen egy értékhez a memóriában.
Az = operátor egy változóból származó mutató segítségével megváltoztat egy értéket a memóriában.
És most egy kicsit az explicit mutatókról. Hozzáadtam őket a nyelvhez, hogy létezzenek.
@ — egy változóra mutató explicit mutató.
? — változó lekérése mutató segítségével.
@= — értéket rendel egy változóhoz egy explicit mutató segítségével.

Minta 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

Kimenet: néhány szám, 10, 11.

Próbáld meg.[catch..][végül..]vége

Minta 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

Tervek a jövőre

Folyamatosan nézem és nézem a GraalVM & Truffle-t. A futtatókörnyezetemben nincs JIT fordító, így teljesítmény szempontjából jelenleg csak a Pythonnal versenyképes. Remélem sikerül megvalósítani a GraalVM vagy LLVM alapú JIT összeállítást.

adattár

Játszhatsz a fejlesztésekkel, és magad is követheted a projektet.

Telek
Adattár a GitHubon

Köszönöm, hogy a végéig elolvastad, ha igen.

Forrás: will.com

Hozzászólás