自己动手写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;
- }
至此 标识符处理我们全部完成,工作完成了一大块,剩下就是关键字处理了,里面涉及到语句逻辑。
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow