পাঠ ১৫ · ৫৮-এর মধ্যে · মডিউল ৩
Home / Courses / Concepts of Programming Languages & Compiler Design / চমস্কি হায়ারার্কি

চমস্কি হায়ারার্কি

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

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

  • চমস্কি হায়ারার্কির চারটি টাইপ এবং প্রতিটির restriction ও রিকগনাইজিং অটোমাটা
  • কেন এই হায়ারার্কি প্রোগ্রামিং ল্যাঙ্গুয়েজ ডিজাইনের জন্য সরাসরি গুরুত্বপূর্ণ (M4 ও M5-এর ভিত্তি)
  • টাইপগুলোর মধ্যে strict containment সম্পর্ক (প্রতিটি regular ভাষা context-free-ও বটে, ইত্যাদি)
  • Python দিয়ে একটি regular-ভাষা checker ও একটি context-free-but-not-regular checker পাশাপাশি বাস্তবায়ন

১ · চারটি টাইপ

চমস্কি হায়ারার্কিChomsky Hierarchyফরমাল গ্রামার/ভাষাকে generative ক্ষমতা অনুযায়ী চারটি নেস্টেড টাইপে (Type 0-3) শ্রেণিবদ্ধ করার একটি ক্লাসিফিকেশন। ফরমাল গ্রামারকে (এবং তারা যে ভাষা বর্ণনা করে) চারটি টাইপে ভাগ করে, প্রতিটি রুলের উপর restriction বাড়ানো/কমানোর ভিত্তিতে — L11-এর গ্রামার ধারণাটি এখানে একটি সুশৃঙ্খল classification পায়। এই টপিকের সম্পূর্ণ গাণিতিক গভীরতা (প্রমাণ, pumping lemma) Discrete Mathematics কোর্সে কভার করা হয়েছে — এখানে আমরা শুধু PL-প্রাসঙ্গিকতার দিকটিতে ফোকাস করব।

Type 3 — Regular
সবচেয়ে সীমাবদ্ধ রুল ফর্ম — ফাইনাইট অটোমাটা দিয়ে রিকগনাইজড। M4-এর লেক্সিক্যাল অ্যানালাইসিস ঠিক এই টাইপের উপর নির্মিত।
Type 2 — Context-Free
L13-এর CFG — প্রতিটি রুলের বাম পাশে একটি নন-টার্মিনাল। পুশডাউন অটোমাটা দিয়ে রিকগনাইজড। M5-এর পার্সিং ঠিক এই টাইপের উপর নির্মিত।
Type 1 — Context-Sensitive
রুল প্রয়োগ প্রসঙ্গ-নির্ভর হতে পারে। লিনিয়ার-বাউন্ডেড অটোমাটা দিয়ে রিকগনাইজড। বাস্তব PL সিনট্যাক্সে খুব কম ব্যবহৃত (শুধু উল্লেখযোগ্য)।
Type 0 — Unrestricted
কোনো বিধিনিষেধ নেই — টুরিং মেশিনের সমান ক্ষমতাশালী। শুধু তাত্ত্বিক সীমা হিসেবে উল্লেখযোগ্য।

২ · নেস্টিং সম্পর্ক — একটি কঠোর containment হায়ারার্কি

প্রতিটি regular ভাষা একটি context-free ভাষাও বটে; প্রতিটি context-free ভাষা একটি context-sensitive ভাষাও বটে; প্রতিটি context-sensitive ভাষা একটি unrestricted ভাষাও বটে — এটি একটি কঠোর, নেস্টেড containment: $Type\ 3 \subset Type\ 2 \subset Type\ 1 \subset Type\ 0$। অর্থাৎ যত বেশি টাইপ নম্বর কমে, তত বেশি ক্ষমতাশালী গ্রামার, কিন্তু ছোট নম্বরের টাইপের প্রতিটি ভাষা বড় নম্বরের টাইপেও থাকে — কখনো উল্টো নয়।

Type 0 — Unrestricted (Turing Machine) Type 1 — Context-Sensitive (Linear-Bounded Automaton) Type 2 — Context-Free (Pushdown Automaton) — M5 Type 3 — Regular (Finite Automaton) — M4 লেক্সার-এর টোকেন প্যাটার্ন এখানে বাস করে
প্রতিটি ভেতরের বাক্স তার বাইরের বাক্সের একটি উপসেট — একটি regular ভাষা সবসময়ই context-free, কিন্তু উল্টোটা সত্য নয় (যেমন balanced parentheses, নিচের কোড সেলে দেখুন)।

৩ · কেন এই হায়ারার্কি এই কোর্সের জন্য গুরুত্বপূর্ণ

বাস্তব প্রোগ্রামিং ল্যাঙ্গুয়েজ সিনট্যাক্স ডিজাইন করার সময় দুটো ইচ্ছাকৃত সিদ্ধান্ত নেওয়া হয় —

  • সিনট্যাক্স (গ্রামার স্তর) প্রায় সম্পূর্ণভাবে context-free (Type 2) রাখা হয় — কারণ context-free ভাষার জন্য efficient, সুপরিচিত পার্সিং অ্যালগরিদম আছে (M5 — recursive descent, LL(1), LR)। Type 1/0-এর মতো আরও ক্ষমতাশালী গ্রামার ব্যবহার করা যেত, কিন্তু সেগুলোর জন্য efficient পার্সিং অ্যালগরিদম সাধারণভাবে নেই।
  • লেক্সিক্যাল স্তর (টোকেন প্যাটার্ন) ইচ্ছাকৃতভাবে regular (Type 3) রাখা হয় — কারণ ফাইনাইট অটোমাটা-ভিত্তিক লেক্সার (M4) ইনপুটের উপর একটি মাত্র লিনিয়ার পাসে, খুবই দ্রুত কাজ করতে পারে — কোনো ব্যাকট্র্যাকিং বা আনবাউন্ডেড মেমরির দরকার হয় না।

এই দুটো সিদ্ধান্ত মিলেই কম্পাইলার পাইপলাইনের প্রথম দুই ধাপকে (L05-এর ভাষায়) efficient ও ভালোভাবে-বোঝা রাখে।

Python
# অংশ ১ -- চারটি টাইপের রেফারেন্স টেবিল

chomsky_types = {
    "Type 0 (Unrestricted)":        {"automaton": "Turing Machine",           "pl_relevance": "তাত্ত্বিক সীমা -- সরাসরি PL সিনট্যাক্সে ব্যবহৃত হয় না"},
    "Type 1 (Context-Sensitive)":   {"automaton": "Linear-Bounded Automaton", "pl_relevance": "বিরল -- কিছু semantic constraint বোঝাতে উল্লেখ করা হয়, সিনট্যাক্সে নয়"},
    "Type 2 (Context-Free)":        {"automaton": "Pushdown Automaton",       "pl_relevance": "M5 -- বাস্তব PL সিনট্যাক্সের মূল ভিত্তি"},
    "Type 3 (Regular)":             {"automaton": "Finite Automaton",         "pl_relevance": "M4 -- লেক্সিক্যাল/টোকেন প্যাটার্নের ভিত্তি"},
}

print("চমস্কি হায়ারার্কি রেফারেন্স টেবিল:\n")
for typ, info in chomsky_types.items():
    print(f"  {typ:32s} → {info['automaton']:24s} | {info['pl_relevance']}")

# অংশ ২ -- Type 3 (regular) checker: "a*b*" -- শূন্য বা বেশি a, তারপর শূন্য বা বেশি b
def matches_a_star_b_star(s):
    seen_b = False
    for ch in s:
        if ch == 'a':
            if seen_b:
                return False   # b এসে যাওয়ার পর আবার a -- অবৈধ
        elif ch == 'b':
            seen_b = True
        else:
            return False
    return True

# অংশ ৩ -- একটি "bounded-state" checker: একটি ফাইনাইট অটোমাটার মতোই সসীম (max_depth+2 টি) স্টেট ব্যবহার করে
def fixed_depth_paren_checker(s, max_depth):
    depth = 0
    for ch in s:
        if ch == '(':
            depth += 1
            if depth > max_depth:
                return False   # স্টেট স্পেস ফুরিয়ে গেছে -- আর গভীরতা গুনতে পারছে না
        elif ch == ')':
            depth -= 1
            if depth < 0:
                return False
    return depth == 0

# অংশ ৪ -- একটি stack-based checker: unbounded গভীরতা সঠিকভাবে ট্র্যাক করতে পারে
def stack_based_paren_checker(s):
    stack = []
    for ch in s:
        if ch == '(':
            stack.append(ch)
        elif ch == ')':
            if not stack:
                return False
            stack.pop()
    return len(stack) == 0

print("\n--- Type 3 (regular) checker: a*b* ---")
for test in ["", "aaa", "bbb", "aaabbb", "aba", "abab"]:
    print(f"  matches_a_star_b_star({test!r:10s}) = {matches_a_star_b_star(test)}")

print("\n--- গভীরভাবে নেস্টেড balanced parentheses: bounded বনাম stack ---")
deep = "(" * 8 + ")" * 8   # ৮ স্তর গভীর নেস্টিং
print(f"  টেস্ট স্ট্রিং: {'(' * 8}{')' * 8}  (৮ স্তর গভীর)")
print(f"  fixed_depth_paren_checker(deep, max_depth=5) = {fixed_depth_paren_checker(deep, max_depth=5)}   -- সসীম স্টেট (৫) ফুরিয়ে গেছে, ভুল করে reject!")
print(f"  fixed_depth_paren_checker(deep, max_depth=8) = {fixed_depth_paren_checker(deep, max_depth=8)}    -- এবার যথেষ্ট স্টেট আছে বলে সঠিকভাবে accept")
print(f"  stack_based_paren_checker(deep)               = {stack_based_paren_checker(deep)}    -- স্ট্যাক unbounded, তাই যেকোনো গভীরতায় সঠিক")

deeper = "(" * 20 + ")" * 20   # আরও গভীর -- ২০ স্তর
print(f"\n  আরও গভীর টেস্ট (২০ স্তর): fixed_depth_paren_checker(deeper, max_depth=8) = {fixed_depth_paren_checker(deeper, max_depth=8)} (আবারও ফুরিয়ে গেছে)")
print(f"  stack_based_paren_checker(deeper)             = {stack_based_paren_checker(deeper)}    -- এখনও সঠিক, কোনো fixed limit নেই")

    
fixed_depth_paren_checker-এর max_depth প্যারামিটারটি একটি বাস্তব ফাইনাইট অটোমাটার সীমাবদ্ধতার হুবহু সিমুলেশন — একটি ফাইনাইট অটোমাটার সসীম সংখ্যক স্টেট থাকে, তাই এটি সর্বোচ্চ ততগুলো ভিন্ন "গভীরতা" মনে রাখতে পারে, তার বেশি নয়। যতই max_depth বাড়ানো হোক না কেন, সবসময় তার চেয়ে গভীর একটি ইনপুট বানানো যাবে যা সেই checker ভুল করে reject করবে — এটাই প্রমাণ করে balanced parentheses-এর ভাষাকে কোনো ফাইনাইট অটোমাটা (তাই কোনো regular গ্রামার) সঠিকভাবে রিকগনাইজ করতে পারে না। স্ট্যাক-ভিত্তিক checker-এর unbounded মেমরি (পুশডাউন অটোমাটা, Type 2-এর ক্ষমতা) এই সমস্যা সমাধান করে।
মূল কথা · Key takeaway

চমস্কি হায়ারার্কি শুধু একটি তাত্ত্বিক classification নয় — এটি সরাসরি ব্যাখ্যা করে কেন M4 (লেক্সিক্যাল অ্যানালাইসিস) regular ভাষা/ফাইনাইট অটোমাটার উপর নির্মিত, আর M5 (পার্সিং) context-free ভাষা/পুশডাউন অটোমাটার উপর নির্মিত — এই দুটো ভিন্ন স্তর, ভিন্ন ক্ষমতার গাণিতিক ভিত্তির উপর দাঁড়িয়ে, একসাথে মিলে সম্পূর্ণ কম্পাইলার ফ্রন্ট-এন্ড (L05) তৈরি করে।

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

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

প্র ০১ Type 1/Type 0 গ্রামার তো Type 2-এর চেয়ে বেশি ক্ষমতাশালী — তাহলে PL সিনট্যাক্স কেন এগুলো ব্যবহার করে না?

বেশি ক্ষমতাশালী গ্রামার মানেই বেশি ভাষা বর্ণনা করা যায়, কিন্তু তার বিনিময়ে পার্সিং কঠিন/ধীর হয়ে যায় — Type 1-এর জন্য জেনারেল পার্সিং অ্যালগরিদম exponential সময় নিতে পারে, যেখানে Type 2 (context-free)-এর জন্য well-understood polynomial-time (আসলে প্রায়ই লিনিয়ার-টাইম) অ্যালগরিদম আছে (M5)। যেহেতু প্রায় সব বাস্তব প্রোগ্রামিং ল্যাঙ্গুয়েজের সিনট্যাক্স context-free দিয়েই পুরোপুরি প্রকাশ করা যায় (বাড়তি ক্ষমতার প্রয়োজনই হয় না), ভাষা-ডিজাইনাররা ইচ্ছাকৃতভাবে সিনট্যাক্সকে Type 2-এর মধ্যে সীমাবদ্ধ রাখেন — দ্রুত, নির্ভরযোগ্য পার্সিং পাওয়ার জন্য একটি সচেতন ট্রেড-অফ (L04-এর ভাষায়)।

প্র ০২ "একটি ভ্যারিয়েবল ব্যবহারের আগে declare করতে হবে" — এই নিয়মটি কি context-free গ্রামার দিয়ে প্রকাশ করা সম্ভব?

সাধারণত না — এই নিয়মটি সত্যিকার অর্থে context-sensitive (Type 1-ঘেঁষা): একটি নির্দিষ্ট আইডেন্টিফায়ার ব্যবহার বৈধ কিনা তা নির্ভর করে প্রোগ্রামের অন্য কোথাও (আগে) কী declare করা হয়েছে তার উপর — এটি বিশুদ্ধ context-free রুল দিয়ে প্রকাশ করা কঠিন। বাস্তবে ভাষা-ডিজাইনাররা এই সমস্যা এড়ান একটি গুরুত্বপূর্ণ কৌশলে: সিনট্যাক্স (গ্রামার, "কী কী টোকেন কোন ক্রমে বসতে পারে") context-free রাখা হয়, আর "declare-before-use"-এর মতো নিয়মগুলো আলাদাভাবে সিমান্টিক অ্যানালাইসিস ধাপে (M6) সিম্বল টেবিল দিয়ে চেক করা হয় — সিনট্যাক্স ও সিমান্টিক্সের এই বিভাজন (L02) সরাসরি এখানে কাজে লাগে।

প্র ০৩ কোড সেলে fixed_depth_paren_checker-এ max_depth কত বড় করলেও কেন এটি কখনো stack_based_paren_checker-এর মতো "সবসময় সঠিক" হবে না?

কারণ যত বড়ই max_depth বেছে নেওয়া হোক না কেন, সেটি একটি নির্দিষ্ট, সসীম সংখ্যা — আর সবসময়ই তার চেয়ে এক স্তর বেশি গভীর একটি ইনপুট বানানো সম্ভব (শুধু আরও একটি বন্ধনী যোগ করে) যা সেই নির্দিষ্ট checker ভুল করবে। এটাই মূল পার্থক্য — fixed_depth_paren_checker একটি নির্দিষ্ট, পূর্ব-নির্ধারিত সীমার মধ্যে কাজ করে (একটি বাস্তব ফাইনাইট অটোমাটার মতোই), যেখানে stack_based_paren_checker-এর মেমরি (Python লিস্ট/স্ট্যাক) রানটাইমে ইনপুট অনুযায়ী বাড়ে — কোনো পূর্ব-নির্ধারিত সীমা নেই। এই "unbounded বনাম fixed" পার্থক্যটাই regular বনাম context-free ভাষার মধ্যে মৌলিক পার্থক্য।

অনুশীলন

  1. শ্রেণিবদ্ধ করুন: নিচের রুলগুলো কোন চমস্কি টাইপের সাথে মেলে তা চিহ্নিত করুন — (ক) A ::= aA | b (খ) AB ::= BA (গ) S ::= aSb | ε

    (ক) Type 3 (regular) — বাম পাশে একটি নন-টার্মিনাল, ডানপাশে সবসময় সর্বোচ্চ একটি নন-টার্মিনাল (right-linear ফর্ম)। (খ) Type 1 বা তার বেশি সাধারণ — বাম পাশে একাধিক সিম্বল (AB), তাই এটি context-free নয় (CFG-তে বাম পাশে ঠিক একটি নন-টার্মিনাল থাকতেই হবে)। (গ) Type 2 (context-free) — বাম পাশে একটি নন-টার্মিনাল, কিন্তু ডানপাশে দুটো নন-টার্মিনাল-নয় এমন সিম্বলের মাঝে একটি নন-টার্মিনাল নেস্টেড (right-linear নয়, তাই regular নয়, কিন্তু context-free)।

  2. পরীক্ষা করুন: কোড সেলে deeper-এর গভীরতা ৫০ স্তরে বাড়িয়ে এবং max_depth=30 দিয়ে fixed_depth_paren_checker ও stack_based_paren_checker দুটোই আবার চালিয়ে দেখুন।

    fixed_depth_paren_checker(deeper, max_depth=30) এখনও False দেবে (৫০ স্তর গভীরতা ৩০-এর সীমা ছাড়িয়ে যায়), আর stack_based_paren_checker(deeper) এখনও True দেবে — যত গভীরই করা হোক, স্ট্যাক-ভিত্তিক checker সবসময় সঠিক থাকবে, কিন্তু fixed-depth checker-এর জন্য সবসময় একটি নতুন, আরও গভীর কাউন্টার-উদাহরণ বানানো সম্ভব।

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

আগের পাঠ
L14 · পার্স ট্রি ও অ্যাম্বিগুইটি