11

मैंने पहले से ही एक जेनरेटर लिखा है जो चाल करता है, लेकिन मैं ऑफ-साइड नियम को लागू करने का सबसे अच्छा तरीका जानना चाहता हूं।आप ऑफ-साइड नियम को लागू करने के बारे में कैसे जाएंगे?

संक्षेप में: Off-side rule का अर्थ इस संदर्भ में है कि इंडेंटेशन को वाक्य रचनात्मक तत्व के रूप में पहचाना जा रहा है।

यहाँ tokenizers बनाने के लिए स्यूडोकोड में ऑफसाइड नियम यह है कि प्रयोग करने योग्य रूप में कब्जा खरोज, मैं नहीं चाहता कि भाषा के आधार पर जवाब को सीमित करना चाहते है:

token NEWLINE 
    matches r"\n\ *" 
    increase line count 
    pick up and store the indentation level 
    remember to also record the current level of parenthesis 

procedure layout tokens 
    level = stack of indentation levels 
    push 0 to level 
    last_newline = none 
    per each token 
     if it is NEWLINE put it to last_newline and get next token 
     if last_newline contains something 
      extract new_level and parenthesis_count from last_newline 
      - if newline was inside parentheses, do nothing 
      - if new_level > level.top 
       push new_level to level 
       emit last_newline as INDENT token and clear last_newline 
      - if new_level == level.top 
       emit last_newline and clear last_newline 
      - otherwise 
       while new_level < level.top 
        pop from level 
        if new_level > level.top 
         freak out, indentation is broken. 
        emit last_newline as DEDENT token 
       clear last_newline 
     emit token 
    while level.top != 0 
     emit token as DEDENT token 
     pop from level 

comments are ignored before they are getting into the layouter 
layouter lies between a lexer and a parser 

यह layouter में एक से अधिक NEWLINE उत्पन्न नहीं करता है समय, और इंडेंटेशन आने पर न्यूलाइन उत्पन्न नहीं करता है। इसलिए पार्सिंग नियम काफी सरल रहते हैं। यह बहुत अच्छा लगता है लेकिन मुझे लगता है कि इसे पूरा करने का बेहतर तरीका है या नहीं।

थोड़ी देर के लिए इसका उपयोग करते समय, मैंने देखा है कि DEDENTs के बाद भी नई लाइन को उत्सर्जित करना अच्छा हो सकता है, इस तरह आप अभिव्यक्ति के लिए ट्रेलर के रूप में इंडेंट डेडेंट को रखते हुए अभिव्यक्ति को NEWLINE से अलग कर सकते हैं।

उत्तर

8

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

पार्सर को हिट करने से पहले केवल इंडेंट के लिए न्यूलाइन न्यूलाइन इंडेंट को कनवर्ट करना निश्चित रूप से चीजों को करने का सही तरीका लगता है - यह एक दर्द (आईएमई) हमेशा पार्सर में आगे बढ़ने के लिए है! मैंने वास्तव में एक चरण को एक अलग परत के रूप में किया है जो तीन चरणों की प्रक्रिया समाप्त हो गया है: पहला संयुक्त जो आपके लेक्सर और लेउटर ने सभी न्यूलाइन लुकहेड सामान को घटाया (जो इसे बहुत आसान बना देता है), दूसरा (यह भी बहुत आसान है) परत लगातार नई लाइनों को जोड़ती है और न्यूलाइन इंडेंट को केवल इंडेंट (या, वास्तव में, COLON NEWLINE इंडेंट को इंडेंट में परिवर्तित करती है, क्योंकि इस मामले में सभी इंडेंट ब्लॉक हमेशा कॉलन से पहले होते थे), तो पार्सर उस पर तीसरा चरण था। लेकिन यह उन चीजों को करने के लिए मुझे बहुत समझ में आता है जैसे आपने उन्हें वर्णित किया है, विशेष रूप से यदि आप लेजर को लेआउट से अलग करना चाहते हैं, तो संभवतः आप ऐसा करना चाहते हैं यदि आप कोड-जनरेशन टूल का उपयोग कर रहे हों उदाहरण के लिए, सामान्य अभ्यास के रूप में, अपने लेक्सर बनाने के लिए।

this line introduces an indented block of literal text: 
    this line of the block is indented four spaces 
    but this line is only indented two spaces 
: उदाहरण के लिए कुछ संदर्भों में मान्य होने की जरूरत के बाद, -

मैं एक आवेदन है कि, थोड़ा और खरोज नियमों के बारे में लचीला होना अनिवार्य रूप से जब जरूरत इन्हें लागू करने के पार्सर छोड़ने की जरूरत थी

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

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

3

मैं हाल ही में इसका प्रयोग कर रहा हूं, और मैं इस निष्कर्ष पर आया कि, मेरी ज़रूरतों के लिए कम से कम, मैं चाहता था कि NEWLINES प्रत्येक "कथन" के अंत को चिह्नित करे, चाहे वह इंडेंट ब्लॉक में अंतिम बयान था या नहीं, यानी मुझे डेडेंट से पहले भी नई लाइनों की आवश्यकता है।

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

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

line1 
    line2 
    line3 
line4 

टोकन धारा

LINE "line1" INDENT LINE "line2" LINE "line3" DEDENT LINE "line4" EOF 

यह मैं तब भी जब वे नेस्टेड, दांतेदार, subblocks, कुछ के साथ समाप्त बयान के अंत का पता लगाने के बारे में चिंता किए बिना बयानों के लिए स्पष्ट व्याकरण प्रस्तुतियों लिखने की अनुमति देता देना होगा यदि आप इसके बजाय NEWLINES (और DEDENTS) से मेल खाते हैं तो यह कठिन हो सकता है।

यहाँ पूर्वप्रक्रमक, O'Caml में लिखा की मूल है:

match next_token() with 
     LINE indentation -> 
     if indentation > !current_indentation then 
      (
      Stack.push !current_indentation indentation_stack; 
      current_indentation := indentation; 
      INDENT 
     ) 
     else if indentation < !current_indentation then 
      (
      let prev = Stack.pop indentation_stack in 
       if indentation > prev then 
       (
        current_indentation := indentation; 
        BAD_DEDENT 
       ) 
       else 
       (
        current_indentation := prev; 
        DEDENT 
       ) 
     ) 
     else (* indentation = !current_indentation *) 
      let token = remove_next_token() in 
      if next_token() = EOF then 
       remove_next_token() 
      else 
       token 
    | _ -> 
     remove_next_token() 

मैं नहीं जोड़ा है कोष्ठकों अभी तक के लिए समर्थन है, लेकिन यह एक सरल विस्तार होना चाहिए। हालांकि, फ़ाइल के अंत में एक भटक लाइन को उत्सर्जित करने से बचें।

+0

आपका कोड एकाधिक DEDENTs को निकालने में सक्षम नहीं है, न ही यह ईओएफ से पहले समर्पित है। यह किसी चीज़ के लिए उपयोगी हो सकता है, लेकिन उन चीजें हैं जो ब्रांड्स समर्थन से अधिक महत्वपूर्ण हैं। – Cheery

+0

इसके अलावा, कोष्ठक के लिए विशेष समर्थन के बारे में परेशान न करें, आप केवल सर्वोत्तम बिंदु को याद करने जा रहे हैं, जैसे कि पाइथन करता है। लेआउटिंग का बिंदु आपको उत्कृष्ट मल्टी-लाइन सिंटैक्स प्रदान करने की अनुमति देना है, यह ब्रांड्स के साथ संघर्ष नहीं करता है, जब तक कि आप उन दोनों को गठबंधन नहीं कर पाते। – Cheery

+0

मेरा कोड एकाधिक डेडेंट उत्सर्जित करता है, इसलिए मुझे लगता है कि आप इसे गलत तरीके से पढ़ रहे हैं। लेकिन मैं मानता हूं कि मुझे ऐसा कुछ चाहिए जो पाइथन की बजाय हास्केल जैसा दिखता है, इसलिए मुझे एक नया दृष्टिकोण चाहिए। – dkagedal

1
मनोरंजन के लिए

माणिक में Tokenizer:

def tokenize(input) 
    result, prev_indent, curr_indent, line = [""], 0, 0, "" 
    line_started = false 

    input.each_char do |char| 

    case char 
    when ' ' 
     if line_started 
     # Content already started, add it. 
     line << char 
     else 
     # No content yet, just count. 
     curr_indent += 1 
     end 
    when "\n" 
     result.last << line + "\n" 
     curr_indent, line = 0, "" 
     line_started = false 
    else 
     # Check if we are at the first non-space character. 
     unless line_started 
     # Insert indent and dedent tokens if indentation changed. 
     if prev_indent > curr_indent 
      # 2 spaces dedentation 
      ((prev_indent - curr_indent)/2).times do 
      result << :DEDENT 
      end 
      result << "" 
     elsif prev_indent < curr_indent 
      result << :INDENT 
      result << "" 
     end 

     prev_indent = curr_indent 
     end 

     # Mark line as started and add char to line. 
     line_started = true; line << char 
    end 

    end 

    result 
end 

दो अंतरिक्ष खरोज के लिए ही काम करता है। परिणाम ["Hello there from level 0\n", :INDENT, "This\nis level\ntwo\n", :DEDENT, "This is level0 again\n"] जैसा कुछ है।