मैंने पिछले कुछ दिनों में बहुत सारे शोध किए हैं, यह समझने के लिए कि ये अलग-अलग प्रौद्योगिकियां क्यों मौजूद हैं, और उनकी ताकत और कमजोरियां क्या हैं।
पहले से मौजूद कुछ जवाबों ने उनके कुछ मतभेदों को इंगित किया, लेकिन उन्होंने पूरी तस्वीर नहीं दी, और कुछ हद तक यह माना गया, यही कारण है कि यह उत्तर लिखा गया था।
यह प्रदर्शनी लंबी है, लेकिन महत्वपूर्ण है। मेरे साथ सहन करें (या यदि आप अधीर हैं, तो फ़्लोचार्ट देखने के लिए अंत में स्क्रॉल करें)।
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, डॉ:
यहाँ एक फ़्लोचार्ट है जो ज्यादातर मामलों पर लागू होता है:
javac
, स्काला) के लिए कार्यान्वयन का पसंदीदा रूप है । यह आपको आंतरिक पार्सर राज्य पर सबसे अधिक नियंत्रण प्रदान करता है, जो अच्छे त्रुटि संदेश उत्पन्न करने में मदद करता है (जो हाल के वर्षों में…