เป็นเวลาหลายปีที่ฉันพยายามพัฒนาภาษาโปรแกรมของตัวเอง ในความคิดของฉัน ฉันต้องการสร้างภาษาที่เรียบง่าย ใช้งานได้เต็มรูปแบบ และสะดวกที่สุดเท่าที่จะเป็นไปได้
ในบทความนี้ ฉันต้องการเน้นขั้นตอนหลักของงานของฉัน และเริ่มต้นด้วยการอธิบายแนวคิดที่สร้างขึ้นของภาษาและการใช้งานครั้งแรกซึ่งฉันกำลังดำเนินการอยู่
ขอบอกล่วงหน้าเลยว่าผมเขียนทั้งโปรเจ็กต์ด้วย Free Pascal เพราะ... โปรแกรมบนนั้นสามารถประกอบได้สำหรับแพลตฟอร์มจำนวนมากและคอมไพเลอร์เองก็สร้างไบนารีที่ได้รับการปรับปรุงให้เหมาะสมที่สุด (ฉันรวบรวมส่วนประกอบทั้งหมดของโปรเจ็กต์ด้วยแฟล็ก O2)
รันไทม์ภาษา
ก่อนอื่น คุ้มค่าที่จะพูดถึงเครื่องเสมือนที่ฉันต้องเขียนเพื่อรันแอปพลิเคชันในอนาคตในภาษาของฉัน ฉันตัดสินใจใช้สถาปัตยกรรมสแต็ก เพราะอาจเป็นวิธีที่ง่ายที่สุด ฉันไม่พบบทความปกติเกี่ยวกับวิธีการทำเช่นนี้ในภาษารัสเซีย ดังนั้นหลังจากทำความคุ้นเคยกับเนื้อหาภาษาอังกฤษแล้ว ฉันจึงนั่งลงเพื่อออกแบบและเขียนจักรยานของตัวเอง ต่อไปผมจะนำเสนอแนวคิดและพัฒนาการ “ขั้นสูง” ของผมในเรื่องนี้
การใช้งานสแต็ก
แน่นอนว่าที่ด้านบนของ VM คือสแต็ก ในการใช้งานของฉันมันทำงานเป็นบล็อก โดยพื้นฐานแล้ว นี่คืออาร์เรย์ของพอยน์เตอร์แบบธรรมดาและเป็นตัวแปรสำหรับจัดเก็บดัชนีที่ด้านบนของสแต็ก
เมื่อเริ่มต้นแล้ว จะมีการสร้างอาร์เรย์ที่มีองค์ประกอบ 256 รายการ หากมีการพุชพอยน์เตอร์ลงบนสแต็กมากขึ้น ขนาดของมันจะเพิ่มขึ้นอีก 256 องค์ประกอบถัดไป ดังนั้นเมื่อลบองค์ประกอบออกจากสแต็ก ขนาดขององค์ประกอบจะถูกปรับขนาด
VM ใช้หลายสแต็ก:
- กองหลัก
- กองสำหรับเก็บคะแนนคืน
- กองเก็บขยะ.
- ลอง/จับ/บล็อกตัวจัดการสแต็กในที่สุด
ค่าคงที่และตัวแปร
อันนี้ง่าย ค่าคงที่ได้รับการจัดการในโค้ดเล็กๆ ที่แยกจากกัน และพร้อมใช้งานในแอปพลิเคชันในอนาคตผ่านที่อยู่แบบคงที่ ตัวแปรคืออาร์เรย์ของพอยน์เตอร์ในขนาดที่กำหนด การเข้าถึงเซลล์จะดำเนินการโดยดัชนี - เช่น ที่อยู่แบบคงที่ คุณสามารถผลักตัวแปรไปที่ด้านบนของสแต็กหรืออ่านจากตรงนั้นได้ จริงๆแล้วเพราะว่า แม้ว่าตัวแปรของเราจะจัดเก็บพอยน์เตอร์เป็นค่าในหน่วยความจำ VM เป็นหลัก แต่ภาษาก็ถูกครอบงำโดยการทำงานกับพอยน์เตอร์โดยนัย
คนเก็บขยะ
ใน VM ของฉันมันเป็นแบบกึ่งอัตโนมัติ เหล่านั้น. นักพัฒนาเองตัดสินใจว่าเมื่อใดจะโทรหาคนเก็บขยะ มันใช้งานไม่ได้กับตัวนับพอยน์เตอร์ทั่วไป เช่นเดียวกับใน Python, Perl, Ruby, Lua ฯลฯ ดำเนินการผ่านระบบมาร์กเกอร์ เหล่านั้น. เมื่อตัวแปรตั้งใจที่จะกำหนดค่าชั่วคราว ตัวชี้ไปยังค่านี้จะถูกเพิ่มลงในสแต็กของตัวรวบรวมขยะ ในอนาคตนักสะสมจะวิ่งผ่านรายการพอยน์เตอร์ที่เตรียมไว้อย่างรวดเร็ว
การจัดการลอง/จับ/บล็อกในที่สุด
เช่นเดียวกับภาษาสมัยใหม่ การจัดการข้อยกเว้นถือเป็นองค์ประกอบที่สำคัญ แกน VM ถูกรวมไว้ในบล็อก try..catch ซึ่งสามารถกลับไปใช้โค้ดได้หลังจากตรวจพบข้อยกเว้นโดยการพุชข้อมูลบางอย่างเกี่ยวกับมันลงบนสแต็ก ในโค้ดแอปพลิเคชัน คุณสามารถกำหนดบล็อก try/catch/finally ของโค้ด โดยระบุจุดเริ่มต้นที่ catch (ตัวจัดการข้อยกเว้น) และสุดท้าย/สิ้นสุด (จุดสิ้นสุดของบล็อก)
มัลติเธรด
ได้รับการรองรับในระดับ VM ใช้งานง่ายและสะดวก มันทำงานโดยไม่มีระบบขัดจังหวะ ดังนั้นโค้ดควรดำเนินการในหลายเธรดเร็วขึ้นหลายเท่าตามลำดับ
ไลบรารีภายนอกสำหรับ VM
ไม่มีทางทำได้หากไม่มีสิ่งนี้ VM รองรับการนำเข้า เช่นเดียวกับการใช้งานในภาษาอื่นๆ คุณสามารถเขียนโค้ดบางส่วนใน Mash และเขียนโค้ดบางส่วนในภาษาท้องถิ่น จากนั้นจึงเชื่อมโยงเป็นโค้ดเดียว
เครื่องมือแปลจากภาษา Mash ระดับสูงเป็น bytecode สำหรับ VM
ภาษาระดับกลาง
หากต้องการเขียนนักแปลจากภาษาที่ซับซ้อนเป็นโค้ด VM อย่างรวดเร็ว ฉันจึงพัฒนาภาษาระดับกลางก่อน ผลลัพธ์ที่ได้คือภาพที่น่าสยดสยองเหมือนการประกอบซึ่งไม่มีประเด็นใดเป็นพิเศษในการพิจารณาที่นี่ ฉันจะบอกว่าในระดับนี้นักแปลจะประมวลผลค่าคงที่และตัวแปรส่วนใหญ่ คำนวณที่อยู่คงที่และที่อยู่ของจุดเริ่มต้น
สถาปัตยกรรมนักแปล
ฉันไม่ได้เลือกสถาปัตยกรรมที่ดีที่สุดสำหรับการนำไปปฏิบัติ นักแปลไม่ได้สร้างแผนผังโค้ดเหมือนที่นักแปลคนอื่นๆ ทำ เขามองไปที่จุดเริ่มต้นของโครงสร้าง เหล่านั้น. หากส่วนของโค้ดที่ถูกแยกวิเคราะห์ดูเหมือน “ while <condition>:” แสดงว่านี่คือโครงสร้าง while loop และจำเป็นต้องได้รับการประมวลผลเป็นโครงสร้าง while loop บางอย่างเช่นสวิตช์เคสที่ซับซ้อน
ต้องขอบคุณโซลูชันทางสถาปัตยกรรมนี้ นักแปลจึงกลายเป็นคนไม่เร็วมาก อย่างไรก็ตามความง่ายในการปรับเปลี่ยนได้เพิ่มขึ้นอย่างมาก ฉันเติมโครงสร้างที่จำเป็นเร็วกว่าที่กาแฟจะเย็นลง การสนับสนุน OOP เต็มรูปแบบถูกนำมาใช้ภายในเวลาไม่ถึงหนึ่งสัปดาห์
การเพิ่มประสิทธิภาพโค้ด
แน่นอนว่ามันสามารถนำไปปฏิบัติได้ดีขึ้น (และจะถูกนำไปปฏิบัติ แต่ภายหลัง ทันทีที่ใครๆ ก็สามารถนำไปใช้ได้) จนถึงตอนนี้ เครื่องมือเพิ่มประสิทธิภาพรู้เพียงวิธีตัดโค้ดที่ไม่ได้ใช้ ค่าคงที่ และการนำเข้าจากแอสเซมบลีเท่านั้น นอกจากนี้ ค่าคงที่หลายค่าที่มีค่าเดียวกันจะถูกแทนที่ด้วยค่าเดียว นั่นคือทั้งหมดที่
ภาษา
แนวคิดพื้นฐานของภาษา
แนวคิดหลักคือการพัฒนาภาษาที่ใช้งานได้ง่ายและเรียบง่ายที่สุดเท่าที่จะเป็นไปได้ ฉันคิดว่าการพัฒนาสามารถรับมือกับงานของมันได้เป็นอย่างดี
บล็อกโค้ด ขั้นตอน และฟังก์ชัน
โครงสร้างทั้งหมดในภาษาจะเปิดขึ้นด้วยเครื่องหมายโคลอน : และปิดโดยผู้ดำเนินการ ปลาย.
ขั้นตอนและฟังก์ชันได้รับการประกาศเป็น proc และ func ตามลำดับ ข้อโต้แย้งแสดงอยู่ในวงเล็บ ทุกอย่างก็เหมือนกับภาษาอื่นๆ ส่วนใหญ่
โอเปอเรเตอร์ กลับ คุณสามารถคืนค่าจากฟังก์ชัน โอเปอเรเตอร์ได้ ทำลาย อนุญาตให้คุณออกจากขั้นตอน/ฟังก์ชัน (หากอยู่นอกลูป)
รหัสตัวอย่าง:
...
func summ(a, b):
return a + b
end
proc main():
println(summ(inputln(), inputln()))
end
การออกแบบที่รองรับ
- วนซ้ำ: for..end, while..end, until..end
- เงื่อนไข: if..[else..]end, switch..[case..end..][else..]end
- วิธีการ: proc <name>():... end, func <name>():... end
- ป้ายกำกับ & ไปที่: <name>:, ข้าม <name>
- การแจงนับและอาร์เรย์คงที่
ตัวแปร
นักแปลสามารถระบุได้โดยอัตโนมัติ หรือหากนักพัฒนาเขียน 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 ได้
ที่เก็บ
คุณสามารถเล่นกับการพัฒนาและติดตามโครงการได้ด้วยตัวเอง
ขอบคุณที่อ่านจนจบถ้าคุณทำ
ที่มา: will.com