प्रोग्रामिंग लैंग्वेज फ़ंक्शंस को कैसे परिभाषित करती हैं?


28

प्रोग्रामिंग भाषा कैसे कार्य / विधियों को परिभाषित और सहेजती है? मैं रूबी में एक व्याख्या की गई प्रोग्रामिंग भाषा बना रहा हूं, और यह पता लगाने की कोशिश कर रहा हूं कि फ़ंक्शन घोषणा को कैसे लागू किया जाए।

मेरा पहला विचार एक मानचित्र में घोषणा की सामग्री को सहेजना है। उदाहरण के लिए, अगर मैंने कुछ ऐसा किया है

def a() {
    callSomething();
    x += 5;
}

फिर मैं अपने नक्शे में एक प्रविष्टि जोड़ूंगा:

{
    'a' => 'callSomething(); x += 5;'
}

इसके साथ समस्या यह है कि यह पुनरावर्ती हो जाएगा, क्योंकि मुझे अपनी parseविधि को स्ट्रिंग पर कॉल करना होगा, जो तब parseसामना करने पर फिर से कॉल करेगा doSomething, और फिर मैं अंततः स्टैक स्पेस से बाहर चला जाऊंगा।

तो, व्याख्यात्मक भाषाएं इसे कैसे संभालती हैं?


ओह, और यह Programmers.SE पर मेरी पहली पोस्ट है, इसलिए कृपया मुझे सूचित करें कि मैं कुछ गलत कर रहा हूं या यह ऑफ-टॉपिक है। :)
डोरकनब

अतीत में मैंने उन्हें अपने टोकन के भीतर सभी इनलाइन संग्रहीत किया है और फ़ंक्शन कॉल केवल एक विशिष्ट ऑफसेट (विधानसभा में लेबल की तरह) के लिए कूदता है। क्या आप स्क्रिप्ट को टोकन कर रहे हैं? या हर बार तार लगाना?
साइमन व्हाइटहेड

@SimonWhitehead मैंने स्ट्रिंग को टोकन में विभाजित किया, और फिर प्रत्येक टोकन को अलग से पार्स किया।
Doorknob

3
यदि आप प्रोग्रामिंग भाषा डिजाइन और कार्यान्वयन के लिए नए हैं, तो आप इस विषय पर कुछ साहित्य की जांच करना चाहते हैं। सबसे लोकप्रिय एक "ड्रैगन बुक" है: en.wikipedia.org/wiki/… , लेकिन अन्य, अधिक संक्षिप्त ग्रंथ भी हैं जो बहुत अच्छे हैं। उदाहरण के लिए, आरने रंता द्वारा प्रोग्रामिंग भाषाओं को लागू करना यहां मुफ्त में प्राप्त किया जा सकता है: bit.ly/15CF6gC
badcandybag

1
@ddyer धन्यवाद! मैं विभिन्न भाषाओं में एक लिस्प दुभाषिया के लिए googled और वास्तव में मदद की। :)
Doorknob

जवाबों:


31

क्या मैं यह मानने में सही होगा कि आपका "पार्स" फ़ंक्शन न केवल कोड को पार्स करता है, बल्कि एक ही समय में इसे निष्पादित भी करता है? यदि आप इसे इस तरह से करना चाहते हैं, तो अपने नक्शे में फ़ंक्शन की सामग्री को संग्रहीत करने के बजाय, फ़ंक्शन के स्थान को संग्रहीत करें ।

लेकिन एक बेहतर तरीका है। यह थोड़ा और अधिक प्रयास करता है, लेकिन इससे जटिलताएं बढ़ने पर बेहतर परिणाम मिलते हैं: एक सार सिंटेक्स ट्री का उपयोग करें।

मूल विचार यह है कि आप केवल एक बार कोड को पार्स करते हैं, कभी भी। तब आपके पास संचालन और मूल्यों का प्रतिनिधित्व करने वाले डेटा प्रकारों का एक सेट होता है, और आप उनमें से एक पेड़ बनाते हैं, जैसे:

def a() {
    callSomething();
    x += 5;
}

हो जाता है:

Function Definition: [
   Name: a
   ParamList: []
   Code:[
      Call Operation: [
         Routine: callSomething
         ParamList: []
      ]
      Increment Operation: [
         Operand: x
         Value: 5
      ]
   ]
]

(यह सिर्फ एक काल्पनिक AST की संरचना का एक पाठ प्रतिनिधित्व है। वास्तविक पेड़ संभवतः पाठ रूप में नहीं होगा।) वैसे भी, आप अपने कोड को एएसटी में पार्स करते हैं, और फिर आप सीधे एएसटी पर अपना दुभाषिया चलाते हैं, या एएसटी को कुछ आउटपुट फॉर्म में बदलने के लिए एक दूसरी ("कोड पीढ़ी") पास का उपयोग करें।

अपनी भाषा के मामले में, आप शायद ऐसा क्या करेंगे, जिसमें मैप्स होते हैं जो फ़ंक्शन का नाम ASTs को कार्य करने के लिए बनाते हैं, फ़ंक्शन नाम के बजाय फ़ंक्शन स्ट्रिंग के लिए।


ठीक है, लेकिन समस्या अभी भी है: यह पुनरावृत्ति का उपयोग करता है। अगर मैं ऐसा करता हूं तो मैं स्टैक स्पेस से बाहर निकल जाऊंगा।
डोरकनब सेप

3
@ डॉर्कनोब: क्या विशेष रूप से पुनरावृत्ति का उपयोग करता है? कोई भी ब्लॉक-संरचित प्रोग्रामिंग भाषा (जो ASM की तुलना में उच्च स्तर पर हर आधुनिक भाषा है) स्वाभाविक रूप से वृक्ष-आधारित है और इस प्रकार प्रकृति में पुनरावर्ती है। स्टैक ओवरफ्लो होने के बारे में आप किस विशिष्ट पहलू से चिंतित हैं?
मेसन व्हीलर

1
@ डॉर्कनोब: हाँ, यह किसी भी भाषा की एक अंतर्निहित संपत्ति है, भले ही यह मशीन कोड के लिए संकलित हो। (कॉल स्टैक इस व्यवहार का प्रकटन है।) मैं वास्तव में एक स्क्रिप्ट प्रणाली में योगदानकर्ता हूं जो मेरे वर्णित तरीके से काम करता है। मुझे chat.stackexchange.com/rooms/10470/… पर चैट में शामिल करें और मैं कुशल व्याख्या के लिए कुछ तकनीकों पर चर्चा करूँगा और आपके साथ स्टैक आकार पर प्रभाव को कम करूँगा। :)
मेसन व्हीलर

2
@Doorknob: यहां कोई पुनरावृत्ति समस्या नहीं है क्योंकि एएसटी में फ़ंक्शन कॉल नाम से फ़ंक्शन को संदर्भित कर रहा है , इसे वास्तविक फ़ंक्शन के संदर्भ की आवश्यकता नहीं है । यदि आप मशीन कोड के लिए संकलित कर रहे थे, तो अंततः आपको फ़ंक्शन पते की आवश्यकता होगी, यही वजह है कि अधिकांश संकलक कई पास बनाते हैं। यदि आप एक-पास संकलक रखना चाहते हैं तो आपको सभी कार्यों की "आगे की घोषणा" की आवश्यकता है ताकि संकलक पहले से पते दे सके। बाइटकोड कंपाइलर भी इससे परेशान नहीं होते हैं, जिटर नाम लुकअप संभालता है।
एरोन

5
@ डॉर्कनोब: यह वास्तव में पुनरावर्ती है। और हाँ, यदि आपके स्टैक में केवल 16 प्रविष्टियाँ हैं, तो आप पार्स करने में विफल रहेंगे (((((((((((((((( x )))))))))))))))))। वास्तव में, स्टैक कहीं अधिक बड़े हो सकते हैं, और वास्तविक कोड की व्याकरणिक जटिलता काफी सीमित है। निश्चित रूप से अगर उस कोड को मानव-पठनीय होना चाहिए।
एमएसलटर्स

4

आपको देखने के बाद पार्स नहीं कहा जाना चाहिए callSomething()(मुझे लगता है कि आप के callSomethingबजाय इसका मतलब है doSomething)। के बीच का अंतर aऔर callSomethingहै कि एक है एक विधि परिभाषा, जबकि अन्य एक विधि कॉल है।

जब आप एक नई परिभाषा देखते हैं, तो आप यह सुनिश्चित करने से संबंधित जांच करना चाहेंगे कि आप उस परिभाषा को जोड़ सकते हैं, इसलिए:

  • जांचें कि क्या फ़ंक्शन पहले से ही समान हस्ताक्षर के साथ मौजूद नहीं है
  • सुनिश्चित करें कि विधि घोषणा उचित दायरे में की जा रही है (अर्थात क्या अन्य विधि घोषणाओं के अंदर तरीकों को घोषित किया जा सकता है?)

इन चेकों को पास मानते हुए, आप इसे अपने नक्शे में जोड़ सकते हैं और उस पद्धति की सामग्री की जाँच शुरू कर सकते हैं।

जब आपको एक विधि कॉल मिलती है callSomething(), तो आपको निम्नलिखित जांच करनी चाहिए:

  • क्या callSomethingआपके नक्शे में मौजूद है?
  • क्या इसे ठीक से बुलाया जा रहा है (तर्कों की संख्या आपके द्वारा पाए गए हस्ताक्षर से मेल खाती है)?
  • क्या तर्क मान्य हैं (यदि चर नामों का उपयोग किया जाता है, तो क्या वे घोषित हैं? क्या उन्हें इस दायरे में पहुँचा जा सकता है?)
  • CallSomething को कहा जा सकता है कि आप कहां से हैं (क्या यह निजी, सार्वजनिक, संरक्षित है?)।

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

यदि आप आगे जाना चाहते हैं, तो आप न केवल स्ट्रिंग को बचा सकते हैं, बल्कि वास्तविक विधि का लिंक भी बना सकते हैं। यह अधिक कुशल होगा, लेकिन यदि आपको स्मृति का प्रबंधन करना है, तो यह भ्रमित हो सकता है। मैं आपको सबसे पहले स्ट्रिंग पर रखने की सलाह दूंगा। बाद में आप अनुकूलन करने का प्रयास कर सकते हैं।

ध्यान दें कि यह सब यह मानकर चल रहा है कि आपने अपने कार्यक्रम को समाप्त कर दिया है, जिसका अर्थ है कि आपने अपने कार्यक्रम में सभी टोकन को पहचान लिया है और जानते हैं कि वे क्या हैं । यह कहना नहीं है कि आप जानते हैं कि क्या वे अभी तक एक साथ हैं, जो कि एक चरण है। यदि आप अभी तक नहीं जानते हैं कि टोकन क्या हैं, तो मेरा सुझाव है कि आप पहले उस जानकारी को प्राप्त करने पर ध्यान दें।

मुझे आशा है कि वह मदद करेंगे! प्रोग्रामर एसई में आपका स्वागत है!


2

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

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


1

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

  • "कॉल स्टैक फ्रेम" के रूप में जाना जाता संरचना में, वर्तमान फ़ंक्शन के अगले निर्देश के लिए सभी मापदंडों के डेटा, साथ ही एक पॉइंटर को मिलाएं।
  • कॉल स्टैक पर इस फ़्रेम को पुश करें।
  • फ़ंक्शन के कोड की पहली पंक्ति की मेमोरी ऑफ़सेट पर जाएं।

एक "वापसी" कथन या इसके बाद निम्न कार्य करेगा:

  • रजिस्टर में लौटाए जाने वाले मान को लोड करें।
  • एक रजिस्टर में कॉल करने के लिए पॉइंटर लोड करें।
  • वर्तमान स्टैक फ्रेम पॉप।
  • कॉलर के पॉइंटर पर जाएं।

इसलिए, कार्य केवल उच्च-स्तरीय भाषा विनिर्देश में अमूर्तताएं हैं, जो मनुष्यों को एक अधिक बनाए रखने योग्य और सहज तरीके से कोड को व्यवस्थित करने की अनुमति देते हैं। जब एक असेंबली या इंटरमीडिएट भाषा (JIL, MSIL, ILX) में संकलित किया जाता है, और निश्चित रूप से जब मशीन कोड के रूप में प्रस्तुत किया जाता है, तो लगभग सभी ऐसे सार चले जाते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.