Uusi ohjelmointikieli Mash

Olen yrittänyt usean vuoden ajan kehittää omaa ohjelmointikieltäni. Halusin luoda mielestäni yksinkertaisimman, täysin toimivan ja kätevän kielen.

Tässä artikkelissa haluan korostaa työni päävaiheita ja aluksi kuvailla luotua kielen konseptia ja sen ensimmäistä toteutusta, jonka parissa työskentelen parhaillaan.

Sanon etukäteen, että kirjoitin koko projektin Free Pascalilla, koska... siinä olevat ohjelmat voidaan koota valtavalle määrälle alustoja, ja kääntäjä itse tuottaa erittäin optimoituja binaaritiedostoja (kerään kaikki projektin komponentit O2-lipulla).

Kielen suoritusaika

Ensinnäkin on syytä puhua virtuaalikoneesta, joka minun oli kirjoitettava suorittaakseni tulevia sovelluksia kielelläni. Päätin ottaa käyttöön pinoarkkitehtuurin, ehkä koska se oli helpoin tapa. En löytänyt ainuttakaan normaalia artikkelia tämän tekemisestä venäjäksi, joten tutustuttuani englanninkieliseen materiaaliin ryhdyin suunnittelemaan ja kirjoittamaan omaa polkupyörääni. Seuraavaksi esitän "edistyneet" ideani ja kehitystyöni tässä asiassa.

Pinon toteutus

Ilmeisesti VM:n yläosassa on pino. Toteutuksessani se toimii lohkoissa. Pohjimmiltaan tämä on yksinkertainen joukko osoittimia ja muuttuja pinon yläosan indeksin tallentamiseen.
Kun se alustetaan, luodaan 256 elementin joukko. Jos pinoon työnnetään lisää osoittimia, sen koko kasvaa seuraavalla 256 elementillä. Vastaavasti, kun elementtejä poistetaan pinosta, sen kokoa säädetään.

VM käyttää useita pinoja:

  1. Pääpino.
  2. Pino palautuspisteiden tallentamiseen.
  3. Roskakeräyspino.
  4. Kokeile/saappaa/lope lopuksi käsittelijäpino.

Vakiot ja muuttujat

Tämä on yksinkertainen. Vakiot käsitellään erillisessä pienessä koodinpätkässä, ja ne ovat saatavilla tulevissa sovelluksissa staattisten osoitteiden kautta. Muuttujat ovat joukko tietynkokoisia osoittimia, joiden soluihin pääsy tapahtuu indeksillä - ts. staattinen osoite. Muuttujat voidaan työntää pinon yläosaan tai lukea sieltä. Itse asiassa, koska Vaikka muuttujamme olennaisesti tallentavat osoittimia arvoihin VM-muistiin, kieltä hallitsee implisiittisten osoittimien käyttäminen.

Roskankerääjä

VM:ssäni se on puoliautomaattinen. Nuo. kehittäjä itse päättää milloin soittaa roskakoriin. Se ei toimi tavallisella osoitinlaskurilla, kuten Pythonissa, Perlissä, Rubyssa, Luassa jne. Se toteutetaan merkintäjärjestelmän kautta. Nuo. kun muuttujalle on tarkoitus antaa väliaikainen arvo, osoitin tähän arvoon lisätään roskakeräimen pinoon. Jatkossa keräilijä käy nopeasti läpi jo valmistetun osoitinluettelon.

Käsittele try/catch/lopult-lohkot

Kuten missä tahansa nykykielessä, poikkeusten käsittely on tärkeä osa. VM-ydin on kääritty try..catch-lohkoon, joka voi palata koodin suorittamiseen havaittuaan poikkeuksen työntämällä sitä koskevia tietoja pinoon. Sovelluskoodissa voit määrittää koodin try/catch/finlyly-lohkot määrittämällä sisääntulopisteet catchissä (poikkeuskäsittelijä) ja lopuksi/lopussa (lohkon loppu).

Monisäikeinen

Sitä tuetaan VM-tasolla. Se on yksinkertainen ja kätevä käyttää. Se toimii ilman keskeytysjärjestelmää, joten koodi tulee suorittaa useissa säikeissä useita kertoja nopeammin.

Ulkoiset kirjastot virtuaalisille koneille

Ilman tätä ei tule toimeen. VM tukee tuontia samalla tavalla kuin se on toteutettu muilla kielillä. Voit kirjoittaa osan koodista Mash-kielellä ja osan koodista äidinkielellä ja sitten linkittää ne yhdeksi.

Kääntäjä korkean tason Mash-kielestä tavukoodiin virtuaalikoneille

Keskitason kieli

Jotta voisin nopeasti kirjoittaa kääntäjän monimutkaisesta kielestä VM-koodiksi, kehitin ensin välikielen. Tuloksena oli assembler-tyyppinen kauhea spektaakkeli, jota tässä ei ole erityistä syytä pohtia. Sanon vain, että tällä tasolla kääntäjä käsittelee useimmat vakiot ja muuttujat, laskee niiden staattiset osoitteet ja tulopisteiden osoitteet.

Kääntäjän arkkitehtuuri

En valinnut toteutukseen parasta arkkitehtuuria. Kääntäjä ei rakenna koodipuuta, kuten muut kääntäjät tekevät. Hän katsoo rakenteen alkua. Nuo. jos jäsennettävä koodinpätkä näyttää tältä "while <ehto>:", niin on selvää, että tämä on while-silmukkarakenne ja se on käsiteltävä while-silmukkakonstruktina. Jotain monimutkaista kytkinkoteloa.

Tämän arkkitehtonisen ratkaisun ansiosta kääntäjä ei osoittautunut kovin nopeaksi. Sen muuttamisen helppous on kuitenkin lisääntynyt huomattavasti. Lisäsin tarvittavat rakenteet nopeammin kuin kahvini ehti jäähtyä. Täysi OOP-tuki otettiin käyttöön alle viikossa.

Koodin optimointi

Täällä se olisi tietysti voitu toteuttaa paremmin (ja toteutetaan, mutta myöhemmin, heti kun siihen pääsee). Toistaiseksi optimoija osaa vain katkaista käyttämättömän koodin, vakiot ja tuonnin kokoonpanosta. Myös useita samanarvoisia vakioita korvataan yhdellä. Siinä kaikki.

Mash kieli

Kielen peruskäsite

Pääideana oli kehittää mahdollisimman toimiva ja yksinkertainen kieli. Uskon, että kehitys selviää tehtävästään räjähdysmäisesti.

Koodilohkot, menettelyt ja toiminnot

Kaikki kielen konstruktit avataan kaksoispisteellä. : ja operaattori sulkee ne loppu.

Proseduurit ja toiminnot on ilmoitettu pro- ja funktiona, vastaavasti. Argumentit on lueteltu suluissa. Kaikki on kuten useimmat muut kielet.

Operaattori palata voit palauttaa arvon funktiosta, operaattorista rikkoa voit poistua proseduurista/funktiosta (jos se on silmukoiden ulkopuolella).

Koodiesimerkki:

...

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

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

Tuetut mallit

  • Silmukat: for..lop, while..end, till..end
  • Ehdot: jos..[else..]lopetta, vaihda..[case..end..][else..]lopu
  • Menetelmät: proc <nimi>():... end, func <nimi>():... end
  • Label & goto: <nimi>:, hyppää <nimi>
  • Luetteloluettelot ja vakiotaulukot.

muuttujat

Kääntäjä voi määrittää ne automaattisesti, tai jos kehittäjä kirjoittaa var ennen niiden määrittelyä.

Esimerkkejä koodista:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Globaalit ja paikalliset muuttujat ovat tuettuja.

OOP

No, olemme tulleet herkullisimpaan aiheeseen. Mash tukee kaikkia olio-ohjelmointiparadigmoja. Nuo. luokat, perinnöllisyys, polymorfismi (mukaan lukien dynaaminen), dynaaminen automaattinen heijastus ja itsetutkiskelu (täysi).

Puhumatta enempää, on parempi antaa vain koodiesimerkkejä.

Yksinkertainen luokka ja sen kanssa työskentely:

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

Tulos: 30.

Perinnöllisyys ja polymorfismi:

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

Tulos: 60.

Entä dynaaminen polymorfismi? Kyllä, tämä on heijastus!:

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

Tulos: 60.

Otetaan nyt hetki yksinkertaisten arvojen ja luokkien tutkimiseen:

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

Tulostaa: tosi, tosi.

Tietoja tehtäväoperaattoreista ja eksplisiittisistä osoittimista

?=-operaattoria käytetään määrittämään muuttuja osoitin muistissa olevalle arvolle.
=-operaattori muuttaa arvon muistissa muuttujan osoittimen avulla.
Ja nyt vähän selkeistä viitteistä. Lisäsin ne kieleen, jotta ne ovat olemassa.
@<muuttuja> — vie eksplisiittinen osoitin muuttujaan.
?<muuttuja> — saada muuttuja osoittimella.
@= — määritä muuttujalle arvo eksplisiittisellä osoittimella.

Koodiesimerkki:

uses <bf>
uses <crt>

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

Tulostaa: jokin numero, 10, 11.

Kokeile..[catch..][finally..]loppuu

Koodiesimerkki:

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

Tulevaisuuden suunnitelmat

Katson jatkuvasti GraalVM & Trufflea. Ajonaikaisessa ympäristössäni ei ole JIT-kääntäjää, joten suorituskyvyltään se on tällä hetkellä kilpailukykyinen vain Pythonin kanssa. Toivon, että pystyn toteuttamaan GraalVM- tai LLVM-pohjaisen JIT-kokoelman.

arkisto

Voit leikkiä kehityksen kanssa ja seurata projektia itse.

Paikka
Arkisto GitHubissa

Kiitos, että luit loppuun, jos luit.

Lähde: will.com

Lisää kommentti