วันอาทิตย์ที่ 31 ตุลาคม พ.ศ. 2553

Compiler : การตรวจสอบ Syntax (ภาษา Java)

กระบวนการ Parser (ต่อ) : ขั้นตอนการตรวจสอบ Syntax
    เราจะใช้วิธีการให้ค่าล่วงหน้าแก่ currentToken หนึ่งค่าเสมอ เพื่อนำค่าที่ได้ไปตรวจสอบกับ Terminal ของแต่ละ Grammar ย่อยที่จะถูกเปลี่ยนให้อยู่ในรูปแบบเมธอด ดังนี้
(รูปหมายเลข 181)
- กลับไปยังบทความ โปรแกรม Compiler 2 (ภาษา Java) เพื่อคัดลอก Grammar ที่ออกแบบไว้แล้วมาใช้ (ตัวอักษรสีน้ำเงิน)

(รูปหมายเลข 182)
- วาง Grammar ที่คัดลอกไว้ส่วนท้ายสุดของคลาส Parser (แต่ยังอยู่ภายในขอบเขตของคลาสนะครับ) แล้วใส่ Comment ไว้ตามรูป

(รูปหมายเลข 183)
- แต่ละ Grammar ย่อยจะหมายถึงหนึ่งเมธอดที่มี private void นำเสมอ
- เปลี่ยน Grammar ย่อยทั้งหมดให้เป็นเมธอดที่มีชื่อเหมือนตัวผลิตของ Grammar ย่อยเหล่านั้น (ดังตัวอย่างรูปข้างต้น)

*** หมายเหตุ ตัวผลิตหมายถึง Non-Terminal ทางซ้ายของเครื่องหมาย ->

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

(รูปหมายเลข 184)
- กรณีตัวผลิต (ซ้ายของเครื่องหมาย ->) ระบุถึงผลผลิต Non-Terminal ใดๆ (ขวาเครื่องหมาย ->) ให้เรียกไปยังเมธอดของผลผลิตนั้นได้เลย
- จากรูปตัวผลิต start ระบุถึงผลผลิต variableDeclaration ตามด้วย mainFunction จึงเรียกเมธอด variableDeclaration() และ mainFunction() ตามลำดับครับ

(รูปหมายเลข 185)
- กรณีนี้ผลผลิตมีสองทางเลือก คือไปทาง type name ass... หรือไปทาง empty
- เราจะต้องโค้ดให้คอมพิวเตอร์เลือกทางหนึ่งทางใด ดังนั้นบรรทัดที่ 98 (จากรูป) ผมจึงถามว่า currentToken ปัจจุบันใช่ type หรือไม่ หากใช่จึงเลือกทาง type name ass... หากไม่ใช่จึงเลือกทาง empty
- นอกจากนี้ภายใน Grammar ย่อยนี้ยังพบว่าทางเลือก type name ass... มี Terminal ที่เป็น ";" อยู่ด้วย บรรทัดที่ 107 (จากรูป) จึงต้องตรวจสอบ currentToken ปัจจุบัน ใช่สัญลักษณ์ ";" หรือไม่ ถ้าไม่ใช่จะต้องแจ้ง error ครับ แต่ถ้าใช่จึงผ่านไป พร้อมกับให้ค่า currentToken เป็นค่าต่อไปด้วยการเรียกเมธอด read (เพราะ ";" เป็นส่วนหนึ่งของ Grammar ย่อยนี้)

(รูปหมายเลข 186)
- กรณีที่มีสองทางเลือกขึ้นไป และมี Terminal ขึ้นต้นในหนึ่งหรือหลายทางเลือกเหล่านั้น เราสามารถใช้ Terminal ดังกล่าวเป็นตัวเลือกทางเลือกได้
- บรรทัดที่ 121 (จากรูป) ใช้ Terminal ที่เป็น "," เลือกระหว่าง "," name ass... กับ empty
- สำหรับทางเลือก "," name ass... หลังจากถามถึง "," ต้องเรียกเมธอด read ด้วย (เพราะ "," เป็นส่วนหนึ่งของ Grammar ย่อยนี้)

(รูปหมายเลข 187)
- กรณี Grammar ย่อยผลิตได้เฉพาะ Terminal เพียงอย่างเดียว (จะกี่ทางเลือกก็ตาม) เช่น addSubSymbol, mulDivSymbol, name, type, literal และอื่นๆ ให้เขียนเรียกเมธอด read เพื่อให้ค่าแก่ currentToken ได้เลย

(รูปหมายเลข 188)
- รูปนี้เป็นอีกกรณีตัวอย่างของกรณีที่ผ่านมา (งงเปล่า) คือสามารถใช้ Terminal เป็นทางเลือกได้
- ให้สังเกตว่า "(" expression แล้วจะต้องปิดท้ายด้วย ")" ดังนั้นกรณีเช่นนี้ก็อย่าลืมถามถึง ")" ในบรรทัดที่ 212 (ตามรูป)

คำถามที่ถามกันบ่อย : จะรู้ได้อย่างไรว่าเมื่อไหร่ควรเรียกเมธอด read ?
- เมื่อใดก็ตามที่ Grammar ย่อยนั้นมี Terminal ที่เป็นผลผลิตอยู่ภายใน จะเรียกเมธอด read
- หรืออีกความหมายหนึ่งคือ จะไม่เรียกเมธอด read หากเพียงถามถึง Terminal ที่ไม่มีอยู่ใน Grammar ย่อยหรือเมธอดที่กำลังพิจารณาอยู่ เช่น ไม่เรียกเมธอด read ในเมธอด isType เพราะไม่มี Grammar ย่อยระบุตัวตนของ "int" หรือ "float"

(รูปหมายเลข 189)
- เมื่อเพื่อนๆโค้ดการทำงานภายในของเมธอดเสร็จสิ้นแล้ว ก่อนเราจะทดสอบ Syntax ภาษา ให้เพิ่มโค้ดเรียกเมธอด read เพื่อเริ่มต้นให้ค่า currentToken ตามด้วยเมธอด start ที่ Constructor ของคลาส Parser นี้ด้วยครับ

ทดสอบ
(รูปหมายเลข 190)
- บรรทัดที่ 17 (จากรูป) เปิด Comment ในส่วนของ Parser แล้ว Run ครับ
- หากทำถูกวิธีและไม่ได้ปรับเปลี่ยน Source Code ที่อยู่ในไฟล์ SourceCode.txt จะไม่เกิด error ใดๆครับ (ไม่มีอะไรเกิดขึ้นเลย)

***หมายเหตุ อยากให้ลองปรับเปลี่ยน Source Code ที่อยู่ในไฟล์ SourceCode.txt ให้ไม่เป็นไปตาม Grammar แล้ว Run ดูผล error ที่เกิดขึ้นนะครับ

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

วันพฤหัสบดีที่ 28 ตุลาคม พ.ศ. 2553

Compiler : คลาส Parser (ภาษา Java)

กระบวนการ Parser
ทั่วไปแล้วกระบวนการนี้แบ่งออกเป็นสองขั้นตอน ได้แก่ Syntax และ Semantic ซึ่งอาจเขียนแยกหรือรวมกันไว้ในกระบวนการ Parser ก็ได้ แต่ตามความเห็นส่วนตัว ผมแนะนำให้เขียนแบบรวมกันครับ (น่าจะง่ายกว่าหรือเปล่า ?)

เตรียมคลาส Parser
(รูปหมายเลข 174)
- ประกาศแอตทริบิวต์ชื่อ symbolTable มีชนิดเป็นคลาส SymbolTable และชื่อ tokenStream มีชนิดเป็นคลาส TokenStream
- สร้าง Constructor รับอาร์กิวเมนต์ symbolTable กำหนดให้แอตทริบิวต์ symbolTable และอาร์กิวเมนต์ tokenStream กำหนดให้แอตทริบิวต์ tokenStream ซึ่งแยกจากกันด้วย this

(รูปหมายเลข 175)
- บรรทัดที่ 10 ประกาศแอตทริบิวต์ชื่อ currentToken พร้อมกำหนดค่าออบเจ็กต์ให้ เราจะใช้ currentToken เพื่อเก็บ Token ปัจจุบันเอาไว้ทุกครั้งที่เรียกเมธอด read
- บรรทัดที่ 11 ประกาศแอตทริบิวต์ชื่อ end พร้อมกำหนดค่าออบเจ็กต์ให้ เราจะใช้ end แสดงถึงการอ่านกระทั่งสิ้นสุดสาย tokenStream
- บรรทัดที่ 12 ประกาศตัวแปรชื่อ countOfTokenStream เพื่อนับจำนวน Token ที่ถูกอ่านโดยเมธอด read
- บรรทัดที่ 14 สร้างเมธอดชื่อ read เพื่อทำหน้าที่อ่าน Token จากออบเจ็กต์ tokenStream ทีละ Token ทุกครั้งที่เรียกเมธอดนี้ การทำงานที่สำคัญคือ copy by value จากอินสแตนท์ token ให้กับ currentToken (กรอบสีส้ม) กระทั่งอ่านจนสิ้นสาย tokenStream (ค่า countOfTokenStream เท่ากับ tokenStream.size()) currentToken จะมีค่าเป็น end

(รูปหมายเลข 176)
- สร้างเมธอดชื่อ isMatchSymbol เพื่อถามว่าสตริงที่กำหนดให้นี้ เป็นเช่นเดียวกับ (equals) สตริงชื่อของ currentToken หรือไม่
- กลไกที่จะทำให้ชื่อรหัส (tokenNameIndex) เปลี่ยนเป็นชื่อ Token ได้ (tokenName) จะต้องส่งชื่อรหัสดังกล่าวให้กับบริการ getTokenName ของออบเจ็คก์ symbolTable

(รูปหมายเลข 177)
- สร้างเมธอดชื่อ isMathType เพื่อถามว่าค่าคงที่ (int) ที่กำหนดให้นี้ เหมือนกันกับ (==) ประเภทรหัส (tokenTypeIndex) หรือไม่ เช่นส่ง TokenType.KEYWORDS เหมือนกันกับ tokenTypeIndex คืนค่า true หรือไม่คืนค่า false

(รูปหมายเลข 178)
- สร้างเมธอดชื่อ error เพื่อแจ้งความผิดพลาด และยุติการทำงานของ Parser จากคำสั่ง System.exit(0);

(รูปหมายเลข 179)
- บรรทัดที่ 50 สร้างเมธอดชื่อ isName เพื่อถามว่า currentToken ปัจุบันมีประเภทเป็น IDENTIFIERS คืนค่า true หรือไม่คืนค่า false
- บรรทัดที่ 57 สร้างเมธอดชื่อ isType เพื่อถามว่า currentToken ปัจจุบันมีชื่อเป็น int หรือ float คืนค่า true หรือไม่คืนค่า false
- บรรทัดที่ 64 สร้างเมธอดชื่อ isLiteral เพื่อถามว่า currentToken ปัจจุบันมีประเภทเป็น INTEGER_LITERALS หรือ FLOATING_POINT_LITERALS คืนค่า true หรือไม่คืนค่า false

(รูปหมายเลข 180)
- สร้างเมธอดชื่อ isAddSubSymbol เพื่อถามว่า currentToken ปัจจุบันใช่เครื่องหมาย "+" หรือ "-" หรือไม่
- สร้างเมธอดชื่อ isMulDivSymbol เพื่อถามว่า currentToken ปัจจุบันใช่เครื่องหมาย "*" หรือ "/" หรือ "%" หรือไม่

บทความถัดไปคือการโค้ดขั้นตอนตรวจสอบ Syntax ภาษาโปรแกรมครับ

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

Compiler : ทดสอบคลาส Lexical (ภาษา Java)

ทดสอบคลาส Lexical
(รูปหมายเลข 169)
- นำ Comment ส่วนของคลาส Lexical ทั้งหมดออก
- แล้วเพิ่มส่วนแสดงผลลัพธ์ (กรอบสีส้ม) โดยเรียกผ่านอินสแตนท์ symbolTable.display() และ tokenStream.display() ครับ

***หมายเหตุ อินสแตนท์เดิมชื่อ symboltable ถูกเปลี่ยนเป็น symbolTable นะครับ (t เล็กเป็น T ใหญ่)

(รูปหมายเลข 170)
- รูปผลลัพธ์เมื่อสั่ง Run ด้วย Shift + F6

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

วันพุธที่ 27 ตุลาคม พ.ศ. 2553

Compiler : คลาส Lexical 2 (ภาษา Java)

กลับมายังคลาส Lexical เพื่อโค้ดให้สำเร็จครับ
(รูปหมายเลข 159)
- โดยปกติแล้วค่าคงที่ใดๆในจาวาจะระบุด้วย final และเขียนด้วยตัวพิมพ์ใหญ่ทั้งหมด ทว่าค่าคงที่ end ยังคงเป็นพิมพ์เล็ก ฉะนั้นโอกาสนี้ผลจึงเสนอวิธีการเปลี่ยนชื่อ โดยมีผลกับทุกส่วนของโค้ดที่มีชื่อนี้อยู่ หรือใช้กับชื่อคลาสหรือชื่อเมธอดก็ได้ครับ
- แดรกเลือกชื่อ ขณะนี้คือ end แล้วคลิกขวาเลือก Refactor ตามด้วยเลือก Rename...

(รูปหมายเลข 160)
- เปลี่ยนจาก end เป็น END แล้วกดปุ่ม Refactor เป็นอันว่าเรียบร้อย

(รูปหมายเลข 161)
- เลื่อนบรรทัดมายังส่วนท้ายสุดของคลาส Lexical แต่ยังอยู่ในขอบเขตของคลาสนะครับ
- บรรทัดที่ 96 และ 97 ประกาศแอตทริบิวต์คลาส SymbolTable และ TokenStream ตามลำดับ
- บรรทัดที่ 99 สร้างเมธอดชื่อ addNewToken โดยมี tokenName และ tokenTypeIndex เป็น Parameters กระบวนการคือเพิ่ม tokenName และ tokenTypeIndex ให้กับ symbolTable
- บรรทัดที่ 101 ขอ tokenNameIndex ผ่านเมธอด getTokenNameIndex ของ symbolTable เพื่อนำไปเพิ่มยัง tokenStream พร้อมกับ tokenTypeIndex

(รูปหมายเลข 162)
- ถัดจากเมธอดชื่อ addNewToken เพิ่มเมธอด getSymbolTable และ getTokenStream เพื่อขอออบเจ็คก์ symbolTable และ tokenStream (บริการนี้จะถูกเรียกอย่างชัดเจนที่เมธอด main ของไฟล์ MainProgram.java)

(รูปหมายเลข 163)
- ถัดจากนั้นเพิ่มเมธอด isWhiteSpacesAndLineTerminators เพื่อกำหนดให้ภาษาของเรายอมรับ White Spaces และ Terminators ครับ

(รูปหมายเลข 164)
- กลับมาที่เมธอด start (ได้สร้างไว้แล้ว) ภายใต้ประโยค isSeparators... ของประโยค if เรียกเมธอด addNewToken เพื่อกำหนดให้ currentChar มีประเภทเป็น Separators
- เช่นเดียวกัน ภายใต้ประโยค isOperators... ของประโยค else if เรียกเมธอด addNewToken เพื่อกำหนดให้ currentChar มีประเภทเป็น Operators

(รูปหมายเลข 165)
- ภายใต้ประโยค isLetter... ของ else if (ยังอยู่ในเมธอด start) เขียนอัลกอริทึมเชื่อมตัวอักษรให้เป็นคำ (สตริง) เก็บไว้กับตัวแปรชื่อ word
- เมื่อได้หนึ่งคำ (หรือ word) จึงนำไปถามต่อว่า คำนี้ใช่ประเภท Keywords หรือไม่ ถ้าใช่ก็เรียกเมธอด addNewToken แล้วกำหนดให้เป็นประเภท Keywords ถ้าไม่ใช่จึงให้เป็นประเภท Identifiers
- สุดท้ายถามว่า currentChar ไม่ใช่ END (คือยังไม่จบ Source Code) ใช่ไหม หากผลลัพธ์เป็น true ให้ลดค่า countOfSourceCode ลงเสียหนึ่ง เพื่อนป้องกันการอ่านข้าม currentChar ตัวปัจจุบันที่หลุดออกจาก while ลูปในอัลกอริทึมครับ

(รูปหมายเลข 166)
- ภายใต้ประโยค isDigit คืออัลกอริทึมตัดตัวเลขจำนวนเต็มและทศนิยม (กรอบสีเขียว)
- การทำงานก็คล้ายกับการตัด Keywords และ Identifiers ที่เพื่อนๆพึ่งสังเกตผ่านมาครับ เพียงแต่ผมใช้ boolean ที่ชื่อ isFloatingPointLiterals ช่วยชี้ว่าควรเป็นจำนวนเต็ม (Integer Literals) หรือทศนิยม (Floating-point Literals) หากพบว่าตัวแปร currentChar มีค่าเป็น '.' (dot) ก็จะเข้าสู่ส่วนที่เป็นทศนิยมทันทีครับ (กรอบสีน้ำตาลเข้ม)
- ภายหลังจึงถามว่าตัวแปร isFloatingPointLiterals มีค่าเป็น ture ให้เป็นจำนวนทศนิยม เมื่อเป็น false ให้เป็นจำนวนเต็ม
- สุดท้ายถามว่า currentChar ไม่ใช่ END (คือยังไม่จบ Source Code) ใช่ไหม หากผลลัพธ์เป็น true ให้ลดค่า countOfSourceCode ลงเสียหนึ่ง เพื่อนป้องกันการอ่านข้าม currentChar ตัวปัจจุบันที่หลุดออกจาก while ลูปในอัลกอริทึมครับ

(รูปหมายเลข 167)
- แทรกโค้ดเหมือนกับในรูปเพื่อให้การตัดคำไม่เกิด error
- ส่วนการทำงานภายในขณะนี้ยังไม่มีครับ ^^ (ก็ยังไม่ได้ใช้ประโยชน์)

(รูปหมายเลข 168)
- เนื่องจากเมธอด start ที่โค้ดเสร็จไปนั้นมี Access Modifier เป็น private ผมจึงตั้งใจให้มันถูกเรียกจากอย่างอื่นภายในคลาส ซึ่งคือ Constructor ของคลาส Lexical นั่นเอง ฉะนั้นเขียนเพิ่มอีกนิดตามรูปครับ

เป็นอันว่าคลาส Lexical ของเราพร้อมตัดคำแล้ว บทความต่อไปเรามาทดสอบกันครับ

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

Compiler : คลาส TokenStream (ภาษา Java)

คลาส TokenStream
เพื่อนๆคงคุ้นเคยแล้วกับ Token Stream แล้วมันคืออะไร ?
    Token Stream คือแหล่งเก็บ Tokens ที่ได้มาจาก Source Code โดยกระบวนการ Lexical สมมติเรามี Source Code เป็น "int" "a" "=" "10" + "10" ";" (สตริงทั้งหมด) ผลจึงเป็น

ด้วยเหตุผลเรื่องหน่วยความจำ เราสามารถเก็บสตริงทั้งหมดให้อยู่ในรูปตัวเลขแทน โดยนำ Symbol Table และตาราง Token Type มาช่วย

ผลลัพธ์จึงเปลี่ยนเป็น

ฉะนั้นหากต้องการเปลี่ยนจากหมายเลขเป็นสตริงเช่นเดิม ก็เพียงนำไปเทียบกับ Symbol Table และตาราง Token Type ข้างต้น

(รูปหมายเลข 156)
- สร้างคลาส TokenStream

(รูปหมายเลข 157)
- บรรทัดที่ 5 ประกาศแอตทริบิวต์ชื่อ tokenStream พร้อมให้ค่าออบเจ็คก์ของคลาส LinkedList โดยบังคับ Generic เป็นคลาส Token
- บรรทัดที่ 7 สร้างเมธอดชื่อ add สำหรับบริการเพิ่ม Token โดยรับ tokenNameIndex และ tokenTypeIndex ซึ่งมีชนิดเป็น int ทั้งคู่

(รูปหมายเลข 158)
- บรรทัดที่ 12 สร้างเมธอดชื่อ size สำหรับคืนจำนวน Token ทั้งหมดที่มี
- บรรทัดที่ 16 สร้างเมธอดชื่อ get สำหรับร้องขอ Token ที่เก็บไว้กับ tokenStream โดยระบุด้วยหมายเลขลำดับที่เรียกว่า index
เช่น ใส่ 0 ผลคือออบเจ็กค์ของคลาส Token เป็น (0, 100) ซึ่งหมายถึง ("int", "Keywords")
- บรรทัดที่ 24 สร้างเมธอดชื่อ display สำหรับแสดง Token ทั้งหมดที่เก็บไว้ โดยเรียกเมธอด toString ของคลาส Token

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

Compiler : คลาส TokenType (ภาษา Java)

อย่างที่ได้กล่าวมา จากตาราง Token Type ด้านล่าง ผมจึงออกแบบคลาสดังนี้
(รูปหมายเลข 139)

สร้างคลาสชื่อ TokenType
(รูปหมายเลย 155)

(รูปหมายเลข 155_1)
- ชื่อของ Type ทั้งหมดในตารางกำหนดให้เป็นค่าคงที่ (final)
- กำหนดให้เป็น static (ถูกโหลดเข้าสู่หน่วยความจำทั้งหมดเมื่อเรียกใช้ครั้งแรก และสามารถอ้างถึงโดยใช้เครื่องหมาย . (dot) หลังชื่อคลาส โดยไม่ต้องสร้างคลาสออบเจ็กต์)
เช่น TokenType.KEYWORDS, TokenType.IDENTIFIERS เป็นต้น

(รูปหมายเลข 155_2)
- เพิ่มบริการขอชื่อ Type ที่มีชนิดเป็น String โดยส่ง Index ของ Type (จากค่าคงที่) เข้าไปแทน
เช่น ส่ง 103 ผลลัพธ์จะได้ "Identifiers"

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

วันอังคารที่ 26 ตุลาคม พ.ศ. 2553

Compiler : คลาส SymbolTable (ภาษา Java)

เสร็จจากคลาส Token คราวนี้กลับมาจัดการคลาส SymbolTable ต่อ (มึนหรือยังครับ ^^)
(รูปหมายเลข 148)
- บรรทัดที่ 2 สร้างคลาสชื่อ EToken โดยให้สืบทอดมาจากคลาส Token อีกที เราเรียกคลาส EToken ว่าคลาสภายในคลาส (SymbolTable)
- บรรทัดที่ 3 คลาส EToken เพิ่มแอตทริบิวต์ชื่อ name มีชนิดเป็น String และเราจะใช้แอตทริบิวต์นี้เก็บชื่อ Token หรือคำที่เพิ่มเข้ามาครับ
- บรรทัดที่ 6 ภายหลังสร้าง Constructor ของคลาส EToken ที่รับเอา name, nameIndex, typeIndex มาแล้ว เราเรียกไปยัง Constructor ของคลาสที่สืบทอดมา (คลาสแม่) ซึ่งคือคลาส Token โดยคำสั่ง supper เพื่อส่ง nameIndex และ typeIndex ไปเก็บไว้ที่นั่น (ส่วน name เก็บไว้ยังแอตทริบิวต์ชื่อ name ของคลาส EToken เอง)
- บรรทัดที่ 10 เป็นต้นไปสร้าง Getter และ Setter ตามวิธีการที่ได้เสนอมาแล้วจากบทความก่อนหน้านี้ครับ

(รูปหมายเลข 148_1)
- เพิ่มเมธอด toString() ให้กับคลาส EToken
- กรอบสี่เหลี่ยมในรูป เราเรียกไปยัง toString() ของคลาสที่สืบทอดมา (Token) หรือกล่าวว่า นำสตริงของคลาสลูกต่อกับสตริงของคลาสแม่นั่นเองครับ

(รูปหมายเลข 149)
- บรรทัดที่ 22 ออกจากขอบเขตคลาส EToken (ในรูปหมายเลขบรรทัดอาจไม่สอดคล้องกับความจริง เนื่องจากผมได้ปรับปรุงคลาสนี้อีกหลายครั้ง หลังจากบทความนี้ถูก upload ไปแล้ว) สร้างแอตทริบิวต์ชื่อ symbolTable มีชนิดเป็นโครงสร้างข้อมูลชื่อ LinkedList โดยบังคับให้สามารถจัดการกับคลาส EToken หรือเรียกว่าเป็นการระบุ Generics คลาส

(รูปหมายเลข 150)
- เพื่อนๆต้องบอกตัวแปลภาษาจาวาด้วยว่า จะหา LinkedList ได้จากคำสั่ง
import java.util.LinkedList;
แต่หากเพื่อนๆใช้วิธีการกด Ctrl + Space bar แล้วค่อยๆพิมพ์ LinkedL... ณ บรรทัดที่ 22 ดังกล่าว แล้วกด Enter, NetBeans IDE จะช่วยจัดการเรื่อง import ให้อัตโนมัติ

(รูปหมายเลข 151)
- บรรทัดที่ 24 สร้างเมธอดชื่อ add โดยคืนค่าเป็นชนิด boolean
- เมธอดนี้รับ tokenName มีชนิดเป็น String และ tokenTypeIndex มีชนิดเป็น int
- สำหรับ tokenName คือชื่อ Token หรือคำที่ตัดได้จากกระบวนการ Lexical (ขณะนี้ยังโค้ดไม่เสร็จ) ที่ถูกส่งเข้ามาเพิ่มตรวจสอบว่าซ้ำกับชื่อ Token หรือคำที่ถูกบรรจุไว้ก่อนหรือไม่ ?
- บรรทัดที่ 25 เขียนอัลกอริทึมตรวสอบคำที่รับเข้ามา (tokenName) หากซ้ำกับชื่อคำที่มีอยู่ก่อน ก็จะคืนค่า false แล้วออกจากเมธอดทันที
- บรรทัดที่ 30 เมื่อคำไม่ซ้ำ สร้างออบเจ็กค์จากคลาส EToken โดยอ้างอิงกับอินสแตนท์ชื่อ newToken ผ่าน Constructor ส่ง tokenName, อันดับ (รหัส) ที่ได้รับ (ไม่ซ้ำ) และ tokenTypeIndex
- บรรทัดที่ 31 เพิ่มออบเจ็กค์ newToken ให้กับ LinkedList ชื่อ symbolTable ที่ได้สร้างไว้ก่อนแล้ว กล่าวคือเพิ่มคำใหม่ลงใน Symbol Table นั่นเอง
- สุดท้ายคืนค่า true ครับ

***หมายเหตุ อันดับ (รหัส) ที่ได้รับ (ไม่ซ้ำ) ในที่นี้ผมใช้จำนวนของคำที่อยู่ใน Symbol Table ครับ โดยเขียนเป็น symbolTable.size()

(รูปหมายเลข 152)
- สร้างเมธอด size() เพื่อคืนค่าจำนวนของคำที่มีอยู่ใน Symbol Table

(รูปหมายเลข 153)
- สร้างเมธอดชื่อ getTokenNameIndex() เพื่อขอหมายเลขรหัสของคำ โดยส่งชื่อคำเข้าไป (เอาชื่อไป เอารหัสมา)
- สร้างเมธอดชื่อ getTokenName() คราวนี้ส่งหมายเลขรหัสของคำเข้าไป ให้ออกมาเป็นชื่อของคำแทน (เอารหัสไป เอาชื่อมา)

(รูปหมายเลข 154)
- สร้างเมธอดชื่อ display() เพื่อแสดงรายการ Token ทั้งหมดที่อยู่ใน Symbol Table จากรูปเราเรียกไปยัง toString() ของคลาส EToken ในแต่ละรอบครับ

สิ้นสุดคลาส SymbolTable เท่านี้ โอกาสต่อไปเป็นทีของคลาส TokenType ครับ

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

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

Compiler : คลาส Token (ภาษา Java)

กลับมาโค้ดโปรแกรมสำหรับ Symbol Table และตาราง Token Type กันครับ

เริ่มจากสร้างคลาส SymbolTable (รูปหมายเลข 141)

ทว่า Symbol Table ประกอบด้วย Tokens ดังนั้นสร้างคลาส Token เพิ่ม
(รูปหมายเลข 142)

เรามาดูรายละเอียดโค้ดของคลาส Token เสียก่อน
(รูปหมายเลข 143)
- บรรทัดที่ 2 และ 3 ประกาศแอตทริบิวต์ชื่อ nameIndex และ typeIndex ทั้งสองนี้จะใช้แทนชื่อฟิวด์ของ Symbol Table ที่ได้นำเสนอในบทความก่อนหน้านี้นั่นคือ tokenIndex และ tokenType ตามลำดับ
- บรรทัดที่ 5 และ 10 สร้าง Constructor แบบที่รับ Argument เป็น nameIndex และ typeIndex กับแบบที่ไม่รับ Argument ใดๆ
- บรรทัดที่ 11 ประโยคนี้หมายถึงเรียกไปยัง Constructor แบบที่รับ Argument เป็น nameIndex และ typeIndex โดยส่งค่า -1 ไปให้ทั้งคู่

(รูปหมายเลข 144)
- คุณสมบัติพื้นฐานอย่างหนึ่งที่เกี่ยวกับแอตทริบิวต์ก็คือการเขียนโค้ดส่วนที่เรียกว่า Getter และ Setter เพื่อขอค่าและกำหนดค่าให้กับแอตทริบิวต์ตามลำดับ
- คลิกขวา ณ บรรทัดที่ 14 (ตามรูปข้างต้น) เลือก Insert Code...

(รูปหมายเลข 145)
- เลือก Getter and Setter...

(รูปหมายเลข 146)
- คลิกเพื่อสร้างเครื่องหมายถูกด้านหน้าช่องว่างของ Token แล้วกด Generate

(รูปหมายเลข 147)
- NetBeans IDE จะสร้างโค้ดส่วน Getter และ Setter ให้โดยอัตโนมัติครับ

(รูปหมายเลข 147_1)
- คลิกขวา ณ บรรทัดที่ 30 (ตามรูปข้างต้น) เลือก Insert Code...

(รูปหมายเลข 147_2)
- คลาสใดๆที่ถูกสร้างขึ้น จะต้องถูกสืบทอดจากคลาสบรรพบุรุษเสมอ (Object Class) หนึ่งในหลายเมธอดที่ถูกสืบทอดมาชื่อ toString()
- เราจะเขียนพฤติกรรมของเมธอด toString() นี้เสียใหม่ เรียกกระบวนการนี้ว่า Overriding (เพิ่มเติม)
- จากรูปเลือก toString()...

(รูปหมายเลข 147_3)
- เลือกแอตทริบิวต์ nameIndex และ typeIndex แล้วกดปุ่ม Generate

(รูปหมายเลข 147_4)
- โค้ดส่วนนี้คือผลลัพธ์การ Overriding ครับ

ขณะนี้เราเตรียม (จัดการ) คลาส Token เรียบร้อยแล้ว จะได้เริ่มเขียนคลาส SymbolTable ต่อเสียที ^^

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