Nov programski jezik Mash

Več let sem se preizkušal v razvoju lastnega programskega jezika. Po mojem mnenju sem želel ustvariti najbolj preprost, popolnoma funkcionalen in priročen jezik.

V tem članku želim izpostaviti glavne faze svojega dela in za začetek opisati ustvarjen koncept jezika in njegovo prvo implementacijo, na kateri trenutno delam.

Že vnaprej naj povem, da sem celoten projekt napisal v Free Pascalu, ker... programi na njem se lahko prevedejo za ogromno platform, sam prevajalnik pa izdeluje zelo optimizirane binarne datoteke (vse komponente projekta zbiram z oznako O2).

Jezikovno izvajanje

Najprej je vredno govoriti o virtualnem stroju, ki sem ga moral napisati za izvajanje prihodnjih aplikacij v svojem jeziku. Morda sem se odločil implementirati arhitekturo skladov, ker je bil to najlažji način. Nisem našel niti enega normalnega članka o tem, kako to storiti v ruščini, zato sem se po seznanitvi z gradivom v angleškem jeziku lotil oblikovanja in pisanja lastnega kolesa. Nato bom predstavil svoje "napredne" ideje in razvoj v tej zadevi.

Implementacija sklada

Očitno je na vrhu VM sklad. V moji izvedbi deluje v blokih. V bistvu je to preprosto polje kazalcev in spremenljivka za shranjevanje indeksa vrha sklada.
Ko je inicializiran, se ustvari niz 256 elementov. Če je na sklad potisnenih več kazalcev, se njegova velikost poveča za naslednjih 256 elementov. V skladu s tem se pri odstranjevanju elementov iz sklada prilagodi njegova velikost.

VM uporablja več skladov:

  1. Glavni sklad.
  2. Sklad za shranjevanje povratnih točk.
  3. Zbiralnik smeti.
  4. Sklad obravnave poskusi/ulovi/končno blokiraj.

Konstante in spremenljivke

Ta je preprosta. Konstante se obravnavajo v ločenem majhnem delu kode in so na voljo v prihodnjih aplikacijah prek statičnih naslovov. Spremenljivke so niz kazalcev določene velikosti, dostop do njegovih celic se izvaja z indeksom - tj. statični naslov. Spremenljivke je mogoče potisniti na vrh sklada ali prebrati od tam. Pravzaprav, ker Medtem ko naše spremenljivke v bistvu shranjujejo kazalce na vrednosti v pomnilnik VM, v jeziku prevladuje delo z implicitnimi kazalci.

Zbiralec smeti

V mojem VM je polavtomatsko. Tisti. razvijalec se sam odloči, kdaj bo poklical pobiralca smeti. Ne deluje z običajnim števcem kazalcev, kot v Pythonu, Perlu, Rubyju, Lui itd. Izvaja se preko sistema markerjev. Tisti. ko naj bi spremenljivki dodelili začasno vrednost, je kazalec na to vrednost dodan v sklad zbiralnika smeti. V nadaljevanju zbiralec hitro preleti že pripravljen seznam kazalcev.

Ravnanje z bloki poskusi/ulovi/končno

Kot v vsakem sodobnem jeziku je obravnava izjem pomembna komponenta. Jedro VM je ovito v blok try..catch, ki se lahko vrne k izvajanju kode, potem ko ujame izjemo, tako da potisne nekaj informacij o njej na sklad. V kodi aplikacije lahko definirate bloke kode try/catch/finally, pri čemer določite vstopne točke pri catch (obravnavalnik izjem) in finally/end (konec bloka).

Večnitnost

Podprt je na ravni VM. Je preprost in priročen za uporabo. Deluje brez prekinitvenega sistema, zato je treba kodo izvajati v več nitih nekajkrat hitreje oz.

Zunanje knjižnice za VM

Brez tega ne gre. VM podpira uvoz, podobno kot je implementiran v drugih jezikih. Lahko napišete del kode v Mash in del kode v maternih jezikih, nato pa ju povežete v eno.

Prevajalnik iz visokonivojskega jezika Mash v bajtno kodo za VM

Vmesni jezik

Za hitro pisanje prevajalnika iz kompleksnega jezika v kodo VM sem najprej razvil vmesni jezik. Rezultat je bil monterju podoben grozen spektakel, ki ga tukaj nima posebnega smisla obravnavati. Rekel bom le, da na tej ravni prevajalnik obdela večino konstant in spremenljivk, izračuna njihove statične naslove in naslove vstopnih točk.

Arhitektura prevajalnika

Nisem izbral najboljše arhitekture za izvedbo. Prevajalec ne gradi kodnega drevesa, kot to počnejo drugi prevajalci. Pogleda na začetek strukture. Tisti. če je del kode, ki se razčlenjuje, videti kot "while :", potem je očitno, da je to konstrukcija zanke while in jo je treba obdelati kot konstrukcijo zanke while. Nekaj ​​podobnega zapletenemu stikalnemu ohišju.

Zahvaljujoč tej arhitekturni rešitvi se je izkazalo, da prevajalec ni zelo hiter. Vendar se je enostavnost njegovega spreminjanja znatno povečala. Dodal sem potrebne strukture hitreje, kot se je moja kava ohladila. Popolna OOP podpora je bila implementirana v manj kot enem tednu.

Optimizacija kode

Tu bi se seveda dalo bolje implementirati (in se bo izvajalo, a kasneje, ko bo komu šlo na pamet). Optimizator zaenkrat zna le izrezati neuporabljeno kodo, konstante in uvoze iz sklopa. Prav tako se več konstant z isto vrednostjo nadomesti z eno. To je vse.

Language Mash

Osnovni koncept jezika

Glavna ideja je bila razviti čim bolj funkcionalen in preprost jezik. Mislim, da se razvoj uspešno spopada s svojo nalogo.

Kodni bloki, postopki in funkcije

Vse konstrukcije v jeziku se odprejo z dvopičjem. : in jih operater zapre konec.

Postopki in funkcije so deklarirane kot proc oziroma func. Argumenti so navedeni v oklepajih. Vse je kot večina drugih jezikov.

Operater vrnitev lahko vrnete vrednost iz funkcije, operatorja odmor omogoča izhod iz procedure/funkcije (če je zunaj zank).

Vzorčna koda:

...

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

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

Podprti modeli

  • Zanke: for..end, while..end, until..end
  • Pogoji: if.[else..]end, switch..[case..end..][else..]end
  • Metode: proc ():... end, func ():... end
  • Oznaka & goto: :, jump
  • Enum naštevanja in konstantna polja.

Spremenljivke

Prevajalec jih lahko določi samodejno ali če razvijalec napiše var, preden jih definira.

Primeri kod:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Podprte so globalne in lokalne spremenljivke.

OOP

Pa smo prišli do najbolj slastne teme. Mash podpira vse paradigme objektno usmerjenega programiranja. Tisti. razredi, dedovanje, polimorfizem (vključno z dinamičnim), dinamična avtomatska refleksija in introspekcija (polna).

Brez nadaljnjega odlašanja je bolje, da navedete samo primere kode.

Preprost razred in delo z njim:

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

Izhod: 30.

Dedovanje in polimorfizem:

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

Izhod: 60.

Kaj pa dinamični polimorfizem? Ja, to je refleksija!:

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

Izhod: 60.

Zdaj pa si vzemimo trenutek in se poglobimo v preproste vrednosti in razrede:

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

Izhod bo: res, res.

O operatorjih dodeljevanja in eksplicitnih kazalcih

Operator ?= se uporablja za dodelitev spremenljivki kazalca na vrednost v pomnilniku.
Operator = spremeni vrednost v pomnilniku z uporabo kazalca iz spremenljivke.
In zdaj malo o eksplicitnih kazalcih. Dodal sem jih v jezik, da obstajajo.
@ — sprejme eksplicitni kazalec na spremenljivko.
? — pridobi spremenljivko s kazalcem.
@= — dodelite vrednost spremenljivki z eksplicitnim kazalcem nanjo.

Vzorčna koda:

uses <bf>
uses <crt>

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

Izpis bo: neko število, 10, 11.

Poskusi.[catch..][finally..]end

Vzorčna koda:

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

Načrti za prihodnost

Kar naprej gledam in gledam GraalVM & Truffle. Moje izvajalno okolje nima prevajalnika JIT, tako da je glede zmogljivosti trenutno konkurenčno samo Pythonu. Upam, da mi bo uspelo implementirati prevajanje JIT na osnovi GraalVM ali LLVM.

repozitorij

Lahko se igrate z razvojem in sami spremljate projekt.

Stran
Repozitorij na GitHubu

Hvala, ker ste prebrali do konca, če ste.

Vir: www.habr.com

Dodaj komentar