Për disa vite, kam provuar të zhvilloj gjuhën time të programimit. Doja të krijoja atë që besoja se ishte gjuha më e thjeshtë, më plotësisht funksionale dhe më e përshtatshme e mundshme.
Në këtë artikull, dua të nxjerr në pah fazat kryesore të punës sime dhe, për të filluar, të përshkruaj konceptin gjuhësor që krijova dhe zbatimin e tij të parë, mbi të cilin po punoj aktualisht.
Do ta them që në fillim se e shkrova të gjithë projektin në Free Pascal, pasi programet mund të kompilohen për një numër të madh platformash, dhe vetë kompiluesi prodhon skedarë binare shumë të optimizuar (unë i kompiloj të gjithë komponentët e projektit me flamurin O2).
Mjedisi i kohës së ekzekutimit të gjuhës
Së pari, duhet të flas për makinën virtuale që më është dashur të shkruaj për të ekzekutuar aplikacione të ardhshme në gjuhën time. Vendosa të zbatoj një arkitekturë të bazuar në stack, ndoshta sepse ishte më e lehtë. Nuk munda të gjej asnjë artikull të mirë se si ta bëj këtë në rusisht, kështu që pasi lexova disa materiale në gjuhën angleze, fillova punën për të projektuar dhe shkruar makinën time. Më poshtë, do të ndaj idetë dhe zhvillimet e mia më të fundit në këtë fushë.
Implementimi i stivës
Natyrisht, pirgu është në krye të makinës virtuale. Në implementimin tim, funksionon në blloqe. Në thelb, është një varg i thjeshtë treguesish dhe një variabël për ruajtjen e indeksit të majës së pirgut.
Kur inicializohet, krijohet një varg me 256 elementë. Nëse në grumbull shtohen më shumë tregues, madhësia e tij rritet me 256 elementë të tjerë. Prandaj, kur elementët hiqen nga grumbulli, madhësia e tij rregullohet.
VM përdor disa pirgje:
- Pirgu kryesor.
- Stivë për ruajtjen e pikave të kthimit.
- Pirg për mbledhjen e mbeturinave.
- Një grumbull trajtuesish "try/catch/final blocks".
Konstantet dhe variablat
Kjo është e thjeshtë. Konstantet trajtohen nga një pjesë e vogël dhe e veçantë e kodit dhe janë të arritshme në aplikacionet e ardhshme në adresat statike. Variablat janë vargje treguesish me një madhësi të caktuar, dhe qelizat e tyre aksesohen nga indeksi - d.m.th., një adresë statike. Variablat mund të shtyhen në krye të pirgut ose të lexohen prej andej. Në fakt, meqenëse variablat tona në thelb ruajnë tregues për vlera në memorien VM, gjuha përdor kryesisht tregues të nënkuptuar.
Mbledhës plehrash
Në makinën time virtuale, është gjysmë-automatike. Domethënë, zhvilluesi vendos se kur të thërrasë mbledhësin e mbeturinave. Nuk mbështetet në një numërues tradicional të pointer-ave, si në Python, Perl, Ruby, Lua e kështu me radhë. Është i implementuar nëpërmjet një sistemi shënuesish. Domethënë, kur një variabli i caktohet një vlerë e përkohshme, një pointer për atë vlerë shtohet në grumbullin e mbledhësit të mbeturinave. Mbledhësi pastaj kalon shpejt nëpër listën ekzistuese të pointer-ave.
Trajtimi i bllokimeve "try/catch/final"
Ashtu si në çdo gjuhë moderne, trajtimi i përjashtimeve është një komponent thelbësor. Bërthama e VM është e mbështjellë në një bllok try..catch, i cili mund të kthehet në ekzekutimin e kodit pasi të kapë një përjashtim, duke shtyrë disa informacione rreth tij në stick. Kodi i aplikacionit mund të përcaktojë blloqet try/catch/finally të kodit, duke specifikuar pikat e hyrjes në catch (trajtuesi i përjashtimeve) dhe finally/end (fundi i bllokut).
Multithreading
Mbështetet në nivelin e VM-së. Është i thjeshtë dhe i lehtë për t’u përdorur. Funksionon pa ndërprerje, kështu që kodi duhet të funksionojë disa herë më shpejt nëpër fije të shumëfishta.
Biblioteka të jashtme për makinat virtuale
Nuk ka zgjidhje tjetër. VM mbështet importet, njësoj si gjuhët e tjera. Mund të shkruash kod në Mash dhe pak në gjuhët amtare, pastaj t'i lidhësh ato së bashku.
Një përkthyes nga gjuha Mash e nivelit të lartë në bytecode për VM-të
Gjuhë e ndërmjetme
Për të shkruar shpejt një përkthyes nga një gjuhë komplekse në kodin VM, fillimisht zhvillova një gjuhë të ndërmjetme. Rezultati ishte një spektakël i tmerrshëm, i ngjashëm me asamblenë, që nuk ka kuptim këtu. Më lejoni të them vetëm se në këtë nivel, përkthyesi përpunon shumicën e konstanteve dhe variablave, duke llogaritur adresat e tyre statike dhe adresat e pikave të hyrjes.
Arkitektura e përkthyesit
Nuk zgjodha arkitekturën më të mirë për implementimin. Përkthyesi nuk ndërton një pemë kodi, siç bëjnë përkthyesit e tjerë. Ai shikon fillimin e konstruksionit. Pra, nëse kodi që po analizohet duket si "while <kusht>:", atëherë është padyshim një konstruksion i ciklit while dhe duhet të përpunohet si një konstruksion i ciklit while. Diçka si një rast ndërrimi kompleks.
Falë kësaj zgjidhjeje arkitekturore, kompiluesi nuk ishte veçanërisht i shpejtë. Megjithatë, lehtësia e modifikimit të tij u rrit në mënyrë eksponenciale. Shtova konstruktet e nevojshme më shpejt sesa mund të ftohej kafeja ime. Mbështetja e plotë OOP u zbatua në më pak se një javë.
Optimizimi i kodit
Sigurisht, kjo mund të ishte zbatuar më mirë (dhe do të zbatohet, por më vonë, kur të mësoj ta bëj). Për momentin, optimizuesi mund të heqë vetëm kodin e papërdorur, konstantet dhe importet nga ndërtimi. Gjithashtu, konstantet e shumta me të njëjtën vlerë zëvendësohen me një të vetme. Kaq.
Gjuha Mash
Koncepti bazë i gjuhës
Qëllimi kryesor ishte të zhvillohej një gjuhë sa më funksionale dhe e thjeshtë. Unë besoj se projekti e arrin qëllimin e tij me sukses të plotë.
Blloqe kodi, procedura dhe funksione
Të gjitha ndërtimet në gjuhë hapen me dy pika : dhe janë të mbyllura nga operatori fund.
Procedurat dhe funksionet deklarohen përkatësisht si proc dhe func. Argumentet renditen në kllapa. Kjo është e njëjtë me shumicën e gjuhëve të tjera.
Operatori kthim Mund të ktheni një vlerë nga një funksion, operator pushim ju lejon të dilni nga një procedurë/funksion (nëse është jashtë sytheve).
Shembull i kodit:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
Ndërtimet e mbështetura
- Unaza: për..fund, ndërsa..fund, deri në..fund
- Kushtet: nëse..[përndryshe..]fund, ndërro..[rast..fund..][përndryshe..]fund
- Metodat: proc <emri>():… fund, func <emri>():… fund
- Etiketë & shko te: <emri>:, kërce <emri>
- Numërimet enumeve dhe vargjet konstante.
variabla
Përkthyesi mund t'i përcaktojë ato automatikisht, ose nëse zhvilluesi shkruan var para përkufizimit të tyre.
Shembuj kodesh:
a ?= 10
b ?= a + 20
var a = 10, b = a + 20
Variablat globale dhe lokale mbështeten.
OOP
Epo, më në fund kemi arritur në temën më emocionuese. Gjuha Mash mbështet të gjitha paradigmat e programimit të orientuar nga objektet. Kjo do të thotë klasa, trashëgimi, polimorfizëm (përfshirë dinamik), reflektim automatik dinamik dhe introspeksion (i plotë).
Pa humbur kohë, do të jap vetëm disa shembuj kodi.
Një klasë e thjeshtë dhe si të punohet me të:
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
Prodhimi: 30.
Trashëgimia dhe polimorfizmi:
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
Prodhimi: 60.
Po polimorfizmi dinamik? Ky është reflektim!
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
Prodhimi: 60.
Tani le të ndalemi një moment për të analizuar vetveten për vlera dhe klasa të thjeshta:
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
Rezultatet: e vërtetë, e vërtetë.
Rreth operatorëve të caktimit dhe treguesve të qartë
Operatori ?= përdoret për t'i caktuar një tregues një vlere në memorie një variabli.
Operatori = ndryshon vlerën në memorie të treguar nga një ndryshore.
Dhe tani pak për pointerët eksplicitë. I shtova në gjuhë vetëm që të ekzistonin.
@<variabël> — merr një tregues të qartë për një variabël.
?<ndryshore> — merr një ndryshore me anë të treguesit.
@= — cakton një vlerë një variabli duke përdorur një tregues të qartë për të.
Shembull i kodit:
uses <bf>
uses <crt>
proc main():
var a = 10, b
b ?= @a
PrintLn(b)
b ?= ?b
PrintLn(b)
b++
PrintLn(a)
InputLn()
end
Do të japë rezultatin: një numër, 10, 11.
Provo..[kap..][më në fund..]fund
Shembull i kodit:
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
Planet për të ardhmen
Ende po i mbaj nën vëzhgim GraalVM dhe Truffle. Mjedisi im i kohës së ekzekutimit nuk ka një kompilues JIT, kështu që për sa i përket performancës, aktualisht është konkurrues vetëm me Python. Shpresoj të mund të implementoj kompilimin JIT duke përdorur GraalVM ose LLVM.
depo
Mund të luani me zhvillimet dhe ta ndiqni vetë projektin.
Faleminderit që lexuat deri në fund, nëse po.
Burimi: www.habr.com
