Gjuha e re programimit Mash.

Për disa vjet kam provuar të zhvilloj gjuhën time të programimit. Kisha dëshirë të krijoja një gjuhë që do të ishte sa më e thjeshtë, funksionale dhe e përshtatshme.

Në këtë artikull dëshiroj të përshkruaj hapat kryesorë të punës sime dhe për t'u nisur, të përshkruaj konceptin e krijuar të gjuhës dhe implementimin e saj të parë mbi të cilin aktualisht po punoj.

Më lejoni të them paraprakisht se kam shkruar të gjithë projektin në Free Pascal, sepse programet në të mund të përdoren në shumë platforma, dhe vetë kompajleri jep binarë mjaft të optimizuar (krijoj të gjitha përbërësit e projektit me flamurin O2).

Mediat e ekzekutimit të gjuhës

E para që duhet të flas është për makinë virtuale, që më është dashur ta shkruaj për të ekzekutuar aplikacionet e ardhshme në gjuhën time. Kam vendosur të realizoj një arkitekturë me stekë, ndoshta sepse ishte më e lehtë. Nuk kam gjetur asnjë artikull normal se si ta bëj këtë në rusisht, e prandaj, pas një njohjeje me materialin anglisht, fillova të dizajnoja dhe shkruaja biçikletën time. Më poshtë do të paraqes idetë dhe zhvillimet e mia "të avancuara" në këtë fushë.

Implementimi i stekës

Mund të sigurohet që adoption e VM ka një stek. Në implementimin tim, ai funksionon me blloqe. Në thelb, ky është një array i thjeshtë i treguesve dhe një variabël për ruajtjen e indeksit të majës së stekut.
Në inicializimin e tij, krijohet një array me 256 elemente. Nëse në stek hidhen më shumë tregues, madhësia e tij rritet me 256 elemente të tjera. Po ashtu, kur elementet hiqen nga steku, madhësia e tij rregullohet.

Në VM përdoren disa steke:

  1. Steku kryesor.
  2. Steku për ruajtjen e pikave të kthimit.
  3. Steku i mbledhësit të plehrave.
  4. Steku i trajtuesit të bllokimeve try/catch/finally.

Konsistat dhe variablat

Me këtë gjithçka është e thjeshtë. Konstantët përpunohen me një copë të vogël kodi dhe janë të aksesueshme në aplikacione në të ardhmen me adresa statike. Variablat janë një array treguesish me një madhësi të caktuar, ku qasje në selitë e tij bëhet përmes indeksit — dmth. adresës statike. Variablat mund të vendosen në majën e stekut ose të lexohen prej tij. Në fakt, pasi variablat tona thelbësisht mbajnë tregues në vlera në memorien e VM, gjuha dominon në punën me tregues implicit.

Grumbulluesi i mbeturinave

Në VM-në time është gjysmëautomatike. Domethënë, zhvilluesi vetë vendos kur është e nevojshme të thërrasë mbledhësin e mbetjeve. Ai nuk funksionon sipas treguesve të zakonshëm të treguesve, siç është në Python, Perl, Ruby, Lua dhe të tjerë. Ai realizohet përmes një sistemi treguesish. Domethënë, kur supozohet se një variabël atribuojnë një vlerë përkohshme — treguesi për këtë vlerë shtohet në grumbullin e mbledhësit të mbetjeve. Më pas, mbledhësi kalon shpejt përmes listës së përgatitur të treguesve.

Trajtimi i blloqeve try/catch/finally

Siç ndodh në çdo gjuhë moderne, trajtimi i përjashtimeve është një përbërës i rëndësishëm. Në thelbin e VM-së, është i mbështjellë në një bllok try..catch, i cili mund të kthehet në ekzekutimin e kodit, pas kapjes së një përjashtimi, duke vendosur në grumbull pak informacion në lidhje me të. Në kodin e aplikacioneve, mund të vendosen blloqet try/catch/finally të kodit, duke treguar pikët e hyrjes në catch (trajtohet përjashtimin) dhe në finally/end (fundin e bllokut).

Multithreading

Ajo mbështetet në nivelin e VM-së. Është e thjeshtë dhe e lehtë për t’u përdorur. Funksionon pa sistemin e ndërprerjeve, kështu që kodi duhet të ekzekutohet në disa thjesht dhe disa herë më shpejt përkatësisht.

Biblioteka të jashtme për VM-në

Ky është një element i nevojshëm. VM mbështet importet, ashtu siç është realizuar në gjuhë të tjera. Mund të shkruani pjesën e kodit në Mash dhe pjesën tjetër në gjuhë natyrore, duke i lidhur ato në një tërësi.

Përkthyesi nga një gjuhë të nivelit të lartë Mash në bytecode për VM

Gjuha ndërmjetëse

Për të shkruar shpejt një përkthyes nga një gjuhë të ndërlikuar në kod për VM, fillimisht kam zhvilluar një gjuhë ndërmjetëse. Kjo doli të ishte një pamje e tmerrshme, si asamble. Nuk ka kuptim ta shqyrtojmë këtu. Thjesht do të them se në këtë nivel, përkthyesi përpunon shumicën e konstanteve, variablave, llogarit adresat e tyre statike dhe adresat e pikave të hyrjes.

Arkitektura e përkthyesit

Zgjodha një arkitekturë të mos shumë të mirë për realizimin. Përkthyesi nuk ndalon ndërtimin e një peme kodi, siç i takon përkthyesve të tjerë. Ai shikon fillimin e konstrukcionit. Domethënë, nëse pjesa e kodit e shqyrtuar ka formën "while ", është evidente se kjo është një konstrukcion while dhe duhet ta përpunojë si një konstrukcion while. Diku si një switch-case të ndërlikuar.

Falë këtyre zgjidhjeve arkitektonike, përkthyesi rezultoi të jetë i shpejtë, megjithatë, thjeshtësia e modifikimeve të tij u rrit shumë. Konstrukcionet e nevojshme i shtoja më shpejt se sa mund të ftohej kafja ime. Mbështetje e plotë për OOP u realizua për më pak se një javë.

Optimizimi i kodit

Sigurisht, do të kishte mundur të realizohej dhe më mirë (dhe do të realizohet, por më vonë, kur të kem mundësi). Aktualisht, optimizuesi di vetëm të shqiptojë kodin e papërdorur, konstantet dhe importet nga ndërtimi. Gjithashtu, disa konstante me vlera të njëjta zëvendësohen me një të vetme. Kështu që, kjo është ajo.

Gjuha Mash

Koncepsioni kryesor i gjuhës

Idetë kryesore ishin të zhvillohej një gjuhë sa më funksionale dhe e thjeshtë. Besoj se zhvillimi po e kryen detyrën e tij me ndihmën e përkryer.

Blloqet e kodit, procedurat dhe funksionet

Të gjitha konstrukucionet në gjuhë fillojnë me dy pika : dhe përfundojnë me operatorin end.

Procedurat dhe funksionet shpallën si proc dhe func përkatësisht. Brenda prapakohësit listohen argumentet. Të gjitha si në shumicën e gjuhëve të tjera.

Me operatorin kthehu mund të kthehet një vlerë nga funksioni, operatori break lejon daljen nga procedura/funksioni (nëse ndodhet jashtë cikleve).

Shembulli i kodit:

... 

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

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

Strukturat e mbështetura

  • Ciklet: for..end, while..end, until..end
  • Kushtet: if..[else..]end, switch..[case..end..][else..]end
  • Metodat: proc <emri>():… end, func <emri>():… end
  • Label & goto: <emri>:, jump <emri>
  • Enum për enumerimet dhe tabulat e konstantave.

Variablat

Translatori mund t'i përcaktojë automatikisht, ose nëse zhvilluesi shkruan var para përcaktimit të tyre.

Shembujt e kodit:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Përkrahën variablat globale dhe lokale.

OOP

Ja kemi arritur te tema më e mirë. Në gjuhën Mash përkrahen të gjitha paradigmave të programimit objekt-orientuar. Kështu, klasat, trashëgimet, polimorfizmi (përfshirë dinamik), refleksioni dinamik automatik dhe introspeksioni (i plotë).

Pa fjalë të tepërta, është më mirë të jap shembujt e kodit.

Një klasë e thjeshtë dhe puna 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

Do të nxjerrë: 30.

Trashëgimia dhe polimorfizmi:

përdor <bf>
përdor <crt>

klasa 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

klasa 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

Do të printojë: 60.

Çfarë ka të bëjë me polimorfizmin e dinamik? Po kjo është refleksion!:

përdor <bf>
përdor <crt>

klasa 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

klasa 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

Do të printojë: 60.

Tani, le të kushtojmë një minutë introspeksionit për vlerat e thjeshta dhe klasat:

përdor <bf>
përdor <crt>

klasa MyClass:
  var a, b
end

proc main():
  x ?= new MyClass
  println(BoolToStr(x->type == MyClass))
  x->rem()
  println(BoolToStr(typeof(3.14) == typeReal))
end

Do të printojë: true, true.

Për operatorët e caktimit dhe treguesit e qartë

Operatori ?= shërben për t'i dhënë një variabli një tregues për një vlerë në memorje.
Operatori = ndryshon vlerën në memorje përmes treguesit nga variabli.
Dhe tani pak për treguesit e qartë. I kam shtuar në gjuhë që ata të ishin.
@<variabli> — merrni një tregues të qartë për variablën.
?<переменная> — получить переменную по указателю.
@= — caktë vlerën e variablit përmes një treguesi të qartë ndaj saj.

Shembulli i kodit:

përdor <bf>
përdor <crt>

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

Do të shfaqë: një numër, 10, 11.

Provo..[kap..][më në fund..]end

Shembulli i kodit:

përdor <bf>
përdor <crt>

proc main():
  println("Fillimi")
  provo:
    println("Po përpiqem të bëj diçka...")
    a ?= 10 / 0
  kap:
    println(getError())
  më në fund:
    println("Më në fund")
  end
  println("Fund")
  inputln()
end

Planet për të ardhmen

Po shikoj me vëmend GraalVM & Truffle. Mjedisi im i ekzekutimit nuk ka një përkthyes JIT, kështu që në aspektin e performancës, deri tani mund të konkurojë vetëm me Pythonin. Shpresoj se do të arrij ta realizoj përkthimin JIT mbi bazën e GraalVM ose LLVM.

Repozitori

Mund të eksperimentoni me zhvillimet dhe të ndiqni projektin vetë.

Faqja
Repositori në GitHub

Faleminderit që e lexuat deri në fund, nëse e bëtë këtë.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster