compiler – ตอนที่ 4.2 (ต่อ) Grammar ไวยากรณ์ภาษา ทำยังไงให้คอมมันรู้ว่าประโยคที่พิมพ์แปลว่าอะไร

อ่านตอนก่อนหน้านี้ได้ที่ #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 นี้ได้กำหมดไว้ว่าคำสั่งทั้งหมดมีแบบนี้

  1. Assign Command - เอาไว้เซ็ตค่าตัวแปร เช่น
    x := 10
  2. Increment by - เอาไว้เพิ่มค่าให้ตัวแปรตามที่เรากำหนด เช่น
    x += 10 (อารมณ์คล้ายๆ ภาษา C, Java พวกนั้นเลย)
  3. Increment one - เพิ่มค่าให้ตัวแปรอีก 1 เช่น
    x++
  4. Call Function - เรียกใช้ฟังก์ชั่น เช่น
    random()
    min(10, x)

    ใน Rule ตัวนี้ลองสังเกตนะ ว่ามันมี RegExp ผสมอยู่ด้วยซึ่งเป็น [ ? ] หมายความว่าเราสามารถส่ง "parameter" เข้าไปได้ด้วยล่ะ (กี่ตัวก็ได้เพราะเป็น [ * ] )

  5. output - คำสั่งสำหรับแสดงผล ความจริงตอนแรกกะว่าจะถือเป็น Function ตัวหนึ่งแบบข้อที่แล้ว แต่อยากแสดงตัวอย่างให้ดูว่าเราจะกำหนด Rule ลงไปเลยก็ยังได้นะว่ามันสามารถเรียกแบบนี้ได้
    print( x, 10, 'A' )
  6. If (Condition) - คำสั่ง if นั่นแหละ เขียนแบบ
    if x > 0 then x := 1 end

    ซึ่งเราอนุญาตว่าอาจจะมี else เข้ามาผสมด้วยก็ได้นะ (เป็น RegExp [ ? ] )

  7. 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" เป็นตัวอ่าน ... แล้วเจอกันบทต่อไป ^__^

 

617 Total Views 3 Views Today
Ta

Ta

สิ่งมีชีวิตตัวอ้วนๆ กลมๆ เคลื่อนที่ไปไหนโดยการกลิ้ง .. ถนัดการดำรงชีวิตโดยไม่โดนแสงแดด
ปัจจุบันเป็น Senior Software Engineer อยู่ที่ Centrillion Technology
งานอดิเรกคือ เขียนโปรแกรม อ่านหนังสือ เขียนบทความ วาดรูป และ เล่นแบดมินตัน

You may also like...

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องที่ต้องการถูกทำเครื่องหมาย *