আমরা আশা করি আপনি আপনার কফির উপরে আপনার স্ট্রোপওয়াফেলগুলিকে উষ্ণ করেছেন কারণ আজ আমরা স্টিকি স্ট্রোপ (যে সিরাপটি একটি স্ট্রোপওয়াফেলের দুটি অংশকে একসাথে আটকে দেয়) দিয়ে জিনিসগুলিকে আঠালো করে দিচ্ছি৷ আমাদের সিরিজের প্রথম দুটি অংশে, আমরা একটি লেক্সার এবং একটি পার্সার বেক করেছি এবং এখন, আমরা দোভাষী যোগ করছি এবং সেগুলির উপর স্ট্রুপ ঢেলে জিনিসগুলিকে একত্রিত করছি৷
উপকরণ
ঠিক আছে! আসুন রান্নাঘর বেক করার জন্য প্রস্তুত করি এবং আমাদের উপাদানগুলি টেবিলে রাখি। আমাদের দোভাষীর কাজ করার জন্য দুটি উপাদান বা তথ্যের টুকরো প্রয়োজন:পূর্বে তৈরি করা অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি (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
শুধুমাত্র সারফেস স্ক্র্যাচ করা
এই তিন-অংশের সিরিজে, আমরা একটি টেমপ্লেটিং ভাষা তৈরির মূল বিষয়গুলি কভার করেছি। এই ধারণাগুলি ব্যাখ্যা করা প্রোগ্রামিং ভাষা তৈরি করতেও ব্যবহার করা যেতে পারে (যেমন রুবি)। অবশ্যই, আমরা কয়েকটি জিনিস (যেমন সঠিক ত্রুটি হ্যান্ডলিং 🙀) নিয়ে আলোচনা করেছি এবং শুধুমাত্র আজকের প্রোগ্রামিং ভাষাগুলির আন্ডারপিনিংগুলির উপরিভাগ স্ক্র্যাচ করেছি৷
আমরা আশা করি আপনি সিরিজটি উপভোগ করেছেন এবং আপনি যদি এর আরও কিছু চান তবে রুবি ম্যাজিক তালিকায় সদস্যতা নিন। আপনি যদি এখন স্ট্রোপওয়াফেলের জন্য ক্ষুধার্ত হন, আমাদের একটি লাইন দিন এবং আমরা সেগুলি দিয়েও আপনাকে জ্বালানি দিতে সক্ষম হতে পারি!