চমস্কি হায়ারার্কি
এই পাঠে যা শিখবেন
- চমস্কি হায়ারার্কির চারটি টাইপ এবং প্রতিটির 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-প্রাসঙ্গিকতার দিকটিতে ফোকাস করব।
সবচেয়ে সীমাবদ্ধ রুল ফর্ম — ফাইনাইট অটোমাটা দিয়ে রিকগনাইজড। M4-এর লেক্সিক্যাল অ্যানালাইসিস ঠিক এই টাইপের উপর নির্মিত।
L13-এর CFG — প্রতিটি রুলের বাম পাশে একটি নন-টার্মিনাল। পুশডাউন অটোমাটা দিয়ে রিকগনাইজড। M5-এর পার্সিং ঠিক এই টাইপের উপর নির্মিত।
রুল প্রয়োগ প্রসঙ্গ-নির্ভর হতে পারে। লিনিয়ার-বাউন্ডেড অটোমাটা দিয়ে রিকগনাইজড। বাস্তব PL সিনট্যাক্সে খুব কম ব্যবহৃত (শুধু উল্লেখযোগ্য)।
কোনো বিধিনিষেধ নেই — টুরিং মেশিনের সমান ক্ষমতাশালী। শুধু তাত্ত্বিক সীমা হিসেবে উল্লেখযোগ্য।
২ · নেস্টিং সম্পর্ক — একটি কঠোর containment হায়ারার্কি
প্রতিটি regular ভাষা একটি context-free ভাষাও বটে; প্রতিটি context-free ভাষা একটি context-sensitive ভাষাও বটে; প্রতিটি context-sensitive ভাষা একটি unrestricted ভাষাও বটে — এটি একটি কঠোর, নেস্টেড containment: $Type\ 3 \subset Type\ 2 \subset Type\ 1 \subset Type\ 0$। অর্থাৎ যত বেশি টাইপ নম্বর কমে, তত বেশি ক্ষমতাশালী গ্রামার, কিন্তু ছোট নম্বরের টাইপের প্রতিটি ভাষা বড় নম্বরের টাইপেও থাকে — কখনো উল্টো নয়।
৩ · কেন এই হায়ারার্কি এই কোর্সের জন্য গুরুত্বপূর্ণ
বাস্তব প্রোগ্রামিং ল্যাঙ্গুয়েজ সিনট্যাক্স ডিজাইন করার সময় দুটো ইচ্ছাকৃত সিদ্ধান্ত নেওয়া হয় —
- সিনট্যাক্স (গ্রামার স্তর) প্রায় সম্পূর্ণভাবে context-free (Type 2) রাখা হয় — কারণ context-free ভাষার জন্য efficient, সুপরিচিত পার্সিং অ্যালগরিদম আছে (M5 — recursive descent, LL(1), LR)। Type 1/0-এর মতো আরও ক্ষমতাশালী গ্রামার ব্যবহার করা যেত, কিন্তু সেগুলোর জন্য efficient পার্সিং অ্যালগরিদম সাধারণভাবে নেই।
- লেক্সিক্যাল স্তর (টোকেন প্যাটার্ন) ইচ্ছাকৃতভাবে regular (Type 3) রাখা হয় — কারণ ফাইনাইট অটোমাটা-ভিত্তিক লেক্সার (M4) ইনপুটের উপর একটি মাত্র লিনিয়ার পাসে, খুবই দ্রুত কাজ করতে পারে — কোনো ব্যাকট্র্যাকিং বা আনবাউন্ডেড মেমরির দরকার হয় না।
এই দুটো সিদ্ধান্ত মিলেই কম্পাইলার পাইপলাইনের প্রথম দুই ধাপকে (L05-এর ভাষায়) efficient ও ভালোভাবে-বোঝা রাখে।
# অংশ ১ -- চারটি টাইপের রেফারেন্স টেবিল
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-এর ক্ষমতা) এই সমস্যা
সমাধান করে।
চমস্কি হায়ারার্কি শুধু একটি তাত্ত্বিক 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
ভাষার মধ্যে মৌলিক পার্থক্য।
অনুশীলন
-
শ্রেণিবদ্ধ করুন: নিচের রুলগুলো কোন চমস্কি টাইপের সাথে মেলে তা চিহ্নিত করুন — (ক)
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)। -
পরীক্ষা করুন: কোড সেলে
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-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৮টি পাঠ M3 এখানেই শেষ — পরবর্তী পাঠ থেকে M4 শুরু: টোকেন, লেক্সিম ও প্যাটার্ন, যা সরাসরি এই পাঠের Type 3/regular আলোচনার উপর নির্মিত।
- পাঠ ১৪ · পার্স ট্রি ও অ্যাম্বিগুইটি পূর্ববর্তী পাঠ Type 2 (context-free) গ্রামারের পার্স ট্রি ও অ্যাম্বিগুইটি নিয়ে বিস্তারিত।
- Discrete Mathematics কোর্স তাত্ত্বিক পূর্বসূরি চমস্কি হায়ারার্কি, পাম্পিং লেমা ও অটোমাটা থিওরির সম্পূর্ণ গাণিতিক গভীরতা এখানে কভার করা হয়েছে।