Gjuhë e re programimi Mash

Për disa vite provova dorën time në zhvillimin e gjuhës time të programimit. Doja të krijoja, për mendimin tim, gjuhën më të thjeshtë, plotësisht funksionale dhe më të përshtatshme të mundshme.

Në këtë artikull dua të nënvizoj fazat kryesore të punës sime dhe, për të filluar, të përshkruaj konceptin e krijuar të gjuhës dhe zbatimin e saj të parë, mbi të cilin jam duke punuar aktualisht.

Më lejoni të them paraprakisht se të gjithë projektin e kam shkruar në Free Pascal, sepse... programet në të mund të përpilohen për një numër të madh platformash, dhe vetë përpiluesi prodhon binare shumë të optimizuara (Unë mbledh të gjithë përbërësit e projektit me flamurin O2).

Koha e ekzekutimit të gjuhës

Para së gjithash, ia vlen të flasim për makinën virtuale që më është dashur të shkruaj për të ekzekutuar aplikacionet e ardhshme në gjuhën time. Vendosa të zbatoj një arkitekturë stack, ndoshta, sepse ishte mënyra më e lehtë. Nuk gjeta një artikull të vetëm normal se si ta bëj këtë në rusisht, kështu që pasi u njoha me materialin në gjuhën angleze, u ula për të projektuar dhe shkruar biçikletën time. Më pas do të paraqes idetë dhe zhvillimet e mia "të avancuara" në këtë çështje.

Implementimi i stivës

Natyrisht, në krye të VM është pirg. Në zbatimin tim funksionon në blloqe. Në thelb ky është një grup i thjeshtë treguesish dhe një variabël për të ruajtur indeksin e pjesës së sipërme të pirgut.
Kur inicializohet, krijohet një grup prej 256 elementësh. Nëse më shumë tregues shtyhen në pirg, madhësia e tij rritet me 256 elementët e ardhshëm. Prandaj, kur hiqni elementët nga pirgja, madhësia e saj rregullohet.

VM përdor disa rafte:

  1. Rafti kryesor.
  2. Një pirg për ruajtjen e pikave të kthimit.
  3. Pirg grumbulluesi i plehrave.
  4. Provo/kap/blloko përfundimisht grumbullin e mbajtësve.

Konstantet dhe variablat

Kjo është e thjeshtë. Konstantet trajtohen në një pjesë të vogël të veçantë kodi dhe janë të disponueshme në aplikacionet e ardhshme nëpërmjet adresave statike. Variablat janë një grup treguesish të një madhësie të caktuar, qasja në qelizat e saj kryhet me indeks - d.m.th. adresa statike. Variablat mund të shtyhen në krye të pirgut ose të lexohen prej andej. Në fakt, sepse Ndërsa variablat tanë në thelb ruajnë tregues për vlerat në kujtesën VM, gjuha dominohet nga puna me tregues të nënkuptuar.

Mbledhës plehrash

Në VM-në time është gjysmë automatike. Ato. zhvilluesi vetë vendos se kur të thërrasë grumbulluesin e plehrave. Nuk funksionon duke përdorur një numërues të rregullt tregues, si në Python, Perl, Ruby, Lua, etj. Ai zbatohet përmes një sistemi shënues. Ato. kur një ndryshore synohet t'i caktohet një vlerë e përkohshme, një tregues për këtë vlerë i shtohet pirgut të grumbulluesit të mbeturinave. Në të ardhmen, koleksionisti kalon shpejt nëpër listën tashmë të përgatitur të treguesve.

Trajtimi i provo/kap/në fund bllokon

Si në çdo gjuhë moderne, trajtimi i përjashtimeve është një komponent i rëndësishëm. Bërthama e VM-së ë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ë pirg. Në kodin e aplikacionit, mund të përcaktoni blloqet e kodit provo/catch/fund, duke specifikuar pikat e hyrjes në catch (trajtues përjashtimi) dhe në fund/fund (fundi i bllokut).

Multithreading

Ai mbështetet në nivelin VM. Është i thjeshtë dhe i përshtatshëm për t'u përdorur. Ai funksionon pa një sistem ndërprerjeje, kështu që kodi duhet të ekzekutohet në disa fije, respektivisht disa herë më shpejt.

Bibliotekat e jashtme për VM-të

Nuk ka asnjë mënyrë për të bërë pa këtë. VM mbështet importet, ngjashëm me mënyrën se si zbatohet në gjuhë të tjera. Ju mund të shkruani një pjesë të kodit në Mash dhe një pjesë të kodit në gjuhët amtare, pastaj t'i lidhni ato në një.

Përkthyes nga gjuha Mash e nivelit të lartë në kodin bajt për VM-të

Gjuha 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 si montues që nuk ka asnjë pikë të veçantë për ta konsideruar këtu. Do të them vetëm se në këtë nivel përkthyesi përpunon shumicën e konstantave dhe variablave, llogarit adresat e tyre statike dhe adresat e pikave hyrëse.

Arkitektura e përkthyesit

Nuk zgjodha arkitekturën më të mirë për zbatim. Përkthyesi nuk ndërton një pemë kodi, siç bëjnë përkthyesit e tjerë. Ai shikon në fillim të strukturës. Ato. nëse pjesa e kodit që analizohet duket si "while <condition>:", atëherë është e qartë se ky është një konstrukt i ciklit while dhe duhet të përpunohet si një konstrukt i ciklit while. Diçka si një këllëf i ndërlikuar.

Falë kësaj zgjidhjeje arkitekturore, përkthyesi doli të ishte jo shumë i shpejtë. Sidoqoftë, lehtësia e modifikimit të tij është rritur ndjeshëm. I shtova strukturat 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

Këtu, sigurisht, ai mund të ishte zbatuar më mirë (dhe do të zbatohet, por më vonë, sapo t'i afrohet). Deri më tani, optimizuesi di vetëm të ndërpresë kodin e papërdorur, konstantet dhe importet nga montimi. Gjithashtu, disa konstante me të njëjtën vlerë zëvendësohen me një. Kjo eshte e gjitha.

Gjuha Mash

Koncepti bazë i gjuhës

Ideja kryesore ishte zhvillimi i gjuhës më funksionale dhe më të thjeshtë të mundshme. Unë mendoj se zhvillimi e përballon detyrën e tij me një zhurmë.

Blloqet e kodeve, procedurat dhe funksionet

Të gjitha ndërtimet në gjuhë hapen me dy pika. : dhe mbyllen nga operatori fund.

Procedurat dhe funksionet janë deklaruar si proc dhe funksion, përkatësisht. Argumentet janë renditur në kllapa. Gjithçka është si shumica e gjuhëve të tjera.

Operatori kthim ju mund të ktheni një vlerë nga një funksion, operator pushim ju lejon të dilni nga procedura/funksioni (nëse është jashtë sytheve).

Shembull i kodit:

...

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

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

Modelet e mbështetura

  • Sythe: për..fund, ndërsa..fund, deri në..fund
  • Kushtet: nëse..[ndryshe..]fund, ndërro..[rast..fund..][ndryshe..]fund
  • Metodat: proc <emri>():... fundi, func <emri>():... fundi
  • Etiketoni dhe shkoni: <emri>:, hidheni <emri>
  • Numërimi i numërimit dhe vargjeve konstante.

variabla

Përkthyesi mund t'i përcaktojë ato automatikisht, ose nëse zhvilluesi shkruan var përpara se t'i përcaktojë ato.

Shembuj kodesh:

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Variablat globalë dhe lokalë janë të mbështetur.

OOP

Epo, kemi ardhur në temën më të shijshme. Mash mbështet të gjitha paradigmat e programimit të orientuara nga objekti. Ato. klasa, trashëgimi, polimorfizëm (përfshirë dinamikë), reflektim automatik dinamik dhe introspeksion (i plotë).

Pa zgjatje të mëtejshme, është më mirë të jepni vetëm shembuj kodesh.

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ë dalë: 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

Do të dalë: 60.

Po polimorfizmi dinamik? Po, 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

Do të dalë: 60.

Tani le të marrim një moment për të analizuar vlerat dhe klasat e 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

Do të dalë: e vërtetë, e vërtetë.

Rreth operatorëve të caktimit dhe treguesve të qartë

Operatori ?= përdoret për t'i caktuar një variabli një tregues në një vlerë në memorie.
Operatori = ndryshon një vlerë në memorie duke përdorur një tregues nga një ndryshore.
Dhe tani pak për treguesit e qartë. I shtova në gjuhë që të ekzistojnë.
@<variable> — merrni një tregues të qartë në një ndryshore.
?<variable> — merrni një variabël sipas treguesit.
@= — caktoni një vlerë për një ndryshore me anë të një treguesi 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ë dalë: një numër, 10, 11.

Provoni..[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

Vazhdoj të shikoj dhe shikoj GraalVM & Truffle. Mjedisi im i ekzekutimit nuk ka një përpilues JIT, kështu që për sa i përket performancës aktualisht është vetëm konkurrues me Python. Shpresoj se do të jem në gjendje të zbatoj përpilimin JIT bazuar në GraalVM ose LLVM.

depo

Ju mund të luani me zhvillimet dhe ta ndiqni vetë projektin.

Faqe
Depoja në GitHub

Faleminderit që lexuat deri në fund nëse e keni lexuar.

Burimi: www.habr.com

Shto një koment