ตัวแยกวิเคราะห์แบบสืบเชื้อสายซ้ำพร้อมการย้อนรอยสำหรับไวยากรณ์


10

คนที่สามารถสอนให้ฉันทำไม parser โคตร recursive กับ backtracking ที่พยายามที่โปรดักชั่นและ (ตามลำดับ) ไม่รู้จักภาษาเกิดขึ้นจากไวยากรณ์AASaSaSaaSaSa | aa

มันจะปรากฏคำเฉพาะแยกจากภาษา\}{a2n | n1}

ฉันสร้างตัวแยกวิเคราะห์ดังกล่าวโดยใช้ตัวแยกวิเคราะห์ ABNF นี้กับกฎการผลิตS = "a" S "a" / "aa"และตัวแยกวิเคราะห์ไม่รู้จักคำaaaaaaเช่น

ฉันคาดหวังว่ามันจะใช้การผลิตจนกระทั่งการต่อเชื่อมของโหนดเทอร์มินัลของการแยกจากซ้ายเริ่มต้นด้วย 7 ของจากนั้นขึ้นไปที่การแยกเลือกการผลิตแทนจนกระทั่งต้นไม้ดูเหมือน นี้:SaSaaSaa

   S 
 / | \
a  S  a
 / | \
a  S  a
  / \
 a   a

2
ทำไมคุณคิดว่ามันไม่สามารถแยกคำนี้
Yuval Filmus

@Yuval ฉันคิดว่ามันควรแยกมันดังนั้นฉันจะต้องหายไปบางสิ่งบางอย่าง
meribold

อ่าตอนนี้คำถามมีเหตุผลมากกว่า ขอบคุณสำหรับการแก้ไข! หากสิ่งที่คุณเขียนเป็นจริง (ฉันไม่ได้ตรวจสอบ) เครื่องกำเนิดไฟฟ้าดูเหมือนว่าจะมีข้อผิดพลาด (หรือไม่ได้ระบุไว้สำหรับไวยากรณ์ของคุณ: ฉันคิดว่ามันไม่น่าเป็นไปได้เนื่องจากไวยากรณ์เป็นระดับประถมศึกษาและไม่คลุมเครือ
Raphael

@ ราฟาเอลฉันแก้ไขคำถามอีกครั้ง (หวังว่าจะไม่เปลี่ยนความหมาย) ฉันได้รับมอบหมายให้อธิบายว่าทำไมตัวแยกวิเคราะห์เช่นนั้นจึงไม่รู้จักคำaaaaaaนั้น
meribold

คุณได้รับต้นไม้นั้นจากที่ไหน ฉันไม่ได้รับมากนักจากตัวแยกวิเคราะห์ ABNF ต้นไม้ที่คุณให้ไม่มีความหมายมากนัก แต่สตริงaaaaaaควรแยกวิเคราะห์และไม่ แต่aaaaจะแยกวิเคราะห์ เห็นได้ชัดว่าคุณถูกต้องเกี่ยวกับพลังของ 2 สิ่งนี้จะต้องถูกบั๊ก มันจะแยกวิเคราะห์เท่านั้นที่มีaa S = "aa" / "a" [S] "a"คุณสามารถติดตามสิ่งที่โปรแกรมแยกวิเคราะห์ทำอะไรได้บ้าง
Babou

คำตอบ:


6

นี่ไม่ใช่คำตอบที่มากนัก แต่ต้นไม้แจงไม่เหมาะกับความคิดเห็นปกติ

คุณไวยากรณ์ควรแยกสตริง aaaaaaSaSa | aaaaaaaa

แต่ต้นไม้แยกมีรูปแบบดังต่อไปนี้:

      S 
     /|\
    / S \
   / /|\ \
  / / S \ \
 / / / \ \ \
a a a   a a a

หรือถ้าคุณชอบการนำเสนอนี้โดยมีเทอร์มินัลเป็นบรรทัดต่าง ๆ

     S 
   / | \
  a  S  a
   / | \
  a  S  a
    / \
   a   a

ฉันตรวจสอบว่าตัวแยกวิเคราะห์ ABNF ดูเหมือนจะไม่ทำงาน แต่ฉันไม่ทราบวิธีการติดตามสิ่งที่ทำ

ดูเหมือนว่ามันจะทำให้ชุด ซึ่งไม่ใช่ไวยากรณ์กำหนดไว้{a2n | n1}

เป็นเรื่องที่น่าแปลกใจที่มีไซต์ที่ซับซ้อนรอบ ๆ ตัวแยกวิเคราะห์บั๊กกี้ซึ่งใช้เทคนิคการแยกวิเคราะห์ที่ไม่น่าสนใจอย่างสิ้นเชิง


หลังจากดูเพิ่มเติม:

ฉันคิดว่าฉันพบปัญหาหนึ่งสาเหตุ วงเล็บหมายถึง ตัวเลือก

ดังนั้นไวยากรณ์ของคุณควรจะเขียนอย่างใดอย่างหนึ่ง หรือS = "a" S "a" / "aa" S = "a" [S] "a"ดูเหมือนว่าจะทำงานอย่างถูกต้อง

แต่ดูเหมือนว่าระบบจะหายไปเมื่อมีกฎเดียวกันสองครั้งในรูปแบบที่แตกต่างกัน ฉันไม่แน่ใจว่าทำไม

ฉันไม่พบหน้าที่อธิบายปัญหาเกี่ยวกับวากยสัมพันธ์เหล่านี้เพื่อระบุไวยากรณ์

ฉันยังคิดว่าบั๊กกี้นั้น


1
อุ๊ยตาย ใช่. ฉันไม่รู้ว่าฉันคิดอะไรอยู่ตอนที่ฉันเขียนทรี ฉันจะแก้ไขคำถามของฉันและวางของคุณ
meribold

ฉันพบโคตรตัวกำเนิดซ้ำตัวแยกวิเคราะห์ย้อนกลับด้วยการสาธิตออนไลน์ที่นี่และมันแสดงพฤติกรรมแบบเดียวกันกับกฎนี้:S ::= 'a'<S>'a' | 'a''a'
meribold

มันยังไม่แยกวิเคราะห์aaaaaaเมื่อใช้S = "a" S "a" / "aa"แต่คุณดูเหมือนถูกต้องเกี่ยวกับวงเล็บ
meribold

ฉันไม่เห็นประเด็นของการสำรวจตัวแยกวิเคราะห์ซ้ำ ๆ ตัวแยกวิเคราะห์ย้อนรอย
Babou

คุณถูกต้องเกี่ยวกับS = "a" S "a" / "aa"... ฉันทดสอบเร็วเกินไปและคลิกสร้างขึ้นแทนที่จะแยกวิเคราะห์
babou

3

s1()SaSatrues()s2()Saa

พิจารณาแยกคำaaaaaaอีกครั้ง เมื่อถึงจุดหนึ่งต้นไม้แยกจะมีลักษณะเช่นนี้:

   S 
 / | \
a  S  a
 / | \
a  S  a    <--
 / | \
a  S  a
  / \
 a   a

s()trueSSaa

   S 
 / | \
a  S  a
  / \
 a   a

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

#include <iostream>

char* next;    
bool term(char token) {
    if (*next != '\0')
        return *next++ == token;
    else
        return false;
}

bool s();    
bool s1() {
    return term('a') && s() && term('a');
}    
bool s2() {
    return term('a') && term('a');
}    
bool s() {
    auto save = next;
    return s1() or (next = save, s2());
}    

int main(int argc, char* argv[]) {
    next = "aaaaaa";
    if (s() && *next == '\0') {
        std::cout << "match";
    }
    else
        std::cout << "no match";
}

2

มันเป็นคุณสมบัติที่ไม่ได้เป็นข้อบกพร่อง

ดูอย่างใกล้ชิดเมื่อไหร่ & ที่การย้อนรอยเกิดขึ้น:

     1.           2.          3.          4.          5.          6.          7.          8.          9.          10.         11.         12.

     S            S           S           S           S           S           S           S           S           S           S           S      
   / | \        / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \
  a  S  a      a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a
                / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       /   \
               a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a     a
                            / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \       / | \
                           a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a
                                        / | \       / | \       / | \       / | \       / | \       / | \       / | \       /   \
                                       a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a     a
                                                    / | \       / | \       / | \       / | \       / | \       /   \
                                                   a  S  a     a  S  a     a  S  a     a  S  a     a  S  a     a     a
                                                                / | \       / | \       / | \       /   \   
                                                               a  S  a     a  S  a     a  S  a     a     a
                                                                            / | \       /   \
                                                                           a  S  a     a     a



w[] = 'aaaaaa'  //input
l[] = ''        //current tree leafs


 1. tree:   The parser starts with the start symbol S and tries first alternative S->aSa:       Result: w[0]  = l[0]     w = aaaaaa    l = aSa
 |          -- S->aSa works                                                                         | |     | | 
 6. tree:   The parser matches a after a:                                                       Result: w[6]  = l[6]     w = aaaaaa    l = aaaaaaSaaaaaa
 7. tree:   The parser tries S->aSa again but there is no match!                                Result: w[7] != l[7]     w = aaaaaa    l = aaaaaaaSaaaaaaa 
 8. tree:   The parser tries S->aa but there is still no match!                                 Result: w[7] != l[7]     w = aaaaaa    l = aaaaaaaaaaaaaa
 9. tree:   Backtracking after the last symbol that matched => Backtracking at l[7]             Result: w[7] != l[7]     w = aaaaaa    l = aaaaaaaaaaaa
10. tree:   Backtracking after the last symbol that matched => Backtracking at l[7]             Result: w[7] != l[7]     w = aaaaaa    l = aaaaaaaaaa
11. tree:   Backtracking after the last symbol that matched => Backtracking at l[7]             Result: w[7] != l[7]     w = aaaaaa    l = aaaaaaaa
12. tree:   Backtracking after the last symbol that matched => Backtracking at l[7]             Result: w[7] != l[7]     w = aaaaaa    l = aaaa

จุดสำคัญที่นี่ก็คือตัวแยกวิเคราะห์ย้อนรอยหลังตำแหน่งซึ่งพบอักขระที่ตรงกันล่าสุด นั่นเป็นสาเหตุที่ "กระโดด" จากทรี 11 พร้อมl = aaaaaaaaไปยังทรีที่ 12 พร้อมl = aaaaโดยใช้S -> aaที่ l [7]


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