Computer >> कंप्यूटर >  >> प्रोग्रामिंग >> Ruby

एक साधारण पार्सर के साथ एक जटिल नियमित अभिव्यक्ति को बदलना

स्वीकारोक्ति का समय:मुझे विशेष रूप से नियमित अभिव्यक्तियों के साथ काम करना पसंद नहीं है। जबकि मैं हर समय उनका उपयोग करता हूं, /^foo.*$/ . से अधिक जटिल कुछ भी मुझे रुकने और सोचने की आवश्यकता है। जबकि मुझे यकीन है कि ऐसे लोग हैं जो \A(?=\w{6,10}\z)(?=[^a-z]*[a-z])(?=(?:[^A-Z]*[A-Z]){3}) एक नज़र में, लेकिन मुझे गुगल करने में कई मिनट लगते हैं और मुझे दुखी करता है। रूबी पढ़ने से काफी अंतर है।

<ब्लॉकक्वॉट>

यदि आप उत्सुक हैं, तो ऊपर दिया गया उदाहरण इस आलेख से रेगेक्स लुकहेड पर लिया गया है।

स्थिति

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

occurred:[2017-06-12T16:10:00Z TO 2017-06-12T17:10:00Z]

आउच!

नए खोज UI में, हम यह पता लगाना चाहते हैं कि आप कब दिनांक-संबंधित क्वेरी टाइप करना प्रारंभ करते हैं और एक सहायक दिनांक चयनकर्ता पॉप अप करते हैं। और हां, डेटपिकर सिर्फ शुरुआत है। अंततः हम अधिक प्रकार के खोज शब्दों को शामिल करने के लिए संदर्भ-संवेदनशील संकेत का विस्तार करेंगे। यहां कुछ उदाहरण दिए गए हैं:

assigned:jane@email.com context.user.id=100
resolved:false ignored:false occurred:[
params.article.title:"Starr's parser post"       foo:'ba

मुझे इन स्ट्रिंग्स को इस तरह से टोकननाइज़ करने की ज़रूरत है कि:

  • व्हाइटस्पेस टोकन को अलग करता है, सिवाय इसके कि जब '', "" या []
  • से घिरा हो
  • उद्धृत खाली स्थान स्वयं का टोकन है
  • मैं चला सकता हूं tokens.join("") इनपुट स्ट्रिंग को फिर से बनाने के लिए

उदाहरण के लिए:

tokenize(%[params.article.title:"Starr's parser post"       foo:'ba])
=> ["params.article.title:\"Starr's parser post\"", "       ", "foo:'ba"]

रेगुलर एक्सप्रेशन का उपयोग करना

मेरा पहला विचार था कि एक वैध टोकन कैसा दिखना चाहिए, यह परिभाषित करने के लिए एक कैप्चरिंग रेगुलर एक्सप्रेशन का उपयोग करें, फिर String#split का उपयोग करें स्ट्रिंग को टोकन में विभाजित करने के लिए। यह वास्तव में एक बहुत अच्छी चाल है:

# The parens in the regexp mean that the separator is added to the array
"foo  bar  baz".split(/(foo|bar|baz)/)
=> ["", "foo", "  ", "bar", "  ", "baz"]

अजीब खाली-तार के बावजूद, यह शुरुआत में आशाजनक लग रहा था। लेकिन मेरी वास्तविक दुनिया की नियमित अभिव्यक्ति कहीं अधिक जटिल थी। मेरा पहला ड्राफ्ट इस तरह दिखता था:

/
  (                          # Capture group is so split will include matching and non-matching strings
    (?:                      # The first character of the key, which is
      (?!\s)[^:\s"'\[]{1}    # ..any valid "key" char not preceeded by whitespace
      |^[^:\s"'\[]{0,1}      # ..or any valid "key" char at beginning of line
    )
    [^:\s"'\[]*              # The rest of the "key" chars
    :                        # a colon
    (?:                      # The "value" chars, which are
      '[^']+'                # ..anything surrounded by single quotes
      | "[^"]+"              # ..or anything surrounded by double quotes
      | \[\S+\sTO\s\S+\]     # ..or anything like [x TO y]
      | [^\s"'\[]+           # ..or any string not containing whitespace or special chars
    )
  )
/xi 

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

... इस समय की बात है कि इस सब की बेरुखी ने मुझे चौंका दिया। मैं जिस नियमित अभिव्यक्ति दृष्टिकोण का उपयोग कर रहा था वह खरोंच से एक साधारण पार्सर लिखने की तुलना में कहीं अधिक जटिल था।

एनाटॉमी ऑफ़ ए पार्सर

मैं कोई विशेषज्ञ नहीं हूं, लेकिन साधारण पार्सर्स सरल हैं। वे बस इतना ही करते हैं:

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

इसे जानने के बाद, हम एक साधारण पार्सर सेट कर सकते हैं जो स्ट्रिंग्स को व्हाइटस्पेस से विभाजित करता है। यह मोटे तौर पर "foo bar".split(/(\s+)/) के बराबर है। ।

class Parser

  WHITESPACE = /\s/
  NON_WHITESPACE = /\S/

  def initialize
    @buffer = []
    @output = []
  end

  def parse(text) 
    text.each_char do |c|
      case c
      when WHITESPACE
        flush if previous.match(NON_WHITESPACE)
        @buffer << c
      else
        flush if previous.match(WHITESPACE)
        @buffer << c
      end
    end

    flush
    @output
  end

  protected

  def flush
    if @buffer.any?
      @output << @buffer.join("")
      @buffer = []
    end
  end

  def previous
    @buffer.last || ""
  end

end


puts Parser.new().parse("foo bar baz").inspect

# Outputs ["foo", " ", "bar", " ", "baz"]

मैं जो चाहता हूं उसकी दिशा में यह एक कदम है, लेकिन इसमें उद्धरण और कोष्ठक के लिए समर्थन नहीं है। सौभाग्य से, इसे जोड़ने पर कोड की केवल कुछ पंक्तियाँ ही लगती हैं:

  def parse(text) 

    surround = nil

    text.each_char do |c|
      case c
      when WHITESPACE
        flush if previous.match(NON_WHITESPACE) && !surround
        @buffer << c
      when '"', "'"
        @buffer << c
        if !surround
          surround = c
        elsif surround == c
          flush
          surround = nil
        end
      when "["
        @buffer << c
        surround = c if !surround
      when "]"
        @buffer << c
        if surround == "["
          flush
          surround = nil
        end
      else
        flush() if previous().match(WHITESPACE) && !surround
        @buffer << c
      end
    end

    flush
    @output
  end

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

विभाजन विचार

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

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


  1. जावास्क्रिप्ट रेगुलर एक्सप्रेशन संशोधक को उदाहरणों के साथ समझाएं

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

  1. सी ++ में उदाहरण के साथ अभिव्यक्ति वृक्ष

    एक्सप्रेशन ट्री एक विशेष प्रकार का बाइनरी ट्री होता है जिसमें ट्री के प्रत्येक नोड में या तो एक ऑपरेटर या ऑपरेंड होता है। लीफ नोड्स पेड़ का एक संचालन . का प्रतिनिधित्व करता है . गैर-पत्ती नोड्स पेड़ का एक ऑपरेटर . का प्रतिनिधित्व करता है । उदाहरण: इंफिक्स एक्सप्रेशन प्राप्त करने के लिए जिस

  1. रूबी के साथ एक पार्सर कैसे बनाएं

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