Նոր ծրագրավորման լեզու Mash

Մի քանի տարի ես փորձեցի ուժերս մշակելու իմ սեփական ծրագրավորման լեզուն: Ես ուզում էի ստեղծել, իմ կարծիքով, հնարավորինս պարզ, լիովին գործունակ և հարմար լեզուն:

Այս հոդվածում ես ուզում եմ առանձնացնել իմ աշխատանքի հիմնական փուլերը և, սկզբից, նկարագրել լեզվի ստեղծված հայեցակարգը և դրա առաջին իրականացումը, որի վրա այժմ աշխատում եմ։

Նախապես ասեմ, որ ամբողջ նախագիծը գրել եմ Free Pascal-ով, քանի որ... դրա վրա ծրագրերը կարող են հավաքվել հսկայական թվով հարթակների համար, և կոմպիլյատորն ինքն է արտադրում շատ օպտիմիզացված երկուականներ (ես հավաքում եմ նախագծի բոլոր բաղադրիչները O2 դրոշակով):

Լեզվի գործարկման ժամանակը

Նախ, արժե խոսել այն վիրտուալ մեքենայի մասին, որը ես պետք է գրեի ապագա հավելվածները իմ լեզվով գործարկելու համար: Ես որոշեցի իրականացնել stack ճարտարապետություն, հավանաբար, քանի որ դա ամենահեշտ ճանապարհն էր: Ես չգտա ոչ մի նորմալ հոդված, թե ինչպես դա անել ռուսերենով, ուստի ծանոթանալով անգլերեն լեզվի նյութին, ես նստեցի իմ սեփական հեծանիվը նախագծելու և գրելու համար: Հաջորդիվ կներկայացնեմ իմ «առաջադեմ» գաղափարներն ու զարգացումները այս հարցում։

Դույների իրականացում

Ակնհայտ է, որ VM-ի վերևում գտնվում է բուրգը: Իմ իրականացման դեպքում այն ​​աշխատում է բլոկներով։ Ըստ էության, սա ցուցիչների մի պարզ զանգված է և փոփոխական, որը պահում է կույտի վերին մասի ինդեքսը:
Երբ այն սկզբնավորվում է, ստեղծվում է 256 տարրերից բաղկացած զանգված: Եթե ​​ավելի շատ ցուցիչներ մղվեն կույտի վրա, դրա չափը մեծանում է հաջորդ 256 տարրերով: Համապատասխանաբար, կույտից տարրերը հեռացնելիս դրա չափը ճշգրտվում է:

VM-ն օգտագործում է մի քանի կույտ.

  1. Հիմնական բուրգ.
  2. Վերադարձի կետերը պահելու համար նախատեսված կույտ:
  3. Աղբահավաք բուրգ.
  4. Փորձեք/բռնել/վերջապես արգելափակել կարգավորիչի կույտը:

հաստատուններ և փոփոխականներ

Սա պարզ է. Հաստատությունները մշակվում են կոդերի առանձին փոքր կտորով և հասանելի են ապագա հավելվածներում ստատիկ հասցեների միջոցով: Փոփոխականները որոշակի չափի ցուցիչների զանգված են, դրանց բջիջների մուտքն իրականացվում է ինդեքսով, այսինքն. ստատիկ հասցե. Փոփոխականները կարող են մղվել կույտի վերևում կամ կարդալ այնտեղից: Իրականում, քանի որ Մինչ մեր փոփոխականները հիմնականում պահում են ցուցիչներ դեպի արժեքներ VM հիշողության մեջ, լեզուն գերակշռում է անուղղակի ցուցիչների հետ աշխատելով:

Աղբահավաք

Իմ VM-ում այն ​​կիսաավտոմատ է։ Նրանք. կառուցապատողն ինքն է որոշում, թե երբ կանչի աղբահանին: Այն չի աշխատում սովորական ցուցիչի հաշվիչի միջոցով, ինչպես Python-ում, Perl-ում, Ruby-ում, Lua-ում և այլն: Այն իրականացվում է մարկերային համակարգի միջոցով: Նրանք. երբ փոփոխականին նախատեսվում է ժամանակավոր արժեք վերագրել, այս արժեքի ցուցիչը ավելացվում է աղբահանի կույտին: Ապագայում կոլեկցիոներն արագ անցնում է ցուցիչների արդեն պատրաստված ցանկը:

Փորձել/բռնել/վերջապես արգելափակել

Ինչպես ցանկացած ժամանակակից լեզվում, բացառությունների մշակումը կարևոր բաղադրիչ է: VM միջուկը փաթաթված է try..catch բլոկով, որը կարող է վերադառնալ կոդի կատարման՝ բացառություն որսալուց հետո՝ դրա մասին որոշ տեղեկություններ հրելով փաթեթի վրա: Հավելվածի կոդում դուք կարող եք սահմանել փորձ/բռնել/վերջապես կոդի բլոկներ՝ նշելով մուտքի կետերը catch (բացառություն մշակող) և վերջապես/վերջ (բլոկի վերջ):

Multithreading

Այն աջակցվում է VM մակարդակով: Դա պարզ և հարմար է օգտագործման համար: Այն աշխատում է առանց ընդհատման համակարգի, ուստի կոդը պետք է կատարվի մի քանի թելերով, համապատասխանաբար, մի քանի անգամ ավելի արագ:

Արտաքին գրադարաններ VM-ների համար

Առանց սրա հնարավոր չէ անել։ VM-ն աջակցում է ներմուծմանը, ինչպես այն իրականացվում է այլ լեզուներով: Կարող եք կոդի մի մասը գրել Mash-ով, իսկ կոդի մի մասը մայրենի լեզուներով, այնուհետև դրանք կապել մեկի մեջ:

Թարգմանիչ բարձր մակարդակի Mash լեզվից դեպի բայթկոդ VM-ների համար

Միջանկյալ լեզու

Բարդ լեզվից թարգմանիչ VM կոդ արագ գրելու համար ես նախ մշակեցի միջանկյալ լեզու: Արդյունքը մոնտաժային նման ահավոր տեսարան էր, որն այստեղ դիտարկելու առանձնահատուկ իմաստ չկա: Ես միայն կասեմ, որ այս մակարդակում թարգմանիչը մշակում է հաստատունների և փոփոխականների մեծ մասը, հաշվարկում է դրանց ստատիկ հասցեները և մուտքի կետերի հասցեները:

Թարգմանչի ճարտարապետություն

Իրականացման համար ես չեմ ընտրել լավագույն ճարտարապետությունը։ Թարգմանիչը կոդի ծառ չի կառուցում, ինչպես անում են մյուս թարգմանիչները: Նա նայում է կառույցի սկզբին։ Նրանք. եթե վերլուծվող կոդի կտորը նման է «while <condition>:»-ին, ապա ակնհայտ է, որ սա while հանգույցի կառուցում է և պետք է մշակվի որպես while հանգույցի կառուցում: Բարդ անջատիչի նման մի բան:

Այս ճարտարապետական ​​լուծման շնորհիվ թարգմանիչը ոչ այնքան արագաշարժ է ստացվել։ Այնուամենայնիվ, դրա ձևափոխման հեշտությունը զգալիորեն աճել է: Ես ավելացրեցի անհրաժեշտ կառուցվածքները ավելի արագ, քան իմ սուրճը կարող էր սառչել: OOP-ի ամբողջական աջակցությունն իրականացվել է մեկ շաբաթից էլ քիչ ժամանակում:

Կոդի օպտիմալացում

Այստեղ, իհարկե, այն կարող էր ավելի լավ իրագործվել (և կիրականացվի, բայց ավելի ուշ, հենց որ մարդ հասնի դրան): Առայժմ, օպտիմիզատորը գիտի միայն, թե ինչպես կտրել չօգտագործված ծածկագիրը, հաստատունները և ներմուծումները հավաքից: Նաև նույն արժեքով մի քանի հաստատուն փոխարինվում է մեկով։ Այսքանը:

Լեզվի Mash

Լեզվի հիմնական հայեցակարգը

Հիմնական գաղափարը հնարավորինս ֆունկցիոնալ և պարզ լեզու մշակելն էր: Կարծում եմ, որ զարգացումն իր առջեւ դրված խնդիրն արագորեն կատարում է։

Կոդի բլոկներ, ընթացակարգեր և գործառույթներ

Լեզվի բոլոր կոնստրուկցիաները բացվում են երկու կետով։ : և փակված են օպերատորի կողմից վերջ.

Ընթացակարգերը և գործառույթները համապատասխանաբար հայտարարված են որպես proc և func: Փաստարկները թվարկված են փակագծերում: Ամեն ինչ նման է այլ լեզուների:

Օպերատոր վերադարձնել կարող եք արժեք վերադարձնել ֆունկցիայից, օպերատորից կոտրել թույլ է տալիս դուրս գալ ընթացակարգից/ֆունկցիայից (եթե այն գտնվում է օղակներից դուրս):

Օրինակ կոդը:

...

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

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

Աջակցվող նմուշներ

  • Օղակներ՝ համար..վերջ, մինչդեռ..վերջ, մինչև..վերջ
  • Պայմաններ՝ եթե..[այլ...]վերջ, անցեք..[դեպ..վերջ..][այլևս..]վերջ
  • Մեթոդներ՝ proc <name>():... end, func <name>():... end
  • Պիտակել և անցնել՝ <անուն>:, ցատկել <անուն>
  • Թվարկումներ և հաստատուն զանգվածներ:

Փոփոխականներ

Թարգմանիչը կարող է դրանք ավտոմատ կերպով որոշել, կամ եթե մշակողը գրում է var նախքան դրանք սահմանելը:

Կոդի օրինակներ.

a ?= 10
b ?= a + 20

var a = 10, b = a + 20

Աջակցվում են գլոբալ և տեղական փոփոխականները:

OOP

Դե, մենք հասանք ամենահամեղ թեմային: Mash-ն աջակցում է օբյեկտի վրա հիմնված ծրագրավորման բոլոր պարադիգմներին: Նրանք. դասեր, ժառանգականություն, պոլիմորֆիզմ (ներառյալ դինամիկ), դինամիկ ավտոմատ արտացոլում և ներդաշնակություն (լրիվ):

Առանց հետագա անհանգստության, ավելի լավ է պարզապես կոդերի օրինակներ տալ:

Պարզ դաս և դրա հետ աշխատելը.

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

Արդյունք՝ 30:

Ժառանգականություն և պոլիմորֆիզմ.

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

Արդյունք՝ 60:

Ինչ վերաբերում է դինամիկ պոլիմորֆիզմին: Այո, սա արտացոլումն է:

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

Արդյունք՝ 60:

Հիմա եկեք մի պահ տրամադրենք պարզ արժեքների և դասերի ներքնաբանման համար.

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

Արդյունք՝ ճշմարիտ, ճշմարիտ:

Հանձնարարությունների օպերատորների և բացահայտ ցուցիչների մասին

?= օպերատորն օգտագործվում է փոփոխականին ցուցիչ հատկացնելու հիշողության արժեքին:
= օպերատորը փոխում է արժեք հիշողության մեջ՝ օգտագործելով փոփոխականից ցուցիչ:
Եվ հիմա մի փոքր բացահայտ ցուցիչների մասին: Ես դրանք ավելացրել եմ լեզվին, որպեսզի գոյություն ունենան։
@<variable> — բացահայտ ցուցիչ վերցրեք փոփոխականին:
?<variable> — ստացեք փոփոխական ըստ ցուցիչի:
@= — փոփոխականին արժեք վերագրեք դրա բացահայտ ցուցիչով:

Օրինակ կոդը:

uses <bf>
uses <crt>

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

Արդյունք՝ ինչ-որ թիվ, 10, 11:

Փորձիր..[բռնել..][վերջապես..]վերջ

Օրինակ կոդը:

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

Պլանները ապագայի համար

Ես շարունակում եմ նայել և նայել GraalVM & Truffle-ին: Իմ գործարկման միջավայրը չունի JIT կոմպիլյատոր, ուստի կատարողականի առումով այն ներկայումս մրցունակ է միայն Python-ի հետ: Հուսով եմ, որ կկարողանամ իրականացնել JIT կոմպիլյացիան՝ հիմնված GraalVM-ի կամ LLVM-ի վրա:

պահոց

Կարող եք խաղալ զարգացումների հետ և ինքներդ հետևել նախագծին։

Site
Պահեստ GitHub-ում

Շնորհակալություն, եթե կարդացել եք մինչև վերջ:

Source: www.habr.com

Добавить комментарий