การแยกวิเคราะห์ LL และ LR แตกต่างกันอย่างไร?


คำตอบ:


483

ในระดับสูงความแตกต่างระหว่าง LL parsing และ LR parsing คือ LL parsers เริ่มต้นที่สัญลักษณ์เริ่มต้นและพยายามนำโปรดักชั่นมาถึงสตริงเป้าหมายในขณะที่ LR parsers เริ่มที่สตริงเป้าหมายและพยายามกลับมาที่จุดเริ่มต้น สัญลักษณ์.

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

ในระหว่างการแยกวิเคราะห์ LL ตัวแยกวิเคราะห์จะเลือกระหว่างการกระทำสองอย่างต่อเนื่อง:

  1. การคาดการณ์ : อิงจาก nonterminal ซ้ายสุดและโทเค็น lookahead จำนวนหนึ่งให้เลือกว่าควรใช้การผลิตใดเพื่อให้ใกล้กับสตริงอินพุตมากขึ้น
  2. การจับคู่ : จับคู่สัญลักษณ์เทอร์มินัลที่คาดเดาซ้ายสุดกับสัญลักษณ์ที่ไม่ได้ระบุซ้ายสุดของอินพุต

เป็นตัวอย่างให้ไวยากรณ์นี้:

  • S → E
  • E → T + E
  • E → T
  • T → int

จากนั้นให้สตริงint + int + intตัวแยกวิเคราะห์ LL (2) (ซึ่งใช้สองโทเค็นของ lookahead) จะแยกสตริงดังต่อไปนี้:

Production       Input              Action
---------------------------------------------------------
S                int + int + int    Predict S -> E
E                int + int + int    Predict E -> T + E
T + E            int + int + int    Predict T -> int
int + E          int + int + int    Match int
+ E              + int + int        Match +
E                int + int          Predict E -> T + E
T + E            int + int          Predict T -> int
int + E          int + int          Match int
+ E              + int              Match +
E                int                Predict E -> T
T                int                Predict T -> int
int              int                Match int
                                    Accept

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

ในตัวแยกวิเคราะห์ LR มีสองการกระทำ:

  1. Shift : เพิ่มโทเค็นถัดไปของอินพุตลงในบัฟเฟอร์เพื่อพิจารณา
  2. ลด : ลดการรวบรวมเทอร์มินัลและ nonterminals ในบัฟเฟอร์นี้กลับไปที่ไม่ใช่เทอร์มินัลโดยย้อนกลับการผลิต

ตัวอย่างเช่นตัวแยกวิเคราะห์ LR (1) (ที่มีโทเค็นหนึ่งของ lookahead) อาจแยกสตริงที่เหมือนกันดังนี้:

Workspace        Input              Action
---------------------------------------------------------
                 int + int + int    Shift
int              + int + int        Reduce T -> int
T                + int + int        Shift
T +              int + int          Shift
T + int          + int              Reduce T -> int
T + T            + int              Shift
T + T +          int                Shift
T + T + int                         Reduce T -> int
T + T + T                           Reduce E -> T
T + T + E                           Reduce E -> T + E
T + E                               Reduce E -> T + E
E                                   Reduce S -> E
S                                   Accept

อัลกอริทึมการแยกวิเคราะห์สองแบบที่คุณกล่าวถึง (LL และ LR) นั้นมีลักษณะที่แตกต่างกัน ตัวแยกวิเคราะห์ LL มักจะเขียนด้วยมือได้ง่ายกว่า แต่พวกมันมีพลังน้อยกว่าตัวแยกวิเคราะห์ LR และยอมรับไวยากรณ์น้อยกว่าตัวแยกวิเคราะห์ LR ตัวแยกวิเคราะห์ LR มีหลากหลายรสชาติ (LR (0), SLR (1), LALR (1), LR (1), IELR (1), GLR (0), ฯลฯ ) และมีประสิทธิภาพมากกว่า พวกเขายังมีแนวโน้มที่จะมีความซับซ้อนมากขึ้นและจะมีการสร้างเกือบเสมอโดยเครื่องมือเช่นหรือyacc bisonตัวแยกวิเคราะห์ LL มีหลายรสชาติ (รวมถึง LL (*) ซึ่งใช้โดยANTLRเครื่องมือ) แต่ในทางปฏิบัติ LL (1) นั้นถูกใช้อย่างกว้างขวางที่สุด

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


40
สไลด์บรรยายของคุณเป็นปรากฎการณ์คำอธิบายที่สนุกที่สุดที่ฉันเคยเห็น :) นี่คือสิ่งที่ทำให้เกิดความสนใจ
kizzx2

1
ฉันต้องแสดงความคิดเห็นในสไลด์เช่นกัน! ผ่านพวกเขาทั้งหมดไปเดี๋ยวนี้ ช่วยได้มาก! ขอบคุณ!
kornfridge

เพลิดเพลินไปกับสไลด์จริงๆ ฉันไม่คิดว่าคุณสามารถโพสต์ไฟล์โครงการที่ไม่ใช่ Windows (และไฟล์ scanner.l สำหรับ pp2)? :)
Erik P.

1
สิ่งหนึ่งที่ฉันสามารถมีส่วนร่วมกับคำตอบสรุปที่ยอดเยี่ยมของ Matt คือไวยากรณ์ใด ๆ ที่สามารถแยกวิเคราะห์โดยตัวแยกวิเคราะห์ LL (k) (เช่นการมองไปข้างหน้า "k" เทอร์มินัลเพื่อตัดสินใจเกี่ยวกับการแยกวิเคราะห์ถัดไป) 1) โปรแกรมแยกวิเคราะห์ สิ่งนี้ให้คำแนะนำอย่างหนึ่งเกี่ยวกับพลังที่เหลือเชื่อของ LR ในการแยกวิเคราะห์ LL ที่มา: หลักสูตรคอมไพเลอร์ที่ UCSC สอนโดย Dr. F. DeRemer ผู้สร้างตัวแยกวิเคราะห์ LALR ()
JoGusto

1
ทรัพยากรที่ยอดเยี่ยม! ขอบคุณที่ให้สไลด์เอกสารประกอบคำบรรยายโครงการเช่นกัน
P. Hinker

58

จอช Haberman ในบทความของเขาLL และ LR แยก Demystifiedอ้างว่า LL แยกโดยตรงสอดคล้องกับโปแลนด์โน้ตขณะที่สอดคล้อง LR เพื่อย้อนกลับโปแลนด์สัญกรณ์ ความแตกต่างระหว่าง PN และ RPN คือลำดับของการสำรวจต้นไม้ไบนารีของสมการ:

ต้นไม้ไบนารีของสมการ

+ 1 * 2 3  // Polish (prefix) expression; pre-order traversal.
1 2 3 * +  // Reverse Polish (postfix) expression; post-order traversal.

ตาม Haberman นี่แสดงให้เห็นถึงความแตกต่างที่สำคัญระหว่าง LL และ LR parsers:

ความแตกต่างหลักระหว่างวิธีที่ตัวแยกวิเคราะห์ LL และ LR ทำงานคือ LL ตัวแยกวิเคราะห์แสดงผลลัพธ์การสำรวจเส้นทางการสั่งซื้อล่วงหน้าของต้นไม้แยกวิเคราะห์

สำหรับคำอธิบายในเชิงลึกตัวอย่างและข้อสรุปตรวจสอบ Haberman ของบทความ


9

LL ใช้การเลื่อนจากบนลงล่างในขณะที่ LR ใช้วิธีจากล่างขึ้นบน

หากคุณแยกวิเคราะห์ภาษาการเขียนโปรแกรม:

  • LL เห็นซอร์สโค้ดซึ่งมีฟังก์ชั่นซึ่งมีการแสดงออก
  • LR เห็นนิพจน์ซึ่งเป็นของฟังก์ชั่นซึ่งส่งผลให้แหล่งข้อมูลเต็ม

6

LL การแยกวิเคราะห์เป็นคนพิการเมื่อเทียบกับ LR นี่คือไวยากรณ์ที่เป็นฝันร้ายสำหรับตัวแยกวิเคราะห์ LL:

Goal           -> (FunctionDef | FunctionDecl)* <eof>                  

FunctionDef    -> TypeSpec FuncName '(' [Arg/','+] ')' '{' '}'       

FunctionDecl   -> TypeSpec FuncName '(' [Arg/','+] ')' ';'            

TypeSpec       -> int        
               -> char '*' '*'                
               -> long                 
               -> short                   

FuncName       -> IDENTIFIER                

Arg            -> TypeSpec ArgName         

ArgName        -> IDENTIFIER 

FunctionDef ดูเหมือนกับ FunctionDecl จนกระทั่ง ';' หรือ '{' ถูกพบ

ตัวแยกวิเคราะห์ LL ไม่สามารถจัดการกับกฎสองข้อในเวลาเดียวกันดังนั้นจึงต้องเลือก FunctionDef หรือ FunctionDecl แต่การที่จะรู้ว่าสิ่งใดถูกต้องมันต้องมองหา ';' หรือ '{'. ในช่วงเวลาของการวิเคราะห์ไวยากรณ์ lookahead (k) ดูเหมือนจะไม่มีที่สิ้นสุด ในการแยกวิเคราะห์เวลามัน จำกัด แต่อาจมีขนาดใหญ่

ตัวแยกวิเคราะห์ LR ไม่ต้องมองหาเพราะมันสามารถจัดการกับกฎสองข้อในเวลาเดียวกัน ดังนั้นตัวแยกวิเคราะห์ LALR (1) สามารถจัดการไวยากรณ์นี้ได้อย่างง่ายดาย

รับรหัสการป้อน:

int main (int na, char** arg); 

int main (int na, char** arg) 
{

}

ตัวแยกวิเคราะห์ LR สามารถแยกวิเคราะห์

int main (int na, char** arg)

โดยไม่สนใจว่ากฎใดที่ได้รับการยอมรับจนกว่าจะพบ ';' หรือ '{'

ตัวแยกวิเคราะห์ LL จะถูกวางสายที่ 'int' เนื่องจากจำเป็นต้องรู้ว่ากฎใดที่จะถูกจดจำ ดังนั้นมันจะต้องมองหา ';' หรือ '{'.

ฝันร้ายอื่น ๆ สำหรับตัวแยกวิเคราะห์ LL จะถูกเรียกซ้ำในไวยากรณ์ การเรียกซ้ำทางซ้ายเป็นเรื่องปกติในไวยากรณ์ไม่มีปัญหาสำหรับตัวแยกวิเคราะห์ LR แต่ LL ไม่สามารถจัดการได้

ดังนั้นคุณต้องเขียนไวยากรณ์ของคุณด้วยวิธีที่ผิดธรรมชาติด้วย LL


0

ตัวอย่างที่เหลือมาส่วนใหญ่: ไวยากรณ์ G ซึ่งไม่มีบริบทมีการผลิต

z → xXY (กฎ: 1) X → Ybx (กฎ: 2) Y → bY (กฎ: 3) Y → c (กฎ: 4)

คำนวณ String w = 'xcbxbc' โดยมีค่ามาจากซ้ายสุด

z ⇒ xXY (กฎ: 1) ⇒ xYbxY (กฎ: 2) ⇒ xcbxY (กฎ: 4) ⇒ xcbxbY (กฎ: 3) ⇒ xcbxbc (กฎ: 4)


ตัวอย่างที่ถูกต้องที่สุด: K → aKK (กฎ: 1) A → b (กฎ: 2)

คำนวณ String w = 'aababbb' ด้วยการสืบทอดที่ถูกต้องที่สุด

K ⇒ aKK (กฎ: 1) ⇒ aKb (กฎ: 2) ⇒ aaKKb (กฎ: 1) ⇒ aaKaKKb (กฎ: 1) ⇒ aaKaKbb (กฎ: 2) ⇒ aaKabbb (กฎ: 2)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.