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

 

335 Total Views 3 Views Today
Ta

Ta

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

You may also like...

ใส่ความเห็น

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