Create A Programming Language - The Easiest Way
Create A Programming Language - The Easiest Way
Program တစ်ခုတည်ဆောက်ချင်ပြီဆိုတာနဲ့ Programming Language တစ်ခုကိုလေ့လာရပါတယ်။ Program တစ်ခုရေးဖို့ Programming Language တစ်ခုလိုအပ်လို့ဖြစ်တယ်။ဒီ့ထက်ပိုလိုအပ်တဲ့အခါမျိုးတွေလည်းရှိလိမ့်မယ်။ ဉပမာ - E commerce Website တစ်ခုရေးမယ်ဆိုရင် Frontend အတွက် JavaScript ၊ Backend အတွက် PHP ကိုသုံးရတဲ့အခါမျိုးပေါ့။
Programming Language တစ်ခုကိုတော့လေ့လာဖူးတယ်။ ကိုယ်လေ့လာလိုက်တဲ့ Programming Language က နောက်ကွယ်မှာဘယ်လိုအလုပ်လုပ်သွားလဲဆိုတာသိချင်တယ်။ ခု Article က အဲ့လို Curious ဖြစ်တဲ့သူတွေအတွက်သင့်တော်ပါမယ်။
ခု Article မှာ Programming Language တစ်ခု ယေဘုယျအားဖြင့်လုပ်ဆောင်သွားတဲ့ပုံကိုလူနားလည်အောင်ရေးသွားမှာဖြစ်ပါတယ်။ လက်တွေ့လိုက်လုပ်မယ့်သူတွေကတော့လိုအပ်ချက်အနည်းငယ်ရှိပါတယ်။ JS နဲ့ NodeJS ကို File System (FS module) အသုံးပြုပုံကိုသိထားဖို့လိုအပ်ပါတယ်။
Theory Phase
Language Design
Language Design ကတော့ရိုးရှင်းပါတယ်။ ပေါင်းမယ်။နှုတ်မယ်။ ဒီလောက်ပါပဲ။ Operator Precedent တော့မပါဘူး။
Example Program
|
|
Basic Computing
Computer တစ်ခုတည်ဆောက်ချင်တဲ့အခါ သူ့ရဲ့ Abstract Model တွေကိုသတ်မှတ်ဖို့လိုအပ်တယ်။ Abstract Model ဆိုတာ Instruction Set Architecture (ISA) လို့ခေါ်တယ်။ ISA ဆိုတာ ဘယ်လိုကောင်မျိုးတွေလည်းဆိုတော့ Data Type တွေ၊ Register Values တွေ၊ I/O (Input Output) လုပ်ငန်းတွေ နဲ့ Machine Language Instructions တွေပဲဖြစ်တယ်။ ISA ကိုအလွယ်ပြောရရင် Computer တစ်လုံးမှာလိုအပ်တဲ့ Element လေးတွေကိုသတ်မှတ်တဲ့လုပ်ငန်းစဉ်လို့မှတ်လို့ရပါတယ်။
Instruction တွေကို Instruction Code တွေနဲ့ဖွဲ့စည်းထားတယ်။ Instruction Code ဆိုတာ CPU ပေါ်မှာတိုက်ရိုက်အလုပ်လုပ်နိုင်တဲ့ကောင်တွေကိုပြောတာ။ opcode (သို့မဟုတ်) p-code (သို့မဟုတ်) Binary Numbers လို့လည်းခေါ်တယ်။ opcode တွေကို Atoms of Computing လို့ခေါ််တယ်။ Atom က Matter တွေအတွက်အသေးဆုံးဆိုတော့ Computing မှာဆိုရင်လည်း opcode တွေကအသေးဆုံးပဲလို့ပြောချင်တာဖြစ်တယ်။
opcode လေးတွေက CPU ပေါ်မှာတိုက်ရိုက်အလုပ်လုပ်နိုင်တယ်။ Machine Language လို့ခေါ်ပါတယ်။ opcode လေးတွေကို CPU အလိုက် Table နဲ့ဖော်ပြကြတယ်။ Intel ရဲ့ x86 assembly opcode table ကိုသွားကြည့်လို့ရတယ်။
http://sparksandflames.com/files/x86InstructionChart.html
Assembly Language
Opcode တွေကမှတ်ရခက်တဲ့အတွက် Opcode ကို Name ခေါ်သုံးလို့ရတဲ့ Assembly Language ကိုဖန်တီးလိုက်တယ်။ ဒါ့ကြောင့် Assembly ကိုသုံးပြီး Machine Language ကို Translate လုပ်ကာ Computer ကိုကိုယ်လုပ်ဆောင်စေချင်တဲ့လုပ်ဆောင်ချက်တွေကိုလုပ်ဆောင်ခိုင်းလို့ရတယ်။ Assembly သုံးပြီး Translate လုပ်တဲ့ဖြစ်စဉ်ကို Assembler လုပ်တယ်လို့ခေါ်တယ်။
Compiler
Compiler ဆိုတာ Program တစ်ခုပါပဲ။ သူက Source Program တစ်ခုကိုယူပြီးတော့ Machine မှာအလုပ်လုပ်နိုင်တဲ့ကောင်ကိုပြန်ထုတ်ပေးတယ်။ ဉပမာ - Rust နဲ့ Program တစ်ခုရေးထားတယ်ဆိုပါစို့။ Rust Compiler က ရေးထားတဲ့ Program Code တွေကိုယူပြီး Analysis လုပ်တယ်။ အဲ့နောက်မှာ Native Executable Code တွေပြန်ထုတ်ပေးတယ်။ Native Code တွေက CPU ပေါ်မှာ Run လို့ရတယ်။ ဒါက Rust Compiler ရဲ့အခြေခံအလုပ်လုပ်ပုံ။
Compiler တစ်ခုတည်ဆောက်မယ်ဆိုရင် Front End နဲ့ Backend ဆိုတဲ့ လုပ်ငန်းစဉ်နှစ်ခုလုပ်ရတယ်။
Frontend မှာ Source Code ကနေ Abstract Syntax Tree (AST) ရအောင် ထုတ်တယ်။ Backend ကတော့ Frontend ကထွက်လာတဲ့ AST ကိုယူပြီး Bytecode (သို့မဟုတ်) Native Code ထုတ်တယ်။ Transpiler မှာတော့ Higher Level Programming Language တစ်ခုပြန်ထွက်လာမယ်။
Compiler မှာသုံးတဲ့ JIT,AOT နဲ့ VM စတဲ့ Technology အကြောင်းကိုမပြောတော့ဘူး။ကျွန်တော်တို့ရေးသွားမှာက Transpiler မို့လို့ပါ။
Transpiler
Transplier ဆိုတာသူလည်း Source Program ကိုယူတယ်။ Frontend မှာ AST ထုတ်တယ်။ Backend မှာ Code Generate တယ်။ ကွာတာက Higher Level Programming Language တစ်ခုကိုပြန်ထုတ်ပေးတာ။ ဉပမာ - Typescript လိုမျိုးပေါ့။
Grammar , Lexical Analysis , Syntax Analysis and Parsing
Programming Language တိုင်းမှာ Formal Grammar (ရေးထုံးသဒ္ဒါ) လိုအပ်တယ်။ Grammar ကို ရေးထုံးမှန်မမှန်စစ်တဲ့အခါမျိုးမှာသုံးပါတယ်။
ဉပမာ
Subject + Verb + Object ဆိုတဲ့ Grammar တစ်ခုရှိတယ်ဆိုပါစို့။ ပြီးရင်အောက်ကစာကြောင်းမှန်၊ မမှန် Analysis လုပ်ကြည့်မယ်။
|
|
ပထမဆုံး Token လေးတွေခွဲရပါမယ်။ I သည် Subject။ Love သည် Verb။ You သည် Object။
Token/Lexeme လေးတွေခွဲတဲ့ လုပ်ငန်းစဉ်ကို Tokenizing/Lexical Analysis လို့ခေါ်တယ်။ Token တစ်ခုခြင်းစီကို မှန်၊မမှန်စစ်တယ်။ I နေရာမှာတစ်ခြားဟာဝင်နေသလားပေါ့။မမှန်ရင် Error ပြန်ပေးလိမ့်မယ်။
ပြီးရင် Grammar မှန်၊မမှန်စစ်တယ်။ Syntax Analysis/Parsing လို့ခေါ်တယ်။
Parse လုပ်တယ်ဆိုတာ Grammar မှန်တဲ့ Token လေးတွေကိုပြန်ပေးလိုက်တာပါပဲ။ Parse Tree ပြန်ထုတ်ပေးတယ်လို့လည်းခေါ်လို့ရပါတယ်။ Parser ကထွက်လာတဲ့ Parse Tree ကရှုပ်နေတဲ့အတွက် ဒီ့အတွက် ပိုရှင်းအောင် AST ဆောက်ပေးရပါတယ်။ ဒါမှသာ Backend ကနေ Code Generate တဲ့အခါအဆင်ပြေမှာဖြစ်တယ်။
Code Generation
Frontend ကရတဲ့ AST ကို Traverse လုပ်ပြီး ဆိုင်ရာဆိုင်ရာ Code ပြန်ထုတ်ပေးတယ်။ ဉပမာ - Rust ဆိုရင် Native Code ထုတ်ပေးမယ်။ Typescript ဆိုရင် JavaScript ပြန်ထုတ်ပေးတယ်။
Practical Phase
အထက်ကအကျဉ်းချုပ်ထားတဲ့သီအိုရီကိုလက်တွေ့သုံးကြည့်ပါမယ်။ တကယ့် Programming Language တစ်ခုရေးတော့မယ်ဆိုရင် အကျယ်အထိသိဖို့လိုပါမယ်။ ကျွန်တော်ကတော့ အပေါ်ကရှင်းထားတဲ့သီအိုရီနဲ့ယှဉ်ပြီးတော့ တကယ်အလုပ်လုပ်တဲ့ Tiny Transpiler လေးဆောက်သွားမှာမို့လို့အကျယ်မချဲ့တော့ဘူး။
ကျွန်တော်တို့ Project တစ်ခု Create ပါမယ်။
|
|
Project Directory ထဲကိုဝင်ပါမယ်။
|
|
ပြီးရင် Node Package တစ်ခုကို Install လုပ်ပါမယ်။
|
|
Nearley က Parser Generator Tool ဖြစ်ပါတယ်။ ဒီ့အတွက်ကျွန်တော်တို့ကိုယ်တိုင် Parser ကိုမရေးတော့ပါဘူး။လိုချင်တဲ့ Grammar ကိုရေးပေးပြီးတော့ Nearley ကို Parser Generate လုပ်ခိုင်းလိုက်မှာပဲဖြစ်ပါတယ်။
App.js
ဖိုင်ကိုဖွင့်လိုက်ပါ။
အောက်ပါ Code တွေကိုထည့်လိုက်ပါ။
|
|
Nearley ရဲ့ Getting started က Code တွေဖြစ်ပါတယ်။ https://nearley.js.org/docs/getting-started
ပြီးတဲ့နောက်မှာ grammar.ne
ဆိုတဲ့ဖိုင်တစ်ဖိုင်တည်ဆောက်ပါမယ်။Grammar Rule တွေရေးဖို့အတွက်ဖြစ်ပါတယ်။
|
|
Grammar ဖိုင်ထဲမှာအောက်က Grammar Rule တွေကိုရေးပါမယ်။
|
|
အထက်မှာရေးထားတဲ့စာကြောင်းကို Production Rule လို့ခေါ်ပါတယ်။ Production Rule မှာ -> ရဲ့ ဘယ်ဘက်က Symbol ရှိပါမယ်။ ညာဘက်မှာတော့ symbol ကဘာပဲဖြစ်ရမယ်ဆိုပြီးသတ်မှတ်ပါတယ်။ ကျွန်တော်တို့ရေးတဲ့ Grammar ကို Context Free Grammar (CFG) လို့ခေါ်ပါတယ်။
အထက်က Production Rule ရဲ့အဓိပ္ပာယ်က number သည် 0 ကနေ 9 ထိဂဏန်းဖြစ်ရပါမယ်။ :+ ကတော့ တစ်လုံးထက်ပိုပြီးရှိရပါမယ်လို့ပြောတာပါ။ Regular Expression ကိုသုံးဖူးရင်သဘောပေါက်မှာပါ။ number (symbol) ကိုတော့ကြိုက်ရာနာမည်ပေးလို့ရပါတယ်။ သို့သော် အဓိပ္ပာယ်ရှိတဲ့ နာမည်ကိုပေးဖို့လိုပါတယ်။
ခု Parser Generate ပါမယ်။
|
|
ပြီးနောက် App.js ဖိုင်ထဲက paraer.feed(“1”) လို့ပြောင်းလိုက်ပါမယ်။
node parser.js လို့ Run လိုက်တဲ့အခါ Parse လုပ်လို့ရလာတဲ့ Result ကိုပြပါလိမ့်မယ်။ Parse လုပ်လို့ရလာတဲ့ Result ကို Parse Tree လို့ခေါ်ပါတယ်။
|
|
Square Bracket ကတော့ Parse လုပ်ရတဲါအဆင့်တွေကိုပြတာပါ။ မူလကတည်းက [] ရှိပါမယ်။ Parse လုပ်တဲ့အခါ 1 ကို အထက်မှာရေးထားတဲ့ Production Rule ဖြစ်တဲ့ number အရ [[]] Bracket နှစ်ခုဖြစ်သွားပါတယ်။ 1 သည် Grammar Rule အရမှန်တဲ့ Token ဖြစ်တဲ့အတွက် Array ထဲထည့်ပြီးဖော်ပြပါတယ်။ ဒီ့အတွက် [ [ [ ‘1’ ] ] ] ဖြစ်သွားရပါတယ်။ ကျွန်တော်တို့လိုချင်တာ [‘1’] ပဲလိုချင်တာပါ။ ဒီ့အတွက် Production Rule ကိုအောက်ပါအတိုင်းပြောင်းပေးလို့ရပါတယ်။
|
|
Grammar Rule ပြောင်းပြီးရင် Parser ကို ပြန်ပြီးGenerate လုပ်ပေးရပါတယ်။
|
|
ပြီးနောက် node parser.js လို့ Run ကြည့်ရင် [ [ ‘1’ ] ] ရမှာပါ။ ကျွန်တော်တို့လိုချင်တာကိုရပါတော့မယ်။ Index No 0 လေးလိုချင်လို့ အောက်ပါအရှည်ကြီးရေးရတာကမကိုက်ပါဘူး။
|
|
ဒီ့အတွက် အောက်ပါအတိုင်းပြောင်းရေးလို့ရပါတယ်။
|
|
ပြန်စမ်းကြည့်ရင် အတူတူပဲဆိုတာကိုတွေ့ရမှာပါ။
ခု Grammar ကိုအောက်ပါအတိုင်းစရေးပါမယ်။
|
|
Parser
Parser.js
ကိုလည်းအောက်ပါအတိုင်းထပ်ထည့်ပါမယ်။
|
|
Transpiler
Code Generation အတွက် Translator.js ဖိုင်ကိုအောက်ပါအတိုင်းရေးရပါမယ်။ Translator.js သည် JS Code ကိုထုတ်ပေးမှာဖြစ်ပါတယ်။
|
|
Usage
|
|
Simple Program
|
|
Generate AST
|
|
Transpile To JS
|
|
Run JS
|
|
Conclusion
နိဂုံးချုပ်အနေနဲ့ Programming Language တစ်ခုအလုပ်လုပ်ပုံကိုသီအိုရီအရကော၊လက်တွေ့အရကောနားလည်သွားမယ်လို့မျှော်လင့်ပါတယ်။ နားမလည်သေးရင်လည်းထပ်လေ့လာပါ။အချိန်တော့ပေးရပါမယ်။ ကျွန်တော်ရေးထားတာကသီအိုရီအကျဉ်းချုပ်ဖြသ်လို့အကျယ်ကိုရှာဖတ်ပါ။ ကိုယ်ပိုင် Programming Language တစ်ခုရေးပြီးသွားရင်လည်းကျွန်တောိ့ကိုလက်တို့လိုက်ပါအုံးနော်။