翻译一种语言(源语言)的程序成为等价的另一种语言(目标语言)的程序,并能报告源语言中错误的一种程序。
读入一个可执行的程序并产生执行该程序的结果的一种程序。
C 是编译过程
Scheme 是解释过程
Java 结合了编译与解释过程
程序可能被分割成为多个模块,并存放于独立的文件中。把源程序聚合在一起的任务有时会由一个被称为预处理器(preprocessor)的程序独立完成。预处理器还负责把那些称为宏的缩写形式转换为源语言的语句。
然后,将经过预处理的源程序作为输入传递给一个编降器。编译器可能产生一个汇编语言程序作为其输出,因为汇编语言比较容易输出和调试。接着,这个汇编语言程序由称为汇编器(assembler)的程序进行处理,并生成可重定位的机器代码。
大部分程序经常被分为多个部分进行编译,一个文件中的代码可能指向另一个文件中的位置,而链接器(linker)能够解决外部内存地址的问题。最后,加载器(loader)把所有的可执行目标文件放到内存中执行。
1.词法分析——线性的非递归的,仅识别各个“单词” ,它们是该语言的Tokens
2.语法分析-——为了识别表达式的构造需要递归
3.语义分析——决定该句子是否有且仅有一种无二义的解释
4.中间代码生成
5.代码优化
6.目标代码生成
C:强制式的、冯·诺依曼式的、第三代
C++:强制式的、冯·诺依曼式的、面向对象的、第三代
Cobol:强制式的、冯·诺依曼式的、第三代
Fortran:强制式的、冯·诺依曼式的、第三代
Java:强制式的、冯·诺依曼式的、面向对象的、第三代
Lisp:声明式的、函数式的、第三代
ML:强制式的、函数式的
Perl:强制式的、脚本语言
Python:强制式的、脚本语言
VB: 强制式的、面向对象的
a)w = 13 ,x = 11 ,y = 13 , z = 11
b)w = 9 ,x = 7 ,y = 13 ,z = 11
对于图1-14中的块结构代码,假设使用常见的声明的静态作用域规则,给出其中12个声明中的每一个的作用域。
w1——B1 - B3 - B4
x1——B1 - B2 - B4
y1——B1 - B5
z1——B1 - B2 - B5
x2——B2 - B3
z2——B2
w3——B3
x3——B3
w4——B4
x4——B4
y5——B5
Z5——B5
1) 终结符号的集合
2)非终结符号的集合
3)产生式的集合
4)指定一个非终结符号为开始符号
根据文法推导符号串时,我们首先从开始符号出发,不断将某个非终结符号替换为该非终结符号的某个产生式的体。
依据上面的文法,对产生串 9 - 5 + 2 进行推导
1)根结点的标号为文法的开始符号。
2)每个叶子结点的标号为一个终结符号或ε。
3)每个内部结点的标号为一个非终结符号。
4)如果非终结符号A是某个内部结点的标号,并且它的子结点的标号从左至右分别为X1,X2,…,Xn,那么必然存在产生式A→X1 X2 … Xn,其中X1,X2,…,Xn 既可以是终结符号,也可以是非终结符号。作为一个特殊情况,如果A→ε是一个产生式,那么一个标号为A的结点可以只有一个标号为ε的子结点。
一个文法可能有多棵语法分析树能够生成同一个给定的终结符号串。这样的文法称为具有二义性(ambiguous)。要证明一个文法具有二义性,我们只需要找到一个终结符号串,说明它是两棵以上语法分析树的结果。
个人说明一个快速判断一个文法是否具有二义性:只要该文法的某一个产生式的右部的非终结符是对称的,那这个文法一般就是具有二义性的。 (充分条件)
例如:
文法如下
(其中产生式 string -> string + string 右部的非终结符是对称的,所以他是二义性文法。) —— 选择题可以用,不能作为证明或者简答
因为该文法可以推导产生串 9 - 5 + 2 有两棵分析树,所以该文法具有二义性。
S -> S S *
-> S S + S *
-> a S + S *
-> a a + S *
-> a a + a *
用变量a进行乘和加操作的后缀表达式
下面的各个文法生成什么语言?证明你的每一个答案
1)生成至少1个0后面跟着至少1个1构成的表达式
2)生成用a进行加和减操作的前缀表达式
3)生成匹配的括号的表达式
4)生成任意个a和b构成的表达式
5)【不好描述,难搞!】生成以a+a,aa ,a* ,(a)组合或者嵌套的表达式。
练习2.2.2中哪些文法具有二义性?
1)2)不是,3)4)5)是
字母表:有限符号的集合。
串:来自字母表符号的有限序列
串S的前缀:截取串S首字母开始,连续的,长度为n(0 <= n <= | S | ) 的串,串的前缀最多有 N + 1 个。( N = | S | )
串S的真前缀:串S的前缀的集合删除 ε 和 串S本身。
串S的后缀:删除串S字母开始,连续的,长度为n(0 <= n <= | S | ) 的串后的串,串的后缀最多有 N + 1 个。( N = | S | )
串S的真后缀:串S的后缀的集合删除 ε 和 串S本身。
串S的子串:截取串S中连续的,长度为n(0 <= n <= | S | ) 的串,串的子串最多有 N ( N + 1 ) / 2 + 1个。( N = | S | )
串S的真子串:串S的子串的集合删除 ε 和 串S本身。
串S的子序列:截取串S中长度为n(0 <= n <= | S | ) 的串,串的子序列最多有 2 N 个。( N = | S | )
一个正则表达式是规则的集合,用于从一个字母表构造符号串的技术。
设A是个字母表,r 是个正则表达式,那么 L( r ) 是个由 r 的规则所刻画的语言。
归纳基础:
ε是一个正则表达式,L(ε) = lεI,即该语言只包含空串。
如果a是A上的一个符号,那么α是一个正则表达式,并且L(α)=l a l。也就是说,这个语言仅包含一个长度为1的符号串a。
归纳步骤:
由小的正则表达式构造较大的正则表达式的步骤有四个部分,假定r和s都是正则表达式,分别表示语言L( r )和L( s ),那么:
按照上面的定义,正则表达式经常会包含一些不必要的括号。如果我们采用如下的约定,就可以丢掉一些括号:
一元运算符*具有最高的优先级,并且是左结合的。
连接具有次高的优先级,它也是左结合的。
| 的优先级最低,并且也是左结合的。
将正则表达式与名字结合,形式为产生式,列如:letter -> A | B | … | Z | a | b | … | z | 。
一个或多个实例。单目后缀运算符 + 表示一个正则表达式及其语言的正闭包。运算符 + 和运算符*具有同样的优先级和结合性。两个有用的代数定律 r* = r+ l ε 和 r+ = r r* = r* r 说明了闭包*和正闭包之间的关系。
零个或一个实例。单目后缀运算符 ? 的意思是“零个或一个出现”。也就是说,r?等价于r l ε。运算符?与运算符+和运算符*具有同样的优先级和结合性。
字符类。一个正则表达式 a l a1 l … l an。(其中 ai 是字母表中的各个符号)可以缩写为[ a1a2…an.]。更重要的是,当a1,a2,…,,an形成一个逻辑上连续的序列时,比如连续的大写字母、小写字母或数位时,我们可以把它们表示成a1-an。也就是说,只写出第一个和最后一个符号,中间用连词符隔开。因此,[ abc ] 是 a l b l c 的缩写,[ a-z ]是 a l b l … I z 的缩写。
1)以字符a作为开始和结束,中间穿插任意个字符a或b的语言。
2)以任意个字符a和字符b构成的语言
3)以任意个字符a和字符b构成的,长度大于等于3且倒数第三个字符为a的语言
4)以任意个字符a和3个字符b构成的语言
5)以任意偶数个字符a和字符b构成的语言
试说明在一个长度为n的字符串中,分别有多少个
前缀——n + 1
后缀——n + 1
真前缀——n - 1
子串——n(n + 1) / 2 + 1
子序列—— 2n
1)other -> [ bcdfghjklmnpqrstvwxyz ]
other* a ( other | a )* e ( other | e )* i (other | i )* o (other | o )* u ( other | u )*
2)a* b* … z*
确定的和不确定的有穷自动机能识别的语言的集合是相同的。事实上,这些语言的集合正好是能够用正则表达式描述的语言的集合。这个集合中的语言称为正则语言。
一个不确定的有穷自动机(NFA)由以下几个部分组成:
不管是 NFA还是DFA,我们都可以将它表示为一张转换图( transition graph)。图中的结点是状态,带有标号的边表示自动机的转换函数。从状态 s 到状态 t 存在一条标号为 u 的边,当且仅当状态 t 是状态 s 在输入 a 上的后继状态之一。这个图与状态转换图十分相似,但是:
① 同一个符号可以标记从同一状态出发到达多个目标状态的多条边。
② 一条边的标号不仅可以是输入字母表中的符号,也可以是空符号串 ε 。
例如:( a | b )* abb 的NFA
我们也可以将一个 NFA表示为一张转换表( transition table),表的各行对应于状态,各列对应于输入符号和 ε。对应于一个给定状态和给定输入的条目是将 NFA的转换函数应用于这些参数后得到的值。如果转换函数没有给出对应于某个状态 – 输入对的信息,则填空集符号。
例如:( a | b )* abb 的转换表
确定的有穷自动机(简称DFA)是不确定有穷自动机的一个特例,其中:
为 a ( a | b )* a 设计一个DFA 和 NFA。
给出以下练习的NFA的转换表:
1)
2)
3)
“语法制导”——按正规式的语法规则指引构造过程。
“语法制导”构造的特点:
“语法制导”构造的构造步骤:
第一步 划分出正规式的子表达式
第二步 为每个子表达式构造相应的NFA“ 片断 ”
第三步 将得到的NFA“片断”按一定规则拼接成NFA。
示例: ( ab*c ) | ( a ( b | c* ) )
定理:
设L为一个由不确定的有穷自动机接受的集合,则存在一个接受L的确定的有穷自动机。
算法:
子集法思想
算法步骤:
定义对状态集合I的有关运算:
状态集合I的ε-闭包,表示为ε-Closure(I)。
解释:集合I中的状态经过任意条弧ε能到达的状态的集合。
状态集合I的a弧转换,表示为move(I,a)。
解释:集合I中的状态只经过一条弧a能到达的状态的集合。
示例:( a | b )* abb 的NFA
第一步,计算ε-Closure( { 0 } ) 。
ε-Closure( { 0 } ) = { 0,1,2,4,7 } = A
第二步,对A
第三步,对B
第四步,对C
第五步,对D
第五步,对E
一个有穷自动机可以通过消除多余状态和合并等价状态而转换成一个最小的与之等价的有穷自动机。
分割法思想:
把DFA分成一些不相交的子集,使得任何不同的两子集的状态都是可区别的,而同一子集中的任何两个状态都是等价的。
示例:
第一步,根据是否为结束状态将所有状态分为两个集合。(有时可能所有状态都是结束状态,所以只存在一个集合)
I1 = { A ,C,D }
I2 = { B,F }
第二步,考察集合的元素个数大于1个的集合
对I1:
move( A,a ) = D 属于 I1,move( A,b ) = D 属于 I1 。
move( C,a ) = F 属于 I2,move( C,b ) = D 属于 I1 。
move( D,a ) = B 属于 I2,move( D,b ) = D 属于 I1 。
发现A与CD经过一条弧到的的集合不同,将A分离单独一个集合,得到下面的集合:
I1 = { C,D }
I2 = { B,F }
I3 = { A }
对I2:
move( B,a ) = F 属于 I2,move( B,b ) = C 属于 I1 。
move( F,a ) = F 属于 I2,move( F,b ) = D 属于 I1 。
发现Bf经过一条弧到的的集合相同,不用分离。
错误类型的不同层次:
错误处理:
语法分析树是推导的图形表示形式,它过滤掉了推导过程中对非终结符号应用产生式的顺序。语法分析树的每个内部结点表示一个产生式的应用。该内部结点的标号是此产生式头中的非终结符号A,这个结点的子结点的标号从左到右组成了在推导过程中替换这个A的产生式体。
一棵语法分析树的叶子结点的标号既可以是非终结符号,也可以是终结符号。从左到右排列这些符号就可以得到一个句型,它称为这棵树的结果(yield)或边缘(frontier)。
如果一个文法可以为某个句子生成多棵语法分析树,那么它就是二义性的(ambiguous)。换句话说,二义性文法就是对同一个句子有多个最左推导或多个最右推导的文法。
文法是比正则表达式表达能力更强的表示方法。每个可以使用正则表达式描述的构造都可以使用文法来描述,但是反之不成立。换句话说,每个正则语言都是一个上下文无关语言,但是反之不成立。
例如,语言L = { anbn I n≥1 }是一个可以用文法描述但不能用正则表达式描述的语言的原型例子。
我们通俗地说“有穷自动机不能计数”,这意味着有穷自动机不能接受像{ anbn I n≥1 }这样的语言,因为它不能记录下在它看到第一个b之前读入的a的个数。类似地,“一个文法可以对两个个体进行计数,但是无法对三个个体计数”。
1)
2)
3)
4) 不是
5)进行加法和乘法的后缀表达式集合
把左递归的产生式A=>Aα | β替换为非左递归的产生式:
A => βA’
A’ => αA’ l ε
推广到一般式:
消除非立即的左递归:
将A中的S进行推导,
在对A进行立即左递归的消除,
First集:终结符的First集为本身。
对产生式A => αβ 进行分析,
Follow集:起始将 $ 放入 Follow( S ) 中,其中 S 为文法开始符号, $ 为结束标记
对产生式 A => αBβ 进行分析,
一个文法G是LL(1)的,当且仅当G的任意两个不同的产生式 A→α l β 满足下面的条件:
前两个条件等价于说First(α) 和 First(B)是不相交的集合。第三个条件等价于说如果 ε 在First( β )中,那么 First(α)和Follow( A )是不相交的集合,并且当 ε 在 FIRST( α )中时类似结论成立。
例如:
1)将文法中的“或”消去,即展开文法。
2)计算所有非终结符的First集和Follow集
计算First集和Follow集注意不要忘记该终结符可以推出 ε 的情况!!!
3)根据展开的文法和First集、Follow集 构造预测分析表
对每一个产生式 A => α 进行分析,
另一种算法:Select( A => α )集的定义:
First( α ) 不包括ε,Select( A => α ) = First( α )
First( α ) 包括ε,Select( A => α ) = ( First( α ) - ε ) U Follow( A )
将每一个产生式A => α 填入对应的Array[ A , a ] ,其中a属于Select( A => α )
规约就是最右推导的逆过程。
如果有S=*=> αAω => αBω ,那么紧跟 α 的产生式 A→B 是 αBω 的一个句柄( handle)。换句话说,最右句型γ的一个句柄是满足下述条件的产生式A→β及串β在y中出现的位置:将这个位置上的β替换为A之后得到的串是γ的某个最右推导序列中出现在位于γ之前的最右句型。
有些上下文无关文法不能使用移入-归约语法分析技术。即使知道了栈中的所有内容以及接下来的 k 个输入符号,我们仍然无法判断应该进行移人还是归约操作(移入/归约冲突),或者无法在多个可能的归约方法中选择正确的归约动作(归约/归约冲突)。我们把它们称为非LR文法。LR( k )中的k表示在输人中向前看k个符号。在编译中使用的文法通常属于LR(1)文法类,即最多只需要向前看一个符号。
一个文法G的一个 LR(O)项(简称为项)是G的一个产生式再加上一个位于它的体中某处的点。因此,产生式A→XYZ产生了四个项:
产生式A→ε 只生成一个项A→·。
如果G是一个以S为开始符号的文法,那么G的增广文法G’就是在G中加上新开始符号S’和产生式S’→S而得到的文法。引入这个新的开始产生式的目的是告诉语法分析器何时应该停止语法分析并宣称接受输入符号串。也就是说,当且仅当语法分析器要使用规则S’=>S进行归约时,输入符号串被接受。
1-4步与LR(0) 一样,在构造Action表和Goto表时多考虑了Follow集。在规约项只填Follow集的元素,在移进-规约冲突的项判断:
解决了LR(0)的移进-规约冲突。
构造项集时比LR(0)多了向前搜索符,向前搜索符开始产生式为 $ ,其他产生式则:
构造Action表和Goto表是,类似SLR,只不过不适用Follow集而使用向前搜索符。
LALR(1)则就是合并同心集的LR(1),不过合并后如果产生规约规约冲突,则不能合并。
文法是LR(0),就一定是SLR。
综合属性:结点N上的综合属性只能通过N的子结点或者N本身来定义。
继承属性:结点N上的继承属性只能由N的父结点、N的兄弟结点和N本身来定义。
终结符可以由综合属性,但不能由继承属性。
就是在语法分析树的继承上用语义规则来代替结点并计算好相应的属性值。
结合语法分析树,按照语义规则画好依赖。
如果一个SDD每个属性都是综合属性,那它就是S属性的。
对于S属性的SDD,可以按照语法分析树自底向上的顺序来计算它的各个结点的属性值。
如果一个SDD每个属性要么是综合属性,要么是有限制的继承属性,其中对继承属性的限制为,它的属性计算只能使用它的父结点,左边兄弟结点和自己本身结点。
对于L属性的SDD,适合自顶向下的语法分析顺序。
将语法分析树的去掉非终结符,合并相同子树,即为有向无环图(DAG)
对DAG进行自底向上的顺序,按照< op , l , r >的格式书写,其中op为标号,l为左结点,r为右结点。变量和值特殊处理。
注意:左右结点的值不能随便选择,看表达式来确定!!!
将指令:
do {
i = i + 1;
} while( a[ i ] < v );
格式为< op , arg1 , arg2 , result >
示例:
格式: < op , arg1 , arg2 >