自己動手寫basic解釋器
刺蝟@http://blog.csdn.net/littlehedgehog
注: 文章basic解釋源碼摘自梁肇新先生的《編程高手箴言》(據他所說這個代碼也是網上摘錄的),源碼解讀參考《java編程藝術》。《java編程藝術》裏面自然是java版了(可能旭哥更加適合點兒),我這裏還是解讀的C版basic解釋器代碼。
上次我們把程序裝載入內存,這裏我們開始做詞法分析了。hoho~ 開始
一開始我們先再來回顧下:當我們的解釋器在執行時,每次讀入一條語句,並且根據這條語句執行特定的操作;然後再讀入下一條語句,依此執行下去。也就是說解釋器執行時,每次從程序的源代碼中讀入一個標識符。如果讀入的是關鍵字,解釋器就按照該關鍵字的要求執行規定的操作。舉例來說,當解釋器讀入一個 PRINT後,它將打印PRINT之後的字符;當讀入一個GOSUB時,它就執行指定的子程序。在到達程序的結尾之前,這個過程將反覆進行。
按照上面的分析,我們所做的第一步就是要讀取標識符,要一個單詞一個數字的區分,比如print要作爲一個關鍵字讀入,2345要作爲一個數字讀入,而a=3這裏要作爲三個標識符讀入,分別是變量a、賦值符號=以及數值3。看來我們在讀取標識符時,我們還需要給標識符分類。
- #define DELIMITER 1 //分界符 比如逗號 分號 等號 都屬於這之列
- #define VARIABLE 2 //變量
- #define NUMBER 3 //數字
- #define COMMAND 4 //關鍵字(命令)
- #define STRING 5 //字符串(這個比較特殊 既包括關鍵字 又包括常量字符串)
- #define QUOTE 6 //常量字符串 比如"hello world"
- /* get a token
- * 從basic源碼中讀取一個符號(token) 並且區分出符號類型
- * 我們把獲取的符號放在token字符數組裏面 在token_type中設置符號類型 在tok中設置
- */
- get_token()
- {
- register char *temp;
- token_type = 0; //記錄標識符的類型
- tok = 0; //記錄關鍵字
- temp = token;
- /* */
- if (*prog == '/0') { /* 文件結束 */
- *token = 0;
- tok = FINISHED; //設置文件結束符號
- return (token_type = DELIMITER); /* 標號類型設置爲分界符*/
- }
- while (iswhite(*prog)) ++prog; /* 這裏主要是除去字符串裏面的空格 */
- if (*prog == '/r')
- { /* 換行符處理 */
- ++prog;++prog; //這裏跳過的字符"/r/n"
- tok = EOL; //設置一行結束的標識 EOL(End Of Line)
- *token = '/r';token[1] = '/n';token[2] = 0;
- return (token_type = DELIMITER); //設置爲分界符
- }
- if (strchr("+-*^/%=;(),><",*prog)) /* 在"+-*^/%=;(),><"查找prog */
- {
- *temp = *prog; //把這個分界符拷貝到token中
- prog++; /* advance to next position */
- temp++;
- *temp=0; //最後補上零 這樣讓token形成字符串
- return (token_type = DELIMITER);
- }
- /* 這裏'"'表明後面的是字符串 basic中常常出現的情況是 print "hello world"
- * 下面的例子就以此舉例了
- */
- if (*prog == '"')
- {
- prog++; //prog指向了字符'h',注意這裏已經進入了字符串
- while (*prog!='"'&&*prog!='/r') *temp++=*prog++; //只要字符串沒結束(prog遇到雙引號),或者是沒換行('/r') 我們就要把原代碼拷貝到token中
- if (*prog=='/r') serror(1); //字符串不能換行
- prog++;*temp=0; //prog已經走出字符串啦,照樣的token要補上結束符
- return (token_type = QUOTE); //token_type爲字符串
- }
- /* 數字處理
- * 唯一要注意的是我們現在的數字是字符串形式的...
- */
- if (isdigit(*prog))
- {
- while (!isdelim(*prog)) *temp++=*prog++; //如果沒有分界符,拷貝之
- *temp = '/0';
- return (token_type = NUMBER);
- }
- /* 處理字符 仍舉例 print "hello world"
- * 這裏我們得到print
- * 注意該代碼塊沒有馬上返回
- */
- if (isalpha(*prog))
- {
- while (!isdelim(*prog)) *temp++=*prog++; //沒有分界符拷貝之
- token_type = STRING; //string類型 這只是一箇中間類型 具體要劃分爲變量、關鍵字 你會想爲什麼不會是我們打印的常量字符串呢 因爲上面我們已經處理了
- }
- *temp = '/0'; //不過這一句放在外面 其實沒多大意義 前面的情況都return了, 剩下一個孤零零 因爲接下來我們還要處理這玩意兒
- /* 查看究竟是個命令呢,還是一個變量,拭目以待... */
- if (token_type == STRING)
- {
- tok = look_up(token); /* 在變量hash表中查之 */
- if (!tok)
- token_type = VARIABLE; //變量
- else
- token_type = COMMAND; //關鍵字
- }
- return token_type;
- }
get_token裏面還有一些小函數,這裏我們同樣也羅列出來。裏面嵌套的函數我都註釋了的,都比較好懂,注意點我都有解釋
- /* 回滾 */
- void putback()
- {
- char *t;
- t = token;
- for (;*t;t++) prog--; //
- }
- /* 這丫是幫我們在關鍵字table表裏面搜索是否包含當前字符串 */
- look_up(char *s)
- {
- register int i,j;
- char *p;
- /* 命令因爲我們全都用小寫字符 這裏不得不轉換了 */
- p = s;
- while (*p) { *p = tolower(*p); p++; }
- /* 順序查找 呃 這個是個tiny 所以效率不太考慮了
- * 想想如果我們關鍵字很多的話 應該做一個hash表
- * 這裏如果查找成功返回一個標號 失敗返回0 請參閱table結構
- */
- for (i=0;*table[i].command;i++)
- if (!strcmp(table[i].command,s))
- return table[i].tok;
- return 0;
- }
- /* 查字符c是否是分界符
- * 恩 有一點要注意 比如我們的數字是909 那麼這裏傳進來的c的ASCII碼可不是909 應該是9+'0','0',9+'0'*/
- isdelim(char c)
- {
- if (strchr(";,+-<>/*%^=() ",c)||c==9||c=='/r'||c==0) //所以這裏c==9 表示'/t' 0自然是'/0'
- return 1;
- return 0;
- }
- /* 查是不是"空白",就是空格和tab鍵*/
- iswhite (char c)
- {
- if (c==' '||c=='/t') return 1;
- else return 0;
- }
至此 標識符處理我們全部完成,工作完成了一大塊,剩下就是關鍵字處理了,裏面涉及到語句邏輯。