การออกแบบคอมไพเลอร์ - การวิเคราะห์ไวยากรณ์

การวิเคราะห์ไวยากรณ์หรือการแยกวิเคราะห์เป็นขั้นตอนที่สองของคอมไพเลอร์ ในบทนี้เราจะเรียนรู้แนวคิดพื้นฐานที่ใช้ในการสร้างโปรแกรมแยกวิเคราะห์

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

CFG ในทางกลับกันเป็นส่วนเหนือของไวยากรณ์ปกติดังที่แสดงด้านล่าง:

หมายความว่าไวยากรณ์ปกติทุกตัวไม่มีบริบทเช่นกัน แต่มีปัญหาบางอย่างซึ่งอยู่นอกเหนือขอบเขตของไวยากรณ์ปกติ CFG เป็นเครื่องมือที่มีประโยชน์ในการอธิบายไวยากรณ์ของภาษาโปรแกรม

ไวยากรณ์ที่ไม่มีบริบท

ในส่วนนี้ก่อนอื่นเราจะดูคำจำกัดความของไวยากรณ์ที่ไม่มีบริบทและแนะนำคำศัพท์ที่ใช้ในเทคโนโลยีการแยกวิเคราะห์

ไวยากรณ์ที่ไม่มีบริบทมีองค์ประกอบสี่ส่วน:

  • ชุดของ non-terminals(V). ไม่ใช่เทอร์มินัลเป็นตัวแปรทางวากยสัมพันธ์ที่แสดงถึงชุดของสตริง non-terminals กำหนดชุดของสตริงที่ช่วยกำหนดภาษาที่สร้างโดยไวยากรณ์

  • ชุดของโทเค็นที่เรียกว่า terminal symbols(Σ). เทอร์มินัลเป็นสัญลักษณ์พื้นฐานที่ใช้สร้างสตริง

  • ชุดของ productions(ป). การผลิตของไวยากรณ์ระบุลักษณะที่สามารถรวมเทอร์มินัลและไม่ใช่เทอร์มินัลเพื่อสร้างสตริงได้ การผลิตแต่ละรายการประกอบด้วยไฟล์non-terminal เรียกว่าด้านซ้ายของการผลิตลูกศรและลำดับของโทเค็นและ / หรือ on- terminalsเรียกว่าด้านขวาของการผลิต

  • หนึ่งในขั้วที่ไม่ใช่ขั้วถูกกำหนดให้เป็นสัญลักษณ์เริ่มต้น (S); จากจุดเริ่มต้นของการผลิต

สตริงได้มาจากสัญลักษณ์เริ่มต้นโดยการแทนที่เทอร์มินัลที่ไม่ใช่เทอร์มินัลซ้ำ ๆ (เริ่มแรกคือสัญลักษณ์เริ่มต้น) ทางด้านขวาของการผลิตสำหรับสิ่งที่ไม่ใช่เทอร์มินัลนั้น

ตัวอย่าง

เราใช้ปัญหาของภาษา palindrome ซึ่งไม่สามารถอธิบายได้ด้วย Regular Expression นั่นคือ L = {w | w = w R } ไม่ใช่ภาษาปกติ แต่สามารถอธิบายได้โดยใช้ CFG ดังภาพประกอบด้านล่าง:

G = ( V, Σ, P, S )

ที่ไหน:

V = { Q, Z, N }
Σ = { 0, 1 }
P = { Q → Z | Q → N | Q → ℇ | Z → 0Q0 | N → 1Q1 }
S = { Q }

ไวยากรณ์นี้อธิบายภาษา Palindrome เช่น: 1001, 11100111, 00100, 1010101, 11111 เป็นต้น

เครื่องวิเคราะห์ไวยากรณ์

ตัววิเคราะห์ไวยากรณ์หรือตัวแยกวิเคราะห์จะรับอินพุตจากตัววิเคราะห์คำศัพท์ในรูปแบบของโทเค็นสตรีม ตัวแยกวิเคราะห์จะวิเคราะห์ซอร์สโค้ด (โทเค็นสตรีม) กับกฎการใช้งานจริงเพื่อตรวจหาข้อผิดพลาดในโค้ด ผลลัพธ์ของเฟสนี้คือไฟล์parse tree.

ด้วยวิธีนี้โปรแกรมแยกวิเคราะห์จะทำงานสองอย่างให้สำเร็จนั่นคือการแยกวิเคราะห์รหัสค้นหาข้อผิดพลาดและสร้างแผนภูมิแยกวิเคราะห์เป็นผลลัพธ์ของเฟส

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

ที่มา

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

  • การตัดสินใจว่าจะเปลี่ยนขั้วที่ไม่ใช่ขั้วใด
  • การตัดสินใจกฎการผลิตโดยที่ไม่ใช่ขั้วจะถูกแทนที่

ในการตัดสินใจว่าจะแทนที่ non-terminal ใดด้วยกฎการผลิตเรามีสองตัวเลือก

การมาจากซ้ายสุด

หากรูปแบบความรู้สึกของข้อมูลเข้าถูกสแกนและแทนที่จากซ้ายไปขวาจะเรียกว่าการมาจากซ้ายสุด รูปแบบความรู้สึกที่ได้มาจากการมาจากทางซ้ายสุดเรียกว่ารูปแบบความรู้สึกทางซ้าย

ที่มาที่ถูกต้องที่สุด

หากเราสแกนและแทนที่อินพุตด้วยกฎการผลิตจากขวาไปซ้ายจะเรียกว่าการมาจากขวาสุด รูปแบบความรู้สึกที่ได้มาจากรากศัพท์ด้านขวาที่สุดเรียกว่ารูปแบบความรู้สึกทางขวา

Example

กฎการผลิต:

E → E + E
E → E * E
E → id

สตริงอินพุต: id + id * id

รากศัพท์ด้านซ้ายสุดคือ:

E → E * E
E → E + E * E
E → id + E * E
E → id + id * E
E → id + id * id

สังเกตว่า non-terminal ด้านซ้ายสุดจะถูกประมวลผลก่อนเสมอ

รากศัพท์ที่ถูกต้องที่สุดคือ:

E → E + E
E → E + E * E
E → E + E * id
E → E + id * id
E → id + id * id

แยกวิเคราะห์ต้นไม้

ต้นไม้แยกวิเคราะห์คือการแสดงภาพกราฟิกของการได้มา สะดวกในการดูว่าสตริงมาจากสัญลักษณ์เริ่มต้นอย่างไร สัญลักษณ์เริ่มต้นของการได้มากลายเป็นรากของต้นไม้แยกวิเคราะห์ ให้เราดูตัวอย่างจากหัวข้อสุดท้าย

เราหารากศัพท์ทางซ้ายสุดของ a + b * c

รากศัพท์ด้านซ้ายสุดคือ:

E → E * E
E → E + E * E
E → id + E * E
E → id + id * E
E → id + id * id

ขั้นตอนที่ 1:

E → E * E

ขั้นตอนที่ 2:

E → E + E * E

ขั้นตอนที่ 3:

E → id + E * E

ขั้นตอนที่ 4:

E → id + id * E

ขั้นตอนที่ 5:

E → id + id * id

ในต้นไม้แยกวิเคราะห์:

  • โหนดใบทั้งหมดเป็นขั้ว
  • โหนดภายในทั้งหมดไม่ใช่เทอร์มินัล
  • การส่งผ่านตามลำดับให้สตริงอินพุตดั้งเดิม

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

ความคลุมเครือ

ไวยากรณ์ G มีความคลุมเครือหากมีโครงสร้างการแยกวิเคราะห์มากกว่าหนึ่งรายการ (รากศัพท์ด้านซ้ายหรือด้านขวา) อย่างน้อยหนึ่งสตริง

Example

E → E + E
E → E – E
E → id

สำหรับสตริง id + id - id ไวยากรณ์ข้างต้นจะสร้างโครงสร้างการแยกวิเคราะห์สองแบบ:

มีการกล่าวถึงภาษาที่สร้างโดยไวยากรณ์ที่ไม่ชัดเจน inherently ambiguous. ความคลุมเครือในไวยากรณ์ไม่ดีสำหรับการสร้างคอมไพเลอร์ ไม่มีวิธีใดที่สามารถตรวจจับและลบความคลุมเครือได้โดยอัตโนมัติ แต่สามารถลบออกได้โดยการเขียนไวยากรณ์ใหม่ทั้งหมดโดยไม่มีความคลุมเครือหรือโดยการตั้งค่าและปฏิบัติตามข้อ จำกัด ในการเชื่อมโยงและลำดับความสำคัญ

ความสัมพันธ์

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

Example

การดำเนินการเช่นการบวกการคูณการลบและการหารจะเหลือเพียงการเชื่อมโยง หากนิพจน์ประกอบด้วย:

id op id op id

จะได้รับการประเมินเป็น:

(id op id) op id

ตัวอย่างเช่น (id + id) + id

การดำเนินการเช่นการยกกำลังเป็นการเชื่อมโยงที่ถูกต้องกล่าวคือลำดับของการประเมินผลในนิพจน์เดียวกันจะเป็น:

id op (id op id)

ตัวอย่างเช่น id ^ (id ^ id)

ลำดับความสำคัญ

หากตัวดำเนินการสองตัวใช้ตัวถูกดำเนินการร่วมกันลำดับความสำคัญของตัวดำเนินการจะตัดสินใจว่าจะใช้ตัวถูกดำเนินการใด นั่นคือ 2 + 3 * 4 สามารถมีต้นไม้แยกวิเคราะห์ที่แตกต่างกันได้สองแบบโดยต้นหนึ่งที่สอดคล้องกับ (2 + 3) * 4 และอีกอันที่เกี่ยวข้องกับ 2+ (3 * 4) ด้วยการกำหนดลำดับความสำคัญระหว่างตัวดำเนินการปัญหานี้สามารถลบออกได้อย่างง่ายดาย ดังตัวอย่างก่อนหน้านี้ทางคณิตศาสตร์ * (การคูณ) มีความสำคัญมากกว่า + (การบวก) ดังนั้นนิพจน์ 2 + 3 * 4 จะถูกตีความเป็น:

2 + (3 * 4)

วิธีการเหล่านี้ช่วยลดโอกาสของความคลุมเครือในภาษาหรือไวยากรณ์

วนซ้าย

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

Example:

(1) A => Aα | β

(2) S => Aα | β 
    A => Sd

(1) เป็นตัวอย่างของการเรียกซ้ำด้านซ้ายทันทีโดยที่ A คือสัญลักษณ์ที่ไม่ใช่เทอร์มินัลใด ๆ และαแทนสตริงของขั้วที่ไม่ใช่ขั้ว

(2) เป็นตัวอย่างของการเรียกซ้ำทางอ้อมด้านซ้าย

ตัวแยกวิเคราะห์จากบนลงล่างจะแยกวิเคราะห์ A ก่อนซึ่งในทางกลับกันจะให้สตริงที่ประกอบด้วย A เองและตัวแยกวิเคราะห์อาจวนซ้ำตลอดไป

การลบการเรียกซ้ำด้านซ้าย

วิธีหนึ่งในการลบการเรียกซ้ำทางซ้ายคือการใช้เทคนิคต่อไปนี้:

การผลิต

A => Aα | β

ถูกแปลงเป็นโปรดักชั่นต่อไปนี้

A => βA'
A'=> αA' | ε

สิ่งนี้ไม่ส่งผลกระทบต่อสตริงที่มาจากไวยากรณ์ แต่จะลบการเรียกซ้ำทางซ้ายทันที

วิธีที่สองคือการใช้อัลกอริทึมต่อไปนี้ซึ่งควรกำจัดการวนซ้ำทางซ้ายและทางอ้อมทั้งหมด

START

Arrange non-terminals in some order like A1, A2, A3,…, An

   for each i from 1 to n
      {
      for each j from 1 to i-1
         {
         replace each production of form Ai ⟹Aj
         with Ai ⟹ δ1  | δ2 | δ3 |…|  
         where Aj ⟹ δ1 | δ2|…| δn  are current Aj productions
         }
      }
   eliminate immediate left-recursion
   
END

Example

ชุดการผลิต

S => Aα | β 
A => Sd

หลังจากใช้อัลกอริทึมข้างต้นควรเป็น

S => Aα | β 
A => Aαd | βd

จากนั้นลบการเรียกซ้ำทางซ้ายทันทีโดยใช้เทคนิคแรก

A  => βdA'
A' => αdA' | ε

ตอนนี้ไม่มีการผลิตใดที่มีการเรียกซ้ำด้านซ้ายทั้งทางตรงและทางอ้อม

แฟคตอริ่งด้านซ้าย

หากกฎการผลิตไวยากรณ์มากกว่าหนึ่งรายการมีสตริงคำนำหน้าทั่วไปตัวแยกวิเคราะห์จากบนลงล่างจะไม่สามารถเลือกได้ว่าควรใช้การผลิตใดในการแยกวิเคราะห์สตริงในมือ

Example

หากตัวแยกวิเคราะห์จากบนลงล่างพบการผลิตเช่น

A ⟹ αβ | α | …

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

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

Example

โปรดักชั่นข้างต้นสามารถเขียนเป็นไฟล์

A => αA'
A'=> β |  | …

ตอนนี้ตัวแยกวิเคราะห์มีการผลิตเพียงรายการเดียวต่อคำนำหน้าซึ่งช่วยให้ตัดสินใจได้ง่ายขึ้น

ชุดแรกและชุดติดตาม

ส่วนสำคัญของการสร้างตาราง parser คือการสร้างชุดแรกและตามหลัง ชุดเหล่านี้สามารถระบุตำแหน่งจริงของเทอร์มินัลใดก็ได้ในการสร้าง สิ่งนี้ทำเพื่อสร้างตารางการแยกวิเคราะห์ที่การตัดสินใจแทนที่ T [A, t] = αด้วยกฎการผลิตบางส่วน

ชุดแรก

ชุดนี้สร้างขึ้นเพื่อให้ทราบว่าสัญลักษณ์เทอร์มินัลใดได้มาในตำแหน่งแรกโดย non-terminal ตัวอย่างเช่น,

α → t β

นั่นคือαมาจาก t (เทอร์มินัล) ในตำแหน่งแรกสุด ดังนั้น t ∈ FIRST (α)

อัลกอริทึมสำหรับการคำนวณชุดแรก

ดูคำจำกัดความของชุด FIRST (α):

  • ถ้าαเป็นเทอร์มินัลแล้ว FIRST (α) = {α}
  • ถ้าαไม่ใช่เทอร์มินัลและα→ℇเป็นการผลิตดังนั้น FIRST (α) = {ℇ}
  • ถ้าαเป็น non-terminal และα→ 1 2 3 … n และ FIRST () ใด ๆ มี t ดังนั้น t จะอยู่ใน FIRST (α)

ชุดแรกสามารถมองเห็นได้ดังนี้:

ติดตาม Set

ในทำนองเดียวกันเราคำนวณว่าสัญลักษณ์เทอร์มินัลใดตามหลังαที่ไม่ใช่เทอร์มินัลในกฎการผลิตทันที เราไม่ได้พิจารณาสิ่งที่ non-terminal สามารถสร้างได้ แต่เราจะเห็นว่าอะไรคือสัญลักษณ์เทอร์มินัลถัดไปที่ตามหลังการผลิตของ non-terminal

อัลกอริทึมสำหรับการคำนวณชุดติดตาม:

  • ถ้าαเป็นสัญลักษณ์เริ่มต้นให้ FOLLOW () = $

  • ถ้าαไม่ใช่เทอร์มินัลและมีการผลิตα→ AB FIRST (B) จะอยู่ใน FOLLOW (A) ยกเว้นℇ

  • ถ้าαไม่ใช่เทอร์มินัลและมีการผลิตα→ AB โดยที่ B ℇดังนั้น FOLLOW (A) จะอยู่ใน FOLLOW (α)

ชุดติดตามสามารถมองเห็นได้ดังนี้: FOLLOW (α) = {t | S * αt *}

ข้อ จำกัด ของเครื่องวิเคราะห์ไวยากรณ์

ตัววิเคราะห์ไวยากรณ์ได้รับอินพุตในรูปแบบของโทเค็นจากตัววิเคราะห์ศัพท์ ตัววิเคราะห์คำศัพท์มีหน้าที่รับผิดชอบต่อความถูกต้องของโทเค็นที่มาจากเครื่องวิเคราะห์ไวยากรณ์ เครื่องวิเคราะห์ไวยากรณ์มีข้อบกพร่องดังต่อไปนี้ -

  • ไม่สามารถระบุได้ว่าโทเค็นถูกต้องหรือไม่
  • ไม่สามารถระบุได้ว่ามีการประกาศโทเค็นก่อนที่จะใช้หรือไม่
  • ไม่สามารถระบุได้ว่าโทเค็นถูกเตรียมใช้งานก่อนที่จะถูกใช้หรือไม่
  • ไม่สามารถระบุได้ว่าการดำเนินการกับประเภทโทเค็นถูกต้องหรือไม่

งานเหล่านี้ทำได้โดยตัววิเคราะห์ความหมายซึ่งเราจะศึกษาในการวิเคราะห์ความหมาย