ในการออกแบบคอมไพเลอร์เหตุใดจึงควรเหลือการเรียกซ้ำไปเป็นแกรมม่า ฉันกำลังอ่านว่าเป็นเพราะมันสามารถทำให้เกิดการเรียกซ้ำแบบไม่สิ้นสุด แต่มันไม่เป็นความจริงสำหรับไวยากรณ์แบบเรียกซ้ำที่ถูกต้องเช่นกัน?
ในการออกแบบคอมไพเลอร์เหตุใดจึงควรเหลือการเรียกซ้ำไปเป็นแกรมม่า ฉันกำลังอ่านว่าเป็นเพราะมันสามารถทำให้เกิดการเรียกซ้ำแบบไม่สิ้นสุด แต่มันไม่เป็นความจริงสำหรับไวยากรณ์แบบเรียกซ้ำที่ถูกต้องเช่นกัน?
คำตอบ:
ซ้าย recursive ไวยากรณ์ไม่จำเป็นต้องมีสิ่งที่ไม่ดี ไวยากรณ์เหล่านี้มีการแยกวิเคราะห์อย่างง่ายดายโดยใช้สแต็คในการติดตามของวลีที่แยกวิเคราะห์แล้วมันเป็นกรณีในLR parser
โปรดจำไว้ว่ากฎการเรียกซ้ำซ้ำซากของ CF ไวยากรณ์ เป็นรูปแบบ:
กับองค์ประกอบของและองค์ประกอบของ\ (ดูคำจำกัดความที่เป็นทางการที่สมบูรณ์สำหรับ tupleนั่น )
โดยปกติแล้วเป็นลำดับของเทอร์มินัลและไม่ใช่เทอร์มินัลและมีกฎอื่นสำหรับโดยที่ไม่ปรากฏที่ด้านขวามือ
เมื่อใดก็ตามที่ขั้วใหม่จะถูกรับโดย parser ไวยากรณ์ (จาก lexer) ที่สถานีนี้จะถูกผลักบนสแต็ค: การดำเนินการนี้เรียกว่าการเปลี่ยนแปลง
ทุกครั้งที่จับคู่ทางด้านขวามือของกฎโดยกลุ่มองค์ประกอบต่อเนื่องที่ด้านบนสุดของสแต็กกลุ่มนี้จะถูกแทนที่ด้วยองค์ประกอบเดียวที่แสดงวลีที่ตรงกันใหม่ การเปลี่ยนนี้เรียกว่าการลดลง
ด้วยแกรมม่าแบบเรียกซ้ำที่ถูกต้องสแต็กอาจเติบโตอย่างไม่มีกำหนดจนกว่าการลดลงจะเกิดขึ้น อย่างไรก็ตามการวนซ้ำแบบซ้ำจะทำให้คอมไพเลอร์สร้างการลดลงก่อนหน้านี้ (ในความเป็นจริงโดยเร็วที่สุด) ดูรายการวิกิพีเดียสำหรับข้อมูลเพิ่มเติม
พิจารณากฎนี้:
example : 'a' | example 'b' ;
ในตอนนี้ให้ลองใช้ตัวแยกวิเคราะห์ LL ที่พยายามจับคู่สตริงที่ไม่ตรงกันเหมือนกับ'b'
กฎนี้ เนื่องจากไม่ตรงก็จะพยายามให้ตรงกับ'a'
example 'b'
แต่เพื่อที่จะทำเช่นนั้นจะต้องมีการจับคู่example
... ซึ่งเป็นสิ่งที่มันพยายามที่จะทำในสถานที่แรก อาจพยายามอย่างต่อเนื่องตลอดไปเพื่อดูว่าสามารถจับคู่ได้หรือไม่เพราะพยายามจับคู่โทเค็นสตรีมเดียวกันกับกฎเดียวกันเสมอ
เพื่อป้องกันไม่ให้คุณต้องแยกวิเคราะห์จากด้านขวา (ซึ่งค่อนข้างผิดปกติเท่าที่ฉันเคยเห็นและจะทำให้การเรียกซ้ำปัญหาถูกต้องแทน), จำกัด จำนวนการซ้อนที่อนุญาตหรือการจับคู่เทียม โทเค็นก่อนที่การเรียกซ้ำจะเริ่มต้นดังนั้นจึงมีกรณีพื้นฐานเสมอ (กล่าวคือที่โทเค็นทั้งหมดถูกใช้ไปแล้วและยังไม่มีการจับคู่ที่สมบูรณ์) เนื่องจากกฎการเรียกซ้ำถูกต้องทำกฎข้อที่สามอยู่แล้วจึงไม่มีปัญหาเดียวกัน
(ตอนนี้ฉันรู้ว่าคำถามนี้ค่อนข้างเก่า แต่ในกรณีที่คนอื่นมีคำถามเดียวกัน ... )
คุณกำลังถามในบริบทของตัวแยกวิเคราะห์ที่สืบเชื้อสายซ้ำหรือไม่? ตัวอย่างเช่นสำหรับไวยากรณ์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()
มากขึ้นเพื่อ
จากคู่มือ Bison:
"ลำดับใด ๆ ที่สามารถกำหนดได้โดยใช้การเรียกซ้ำซ้ายหรือการเรียกซ้ำที่ถูกต้อง แต่คุณควรใช้การเรียกซ้ำที่เหลืออยู่เสมอเพราะมันสามารถแยกลำดับขององค์ประกอบจำนวนเท่าใดก็ได้ที่มีขอบเขตพื้นที่สแต็ก สัดส่วนกับจำนวนองค์ประกอบในลำดับเนื่องจากองค์ประกอบทั้งหมดจะต้องเลื่อนไปยังสแต็กก่อนที่กฎจะสามารถใช้ได้แม้แต่ครั้งเดียวดูอัลกอริทึม Bison Parser สำหรับคำอธิบายเพิ่มเติมของเรื่องนี้ "
http://www.gnu.org/software/bison/manual/html_node/Recursion.html
ดังนั้นขึ้นอยู่กับอัลกอริทึมของตัวแยกวิเคราะห์ แต่ตามที่ระบุไว้ในคำตอบอื่น ๆ ตัวแยกวิเคราะห์บางตัวอาจไม่ทำงานกับการวนรอบแบบวนซ้ำ