【编译原理系列】自上而下分析法与 LL(1) 文法

自上而下分析

自上而下分析是一种试探的过程,是反复使用不同产生式谋求与输入序列匹配的过程

当既有左递归又有左因子的时候,先消除左递归

消除左递归

避免陷入死循环

消除直接左递归

若文法G中的非终结符A,对某个文法符号序列α存在推导 A = + > A α A=^+>Aα A=+>Aα,则称G是左递归的。若G中有形如A→Aα的产生式,则称该产生式对A直接左递归

  • 首先,整理A产生式为如下形式:
    A→ Aα1|Aα2|...|Aαm|β1|β2|...|βn
  • 其中αi非空[若αi为空,则形成一个有环的A产生式],βj均不以A开始。然后用下述产生式代替A产生式:
    A → β1 A' |β2 A' | ...|βn A'
    A'→ α1 A' | α2 A' | ... | αm A' |ε

消除文法左递归

核心思想:

  • 将不是直接左递归的非终结符右部展开到其他产生式中

  • 但是若G产生句子的过程中出现 A = + > A A=^+>A A=+>A的推导,则无法消除左递归

步骤

  • 合理排序非终结符:A1,A2,…,An;【通过两层for循环检验】
  • 然后用用 A j → δ 1 ∣ δ 2 ∣ … ∣ δ k Aj→δ1|δ2|…|δk Ajδ1δ2δk右部替换 A i → A j γ Ai→Ajγ AiAjγ中的Aj得到 A i → δ 1 γ ∣ δ 2 γ ∣ … ∣ δ k γ Ai→δ1γ|δ2γ|…|δkγ Aiδ1γδ2γδkγ;再消除Ai产生式中的直接左递归;

S→Aa|b A→Ac|Sd|ε

  • 将S的右部展开在A中,得到:A→Ac|Aad|bd|ε

  • 消除新产生式中的直接左递归,得到:S→ Aa | b A→ bdA' | A' A'→ cA' | adA' | ε

提取左因子

避免回溯

将:A → αβ1|αβ2,替换为:A →αA' A'→β1|β2

递归下降分析

  1. 直接以程序的方式模拟产生式产生语言的过程
  2. 每个产生式对应一个子程序,产生式右边的非终结符对应子程序调用终结符与输入序列匹配
  3. 它对文法的限制不能有公共左因子和左递归
  4. 它是一种**非形式化的方法,**只要能写出子程序,用什么样的方法和步骤均可

对比

  • 优点:简单灵活、容易构造
  • 缺点:程序与文法直接相关,对文法的任何改变均需对程序进行相应的修改
  • 适合规模比较小的语言

稳妥的笨方法:

  1. 构造文法的状态转换图并且化简

    • 标记为A的边可等价为标记ε的边转向A转换图的初态
    • ε ε ε边连接的两个状态可以合并
    • 标记相同的路径可以合并
    • 不可区分的状态可以合并
  2. 将转换图转化为EBNF表示

    EBNF:扩展BNF(和正规式一样,为了表示方便加入的+、?、[]等等)

    { } 重复0或若干次(while)
    ② [ ]:可选择(if或while)
    ③ |:括弧( )之内的或关系(case)
    ④ ( ):改变运算的优先级和结合性

  3. 从EBNF构造子程序

构造递归下降字程序:

  • 首先设计两个变量lookahead(当前的下一输入的终结符)和eof(输入结束标志)

  • 另外设计一个函数match(t),进行终结符匹配

预测分析器

预测分析器是下推自动机的一个具体实现

栈中的内容是符号;而在移进-归约分析器的栈中内容是状态

预测分析表

M[A, a]的内容:

  • 若当前栈顶是非终结符A,下一输入终结符是a,则M[A, a]指示下一步动作;其中A为行下标,a为列下标

格局

  • 格局是一个三元组(栈内容,当前剩余输入,改变格局的动作)

改变格局的动作:

  • 匹配终结符:若^top=^ip(但≠#),则pop且next(ip)
  • 展开非终结符:若^top= X且 M[X,^ip]=α(X→α),则pop且push(α)
  • 报告分析成功:若^top=^ip=#,则分析成功并结束
  • 报告出错:其它情况,调用错误恢复例程

构造预测分析表

  1. 首先构造FIRST集合与FOLLOW集合
  2. 然后根据两个集合构造预测分析表

文法符号序列α的FIRST集合为:

  • F I R S T ( α ) = { a ∣ α = ∗ > a … , a ∈ T } FIRST(α)=\{a|α=^*>a…,a∈T\} FIRST(α)={aα=>aaT}
    α = ∗ > ε α=^*>ε α=>ε,则 ε ∈ F I R S T ( α ) ε∈FIRST(α) εFIRST(α)

非终结符A的FOLLOW集合如下:

  • F O L L O W ( A ) = { a ∣ S = ∗ > … A a … , a ∈ T } FOLLOW(A) = \{ a |S=^*>…Aa…,a∈T\} FOLLOWA={aS=>AaaT}
    若A是某句型的最右符号,则 # ∈ F O L L O W ( A ) \#∈FOLLOW(A) #FOLLOWA

通俗地讲

  • α的FIRST集合就是从α开始可以导出的所有以终结符开头的序列中的开头终结符
  • 而A的FOLLOW集合,就是从开始符号可以导出的所有含A的文法符号序列中紧跟A之后的终结符
FIRST集合计算

自下而上计算FIRST

  1. 若X∈T,则 F I R S T ( X ) = X FIRST(X)={X} FIRST(X)=X
  2. 若X是非终结符且有X→ε,则加入ε到FIRST(X);
  3. 若X是非终结符且有X→Y1Y2…Yk,并设Y0=ε,Yk+1=ε。那么对所有从0开始的j(0≤j≤k),若a∈FIRST(Yj+1)且ε∈FIRST(Y1~Yj)【这里表示1到j都有ε】,则加入a到FIRST(X)。

说明

  • FIRST(X1X2…Xn)是所有FIRST(Xi)(i=1,2,…,k)的并集,其中k为第一个具有性质ε不属于FIRST(Xk)k>n的文法符号
  • First集合里的符号一定是终结符,ε不是终结符,也不是非终结符,只是一个表示空的标志而已
  • T->array[num] of intarray, [, num, ], of, int都是终结符
FOLLOW集合计算

自上而下计算FOLLOW

  1. 加入#到FOLLOW(S),其中S是开始符号,#是输入结束标记

  2. 若有产生式A→αBβ,则除ε外,FIRST(β)的全体加入到FOLLOW(B)

  3. 若有产生式A→αB或A→αBβ且ε∈FIRST(β),则FOLLOW(A)的全体加入到FOLLOW(B)

    S = ∗ > δ A a S =^*>δAa S=>δAa (a紧跟A之后),则 = > δ α B a =>δαBa =>δαBa a也紧跟B之后(A→αB)

    或者 = > δ α B β a = ∗ > δ α B a =>δαBβa =^*>δαBa =>δαBβa=>δαBa (A→αBβ)
    因为 ε∈FIRST(β) 使得B成为A产生式右部最右的文法符号,即 对任何a∈FOLLOW(A),均有a∈FOLLOW(B)

构造预测分析表:

预测分析表的列都是终结符

  1. 对文法的每个产生式A→α,执行2和3

  2. 对FIRST(α)的每个终结符a,加入α到M[A,a]

    若当前栈顶为A,当前输入为a,则规则2表示下一步动作是展开A→α,因为a∈FIRST(α),所以展开后下一次正好匹配a【这里α是aB…这样的表示,因为FIRST(α)里有a】

  3. ε ∈ F I R S T ( α ) ε∈FIRST(α) εFIRST(α),则对FOLLOW(A)的每个终结符b(包括#)加入α到M[A,b]

    若当前栈顶为A,当前输入为b且b∈FOLLOW(A),则规则3表示下一步动作是展开A→ε,即栈顶弹出A,继续分析A之后的部分,因为b∈FOLLOW(A),所以弹出A后下一次正好匹配A的后继b

  4. M中其它没有定义的条目均是error

驱动器

初始格局为:#S,ω#,分析器的第一个动作)[其中ω是输入序列]
令ip指向ω#中的第一个终结符,top指向S;
  loop x:=top^; a:=ip^;
	if x ∈ T
	then  	if x=a 
			then pop(x); next(ip); -- 匹配终结符
      		else error(1);     -- 出错:栈顶终结符不是a
      		end if;
	else  	if   M[x, a] = X→Y1Y2...Yk
			then pop(X); push(YkYk-1...Y2Y1);--展开产生式
      		else error(2);     -- 出错:产生式不匹配
      		end if;
	end if;
	exit when x=# and a=#;           -- 分析成功
  end loop;

LL(1)文法

文法G被称为是LL(1)文法,当且仅当为它构造的预测分析表中不含多重定义的条目

  • 由此分析表所组成的分析器被称为LL(1)分析器,它所分析的语言被称为LL(1)语言;
  • 第一个L代表从左到右扫描输入序列,第二个L表示产生最左推导,1表示在确定分析器的每一步动作时向前看一个终结符

任何二义文法都不是LL(1)文法

证明是LL(1)文法

G是LL(1)的,当且仅当G的任何两个产生式A→α|β满足:

  1. 对任何终结符a,α和β不能同时推导出以a开始的串
    向前看一个就不够了,M[A,a]中有多重定义A→α和A→β
  2. α和β多有一个可以推导出ε
    向前看一个就不够了,任何属于FOLLOW(A)的终结符b(包括#),M[A,b]中有多重定义A→α和A→β
  3. β=*>ε,则α不能导出以FOLLOW(A)中终结符开始的任何串

若条件3不满足,即存在终结符b,它既在FOLLOW(A)中,又在FIRST(α)中,
则步骤2把条目A→α加入到M[A,b]中,而步骤3又把条目A→β加入到M[A,b]中,即M[A,b] 中有多重定义A→α和A→β

所以LL(1)文法既无左递归也无左因子

缺点:

  1. 文法难写,难懂
  2. 适应范围有限,往往写不出有些语言的LL(1)文法

实际编译器中使用更多的是一类LL(1)文法的真超集——LR(1)文法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值