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

रूबी टेम्प्लेटिंग:एक दुभाषिया पकाना

हमें उम्मीद है कि आपने अपनी कॉफी के ऊपर अपने स्ट्रूपवाफल्स को गर्म कर लिया होगा क्योंकि आज हम चीजों को चिपचिपे स्ट्रूप (वह सिरप जो एक स्ट्रूपवाफेल के दो हिस्सों को एक साथ चिपकाते हैं) के साथ चिपका रहे हैं। हमारी श्रृंखला के पहले दो भागों में, हमने एक लेक्सर और एक पार्सर को बेक किया और अब, हम इंटरप्रेटर और ग्लूइंग चीजों को एक साथ जोड़ रहे हैं, उन पर स्ट्रूप डालकर।

सामग्री

ठीक है! आइए रसोई को पकाने के लिए तैयार करें और हमारी सामग्री को टेबल पर रख दें। हमारे दुभाषिया को अपना काम करने के लिए दो अवयवों या जानकारी के टुकड़ों की आवश्यकता होती है:पहले से उत्पन्न सार सिंटैक्स ट्री (एएसटी) और वह डेटा जिसे हम टेम्पलेट में एम्बेड करना चाहते हैं। हम इस डेटा को environment . कहेंगे ।

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

module Magicbars
  class Interpreter
    attr_reader :root, :environment
 
    def self.render(root, environment = {})
      new(root, environment).render
    end
 
    def initialize(root, environment = {})
      @root = root
      @environment = environment
    end
 
    def render
      visit(root)
    end
 
    def visit(node)
      # Process node
    end
  end
end

जारी रखने से पहले, आइए एक छोटा Magicbars.render भी बनाएं वह विधि जो किसी टेम्पलेट और परिवेश को स्वीकार करती है और रेंडर किए गए टेम्पलेट को आउटपुट करती है।

module Magicbars
  def self.render(template, environment = {})
    tokens = Lexer.tokenize(template)
    ast = Parser.parse(tokens)
    Interpreter.render(ast, environment)
  end
end

इसके साथ, हम हाथ से एएसटी का निर्माण किए बिना दुभाषिया का परीक्षण करने में सक्षम होंगे।

Magicbars.render('Welcome to {{name}}', name: 'Ruby Magic')
# => nil

कोई आश्चर्य नहीं, यह वर्तमान में कुछ भी वापस नहीं करता है। तो चलिए visit को लागू करना शुरू करते हैं तरीका। एक त्वरित अनुस्मारक के रूप में, इस टेम्पलेट के लिए एएसटी कैसा दिखता है।

इस टेम्पलेट के लिए, हमें चार अलग-अलग नोड प्रकारों को संसाधित करना होगा:Template , Content , Expression , और Identifier . ऐसा करने के लिए, हम बस एक बड़ा case डाल सकते हैं हमारे visit . के अंदर स्टेटमेंट तरीका। हालाँकि, यह बहुत जल्दी अपठनीय हो जाएगा। इसके बजाय, आइए अपने कोड को थोड़ा अधिक व्यवस्थित और पठनीय रखने के लिए रूबी की मेटाप्रोग्रामिंग क्षमताओं का उपयोग करें।

module Magicbars
  class Interpreter
    # ...
 
    def visit(node)
      short_name = node.class.to_s.split('::').last
      send("visit_#{short_name}", node)
    end
  end
end

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

module Magicbars
  class Interpreter
    # ...
 
    def visit_Template(node)
      # Process template nodes
    end
 
    def visit_Content(node)
      # Process content nodes
    end
 
    def visit_Expression(node)
      # Process expression nodes
    end
 
    def visit_Identifier(node)
      # Process identifier nodes
    end
  end
end

आइए visit_Template . को लागू करके शुरुआत करें तरीका। इसे बस सभी statements . को प्रोसेस करना चाहिए नोड के और परिणामों में शामिल हों।

def visit_Template(node)
  node.statements.map { |statement| visit(statement) }.join
end

इसके बाद, आइए visit_Content देखें तरीका। एक सामग्री नोड के रूप में बस एक स्ट्रिंग लपेटता है, विधि जितनी सरल हो जाती है।

def visit_Content(node)
  node.content
end

अब, चलिए visit_Expression पर चलते हैं विधि जहां वास्तविक मूल्य के साथ प्लेसहोल्डर का प्रतिस्थापन होता है।

def visit_Expression(node)
  key = visit(node.identifier)
  environment.fetch(key, '')
end

और अंत में, visit_Expression . के लिए यह जानने के लिए कि पर्यावरण से कौन सी कुंजी प्राप्त करनी है, आइए visit_Identifier को लागू करें विधि।

def visit_Identifier(node)
  node.value
end

इन चार विधियों के साथ, जब हम टेम्पलेट को फिर से प्रस्तुत करने का प्रयास करते हैं तो हमें वांछित परिणाम मिलता है।

Magicbars.render('Welcome to {{name}}', name: 'Ruby Magic')
# => Welcome to Ruby Magic

ब्लॉक एक्सप्रेशन की व्याख्या करना

हमने एक साधारण gsub . को लागू करने के लिए बहुत सारे कोड लिखे हैं कर सकता है। तो चलिए एक अधिक जटिल उदाहरण की ओर बढ़ते हैं।

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}}

एक अनुस्मारक के रूप में, संबंधित एएसटी कैसा दिखता है।

केवल एक नोड प्रकार है जिसे हम अभी तक संभाल नहीं पाए हैं। यह है visit_BlockExpression नोड. एक तरह से यह visit_Expression . के समान है नोड, लेकिन मान के आधार पर यह या तो statements . को संसाधित करना जारी रखता है या inverse_statements BlockExpression . का नोड.

def visit_BlockExpression(node)
  key = visit(node.identifier)
 
  if environment[key]
    node.statements.map { |statement| visit(statement) }.join
  else
    node.inverse_statements.map { |statement| visit(statement) }.join
  end
end

विधि को देखते हुए, हम देखते हैं कि दोनों शाखाएँ बहुत समान हैं, और वे visit_Template के समान भी दिखती हैं। तरीका। वे सभी Array . के सभी नोड्स के विज़िटिंग को संभालते हैं , तो आइए एक visit_Array निकालें चीजों को थोड़ा साफ करने का तरीका।

def visit_Array(nodes)
  nodes.map { |node| visit(node) }
end

नई विधि के साथ, हम visit_Template . से कुछ कोड हटा सकते हैं और visit_BlockExpression तरीके।

def visit_Template(node)
  visit(node.statements).join
end
 
def visit_BlockExpression(node)
  key = visit(node.identifier)
 
  if environment[key]
    visit(node.statements).join
  else
    visit(node.inverse_statements).join
  end
end

अब जबकि हमारा दुभाषिया सभी नोड प्रकारों को संभालता है, आइए जटिल टेम्पलेट को प्रस्तुत करने का प्रयास करें।

Magicbars.render(template, { name: 'Ruby Magic', subscribed: true, company_name: 'AppSignal' })
# => Welcome to Ruby Magic!
#
#
#  Please sign up for our mailing list to be notified about new articles!
#
#
# Your friends at AppSignal

वह लगभग सही दिखता है। लेकिन करीब से देखने पर, हम देखते हैं कि संदेश हमें मेलिंग सूची के लिए साइन अप करने के लिए प्रेरित कर रहा है, भले ही हमने subscribed: true प्रदान किया हो पर्यावरण में। यह सही नहीं लगता...

सहायक विधियों के लिए समर्थन जोड़ना

टेम्प्लेट पर पीछे मुड़कर देखने पर, हम देखते हैं कि एक if . है ब्लॉक अभिव्यक्ति में। subscribed . का मान देखने के बजाय परिवेश में, visit_BlockExpression if . का मान देख रहा है . चूंकि यह वातावरण में मौजूद नहीं है, कॉल रिटर्न nil , जो गलत है।

हम यहां रुक सकते हैं और घोषणा कर सकते हैं कि हम हैंडलबार्स की नकल करने की कोशिश नहीं कर रहे हैं, लेकिन मूंछें, और if से छुटकारा पाएं टेम्पलेट में, जो हमें वांछित परिणाम देगा।

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

लेकिन जब हम मस्ती कर रहे हैं तो रुकें क्यों? आइए अतिरिक्त मील जाएं और सहायक विधियों को लागू करें। वे अन्य चीजों के लिए भी काम आ सकते हैं।

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

def helpers
  @helpers ||= {
    reverse: ->(value) { value.to_s.reverse },
    debug: ->(value) { value.class }
  }
end

हम इन हेल्पर्स को लागू करने के लिए साधारण लैम्ब्डा का उपयोग करते हैं और उन्हें हैश में स्टोर करते हैं ताकि हम उन्हें उनके नाम से देख सकें।

इसके बाद, आइए visit_Expression को संशोधित करें परिवेश में मूल्य देखने की कोशिश करने से पहले एक सहायक लुकअप करने के लिए।

def visit_Expression(node)
  key = visit(node.identifier)
 
  if helper = helpers[key]
    arguments = visit(node.arguments).map { |k| environment[k] }
 
    return helper.call(*arguments)
  end
 
  environment[key]
end

यदि दिए गए पहचानकर्ता से मेल खाने वाला कोई सहायक है, तो विधि सभी तर्कों पर जाएगी और उनके लिए मूल्यों को देखने का प्रयास करेगी। बाद में, यह विधि को कॉल करेगा और सभी मानों को तर्क के रूप में पारित करेगा।

Magicbars.render('Welcome to {{reverse name}}', name: 'Ruby Magic')
# => Welcome to cigaM ybuR
 
Magicbars.render('Welcome to {{debug name}}', name: 'Ruby Magic')
# => Welcome to String

उस जगह के साथ, आइए अंत में एक if . लागू करें और एक unless सहायक। तर्कों के अलावा, हम उन्हें दो लैम्ब्डा देंगे ताकि वे तय कर सकें कि हमें नोड के statements की व्याख्या जारी रखनी चाहिए या नहीं या inverse_statements

def helpers
  @helpers ||= {
    if: ->(value, block:, inverse_block:) { value ? block.call : inverse_block.call },
    unless: ->(value, block:, inverse_block:) { value ? inverse_block.call : block.call },
    # ...
  }
end
 

visit_BlockExpression . में आवश्यक परिवर्तन जैसा हमने visit_Expression . के साथ किया था, वैसा ही है , केवल इस बार, हम दो लैम्ब्डा भी पास करते हैं।

def visit_BlockExpression(node)
  key = visit(node.identifier)
 
  if helper = helpers[key]
    arguments = visit(node.arguments).map { |k| environment[k] }
 
    return helper.call(
      *arguments,
      block: -> { visit(node.statements).join },
      inverse_block: -> { visit(node.inverse_statements).join }
    )
  end
 
  if environment[key]
    visit(node.statements).join
  else
    visit(node.inverse_statements).join
  end
end

और इससे हमारी बेकिंग हो जाती है! हम उस जटिल टेम्पलेट को प्रस्तुत कर सकते हैं जिसने इस यात्रा को लेक्सर्स, पार्सर्स और दुभाषियों की दुनिया में शुरू किया।

Magicbars.render(template, { name: 'Ruby Magic', subscribed: true, company_name: 'AppSignal' })
# => Welcome to Ruby Magic!
#
#
#  Thank you for subscribing to our mailing list.
#
#
# Your friends at AppSignal

केवल सतह को खरोंचना

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

हमें उम्मीद है कि आपने श्रृंखला का आनंद लिया है और यदि आप इससे अधिक चाहते हैं, तो रूबी मैजिक सूची की सदस्यता लें। यदि आप अब स्ट्रूपवाफल्स के भूखे हैं, तो हमें एक पंक्ति दें और हम आपको उनसे भी ईंधन भरने में सक्षम हो सकते हैं!


  1. Decipher. final() Node.js में विधि

    decipher. final() का उपयोग बफर या स्ट्रिंग को वापस करने के लिए किया जाता है जिसमें डिक्रिप्ट ऑब्जेक्ट का मान होता है। यह क्रिप्टो मॉड्यूल के भीतर क्लास सिफर द्वारा प्रदान की गई इनबिल्ट पद्धति में से एक है। एक बार गूढ़लेख विधि कहलाने के बाद डेटा को डिक्रिप्ट करने के लिए गूढ़ विधि का उपयोग नहीं किया ज

  1. क्रिप्टो.प्राइवेटएन्क्रिप्ट () Node.js में विधि

    Crypto.privateEncrypt() का उपयोग फ़ंक्शन में दिए गए निजी कुंजी पैरामीटर का उपयोग करके दी गई डेटा सामग्री को एन्क्रिप्ट करने के लिए किया जाता है। सिंटैक्स crypto.privateEncrypt(privateKey, बफर) पैरामीटर उपरोक्त पैरामीटर नीचे वर्णित हैं - निजी कुंजी - इसमें निम्नलिखित डेटा प्रकार हो सकते हैं - ऑब

  1. रूबी में प्रैक्टिकल लिंक्ड लिस्ट

    यह रूबी में प्रैक्टिकल कंप्यूटर साइंस श्रृंखला में तीसरी प्रविष्टि है! आज हम लिंक्ड लिस्ट के बारे में बात करने जा रहे हैं। तो लिंक की गई सूची क्या है? जैसा कि नाम से पता चलता है, एक लिंक की गई सूची डेटा को सूची प्रारूप में संग्रहीत करने का एक तरीका है (धन्यवाद, कप्तान स्पष्ट!)। लिंक्ड भाग इस तथ्य