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

रूबी में अपना खुद का टेम्प्लेट लेक्सर बनाना

अपने स्कूबा डाइविंग सूट पर रखो और अपने स्टेंसिल पैक करो, हम आज टेम्पलेट्स में गोता लगा रहे हैं!

अधिकांश सॉफ़्टवेयर जो वेब पेजों को प्रस्तुत करते हैं या ईमेल उत्पन्न करते हैं, वे टेक्स्ट दस्तावेज़ों में चर डेटा को एम्बेड करने के लिए टेम्प्लेटिंग का उपयोग करते हैं। दस्तावेज़ की मुख्य संरचना अक्सर डेटा के लिए प्लेसहोल्डर के साथ एक स्थिर टेम्पलेट में सेट की जाती है। चर डेटा, जैसे उपयोगकर्ता नाम या वेब पेज सामग्री, पेज को रेंडर करते समय प्लेसहोल्डर्स को बदल देते हैं।

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

नेटिव स्ट्रिंग इंटरपोलेशन का उपयोग करना

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

name = "Ruby Magic"
template = "Welcome to #{name}"
# => Welcome to Ruby Magic

महान! यह साध्य था। हालांकि, क्या होगा यदि हम कई अवसरों के लिए टेम्पलेट का पुन:उपयोग करना चाहते हैं, या अपने उपयोगकर्ताओं को टेम्पलेट को अपडेट करने की अनुमति देना चाहते हैं?

प्रक्षेप तुरंत मूल्यांकन करता है। हम टेम्पलेट का पुन:उपयोग नहीं कर सकते (जब तक कि हम इसे फिर से परिभाषित नहीं करते - उदाहरण के लिए, एक लूप में) और हम Welcome to #{name} में आपका स्वागत है, संग्रहीत नहीं कर सकते एक डेटाबेस में टेम्पलेट और संभावित खतरनाक eval . का उपयोग किए बिना इसे बाद में पॉप्युलेट करें समारोह।

सौभाग्य से, रूबी के पास तारों को प्रक्षेपित करने का एक अलग तरीका है:Kernel#sprintf या String#% . ये विधियां हमें टेम्पलेट को बदले बिना एक इंटरपोलेटेड स्ट्रिंग प्राप्त करने की अनुमति देती हैं। इस तरह, हम एक ही टेम्पलेट को कई बार पुन:उपयोग कर सकते हैं। यह मनमानी रूबी कोड के निष्पादन की भी अनुमति नहीं देता है। आइए इसका इस्तेमाल करें।

name = "Ruby Magic"
template = "Welcome to %{name}"
 
sprintf(template, name: name)
# => "Welcome to Ruby Magic"
 
template % { name: name }
# => "Welcome to Ruby Magic"

Regexp टेम्प्लेट करने का दृष्टिकोण

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

name = "Ruby Magic"
template = "Welcome to %d"
 
sprintf(template, name: name)
# => TypeError (can't convert Hash into Integer)

दोनों Kernel#sprintf और String#% विभिन्न प्रकार के डेटा को संभालने के लिए विशेष सिंटैक्स की अनुमति दें। वे सभी हमारे द्वारा पास किए गए डेटा के अनुकूल नहीं हैं। इस उदाहरण में, टेम्प्लेट एक संख्या को प्रारूपित करने की अपेक्षा करता है, लेकिन एक TypeError उत्पन्न करते हुए एक हैश पास हो जाता है ।

लेकिन हमारे शेड में अधिक बिजली उपकरण हैं:हम नियमित अभिव्यक्तियों का उपयोग करके अपने स्वयं के प्रक्षेप को लागू कर सकते हैं। रेगुलर एक्सप्रेशन का उपयोग करने से हम कस्टम सिंटैक्स को परिभाषित कर सकते हैं, जैसे मूंछें/हैंडलबार से प्रेरित शैली।

name = "Ruby Magic"
template = "Welcome to {{name}}"
assigns = { "name" => name }
 
template.gsub(/{{(\w+)}}/) { assigns[$1] }
# => Welcome to Ruby Magic

हम String#gsub . का उपयोग करते हैं सभी प्लेसहोल्डर्स (डबल कर्ली ब्रेसेस में शब्द) को assigns में उनके मान से बदलने के लिए हैश। यदि कोई संगत मान नहीं है, तो यह विधि बिना कुछ डाले प्लेसहोल्डर को हटा देती है।

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

मान लें कि हमें टेम्पलेट में सशर्त होने की आवश्यकता है। परिणाम एक चर के मान के आधार पर भिन्न होना चाहिए।

Welcome to
{{name}}!
 
{{#if subscribed}}
  Thank you for subscribing to our mailing list.
{{else}}
  Please sign up for our mailing list to be notified about new articles!
{{/if}}
 
Your friends at
{{company_name}}

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

टेम्पलेटिंग भाषा बनाना

एक टेम्प्लेटिंग भाषा को लागू करना अन्य प्रोग्रामिंग भाषाओं को लागू करने के समान है। एक स्क्रिप्टिंग भाषा की तरह, एक टेम्प्लेट भाषा को तीन घटकों की आवश्यकता होती है:एक लेक्सर, एक पार्सर और एक दुभाषिया। हम इन्हें एक-एक करके देखेंगे।

लेक्सर

पहला कार्य जिसे हमें निपटाने की आवश्यकता है उसे टोकननाइज़ेशन, या लेक्सिकल विश्लेषण कहा जाता है। यह प्रक्रिया प्राकृतिक भाषाओं में शब्द श्रेणियों की पहचान करने के समान ही है।

एक उदाहरण लें जैसे Ruby is a lovely language . वाक्य में विभिन्न श्रेणियों के पाँच शब्द हैं। यह पहचानने के लिए कि वे किस श्रेणी के हैं, आप एक शब्दकोष लेंगे और प्रत्येक शब्द की श्रेणी देखेंगे, जिसके परिणामस्वरूप एक सूची इस प्रकार होगी:संज्ञा , क्रिया , अनुच्छेद , विशेषण , संज्ञा . प्राकृतिक भाषा प्रसंस्करण इन्हें "भाषण के भाग" कहते हैं। औपचारिक भाषाओं में--जैसे प्रोग्रामिंग भाषाएं-- उन्हें टोकन . कहा जाता है ।

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

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

हम एक नया StringScanner . बनाकर ऐसा करते हैं उदाहरण और इसे until . का उपयोग करके अपना काम करने देना लूप जो केवल तभी रुकता है जब स्कैनर स्ट्रिंग के अंत तक पहुँचता है।

अभी के लिए, हम इसे प्रत्येक वर्ण से मेल खाने देते हैं (.* ) कई पंक्तियों में (m संशोधक) और एक CONTENT लौटाएं इस सब के लिए टोकन। हम पहले तत्व के रूप में टोकन नाम और दूसरे तत्व के रूप में किसी भी डेटा के साथ एक सरणी के रूप में एक टोकन का प्रतिनिधित्व करते हैं। हमारा बहुत ही बुनियादी लेक्सर कुछ इस तरह दिखता है:

require 'strscan'
 
module Magicbars
  class Lexer
    def self.tokenize(code)
      new.tokenize(code)
    end
 
    def tokenize(code)
      scanner = StringScanner.new(code)
      tokens = []
 
      until scanner.eos?
        tokens << [:CONTENT, scanner.scan(/.*?/m)]
      end
 
      tokens
    end
  end
end

इस कोड को Welcome to {{name}} के साथ चलाते समय आपका स्वागत है हमें ठीक एक CONTENT . की एक सूची वापस मिलती है इससे जुड़े सभी कोड के साथ टोकन।

Magicbars::Lexer.tokenize("Welcome to {{name}}")
=> [[:CONTENT, "Welcome to {{name}}"]]

अगला, आइए अभिव्यक्ति का पता लगाएं। ऐसा करने के लिए, हम लूप के अंदर कोड को संशोधित करते हैं, इसलिए यह {{ . से मेल खाता है और }} OPEN_EXPRESSION . के रूप में और CLOSE

हम इसे एक सशर्त जोड़कर करते हैं जो विभिन्न मामलों की जांच करता है।

until scanner.eos?
  if scanner.scan(/{{/)
    tokens << [:OPEN_EXPRESSION]
  elsif scanner.scan(/}}/)
    tokens << [:CLOSE]
  elsif scanner.scan(/.*?/m)
    tokens << [:CONTENT, scanner.matched]
  end
end

घुंघराले ब्रेसिज़ को OPEN_EXPRESSION से जोड़ने में कोई अतिरिक्त मूल्य नहीं है और CLOSE टोकन, इसलिए हम उन्हें छोड़ देते हैं। scan . के रूप में कॉल अब शर्त का हिस्सा हैं, हम scanner.matched . का उपयोग करते हैं पिछले मैच के परिणाम को CONTENT . से जोड़ने के लिए टोकन।

दुर्भाग्य से, लेक्सर को फिर से चलाते समय, हमें अभी भी केवल एक CONTENT मिलता है पहले की तरह टोकन। खुली अभिव्यक्ति तक सब कुछ मिलान करने के लिए हमें अभी भी अंतिम अभिव्यक्ति को संशोधित करना होगा। हम scan_until . का उपयोग करके ऐसा करते हैं डबल घुंघराले ब्रेसिज़ के लिए एक सकारात्मक लुकहेड एंकर के साथ जो स्कैनर को उनके ठीक पहले रोकता है। लूप के अंदर हमारा कोड अब इस तरह दिखता है:

until scanner.eos?
  if scanner.scan(/{{/)
    tokens << [:OPEN_EXPRESSION]
  elsif scanner.scan(/}}/)
    tokens << [:CLOSE]
  elsif scanner.scan_until(/.*?(?={{|}})/m)
    tokens << [:CONTENT, scanner.matched]
  end
end

लेक्सर को फिर से चलाना, अब चार टोकन में परिणत होता है:

Magicbars::Lexer.tokenize("Welcome to {{name}}")
=> [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:CONTENT, "name"], [:CLOSE]]

हमारा लेक्सर उस परिणाम के काफी करीब दिखता है जो हम चाहते हैं। हालांकि, name नियमित सामग्री नहीं है; यह एक पहचानकर्ता है! डबल कर्ली ब्रेसिज़ के बीच की स्ट्रिंग्स को बाहर के स्ट्रिंग्स की तुलना में अलग तरीके से व्यवहार किया जाना चाहिए।

एक स्टेट मशीन

ऐसा करने के लिए, हम लेक्सर को दो अलग-अलग राज्यों के साथ एक राज्य मशीन में बदल देते हैं। यह default . में प्रारंभ होता है राज्य। जब यह हिट होता है तो OPEN_EXPRESSION . होता है टोकन, यह expression . पर चला जाता है राज्य और वहां तब तक रहता है जब तक कि वह CLOSE . पर न आ जाए टोकन जो इसे default . पर वापस संक्रमण करता है राज्य।

हम वर्तमान स्थिति को प्रबंधित करने के लिए सरणी का उपयोग करने वाली कुछ विधियों को जोड़कर राज्य मशीन को लागू करते हैं।

def stack
  @stack ||= []
end
 
def state
  stack.last || :default
end
 
def push_state(state)
  stack.push(state)
end
 
def pop_state
  stack.pop
end

state विधि या तो वर्तमान स्थिति लौटा देगी या default . push_state स्टैक में जोड़कर लेक्सर को एक नई अवस्था में ले जाता है। pop_state लेक्सर को वापस पिछली स्थिति में ले जाता है।

इसके बाद, हम लूप के भीतर सशर्त को विभाजित करते हैं और इसे एक सशर्त द्वारा लपेटते हैं जो वर्तमान स्थिति की जांच करता है। default . में रहते हुए राज्य, हम दोनों OPEN_EXPRESSION . को संभालते हैं और CONTENT टोकन इसका मतलब यह भी है कि CONTENT . के लिए रेगुलर एक्सप्रेशन }} की आवश्यकता नहीं है आगे देखो, इसलिए हम इसे छोड़ देते हैं। expression में राज्य, हम CLOSE . को संभालते हैं टोकन और IDENTIFIER . के लिए एक नया रेगुलर एक्सप्रेशन जोड़ें . बेशक, हम एक push_state . जोड़कर राज्य के बदलावों को भी लागू करते हैं OPEN_EXPRESSION पर कॉल करें और एक pop_state CLOSE पर कॉल करें ।

if state == :default
  if scanner.scan(/{{/)
    tokens << [:OPEN_EXPRESSION]
    push_state :expression
  elsif scanner.scan_until(/.*?(?={{)/m)
    tokens << [:CONTENT, scanner.matched]
  end
elsif state == :expression
  if scanner.scan(/}}/)
    tokens << [:CLOSE]
    pop_state
  elsif scanner.scan(/[\w\-]+/)
    tokens << [:IDENTIFIER, scanner.matched]
  end
end

इन परिवर्तनों के स्थान पर, लेक्सर अब हमारे उदाहरण को सही ढंग से चिह्नित करता है।

Magicbars::Lexer.tokenize("Welcome to {{name}}")
# => [[:CONTENT, "Welcome to "], [:OPEN_EXPRESSION], [:IDENTIFIER, "name"], [:CLOSE]]

इसे अपने लिए कठिन बनाना

आइए अधिक उन्नत उदाहरण पर चलते हैं। यह एक से अधिक अभिव्यक्तियों के साथ-साथ एक ब्लॉक का भी उपयोग करता है।

Welcome to {{name}}!
 
{{#if subscribed}}
  Thank you for subscribing to our mailing list.
{{else}}
  Please sign up for our mailing list to be notified about new articles!
{{/if}}
 
Your friends at {{company_name}}

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

if state == :default
  if scanner.scan(/{{#/)
    tokens << [:OPEN_BLOCK]
    push_state :expression
  elsif scanner.scan(/{{\//)
    tokens << [:OPEN_END_BLOCK]
    push_state :expression
  elsif scanner.scan(/{{else/)
    tokens << [:OPEN_INVERSE]
    push_state :expression
  elsif scanner.scan(/{{/)
    tokens << [:OPEN_EXPRESSION]
    push_state :expression
  elsif scanner.scan_until(/.*?(?={{)/m)
    tokens << [:CONTENT, scanner.matched]
  else
    tokens << [:CONTENT, scanner.rest]
    scanner.terminate
  end
elsif state == :expression
  if scanner.scan(/\s+/)
    # Ignore whitespace
  elsif scanner.scan(/}}/)
    tokens << [:CLOSE]
    pop_state
  elsif scanner.scan(/[\w\-]+/)
    tokens << [:IDENTIFIER, scanner.matched]
  else
    scanner.terminate
  end
end

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

लेक्सर के अंतिम संस्करण का उपयोग करते हुए, उदाहरण अब इसमें शामिल है:

[
  [:CONTENT, "Welcome to "],
  [:OPEN_EXPRESSION],
  [:IDENTIFIER, "name"],
  [:CLOSE],
  [:CONTENT, "!\n\n"],
  [:OPEN_BLOCK],
  [:IDENTIFIER, "if"],
  [:IDENTIFIER, "subscribed"],
  [:CLOSE],
  [:CONTENT, "\n  Thank you for subscribing to our mailing list.\n"],
  [:OPEN_INVERSE],
  [:CLOSE],
  [:CONTENT, "\n  Please sign up for our mailing list to be notified about new articles!\n"],
  [:OPEN_END_BLOCK],
  [:IDENTIFIER, "if"],
  [:CLOSE],
  [:CONTENT, "\n\nYour friends at "],
  [:OPEN_EXPRESSION],
  [:IDENTIFIER, "company_name"],
  [:CLOSE],
  [:CONTENT, "\n"]
]

अब जब हम समाप्त कर चुके हैं, तो हमने सात अलग-अलग प्रकार के टोकन की पहचान कर ली है:

टोकन उदाहरण
OPEN_BLOCK {{#
OPEN_END_BLOCK {{/
OPEN_INVERSE {{else
OPEN_EXPRESSION {{
CONTENT अभिव्यक्तियों के बाहर कुछ भी (सामान्य HTML या टेक्स्ट)
CLOSE }}
IDENTIFIER पहचानकर्ता में शब्द वर्ण, संख्याएं, _ होते हैं , और -

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

आगे का रास्ता

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

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


  1. बाश में TXT टेम्प्लेट स्क्रिप्ट कैसे बनाएं?

    यदि आप दिन-ब-दिन टर्मिनल में इसी तरह के टेक्स्ट बनाते हुए पाते हैं, तो प्रक्रिया को सरल क्यों न करें और इसके लिए .txt टेम्प्लेट स्क्रिप्ट बनाकर अपना समय बचाएं? अगर यह दिलचस्प लगता है, तो टर्मिनल को सक्रिय करें, अपना पसंदीदा टेक्स्ट एडिटर चुनें, और चलिए शुरू करते हैं! नई टेम्प्लेट स्क्रिप्ट बनाएं आप

  1. रूबी के कार्य और तरीके:अपना खुद का कैसे परिभाषित करें

    रूबी विधि क्या है? एक विशिष्ट उद्देश्य के लिए एक साथ समूहीकृत रूबी कोड की एक, या एकाधिक, एक विधि है। इस समूहीकृत कोड को एक नाम दिया गया है, ताकि आप जब चाहें, कोड को दोबारा लिखे या कॉपी और पेस्ट किए बिना इसका उपयोग कर सकें। विधि का उद्देश्य हो सकता है : जानकारी प्राप्त करें। वस्तुओं को बदलें या ब

  1. रूबी में 9 नई सुविधाएँ 2.6

    रूबी का एक नया संस्करण नई सुविधाओं और प्रदर्शन में सुधार के साथ आ रहा है। क्या आप परिवर्तनों के साथ बने रहना चाहेंगे? आइए एक नज़र डालते हैं! अंतहीन रेंज रूबी 2.5 और पुराने संस्करण पहले से ही अंतहीन श्रेणी के एक रूप का समर्थन करते हैं (Float::INFINITY के साथ) ), लेकिन रूबी 2.6 इसे अगले स्तर पर ले