เหตุใดการเรียกคืนซ้ำจึงไม่ดี


20

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


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

6
การเรียกซ้ำทางซ้ายนั้นไม่ดีเพราะในสมัยก่อนเมื่อคอมพิวเตอร์มี RAM ขนาด 16 KB ตัวแยกวิเคราะห์ที่ใช้บ่อยที่สุดไม่สามารถรับมือกับมันได้
Andrej Bauer

คำตอบ:


15

ซ้าย recursive ไวยากรณ์ไม่จำเป็นต้องมีสิ่งที่ไม่ดี ไวยากรณ์เหล่านี้มีการแยกวิเคราะห์อย่างง่ายดายโดยใช้สแต็คในการติดตามของวลีที่แยกวิเคราะห์แล้วมันเป็นกรณีในLR parser

โปรดจำไว้ว่ากฎการเรียกซ้ำซ้ำซากของ CF ไวยากรณ์ เป็นรูปแบบ:G=(V,Σ,R,S)

ααβ

กับองค์ประกอบของและองค์ประกอบของ\ (ดูคำจำกัดความที่เป็นทางการที่สมบูรณ์สำหรับ tupleนั่น )αVβVΣ(V,Σ,R,S)

โดยปกติแล้วเป็นลำดับของเทอร์มินัลและไม่ใช่เทอร์มินัลและมีกฎอื่นสำหรับโดยที่ไม่ปรากฏที่ด้านขวามือβαα

เมื่อใดก็ตามที่ขั้วใหม่จะถูกรับโดย parser ไวยากรณ์ (จาก lexer) ที่สถานีนี้จะถูกผลักบนสแต็ค: การดำเนินการนี้เรียกว่าการเปลี่ยนแปลง

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

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


มันจะช่วยถ้าคุณกำหนดตัวแปรของคุณ
Andrew S

12

พิจารณากฎนี้:

example : 'a' | example 'b' ;

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

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


3
คุณกำลังเรียงลำดับของสุ่มสี่สุ่มห้าสมมติว่าการแยกวิเคราะห์นั้นจำเป็นต้องแยกวิเคราะห์จากบนลงล่างอย่างไร้เดียงสา
reinierpost

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

ใช่นั่นเป็นวิธีที่สร้างสรรค์และมีประโยชน์มากขึ้นในการวาง
reinierpost

4

(ตอนนี้ฉันรู้ว่าคำถามนี้ค่อนข้างเก่า แต่ในกรณีที่คนอื่นมีคำถามเดียวกัน ... )

คุณกำลังถามในบริบทของตัวแยกวิเคราะห์ที่สืบเชื้อสายซ้ำหรือไม่? ตัวอย่างเช่นสำหรับไวยากรณ์expr:: = expr + term | termทำไมบางสิ่งเช่นนี้ (เหลือซ้ำ):

// expr:: = expr + term
expr() {
   expr();
   if (token == '+') {
      getNextToken();
   }
   term();
}

เป็นปัญหา แต่ไม่ใช่สิ่งนี้ (เรียกซ้ำถูก)

// expr:: = term + expr
expr() {
   term();
   if (token == '+') {
      getNextToken();
      expr();
   }
}

ดูเหมือนว่าจะมีการexpr()โทรด้วยกันทั้งสองรุ่น แต่ความแตกต่างที่สำคัญคือบริบท - เช่นโทเค็นปัจจุบันเมื่อมีการโทรซ้ำ

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

ตัวอย่างเช่นพิจารณาแยก 2 + 3 + 4 ซ้าย recursive โทร parser expr()อนันต์ขณะที่ติดอยู่บนโทเค็นแรกในขณะที่ด้านขวา recursive กิน parser "2 +" ก่อนที่จะเรียกexpr()อีกครั้ง สายที่สองที่expr()ตรงกับ "3 +" และสายที่expr()เหลือเพียง 4 สาย 4 แมตช์กับคำและยุติการแยกโดยไม่ต้องสายใด ๆ expr()มากขึ้นเพื่อ


2

จากคู่มือ Bison:

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

http://www.gnu.org/software/bison/manual/html_node/Recursion.html

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

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