কম্পিউটার

রুবি টেমপ্লেটিং:একটি দোভাষী বেকিং

আমরা আশা করি আপনি আপনার কফির উপরে আপনার স্ট্রোপওয়াফেলগুলিকে উষ্ণ করেছেন কারণ আজ আমরা স্টিকি স্ট্রোপ (যে সিরাপটি একটি স্ট্রোপওয়াফেলের দুটি অংশকে একসাথে আটকে দেয়) দিয়ে জিনিসগুলিকে আঠালো করে দিচ্ছি৷ আমাদের সিরিজের প্রথম দুটি অংশে, আমরা একটি লেক্সার এবং একটি পার্সার বেক করেছি এবং এখন, আমরা দোভাষী যোগ করছি এবং সেগুলির উপর স্ট্রুপ ঢেলে জিনিসগুলিকে একত্রিত করছি৷

উপকরণ

ঠিক আছে! আসুন রান্নাঘর বেক করার জন্য প্রস্তুত করি এবং আমাদের উপাদানগুলি টেবিলে রাখি। আমাদের দোভাষীর কাজ করার জন্য দুটি উপাদান বা তথ্যের টুকরো প্রয়োজন:পূর্বে তৈরি করা অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি (AST) এবং যে ডেটা আমরা টেমপ্লেটে এম্বেড করতে চাই। আমরা এই ডেটাটিকে environment বলব .

AST অতিক্রম করতে, আমরা ভিজিটর প্যাটার্ন ব্যবহার করে দোভাষী প্রয়োগ করব। একজন ভিজিটর (এবং আমাদের দোভাষী) একটি জেনেরিক ভিজিট পদ্ধতি প্রয়োগ করে যা একটি নোডকে প্যারামিটার হিসাবে গ্রহণ করে, এই নোডটি প্রক্রিয়া করে এবং সম্ভাব্যভাবে 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

এটির সাথে, আমরা হাতে AST তৈরি না করেই দোভাষী পরীক্ষা করতে সক্ষম হব।

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

অবাক হওয়ার কিছু নেই, এটি বর্তমানে কিছুই ফেরত দেয় না। তাহলে চলুন visit বাস্তবায়ন শুরু করি পদ্ধতি একটি দ্রুত অনুস্মারক হিসাবে, এই টেমপ্লেটের জন্য AST দেখতে কেমন।

এই টেমপ্লেটের জন্য, আমাদের চারটি ভিন্ন ধরনের নোড প্রক্রিয়া করতে হবে: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 ব্যবহার করি এই নির্দিষ্ট ধরনের নোড পরিচালনা করে এমন একটি পদ্ধতিকে কল করতে। প্রতিটি প্রকারের পদ্ধতির নাম demodulized ক্লাস নাম এবং 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}}

অনুস্মারক হিসাবে, সংশ্লিষ্ট AST দেখতে কেমন তা এখানে।

শুধুমাত্র একটি নোড টাইপ আছে যা আমরা এখনও পরিচালনা করি না। এটি হল 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. Node.js এ process.argv0() পদ্ধতি

  2. রুবি হ্যাশ কী হিসাবে অবজেক্ট

  3. রুবি মেথড লুকআপ বোঝা

  4. রুবিতে ব্যবহারিক লিঙ্কড তালিকা