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:
- Pääpino.
- Pino palautuspisteiden tallentamiseen.
- Roskakeräyspino.
- 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.
Kiitos, että luit loppuun, jos luit.
Lähde: will.com