কম্পিউটার

রুবি টেমপ্লেটিং:দ্য পার্সারের গভীরে খনন করা

আজ, আমরা রুবি টেমপ্লেটিং-এ আমাদের যাত্রা চালিয়ে যাচ্ছি। লেক্সারের জায়গায়, চলুন পরবর্তী ধাপে যাওয়া যাক:পার্সার।

শেষবার, আমরা স্ট্রিং ইন্টারপোলেশন দেখেছিলাম এবং পরবর্তীকালে, আমাদের নিজস্ব টেমপ্লেটিং ভাষা তৈরিতে ডুব দিয়েছিলাম। আমরা একটি লেক্সার প্রয়োগ করে শুরু করেছি যা একটি টেমপ্লেট পড়ে এবং এটিকে টোকেনের একটি প্রবাহে রূপান্তর করে। আজ, আমরা সহগামী পার্সার বাস্তবায়ন করব। আমরা আমাদের পায়ের আঙ্গুলগুলিকে কিছুটা ভাষা তত্ত্বে ডুবিয়ে দেব।

এই যে আমরা!

বিমূর্ত সিনট্যাক্স গাছ

আসুন Welcome to {{name}}-এ স্বাগতম আমাদের সহজ উদাহরণ টেমপ্লেটের দিকে ফিরে তাকাই . স্ট্রিংকে টোকেনাইজ করার জন্য লেক্সার ব্যবহার করার পরে, আমরা এই ধরনের টোকেনগুলির একটি তালিকা পাই৷

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

শেষ পর্যন্ত, আমরা টেমপ্লেটটি মূল্যায়ন করতে চাই এবং অভিব্যক্তিটিকে বাস্তব মান দিয়ে প্রতিস্থাপন করতে চাই। জিনিসগুলিকে একটু বেশি চ্যালেঞ্জিং করতে, আমরা পুনরাবৃত্তি এবং শর্তসাপেক্ষের অনুমতি দিয়ে জটিল ব্লক এক্সপ্রেশনগুলিকেও মূল্যায়ন করতে চাই৷

এটি করার জন্য, আমাদের একটি বিমূর্ত সিনট্যাক্স ট্রি (AST) তৈরি করতে হবে যা টেমপ্লেটের যৌক্তিক কাঠামো বর্ণনা করে। ট্রিটি নোড নিয়ে গঠিত যা অন্য নোডের উল্লেখ করতে পারে বা টোকেন থেকে অতিরিক্ত ডেটা সঞ্চয় করতে পারে।

আমাদের সাধারণ উদাহরণের জন্য, পছন্দসই বিমূর্ত সিনট্যাক্স ট্রি দেখতে এইরকম:

একটি ব্যাকরণ সংজ্ঞায়িত করা

ব্যাকরণ সংজ্ঞায়িত করতে, আসুন একটি ভাষার তাত্ত্বিক ভিত্তি দিয়ে শুরু করা যাক। অন্যান্য প্রোগ্রামিং ভাষার মতো, আমাদের টেমপ্লেটিং ভাষা একটি প্রসঙ্গ-মুক্ত ভাষা এবং তাই একটি প্রসঙ্গ-মুক্ত ব্যাকরণ দ্বারা বর্ণনা করা যেতে পারে।

একটি প্রসঙ্গ-মুক্ত ব্যাকরণ হল নিয়মগুলির একটি সেট যা বর্ণনা করে যে কীভাবে একটি ভাষার সমস্ত সম্ভাব্য স্ট্রিং তৈরি করা হয়। আসুন EBNF স্বরলিপিতে আমাদের টেমপ্লেটিং ভাষার ব্যাকরণটি দেখি:

template = statements;
statements = { statement };
statement = CONTENT | expression | block_expression;
expression = OPEN_EXPRESSION, IDENTIFIER, arguments, CLOSE;
block_expression = OPEN_BLOCK, IDENTIFIER, arguments, CLOSE, statements, [ OPEN_INVERSE, CLOSE, statements ], OPEN_END_BLOCK, IDENTIFIER, CLOSE;
arguments = { IDENTIFIER };

প্রতিটি অ্যাসাইনমেন্ট একটি নিয়ম সংজ্ঞায়িত করে। নিয়মের নাম বাম দিকে এবং আমাদের লেক্সার থেকে অন্যান্য নিয়মের একটি গুচ্ছ (লোয়ার কেস) বা টোকেন (বড় হাতের) ডানদিকে রয়েছে। নিয়ম এবং টোকেন কমা , ব্যবহার করে সংযুক্ত করা যেতে পারে অথবা পাইপ | ব্যবহার করে বিকল্প প্রতীক কোঁকড়া ধনুর্বন্ধনীর ভিতরে নিয়ম এবং টোকেন { ... } কয়েকবার পুনরাবৃত্তি হতে পারে। যখন তারা বন্ধনী [ ... ] এর ভিতরে থাকে , সেগুলি ঐচ্ছিক বলে বিবেচিত হয়৷

উপরের ব্যাকরণটি বর্ণনা করার একটি সংক্ষিপ্ত উপায় যে একটি টেমপ্লেট বিবৃতি নিয়ে গঠিত। একটি বিবৃতি হয় একটি CONTENT টোকেন, একটি অভিব্যক্তি, বা একটি ব্লক অভিব্যক্তি। একটি অভিব্যক্তি হল একটি OPEN_EXPRESSION টোকেন, একটি IDENTIFIER অনুসরণ করে টোকেন, তারপরে আর্গুমেন্ট, তারপর একটি CLOSE টোকেন. এবং একটি ব্লক এক্সপ্রেশন হল একটি নিখুঁত উদাহরণ কেন এটি একটি প্রাকৃতিক ভাষা দিয়ে বর্ণনা করার চেষ্টা করার পরিবর্তে উপরেরটির মতো একটি স্বরলিপি ব্যবহার করা ভাল৷

এমন সরঞ্জাম রয়েছে যা স্বয়ংক্রিয়ভাবে উপরের মত ব্যাকরণের সংজ্ঞা থেকে পার্সার তৈরি করে। কিন্তু সত্যিকারের রুবি ম্যাজিক ঐতিহ্যে, আসুন কিছু মজা করি এবং নিজেরাই পার্সার তৈরি করি, আশা করি এই প্রক্রিয়ায় একটি বা দুটি জিনিস শিখতে পারব।

পার্সার তৈরি করা

ভাষা তত্ত্বকে একপাশে রেখে, আসুন প্রকৃতপক্ষে পার্সার তৈরিতে ঝাঁপিয়ে পড়ি। চলুন আরও ন্যূনতম, কিন্তু এখনও বৈধ, টেমপ্লেট দিয়ে শুরু করা যাক:Welcome to Ruby Magic . এই টেমপ্লেটটিতে কোনো অভিব্যক্তি নেই এবং টোকেনের তালিকায় শুধুমাত্র একটি উপাদান রয়েছে। এটি দেখতে কেমন তা এখানে:

[[:CONTENT, "Welcome to Ruby Magic"]]

প্রথমত, আমরা আমাদের পার্সার ক্লাস সেট আপ করি। এটা এই মত দেখায়:

module Magicbars
  class Parser
    def self.parse(tokens)
      new(tokens).parse
    end
 
    attr_reader :tokens
 
    def initialize(tokens)
      @tokens = tokens
    end
 
    def parse
      # Parsing starts here
    end
  end
end

ক্লাস টোকেনগুলির একটি অ্যারে নেয় এবং এটি সঞ্চয় করে। এটিতে শুধুমাত্র parse নামে একটি সর্বজনীন পদ্ধতি রয়েছে যা টোকেনকে AST-তে রূপান্তর করে।

আমাদের ব্যাকরণের দিকে ফিরে তাকালে, সেরা নিয়ম হল template . এটি বোঝায় যে parse , পার্সিং প্রক্রিয়ার শুরুতে, একটি template ফেরত দেবে নোড।

নোড হল সহজ ক্লাস যার নিজস্ব কোন আচরণ নেই। তারা শুধু অন্যান্য নোড সংযোগ করে বা টোকেন থেকে কিছু মান সঞ্চয় করে। এখানে template কি আছে নোড এর মত দেখাচ্ছে:

module Magicbars
  module Nodes
    class Template
      attr_reader :statements
 
      def initialize(statements)
        @statements = statements
      end
    end
  end
end

আমাদের উদাহরণ কাজ করার জন্য, আমাদের একটি Content প্রয়োজন নোড এটি কেবল পাঠ্য সামগ্রী সংরক্ষণ করে ("Welcome to Ruby Magic"৷ ) টোকেন থেকে।

module Magicbars
  module Nodes
    class Content
      attr_reader :content
 
      def initialize(content)
        @content = content
      end
    end
  end
end

এর পরে, template-এর একটি উদাহরণ তৈরি করতে পার্স পদ্ধতি প্রয়োগ করা যাক এবং Content এর একটি উদাহরণ এবং তাদের সঠিকভাবে সংযুক্ত করুন।

def parse
  Magicbars::Nodes::Template.new(parse_content)
end
 
def parse_content
  return unless tokens[0][0] == :CONTENT
 
  Magicbars::Nodes::Content.new(tokens[0][1])
end

যখন আমরা পার্সার চালাই, তখন আমরা সঠিক ফলাফল পাই:

Magicbars::Parser.parse(tokens)
# => #<Magicbars::Nodes::Template:0x00007fe90e939410 @statements=#<Magicbars::Nodes::Content:0x00007fe90e939578 @content="Welcome to Ruby Magic">>

স্বীকার্য, এটি শুধুমাত্র আমাদের সাধারণ উদাহরণের জন্য কাজ করে যার শুধুমাত্র একটি বিষয়বস্তু নোড রয়েছে। আসুন একটি আরও জটিল উদাহরণে স্যুইচ করি যা আসলে একটি অভিব্যক্তি অন্তর্ভুক্ত করে:Welcome to {{name}} এ স্বাগতম .

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

এর জন্য, আমাদের একটি Expression প্রয়োজন নোড এবং একটি Identifier নোড Expression নোড শনাক্তকারীর পাশাপাশি যেকোনো আর্গুমেন্ট সংরক্ষণ করে (যা, ব্যাকরণ অনুসারে, শূন্য বা তার বেশি Identifier এর একটি অ্যারে। নোড)। অন্যান্য নোডের মতো, এখানে দেখার মতো অনেক কিছু নেই৷

module Magicbars
  module Nodes
    class Expression
      attr_reader :identifier, :arguments
 
      def initialize(identifier, arguments)
        @identifier = identifier
        @arguments = arguments
      end
    end
  end
end
 
module Magicbars
  module Nodes
    class Identifier
      attr_reader :value
 
      def initialize(value)
        @value = value.to_sym
      end
    end
  end
end

নতুন নোডের জায়গায়, আসুন parse পরিবর্তন করি নিয়মিত বিষয়বস্তু এবং অভিব্যক্তি উভয়ই পরিচালনা করার পদ্ধতি। আমরা একটি parse_statements প্রবর্তনের মাধ্যমে তা করি পদ্ধতি যা শুধু parse_statement কল করতে থাকে যতক্ষণ না এটি একটি মান প্রদান করে।

def parse
  Magicbars::Nodes::Template.new(parse_statements)
end
 
def parse_statements
  results = []
 
  while result = parse_statement
    results << result
  end
 
  results
end

parse_statement নিজে প্রথমে কল করে parse_content এবং যদি এটি কোনো মান ফেরত না দেয় তবে এটি parse_expression কল করে .

def parse_statement
  parse_content || parse_expression
end

আপনি কি লক্ষ্য করেছেন যে parse_statement পদ্ধতিটি statement-এর অনুরূপ দেখতে শুরু করছে ব্যাকরণে নিয়ম? এখানেই ব্যাকরণটি আগে থেকে স্পষ্টভাবে লিখতে সময় নেওয়া আমরা সঠিক পথে আছি তা নিশ্চিত করতে অনেক সাহায্য করে৷

এর পরে, আসুন parse_content পরিবর্তন করি পদ্ধতি যাতে এটি শুধুমাত্র প্রথম টোকেনের দিকে তাকায় না। আমরা একটি অতিরিক্ত @position প্রবর্তন করে এটি করি ইনস্ট্যান্স ভেরিয়েবল ইনিশিয়ালাইজার এবং বর্তমান টোকেন আনতে এটি ব্যবহার করুন।

attr_reader :tokens, :position
 
def initialize(tokens)
  @tokens = tokens
  @position = 0
end
 
# ...
 
def parse_content
  return unless token = tokens[position]
  return unless token[0] == :CONTENT
 
  @position += 1
 
  Magicbars::Nodes::Content.new(token[1])
end

parse_content পদ্ধতি এখন বর্তমান টোকেন দেখে এবং এর ধরন পরীক্ষা করে। যদি এটি একটি CONTENT হয় টোকেন, এটি অবস্থান বৃদ্ধি করে (কারণ বর্তমান টোকেন সফলভাবে পার্স করা হয়েছে) এবং Content তৈরি করতে টোকেনের সামগ্রী ব্যবহার করে নোড যদি কোন বর্তমান টোকেন না থাকে (কারণ আমরা টোকেনের শেষে) বা টাইপ মেলে না, পদ্ধতিটি তাড়াতাড়ি প্রস্থান করে এবং nil ফেরত দেয় .

উন্নত parse_content সহ পদ্ধতি চালু আছে, আসুন নতুন parse_expression মোকাবেলা করি পদ্ধতি।

def parse_expression
  return unless token = tokens[position]
  return unless token[0] == :OPEN_EXPRESSION
 
  @position += 1
 
  identifier = parse_identifier
  arguments = parse_arguments
 
  if !tokens[position] || tokens[position][0] != :CLOSE
    raise "Unexpected token #{tokens[position][0]}. Expected :CLOSE."
  end
 
  @position += 1
 
  Magicbars::Nodes::Expression.new(identifier, arguments)
end

প্রথমে, আমরা পরীক্ষা করে দেখছি যে একটি বর্তমান টোকেন আছে এবং এর ধরন হল OPEN_EXPRESSION . যদি তা হয়, আমরা পরবর্তী টোকেনে অগ্রসর হই এবং parse_identifier কল করে শনাক্তকারীর পাশাপাশি আর্গুমেন্টগুলিকে পার্স করি এবং parse_arguments , যথাক্রমে। উভয় পদ্ধতিই নিজ নিজ নোড ফিরিয়ে দেবে এবং বর্তমান টোকেনকে অগ্রসর করবে। এটি হয়ে গেলে, আমরা নিশ্চিত করি যে বর্তমান টোকেনটি বিদ্যমান এবং এটি একটি :CLOSE টোকেন. যদি এটি না হয়, আমরা একটি ত্রুটি উত্থাপন. অন্যথায়, নতুন তৈরি Expression ফেরত দেওয়ার আগে আমরা শেষবারের মতো অবস্থানে অগ্রসর হই নোড।

এই সময়ে, আমরা কিছু নিদর্শন উত্থান দেখতে. আমরা বেশ কয়েকবার পরবর্তী টোকেনে অগ্রসর হচ্ছি এবং আমরা এটিও পরীক্ষা করছি যে একটি বর্তমান টোকেন এবং এর ধরন রয়েছে। কারণ এটির জন্য কোডটি কিছুটা কষ্টকর, আসুন দুটি সহায়ক পদ্ধতি চালু করা যাক।

def expect(*expected_tokens)
  upcoming = tokens[position, expected_tokens.size]
 
  if upcoming.map(&:first) == expected_tokens
    advance(expected_tokens.size)
    upcoming
  end
end
 
def advance(offset = 1)
  @position += offset
end

expect পদ্ধতিটি পরিবর্তনশীল সংখ্যক টোকেন প্রকার নেয় এবং টোকেন স্ট্রীমের পরবর্তী টোকেনগুলির বিরুদ্ধে তাদের পরীক্ষা করে। যদি সেগুলি সব মিলে যায়, তাহলে এটি মিলে যাওয়া টোকেনগুলিকে অতিক্রম করে এবং সেগুলিকে ফেরত দেয়৷ advance পদ্ধতি শুধুমাত্র @position বৃদ্ধি করে প্রদত্ত অফসেট দ্বারা ইনস্ট্যান্স ভেরিয়েবল।

যে ক্ষেত্রে পরবর্তী প্রত্যাশিত টোকেন সম্পর্কে কোনো নমনীয়তা নেই, আমরা এমন একটি পদ্ধতিও চালু করি যা টোকেন না মিললে একটি সুন্দর ত্রুটির বার্তা উত্থাপন করে।

def need(*required_tokens)
  upcoming = tokens[position, required_tokens.size]
  expect(*required_tokens) or raise "Unexpected tokens. Expected #{required_tokens.inspect} but got #{upcoming.inspect}"
end

এই সহায়ক পদ্ধতিগুলি ব্যবহার করে, parse_content এবং parse_expression এখন পরিষ্কার এবং আরও পঠনযোগ্য৷

def parse_content
  if content = expect(:CONTENT)
    Magicbars::Nodes::Content.new(content[0][1])
  end
end
 
def parse_expression
  return unless expect(:OPEN_EXPRESSION)
 
  identifier = parse_identifier
  arguments = parse_arguments
 
  need(:CLOSE)
 
  Magicbars::Nodes::Expression.new(identifier, arguments)
end

অবশেষে, আসুন parse_identifierও দেখি এবং parse_arguments . সাহায্যকারী পদ্ধতির জন্য ধন্যবাদ, parse_identifier পদ্ধতিটি parse_content এর মতই সহজ পদ্ধতি শুধুমাত্র পার্থক্য হল এটি অন্য নোড টাইপ প্রদান করে।

def parse_identifier
  if identifier = expect(:IDENTIFIER)
    Magicbars::Nodes::Identifier.new(identifier[0][1])
  end
end

parse_arguments বাস্তবায়ন করার সময় পদ্ধতি, আমরা লক্ষ্য করেছি যে এটি প্রায় parse_statements-এর সাথে অভিন্ন পদ্ধতি শুধুমাত্র পার্থক্য হল এটি parse_identifier কল করে parse_statement এর পরিবর্তে . আমরা অন্য সহায়ক পদ্ধতি প্রবর্তন করে নকল যুক্তি থেকে পরিত্রাণ পেতে পারি।

def repeat(method)
  results = []
 
  while result = send(method)
    results << result
  end
 
  results
end

repeat পদ্ধতি send ব্যবহার করে প্রদত্ত পদ্ধতির নামটি কল করতে যতক্ষণ না এটি আর একটি নোড ফেরত না দেয়। একবার এটি ঘটলে, সংগৃহীত ফলাফল (বা শুধু একটি খালি অ্যারে) ফেরত দেওয়া হয়। এই সাহায্যকারীর সাথে, উভয়ই parse_statements এবং parse_arguments এক-লাইন পদ্ধতি হয়ে ওঠে।

def parse_statements
  repeat(:parse_statement)
end
 
def parse_arguments
  repeat(:parse_identifier)
end

এই সমস্ত পরিবর্তনের সাথে, আসুন টোকেন স্ট্রীমটি পার্স করার চেষ্টা করি:

Magicbars::Parser.parse(tokens)
# => #<Magicbars::Nodes::Template:0x00007f91a602f910
#     @statements=
#      [#<Magicbars::Nodes::Content:0x00007f91a58802c8 @content="Welcome to ">,
#       #<Magicbars::Nodes::Expression:0x00007f91a602fcd0
#        @arguments=[],
#        @identifier=
#         #<Magicbars::Nodes::Identifier:0x00007f91a5880138 @value=:name>  >

এটা পড়া একটু কঠিন কিন্তু এটা আসলে সঠিক বিমূর্ত সিনট্যাক্স ট্রি। template নোডের একটি Content আছে এবং একটি Expression বিবৃতি Content নোডের মান হল "Welcome to " এবং Expression নোডের শনাক্তকারী হল Identifier :name সহ নোড এর মান হিসাবে।

পার্সিং ব্লক এক্সপ্রেশন

আমাদের পার্সার বাস্তবায়ন সম্পূর্ণ করতে, আমাদের এখনও ব্লক এক্সপ্রেশনের পার্সিং বাস্তবায়ন করতে হবে। একটি অনুস্মারক হিসাবে, এখানে টেমপ্লেটটি আমরা পার্স করতে চাই:

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

এটি করার জন্য, প্রথমে একটি BlockExpression প্রবর্তন করা যাক নোড যদিও এই নোডটি একটু বেশি ডেটা সঞ্চয় করে, এটি অন্য কিছু করে না এবং তাই খুব উত্তেজনাপূর্ণ নয়৷

module Magicbars
  module Nodes
    class BlockExpression
      attr_reader :identifier, :arguments, :statements, :inverse_statements
 
      def initialize(identifier, arguments, statements, inverse_statements)
        @identifier = identifier
        @arguments = arguments
        @statements = statements
        @inverse_statements = inverse_statements
      end
    end
  end
end

Expression এর মত নোড, এটি শনাক্তকারীর পাশাপাশি যেকোনো আর্গুমেন্ট সংরক্ষণ করে। উপরন্তু, এটি ব্লক এবং ইনভার্স ব্লকের স্টেটমেন্টও সংরক্ষণ করে।

ব্যাকরণের দিকে ফিরে তাকালে, আমরা লক্ষ্য করি যে ব্লক এক্সপ্রেশন পার্স করতে, আমাদের parse_statements সংশোধন করতে হবে parse_block_expression এ কল সহ পদ্ধতি . এটি এখন ব্যাকরণের নিয়মের মতো দেখায়৷

def parse_statement
  parse_content || parse_expression || parse_block_expression
end

parse_block_expression পদ্ধতি নিজেই একটু জটিল। কিন্তু আমাদের সাহায্যকারী পদ্ধতির জন্য ধন্যবাদ, এটি এখনও বেশ পঠনযোগ্য।

def parse_block_expression
  return unless expect(:OPEN_BLOCK)
 
  identifier = parse_identifier
  arguments = parse_arguments
 
  need(:CLOSE)
 
  statements = parse_statements
 
  if expect(:OPEN_INVERSE, :CLOSE)
    inverse_statements = parse_statements
  end
 
  need(:OPEN_END_BLOCK)
 
  if identifier.value != parse_identifier.value
    raise("Error. Identifier in closing expression does not match identifier in opening expression")
  end
 
  need(:CLOSE)
 
  Magicbars::Nodes::BlockExpression.new(identifier, arguments, statements, inverse_statements)
end

প্রথম অংশটি parse_expression-এর সাথে খুব মিল পদ্ধতি এটি শনাক্তকারী এবং আর্গুমেন্টের সাথে খোলার ব্লক এক্সপ্রেশনকে পার্স করে। পরে, এটি parse_statements কল করে ব্লকের ভিতরে পার্স করতে।

এটি হয়ে গেলে, আমরা একটি {{else}} চেক করি অভিব্যক্তি, একটি OPEN_INVERSE দ্বারা চিহ্নিত একটি CLOSE অনুসরণ করে টোকেন টোকেন. যদি উভয় টোকেন পাওয়া যায়, আমরা parse_statements কল করি আবার বিপরীত ব্লক পার্স করতে. অন্যথায়, আমরা সেই অংশটি সম্পূর্ণভাবে এড়িয়ে যাই।

একটি চূড়ান্ত জিনিস হিসাবে, আমরা নিশ্চিত করি যে খোলা ব্লক এক্সপ্রেশনের মতো একই শনাক্তকারী ব্যবহার করে একটি শেষ ব্লক অভিব্যক্তি রয়েছে। যদি শনাক্তকারী মেলে না, আমরা একটি ত্রুটি উত্থাপন করি। অন্যথায়, আমরা একটি নতুন BlockExpression তৈরি করি নোড এবং এটি ফেরত দিন।

উন্নত ব্লক এক্সপ্রেশন টেমপ্লেটের টোকেন সহ পার্সারকে কল করলে টেমপ্লেটের জন্য AST ফিরে আসবে। আমি এখানে উদাহরণ আউটপুট অন্তর্ভুক্ত করব না, কারণ এটি সবেমাত্র পঠনযোগ্য। পরিবর্তে, এখানে তৈরি করা AST-এর একটি ভিজ্যুয়াল উপস্থাপনা রয়েছে।

কারণ আমরা parse_statements কল করছি parse_block_expression এর ভিতরে , ব্লক এবং ইনভার্স ব্লক উভয়ই আরও এক্সপ্রেশন, ব্লক এক্সপ্রেশন, সেইসাথে নিয়মিত বিষয়বস্তু অন্তর্ভুক্ত করতে পারে।

যাত্রা অব্যাহত থাকে...

আমরা আমাদের নিজস্ব টেমপ্লেটিং ভাষা বাস্তবায়নের দিকে আমাদের যাত্রার সাথে শালীন অগ্রগতি করেছি। ভাষা তত্ত্বে সংক্ষিপ্ত ডুব দেওয়ার পরে, আমরা আমাদের টেমপ্লেটিং ভাষার জন্য একটি ব্যাকরণকে সংজ্ঞায়িত করেছি এবং এটিকে স্ক্র্যাচ থেকে একটি পার্সার প্রয়োগ করতে ব্যবহার করেছি৷

লেক্সার এবং পার্সার উভয়ের জায়গায়, আমরা আমাদের টেমপ্লেট থেকে ইন্টারপোলেটেড স্ট্রিং তৈরি করার জন্য শুধুমাত্র একজন দোভাষীকে মিস করছি। আমরা রুবিম্যাজিকের একটি আসন্ন সংস্করণে এই অংশটি কভার করব। রুবি ম্যাজিক মেইলিংলিস্টে সাবস্ক্রাইব করুন, এটি বের হলে সতর্ক হতে।


  1. রুবি আলিয়াস কীওয়ার্ড কীভাবে ব্যবহার করবেন

  2. রুবিতে ডেকোরেটর ডিজাইন প্যাটার্ন

  3. রুবি ট্রান্সপোজ পদ্ধতিতে সারিগুলিকে কলামে পরিণত করুন

  4. রুবি দিয়ে কীভাবে পার্সার তৈরি করবেন