MYC编译器源码之词法分析

词法解析

词法解析的工作都由Tok类处理,其构造函数接受一个Io对象做文件处理,下面是Tok构造函数的源码:

public Tok(Io ihandle)
{
    io = ihandle;
    // 初始化Token(字符归类)字典
    InitHash();         // initialize the tokens hashtable
    // 读入文件的第一个字符
    io.ReadChar();
    // 逐个扫描文件里的字符,获取
    // 第一个字符归类(Token)
    scan();
}

构造函数中第一个函数调用InitHash的目的是将关键字和操作符解析成更容易识别的字符类型识别号 - Token,这样做的目的是为了便于语法解析器parser处理。例如,对于下面这条C语句:

int foo(int a)

与其让语法解析器去逐个处理单个字符,词法解析器的作用是将去上面一行语句归类成类似下面的格式:

T_INT T_IDENT ‘(‘ T_INT T_IDENT ‘)’

因为T_INT,T_IDENT都是一个整数型常量,而’(‘这样的单个字符也可以当作整数型常量对待,这样语法解析器在分析语法的时候工作会更轻松些。所以在InitHash函数里,其把编程语言里所有的关键字和多字符操作符(如左移赋值操作符 <<=)都设置了类型标识号(Token),在Tok对象的scan()函数扫描源文件时,会逐一在这个字典里查询关键字的标识号:

public void InitHash()
{
    // 为字符类型识别号对照表 – tokens分配空间
    tokens = new Hashtable();
    AddTok(T_LEFT_ASSIGN,   "<<=");     
    // ... ...
    AddTok(T_IF,        "if");
    // ... ...
    AddTok(T_STATIC,        "static");
    AddTok(T_INT,       "int");
    // ... ...
}

而对应的每个标识号(Token)的定义,则可以在Tok.cs源文件的最上面找到:

public const int T_LEFT_ASSIGN  = 10001;
// ... ...
public const int T_IF           = 20001;
// ... ...
public const int T_STATIC       = 30002;
// ... ...
public const int T_INT      = 40003;
// ... ...
public const int T_IDENT        = 50001;
public const int T_DIGITS       = 50002;
public const int T_UNKNOWN      = 99999;
public const int T_EOF      = -1;

字符类型识别号对照表初始化完毕后,语法分析器就可以调用Tok对象的scan函数进行语法处理了,scan函数每次只处理并返回一个字符类型:

public void scan()
{
    // 跳过注释、换行符、空格等字符
    skipWhite();
    // 先判断当前读取的字符是不是一个字母
    // 如果是字母开头的话,要么是关键字,
    // 要么就是变量名
    if (Char.IsLetter(io.getNextChar()))
      // 逐个扫描后面的字符,直到识别出关键字
      // 或者变量名为止才退出
      LoadName();
    // 如果当前的字符是 0 - 9的数字
    else if (Char.IsDigit(io.getNextChar()))
      // 扫描完后面的数字并归类
      LoadNum();
    // 如果是操作符,扫描完后面的操作符字符串
    else if (isOp(io.getNextChar()))
      LoadOp();
    // 如果文件已经读取完毕了
    else if (io.EOF())
      {
      // 返回特殊的识别符 T_EOF,表示文件读取完毕
      value = null;
      token_id = T_EOF;
      }
    else
      {
      // 这个字符不是一个合法的字符,归类成T_UNKNOWN
      // T_UNKNOWN没有被任何语法引用
      // 如果语法分析器在扫描语法的过程中
      // 看到这个识别符,很有可能是源码里有语法错误
      value = new StringBuilder(MyC.MAXSTR);
      value.Append(io.getNextChar());
      token_id = T_UNKNOWN;
      io.ReadChar();
      }
    skipWhite();
    // 条件编译,如果是myc.exe是调试版本,则在命令行里
    // 打印出当前识别的字符类型,便于myc.exe的开发者排错
#if DEBUG
    Console.WriteLine("[tok.scan tok=["+this+"]");
#endif
}

scan函数是Tok对象里最核心的函数,它实际上是完成前面myc语法里这些词法规则(还有隐含的关键字和操作符识别):

letter ::= "A-Za-z";
digit ::= "0-9";

name ::= letter { letter | digit };
integer ::= digit { digit };

我们再通过说明LoadName函数来解释词法分析的细节:

void LoadName()
{
  // 缓存读取到的字符
  value = new StringBuilder(MyC.MAXSTR);
  skipWhite();  
  // 错误验证 - 确保第一个字符是字母
  if (!Char.IsLetter(io.getNextChar()))
    throw new ApplicationException("?Expected Name");
  // 后面跟着的字符只能是数字或者字母
  while (Char.IsLetterOrDigit(io.getNextChar()))
    {
    // 缓存字符,以便判断是变量名,还是关键字
    value.Append(io.getNextChar());
    // 从源文件里读取下一个字符
    io.ReadChar();
    }
  // 在字符类型识别表里查询读取到的词组是不是关键字
  token_id = lookup_id();
  // 不是关键字的话,那么就是变量名(或函数名)
  if (token_id <= 0)
    token_id = T_IDENT;
  skipWhite();
}

上面基本上就是词法分析的关键代码了,不过在说明的时候,我特意跳过了构造函数的 io.ReadChar()这个函数,这个函数从字面意义上看是读取一个字符,但实际上从源文件一个字符一个字符的读取效率实在是太低了,因此一般都是从源文件里读取一大段字符并缓存在内存里,提高效率:

// Io.cs – ReadChar函数

public void ReadChar()
{
  // 判断是不是读到文件末尾了
  if (_eof)         // if already eof, nothing to do here
    return;
  // 如果缓存还没有实例化,或者缓存里的字符
  // 已经处理完毕了,创建一个新的缓存
  // 对于老的缓存数组,丢给垃圾回收机制处理
  if (ibuf == null || ibufidx >= MyC.MAXBUF)
    {
    ibuf = new char[MyC.MAXBUF];
    _eof = false;
    // 从源文件里读取一大块内容到缓存里
    ibufread = rfile.Read(ibuf, 0, MyC.MAXBUF);
    ibufidx = 0;
    if (buf == null)
      buf = new StringBuilder(MyC.MAXSTR);
    }
  // 从缓存里读取下一个字符
  look = ibuf[ibufidx++];
  // 判断这次读取时,是否已经到源文件末尾了
  if (ibufread < MyC.MAXBUF && ibufidx > ibufread)
    _eof = true;

  /*
   * track the read characters
   */
  // 保存当前读取的字符,以便在生成IL源文件的时候
  // 可以把C源码跟生成的IL源码对应起来
  buf.Append(look);
  // 如果碰到换行,更新行号,行号在报告语法错误
  // 的时候会用到,告知具体语法出错的行号便于
  // 程序员找到错误
  if (look == '\n')
    bufline++;
}

在Io.ReadChar函数里,会保存读取的C源码,当要生成IL源文件的时候,这个信息用来保存C语句跟IL语句的对应关系,如用下面的命令编译myc里自带的测试源码文件:

myc-list-cmd.png

效果如下图:

myc-list-result.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 本文涉及的javac编译器来自openjdk. javac的目录地址为: 解压目录/langtools/src/s...
    whthomas阅读 1,349评论 3 3
  • 车无疑是梦的翅膀,自由更是梦的天堂! 我的车里没有美女,只有酒!我酒量不大,里边的酒绝大部分是给别人准备的。我喜欢...
    英驰商贸阅读 398评论 0 1
  • 目录 叶姗姗说过让我不要去看她,可是这几天我越想越觉得愧疚,抛开别的不说,全靠她心甘情愿把事情经过说出来,并且把布...
    疏牧风阅读 399评论 0 1
  • 风和日丽的一天,冬娜打扮了三个小时才出门,出门前照着镜子看了很久,像被什么勾了魂儿似的。出门后她没有按照计划的方向...
    彼岸久伴阅读 204评论 0 0
  • github 新账号: ziran655@126.com 密码: ziran4082227 第二个邮箱: olif...
    olifer阅读 165评论 0 0