前文  .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. Python3 tuple 函数

    Python3 tuple 函数  Python3 内置函数 描述 tuple 函数将列表转换为元组.. 语法 以下是 tuple 的语法: tuple( seq ) 参数 seq -- 要转换为元组 ...

  2. unix架构

    UNIX Kernel(UNIX内核):指挥机器的运行,控制计算机的资源 UNIX Shell(UNIX外壳):是UNIX内核和用户的接口,是UNXI的命令解释器.目前常用的Shell有3种 Bour ...

  3. hover

    hover - Bing dictionary US[ˈhɒvə(r)] v.盘旋:徘徊:犹豫:巡弋 网络翱翔:悬停:盘旋于

  4. Python Socket 编程详细介绍(转)

    Python 提供了两个基本的 socket 模块: Socket 它提供了标准的BSD Socket API. SocketServer 它提供了服务器重心,可以简化网络服务器的开发. 下面讲解下 ...

  5. mysql简单实现查询结果添加序列号的方法

    方法1: SELECT @rownum :=@rownum + 1 AS rownum, t.* FROM integral_system_user t, (SELECT @rownum := 0) ...

  6. 关于MySQL在内网中使用另一台机器访问的问题

    要在内网中访问另一台机器的MySQL数据库,需要两步操作 一是把运行MySQL的机器的3306端口打开,最好是能限制访问IP保证安全性. 二是更改MySQL账户的访问权限.MySQL的root账户默认 ...

  7. Speeding up Homestead on Windows Using NFS

    Speeding up Homestead on Windows Using NFS Sep 07 2015 Homestead Laravel EDIT: I have another articl ...

  8. Windows10 Virtualization Technology虚拟化技术功能

    为什么要开启VT功能,做机器学习环境搭建.运用Docker容器等等,所以首先要确认一下机器是否已经开启了VT技术功能,以此记录一下经历而已. VT是什么?为什么要开启VT?VT是一种虚拟化技术,可以扩 ...

  9. aspx导出文件

    System.IO.StringWriter sw = new System.IO.StringWriter(); HtmlTextWriter htw = new HtmlTextWriter(sw ...

  10. 继续修改爬虫百度贴吧,这次随意贴吧的任何一个index页都行,然后自动d盘生成tupian文件夹来保存

    from urllib.request import urlopenfrom bs4 import BeautifulSoupfrom urllib.request import urlopenimp ...