एक लेक्सर के लिए टोकन के साथ आ रहा है


14

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

मैं यहां पार्सर्स के बारे में पढ़ रहा हूं: http://www.ferg.org/parsing/index.html , और मैं लेक्सर लिखने पर काम कर रहा हूं, जिसे अगर मुझे सही तरीके से समझ में आता है, तो सामग्री को टोकन में विभाजित करें। मुझे यह समझने में परेशानी हो रही है कि मुझे किस प्रकार के टोकन का उपयोग करना चाहिए या उन्हें कैसे बनाना चाहिए। उदाहरण के लिए, मेरे द्वारा लिंक किए गए उदाहरण में टोकन प्रकार हैं:

  • STRING
  • IDENTIFIER
  • संख्या
  • खाली स्थान के
  • टिप्पणी
  • EOF
  • कई प्रतीक जैसे {और (अपने स्वयं के टोकन प्रकार के रूप में गिना जाता है

मुझे जो समस्या हो रही है, वह यह है कि अधिक सामान्य टोकन मेरे लिए थोड़े अनियंत्रित लगते हैं। उदाहरण के लिए, STRING का अपना अलग टोकन प्रकार बनाम IDENTIFIER क्यों है। एक स्ट्रिंग को STRING_START + (IDENTIFIER | WHITESPACE) + STRING_START के रूप में दर्शाया जा सकता है।

यह मेरी भाषा की कठिनाइयों के साथ भी हो सकता है। उदाहरण के लिए, परिवर्तनशील घोषणाएँ लिखी जाती हैं {var-name var value}और उनके साथ तैनात की जाती हैं {var-name}। ऐसा लगता है '{'और '}'उनके स्वयं के टोकन होने चाहिए, लेकिन क्या VAR_NAME और VAR_VALUE टोकन प्रकार हैं, या ये दोनों IDENTIFIER के अंतर्गत आएंगे? क्या अधिक है कि VAR_VALUE में वास्तव में व्हॉट्सएप हो सकता है। के बाद व्हाट्सएप var-nameका उपयोग घोषणा में मूल्य की शुरुआत को इंगित करने के लिए किया जाता है .. किसी अन्य व्हाट्सएप का मूल्य का हिस्सा है। क्या यह व्हॉट्सएप अपना टोकन बन गया है? इस संदर्भ में व्हॉट्सएप का केवल वही अर्थ है। इसके अलावा, {एक चर घोषणा की शुरुआत नहीं हो सकती है .. यह संदर्भ पर निर्भर करता है (फिर से वह शब्द है!)। {:एक नाम घोषणा शुरू करता है, और{ कुछ मूल्य के हिस्से के रूप में भी इस्तेमाल किया जा सकता है।

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

मेरा अंतिम प्रश्न सबसे बेवकूफ़ है: क्यों एक लेक्सर भी आवश्यक है? यह मुझे लगता है कि पार्सर चरित्र-दर-चरित्र जा सकता है और यह पता लगा सकता है कि यह कहां है और यह क्या उम्मीद करता है। क्या लेक्सर सादगी का लाभ जोड़ता है?


2
आगे बढ़ो और एक स्कैनर रहित पार्सर लिखने की कोशिश करो। अगर यह बिल्कुल काम करता है (मुझे लगता है कि परिणाम कुछ पार्सिंग एल्गोरिदम के लिए बहुत अस्पष्ट हो सकता है), तो संभावना है कि आप सभी "व्हाट्सएप को यहां भी अनुमति दी गई है" और "प्रतीक्षा करें" के नीचे कोई वास्तविक व्याकरण नहीं देखेगा, क्या मैं पार्सिंग कर रहा था पहचानकर्ता या एक संख्या? ”। मैं अनुभव से बोलता हूं।

एक कस्टम व्हील को क्यों मजबूत करें? किसी ऐसी भाषा को डिजाइन करने के बजाय जिसे कस्टम-निर्मित लेक्सर की आवश्यकता होती है, क्या आपने मौजूदा भाषा का उपयोग करने पर विचार किया है जो पहले से ही एक अंतर्निहित लेसर के साथ आती है, जैसे LISP, या यहां तक ​​कि FORTH?
जॉन आर। स्ट्रॉहैम

2
शैक्षिक उद्देश्यों के लिए @ JohnR.Strohm। भाषा शायद वैसे भी व्यावहारिक रूप से उपयोगी नहीं होगी।
धमाका

जवाबों:


11

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

जवाब है, आगे या पीछे-पीछे दिखने वाली बाधाओं का उपयोग करने की आवश्यकता के बिना जितना आप कर सकते हैं उतना ही करें।

इसका मतलब यह है कि (भाषा के विवरण पर निश्चित रूप से निर्भर करता है) आपको एक स्ट्रिंग को "चरित्र के बाद अनुक्रम नहीं" के रूप में पहचानना चाहिए और फिर एक और "चरित्र। एक एकल इकाई के रूप में पार्सर पर लौटें। कई हैं। इसके कारण हैं, लेकिन महत्वपूर्ण हैं

  1. यह पार्सर को अपनी स्मृति खपत को सीमित करने के लिए राज्य की मात्रा को कम करने की आवश्यकता है।
  2. यह लेक्सर कार्यान्वयन को मौलिक भवन ब्लॉकों को पहचानने पर ध्यान केंद्रित करने की अनुमति देता है और पार्सर को यह वर्णन करने के लिए मुक्त करता है कि प्रोग्राम को बनाने के लिए व्यक्तिगत सिंटैक्टिक तत्वों का उपयोग कैसे किया जाता है।

बहुत बार पार्सर लेसर से टोकन प्राप्त करने पर तत्काल कार्रवाई कर सकते हैं। उदाहरण के लिए, जैसे ही IDENTIFIER प्राप्त होता है, पार्सर यह पता लगाने के लिए एक प्रतीक तालिका लुकअप कर सकता है कि क्या प्रतीक पहले से ही ज्ञात है। यदि आपका पार्सर स्ट्रिंग स्थिरांक को QUOTE (IDENTIFIER SPACES) * के रूप में भी देता है, तो * QUOTE आप बहुत अप्रासंगिक प्रतीक तालिका लुकअप प्रदर्शन करेंगे, या आप सिंटैक्स तत्वों के पार्सर के पेड़ को ऊंचा करते हुए प्रतीक तालिका लुक अप को समाप्त करेंगे, क्योंकि आप केवल कर सकते हैं इस बिंदु पर अब आप सुनिश्चित हैं कि आप एक स्ट्रिंग नहीं देख रहे हैं।

यह कहने के लिए कि मैं क्या कहने की कोशिश कर रहा हूं, लेकिन अलग-अलग तरीके से, शब्द की वर्तनी और चीज़ की संरचना के साथ पार्सर का संबंध होना चाहिए।

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

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

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

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

अंतिम एक ऐतिहासिक नोट। दशकों पहले, पार्सर को सौंपने से पहले लेक्सर के लिए जितना संभव हो उतना महत्वपूर्ण था, क्योंकि दोनों कार्यक्रम एक ही समय में स्मृति में फिट नहीं होंगे। लेसर में अधिक करने से पार्सर को स्मार्ट बनाने के लिए अधिक मेमोरी उपलब्ध हो गई। मैं कई वर्षों तक व्हॉट्समिथ्स सी कंपाइलर का उपयोग करता था , और अगर मैं सही तरीके से समझूं , तो यह केवल 64KB RAM (यह एक छोटा-मॉडल MS-DOS प्रोग्राम था) में काम करेगा और यहां तक ​​कि इसने C के एक प्रकार का अनुवाद किया। एएनएसआई सी के बहुत करीब था।


स्मृति आकार के बारे में अच्छा ऐतिहासिक ध्यान दें पहली जगह में लेसर और पार्सर में नौकरी के बंटवारे का एक कारण।
स्टेवगेट

3

मैं आपके अंतिम प्रश्न पर विचार करूंगा, जो वास्तव में बेवकूफ नहीं है। पार्सर्स चरित्र-दर-वर्ण आधार पर जटिल निर्माण कर सकते हैं और कर सकते हैं। अगर मुझे याद है, हारबिसन और स्टील ("सी - ए संदर्भ पुस्तिका") में व्याकरण में ऐसे प्रोडक्शंस हैं जो एकल वर्णों को टर्मिनलों के रूप में उपयोग करते हैं, और एकल वर्णों से गैर-टर्मिनलों के रूप में पहचानकर्ता, तार, संख्या आदि का निर्माण करते हैं।

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

एक एल्गोरिथम दृष्टिकोण से, एक नियमित अभिव्यक्ति पहचानकर्ता किसी भी पार्सर की तुलना में कहीं अधिक तेजी से चल सकता है। एक संज्ञानात्मक दृष्टिकोण से, एक प्रोग्रामर के लिए नियमित अभिव्यक्ति-लेक्सर और एक पार्सर-जनरेटर लिखित पार्सर के बीच काम को तोड़ना आसान है।

मैं कहता हूं कि व्यावहारिक विचार लोगों को अलग लेक्सर और पार्स करने का निर्णय लेने का कारण बनता है।


हां - और सी मानक स्वयं एक ही काम करता है, जैसे कि मैं सही ढंग से याद करता हूं, कर्निघन और रिची के दोनों संस्करण।
जेम्स यंगमैन

3

ऐसा लगता है कि आप वास्तव में व्याकरण को समझने के बिना एक lexer / पार्सर लिखने का प्रयास कर रहे हैं। आमतौर पर, जब लोग एक लेक्सर और पार्सर लिख रहे होते हैं, तो वे उन्हें कुछ व्याकरण के अनुरूप लिखने के लिए लिखते हैं। व्याकरण व्याकरण में टोकन वापस करना चाहिए जबकि पार्सर नियमों / गैर-टर्मिनलों से मिलान करने के लिए उन टोकन का उपयोग करता है । यदि आप आसानी से अपने इनपुट को केवल बाइट के द्वारा बाइट कर सकते हैं, तो एक लेक्सर और पार्सर ओवरकिल हो सकता है।

लेक्सर्स चीजों को सरल बनाते हैं।

व्याकरण अवलोकन : एक व्याकरण नियमों का एक सेट है कि कुछ वाक्यविन्यास या इनपुट को कैसे देखना चाहिए। उदाहरण के लिए, यहां एक खिलौना व्याकरण (simple_command प्रारंभ प्रतीक है):

simple_command:
 WORD DIGIT AND_SYMBOL
simple_command:
     addition_expression

addition_expression:
    NUM '+' NUM

इस व्याकरण का अर्थ है कि -
A simple_command A से बना है
) WORD उसके बाद DIGIT और उसके बाद AND_SYMBOL (ये "टोकन" हैं, जिन्हें मैं परिभाषित करता हूं)
B) एक " जोड़_ एक्सप्रेशन " (यह एक नियम है या "नॉन-टर्मिनल")

इसके अलावा_निष्कासन से बना है:
NUM के बाद '+' और उसके बाद NUM (NUM एक "टोकन" है जिसे मैं परिभाषित करता हूं, '+' एक शाब्दिक प्लस चिह्न है)।

इसलिए, चूंकि simple_command "स्टार्ट सिंबल" है (जिस स्थान पर मैं शुरू करता हूं), जब मुझे एक टोकन मिलता है, तो मैं यह देखने के लिए जांच करता हूं कि क्या वह simple_command में फिट बैठता है। यदि इनपुट में पहला टोकन एक WORD है और अगला टोकन एक DIGIT है और अगला टोकन AND_SYMBOL है, तो मैंने कुछ simple_command से मिलान किया है और कुछ कार्रवाई कर सकता है। अन्यथा, मैं इसे simple_command के अन्य नियम से मिलाने का प्रयास करूँगा जो इसके अलावा__प्रकरण है। इस प्रकार, यदि पहला टोकन एक NUM था, उसके बाद '+' और उसके बाद NUM था, तो मैंने एक simple_command से मिलान किया और मैं कुछ कार्रवाई करता हूं। यदि यह उन चीजों में से नहीं है, तो मेरे पास एक सिंटैक्स त्रुटि है।

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

एक लेसर / पार्सर व्यवस्था का उपयोग करते हुए, यहां एक उदाहरण दिया गया है कि आपका पार्सर कैसा दिख सकता है:

bool simple_command(){
   if (peek_next_token() == WORD){
       get_next_token();
       if (get_next_token() == DIGIT){
           if (get_next_token() == AND_SYMBOL){
               return true;
           } 
       }
   }
   else if (addition_expression()){
       return true;
   }

   return false;
}

bool addition_expression(){
    if (get_next_token() == NUM){
        if (get_next_token() == '+'){
             if (get_next_token() == NUM){
                  return true;
             }
        }
    }
    return false;
}

ठीक है, ताकि कोड की तरह बदसूरत हो और मैं बयानों में कभी भी नेस्टेड नेस्टेड की सिफारिश नहीं करूंगा। लेकिन बिंदु यह है कि अपने अच्छे मॉड्यूलर "get_next_token" और "pic_next_token" फ़ंक्शन का उपयोग करने के बजाय चरित्र द्वारा उस चीज़ को चरित्र से ऊपर करने की कोशिश करें । गंभीरता से, यह एक शॉट दे। आप परिणाम पसंद नहीं करेंगे। अब ध्यान रखें कि उपरोक्त व्याकरण लगभग किसी भी उपयोगी व्याकरण की तुलना में लगभग 30x कम जटिल है। क्या आपको लेक्सर का उपयोग करने का लाभ दिखाई देता है?

ईमानदारी से, lexers और parsers दुनिया में सबसे बुनियादी विषय नहीं हैं। मैं पहले व्याकरण के बारे में पढ़ने और समझने की सलाह दूंगा, फिर लेकर्स / पार्सर के बारे में थोड़ा पढ़ना, फिर डाइविंग करना।


क्या आपके पास व्याकरण के बारे में जानने के लिए कोई सिफारिश है?
धमाका गोलियां

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

1

मेरा अंतिम प्रश्न सबसे बेवकूफ़ है: क्यों एक लेक्सर भी आवश्यक है? यह मुझे लगता है कि पार्सर चरित्र-दर-चरित्र जा सकता है और यह पता लगा सकता है कि यह कहां है और यह क्या उम्मीद करता है।

यह बेवकूफी नहीं है, यह सिर्फ सच्चाई है।

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

लेक्सर का उपयोग करना या न करना आपकी भाषा और चरित्र के स्तर के बीच अमूर्तता का स्तर होने का मामला है। ध्यान दें कि आजकल चरित्र स्तर, बाइट स्तर से ऊपर एक और अमूर्त है, जो कि बिट स्तर से ऊपर का अमूर्त है।

तो, अंत में, आप बिट स्तर पर भी पार्स कर सकते हैं।


0
STRING_START + (IDENTIFIER | WHITESPACE) + STRING_START.

नहीं, यह नहीं हो सकता। किस बारे में "("? आपके अनुसार, यह एक वैध स्ट्रिंग नहीं है। और बच जाता है?

सामान्य तौर पर, व्हाट्सएप का इलाज करने का सबसे अच्छा तरीका यह है कि इसे अनदेखा करना, टोकन को नष्ट करने से परे। बहुत से लोग बहुत अलग व्हाट्सएप पसंद करते हैं और व्हॉट्सएप के नियमों को लागू करना विवादास्पद है।

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