Parser Combinator का उपयोग कब करें? पार्सर जेनरेटर का उपयोग कब करें?


59

मैंने हाल ही में पार्सर्स की दुनिया में एक गहरी डुबकी ली है, मैं अपनी प्रोग्रामिंग भाषा बनाना चाहता हूं।

हालांकि, मुझे पता चला कि पार्सर्स लिखने के दो अलग-अलग दृष्टिकोण मौजूद हैं: पार्सर जेनरेटर और पार्सर कॉम्बिनेटर।

दिलचस्प है, मैं किसी भी संसाधन को खोजने में असमर्थ रहा हूं जिसमें बताया गया है कि किन मामलों में दृष्टिकोण बेहतर है; बल्कि, कई संसाधनों (और व्यक्तियों) के विषय के बारे में मैं दूसरे दृष्टिकोण से अवगत नहीं था, केवल उनके दृष्टिकोण को दृष्टिकोण के रूप में समझा रहा था और दूसरे का उल्लेख नहीं कर रहा था:

सरल अवलोकन:

पार्सर जेनरेटर

एक पार्सर जेनरेटर डीएसएल में लिखी गई एक फ़ाइल लेता है जो विस्तारित बैकस-नौर फॉर्म की कुछ बोली है , और इसे स्रोत कोड में बदल देता है जो तब (जब संकलित किया जाता है) इनपुट भाषा के लिए एक पार्सर बन जाता है जिसे इस डीएसएल में वर्णित किया गया था।

इसका मतलब है कि संकलन प्रक्रिया दो अलग-अलग चरणों में की जाती है। दिलचस्प बात यह है कि पार्सर जेनरेटर खुद भी संकलक हैं (और उनमें से कई वास्तव में स्वयं-होस्टिंग हैं )।

पार्सर संयोजक

एक पार्सर कॉम्बिनेटर पार्सर्स नामक सरल कार्यों का वर्णन करता है जो सभी इनपुट को पैरामीटर के रूप में लेते हैं, और यदि वे मेल खाते हैं तो इस इनपुट के पहले चरित्र (एस) को बंद करने का प्रयास करें। वे एक टपल लौटाते हैं (result, rest_of_input), जहां resultखाली हो सकता है (जैसे nilया Nothing) यदि पार्सर इस इनपुट से कुछ भी पार्स करने में असमर्थ था। एक उदाहरण एक digitपार्सर होगा। अन्य पार्सर निश्चित रूप से पहले तर्क के रूप में पार्सर्स ले सकते हैं (अंतिम तर्क अभी भी इनपुट स्ट्रिंग शेष है) उन्हें संयोजित करने के लिए: उदाहरण के many1लिए संभव के रूप में कई बार एक और पार्सर (लेकिन कम से कम एक बार, या यह स्वयं विफल रहता है) से मेल खाने का प्रयास करता है।

अब आप निश्चित रूप से एक नया पार्सर बनाने के लिए गठबंधन (रचना) digitऔर कर सकते हैं many1, कहते हैं integer

इसके अलावा, एक उच्च-स्तरीय choiceपार्सर लिखा जा सकता है जो पार्सर्स की एक सूची लेता है, बारी-बारी से उनमें से प्रत्येक की कोशिश कर रहा है।

इस तरह, बहुत जटिल लेक्सर्स / पार्सर बनाए जा सकते हैं। ऑपरेटर ओवरलोडिंग का समर्थन करने वाली भाषाओं में, यह ईबीएनएफ की तरह बहुत अधिक दिखता है, भले ही यह अभी भी सीधे लक्ष्य भाषा में लिखा गया है (और आप जिस लक्ष्य भाषा की सभी सुविधाओं का उपयोग कर सकते हैं, वह आप चाहते हैं)।

सरल अंतर

भाषा: हिन्दी:

  • पार्सर जेनरेटर्स EBNF-ish DSL के कोड में लिखे गए हैं और कोड जो इन बयानों को मैच करते समय उत्पन्न करना चाहिए।
  • Parser Combinators सीधे लक्ष्य भाषा में लिखे जाते हैं।

Lexing / पार्सिंग:

  • Parser Generators में 'lexer' (जो एक स्ट्रिंग को टोकन में विभाजित करता है, के बीच एक बहुत अंतर होता है, जिसे यह दिखाने के लिए टैग किया जाता है कि हम किस तरह के मूल्य के साथ काम कर रहे हैं) और 'parser' (जो lexer से टोकन की आउटपुट सूची लेता है) और उन्हें संयोजित करने का प्रयास करता है, एक सार सिंटेक्स ट्री)।
  • पार्सर संयोजकों को इस अंतर की आवश्यकता नहीं है; आमतौर पर, साधारण पार्सर 'लेक्सर' का काम करते हैं और उच्च स्तर के पार्सर इन सरल लोगों को यह तय करने के लिए कहते हैं कि किस तरह का एएसटी-नोड बनाना है।

सवाल

हालाँकि, यहां तक ​​कि इन मतभेदों को देखते हुए (और यह मतभेदों की सूची है, शायद पूरी तरह से दूर है!), मैं एक शिक्षित विकल्प नहीं बना सकता कि कब किसका उपयोग करना है। मैं यह देखने में विफल हूं कि इन मतभेदों के क्या निहितार्थ / परिणाम हैं।

क्या समस्या गुण इंगित करेगा कि पार्सर जनरेटर का उपयोग करके एक समस्या को बेहतर ढंग से हल किया जाएगा? क्या समस्या गुण इंगित करता है कि एक समस्या बेहतर समाधान और एक Parser Combinator का उपयोग कर हल किया जाएगा?


4
पार्सर लागू करने के कम से कम दो और तरीके हैं जिनका आपने उल्लेख नहीं किया: पार्सर व्याख्याकार (पार्सर जनरेटर के समान, उदाहरण के लिए सी या जावा को पार्सर भाषा को संकलित करने के बजाय, पार्सर भाषा को सीधे निष्पादित किया जाता है), और बस लेखन हाथ से तोता। पार्सर को हाथ से लिखना कई आधुनिक उत्पादन-तैयार औद्योगिक-शक्ति भाषा कार्यान्वयन (जैसे जीसीसी, क्लैंग javac, स्काला) के लिए कार्यान्वयन का पसंदीदा रूप है । यह आपको आंतरिक पार्सर राज्य पर सबसे अधिक नियंत्रण प्रदान करता है, जो अच्छे त्रुटि संदेश उत्पन्न करने में मदद करता है (जो हाल के वर्षों में…
Jörg W Mittag

3
... भाषा कार्यान्वयनकर्ताओं के लिए एक बहुत ही उच्च प्राथमिकता बन गई है)। इसके अलावा, कई मौजूदा पार्सर जनरेटर / दुभाषिए / कॉम्बीनेटर वास्तव में विभिन्न प्रकार की मांगों के साथ सामना करने के लिए डिज़ाइन नहीं किए गए हैं जिन्हें आधुनिक भाषा कार्यान्वयन को पूरा करना होगा। उदाहरण के लिए, कई आधुनिक भाषा कार्यान्वयन बैच संकलन के लिए समान कोड का उपयोग करते हैं, आईडीई पृष्ठभूमि संकलन, वाक्यविन्यास हाइलाइटिंग, स्वचालित रीफ़ैक्टरिंग, बुद्धिमान कोड पूरा करना, स्वचालित प्रलेखन पीढ़ी, स्वचालित आरेख, आदि। स्केला भी रनर्स रिफ्लेक्शन और इसकी मैक्रो प्रणाली के लिए कंपाइलर का उपयोग करता है। । कई मौजूदा पार्सर…
जॉर्ग डब्ल्यू मित्तग

1
... इससे निपटने के लिए चौखटे लचीले नहीं हैं। यह भी ध्यान दें कि पार्सर फ्रेमवर्क हैं जो ईबीएनएफ पर आधारित नहीं हैं। उदाहरण के लिए व्याकरण के व्याकरणों के लिए एपैकरट पार्सर
जोर्ग डब्ल्यू मित्तग

2
मुझे लगता है कि यह उस भाषा पर बहुत निर्भर करता है जिसे आप संकलित करने की कोशिश कर रहे हैं। यह किस प्रकार का है (LR, ...)?
qwerty_so

1
ऊपर आपकी धारणा बीएनएफ पर आधारित है, जो आमतौर पर केवल लेसर / एलआर पार्सर संयोजन के साथ संकलित की जाती है। लेकिन जरूरी नहीं कि भाषाएं एलआर व्याकरण पर आधारित हों। तो आप कौन से हैं जो आप संकलन करने की योजना बना रहे हैं?
qwerty_so

जवाबों:


59

मैंने पिछले कुछ दिनों में बहुत सारे शोध किए हैं, यह समझने के लिए कि ये अलग-अलग प्रौद्योगिकियां क्यों मौजूद हैं, और उनकी ताकत और कमजोरियां क्या हैं।

पहले से मौजूद कुछ जवाबों ने उनके कुछ मतभेदों को इंगित किया, लेकिन उन्होंने पूरी तस्वीर नहीं दी, और कुछ हद तक यह माना गया, यही कारण है कि यह उत्तर लिखा गया था।

यह प्रदर्शनी लंबी है, लेकिन महत्वपूर्ण है। मेरे साथ सहन करें (या यदि आप अधीर हैं, तो फ़्लोचार्ट देखने के लिए अंत में स्क्रॉल करें)।


Parser Combinators और Parser Generators के बीच के अंतर को समझने के लिए, किसी को पहले मौजूद विभिन्न प्रकार के पार्स के बीच के अंतर को समझना होगा।

पदच्छेद

पार्सिंग एक औपचारिक व्याकरण के अनुसार प्रतीकों की एक स्ट्रिंग के विश्लेषण की प्रक्रिया है। (कम्प्यूटिंग साइंस में) पार्सिंग का उपयोग कंप्यूटर को किसी भाषा में लिखे गए टेक्स्ट को समझने में सक्षम बनाने के लिए किया जाता है, जो आमतौर पर एक पार्स ट्री बनाते हैं जो लिखित टेक्स्ट का प्रतिनिधित्व करता है, जो पेड़ के प्रत्येक नोड में अलग-अलग लिखित भागों के अर्थ को संग्रहीत करता है। इस पार्स ट्री का उपयोग विभिन्न प्रकार के प्रयोजनों के लिए किया जा सकता है, जैसे कि इसे दूसरी भाषा में अनुवाद करना (कई कंपाइलरों में उपयोग किया जाता है), लिखित निर्देशों की सीधे किसी तरह से व्याख्या करना (SQL, HTML), जिससे लिंटर जैसे उपकरण अपना काम कर सकें। , आदि कभी-कभी, एक तोता पेड़ स्पष्ट रूप से नहीं होता हैउत्पन्न, बल्कि उस क्रिया को जो पेड़ में प्रत्येक प्रकार के नोड पर की जानी चाहिए, सीधे निष्पादित की जाती है। यह दक्षता बढ़ाता है, लेकिन पानी के भीतर अभी भी एक निहित पार्स पेड़ मौजूद है।

पार्सिंग एक ऐसी समस्या है जो कम्प्यूटेशनल रूप से कठिन है। इस विषय पर शोध के पचास साल से अधिक हो गए हैं, लेकिन अभी भी बहुत कुछ सीखना बाकी है।

मोटे तौर पर, कंप्यूटर पार्स इनपुट करने के लिए चार सामान्य एल्गोरिदम हैं:

  • एलएल पार्सिंग। (संदर्भ-मुक्त, ऊपर-नीचे पार्सिंग।)
  • एलआर पार्सिंग। (संदर्भ-मुक्त, नीचे-ऊपर पार्सिंग।)
  • खूंटी + पैकराट पार्सिंग।
  • अर्ली पार्सिंग।

ध्यान दें कि इस प्रकार के पार्सिंग बहुत सामान्य, सैद्धांतिक विवरण हैं। विभिन्न मशीनों के साथ भौतिक मशीनों पर इनमें से प्रत्येक एल्गोरिदम को लागू करने के कई तरीके हैं।

एलएल और एलआर केवल कॉनटेक्स्ट-फ्री व्याकरणों को देख सकते हैं (अर्थात, जो टोकन लिखे गए हैं उनके आसपास का संदर्भ यह समझने के लिए महत्वपूर्ण नहीं है कि उनका उपयोग कैसे किया जाता है)।

पेग / packrat पार्स करने और Earley पार्स बहुत कम उपयोग किया जाता है: Earley-पार्सिंग अच्छा में है कि यह और भी बहुत कुछ व्याकरण (उन है कि जरूरी विषय से मुक्त नहीं हैं सहित) को संभाल सकता है, लेकिन यह कम कुशल के रूप में अजगर ने दावा किया है ( पुस्तक (खंड ४.१.१), मुझे यकीन नहीं है कि ये दावे अभी भी सटीक हैं)। पार्सिंग एक्सप्रेशन ग्रामर + पैकरैट-पार्सिंग एक ऐसी विधि है जो अपेक्षाकृत कुशल है और एलएल और एलआर दोनों की तुलना में अधिक व्याकरण को भी संभाल सकती है, लेकिन अस्पष्टताओं को छिपाती है, जैसा कि जल्दी से नीचे स्पर्श किया जाएगा।

एलएल (बाएं-से-दाएं, सबसे बाएं व्युत्पत्ति)

यह संभवतः पार्सिंग के बारे में सोचने का सबसे स्वाभाविक तरीका है। यह विचार इनपुट स्ट्रिंग में अगले टोकन को देखने का है और फिर तय करना है कि पेड़ की संरचना को उत्पन्न करने के लिए संभवतया कई संभावित पुनरावर्ती कॉलों में से कौन सा लिया जाना चाहिए।

यह पेड़ 'टॉप-डाउन' बनाया गया है, जिसका अर्थ है कि हम पेड़ की जड़ से शुरू करते हैं, और व्याकरण के नियमों को उसी तरह से यात्रा करते हैं जैसे हम इनपुट स्ट्रिंग के माध्यम से यात्रा करते हैं। इसे 'इन्फिक्स' टोकन स्ट्रीम के लिए एक 'पोस्टफिक्स' के बराबर के रूप में भी देखा जा सकता है जिसे पढ़ा जा रहा है।

एलएल-शैली के पार्सिंग का प्रदर्शन करने वाले पार्स को बहुत ही मूल व्याकरण की तरह लिखा जा सकता है जिसे निर्दिष्ट किया गया था। इससे उन्हें समझना, बहस करना और उन्हें बढ़ाना अपेक्षाकृत आसान हो जाता है। क्लासिकल पार्सर कॉम्बिनेटर 'लेगो टुकड़ों' से ज्यादा कुछ नहीं हैं जिन्हें एलएल-स्टाइल पार्सर बनाने के लिए एक साथ रखा जा सकता है।

LR (बाएं-से-दाएं, सबसे दाहिनी व्युत्पत्ति)

एलआर पार्सिंग दूसरे तरीके से यात्रा करता है, नीचे-ऊपर: प्रत्येक चरण पर, स्टैक पर शीर्ष तत्व (ओं) को व्याकरण की सूची से तुलना किया जाता है, यह देखने के लिए कि क्या वे व्याकरण में उच्च-स्तरीय नियम में कम हो सकते हैं । यदि नहीं, तो इनपुट स्ट्रीम से अगला टोकन शिफ्ट एड है और स्टैक के शीर्ष पर रखा गया है।

एक कार्यक्रम सही है अगर अंत में हम स्टैक पर एक नोड के साथ समाप्त होते हैं जो हमारे व्याकरण से शुरुआती नियम का प्रतिनिधित्व करता है।

भविष्य का ध्यान करना

इन दोनों प्रणालियों में से किसी एक में, कभी-कभी इनपुट से अधिक टोकन पर नज़र रखने के लिए आवश्यक होता है, जो तय करने में सक्षम होता है कि कौन सा विकल्प बनाना है। यह वह जगह है (0), (1), (k)या (*)-syntax आप इस तरह के रूप में इन दो सामान्य एल्गोरिदम, के नाम के बाद देखने LR(1) या LL(k)kआमतौर पर 'जितना आपके व्याकरण की जरूरत होती है' *के लिए खड़ा होता है , जबकि आमतौर पर 'यह पार्सर बैकट्रैकिंग करता है' के लिए खड़ा होता है, जो लागू करने के लिए अधिक शक्तिशाली / आसान होता है, लेकिन पार्सर की तुलना में बहुत अधिक मेमोरी और समय का उपयोग होता है जो सिर्फ पार्सिंग के लिए रख सकता है रैखिक।

ध्यान दें कि जब वे 'आगे देखना' करने का निर्णय कर सकते हैं, तो LR-स्टाइल पार्सर्स के पास पहले से ही ढेर पर कई टोकन हैं, इसलिए उनके पास पहले से ही अधिक जानकारी है। इसका मतलब यह है कि उन्हें अक्सर एक ही व्याकरण के लिए एलएल-शैली के पार्सर की तुलना में कम 'लुकहेड' की आवश्यकता होती है।

एलएल बनाम एलआर: एम्बिगुएटी

ऊपर दिए गए दो विवरणों को पढ़ते हुए, किसी को आश्चर्य हो सकता है कि एलआर-शैली पार्सिंग क्यों मौजूद है, क्योंकि एलएल-शैली पार्सिंग बहुत अधिक प्राकृतिक लगता है।

हालांकि, एलएल-स्टाइल पार्सिंग में एक समस्या है: लेफ्ट रिकर्सन

व्याकरण लिखना बहुत स्वाभाविक है जैसे:

expr ::= expr '+' expr | term term ::= integer | float

लेकिन, इस व्याकरण को पार्स करते समय एक एलएल-स्टाइल पार्सर एक अनंत पुनरावर्ती लूप में फंस जाएगा: जब exprनियम की बाईं सबसे अधिक संभावना की कोशिश करते हैं , तो यह बिना किसी इनपुट का उपभोग किए दोबारा इस नियम को पुन: प्राप्त करता है।

इस समस्या को हल करने के तरीके हैं। सबसे सरल आपके व्याकरण को फिर से लिखना है ताकि इस तरह की पुनरावृत्ति न हो:

expr ::= term expr_rest expr_rest ::= '+' expr | ϵ term ::= integer | float (यहाँ, ε 'रिक्त स्ट्रिंग' के लिए खड़ा है)

यह व्याकरण अब सही पुनरावर्ती है। ध्यान दें कि इसे पढ़ना बहुत मुश्किल है।

व्यवहार में, बाएं-पुनरावृत्ति अप्रत्यक्ष रूप से कई अन्य चरणों के बीच में हो सकता है । यह यह एक कठिन समस्या के लिए बाहर देखने के लिए बनाता है। लेकिन इसे हल करने की कोशिश करने से आपका व्याकरण पढ़ने में कठिन हो जाता है।

ड्रैगन बुक की धारा 2.5 के अनुसार:

हमें एक संघर्ष दिखाई देता है: एक तरफ हमें एक व्याकरण की आवश्यकता होती है जो अनुवाद की सुविधा प्रदान करता है, दूसरी तरफ हमें एक अलग व्याकरण की आवश्यकता होती है जो पार्सिंग की सुविधा देता है। समाधान आसान अनुवाद के लिए व्याकरण के साथ शुरू करना है और ध्यान से इसे पार्सिंग की सुविधा के लिए बदलना है। बाईं पुनरावृत्ति को समाप्त करके हम एक पूर्वानुमानित पुनरावर्ती-वंश अनुवादक में उपयोग के लिए उपयुक्त व्याकरण प्राप्त कर सकते हैं।

एलआर-शैली के पार्सर्स को इस वाम-पुनरावृत्ति की समस्या नहीं है, क्योंकि वे नीचे-ऊपर से पेड़ का निर्माण करते हैं। हालाँकि , एलआर-शैली के पार्सर (जिसे अक्सर फिनिट-स्टेट ऑटोमेटन के रूप में कार्यान्वित किया जाता है ) के ऊपर एक व्याकरण का मानसिक अनुवाद
बहुत कठिन (और त्रुटि-प्रवण) होता है, क्योंकि अक्सर सैकड़ों या हजारों राज्य होते हैं + विचार करने के लिए राज्य परिवर्तन। यही कारण है कि एलआर-शैली के पार्सर आमतौर पर एक पार्सर जनरेटर द्वारा उत्पन्न होते हैं , जिसे 'कंपाइलर कंपाइलर' के रूप में भी जाना जाता है।

एम्बिगुएटी को कैसे हल करें

हमने वाम-पुनरावृत्ति अस्पष्टताओं को हल करने के लिए दो तरीकों को देखा: 1) वाक्यविन्यास 2 को फिर से लिखना) एक LR-parser का उपयोग करें।

लेकिन अन्य प्रकार की अस्पष्टताएं हैं जिन्हें हल करना कठिन है: क्या होगा यदि दो अलग-अलग नियम एक ही समय में समान रूप से लागू हों?

कुछ सामान्य उदाहरण हैं:

एलएल-शैली और एलआर-शैली के पर्सर्स दोनों को इन के साथ समस्या है। ऑपरेटर की पूर्वता को पेश करके अंकगणित की अभिव्यक्तियों को हल करने की समस्याओं को हल किया जा सकता है। इसी तरह, अन्य समस्याओं को हल किया जा सकता है, जैसे एक पूर्व व्यवहार और इसके साथ चिपके रहने से। (C / C ++ में, उदाहरण के लिए, झूलना हमेशा निकटतम 'if' का है)।

इसका एक और 'समाधान' पार्सर एक्सप्रेशन ग्रामर (पीईजी) का उपयोग करना है: यह ऊपर इस्तेमाल किए गए बीएनएफ-व्याकरण के समान है, लेकिन एक अस्पष्टता के मामले में, हमेशा 'पहले उठाओ'। बेशक, यह वास्तव में समस्या को 'हल' नहीं करता है, बल्कि यह छिपाना है कि वास्तव में एक अस्पष्टता मौजूद है: अंत उपयोगकर्ताओं को पता नहीं हो सकता है कि पार्सर कौन सी पसंद करता है, और इससे अप्रत्याशित परिणाम हो सकते हैं।

अधिक जानकारी जो इस पोस्ट की तुलना में पूरी तरह से अधिक गहन है, जिसमें यह जानना सामान्य रूप से असंभव है कि क्या आपके व्याकरण में कोई अस्पष्टता नहीं है और इस के निहितार्थ अद्भुत ब्लॉग लेख एलएल और एलआर संदर्भ में हैं: क्यों pinging उपकरण कठिन हैं । मैं इसकी अत्यधिक सिफारिश कर सकता हूँ; इसने मुझे उन सभी चीजों को समझने में बहुत मदद की, जिनके बारे में मैं अभी बात कर रहा हूं।

50 साल का शोध

जीवन चलता रहता है। यह पता चला कि 'सामान्य' एलआर-शैली के पार्सर्स को परिमित राज्य ऑटोमेटोन के रूप में लागू किया गया था, जिन्हें अक्सर हजारों राज्यों + संक्रमणों की आवश्यकता होती थी, जो कार्यक्रम के आकार में एक समस्या थी। तो, सरल एलआर (एसएलआर) और एलएएलआर (लुक- फॉरवर्ड एलआर) जैसे वेरिएंट में लिखा गया था कि ऑटोमेशन को छोटा बनाने के लिए अन्य तकनीकों को मिलाएं , पार्सर कार्यक्रमों की डिस्क और मेमोरी फुटप्रिंट को कम करें।

इसके अलावा, ऊपर सूचीबद्ध अस्पष्टताओं को हल करने का एक और तरीका सामान्यीकृत तकनीकों का उपयोग करना है जिसमें, अस्पष्टता के मामले में, दोनों संभावनाओं को रखा जाता है और पार्स किया जाता है: या तो एक पंक्ति को पार्स करने में विफल हो सकता है (जिस मामले में दूसरी संभावना है 'सही' एक), साथ ही दोनों (और इस तरह से यह दर्शाता है कि अस्पष्टता मौजूद है) दोनों के मामले में दोनों सही हैं।

दिलचस्प बात यह है कि सामान्यीकृत एलआर एल्गोरिथ्म का वर्णन किए जाने के बाद , यह पता चला कि सामान्यीकृत एलएल पार्सर्स को लागू करने के लिए एक समान दृष्टिकोण का उपयोग किया जा सकता है , जो समान रूप से तेज है (अस्पष्ट रूप से व्याकरण के लिए $ O (n ^ 3) $ समय जटिलता, $ O (n) पूरी तरह से अस्पष्ट व्याकरण के लिए $, एक साधारण (LA) LR पार्सर की तुलना में अधिक बहीखाता पद्धति के साथ, जिसका अर्थ है एक उच्च स्थिर कारक) लेकिन फिर से एक पार्सर को पुनरावर्ती वंश (टॉप-डाउन) शैली में लिखने की अनुमति दें जो एक बहुत अधिक प्राकृतिक है लिखने और डिबग करने के लिए।

Parser Combinators, Parser जनरेटर्स

इसलिए, इस लंबे समय के लिए, अब हम प्रश्न के मूल में आ रहे हैं:

Parser Combinators और Parser Generators में क्या अंतर है, और एक दूसरे पर कब इस्तेमाल किया जाना चाहिए?

वे वास्तव में विभिन्न प्रकार के जानवर हैं:

पार्सर कॉम्बिनेटर बनाए गए क्योंकि लोग टॉप-डाउन पार्सर लिख रहे थे और महसूस किया कि इनमें से कई में बहुत कुछ था

पार्सर जेनरेटर बनाए गए थे क्योंकि लोग उन पार्सर का निर्माण करना चाह रहे थे जिनमें एलएल-शैली के पार्सर (यानी एलआर-शैली के पार्सर) की समस्या नहीं थी, जो हाथ से करना बहुत मुश्किल साबित हुआ। सामान्य लोगों में Yacc / Bison शामिल है, जो कार्यान्वयन (LA) LR) करता है।

दिलचस्प है, आजकल परिदृश्य कुछ हद तक गड़बड़ है:

  • Parser Combinators लिखना संभव है , जो GLL एल्गोरिदम के साथ काम करता है , जो अस्पष्टता-मुद्दों को हल करता है जो शास्त्रीय LL- शैली के पार्सर्स के पास था, जबकि सभी प्रकार के टॉप-डाउन पार्सिंग के रूप में पढ़ने योग्य / समझने योग्य था।

  • पार्सर जेनरेटर एलएल-शैली पार्सर्स के लिए भी लिखे जा सकते हैं। एएनटीएलआर ठीक यही करता है, और शास्त्रीय एलएल-शैली पार्सर्स की अस्पष्टता को हल करने के लिए अन्य सांख्यिकी (अनुकूली एलएल (*)) का उपयोग करता है।

सामान्य तौर पर, एक LR पार्सर जेनरेटर बनाना और आपके व्याकरण पर चलने वाले एक (LA) LR-स्टाइल पार्सर जनरेटर के आउटपुट को डिबग करना मुश्किल है, क्योंकि आपके मूल व्याकरण का अनुवाद 'इनसाइड-आउट' एलआर फॉर्म में है। दूसरी ओर, Yacc / Bison जैसे उपकरणों में कई वर्षों के अनुकूलन हुए हैं, और जंगली में बहुत अधिक उपयोग देखा गया है, जिसका अर्थ है कि बहुत से लोग अब इसे पार्स करने का तरीका मानते हैं और नए दृष्टिकोणों के प्रति संदेह करते हैं।

आपको किसका उपयोग करना चाहिए, यह इस बात पर निर्भर करता है कि आपका व्याकरण कितना कठिन है, और पार्सर को कितना तेज़ होना चाहिए। व्याकरण के आधार पर, इनमें से एक तकनीक (/ विभिन्न तकनीकों का कार्यान्वयन) तेज हो सकती है, एक छोटी मेमोरी फ़ुटप्रिंट हो सकती है, एक छोटी डिस्क फ़ुटप्रिंट हो सकती है, या दूसरों की तुलना में डिबग करने के लिए अधिक एक्स्टेंसिबल या आसान हो सकती है। आपका माइलेज मई वैरी

साइड नोट: लेक्सिकल एनालिसिस के विषय पर।

लेसरल एनालिसिस को Parser Combinators और Parser Generators दोनों के लिए इस्तेमाल किया जा सकता है। विचार के लिए एक 'गूंगा' पार्सर है जिसे लागू करना बहुत आसान है (और इसलिए तेजी से) जो आपके स्रोत कोड पर पहला पास करता है, उदाहरण के लिए सफेद रिक्त स्थान, टिप्पणियों, आदि को दोहराते हुए, और संभवतः 'टोकन'। मोटे तौर पर विभिन्न तत्व जो आपकी भाषा बनाते हैं।

मुख्य लाभ यह है कि यह पहला कदम वास्तविक पार्सर को बहुत सरल बनाता है (और संभवतः उसी के कारण तेजी से)। मुख्य नुकसान यह है कि आपके पास एक अलग अनुवाद कदम है, और उदाहरण के लिए लाइन- और स्तंभ संख्या के साथ त्रुटि रिपोर्टिंग सफेद-स्थान को हटाने के कारण कठिन हो जाती है।

अंत में एक लेसर एक और पार्सर just सिर्फ ’है और इसे उपरोक्त किसी भी तकनीक का उपयोग करके लागू किया जा सकता है। इसकी सरलता के कारण, अक्सर मुख्य पार्सर की तुलना में अन्य तकनीकों का उपयोग किया जाता है, और उदाहरण के लिए अतिरिक्त 'लेक्सर जनरेटर' मौजूद हैं।


Tl, डॉ:

यहाँ एक फ़्लोचार्ट है जो ज्यादातर मामलों पर लागू होता है: यहाँ छवि विवरण दर्ज करें


@Sjoerd यह वास्तव में बहुत पाठ है, क्योंकि यह एक बहुत कठिन समस्या है। यदि आप इस तरह से जानते हैं कि मैं अंतिम पैराग्राफ को स्पष्ट कर सकता हूं, तो मैं सभी कान हूं: "आपको किसका उपयोग करना चाहिए, यह इस बात पर निर्भर करता है कि आपका व्याकरण कितना कठिन है, और पार्सर को कितना तेज होना चाहिए। व्याकरण के आधार पर, इनमें से एक तकनीक (/ विभिन्न तकनीकों का क्रियान्वयन) तेज़ हो सकती है, एक छोटी मेमोरी फ़ुटप्रिंट हो सकती है, एक छोटी डिस्क फ़ुटप्रिंट हो सकती है, या दूसरों की तुलना में डिबग करने के लिए अधिक एक्स्टेंसिबल या आसान हो सकती है। आपका माइलेज मई वैरी। "
Qqwy

1
अन्य उत्तर दोनों बहुत छोटे और अधिक स्पष्ट हैं, और उत्तर देने में बहुत बेहतर काम करते हैं।
जोएर्ड

1
@ कारण यह है कि मैंने यह उत्तर लिखा था, क्योंकि अन्य उत्तर या तो समस्या की देखरेख कर रहे थे, पूर्ण उत्तर के रूप में एक आंशिक उत्तर प्रस्तुत करने और / या उपाख्यानात्मक पतन जाल में गिरने का । ऊपर दिए गए उत्तर पर चर्चा जार्ज डब्ल्यू मित्तग, थॉमस किलियन और मैंने इस प्रश्न की टिप्पणियों के बाद की थी कि वे क्या समझ रहे थे और पूर्व ज्ञान ग्रहण किए बिना इसे प्रस्तुत कर रहे थे।
Qqwy

किसी भी मामले में, मैंने प्रश्न में एक tl; डॉ फ्लोचार्ट जोड़ा है। क्या वह आपको संतुष्ट करता है, @Sjoerd?
Qqwy

2
Parser Combinators वास्तव में समस्या को हल करने में विफल रहते हैं जब आप वास्तव में उनका उपयोग नहीं करते हैं। बस की तुलना में अधिक संयोजन हैं |, यह पूरे बिंदु है। इसके लिए सही री-राइट exprऔर भी अधिक सक्सेसफुल है expr = term 'sepBy' "+"(जहां यहां सिंगल कोट्स एक फंक्शन्स को चालू करने के लिए बैकटिक्स का विकल्प बना रहे हैं, क्योंकि मिनी-मार्कडाउन में कैरेक्टर एस्केपिंग नहीं है)। अधिक सामान्य मामले में chainByकॉम्बिनेटर के रूप में अच्छी तरह से है। मुझे लगता है कि एक सरल पार्सिंग कार्य को एक उदाहरण के रूप में खोजना मुश्किल है जो पीसी के लिए अच्छी तरह से अनुकूल नहीं है, लेकिन वास्तव में उनके पक्ष में एक मजबूत तर्क है।
स्टीवन आर्मस्ट्रांग

8

इनपुट के लिए जो वाक्यविन्यास त्रुटियों से मुक्त होने की गारंटी है, या जहां वाक्यविन्यास शुद्धता पर एक समग्र पास / असफलता ठीक है, पार्सर कॉम्बीनेटरों के साथ काम करने के लिए बहुत सरल हैं, खासकर कार्यात्मक प्रोग्रामिंग भाषाओं में। ये प्रोग्रामिंग पज़ल्स, डेटा फ़ाइल पढ़ने, आदि जैसी स्थितियाँ हैं।

जो सुविधा आपको पार्सर जनरेटर की जटिलता को जोड़ना चाहती है वह त्रुटि संदेश है। आप त्रुटि संदेश चाहते हैं जो उपयोगकर्ता को एक पंक्ति और स्तंभ पर इंगित करता है, और उम्मीद है कि मानव द्वारा भी समझा जा सकता है। इसे ठीक से करने के लिए बहुत सारे कोड लगते हैं, और एंटीएलआर जैसे बेहतर पार्सर जनरेटर आपकी मदद कर सकते हैं।

हालांकि, स्वचालित पीढ़ी केवल आपको अभी तक प्राप्त कर सकती है, और अधिकांश वाणिज्यिक और लंबे समय तक खुले रहने वाले स्रोत संकलक मैन्युअल रूप से अपने पार्सर्स लिख रहे हैं। मुझे लगता है कि अगर आपको ऐसा करने में सहज महसूस हुआ, तो आपने यह सवाल नहीं पूछा होगा, इसलिए मैं पार्सर जनरेटर के साथ जाने की सलाह दूंगा।


2
आपके उत्तर के लिए धन्यवाद! पार्सर जनरेटर की तुलना में पार्सर जेनरेटर का उपयोग करके पठनीय त्रुटि-संदेशों का निर्माण करना आसान क्यों होगा? (भले ही हम किस कार्यान्वयन के बारे में बात कर रहे हैं, विशेष रूप से) उदाहरण के लिए, मुझे पता है कि पारसेक और स्पिरिट दोनों में लाइन + कॉलम जानकारी सहित त्रुटि संदेशों को प्रिंट करने की कार्यक्षमता है, इसलिए पार्सर कॉम्बिनेटरों में भी ऐसा करना निश्चित रूप से संभव लगता है।
18

ऐसा नहीं है कि आप पार्सर कॉम्बिनेटरों के साथ त्रुटि संदेश नहीं छाप सकते हैं, यह है कि जब आप मिक्स में त्रुटि संदेश फेंकते हैं तो उनके फायदे कम स्पष्ट होते हैं। दोनों विधियों का उपयोग करके एक अपेक्षाकृत जटिल व्याकरण करें और आप देखेंगे कि मेरा क्या मतलब है।
कार्ल

Parser Combinator के साथ, सभी परिभाषा में आप एक त्रुटि स्थिति में प्राप्त कर सकते हैं "इस बिंदु पर शुरू होने पर, कोई कानूनी इनपुट नहीं मिला"। यह वास्तव में आपको नहीं बताता कि क्या गलत था। सिद्धांत रूप में, उस बिंदु पर बुलाए गए व्यक्तिगत पार्सर्स आपको यह बता सकते हैं कि यह क्या अपेक्षित था और DIDN'T नहीं मिला, लेकिन आप जो कर सकते थे, वह सभी प्रिंट आउट है, जो एक लूडो त्रुटि संदेश के लिए बना रहा है।
जॉन आर। स्ट्रॉहम

1
पार्सर जनरेटर वास्तव में अपने अच्छे त्रुटि संदेशों के लिए ईमानदार होने के लिए नहीं जाने जाते हैं।
माइल्स राउत

डिफ़ॉल्ट रूप से नहीं, नहीं, लेकिन उनके पास अच्छे त्रुटि संदेश जोड़ने के लिए अधिक सुविधाजनक हुक हैं।
कार्ल बेज़ेलफेल्ट

4

सैम हैवेल, ANTLR पार्सर जनरेटर के अनुरक्षकों में से एक, ने हाल ही में लिखा था :

मैंने पाया कि [कॉम्बिनेटर] मेरी जरूरतों को पूरा नहीं करते:

  1. ANTLR मुझे अस्पष्टता जैसी चीजों के प्रबंधन के लिए उपकरण प्रदान करता है। विकास के दौरान ऐसे उपकरण हैं जो मुझे अस्पष्ट पार्स परिणाम दिखा सकते हैं ताकि मैं व्याकरण में उन अस्पष्टताओं को समाप्त कर सकूं। रनटाइम के दौरान, मैं आईडीई में अपूर्ण इनपुट से उत्पन्न अस्पष्टता का लाभ उठा सकता हूं, जिससे कोड पूरा होने जैसी सुविधाओं में अधिक सटीक परिणाम मिल सके।
  2. व्यवहार में मैंने पाया है कि पार्सर कॉम्बिनेटर मेरे प्रदर्शन लक्ष्यों को पूरा करने के लिए उपयुक्त नहीं थे। इसका एक हिस्सा वापस चला जाता है
  3. जब पार्स परिणाम आउटलाइनिंग, कोड पूरा करने और स्मार्ट इंडेंट जैसी सुविधाओं के लिए उपयोग किया जाता है, तो उन परिणामों की सटीकता को प्रभावित करने के लिए व्याकरण में सूक्ष्म परिवर्तन करना आसान होता है। ANTLR ऐसे उपकरण प्रदान करता है जो इन मिसमैच को संकलित त्रुटियों में बदल सकते हैं, यहां तक ​​कि उन मामलों में जहां प्रकार अन्यथा संकलित करेंगे। मैं विश्वास के साथ एक नई भाषा सुविधा प्रदान कर सकता हूं जो व्याकरण को प्रभावित करता है यह जानकर कि आईडीई बनाने वाले सभी अतिरिक्त कोड शुरू से ही नई सुविधा के लिए एक पूर्ण अनुभव प्रदान करेंगे। ANTLR 4 का मेरा कांटा (जो कि C # लक्ष्य पर आधारित है) एकमात्र उपकरण है जो मुझे पता है कि इस सुविधा को प्रदान करने का भी प्रयास किया गया है।

अनिवार्य रूप से, पार्सर कॉम्बिनेटर एक अच्छा खिलौना है, जिसके साथ खेलना है, लेकिन वे गंभीर काम करने के लिए बस बाहर नहीं हैं।


3

जैसा कि कार्ल उल्लेख करते हैं, पार्सर जनरेटर बेहतर त्रुटि रिपोर्टिंग करते हैं। के अतिरिक्त:

  • वे तेजी से होते हैं, क्योंकि उत्पन्न कोड को सिंटैक्स के लिए विशेष किया जा सकता है और लुकहेड के लिए जंप टेबल उत्पन्न कर सकते हैं।
  • वे बेहतर टूलिंग करते हैं, अस्पष्ट सिंटैक्स की पहचान करने के लिए, बाईं पुनरावृत्ति को हटाते हैं, त्रुटि शाखाओं में भरते हैं, आदि।
  • वे पुनरावर्ती परिभाषाओं को बेहतर ढंग से संभालते हैं।
  • वे अधिक मजबूत होते हैं, क्योंकि जनरेटर लंबे समय तक साथ रहे हैं और आपके लिए बॉयलरप्लेट के अधिक काम करते हैं, जिससे आपको इसे खराब करने की संभावना कम हो जाती है।

दूसरी ओर, कॉम्बिनेटरों के अपने फायदे हैं:

  • वे कोड में हैं, इसलिए यदि आपका सिंटैक्स रनटाइम में बदलता है, तो आप चीजों को आसानी से म्यूट कर सकते हैं।
  • वे आसानी से बाँध लेते हैं और वास्तव में उपभोग करते हैं (पार्सर जनरेटर का उत्पादन बहुत सामान्य और उपयोग करने के लिए अजीब हो जाता है)।
  • वे कोड में हैं, इसलिए जब आपका व्याकरण आपकी अपेक्षा के अनुरूप नहीं होता है, तो डीबग करना थोड़ा आसान हो जाता है।
  • जब से वे किसी अन्य कोड की तरह काम करते हैं, तब उनके पास एक सीखने की अवस्था होती है। Parser जनरेटर सामान काम करने के लिए सीखने के लिए अपने स्वयं के quirks करते हैं।

पार्सर जनरेटर वास्तविक दुनिया में उपयोग किए जाने वाले हाथ से लिखे गए एलएल पुनरावर्ती-वंशीय पार्सर के सापेक्ष भयानक त्रुटि रिपोर्टिंग करते हैं। पार्सर जनरेटर शायद ही कभी महान निदान जोड़ने के लिए आवश्यक राज्य तालिका संक्रमण हुक प्रदान करते हैं। यही कारण है कि लगभग हर वास्तविक संकलक पार्सर कॉम्बिनेटर या पार्सर जनरेटर का उपयोग नहीं करता है। एलएल पुनरावर्ती-सभ्य पार्सर निर्माण के लिए तुच्छ हैं, हालांकि "स्वच्छ" पीसी / पीजी के रूप में नहीं, वे अधिक उपयोगी हैं।
६:४२ बजे धचढ़
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.