วันอาทิตย์ที่ 7 พฤศจิกายน พ.ศ. 2553

Compiler : Semantic กับ ATG (ภาษา Java)

การตรวจสอบ Semantic
การกระทำนี้จะรายงานความผิดพลาดก็ต่อเมื่อ
- ประกาศตัวแปร Global หรือ Local ในพื้นที่เดียวกัน ด้วยชื่อซ้ำกัน
- เรียกใช้งานตัวแปรที่ไม่ถูกประกาศ
- ให้ค่าตัวแปรด้วย Type ต่างชนิดกัน (ไม่อนุญาตให้แปลง Type)
เป็นต้น

***หมายเหตุ แท้จริงแล้วเพื่อนๆที่ต้องเขียน Compiler ในวิชา Compiler (รหัส CT414) ยังต้องสร้าง Grammar สำหรับฟังก์ชัน การเรียกฟังก์ชัน การกำหนดค่าเริ่มต้นให้กับพารามิเตอร์ และอื่นๆอีกมาก (แล้วแต่ข้อกำหนดของอาจารย์ผู้สอน) ซึ่งบทความเรื่อง "โปรแกรม Compiler" ไม่อาจนำเสนอได้หมด จึงหวังว่าเพื่อนๆจะนำเอาแนวคิดและวิธีการที่กล่าวไว้ในบทความไปประยุกต์ใช้ให้เกิดผลสำเร็จนะครับ

ATG
เรื่องของ Semantic นี้เกี่ยวข้องกับ ATG (Attribute Grammar) อย่างมาก ฉะนั้นผมควรกล่าวถึงสักเล็กน้อย ขออนุญาตให้นิยามในแบบฉบับที่ผมเข้าใจก็แล้วกัน
- ATG คือการอธิบายการทำงาน Grammar โดยอาศัย Attribute ที่เป็นสมาชิกของ Non-Terminal ใน Grammar นั้น
- Non-Terminal แต่ละตัวจะมี Attribute หรือไม่ก็ได้ จะมากหรือน้อยก็ได้ แต่ชื่อต้องไม่ซ้ำกัน
- เมื่อ Non-Terminal ใดมี Attribute แล้ว Attribute เหล่านั้นจะถือเป็นของ Non-Terminal ดังกล่าว ตลอดทั้ง Grammar
- Attribute มีสองประเภทคือ Inherited และ Synthesized
- Attribute ประเภท Inherited จะแสดงถึงการให้ค่า (Copy) จาก Attribute สู่ Attribute โดยอาจเป็น Non-Terminal เดียวกันหรือต่างกันก็ได้
- Attribute ประเภท Synthesized จะแสดงถึงการคืนค่าหรืออ้างอิงค่า (Reference) จาก Attribute สู่ Attribute โดยอาจเป็น Non-Terminal เดียวกันหรือต่างกันก็ได้
- เราเรียก Grammar ที่มีแต่ Synthesized Attribute ว่า S-Attributed และเรียก Grammar ที่ผสมระหว่าง Attribute ทั้งสองประเภทว่า L-Attributed
- Attribute ไม่มี Type ที่แน่นอนเหมือนกับตัวแปร แต่หากประยุกต์เป็นโค้ด Attribute จะมี Type และถือว่าเป็นตัวแปร

ตัวอย่าง
Grammar 1 ก่อนเขียน Semantic

<S> -> <ID>
<ID> -> "a" | "b" | "c"

Grammar 1 หลังเขียน Semantic
<S> -> <ID> { print( ID.synName ); }
<ID> -> "a" { ID.synName = "a" } | "b" { ID.synName = "b" } | "c" { ID.synName = "c" }

    จาก Semantic ข้างต้น Grammar 1 นี้ยอมรับชื่อ a หรือ b หรือ c และต้องการแสดงเป็นผลลัพธ์ผ่านฟังก์ชัน print
- Semantic เขียนอธิบายด้วย ATG ภายใต้เครื่องหมาย { }
- เครื่องหมาย . (dot) หลังชื่อ Non-Terminal คือชื่อ Attribute ฉะนั้นจาก Grammar 1 ข้างต้น ID มี Attribute ชื่อ synName และเป็นประเภท Synthesized
- เราอาจใช้เครื่องหมายลูกศรชี้ลงแทนการ Inherited แต่ผมใช้คำว่า in
- เราอาจใช้เครื่องหมายลูกศรชี้ขึ้นแทนการ Synthesized แต่ผมใช้คำว่า syn เป็นอันว่าทราบร่วมกัน
- เราเรียก Grammar 1 นี้ว่า S-Attributed
- ดาวน์โหลดโปรแกรม Compiler ที่เขียนด้วย Grammar 1 (Run ด้วย NetBeans IDE)

ตัวอย่าง
Grammar 2 ก่อนเขียน Semantic

<S> -> <AddDigit>
<AddDigit> -> <Digit> <AddDigit2>
<AddDigit2> -> "+" <Digit> <AddDigit2> | empty
<Digit> -> "1" | "2" | "3"

Grammar 2 หลังเขียน Semantic
<S> -> { AddDigit.synValue = 0 } <AddDigit> { print( AddDigit.synValue ); }
<AddDigit> -> <Digit> { AddDigit2.inValue = Digit.synValue } <AddDigit2> { AddDigit.synValue = AddDigit2.synValue }
<AddDigit2> -> "+" <Digit> { AddDigit2_1.inValue = AddDigit2.inValue + Digit.synValue } <AddDigit2_1> { AddDigit2.synValue = AddDigit2_1.synValue } | empty { AddDigit2.synValue = AddDigit2.inValue }
<Digit> -> "1" { Digit.synValue = 1 } | "2" { Digit.synValue = 2 } | "3" { Digit.synValue = 3 }

    Grammar 2 นี้ทำงานบวกตัวเลข 1, 2 และ 3 เป็นจำนวนเท่าใดก็ได้ โดยผลลัพธ์สุดท้ายจะเก็บไว้ ณ Attribute ชื่อ synValue ของ Non-Terminal ชื่อ AddDigit

    เริ่มจากกำหนดค่า AddDigit.synValue เป็นศูนย์ (คล้ายกับประกาศตัวแปรชื่อ sum แล้วกำหนดค่าให้เป็นศูนย์เพื่อเก็บผลลัพธ์การบวก แต่แท้จริงไม่ต้องก็ได้ครับ เพราะผลลัพธ์ในทางโค้ดเกิดขึ้นจากการให้ค่าระหว่างตัวแปร) ด้วย Semantic ที่ว่า
{ AddDigit.synValue = 0 }

    เมื่อโปรแกรมทำงานเมธอด AddDigit มันจะตามหาตัวเลขโดยเรียกเมธอด Digit เมธอดนี้จะให้ตัวเลขที่เขียนไว้ภายในไฟล์ SourceCode.txt เราจะส่งตัวเลขดังกล่าวนี้ลงไปยังเมธอด AddDigit2 ผ่าน Attribute ของเมธอด AddDigit2 ที่ชื่อ inValue ด้วย Semantic ที่ว่า
{ AddDigit2.inValue = Digit.synValue }

    กระทั่งเมธอด AddDigit2 นี้ทำงานเสร็จมันจึงส่งผลลัพธ์การบวกทั้งหมดคืนให้กับ AddDigit.synValue ถือว่าเสร็จสิ้นการทำงานของเมธอด AddDigit นี้แล้ว
{ AddDigit.synValue = AddDigit2.synValue }

    เมื่อโปรแกรมทำงานเมธอด AddDigit2 มันจะตรวจสอบเครื่องหมาย + แล้วเรียกเมธอด Digit ตามลำดับ จึงทำให้มันมีตัวเลขสองจำนวนพร้อมที่จะดำเนินการบวก ตัวเลขแรกได้มาจากกระบวนการก่อนหน้านี้ซึ่งเก็บไว้ใน AddDigit2.inValue ตัวเลขที่สองได้มาจากเมธอด Digit นั่นคือ Digit.synValue ภายหลังการบวก (กรณีนี้ผมจะบวกจริงๆ) ผลลัพธ์จะเก็บไว้ยัง AddDigit2.inValue ตามเดิม แต่เนื่องจากชื่อของเมธอด AddDigit2 ที่จะถูกเรียกเป็นลำดับต่อไปนั้นซ้ำกับชื่อของตัวมันเอง จึงต้องใส่ subscript ให้กับ AddDigit2.inValue ภายใน Grammar กลายเป็น AddDigit2_1.inValue (หรือแท้จริงในทางปฏิบัติ (โปรแกรม) ก็คือเมธอด AddDigit2 ตัวเดิมนั่นละครับ)
{ AddDigit2_1.inValue = AddDigit2.inValue + Digit.synValue }

    เมื่อบวกตัวเลขชุดใดๆเสร็จแล้ว (สองจำนวน) เมธอด AddDigit2 จะเรียกตัวเองอีกครั้ง (หลัง Semantic การบวกข้างต้น) เป็นผลให้หากมีตัวเลขจำนวนที่สาม, สี่, หรือต่อๆไป จะถูกบวกค่าแบบ recursive กระทั่งโค้ดใน SourceCode.txt ไม่พบเครื่องหมาย + (ซึ่งมันชักนำการบวก) กระบวนการ recursive จึงจะสิ้นสุดลง เมธอด AddDigit2 นี้จึงคืนค่าการบวกสะสมให้กับ AddDigit2.synValue โดย Semantic ที่ว่า
{ AddDigit2.synValue = AddDigit2_1.synValue }

    ดังที่กล่าวข้างต้นเมธอด AddDigit2 จะยุติการบวกเมื่ออ่านไม่พบเครื่องหมาย + นั่นหมายความว่ามันพบกับ empty เราจึงพยายามส่งผลลัพธ์การบวกสะสมที่เก็บไว้ ณ AddDigit2.inValue ให้กับ AddDigit2.synValue เพื่อผลย้อยรอยของ recursive จะส่งมันต่อไปยังเมธอด AddDigit และแสดงผลลัพธ์เป็นลำดับสุดท้าย
| empty { AddDigit2.synValue = AddDigit2.inValue }

- เราเรียก Grammar 2 นี้ว่า L-Attributed
- ดาวน์โหลดโปรแกรม Compiler ที่เขียนด้วย Grammar 2 (Run ด้วย NetBeans IDE)

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

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