วิธีการจำลอง backreferences, lookaheads และ lookbehinds ใน automata state ที่ จำกัด ?


26

ฉันสร้างนิพจน์ทั่วไปอย่างง่าย lexer และ parser เพื่อทำนิพจน์ปกติและสร้างแผนภูมิการแยกวิเคราะห์ การสร้างออโตเมติกอัน จำกัด ที่ไม่ได้กำหนดค่าจากต้นไม้การแยกวิเคราะห์นี้ค่อนข้างง่ายสำหรับนิพจน์ทั่วไปขั้นพื้นฐาน อย่างไรก็ตามฉันไม่สามารถคาดศีรษะได้ว่าจะทำอย่างไรในการจำลองการอ้างอิงย้อนกลับ, Lookaheads และ Lookbehinds

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

สิ่งที่เกี่ยวกับการจำลอง lookaheads เชิงลบและ lookbehinds? ฉันจะขอบคุณถ้าคุณจะเชื่อมโยงฉันไปยังแหล่งข้อมูลที่อธิบายวิธีการทำอย่างละเอียด



คำตอบ:


21

ก่อนอื่นการ backreferences ไม่สามารถจำลองโดยออโต จำกัด ได้เนื่องจากมันช่วยให้คุณสามารถอธิบายภาษาที่ไม่ใช่ภาษาปกติได้ ตัวอย่างเช่น([ab]^*)\1จับคู่ซึ่งไม่ได้เป็นบริบท{www{a,b}}

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

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

ยกตัวอย่างเช่นการมองที่ด้านหลัง เราสามารถเลียนแบบความหมายของ regexp โดยดำเนินการตรวจสอบ regexp พร้อมกับ regexp "จับคู่ทั้งหมด" โดยปริยาย เฉพาะจากรัฐที่ออโตเมติกของนิพจน์ที่อยู่เบื้องหลังอยู่ในสถานะสุดท้ายเท่านั้นที่สามารถป้อนออโตเมติกของนิพจน์ที่มีการป้องกันได้ ตัวอย่างเช่น regexp /(?=c)[ab]+/(สมมติว่าเป็นตัวอักษรเต็มรูปแบบ) - โปรดทราบว่ามันแปลเป็นนิพจน์ทั่วไป{ a , b , c } c { a , b } + { a , b , c }{a,b,c} - สามารถจับคู่ได้{a,b,c}c{a,b}+{a,b,c}

ป้อนคำอธิบายรูปภาพที่นี่
[ แหล่งที่มา ]

และคุณจะต้อง

  • เก็บดัชนีปัจจุบันเป็นเมื่อใดก็ตามที่คุณป้อนq 2 (เริ่มแรกหรือจากq 2 ) และiq2q2
  • รายงาน (สูงสุด) การแข่งขันจากไปที่ดัชนีปัจจุบัน ( - 1 ) เมื่อใดก็ตามที่คุณตี (ลา) Q 2i1q2

สังเกตว่าส่วนซ้ายของหุ่นยนต์เป็นหุ่นยนต์แบบขนานของออโตคาตาแบบบัญญัติสำหรับ[abc]*และc(ซ้ำ) ตามลำดับ

ijij

โปรดทราบว่าการไม่กำหนดเป็นสิ่งที่มีอยู่ในส่วนนี้: หลักและการมองไปข้างหน้า / -behind ออโตเมตอาจทับซ้อนกันดังนั้นคุณต้องจัดเก็บการเปลี่ยนแปลงทั้งหมดระหว่างพวกเขาเพื่อรายงานสิ่งที่ตรงกันในภายหลังหรือย้อนกลับ


11

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

Lookaheads และ lookbehinds เช่นคุณลักษณะหลายอย่างของเครื่องมือจับคู่รูปแบบ regex ไม่เหมาะกับกระบวนทัศน์ของการตัดสินใจว่าสตริงนั้นเป็นสมาชิกของภาษาหรือไม่ แทนที่จะใช้ regexes เรามักจะค้นหาสตริงย่อยภายในสตริงที่ใหญ่กว่า "การจับคู่" เป็นสตริงย่อยที่เป็นสมาชิกของภาษาและค่าส่งคืนคือจุดเริ่มต้นและจุดสิ้นสุดของสตริงย่อยภายในสตริงที่มีขนาดใหญ่กว่า

จุดของ lookaheads และ lookbehinds นั้นไม่มากนักที่จะแนะนำความสามารถในการจับคู่ภาษาที่ไม่ใช่ภาษาปกติ แต่ควรปรับตำแหน่งที่เครื่องยนต์รายงานจุดเริ่มต้นและจุดสิ้นสุดของสตริงย่อยที่ตรงกัน

ฉันอาศัยคำอธิบายที่http://www.regular-expressions.info/lookaround.html เอนจิ้นของ regex ที่สนับสนุนฟีเจอร์นี้ (Perl, TCL, Python, Ruby, ... ) ดูเหมือนว่าจะมีพื้นฐานมาจากการย้อนรอย (กล่าวคือมันรองรับชุดภาษาที่มีขนาดใหญ่กว่าภาษาทั่วไป) ดูเหมือนว่าพวกเขากำลังใช้งานคุณลักษณะนี้เป็นส่วนขยาย "ย้อนกลับ" ที่ค่อนข้างง่ายของการย้อนรอยแทนที่จะพยายามสร้างออโต จำกัด อันแท้จริงเพื่อดำเนินงาน

มองเชิงบวก

ไวยากรณ์สำหรับlookahead บวกคือregex(?= )ดังนั้นสำหรับตัวอย่างq(?=u)ตรงqเท่านั้นถ้ามันจะตามมาด้วยแต่ไม่ตรงกับu uฉันคิดว่าพวกเขาใช้สิ่งนี้ด้วยความหลากหลายในการย้อนรอย สร้าง FSM สำหรับนิพจน์ก่อนที่จะมองเชิงบวก เมื่อการแข่งขันนั้นจำได้ว่ามันสิ้นสุดที่ไหนและเริ่ม FSM ใหม่ที่แสดงถึงการแสดงออกภายใน lookahead เชิงบวก หากการแข่งขันนั้นคุณมี "การแข่งขัน" แต่การแข่งขัน "จบ" ก่อนที่ตำแหน่งที่เริ่มการแข่งขันเชิงบวก lookahead

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

Lookahead เชิงลบ

ไวยากรณ์สำหรับlookahead เชิงลบเป็นregex(?! )ดังนั้นสำหรับตัวอย่างq(?!u)ตรงแต่ถ้ามันไม่ได้ตามมาด้วยq uนี่อาจเป็นได้ทั้งqตัวอักษรอื่นหรือqตามท้ายสุดของสตริง ฉันคิดว่าสิ่งนี้ถูกนำไปใช้โดยการสร้าง NFA สำหรับนิพจน์ lookahead จากนั้นทำสำเร็จก็ต่อเมื่อ NFA ไม่สามารถจับคู่สตริงที่ตามมาได้

หากคุณต้องการที่จะทำโดยไม่ต้องพึ่งพา backtracking คุณสามารถลบล้าง NFA ของนิพจน์ lookahead จากนั้นให้ปฏิบัติเช่นเดียวกับที่คุณปฏิบัติกับ lookahead เชิงบวก

มองโลกในแง่ดี

(?<=)(?=q)uuqqnnn

คุณอาจจะสามารถใช้สิ่งนี้ได้โดยไม่ต้องย้อนรอยโดยการตัดกันของ "สตริงที่ลงท้ายด้วยregex " กับส่วนใด ๆ ของ regex ที่มาก่อนตัวดำเนินการ lookbehind นี่จะเป็นเรื่องยุ่งยากเพราะ lookbehind regexอาจต้องมองย้อนกลับไปมากกว่าจุดเริ่มต้นปัจจุบันของอินพุต

มองโลกในแง่ลบ

ไวยากรณ์สำหรับlookbehind เชิงลบเป็นregex(?<! )ดังนั้นสำหรับตัวอย่างเช่น(?<!q)uการแข่งขันแต่ถ้ามันไม่ได้นำหน้าด้วยu qดังนั้นมันจะตรงกับuในumbrellaและuในdoubtแต่ไม่ได้อยู่ในu quickอีกครั้งดูเหมือนว่าจะทำได้โดยการคำนวณความยาวของregexสำรองข้อมูลตัวละครหลายตัวทดสอบการจับคู่กับregexแต่ตอนนี้การแข่งขันทั้งหมดล้มเหลวหาก lookbehind ตรงกัน

คุณอาจจะสามารถใช้สิ่งนี้ได้โดยไม่ต้องย้อนรอยโดยการปฏิเสธregexจากนั้นทำเช่นเดียวกับที่คุณทำเพื่อการมองในแง่ดี


5

อย่างน้อยสำหรับการอ้างอิงย้อนกลับเป็นไปไม่ได้ ตัวอย่างเช่น regex (.*)\1แสดงถึงภาษาที่ไม่ปกติ สิ่งที่แปลว่าเป็นไปไม่ได้ที่จะสร้างหุ่นยนต์ จำกัด (กำหนดขึ้นหรือไม่) ที่จะรับรู้ภาษานี้ หากคุณต้องการที่จะพิสูจน์เรื่องนี้อย่างเป็นทางการคุณสามารถใช้บทแทรกสูบน้ำ


4

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

ความกว้างคงที่ควรเป็นไปได้อย่างสมบูรณ์แบบโดยไม่ต้องย้อนรอย ให้nเป็นความกว้าง เริ่มต้นจากจุดใน NFA คุณที่ lookbehind เริ่มที่คุณต้องการแยกออกจากรัฐมองย้อนกลับเพื่อให้เส้นทางเข้า lookbehind จบลงด้วยทุกnตัวอักษรมูลค่าของรัฐที่เพียงเดินเข้าไปใน lookbehind จากนั้นเพิ่ม lookahead ไปที่จุดเริ่มต้นของสถานะเหล่านั้น (และรวบรวมกราฟย่อยจาก AFA ไปยัง NFA ทันทีหากต้องการ)

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

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