自己动手写basic解释器
刺猬@http://blog.csdn.net/littlehedgehog
注: 文章basic解释源码摘自梁肇新先生的《编程高手箴言》(据他所说这个代码也是网上摘录的),源码解读参考《java编程艺术》。《java编程艺术》里面自然是java版了(可能旭哥更加适合点儿),我这里还是解读的C版basic解释器代码。
是所谓先来后到,我们还是按照主函数出现的顺序,依次分析逻辑函数。
1、print
print函数主要的关注两点,第一点是打印格式,第二点是打印方式。比如我举两例:
- print a
- print "hello world " '要分清楚打印变量还是打印字符串 这是打印方式的问题
- print a,b
- print a;b '要分清楚打印格式,注意一个是分号一个是逗号 两者有区别!
- /* 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 ; */
- }
2、goto
关于goto的争论还是上个世纪计算机洪荒时代,争论的结果是大量程序员在程序中尽量回避goto这个关键字,其实合理的使用goto不但提升效率,还能增加代码可读性。
- /* 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指针 指出了下一个我们运行的地址 我们得完全听他的*/
- }
label表在我们最开始处理程序的时候已经完成,我理解为"预处理",代码如下,总的思路是扫描一遍代码,搜索标号。 注意 在我们这个basic中,我们标号的规定是必须在一行开头,并且是数字. 这个例子可以参照文章第一部分我举的那个basic例子,其实我们可以看到现实中确实有这样basic规定,比如smallbasic。
标签初始化:scan_labels():
- /* 搜索所有标签
- * 这个函数可以说是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 */
- /* 如果源代码中第一个token是个数字的话 存入标签表中 表明是个标签*/
- 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; /* 标签干什么用的 就是方便跳转阿 所以我们要记录标签所在地址 */
- }
- /* if not on a blank line , find next line */
- if (tok!=EOL) find_eol();
- } while (tok!=FINISHED);
- prog = temp; /* restore to original */
- }
if这个函数貌似有点儿问题。我直接加在注释里的,如果我理解错了,还请各位指出:
- /* 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 */
- }