मैं इसे आम आदमी की शर्तों में डालकर एक छुरा लूंगा।
यदि आप पार्स ट्री (एएसटी नहीं, बल्कि पार्सर की यात्रा और इनपुट के विस्तार) के संदर्भ में सोचते हैं, तो एक पेड़ में पुनरावृत्ति का परिणाम होता है जो बाएं और नीचे बढ़ता है। सही पुनरावृत्ति इसके विपरीत है।
एक उदाहरण के रूप में, एक संकलक में एक सामान्य व्याकरण मदों की एक सूची है। आओ हम स्ट्रिंग्स की सूची लें ("लाल", "ग्रीन", "ब्लू") और इसे पार्स करें। मैं व्याकरण को कुछ तरीके लिख सकता था। निम्नलिखित उदाहरण क्रमशः बाएँ या दाएँ पुनरावर्ती हैं, क्रमशः:
arg_list: arg_list:
STRING STRING
| arg_list ',' STRING | STRING ',' arg_list
इन पार्स के लिए पेड़:
(arg_list) (arg_list)
/ \ / \
(arg_list) BLUE RED (arg_list)
/ \ / \
(arg_list) GREEN GREEN (arg_list)
/ /
RED BLUE
ध्यान दें कि यह पुनरावर्तन की दिशा में कैसे बढ़ता है।
यह वास्तव में कोई समस्या नहीं है, यह एक बाईं पुनरावर्ती व्याकरण लिखना चाहता है ठीक है ... यदि आपका पार्सर उपकरण इसे संभाल सकता है। नीचे वाले पार्सर्स इसे ठीक से संभालते हैं। तो आधुनिक एलएल पार्सर कर सकते हैं। पुनरावर्ती व्याकरणों के साथ समस्या पुनरावृत्ति नहीं है, यह पार्सर को आगे बढ़ाए बिना या एक टोकन का उपभोग किए बिना पुनरावृत्ति है। यदि हम पुनरावृत्ति होने पर हमेशा कम से कम 1 टोकन का उपभोग करते हैं, तो हम अंततः पार्स के अंत तक पहुंचते हैं। लेफ्ट रिकर्सन को बिना खपत के रिकेशिंग के रूप में परिभाषित किया गया है, जो एक अनंत लूप है।
यह सीमा विशुद्ध रूप से एक भोले टॉप-डाउन एलएल पार्सर (पुनरावर्ती वंश पार्सर) के साथ एक व्याकरण को लागू करने का कार्यान्वयन विवरण है। यदि आप बचे हुए पुनरावर्ती व्याकरणों से चिपके रहना चाहते हैं, तो आप उत्पादन को फिर से लिखने से पहले कम से कम 1 टोकन का उपभोग करने के लिए फिर से लिख सकते हैं, इसलिए यह सुनिश्चित करता है कि हम कभी भी गैर-उत्पादक लूप में फंस न जाएं। किसी भी व्याकरण नियम के लिए जो वाम-पुनरावर्ती है, हम इसे एक मध्यवर्ती नियम जोड़कर फिर से लिख सकते हैं जो व्याकरण को सिर्फ एक स्तर के दिखावे के लिए समतल करता है, पुनरावर्ती प्रस्तुतियों के बीच एक टोकन का उपभोग करता है। (नोट: मैं यह नहीं कह रहा हूं कि व्याकरण को फिर से लिखने का एकमात्र तरीका या पसंदीदा तरीका है, बस सामान्य नियम को इंगित करना है। इस सरल उदाहरण में, सबसे अच्छा विकल्प सही-पुनरावर्ती रूप का उपयोग करना है)। चूंकि यह दृष्टिकोण सामान्यीकृत है, एक पार्सर जनरेटर प्रोग्रामर (सैद्धांतिक रूप से) को शामिल किए बिना इसे लागू कर सकता है। व्यवहार में, मेरा मानना है कि ANTLR 4 अब बस यही करता है।
ऊपर दिए गए व्याकरण के लिए, लेफ्ट रिकर्शन प्रदर्शित करने वाला एलएल कार्यान्वयन इस तरह दिखेगा। पार्सर एक सूची की भविष्यवाणी के साथ शुरू होगा ...
bool match_list()
{
if(lookahead-predicts-something-besides-comma) {
match_STRING();
} else if(lookahead-is-comma) {
match_list(); // left-recursion, infinite loop/stack overflow
match(',');
match_STRING();
} else {
throw new ParseException();
}
}
वास्तव में, जो हम वास्तव में कर रहे हैं वह है "भोली कार्यान्वयन", यानी। हमने शुरू में एक दिए गए वाक्य की भविष्यवाणी की, फिर पुनरावृत्ति को उस भविष्यवाणी के लिए फ़ंक्शन कहा जाता है, और वह फ़ंक्शन भोलेपन से उसी भविष्यवाणी को फिर से कहता है।
नीचे के पार्सरों के पास किसी भी दिशा में पुनरावर्ती नियमों की समस्या नहीं है, क्योंकि वे एक वाक्य की शुरुआत को वापस नहीं करते हैं, वे वाक्य को एक साथ रखकर काम करते हैं।
एक व्याकरण में पुनरावृत्ति केवल एक समस्या है अगर हम ऊपर से नीचे का उत्पादन करते हैं, अर्थात। जब हम टोकन का उपभोग करते हैं तो हमारा पार्सर हमारी भविष्यवाणियों को "विस्तारित" करके काम करता है। यदि विस्तार करने के बजाय, हम गिर जाते हैं (निर्माण "कम हो जाते हैं"), जैसा कि एक एलएएलआर (Yacc / बाइसन) में पार्सर के नीचे होता है, तो दोनों तरफ की पुनरावृत्ति समस्या नहीं है।
::=
सेExpression
करने के लिएTerm
, और यदि आप पहले के बाद भी ऐसा ही किया||
, वह अब बाएं पुनरावर्ती किया जाएगा? लेकिन अगर आपने इसे केवल बाद में ही किया है::=
, लेकिन ऐसा नहीं है||
, यह अभी भी बचा हुआ है?