自己动手写basic解释器
刺猬@http://blog.youkuaiyun.com/littlehedgehog
注: 文章basic解释源码摘自梁肇新先生的《编程高手箴言》(据他所说这个代码也是网上摘录的),源码解读参考《java编程艺术》。《java编程艺术》里面自然是java版了(可能旭哥更加适合点儿),我这里还是解读的C版basic解释器代码。
4、for
接着看for,for循环这里借助了栈来实现,很不错的方法。
- /* 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循环要执行的语句 所以一直执行
- }
- void fpush(struct for_stack i)
- {
- if (ftos>FOR_NEST)
- serror(10);
- fstack[ftos]=i;
- ftos++;
- }
- /* execute a NEXT statement */
- void next()
- {
- struct for_stack i;
- i = fpop(); /* 这里我们读出当年压栈的记录 */
- variables[i.var]++; /* 变量加一 */
- if (variables[i.var]>i.target) return; /* 这个自然是作完了 */
- fpush(i); /* 没做完事情,记录当前变量 压栈 */
- prog = i.loc; /* 记得么 loc记录了for循环的第一个要执行的语句 这样我们就顺利地转入下次循环开始执行 */
- }
出栈代码也很简单,就是控制栈顶指针而已。
- struct for_stack fpop()
- {
- ftos--;
- if (ftos<0) serror(11);
- return (fstack[ftos]);
- }
通过栈不仅可以解决for循环的问题,而且gosub这个关键字也是通过栈来解决的。回想一下,我们在函数调用过程,cpu会先把当前的eip所指向的地址压栈,函数执行完后我们再出栈以跳回原执行地址。
- /* 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 */
- }
- }
- char *gstack[SUB_NEST]; /* stack for gosub 就是一个字符指针数组*/
- int gtos; /* index to top of GOSUB 栈顶指针 */
相关的函数也很简单:
- /* 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--];
- }
go_sub的处理体现出了程序语言运行的精髓,通过压栈保留当前信息(当前地址),必要时出栈又获取相应的信息 (想要返回的地址) 。cpu就是这样执行的,当然我们设计解释器也可以参照这个模型,来看看如何从一个"函数"(Basic中应该称作sub,子过程)中返回。
- /* return from GOSUB */
- void greturn()
- {
- prog = gpop(); //出栈,并把栈顶元素(即原来的地址)付给prog
- }