朱娜斐编译原理复习笔记-北京工业大学软件学院

朱娜婓编译原理学习笔记

说明

  • 笔记大部分内容来自参考资料[1], 看了B站上中科大华保健老师的编译原理课视频(参考资料[2]),补充完善了DFA的代码表示、Hopcroft 算法、文法重写、LL(1)算法、LR算法等内容

  • 有许多知识是结合了自己的理解进行整理,所以可能会有错误之处

  • 再往后因为时间问题就有点烂尾了...

  • 文章末尾有北京工业大学2019年软件学院编译原理的考题回忆及朱娜斐老师划分的考点(recoded by @杰哥)

Keyword

  • 中科大华宝健编译原理学习笔记
  • 北京工业大学朱娜婓编译原理学习笔记
  • 2019年北京工业大学软件学院编译原理期末考试试卷回忆版

什么是编译器

什么是编译器

编译器是一个程序,核心功能是把源代码翻译成目标代码

编译器的工作解释

源代码-->编译器的静态计算-->目标程序-->计算机的动态计算-->计算结果

静态计算 是指编译器只根据程序文本静态地分析(如做报错分析、优化分析),而不是真的拿 CPU 去执行

计算机 可能是一个 x86 的物理器(如对应 C 语言),也可能是 JVM java 虚拟机(如对应 java)。

编译器和解释器的比较

解释器也是一类处理程序的程序

区别在于:

  • 编译器:输入源代码,输出一个可执行程序,但不去执行

    (存放在磁盘上等待被加载到内存中执行)

  • 解释器:输入源代码,直接输出执行结果

    其实 JVM 就是一个解释器,而不是一个单纯的编译器。输入 java 字节码 bytecode ,然后直接输出执行结果,而不是输出汇编代码。

编译器简史

第一个编译器是Fortran语言的编译器

该编译器给计算机科学的发展产生了巨大的影响:

  • 理论上:算法、数据结构、形式语言与自动机
  • 实践上:软件工程、体系结构等
  • 编译器架构

编译器内部结构

简述

编译器具有非常模块化的高层结构

编译器可以看做多个阶段构成的“流水线” 结构

编译器规模庞大,拆分模块容易实现和维护

一种没有优化的编译器结构

7346071-589ddf961bef38d2.png
一种没有优化的编译器结构

一种更复杂的编译器结构

7346071-c19fe0a2a913a1c7.png
一种更复杂的编译器结构

编译器通常会被划分为两个部分(如下图):

  • 前端:源代码生成中间代码,和源代码有关
  • 后端:中间代码生成目标代码并优化,和目标代码有关
  • 两者以抽象语法树 AST(Abstract Syntax Tree) 作为连接数据

[图片上传失败...(image-188606-1560422078662)]

一个简单的例子

背景一:现在我们设计一个叫做 Sum 的语言,特别简单,仅仅支持两种语法。第一是整形数字 n ,第二是加法表达式 e1 + e2 。举几个例子:

  • 3
  • 5 + 6
  • 7 + 8 + 9 (加法要满足左结合性,即先计算 7 + 8
  • 7 + (8 + 9)
  • 但不支持 7 + 8 * 9 ,Sum 语言中没有乘法

背景二:有一个栈式计算机 Stack (后面会再次讲到),其中有一个操作数栈,只支持两条指令,push nadd 。之所以选择栈式计算机,第一是因为简单,第二是因为 JVM 就是采用了这种形式。其指令的详细解释例子如下:

  • push 3 将 3 压栈
  • push 4 将 4 压栈
  • add 将 3 和 4 出栈,然后做加法得到 7 ,再将 7 压栈。即将栈顶的两个元素都出栈,做加法,将结果再压栈

有了上述两个背景之后,接下来的任务是:将程序 1 + 2 + 3 编译到栈式计算机 Stack 。

第一个阶段是词法分析,先不管其中的原理是什么,总之词法分析会将 1 + 2 + 3 拆分为 1 + 2 + 3 这 5 个部分。(后面会提到词法分析的原理就是用正则表达式匹配)

第二阶段是语法分析,就是将词法分析拆分出来的内容,分析是否满足 Sum 语言的语法要求,即 ne1 + e2 这种语法。

第三个阶段是语法树构造(有时算在语法分析阶段里) ,经过某些计算之后(可以看出是按中序遍历生成了二叉树),得到的抽象语法树如下图:

[图片上传失败...(image-d8b081-1560422078662)]

第四个阶段是根据抽象语法树做代码生成。首先,要满足加法的左结合性,对树进行遍历的时候就要优先遍历左子树,即后序遍历(左右根)

在遍历树节点的过程中,如果遇到整数 n 就生成一条 push n 指令,如果遇到 + 就生成一条 add 指令。

接下来详细看一下这棵树的遍历过程:

  • 第一步要访问的节点是 1 ,生成 push 1 ,将 1 压栈
  • 第二步要访问的节点是 2 ,生成 push 2 ,将 2 压栈
  • 第三步要访问的节点是 + ,生成 add ,将 1 2 出栈,计算加法得到 3 ,将 3 压栈 (这里即体现了加法的左结合性)
  • 第四步要访问的节点是 3 ,生成 push 3 ,将 3 压栈
  • 第五步要访问的节点是 + ,生成 add ,将 3 3 出栈,计算加法得到 6 ,将 6 压栈,完成

词法分析

词法分析简介

简介

从编译器内部结构得知,执行编译的第一个阶段就是词法分析。

词法分析的任务:将字符流转为记号流

字符流即源程序代码,记号流即编译器内部定义的数据结构、编码所识别出的词法单元

词法分析即将源程序代码与编译器内部定义的数据结构相对应

通俗来说,就是将源代码进行最细粒度的拆解,例如上面的例子将 1 + 2 + 3 拆分为 1 + 2 + 3 一样

一个例子

[图片上传失败...(image-58705d-1560422078662)]

如上图,从源代码到记号流(单词流)。

词法分析器会将源程序根据关键字、标识符(变量)、括号、引号、运算符、值(整数、字符串)等这些要素,将其从左到右拆分为若干个记号(单词),其中会忽略空格和换行等。上图中记号流输出的含义:

  • IF 关键字
  • LPAREN RPAREN 左右括号
  • INDENT(x) 即标识符(变量),有一个属性 x ,表示变量名
  • GR>
  • INT(t)int 类型值,属性是 5
  • 其他同理……
  • 最后红色的 EOF 是结束符

根据上面的例子,可以总结出 token 其实有固定的形式,就可以定义其数据结构,如下图(本文中高级语言的示例,默认情况下都是 C 语言)

[图片上传失败...(image-61f347-1560422078662)]

理解了例子,定义了数据,接下来就要去探寻词法分析的实现算法,第一,手工构造;第二,自动生成 。

词法分析的手工构造法

手工构造即手写一个词法分析器,例如 GCC LLVM ,优点是利于掌控和优化细节,缺点是工作量大、易出错。手工构造法主要用到“转移图”这种数据结构,下面举两个例子说明。

[图片上传失败...(image-fafc05-1560422078662)]

上图的转移图模型,即可识别逻辑运算符,如 <= < <> >= > 。识别到第一个字符,就继续往下做分支判断,直到返回一个确定的运算符。

图中的 * 即一次回溯,即将当前的这个字符再返回到词法分析器重新进行分析。

例如 >1 ,读到了 1 这个字符时,此时已经确定了运算符是 > ,而当前的 1 并不是运算符的一部分,因此将 1 再重新返回到词法分析器中重新进行分析。

[图片上传失败...(image-8d0828-1560422078662)]

上图是标识符(变量)的转移图模型,以及伪代码。其中 * 即一次回溯,跟上面一样。

关键字(如 class if for 等)是一种特殊的标识符,也满足标识符的规则。

要识别关键字,有两种解决方案:

  • 继续扩展转移图的分支,识别到关键字走不通的分支逻辑,最后识别出关键字。
  • (关键字表算法) 先识别所有的合法标识符,然后从已经识别出来的标识符中查找关键字。此时需要为该语言所有的关键字维护一个哈希表,如果数据结构合理(完美哈希),查询可以在 O(1) 复杂度内完成。

词法分析的自动生成技术

简介

所谓自动生成技术,就是有这样现成的工具(如 lex flex jlex),输入一些声明式的规范,即可自动生成一个词法分析器。优点当然是简单快速,缺点就是无法控制细节。而这里的“声明式规范”,就是我们常见的正则表达式。下文的内容,就是如何用程序去解析正则表达式,如果你之前看过关于“正则表达式 原理”这类的文章,可能早就有了解了。

先说一下自动生成技术的几个阶段,专业术语后面都有解释:

  • 正则表达式 -> NFA(Thompson 算法)
  • NFA -> DFA(子集构造算法)
  • (DFA的优化( Hopcroft 最小化算法))
  • DFA -> 词法分析代码,即完成自动生成
正则表达式
概念解释

正则表达式是一种数学上的概念,

首先它要有一个完整的字符集 Σ = {...} 要能涵盖程序所有的关键字、变量名、数字、运算符、括号、引号、特殊符号等

  • 如 C 语言的这个字符集就是ASC 编码,即 256 个字符
  • 如 java 的字符集就是 unicode 编码,可能几万甚至十几万个字符集(因为 java 的变量名称并不仅限于英文、中文也可以作为变量)

然后只有以下几个基本的逻辑:

  • 空串 \varepsilon 是正则表达式
  • 对于字符集中的任意单个字符是正则表达式
  • 如果M和N是正则表达式,则以下也是正则表达式:
    • 选择 M|N 两者取并集
    • 连接 MN 是正则表达式,两者相连
    • 闭包 M*={ \varepsilon​ ,M,MM,MMM...} 称为“闭包”(和程序的闭包不一样),即可以有 0 或者若干个 M
  • 以上随机组合,都是正则表达式,例如 a|(bc*)

这就是正则表达式的定义,而现代正则表达式这么多的语法,例如 [a-b] ? + 等,都是后来扩展出的语法糖,即对基本规则的一种简写方式。

7346071-3e8e400462e49767.jpg
正则表达式的形式表示
正则表达式例子
  • C语言中的关键字,例如if,while等,如何用正则表达式表示?

    就是用 if , while 表示,因为 i 是字符集(C语言对应ASC字符集)的元素, f 是字符集中的元素,所以 它们连接形成if 是正则表达式

    同理可说明为什么其它关键字是正则表达式

  • C语言中的标识符:以字母或下划线开头,后跟零个或多个字母、数字或下划
    线。如何用正则表达式表示?

    (a|b|c|...|z|A|B|...|Z|_ )(a|b|c|...|z|A|B|...|Z|_ |0|1|2...|9)*

    如果用语法糖来表示的话就是[a-zA-Z_] [a-zA-Z_0-9]*

语法糖
7346071-63554f402cb9db45.jpg
语法糖
有限状态自动机 FA

也称“有穷自动机”,是一种数学模型。

简单理解,就是输入一个字符串,输出这个字符串是否满足某个规则(true / false)。

FA(有限状态自动机) 实质上是带有边和节点的有向图。

  • 9
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值