自己动手写basic解释器(二)

自己动手写basic解释器

刺猬@http://blog.csdn.net/littlehedgehog

 





注: 文章basic解释源码摘自梁肇新先生的《编程高手箴言》(据他所说这个代码也是网上摘录的),源码解读参考《java编程艺术》。《java编程艺术》里面自然是java版了(可能旭哥更加适合点儿),我这里还是解读的C版basic解释器代码。

上次我们把程序装载入内存,这里我们开始做词法分析了。hoho~ 开始 

一开始我们先再来回顾下:当我们的解释器在执行时,每次读入一条语句,并且根据这条语句执行特定的操作;然后再读入下一条语句,依此执行下去。也就是说解释器执行时,每次从程序的源代码中读入一个标识符。如果读入的是关键字,解释器就按照该关键字的要求执行规定的操作。举例来说,当解释器读入一个 PRINT后,它将打印PRINT之后的字符;当读入一个GOSUB时,它就执行指定的子程序。在到达程序的结尾之前,这个过程将反复进行。
按照上面的分析,我们所做的第一步就是要读取标识符,要一个单词一个数字的区分,比如print要作为一个关键字读入,2345要作为一个数字读入,而a=3这里要作为三个标识符读入,分别是变量a、赋值符号=以及数值3。看来我们在读取标识符时,我们还需要给标识符分类。

  1. #define DELIMITER 1  //分界符 比如逗号 分号 等号 都属于这之列
  2. #define VARIABLE 2   //变量
  3. #define NUMBER 3     //数字
  4. #define COMMAND 4    //关键字(命令)
  5. #define STRING 5     //字符串(这个比较特殊 既包括关键字 又包括常量字符串)
  6. #define QUOTE 6      //常量字符串 比如"hello world"
这里仔细谈分类收获不大,待看了get_token 这个取标识符的函数之后我们就大致明白为什么要这样分了。 token主要目的就是读取当前的标识符,放入token字符数组中,并确定标识符类型,放入token_type中,如果是关键字,那么我们还需要处理tok,标记为相应关键字。
  1. /* get a token 
  2.  * 从basic源码中读取一个符号(token) 并且区分出符号类型
  3.  * 我们把获取的符号放在token字符数组里面 在token_type中设置符号类型 在tok中设置
  4.  */
  5. get_token()
  6. {
  7.     register char *temp;
  8.     token_type = 0;     //记录标识符的类型
  9.     tok = 0;            //记录关键字  
  10.     temp = token;
  11.    
  12.    /* */
  13.     if (*prog == '/0')  {   /* 文件结束 */
  14.         *token = 0;
  15.         tok = FINISHED;     //设置文件结束符号
  16.         return (token_type = DELIMITER);    /* 标号类型设置为分界符*/
  17.     }
  18.    
  19.     while (iswhite(*prog))  ++prog;  /* 这里主要是除去字符串里面的空格 */
  20.     if (*prog == '/r')  
  21.     {   /* 换行符处理 */
  22.         ++prog;++prog;  //这里跳过的字符"/r/n"
  23.         tok = EOL;      //设置一行结束的标识 EOL(End Of Line) 
  24.         *token = '/r';token[1] = '/n';token[2] = 0;
  25.         return (token_type = DELIMITER);  //设置为分界符
  26.     }
  27.    
  28.     if (strchr("+-*^/%=;(),><",*prog))    /* 在"+-*^/%=;(),><"查找prog */
  29.      { 
  30.         *temp = *prog;              //把这个分界符拷贝到token中
  31.         prog++;  /* advance to next position */
  32.         temp++;
  33.         *temp=0; //最后补上零 这样让token形成字符串  
  34.         return (token_type = DELIMITER);
  35.     }
  36.    
  37.    /* 这里'"'表明后面的是字符串  basic中常常出现的情况是 print "hello world" 
  38.     * 下面的例子就以此举例了
  39.     */
  40.     if (*prog == '"')   
  41.     {  
  42.         prog++;         //prog指向了字符'h',注意这里已经进入了字符串
  43.         while (*prog!='"'&&*prog!='/r')  *temp++=*prog++;   //只要字符串没结束(prog遇到双引号),或者是没换行('/r') 我们就要把原代码拷贝到token中
  44.         if (*prog=='/r')  serror(1);    //字符串不能换行
  45.         prog++;*temp=0;     //prog已经走出字符串啦,照样的token要补上结束符
  46.         return (token_type = QUOTE);  //token_type为字符串
  47.     }
  48.    
  49.    /* 数字处理  
  50.     * 唯一要注意的是我们现在的数字是字符串形式的...
  51.     */
  52.     if (isdigit(*prog))  
  53.     {   
  54.         while (!isdelim(*prog))  *temp++=*prog++;   //如果没有分界符,拷贝之 
  55.         *temp = '/0';
  56.         return (token_type = NUMBER);
  57.     }
  58.     /* 处理字符  仍举例 print "hello world"
  59.      * 这里我们得到print
  60.      * 注意该代码块没有马上返回
  61.      */
  62.     if (isalpha(*prog))  
  63.     {   
  64.         while (!isdelim(*prog))  *temp++=*prog++;   //没有分界符拷贝之
  65.         token_type = STRING;    //string类型  这只是一个中间类型  具体要划分为变量、关键字  你会想为什么不会是我们打印的常量字符串呢  因为上面我们已经处理了
  66.     }
  67.     *temp = '/0';   //不过这一句放在外面  其实没多大意义  前面的情况都return了, 剩下一个孤零零   因为接下来我们还要处理这玩意儿
  68.     /* 查看究竟是个命令呢,还是一个变量,拭目以待... */
  69.     if (token_type == STRING)  
  70.     {
  71.         tok = look_up(token);  /* 在变量hash表中查之 */
  72.         if (!tok)  
  73.             token_type = VARIABLE;  //变量
  74.         else 
  75.             token_type = COMMAND;  //关键字
  76.     }
  77.     return token_type;
  78. }


get_token里面还有一些小函数,这里我们同样也罗列出来。里面嵌套的函数我都注释了的,都比较好懂,注意点我都有解释
  1. /* 回滚 */
  2. void putback()
  3. {
  4.     char *t;
  5.     t = token;
  6.     for (;*t;t++)  prog--;      //
  7. }
  8.  /* 这丫是帮我们在关键字table表里面搜索是否包含当前字符串  */
  9. look_up(char *s)
  10. {
  11.     register int i,j;
  12.     char *p;
  13.     /* 命令因为我们全都用小写字符 这里不得不转换了 */
  14.     p = s;
  15.     while (*p)  { *p = tolower(*p); p++;  }
  16.     /* 顺序查找  呃  这个是个tiny 所以效率不太考虑了  
  17.      * 想想如果我们关键字很多的话  应该做一个hash表
  18.      * 这里如果查找成功返回一个标号 失败返回0 请参阅table结构
  19.      */
  20.     for (i=0;*table[i].command;i++)
  21.         if (!strcmp(table[i].command,s))  
  22.             return table[i].tok;
  23.     return 0;   
  24. }
  25. /* 查字符c是否是分界符 
  26.  * 恩 有一点要注意  比如我们的数字是909 那么这里传进来的c的ASCII码可不是909 应该是9+'0','0',9+'0'*/
  27. isdelim(char c)
  28. {
  29.     if (strchr(";,+-<>/*%^=() ",c)||c==9||c=='/r'||c==0)  //所以这里c==9 表示'/t' 0自然是'/0'
  30.         return 1;
  31.     return 0;
  32. }
  33. /* 查是不是"空白",就是空格和tab键*/
  34. iswhite (char c)
  35. {
  36.     if (c==' '||c=='/t')  return 1;
  37.     else return 0;
  38. }

至此 标识符处理我们全部完成,工作完成了一大块,剩下就是关键字处理了,里面涉及到语句逻辑。




你可能感兴趣的:(Unix/Linux,Programming)