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