接下来我们讨论自顶向下文法分析方法中的非递归的预测分析。

自顶向下文法分析可以看做是为1. 输入串构造语法分析树的问题,它从语法分析树的根开始,深度优先地按照先根顺序创建语法分析树的各个节点。2.也可以看做是寻找输入串的最左推导的过程。

更通俗地讲,自顶向下分析就是从起始符号开始,不断的挑选出合适的产生式,将中间句子中的非终结符的展开,最终展开到给定的句型。因此,该方法的关键字在于如何挑选出合适的产生式。

我们来看两个例子。

对于下面的文法

S -> aS | bS | c

如何生成句型abac?

可以写出来如下的推导:

Working-string      Production
S                   S -> aS
aS                  S -> bS
abS                 S -> aS
abaS                S -> c
abac                ACCEPT

简单分析一下,这个推导式如何产生的;首先,假设有一个 strcmp 函数来比较符号串 “S” 和 “abac” ,找到第一个不匹配的符号,也就是 “S” 和 “a” ,因此,此时必须将中间句子中的 “S” 展开,才能得到最终句子。将最终句子中不匹配的这个 “a” ,和 S 的三个产生式的右边 aS 、 bS 和 c 相比,可以看出,只能选择 S -> aS 展开,才可以和 “a” 匹配。剩下的部分类似分析。

分析中需要回溯的情况。

看一个例子:

S → rXd | rZd
X → oa | ea
Z → ai

在接收到的输入串是read的时候:

上述产生回溯的原因主要是?

S → rS'
S'-> Xd | Zd
X → oa | ea
Z → ai

再考虑文法

S–> AB
A –> aA | ε
B –> b | bB

假设要分析的句子为: aaab ,首先从起始符号 S 开始。

第 1 步,起始符号只有一个产生式: S -> AB ,所以只能选择这个产生式,用这个产生式的右边代替 S ,于是得到一个中间句子 AB ,将选择的产生式和得到中间句子(working-string)写成列表的形式,如下:

Working-string    Production
S                 S –> AB
AB

第 2 步,从 AB 开始,首先展开 A , A 有两个产生式: A -> aA, A -> ε ,我们对比一下最终句子 aaab 和 目前得到的中间句子 AB ,发现只能选择 A -> aA ,否则将无法推导出 aaab 。因此选择这个产生式,将其右边替换掉中间句子 AB 中的 A ,于是得到中间句子 aAB :

Working-string      Production
S                   S –> AB
AB                  A –> aA
aAB

继续尝试展开 aAB 中的 A ,再次对比发现,还是只能选择产生式 A -> aA ,得到:

Working-string    Production
S                 S –> AB
AB                A –> aA
aAB               A –> aA
aaAB

再次应用产生式 A -> aA ,得到:

Working-string   Production
S                S –> AB
AB               A –> aA
aAB              A –> aA
aaAB             A –> aA
aaaAB

到了这里,可以发现只能使用 A -> ε (否则无法得到 aaab ),应用此产生式后得到:

Working-string    Production
S                 S –> AB
AB                A –> aA
aAB               A –> aA
aaAB              A –> aA
aaaAB             A -> ε
aaaB

第 3 步,从 aaaB 开始,按上面同样的原则尝试展开 B ,最终得到:

Working-string   Production
S                S –> AB
AB               A –> aA
aAB              A –> aA
aaAB             A –> aA
aaaAB            A -> ε
aaaB             B -> b
aaab             ACCEPT

这个例子当中我们希望大家注意什么问题呢?就是空产生式。什么时候可以使用这个空产生式呢?


从上面的讨论中,我们可以知道,在自顶向下的分析过程中,在产生式选择时,

  1. 对于非空产生式而言,知道这个产生式能产生的字符串的首字符是非常重要的;
  2. 对于空产生式而言,知道这个空产生式的左部非终结符后面跟着的字符是非常重要的;

同时,我们也可以很容易想到,如果一个非终结符的两个或多个产生式有相同的首字符,那么朝前看一个字符不能解决问题;如果我们希望只通过朝前看一个字符就能确定选择哪个产生式,必须要求同一个非终结符的两个或多个产生式不能有相同的首字符。【还有其他条件】

自顶向下的分析器的构造可以使用和文法相关的两个函数FIRST和FOLLOW来实现。在分析过程中,FIRST和FOLLOW可以使得我们根据下一个输入符号来选择使用哪个产生式。

我们结合FIRST和FOLLOW讨论一下。考虑两个产生式

只要FIRST(

)和FIRST(
)是不相交的集合,那么只需要查看下一个输入符号就可以在这两个产生式中进行选择。

FIRST(

)被定义为可以从
推导得到的串的首符号的集合。其中
是任意的文法符号串。如果
推导出
,那么
也在FIRST集合中。

如何在预测分析中求得FIRST集合呢?考虑产生式

A -> a

那就很好理解,A的FIRST集就是{a}。

如果产生式的右部是非终结符呢?

S -> AB
A -> a

那么,

FIRST(A) = {a}
FIRST(S) = {a}

如果产生式的右部是非终结符,并且右部的第一个非终结符还有空产生式呢?

S -> AB
A -> a | ε
B -> b

那么,

FIRST(A) = {a,ε}
FIRST(S) = ?

如果产生式的右部是非终结符,并且右部的所有非终结符都有空产生式呢?

S -> AB
A -> a | ε
B -> b | ε

那么,

FIRST(A) = {a,ε}
FIRST(S) = ?

以上是直观的解释,可能比较好懂;如果把FIRST集的规则写出来,可能有点难懂:

对于非终结符

,FOLLOW(A)被定义为可能在某些句型中紧跟在A右边的终结符号的集合。譬如,下面的文法产生式

那么,终结符

就在A的FOLLOW集中。

我们首先看一下,FOLLOW集如何产生。

S -> AB
A -> a | ε
B -> b

来看一下A的FOLLOW集。A后面能跟着什么呢?

这里我们要明确一点,在LL(1)分析方法中,FOLLOW集的作用就是帮忙确认啥时候该用空产生式。

譬如对上面的文法的一个句子 b。应该怎么展开呢?

看到b的时候,知道使用

S->AB

接着要展开A,A看到的依然是b,这是就该使用

A->ε

接下来看到

再看一个例子:

S -> AB
A -> C|a
B -> b
C -> c | ε

这里,对于句子 b,应该如何分析呢?所以,从这里我们能得出什么?

有一个产生式

A -> C

那么,A的FOLLOW集也应该是C的FOLLOW集的子集。

另外,如果A是某些句型的最右符号,那么结束符号$也在A的FOLLOW集中。

对于FOLLOW集而言,

虽然看起来不怎么直观,但是描述的相当精确,我们只需要严格按照这个规则,就可以生成FIRST集和FOLLOW集。

练习:

S -> aS | bS | c

再一个练习:

S–> AB
A –> aA | ε
B –> b | bB

再次使用下面的文法做例子

E->TE'
E'->+TE'|Ɛ
T->FT'
T'->*FT'|Ɛ
F->(E)|id

计算出FIRST集和FOLLOW集。

FIRST(E) = FIRST(T) = FIRST(F) = { ( , id }
FIRST(E') = {+, Ɛ}
FRIST(T') = {*, Ɛ}
FOLLOW(E) = FOLLOW(E') = { ), $}
FOLLOW(T) = FOLLOW (T') = {+, ), $}
FOLLOW(F) = {+, *, ), $} 

继续做练习:

S->SS+|SS*|a

这个文法,FIRST集,FOLLOW集是什么?

请对它进行提左公因子、消除左递归。

下面的文法,

S->aS’
S’->aS’AS’|Ɛ
A->+|*

FIRST集,FOLLOW集是什么?

答案:

第一个文法:

FIRST(S)={a}
FOLLOW(S)={a,+,*,$}

第二个文法:

First(S) = First(aS’)={a}
First(S’)= First(aS’AS’) ∪ First(Ɛ)= {a} ∪{Ɛ}= {a, Ɛ}
First(A) = { +,*}
Follow(S) ={$}
Follow(S’)= {$, +,*}
Follow(A)= {a, +,*,$}


计算FIRST和FOLLOW有什么用呢?首先,我们可以用来判断LL(1)文法。其次,可以用来构造自动进行LL文法分析的预测分析表。

如果文法中的任何两个产生式

都满足下列条件:

FIRST(

)

FIRST(

) =

* Ɛ ,那么FIRST(

)

FOLLOW(A) =

类似】

那么文法满足LL(1)文法。

【LL(1)文法由一些明显的特征,譬如,无二义性、无左递归以及没有公共左因子。因此,明显,有以上三个特征的文法,不是LL(1)文法。】

第一个条件很好理解;

第二个条件什么意思呢?

考虑下面的文法:

S->Aa
A->B|C
C->Ɛ
B->a

这个文法非常简单,只能生成串a或者aa。这是两个不同的句型,但这里的问题是,如果只朝前看一个字符a,那么在对A进行展开时,无法判断应该选择哪个产生式,因为如果选择B,B可以产生a,而如果选择C,A自己的后继本身也是a,所以对于输入a,有两种推导方式。

以上对aa以及a的推导对于人而言,可能觉得非常简单,甚至觉得不会出错,但是对于机器而言,要进行判断就不那么容易了。

练习:

下面的文法是否是LL(1)文法,为什么?

S ->AB|PQx
A -> xy
B -> bc
P -> dP| ε
Q -> aQ| ε

接下来,我们来看一下,构造出FIRST和FOLLOW之后,怎么样进行自动推导。


预测分析表

预测分析表M[A,a],是一个二维数组,其中,A是一个非终结符号,a是一个终结符号, 或者特殊符号$,也即输入的结束符号。

在构造了预测分析表之后,只有当下一个输入符号a在FIRST(

)中时才选择产生式
。在
可以推导出空的情况下,如果当前输入符号在FOLLOW(A)中,或者已经达到输入中的$符且$在FOLLOW(A)中,那么仍应该选择A->

构造预测分析表的算法如下:

对于文法:

E->TE'
E'->+TE'|ε
T->FT'
T'->*FT'|ε
F->(E)|id

结合上面计算得出的FIRST集和FOLLOW集

FIRST(E) = FIRST(T) = FIRST(F) = { ( , id }
FIRST(E') = {+, Ɛ}
FRIST(T') = {*, Ɛ}
FOLLOW(E) = FOLLOW(E') = { ), $}
FOLLOW(T) = FOLLOW (T') = {+, ), $}
FOLLOW(F) = {+, *, ), $} 

对应的预测分析表如下:

其中,

的产生式右部的FIRST集中有Ɛ,所以它们的FOLLOW集中的符号的位置也有产生式。

至于为何有这样的规则,我们可以考虑一下,下面的文法:

S->Ɛ

对于这个文法,FIRST集为{Ɛ},FOLLOW集为{$}。那么预测分析表应该是什么呢?

另外可以再看前面的例子:

S –> AB
A –> aA | ε
B –> b | bB

因为A的产生式中有ε,所以A的FOLLOW集,也即{b}也会加入到预测分析表中。

Working-string   Production
S                S –> AB
AB               A –> aA
aAB              A –> aA
aaAB             A –> aA
aaaAB            A -> εaaaB
B -> b           aaab
ACCEPT

这里可以看到,当A遇上b的时候,就可以使用A->ε产生式。

表中的空白指示错误。

对于每个LL(1)文法,分析表中的每个条目都唯一地指定了一个产生式或者标明一个语法错误。当然,对于左递归或者二义性文法,M至少会包含一个多重定义的条目。

基于以上内容对文法

S->Aa
A->B|C
C->Ɛ
B->a

构造一下预测分析表。

可以发现,当输入是a时,A->B 以及A->C 都可以被使用。


在构造出预测分析表之后,如何进行文法推导呢?

练习:

构造下面文法的LL(1)分析表。

D -> TL
T -> int | real
L -> id R
R -> , idR | ε


关于LL,我们最后看一段代码,体会一下LL的工作过程。

#include <iostream>
#include <map>
#include <stack>/*grammar:
S->F;
S->(S+F);
F->a;
sentence:a,(a+a),((a+a)+a) */enum Symbols {// the symbols:// Terminal symbols:TS_L_PARENS,    // (TS_R_PARENS,    // )TS_A,           // aTS_PLUS,        // +TS_EOS,         // $, in this case corresponds to '0'TS_INVALID,     // invalid token// Non-terminal symbols:NTS_S,          // SNTS_F           // F
};/*
Converts a valid token to the corresponding terminal symbol
*/
Symbols lexer(char c)
{switch(c){case '(':  return TS_L_PARENS;case ')':  return TS_R_PARENS;case 'a':  return TS_A;case '+':  return TS_PLUS;case '0': return TS_EOS; // end of stack: the $ terminal symboldefault:   return TS_INVALID;}
}int main(int argc, char **argv)
{using namespace std;if (argc < 2){cout << "usage:ntll '(a+a)'" << endl;return 0;}// LL parser table, maps < non-terminal, terminal> pair to action       map< Symbols, map<Symbols, int> > table;stack<Symbols>  ss;     // symbol stackchar *p;        // input buffer// initialize the symbols stackss.push(TS_EOS);        // terminal, $ss.push(NTS_S);         // non-terminal, S// initialize the symbol stream cursorp = &argv[1][0];// set up the parsing table 预测分析表;table[NTS_S][TS_L_PARENS] = 2;table[NTS_S][TS_A] = 1;table[NTS_F][TS_A] = 3;while(ss.size() > 0){if(lexer(*p) == ss.top()){cout << "Matched symbols: " << lexer(*p) << endl;p++;ss.pop();}else{cout << "Rule " << table[ss.top()][lexer(*p)] << endl;switch(table[ss.top()][lexer(*p)]){case 1: // 1. S → Fss.pop();ss.push(NTS_F); // Fbreak;case 2: // 2. S → ( S + F ) //使用产生式的右部替换左部ss.pop();ss.push(TS_R_PARENS);   // )ss.push(NTS_F);         // Fss.push(TS_PLUS);       // +ss.push(NTS_S);         // Sss.push(TS_L_PARENS);   // (break;case 3: // 3. F → ass.pop();ss.push(TS_A);  // abreak;default:cout << "parsing table defaulted" << endl;return 0;break;}}}cout << "finished parsing" << endl;return 0;
}

以上的代码体现了之前介绍的LL分析的思想。首先,构造预测分析表,描述了当非终结符遇到特定输入时应该使用哪个产生式;其次,在使用产生式进行处理的,将产生式的右部替换左部即可。

参考:

  1. https://pandolia.net/tinyc/ch10_top_down_parse.html
  2. http://blog.reverberate.org/2013/07/ll-and-lr-parsing-demystified.html
  3. https://www.tutorialspoint.com/compiler_design/compiler_design_top_down_parser.htm

根据文法画出语法树_编译工程5:语法分析(3)相关推荐

  1. 根据文法画出语法树_更多确定子句语法

    本章有两个主要目标: 1.研究DCG表示法提供的两个重要功能:额外的参数和额外的 目标. 2.讨论DCGs的现状和局限性. 1  额外参数 在上一章中,我们介绍了基本的DCG表示法.但是DCG所提供的 ...

  2. 根据文法画出语法树_几种常用的英语教学法误导了语法教学

    多年来,我国的英语教学一直采用语法翻译法,教法比较单一.上世纪八十年代初,随着国际交流的日益加强和教学改革的进一步深入,各种英语教学法,尤其是从国外推介的教学法都相继进入英语教学领域.尽管有一些教学法 ...

  3. 根据文法画出语法树_输入语法推断的强化学习

    引用:Wu Z, Johnson E, Yang W, et al. REINAM: reinforcement learning for input-grammar inference[C]. fo ...

  4. 编译原理学习笔记(二十九)~习题:分析句子 id--id*id的 最右推导过程,画出分析树,找出和分析过程中每一步的对应关系。

    题目 分析句子 id–id*id的 最右推导过程,画出分析树,找出和分析过程中每一步的对应关系. 语法如下: E → E - T (1) E → T (2) T → T * F (3) T → F(4 ...

  5. 【LaTex树状图】LaTex画树状图_编译原理语法树_直角分叉_Forking links

    语法树 过程 使用Overleaf在线编辑LaTex,由于使用汉字ctex包,需在overleaf中将编译器换成XeLaTex(界面左上角Menu→Compiler→XeLaTex). 详细语法参考V ...

  6. 【编译原理】-- 第二章(二)(短语、简单短语、句柄、文法二义性、语法树、例题)

    目录 一.句型的分析 1.规范推导和规范归约 2.短语.简单短语和句柄 3.语法树 4.通过树来寻找短语.简单短语.句柄 二.文法的二义性 1.文法二义性的定义 2.文法二义性的消除 (1)定义规定或 ...

  7. 编译原理抽象语法树_平衡抽象原理

    编译原理抽象语法树 使代码复杂易读和理解的一件事是,方法内部的指令处于不同的抽象级别. 假设我们的应用程序仅允许登录用户查看其朋友的旅行. 如果用户不是朋友,则不会显示任何行程. 一个例子: publ ...

  8. python根据频率画出词云_利用pandas+python制作100G亚马逊用户评论数据词云

    原标题:利用pandas+python制作100G亚马逊用户评论数据词云 数据挖掘入门与实战 公众号: datadw 我们手里面有一个差不多100G的亚马逊用户在购买商品后留下的评论数据(数据格式为j ...

  9. java抽象语法树_抽象语法树AST的全面解析(一)

    Javac编译概述 将.java源文件编译成.class文件,这一步大致可以分为3个过程: 1.把所有的源文件解析成语法树,输入到编译器的符号表: 2.注解处理器的注解处理过程: 3.分析语法树并生成 ...

最新文章

  1. [Python陷阱]os.system调用shell脚本获取返回值
  2. QT的QDateTimeAxis类的使用
  3. stream 过滤俩个字段_Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合...
  4. 在Sql Server 2005使用公用表表达式CTE简化复杂的查询语句
  5. 计算机网络学习笔记-1.2.3OSI参考模型(2)
  6. ireport参数传递json_Json传递数据两种方式(json大全)
  7. Java设计模式05:常用设计模式之原型模式(创建型模式)
  8. 云版 Android 系统来了?
  9. FA 工业自动化设备设计基础
  10. np学习——OSPF的典型配置案例
  11. 浏览器禁用第三方Cookie
  12. oracle视图、函数、循环、case when
  13. 访问不了共享文件夹提示“网络错误“的解决方法
  14. mkdir 创建目录命令
  15. 暗影精灵5风扇怎么调_惠普HP暗影精灵5 Super游戏主机改装猫头鹰风扇攻略
  16. Java架构师-容器化(一):服务容器化技术-Docker、Cloud Foundry
  17. 快速将非Word中的不可编辑的公式转换为Word文档中公式编辑器里可编辑的公式的办法
  18. C++ MFC学习笔记(第三课)绘制统计直方图
  19. 行存储和列存储小介绍
  20. 第三章:晶体三极管及应用电路

热门文章

  1. android点击按钮底部暗影,android – 圆形按钮,像5.0 FAB一样的阴影
  2. native封装卡片 react_自己动手封装一个React Native多级联动
  3. jQuery 的常用选择器,筛选器
  4. Idea debug时报错:Command line is too long
  5. react日期格式化实例
  6. Architecture:话说科学家/工程师/设计师/商人
  7. java获取服务器状态_获取远程服务器上 Java 进程的运行状态
  8. 智能市场变革,独辟蹊径的机器人营销
  9. 前嗅ForeSpider教程:如何创建新任务
  10. Kotlin系列之循环