วันจันทร์ที่ 17 ตุลาคม พ.ศ. 2554

อ่าน EBNF ให้เข้าใจ

<< CT414 ภาค 1/54 | ตัดคำ >>

       จากตัวอย่างหน้าที่ผ่านมา เราจะแยกคลาสสำหรับทดสอบซึ่งมีเมธอด main ออกมาต่างหาก และเขียนคลาสที่เกี่ยวข้องกับงานทั้งหมดขึ้นมาใหม่ อาจมีเพียงหนึ่งคลาสหรือมากกว่าก็อยู่ที่การออกแบบเป็นหลักครับ เนื่องจากเวลามีน้อย เนื้อหาหน้านี้เราไปทำความรู้จัก EBNF กันก่อนเลย

EBNF ที่เราจะใช้ศึกษาก็คือโจทย์หรือ grammar ที่น้อง "PetPraUMa" ได้โพสต์ไว้ในกระดานข่าวของภาควิชาวิทยาการคอมฯ ให้ดาวน์โหลดมาดูพร้อมกันได้เลย คลิกที่นี่ ดังที่ได้ยกมาบางส่วนด้านล่างนี้

<S> ::= <Sp> main() { <B> }
<Sp> ::= id() { <B> } <Sp>
<Sp> ::= empty_string

<B> ::= <T> <S1>

<T> ::= <ET> dim id <T1> as <T2> ; <T>
<T> ::= empty_string

<ET> ::= extern
<ET> ::= empty_string

<T1> ::= , id <T1>
<T1> ::= empty_string

...เรื่อยไปนะครับ

EBNF ใช้อธิบายโครงสร้างของภาษาหรือที่เรียกว่า "กฏไวยากรณ์" (syntax) ตัวผลิตจะอยู่ทางซ้ายของเครื่องหมาย ::= ส่วนผลิตผลจะอยู่ทางขวาของเครื่องหมาย ::=

EBNF ประกอบด้วย non-terminal และ terminal
นิยามอย่างง่ายที่สุด non-terminal สามารถแบ่งแยกได้เป็น non-terminal หรือ terminal อีกเท่าใดก็ได้
ส่วน terminal ไม่สามารถแบ่งแยกได้อีกแล้ว

ข้อตกลงทั่วไปสำหรับ non-terminal คือต้องเปิดด้วยเครื่องหมาย < และปิดด้วยเครื่องหมาย > เช่น <S> <B> <T> เป็นต้น ทว่าบางทีอาจารย์ท่านก็เขียนเพียงตัวอักษรเฉยๆ แต่อย่างไรก็คือ non-terminal เหมือนกัน ได้แก่ id ซึ่งย่อมาจาก identifier หมายถึงการตั้งชื่อตามกฎการตั้งชื่อตัวแปรของภาษาใดๆ (เรากำหนดเอาเองหรือเลียนแบบภาษา C ก็ได้)

และข้อตกลงทั่วไปสำหรับ terminal คือเป็นตัวอักษรเฉยๆ (ไม่นับ id นะครับ) จาก grammar ข้างต้น เช่น main เครื่องหมาย ( เครื่องหมาย ) เครื่องหมาย { เครื่องหมาย } เหล่านี้เป็นต้น

สุดท้ายสำหรับ empty_string นั้นหมายถึง ว่างเปล่า คือเลือกที่จะไม่ผลิตสิ่งใดหรือกล่าวว่าไม่มีผลิตผลใดๆ

ควรทราบว่า non-terminal อาจเขียนด้วยตัวพิมพ์ใหญ่ เช่น S B T เฉยๆ ไม่มีเครื่องหมาย < และ > ครอบก็เป็นได้ ขึ้นอยู่กับข้อตกลงและตำราที่ใช้เป็นหลัก ส่วน terminal ก็อาจเขียนเป็นตัวพิมพ์เล็กที่อาจมีเครื่องหมาย " และ " ครอบหรือไม่ก็ได้

เราควรอ่าน grammar ให้เป็น คือสามารถนำมันมาสร้างเป็น source code ได้ ตัวอย่าง

<S> ::= <Sp> main() { <B> }
<Sp> ::= id() { <B> } <Sp>
<Sp> ::= empty_string

ในเมื่อ <S> เป็น non-terminal เริ่มต้นของ grammar นี้ ตัวมันสามารถผลิต <Sp> main() { <B> } อย่างนี้จะสร้างเป็น source code อย่างง่ายที่สุดอย่างไร
- กำหนดให้ <Sp> ผลิตได้ empty_string
- กำหนดให้ <B> ผลิตได้ empty_string
ดังนั้น source code จึงเป็น

main() {
}

อ่านถึงตรงนี้พอเข้าใจไหมครับ ทีนี้หากว่า <Sp> ไม่ได้ผลิตเป็น empty_string แต่กลับได้เป็น id() { <B> } <Sp> และกำหนดให้ <B> ผลิตได้ empty_string ดังเดิม เมื่อนั้นหน้าตา source code ก็จะออกมาอย่างนี้

A() {
}
main() {
}

หรือ

A() {
}
myFunction() {
}
main() {
}

หรือทำนองใดๆ พอเข้าใจนะครับ คล้ายกับการประกาศฟังก์ชันแบบ inline ในภาษา C (ฟังก์ชันในภาษา C เรียกการเขียนฟังก์ชันใดๆไว้ก่อน main ว่า inline function มันจะจองหน่วยความจำเท่าที่ตัวฟังก์ชันระบุทันที โดยไม่สนใจว่าฟังก์ชันดังกล่าวจะถูกเรียกใช้หรือไม่ก็ตาม ต่างจากการเขียนฟักง์ชันหลัง main ซึ่งจะจองหน่วยความจำก็ต่อเมื่อเกิดการเรียกใช้ฟังก์ชันจากภายใน main เท่านั้น)

จาก source code ข้างต้นจะเห็นว่าตำแหน่ง id ของตัวผลิต <Sp> สามารถเปลี่ยนเป็นชื่อใดๆก็ได้ตามหลักการตั้งชื่อตัวแปรทั่วไป เช่น A myFunction B Phai น้องส้มโอน่ารัก หรือใดๆตราบเท่าที่เราต้องการ (ต้องอยู่ในข้อตกลงของอาจารย์ด้วยนะ)

สงสัยหรือต้องการให้เพิ่มเติมอย่างไรก็แสดงความเห็น (comment) ด้านล่างนี้ได้เลยครับ หากเข้าใจแล้วให้คลิกลิงค์สู่หน้าต่อไป

<< CT414 ภาค 1/54 | ตัดคำ >>

ไม่มีความคิดเห็น:

แสดงความคิดเห็น