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

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

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

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

Լեզվի կատարման ժամանակ

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

Ստեքի իրականացում

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

Վիրտուալ մեքենան օգտագործում է մի քանի կույտեր՝

  1. Գլխավոր կույտ։
  2. Վերադարձի կետերը պահելու համար նախատեսված կույտ։
  3. Աղբահավաքի կույտ։
  4. «Try/catch/final block» մշակողների մի կույտ։

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

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

Աղբահավաք

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

Try/Cats/Finally բլոկների կառավարում

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

Multithreading

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

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

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

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

Միջին լեզու

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

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

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

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

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

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

Mash լեզու

Լեզվի հիմնական հասկացությունը

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

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

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

Պրոցեդուրաները և ֆունկցիաները հայտարարվում են համապատասխանաբար որպես proc և func: Արգումենտները նշված են փակագծերում: Ինչպես մյուս լեզուների մեծ մասում:

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

Օրինակ կոդը:

...

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

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

Աջակցվող կառուցվածքներ

  • ցիկլեր՝ for..end, while..end, until..end
  • Պայմաններ՝ եթե..[այլապես..]վերջ, switch..[պատճառ..վերջ..][այլապես..]վերջ
  • Մեթոդներ՝ 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

Արդյունքը կլինի՝ ճիշտ է, ճիշտ է։

Վերագրման օպերատորների և բացահայտ ցուցիչների մասին

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

Օրինակ կոդը:

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

Գնեք հուսալի հոստինգ DDoS պաշտպանությամբ կայքերի, VPS VDS սերվերների համար 🔥 Գնեք հուսալի կայքերի հոսթինգ՝ DDoS պաշտպանությամբ, VPS VDS սերվերներով | ProHoster