[toc]
- 接收词法分析器输出的记号串
- 报告并尝试语法错误
- 词法错误/语法错误/语义错误(算法作用于不相容的运算对象)/逻辑错误
- 清楚准确报告错误,并从中恢复以诊断后文(抑制伪错误)
- 紧急方式(恐慌模式)恢复(跳过部分错误,无死循环)
- 短语层次恢复(需要避免死循环)
- 出错产生式(常见错误)
- 全局纠正(理论,开销大,作为评价标准)
- 输出某种形式的分析树
- 把记号信息收入符号表,类型检查,生成中间代码
- 推导$\Rightarrow$
-
$\xRightarrow{Rule}$ ;$\xRightarrow{*}$;$\xRightarrow{+}$ - 最左推导$\xRightarrow[lm]{}$;最右推导$\xRightarrow[rm]{}$(规范推导)
- 每个语法树只有一个最右(左)推导。分析树构建过程对应一个推导过程,但分析树本身对应推导不唯一
-
- 二义性
- 某些情况下有便利性,但需要有消除的机制
- 语法和语言等价的证明(两方向归纳)
- 任何一个3型文法都是2型文法
- 由NFA得到识别同样语言的2型文法
- 上下文无关文法适合描述循环和对称结构
- 悬空二义性的处理(悬挂else)
- 固有二义性语言:无论如何改写文法都无法消除二义性
- 无关紧要的二义性(如结合律)
- 可能增大自上而下的回溯开销
- 避免回溯
- 引入新变量来提取左因子
- 存在形如$A \Rightarrow A\alpha$的推导
- 导致自顶向下的方法进入死循环
- 可以用非左递归的产生式取代(引入中间变量)
- 左递归的分类
- 直接见诸产生式的左递归:把$A \rightarrow A\alpha | \beta$改写为$A \rightarrow \beta A^\prime,A^\prime \rightarrow \alpha A^\prime | \epsilon$
- 一般的左递归(推导下的左递归):用产生式替换出直接左递归,再消除
- 一般策略:自上而下、从左到右的先根次序建立语法树的试探过程
- 递归下降的语法分析
- 每个过程联系到一个非终结符
- 可能存在回溯(左递归)
- 分析失败时难以得知错误的准确位置
- 产生式右部由终结符开始,左部相同时右部首终结符
- 不需要回溯的条件:任何非终结符能根据输入符号唯一确定执行任务
- FIRST集(终结首符集合):所有推导式中右部的终结首符。可能包括$\epsilon$
- 任意两个FIRST集交集为空集时可以唯一指派
- 终结符的FIRST只包含自身
- 文法符号串的FIRST集计算方法类似
- FOLLOW集(非终结符的后继符集合):当非终结符是某个句型最右符号时,FOLLOW集包括$
- 开始符的FOLLOW集包含输入结束符号$
- 对产生式$A \rightarrow \alpha B$或$A \rightarrow \alpha B \beta$($\epsilon \in FIRST(\beta)$),则$FOLLOW(A) \subset FOLLOW(B)$
-
$\epsilon$ 一定不属于FOLLOW集
- 预测分析器的转换图
- 消除左递归、提取左公因子
- 对每个非终结符创建一个初始一个结束状态,对每个产生式创建一条路径,边的标号为终结符或词法单元(执行转换)或非终结符号(调用对应过程)
- 可以进行消除尾递归的优化
- 定义:任意两个产生式$A\rightarrow \alpha | \beta$,满足$FIRST(\alpha) \cap FIRST(\beta) = \empty$,且$\alpha \rightarrow \epsilon$时$FIRST(\beta) \cap FOLLOW(A) = \empty$
- 解释:从左向右扫描产生最左推导,每一步只向前看一个输入符号即确定语法分析动作
- 性质:无二义性;不含左递归;无回溯
- 不是所有文法都能变换为LL(1)文法
- 非递归的预测分析:用状态栈取代递归调用
- 包括输入缓冲、输出、预测分析表和状态栈(栈底为$)。状态栈中的内容通过不断展开增加,匹配输入流后出栈(栈顶符号匹配),每一刻的展开结果都应当与输入的剩余部分相匹配
- 预测分析表M的构造
- 对产生式$A \rightarrow \alpha$,对$FIRST{\alpha}$中的每个终结符$a$,把产生式加入$M[A,a]$。若$\epsilon \in FIRST(\alpha)$,则对$FOLLOW(A)$中的终结符$b$(包括$),把产生式加入$M[A,b]$
- 对空的$M[A,a]$项,置为error(语法错误)
- 适用于任何文法G
- 对LL(1)文法,每个M条目至多有一个产生式
- 左递归或二义性文法有至少一个多重定义的条件
- 恐慌模式
- 跳过一些输入符号,直到期望的同步记号出现
- 效果依赖于同步记号集合选取
- 将FOLLOW集中所有符号放入同步集合。跳过记号匹配到FOLLOW集元素时pop,继续分析
- 把高层结构的开始符号加入底层结构的同步集合,如把语句开始关键字加入表达式非终结符的同步集合
- 将FIRST集中所有符号放入同步集合(认为前面是冗余的)
- 出错时把栈顶可以产生空串的非终结符视作产生空串弹出。导致延迟错误发现但不会遗漏,可减少恢复要考虑的非终结符
- 直接弹出栈顶终结符
- 短语级恢复
- 可以修改、插入、删除输入符号,也可以弹出栈内符号,但不能替换或压入栈顶符号(保证恢复动作使剩余输入串缩短,避免无限循环)
- 从叶结点逆序构建树
- 对应于最右推导的逆过程(最右规约)
- 移进-规约的语法分析,包括R分析和算符优先分析
- 移进-归约分析的动作包括移进、规约、接受、出错
- 可以处理左递归
- 动作判断可使用栈中所有符号
- 预测分析器模拟推导,自底向上分析描述规约过程,后者更复杂
- 特定算法会影响分析程序检测错误位置的能力
- 移进-规约冲突
- 根据栈中所有内容和下一个输入符号不能决定移进和规约
- 解决方法:移进优先原则解决一些二义性
- 规约-规约冲突
- 根据栈内所有内容和下一个输入符号无法确定用于规约的产生式
- 解决方法:引入中间符号
- 定义:若存在最右推导$S\Rightarrow \alpha A \omega \Rightarrow \alpha \beta \omega$,则$A\rightarrow \beta$是$\alpha \beta \omega$在$\alpha$后的句柄
- 文法无二义性时句柄唯一,否则不一定唯一
- 移进-规约分析器将终结符push入栈,直到能执行规约得到右句型;句柄对应产生式时下步归约中应用的产生式,且句柄最右位置与栈顶平齐
- 句柄代表了最左边的由一个内部节点和后代组成的子树
- 句柄右边的符号串只包括终结符
- 活前缀(可行前缀)
- 规范句型(右句型)中不含句柄后终结符的一个前缀
- 已扫描部分若可规约成活前缀,则扫描部分没有错误
- 从左向右扫描输入、构造最右推导的逆。LR(k)分析中k表示分析动作时向前看的符号个数,省略时默认为1。
- 几乎所有可以表示为上下文无关文法的程序语言都可以被识别
- 已知最一般的无回溯移进-规约方法
- 最大可能地察觉语法错误,报告错误前不做任何无效规约
- 项(item)对应一组分析的状态(位置+产生式)
- LR(0)项目:产生式右部有n个项时,LR(0)项目有n+1个
- 转移DFA构造
-
$A \rightarrow \beta . \gamma$ 表示$\beta$已在栈顶,下面输入符号可能获取$\gamma$ -
$A \rightarrow . \alpha$ 表示将用$A \rightarrow \alpha$识别A,称为初始项目 -
$A \rightarrow \alpha .$ 表示$\alpha$已在栈顶,且下一个规约使用此产生式时可能是句柄,称为完整项目 - 首先构造增广文法(新的起始符号)
- 需要增加CLOSURE函数和GOTO函数
- 构造有限自动机时,对项目$A \rightarrow \alpha . X \eta$,若X为非终结符,则需要添加到每个以X为左部的产生式对应项目$X \rightarrow . \beta$的$\epsilon$转换
- 初始文法时拓广文法开始符号的产生式
- 可以没有接收状态,接受与否由编译器决定而非DFA
- 子集构造法转换DFA的项目集规范族
- 闭包计算:每次推移点,闭包=内核项(初始项目$S^\prime \rightarrow .S$和所有点不在左端的推移结果)+非内核项(后续非终结符点在最左展开项目)
- 转换即为GOTO函数(对项目集和变元输出项目集)
- 格局是栈的内容和剩余句子内容的二元组
- 若将LR(0)的DFA每个状态都定为接收状态,则识别文法的活前缀(因为栈里的内容不能越过句柄)
-
- 定义:能构造出所有条目都唯一的LR分析表
- 性质:当句柄出现在栈顶时,从左向右扫描的移进规约分析器能只通过栈顶信息进行识别
- LR(k)文法:最多向前看k个文法符号就可以决定动作的LR分析器的文法
- LL文法只要看右部推出的前k个符号,LR文法还看到了产生式右部推导出的所有东西(LR分析器构建过程中用到了所有产生式,
雪崩时每一片雪花都在勇闯天涯)
- 规约分析表
- 对所有终结符记录ACTION项,对所有非终结符记录GOTO项
- ACTION项包括error和acc和si和ri,其中LR(0)分析若存在ri,则必定整行都是ri(因为缺少向前看的能力)
- 对于完整项目进行规约
- si时对stack和symbol压入栈,ri时对stack和symbol弹出栈产生式右部文法符号次数
- 栈中存储的实际是文法符号和状态的二元组
- LR(0)项目$A \rightarrow \beta_1 . \beta_2$对活前缀$\alpha \beta_1$有效,若存在推导序列$S \Rightarrow \alpha A \omega \Rightarrow \alpha \beta_1 \beta_2 \omega$
- 这表明$\alpha \beta_1$在分析栈顶时,若$\beta_2 = \epsilon$则$A \rightarrow \beta$为句柄,是应选择的规约式;否则句柄未完全进栈,需要继续移进
- 若活前缀的复数个有效项目对应了不同动作(相当于是有左公因子?),则需要向前看等方式解决冲突
- SLR(1)分析表
- 针对需要考察FOLLOW集的冲突情况
- 包含$[ S^\prime \rightarrow S. ]$的项目集读取$$$时转入acc
- 包含$[ S^\prime \rightarrow .S ]$的项目集时初始状态
- 若项目集$I_i$包含编号为$j$的$[ A \rightarrow \alpha. ]$,则对所有$FOLLOW(A)$中的$a$,置$ACTION(i,a)=rj$
- 所有动作都无冲突时是SLR(1)文法
- 无二义性是SLR(1)文法的必要非充分条件
- 保证无规约-规约冲突:对$A \rightarrow \alpha .$和$B \rightarrow \gamma .$,$FOLLOW(A) \cap FOLLOW(B) = \empty$
- 保证无移进-规约冲突:对终结符$X \in FOLLOW(B)$,任何状态不同时包含项目$A \rightarrow \alpha . X \beta$和$B \rightarrow \gamma .$
- SLR(1)分析能力强于LR(0),但仍有不足:活前缀的有效项目冲突(移进-规约冲突)
- 有些规约存在发生条件,但SLR分析不足以记住这些条件
- SLR(1)分析构造LR(0)项目的DFA时不考虑向前看的符号,构造完成后考虑;LR(1)在向前看符号的基础上进行DFA构造
- LR(1)项目:$[ A \rightarrow \alpha . \beta, a ]$,其中终结符$a$可以是$$$
- LR(1)的1即项目第二项(搜索符)的长度。在$\beta = \epsilon$时,表示只有下一个输入符号为搜索符时才能规约
- LR(1)项目$A \rightarrow \alpha . \beta$对活前缀$\delta \alpha$有效,若存在推导序列$S \Rightarrow \delta A \omega \Rightarrow \delta \alpha \beta \omega$,其中$a$为$\omega$的首终结符($\omega = \epsilon$时$a = $$)
- 若对活前缀$\gamma$有效的项目集中包括项目$[A \rightarrow \alpha . B \beta, a]$,则对所有形如$B \rightarrow \eta$的产生式,$[B \rightarrow . \eta, b]$对$\gamma$也有效。其中$b = FIRST(\beta a)$
- LR(1)分析表构造
- 构造项目集规范族
- 若$[ A \rightarrow \alpha . a \beta , b ] \in I_i$且$GOTO(I_i, a) = I_j$,则$ACTION(i, a) = sj$
- 若$[ A \rightarrow \alpha . , a ] \in I_i$且$A \neq S$,则$ACTION(i, a) = r \alpha$
- 若$[ S^\prime \rightarrow S . , $ ] \in I_i$,则$ACTION(i,
$) = acc$ - 若$GOTO(I_i, A) = I_j$,则$GOTO[i, A] = j$
- 空白条目置error
- 初始状态为包含$[ S^\prime \rightarrow .S , $ ]$的项目集
- lookahead-LR分析,为了减少状态数至SLR的水准,但保留部分特征
- 构造同心的LR(1)项目集:第一个成分集合相同。具有相同核心的两个LR(1)项目DFA状态,必定对所有符号有相同的转换,且转换结果具有相同的核心
- 构造方法一:先构建LR(1)分析表,再合并同心项。
- 可能引入规约-规约冲突
- 可以发现所有LR(1)发现的错误,但可能需要在报错前作虚假规约
- 构造方法二:只使用内核项($[ S^\prime \rightarrow .S]$和$[ S^\prime \rightarrow .S ,
$]$ 和所有点不在产生式右部左端的项)表示LR(0)或LR(1)项目集- 用$#$代替向前看符号,判断传播/自发。
- 搜索符自发生成:对$[A \rightarrow \alpha . \beta , #]$的闭集$I$,若存在$[B \rightarrow \gamma . X \delta , a]$,则$GOTO(I,X)$中的项$B \rightarrow \gamma X . \delta$的向前看符号$a$是自发生成的
- 搜索符传播生成:对$[A \rightarrow \alpha . \beta , #]$的闭集$I$,若存在$[B \rightarrow \gamma . X \delta , #]$,则$GOTO(I,X)$中的项$B \rightarrow \gamma X . \delta$的向前看符号$#$是传播生成的
- 闭包构建初只包含自发生成的向前看符号,然后通过若干趟扫描获得传播生成的
- 复杂度为$n^2$
- 二义性文法在一些情况下更简单
- LR分析中消除二义性的规则
- 优先级和结合规则分析动作冲突
- 如明确规定左结合、优先级
- 移进优先于规约的规则解决最长匹配问题
- 如悬挂else
- 按特殊情况产生式规约,解决特殊情况产生式二义性导致的规约-规约冲突
- 优先级和结合规则分析动作冲突
- LR分析中访问转移表不会发现错误,检测到分析表中的空白项会报错
- LR分析的紧急方式
- 抛弃若干个输入符号,弹出栈顶若干个状态,直至找到能合法跟随当前非终结符的符号,再把GOTO状态压栈,恢复正常分析
- 短语级回复:考察LR分析表的每个出错条目,并根据语言使用情况决定可能错误原因,编一个适当的错误恢复例程
- Yacc:LALR(1)分析程序的生成器
- 默认选择第一个规约式解决规约-规约冲突,移进优先解决移进-规约冲突
- 优先级越下越优先
- 错误纠正:错误产生式$A \rightarrow error \alpha$