Gjuhë e re programimi Mash

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:

  1. Pirgu kryesor.
  2. Stivë për ruajtjen e pikave të kthimit.
  3. Pirg për mbledhjen e mbeturinave.
  4. 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.

Faqe
Depoja në GitHub

Faleminderit që lexuat deri në fund, nëse po.

Burimi: www.habr.com

Shto një koment