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

词法解析的工作都由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    = ;
// ... ...
public const int T_IF = ;
// ... ...
public const int T_STATIC = ;
// ... ...
public const int T_INT = ;
// ... ...
public const int T_IDENT = ;
public const int T_DIGITS = ;
public const int T_UNKNOWN = ;
public const int T_EOF = -;

字符类型识别号对照表初始化完毕后,语法分析器就可以调用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 <= )
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, , MyC.MAXBUF);
ibufidx = ;
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编译器源码之词法分析的更多相关文章

  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. js base64转二进制

    base64 编码规则 1.把3个字符变成4个字符.2.每76个字符加一个换行符.3.最后的结束符也要处理. 转换前 11111101, 11111111, 11111111 (二进制) 转换后 00 ...

  2. angluarjs ng-repeat 行号

    参考 https://zhidao.baidu.com/question/1882914672116911828.html $index

  3. 基于AspectJ的XML方式进行AOP开发

    -------------------siwuxie095                                 基于 AspectJ 的 XML 方式进行 AOP 开发         1 ...

  4. OC 线程操作 - GCD使用 -线程通讯, 延迟函数和一次性代码

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // [self downImag ...

  5. 18-从n个数中选m个

    #include <iostream>using namespace std; int f(int n, int m){        if(n < m)         //这个条 ...

  6. Linux下Google Test (GTest)测试环境搭建步骤

    1.下载GTEST 下载链接为:https://code.google.com/p/googletest/downloads/list 目前GTEST的最新版本为gtest-1.7.0.zip,因此我 ...

  7. centos6.6 下 安装 php7 按 nginx方式

    1.安装必要的依赖库 > yum -y install gd zlib libjpeg libjpeg-devel libpng libpng-devel freetype freetype-d ...

  8. nginx中图片无法显示

    如果没有配置虚拟主机,则修改nginx.conf. 如果已创建单独虚拟主机,则在vhost下找到指定的主机配置文件, 如:www.xxx.com.conf location ~ .*\.(gif|jp ...

  9. 【转】HttpRuntime的认识与加深理解

    原文:http://www.cnblogs.com/whtydn/archive/2009/10/16/1584418.html   下面最先介绍HttpRuntime的Web.config里的配置 ...

  10. Castle ActiveRecord学习(四)延迟加载、分页查询、where条件

    一.延迟加载 //用户发布的主题,一对多:Table:外键表:ColumnKey:外键:Lazy:延迟加载:Cascade:级联操作(级联删除) [HasMany(typeof(ThemeInfo), ...