การแยกวิเคราะห์และการแยก lexing ผ่านการฝึกฝนที่ดีกับตัวแยกวิเคราะห์ parser หรือไม่?


18

เมื่อฉันเริ่มใช้ตัวแยกวิเคราะห์ parser ปฏิกิริยาแรกของฉันคือความรู้สึกของการปลดปล่อยจากสิ่งที่รู้สึกเหมือนความแตกต่างเทียมระหว่างการแยกและ lexing ทันใดนั้นทุกอย่างก็แค่การแยกวิเคราะห์!

อย่างไรก็ตามฉันเพิ่งเจอโพสต์นี้ใน codereview.stackexchange แสดงให้เห็นว่ามีคนเรียกคืนความแตกต่างนี้ ตอนแรกฉันคิดว่านี่เป็นสิ่งที่โง่มาก ๆ แต่แล้วความจริงที่ว่ามีฟังก์ชั่นใน Parsec เพื่อสนับสนุนพฤติกรรมนี้ทำให้ฉันตั้งคำถามกับตัวเอง

อะไรคือข้อดี / ข้อเสียของการแยกวิเคราะห์กระแสข้อมูลที่มีอยู่แล้วในตัวแยกวิเคราะห์


กรุณามีคนเพิ่มแท็ก [parser-combinator] ได้ไหม
Eli Frey

คำตอบ:


15

ภายใต้การแยกวิเคราะห์เราเข้าใจการวิเคราะห์ภาษาที่ไม่มีบริบท ภาษาอิสระตามบริบทมีประสิทธิภาพมากกว่าภาษาปกติดังนั้น parser สามารถ (ส่วนใหญ่) ทำงานของตัววิเคราะห์คำได้ทันที

แต่นี่คือก) ค่อนข้างผิดธรรมชาติข) มักไม่มีประสิทธิภาพ

สำหรับ a) ถ้าฉันคิดว่าตัวอย่างเช่นการifแสดงออกฉันคิดว่าถ้าexprแล้วexpr ELSE exprและไม่ใช่ 'i' 'f' อาจจะมีช่องว่างบางตัวอักขระใด ๆ ที่การแสดงออกสามารถเริ่มต้นด้วย ฯลฯ คุณได้รับ ความคิด

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

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

แค่ 2 เซ็นต์ของฉัน


3
เพียงเพื่อประโยชน์ของความแม่นยำ: ตัวแยกวิเคราะห์สามารถเสมอทำงานของการวิเคราะห์ศัพท์
จอร์โจ

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

@Giorgio - เกี่ยวกับความคิดเห็นที่ 1 ของคุณ: คุณพูดถูก สิ่งที่ฉันมีอยู่ในใจคือกรณีที่ lexer ปฏิบัติงานบางอย่างที่ทำให้ไวยากรณ์ง่ายขึ้นเพื่อให้สามารถใช้ LALR (1) แทน LALR (2)
Ingo

2
ฉันลบคำตอบของคุณออกหลังจากทดลองและไตร่ตรองต่อไปแล้ว มันเป็นสิ่งที่คุณสองคนมาจากโลกอันไร้ค่าของ Antlr และทั้งหมด เมื่อพิจารณาถึงลักษณะชั้นหนึ่งของ combinators parser ฉันมักจะจบลงด้วยการกำหนด parser wrapper สำหรับ parsers token ของฉันออกจาก token แต่ละชื่อเป็นชื่อเดียวในชั้นแยกวิเคราะห์ของ parser if = string "if" >> expr >> string "then" >> expr >> string "else" >> exprตัวอย่างเช่นถ้าคุณตัวอย่างจะมีลักษณะเหมือน
Eli Frey

1
ประสิทธิภาพการทำงานยังคงเป็นคำถามเปิดฉันจะทำมาตรฐาน
Eli Frey

8

ทุกคนแนะนำว่าการแยก lexing และ parsing เป็น "แนวปฏิบัติที่ดี" - ฉันต้องไม่เห็นด้วย - ในหลาย ๆ กรณีการใช้ lexing และการแยกวิเคราะห์ในการส่งครั้งเดียวให้พลังงานมากกว่าและผลกระทบด้านประสิทธิภาพไม่เลวร้ายอย่างที่แสดงใน คำตอบอื่น ๆ (ดูPackrat )

วิธีนี้จะส่องแสงเมื่อมีการผสมภาษาต่าง ๆ จำนวนมากในสตรีมอินพุตเดียว นี้ไม่จำเป็นโดยเฉพาะภาษา metaprogramming เชิงแปลกเหมือนKatahdinและเหมือนกันแต่สำหรับการใช้งานมากกระแสหลักมากขึ้นเช่นกันเช่นการเขียนโปรแกรมความรู้ (ผสมน้ำยางข้นและการพูด, C ++) โดยใช้ HTML ในความคิดเห็นบรรจุ Javascript เป็น HTML และ เป็นต้น


ในคำตอบของฉันฉันแนะนำว่ามันเป็น "การปฏิบัติที่ดีในบริบทบางอย่าง" และไม่ใช่ว่ามันเป็น "การปฏิบัติที่ดีกว่าในทุกบริบท"
Giorgio

5

lexical analyser รู้จักภาษาปกติและ parser รู้จักภาษาที่ไม่มีบริบท เนื่องจากภาษาปกติแต่ละภาษานั้นไม่มีบริบท (สามารถกำหนดได้ด้วยไวยากรณ์ขวา - เส้นเชิงเส้น ) ผู้วิเคราะห์คำจึงสามารถจดจำภาษาปกติและความแตกต่างระหว่างตัววิเคราะห์คำและตัววิเคราะห์คำดูเหมือนจะเพิ่มความซับซ้อนที่ไม่จำเป็น: บริบทเดียว - ไวยากรณ์ฟรี (parser) สามารถทำงานของ parser และวิเคราะห์คำ

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

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

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

แก้ไข

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


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

1
คุณสามารถยกตัวอย่างภาษาที่เหมาะสมกับการกำหนดคำศัพท์ที่ไม่สามารถอธิบายเป็นภาษาปกติได้หรือไม่?
Giorgio

1
ตัวอย่างเช่นในสองสามภาษาเฉพาะโดเมนที่ฉันสร้างขึ้นตัวระบุอาจเป็นนิพจน์ TeX ซึ่งทำให้การพิมพ์โค้ดที่ทำได้ง่ายเช่นนิพจน์เช่น\alpha'_1 (K_0, \vec{T})ที่ \ alpha'_1, K_0 และ \ vec {T} เป็นตัวระบุ
SK-logic

1
ด้วยไวยากรณ์ที่ไม่ต้องใช้บริบทคุณสามารถรับ N ที่ไม่ใช่เทอร์มินัลและรักษาคำที่มันสามารถได้รับเป็นหน่วยที่มีความหมายที่เป็นประโยชน์ในตัวเอง (เช่นการแสดงออกคำศัพท์ตัวเลขคำสั่ง) สิ่งนี้สามารถทำได้โดยไม่คำนึงถึงวิธีการแยกวิเคราะห์หน่วยนั้น (parser, parser + lexer ฯลฯ ) IMO ตัวเลือกของ parser + lexer เป็นอีกเทคนิคหนึ่ง (วิธีใช้การแยกวิเคราะห์) มากกว่า semantic (ความหมายของบล็อกของซอร์สโค้ดที่คุณแยกวิเคราะห์) คืออะไร บางทีฉันอาจจะมองอะไรบางอย่าง แต่ทั้งสองด้านดูเป็นมุมฉากสำหรับฉัน
Giorgio

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

3

เพียงแค่ lexing และการแยกควรจะแยกเพราะความซับซ้อนที่แตกต่างกัน Lexing เป็น DFA (จำกัด ขอบเขตอัตโนมัติ) และ parser เป็น PDA (ออโตเมติกแบบกดลง) ซึ่งหมายความว่าการแยกวิเคราะห์ใช้ทรัพยากรมากกว่า lexing โดยเนื้อแท้และมีเทคนิคการปรับให้เหมาะสมเฉพาะสำหรับ DFA เท่านั้น นอกจากนี้การเขียนเครื่องจักรสถานะ จำกัด มีความซับซ้อนน้อยกว่ามากและง่ายขึ้นโดยอัตโนมัติ

คุณกำลังสิ้นเปลืองโดยใช้อัลกอริทึมการแยกวิเคราะห์เพื่อ lex


ถ้าคุณใช้ parser ทำการวิเคราะห์คำศัพท์ PDA จะไม่ใช้ stack มันจะทำงานเป็น DFA โดยทั่วไป: เพียงแค่ใช้อินพุตและการกระโดดระหว่างรัฐ ฉันไม่แน่ใจ 100% แต่ฉันคิดว่าเทคนิคการปรับให้เหมาะสม (ลดจำนวนของรัฐ) ที่สามารถนำไปใช้กับ DFA สามารถนำไปใช้กับ PDA ได้ แต่ใช่: การเขียนตัววิเคราะห์คำเป็นเช่นนั้นง่ายกว่าโดยไม่ต้องใช้เครื่องมือที่ทรงพลังกว่าและจากนั้นให้เขียนตัวแยกวิเคราะห์ที่ง่ายกว่าที่ด้านบนของมัน
จอร์โจ

นอกจากนี้ยังทำให้ทุกอย่างยืดหยุ่นและบำรุงรักษาได้ดีขึ้น ตัวอย่างเช่นสมมติว่าเรามีโปรแกรมแยกวิเคราะห์สำหรับภาษา Haskell โดยไม่มีกฎการจัดวาง (เช่นมีเครื่องหมายอัฒภาคและเครื่องหมายวงเล็บ) หากเรามี lexer แยกจากกันตอนนี้เราสามารถเพิ่มกฎโครงร่างโดยเพียงแค่ส่งผ่านโทเค็นอื่นเพิ่มการจัดฟันและเครื่องหมายอัฒภาคตามต้องการ หรือสำหรับตัวอย่างที่ง่ายกว่า: สมมติว่าเราเริ่มต้นด้วยภาษาที่รองรับอักขระ ASCII ในตัวระบุเท่านั้นและตอนนี้เราต้องการสนับสนุนตัวอักษร unicode ในตัวระบุ
Ingo

1
@Ingo และทำไมคุณต้องทำมันด้วย lexer ต่างหาก? เพียงแค่แยกขั้วเหล่านั้นออก
SK-logic

1
@ SK-logic: ฉันไม่แน่ใจว่าฉันเข้าใจคำถามของคุณ เหตุใด lexer ที่แยกต่างหากอาจเป็นตัวเลือกที่ดีฉันได้ลองยืนยันในบทความของฉัน
Ingo

จอร์โจ, หมายเลข สแต็กเป็นองค์ประกอบที่สำคัญของตัวแยกวิเคราะห์สไตล์ LALR ปกติ การทำ lexing ด้วย parser เป็นการสูญเสียความทรงจำที่น่ากลัว (ทั้งที่จัดเก็บแบบคงที่และการจัดสรรแบบไดนามิก) และจะช้ากว่ามาก รูปแบบของ Lexer / Parser นั้นมีประสิทธิภาพ - ใช้มัน :)
riwalk

1

หนึ่งในข้อได้เปรียบหลักของการแยกวิเคราะห์ / lex คือการเป็นตัวแทนสื่อกลาง - โทเค็นสตรีม สิ่งนี้สามารถถูกประมวลผลได้หลายวิธีซึ่งไม่สามารถทำได้ด้วย lex / parse รวมกัน

ที่กล่าวว่าฉันได้พบว่าดี 'ol recursive ดีสามารถซับซ้อนน้อยกว่าและง่ายต่อการทำงานกับ vs การเรียนรู้ตัวแยกวิเคราะห์ parser และต้องคิดหาวิธีการแสดงจุดอ่อนของ grammer ภายในกฎของตัวแยกวิเคราะห์


คุณช่วยอธิบายเพิ่มเติมเกี่ยวกับไวยากรณ์ที่แสดงได้ง่ายขึ้นในสตรีมแบบ prefabbed ที่ดำเนินการในเวลาการแยกวิเคราะห์ได้หรือไม่ ฉันมีประสบการณ์ใช้ภาษาของเล่นและรูปแบบข้อมูลเพียงเล็กน้อยเท่านั้นดังนั้นบางทีฉันอาจพลาดอะไรบางอย่างไป คุณสังเกตเห็นลักษณะการทำงานใด ๆ ระหว่างคอมพาวเดอร์ parser / lex ที่รีดด้วยมือของคุณกับ BNF ที่ป้อน (ฉันสมมติ) เครื่องกำเนิดไฟฟ้าหรือไม่?
Eli Frey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.