ตัวรวมตัวแยกวิเคราะห์
ในการเขียนโปรแกรมคอมพิวเตอร์ตัวรวมตัวแยกวิเคราะห์ (parser combinator)คือฟังก์ชันลำดับสูงที่รับตัวแยกวิเคราะห์หลายตัวเป็นอินพุตและส่งคืนตัวแยกวิเคราะห์ใหม่เป็นเอาต์พุต ในบริบทนี้ ตัวแยกวิเคราะห์คือฟังก์ชันที่รับสตริงเป็นอินพุตและส่งคืนโครงสร้างบางอย่างเป็นเอาต์พุต โดยทั่วไปจะเป็นแผนผัง การแยกวิเคราะห์ (parse tree)หรือชุดดัชนีที่แสดงตำแหน่งในสตริงที่การแยกวิเคราะห์หยุดลงอย่างสำเร็จ ตัวรวมตัวแยกวิเคราะห์ช่วยให้สามารถใช้ กลยุทธ์ การแยกวิเคราะห์แบบเรียกซ้ำ (recursive descent parsing strategy) ซึ่งอำนวยความสะดวกในการสร้างและการทดสอบแบบโมดูลาร์เป็นส่วนๆ เทคนิคการแยกวิเคราะห์นี้เรียกว่าการแยกวิเคราะห์แบบผสมผสาน (combinatory parsing )
ตัวแยกวิเคราะห์ที่ใช้ตัวรวมได้รับการใช้อย่างกว้างขวางในการสร้างต้นแบบของคอมไพเลอร์และโปรเซสเซอร์สำหรับภาษาเฉพาะโดเมนเช่นอินเทอร์เฟซผู้ใช้ภาษาธรรมชาติสำหรับฐานข้อมูล ซึ่งการกระทำเชิงความหมายที่ซับซ้อนและหลากหลายจะถูกรวมเข้ากับการประมวลผลทางไวยากรณ์อย่างใกล้ชิด ในปี พ.ศ. 2532 Richard Frost และ John Launchbury ได้สาธิต[ 1 ]การใช้ตัวรวมตัวแยกวิเคราะห์เพื่อสร้างตัวแปลภาษาธรรมชาติ Graham Hutton ยังใช้ฟังก์ชันลำดับสูงสำหรับการแยกวิเคราะห์พื้นฐานในปี 1992 [ 2 ]และการแยกวิเคราะห์แบบโมนาดิกในปี 1996 [ 3 ] SD Swierstra ยังได้แสดงให้เห็นถึงแง่มุมเชิงปฏิบัติของตัวรวมการแยกวิเคราะห์ในปี 2001 [ 4 ]ในปี 2008 Frost, Hafiz และ Callaghan [ 5 ]ได้อธิบายชุดของตัวรวมการแยกวิเคราะห์ในภาษาการเขียนโปรแกรมเชิงฟังก์ชันHaskellซึ่งแก้ปัญหาที่มีมานานเกี่ยวกับการรองรับการเรียกซ้ำทางซ้ายและทำงานเป็น เครื่องมือ การแยกวิเคราะห์แบบบนลงล่างที่ สมบูรณ์ ในเวลาและพื้นที่พหุนาม
แนวคิดพื้นฐาน
ในภาษาโปรแกรม ใดๆ ที่มีฟังก์ชันระดับเฟิร์สคลาส ตัวรวมตัวแยกวิเคราะห์ (parser combinator) สามารถใช้เพื่อรวมตัวแยกวิเคราะห์พื้นฐานเพื่อสร้างตัวแยกวิเคราะห์สำหรับกฎที่ซับซ้อนมากขึ้นได้ ตัวอย่างเช่นกฎการผลิตของ ไวยากรณ์แบบไร้บริบท (Context-Free Grammar : CFG) อาจมีทางเลือกหนึ่งหรือมากกว่า และแต่ละทางเลือกอาจประกอบด้วยลำดับของสัญลักษณ์ที่ไม่ใช่เทอร์มินัลและ/หรือสัญลักษณ์เทอร์มินัล หรือทางเลือกอาจประกอบด้วยสัญลักษณ์ที่ไม่ใช่เทอร์มินัลหรือสัญลักษณ์เทอร์มินัลเพียงตัวเดียว หรือสตริงว่าง หากมีตัวแยกวิเคราะห์แบบง่ายสำหรับแต่ละทางเลือกเหล่านี้ ตัวรวมตัวแยกวิเคราะห์สามารถใช้เพื่อรวมตัวแยกวิเคราะห์แต่ละตัวเหล่านี้เข้าด้วยกัน โดยจะส่งคืนตัวแยกวิเคราะห์ใหม่ที่สามารถจดจำทางเลือกใดๆ หรือทั้งหมดได้
ในภาษาโปรแกรมที่รองรับการโอเวอร์โหลดตัวดำเนินการตัวรวมตัวแยกวิเคราะห์สามารถอยู่ในรูปแบบของตัวดำเนินการแบบอินฟิกซ์ซึ่งใช้ในการเชื่อมต่อตัวแยกวิเคราะห์ต่างๆ เพื่อสร้างกฎที่สมบูรณ์ ตัวรวมตัวแยกวิเคราะห์จึงช่วยให้สามารถกำหนดตัวแยกวิเคราะห์ในรูปแบบฝังตัวได้ ในโค้ดที่มีโครงสร้างคล้ายกับกฎของไวยากรณ์ที่เป็นทางการ ดังนั้น การใช้งานจึงสามารถมองได้ว่าเป็นข้อกำหนดที่สามารถดำเนินการได้ พร้อมด้วยข้อดีต่างๆ เช่น ความอ่านง่าย
ตัวรวมสัญญาณ
เพื่อให้การอธิบายค่อนข้างตรงไปตรงมา เราจะกล่าวถึงตัวรวมตัวแยกวิเคราะห์ (parser combinators) ในแง่ของตัวรับรู้ (recognizer ) เท่านั้น หากสตริงอินพุตมีความยาว#inputและสมาชิกของสตริงนั้นเข้าถึงได้ผ่านทางดัชนีjตัวรับรู้คือตัวแยกวิเคราะห์ที่ส่งคืนชุดดัชนีเป็นเอาต์พุต ซึ่งแสดงถึงดัชนีที่ตัวแยกวิเคราะห์สามารถรับรู้ลำดับของโทเค็นที่เริ่มต้นที่ดัชนีนั้นได้สำเร็จjชุดผลลัพธ์ว่างเปล่าแสดงว่าตัวรับรู้ไม่สามารถรับรู้ลำดับใด ๆ ที่เริ่มต้นที่ดัชนีนั้นjได้
- ตัว
emptyตรวจจับจะตรวจจับสตริงว่าง ตัวแยกวิเคราะห์นี้จะทำงานสำเร็จเสมอ โดยจะส่งคืนชุดข้อมูลที่มีดัชนีอินพุตเพียงรายการเดียว:
- ตัวตรวจจับจะตรวจจับเทอร์มินัลหากโทเค็นที่ดัชนีในสตริงอินพุตคือตัวแยกวิเคราะห์นี้จะส่งคืนเซตที่มีโทเค็นเดียวซึ่งประกอบด้วยมิฉะนั้น จะส่งคืนเซตว่าง
term xxjxj + 1
เมื่อมีตัวรับรู้สองตัวคือpและqเราสามารถกำหนดตัวรวมการวิเคราะห์หลักสองตัว ตัวหนึ่งสำหรับจับคู่กฎทางเลือก และอีกตัวหนึ่งสำหรับจัดลำดับกฎ:
- ตัวรวมการแยกวิเคราะห์ทางเลือก ⊕ จะใช้ตัวรับรู้แต่ละตัวกับดัชนีเดียวกัน
jและส่งคืนค่ารวมของดัชนีสุดท้ายของตัวรับรู้เหล่านั้น:
- ตัวรวมลำดับ '⊛' จะใช้ตัวรับรู้ตัวแรก
pกับดัชนีอินพุตjและสำหรับแต่ละดัชนีสิ้นสุด จะใช้ตัวรับรู้ตัวที่สอง โดยใช้ดัชนีนั้นเป็นดัชนีเริ่มต้น โดยจะส่งคืนค่ารวมของดัชนีสิ้นสุดที่ได้จากการเรียกใช้ ฟังก์ชันqทั้งหมดq
อาจมีหลายวิธีที่แตกต่างกันในการวิเคราะห์สตริงโดยที่ผลลัพธ์สุดท้ายอาจอยู่ที่ดัชนีเดียวกัน ซึ่งบ่งชี้ถึงไวยากรณ์ที่ไม่ชัดเจน ตัวตรวจสอบไวยากรณ์แบบง่ายจะไม่รับรู้ถึงความไม่ชัดเจนเหล่านี้ โดยดัชนีสุดท้ายที่เป็นไปได้แต่ละตัวจะแสดงเพียงครั้งเดียวในชุดผลลัพธ์ เพื่อให้ได้ผลลัพธ์ที่สมบูรณ์ยิ่งขึ้น จะต้องส่งคืนออบเจ็กต์ที่ซับซ้อนกว่า เช่น โครงสร้างต้นไม้การวิเคราะห์ (parse tree )
ตัวอย่าง
พิจารณาไวยากรณ์แบบไร้บริบทที่มีความกำกวมสูงโดยใช้ตัวรวมที่ได้นิยามไว้ก่อนหน้านี้ เราสามารถกำหนดสัญกรณ์ที่สามารถดำเนินการได้ของไวยากรณ์นี้ใน ภาษา การเขียนโปรแกรมเชิงฟังก์ชัน สมัยใหม่ (เช่นHaskell ) ได้เป็น เมื่อ ใช้ตัวรู้จำ ที่ดัชนี ของลำดับอินพุตมันจะส่งคืนชุดผลลัพธ์ซึ่งบ่งชี้ว่ามีการจับคู่เริ่มต้นที่ดัชนี 2 และสิ้นสุดที่ดัชนีใด ๆ ระหว่าง 2 ถึง 5 รวมทั้งสองค่าs ::= ‘x’ s s | εs = term ‘x’ <*> s <*> s <+> emptys2x x x x x{2,3,4,5}
ข้อบกพร่องและแนวทางแก้ไข
ตัวรวมการวิเคราะห์ไวยากรณ์ (Parser combinators) เช่นเดียวกับ ตัววิเคราะห์ไวยากรณ์แบบเรียกซ้ำ (recursive descent parsers ) ทั้งหมดไม่ได้จำกัดอยู่เฉพาะไวยากรณ์แบบไร้บริบท (context-free grammars ) และดังนั้นจึงไม่ได้ค้นหาความกำกวมใน ชุด First และ Follow LL( k ) ทั่วโลก ดังนั้น ความกำกวมจึงจะไม่เป็นที่รู้จักจนกว่าจะถึงเวลาทำงาน หากและจนกว่าอินพุตจะกระตุ้นให้เกิดความกำกวมนั้น ในกรณีเช่นนี้ ตัววิเคราะห์ไวยากรณ์แบบเรียกซ้ำอาจเลือกเส้นทางความกำกวมที่เป็นไปได้เส้นใดเส้นหนึ่งโดยค่าเริ่มต้น (ซึ่งอาจไม่เป็นที่รู้จักของผู้ออกแบบไวยากรณ์) ส่งผลให้เกิดความสับสนทางความหมาย (aliasing) ในการใช้ภาษา สิ่งนี้ทำให้เกิดข้อผิดพลาดโดยผู้ใช้ภาษาโปรแกรมที่มีความกำกวม ซึ่งไม่ได้รับการรายงานในเวลาคอมไพล์ และไม่ได้เกิดจากความผิดพลาดของมนุษย์ แต่เกิดจากไวยากรณ์ที่มีความกำกวม วิธีแก้ปัญหาเดียวที่ขจัดข้อผิดพลาดเหล่านี้ได้คือการลบความกำกวมและใช้ไวยากรณ์แบบไร้บริบท
การใช้งานตัวรวมตัวแยกวิเคราะห์แบบง่ายมีข้อบกพร่องบางประการ ซึ่งเป็นเรื่องปกติในการแยกวิเคราะห์แบบบนลงล่าง การแยกวิเคราะห์แบบรวมตัวแบบง่ายต้องใช้ เวลาและพื้นที่ แบบเลขชี้กำลังเมื่อแยกวิเคราะห์ไวยากรณ์แบบไร้บริบทที่กำกวม ในปี 1996 Frost และ Szydlowski ได้แสดงให้เห็นว่า การใช้ การจดจำสามารถใช้ร่วมกับตัวรวมตัวแยกวิเคราะห์เพื่อลดความซับซ้อนของเวลาให้เหลือเพียงพหุนามได้[ 6 ]ต่อมา Frost ได้ใช้โมนาดเพื่อสร้างตัวรวมตัวสำหรับการเชื่อมโยงตารางการจดจำอย่างเป็นระบบและถูกต้องตลอดการคำนวณ[ 7 ]
เช่นเดียวกับการวิเคราะห์ไวยากรณ์แบบเรียกซ้ำ จากบนลงล่าง ตัวรวมการวิเคราะห์ไวยากรณ์แบบดั้งเดิม (เช่น ตัวรวมที่อธิบายไว้ข้างต้น) จะไม่สิ้นสุดในขณะที่ประมวลผลไวยากรณ์แบบเรียกซ้ำทางซ้าย (เช่น) อัลกอริทึมการ รับรู้ ที่รองรับไวยากรณ์ที่กำกวมที่มีกฎการเรียกซ้ำทางซ้ายโดยตรงนั้นได้รับการอธิบายโดย Frost และ Hafiz ในปี 2549 [ 8 ]อัลกอริทึมนี้จะจำกัดการวิเคราะห์ไวยากรณ์แบบเรียกซ้ำทางซ้ายที่เพิ่มขึ้นเรื่อยๆ โดยการกำหนดข้อจำกัดด้านความลึก อัลกอริทึมดังกล่าวได้รับการขยายเป็นอัลกอริทึมการวิเคราะห์ไวยากรณ์ที่สมบูรณ์เพื่อรองรับการเรียกซ้ำทางซ้ายทั้งทางอ้อมและทางตรงในเวลาพหุนามและเพื่อสร้างการแสดงแทนขนาดพหุนามที่กะทัดรัดของจำนวนต้นไม้การวิเคราะห์ไวยากรณ์ที่อาจเป็นแบบเลขชี้กำลังสำหรับไวยากรณ์ที่กำกวมสูงโดย Frost, Hafiz และ Callaghan ในปี 2550 [ 9 ]อัลกอริทึมที่ขยายนี้รองรับการเรียกซ้ำทางซ้ายทางอ้อมโดยการเปรียบเทียบ 'บริบทที่คำนวณ' กับ 'บริบทปัจจุบัน' ผู้เขียนกลุ่มเดียวกันยังได้อธิบายการใช้งานชุดตัวรวมตัวแยกวิเคราะห์ที่เขียนด้วยภาษา Haskell โดยอิงตามอัลกอริทึมเดียวกัน[ 5 ] [ 10 ]s ::= s <*> term ‘x’|empty
หมายเหตุ
- ↑ Frost & Launchbury 1989
- ↑ ฮัต ตัน 1992
- ↑ Hutton, Graham; Meijer, Erik. Monadic Parser Combinators (PDF) (รายงาน). มหาวิทยาลัยนอตติงแฮม. สืบค้นเมื่อ13 กุมภาพันธ์ 2023 .
- ↑ สเวี ยร์สตรา 2001
- 1 2ฟรอสต์, ฮาฟิซและคัลลาแกน 2008
- ↑ฟรอสต์และ ซิดโลว์ สกี 1996
- ↑ ฟรอส ต์ 2003
- ↑ฟรอสต์และฮาฟิซ 2006
- ↑ฟรอสต์, ฮาฟิซและคัลลาแกน 2007
- ↑อ้างอิงจากX- SAIGA —สามารถตัดส่วนเฉพาะของg ra mmars ได้