ในแง่ของคนธรรมดาเรียกซ้ำคืออะไร?


12

ตามหน้าหนึ่งใน code.google.com มีการกำหนด "การเรียกซ้ำแบบด้านซ้าย" ดังนี้:

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

Wikipediaมีคำจำกัดความที่แตกต่างกันสองแบบ:

  1. ในแง่ของไวยากรณ์ที่ไม่ใช้บริบทเทอร์มินัล r จะไม่เกิดซ้ำถ้าสัญลักษณ์ซ้ายสุดในโปรดักชั่นใด ๆ ของ r (ทางเลือก ') ไม่ว่าจะในทันที คำจำกัดความ (ทางอ้อม / ซ่อนซ้ายซ้ำ) เขียนซ้ำเพื่อ r อีกครั้ง

  2. "ไวยากรณ์ซ้ำซากถ้าเราสามารถหาเทอร์มินัล A บางอันซึ่งในที่สุดจะได้รับแบบฟอร์มประโยคด้วยตัวมันเองเป็นสัญลักษณ์ซ้าย"

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

คำตอบ:


21

กฎRจะวนซ้ำถ้าหากคุณต้องการทราบว่าการRจับคู่คุณต้องค้นหาว่าการRจับคู่นั้นตรงกันหรือไม่ สิ่งนี้เกิดขึ้นเมื่อRปรากฏโดยตรงหรือโดยอ้อมในฐานะเทอมแรกในการผลิตบางส่วนของตัวเอง

ลองนึกภาพไวยากรณ์ของเล่นสำหรับการแสดงออกทางคณิตศาสตร์ด้วยการเติมและการคูณเท่านั้นเพื่อหลีกเลี่ยงความฟุ้งซ่าน:

Expression ::= Multiplication '+' Expression
            || Multiplication

Multiplication ::= Term '*' Term
                 || Term

Term ::= Number | Variable

ตามที่เขียนไว้ไม่มีการเรียกซ้ำซากที่นี่เราสามารถส่งไวยากรณ์นี้ไปยังโปรแกรมแยกวิเคราะห์ที่ซ้ำซ้อน

แต่สมมติว่าคุณพยายามเขียนด้วยวิธีนี้:

Expression ::= Expression '*' Expression
            || Expression '+' Expression
            || Term

Term ::= Number | Variable

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

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


ดังนั้นในตัวอย่างที่สองถ้าคุณเปลี่ยนสิ่งแรกหลังจาก::=จากExpressionเป็นTermและถ้าคุณทำแบบเดียวกันหลังจากครั้งแรก||มันจะไม่เหลือซ้ำอีกไหม? แต่ถ้าคุณทำหลังจาก::=นั้นเท่านั้น แต่ไม่ใช่||มันจะยังคงวนซ้ำอยู่ใช่ไหม?
Panzercrisis

ดูเหมือนว่าคุณกำลังบอกว่ามีนักแยกวิเคราะห์หลายคนไปจากซ้ายไปขวาหยุดที่สัญลักษณ์ทุกอันและประเมินมันซ้ำ ๆ ในกรณีนี้ถ้าExpressionจะเปลี่ยนเป็นครั้งแรกด้วยTermทั้งหลังจาก::=และหลังแรก||ทุกอย่างจะดี เพราะไม่ช้าก็เร็วมันจะวิ่งเข้ามาในสิ่งที่ไม่เป็นNumberหรือมิได้Variableดังนั้นความสามารถในการตรวจสอบบางสิ่งบางอย่างที่ไม่Expressionได้โดยไม่ต้องดำเนินการต่อไป ...
Panzercrisis

... แต่ถ้าหนึ่งในนั้นยังคงเริ่มต้นExpressionมันอาจจะพบสิ่งที่ไม่ใช่Termและมันก็จะตรวจสอบว่าทุกอย่างExpressionซ้ำแล้วซ้ำอีก มันคืออะไร
Panzercrisis

1
@Panzercrisis มากหรือน้อย คุณต้องค้นหาความหมายของ LL, LR และตัวแยกวิเคราะห์แบบเรียกซ้ำ
ฮอบส์

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

4

ฉันจะเอาแทงใส่ลงไปในข้อตกลงของคนธรรมดา

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

เป็นตัวอย่างไวยากรณ์ทั่วไปในคอมไพเลอร์คือรายการของไอเท็ม ให้จดรายการสตริง ("สีแดง", "สีเขียว", "สีฟ้า") แล้วแยกวิเคราะห์ ฉันสามารถเขียนไวยากรณ์ได้สองสามวิธี ตัวอย่างต่อไปนี้ซ้ำซากโดยตรงหรือซ้ายซ้ำตามลำดับ:

arg_list:                           arg_list:
      STRING                              STRING
    | arg_list ',' STRING               | STRING ',' arg_list 

ต้นไม้สำหรับการแยกวิเคราะห์เหล่านี้:

         (arg_list)                       (arg_list)
          /      \                         /      \
      (arg_list)  BLUE                  RED     (arg_list)
       /       \                                 /      \
   (arg_list) GREEN                          GREEN    (arg_list)
    /                                                  /
 RED                                                BLUE

สังเกตว่ามันเติบโตไปในทิศทางของการเรียกซ้ำ

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

ข้อ จำกัด นี้เป็นรายละเอียดการใช้งานอย่างแท้จริงของการใช้ไวยากรณ์ที่มีตัวแยกวิเคราะห์ LL บนลงล่างไร้เดียงสา (parser สืบเชื้อสายซ้ำ) หากคุณต้องการติดกับไวยากรณ์แบบเรียกซ้ำซ้ำซากซ้ายคุณสามารถจัดการกับมันได้โดยเขียนการผลิตใหม่เพื่อใช้โทเค็นอย่างน้อย 1 โทเค็นก่อนที่จะเรียกซ้ำดังนั้นจึงมั่นใจได้ว่าเราจะไม่ติดขัด สำหรับกฎไวยากรณ์ใด ๆ ที่เหลือซ้ำเราสามารถเขียนใหม่ได้โดยการเพิ่มกฎกลางที่จะทำให้ไวยากรณ์เรียบลงในระดับหนึ่งของ lookahead ซึ่งใช้โทเค็นระหว่างการผลิตแบบเรียกซ้ำ (หมายเหตุ: ฉันไม่ได้บอกว่านี่เป็นวิธีเดียวหรือวิธีที่ต้องการเขียนไวยากรณ์เพียงชี้กฎทั่วไปในตัวอย่างง่ายๆนี้ตัวเลือกที่ดีที่สุดคือการใช้แบบฟอร์มเรียกซ้ำขวา) เนื่องจากวิธีการนี้เป็นวิธีทั่วไป ตัวแยกวิเคราะห์สามารถใช้งานได้โดยไม่ต้องเกี่ยวข้องกับโปรแกรมเมอร์ (ในทางทฤษฎี) ในทางปฏิบัติฉันเชื่อว่า ANTLR 4 จะทำเช่นนั้น

สำหรับไวยากรณ์ข้างต้นการใช้ LL ที่แสดงการเรียกซ้ำซ้ายจะมีลักษณะเช่นนี้ โปรแกรมแยกวิเคราะห์จะเริ่มต้นด้วยการทำนายรายการ ...

bool match_list()
{
    if(lookahead-predicts-something-besides-comma) {
       match_STRING();
    } else if(lookahead-is-comma) {
       match_list();   // left-recursion, infinite loop/stack overflow
       match(',');
       match_STRING();
    } else {
       throw new ParseException();
    }
}

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

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

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

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