आप स्मृति में पेड़ स्टोर कर सकते हैं या आप सीधे आवश्यक उत्पादन कोड का उत्पादन कर सकते हैं। इंटरमीडिएट फॉर्म को संग्रहीत करना आमतौर पर आउटपुट उत्पन्न करने से पहले उच्च स्तर पर कोड पर कुछ प्रोसेसिंग करने में सक्षम होता है।
उदाहरण के लिए आपके मामले में यह खोजना आसान होगा कि आपकी अभिव्यक्ति में कोई चर नहीं है और इसलिए परिणाम एक निश्चित संख्या है। एक समय में केवल एक नोड पर देखकर यह संभव नहीं है। अधिक स्पष्ट होने के लिए यदि "2 *" को देखने के बाद आप कुछ कोड की गणना करने के लिए मशीन कोड जेनरेट करते हैं तो यह कोड बर्बाद हो जाता है जब दूसरा भाग उदाहरण के लिए "3" होता है क्योंकि आपका प्रोग्राम "3" की गणना करेगा और फिर गणना करेगा उस के हर बार जबकि केवल "6" लोड करना समकक्ष लेकिन छोटा और तेज़ होगा।
यदि आप मशीन कोड जेनरेट करना चाहते हैं तो आपको पहले यह पता होना चाहिए कि किस प्रकार की मशीन जेनरेट की जा रही है ... सबसे सरल मॉडल एक स्टैक-आधारित दृष्टिकोण है। इस मामले में आपको कोई पंजीकरण आवंटन तर्क की आवश्यकता नहीं है और इंटरमीडिएट प्रतिनिधित्व के बिना सीधे मशीन कोड को संकलित करना आसान है। इस छोटे से उदाहरण पर विचार करें जो केवल पूर्णांक, चार संचालन, अनियमित अस्वीकृति और चर को संभालता है ... आप देखेंगे कि कोई भी डेटा संरचना बिल्कुल उपयोग नहीं की जाती है: स्रोत कोड वर्ण पढ़े जाते हैं और मशीन निर्देश आउटपुट पर लिखे जाते हैं ...
#include <stdio.h>
#include <stdlib.h>
void error(const char *what)
{
fprintf(stderr, "ERROR: %s\n", what);
exit(1);
}
void compileLiteral(const char *& s)
{
int v = 0;
while (*s >= '0' && *s <= '9')
{
v = v*10 + *s++ - '0';
}
printf(" mov eax, %i\n", v);
}
void compileSymbol(const char *& s)
{
printf(" mov eax, dword ptr ");
while ((*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z') ||
(*s >= '0' && *s <= '9') ||
(*s == '_'))
{
putchar(*s++);
}
printf("\n");
}
void compileExpression(const char *&);
void compileTerm(const char *& s)
{
if (*s >= '0' && *s <= '9') {
// Number
compileLiteral(s);
} else if ((*s >= 'a' && *s <= 'z') ||
(*s >= 'A' && *s <= 'Z') ||
(*s == '_')) {
// Variable
compileSymbol(s);
} else if (*s == '-') {
// Unary negation
s++;
compileTerm(s);
printf(" neg eax\n");
} else if (*s == '(') {
// Parenthesized sub-expression
s++;
compileExpression(s);
if (*s != ')')
error("')' expected");
s++;
} else {
error("Syntax error");
}
}
void compileMulDiv(const char *& s)
{
compileTerm(s);
for (;;)
{
if (*s == '*') {
s++;
printf(" push eax\n");
compileTerm(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" imul ebx\n");
} else if (*s == '/') {
s++;
printf(" push eax\n");
compileTerm(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" idiv ebx\n");
} else break;
}
}
void compileAddSub(const char *& s)
{
compileMulDiv(s);
for (;;)
{
if (*s == '+') {
s++;
printf(" push eax\n");
compileMulDiv(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" add eax, ebx\n");
} else if (*s == '-') {
s++;
printf(" push eax\n");
compileMulDiv(s);
printf(" mov ebx, eax\n");
printf(" pop eax\n");
printf(" sub eax, ebx\n");
} else break;
}
}
void compileExpression(const char *& s)
{
compileAddSub(s);
}
int main(int argc, const char *argv[])
{
if (argc != 2) error("Syntax: simple-compiler <expr>\n");
compileExpression(argv[1]);
return 0;
}
उदाहरण के लिए 1+y*(-3+x)
साथ संकलक चल इनपुट के रूप में आप उत्पादन
mov eax, 1
push eax
mov eax, dword ptr y
push eax
mov eax, 3
neg eax
push eax
mov eax, dword ptr x
mov ebx, eax
pop eax
add eax, ebx
mov ebx, eax
pop eax
imul ebx
mov ebx, eax
pop eax
add eax, ebx
के रूप में मिलता है हालांकि compilers लेखन के इस दृष्टिकोण एक अनुकूलन संकलक करने के लिए अच्छी तरह से बड़े पैमाने नहीं है।
आउटपुट चरण में "पेफोल" ऑप्टिमाइज़र जोड़कर कुछ अनुकूलन प्राप्त करना संभव है, लेकिन कई उपयोगी अनुकूलन केवल उच्च बिंदु दृश्य से कोड को देख सकते हैं।
इसके अलावा भी नंगे मशीन कोड पीढ़ी को और कोड देखकर लाभ हो सकता है, उदाहरण के लिए यह तय करने के लिए कि कौन सा रजिस्टर निर्दिष्ट करता है या यह तय करने के लिए कि कौन से संभावित असेंबलर कार्यान्वयन एक विशिष्ट कोड पैटर्न के लिए सुविधाजनक होंगे।
उदाहरण के लिए एक ही अभिव्यक्ति
mov eax, dword ptr x
sub eax, 3
imul dword ptr y
inc eax