【编译原理】递归下降和LL语法

提示

LL(1):

  1. 从左到右的token处理
  2. 最左侧的推导
  3. 自上而下的解析
  4. 一个向前看的token

目标

  1. 了解什么是递归下降解析器
  2. 了解其工作原理和作用
  3. 如何处理非终结符

自上而下的解析

从顶部(根)到底部(叶)构造解析树。
从S开始派生字符串(w)。

在代换过程中,我们必须做出两个选择

  1. 哪个非终结符应该被替换
  2. 非终结符的哪个规则应该被用来替换

最左递归(Left Most Recursion)
从最左非终结符开始递归:
E => E + E 然后 E => E + E
对应于最右递归

最右递归(Right Most Recursion)
E => E + E
对应于最左递归

递归下降解析

我们从编写解析器的自上而下策略开始,称为递归下降 (也称为预测递归下降)。

它对于从语法手工生成编译器尤其有用。

最左推导
根据输入流中的下一个终结符选择最左边的规则。

递归下降解析器

解析器将由一组方法(进程)组成:

  1. 每个语法的非终结符对应一个。
  2. 以非终结符命名。
  3. 其方法体半自动地从该非终结符的语法规则派生。

解析器概述I

当解析器决定要识别输入流中从该点开始的X时,调用特定非终结符X的方法。

该方法将“消耗”组成X的token,并让解析准备好处理输入流中下一个非终结符的第一个终结符(作为副产品,该方法还将构建解析树的适当部分,以及X所需的其他内容)。

回退

A→ X1X2…Xn可能有许多类似的表达式
我们无法确定选择哪一个进行分析
尝试每一个导致失败的产生式
需要回退到另一个产生式
这将导致效率低下

预测性分析

  1. 展望k个符号(通常为1)以选择正确的产量
  2. 递归下降解析的特殊示例
  3. LL(k)
  4. 不需要回退

解析器概述II

解析器必须在每个点都知道它想要识别什么非终结符。特别是,有许多非终结符规则遵循。

它通过允许查看输入流中的下一个token来实现这一点。也就是说,它决定识别的非终结符的第一个令牌。

解析器概述III

解析器需要知道每个非终结符的所有可能的第一终结符或token。

为了实现这一点,我们看到这些终结符集合的重叠方式存在限制。

这些限制是语法LL(1)的要求。

解析器的结构

我们有一套方法来识别语法的各种元素,每个非终结符都有一种方法。

有一个变量nextSymbol,它包含词汇分析短语识别的下一个token。

对应于非终结符X的方法希望在nextSymbol中找到FIRST(X)中列出的token之一。

它希望通过在nextSymbol中留下一个token来完成,其中一个token位于FIRST(Y)中,而某些Y可能会在X之后立即出现。

也就是说,它是集合FOLLOW(X)中的一个token,也称为lookhead或FOLLOW集合。

非终结符

对于每个非终结符,我们有一种方法。

假设我们有一个非终止符X,其语法规则是X -> α….
α可能是什么?换句话说,产生规则的右侧可以是什么?

…然后我们有一个方法X,带有方法体T(α)
我们将展示四种可能性中的每一种,如何导出方法体T(α)

非终结符

在这里插入图片描述

X -> α 第一种可能性

一个终结符
X->t 例如 X -> return
如果α是一个终结符T,则T(α)是

  1. acceptTerminal(t);
  2. 如果α是“return”,T(α)将是“acceptTerminal(returnSymbol)”

X -> α 第二种可能性

一个非终结符
X -> NT
例如 X -> declarationList
如果α是一个非终结符NT,则T(α)是
NT();
即,调用与非终结符Y关联的方法
如果α是“<declarationList>”,则T(α)将是“declarationList();”

X -> α 第三种可能性

一系列终结符和非终结符
X -> a1 a2 …aN
如果α是终结符和非终结符a1 a2…an的序列,则T(α)是序列:
T (a1 ) ;
T (a2 ) ;
…… ;
T (an ) ;

例子
X → if < expression > then < statement >….
例如,如果α是“if<expression>then<statement>…”,T(α)将是
acceptTerminal (ifSymbol ) ;
expression ( ) ;
acceptTerminal (thenSymbol ) ;
Statement( )

X -> α 第四种可能性

一组备选方案
X → a1|a2|…|aN
在这里插入图片描述
X → < ifStatement > |<whileStatement|…
如果α是“< ifStatement >|< whileStatement >|…”,则T(α)是
在这里插入图片描述

主方法

开始一切的主要方法是:
从词法分析器获取第一个token到nextSymbol;
< program >(); (或任何区别符号)
acceptTerminal(eofSymbol);
report success;

我们需要确保在有效的<program>字符串之后没有多余的字符。

让解析器工作

要使解析器工作,它必须始终能够决定下一步要做什么。

因此,如果有一条语法规则的右手边是a1|a2|…an,这将转化为switch语句,其中所有的替代项都必须是不同的。

换句话说,规则中所有替代项a1|a2|…an的FIRST集合必须是不相交的(即没有重叠)。

离散FIRST集合的一个例子

< statement >->if< expression >then< statement >fi|
< variable >->< expression >

在上面的语句中,第一个规则的RHS具有first集合{ifSymbol}和{ident}

这些是不相交的,因此< statement >方法中的开关是确定性的。

另一个例子

< statement > ->if < expression > then < statement > fi | if < expression > then < statement> else < statement> fi | < variable > -> < expression >
假设这是规则
然后,三个备选方案的FIRST集合是{ifSymbol}、{ifSymbol}和{ident}
所以解析器在这种情况下无法做出决定。
解决这一问题的一种可能方法是通过左因子分解重写规则。

左递归

A -> Av|u
使用 A => Av, 推导出 A => AAv, 那么A => AAAv…, A => uvvvvv… A=uv+
FIRST(Av)和FIRST(u)都是{u}。
我们不能在递归得体的解析器中直接处理左递归。

或者我们可以写这个
A -> uB
B -> vB | ε
这两种语法都会生成相同的句子形式:u{v}+,因此是等价的。

空产生式

我们现在对语法有了新的限制,我们可以为其编写递归下降解析器。

在我们有null-productions,如上面的示例,FOLLOW(X)必须与所有FIRST(ai)集合不相交(无重叠)

X -> a1 | a2 | … | an | ε

左递归

消除直接左递归
A -> Aα|β (α≠ε, β doesn’t start with A)
A => Aα, 导出 A => Aαα, 然后 A => A α α α… α, ⇒ β α*

因为A以β开头,所以我们也可以这样写
A -> βA’, 所以A’ -> α A’ |ε

两种语法都会生成相同的句子形式(βα* )

我们将左递归更改为右递归

LL(1)语法的函数

我们提到后退会导致分析器的效率问题。

如果我们能够预测每个步骤中每个产品的正确选项,这将是可以避免的。

预测分析。

两种不同的语法

我们已经知道了S语法

  1. 每个产生式的右侧必须以终结符开始。
  2. 对于相同的非终结符,每个候选产生式的第一个终结符必须不同。

q语法

  1. 产生式的右侧是ε或以终结符开始的。
  2. 如果产生式具有相同的左侧,则没有相同的集合。
  3. q语法不包括:其右侧以非终结符开始的产生式。

引入LL(1)语法。

LL(1)语法

如果编程语言的语法为LL(1),我们可以为其编写递归下降解析器。

对于LL(1)语法,它必须遵守以下规则:

  1. 我们假设有一个没有扩展的简单BNF,但是可以重新制定规则来处理扩展的BNF(这里我们忽略了扩展情况)。

LL(1)语法:条件

如果非终结符具有如下语法规则:
X -> a1|a2|…|an|
那么我们必须会有
FIRST(ai)∩FIRST(aj)=Ø 对于所有的 i≠j

如果任何非终结符X可以生成空字符串(ε)那么我们必须会有:
FIRST(X)∩FOLLOW(X)=Ø

“悬挂的else”问题

语法并不明确,下面这个语句有两个解析
if E1 then if E2 then S1 else S2
可以指的是
if E1 then {if E2 then S1 else S2}
或者
if E1 then {if E2 then S1} else S2

解析v识别

还要注意,这个解析器实际上只是一个识别器。

它只报告解析输入字符串的成功或失败。

当它已经将某个特定的非终结符识别为一系列终结符和其他非终结符时,需要对其进行扩展以做一些有用的事情(例如,构建一点解析树)。

实现这一点的一种方法是让每个方法传回它生成的解析树片段。

例如,我们可以重写<variable>的方法,以返回指向Node对象结构的指针,该结构包含:

  1. 用于生成此节点的规则的指示。
  2. 指向解析树上相关从属节点的指针。

Thanks to Dr. John: Some contents are from their slides.

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
递归下降分析法 一、实验目的: 根据某一文法编制调试递归下降分析程序,以便对任意输入的符号串进行分析。本次实验的目的主要是加深对递归下降分析法的理解。 二、实验说明 1、递归下降分析法的功能 词法分析器的功能是利用函数之间的递归调用模拟语法树自上而下的构造过程。 2、递归下降分析法的前提 改造文法:消除二义性、消除左递归、提取左因子,判断是否为LL(1)文法, 3、递归下降分析法实验设计思想及算法 为G的每个非终结符号U构造一个递归过程,不妨命名为U。 U的产生式的右边指出这个过程的代码结构: (1)若是终结符号,则和向前看符号对照, 若匹配则向前进一个符号;否则出错。 (2)若是非终结符号,则调用与此非终结符对应的过程。当A的右部有多个产生式时,可用选择结构实现。 三、实验要求 (一)准备: 1.阅读课本有关章节, 2.考虑好设计方案; 3.设计出模块结构、测试数据,初步编制好程序。 (二)上课上机: 将源代码拷贝到机上调试,发现错误,再修改完善。第二次上机调试通过。 (三)程序要求: 程序输入/输出示例: 对下列文法,用递归下降分析法对任意输入的符号串进行分析: (1)E->eBaA (2)A->a|bAcB (3)B->dEd|aC (4)C->e|dc 输出的格式如下: (1)递归下降分析程序,编制人:姓名,学号,班级 (2)输入一以#结束的符号串:在此位置输入符号串例如:eadeaa# (3)输出结果:eadeaa#为合法符号串 注意: 1.如果遇到错误的表达式,应输出错误提示信息(该信息越详细越好); 2.对学有余力的同学,可以详细的输出推导的过程,即详细列出每一步使用的产生式。 (四)程序思路 0.定义部分:定义常量、变量、数据结构。 1.初始化:从文件将输入符号串输入到字符缓冲区中。 2.利用递归下降分析法分析,对每个非终结符编写函数,在主函数中调用文法开始符号的函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值