Stanford公开课《编译原理》学习笔记(1~4课)
示例代码托管在:http://www.github.com/dashnowords/blogs
博客园地址:《大史住在大前端》原创博文目录
华为云社区地址:【你要的前端打怪升级指南】
B站地址:【编译原理】
Stanford公开课:【Stanford大学公开课官网】
课程里涉及到的内容讲的还是很清楚的,但个别地方有点脱节,任何看不懂卡住的地方,请自行查阅经典著作《Compilers——priciples, Techniques and Tools》(也就是大名鼎鼎的龙书)的对应章节。
一. 编译的基本流程
完整的编译的5个基本步骤包括lexcical anlysis
,parse
,sematic
,optimize
,code generate
。课程中并没有使用复杂的编程语言,而是一种用于课堂教学的自发明语言COOL,很明显老师为它写好了编译器程序。
二. Lexical Analysis(词法分析阶段)
任务:将字符串分解成为[Type, (Value)]
元组的形式的词法单元。
“龙书”里的示例更为直观,例如表达式语句
E = M * C ** 2
进行词法分析后会得到如下的类似结果:[
id
,指向符号表中E的条目的指针][
assign_op
][
id
,指向符号表中M的条目的指针][
mult_op
][
id
,指向符号表中C的条目的指针][
exp_op
][
number
,整数值2]
词法分析基本需要经历如下几个阶段:
Lexical Specification
——>Regular expressions
——>NFA
——>DFA
——>Table-driven Implementation of DFA
2.1 Lexical Specification(分词原则)
COOL中的基本Type
包括如下几个类别:
Indentifier
标识符-指以字母开头后续为若干个字母或数字的字符组Integer
-指一组非空的数字字符Keyword
- 指语言中的关键词,例如if,else等Whitespace
- 指一组非空的空格字符或换行符或制表符
很多程序设计语言中的分词原则基本都会覆盖关键字,运算符,标识符,常量,标点符号,他们也会在后面的实现中被作为终止符集合,课程板书中也提供了COOL分词原则的类正则形式。
分词时类型的正则匹配默认为贪婪模式,即匹配更多的字符。词法单元也具备一定的优先级次序(通常也是代码逻辑的实现顺序),例如if
从正则上来判断既符合Keywords
也符合Identifier
,此时该单元的类型就应该标记为Keywords
。这个阶段就完成了从Lecical Specification
——>Regular expressions
的部分。
2.2 Finite Automata (典型分词算法-有穷自动机)
FA是一个可以自动识别词法单元的机器,它是一个状态转换图,“有限”是指它包含的状态是有限的,一个状态读入一个字符后,后继的状态可能为:
- 后继状态为自身
- 后继状态只有一个
- 后继状态有多个
如果每次转换后的后继状态都是唯一的,则称为DFA
(确定有限自动机),如果后继状态可能有多个则称为NFA
(不确定有限状态机)。由于DFA
的状态转移路径是唯一的,所以作为状态查询图时,无论成功或者失败只需要运行一次,但NFA
就可能需要运行多次。
正则表达式是可以转换为NFA
形式的,或许你已经在一些可视化正则表达式的网站上[https://regexper.com ]见过类似的形式。下图比较清晰地展示了从正则表达式到NFA
状态图的转换规则(Regular expressions
——>NFA
):
如果一个DFA
和一个NFA
能够识别的字符集是一致的,则称它们为等价的,对于任意NFA
,一定存在一个DFA
与其等价,由NFA
构建DFA
的过程被称为DFA的确定化,也就是NFA
——>DFA
的过程。这个过程是围绕ε -closure
状态集合的概念展开的,大致的过程就是从起点开始,每次将当前状态和通过若干次ε转换
(它是一个特殊的状态转移函数,表示转换后的状态还是当前状态)作为一个新的ε -closure
状态集合 ,使用矩阵记录每个ε -closure
集合转换前后的集合,最后对整个状态转移矩阵进行标记重命名,就可以得到一个DFA
,事实上转化后的DFA
中的每一个状态,就是NFA
中的一个ε -closure
集合,你可以将它理解成一个通过分组来简化表达方式的过程,相关的过程可以参考下面这个文章西北农林科技大学编译原理课程PPT【词法分析】,里面图比较多,能够辅助理解,本文不再赘述。
三. 手动实现分词器
至此1-4课就结束了,估计看视频课程的人也是一脸懵逼,因为课程并没有讲解如何利用DFA
得到最终期望的形式——Token
元组,那么最后我们就自己手动来实现一下。
3.1 基本定义
假设我们需要对下面这段代码进行分词解析:
let snippet = `
var b3 = 2;
a = 1 + ( b3 + 4);
return a;
`;
那么先来进行一些基本类型集合定义:
//解析结束标记
const EOF = undefined;
//Token Type 可识别的Token类型,
const TT = {
num: 'num',
id: 'id',
keywords: 'keywords', //var | return
lparen: 'lparen',// (
rparen: 'rparen',// )
semicolon: 'semicolon', //;
whitespace: 'whitespace', // \n | \t | \s (空格,制表符,换行符)
plus: 'plus', // +
assign: 'assign',// =
}
// 状态集类型,除开始和结束外,其他可以与Token支持的类型相对应,每次分词从start状态开始,接收一个字符后改变状态,直到在done状态结束时,可以得到一个token
const S = {
start: 'start',
done: 'done',
...TT
}
进行工具函数定义:
//判断是否为关键词(为简化流程,仅检测上面示例中包含的关键词)
const isKeywords = (token) => ['function', 'return', 'if', 'var'].includes(token);
//判断是否为数字
const isDigit = c => /\d/.test(c);
//判断是否为合法的标识符字符
const isValidId = c => /[A-Za-z0-9]/.test(c);
//判断是否为空格
const isBlank = c => /(\s|\t|\n)/.test(c);
3.2 构建DFA
以上面定义的状态集合和token
类别为依据构建DFA
:
3.3 开始分词
分词的逻辑实际上就是,每次先将状态置为start
,然后读入一个字符,根据该字符判断下一个状态,只要没有到达完成状态 done
就继续读入字符,每次到达done
状态时,就可以得到一个token
,将其记录下来,然后重新将状态置为start
,开始寻找下一个token
直到分析完整个代码段。也就是说DFA
状态机每运行一轮,就得到一个token
。参考代码如下:
/**
* 词法分析
*/
function tokenize(code) {
let state = S.start;
let currentToken;//标记当前寻找到的token
let index = 0;//起始指针,每次分析指向start状态
let lookup = 0;//前探指针,每次分析最终指向done状态,start->done之间的字符即为token
while (code[lookup] !== EOF) { //如果还有字符
while (state !== S.done) { //开始拆分token
//获取下一个字符
let c = code[lookup++];
//根据当前状态和下一个字符判断DFA如何跳转
switch (state) {
case S.start: //开始为空集,实现DFA中各个状态转移分支
if (isDigit(c)) {
state = S.num;
} else if (isValidId(c)) {
state = S.id;
} else if (isBlank(c)) {
state = S.done;
} else if (c === '=') {
currentToken = [TT.assign, '=']
state = S.done;
} else if (c === '+') {
currentToken = [TT.plus, '+']
state = S.done;
} else if (c === ';') {
currentToken = [TT.semicolon, ';']
state = S.done;
};
break;
case S.num: //如果是整数
if (isDigit(c)) {
state = S.num;
} else {
currentToken = [TT.num, code.slice(index,lookup - 1)];
lookup -= 1; //从数字状态跳出后,最后一位需要参与下一轮分词,故回退一位
state = S.done;
}
break;
case S.id: //如果是标识符状态
if (isValidId(c)) {
state = S.id;
} else {
let tempToken = code.slice(index,lookup - 1);
lookup -= 1; //从标识符状态跳出后,最后一位需要参与下一轮分词,故回退一位
if (isKeywords(tempToken)) {
currentToken = [TT.keywords, tempToken];
}else{
currentToken = [TT.id, tempToken];
}
state = S.done;
}
break;
}
}
//state = S.done时跳出
currentToken && console.log(currentToken);
currentToken = undefined;
//起指针跟上末指针
index = lookup;
//开始下一轮分词
state = S.start;
}
}
3.4 查看分词结果
运行上述代码即可看到目标程序片段的分词结果:
四. 小结
至此,我们就得到了元组形式的分词结果,完成了编译中第一步lexical analysis
的部分,笔者同时提供了一份包含token
所在行列信息的版本,你可以从附件或【我的github仓库】中拿到示例代码,如果觉得对你有帮助,可以在github上为我加个星星哦~
Stanford公开课《编译原理》学习笔记(1~4课)的更多相关文章
- 编译原理学习笔记·语法分析(LL(1)分析法/算符优先分析法OPG)及例子详解
语法分析(自顶向下/自底向上) 自顶向下 递归下降分析法 这种带回溯的自顶向下的分析方法实际上是一种穷举的不断试探的过程,分析效率极低,在实际的编译程序中极少使用. LL(1)分析法 又称预测分析法, ...
- Stanford公开课《编译原理》学习笔记(2)递归下降法
目录 一. Parse阶段 CFG Recursive Descent(递归下降遍历) 二. 递归下降遍历 2.1 预备知识 2.2 多行语句的处理思路 2.3 简易的文法定义 2.4 文法产生式的代 ...
- Unity3D 骨骼动画原理学习笔记
最近研究了一下游戏中模型的骨骼动画的原理,做一个学习笔记,便于大家共同学习探讨. ps:最近改bug改的要死要活,博客写的吭哧吭哧的~ 首先列出学习参考的前人的文章,本文较多的参考了其中的表述: 1. ...
- 2011年冬斯坦福大学公开课 iOS应用开发教程学习笔记(第三课)
第二课名称是:Objective-C 回顾上节课的内容: 创建了单个MVC模式的项目 显示项目的各个文件,显示或隐藏导航,Assistant Editor, Console, Object Libra ...
- Java并发之底层实现原理学习笔记
本篇博文将介绍java并发底层的实现原理,我们知道java实现的并发操作最后肯定是由我们的CPU完成的,中间经历了将java源码编译成.class文件,然后进行加载,然后虚拟机执行引擎进行执行,解释为 ...
- TCP/IP协议原理学习笔记
昨天学习了杨宁老师的TCP/IP协议原理第一讲和第二讲,主要介绍了OSI模型,整理如下: OSI是open system innerconnection的简称,即开放式系统互联参考模型,它把网络协议从 ...
- elasticsearch原理学习笔记
https://mp.weixin.qq.com/s/dn1n2FGwG9BNQuJUMVmo7w 感谢,透彻的讲解 整理笔记 请说出 唐诗中 包含 前 的诗句 ...... 其实你都会,只是想不起 ...
- 个人MySQL的事务特性原理学习笔记总结
目录 个人MySQL的事务特性原理笔记总结 一.基础概念 2. 事务控制语句 3. 事务特性 二.原子性 1. 原子性定义 2. 实现 三.持久性 1. 定义 2. 实现 3. redo log存在的 ...
- Jvm工作原理学习笔记(转)
一. JVM的生命周期 1. JVM实例对应了一个独立运行的java程序它是进程级别 a) 启动.启动一个Java程序时,一个JVM实例就产生了,任何一个拥有pub ...
随机推荐
- 40道经典java多线程面试题
40道经典java多线程面试题 题目来源 看完了java并发编程的艺术,自认为多线程"大成",然后找了一些面试题,也发现了一些不足. 一下问题来源于网上的博客,答案均为本人个人见解 ...
- python编辑已存在的excel坑: BadZipFile: File is not a zip file
背景-原代码如下,期望能自动创建excel,并且可以反复调用编辑: import xlwt,osfrom openpyxl.styles import Font, colors class Write ...
- NOIP前的模板复习和注意事项
联赛除去今天刚好只有一个星期了,最后一个星期也很关键,要吃好睡好保持心情愉悦.当然也免不了最后的复习计划. 首先是模板,之前还有很多模板没有复习到,这些东西是一定要落实到位的. 每天往后面写一点... ...
- VR、AR、MR、CR 与 AI与SaaS、CRM、MRP与B2B、B2C、C2C、O2O、P2P
一.VR.AR.MR.CR VR ( Virtual Reality ),虚拟现实 AR(Augmented Reality),增强现实 MR(Mix Reality),混合现实 CR(Cinema ...
- 纯数据结构Java实现(5/11)(Set&Map)
纯数据结构Java实现(5/11)(Set&Map) Set 和 Map 都是抽象或者高级数据结构,至于底层是采用树还是散列则根据需要而定. 可以细想一下 TreeMap/HashMap, T ...
- C#数据结构_图
顶点的度=顶点的入度+顶点的出度. 顶点 v 的入度是指以该顶点 v 为弧头的弧的数目:顶点 v 的出度是指以该顶点 v 为弧尾的弧的数目. 简单路径:一条路径上顶点不重复出现. 回路:第一个顶点和最 ...
- Python 基础 2-3 列表的反转与排序
引言 列表是按照特定格式排序而成的,有时候这种排序方式我们并不喜欢,我们希望它可以按照我们的方式来进行正序或者倒序排序,或其他的排序方式 反转与排序 比如说我这里有一组列表,里面存放的全部都是数值,但 ...
- Servlet,过滤器和监听器的配置和使用
一.什么是Servlet Servlet使用Java语言实现的程序,运行于支持Java语言的Web服务器或者应用服务器中.Servlet先于JSP出现,提供和客户端动态交互的功能.Servlet可以处 ...
- java基础-多线程二
java基础-多线程二 继承thread和实现Runnable的多线程每次都需要经历创建和销毁的过程,频繁的创建和销毁大大影响效率,线程池的诞生就可以很好的解决这一个问题,线程池可以充分的利用线程进行 ...
- hdu-6701 Make Rounddog Happy
题目链接 Make Rounddog Happy Problem Description Rounddog always has an array a1,a2,⋯,an in his right po ...