自己動手寫basic解釋器
刺蝟@http://blog.csdn.net/littlehedgehog
注: 文章basic解釋源碼摘自梁肇新先生的《編程高手箴言》(據他所說這個代碼也是網上摘錄的),源碼解讀參考《java編程藝術》。《java編程藝術》裏面自然是java版了(可能旭哥更加適合點兒),我這裏還是解讀的C版basic解釋器代碼。
終於把這個basic解釋器主幹源碼解述完了。其實說來這個解釋器實際意義並不大,但是通過閱讀源代碼我們可以深一步領悟程序語言執行內部機理。我覺得特別值得提的三點:
1、通過prog指針模擬CPU中的eip寄存器,巧妙地借鑑了世界最頂尖級的硬件工程師在處理程序運行問題上的思路。
2、模擬函數調用棧,這個在go_sub函數中得到了淋漓盡致地體現。
3、p_buf就相當於計算機內存,或者說是程序運行空間的text段,26個變量就相當於data段,而我們的模擬棧恰好就是程序中的棧空間。
最後我把主程序的代碼貼出來,方便兄弟夥們:
- #include <stdio.h>
- #include <setjmp.h>
- #include <math.h>
- #include <ctype.h>
- #include <stdlib.h>
- #define NUM_LAB 100
- #define LAB_LEN 10
- #define FOR_NEST 25
- #define SUB_NEST 25
- #define PROG_SIZE 10000
- #define DELIMITER 1
- #define VARIABLE 2
- #define NUMBER 3
- #define COMMAND 4
- #define STRING 5
- #define QUOTE 6
- #define PRINT 1
- #define INPUT 2
- #define IF 3
- #define THEN 4
- #define FOR 5
- #define NEXT 6
- #define TO 7
- #define GOTO 8
- #define EOL 9
- #define FINISHED 10
- #define GOSUB 11
- #define RETURN 12
- #define END 13
- char *prog; /* holds expression to be analyzed */
- jmp_buf e_buf; /* hold environment for longjmp() */
- int variables[26]= { /* 26 user variables,A-Z */
- 0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0,0,0,0,0,
- 0,0,0,0,0,0
- };
- struct commands { /* keyword lookup table */
- char command[20];
- char tok;
- } table[] = { /* command must be entered lowercase */
- "print",PRINT, /* in this table */
- "input",INPUT,
- "if",IF,
- "then",THEN,
- "goto",GOTO,
- "for",FOR,
- "next",NEXT,
- "to",TO,
- "gosub",GOSUB,
- "return",RETURN,
- "end",END,
- NULL,END
- };
- char token[80]; //注意token是數組類型
- char token_type,tok;
- struct label {
- char name [LAB_LEN];
- char *p; /* point to place to go in source */
- };
- struct label label_table[NUM_LAB];
- char *find_label(),*gpop();
- struct for_stack {
- int var; /* counter variable */
- int target; /* target value */
- char *loc;
- } fstack[FOR_NEST]; /* stack for FOR/NEXT loop */
- struct for_stack fpop();
- char *gstack[SUB_NEST]; /* stack for gosub */
- int ftos; /* index to top of FOR stack */
- int gtos; /* index to top of GOSUB */
- void print(),scan_labels(),find_eol(),exec_goto();
- void gosub(),greturn(),gpush(),label_init(),fpush();
- /* Load a program */
- load_program (char *p,char *fname)
- {
- FILE *fp;
- int i=0;
- if (!(fp=fopen(fname,"rb"))) return 0;
- i=0;
- do {
- *p = getc(fp);
- p++;i++;
- } while (!feof(fp)&&i<PROG_SIZE);
- *(p-2) = '/0'; /* null terminate the program */
- fclose (fp);
- return 1;
- }
- /* 給變量賦值 比如 a=3
- * 注意這裏爲了簡化起見,我們的變量就設置爲26個字母
- */
- assignment()
- {
- int var,value;
- /* getthe variable name */
- get_token();
- if (!isalpha(*token)) //因爲變量我們用字母代替 所以必定是字母類型
- {
- serror(4);
- return;
- }
- var = toupper(*token)-'A'; //轉化爲大寫字母 然後減去'A' 這樣讓變量在hash表中有了座次 比如A減去A爲0 這樣A字符變量在變量hash表中第一個位置
- /* get the equals sign
- * 這裏我們取a=3 中間的等號*/
- get_token();
- if (*token!='=') //既然賦值麼 肯定有等號了
- {
- serror(3);
- return;
- }
- /* a=3 等號取走了 我們來取數值 */
- get_exp(&value);
- /* 把我們取到的變量 比如a 值爲3 存放在hash表中 */
- variables[var] = value;
- }
- /* execute a simple version of the BASIC PRINT statement
- * 執行打印 這裏我們還是舉例說明*/
- void print()
- {
- int answer;
- int len=0,spaces;
- char last_delim;
- do {
- get_token(); /* get next list item */
- if (tok==EOL||tok==FINISHED) break; //如果取到的符號是一行結束或者文件結束 自然的打印結束
- //BASIC 中print一般有兩種用法 第二種就是print "hello world" 打印字符串
- if (token_type==QUOTE)
- {
- printf ("%s",token);
- len+=strlen(token);
- get_token(); //注意我們打印了後又取了一次符號
- }
- else //打印變量的
- {
- putback();
- get_exp(&answer);
- get_token(); //注意我們打印了後又取了一次符號
- len += printf ("%d",answer);
- }
- last_delim = *token;
- /* Basic 有兩種打印間隔標識
- * 比如 print a,b 表示按標準格式打印
- * 而print a;b 表示按照緊湊格式打印
- * 所謂標準格式簡單來講就是間隔大點兒 緊湊自然間隔小點兒
- */
- if (*token==',')
- {
- /* compute number of move to next tab */
- spaces = 8-(len%8);
- len += spaces; /* add in the tabbing position */
- while (spaces) {
- printf (" ");
- spaces--;
- }
- }
- else if (*token==';')
- printf (" ");
- else if (tok != EOL && tok != FINISHED) serror (0); //print a,b 打完一次後 要麼是逗號、分號 要麼就是行結束或者文件結束 如果四者不居其一 必然錯了
- } while (*token==';'||*token==','); //例如 print a,b,c 如果token是逗號、分號 那麼表示後面還有打印 繼續來
- /* 當處於行結束或者文件結束 那麼前一次分界符不能是;或者,
- * 示例 如果 "print a," 這個明顯是語法錯誤 a後面不應該要逗號
- * 那麼打印完a取出token是逗號 我們賦值給last_delim 繼續循環
- * 下一個是行結束 跳出打印但是檢驗出last_delim是逗號 出錯 */
- if (tok==EOL||tok==FINISHED)
- {
- if (last_delim != ';' && last_delim != ',') printf ("/n");
- }
- else serror(0); /* error is not, or ; */
- }
- /* 搜索所有標籤
- * 這個函數可以說是basic裏面的預處理
- * 我們搜索源代碼 找出裏面的標籤 將其存入標籤表
- * 所謂標籤label 其實C語言也有 不過一般不常用 因爲label多半和goto一起出現的 而在結構化程序設計中 goto出現被人認爲是絕對不能的
- * 不過內核中goto卻是常常出現
- * 下面這個函數最大的困惑判斷標籤的特徵類型 我們設置爲數字 要知道這裏標籤我們都是設置爲數字的
- * 但是如何把標籤與普通數值分開呢?
- */
- void scan_labels()
- {
- int addr;
- char *temp;
- label_init(); /* zero all labels */
- temp = prog; /* save poiter to top of program */
- /* 如果源代碼中第一個是個數字的話 存入標籤表中 不過說實話 我沒理解這個有什麼意義*/
- get_token();
- if (token_type==NUMBER)
- {
- strcpy (label_table[0].name,token);
- label_table[0].p=prog;
- }
- find_eol(); //提行
- do {
- get_token();
- if (token_type==NUMBER) //如果是數字 這裏是一行開頭 開頭的數字不可能是一個數值
- {
- addr = get_next_label(token);
- if (addr==-1||addr==-2)
- {
- (addr==-1) ? serror(5):serror(6);
- }
- strcpy (label_table[addr].name,token);
- label_table[addr].p = prog; /* current point in program */
- }
- /* if not on a blank line , find next line */
- if (tok!=EOL) find_eol();
- } while (tok!=FINISHED);
- prog = temp; /* restore to original */
- }
- /* find the start of next line */
- void find_eol()
- {
- while (*prog!='/n'&&*prog!='/0') ++prog;
- if (*prog) prog++;
- }
- /* return index of next free posion in the label array
- -1 is returned if the array is full.
- -2 is returned when duplicate label is found.
- */
- get_next_label(char *s)
- {
- register int t;
- for (t=0;t<NUM_LAB;++t) {
- if (label_table[t].name[0]==0) return t;
- if (!strcmp(label_table[t].name,s)) return -2; /* dup */
- }
- return -1;
- }
- /* find location of given label. A null is returned if
- label is not found; ohtherwise a pointer to the position
- of the label is returned.
- */
- char *find_label(char *s)
- {
- register int t;
- for (t=0;t<NUM_LAB;++t)
- if (!strcmp(label_table[t].name,s)) return label_table[t].p;
- return '/0'; /* error condition */
- }
- /* execute a GOTO statement.
- * goto一般形式即是 goto label
- */
- void exec_goto()
- {
- char *loc;
- get_token(); /* 這裏獲取標號,即是標籤內容 */
- loc = find_label (token); //標籤是爲跳轉所用,所以獲取標籤後我們馬上要想辦法得到標籤所代表地址
- if (loc=='/0')
- serror(7); /* 出錯 */
- else prog=loc; /* 重新 設置prog指針 指出了下一個我們運行的地址 我們得完全聽他的*/
- }
- /* initialize the array that holds the labels.
- by convention , a null label name indicates that
- array posiiton is unused.
- */
- void label_init()
- {
- register int t;
- for (t=0;t<NUM_LAB;++t) label_table[t].name[0]='/0';
- }
- /* execute an IF statement
- * 執行if語句
- */
- void exec_if()
- {
- int x,y,cond;
- char op;
- /* 這裏我們只是處理一個簡單的if 就是if (x operator y) */
- get_exp(&x); /* 獲取操作符左邊數值 */
- get_token(); /* 獲取操作符 "比較符" */
- if (!strcmp("<>",*token)) //這裏有點兒問題 一個字符串不可能跟一個字符比較吧
- {
- serror(0); /* not a leagal oprator */
- return;
- }
- op = *token;
- get_exp(&y); /* 操作符右邊 */
- /* determine the outcome */
- cond = 0;
- switch(op) {
- case '<':
- if (x<y) cond=1;
- break;
- case '>':
- if (x>y) cond=1;
- break;
- case '==': //這裏也是有點兒問題,op是字符類型 怎麼會可能會是'==',而且好笑的是basic沒有這個符號
- if (x==y) cond=1;
- break;
- }
- if (cond) { /* is true so process target of IF */
- get_token();
- if (tok != THEN) { //if 後面會連上then 所以有if沒then是錯誤的
- serror(8);
- return;
- } /* else program execution starts on next line */
- }
- else find_eol(); /* find start of next line */
- }
- /* execute a FOR loop
- * for 循環 其主要格式 文章第一篇已經給出
- * for i=1 to 10
- * next i
- * 下面就引用此例了
- */
- void exec_for()
- {
- struct for_stack i; //申請一個棧元素 到時候加入
- int value;
- get_token(); /* 獲取標號 這裏獲取到變量i */
- if (!isalpha(*token)) //變量必定是字符型
- {
- serror(4);
- return;
- }
- i.var = toupper(*token) - 'A'; /* 我們是把變量放在hash表中的 所以這裏來計算變量在hash表中位置 */
- get_token(); /* 這裏得到了等號 */
- if (*token!='=')
- {
- serror(3);
- return;
- }
- get_exp(&value); /* 初始值 比如這裏是1 */
- variables[i.var]=value; //這裏把初始值放在變量數組中
- get_token();
- if (tok != TO) serror(9); /* 讀取to單詞 */
- get_exp(&i.target); /* 取得最終要達到的數值 比如這裏是10 */
- /* if loop can execute at least once, push into on stack */
- if (value<=i.target) {
- i.loc = prog; //記錄要執行的語句 這裏是for循環的裏面要執行的語句
- fpush(i); //壓棧
- }
- else /* otherwise, skip loop code altogether */
- while (tok!=NEXT) get_token(); //每到next之前 都輸入for循環要執行的語句 所以一直執行
- }
- /* execute a NEXT statement */
- void next()
- {
- struct for_stack i;
- i = fpop(); /*read the loop info */
- variables[i.var]++; /* increment control variable */
- if (variables[i.var]>i.target) return; /* all done */
- fpush(i); /* otherwise,return the info */
- prog = i.loc; /* loop */
- }
- /* push function for the FOR stack */
- void fpush(struct for_stack i)
- {
- if (ftos>FOR_NEST)
- serror(10);
- fstack[ftos]=i;
- ftos++;
- }
- struct for_stack fpop()
- {
- ftos--;
- if (ftos<0) serror(11);
- return (fstack[ftos]);
- }
- /* exec a simple form of BASIC INPUT command */
- void input()
- {
- char str[80],var;
- int i;
- get_token(); /* see if prompt string id=s present */
- if (token_type == QUOTE) {
- printf (token); /* if so , print it and check for command */
- get_token();
- if (*token != ',') serror(1);
- get_token();
- }
- else printf ("? "); /* otherwise, prompt with / */
- var = toupper(*token) - 'A'; /* get the input var */
- scanf ("%d",&i); /* read input */
- variables[var] = i; /* store it */
- }
- /* execute a GOSUB command
- * 這個類似c語言中的函數調用 */
- void gosub()
- {
- char *loc;
- get_token();
- /* find the label to call */
- loc = find_label(token);
- if (loc=='/0')
- serror(7); /* label not defined */
- else
- {
- gpush(prog); /* 當前執行的地址壓棧 */
- prog = loc; /* 重新把要執行的地址賦值給prog */
- }
- }
- /* return from GOSUB */
- void greturn()
- {
- prog = gpop();
- }
- /* GOSUB stack push function */
- void gpush(char *s)
- {
- gtos++;
- if (gtos==SUB_NEST)
- {
- serror(12);
- return;
- }
- gstack[gtos] = s;
- }
- /* GOSUB stack pop function */
- char *gpop()
- {
- if (gtos==0) {
- serror(13);
- return 0;
- }
- return gstack[gtos--];
- }
- main (int argc,char *argv[])
- {
- char in[80];
- int answer;
- char *p_buf;
- char *t;
- if (argc!=2) {
- printf ("usage: run <filename>/n");
- exit (1);
- }
- /* allocate memory for the program */
- if (!(p_buf=(char *)malloc(PROG_SIZE))) {
- printf ("allocation failure");
- exit (1);
- }
- /* load the program to execute */
- if (!load_program(p_buf,argv[1])) exit(1);
- if (setjmp(e_buf)) exit(1); /* initialize the long jump */
- prog = p_buf;
- scan_labels(); /* 搜索所有的標籤 */
- ftos = 0; /* 初始化棧 這個是爲for循環作準備的 */
- gtos = 0; /* 初始化棧 這個是爲gosub作準備的 */
- do {
- token_type = get_token();
- /* 如果當前是變量 */
- if (token_type==VARIABLE) {
- putback(); /* 回退prog指針到變量前 */
- assignment(); /* 賦值 */
- }
- else /* 除了變量那就是關鍵字了 可能有同學會問 呃 那個比如一個數字怎麼沒考慮 請想想一個數字怎麼會單獨出現 */
- switch (tok) {
- case PRINT:
- print();
- break;
- case GOTO:
- exec_goto();
- break;
- case IF:
- exec_if();
- break;
- case FOR:
- exec_for();
- break;
- case NEXT:
- next();
- break;
- case INPUT:
- input();
- break;
- case GOSUB:
- gosub();
- break;
- case RETURN:
- greturn();
- break;
- case END:
- exit(0);
- }
- }while (tok != FINISHED);
- }