1 पॉइंट द्वारा GN⁺ 2025-12-12 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 1988~1995 के बीच प्रकाशित Jack Crenshaw की ‘Let’s Build a Compiler’ ट्यूटोरियल को Python और WebAssembly के साथ फिर से बनाया गया एक प्रोजेक्ट
  • मूल रूप में Pascal और Motorola 68000 Assembly का उपयोग होता था, लेकिन इस बार कोड को आधुनिक वातावरण में रन करने योग्य रूप में बदल दिया गया
  • ट्यूटोरियल का मुख्य फोकस recursive-descent parser को खुद implement करना और शुरुआती चरण से ही वास्तविक assembly code generate करने की approach पर था
  • Crenshaw का तरीका syntax-directed translation का उपयोग करता है, लेकिन type handling चरण में सीमाएँ दिखाता है
  • इस पुनर्निर्माण का महत्व इस बात में है कि क्लासिक ट्यूटोरियल को आधुनिक डेवलपर्स के लिए सीधे रन करके experiment करने योग्य रूप में पुनर्जीवित किया गया है

क्लासिक ट्यूटोरियल का संदर्भ

  • Jack Crenshaw की ‘Let’s Build a Compiler’ 1988~1995 के बीच जारी की गई एक compiler निर्माण परिचय पुस्तिका है, जिसे आज भी अक्सर reference के रूप में लिया जाता है
    • original संस्करण Pascal में लिखा गया था और इसमें Motorola 68000 Assembly code output होता था
    • 35 साल बाद 2025 में भी Hacker News जैसे प्लेटफॉर्म पर यह लगातार चर्चा में बनी रही
  • लेखक ने पहली बार इस ट्यूटोरियल को 2003 में पढ़ा था और उस समय से प्रभावित रहे; 2025 में उन्होंने इसे Python और WebAssembly में port किया

Python और WebAssembly में पुनर्निर्माण

  • केवल पढ़ने तक सीमित न रहकर, लेखक ने tutorial compiler को Python में port किया और output को WebAssembly (WASM) के रूप में generate करने के लिए implement किया
  • परिणाम सार्वजनिक GitHub repo में उपलब्ध है: eliben/letsbuildacompiler
    • TUTORIAL.md में बताया गया है कि original के हर भाग को Python code में कैसे map किया गया है
    • उपयोगकर्ता original ट्यूटोरियल पढ़ते हुए directly executable code के साथ experiments कर सकता है

KISS भाषा उदाहरण और WASM output

  • Crenshaw द्वारा डिज़ाइन की गई KISS भाषा के sample program को Python version compiler से compile करने का परिणाम दिखाया गया है
    • उदाहरण में procedure, while loop, तथा value passing और reference passing शामिल हैं
  • निकलने वाली WASM text में by-reference parameter हैंडल करने वाली logic मौजूद है, जबकि optimization लगभग नहीं है
  • ग्लोबल variable X का main से implicit return होना testing convenience के लिए जोड़ा गया हिस्सा है
  • generated WASM code का उपयोग वास्तविक रन के जरिए expected परिणाम validate करने वाले tests में किया गया

ट्यूटोरियल की ताकत

  • Crenshaw का ट्यूटोरियल चरण-दर-चरण recursive-descent parser बनाते हुए चलता है और जटिल theory की बजाय काम करने वाला code generation दिखाने पर केंद्रित रहता है
    • उस समय lex और yacc मानक माने जाते थे, लेकिन हाथ से लिखे गए parser की simplicity और clarity का असर बहुत बड़ा था
    • बाद के 20 सालों में लेखक का झुकाव manual recursive-descent parsing की तरफ हो गया
  • साथ ही जब अधिकतर lectures parsing पर फोकस करते थे, Crenshaw ने शुरुआती स्तर से ही assembly code generation शामिल किया

सीमाएँ और सुधार की संभावनाएँ

  • ट्यूटोरियल syntax-directed translation के तरीके से parsing के दौरान सीधे code generate करता है
    • यह सीखने के लिए उपयोगी है, पर type जानकारी पहले से नहीं होने के कारण optimization करना कठिन हो जाता है
  • खासकर type introduction के बाद, यानी part 14 के बाद, IR या AST जैसे structured तरीके की जरूरत साफ दिखने लगती है
  • Crenshaw ने part 14 के बाद tutorial क्यों बंद किया, यह स्पष्ट नहीं है, लेकिन संभवतः इन सीमाओं से इसका संबंध रहा होगा
  • लेखक के अनुसार part 14 को turning point मानकर पहले AST बनाना, फिर type checking और code generation करना बेहतर होता

निष्कर्ष

  • मूल ट्यूटोरियल आज भी compiler निर्माण परिचय पुस्तिका के रूप में पढ़ने में सरलता और व्यवहारिकता के साथ मजबूत बना हुआ है
  • यह Python·WASM संस्करण आधुनिक डेवलपर्स को पुरानी तकनीकों पर निर्भर हुए बिना सीखने के लिए एक बेहतर रूप देता है
  • GitHub repo के माध्यम से कोई भी इसे आसान प्रयोग के लिए चला सकता है, और इसे compiler structure को hands-on तरीके से समझने वाली आधुनिक reinterpretation के रूप में देखा जा सकता है

1 टिप्पणियां

 
GN⁺ 2025-12-12
Hacker News की राय
  • मुझे यह ट्यूटोरियल पसंद आया कि यह frontend की बारीकियों में फँसे बिना शुरुआत से ही काम करने वाला assembly code जनरेट करता है
    Ghuloum के approach की तरह, भाषा के बहुत छोटे हिस्से से शुरू करके पूरे सिस्टम को धीरे-धीरे बढ़ाने का तरीका खास तौर पर आकर्षक लगा
    हाल में मिली एक अच्छी किताब यह लिंक है; हालाँकि C और x86 शुरुआती लोगों के लिए आदर्श target नहीं हैं, लेकिन पहला compiler बनाने के लिए यह शानदार सामग्री थी
    संदर्भ के लिए Ghuloum का paper यहाँ है

    • यह शायद दुर्लभ मामला है, लेकिन मुझे लगता है कि academic approach यहाँ व्यावहारिकता से मेल नहीं खाती
      पहले parsing सबसे अहम हिस्सा हुआ करती थी, इसलिए curriculum उसी हिसाब से बना, लेकिन आजकल ज़्यादा महत्वपूर्ण यह है कि language features को implement कैसे किया जाए
      अब अगर आप खुद compiler बना रहे हों, तो “Claude, इस grammar के लिए recursive descent parser बना दो” कहने पर लगभग एक ही बार में नतीजा मिल जाता है
    • यहाँ academic researcher और practical developer का फर्क महसूस हुआ
      academia आम तौर पर layer आधारित सोचती है, जबकि practitioners pipe आधारित approach लेते हैं
      layer तरीका build होने वाला code देता है, लेकिन उसे चलाना कठिन होता है; pipe तरीका चलाना आसान बनाता है, पर structure कमज़ोर रहता है
      आखिरकार असली बात यह है कि development को executable न्यूनतम इकाइयों में बाँटा जाए
  • मुझे लगा इस लेख ने सच में मुद्दे के केंद्र को पकड़ा है
    कॉलेज जाने से पहले से ही मुझे compilers में दिलचस्पी थी, और यह सामग्री सबसे सुलभ शुरुआती परिचय थी
    17 साल की उम्र में recursive descent parser खुद बनाते हुए मुझे समझ आया कि जो समस्या जटिल लगती है, वह सही primitive में तोड़ने पर सरल हो जाती है

    • “समस्या को सही primitive में बाँटना” ही programming का असली सार है
      algorithms पर सामग्री बहुत है, लेकिन समस्याओं को primitives के ज़रिए कैसे पकड़ा जाए, इस पर सामग्री कम मिलती है
    • recursive descent सिर्फ parser बनाने की तकनीक नहीं, बल्कि program design का सबक भी है
      पहले yacc जैसे table-based parser मुख्यधारा में थे, लेकिन आज recursive descent ज़्यादा practical और flexible है
      LLM code generation की वजह से यह approach और आसान हो गई है
      सीखने वाले toy compiler में AST, IR, और optimization को छोड़कर सीधे code generation तक जाना भी काफ़ी उपयोगी हो सकता है
      पहले कंपनी में C के subset को support करने वाला एक test tool बनाते समय हमने parser से code generate कराने के बजाय उसे executable data structure लौटाने दिया था
      उदाहरण के लिए, ForLoopStatement class में testExpression होता था, और Execute() method में उसे सीधे call किया जाता था
    • Niklaus Wirth का 1971 का paper Program Development by Stepwise Refinement इस तरह की सोच को शुरुआती दौर में व्यवस्थित रूप देने वाला क्लासिक है
      Fred Brooks की The Mythical Man-Month में भी इसका उल्लेख है
    • आजकल जब भी parsing की ज़रूरत पड़ती है, मैं parser combinator का उपयोग करता हूँ
      वैचारिक रूप से यह बहुत स्वाभाविक और साफ-सुथरा लगता है
  • यह व्याख्या रोचक लगी कि Jack Crenshaw का ट्यूटोरियल syntax-directed translation तरीका इस्तेमाल करता है
    जिज्ञासा है कि क्या इसका मतलब सिर्फ single-pass compiler है, या यह इससे अधिक विशिष्ट अवधारणा है
    यह तो समझ आता है कि statically typed language में type-based optimization कठिन होती है, लेकिन क्या type checking के स्तर पर भी कोई पाबंदी होती है?

    • अगर target language define-before-use rule का पालन करती हो और उसमें जटिल type inference न हो, तो type-based optimization या constant folding जैसी चीज़ें संभव हैं
      लेकिन IR के बिना LICM, CSE, GVN जैसे advanced optimization संभव नहीं हैं
      व्यक्तिगत रूप से मैं function-level 2-pass compilation पसंद करता हूँ — पहले pass में IR बनाओ, फिर तुरंत code generation करके state हटा दो; इससे तेज़ और efficient नतीजा मिलता है
    • syntax-directed translation थोड़ा अधिक विशिष्ट विचार है, जिसमें source code पढ़ते ही तुरंत code generation किया जाता है
      single-pass compiler होने पर भी चरणों को अलग रखकर अंत में सिर्फ code generation किया जा सकता है
  • Crenshaw का ट्यूटोरियल व्यावहारिक स्तर तक तो नहीं पहुँचता, लेकिन अगर इसे precedence climbing technique से बढ़ाया जाए तो यह कहीं ज़्यादा उपयोगी हो जाता है
    इस पर अच्छा लेख इस लिंक में है

  • मैंने “Let’s Build a Compiler” से जुड़े threads फिर से देखे
    2007 से 2023 तक इसके कई HN रिकॉर्ड मिलते हैं
    खास तौर पर Jack Crenshaw interview में comments नहीं हैं, फिर भी वह बहुत दिलचस्प पढ़ाई थी

    • चौथा लिंक Crenshaw का लेख नहीं, बल्कि उसी शीर्षक वाला दूसरा लेख है
    • Crenshaw interview सच में बहुत अच्छा था
  • अपने प्रोजेक्ट का परिचय और थोड़ा प्रचार: cwerg.org
    यह Python और recursive descent parsing का उपयोग करता है, और frontend तथा backend को IR के ज़रिए अलग करता है
    यह x86 और ARM के लिए ELF binaries बनाता है और असली उपयोग को लक्ष्य करता है
    हालाँकि यह जटिल है और ट्यूटोरियल शैली में नहीं है

  • मुझे पुराने USENET के comp.compilers newsgroup में Jack Crenshaw का ट्यूटोरियल सीखने की याद है
    जानकारी के लिए, वह group अभी भी सक्रिय है

  • आधुनिक compiler approach के लिए मैं यह Cornell blog post सुझाऊँगा

    • LLVM की वजह से compiler बनाना हैरान कर देने जितना आसान हो गया है
      हर बार इसका उपयोग करने पर ऐसा लगता है जैसे किसी pyramid के ऊपर कुछ पत्थर और रख दिए हों
    • लेकिन LLVM एक indirect approach है, इसलिए इसकी सीमाएँ हैं
      LLVM को backend की तरह इस्तेमाल करने वाली भाषाओं में आम तौर पर compile speed धीमी होती है
      Zig इसे समझता है और अपना backend बना रहा है, जबकि Rust अब भी धीमे compilation से बँधा हुआ है
      मुझे लगता है LLVM यह भ्रम पैदा करता है कि जटिलता अपने-आप गुणवत्ता की गारंटी है, और यह developer की स्वायत्तता को कम करने वाली तकनीक है
      यह कुछ दूसरी लोकप्रिय technologies जैसी भी लगती है जिनके initials मिलते-जुलते हैं
  • मैंने भी कुछ इसी वजह से tiny-optimizing-compiler नाम का ट्यूटोरियल लिखा था
    यह compiler के middle-end और backend पर केंद्रित एक संक्षिप्त उदाहरण है
    GitHub लिंक

    • इसे Lean में implement करना मज़ेदार हो सकता है
  • बचपन में मैं इस ट्यूटोरियल को प्रिंट करके साथ लेकर घूमता था और पढ़ता था
    कम समय में compiler को पूरा बनते देखना सच में शानदार था
    Beej’s Guide जैसी ऐसी सामग्री CS शिक्षा के सबसे मूल्यवान दस्तावेज़ों में से है, लेकिन इन्हें जितनी सराहना मिलनी चाहिए उतनी नहीं मिलती