পাঠ ০১ · ৫৮-এর মধ্যে · মডিউল ১

প্রোগ্রামিং ল্যাঙ্গুয়েজ ও কম্পাইলার ডিজাইন কী ও কেন গুরুত্বপূর্ণ

What is a programming language & compiler design, and why it matters
৯ মিনিট পড়া শুরু · Beginner Python কোডসহ সম্পূর্ণ বাংলায়

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

  • প্রোগ্রামিং ল্যাঙ্গুয়েজের সংজ্ঞা এবং এটি স্বাভাবিক ভাষা থেকে ঠিক কীভাবে আলাদা
  • ট্রান্সলেটর ধারণা — কম্পাইলার ও ইন্টারপ্রেটার সংক্ষেপে (বিস্তারিত M1/L03-এ)
  • সোর্স কোড থেকে এক্সিকিউশন পর্যন্ত ফেজ-ভিত্তিক কম্পাইলার পাইপলাইন — এই কোর্সের সম্পূর্ণ মানচিত্র
  • Python দিয়ে একটি ছোট্ট টোকেনাইজার — পাইপলাইনের প্রথম ধাপের একটি প্রিভিউ

১ · প্রোগ্রামিং ল্যাঙ্গুয়েজ কী

প্রোগ্রামিং ল্যাঙ্গুয়েজProgramming Languageগণনা/কম্পিউটেশন প্রকাশ করার জন্য একটি ফরমাল, নিয়মবদ্ধ নোটেশন — প্রতিটি বৈধ বাক্যের একটি এবং মাত্র একটিই অর্থ থাকে, স্বাভাবিক ভাষার মতো অস্পষ্টতা নেই। হলো এমন একটি নোটেশন সিস্টেম যা দিয়ে আপনি একটি কম্পিউটারকে ঠিক কী করতে হবে তা নির্দিষ্টভাবে বলে দিতে পারেন। স্বাভাবিক ভাষা (যেমন বাংলা বা ইংরেজি) অস্পষ্ট হতে পারে — একই বাক্যের একাধিক অর্থ হতে পারে, প্রসঙ্গের উপর নির্ভর করে। প্রোগ্রামিং ল্যাঙ্গুয়েজ ঠিক এর বিপরীত — প্রতিটি বৈধ প্রোগ্রামের একটিমাত্র, সুনির্দিষ্ট অর্থ থাকে, যাতে কম্পিউটার (যার কোনো "প্রসঙ্গ বোঝার" ক্ষমতা নেই) নির্ভুলভাবে তা এক্সিকিউট করতে পারে।

সোর্স কোড (টেক্সট) লেক্সিক্যাল অ্যানালাইসিস (M4) → টোকেন পার্সিং (M5) → পার্স ট্রি সিমান্টিক অ্যানালাইসিস (M6-M8) কোড জেনারেশন ও অপ্টিমাইজেশন (M12)
সোর্স কোড এই প্রতিটি ধাপ পার হয়ে শেষে মেশিন-এক্সিকিউটেবল কিছুতে পরিণত হয় — এই কোর্স ধাপে ধাপে এই পুরো পাইপলাইন কভার করবে।

২ · ট্রান্সলেটর — কম্পাইলার ও ইন্টারপ্রেটার সংক্ষেপে

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

৩ · এই কোর্স ঠিক কী কভার করে

PL কনসেপ্ট (ভাষা-ডিজাইন সাইড)
প্যারাডাইম (M2), টাইপ সিস্টেম (M7), নেম/স্কোপ/বাইন্ডিং (M8), কন্ট্রোল ফ্লো (M9), ডেটা টাইপ (M10), সাবপ্রোগ্রাম (M11) — একটি ভাষা ডিজাইন করার সময় যে সিদ্ধান্তগুলো নিতে হয়।
কম্পাইলার কনস্ট্রাকশন (বাস্তবায়ন সাইড)
ফরমাল গ্রামার (M3), লেক্সিক্যাল অ্যানালাইসিস (M4), পার্সিং (M5), সিমান্টিক অ্যানালাইসিস (M6), কোড জেনারেশন ও অপ্টিমাইজেশন (M12) — সেই ডিজাইন সিদ্ধান্তগুলো বাস্তবে কীভাবে প্রয়োগ করা হয়।
Computer Architecture ও DSA কোর্সের সাথে সম্পর্ক

Computer Architecture কোর্স শেখায় কীভাবে হার্ডওয়্যার ইনস্ট্রাকশন এক্সিকিউট করে — কম্পাইলারের কোড জেনারেশন ধাপ (M12) ঠিক সেই হার্ডওয়্যারকেই টার্গেট করে মেশিন কোড তৈরি করে। আর Data Structures & Algorithms কোর্সের stack, tree, graph সরাসরি এই কোর্সের পার্সার (M5) ও সিম্বল টেবিল (M6) বাস্তবায়নে ব্যবহৃত হয় — একটি পার্স ট্রি আক্ষরিক অর্থেই একটি ট্রি ডেটা স্ট্রাকচার।

৪ · এই স্তর বোঝা কেন প্রতিটি প্রোগ্রামারের জন্য গুরুত্বপূর্ণ

আপনি হয়তো কখনো নিজে একটি কম্পাইলার লিখবেন না — কিন্তু এই পাইপলাইন বোঝা আপনার প্রতিদিনের প্রোগ্রামিংকেও প্রভাবিত করে —

  • এরর মেসেজ পড়া: "SyntaxError" মানে পার্সিং ধাপে সমস্যা (M5), আর "TypeError" মানে সিমান্টিক/টাইপ-চেকিং ধাপে সমস্যা (M6-M7) — এই পার্থক্য জানলে এরর দ্রুত ডিবাগ করা সহজ হয়।
  • নতুন ভাষা মূল্যায়ন করা: একটি নতুন ভাষা শেখার সময় "এটা কোন প্যারাডাইমের?", "টাইপিং স্ট্যাটিক না ডায়নামিক?", "স্কোপিং কেমন?" — এই প্রশ্নগুলোই এই কোর্সের M2, M7, M8-এর মূল বিষয়।
  • পারফরম্যান্স বোঝা: কেন কম্পাইল করা ভাষা (C, Rust) সাধারণত ইন্টারপ্রেটেড ভাষার (Python) চেয়ে দ্রুত চলে — M12-এর কোড অপ্টিমাইজেশন থেকে বোঝা যাবে।

৫ · একটি ছোট্ট টোকেনাইজার প্রিভিউ

নিচের কোড সেলে কম্পাইলার পাইপলাইনের প্রথম ধাপ — লেক্সিক্যাল অ্যানালাইসিসসোর্স কোডের raw টেক্সটকে অর্থপূর্ণ ছোট ছোট টুকরো (টোকেন) এ ভাগ করার ধাপ — M4-এ বিস্তারিত। — এর একটি প্রিভিউ দেখা যাক। একটি সাধারণ গাণিতিক এক্সপ্রেশনকে "টোকেন"-এ ভাঙা হচ্ছে।

Python
# একটি সরল টোকেনাইজার -- M4-এ পূর্ণাঙ্গ লেক্সার বানানো হবে (regex ব্যবহার ছাড়াই, হাতে লেখা)
def tokenize(expr):
    tokens = []
    i = 0
    while i < len(expr):
        ch = expr[i]
        if ch.isspace():
            i += 1
            continue
        elif ch.isdigit():
            start = i
            while i < len(expr) and expr[i].isdigit():
                i += 1
            tokens.append(("NUMBER", expr[start:i]))
        elif ch in "+-*/()":
            tokens.append(("OP", ch))
            i += 1
        else:
            raise ValueError(f"অজানা ক্যারেক্টার: {ch!r}")
    return tokens

expr = "3 + 4 * 2"
print(f"সোর্স টেক্সট: {expr!r}\n")
print("টোকেন স্ট্রিম:")
for kind, value in tokenize(expr):
    print(f"  ({kind}, {value!r})")

    
লক্ষ্য করুন — tokenize ফাংশন "raw টেক্সট" থেকে "গঠনবদ্ধ ডেটা" (একটি টোকেনের তালিকা) তৈরি করছে। এখন থেকে বাকি পাইপলাইনের কোনো ধাপই টেক্সটের ক্যারেক্টার নিয়ে সরাসরি কাজ করে না — সবাই এই টোকেন-তালিকা নিয়ে কাজ করে। এটিই লেক্সিক্যাল অ্যানালাইসিসের মূল উদ্দেশ্য: বাকি সব ধাপকে raw টেক্সটের খুঁটিনাটি থেকে মুক্ত করা।
মূল কথা · Key takeaway

একটি প্রোগ্রামিং ল্যাঙ্গুয়েজ ও তার কম্পাইলার/ইন্টারপ্রেটার একসাথে দেখায় কীভাবে মানুষের লেখা টেক্সট ধাপে ধাপে একটি চলমান প্রোগ্রামে রূপান্তরিত হয়। এই কোর্স সেই প্রতিটি ধাপ — এবং প্রতিটি ধাপে ভাষা-ডিজাইনাররা কী কী সিদ্ধান্ত নিতে পারেন — একে একে খুলে দেখাবে।

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

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

প্র ০১ প্রোগ্রামিং ল্যাঙ্গুয়েজে কম্পিউটেশন প্রকাশ করার জন্য কেন স্বাভাবিক ভাষা (যেমন বাংলা/ইংরেজি) সরাসরি ব্যবহার করা যায় না?

স্বাভাবিক ভাষা স্বভাবতই অস্পষ্ট (ambiguous) — একই বাক্যের একাধিক অর্থ হতে পারে, প্রসঙ্গ বা শ্রোতার পূর্ব-জ্ঞানের উপর নির্ভর করে ("তাকে বলো সে যেন কল করে" — কাকে বলবে? কে কল করবে?)। কম্পিউটারের এই ধরনের প্রসঙ্গ বোঝার ক্ষমতা নেই — এটি নির্ভুলভাবে, প্রতিবার একই নিয়মে ইনস্ট্রাকশন এক্সিকিউট করতে হয়। তাই প্রোগ্রামিং ল্যাঙ্গুয়েজকে ফরমাল, দ্ব্যর্থহীন হতে হয় — প্রতিটি বৈধ প্রোগ্রামের একটিমাত্র সম্ভাব্য অর্থ থাকতে হয় (M3-এর ফরমাল গ্রামার ঠিক এই দ্ব্যর্থহীনতা নিশ্চিত করার হাতিয়ার)।

প্র ০২ "PL কনসেপ্ট" ও "কম্পাইলার কনস্ট্রাকশন" — এই কোর্সের দুটো দিক কি একে অপরের থেকে সম্পূর্ণ আলাদা, নাকি সংযুক্ত?

ঘনিষ্ঠভাবে সংযুক্ত। "PL কনসেপ্ট" সাইড প্রশ্ন করে — একটি ভাষায় কী কী ফিচার/নিয়ম থাকা উচিত (যেমন: স্ট্যাটিক নাকি ডায়নামিক টাইপিং?)। "কম্পাইলার কনস্ট্রাকশন" সাইড উত্তর দেয় — সেই সিদ্ধান্তটি বাস্তবে কীভাবে প্রয়োগ করা হয় (যেমন: টাইপ চেকার M6/M7-এ কীভাবে লেখা হয়)। একটি ছাড়া অন্যটি অসম্পূর্ণ — শুধু ডিজাইন সিদ্ধান্ত জানলে বাস্তবায়ন বোঝা যায় না, আবার শুধু বাস্তবায়ন কৌশল জানলে "কেন এই সিদ্ধান্ত?" প্রশ্নের উত্তর মেলে না।

প্র ০৩ উপরের কোড সেলে tokenize("3 + 4 * 2") চালালে "42" একটি সংখ্যা হয়ে যায় না কেন — বরং "4" ও "2" আলাদা টোকেন হিসেবে থাকে?

"3 + 4 * 2"-এ "4" ও "2"-এর মাঝে একটি স্পেস ও একটি "*" ক্যারেক্টার আছে — তাই কোড এই দুটোকে কখনোই একটানা পড়ে না। while i < len(expr) and expr[i].isdigit() লুপটি শুধুমাত্র একটানা (consecutive) ডিজিট ক্যারেক্টার একসাথে জমা করে — যেই মুহূর্তে একটি নন-ডিজিট ক্যারেক্টার (স্পেস বা অপারেটর) পাওয়া যায়, লুপ থেমে যায় এবং জমা হওয়া ডিজিটগুলো একটি একক NUMBER টোকেন হিসেবে যোগ হয়। যদি ইনপুট হতো "42" (মাঝে কিছু না থাকলে), তখনই এটি একটি একক টোকেন হতো।

অনুশীলন

  1. চিন্তা করুন: আপনি যে ভাষাগুলোতে কোড লিখেছেন (Python, C, JavaScript ইত্যাদি) তাদের একটি তালিকা করুন এবং প্রতিটির জন্য অনুমান করুন এটি কম্পাইলড, ইন্টারপ্রেটেড, নাকি হাইব্রিড (JIT)।

    সাধারণ উদাহরণ: C/C++/Rust — সরাসরি মেশিন কোডে কম্পাইলড। Python/Ruby — মূলত ইন্টারপ্রেটেড (যদিও প্রথমে বাইটকোডে কম্পাইল হয়)। Java — বাইটকোডে কম্পাইল হয়ে JVM-এ JIT (হাইব্রিড) দিয়ে চলে। JavaScript (V8 ইঞ্জিন) — আধুনিক ব্রাউজারে JIT কম্পাইলেশন ব্যবহার করে। এই পার্থক্যের বিস্তারিত M1/L03-এ আসবে।

  2. পরীক্ষা করুন: উপরের কোড সেলে expr-এর মান "12 + (3 * 45)" করে Run চেপে দেখুন টোকেন স্ট্রিমে কী কী পরিবর্তন হয়।

    নতুন টোকেন স্ট্রিম হবে: (NUMBER, '12'), (OP, '+'), (OP, '('), (NUMBER, '3'), (OP, '*'), (NUMBER, '45'), (OP, ')') — মোট ৭টি টোকেন। লক্ষ্য করুন বন্ধনী "(" ও ")" প্রতিটি নিজে একটি আলাদা OP টোকেন, এবং "45" একটানা দুটো ডিজিট হওয়ায় একটিমাত্র NUMBER টোকেন হয়েছে — উপরের প্র-০৩ প্রশ্নের ঠিক বিপরীত উদাহরণ।

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

কোর্সে ফিরে যান
Concepts of Programming Languages & Compiler Design — সব পাঠ