แยกวิเคราะห์ไวยากรณ์ที่ไม่มีบริบทโดยพลการซึ่งเป็นตัวอย่างสั้น ๆ


20

ฉันต้องการแยกภาษาเฉพาะโดเมนที่ผู้ใช้กำหนด ภาษาเหล่านี้มักจะใกล้กับสัญลักษณ์ทางคณิตศาสตร์ (ฉันไม่ได้แยกภาษาธรรมชาติ) ผู้ใช้กำหนด DSL ในสัญลักษณ์ BNF เช่นนี้

expr ::= LiteralInteger
       | ( expr )
       | expr + expr
       | expr * expr

การป้อนข้อมูลเช่น1 + ( 2 * 3 )ต้องได้รับการยอมรับในขณะที่การป้อนข้อมูลเช่น1 +ต้องได้รับการปฏิเสธเป็นไม่ถูกต้องและการป้อนข้อมูลเช่น1 + 2 * 3ต้องได้รับการปฏิเสธเป็นที่คลุมเครือ

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

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

โดยทั่วไปฉันจะเรียกใช้ parser ในอินพุตที่ค่อนข้างสั้น ดังนั้นอัลกอริธึมที่เร็วกว่า asymptotically อาจไม่ใช่ทางเลือกที่ดีที่สุด ฉันต้องการที่จะปรับให้เหมาะสมสำหรับการกระจายของอินพุตประมาณ 80% ความยาวน้อยกว่า 20 สัญลักษณ์, 19% ระหว่าง 20 และ 50 สัญลักษณ์และอินพุต 1% ที่หายากอีกต่อไป ความเร็วสำหรับอินพุตที่ไม่ถูกต้องไม่ใช่ประเด็นหลัก นอกจากนี้ฉันคาดว่าจะมีการปรับเปลี่ยน DSL รอบ ๆ อินพุตทุก 1,000 ถึง 100,000 ฉันสามารถใช้เวลาสองสามวินาทีในการประมวลผลไวยากรณ์ก่อนไม่กี่นาที

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

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


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

ฉันเพิ่งตอบไปด้านล่าง เป็นเรื่องที่น่าอึดอัดใจเล็กน้อยที่จะตอบคำถามแบบเก่าที่ได้รับคำตอบที่ดี เห็นได้ชัดว่าฉันไม่ควรตอบด้วยวิธีเดียวกัน แต่ผู้ใช้จะอ่านทั้งคำตอบราวกับว่าพวกเขาตอบคำถามเดียวกัน
Babou

x+y+zอย่าคาดหวังว่าคุณจริงข้อผิดพลาดสำหรับการป้อนข้อมูลที่ไม่ชัดเจนถ้าเขียนผู้ใช้
Babou

@bou ฉันไม่ได้เปลี่ยนคำถามฉันเพิ่มเฉพาะคำอธิบายที่ร้องขอในความคิดเห็น (ตอนนี้ถูกลบ) สำหรับไวยากรณ์เล็ก ๆ ที่ระบุไว้ที่นี่ฉันไม่ได้ระบุความสัมพันธ์สำหรับ+ดังนั้นจึงx+y+zเป็นที่คลุมเครือดังนั้นจึงผิดพลาด
Gilles 'หยุดความชั่วร้าย'

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

คำตอบ:


19

อาจเป็นอัลกอริธึมที่เหมาะสมที่สุดสำหรับความต้องการของคุณคือการแยกวิเคราะห์ทั่วไป LLหรือ GLL นี่เป็นอัลกอริธึมใหม่มาก (กระดาษถูกตีพิมพ์ในปี 2010) มันเป็นอัลกอริธึม Earley ที่เพิ่มเข้ามาด้วยกราฟโครงสร้างแบบกองซ้อน (GSS) และใช้ LL (1) lookahead

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

อัลกอริทึมมีข้อดีที่ชัดเจนจำนวนหนึ่งที่เกี่ยวข้องกับความต้องการของคุณเหนืออัลกอริทึมการแยกวิเคราะห์ทั่วไปอื่น ๆ (ที่ฉันรู้) ประการแรกการติดตั้งใช้งานง่ายมาก: ฉันคิดว่ามีเพียง Earley เท่านั้นที่ใช้งานได้ง่ายกว่า ประการที่สองประสิทธิภาพค่อนข้างดีในความเป็นจริงมันเร็วเท่ากับ LL (1) ในไวยากรณ์ที่ LL (1) ประการที่สามการกู้คืนการแยกวิเคราะห์ค่อนข้างง่ายและตรวจสอบว่ามีการแยกวิเคราะห์ที่เป็นไปได้มากกว่าหนึ่งรายการเช่นกันหรือไม่

ข้อได้เปรียบหลักของ GLL คือการใช้ LL (1) ดังนั้นจึงเป็นเรื่องง่ายที่จะเข้าใจและตรวจแก้จุดบกพร่องเมื่อใช้งานเมื่อออกแบบไวยากรณ์และการแยกวิเคราะห์อินพุต นอกจากนี้ยังทำให้การจัดการข้อผิดพลาดง่ายขึ้น: คุณรู้ได้อย่างแม่นยำว่าตัวแยกวิเคราะห์ที่เป็นไปได้อยู่ที่ไหนและพวกมันจะยังคงทำงานต่อไปอย่างไร คุณสามารถให้การแยกวิเคราะห์ที่เป็นไปได้ที่จุดของข้อผิดพลาดและพูด 3 จุดสุดท้ายที่แยกวิเคราะห์การควั่น คุณอาจเลือกที่จะพยายามกู้คืนจากข้อผิดพลาดแทนและทำเครื่องหมายการผลิตที่การแยกวิเคราะห์ที่ไกลที่สุดกำลังทำงานเป็น 'สมบูรณ์' สำหรับการแยกวิเคราะห์นั้นและดูว่าการแยกวิเคราะห์สามารถดำเนินการต่อหลังจากนั้น (พูดว่ามีคนลืมวงเล็บ) คุณสามารถทำเช่นนั้นพูด 5 parses ที่ไกลที่สุด

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


ยินดีที่ได้เรียนรู้สิ่งใหม่ เมื่อฉันต้องการสิ่งนี้ (ไม่กี่ปีที่ผ่านมาในโครงการที่ฉันต้องการที่จะชุบชีวิตบางวัน) ฉันใช้ CYK ส่วนใหญ่เพราะมันเป็นอัลกอริทึมแรกที่ฉันพบ GLL จัดการกับอินพุตที่ไม่ชัดเจนได้อย่างไร ดูเหมือนว่าบทความจะไม่พูดถึงเรื่องนี้ แต่ฉันแค่อ่านอย่างเดียว
Gilles 'SO- หยุดความชั่วร้าย'

@Gilles: มันสร้างสแต็กที่มีโครงสร้างแบบกราฟและแยกวิเคราะห์ (ในจำนวนมากที่อาจอธิบายได้) ในกราฟนี้คล้ายกับวิธีการทำงานของ GLR หากฉันจำได้ถูกต้องกระดาษที่กล่าวถึงในcstheory.stackexchange.com/questions/7374/…เกี่ยวข้องกับสิ่งนี้
Alex สิบ Brink

@Gilles ตัวแยกวิเคราะห์ปี 2010 นี้ดูเหมือนว่าจะต้องถูกตั้งโปรแกรมด้วยมือจากไวยากรณ์ไม่เพียงพอหากคุณมีหลายภาษาหรือหากคุณมักจะปรับเปลี่ยนภาษา เทคนิคสำหรับการสร้างอัตโนมัติจากไวยากรณ์ของตัวแยกวิเคราะห์ทั่วไปตามกลยุทธ์ที่เลือก (LL, LR หรืออื่น ๆ ) และการผลิตป่าของการแยกวิเคราะห์ทั้งหมดเป็นที่รู้จักกันมานานกว่า 40 ปี อย่างไรก็ตามมีปัญหาที่ซ่อนอยู่เกี่ยวกับความซับซ้อนและการจัดระเบียบของกราฟที่เป็นตัวแทนแยกวิเคราะห์ จำนวนการวิเคราะห์คำอาจแย่กว่าเลขชี้กำลัง: อนันต์ การกู้คืนข้อผิดพลาดสามารถใช้เทคนิคที่เป็นระบบมากขึ้นและแยกวิเคราะห์ได้อย่างอิสระ
babou

GLL เกี่ยวข้องกับ LL (*) ที่พบใน ANTLR อย่างไร
Raphael

6

บริษัท ของฉัน (Semantic Designs) ใช้ตัวแยกวิเคราะห์ GLRอย่างประสบความสำเร็จอย่างมากในการทำสิ่งที่ OP แนะนำในการแยกวิเคราะห์ทั้งภาษาเฉพาะโดเมน สิ่งนี้สนับสนุนการแปลงโปรแกรมจากต้นทางสู่ซอร์สที่ใช้สำหรับการปรับโครงสร้างโปรแกรมขนาดใหญ่ / วิศวกรรมย้อนกลับ / การสร้างรหัสไปข้างหน้า ซึ่งรวมถึงการซ่อมแซมข้อผิดพลาดทางไวยากรณ์โดยอัตโนมัติในวิธีที่ใช้ได้จริง การใช้ GLR เป็นพื้นฐานและการเปลี่ยนแปลงอื่น ๆ (semantic predicates, token-set input แทนที่จะเป็นเพียง token input, ... ) เราได้ทำการสร้าง parsers สำหรับ 40 ภาษา

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

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

ประสิทธิภาพที่ชาญฉลาดตัวแยกวิเคราะห์ GLR ของเรานั้นเร็วพอสมควร: นับหมื่นบรรทัดต่อวินาที สิ่งนี้ต่ำกว่าสถานะของศิลปะ แต่เราไม่ได้พยายามอย่างจริงจังในการเพิ่มประสิทธิภาพนี้และคอขวดบางส่วนอยู่ในการประมวลผลตัวละครสตรีม (Unicode เต็ม) ในการสร้างตัวแยกวิเคราะห์เราประมวลผลไวยากรณ์ฟรีตามบริบทโดยใช้สิ่งที่อยู่ใกล้กับเครื่องสร้างตัวแยกวิเคราะห์ LR (1) โดยปกติจะทำงานบนเวิร์กสเตชันที่ทันสมัยในสิบวินาทีบนไวยากรณ์ขนาดใหญ่ของ C ++ น่าแปลกสำหรับภาษาที่ซับซ้อนอย่าง COBOL และ C ++ สมัยใหม่การสร้าง lexers ใช้เวลาประมาณหนึ่งนาที DFA บางตัวที่ถูกกำหนดเหนือ Unicode นั้นค่อนข้างมีขนดก ฉันเพิ่งทำ Ruby (กับโปรแกรมย่อยเต็มรูปแบบสำหรับ regexps ที่เหลือเชื่อ) เป็นแบบฝึกหัดนิ้ว DMS สามารถประมวลผล lexer และไวยากรณ์ของมันด้วยกันในเวลาประมาณ 8 วินาที


@ ราฟาเอล: ลิงก์ "การเปลี่ยนแปลงครั้งใหญ่" ชี้ไปที่ชุดของเอกสารทางเทคนิคแบบวิชาการรวมถึงบางส่วนในสถาปัตยกรรม C ++ อีกครั้งวิศวกรรมหนึ่งในเครื่องยนต์ DMS ตัวเอง (ค่อนข้างเก่า แต่อธิบายพื้นฐานที่ดี) และหนึ่งใน หัวข้อที่แปลกใหม่ของการจับภาพการออกแบบและการใช้ซ้ำซึ่งเป็นแรงบันดาลใจดั้งเดิมสำหรับ DMS (ยังไม่ประสบความสำเร็จโชคไม่ดี แต่ DMS กลับกลายเป็นว่ามีประโยชน์ทีเดียว)
Ira Baxter

1

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

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

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

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

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

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

การประมวลผลข้อผิดพลาดบางอย่างสามารถรวมอยู่ในอัลกอริทึม CF ทั่วไปเหล่านี้ด้วยวิธีง่าย ๆ แต่ฉันต้องเข้าใจว่าการประมวลผลข้อผิดพลาดแบบใดที่คุณคาดว่าจะยืนยันได้มากกว่านี้ คุณจะมีตัวอย่าง

ฉันไม่สบายใจที่จะพูดมากกว่านี้เพราะฉันไม่เข้าใจว่าแรงจูงใจและข้อ จำกัด ของคุณคืออะไร บนพื้นฐานของสิ่งที่คุณพูดฉันจะยึดติดกับ CYK (และฉันรู้ขั้นตอนวิธีอื่นและคุณสมบัติบางอย่างของพวกเขา)

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