ทางเลือกแบบพกพามากขึ้นอย่างไม่ จำกัด สำหรับภาษาการเขียนโปรแกรม C

สำหรับคนที่จำได้ว่า "ฟรี" โอ๊คเป็นรุ่นที่แข็งแกร่งและสูงกว่าของโครงการนั้น เป้าหมายของต้นโอ๊กคือระดับสูงที่สุดเท่าที่จะเป็นไปได้ในส่วนหน้า แต่มีขนาดเล็กและต่ำที่สุดเท่าที่จะทำได้ในแบ็กเอนด์
ฉันจบการศึกษาระดับมัธยมปลายและน้องใหม่ในวิทยาลัยที่กำลังมองหางาน หากคุณสนุกกับโครงการของฉันลองสนับสนุนฉันด้วยการซื้อกาแฟให้ฉัน!
กุญแจสำคัญในการพกพาที่บ้าของโอ๊คคือการใช้งานแบ็กเอนด์ขนาดกะทัดรัดอย่างไม่น่าเชื่อ รหัสสำหรับแบ็กเอนด์ของต้นโอ๊กสามารถแสดงได้ในระดับต่ำกว่า 100 บรรทัดของ C การใช้งานขนาดเล็กเช่นนี้เป็นไปได้เพียงเพราะชุดคำสั่งเล็ก ๆ ของการเป็นตัวแทนระดับกลาง IR ของ Oak ประกอบด้วย 17 คำแนะนำที่แตกต่างกัน เท่านั้น เทียบเท่ากับ Brainfuck!
แบ็กเอนด์ของโอ๊คทำหน้าที่ได้ง่ายมาก คำสั่งทุกคำสั่งทำงานบน เทปหน่วยความจำ เทปนี้เป็นอาร์เรย์แบบคงที่ของการลอยตัวสองเท่า
let x : num = 5.25 ; ... let p : & num = & x ; `beginning of heap`
| | |
v v v
[ 0 , 0 , 0 , 5.25 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 3 , 0 , 0 , 0 , 0 , 0 , 0 , ... ]
^
|
`current location of the stack pointer`เมื่อตัวแปรถูกกำหนดไว้ในฟังก์ชันจะได้รับตำแหน่งคงที่เทียบกับตัวชี้ฐานปัจจุบันของเครื่องเสมือน ดังนั้นเมื่อมีการเรียกฟังก์ชั่นพื้นที่สำหรับตัวแปรของฟังก์ชั่นจะถูกจัดสรรบนสแต็กและตัวชี้ฐานจะเพิ่มขึ้นเพื่อใช้พื้นที่ใหม่นี้ จากนั้นคอมไพเลอร์เพียงแทนที่ตัวแปรด้วยที่อยู่ที่เพิ่มไปยังตัวชี้ฐานในส่วนที่เหลือของรหัส!
นอกจากนี้เทปหน่วยความจำทำหน้าที่เป็น สแต็ก และ กอง หลังจากที่มีพื้นที่สำหรับตัวแปรทั้งหมดของโปรแกรมถูกกำหนดหน่วยความจำที่ใช้สำหรับสแต็กจะเริ่มขึ้น สแต็ก เติบโต และ หดตัว ด้วยข้อมูลตลอดโปรแกรม: เมื่อมีการรวมตัวเลขสองตัวตัวอย่างเช่นพวกเขาจะถูกผุดออกจากสแต็กและแทนที่ด้วยผลลัพธ์ ในทำนองเดียวกันกองเติบโตและหดตัวตลอดโปรแกรม อย่างไรก็ตามฮีปใช้สำหรับข้อมูล ที่จัดสรรแบบไดนามิก : ข้อมูลที่มีรอยเท้าหน่วยความจำ ที่ไม่รู้จักในเวลาคอมไพล์
ตอนนี้คุณเข้าใจว่าแบ็กเอนด์ของ Oak ดำเนินการโดยพื้นฐานแล้วนี่คือชุดคำสั่งที่สมบูรณ์!
| คำแนะนำ | ผลข้างเคียง |
|---|---|
push(n: f64); | กดหมายเลขลงบนสแต็ก |
add(); | ป๊อปตัวเลขสองตัวออกจากสแต็กและผลักผลรวมของพวกเขา |
subtract(); | ป๊อปสองตัวเลขจากสแต็ค ลบครั้งแรกจากครั้งที่สองและผลักผลลัพธ์ |
multiply(); | ป๊อปตัวเลขสองตัวจากสแต็กและผลักดันผลิตภัณฑ์ของพวกเขา |
divide(); | ป๊อปสองตัวเลขจากสแต็ค หารครั้งที่สองโดยครั้งแรกและผลักดันผลลัพธ์ |
sign(); | วางตัวเลขออกจากสแต็ก หากมีมากขึ้นหรือเท่ากับศูนย์กด 1 มิฉะนั้นผลัก -1 |
allocate(); | วางตัวเลขออกจากสแต็กแล้วส่งคืนตัวชี้ไปยังจำนวนเซลล์อิสระบนกอง |
free(); | วางตัวเลขออกจากสแต็กแล้วไปที่จุดที่ตัวเลขนี้อยู่ในหน่วยความจำ วางหมายเลขอื่นจากสแต็กและปลดปล่อยเซลล์จำนวนมากในตำแหน่งนี้ในหน่วยความจำ |
store(size: i32); | วางตัวเลขออกจากสแต็กแล้วไปที่จุดที่ตัวเลขนี้อยู่ในหน่วยความจำ จากนั้นตัวเลข size ป๊อปออกจากสแต็ก จัดเก็บตัวเลขเหล่านี้ตามลำดับย้อนกลับที่ตำแหน่งนี้ในหน่วยความจำ |
load(size: i32); | วางตัวเลขออกจากสแต็กแล้วไปที่จุดที่ตัวเลขนี้อยู่ในหน่วยความจำ จากนั้นผลักจำนวน size ของเซลล์หน่วยความจำติดต่อกันไปยังสแต็ก |
call(fn: i32); | เรียกฟังก์ชั่นที่ผู้ใช้กำหนดโดย IT ของคอมไพเลอร์ที่กำหนดไว้ |
call_foreign_fn(name: String); | เรียกฟังก์ชั่นต่างประเทศด้วยชื่อในแหล่งที่มา |
begin_while(); | เริ่มวนซ้ำ สำหรับการทำซ้ำแต่ละครั้งให้วางหมายเลขจากสแต็ก หากหมายเลขไม่เป็นศูนย์ให้ดำเนินการต่อ |
end_while(); | ทำเครื่องหมายจุดสิ้นสุดของการวนรอบในขณะที่ |
load_base_ptr(); | โหลดตัวชี้ฐานของเฟรมสแต็กที่กำหนดไว้ซึ่งมักจะน้อยกว่าหรือเท่ากับตัวชี้สแต็ก ตัวแปรจะถูกเก็บไว้สัมพันธ์กับตัวชี้ฐานสำหรับแต่ละฟังก์ชัน ดังนั้นฟังก์ชันที่กำหนด x: num และ y: num , x อาจถูกเก็บไว้ที่ base_ptr + 1 และ y อาจถูกเก็บไว้ที่ base_ptr + 2 สิ่งนี้ช่วยให้ฟังก์ชั่นจัดเก็บตัวแปรในหน่วยความจำแบบไดนามิกและตามต้องการแทนที่จะใช้ตำแหน่งหน่วยความจำแบบคงที่ |
establish_stack_frame(arg_size: i32, local_scope_size: i32); | ปรากฏจำนวนเซลล์ arg_size ออกจากสแต็กแล้วเก็บไว้ จากนั้นเรียกใช้ load_base_ptr เพื่อกลับมาใช้เฟรมสแต็กหลักเมื่อฟังก์ชั่นนี้สิ้นสุดลง กดจำนวนศูนย์ local_scope_size ลงบนสแต็กเพื่อให้มีที่ว่างสำหรับตัวแปรของฟังก์ชั่น ในที่สุดผลักดันเซลล์อาร์กิวเมนต์ที่เก็บไว้กลับไปยังสแต็กตามที่พวกเขาได้รับคำสั่งเดิม |
end_stack_frame(return_size: i32, local_scope_size: i32); | ปรากฏขึ้น return_size จำนวนเซลล์ออกจากสแต็กแล้วเก็บไว้ จากนั้นป๊อป local_scope_size จำนวนเซลล์ออกจากสแต็กเพื่อทิ้งหน่วยความจำของเฟรมสแต็ก นำค่าออกจากสแต็กแล้วเก็บไว้ในตัวชี้ฐานเพื่อกลับมาเฟรมสแต็กหลัก ในที่สุดผลักดันเซลล์ค่าคืนกลับที่เก็บไว้กลับไปยังสแต็กตามที่พวกเขาได้รับคำสั่งเดิม |
การใช้คำแนะนำเหล่านี้เท่านั้นโอ๊คสามารถใช้ นามธรรมระดับสูงกว่า C สามารถเสนอได้ !!! นั่นอาจฟังดูไม่มากนัก แต่มันทรงพลังมากสำหรับภาษาขนาดเล็กนี้
ไวยากรณ์ของต้นโอ๊กได้รับแรงบันดาลใจอย่างมากจากภาษาการเขียนโปรแกรมสนิม
ฟังก์ชั่นจะถูกประกาศด้วยคำหลัก fn และมีลักษณะคล้ายกันกับฟังก์ชั่นการเกิดสนิมยกเว้นความหมายของ return นอกจากนี้ประเภทและค่าคงที่ที่ผู้ใช้กำหนดจะถูกประกาศด้วยคำหลัก type และ const ตามลำดับ
เช่นเดียวกับคุณลักษณะภายนอกของ Rust Oak แนะนำธงเวลาคอมไพล์จำนวนมาก บางส่วนของสิ่งเหล่านี้แสดงให้เห็นด้านล่างพร้อมกับคุณสมบัติไม้โอ๊คอื่น ๆ

แล้วคอมไพเลอร์โอ๊คทำงานอย่างไร?
โครงสร้างแบนลงในฟังก์ชั่นของพวกเขา
putnumln(*bday.day) กลายเป็น putnumln(*Date::day(&bday)) นี่เป็นกระบวนการที่ค่อนข้างง่ายคำนวณขนาดของทุกประเภทการดำเนินการ
// `3` is the size of the structure on the stack
fn Date :: new ( month : 1 , day : 1 , year : 1 ) -> 3 {
month ; day ; year
}
// self is a pointer to an item of size `3`
fn Date :: day ( self : & 3 ) -> & 1 { self + 1 }
fn main ( ) -> 0 {
let bday : 3 = Date :: new ( 5 , 14 , 2002 ) ;
}คำนวณรอยเท้าหน่วยความจำของโปรแกรมแบบคงที่
แปลงนิพจน์และคำสั่งของต้นโอ๊กเป็นคำแนะนำ IR ที่เทียบเท่า
มีสถานการณ์ที่แตกต่างกัน มากมาย ที่มีการเรียกวิธีการที่ถูกต้อง วิธีการ ใช้ตัวชี้ไปยังโครงสร้างเป็นอาร์กิวเมนต์เสมอ อย่างไรก็ตาม วัตถุที่เรียกว่าวิธีการไม่จำเป็นต้องเป็นตัวชี้ ตัวอย่างเช่นรหัสต่อไปนี้ถูกต้อง: let bday: Date = Date::new(); bday.print(); - ตัวแปร bday ไม่ใช่ตัวชี้ แต่ยังสามารถใช้วิธี .print() นี่คือเหตุผล
เมื่อคอมไพเลอร์เห็นการเรียกวิธีการแบนมันต้องหาวิธีในการแปลง "นิพจน์อินสแตนซ์" เป็นตัวชี้ สำหรับตัวแปรนี่เป็นเรื่องง่าย: เพิ่มการอ้างอิง! ตัวอย่างเช่นนิพจน์ที่มีพอยน์เตอร์อยู่แล้วมันง่ายกว่า: อย่าทำอะไรเลย! สำหรับการแสดงออกประเภทอื่น ๆ มันเป็นคำอื่น ๆ อีกเล็กน้อย คอมไพเลอร์ย่องในตัวแปรที่ซ่อนอยู่เพื่อเก็บนิพจน์จากนั้นรวบรวมการเรียกวิธีการอีกครั้งโดยใช้ตัวแปรเป็นนิพจน์อินสแตนซ์ สวยเจ๋งใช่มั้ย?
รวบรวมคำแนะนำ IR สำหรับเป้าหมาย
Target หากคุณใช้คำแนะนำของ IR แต่ละคำสำหรับภาษาของคุณโดยใช้ลักษณะ Target OAK สามารถรวบรวมได้โดยอัตโนมัติไปจนถึงการเขียนโปรแกรมใหม่หรือภาษาแอสเซมบลีของคุณ! ใช่มันง่ายอย่างที่มันฟัง! เพื่อให้ผู้ใช้สามารถอ่านเอกสารประกอบของไลบรารีและไฟล์ที่ไม่สามารถเข้าถึงอินเทอร์เน็ตได้ Oak จะจัดเตรียมคำสั่งย่อย doc สิ่งนี้ช่วยให้ผู้เขียนสามารถเพิ่มแอตทริบิวต์เอกสารลงในรหัสของพวกเขาเพื่อช่วยให้ผู้ใช้รายอื่นเข้าใจรหัสหรือ API ของพวกเขาโดยไม่ต้องผ่านแหล่งข้อมูลและอ่านความคิดเห็น
นี่คือรหัสตัวอย่างบางส่วน
# [ std ]
# [ header ( "This file tests Oak's doc subcommand." ) ]
# [ doc ( "This constant is a constant." ) ]
const CONSTANT = 3 ;
// No doc attribute
const TEST = CONSTANT + 5 ;
# [ doc ( "This structure represents a given date in time.
A Date object has three members:
|Member|Value|
|-|-|
|`month: num` | The month component of the date |
|`day: num` | The day component of the date |
|`year: num` | The year component of the date |" ) ]
struct Date {
let month : num , day : num , year : num ;
# [ doc ( "The constructor used to create a date." ) ]
fn new ( month : num , day : num , year : num ) -> Date {
return [ month , day , year ] ;
}
# [ doc ( "Print the date object to STDOUT" ) ]
fn print ( self : & Date ) {
putnum ( self ->month ) ; putchar ( '/' ) ;
putnum ( self ->day ) ; putchar ( '/' ) ;
putnumln ( self ->year ) ;
}
}
# [ doc ( "This function takes a number `n` and returns `n * n`, or `n` squared." ) ]
fn square ( n : num ) -> num {
return n * n
}
fn main ( ) {
let d = Date :: new ( 5 , 14 , 2002 ) ;
d . print ( ) ;
} และนี่คือตัวอย่างการใช้งานของคำสั่งย่อย doc เพื่อพิมพ์เอกสารที่จัดรูปแบบไปยังเทอร์มินัล

เพื่อให้ได้การพัฒนาในปัจจุบันให้โคลนที่เก็บและติดตั้ง
git clone https://github.com/adam-mcdaniel/oakc
cd oakc
cargo install -f --path . หากต้องการรับการสร้างรุ่นปัจจุบันให้ติดตั้งจาก Cute.io
# Also works for updating oakc
cargo install -f oakcจากนั้นไฟล์ไม้โอ๊คสามารถรวบรวมได้ด้วย OAKC ไบนารี
oak c examples/hello_world.ok -c
main.exeC Backend - คอมไพเลอร์ GCC ใด ๆ ที่รองรับ C99
Go Backend - Golang 1.14 Compiler
แบ็กเอนด์ TypeScript - TypeScript 3.9 Compiler