引入——编译过程层次划分
1.什么是编译?
指的是将一种源语言程序转换成目标语言程序的过程。
这个过程通常由编译器(Compiler)来完成,编译器是一个软件工具,它接受一个源程序作为输入,然后进行一系列的词法分析、语法分析、语义分析、优化和代码生成等阶段,最终输出目标语言的等效程序。
2.编译器的结构(前端后端)
前端部分(词法分析器——语法分析器——语义分析器——中间代码生成器)
后端部分(目标代码生成器——机器相关代码优化器)
前端主要是分析部分,对源语言进行词法,语法,语义分析,得到中间代码。
后端是综合部分,与目标语言相关,由中间表示形式得到目标代码。
3.词法分析
任务:从左向右逐行扫描源程序的字符,识别出各个单词,确定单词的类型。将识别出的单词转换为统一的机内表示——词法单元(token)形式。
token:< 种别码,属性值 >
经过词法分析后得到token序列。
例子:
4.语法分析
任务:从词法分析器中输出的token序列中识别出各类短语,并构造语法分析器。
5.语义分析
任务:
集标识符的属性信息。包括:种属,类型,存储位置,长度,值,作用域,参数和返回值信息。
语义检查。
6.中间代码生成
中间表示形式:三地址码,语法树。
7.目标代码生成
任务:以源程序的中间表示形式作为输入,并把它映射到目标语言。目标代码生成的一个重要任务是为程序中使用的变量合理的分配寄存器。
8.代码优化
为改进代码所进行的等价程序转换,使其运行得更加快一些,占用空间更小。
第一章:语言及其文法
字母表
是一个有穷符号集合。符号:字母,数字,标点符号。
运算包括:
乘积:
n次幂:
正闭包:长度为正数的符号串集合
克林闭包:正闭包+空串
串
设A为一个字母表,任意 s属于A*(字母表的克林闭包),s称为是A上的一个串。
串是字母表中符号的一个有穷序列。
|s| :表示串s的长度。也就是串中的符号个数。空串就是长度为0的串。
运算包括:
连接:
幂:
文法
文法解决了无穷语言的有穷表示形式。
G = (Vt , Vn, P, S)
Vt : 终结符集合
Vn :非终结符集合,用来表示语法成分的符号,也叫做语法变量。
注:Vt和Vn相交为空集。并集为文法符号集。
P:产生式,描述了终结符和非终结符组合成串的的方法。一般形式为:a—>b 意为a定义为b。产生式中a,一定含有Vn的字符。
S:开始符号,S属于Vn,表示该文法中最大的语法成分。
约定:不引起歧义的前提下文法可以只写产生式。
产生式写法:
或 | :a -> b1 | b2 | b3 ,意为a定义为b1 || b2 || b3
注:除非特别说明,第一个产生式的左部就是开始符号。
推导和规约
推导就是用产生式的左部替换产生式的右部。
规约就是用产生式的右部替换产生式的左部。
有了文法如何判断串是该文法的句子?
能通过该文法的产生式,从开始符号推导出串。或者从串通过该文法的产生式往回规约,看能不能归约回开始符号。
句型和句子
如果由开始符号S推出a,a属于(Vt U Vn)的克林闭包,则称a是G的一个句型。
句型中可以包含终结符,又可以包含非终结符,也可能是空串。
如果由开始符号S推出a,a属于(Vn)的克林闭包,则称a是G的一个句子。
句子就是不包含非终结符的句型。
文法的分类体系
0型,1型,2型,3型文法。
0型文法:无限制文法。任意 a -> b 属于P ,a中至少包含1个非终结符。
1型文法:上下文有关文法。任意 a -> b 属于P,|a| <= |b|。右部的符号数要大于等于左部。
不包含空产生式。
2型文法:上下文无关文法。任意 a -> b 属于P,a属于Vn。
能描述程序设计语言中的大多数句子。
3型文法:正则文法。分为右线性文法和左线性文法。是对2型文法的进一步约束。
右线性文法:A->wB || A->w
左线性文法:A->Bw || A->w
正则文法能描述程序设计语言的大多数单词。
由什么文法推出的就是什么语言。
四种文法之间关系:逐级增加限制
满足3型文法就满足2型文法,满足2型文法就满足1型文法,满足1型文法就满足0型文法。
分析树
分析树的根节点为文法的开始符号。内部结点表示对一个产生式的应用。叶节点可以是非终结符,也可以是终结符。
分析树是推导的图形化表示。
短语:给定一个句型,其分析树中每一棵子树的叶节点拼接称为该句型的一个短语。
直接短语:如果子树只有父子两代节点,那么这棵子树的叶节点拼接称为该句型的直接短语。
直接短语一定是某个产生式的右部。
文法二义性
一个文法可以为一个句型构造不止一个分析树,就是文法的二义性。
二义性是编译中不希望出现的,会导致歧义。
第二章:词法分析
正则表达式(RE)
是一种用来更紧凑的描述正则语言的表示方法。由较小的正则表达式按照特定规则递归的构建。每一个正则表达式定义一个语言。
正则表达式的使用:
可以用RE定义的语言叫做正则语言或者正则集合。
正则定义:给正则表达式起个新名字。
有穷自动机(FA)
有穷控制器:具有有穷个状态数,根据当前的状态和当前输入符号控制转入下一状态。
可用转换图来表示。
FA接收的语言:给定输入串X,如果存在一个对应于串X的从初始状态到某个终止状态的转换序列,则称串X被FA接收。
由一个有穷自动机接收的所有串构成的集合称为是该自动机接收的语言,记作L(M)。
最长子串匹配原则:
当输入串的多个前缀与一个或者多个模式匹配时,总是选择最长的前缀进行匹配。在到达某个终态之后,只要输入带上还有符号,FA就继续前进,以便找到最长的匹配。
在这个例子中,若输入串为<=,根据最长子串匹配原则,就不会在遇到<到达终态1时结束,还会继续往后匹配,从而匹配<=。
有穷自动机的分类(DFA和NFA)
确定的有穷自动机(DFA)和非确定的有穷自动机(NFA)
先把区别说前面,往后看就会理解:对于识别同一语言的等价的DFA和NFA,从形式上,NFA更加直观,但从计算机实现上DFA更加容易。
DFA:
结合例子理解:
结合转换图分析自动机的参数内容:
S:圈圈0,1,2,3表示的四种状态。
:字母表包含:a,b
:转换函数用表来表示:就是结合转换图中,状态遇到输入后的变化。
:这一步,0状态遇到b时,就会进入到0状态。
:这一步,0状态遇到a时,就会进入到1状态。
S0:开始状态:也就是start
F:终止状态:也就是状态3。有两个圈圈的状态。
一个有穷自动机可用转换图表示也可用转换表表示。
NFA:
定义和DFA没啥区别,就是状态转换函数表示不太一样。
以例子说明:
转换表中发生了一些变化,NFA中写的状态遇到输入后可能得状态集合。转换图中没有,就是空集。
因此DFA和NFA是等价的。只是对同一转换图以不同的方式描述。
综上:正则文法 == 正则表达式 == FA
带空边和不带空边的NFA的等价性:
由RE构造NFA
直接从正则表达式(RE)转换为确定的有穷自动机(DFA)是很困难的,所以中间使用非确定的有穷自动机(NFA)过度。
如图所示:
正则表达式对应NFA形式。
结合例子理解这一概念:此例便是直接将给定正则表达式转换为NFA。
简单说过程就是,先一步写出,再进行拆分。将一个大的正则表达式拆分为小的。
由NFA构造DFA
第三章:语法分析-自顶向下
从分析树的根节点向叶节点方向构造分析树。从开始符号推导出词串的过程。
最左推导:总是选择每个句型的最左非终结符进行替换。相应的是最右归约。
自顶向下分析采用最左推导方式。
最右推导:总是选择每个句型的最右非终结符进行替换。相应的是最左归约。
在自底向上的分析中,总是采用最左归约的方式,因此把最左归约称为规范归约,而最右推导相应的称为规范推导。
文法转换
消除直接左递归:将左递归转换成右递归。
看实例学习此方法:
LL1文法
文法解释
LL(1) 文法是一种上下文无关文法,
LL 表示从左到右扫描输入,同时从左到右(Left-to-right scanning, Leftmost derivation)构建推导;
1 表示在任何时刻,分析器最多向前看一个输入符号(Lookahead),即分析决策只依赖于当前输入符号和一个向前看符号。
预测分析法的工作流程:从文法的开始符号开始,在每一步推导过程中根据当前句型的最左非终结符和当前输入符号,选择正确的产生式。为保证分析的确定性,选出的候选式必须唯一。
所以希望每个产生式的右部都以终结符开始,
同一非终结符的各个候选式的首终结符都不相同。
什么时候使用空产生式:如果当前某非终结符与输入符不匹配时,若存在非终结的的空产生式,可以通过检查输入符是否可以出现在非终结符的后面,来决定是否使用空产生式。
因此引入非终结符的后继符号集(FOLLOW)
可能在某个句型中紧跟在A后边的终结符a集合,记为FOLLOW(A)。
如果A是某个句型的最右符号,则将结束符 “$” 添加到FOLLOW(A)中。
产生式的可选集
产生式 A->B 的可选集是指可以选用该产生式进行推导时对应的输入符号的集合,
记为SELECT(A->B)。
串首终结符集:串首第一个符号并且是终结符。
给定一个文法符号串a,a的串首终结符集FIRST(a)被定义为可以从a推导出所有串首终结符构成的集合。如果a能推出空集,那么空集也在FIRST(a)中。
LL(1) 文法的要求:
文法必须是上下文无关文法。
对于每个非终结符号 A 和每个可能的向前看符号 a,文法的产生式必须满足以下两个条件之一:
不存在 A -> α | β 这样的产生式,其中 α 和 β 是两个不同的右部(产生式右边的符号串),且 α 和 β 开头的终结符号串有可能相同。
如果 A -> α 是一个产生式,那么对任何的终结符号串 a, α 不可能产生一个以 a 开始的句子。
文法G是LL(1)的,当且仅当G的任意两个具有相同左部的产生式 A -> a | b 满足以下条件:
FIRST集和FOLLOW集的计算
符号的FIRST集
FIRST(X):可以从X推导出的所有串首终结符构成的集合,如果X可以推出空集,那么空集也属于FIRST集。
看例子理解:
1.产生式左部以终结符打头,终结符直接加入FIRST集。
2.如果可以推出空集,那么空集也加入FIRST集。
3.如果左部以非终结符打头,那么加入打头非终结符的FIRST集。
串 X1 X2 ... Xn 的FIRST集:
1.向FIRST(X1 X2 ... Xn)中加入FIRST(X1 )中所有的非空符号。
2.如果FIRST(X1 )中包含空集,再往FIRST(X1 X2 ... Xn)中加入FIRST(X2 )中所有的非空符号,以此类推......
3.最后,如果所有X的FIRST集中都包含空集,那么将空集加入FIRST(X1 X2 ... Xn)。
计算非终结符A的FOLLOW集
FOLLOW(A):可能在某个句型中紧跟在A后面的终结符a的集合。
如果A是某个句型的最右符号,则将结束符 “$” 添加到FOLLOW(A)中。
先看实例:
FOLLOW集的计算是十分复杂的,
1.E为开始符号,但是将 $ 加入其中。
2.T的FOLLOW集看后面的E`,所以有+号。当E`推出空时,E->T 那么T的FOLLOW集中加上E的FOLLOW集。
3.E->TE` 那么E`的FOLLOW集元素==E的FOLLOW集元素。
4.T->FT` 并且 T`->*FT`|空 ,那么F的FOLLOW集中就有 * ,当T`推出空时,T->F,那么F的FOLLOW集中再加上T的FOLLOW集。
5.T`的FOLLOW集等于T的FOLLOW集。
6.根据F->(E)| id ,E的FOLLOW集再加上)。
7.此时开始符号E的FOLLOW集改变了,那么又得从头,再改变一次所有的FOLLOW集。
第四章:语法分析-自底向上
它的目标是从输入符号串推导出文法的起始符号,也就是找到输入串的一个最左归约(最右推导)
移入-规约分析
包含四种动作:移入,归约,接收,报错。
保证了每一步归约都是最左归约。
在对输入串从左到右的扫描过程中,语法分析器将零个或多个符号移入栈顶,直到可以对栈顶符号串进行归约,然后将这个符号串归约为某个产生式的左部,不断重复这个过程。直到检测出错误停止,或者栈中包含了开始符号并且输入缓冲区为空时完成语法分析。
L-R分析
L:对输入进行从左到右的扫描。
R:反向构造出一个最右推导序列。
芠旿sky: 是这样的,准确的解法确实是判断文件大小,但是由于我做的项目中如果不断的去计算文件大小,这期间就会丢失很多数据,毕竟io是很慢的,所以就改用了控制时间,一次写入,这样文件大小的精度没那么准确,但误差也不大,主要是保证了数据不丢失
gungun~~: 感谢楼主提供的脚本和思路。控制文件大小,脚本是通过20s的输出结果来限制的,并没有按照单个文件最大100M数据来判定,我的想法是也可以加个文件大小判断,大于100M之后再file_count加1,清空下一个文件
CSDN-Ada助手: MySQL入门 技能树或许可以帮到你:https://edu.csdn.net/skill/mysql?utm_source=AI_act_mysql