Několik let jsem zkoušel vyvíjet svůj vlastní programovací jazyk. Chtěl jsem vytvořit, podle mého názoru, co nejjednodušší, plně funkční a pohodlný jazyk.
V tomto článku chci vyzdvihnout hlavní etapy mé práce a pro začátek popsat vytvořený koncept jazyka a jeho první implementaci, na které právě pracuji.
Předem říkám, že jsem celý projekt napsal ve Free Pascalu, protože... programy na něm lze sestavit pro obrovské množství platforem a samotný kompilátor produkuje velmi optimalizované binární soubory (sbírám všechny komponenty projektu s příznakem O2).
Jazykový běh
Za prvé, stojí za to mluvit o virtuálním stroji, který jsem musel napsat, abych spouštěl budoucí aplikace v mém jazyce. Rozhodl jsem se implementovat architekturu zásobníku, možná proto, že to byl nejjednodušší způsob. Nenašel jsem jediný normální článek o tom, jak to udělat v ruštině, takže poté, co jsem se seznámil s anglicky psaným materiálem, sedl jsem k navrhování a psaní vlastního kola. Dále představím své „pokročilé“ nápady a vývoj v této věci.
Implementace zásobníku
Je zřejmé, že na vrcholu virtuálního počítače je zásobník. V mé implementaci to funguje v blocích. V podstatě se jedná o jednoduché pole ukazatelů a proměnnou pro uložení indexu vrcholu zásobníku.
Po inicializaci se vytvoří pole 256 prvků. Pokud je na zásobník zatlačeno více ukazatelů, jeho velikost se zvětší o dalších 256 prvků. Podle toho se při vyjímání prvků ze stohu upraví jeho velikost.
VM používá několik zásobníků:
- Hlavní zásobník.
- Zásobník pro ukládání návratových bodů.
- Stoh sběrače odpadků.
- Zkuste/chytit/konečně zablokovat zásobník obslužného programu.
Konstanty a proměnné
Tento je jednoduchý. Konstanty jsou zpracovány v samostatném malém kousku kódu a jsou dostupné v budoucích aplikacích prostřednictvím statických adres. Proměnné jsou pole ukazatelů určité velikosti, přístup k jeho buňkám se provádí indexem - tzn. statická adresa. Proměnné lze přesunout na vrchol zásobníku nebo je odtud číst. Vlastně, protože Zatímco naše proměnné v podstatě ukládají ukazatele na hodnoty v paměti VM, jazyku dominuje práce s implicitními ukazateli.
Popelář
V mém VM je to poloautomatické. Tito. vývojář sám rozhodne, kdy zavolat popeláře. Nefunguje to pomocí běžného počítadla ukazatelů, jako v Pythonu, Perlu, Ruby, Lua atd. Je implementován prostřednictvím značkovacího systému. Tito. když má být proměnné přiřazena dočasná hodnota, přidá se ukazatel na tuto hodnotu do zásobníku odpadků. V budoucnu sběratel rychle projde již připravený seznam ukazatelů.
Manipulace s bloky pokus/chytit/konečně
Stejně jako v každém moderním jazyce je důležitou součástí zpracování výjimek. Jádro virtuálního počítače je zabaleno do bloku try..catch, který se může vrátit ke spuštění kódu po zachycení výjimky tím, že o ní vloží nějaké informace do zásobníku. V kódu aplikace můžete definovat bloky kódu try/catch/finally, specifikující vstupní body na catch (obslužný program výjimky) a nakonec/konec (konec bloku).
Vícevláknové zpracování
Je podporován na úrovni VM. Je to jednoduché a pohodlné použití. Funguje bez přerušovacího systému, takže kód by měl být vykonáván v několika vláknech několikrát rychleji, resp.
Externí knihovny pro virtuální počítače
Bez toho se nelze obejít. VM podporuje importy, podobně jako je implementován v jiných jazycích. Část kódu můžete napsat v Mash a část kódu v nativních jazycích a poté je propojit do jednoho.
Překladač z vysokoúrovňového jazyka Mash do bytecode pro virtuální počítače
Středně pokročilý jazyk
Abych rychle napsal překladač ze složitého jazyka do kódu VM, nejprve jsem vyvinul přechodný jazyk. Výsledkem byla strašlivá podívaná podobná assembleru, kterou zde nemá smysl uvažovat. Řeknu jen, že na této úrovni překladač zpracovává většinu konstant a proměnných, počítá jejich statické adresy a adresy vstupních bodů.
Architektura překladače
Nezvolil jsem nejlepší architekturu pro implementaci. Překladač nevytváří strom kódu, jako to dělají ostatní překladatelé. Podívá se na začátek struktury. Tito. pokud analyzovaný kus kódu vypadá jako „while <condition>:“, pak je zřejmé, že se jedná o konstrukci cyklu while a musí být zpracován jako konstrukce cyklu while. Něco jako složitá spínací skříň.
Díky tomuto architektonickému řešení se ukázalo, že překladač není příliš rychlý. Výrazně se však zvýšila snadnost jeho modifikace. Potřebné struktury jsem přidal rychleji, než mi káva stačila vychladnout. Plná podpora OOP byla implementována za méně než týden.
Optimalizace kódu
Tady to samozřejmě mohlo být implementováno lépe (a bude implementováno, ale později, jakmile se k tomu člověk dostane). Optimalizátor zatím umí pouze odříznout nepoužívaný kód, konstanty a importy ze sestavy. Také několik konstant se stejnou hodnotou je nahrazeno jednou. To je vše.
Mash jazyk
Základní pojem jazyka
Hlavní myšlenkou bylo vyvinout co nejfunkčnější a nejjednodušší jazyk. Myslím, že vývoj se se svým úkolem vyrovnává na jedničku.
Kódové bloky, procedury a funkce
Všechny konstrukce v jazyce se otevírají dvojtečkou. : a jsou uzavřeny provozovatelem konec.
Procedury a funkce jsou deklarovány jako proc a func. Argumenty jsou uvedeny v závorkách. Všechno je jako ve většině ostatních jazyků.
Operátor zpáteční můžete vrátit hodnotu z funkce, operátoru rozbít umožňuje opustit proceduru/funkci (pokud je mimo smyčky).
Ukázkový kód:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Podporované návrhy
- Smyčky: pro..konec, zatímco..konec, do..konec
- Podmínky: if.[else..]end, switch.[case..end..][else..]end
- Metody: proc <name>():... end, func <name>():... end
- Label & goto: <name>:, skok <name>
- Výčty výčtů a konstantní pole.
Proměnné
Překladač je může určit automaticky, nebo pokud vývojář před jejich definováním napíše var.
Příklady kódu:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Podporovány jsou globální a lokální proměnné.
OOP
No, dostali jsme se k nejchutnějšímu tématu. Mash podporuje všechna objektově orientovaná programovací paradigmata. Tito. třídy, dědičnost, polymorfismus (včetně dynamického), dynamická automatická reflexe a introspekce (plná).
Bez dalších okolků je lepší uvést příklady kódu.
Jednoduchá třída a práce s ní:
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.
Dědičnost a polymorfismus:
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 co dynamický polymorfismus? Ano, to 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.
Nyní se na chvíli zadívejme na jednoduché hodnoty a třídy:
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
Bude výstup: pravda, pravda.
O operátorech přiřazení a explicitních ukazatelích
Operátor ?= se používá k přiřazení proměnné ukazatel na hodnotu v paměti.
Operátor = mění hodnotu v paměti pomocí ukazatele z proměnné.
A teď něco o explicitních ukazatelích. Přidal jsem je do jazyka, aby existovaly.
@<proměnná> — použije explicitní ukazatel na proměnnou.
?<proměnná> — získání proměnné pomocí ukazatele.
@= — přiřadit hodnotu proměnné pomocí explicitního ukazatele na ni.
Ukázkový 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: nějaké číslo, 10, 11.
Zkuste.[chytit..][konečně..]konec
Ukázkový 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 budoucna
Pořád se dívám a dívám se na GraalVM & Truffle. Moje běhové prostředí nemá JIT kompilátor, takže z hlediska výkonu je momentálně konkurenceschopné pouze Pythonu. Doufám, že se mi podaří implementovat kompilaci JIT založenou na GraalVM nebo LLVM.
úložiště
Můžete si hrát s vývojem a sami sledovat projekt.
Děkuji, že jste to dočetli až do konce.
Zdroj: www.habr.com