खूंटी की शेष विशेषताओं को लागू करना

पिछले पोस्ट में पीईजी-पार्सर जनरेटर के सभी हिस्सों को एक साथ रखने के बाद, मैं यह दिखाने के लिए तैयार हूं कि कुछ दिलचस्प चीजों को कैसे लागू किया जाए।



हम खूंटी की निम्नलिखित विशेषताओं पर विचार करेंगे:


  • नामित आइटम: NAME=item (नाम का उपयोग क्रिया में किया जा सकता है)
  • लुकहेड्स: &item (सकारात्मक) और ( !item ) (नकारात्मक)
  • कोष्ठक में आइटम समूहीकरण: ( item item ... )
  • वैकल्पिक आइटम: [item item ...] और item?
  • डुप्लिकेट आइटम: item* (शून्य या अधिक) और item+ (एक या अधिक)

नामित तर्क


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


 expr: term '+' term 

हम चर नाम में नंबर 1 जोड़कर दूसरे term उल्लेख कर सकते हैं, ताकि कार्रवाई में यह इस तरह से निकले:


 expr: term '+' term { term + term1 } 

लेकिन हर कोई इसे पसंद नहीं करता है, और व्यक्तिगत रूप से, मैं इस तरह से कुछ लिखूंगा:


 expr: left=term '+' right=term { left + right } 

यह आसानी से मेटा-व्याकरण में item लिए नियम बदलकर समर्थित है:


 item: | NAME = atom { NamedItem(name.string, atom) } | atom { atom } atom: | NAME { name.string } | STRING { string.string } 

(जहां atom सिर्फ एक पुरानी item )


इसके लिए हमें NamedItem वर्ग की परिभाषा को grammar.py जोड़ना होगा। यह उन डेटा वर्गों में से एक है जिसका मैंने पहले उल्लेख किया था - इसमें name और item विशेषताएँ भी हैं।


हमें कोड जनरेटर में छोटे बदलाव करने की भी आवश्यकता है, जिसे मैं पाठक के लिए एक अभ्यास के रूप में छोड़ दूंगा (या आप मेरे रिपॉजिटरी :-) में देख सकते हैं। उत्पन्न कोड अब प्रत्येक तत्व को निर्दिष्ट नाम के साथ एक चर से मेल खाने का परिणाम देगा, न कि तत्व नियम नाम से प्राप्त नाम के साथ। यह उन तत्वों के लिए भी काम करेगा जो टोकन हैं (या तो फॉर्म NAME या स्ट्रिंग शाब्दिक जैसे ':=' )।


अग्रावलोकन


Lookahead द्वारा पीछा किया। आप शायद इस अवधारणा को नियमित रूप से व्यक्त करते हैं। फॉरवर्ड लुकहेड के दौरान, टोकनर पॉइंटर को शिफ्ट किए बिना, पार्स किए गए विकल्प को तुरंत अस्वीकार या स्वीकार किया जा सकता है।


वास्तव में, लेज़हेड का उपयोग पार्सर अपवादों के साथ भ्रम को समाप्त करने के लिए एक अधिक सुरुचिपूर्ण तरीके के रूप में किया जा सकता है, जिसे मैंने पिछले लेख में लिखा था। क्रियाओं को पहले से ही स्वीकार किए गए विकल्प को अस्वीकार करने की अनुमति देने के बजाय कोई भी वापस नहीं कर सकता, हम OP को "}" बाहर करने से पहले एक निर्देश जोड़ सकते हैं। पुराना नियम कुछ इस तरह दिखता था:


  | OP { None if op.string in ("{", "}") else op.string } 

नया संस्करण इस तरह दिखता है:


  | !"}" OP { op.string } 

यह घुंघराले ब्रैकेट प्रसंस्करण को कार्रवाई से व्याकरण में स्थानांतरित करता है, जहां यह होता है। हमें "{" जांच करने की आवश्यकता नहीं है, क्योंकि यह पहले के विकल्प से मेल खाती है (वास्तव में, यह पुराने संस्करण के लिए भी सही है, लेकिन मैं इसके बारे में भूल गया था :-)।


हम इस प्रकार के रूप में item लिए नियम में व्याह के लिए व्याकरण जोड़ते हैं:


 item: | NAME = atom { NamedItem(name.string, atom) } | atom { atom } | "&" atom { Lookahead(atom, True) } | "!" atom { Lookahead(atom, False) } 

एक बार फिर, हमें लुकहेड Lookahead को grammar.py जोड़ने की जरूरत है (और इसे @subheader में आयात करें!) और जेनरेटर को निम्न सहायक विधि को जोड़कर थोड़ा संशोधित करें:


  def lookahead(self, positive, func, *args): mark = self.mark() ok = func(*args) is not None self.reset(mark) return ok == positive 

हमारे मामले में, इस विकल्प के लिए उत्पन्न कोड इस तरह दिखता है:


  if (True and self.lookahead(False, self.expect, "}") and (op := self.expect(OP)) ): return op . string 

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


कोष्ठक में समूहन


अब कोष्ठक के साथ समूहों को लागू करते हैं। उन्हें आरेख में जोड़ने के लिए सबसे अच्छी जगह atom लिए नियम atom :


 atom: | NAME { name.string } | STRING { string.string } | "(" alts ")" { self.synthetic_rule(alts) } 

पहले दो विकल्प नहीं बदले हैं। नए विकल्प के लिए क्रिया एक हैक (जिसका कार्यान्वयन अस्पष्टीकृत रहता है) का उपयोग करता है, जो मेटा-पार्सर को मक्खी पर व्याकरण में नए नियमों को जोड़ने की अनुमति देता है। यह सहायक फ़ंक्शन (मेटा-पार्सर में परिभाषित) नए नियम ऑब्जेक्ट का नाम देता है। इसमें एक संख्या के बाद एक निश्चित उपसर्ग शामिल होगा, जो इसे अद्वितीय बनाता है, उदाहरण के लिए, _synthetic_rule_1


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


कोष्ठक के अंदर alts का उपयोग करने का मतलब है कि हम एक ऊर्ध्वाधर पट्टी को एक समूह के भीतर सीमांकक के रूप में परिभाषित कर सकते हैं। उदाहरण के लिए, कि हमारा नया एक्शन सॉल्यूशन गलती से { मेल नहीं खा रहा है, हम इसे नकार सकते हैं:


  | !("{" | "}") OP { op.string } 

इसके अलावा, समूह भी कार्रवाई कर सकते हैं! यह कार्रवाई के साथ समस्या को हल करने में मदद नहीं करेगा, लेकिन अन्य स्थितियों में यह अच्छी तरह से उपयोगी हो सकता है। और जब से हम किसी भी मामले में एक सिंथेटिक नियम उत्पन्न करते हैं, तो इसे लागू करने के लिए किसी भी अतिरिक्त कार्य की आवश्यकता नहीं होती है (सिवाय synthetic_rule :-) के कार्यान्वयन के।


वैकल्पिक आइटम


पुराने पैगेन के रूप में, मैं वैकल्पिक टोकनों के एक समूह को इंगित करने के लिए चौकोर कोष्ठक का उपयोग करता हूं। यह वह जगह है जहाँ यह उपयोगी है। उदाहरण के लिए, एक व्याकरण नियम जो लूप के for एक पायथन का वर्णन करता है, इसका उपयोग यह इंगित करने के for कर सकता है कि कोई else एक्सटेंशन मौजूद else सकता है। और फिर हम atom लिए व्याकरण का विस्तार इस प्रकार कर सकते हैं:


 atom: | NAME { name.string } | STRING { string.string } | "(" alts ")" { self.synthetic_rule(alts) } | "[" alts "]" { Maybe(self.synthetic_rule(alts)) } 

Maybe है Maybe एक item विशेषता के साथ एक और डेटा क्लास हो। हम कोड जनरेटर को संशोधित करते हैं ताकि सिंथेटिक फ़ंक्शन का परिणाम विफल न हो अगर यह मूल्य None । ऐसा करने के लिए, आप कार्यान्वयन में or True जोड़ सकते हैं। उदाहरण के लिए, [term] :


 if (True and ((term := self.term()) or True) ): return term 

डुप्लिकेट आइटम


पुनरावृत्ति पर स्विच करना एक अन्य उपयोगी पीईजी फ़ंक्शन है (अंकन नियमित अभिव्यक्तियों से उधार लिया जाता है और इसका उपयोग पैजन में भी किया जाता है)। दो रूप हैं: एक परमाणु में * को जोड़ने का अर्थ है "शून्य या अधिक पुनरावृत्ति", जबकि + अर्थ है "एक या अधिक दोहराव"। अन्य कारणों से, मुझे item और atom लिए व्याकरण के नियमों को फिर से लिखना पड़ा, एक मध्यवर्ती नियम जोड़ना, जिसे मैंने molecule कहा:


 item: | NAME '=' molecule { NamedItem(name.string, molecule) } | "&" atom { Lookahead(atom) } | "!" atom { Lookahead(atom, False) } | molecule { molecule } molecule: | atom "?" { Maybe(atom) } | atom "*" { Loop(atom, False) } | atom "+" { Loop(atom, True) } | atom { atom } | "[" alts "]" { Maybe(self.synthetic_rule(alts)) } atom: | NAME { name.string } | STRING {string.string } | "(" alts ")" { self.synthetic_rule(alts) } 

ध्यान दें कि यह वैकल्पिक तत्वों ( atom? ) के लिए एक वैकल्पिक वाक्यविन्यास का परिचय देता है। इसे अतिरिक्त कार्यान्वयन प्रयासों की आवश्यकता नहीं है, क्योंकि यह Maybe एक नोड बनाने का एक और तरीका है।


इन नियमों को फिर से लागू करना आवश्यक था क्योंकि मैं कुछ स्थितियों को वैध नहीं बनाना चाहता:


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

हालाँकि, यह अभी भी एक आदर्श समाधान नहीं है, जैसा कि आप कुछ लिख सकते हैं जैसे (foo?)* । पार्सर जनरेटर में इस स्थिति के लिए एक चेक जोड़ना आवश्यक होगा, लेकिन मैं इसे लेख के बाहर करूंगा।


Loop डेटा क्लास में दो विशेषताएं हैं: item और nonempty । उत्पन्न कोड हेल्पर पार्सर loop() विधि का उपयोग करेगा। यह पहले दिखाई गई lookahead() विधि के समान है:


  def loop(self, nonempty, func, *args): mark = self.mark() nodes = [] while node := func(*args) is not None: nodes.append(node) if len(nodes) >= nonempty: return nodes self.reset(mark) return None 

यदि nonempty False (इस अर्थ में व्याकरण * था), तो इससे त्रुटि नहीं होगी। कोई प्रविष्टि नहीं मिलेगी, और एक खाली सूची वापस कर दी जाएगी। ऐसा करने के लिए, हम पार्सर जनरेटर को लागू करते हैं ताकि is not None जोड़ा is not None जाए। पिछली पोस्ट से एक नरम चेक एक खाली सूची के मामले में False लौटाएगा।


आज के लिए बस इतना ही! मैं TatSu में मौजूद "कट" ( ~ ) ऑपरेटर पर चर्चा करने जा रहा था, लेकिन अभी तक मुझे इसका सामना करने का मौका नहीं मिला है। मैं अभी तक इस पर चर्चा करने के लिए तैयार नहीं हूं - टाटसू प्रलेखन केवल एक सरल उदाहरण देता है जो मुझे थोड़ा रूचि देता है। मुझे यह पीईजी-पार्सर्स के अन्य जनरेटर में नहीं मिला, इसलिए, शायद, यह सुविधा केवल टाटू है। शायद किसी दिन मैं उसके बारे में बताऊंगा। (इस बीच, मैंने इसे सिर्फ मामले में लागू किया, आप कभी नहीं जानते हैं। :-)


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


इस लेख के लिए लाइसेंस और कोड का हवाला दिया: CC BY-NC-SA 4.0

Source: https://habr.com/ru/post/hi471992/


All Articles