编译原理常见语法
Ⅰ 编译原理-LL1文法详细讲解
我们知道2型文法( CFG ),它的每个产生式类型都是 α→β ,其中 α ∈ VN , β ∈ (VN∪VT)*。
例如, 一个表达式的文法:
最终推导出 id + (id + id) 的句子,那么它的推导过程就会构成一颗树,即 CFG 分析树:
从分析树可以看出,我们从文法开始符号起,不断地利用产生式的右部替换产生式左部的非终结符,最终推导出我们想要的句子。这种方式我们称为自顶向下分析法。
从文法开始符号起,不断用非终结符的候选式(即产生式)替换当前句型中的非终结符,最终得到相应的句子。
在每一步推导过程中,我们需要做两个选择:
因为一个句型中,可能存在多个非终结符,我们就不确定选择那一个非终结符进行替换。
对于这种情况,我们就需要做强制规定,每次都选择句型中第一个非终结符进行替换(或者每次都选择句型中最后一个非终结符进行替换)。
自顶向下的语法分析采用最左推导方式,即总是选择每个句型的最左非终结符进行替换。
最终的结果是要推导出一个特定句子(例如 id + (id + id) )。
我们将特定句子看成一个输入字符串,而每一个非终结符对应一个处理方法,这个处理方法用来匹配输入字符串的部分,算法如下:
方法解析:
这种方式称为递归下降分析( Recursive-Descent Parsing ):
当选择的候选式不正确,就需要回溯( backtracking ),重新选择候选式,进行下一次尝试匹配。因为要不断的回溯,导致分析效率比较低。
这种方式叫做预测分析( Predictive Parsing ):
要实现预测分析,我们必须保证从文法开始符号起,每一个推导过程中,当前句型最左非终结符 A 对于当前输入字符 a ,只能得到唯一的 A 候选式。
根据上面的解决方法,我们首先想到,如果非终结符 A 的候选式只有一个以终结符 a 开头候选式不就行了么。
进而我们可以得出,如果一个非终结符 A ,它的候选式都是以终结符开头,并且这些终结符都各不相同,那么本身就符合预测分析了。
这就是S_文法,满足下面两个条件:
例子:
这就是一个典型的S_文法,它的每一个非终结符遇到任一终结符得到候选式是确定的。如 S -> aA | bAB , 只有遇到终结符 a 和 b 的时候,才能返回 S 的候选式,遇到其他终结符时,直接报错,匹配不成功。
虽然S_文法可以实现预测分析,但是从它的定义上看,S_文法不支持空产生式(ε产生式),极大地限制了它的应用。
什么是空产生式(ε产生式)?
例子
这里 A 有了空产生式,那么 S 的产生式组 S -> aA | bAB ,就可以是 a | bB ,这样 a , bb , bc 就变成这个文法 G 的新句子了。
根据预测分析的定义,非终结符对于任一终结符得到的产生式是确定的,要么能获取唯一的产生式,要么不匹配直接报错。
那么空产生式何时被选择呢?
由此可以引入非终结符 A 的后继符号集的概念:
定义: 由文法 G 推导出来的所有句型,可以出现在非终结符 A 后边的终结符 a 的集合,就是这个非终结符 A 的后继符号集,记为 FOLLOW(A) 。
因此对于 A -> ε 空产生式,只要遇到非终结符 A 的后继符号集中的字符,可以选择这个空产生式。
那么对于 A -> a 这样的产生式,只要遇到终结符 a 就可以选择了。
由此我们引入的产生式可选集概念:
定义: 在进行推导时,选用非终结符 A 一个产生式 A→β 对应的输入符号的集合,记为 SELECT(A→β)
因为预测分析要求非终结符 A 对于输入字符 a ,只能得到唯一的 A 候选式。
那么对于一个文法 G 的所有产生式组,要求有相同左部的产生式,它们的可选集不相交。
在 S_文法基础上,我们允许有空产生式,但是要做限制:
将上面例子中的文法改造:
但是q_文法的产生式不能是非终结符打头,这就限制了其应用,因此引入LL(1)文法。
LL(1)文法允许产生式的右部首字符是非终结符,那么怎么得到这个产生式可选集。
我们知道对于产生式:
定义: 给定一个文法符号串 α , α 的 串首终结符集 FIRST(α) 被定义为可以从 α 推导出的所有串首终结符构成的集合。
定义已经了解清楚了,那么该如何求呢?
例如一个文法符号串 BCDe , 其中 B C D 都是非终结符, e 是终结符。
因此对于一个文法符号串 X1X2 … Xn ,求解 串首终结符集 FIRST(X1X2 … Xn) 算法:
但是这里有一个关键点,如何求非终结符的串首终结符集?
因此对于一个非终结符 A , 求解 串首终结符集 FIRST(A) 算法:
这里大家可能有个疑惑,怎么能将 FIRST(Bβ) 添加到 FIRST(A) 中,如果问文法符号串 Bβ 中包含非终结符 A ,就产生了循环调用的情况,该怎么办?
对于 串首终结符集 ,我想大家疑惑的点就是,串首终结符集到底是针对 文法符号串 的,还是针对 非终结符 的,这个容易弄混。
其实我们应该知道, 非终结符 本身就属于一个特殊的 文法符号串 。
而求解 文法符号串 的串首终结符集,其实就是要知道文法符号串中每个字符的串首终结符集:
上面章节我们知道了,对于非终结符 A 的 后继符号集 :
就是由文法 G 推导出来的所有句型,可以出现在非终结符 A 后边的终结符的集合,记为 FOLLOW(A) 。
仔细想一下,什么样的终结符可以出现在非终结符 A 后面,应该是在产生式中就位于 A 后面的终结符。例如 S -> Aa ,那么终结符 a 肯定属于 FOLLOW(A) 。
因此求非终结符 A 的 后继符号集 算法:
如果非终结符 A 是产生式结尾,那么说明这个产生式左部非终结符后面能出现的终结符,也都可以出现在非终结符 A 后面。
我们可以求出 LL(1) 文法中每个产生式可选集:
根据产生式可选集,我们可以构建一个预测分析表,表中的每一行都是一个非终结符,表中的每一列都是一个终结符,包括结束符号 $ ,而表中的值就是产生式。
这样进行语法推导的时候,非终结符遇到当前输入字符,就可以从预测分析表中获取对应的产生式了。
有了预测分析表,我们就可以进行预测分析了,具体流程:
可以这么理解:
我们知道要实现预测分析,要求相同左部的产生式,它们的可选集是不相交。
但是有的文法结构不符合这个要求,要进行改造。
如果相同左部的多个产生式有共同前缀,那么它们的可选集必然相交。
例如:
那么如何进行改造呢?
其实很简单,进行如下转换:
如此文法的相同左部的产生式,它们的可选集是不相交,符合现预测分析。
这种改造方法称为 提取公因子算法 。
当我们自顶向下的语法分析时,就需要采用最左推导方式。
而这个时候,如果产生式左部和产生式右部首字符一样(即A→Aα),那么推导就可能陷入无限循环。
例如:
因此对于:
文法中不能包含这两种形式,不然最左推导就没办法进行。
例如:
它能够推导出如下:
你会惊奇的发现,它能推导出 b 和 (a)* (即由 0 个 a 或者无数个 a 生成的文法符号串)。其实就可以改造成:
因此消除 直接左递归 算法的一般形式:
例如:
消除间接左递归的方法就是直接带入消除,即
消除间接左递归算法:
这个算法看起来描述很多,其实理解起来很简单:
思考 : 我们通过 Ai -> Ajβ 来判断是不是间接左递归,那如果有产生式 Ai -> BAjβ 且 B -> ε ,那么它是不是间接左递归呢?
间接地我们可以推出如果一个产生式 Ai -> αAjβ 且 FIRST(α) 包括空串ε,那么这个产生式是不是间接左递归。
Ⅱ 编译原理
4、文法G为:
A->aABe/Ba
B->dB/ε
构造LL(1)分析表并判断adae是否是该文法的句子。
5、文法G为:
A->i:=E;
E->E+E
E->E*E
E->i
构造SLR分析表,并判断i:=i*i是否是该文法的句子。
6、文法G为:
S->E
E->aB/bB
A->cA/d
B->Cb/d
构造该文法的LR(0)和SLR(1)分析表,并模拟分析句子bcd时分析栈和输入串的变化。
7、文法G为:
E->E+T/T
T->T*F/F
F->P^F/P
P->(E)/i
判断该文法是否为算符优先文法,若是,构造优先表。
Ⅲ 急急急,编译原理
using namespace std;
struct BiNode
{
char data;
BiNode *lchild, *rchild;
};
typedef BiNode *BiTree;
int CreateBiTree(BiTree &T, const char *s1, const char *s2, int len)
{
if (len<=0)
{
T = NULL;
return 1;
}
else
{
T = new BiNode;
T->data = *s1;
int i;
for ( i=0; i<len; i++) if (s2[i]==*s1) break;
CreateBiTree(T->lchild, s1+1, s2, i);
CreateBiTree(T->rchild, s1+i+1, s2+i+1, len-(i+1));
}
return 1;
}
int DestroyBiTree(BiTree &T)
{
if (T==NULL) return 1;
DestroyBiTree(T->lchild);
DestroyBiTree(T->rchild);
delete T;
T = NULL;
return 1;
}
int ATraverse(BiTree &T)
{
if (T==NULL) return 1;
ATraverse(T->lchild);
ATraverse(T->rchild);
cout<<T->data;
return 1;
}
main()
{
char a[2000],b[2000];
while(cin>>a>>b)
{
BiTree T;
int count=0;
int n;
for(n=0;a[n]!='\0';n++);
CreateBiTree(T,a,b,n);
ATraverse(T);
cout<<" ";
cout<<endl;
DestroyBiTree(T);
Ⅳ 编译原理笔记17:自下而上语法分析(4)LR(0)、SLR(1) 分析表的构造
(移进项目就是指圆点右边是终结符的项目,规约项目指的就是圆点在右部最右端的项目)
LR(0) 文法可以直接通过识别活前缀的 DFA 来构造 LR 分析表
假定 C = {I 0 , I 1 , ... , I n } (aka. LR(0) 项目规范族、DFA 状态集)
首先为文法产生式进行编号,拓广文法的产生式要标记为 0(这里就是后面分析表中 rj 的产生式编号 j 的由来)
然后令每个项目集 I k 的下标 k 作为分析器的状态(行首),包含 S' → .S 的集合下标为分析器的初态(也就是 DFA 的初态,一般都是 0 )。
下面用一个例子来说明 ACTION、GOTO 子表的构造:
SLR(1) 为解决冲突提出了一个简单的方法:通过识别活前缀的 DFA 和【简单向前看一个终结符】构造 SLR(1) 分析表。
如果我们的识别活前缀的 DFA 中存在移进-规约冲突、规约-规约冲突,都可以尝试使用这个方法来解决冲突。(这里说【尝试】,当然是因为 SLR 也只能解决一部分问题,并不是万能的灵丹妙药。。)
这里,我们拿前面那个 LR(0) 解决不了的文法来举例
该文法不是 LR(0) 文法,但是是 SLR(1) 文法。
观察上图 DFA 中的状态2,想象当我们的自动机正处于这个状态:次栈顶已经规约为 T 了,栈顶也是当前的状态 2 ,而当前剩余输入为 *。
如果这个自动机不会【往前多看一步】的话,那么对处于这个状态的自动机来说,看起来状态 2 中的移进项目和规约项目都是可选的。这就是移进-规约冲突。
想要解决这个冲突,就轮到【往前多看一步】上场了——把当前剩余输入考虑进来,辅助进行项目的选择:
对其他的冲突也使用同样的方法进行判断。
这种冲突性动作的解决办法叫做 SLR(1) 解决办法
准备工作部分,与 LR(0) 分析表的构造差不多:同样使用每个项目集的状态编号作为分析器的状态编号,也就同样用作行下标;同样使用拓广文法产生式作为 0 号产生式。
填表也和 LR(0) 类似,唯一的不同体现在对规约项的处理方法上:如果当前状态有项目 A → α.aβ 和 A → α. ,而次栈顶此时是 α 且读写头读到的是 a,那么当且仅当 a∈FOLLOW(A) 时,我们才会用 A → α 对 α 进行规约。
如果构造出来的表的每个入口都不含多重定义(也就是如上图中表格那样的,每个格子里面最多只有一个动作),那么该表就是该文法的 SLR(1) 表,这个文法就是 SLR(1) 文法。使用 SLR(1) 表的分析器叫做一个 SLR(1) 分析器。
任意的二义文法都不能构造出 SLR(1) 分析表
例:悬空 else
例:
这里的 L 可以理解为左值,R 可以理解为右值
经过计算可以确定其 DFA 如下图所示。
在 状态4 中,由于 "=" 同时存在于 FOLLOW(L) 与 FOLLOW(R) 中,因此该状态内存在移进-规约冲突,故该文法不是 SLR(1) 文法。
这样的非二义文法可以通过增加向前看终结符的个数来解决冲突(比如LL(2)、LR(2))但这会让问题更加复杂,故一般不采用。而二义文法无论向前看多少个终结符都无法解决二义性。
Ⅳ 编译原理 语法
编译原理不同教材用的表示不太一样,翻译也五花八门,我不是很确定你这个活前缀是什么意思。我按我知道的告诉你,我用“。”表示状态的位置,你的书上可能通常是一个黑点表示,先要建立文法的状态集合:
S1:S->。a
S2:S->a。
S3:S->。^
S4:S->^。
S5:S->。(T)
S6:S->(。T)
S7:S->(T。)
S8:S->(T)。
S9:T->。T,S
S10:T->T。,S
S11:T->T,。S
S12:T->T,S。
S13:T->。S
S14:T->S。
归集状态,和状态转换关系(这应该是要画成图的,这里没法画。。。)用I表示
I0:S1 S3 S5 S9 S13
输入a,I0->I1
I1:S2
输入^,I0->I2
I2:S4
输入(,I0->I3
I3:S6 S9 S13 S1 S3 S5
归约S,I0->I4
I4:S14
归约T,I0->I5
I5:S10
归约T,I3->I6
I6:S7
输入),I6->I7
I7:S8
输入,,I5->I8
I8:S11 S1 S3 S5
归约S,I8->I9
I9:S12
输入a,I8->I1
输入^,I8->I2
输入(,I8->I3
输入a,I3->I1
输入^,I3->I2
输入(,I3->I3
然后你要建立状态转换表,网络不能画表格。。。,你将就看看( $是结束符啊.还有你的文法没有开始,即S')
先把题目中的规则编个号:
(1) S->a
(2) S->^
(3) S->(T)
(4) T->T,S
(5) T->S
action goto
状态集 a ^ ( ) , $ S T
0 s1 s2 s3 14 10
1 acc
2 acc
3 s1
因为没有S'不知道最终怎么规约
Ⅵ 编译原理文法
编译原理文法的概念为:每一种自然语言或者是编程语言都需要文法来描述,文法相当于语言学的语义分析,即分析每一句话所表示的含义,编译器需要利用文法来完成其语法分析和语义分析。
字母表是元素的非空有穷集合,字母表中的元素称之为符号,因此,字母表也称之为符号集。例如C语言中的字母表由字母、数字、关键字等组成。
符号串,就是由符号集中的元素组成的序列。例如,给定符号集a、b、c,那么abc、abb、ac就是由该符号集组成的符号串。一个文法中,含有一个,或多个产生式,产生式,描述了将终结符集合和非终结符集合组合成串的方法。
Ⅶ 编译原理有关语法的题
短语:E+F*(E+i),F*(E+i),(E+i),E+i,i
直接短语:i(能直接推出来的)
句柄:i(最左直接短语)
素短语:i(并且至少含有一个终结符并除自身之外不含任何更小的素短语)
这些你根据语法树看,就比较好找了啊~
语法树如图:
Ⅷ 【编译原理】第二章:语言和文法
上述文法 表示,该文法由终结符集合 ,非终结符集合 ,产生式集合 ,以及开始符号 构成。
而产生式 表示,一个表达式(Expression) ,可以由一个标识符(Identifier) 、或者两个表达式由加号 或乘号 连接、或者另一个表达式用括号包裹( )构成。
约定 :在不引起歧义的情况下,可以只写产生式。如以上文法可以简写为:
产生式
可以简写为:
如上例中,
可以简写为:
给定文法 ,如果有 ,那么可以将符号串 重写 为 ,记作 ,这个过程称为 推导 。
如上例中, 可以推导出 或 或 等等。
如果 ,
可以记作 ,则称为 经过n步推导出 ,记作 。
推导的反过程称为 归约 。
如果 ,则称 是 的一个 句型(sentential form )。
由文法 的开始符号 推导出的所有句子构成的集合称为 文法G生成的语言 ,记作 。
即:
例
文法
表示什么呢?
代表小写字母;
代表数字;
表示若干个字母和数字构成的字符串;
说明 是一个字母、或者是字母开头的字符串。
那么这个文法表示的即是,以字母开头的、非空的字符串,即标识符的构成方式。
并、连接、幂、克林闭包、正闭包。
如上例表示为:
中必须包含一个 非终结符 。
产生式一般形式:
即上式中只有当上下文满足 与 时,才能进行从 到 的推导。
上下文有关文法不包含空产生式( )。
产生式的一般形式:
即产生式左边都是非终结符。
右线性文法 :
左线性文法 :
以上都成为正则文法。
即产生式的右侧只能有一个终结符,且所有终结符只能在同一侧。
例:(右线性文法)
以上文法满足右线性文法。
以上文法生成一个以字母开头的字母数字串(标识符)。
以上文法等价于 上下文无关文法 :
正则文法能描述程序设计语言中的多数单词。
正则文法能描述程序设计语言中的多数单词,但不能表示句子构造,所以用到最多的是CFG。
根节点 表示文法开始符号S;
内部节点 表示对产生式 的应用;该节点的标号是产生式左部,子节点从左到右表示了产生式的右部;
叶节点 (又称边缘)既可以是非终结符也可以是终结符。
给定一个句型,其分析树的每一棵子树的边缘称为该句型的一个 短语 。
如果子树高度为2,那么这棵子树的边缘称为该句型的一个 直接短语 。
直接短语一定是某产生式的右部,但反之不一定。
如果一个文法可以为某个句子生成 多棵分析树 ,则称这个文法是 二义性的 。
二义性原因:多个if只有一个else;
消岐规则:每个else只与最近的if匹配。
Ⅸ 编译原理的语法
使用“三二术”替代“语法树”。
不管是常数、对象变量、函数,还是“()”,表达式可以看成具有输出的中间量
量1 + 量2 - 量3 * 量4 & 量5 > 量6
按照表达式的从前到后的顺序,先取表达式三个量和两个计算符,把其中两量和一符优先计算,结果存放在中间量之中,而后再顺序取表达式一符一量,变成新三量两符,重复两量和一符优先计算,直到剩两量和一符,再得到最后计算结果。
Ⅹ 【编译原理】第四章:语法分析
从分析树的根节点到叶节点方向构造分析树。
即从开始符号S推导出词串w的过程。
例:
总是选择每个句型的 最左非终结符 进行替换。
总是选择每个句型的 最右非终结符 进行替换。
在自底向上的分析中,总是采用 最左规约 的方式,因此把 最左规约 称为 规范规约 ,对应的 最右推导 称为 规范推导 。
最左推导、最右推导具有唯一性。
自顶向下的语法分析采用最左推导方试,总是选择每个句型的 最左非终结符 进行替换。
由一组 过程 组成,每一个过程对应一个 非终结符 。
从文法开始符号S开始,递归调用文法中的其他非终结符,最终扫描整个输入串,完成分析。
如果其间有不唯一的产生式,就可能需要退回上一步重新尝试的情况,称为 回溯 。
预测分析 是 递归下降分析 技术的一个特例,通过输入中向前看固定个数的符号选择正确的产生式。
如果一个文法可以构造出向前看k个符号的预测分析器,称为LL(k)文法 。
预测分析不需要回溯,具有确定性。
含有 形式产生式的文法称为是 直接左递归 的。
如果一个文法中有一个非终结符A使得对某个串存在推导 ,那么这个文法是 左递归 的。其中,经过两步或以上推导产生的左递归,称为 间接左递归 的。
左递归会使递归下降分析器陷入无限循环。
文法
即
该文法是直接左递归的,会陷入无限循环。
将以上文法转换为:
即可消除左递归。事实上,这个过程把左递归转换成了右递归。
消除直接左递归的一般形式
使用代入法。
对于一个文法,通过改写产生式来 推迟决定 ,等获得足够多的输入信息再做正确的决定。
例:文法:
可以改写为:
从文法的开始符号S开始,每一步推导根据当前句型的最左非终结符A和当前输入符号α,选择正确的A-产生式。为保证分析的确定性,选出的候选式必须是唯一的。
S_文法(简单的确定型文法)
可能在某个举行中紧跟在A后面的终结符a的集合,记为 FOLLOW(A) 。
如果A是某个句型的最右符号,则将结束符“ $ ”添加到FOLLOW(A)中。
例:文法:
中,FOLLOW(B) = {a, c}
产生式 的可选集是指可以选用该产生式进行推导时对应的输入符号的集合,记为 SELECT(A->β) 。
例如
SELECT(A -> aβ)={a}
SELECT(A -> aβ | bγ)={a, b}
SELECT(A -> ε)=FOLLOW(A)
q_文法
文法符号串α串首终结符的集合,记作 FIRST(A) 。