बहुत बुनियादी संकलक कैसे लिखें


214

उन्नत संकलक जैसे gccसंकलित कोड मशीन में पढ़ने योग्य फाइलों में उस भाषा के अनुसार जिसमें कोड लिखा गया है (जैसे C, C ++, आदि)। वास्तव में, वे प्रत्येक कोड का अर्थ पुस्तकालय और संबंधित भाषाओं के कार्यों के अनुसार करते हैं। यदि मैं गलत हूं तो मुझे सही करों।

मैं एक बहुत ही मूल संकलक (शायद सी में) को एक स्थिर फ़ाइल (उदाहरण के लिए एक पाठ फ़ाइल में हैलो वर्ल्ड) संकलित करके कम्पाइलर को बेहतर ढंग से समझने की इच्छा रखता हूं। मैंने कुछ ट्यूटोरियल और किताबें आजमाईं, लेकिन वे सभी व्यावहारिक मामलों के लिए हैं। वे संबंधित भाषा के साथ जुड़े अर्थों के साथ गतिशील कोड संकलित करते हैं।

स्थैतिक पाठ को मशीन पठनीय फ़ाइल में बदलने के लिए मैं एक मूल संकलक कैसे लिख सकता हूँ?

अगला कदम संकलक में चर पेश करना होगा; कल्पना करें कि हम एक संकलक लिखना चाहते हैं जो किसी भाषा के केवल कुछ कार्यों को संकलित करता है।

व्यावहारिक ट्यूटोरियल और संसाधनों का परिचय बहुत सराहना की जाती है :-)



क्या आपने lex / flex और yacc / bison की कोशिश की है?
मौविसील

15
@mouviciel: कंपाइलर बनाने के बारे में जानने का यह अच्छा तरीका नहीं है। वे उपकरण आपके लिए कड़ी मेहनत का एक महत्वपूर्ण कार्य करते हैं, इसलिए आप वास्तव में इसे कभी नहीं करते हैं और सीखते हैं कि यह कैसे किया जाता है।
मेसन व्हीलर

11
@Mat दिलचस्प है, आपके पहले लिंक 404 देता है, जबकि दूसरा अब इस प्रश्न के डुप्लिकेट के रूप में चिह्नित किया गया है ।
रुस्लान

जवाबों:


326

पहचान

एक विशिष्ट संकलक निम्न चरण करता है:

  • पार्सिंग: स्रोत पाठ एक सार वाक्यविन्यास ट्री (एएसटी) में बदल जाता है।
  • अन्य मॉड्यूल के संदर्भ का समाधान (सी लिंकिंग तक इस चरण को स्थगित करें)।
  • सिमेंटिक मान्यता: वाक्य-रचना संबंधी सही कथनों का निराकरण करना, जिसका कोई अर्थ नहीं है, जैसे अगम्य कोड या डुप्लिकेट घोषणाएँ।
  • समतुल्य परिवर्तन और उच्च-स्तरीय अनुकूलन: एएसटी को एक ही शब्दार्थ के साथ अधिक कुशल संगणना का प्रतिनिधित्व करने के लिए रूपांतरित किया जाता है। इसमें सामान्य सबएक्सप्रेस और प्रारंभिक अभिव्यक्तियों की प्रारंभिक गणना शामिल है, अत्यधिक स्थानीय असाइनमेंट को समाप्त करना ( एसएसए भी देखें ), आदि।
  • कोड जनरेशन: एएसपी को रैखिक निम्न-स्तरीय कोड में बदल दिया जाता है, छलांग, रजिस्टर आवंटन और इसी तरह। इस चरण में कुछ फ़ंक्शन कॉल को इनलाइन किया जा सकता है, कुछ लूप अनियंत्रित, आदि।
  • Peephole अनुकूलन: निम्न-स्तरीय कोड को सरल स्थानीय अक्षमताओं के लिए स्कैन किया जाता है, जिन्हें समाप्त कर दिया जाता है।

अधिकांश आधुनिक संकलक (उदाहरण के लिए, जीसीसी और क्लैंग) पिछले दो चरणों को एक बार फिर दोहराते हैं। वे प्रारंभिक कोड पीढ़ी के लिए एक मध्यवर्ती निम्न-स्तरीय लेकिन प्लेटफ़ॉर्म-स्वतंत्र भाषा का उपयोग करते हैं। फिर उस भाषा को प्लेटफ़ॉर्म-विशिष्ट कोड (x86, ARM इत्यादि) में परिवर्तित कर दिया जाता है, जो प्लेटफ़ॉर्म-अनुकूलित तरीके से लगभग एक ही काम करता है। इसमें उदाहरण के लिए, जब संभव हो तो वेक्टर निर्देशों का उपयोग, शाखा भविष्यवाणी दक्षता को बढ़ाने के लिए पुन: निर्देश, और इसी तरह शामिल हैं।

उसके बाद, लिंक करने के लिए ऑब्जेक्ट कोड तैयार है। अधिकांश देशी-कोड संकलक जानते हैं कि एक निष्पादन योग्य उत्पादन करने के लिए लिंकर को कैसे कॉल किया जाए, लेकिन यह प्रति संकलन संकलन कदम नहीं है। जावा और C # जैसी भाषाओं में लोडिंग समय में VM द्वारा किया गया पूरी तरह से गतिशील हो सकता है।

मूल बातें याद रखें

  • इसे काम करने लायक बनाओ
  • इसे सुंदर बनाओ
  • इसे कुशल बनाएं

यह क्लासिक अनुक्रम सभी सॉफ्टवेयर विकास पर लागू होता है, लेकिन दोहराव को सहन करता है।

अनुक्रम के पहले चरण पर ध्यान लगाओ। सबसे सरल चीज़ बनाएं जो संभवतः काम कर सकती है।

किताबें पढ़ें!

ड्रैगन बुक को अहो और उल्मैन द्वारा पढ़ें । यह क्लासिक है और आज भी काफी लागू है।

मॉडर्न कंपाइलर डिज़ाइन की भी प्रशंसा की जाती है।

यदि यह सामान अभी आपके लिए बहुत कठिन है, तो पहले पार्सिंग पर कुछ इंट्रो पढ़ें; आमतौर पर पुस्तकालयों को पार्स करने में इंट्रो और उदाहरण शामिल होते हैं।

सुनिश्चित करें कि आप ग्राफ़, विशेष रूप से पेड़ों के साथ काम करने में सहज हैं। ये चीजें हैं सामान कार्यक्रम तार्किक स्तर पर बने हैं।

अपनी भाषा को अच्छी तरह से परिभाषित करें

जो भी संकेतन आप चाहते हैं उसका उपयोग करें, लेकिन सुनिश्चित करें कि आपके पास आपकी भाषा का पूर्ण और सुसंगत विवरण हो। इसमें वाक्य रचना और शब्दार्थ दोनों शामिल हैं।

भविष्य संकलक के लिए परीक्षण मामलों के रूप में अपनी नई भाषा में कोड के स्निपेट्स को लिखने के लिए उच्च समय है।

अपनी पसंदीदा भाषा का उपयोग करें

पायथन या रूबी या जो भी भाषा आपके लिए आसान है, में एक कंपाइलर लिखना पूरी तरह से ठीक है। सरल एल्गोरिदम का उपयोग करें जिसे आप अच्छी तरह से समझते हैं। पहले संस्करण को तेज, या कुशल या सुविधा-पूर्ण होने की आवश्यकता नहीं है। इसे केवल पर्याप्त रूप से सही करना और संशोधित करना आसान है।

जरूरत पड़ने पर अलग-अलग भाषाओं में कंपाइलर के विभिन्न चरणों को लिखना भी ठीक है।

बहुत सारे परीक्षण लिखने के लिए तैयार करें

आपकी पूरी भाषा को परीक्षण मामलों द्वारा कवर किया जाना चाहिए; प्रभावी रूप से यह उनके द्वारा परिभाषित किया जाएगा। अपने पसंदीदा परीक्षण ढांचे से अच्छी तरह परिचित हों। पहले दिन से ही परीक्षण लिखें। गलत कोड का पता लगाने के विपरीत, सही कोड स्वीकार करने वाले 'सकारात्मक' परीक्षणों पर ध्यान केंद्रित करें।

सभी परीक्षण नियमित रूप से चलाएं। आगे बढ़ने से पहले टूटे हुए परीक्षणों को ठीक करें। यह एक गलत परिभाषित भाषा के साथ समाप्त होने के लिए शर्म की बात होगी जो मान्य कोड को स्वीकार नहीं कर सकता है।

एक अच्छा पार्सर बनाएं

पार्सर जनरेटर कई हैं । जो चाहो उठाओ। आप अपने स्वयं के पार्सर को खरोंच से भी लिख सकते हैं, लेकिन यह केवल इसके लायक है अगर आपकी भाषा का वाक्यविन्यास मृत सरल है।

पार्सर को सिंटैक्स त्रुटियों का पता लगाना और रिपोर्ट करना चाहिए। सकारात्मक और नकारात्मक दोनों में बहुत सारे परीक्षण मामले लिखें; भाषा को परिभाषित करते हुए आपके द्वारा लिखे गए कोड का पुन: उपयोग करें।

आपके पार्सर का आउटपुट एक सार सिंटैक्स ट्री है।

यदि आपकी भाषा में मॉड्यूल हैं, तो पार्सर का उत्पादन आपके द्वारा उत्पन्न 'ऑब्जेक्ट कोड' का सबसे सरल प्रतिनिधित्व हो सकता है। एक फ़ाइल को एक पेड़ को डंप करने और इसे जल्दी से वापस लोड करने के बहुत सारे सरल तरीके हैं।

एक अर्थपूर्ण सत्यापनकर्ता बनाएँ

अधिकांश शायद आपकी भाषा वाक्यात्मक रूप से सही निर्माण के लिए अनुमति देती है जो कुछ संदर्भों में कोई मतलब नहीं हो सकता है। एक उदाहरण एक ही चर की डुप्लिकेट घोषणा या गलत प्रकार के पैरामीटर को पारित करना है। मान्यवर पेड़ को देखने वाली ऐसी त्रुटियों का पता लगाएगा।

सत्यापनकर्ता आपकी भाषा में लिखे गए अन्य मॉड्यूल के संदर्भों को भी हल करेगा, इन अन्य मॉड्यूल को लोड करेगा और सत्यापन प्रक्रिया में उपयोग करेगा। उदाहरण के लिए, यह चरण सुनिश्चित करेगा कि किसी मॉड्यूल से किसी फ़ंक्शन को दिए गए मापदंडों की संख्या सही है।

फिर से, बहुत सारे परीक्षण मामले लिखें और चलाएं। तुच्छ मामले स्मार्ट और जटिल के रूप में समस्या निवारण में अपरिहार्य हैं।

कोड जनरेट करें

सबसे सरल तकनीकों का उपयोग करें जो आप जानते हैं। अक्सर किसी भाषा निर्माण (जैसे ifकथन) का सीधे हलके-पैराड्राइज्ड कोड टेम्प्लेट में अनुवाद करना ठीक है , एचटीएमएल टेम्पलेट के विपरीत नहीं।

फिर से, दक्षता को अनदेखा करें और शुद्धता पर ध्यान केंद्रित करें।

एक प्लेटफ़ॉर्म-स्वतंत्र निम्न-स्तरीय VM को लक्षित करें

मुझे लगता है कि आप निम्न स्तर के सामान की उपेक्षा करते हैं जब तक कि आप हार्डवेयर-विशिष्ट विवरणों में रुचि नहीं लेते। ये विवरण गैरी और जटिल हैं।

आपके विकल्प:

  • LLVM: आमतौर पर x86 और ARM के लिए कुशल मशीन कोड जेनरेशन की अनुमति देता है।
  • CLR: लक्ष्य .NET, ज्यादातर x86 / Windows- आधारित; अच्छी JIT है।
  • JVM: जावा वर्ल्ड को लक्षित करता है, काफी मल्टीप्लायर्ड, एक अच्छा JIT है।

अनुकूलन को अनदेखा करें

अनुकूलन कठिन है। लगभग हमेशा अनुकूलन समय से पहले होता है। अयोग्य लेकिन सही कोड उत्पन्न करें। परिणामी कोड को अनुकूलित करने का प्रयास करने से पहले पूरी भाषा को लागू करें।

बेशक, तुच्छ अनुकूलन का परिचय देना ठीक है। लेकिन आपके कंपाइलर के स्थिर होने से पहले किसी भी चालाक, बालों वाले सामान से बचें।

तो क्या?

यदि यह सब सामान आपके लिए बहुत डराने वाला नहीं है, तो कृपया आगे बढ़ें! एक सरल भाषा के लिए, प्रत्येक चरण जितना आप सोच सकते हैं उससे अधिक सरल हो सकता है।

एक प्रोग्राम से 'हैलो वर्ल्ड' देखकर जो आपके कंपाइलर ने बनाया है वह प्रयास के लायक हो सकता है।


45
यह उन सर्वश्रेष्ठ उत्तरों में से एक है, जिन्हें मैंने अभी तक देखा है।
गाहू

11
मुझे लगता है कि आपने प्रश्न का एक हिस्सा याद किया ... ओपी एक बहुत ही मूल संकलक लिखना चाहता था । मुझे लगता है कि आप यहां बहुत बुनियादी चीजों से परे हैं।
मार्को-फिसेट

22
@ मार्को-फ़िसेट , इसके विपरीत, मुझे लगता है कि यह एक उत्कृष्ट जवाब है जो ओपी को एक बहुत ही मूल संकलक करने का तरीका बताता है, जबकि अधिक उन्नत चरणों से बचने और परिभाषित करने के लिए जाल को इंगित करता है।
मुस्कुराते हुए

6
यह पूरे स्टैक एक्सचेंज ब्रह्मांड में मैंने अब तक देखे गए सबसे अच्छे उत्तरों में से एक है। कुडोस!
आंद्रे टेरा

3
एक प्रोग्राम से 'हैलो वर्ल्ड' देखकर जो आपके कंपाइलर ने बनाया है वह प्रयास के लायक हो सकता है। -
INDEED

27

जैक क्रैंशव के चलो एक संकलक का निर्माण करते हैं, जबकि अधूरा, एक प्रचलित पठनीय परिचय और ट्यूटोरियल है।

सरल कंपाइलर निर्माण की मूल बातें पर निकलॉस विर्थ का संकलक निर्माण एक बहुत अच्छी पाठ्यपुस्तक है। वह शीर्ष-नीचे पुनरावर्ती वंश पर ध्यान केंद्रित करता है, जो, चलो इसका सामना करते हैं, जो कि lex / yacc या flex / bison की तुलना में बहुत आसान है। मूल PASCAL संकलक जो उसके समूह ने लिखा था, इस तरह से किया गया था।

अन्य लोगों ने ड्रैगन की विभिन्न पुस्तकों का उल्लेख किया है।


1
पास्कल के बारे में एक अच्छी बात यह है कि उपयोग किए जाने से पहले सब कुछ परिभाषित या घोषित किया जाना है। इसलिए इसे एक पास में संकलित किया जा सकता है। टर्बो पास्कल 3.0 ऐसा ही एक उदाहरण है, और यहां के इंटर्नल के बारे में बहुत सारे दस्तावेज हैं
tcrosley

1
PASCAL को विशेष रूप से वन-पास संकलन और ध्यान में रखते हुए बनाया गया था। Wirth के कंपाइलर बुक में मल्टीपर्स कंपाइलर्स का उल्लेख है, और कहा गया है कि उन्हें PL / I कंपाइलर के बारे में पता था, जिसमें 70 (हाँ, सत्तर) पास थे।
जॉन आर। स्ट्रोह्म

ALGOL में तारीखों का उपयोग करने से पहले अनिवार्य घोषणा। टोनी होरे ने ALGOL समिति द्वारा अपने कान वापस ले लिए जब उन्होंने डिफ़ॉल्ट प्रकार के नियमों को जोड़ने का सुझाव देने का प्रयास किया, जो कि FORTRAN के समान था। वे पहले से ही जानते हैं कि यह समस्याएँ पैदा कर सकता है, नाम और डिफ़ॉल्ट नियमों में टंकण त्रुटि के साथ दिलचस्प बग पैदा कर सकता है।
बजे जॉन आर। स्ट्रोह्म ऑक्ट

1
यहाँ मूल लेखक द्वारा स्वयं पुस्तक का एक अधिक अद्यतन और समाप्त संस्करण है: stack.nl/~marcov/compiler.pdf कृपया अपना उत्तर संपादित करें और इसे जोड़ें :)
sonnet

16

मैं वास्तव में Brainfuck के लिए एक कंपाइलर लिखने के साथ शुरू करूँगा । यह प्रोग्राम करने के लिए एक काफी अप्रिय भाषा है, लेकिन इसे लागू करने के लिए केवल 8 निर्देश हैं। यह उतना ही सरल है जितना कि आप संभवतः प्राप्त कर सकते हैं और वाक्यविन्यास ऑफ-पुट ढूंढने पर इसमें शामिल कमांड के लिए बराबर सी निर्देश हैं।


7
लेकिन फिर, एक बार जब आपका बीएफ कम्पाइलर तैयार हो जाता है, तो आपको अपना कोड उसमें लिखना होगा :(
500 - आंतरिक सर्वर त्रुटि

@ 500-इंटरनलसर्वर्रर सी सबसेट विधि का उपयोग करें
विश्व इंजीनियर

12

यदि आप वास्तव में मशीन पठनीय कोड लिखना चाहते हैं और केवल वर्चुअल मशीन को लक्षित नहीं करना है, तो आपको इंटेल मैनुअल पढ़ना होगा और समझना होगा

  • ए। जोड़ने और लोड हो रहा है निष्पादन योग्य कोड

  • ख। COFF और PE प्रारूप (विंडोज़ के लिए), वैकल्पिक रूप से लिनक्स के लिए ELF प्रारूप समझें)

  • सी। .COM फ़ाइल स्वरूपों को समझें (पीई से आसान)
  • घ। असेम्बलर्स को समझें
  • इ। कंपाइलर्स में कंपाइलर्स और कोड जनरेशन इंजन को समझें।

कहा से बहुत कठिन है। मैं आपको एक प्रारंभिक बिंदु के रूप में सी ++ में कंपाइलर्स और इंटरप्रिटेटर पढ़ने का सुझाव देता हूं (रोनाल्ड मैक द्वारा)। वैकल्पिक रूप से, Crenshaw द्वारा "कंपाइलर बनाने देता है" ठीक है।

यदि आप ऐसा नहीं करना चाहते हैं, तो आप अपना खुद का VM लिख सकते हैं और उस VM को लक्षित कोड जनरेटर लिख सकते हैं।

सुझाव: जानें फ्लेक्स और बाइसन सबसे पहले। फिर अपना कंपाइलर / वीएम बनाने के लिए आगे बढ़ें।

शुभ लाभ!


7
मुझे लगता है कि एलएलवीएम को लक्षित करना और वास्तविक मशीन कोड आज उपलब्ध सर्वोत्तम तरीके के बारे में नहीं है।
9000

मैं मानता हूं, मैं अभी कुछ समय के लिए एलएलवीएम का पालन कर रहा हूं और मुझे यह कहना चाहिए कि मैंने इसे लक्ष्य करने के लिए आवश्यक प्रोग्रामर प्रयास के संदर्भ में वर्षों में सबसे अच्छी चीजों में से एक देखा था!
अनिकेत इंजी

2
MIPS के बारे में क्या है और इसे चलाने के लिए स्पिम का उपयोग करें ? या मिक्स ?

@MichaelT मैंने MIPS का उपयोग नहीं किया है लेकिन मुझे यकीन है कि यह अच्छा होगा।
अनिकेत इंग

@PrototypStark RISC इंस्ट्रक्शन सेट, वास्तविक विश्व प्रोसेसर जो आज भी उपयोग में है (यह समझकर कि यह एम्बेडेड सिस्टम में अनुवाद योग्य होगा)। पूरा निर्देश सेट विकिपीडिया पर है । नेट पर देखते हुए, बहुत सारे उदाहरण हैं और इसका उपयोग कई शैक्षणिक कक्षाओं में मशीन भाषा प्रोग्रामिंग के लिए एक लक्ष्य के रूप में किया जाता है। SO पर इस पर थोड़ी गतिविधि होती है ।

10

सरल संकलक के लिए DIY दृष्टिकोण इस तरह दिखाई दे सकता है (कम से कम मेरी यूनि परियोजना कैसी दिखती है):

  1. भाषा के व्याकरण को परिभाषित करें। विषय से मुक्त।
  2. यदि आपका व्याकरण अभी तक LL (1) नहीं है, तो अभी करें। ध्यान दें, कि सादे सीएफ व्याकरण में ठीक लगने वाले कुछ नियम बदसूरत हो सकते हैं। शायद आपकी भाषा बहुत जटिल है ...
  3. लेक्सर लिखें जो पाठ की धारा को टोकन (शब्द, संख्या, शाब्दिक) में काटता है।
  4. अपने व्याकरण के लिए शीर्ष-नीचे पुनरावर्ती वंश पार्सर लिखें, जो इनपुट को स्वीकार या अस्वीकार करता है।
  5. अपने पार्सर में वाक्य रचना पेड़ पीढ़ी जोड़ें।
  6. वाक्य रचना पेड़ से मशीन कोड जनरेटर लिखें।
  7. लाभ और बीयर, वैकल्पिक रूप से आप सोचना शुरू कर सकते हैं कि कैसे बेहतर पार्सर करें या बेहतर कोड उत्पन्न करें।

प्रत्येक चरण का विस्तार से वर्णन करते हुए बहुत साहित्य होना चाहिए।


7 वां बिंदु ओपी के बारे में क्या पूछ रहा है।
फ्लोरियन मार्गाइन

7
1-5 अप्रासंगिक हैं और इस तरह के करीब ध्यान देने योग्य नहीं हैं। 6 सबसे दिलचस्प हिस्सा है। दुर्भाग्य से, अधिकांश किताबें एक ही पैटर्न का पालन करती हैं, कुख्यात ड्रैगन बुक के बाद, पार्सिंग और कोड छोड़ने पर बहुत अधिक ध्यान देना, दायरे से बाहर हो जाता है।
एसके-तर्क
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.