1. 语法分析的目的
构建一个解析树或语法树
- 根:独特的符号,“< program >"或其他
- 叶子:词法分析阶段的tokens序列
- 树中的每个分支点都由语法规则认可
- 可能同时执行语义检查
- 这个阶段可以一次一位地生成一系列机器代码指令或程序的其他中间形式
- 如果程序的语法无效,请尽可能多地给出有用的错误消息
识别比单词更大的单位
- 单位表示语法结构
- 表达式、函数、声明
基于词汇分析,根据语法规则。例如
A:=x + 3.14*y
指出语法错误
生成内部表达式以便进行语义处理
2. 分析树
- 根:语法起始符号
- 内部节点:有一个或多个孩子
- 叶子:没有孩子
3. 两种解析策略
搜索问题 – 给定根和叶,在它们之间找到适当的标记分支点
自顶向下解析策略
从顶部开始搜索,从< program >开始,尝试向下搜索填充在树中的树叶。
自下而上的解析策略
从词汇分析阶段的叶子序列开始,在我们朝着<program>的方向前进时,尝试构建树的一部分。
4. 语法分析器
语法分析器决定了程序的机构。
语法分析的结果是解析树或语法树。
解析树:
5. 语法分析的两种句法分析策略
自上而下分析 LL(1)/** k =1 **/
- 第一个L代表从左边开始扫描
- 第二个L表示产生最左推导
- 数字1表示每一步推导式只需要向后看一个符号就可以
自下而上分析LR(0)/** k =0 **/
- 第一个L代表从左边开始扫描
- 第二个R代表最右推导的逆过程
- 数字0表示向右查看0个字符
语法分析的概念
形式上,对于指定的字符串α,识别它是否是指定语法G(Z)的句子。
解析策略的标准
力量
回退
预测
效率
小规模
解析策略的标准-力量
必须足够强大,以便分析语言。
Earley和CYK可以用于任何上下文无关的语法。
当我们为新的编程语言编写语法时,我们必须检查语法是否与所提出的解析策略兼容。如果没有,我们必须以适当的方式修改语法。
解析策略的标准-回退
一些解析策略涉及反向跟踪。
当解析器确定先前关于解析树某部分的形式的决定是错误的时,它会返回到该决定,并以不同的方式重新执行该决定。
反向跟踪效率很低,因为它需要解析器对输入流进行多次传递。
但该决定的任何后果也必须以某种方式扭转。
- 从符号表中删除或修改的条目
- 删除生成的机器/中间代码
- 等等
一旦对输入程序的某一部分的形式做出决定,就永远不会修改。
本模块中的技术不需要反向跟踪。
解析策略的标准-向前看
回退的另一种选择是向前看,当解析器需要做出决定时,它会查看输入流中接下来的几个标记,以帮助做出正确的决定。
原则上,解析器可以沿着输入程序任意地看得很远,也许可以一直看到底,但这可能效率很低。
我们希望尽量减少向前看。
两种策略都涉及一定的前瞻性,但它们通常需要最多一个前瞻性符号。
当做出决定时,解析器只允许查看来自词法分析器的下一个输入标记。这对于普通编程语言来说通常足够了。
解析策略的标准-效率
需要与程序长度成比例的时间。
解析策略是O(n)算法,其中n是输入字符串的长度。
解析策略的标准-小规模
如果解析器也可以很小,那就太好了,但现在问题不大了,因为存储非常便宜。
一些版本的最佳解析策略实际上可以生成非常大的表,以向解析器指定在每种情况下要做什么。如果我们在一台小型机器上工作,就必须避免这种情况。
自上而下分析
从顶部(根)到底部(叶)构造解析树。
通常从符号S开始的过程导出字符串w。
如果每个字符串都在树叶上,那么这个句子就被接受了。
在每一步中,我们应该有两个选择:
- 应替换哪个非终端
- 这个非终端产品应该用哪个来替代
两种解析策略
两组主要的解析策略,称为LL(K)和LR(K)
第一个L表示从左到右处理令牌的输入流
k是允许的前瞻tokens的数量,结果通常是0或1
第二个字母指定解析器生成的派生类型
LL(k)解析器产生最左边的派生,是一个自顶向下的解析器。
LR(k)解析器产生最右边的派生,是一种自下而上的方法。
LL(k)和LR(k)不像Early和CYK那样完整,但幸运的是足以处理大多数PL语法。
Thanks to Dr. John: Some contents are from their slides.