ทุกคนสามารถให้ตัวอย่างง่ายๆของการแยก LL กับการแยก LR ได้ไหม
ทุกคนสามารถให้ตัวอย่างง่ายๆของการแยก LL กับการแยก LR ได้ไหม
คำตอบ:
ในระดับสูงความแตกต่างระหว่าง LL parsing และ LR parsing คือ LL parsers เริ่มต้นที่สัญลักษณ์เริ่มต้นและพยายามนำโปรดักชั่นมาถึงสตริงเป้าหมายในขณะที่ LR parsers เริ่มที่สตริงเป้าหมายและพยายามกลับมาที่จุดเริ่มต้น สัญลักษณ์.
LL แยกวิเคราะห์เป็นมาจากซ้ายไปขวามาซ้ายสุด นั่นคือเราจะพิจารณาสัญลักษณ์อินพุตจากด้านซ้ายไปทางขวาและพยายามสร้างแหล่งที่มาทางซ้ายสุด สิ่งนี้ทำได้โดยเริ่มต้นที่สัญลักษณ์เริ่มต้นและขยายส่วนที่ไม่ใช่เทอร์มินัลซ้ายสุดซ้ำ ๆ จนกว่าเราจะไปถึงสตริงเป้าหมาย LR แยกเป็นซ้ายไปขวาขวาสุดมาซึ่งหมายความว่าเราสแกนจากซ้ายไปขวาและพยายามที่จะสร้างที่มาขวาสุด ตัวแยกวิเคราะห์จะเลือกสตริงย่อยของอินพุตอย่างต่อเนื่องและพยายามที่จะย้อนกลับไปที่ค่าไม่ใช่เทอร์มินัล
ในระหว่างการแยกวิเคราะห์ LL ตัวแยกวิเคราะห์จะเลือกระหว่างการกระทำสองอย่างต่อเนื่อง:
เป็นตัวอย่างให้ไวยากรณ์นี้:
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 มีสองการกระทำ:
ตัวอย่างเช่นตัวแยกวิเคราะห์ 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 ฉันเพิ่งสอนหลักสูตรคอมไพเลอร์เสร็จแล้วและมีเอกสารประกอบคำบรรยายและสไลด์บรรยายในการแยกวิเคราะห์ในเว็บไซต์หลักสูตร ฉันยินดีที่จะอธิบายรายละเอียดเกี่ยวกับพวกเขาหากคุณคิดว่ามันจะมีประโยชน์
จอช 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 ของบทความ
LL ใช้การเลื่อนจากบนลงล่างในขณะที่ LR ใช้วิธีจากล่างขึ้นบน
หากคุณแยกวิเคราะห์ภาษาการเขียนโปรแกรม:
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
ตัวอย่างที่เหลือมาส่วนใหญ่: ไวยากรณ์ 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)