কম্পিউটার টিউটোরিয়াল

রুবি ইন্টারপ্রিটার ভেতরে: কীভাবে এটি পার্স, কম্পাইল এবং এক্সিকিউট করে আপনার কোড

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

এই পোস্টে আমরা একটি সাধারণ প্রোগ্রামের যাত্রা ফলো করব যেটি লেক্সড, পার্সড এবং বাইটকোডে কম্পাইল হয়। আমরা রুবি যে টুলগুলো দিয়েছে সেগুলো ব্যবহার করে ইন্টারপ্রিটারের প্রতিটি ধাপে জাসوسی করব।

চিন্তা করবেন না—আপনি যদি এক্সপার্ট না হন, এই পোস্ট ফলো করা সহজ হবে। এটি একটি টেকনিক্যাল ম্যানুয়ালের চেয়ে একটি গাইডেড টুরের মতো।

আমাদের স্যাম্পল প্রোগ্রাম পরিচিত

উদাহরণ হিসেবে, আমি একটি সিঙ্গল if/else স্টেটমেন্ট ব্যবহার করব। জায়গা বাঁচাতে, আমি টারনারি অপারেটর ব্যবহার করে লিখব। কিন্তু ফাঁস না খান, এটি শুধু একটি if/else।

x > 100 ? 'foo' : 'bar'

আপনি দেখবেন, এমন একটি সাধারণ প্রোগ্রামও প্রসেস হওয়ার সময় কত বেশি ডেটায় ট্রান্সলেট হয়।

দ্রষ্টব্য: এই পোস্টের সব উদাহরণ রুবি (MRI) ২.২-এ লেখা। আপনি যদি রুবির অন্যান্য ইমপ্লিমেন্টেশন ব্যবহার করেন, সেগুলো সম্ভবত কাজ করবে না।

টোকেনাইজিং

রুবি ইন্টারপ্রিটার আপনার প্রোগ্রাম রান করার আগে এটিকে একটি কিছুটা ফ্রি-ফর্ম প্রোগ্রামিং ল্যাঙ্গুয়েজ থেকে আরও স্ট্রাকচার্ড ডেটায় কনভার্ট করতে হয়।

প্রথম ধাপ হতে পারে প্রোগ্রামকে চাঙ্কে ভাঙা। এই চাঙ্কগুলোকে টোকেন বলে।

# এটি একটি স্ট্রিং
"x > 1"

# এগুলো টোকেন
["x", ">", "1"]

রুবি স্ট্যান্ডার্ড লাইব্রেরি একটি মডিউল দিয়েছে Ripper নামে যা আমাদের রুবি কোড প্রসেস করতে দেয় ইন্টারপ্রিটারের মতোই।

নিচের উদাহরণে আমরা আমাদের রুবি কোডের ওপর tokenize মেথড ব্যবহার করছি। আপনি দেখতে পাচ্ছেন, এটি টোকেনের একটি অ্যারে রিটার্ন করে।

require 'ripper'
Ripper.tokenize("x > 1 ? 'foo' : 'bar'")
# => ["x", " ", ">", " ", "1", " ", "?", " ", "'", "foo", "'", " ", ":", " ", "'", "bar", "'"]

টোকেনাইজার খুব বোকা। আপনি এটিকে সম্পূর্ণ ইনভ্যালিড রুবি দিলেও এটি সেটি টোকেনাইজ করবে।

# খারাপ কোড
Ripper.tokenize("1var @= \/foobar`")
# => ["1", "var"]

লেক্সিং

লেক্সিং টোকেনাইজেশনের এক ধাপ পুড়তে। স্ট্রিং এখনও টোকেনে ভাঙা হয়, কিন্তু টোকেনগুলোর সাথে অতিরিক্ত ডেটা যোগ হয়।

নিচের উদাহরণে আমরা আমাদের ছোট প্রোগ্রামটি Ripper দিয়ে লেক্স করছি। আপনি দেখতে পাচ্ছেন, এখন প্রতিটি টোকেন ট্যাগ করা হয়েছে আইডেন্টিফায়ার :on_ident, অপারেটর :on_op, ইন্টিজার :on_int, ইত্যাদি হিসেবে।

require 'ripper'
require 'pp'
pp Ripper.lex("x > 100 ? 'foo' : 'bar'")
# [[[1, 0], :on_ident, "x"],
#  [[1, 1], :on_sp, " "],
#  [[1, 2], :on_op, ">"],
#  [[1, 3], :on_sp, " "],
#  [[1, 4], :on_int, "100"],
#  [[1, 5], :on_sp, " "],
#  [[1, 6], :on_op, "?"],
#  [[1, 7], :on_sp, " "],
#  [[1, 8], :on_tstring_beg, "'"],
#  [[1, 9], :on_tstring_content, "foo"],
#  [[1, 12], :on_tstring_end, "'"],
#  [[1, 13], :on_sp, " "],
#  [[1, 14], :on_op, ":"],
#  [[1, 15], :on_sp, " "],
#  [[1, 16], :on_tstring_beg, "'"],
#  [[1, 17], :on_tstring_content, "bar"],
#  [[1, 20], :on_tstring_end, "'"]]

এখনও কোনো রিয়েল সিনট্যাক্স চেকিং হচ্ছে না এই পয়েন্টে। লেক্সারও খুশি হয়ে ইনভ্যালিড কোড প্রসেস করবে।

পার্সিং

এখন রুবি কোডটিকে আরও ম্যানেজেবল চাঙ্কে ভাগ করে ফেলেছে, পার্সিং শুরু করার সময়।

পার্সিং স্টেজে, রুবি টেক্সটকে একটি অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি (AST) তে ট্রান্সফর্ম করে। অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি আপনার প্রোগ্রামের একটি মেমরি রিপ্রেজেন্টেশন।

আপনি বলতে পারেন প্রোগ্রামিং ল্যাঙ্গুয়েজ عامة অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি বর্ণনা করার ইউজার-ফ্রেন্ডলি উপায়।

require 'ripper'
require 'pp'
pp Ripper.sexp("x > 100 ? 'foo' : 'bar'")
# [:program,
#  [[:ifop,
#    [:binary, [:vcall, [:@ident, "x", [1, 0]]], :>, [:@int, "100", [1, 4]]],
#    [:string_literal, [:string_content, [:@tstring_content, "foo", [1, 11]]]],
#    [:string_literal, [:string_content, [:@tstring_content, "foobar", [1, 19]]]]]]]

এই আউটপুট পড়া সহজ নয়, কিন্তু আপনি যদি דיক্ষণে তাকিয়ে থাকেন তবে কীভাবে এটি মূল প্রোগ্রামে ম্যাপ হচ্ছে তা কিছুটা বুঝতে পারবেন।

# একটি প্রোগ্রাম ডেফাইন করুন
[:program,
 # একটি "if" অপারেশন করুন
 [[:ifop,
   # কন্ডিশনাল চেক করুন (x > 100)
   [:binary, [:vcall, [:@ident, "x", [1, 0]]], :>, [:@int, "100", [1, 4]]],
   # যদি true হয়, "foo" রিটার্ন করুন
   [:string_literal, [:string_content, [:@tstring_content, "foo", [1, 11]]]],
   # যদি false হয়, "bar" রিটার্ন করুন
   [:string_literal, [:string_content, [:@tstring_content, "foobar", [1, 19]]]]]]]

এই পয়েন্টে, রুবি ইন্টারপ্রিটার ঠিক জানে আপনি কী চাচ্ছেন সেটি করাতে। এটি এখন আপনার প্রোগ্রাম রান করতে পারে। এবং রুবি ১.৯-এর আগে, এটি করতও। কিন্তু এখন, আরেকটি ধাপ বাকি।

বাইটকোডে কম্পাইলিং

অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি সরাসরি ট্রাভার্স করার বদলে, আজকাল রুবি অ্যাবস্ট্রাক্ট সিনট্যাক্স ট্রি কে নিম্ন-স্তরের বাইট কোডে কম্পাইল করে।

এই বাইট কোডটি রুবি ভার্চুয়াল মেশিন দ্বারা রান করা হয়।

আমরা RubyVM::InstructionSequence ক্লাস দিয়ে ভার্চুয়াল মেশিনের ভেতর ঝাকিতে পারি। নিচের উদাহরণে, আমরা আমাদের স্যাম্পল প্রোগ্রাম কম্পাইল করি এবং তারপর ডিসঅ্যাসেম্বল করি মানব-পাঠযোগ্য করতে।

puts RubyVM::InstructionSequence.compile("x > 100 ? 'foo' : 'bar'").disassemble
# == disasm: <RubyVM::InstructionSequence:<compiled>@<compiled>>==========
# 0000 trace 1 ( 1)
# 0002 putself
# 0003 opt_send_without_block <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# 0005 putobject 100
# 0007 opt_gt <callinfo!mid:>, argc:1, ARGS_SIMPLE>
# 0009 branchunless 15
# 0011 putstring "foo"
# 0013 leave
# 0014 pop
# 0015 putstring "bar"
# 0017 leave

ওহ! এটি হঠাৎ অ্যাসেম্বলি ল্যাঙ্গুয়েজের মতো দেখাচ্ছে রুবির চেয়ে। আসুন ধাপে ধাপে এটি বুঝতে চেষ্টা করি।

# `x` মেথড কল করুন self-এ এবং রেজাল্ট স্ট্যাকে সেভ করুন
0002 putself
0003 opt_send_without_block <callinfo!mid:x, argc:0, FCALL|VCALL|ARGS_SIMPLE>
# নম্বর ১০০ স্টাকে রাখুন
0005 putobject 100
# তুলনা করুন (x > 100)
0007 opt_gt <callinfo!mid:>, argc:1, ARGS_SIMPLE>
# যদি তুলনা false হয়, লাইন ১৫-এ যান
0009 branchunless 15
# যদি তুলনা true হয়, "foo" রিটার্ন করুন
0011 putstring "foo"
0013 leave
0014 pop
# এখানেই লাইন ১৫। এখানে জাম্প করল যদি তুলনা false হয়। "bar" রিটার্ন করুন
0015 putstring "bar"
0017 leave

রুবি ভার্চুয়াল মেশিন (YARV) এরপর এই ইনস্ট্রাকশনগুলো ধাপে ধাপে এক্সিকিউট করে। बस!

উপসংহার

এটি শেষ আমাদের খুব সহজ, কার্টুনের মতো রুবি ইন্টারপ্রিটার টুর। আমি যে টুলগুলো দেখিয়েছি সেগুলো ব্যবহার করে আপনি রুবি কীভাবে আপনার প্রোগ্রাম ইন্টারপ্রেট করছে তা নিয়ে অনেক অনুমান দূর করতে পারেন। মানে, AST- থেকে কনক্রিট কিছু নেই। এবং আগামীবার আপনি কোনো विचित्र পারফরম্যান্স ইস্যুতে ফেসলে থাকলে, বাইটকোডে এক নজর দিন। সম্ভবত আপনার সমস্যা সমাধান হবে না, কিন্তু আপনার মস্তিষ্ক একটু শান্ত হয়ে যেতে পারে। :)

  1. Ruby-এ TracePoint দিয়ে মেথড ট্রেস করা: একটি ব্যবহারিক গাইড

  2. রুবি ডিবাগিং মাস্টার করুন: কমন এররর দ্রুত সমাধান

  3. কিলগার সনাক্ত ও অপসারণ: ধাপে ধাপে গাইড

  4. আপনার পিসি থেকে অনিচ্ছাকৃত প্রোগ্রাম সহজে মুছে ফেলুন: ধাপে ধাপে গাইড