Մի քանի տարի ես փորձեցի ուժերս մշակելու իմ սեփական ծրագրավորման լեզուն: Ես ուզում էի ստեղծել, իմ կարծիքով, հնարավորինս պարզ, լիովին գործունակ և հարմար լեզուն:
Այս հոդվածում ես ուզում եմ առանձնացնել իմ աշխատանքի հիմնական փուլերը և, սկզբից, նկարագրել լեզվի ստեղծված հայեցակարգը և դրա առաջին իրականացումը, որի վրա այժմ աշխատում եմ։
Նախապես ասեմ, որ ամբողջ նախագիծը գրել եմ Free Pascal-ով, քանի որ... դրա վրա ծրագրերը կարող են հավաքվել հսկայական թվով հարթակների համար, և կոմպիլյատորն ինքն է արտադրում շատ օպտիմիզացված երկուականներ (ես հավաքում եմ նախագծի բոլոր բաղադրիչները O2 դրոշակով):
Լեզվի գործարկման ժամանակը
Նախ, արժե խոսել այն վիրտուալ մեքենայի մասին, որը ես պետք է գրեի ապագա հավելվածները իմ լեզվով գործարկելու համար: Ես որոշեցի իրականացնել stack ճարտարապետություն, հավանաբար, քանի որ դա ամենահեշտ ճանապարհն էր: Ես չգտա ոչ մի նորմալ հոդված, թե ինչպես դա անել ռուսերենով, ուստի ծանոթանալով անգլերեն լեզվի նյութին, ես նստեցի իմ սեփական հեծանիվը նախագծելու և գրելու համար: Հաջորդիվ կներկայացնեմ իմ «առաջադեմ» գաղափարներն ու զարգացումները այս հարցում։
Դույների իրականացում
Ակնհայտ է, որ VM-ի վերևում գտնվում է բուրգը: Իմ իրականացման դեպքում այն աշխատում է բլոկներով։ Ըստ էության, սա ցուցիչների մի պարզ զանգված է և փոփոխական, որը պահում է կույտի վերին մասի ինդեքսը:
Երբ այն սկզբնավորվում է, ստեղծվում է 256 տարրերից բաղկացած զանգված: Եթե ավելի շատ ցուցիչներ մղվեն կույտի վրա, դրա չափը մեծանում է հաջորդ 256 տարրերով: Համապատասխանաբար, կույտից տարրերը հեռացնելիս դրա չափը ճշգրտվում է:
VM-ն օգտագործում է մի քանի կույտ.
- Հիմնական բուրգ.
- Վերադարձի կետերը պահելու համար նախատեսված կույտ:
- Աղբահավաք բուրգ.
- Փորձեք/բռնել/վերջապես արգելափակել կարգավորիչի կույտը:
հաստատուններ և փոփոխականներ
Սա պարզ է. Հաստատությունները մշակվում են կոդերի առանձին փոքր կտորով և հասանելի են ապագա հավելվածներում ստատիկ հասցեների միջոցով: Փոփոխականները որոշակի չափի ցուցիչների զանգված են, դրանց բջիջների մուտքն իրականացվում է ինդեքսով, այսինքն. ստատիկ հասցե. Փոփոխականները կարող են մղվել կույտի վերևում կամ կարդալ այնտեղից: Իրականում, քանի որ Մինչ մեր փոփոխականները հիմնականում պահում են ցուցիչներ դեպի արժեքներ 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-ի վրա:
պահոց
Կարող եք խաղալ զարգացումների հետ և ինքներդ հետևել նախագծին։
Շնորհակալություն, եթե կարդացել եք մինչև վերջ:
Source: www.habr.com