อ่านตอนก่อนหน้านี้ได้ที่ #Compiler
*** บทนี้จะต่อจากบล๊อกที่แล้ว compiler - ตอนที่ 4.1 Grammar ไวยากรณ์ภาษา ทำยังไงให้คอมมันรู้ว่าประโยคที่พิมพ์แปลว่าอะไร นะ จะพูดถึง EBNF กับ RegExp โดยไม่ทวนแล้วนะ ... ถ้าใครยังไม่ได้อ่านกลับไปอ่านก่อน ไม่งั้นไม่รู้เรื่องแน่
ในขั้นตอนนี้เราจะมากำหนด Rule ของภาษากันว่า programming language ตัวใหม่ที่เรากำลังจะสร้างขึ้นมาน่ะจะให้เขียนยังไงได้บ้าง
ตัวอย่างเช่น
ถ้า รูปแบบภาษาของเรากำหนดว่าเขียนประมาณนี้ เป็นภาษาโปรแกรมแบบง่ายๆ ที่มีส่วนสำหรับประกาศตัวแปรอยู่ข้างบน แล้วเขียนคำสั่งว่าจะทำอะไรอยู่ข้างล่างประมาณนี้
(ถ้าใครรู้จักภาษาปาสคาลน่าจะคุ้นๆ กับ syntax แบบนี้อยู่นะ)
ถ้า แบ่งแบบคร่าวๆ ก็มีอยู่ 2 ส่วนหลักๆ ที่เห็นชัดๆ เลยคือส่วน "Declaration" (ส่วนประกาศตัวแปร) กับส่วน "Command" (คำสั่งว่าจะให้ทำอะไรยังไง)
ย้ำอีกครั้งว่าตัวอย่างที่ภาษาที่เรากำลังจะสร้างขึ้นมาใหม่เนี่ยเป็นภาษาแบบง่ายๆ นะ
แต่ถ้าดูในเรื่องของรายละเอียดแค่นั้นมันยังไม่พอ ... เราต้องแบ่งให้มันชัดเจนกว่านี้ อย่างเช่น
Syntax Rule กฎการเขียนที่เรากำหนดเองได้
หรือก็คือการสร้าง Grammar ให้กับภาษาที่เราสร้างอ่ะนะซึ่งถ้าใครเขียนโปรแกรมเป็นคงพอจะจำได้ว่าเรามักจะเจอพวก
int x; int y; int z; <-- แบบนี้คือโค้ดสร้างตัวแปรใหม่ (declare) x = 5; y = min(100,200); <-- แบบนี้คือโค้ดคำสั่ง (command) (4+7) * 8 / 2 * 10 <-- แบบนี้ึคือพวกสมการ คิดเลขๆ (expression)
โอเค! เข้าเรื่องดีกว่า พูดวนไปวนมานานและ (ฮา) ... อย่างแรกสุดเลยเราจะเขียนกฎ grammar ขึ้นมาว่า
Rule 1 : Program
กฎแรกสุดคือต้องบอกว่า Program ประกอบขึ้นมาจากอะไรได้บ้าง (โดยใช้การเขียนแบบ EBNF นั่นแหละนะ)
ก็คือต้อง ขึ้นด้วย let แล้วตามด้วยส่วนของการ declaration ในระหว่างนี้จะคั่นด้วยตำว่า in begin ก่อนจะเป็นส่วนเนื้อหาของคำสั่ง command แล้วจบด้วย end
สังเกต ดูนะว่าเราจะใช้วิธีประกาศแบบคร่าวๆ ซะก่อน เพราะโค้ดส่วน declaration กับ command นั่นน่ะมันใหญ่มาก เดี๋ยวค่อยลงรายละเอียดอีกทีว่าในส่วนต่างๆ พวกนั้นน่ะมันเขียนยังไงได้บ้าง
Rule 2 : Declaration
ใน ส่วนของการ Declaration เรากำหนดว่ามันจะประกอบขึ้นมาจาก
- single-declaration หรือจะไม่มีเลยก็ได้ (เพราะมี RegExp [ ? ] ต่ออยู่ - แปล 0 หรือ 1 ตัวอ่ะนะ)
- ถ้ามีหลายตัวจะต้องเขียนคั่นด้วย "," (เพราะมี RegExp [ * ] ครอบไว้ - แปลว่าจะมีกี่ตัวก็ได้)
งั้นก็ต้องไปดูต่ออีกว่า single-command แต่ละตัวนี่มันเขียนยังไงกัน (ซอยเป็นชิ้นเล็กๆ ย่อยมาก เพื่อการแบ่งส่วนที่ชัดเจน)
ในส่วนการประกาศตัวแปร (แบบตัวเดี่ยวๆ หรือ single-declaration) นี่เราต้องรู้จักกับ "Identifier" ซะก่อน
Identifier
idenฯ เป็นเหมือนกับตัวแทนชื่อของอะไรสักอย่าง (อะไรก็ได้เลย) ที่มันไม่ตรงกับคำที่ใช้เป็น keyword ในภาษาของเรา เช่นก่อนหน้านี้เราใช้ let in begin end อะไรพวกนี้ไปแล้ว ทีนี้ถ้าเราจะประกาศตัวแปรชื่อ "n" หรือแม้แต่บอกว่ามันเป็น "integer" อะไรแบบนี้
คือมันเป็นคำที่ตั้งขึ้นมาเองน่ะ เราจะจัดเจ้าพวกนี้อยู่ในกลุ่มของ ไอเดนติฟายเออ~ร์!
Rule 3 : Single-Declaration
เอาล่ะ กลับมาที่ single-command อีกครั้ง ... มันก็ประกอบขึ้นมาจาก var เป็นคำเริ่มตามด้วย idenฯ 2 ตัว ตัวแรกเป็น "ชื่อตัวแปร" และตามด้วย "type" (คั่นด้วย ":")
Rule 4 : Command
แล้วเราก็มาถึง Command ตัวนี้จะไม่อิบายอะไรเยอนะเพราะมันเหมือนกับ Declaration เลย แต่มีการใช้กฎที่ไม่เหมือนกันมากเท่าไหร่ คือ...
- อย่างน้อยต้องมี sigle-command 1 ตัวนะ (ครั้งนี้ไม่มี RegExp [ ? ] มาให้แล้ว)
- ถ้าจะมี single-command หลายตัวก็ย่อมได้! (RegExp เป็น [ * ]) แต่ต้องคั่นพวกมันด้วย ";" นะ
Rule 5 : Single-Command
ตัวนี้แหนะซับซ้อน! บอกเลย!
single-command คือคำสั่งย่อยๆ ที่เราเขียนได้ทั้งหมดในโปรแกรมนี้ ึำถามคือปกติเราเขียนโปรแกรมเราเจอคำสั่งอะไรบ้างล่ะ?
- คำสั่งเซ็ตค่า เช่น x = 10
- คำสั่งเรียกใช้ฟังก์ชั่น เช่น print("เฮลโหล๋~โลกกก")
- คำสั่งเลือกว่าจะทำอะไร คือ if-else อ่ะแหละ เช่น if x > 1 then print("เอ็กซ์มากกว่า1นะ") end
- คำสั่ง loop เช่น while i<100 then i++ end
ราวๆ นั้นล่ะนะ...
ใน เมื่อ command มันเป็นได้หลายแบบ เราเลยต้องเขียนมันให้ครบทุกแบบอ่ะนะ (บอกว่าเขียนได้หลายแบบโดยใช้ RegExp [ | ] คือ "หรือ" นะ เป็นตัวไหนก็ได้ )
เอาตัวอย่างแค่นี้ละกัน ใน Rule 5 เรามี Expression อยู่ก็แปลว่าเราจะต้องไปเขียน Rule ของ Expression ต่ออีกที ทำแบบนี้ไปเรื่อยๆ
ลองมาดูของจริงๆ กันบ้างดีกว่าว่า Syntax Rule หรือ Grammar นี่มันเยอะขนาดไหนกัน
ตัวอย่าง Rule ของภาษา "Nartra"
ไหนๆ ก็ไหนๆ และ ... ขอถือโอกาสสร้างภาษาโปรแกรมใหม่ของตัวเองเลยละกัน (ฮา) โดยภาษานี้ชื่อว่า "Nartra" ละกัน หึหึ
ซึ่งกฎการเขียนภาษา nartra ก็มีดังนี้ (ไม่เหมือนตัวอย่างที่ยกไปอธิบายตอนต้นนะ)
** RegExp ที่เขียนอยู่จะใช้สี กับ สีส้ม เน้นให้นะ
1. Nartra ::= start Name : Dclns Functions Body 2. Dclns ::= let (Dcln ;)+ 3. Dcln ::= Name (, Name)* : Type 4. Type ::= Integer | Char | Bool 5. Functions ::= Function* 6. Function ::= func Name ( Params ) : Dclns Body 7. Params ::= ( Dcln (; Dcln)* )? 8. Body ::= begin Command (; Command)* end 9. Command ::= Name := Expression | Name += Expression | Name ++ | Name ((Expression (, Expression)*)?) | print (Expression (, Expression)*)?) | if Expression then Command (else Command)? end | do Command while Expression end | while Expression then Command end | for Name := Expression to Expression then Command end 10. Expression ::= Term <= Term | Term >= Term | Term != Term | Term < Term | Term > Term | Term == Term | ( Expression ) ? Term : Term | Term 11. Term ::= Term + Factor | Term - Factor | Term * Factor | Term / Factor | Term % Factor | Factor 12. Factor ::= Primary & Factor | Primary | Factor | Primary 13. Primary ::= - Primary | + Primary | integer [use a lexical rule <digits>] | 'char' | Name | ( Expression ) 14. Name ::= Identifier [use a lexical rule <identifier>]
มาดูคำอธิบายแต่ละส่วนกัน
เนื่องจากเวลาเราเขียน Rule เราเริ่มจากตัวที่ใหญ่สุดก่อน แล้วค่อนเขียนตัวย่อยๆ ลงมาเรื่อยๆ จนหมด --> ดังนั้นถ้าอธิบายตัวใหญ่สุดก่อนคงจะงงงว่าแต่ละตัวนี่มันอะไรกันเนี่ย! เลยจะขออธิบายจากตัวเล็กสุดก่อนละกัน (อธิบายจาก rule ข้อ 14 กลับขึ้นไปถึงข้อ 1 ละกัน)
14. Name
ข้อนี้เป็นกฎที่บอกว่า [Name] จะสร้างขึ้นมาจาก Identifier คือชื่ออะไรก็ได้บลาๆๆ ซึ่งการตั้งชื่อพวกนี้จะใช้กฎ lexical rule (ไว้เราค่อยมาพูดถึงมันในบทต่อๆ ไปละกัน)
ตัวอย่าง
x y i num count
13. Primary
ข้อนี้กำหนดกฎของส่วนที่เรียกว่า [Primary] ซึ่งบอกว่ามันอาจจะมีสัญลักษณ์เช่น "-" หรือ "+" นำหน้าได้ นอกจากนี้ยังเป็นตัวอักษร ตัวเลขก็ยังได้
ตัวอย่าง
100 -100 'A' num
12. Factor
เป็นตัว bit operator ในภาษานี้ เอาไว้ทำ AND OR ในระดับบิต
ตัวอย่าง
100 & 200
11. Term
เป็นระดับกลุ่มค่าที่สามารถเอามา บวก ลบ คูณ หาร ม๊อด กันได้แล้ว
ตัวอย่าง
1 + 2 10 * 20 / 40
10. Expression
ระดับนี้เป็นระดับที่เราพอจะเริ่มเห็นภาพแล้วว่ามัน คืออะไร ถ้า Term คือการเอาตัวเลข+ตัวแปรมาผสมกันเป็น "สมการ" แล้วละก็ ... expression ก็คือรูปแบบ "สมการที่มีการเปรียบเที่ยบเพิ่มเข้ามา" นั่นแหละนะ
ตัวอย่าง
-10 + x * ( 20 - y ) >= r * count + 1
โดยที่ -10
+ x * ( 20 - y )
>= r * count + 1
- ทั้งหมดเนี่ยถือว่าเป็น Expression (ถือไม่มีการเรียกใช้ >= มีแค่สมการอย่างเดียวก็ยังนับเป็น Expressionได้นะ)
- ดังนั้นในเคสนี้ สมการ 2 ข้างจะนับว่าประกอบมาจาก Term
- ตัวอย่างนี้ไม่มี Factor ข้ามไปเป็น Primary เลย
- ส่วนพวกตัวแปรก็จะเป็น Name ไปนะ
9. Command
ส่วนนี้บางทีจะเรียกว่า "Statement" หรือเรียกง่ายๆ คือคำสั่ง (ย่อยๆ อ่ะนะ) เอาไว้บอกว่าโปรแกรมเราจะต้องทำอะไรยังไงบ้าง
ซึ่งส่วนใหญ่แล้ว command มันจะประกอบขึ้นมาจาก command ย่อยและ expression ละนะ
สำหรับภาษา Nartra นี้ได้กำหมดไว้ว่าคำสั่งทั้งหมดมีแบบนี้
- Assign Command - เอาไว้เซ็ตค่าตัวแปร เช่น
x := 10
- Increment by - เอาไว้เพิ่มค่าให้ตัวแปรตามที่เรากำหนด เช่น
x += 10 (อารมณ์คล้ายๆ ภาษา C, Java พวกนั้นเลย)
- Increment one - เพิ่มค่าให้ตัวแปรอีก 1 เช่น
x++
- Call Function - เรียกใช้ฟังก์ชั่น เช่น
random() min(10, x)
ใน Rule ตัวนี้ลองสังเกตนะ ว่ามันมี RegExp ผสมอยู่ด้วยซึ่งเป็น [ ? ] หมายความว่าเราสามารถส่ง "parameter" เข้าไปได้ด้วยล่ะ (กี่ตัวก็ได้เพราะเป็น [ * ] )
- output - คำสั่งสำหรับแสดงผล ความจริงตอนแรกกะว่าจะถือเป็น Function ตัวหนึ่งแบบข้อที่แล้ว แต่อยากแสดงตัวอย่างให้ดูว่าเราจะกำหนด Rule ลงไปเลยก็ยังได้นะว่ามันสามารถเรียกแบบนี้ได้
print( x, 10, 'A' )
- If (Condition) - คำสั่ง if นั่นแหละ เขียนแบบ
if x > 0 then x := 1 end
ซึ่งเราอนุญาตว่าอาจจะมี else เข้ามาผสมด้วยก็ได้นะ (เป็น RegExp [ ? ] )
- Loop 3 ตัวคือ do..while, while, for เช่น
do i++ while i<100 end while i<100 then i++ end for i := 1 to 100 then print( i ) end
8. Body
เป็นส่วนเนื้อหาหลักของโปรแกรมเรา คล้ายๆ กับที่อธิบายไปข้างต้นโน่นแล้ว มันก็มี command หลายๆ ตัวอยู่นั่นแหละ
ตัวอย่าง
begin x := 1 ; if i > 10 then y := x + 10 else y := 20 end z := min(x, y) ; print( z ) end
7. Params
พารามิเตอร์นี่มันจะใช้คู่กับเวลาประกาศ Function ล่ะ ซึ่งมันจะประกอบมาจาก Dcln (Declaration) ถ้านึกไม่ออกว่าทำไมมันต้องเป็นส่วนประกาศค่าลองดูโค้ดนี่ในภาษาอื่น
// C / Java void my_function( int a, char b ) {...} // PHP / JavaScript function my_function( $a, $b ) {...}
ใช่มั้ยล่ะ! ภาษาอื่นถ้าจะเขียนรับพารามิเตอร์ก็ต้องรับแบประกาศตัวแปรเลย งั้นภาษาเราก็ทำบ้าง (ฮา)
อ้อ เรื่องรูปแบบ Rule ที่เป็น RegExp ไม่อธิบายแล้วนะ มันเหมือนเดิมอ่ะ
6. Function
ส่วนนี้เอาไว้ประกาศฟังก์ชั่นที่เราสร้างเอง
ตัวอย่าง
func my_function ( a : Integer ; b : char) : begin print( a, b ) end
รูป แบบการสร้างฟังก์ชั่นก็คือมีชื่อที่เรากำหมดเองได้ รับพารามิเตอร์มา แล้วจะทำอะไรต่อก็เขียนใน begin ... end นั่นแหละนะ ง่ายๆ ตรงๆ เลย
5. Functions
อันนี้ไม่มีอะไรเลย เข้าใจง่าย Functions ก็คือ Function หลายๆ ตัวนั่นแหละ (หรือไม่มีเลยก็ได้) เพราะปกติแล้วในการเขียนโปรแกรม เราไม่ได้สร้างฟังก์ชั่นได้แค่ตัวเดียว มันสร้างหลายๆ ตัวได้
4. Type
คือชนิดตัวแปร ซึ่งภาษานี้มีให้เลือกแค่ 3 ตัวคือ Integer, Char, Bool แค่นั้นแหละ เราจะเอาไปใช้ร่วมกับ Dcln (การประกาศตัวแปรต้องบอกว่ามันเป็น type อะไรไง)
3. Dcln
ส่วนการประกาศตัวแปรเอามาใช้ในโปรแกรม เช่นถ้าเป็นภาษา Java เราจะใช้แบบ int a; int x, y, z;
แต่ภาษา Nartra จะใช้แบบนี้
x : Integer x , y , z : Integer
2. Dclns
เช่นเคย การประกาศตัวแปรในโปรแกรม มันไม่ได้ประกาศกันตัวเดียว บางทีมันก็มีหลายตัว ดังนั้นเราก็เลยต้องมี Dcln หลายตัวกลายเป็น Dclns ไงล่ะ
1. Nartra
สุดท้ายของท้ายที่สุด หลังจากเรารู้จัดทุกส่วนประกอบของภาษาโปรแกรมเราเลย ก็ต้องบอกว่า Rule ของโปรแกรมเราจะเริ่มและจบด้วยการเขียนแบบนี้ๆๆ นะ
ตัวอย่างแบบเต็ม คือเอาทุกส่วนมาใช้เลย
start MyFirstProgram :
let
x , y , z : Integer ;
c , r , : Char ;
func my_func( a , b : Integer ; x : Bool ) :
let
i : Integer;
begin
for i := a to b then
print( i )
end
end
begin
x := 1 ;
y := 100 ;
if x < y then
my_func( x , y , 1 )
end
end
เรื่อง Grammar ของภาษาก็ขอจบไว้แค่นี้ละกัน ในบทต่อไปเราจะมาดูกันต่อว่าหลังจากเรากำหมด Grammar เสร็จเรียบร้อยจะทำยังไงละ โปรแกรมคอมไพเลอร์ถึงจะอ่านโค้ดเราออก
คำตอบก็คือใช้สิ่งที่เรียกว่า "Parser" เป็นตัวอ่าน ... แล้วเจอกันบทต่อไป ^__^