সিনট্যাক্স-ডাইরেক্টেড ট্রান্সলেশন
এই পাঠে যা শিখবেন
- সিনট্যাক্স-ডাইরেক্টেড ট্রান্সলেশন (SDT) কী এবং এটি কীভাবে L22, L30-এর কাজকে একীভূত করে
- সিনথেসাইজড অ্যাট্রিবিউট — বটম-আপ তথ্য প্রবাহ
- ইনহেরিটেড অ্যাট্রিবিউট — টপ-ডাউন/সাইডওয়ে তথ্য প্রবাহ
- S-attributed বনাম L-attributed গ্রামার — সংক্ষিপ্ত পরিচিতি
- একটি কোড সেলে দুই ধরনের অ্যাট্রিবিউট ফ্লো একসাথে বাস্তবায়ন করা
১ · SDT — একটি একীভূতকারী ফ্রেমওয়ার্ক
সিনট্যাক্স-ডাইরেক্টেড ট্রান্সলেশনSyntax-Directed Translation (SDT)একটি সাধারণ ফ্রেমওয়ার্ক যেখানে গ্রামারের প্রতিটি প্রোডাকশনের সাথে একটি সিমান্টিক অ্যাকশন (যেকোনো গণনা — টাইপ চেকিং, কোড জেনারেশন, মান এভালুয়েশন) সংযুক্ত থাকে, যাতে পার্সিং বা ট্রি-ওয়াকিংয়ের একটি স্বাভাবিক উপজাত হিসেবে সিমান্টিক প্রসেসিং ঘটে। লক্ষ্য করুন — এটি কোনো নতুন কৌশল নয়, বরং একটি নাম যা আমরা ইতিমধ্যে যা করেছি তার সাধারণ প্যাটার্নটি চিহ্নিত করে —
- L22-এর ট্রি-এভালুয়েটর: প্রতিটি গ্রামার রুলের সাথে "এর মান গণনা করো" অ্যাকশন সংযুক্ত।
- L30-এর টাইপ চেকার: প্রতিটি গ্রামার রুলের সাথে "এর টাইপ গণনা/যাচাই করো" অ্যাকশন সংযুক্ত।
- (M12-এ আসছে) কোড জেনারেটর: প্রতিটি গ্রামার রুলের সাথে "এর জন্য ইন্সট্রাকশন তৈরি করো" অ্যাকশন সংযুক্ত।
এই সবগুলোই আসলে SDT-এর নির্দিষ্ট ইনস্ট্যান্স — একই ধারণার ভিন্ন ভিন্ন প্রয়োগ।
২ · সিনথেসাইজড অ্যাট্রিবিউট — তথ্য উপরের দিকে
সিনথেসাইজড অ্যাট্রিবিউটSynthesized Attributeএকটি নন-টার্মিনালের অ্যাট্রিবিউট যা তার সন্তানদের অ্যাট্রিবিউট থেকে গণনা করা হয় — তথ্য পার্স ট্রির উপরের দিকে/বটম-আপ প্রবাহিত হয়। এর সবচেয়ে পরিচিত উদাহরণ — একটি এক্সপ্রেশনের গণনাকৃত মান, যা তার সাব-এক্সপ্রেশনগুলোর মান থেকে সিনথেসাইজ (synthesize) হয় — এটি L22-এর এভালুয়েটর প্যাটার্নের সরাসরি পুনরাবৃত্তি।
৩ · ইনহেরিটেড অ্যাট্রিবিউট — তথ্য নিচের দিকে
ইনহেরিটেড অ্যাট্রিবিউটInherited
Attributeএকটি নন-টার্মিনালের অ্যাট্রিবিউট যা তার অভিভাবক বা সিবলিং-এর অ্যাট্রিবিউট থেকে গণনা করা হয়
— তথ্য পার্স ট্রির নিচের দিকে/সাইডওয়ে প্রবাহিত হয়। এর একটি বাস্তব উদাহরণ — একটি
ডিক্লেয়ার করা ভ্যারিয়েবলের টাইপ, যা ডিক্লারেশন স্টেটমেন্ট থেকে "নিচে" প্রবাহিত হয়ে সেই স্টেটমেন্টের ভেতরের
প্রতিটি ভ্যারিয়েবল-ব্যবহারে সংযুক্ত হতে হয় — যেমন int a, b, c;-তে int টাইপটি
একবারই লেখা হয়েছে, কিন্তু এটি a, b, c — তিনটি নামেই "নিচে" প্রবাহিত
হয়ে সংযুক্ত হতে হবে।
৪ · S-attributed বনাম L-attributed গ্রামার
একটি গ্রামার যা শুধু সিনথেসাইজড অ্যাট্রিবিউট ব্যবহার করে তাকে S-attributed বলা হয় — এটি সম্পূর্ণভাবে বটম-আপ এভালুয়েট করা যায় (যেমন L24-এর বটম-আপ পার্সিংয়ে, প্রতিটি reduce-এ একটি অ্যাকশন সংযুক্ত করে)। যে গ্রামার ইনহেরিটেড অ্যাট্রিবিউটও ব্যবহার করে তাকে L-attributed বলা হয় — এর জন্য একটি নির্দিষ্ট বাম-থেকে-ডানে (left-to-right) এভালুয়েশন অর্ডার প্রয়োজন হয় (পূর্ণ ডেরিভেশন এখানে আলোচনা করা হচ্ছে না, শুধু নামটি পরিচিত করানো হচ্ছে)।
এই পাঠ M6-এর সমাপ্তি — এটি দেখায় L22-এর এভালুয়েটর ও L30-এর টাইপ চেকার আসলে একই অন্তর্নিহিত ধারণার (গ্রামার প্রোডাকশনে অ্যাকশন সংযুক্ত করা) দুটো ভিন্ন প্রয়োগ। M12-এর কোড জেনারেটরও ঠিক এই একই ফ্রেমওয়ার্কের আরেকটি ইনস্ট্যান্স হবে — শুধু অ্যাকশনটি হবে "ইন্সট্রাকশন তৈরি করো"।
৫ · কোড সেল — দুই ধরনের অ্যাট্রিবিউট ফ্লো একসাথে
নিচের কোড সেলে দুটো আলাদা ফাংশন — একটি সিনথেসাইজড অ্যাট্রিবিউট গণনা করছে (একটি এক্সপ্রেশন ট্রির মান, বটম-আপ), আরেকটি ইনহেরিটেড অ্যাট্রিবিউট প্রচার করছে (একটি ডিক্লারেশনের টাইপ, টপ-ডাউন) — দুটোই আলাদা দিক থেকে তথ্য প্রবাহিত করছে, কিন্তু উভয়ই SDT-এর অংশ।
class SymbolTable:
"""L28-এর সিম্বল টেবিল -- ইনহেরিটেড টাইপ অ্যানোটেশন এখানে সংরক্ষণ করা হবে।"""
def __init__(self):
self._table = {}
def insert(self, name, type_):
self._table[name] = {"type": type_}
def lookup(self, name):
return self._table[name]
# --- সিনথেসাইজড অ্যাট্রিবিউট: বটম-আপ, L22/L30-এর প্যাটার্নের পুনরাবৃত্তি ---
def compute_value(node):
"""একটি এক্সপ্রেশন নোডের মান তার সন্তানদের মান থেকে সিনথেসাইজ করে (উপরের দিকে প্রবাহ)।"""
if node[0] == "num":
return node[1]
_, op, left, right = node
left_value = compute_value(left) # সন্তানের অ্যাট্রিবিউট আগে গণনা
right_value = compute_value(right)
return {"+": left_value + right_value,
"-": left_value - right_value,
"*": left_value * right_value,
"/": left_value / right_value}[op]
# --- ইনহেরিটেড অ্যাট্রিবিউট: টপ-ডাউন, ডিক্লারেশন থেকে প্রতিটি নামে প্রবাহিত ---
def propagate_type_context(decl_node, symtab):
"""একটি ডিক্লারেশনের টাইপ তার প্রতিটি ভ্যারিয়েবলে "নিচে" প্রচার করে (নিচের দিকে প্রবাহ)।"""
_, declared_type, names = decl_node
inherited_annotations = {}
for name in names:
inherited_annotations[name] = declared_type # অভিভাবক থেকে সন্তানে টাইপ প্রবাহিত
symtab.insert(name, declared_type)
return inherited_annotations
# --- সিনথেসাইজড দিক: (2 + 3) * 4 -- একটি এক্সপ্রেশনের মান বটম-আপ গণনা ---
expr_tree = ("binop", "*", ("binop", "+", ("num", 2), ("num", 3)), ("num", 4))
synthesized_value = compute_value(expr_tree)
print("--- সিনথেসাইজড অ্যাট্রিবিউট (বটম-আপ) ---")
print(f"এক্সপ্রেশন: (2 + 3) * 4")
print(f"সিনথেসাইজড মান: {synthesized_value}")
# --- ইনহেরিটেড দিক: int a, b, c; -- একটি টাইপ টপ-ডাউন প্রচার ---
decl_tree = ("decl", "int", ["a", "b", "c"])
symtab = SymbolTable()
annotations = propagate_type_context(decl_tree, symtab)
print("\n--- ইনহেরিটেড অ্যাট্রিবিউট (টপ-ডাউন) ---")
print(f"ডিক্লারেশন: int a, b, c;")
for name, inherited_type in annotations.items():
confirmed = symtab.lookup(name)["type"]
print(f" {name} <- ইনহেরিটেড টাইপ: {inherited_type} (সিম্বল টেবিলে নিশ্চিত: {confirmed})")
print("\n--- দুই ফ্লো একসাথে ---")
print(f"সিনথেসাইজড ফলাফল ('উপরে' গণনা): expr মান = {synthesized_value}")
print(f"ইনহেরিটেড ফলাফল ('নিচে' প্রচার): {len(annotations)}টি নাম int টাইপ পেয়েছে")
compute_value() সন্তান থেকে অভিভাবকের দিকে তথ্য নিয়ে যায় (রিকার্সিভ কল আগে হয়,
ফলাফল পরে কম্বাইন হয়) — এটিই সিনথেসাইজড অ্যাট্রিবিউটের সংজ্ঞা। বিপরীতে, propagate_type_context()
একটি একক মান (declared_type) নিয়ে সেটি একাধিক সন্তানে (প্রতিটি নাম) বিতরণ করে — অভিভাবক থেকে
সন্তানের দিকে তথ্য যাচ্ছে, ঠিক ইনহেরিটেড অ্যাট্রিবিউটের সংজ্ঞা অনুযায়ী।
সিনট্যাক্স-ডাইরেক্টেড ট্রান্সলেশন এই কোর্সের M5-M6-এ যা কিছু শেখানো হয়েছে (এভালুয়েশন, টাইপ চেকিং) তার একটি একক ছাতার নিচে একীভূতকরণ — গ্রামার প্রোডাকশনে অ্যাকশন সংযুক্ত করা, যেখানে তথ্য উপরে (সিনথেসাইজড) অথবা নিচে (ইনহেরিটেড) প্রবাহিত হতে পারে। M6 এখানেই শেষ — এখন থেকে ধরে নেওয়া হবে প্রোগ্রাম সিনট্যাক্টিকালি ও সিমান্টিক্যালি বৈধ, এবং M7 থেকে টাইপ সিস্টেমের গভীর ভাষা-ডিজাইন প্রশ্নে প্রবেশ করা হবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ L22-এর এভালুয়েটর ও L30-এর টাইপ চেকার — দুটোই বটম-আপ ট্রি-ওয়াকিং প্যাটার্ন অনুসরণ করে। SDT-এর ভাষায় এই দুটোর মধ্যে ঠিক কী মিল আছে?
দুটোই সিনথেসাইজড অ্যাট্রিবিউটের উদাহরণ — উভয়ই সন্তানদের ফলাফল (একটির ক্ষেত্রে মান, অন্যটির ক্ষেত্রে টাইপ) প্রথমে রিকার্সিভলি গণনা করে, তারপর সেই ফলাফলগুলো কম্বাইন করে অভিভাবক নোডের নিজস্ব ফলাফল গণনা করে। SDT-এর দৃষ্টিকোণ থেকে এই দুটো ভিন্ন কাজ (এভালুয়েশন, টাইপ চেকিং) আসলে একই কাঠামোগত প্যাটার্নের দুটো ভিন্ন "অ্যাকশন" — শুধু গ্রামার প্রোডাকশনে সংযুক্ত অ্যাকশনটাই আলাদা।
প্র ০২
উপরের কোড সেলের propagate_type_context()-এ যদি names লিস্টটি খালি হতো, তাহলে ফাংশনটি কী ফেরত দিত এবং সিম্বল টেবিলের কী অবস্থা হতো?
for name in names লুপটি একবারও চলত না, তাই inherited_annotations একটি খালি dict
থেকে যেত এবং symtab-এ কোনো নতুন নাম যোগ হতো না — ফাংশনটি খালি {} ফেরত দিত।
এটি ইনহেরিটেড অ্যাট্রিবিউট প্রচারের একটি স্বাভাবিক সীমা-কেস (edge case): প্রচার করার মতো কোনো সন্তান না
থাকলে, কিছুই প্রচারিত হয় না — কোনো এরর হয় না, শুধু কাজ করার কিছু থাকে না।
প্র ০৩ একটি গ্রামার যা শুধু সিনথেসাইজড অ্যাট্রিবিউট ব্যবহার করে (S-attributed) কেন L24-এর বটম-আপ পার্সিংয়ে সরাসরি এভালুয়েট করা সহজ, কিন্তু ইনহেরিটেড অ্যাট্রিবিউট থাকলে (L-attributed) তা সহজ নয়?
বটম-আপ পার্সিং (L24) একটি reduce ঘটার সাথে সাথেই সেই মুহূর্তে একটি নোডের সব সন্তান স্ট্যাকে ইতিমধ্যে সম্পূর্ণভাবে প্রসেস হয়ে গেছে — তাই একটি সিনথেসাইজড অ্যাকশন (যা শুধু সন্তানদের ফলাফলের উপর নির্ভর করে) সেই reduce-মুহূর্তেই সরাসরি এভালুয়েট করা যায়, বাড়তি কোনো সমন্বয় ছাড়াই। কিন্তু একটি ইনহেরিটেড অ্যাট্রিবিউটের জন্য তথ্য অভিভাবক/সিবলিং থেকে আসতে হয় — বটম-আপ পার্সিং-এ reduce-মুহূর্তে অভিভাবক এখনো "তৈরি" হয়নি, তাই তথ্যটি এখনও পাওয়া যায় না। এই কারণেই ইনহেরিটেড অ্যাট্রিবিউট বাস্তবায়নে একটি নির্দিষ্ট, সতর্কভাবে বেছে নেওয়া এভালুয়েশন অর্ডার (L-attributed-এর সংজ্ঞা অনুযায়ী) প্রয়োজন হয়।
অনুশীলন
-
চিন্তা করুন: L07-এর
Shape/Rectangle/Circle-এরarea()মেথড — এটি কি সিনথেসাইজড অ্যাট্রিবিউটের একটি উদাহরণ, নাকি ইনহেরিটেড? কারণ ব্যাখ্যা করুন।সিনথেসাইজড — একটি shape-এর
area()সেই shape-এর নিজস্ব ডেটা (যেমনRectangle-এর width/height, বাCircle-এর radius) থেকে সরাসরি গণনা করা হয়, কোনো অভিভাবক বা বাইরের প্রসঙ্গ থেকে তথ্য "নিচে" নামিয়ে আনার প্রয়োজন হয় না। এটি একটি "লিফ-লেভেল" গণনা যার ফলাফল সরাসরি সেই নোডের নিজের ডেটা থেকেই তৈরি হয় — সিনথেসাইজড অ্যাট্রিবিউটের একটি সরল রূপ, যদিও এখানে সন্তান-থেকে-অভিভাবক প্রবাহ ছাড়াই সরাসরি একটি নোডেই ঘটছে। -
পরীক্ষা করুন: উপরের কোড সেলে
decl_tree-এর মান("decl", "float", ["p", "q"])করে Run চেপে দেখুন ইনহেরিটেড অ্যাট্রিবিউট আউটপুট কীভাবে বদলায়।আউটপুট হবে:
p <- ইনহেরিটেড টাইপ: float (সিম্বল টেবিলে নিশ্চিত: float)এবংq <- ইনহেরিটেড টাইপ: float (সিম্বল টেবিলে নিশ্চিত: float)— দুটো নাম, প্রতিটিতে "float" টাইপ প্রচারিত হয়েছে। শেষ লাইনে "২টি নাম int টাইপ পেয়েছে"-এর বদলে "২টি নাম float টাইপ পেয়েছে" দেখাবে, কারণ এই সারাংশ লাইনটিlen(annotations)ব্যবহার করে, যা এখন ২ (আগে ছিল ৩, "a, b, c" থেকে কমে "p, q" হওয়ায়)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ M6 এখানে শেষ হলো — পরের মডিউল (M7) থেকে টাইপ সিস্টেমের গভীর ভাষা-ডিজাইন প্রশ্ন শুরু হবে, প্রথম পাঠ: টাইপ সিস্টেম — স্ট্যাটিক বনাম ডায়নামিক টাইপিং।
- টাইপ চেকিং বেসিকস (L30) M6 · আগের পাঠ এই পাঠের সিনথেসাইজড অ্যাট্রিবিউট প্যাটার্ন যে বটম-আপ টাইপ চেকারকে সরাসরি সাধারণীকরণ করেছে, তার মূল বাস্তবায়ন।
- টাইপ সিস্টেম — স্ট্যাটিক বনাম ডায়নামিক টাইপিং (L32) পরের পাঠ M7-এর শুরু — এই পাঠের টাইপ-চেকিং ধারণাগুলো এখন একটি সম্পূর্ণ ভাষা-ডিজাইন প্রশ্নে সম্প্রসারিত হবে।