前文  .NET框架源码解读之MYC编译器 和 MYC编译器源码分析之程序入口 分别讲解了 SSCLI 里示例编译器的架构和程序入口,本文接着分析它的词法分析部分的代码。

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

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

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

  1. int foo(int a)

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

  1. T_INT T_IDENT ‘(‘ T_INT T_IDENT ‘)’

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

  1. public void InitHash()
  2. {
  3. // 为字符类型识别号对照表 – tokens分配空间
  4. tokens = new Hashtable();
  5. AddTok(T_LEFT_ASSIGN, "<<=");
  6. // ... ...
  7. AddTok(T_IF, "if");
  8. // ... ...
  9. AddTok(T_STATIC, "static");
  10. AddTok(T_INT, "int");
  11. // ... ...
  12. }

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

  1. public const int T_LEFT_ASSIGN = ;
  2. // ... ...
  3. public const int T_IF = ;
  4. // ... ...
  5. public const int T_STATIC = ;
  6. // ... ...
  7. public const int T_INT = ;
  8. // ... ...
  9. public const int T_IDENT = ;
  10. public const int T_DIGITS = ;
  11. public const int T_UNKNOWN = ;
  12. public const int T_EOF = -;

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

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

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

  1. letter ::= "A-Za-z";
  2. digit ::= "0-9";
  3.  
  4. name ::= letter { letter | digit };
  5. integer ::= digit { digit };

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

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

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

  1. // Io.cs – ReadChar函数
  2.  
  3. public void ReadChar()
  4. {
  5. // 判断是不是读到文件末尾了
  6. if (_eof) // if already eof, nothing to do here
  7. return;
  8. // 如果缓存还没有实例化,或者缓存里的字符
  9. // 已经处理完毕了,创建一个新的缓存
  10. // 对于老的缓存数组,丢给垃圾回收机制处理
  11. if (ibuf == null || ibufidx >= MyC.MAXBUF)
  12. {
  13. ibuf = new char[MyC.MAXBUF];
  14. _eof = false;
  15. // 从源文件里读取一大块内容到缓存里
  16. ibufread = rfile.Read(ibuf, , MyC.MAXBUF);
  17. ibufidx = ;
  18. if (buf == null)
  19. buf = new StringBuilder(MyC.MAXSTR);
  20. }
  21. // 从缓存里读取下一个字符
  22. look = ibuf[ibufidx++];
  23. // 判断这次读取时,是否已经到源文件末尾了
  24. if (ibufread < MyC.MAXBUF && ibufidx > ibufread)
  25. _eof = true;
  26.  
  27. /*
  28. * track the read characters
  29. */
  30. // 保存当前读取的字符,以便在生成IL源文件的时候
  31. // 可以把C源码跟生成的IL源码对应起来
  32. buf.Append(look);
  33. // 如果碰到换行,更新行号,行号在报告语法错误
  34. // 的时候会用到,告知具体语法出错的行号便于
  35. // 程序员找到错误
  36. if (look == '\n')
  37. bufline++;
  38. }

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

效果如下图:

MYC编译器源码之词法分析的更多相关文章

  1. MYC编译器源码分析之程序入口

    前文.NET框架源码解读之MYC编译器讲了MyC编译器的架构,整个编译器是用C#语言写的,上图列出了MyC编译器编译一个C源文件的过程,编译主路径如下: 首先是入口Main函数用来解析命令行参数,读取 ...

  2. MYC编译器源码之代码生成

    前面讲过语法的解析之后,代码生成方面就简单很多了.虽然myc是一个简单的示例编译器,但是它还是在解析的过程中生成了一个小的语法树,这个语法树将会用在生成exe可执行文件和il源码的过程中. 编译器在解 ...

  3. MYC编译器源码之语法分析

    MyC编译器采用自顶向下的方法进行语法解析,这种语法解析方式,一般是从最左边的Token开始,然后自顶向下看哪一条语法规则可能包含这个Token,如果包含这个Token,则自左向右根据这条语法规则逐一 ...

  4. TypeScript 编译器源码研究(一)

    TypeScript (以下简称 TS)是一个非常强大的语言,其编译器源码超过 10000 行. 源码在 Github 可以找到:https://github.com/Microsoft/TypeSc ...

  5. .NET框架源码解读之MYC编译器

    在SSCLI里附带了两个示例编译器源码,用来演示CLR整个架构的弹性,一个是简化版的lisp编译器,一个是简化版的C编译器.lisp在国内用的少,因此这里我们主要看看C编译器的源码,源码位置是:\ss ...

  6. TypeScript 源码详细解读(1)总览

    TypeScript 由微软在 2012 年 10 月首发,经过几年的发展,已经成为国内外很多前端团队的首选编程语言.前端三大框架中的 Angular 和 Vue 3 也都改用了 TypeScript ...

  7. PHP7 源码整体框架

    一.PHP7语言执行原理 常用的高级语言有很多种,根据运行的方式不同,大体分为两种:编译型语言和解释型语言. 编译是指在应用源程序执行之前,就将程序源代码“翻译”成汇编语言,然后进一步根据软硬件环境编 ...

  8. python3 源码阅读-虚拟机运行原理

    阅读源码版本python 3.8.3 参考书籍<<Python源码剖析>> 参考书籍<<Python学习手册 第4版>> 官网文档目录介绍 Doc目录主 ...

  9. laravel源码解析

    本专栏系列文章已经收录到 GitBooklaravel源码解析 Laravel Passport——OAuth2 API 认证系统源码解析(下)laravel源码解析 Laravel Passport ...

随机推荐

  1. C语言实现大数四则运算

    一.简介 众所周知,C语言中INT类型是有限制,不能进行超过其范围的运算,而如果采用float类型进行运算,由于float在内存中特殊的存储形式,又失去了计算的进度.要解决整个问题,一种解决方法是通过 ...

  2. android笔记:BroadcastReceiver

    android允许应用程序自由地发送和接收广播. 广播是通过Intent进行数据传递的.接收广播则通过Broadcast Receiver(广播接收器)实现. 广播分为:标准广播和有序广播. 标准广播 ...

  3. mysql数据库的最基本的命令

    #查看mysql有哪些数据库:show databases; 创建一个数据库名称为DataBaseName,字符编码为utf8支持中文create database DataBaseName char ...

  4. 用HttpClient发送HTTPS请求报SSLException: Certificate for <域名> doesn't match any of the subject alternative names问题的解决

    最近用server酱-PushBear做消息自动推送,用apache HttpClient做https的get请求,但是代码上到服务器端就报javax.net.ssl.SSLException: Ce ...

  5. 给乱序的链表排序 · Sort List, 链表重排reorder list LoLn...

    链表排序 · Sort List [抄题]: [思维问题]: [一句话思路]: [输入量]:空: 正常情况:特大:特小:程序里处理到的特殊情况:异常情况(不合法不合理的输入): [画图]: quick ...

  6. 创建和修改主键 (SQL)

    添加主键, ALTER TABLE [表名:OrderInfo] Add PRIMARY KEY ([列名:ProductID, UserID...])  多个列则是组合主键 删除主键, ALTER ...

  7. boost基础环境搭建

    因为现在手上的老的基类库经常出现丢包,以及从ServiceAClient 发送消息到 ServiceBServer时出现消息失败的情况,以及现有的莫名其妙的内存泄露的问题,以及目前还是c++0x,准确 ...

  8. Python图像处理库:Pillow 初级教程-乾颐堂

    Image类 Pillow中最重要的类就是Image,该类存在于同名的模块中.可以通过以下几种方式实例化:从文件中读取图片,处理其他图片得到,或者直接创建一个图片. 使用Image模块中的open函数 ...

  9. mysql添加注释

    -- 查看字段类型-- show columns from campaign_distribute --给表添加注释 -- alter table campaign_distribute commen ...

  10. c++11多线程学习笔记之一 thread基础使用

    没啥好讲的  c++11  thread类的基本使用 #include "stdafx.h" #include <iostream> #include <thre ...