পাঠ ২৮ · ৫৮-এর মধ্যে · মডিউল ৬
Home / Courses / Concepts of Programming Languages & Compiler Design / সিম্বল টেবিল

সিম্বল টেবিল

Symbol tables
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • সিম্বল টেবিল কী এবং কম্পাইলারে এর ভূমিকা
  • insert() ও lookup() — এই দুটো কোর অপারেশনের সঠিক আচরণ
  • ডুপ্লিকেট ডিক্লারেশন ও আনডিক্লেয়ার্ড নাম — দুটো ধ্রুপদী সেমান্টিক এরর
  • নেস্টেড স্কোপে সিম্বল টেবিল কেন সাধারণত একটি স্ট্যাক হিসেবে বাস্তবায়িত হয় (L29-এর প্রিভিউ)

১ · সিম্বল টেবিল কী

সিম্বল টেবিলSymbol Tableএকটি ডেটা স্ট্রাকচার যা আইডেন্টিফায়ার নামকে তার টাইপ, স্কোপ, মেমরি অবস্থান ও অন্যান্য এট্রিবিউটের সাথে ম্যাপ করে রাখে — সিমান্টিক অ্যানালাইসিস জুড়ে ও তার পরেও কম্পাইলার এটি বার বার ব্যবহার করে। কল্পনা করুন M5-এর পার্সার একটি সম্পূর্ণ পার্স ট্রি তৈরি করে ফেলেছে — কিন্তু ট্রিতে যেখানেই x নামটি দেখা যায়, কম্পাইলারের জানা দরকার এটি কী টাইপের, কোথায় ডিক্লেয়ার করা হয়েছে। সিম্বল টেবিল হলো ঠিক এই তথ্যের ভাণ্ডার — এটি বাস্তবায়নের স্বাভাবিক পছন্দ Data Structures & Algorithms কোর্সের হ্যাশ টেবিল, কারণ গড়ে O(1) সময়ে insert ও lookup করা যায়।

২ · দুটো কোর অপারেশন

insert(name, attributes)
একটি নতুন নাম ডিক্লেয়ার করে। একই স্কোপে নামটি আগে থেকেই থাকলে এটি REJECT হয় — "variable already defined" ধরনের একটি ধ্রুপদী কম্পাইল এরর।
lookup(name)
আগে ডিক্লেয়ার করা একটি নামের এট্রিবিউট খুঁজে আনে। নামটি কখনো ডিক্লেয়ার না হলে (বা বর্তমানে দৃশ্যমান কোনো স্কোপে না থাকলে) এটি REJECT হয় — "undeclared variable" এরর।

লক্ষ্য করুন — lookup()-এর "বর্তমানে দৃশ্যমান কোনো স্কোপে না থাকলে" শর্তটি এই পাঠে প্রাসঙ্গিক নয় (কারণ এখানে একটিমাত্র ফ্ল্যাট, স্কোপ-বিহীন টেবিল ব্যবহার করা হচ্ছে) — ঠিক কী "দৃশ্যমান" তার পূর্ণ সংজ্ঞা L29-এর বিষয়, যেখানে একাধিক নেস্টেড স্কোপ একসাথে কাজ করে।

৩ · নেস্টেড স্কোপ — সংক্ষিপ্ত প্রিভিউ

বাস্তব প্রোগ্রামে একই নাম আলাদা আলাদা (নেস্টেড) স্কোপে বৈধভাবে পুনরায় ডিক্লেয়ার করা যায় — একটি ফাংশনের ভেতরে একটি লোকাল x, বাইরের গ্লোবাল x-কে সাময়িকভাবে ঢেকে (shadow) রাখতে পারে। এই কারণে বাস্তব সিম্বল টেবিল সাধারণত একটি একক ফ্ল্যাট টেবিল নয় — বরং প্রতিটি বর্তমানে-খোলা স্কোপের জন্য একটি করে ছোট টেবিলের স্ট্যাক বা চেইন হিসেবে বাস্তবায়িত হয়। এই পাঠ ইচ্ছাকৃতভাবে সরল রাখা হয়েছে — একটিমাত্র ফ্ল্যাট টেবিল, কোনো নেস্টিং ছাড়াই — যাতে insert/lookup-এর মূল আচরণ স্পষ্টভাবে বোঝা যায়। পরের পাঠ (L29) ঠিক এই ধারণাকে একটি স্ট্যাকে সম্প্রসারিত করবে।

insert("x", "int") "x" কি এই স্কোপে আগে থেকেই আছে? না → টেবিলে যোগ করা হলো পরে: lookup("x") → টেবিলে খোঁজা পাওয়া গেলে attrs ফেরত, না পেলে এরর
insert ডুপ্লিকেট আটকায়, lookup আনডিক্লেয়ার্ড নাম আটকায় — দুটোই সেমান্টিক এরর হিসেবে ধরা হয়, সিনট্যাক্স এরর নয় (L02-এর পার্থক্য মনে করুন)।

৪ · SymbolTable বাস্তবায়ন — সফল কেস ও দুটো এরর কেস

নিচের কোড সেলে একটি SymbolTable ক্লাস বাস্তবায়ন করা হয়েছে — একটি ফ্ল্যাট Python dict-এর উপর ভিত্তি করে। প্রথমে একটি সফল ডিক্লারেশন-ও-lookup ধারা দেখানো হচ্ছে, তারপর দুটো এরর কেস ইচ্ছাকৃতভাবে ট্রিগার করে দেখানো হচ্ছে যে এগুলো নীরবে সফল না হয়ে স্পষ্টভাবে ধরা পড়ছে।

Python
class SymbolTable:
    """একটি ফ্ল্যাট (স্কোপ-বিহীন) সিম্বল টেবিল -- নেস্টেড স্কোপ L29-এ আসছে।"""

    def __init__(self):
        self._table = {}

    def insert(self, name, type_):
        if name in self._table:
            raise ValueError(
                f"সেমান্টিক এরর: '{name}' ইতিমধ্যে ডিক্লেয়ার করা হয়েছে (duplicate declaration)"
            )
        self._table[name] = {"type": type_}

    def lookup(self, name):
        if name not in self._table:
            raise ValueError(
                f"সেমান্টিক এরর: '{name}' ডিক্লেয়ার করা হয়নি (undeclared variable)"
            )
        return self._table[name]


symtab = SymbolTable()

print("--- সফল ডিক্লারেশন ও lookup ---")
symtab.insert("x", "int")
symtab.insert("total", "string")
print(f"insert('x', 'int')      -> ok")
print(f"insert('total', 'string') -> ok")
print(f"lookup('x')     -> {symtab.lookup('x')}")
print(f"lookup('total') -> {symtab.lookup('total')}")

print("\n--- এরর কেস ১: ডুপ্লিকেট ডিক্লারেশন ---")
try:
    symtab.insert("x", "float")
    print("(এটা প্রিন্ট হওয়ার কথা না -- এরর ধরা পড়া উচিত ছিল)")
except ValueError as e:
    print(f"সঠিকভাবে REJECT হয়েছে -> {e}")

print("\n--- এরর কেস ২: আনডিক্লেয়ার্ড নাম lookup ---")
try:
    symtab.lookup("y")
    print("(এটা প্রিন্ট হওয়ার কথা না -- এরর ধরা পড়া উচিত ছিল)")
except ValueError as e:
    print(f"সঠিকভাবে REJECT হয়েছে -> {e}")

print(f"\nচূড়ান্ত টেবিলে শুধু বৈধ এন্ট্রি আছে: {list(symtab._table.keys())}")

    
লক্ষ্য করুন — দুটো এরর কেসই ব্যর্থ হয়ে ValueError রেইজ করেছে, এবং টেবিলের ভেতরের অবস্থা অপরিবর্তিত রয়ে গেছে (x-এর মান এখনও প্রথম insert-এর "int", "float" দিয়ে ওভাররাইট হয়নি)। এটিই একটি সঠিক সিম্বল টেবিলের মূল প্রতিশ্রুতি — একটি ডুপ্লিকেট insert কখনো নীরবে পুরনো ডেটা মুছে দেয় না, একটি আনডিক্লেয়ার্ড lookup কখনো নীরবে None বা ভুল কিছু ফেরত দেয় না।
মূল কথা · Key takeaway

সিম্বল টেবিল কম্পাইলারের "নামের বই" — প্রতিটি নাম কখন, কী টাইপে ডিক্লেয়ার হয়েছে তা মনে রাখে। এর দুটো কোর অপারেশন প্রতিরক্ষামূলক হতে হয় — insert ডুপ্লিকেট আটকায়, lookup আনডিক্লেয়ার্ড নাম আটকায় — কারণ এই দুটোই বাস্তব প্রোগ্রামের সাধারণ, ধরা-পড়া-উচিত ভুল।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ যদি insert() ডুপ্লিকেট নাম পেলে এরর না দিয়ে শুধু পুরনো এন্ট্রিটি নীরবে ওভাররাইট করে দিত, তাহলে কী বাস্তব সমস্যা হতে পারত?

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

প্র ০২ সিম্বল টেবিল বাস্তবায়নে হ্যাশ টেবিল (Python dict) কেন স্বাভাবিক পছন্দ — একটি সাধারণ লিস্ট (array) ব্যবহার করলে কী সমস্যা হতো?

একটি লিস্টে insert/lookup করতে হলে প্রতিবার পুরো লিস্ট স্ক্যান করতে হতো (নাম ইতিমধ্যে আছে কিনা যাচাই করতে, অথবা lookup-এ নাম খুঁজতে) — O(n) সময় লাগত, যেখানে n হলো ইতিমধ্যে ডিক্লেয়ার করা নামের সংখ্যা। একটি বড় প্রোগ্রামে হাজার হাজার নাম থাকলে এটি ধীর হয়ে যেত। হ্যাশ টেবিল (dict) গড়ে O(1) সময়ে উভয় অপারেশন করতে পারে, তাই এটি বাস্তব কম্পাইলারের স্বাভাবিক পছন্দ — direct কানেকশন DSA কোর্সের হ্যাশ টেবিল অধ্যায়ের সাথে।

প্র ০৩ উপরের কোড সেলে symtab.lookup("y") এরর দেয়, কিন্তু symtab.lookup("x") ঠিক আগে সফল হয়েছিল। এই দুটোর মধ্যে কোড-লেভেলে ঠিক কোন শর্তটি ভিন্ন ফলাফল দিচ্ছে?

lookup()-এর ভেতরে if name not in self._table শর্তটি চেক করে। "x" আগে insert("x", "int") দিয়ে টেবিলে যোগ করা হয়েছিল, তাই "x" in self._table সত্য — শর্তটি False হয়, এরর রেইজ হয় না, স্বাভাবিকভাবে এট্রিবিউট ফেরত আসে। "y" কখনো insert করা হয়নি, তাই "y" in self._table মিথ্যা — শর্তটি True হয়, ValueError রেইজ হয়। এই একটি শর্তই insert-এ "ডুপ্লিকেট" আর lookup-এ "আনডিক্লেয়ার্ড" — দুটো ভিন্ন এররের মূল ভিত্তি।

অনুশীলন

  1. চিন্তা করুন: SymbolTable-এ একটি update(name, type_) মেথড যোগ করতে চাইলে (একটি বিদ্যমান নামের টাইপ ইচ্ছাকৃতভাবে পরিবর্তন করার জন্য, insert-এর ভুলবশত ডুপ্লিকেট থেকে আলাদা), এটি কীভাবে insert/lookup-এর যুক্তি থেকে আলাদা হওয়া উচিত?

    update()-এর যুক্তি lookup()-এর উল্টো হওয়া উচিত — নামটি টেবিলে থাকতেই হবে (না থাকলে এরর, কারণ কিছু "আপডেট" করার আগে সেটি ডিক্লেয়ার করা থাকতে হবে), এবং তারপর নীরবে নয়, ইচ্ছাকৃতভাবে পুরনো এট্রিবিউট প্রতিস্থাপন করবে। এটি insert()-এর থেকে আলাদা কারণ insert() ইচ্ছাকৃতভাবে ডুপ্লিকেট আটকায় — একটি আলাদা update() থাকলে প্রোগ্রামারের "নতুন ডিক্লারেশন" ও "বিদ্যমান নামের ইচ্ছাকৃত পরিবর্তন" এই দুই ভিন্ন উদ্দেশ্য স্পষ্টভাবে আলাদা থাকে, একে অপরের সাথে গুলিয়ে যায় না।

  2. পরীক্ষা করুন: উপরের কোড সেলে আরেকটি লাইন যোগ করুন — symtab.insert("total", "int") (মনে রাখুন "total" আগেই "string" হিসেবে ডিক্লেয়ার করা হয়েছিল) — এবং দেখুন এটি সফল হয় নাকি এরর দেয়।

    এটি এরর দেবে — ValueError: সেমান্টিক এরর: 'total' ইতিমধ্যে ডিক্লেয়ার করা হয়েছে (duplicate declaration)। লক্ষণীয়, এখানে টাইপ ভিন্ন হলেও (আগেরটি "string", নতুনটি "int") এটি এরর ঠেকায় না — insert() শুধু নামটি ডুপ্লিকেট কিনা তা যাচাই করে, টাইপ ভিন্ন কিনা তা বিবেচনায় নেয় না। একই নামে দুইবার ডিক্লেয়ার করা সবসময় REJECT হবে, টাইপ যাই হোক না কেন।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

আগের পাঠ
সিনট্যাক্স এরর রিকভারি