形式语言与编译 12 自上而下的语法分析
语法分析之自上而下
一、上下文无关文法与程序结构
这种文法的 格式(模式) \(A->\alpha\) \(\alpha\)是变量与常量组成的字符串。
化成乔姆斯基范式(CNF)和格林巴赫范式(GNF)是为了写程序做准备
通常我们程序句子可以划分如下:
- 说明、定义语句
- 普通语句
- 表达式语句以及赋值语句
- 控制语句
- 函数语句
要是一个文法能够把这两大类(四小类)语句能够描述清楚,就可以说其能够描述程序。
顺序语句文法:
控制语句文法:
二、语法分析的任务与分类
语法分析可以作为主函数,按需调用词法分析器。不用一下子全部把Token流写出来。
符号表是一种数据结构,可以读写。供以后使用。
归约:从终结符串(句子、常量串) 一直到变量S
三、自上而下分析面临的问题
文法的左递归;
回溯;
替换符的顺序会影响接受的语言; \(A->ab|a;A->a|ab\)
难报告出错位置
引起上面这些问题的不确定性有3种:
选择哪一个非终结符、选择哪一个产生式、处理句子时读入几个单词符号(Token )
-
仅有产生式选择是非确定的
采用穷尽+回溯的方法
需要对上面的文法做一定的限制
消除左递归 略
消除回溯;
图示情况1可以唯一确定
这种是比较好区分的,通过查看产生式体的第一个字符
这种情况下就不容易区别哪一个产生式了,因为产生式体的第一个字符相同
解决办法:提取左公因子(实际就是对原来产生式进行修改,公共前缀只出现在单个产生式体中)
情况三
\(N_1,N_2\) 一直往下推,直到推出常量,这个句型串的第一个字符是常量符号就可以啦(可以理解,情况二和情况三是情况一的变式和拓展)
推出\(\epsilon\) 的时候,我们当然就得向后看了,比如上面的 \(N_1,N_2\) 推出\(\epsilon\) ,我们就需要看 \(\alpha_1,\alpha_2\)
把\(A ~ ->~\alpha_1,~\alpha_2\) 这些要看透,一直到露出其第一个常量符号
\(FIRST(\alpha)\) 表示的是 \(\alpha\) 的首字母集合
为什么对于选择产生式,要求\(FIRST(\alpha)\) ??
因为对于变量 \(A->\alpha_1|\alpha_2\) 这两个产生式体,我们是选择 \(\alpha_1\) 呢还是选择 \(\alpha_2\) 呢 由于我们有这样的定义 \(FIRST(\alpha_1) \cap FIRST(\alpha_2) = \Phi\) 所以呢,对于输入符号a,如果 \(a\in FIRST(\alpha_1)\) ,则 \(a\notin FIRST(\alpha_2)\) ,所以可以选定产生式 \(A-> \alpha_1\) 。
等一下,我们上面说消除回溯,那么消除回溯的目的是什么??
Ans: 让我们产生式 \(A->\alpha_1|\alpha_2|\alpha_3|…\) 这样的产生式体推导的首字符集不会相交!! 即\(FIRST(\alpha_1)\cap FIRST(\alpha_2) \cap FIRST(\alpha_3) \cap … =\Phi\)
四、TopDown 之 递归下降法
核心就是 不含左递归 和 \(FIRST(\alpha_1 )\cap FIRST(\alpha_2)\)
CFG与扩展的BNF
五、TopDown之预测分析法
栈可以把递归改为非递归 ,时空转换,从而提高效率
输出: 正确与否 分析树
和下推自动机类似 PDA的功能与预测分析表功能一样
分析表的格式 :一个\(2*2\) 的表格
行代表 变量;列代表常量+1(1是额外加入的终结符 # )
表格内容是 产生式 或者 error出错处理程序
预测分析表实际工作过程 与我们的PDA是一样的;产生式输出 就指导了一个最右推导
这个表的用途:句子的规范推导可以在这样一张表的指导下最终推导出正确结论
工作动作示例:
**对待变量 : 左部出栈,右部反序压栈 **
那么上面这张表怎么来的 怎么填的??
对于 \(A->\ X_1X_2X_3\) 如果\(X_1\)是常量\(a\) ,那么就把\(A->\ X_1X_2X_3\) 填入\([A,a]\) ;
如果\(X_1\) 是变量,那么就把\(FIRST(X_1X_2X_3)\) 所有可能的常量符展开\(x,y,z\) ,把 \(A->\ X_1X_2X_3\) 填入到 \([A,x],[A,y],[A,z]\) ;
如果\(\ X_1X_2X_3\) 是\(\epsilon\) , \(\epsilon \in FIRST(\ X_1X_2X_3)\) ,那么把\(A->\ X_1X_2X_3\) ,加入到 \([A,b_1],[A,b_2]\) 其中 \(b_1,b_2 \in FOLLOW(A)\)
什么时候使用 \(A->\epsilon\) ?? 当前字符(c)属于 \(FOLLOW(A)\)时,我们使用 \(A->\epsilon\)
\(FOLLOW(A)\) 表示 A的后面跟什么?
注意细节:\(FIRST(\alpha)\) 的\(\alpha\) 是字符串;
\(FOLLOW(A)\) 的A是变量。
从起始变量开始观察每一个含有A变量的产生式 ,取A后面跟这的常量。然后组成集合。和\(FIRST(\alpha)\) 一样,也是常量符号组成的集合
第四条可以这样理解: \(N->S\beta|other\) 这样的话,就一定有\(FIRST(S\beta) \subseteq FIRST(N)\)
注意到 \(X->Y_1Y_2Y_3\) 求\(FIRST(x)\) 则先求 \(FIRST(Y_1)\) ,若\(Y_1\) 没有\(\epsilon\) ,则\(FIRST(x) = FIRST(Y_1)\) 结束;若 \(Y_1\) 有\(\epsilon\) ,则把踢掉\(\epsilon\) 的其他常量符加入 \(FIRST(x)\) ,然后继续求\(FIRST(Y_2)\) ,对\(FIRST(Y_2)\) 也做类似分析,看看是否含有\(\epsilon\) 。
FOLLOW
对3可以做如下理解:
本来有 \(A->\alpha B\) 我们要求\(FOLLOW(B)\) ,本来对 \(\gamma_1A\gamma_2\) 我们有替换,\(\gamma_1\alpha B \gamma_2\) 则我们要求,发现\(FOLLOW(A) \subseteq FOLLOW(B)\)
预测分析表有一部分需要使用first与follow集合,现在我们回去再看预测分析表怎么填
\(first(\alpha)\) 把一个变量彻底暴露出所有可能的常量集合
分析表的构造还是通过文法得到的 输入:文法 输出:分析表
六、TopDown之LL(1)
LL(1)分析法
第一个L表示从左到右扫描输入串;
第二个L表示 最左推导
(1) 表示分析查表时,只向前看一个符号
LL(1)文法:分析表每个格子要么没有产生式,要么只有一条产生式
使用 LL(1)文法 一定可以实现不带回溯且自上而下 的语法分析
判断G(S)文法是LL(1)文法,就采用两个条件即可:
- $FIRST(\alpha_1) \cap FIRST(\alpha_2) \neq \Phi $
- \(FIRST(\alpha_1) \cap FOLLOW(A) \neq \Phi\)
要是不是 \(\Phi\) 的话,都意味着会有两条产生式仍然面临回溯。也就是说只要有交集,那么就不会是LL(1)文法。
面对这种非 LL(1)的文法,两条产生式在一个单元格。我们强行删掉一条。(强删一条也就是约定,既然约定了,也就面临抉择,不会产生回溯,选择是唯一的)