วันอังคารที่ 29 มีนาคม พ.ศ. 2554

ออกแบบ Grammar วิชา Compiler

       ก่อนเข้าสู่ตัวอย่างวิธีการออกแบบ รบกวนเพื่อนๆอ่านบทความหน้านี้ก่อนครับ Compiler : ออกแบบ Grammar และขออนุญาตใช้ความคิดเห็นของเพื่อนชื่อ boyle หรือเพื่อน บอย เป็นโครงหลักของเนื้อหานะครับ อ่านความคิดเห็นของ boyle (อยู่ด้านล่างของหน้า)

       ต่อไปนี้เป็นข้อตกลงใหม่ระหว่างผมกับเพื่อนๆนะครับ เนื่องจากการเขียนเครื่องหมาย < และ > ค่อนข้างลำบากสำหรับผมนิดหน่อย (เพราะต้องเขียนหน้า html และพอ update หน้าเพจแล้วมันเช้าใจว่าผมเขียนแท็ก html จึงแสดงผลผิดเพี้ยน) เอาเป็นว่า

- สำหรับ non-terminal ผมจะเขียนด้วยพิมพ์ใหญ่ เช่น START, VARIABLE เป็นต้น
- สำหรับ terminal ผมจะเขียนด้วยพิมพ์เล็กมีเครื่องหมาย " และ " ครอบด้วย เช่น "start", "variable" เป็นต้น
- ใช้เครื่องหมาย ::= แทนการผลิตจาก non-terminal เป็น terminal นะครับ (เพื่อนๆจะใช้เครื่องหมายลูกศร -> ก็ได้)

       เพื่อนๆหลายคนสงสัยว่าเมื่อเริ่มออกแบบ grammar นั้นควร ออกแบบอย่างไร ควรเริ่มจากเปลี่ยน source code ตัวอย่างเป็น grammar ดีไหม หรือจาก grammar เป็น source code ดีกว่า...อื่ม อันนี้แล้วแต่ความเข้าใจและความถนัดของแต่ละคนครับ (ยิ้ม)

       ส่วนตัวผมแล้วต้องใช้ทั้งสองแบบ กล่าวคือใช้แบบ source code เปลี่ยนเป็น grammar ก่อนห้าสิบเปอร์เซ็นต์ แล้วที่เหลือก็ใช้แบบ grammar เปลี่ยนเป็น source code ครับ เพราะอย่างไรก็ต้องจินตนาการถึง source code อื่นๆที่สามารถเกิดขึ้นได้อยู่ดี หรือกล่าวว่าเราไม่สามารถเขียน source code ทั้งหมดออกมาได้ แต่สามารถกำหนดรูปแบบของมันได้โดยใช้ grammar นั่นเอง เอาละครับมาเริ่มกันเลยนะ (ยิ้มให้อีกแล้วนะ)

ตัวอย่างที่ 1
var a

       ง่ายๆก่อนนะครับ source code นี้เริ่มต้นด้วยคำว่า var แล้วตามด้วยตัวแปรชื่อ a แน่นอนว่าต่อไป a อาจเปลี่ยนเป็น b หรือ c หรือ love_you อะไรก็ได้จริงไหมครับ (ก็มันเป็นชื่อตัวแปร) ส่วนคำว่า var นั่นไม่เปลี่ยนแปลงเพราะมันเป็นคำสงวน ดังนั้นหากเป็นผมจะออกแบบ grammar ดังนี้ครับ

START ::= "var" VARIABLE
VARIABLE ::= LETTER { LETTER | DIGIT | "_" }
LETTER ::= a | b | c | ... | x | y | z | A | B | C | ... | X | Y | Z
DIGIT ::= 0 | 1 | 2 | ... | 7 | 8 | 9

***หมายเหตุ
- เครื่องหมาย { และ } หมายถึงผลิตเป็นจำนวนเท่าใดก็ได้ หยุดได้ตามต้องการ หรือเรียกว่า zero or more
- เครื่องหมาย | หมายถึง หรือ


ตัวอย่างที่ 2
var a, b, c : int

       source code ข้างต้นนี้เพิ่มการประกาศแบบหลายตัวในครั้งเดียว และยังกำหนดชนิดข้อมูล (type) ได้อีกต่างหาก เช่น int หรือ float หรือ double หรือ หรือ char หรือ string หรือ bool เป็นต้น (จินตนานการเอานะครับ ว่ามันอาจเป็นอะไรได้อีก ก็กำหนดลงใน grammar เลย) จาก grammar ในตัวอย่างที่ 1 เราเพิ่ม grammar ส่วนที่เหลือดังนี้ครับ

START ::= "var" VARIABLE { "," VARIABLE } ":" TYPE
TYPE ::= "int" | "float" | "double" | "char" | "string" | "bool"


ตัวอย่างที่ 3
b:real
c: bool
d : string


       ตัวอย่างนี้เราสามารถประกาศตัวแปรหรือกำหนดชนิดข้อมูลให้กับตัวแปรได้ภายหลังครับ ดังนั้นผมจึงเปลี่ยนแปลง grammar ในตัวอย่างที่ 2 เสียก่อน (เขียนแต่หลักๆนะครับ) แล้วเพิ่ม grammar ของตัวอย่างที่ 3 เข้าไปดังนี้

START ::= DECLARE1 | { DECLARE2 }
DECLARE1 ::= "var" VARIABLE { "," VARIABLE } ":" TYPE
DECLARE2 ::= VARIABLE ":" TYPE

       grammar ข้างต้นนี้เราสามารถกระทำตาม source code ของตัวอย่างที่ 3 ได้ไม่จำกัดจำนวนครั้ง ทว่าสำหรับตัวอย่างที่ 2 นั้นสามารถกระทำได้เพียงครั้งเดียว ดังนั้นหากต้องการให้สามารถประกาศตัวแปรในตัวอย่างที่ 2 ได้ไม่จำกัดจำนวนครั้งเช่นกัน จึงปรับปรุงโดยเพิ่มเครื่องหมาย { } ครับ

START ::= { DECLARE1 } | { DECLARE2 }


ตัวอย่างที่ 4
While() do; ตามด้วย source code อื่นๆแล้วจบด้วย end;

       เข้าสู่ while ลูปแล้วนะครับ หมายความว่าหากเป็นโปรแกรมจริงๆมันจะกระทำตามรอบที่ถูกกำหนดเป็น expression หรือเงื่อนไข (condition) ดังนั้นเราอาจออกแบบ grammar ได้ดังนี้

START ::= VARIABLE_DECLARATION | BODY
VARIABLE_DECLARATION ::= { DECLARE1 } | { DECLARE2 }
BODY ::= { WHILE }
WHILE ::= "while" "(" EXPR ")" "do" ";" STATEMENT "end" ";"

***หมายเหตุ
- EXPR หมายถึง expression หมายถึงเงื่อนไขตรรกศาสตร์หรือการคำนวณค่าต่างๆ ที่ให้ผลเป็น true หรือ false หรือค่า (value) ซึ่งจะแสดงในภายหลัง
- STATEMENT ในที่นี้หมายถึงการกำหนดค่าให้ตัวแปร การใช้ while หรือ if หรือการเรียกฟังก์ชัน เป็นต้น ซึ่งจะแสดงภายหลัง
- grammar ไม่มีผลบังคับให้เกิดรอบนะครับ เราออกแบบ grammar เพื่อกำหนด syntax ของภาษาเท่านั้น ส่วนรอบเราใช้ semantic ช่วยสร้างโค้ดภาษาเครื่องก่อน แล้วโค้ดภาษาเครื่องจึงทำให้เกิดรอบจริงๆอีกทีหนึ่ง หากมันสามารถรันได้จริงอะนะ


ตัวอย่างที่ 5
if() then ตามด้วย source code อื่นๆแล้วจบด้วย end;

       ก็คล้ายกับตัวอย่างที่ 4 ครับเพียงแต่เพิ่ม if เข้ามา สามารถออกแบบเพิ่มได้ดังนี้ครับ

BODY ::= WHILE | { IF }
IF ::= "if" "(" EXPR ")" "then" STATEMENT "end" ";"

       และหากว่า if นี้มี else ด้วยแล้ว ซึ่งอาจมีหน้าตา source code ดังนี้

if ( a > b ) then
       a = a + 1;
else
       b = b * 10;
end;

       เช่นนั้นสามารถปรับเปลี่ยน grammar ได้ดั่งใจฝัน (เวอร์จริงๆ)

IF ::= "if" "(" EXPR ")" "then" STATEMENT [ "else" STATEMENT ] "end" ";"
STATEMENT ::= { ASSIGNMENT | WHILE | IF }
ASSIGNMENT ::= VARIABLE "=" EXPR ";"

***หมายเหตุ
- เครื่องหมาย [ และ ] หมายถึง มี หรือ ไม่มี ก็ได้ หากมีสามารถมีได้เพียงหนึ่ง หรือเรียกว่า zero or one


       เป็นอย่างไรบ้างครับ จากตัวอย่างข้างต้นพอเข้าใจไหม ค่อยๆลองเขียนนะครับ ผิดถูกอย่างไรให้ลองไล่ด้วย source code ที่ต้องการให้เป็นไปทีละประโยค ขาดก็แก้ไข เกินก็ตัดออก ต่อไปนี้คือ grammar ทั้งหมดตั้งแต่เริ่มต้น (ไม่มีรายละเอียดของ EXPR นะครับ)

START ::= VARIABLE_DECLARATION | BODY

VARIABLE_DECLARATION ::= { DECLARE1 } | { DECLARE2 }
DECLARE1 ::= "var" VARIABLE { "," VARIABLE } ":" TYPE
DECLARE2 ::= VARIABLE ":" TYPE

VARIABLE ::= LETTER { LETTER | DIGIT | "_" }
LETTER ::= a | b | c | ... | x | y | z | A | B | C | ... | X | Y | Z
DIGIT ::= 0 | 1 | 2 | ... | 7 | 8 | 9

TYPE ::= "int" | "float" | "double" | "char" | "string" | "bool"

BODY ::= { WHILE } | { IF }

WHILE ::= "while" "(" EXPR ")" "do" ";" STATEMENT "end" ";"

IF ::= "if" "(" EXPR ")" "then" STATEMENT [ "else" STATEMENT ] "end" ";"

STATEMENT ::= { ASSIGNMENT | WHILE | IF }

ASSIGNMENT ::= VARIABLE "=" EXPR ";"

       เราสามารถปรับเปลี่ยน grammar ให้ดียิ่งขึ้น โดยเปลี่ยนการผลิตของ non-terminal ชื่อ BODY และ STATEMENT เสียใหม่ แน่นอนว่าผลของมันทำให้ grammar สามารถกำหนดค่าให้ตัวแปร (ASSIGNMENT) ได้ก่อนการใช้ while หรือ if อีกด้วย ดังนี้

BODY ::= { STATEMENT }
STATEMENT ::= ASSIGNMENT | WHILE | IF

       หากเราต้องการให้ grammar สามารถประกาศตัวแปร (VARIABLE_DECLARATION) ภายใน while หรือ if ได้ ก็เพียงแต่ปรับ grammar เป็นดังนี้

STATEMENT ::=
       ASSIGNMENT | WHILE | IF | VARIABLE_DECLARATION


ตัวอย่างที่ 6
Program CT414; //ประกาศฟัง์ชันชื่อ CT414
var a:int
b:real
c:bool
Begin
       While( a > b + 2 ) do;
              a = a - 1;
       end;
       sub();
//เรียกซับฟังก์ชันชื่อ sub ในในฟังก์ชันอีกที
end;

Begin
//เริ่มต้นฟังก์ชัน main
       CT414(); //เรียกซับฟังก์ชันชื่อ CT414
end. //สิ้นสุดฟังก์ชัน main

       ตัวอย่าง source code ข้างต้นนี้ก็ไม่ยากนัก หากเพื่อนๆสามารถเข้าใจตัวอย่างก่อนหน้านี้ได้ทั้งหมด (ค่อยเป็นค่อยไปนะครับ ผมให้กำลังใจ และเข้าใจผู้ที่เริ่มต้นออกแบบทุกคน เพราะผมก็ไม่มีใครสอนเป็นพิเศษ ใช้ทั้งความจำและการสังเกตเรียนรู้ด้วยตัวเองเรื่อยมา ผิดถูกอย่างไรขออภัยด้วยครับ) เราอาจแบ่ง grammar ออกเป็นสองส่วนเสียก่อน ดังนี้ (rewrite grammar เดิม เขียนเฉพาะหลักๆนะครับ)

START ::= { FUNCTION_DECLARATION } MAIN

       กำหนดให้ FUNCTION_DECLARATION คือส่วนการประกาศฟังก์ชัน เช่น Program CT414 และให้ MAIN คือส่วน main function

FUNCTION_DECLARATION ::=
       "Program" VARIABLE ";" FUNCTION_BODY
FUNCTION_BODY ::= VARIABLE_DECLARATION "Begin" BODY "end" ";"

MAIN ::= "Begin" BODY "end" "."

       สุดท้ายปรับ STATEMENT ให้สามารถเรียกซับฟังก์ชัน (sub function หรือฟังก์ชันย่อย) ดังต่อไปนี้จึงเป็นอันว่าแล้วเสร็จครับ

STATEMENT ::=
       ASSIGNMENT | WHILE | IF | VARIABLE_DECLARATION
       | CALL_FUNCTION

CALL_FUNCTION ::= VARIABLE "(" [ ARGUMENT ] ")" ";"

***หมายเหตุ
- ARGUMENT นี้หมายถึง ตัวแปรหรือค่าคงที่ (const) หรือ EXPR ที่ต้องการส่งให้กับฟังก์ชัน เพราะบางฟังก์ชันสามารถรับค่าได้ แต่บางฟังก์ชันก็ไม่สามารถรับค่าได้ ดังนั้นควรออกแบบให้ มี หรือ ไม่มี ก็ได้ จึงเลือกใช้เครื่องหมาย [ ] ครับ

       หวังว่าเนื้อหาเหล่านี้คงช่วยให้เพื่อนๆสามารถออกแบบ grammar ได้เองในระดับหนึ่ง ถือว่าเป็นการเล่าสู่กันฟังแล้วกันนะครับ โอกาศหน้าค่อยเจอกันใหม่ พยายามเข้านะครับ ^^

อ่านเนื้อหาที่เกี่ยวข้อง ถัดไป

วันอังคารที่ 8 มีนาคม พ.ศ. 2554

ความหมายและหน้าที่ของคำเหล่านี้ class, static, public, void ?

       คำถามนี้เป็นที่สงสัยมากสำหรับว่าที่โปรแกรมเมอร์ เรามักพบคำเหล่านี้เมื่อเริ่มศึกษาแนวคิด Object Oriented Programming (OOP) ซึ่งมีหน้าที่และความหมายพอสังเขปดังนี้ครับ
- อ่านเพิ่มเติม หลักการเชิงวัตถุ

class
- ความหมาย คำสงวนที่ระบุความเป็น abstract data type หรือชนิดข้อมูลนามธรรม
- หน้าที่ ใช้นิยามชนิดข้อมูลใหม่
- ตัวอย่าง

  class Student {
  }

       ขณะนี้เราได้นิยามชนิดข้อมูลใหม่ ซึ่งแนวคิด OOP ให้ใช้คำสงวน class เป็นตัวระบุการนิยามดังกล่าว และตั้งชื่อมันว่า Student เป็นเหตุให้ต่อไปนี้คอมไพเลอร์จะรู้จักชนิดข้อมูลชื่อ Student และสามารถนำไปสร้างเป็นออบเจ็กต์ได้ในอนาคต


static
- ความหมาย โมดิไฟเออร์ (modifier) ลักษณะหนึ่งที่ระบุความคงที่หรือเรียกว่า อพลวัต
- หน้าที่ ระบุให้สมาชิกนั้นๆ (ที่กำหนดเป็น static แล้ว) เป็นของคลาส ไม่ใช่เป็นของออบเจ็กต์
- อ่านเพิ่มเติม บทความ Java และคำ static
- ตัวอย่าง สมาชิกเมธอดที่ไม่เป็น static และเป็น static

  class Student {
    public String getStudentName() { }
    public static String getSchoolName() { }
  }

       ขณะนี้เราเพิ่มสมาชิกของคลาส Student อีกสองสมาชิกซึ่งเป็นสมาชิกประเภทเมธอด (เรียกว่า เมมเบอร์ฟังก์ชัน สำหรับภาษา C++) ชื่อ getStudentName และ getSchoolName
- getStudentName ไม่เป็น static ใช้สำหรับขอดูชื่อของออบเจ็กต์นักเรียนใดๆ (อาจได้ค่าไม่เหมือนกัน)
- getSchoolName เป็น static ใช้สำหรับขอดูชื่อโรงเรียนของทุกออบเจ็กต์ที่สร้างจากคลาส Student (ได้ค่าเหมือนกันหมด)
       ในความเป็นจริงแล้ว เมื่อใช้หลักการเชิงวัตถุ (OOP) นักเรียนหนึ่งคนย่อมมีชื่อเป็นของตัวเอง เช่น แดง, ดำ, เขียว และอาจมีนักเรียนหลายคนที่ชื่อซ้ำกัน เมื่อมีใครคนหนึ่ง (สมมติเป็นบุคคลภายนอก) ถามว่า แดง, ดำ, เขียว สามคนนี้เรียนอยู่โรงเรียนชื่ออะไร แน่นอนว่าสามออบเจ็กต์ดังกล่าวเรียนอยู่โรงเรียนเดียวกัน จึงตอบว่า "โรงเรียนเด็กดีวิทยาคม" (ตัวอย่าง) ครับ


public
- ความหมาย เป็นสาธารณะ
- หน้าที่ ระบุให้คลาส, สมาชิก ใดๆถูกเข้าถึงหรือเรียกใช้งานได้อย่างอิสระ
- อ่านเพิ่มเติม ความแตกต่างของโมดิไฟเออร์, ตัวอย่างการใช้โมดิไฟเออร์


void
- ความหมาย ไม่ส่งค่ากลับ
- หน้าที่ ระบุให้สมาชิกเมธอดไม่ส่งค่ากลับไป ณ จุดเรียก (ซึ่งอาจถูกเรียก ณ main หรือเมธอดใดๆ) เมธอดนั้นจะทำงานแล้วจบไป
- ตัวอย่าง เราไปซื้อของที่ร้านสะดวกซื้อ และต้องจ่ายค่าสินค้าเป็นเงิน 150 บาท เราให้เงินเขาไป 200 บาท เขาจึงทอนเงินกลับมา 50 บาท เมื่อนำพฤติกรรมนี้มาเขียนเป็นเมธอดโดยตั้งชื่อว่า calculate เราอาจออกแบบได้ประมาณนี้

  public double calculate( ราคาสินค้าทั้งหมดของลูกค้า, เงินที่ได้รับจากลูกค้า ) {
    return เงินที่ได้รับจากลูกค้า - ราคาสินค้าทั้งหมดของลูกค้า;
  }

       เพื่อนๆจะสังเกตว่าผลลัพธ์จากการคำนวน "เงินที่ได้รับจากลูกค้า - ราคาสินค้าทั้งหมดของลูกค้า" อาจเป็นทศนิยมหรือไม่ก็ได้ เราจึงเลือกใช้ชนิดข้อมูล double เป็นชนิดข้อมูลของค่าที่ส่งออกมา (ตามตัวอย่างคือ 50.00 นั่นเอง) เรียกเมธอดที่ส่งผลลัพธ์กลับออกมานี้ว่า เมธอดที่มีการคืนค่า
       พฤติกรรมบางอย่างไม่ต้องการส่งผลลัพธ์กลับออกมา เราจึงระบุชนิดการส่งกลับเป็น void เรียกว่า เมธอดที่ไม่คืนค่า เช่น เรามีกระปุกออมสินน้องหมูสดใส ดังนั้นพฤติกรรมหลักๆที่เกี่ยวข้องกับมัน ได้แก่
- การหยอดเงินใส่กระปุก ตั้งชื่อว่า put
- การเอาเงินออกจากกระปุก ตั้งชื่อว่า out
เราอาจออกแบบเมธอด put ได้ประมาณนี้

  public void put( จำนวนเงิน ) {
    เงินสะสมทั้งหมด = เงินสะสมทั้งหมด + จำนวนเงิน;
  }

ส่วนเมธอด out เป็นลักษณะส่งผลลัพธ์กลับออกมา ดังนั้นจึงไม่ขอยกตัวอย่างอีกนะครับ

วันพฤหัสบดีที่ 3 มีนาคม พ.ศ. 2554

Compiler : Code Generator

       เมื่อได้ intermediate code หรือโค้ดกลางจากกระบวนการ Parser กระบวนการ Code Generator จะสร้างผลลัพธ์สุดท้าย (target code) เป็นภาษาเครื่องหรือภาษาใดๆอย่างไรนั้น ก็ขึ้นอยู่กับข้อตกลงของอาจารย์ผู้สอนหรือตามแต่ความต้องการของผู้เขียนโปรแกรมเอง และในขณะที่ผมเรียนเรื่องนี้นั้น มีข้อตกลงร่วมกันดังนี้

ตัวอย่าง *, 2, 1, t0 จะแปลงเป็น
1. LOAD  R1  2
2. MUL  R1  1
3. STORE  R1  t0

ตัวอย่าง /, t0, 3, t1 จะแปลงเป็น
4. LOAD  R1  t0
5. DIV  R1  3
6. STORE  R1  t1

ตัวอย่าง +, 3, t1, t2 จะแปลงเป็น
7. LOAD  R1  3
8. ADD  R1  t1
9. STORE  R1  t2

ตัวอย่าง -, t2, 1, t3 จะแปลงเป็น
10. LOAD  R1  t2
11. SUB  R1  1
12. STORE  R1  t3

ตัวอย่าง =, t3,  , a จะแปลงเป็น
13. LOAD  R1  t3
14. STORE  R1  a

       จากตัวอย่างข้างต้นนี้ intermediate code ทั้งหมดสร้างจาก source code เดียวกัน ผมตั้งใจแยกมันออกเป็นตัวอย่างย่อยๆซึ่งจะสังเกตว่า เราใช้รีจิสเตอร์พักค่าหนึ่งตัวชื่อว่า R1 (แล้วแต่ข้อตกลง) และเทียบเครื่องหมาย
* เป็น MUL
/ เป็น DIV
+ เป็น ADD
- เป็น SUB
= เป็น STORE
หากมีเครื่องหมายอื่นๆ ก็แล้วแต่จะใช้คำว่าอย่างไร นอกเหนื่อจากนั้นรูปแบบก็คล้ายคลึงกันพร้อมกับใส่หมายเลขบรรทัดไปด้วย...

       เพื่อนๆที่เคยเรียนภาษา assembly (ขณะที่ผมเรียนเขียนด้วย MASM32 : Microsoft assembler 32 bit) คงจำรูปแบบคำสั่งควบคุมจำพวก if หรือ while ได้บ้าง การสร้าง intermediate code อาจแตกต่างออกไปเล็กน้อย เช่น

ตัวอย่าง source code เขียนเป็น if ( a > 10 ) { a = a - 1; }
จะถูกสร้างเป็น intermediate code ดังนี้ (แล้วแต่ข้อตกลง)
>, a, 10, t0
JLE, t0,  , label_end
-, a, 1, t1
=, t1,  , a
LBL,  ,  , label_end

       หมายความว่า ผลการเปรียบเทียบระหว่างตัวแปร a และค่าคงที่ 10 จะได้ผลลัพธ์ไปเก็บไว้ ณ ตัวแปรชื่อ t0 (ค่านี้อาจเป็น จริง หรือ เท็จ ก็ได้) ถัดมาคำสั่ง JLE จะอ่านค่าตัวแปร t0 ...เมื่อ "a น้อยกว่าหรือเท่ากับ 10" จริง (คือ t0 มีค่าเป็นจริง) คอมพิวเตอร์จะทำคำสั่ง JLE เป็นเหตุให้กระโดดไปทำคำสั่ง ณ ตำแหน่ง LBL ที่ชื่อ label_end (ที่เขียนว่า LBL,  ,  , label_end) ทันทีโดยข้ามสองคำสั่งต่อไปนี้
-, a, 1, t1
=, t1,  , a
...แต่หากเป็นตรงกันข้ามเพราะ "a มากกว่า 10" จริง (คือ t0 มีค่าเป็นเท็จ) คอมพิวเตอร์จะไม่สนใจคำสั่ง JLE ผลคือคำสั่ง
-, a, 1, t1
=, t1,  , a
จะถูกทำก่อนแล้วจึงค่อยไปทำคำสั่ง ณ ตำแหน่ง label_end เป็นลำดับ (สุดท้าย) ต่อไป...

ตัวอย่าง source code เขียนเป็น while ( a > 10 ) { a = a - 1; }
จะถูกสร้างเป็น intermediate code ดังนี้ (แล้วแต่ข้อตกลง)
LBL,  ,  , label_begin
>, a, 10, t0
JLE, t0,  , label_end
-, a, 1, t1
=, t1,  , a
JMP,  ,  , label_begin
LBL,  ,  , label_end

       intermediate code ตัวอย่างข้างต้นไม่ต่างจากตัวอย่างก่อนหน้ามากนัก เพียงแต่เพิ่มการวนรอบ (JMP) กลับไปตรวจสอบเงื่อนไข a > 10 นั้นอยู่เรื่อย จนกระทั่งเงื่อนไขดังกล่าวจะเป็นเท็จจึงยุติลง... (หรือกล่าวว่าทำคำสั่งถัดไป ณ ตำแหน่ง label_end (ที่เขียนว่า LBL,  ,  , label_end))

       อัลกอริทึมที่ใช้สำหรับกระบวนการ Code Generator มีอยู่หลายวิธี (ขอภัย เพราะผมลืมไปเกือบหมดแล้ว) เมื่อยึดเอา intermediate code จากตัวอย่างล่าสุดข้างต้น ผมเลือกใช้วิธีการที่มีรูปแบบที่แบ่งออกเป็นสองขั้นตอนใหญ่ๆ ดังนี้
- ขั้นตอนแรก บันทึกชื่อของตำแหน่ง (label) จากคำสั่ง LBL เท่านั้น โดยนับบรรทัดไปพร้อมๆกัน เริ่มจาก
---- ให้ตัวแปรชื่อ countLine คอยนับจำนวนบรรทัดที่จะถูกแปลงเป็นโค้ดเป้าหมาย เริ่มต้นค่าเป็นหนึ่ง
---- เมื่ออ่านมาพบ LBL,  ,  , label_begin ให้บันทึกชื่อ label_begin ไว้พร้อมกับระบุหมายเลขบรรทัดไว้เป็น 1
---- เมื่ออ่านมาพบ >, a, 10, t0 ค่าของ countLine จะเพิ่มขึ้นอีก 3 รวมแล้วเท่ากับ 4 (เลขสามนั้นมาจาก intermediate code นี้หนึ่งคำสั่ง ถูกแปลงเป็นโค้ดเป้าหมายจำนวนสามคำสั่ง (หรือสามบรรทัด) นั้นเอง)
---- เมื่ออ่านมาพบ JLE, t0,  , label_end ค่าของ countLine จะเพิ่มขึ้นอีก 1 รวมแล้วเท่ากับ 5 (เพราะมันจะถูกแปลงเป็นโค้ดเป้าหมายเพียงหนึ่งคำสั่ง)
---- เมื่ออ่านมาพบ -, a, 1, t1 ค่าของ countLine จะเพิ่มขึ้นอีก 3 รวมแล้วเท่ากับ 8
---- เมื่ออ่านมาพบ =, t1,  , a ค่าของ countLine จะเพิ่มขึ้นอีก 2 รวมแล้วเท่ากับ 10
---- เมื่ออ่านมาพบ JMP,  ,  , label_begin ค่าของ countLine จะเพิ่มขึ้นอีก 1 รวมแล้วเท่ากับ 11
---- เมื่ออ่านมาพบ LBL,  ,  , label_end ให้บันทึกชื่อ label_end ไว้พร้อมกับระบุหมายเลขบรรทัดไว้เป็น 11

- ขั้นตอนที่สอง อ่าน intermediate code ใหม่อีกรอบ โดยไม่สนใจคำสั่ง LBL แต่กลับสนใจชื่อ label ของคำสั่ง jump ต่างๆแทน (ในที่นี้คำสั่ง jump ได้แก่ JLE และ JMP)
---- ตั้งค่าตัวแปร countLine เป็นหนึ่งอีกครั้ง
---- เมื่ออ่านมาพบ LBL,  ,  , label_begin ให้เลยผ่านไป
---- เมื่ออ่านมาพบ >, a, 10, t0 ให้แปลงเป็นโค้ดเป้าหมาย พร้อมกับระบุหมายเลขบรรทัด
1. LOAD  R1  a
2. GT  R1  10
3. STORE  R1  t0

---- เมื่ออ่านมาพบ JLE, t0,  , label_end ให้นำชื่อ label_end เทียบหาชื่อที่ตรงกันจากชื่อ label ทั้งหมดที่ได้บันทึกไว้ในขั้นตอนแรก แล้วนำหมายเลขบรรทัดที่บันทึกไว้พร้อมกันแปลงเป็นโค้ดเป้าหมาย
4. JLE     11
---- เมื่ออ่านมาพบ -, a, 1, t1
5. LOAD  R1  a
6. SUB  R1  1
7.STORE  R1  t1

---- เมื่ออ่านมาพบ =, t1,  , a
8. LOAD  R1  t1
9. STORE  R1  a

---- เมื่ออ่านมาพบ JMP,  ,  , label_begin
10. JMP     1

ดาวน์โหลดโปรแกรม Compiler ที่เขียนด้วย Grammar 3 (Run ด้วย NetBeans IDE) ซึ่งเพิ่มเติมกระบวนการ Code Generator

ทดสอบ
       รูปด้านล่างนี้แสดงผลการทดสอบ การสร้าง target code โดยใช้ intermediate code ตามตัวอย่างล่าสุดข้างต้น (ของบทความหน้านี้) พร้อมผลลัพธ์ แต่ถ้าเพื่อนๆต้องการให้แสดงผลลัพธ์จาก intermediate code ปกติของ grammar 3 ให้เพื่อนๆยกเลิก comment บรรทัดที่ 36 และใส่ comment บรรทัดที่ 35 แทนครับ

ผลลัพธ์

อ่านเนื้อหาที่เกี่ยวข้อง ก่อนหน้า