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

Ruby टेम्पलेट इंटरप्रेटर बनाना: लेक्सर से निष्पादन तक

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

सामग्री

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

AST को पार करने के लिए, हम विज़िटर पैटर्न का उपयोग करके इंटरप्रेटर को लागू करेंगे। एक विज़िटर (और इसलिए हमारा इंटरप्रेटर) एक सामान्य visit विधि लागू करता है जो पैरामीटर के रूप में एक नोड को स्वीकार करता है, इस नोड को संसाधित करता है और संभावित रूप से नोड के कुछ (या सभी) बच्चों के साथ 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 विधि भी बनाएं जो एक टेम्पलेट और एक environment स्वीकार करती है और रेंडर किया गया टेम्पलेट आउटपुट करती है।

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

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

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

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

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

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 विधि को यह जानने के लिए कि environment से कौन सी कुंजी लानी है, आइए 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}}

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

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

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

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

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

टेम्पलेट पर वापस देखते हुए, हम देखते हैं कि ब्लॉक एक्सप्रेशन में एक if है। environment में subscribed के मान को देखने के बजाय, visit_BlockExpression if के मान को देख रहा है। चूंकि यह environment में मौजूद नहीं है, कॉल nil लौटाता है, जो false है।

हम यहां रुक सकते हैं और घोषणा कर सकते हैं कि हम हैंडलबार्स की नकल नहीं बल्कि Mustache बनाने की कोशिश कर रहे हैं, और टेम्पलेट में 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

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

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

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 हेल्पर को लागू करें। आर्ग्युमेंट्स के अलावा, हम उन्हें दो lambdas पास करेंगे ताकि वे तय कर सकें कि हमें नोड के 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 के साथ हमने जो किया उसके समान हैं, केवल इस बार, हम दो lambdas भी पास करते हैं।

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

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

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

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

  1. - Node.js में decipher.final() मेथड

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

  1. - Node.js में crypto.privateEncrypt() मेथड

    Node.js में crypto.privateEncrypt() मेथड का उपयोग फंक्शन में पास किए गए प्राइवेट की पैरामीटर का उपयोग करके दिए गए डेटा कंटेंट को एन्क्रिप्ट करने के लिए किया जाता है। सिंटैक्स crypto.privateEncrypt(privateKey, buffer) पैरामीटर्स उपरोक्त पैरामीटर्स का विवरण नीचे दिया गया है: privateKey – इसमें निम

  1. रूबी में लिंक्ड लिस्ट्स में महारत: एक व्यावहारिक मार्गदर्शिका रूबी में लिंक्ड लिस्ट्स में महारत: एक व्यावहारिक मार्गदर्शिका

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