自顶向下语法分析(top-down parsing)

2023-11-01

输入程序经过词法分析器后产生单词流,并为每个单词流标注了语法范畴。而词法分析器产生的单词流就是语法分析器的输入,语法分析器将为输入的单词流找到推导(或者给不合法的程序输出错误信息),而语法分析树等价于推导,也可以说语法分析器为输入程序构建语法树。基于上下文无关文法的语法分析器主要有两个流派:

  • 自顶向下语法分析(top-down parsing)。从根根节点向底部的叶节点方向构造分析树。也可以看作寻找输入串最左推导的过程。
  • 自底向上语法分析(bottom-up parsing)。底部的叶节点向顶部的根节点方向构造分析树。

本文将介绍自顶向下语法分析器。从有回溯的自顶向下分析开始介绍,引出无回溯的自顶向下分析,也即 LL(1) 语法分析。

词法分析器和上下文无关文法可以戳下面两篇博客:

正则表达式(RE)、有限自动机(FA)和词法分析(LA)
上下文无关文法(CFG)

有回溯的自顶向下分析:非预测分析法

自顶向下的语法分析器从根开始,向下扩展树,直到树的叶子节点和输入串相匹配。这样的过程其实就是为输入串找推导的过程,很自然给出类似下面的算法:

算法中的 backtrace 是一个回溯的过程,也即为上一个出栈的非终结符选择另一个产生式(右部)压栈。

看一个简单的例子,给定语法 G:
S − > N V T N − > p ∣         t ∣         s V − > e ∣         d T − > g ∣         w ∣         r \begin{aligned} S &-> NVT \\ N &-> p \\ &| \ \ \ \ \ \ \ t \\ &| \ \ \ \ \ \ \ s \\ V &-> e \\ &| \ \ \ \ \ \ \ d \\ T &-> g \\ &| \ \ \ \ \ \ \ w \\ &| \ \ \ \ \ \ \ r \end{aligned} SNVT>NVT>p       t       s>e       d>g       w       r

为输入串 t d w tdw tdw 寻找推导(构建语法树)。使用上述算法迭代过程如下:

也可以采用递归的自顶向下分析算法(递归下降分析):每一个非终结符编写一个调用过程。

这个朴素的自顶向下的无预测分析算法存在的问题就是回溯,在上述算法过程中,如果每次替换非终结符作出的产生式选择都是糟糕的,那么该算法代价将直线上升。如果可以消除回溯,也就是说语法分析器每次都能选择正确的产生式,那么这个算法将是线性时间的。

无回溯的自顶向下分析:预测分析法

前面介绍的自顶向下语法分析是一个有回溯的语法分析,原因是每次为一个非终结符选择产生式是盲目的,发现不匹配,就需要回溯。在语法分析器选择产生式时,如果可以同时考虑当前关注的符号以及下一个输入符号(称为前瞻符号,lookahead symbol),则可以通过前瞻一个符号,消除选择产生式时的盲目性。这样的自顶向下分析算法就是一个无回溯的算法,也称预测性算法。

无回溯的算法需要构建一张预测分析表,语法分析器前瞻一个符号再查询预测表来选择使用哪个产生式,就可以避免盲目的选择产生式。为了构建这样一张预测表,我们需要先构建 FIRST 集合和 FOLLOW 集合。FIRST 和 FOLLOW 集使得我们可以根据一个输入符,选择应用哪个产生式。

FIRST集和FOLLOW集

F I R S T ( α ) FIRST(\alpha) FIRST(α) 是指从 α \alpha α 推导出的字符串开头可能出现的终结符的集合。

例如:
S − > X Y Z X − > a   ∣   b . . . \begin{aligned} S &-> XYZ \\ X &-> a \ | \ b \\ ... \end{aligned} SX...>XYZ>a  b

首先, F I R S T ( S ) = F I R S T ( X Y Z ) FIRST(S) = FIRST(XYZ) FIRST(S)=FIRST(XYZ)。这个时候,计算 F I R S T ( S ) FIRST(S) FIRST(S),只需要计算 F I R S T ( X ) FIRST(X) FIRST(X),则 F I R S T ( S ) = { a , b } FIRST(S) = \{ a, b\} FIRST(S)={a,b} ,此时如果输入符号开头是 b b b,则显然选择产生式 X − > b X -> b X>b。但考虑修改上述文法如下:
S − > X Y Z X − > a   ∣   b ∣   ϵ . . . \begin{aligned} S &-> XYZ \\ X &-> a \ | \ b | \ \epsilon \\ ... \end{aligned} SX...>XYZ>a  b ϵ

计算 F I R S T ( X Y Z ) FIRST(XYZ) FIRST(XYZ) 不能只计算 F I R S T ( X ) FIRST(X) FIRST(X) 了,因为 X X X 可能产生空串。这个时候就需要考虑能够产生空串的符号,同时还必须考虑跟随在空符号之后的其他符号。这就引入了 FIRST、 NULLABLE 和 FOLLOW 三个集合。

  • 如果 X 可以推导出空串,则 X ∈ N U L L A B L E X \in NULLABLE XNULLABLE
  • F I R S T ( α ) FIRST(\alpha) FIRST(α) 是可以推导出的字符串开头可能出现的终结符的集合。
  • F O L L O W ( X ) FOLLOW(X) FOLLOW(X) 是直接跟随在 X X X 之后的终结符的集合。例如,存在推导 X t Xt Xt,则 t ∈ F O L L O W ( X ) t \in FOLLOW(X) tFOLLOW(X)。当存在推导 X Y Z t XYZt XYZt,若 Y Y Y Z Z Z 都能推导出空串 ϵ \epsilon ϵ,则 t ∈ F O L L O W ( X ) t \in FOLLOW(X) tFOLLOW(X)

我们很容易给出 NULLABLE 归纳定义和相应不动点的算法:

对于下面的文法:
0                  Z − > d 1                     ∣        X Y Z 2                  Y − > c 3                     ∣         ϵ 4                  X − > Y 5                     ∣         a \begin{aligned} 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Z &-> d \\ 1 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ &| \ \ \ \ \ \ XYZ \\ 2 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ Y &-> c \\ 3 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ &| \ \ \ \ \ \ \ \epsilon \\ 4 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ X &-> Y \\ 5 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ &| \ \ \ \ \ \ \ a \\ \end{aligned} 0                Z1                   2                Y3                   4                X5                   >d      XYZ>c       ϵ>Y       a

很容易得到 N U L L A B L E = { X , Y } NULLABLE=\{ X, Y\} NULLABLE={X,Y}

类似的,我们也可以得到 FIRST 集合的归纳定义和不动点算法:

还是上面的例子,计算非终结符号的 FIRST 集:

\ 0 1 2
Z { } \{\} {} { d } \{d\} {d} { d , c , a } \{d,c,a\} {d,c,a}
Y { } \{\} {} { c } \{c\} {c} { c } \{c\} {c}
X { } \{\} {} { c , a } \{c,a\} {c,a} { c , a } \{c, a\} {c,a}

FOLLOW 集的不动点算法为:

同样计算上述例子的 FOLLOW集:

\ 0 1
Z { } \{\} {} { } \{\} {}
Y { } \{\} {} { a , c , d } \{a,c,d\} {a,c,d}
X { } \{\} {} { a , c , d } \{a,c,d\} {a,c,d}

进一步,可以得到每条产生式的 FIRST_S 集的不动点算法:

\ 0 1 2 3 4 5
FIRST_S { d } \{d\} {d} { a , c , d } \{a,c,d\} {a,c,d} { c } \{c\} {c} { a , c , d } \{a,c,d\} {a,c,d} { a , c , d } \{a,c,d\} {a,c,d} { a } \{a\} {a}

这样,我们就可以得到预测分析表

\ a c d
Z 1 1 0,1
Y 3 2,3 3
X 4,5 4 4

这里的得到预测分析表是有冲突的,也就是说根据前瞻符号查表会得到多个产生式。在后面的章节我们会介绍预测分析表的冲突问题,这里先按下不表。

两种预测分析算法

到这里,如果我们得到无冲突的预测分析表,我们就可以得到无回溯的分析算法了,每次选择产生式的时候,根据前瞻符号查询该表,就可以选择正确的产生式了。到这里,我们就可以得到预测分析算法,一个是递归预测分析算法,一个是非递归预测分析算法(表驱动预测分析)

递归预测分析算法:根据预测分析表为每一个非终结符编写一个调用过程。

非递归预测分析算法:

非递归的预测分析器其实就是一个下推自动机(PDA,Push-down Automata)。它一个输入带、一个读头和一个带有预测分析表的控制器构成,与 FA 相比,这个自动机增加了个栈,也叫下推存储器,起到记忆的作用。

LL(1)文法

这个例子的文法产生的预测分析表存在多重定义的项,也即有的表项中存在多个产生式。如果预测分析表不存在多重定义的项,就称其文法为LL(1)文法,对应的预测分析表也称为LL(1)分析表。LL(1)得名于:这种语法分析器由左(Left)到右扫描输入,构建一个最左推导(Leftmost),并且只使用一个前瞻符号(1)。LL(1) 文法是无回溯的文法,使用LL(1)分析表的预测分析算法也就是无回溯的分析算法。

一个文法G是LL(1)的,当前仅当G的任意两个产生式 A − > α   ∣   β A->\alpha \ | \ \beta A>α  β 满足下面的条件:

  1. 不存在终结符 a a a,使得 α \alpha α β \beta β 可以推导出以 a a a 开头的串。
  2. α \alpha α β \beta β 只有一个可以推导出空串。
  3. 如果 β ⇒ ∗ ϵ \beta \stackrel{*}\Rightarrow \epsilon βϵ,那么 α \alpha α 不能推导出任何以 F O L L O W ( A ) FOLLOW(A) FOLLOW(A) 中某个终结符开头的串。类似地,如果 α ⇒ ∗ ϵ \alpha \stackrel{*}\Rightarrow \epsilon αϵ,那么 β \beta β 不能推导出任何以 F O L L O W ( A ) FOLLOW(A) FOLLOW(A) 中某个终结符开头的串。

前两个条件等价于 F I R S T ( α ) ∩ F I R S T ( β ) = ∅ FIRST(\alpha) \cap FIRST(\beta)=\emptyset FIRST(α)FIRST(β)=。第三个条件等价于如果 ϵ ∈ F I R S T ( β ) \epsilon \in FIRST(\beta) ϵFIRST(β),则 F I R S T ( α ) ∩ F O L L O W ( A ) = ∅ FIRST(\alpha) \cap FOLLOW(A)=\emptyset FIRST(α)FOLLOW(A)=,并且如果 ϵ \epsilon ϵ F I R S T ( α ) FIRST(\alpha) FIRST(α) 中时类似结论也成立。

这样来看,上一节例子中给的文法显然不是 LL(1) 文法。

文法转换

有的文法不是 LL(1) 文法,可以通过文法转换变成LL(1)文法,常见的转换包括:

  • 消除左递归。
  • 提取左公因子。

消除左递归

考虑在 CFG 文法中介绍的算术表达式文法的例子,截取部分如下:
E X P R − > E X P R + T E R M ∣         E X P R − T E R M ∣         T E R M T E R M − > T E R M ∗ F A C T O R ∣         T E R M − F A C T O R ∣         F A C T O R \begin{aligned} EXPR &-> EXPR + TERM\\ &| \ \ \ \ \ \ \ EXPR - TERM \\ &| \ \ \ \ \ \ \ TERM \\ TERM &-> TERM * FACTOR \\ &| \ \ \ \ \ \ \ TERM- FACTOR \\ &| \ \ \ \ \ \ \ FACTOR \\ \end{aligned} EXPRTERM>EXPR+TERM       EXPRTERM       TERM>TERMFACTOR       TERMFACTOR       FACTOR

该文法第 1、2、4、5 条产生式左边的终结符和右部第一个符号相同,将导致自顶向下分析器无线循环下去:
E X P R − > E X P R + T E R M − > E X P R + E X P R + T E R M − > E X P R + E X P R + E X P R + T E R M − > E X P R + E X P R + E X P R + E X P R + T E R M − > . . . \begin{aligned} EXPR &-> EXPR + TERM\\ &->EXPR + EXPR + TERM\\ &->EXPR + EXPR + EXPR + TERM\\ &->EXPR + EXPR + EXPR + EXPR + TERM\\ &-> ... \end{aligned} EXPR>EXPR+TERM>EXPR+EXPR+TERM>EXPR+EXPR+EXPR+TERM>EXPR+EXPR+EXPR+EXPR+TERM>...

左递归文法

对一个 CFG 文法,如果其右部的第一个符号与左侧的符号相同或这能够推导出左侧符号,则称该文法为左递归文法。

前一种情况为直接左递归文法,而后一种情况为间接左递归文法。

左递归文法将导致自顶向下分析去无限循环,需要转换其文法以消除左递归。

对于直接左递归文法,消除方法如下:
A − > A α   ∣   β           ⇒            A − >   β A ′ A ′ − >   α A ′   ∣ ϵ \begin{aligned} A -> A\alpha \ | \ \beta \ \ \ \ \ \ \ \ \ \Rightarrow \ \ \ \ \ \ \ \ \ \ A &-> \ \beta A^{'} \\ A^{'} &->\ \alpha A^{'} \ | \epsilon \end{aligned} A>Aα  β                   AA> βA> αA ϵ

则上述算术表达式的左递归可以转换为:
E X P R − > T E R M   E X P R ′ E X P R ′ − > +   T E R M   E X P R ′ ∣        −   T E R M   E X P R ′ ∣        ϵ \begin{aligned} EXPR &-> TERM \ EXPR^{'}\\ EXPR^{'} &-> + \ TERM \ EXPR^{'} \\ &| \ \ \ \ \ \ - \ TERM \ EXPR^{'} \\ &| \ \ \ \ \ \ \epsilon \\ \end{aligned} EXPREXPR>TERM EXPR>+ TERM EXPR       TERM EXPR      ϵ

而对于间接左递归,例如下面的文法:
S − > A a   ∣   b A − > A c   ∣   S d   ∣   ϵ \begin{aligned} S &-> Aa \ | \ b \\ A &-> Ac \ | \ Sd \ | \ \epsilon \\ \end{aligned} SA>Aa  b>Ac  Sd  ϵ

由于 S ⇒ A a ⇒ S d a S \Rightarrow Aa \Rightarrow Sda SAaSda,因而这是一个简介左递归。

下面的算法可以系统地消除左递归。如果文法中不存在环(即形如 A ⇒ + A A \stackrel{+} \Rightarrow A A+A)或 ϵ \epsilon ϵ 产生式(即形如 A − > ϵ A->\epsilon A>ϵ)就能保证消除左递归。算法如下:

使用该算法很容易消除上面例子中的间接左递归。上面的算法,如果有 ϵ \epsilon ϵ 产生式,不保证一定能够得到正确的结果,但也可能得到正确的结果。
S − > A a   ∣   b A − > b d A ′   ∣   A ′ A ′ − > c A ′   ∣   a d A ′   ∣   ϵ \begin{aligned} S &-> Aa \ | \ b \\ A &-> bdA^{'} \ | \ A^{'} \\ A^{'} &->cA^{'} \ | \ adA^{'} \ | \ \epsilon \end{aligned} SAA>Aa  b>bdA  A>cA  adA  ϵ

提取左公因子

同一个非终结符的多个产生式有公共左因子会导致自顶向下分析算法中产生回溯。例如:
A − > α B 1   ∣   α B 2 \begin{aligned} A &-> \alpha B_1 \ | \ \alpha B_2 \end{aligned} A>αB1  αB2

对于上面不的文法,开头为 α \alpha α 的输入串,不知道选择 α B 1 \alpha B_1 αB1 还是 α B 2 \alpha B_2 αB2,可以将 A A A 展开为 α A ′ \alpha A^{'} αA,从而推迟决定,等读入足够多的信息后才作出正确的决定。

对上述例子提取左公因子:
A − > α B B − > B 1   ∣   B 2 \begin{aligned} A &-> \alpha B \\ B &-> B_1 \ | \ B_2 \end{aligned} AB>αB>B1  B2

些上下文无关语言没有无回溯语法,消除左递归和提取左公因子通常可以消除回溯。但一般来说,对于任意的上下文无关语言,是否存在无回溯语法是不可判定的。

参考:

  • Engineering a compiler (2nd Edition)
  • Compilers Principles Techniques and Tools (2nd Edition)
  • https://mooc.study.163.com/learn/1000002001?tid=1000003000#/learn/content?type=detail&id=1000044013&cid=1000052033
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

自顶向下语法分析(top-down parsing) 的相关文章

  • python_sqlalchemy

    SQLAlchemy使用和踩坑记 知乎 2 长时间未请求连接自动断开 现象 长时间服务端没有连接数据库 数据库连接自动断开 原因 1 sqlalchemy在create engine时 使用连接池并没有指定连接池回收时间 则连接池的连接不会
  • 交互设计实用指南系列(1) – 操作入口明确

    链接 http ued taobao org blog 2009 12 the practice guidelines of interaction design clear operational entrance of effectiv
  • 微软 AD 已成过去式,这个身份领域国产化替代方案你了解吗?

    随着全球互联网和数字化浪潮的不断发展 信息安全已成为不可忽视的问题 并随着日益复杂的国内外市场格局 其重要性更加凸显 我国政府也相继印发和实施了 数字中国建设整体布局规划 全国一体化大数据体系建设指南 等一系列政策 将核心技术自主可控 安全

随机推荐

  • CSS学习————css的选择器(2)

    选择器的作用 用来查找要设置html样式的元素 css的选择器分为4大类 1 简单选择器 6种 1 1 通配符选择器 1 2 标签选择器 1 3 id选择器 1 4 属性选择器 1 5 类选择器 1 6 分组选择器
  • Mac OS X下Android系统华为手机无法连接问题之解决方案

    一般的android连接mac 很方便不用安装驱动就可以啦 可是不知道为什么特殊情况下有的android手机 小米2 华为等 就是连接不上 下来就说说特殊情况下如何连接 使用USB连接安卓手机后可以做2件事情 关于本机 gt 更多信息 gt
  • 模板的显示实例化与显示具体化

    显式实例化 C 中模板函数 类 的调用与定义分离时 需要使用显式实例化 否则就会出现链接错误 编译器对各个cpp分别编译的时候 模板函数必须被实例化编译 如果我们把调用与定义写在一个文件 在调用语句处有int a b cmp a b 那么编
  • UVM的phase机制(Ⅰ)

    uvm存在phase机制 每个phase完成对应的功能 将所有的程序分解在不同的phase中执行 保证了验证环境的验证代码的执行顺序 并且每个phase完成对应的功能 使验证环境运行仿真层次化 让各种组件的例化次序正确 环境的执行顺序正确
  • 万字长文分享,如何自学Java(方法+步骤)

    目录 收起 大家存在的问题 为什么我觉得方法很重要 五个步骤学习Java 第一阶段 揽全局 怎么办 你需要的是系统化学习 教程式笔记 我的大学 我准备从思想方法和具体的学习步骤上给大家聊一下我的做法 希望对大家有所帮助 看完本篇文章你会得到
  • C语言-商品销售管理系统

    C语言 商品销售管理系统 全部代码如下 include
  • jsp中的stl标签库

    catch标签 forEach标签 remove标签 param标签 set标签 url标签
  • ZooKeeper安装后无法启动JAVA_HOME is not set and java could not be found in PATH.

    JAVA HOME is not set and java could not be found in PATH 在安装后使用命令 zkServer sh start启动出现JAVA HOME找不到的提示 去查看 etc profile文件
  • Java网络编程相关知识

    网络编程 1 在网络通信协议下 不同 计算机上运行的程序 进行的数据传输 2 应用场景 即时通信 网游对战 金融证券 国际贸易 邮件等等 3 Java中可以使用java net包下的技术开发出常见的网络应用程序 4 常见的软件架构有CS和B
  • UE4_蓝图调试器

    蓝图调试器 在蓝图调式器里可以看到所有的断点和 watch this value 的值 下面这幅图可以取消所有的断点和watch this value 节点注释
  • opencv 直方图 CV::calcHist使用

    本文转自 http www xuebuyuan com 1014703 html 特别提醒读者 注意实例中数据成员很多都定义成数据 这是由于calcHist函数形参要求的 直方图在图形处理中很常用 直方图可以统计图像的像素特征分布 用于修改
  • jdk1.8 下 list stream转数组 map 循环 过滤等操作的常见写法

    Jdk1 8中 对于 list 有非常多的快捷操作方便我们处理数据 比如 list 转 map 对象操作 在开发过程我们有时会在for循环过程中查询另一个表的数据 我们可以提前把数据查询出来 转换成map 使用过程中通过map获取数据 避免
  • vue 3 第二十六章:样式(scoped、深度选择器、全局选择器、css modules、自定义注入名称、css中v-bind)

    文章目录 1 介绍 2 基本使用 3 scoped原理 4 深度选择器 5 插槽选择器 6 全局选择器 7 混合使用局部与全局样式 8 CSS Modules 9 自定义注入名称 10 CSS 中的 v bind 1 介绍 在 Vue 中
  • 手把手实战教学!语义分割从0到1:一、数据集制作

    本篇博客 是 手把手实战教学 语义分割从0到1 系列的第一篇实战教学 将重点介绍语义分割相关数据集 以及如何制作自己的数据集 本系列总的介绍 以及其他章节的汇总 见 https blog csdn net oYeZhou article d
  • eclipse debug进入.class_用eclipse创建一个java程序

    1 开启Eclipse程序后 首先开始Eclipse中JAVA项目的新建 在上方的选项栏中选择 File New Java Project 系统会弹出新建项目的属性设置 2 在Java Project的设置页面 主要设置project的项目
  • 网狐荣耀手机端内核源码

    网狐荣耀手机端内核源码 实测 可用 链接 https pan baidu com s 1YT GWgFCDxYqrez7e EJqw 提取码 0ezk
  • 因特网(Internet)的概述

    一 因特网的概述 1 主机 连接在因特网上的计算机都称为主机 2 网络 网络 network 由若干节点 node 和连接这些的结点的链路 link 组成 互联网由网络组成 3 Internet和internet的区别 internet 互
  • 线性代数的本质(四)——行列式

    文章目录 行列式 二阶行列式 n n n 阶行列式 行列式的性质 克拉默法则 行列式的几何理解 行列式 二阶行列式 行列式引自对线性方程组的求解 考虑两个方程的二元线性方程组
  • Flask入门教程(3)-表单验证和WTF扩展

    03 01 普通的表单验证 03 02 flash消息闪现 html代码
  • 自顶向下语法分析(top-down parsing)

    自顶向下语法分析 top down parsing 有回溯的自顶向下分析 非预测分析法 无回溯的自顶向下分析 预测分析法 FIRST集和FOLLOW集 两种预测分析算法 LL 1 文法 文法转换 消除左递归 提取左公因子 输入程序经过词法分