自己动手写编译器:First 集合,Follow 集合和 Select 集合

在上一节内容,我们手动设计了解析跳转表,表的行对应当前解析堆栈上的非终结符,列对应当前读取的终结符,于是对应的表格数字表示当前应该采取哪个推导表达式。本节我们看看如何自动化构建解析跳转表。首先我们引入一个概念叫 First 集合,我们先看一组表达式:

statement -> LEFT_BRACKET expression RIGHT_BRACKET | expression SEMICOLON

expression -> LEFT_PARENT expression RIGHT_PARENT | term MINUS expression | EPSILON

term -> NUMBER | IDENTIFIER

我们看看如果从非终结符expression 开始,它可以通过推导“直达”的终结符有哪些? 首先根据表达式 expression -> LEFT_PARENT expression RIGHT_PARENT 可以看到它能直达非终结符 LEFT_PARENT, 然后通过表达式:
expression -> term -> NUMBER|IDENTIFIER, 可以看到它也能“直达” NUMBER, IDENTIFIER。 此外通过 expression -> EPSILON 得出它也能直达终结符 EPSILON。

注意 expression 不能“直达” RIGHT_PARENT, 因为要抵达 RIGHT_PARENT ,它需要先经过非终结符 expression, 于是这个路径必须先经过 LEFT_PARENT,
NUMBER, INDENTIFIER 中的一个,因此 RIGHT_PARENT 无法“直达”。

由此我们将一个给定的非终结符能直达的终结符的集合称作它的 First 集合,也就是 First(expression)={LEFT_PARENT, NUMBER, IDENTIFIER, EPSILON}. 我们看看计算 First 集合的步骤

1, 如果 A 是一个终结符,那么 Fisrt(A) = {A}

2, 如果存在表达式 s -> A a , 其中 s 是非终结符, a 可能是一个或多个终结符和非终结符,例如表达式 expression -> LEFT_PARENT expression RIGHT_PARENT, 那么 expression 就对应 s, LEFT_PARENT 就对应 A, expression RIGHT_PARENT 就
对应 a, 在 s -> A a 这种情况下, A 属于集合 First(s)。

3, 对于表达式 s -> b a,其中 s, b 对应一个非终结符, a 可以是一个或多个终结符和非终结符的集合,那么 First(b)是 First(a)的一个子集。

4,对于表达式 s -> a b c 其中 s 是一个非终结符,a 是一个非终结符,并且 a 可以推导出 EPSILON,b 可以是一个终结符或者非终结符,那么 First(a) 并上 First(b)是 First(a)的子集。
例如表达式 statement -> expression SEMICOLON,statement 对应 s, expression 对应a, SEMICOLON 对应 c,于是 First(expression)并上 First(SEMICOLON) 是 First(statement)的子集。 因为 First(expression)={LEFT_PARENT, NUMBER,
IDENTIFIER, EPSILON}, First(SEMICOLON)={SEMICOLON}, 同时 expression->EPSILON,也就是 expression 能推导到EPSILON,所以两个集合并起来也就是{LEFT_PARENT, NUMBER, IDENTIFIER, EPSILON, SEMICOLON}是 First(statement)的一个
子集。需要注意的是,如果a对应三个非终结符的集合x,y,z,并且他们都能推导到 EPSILON, 那么 First(s)就会包含 First(x), First(y), First(z)。

我们看个具体例子:

stmt -> expr SEMICOLON

expr -> term expr_prime | EPSILON

expr_prime -> PLUS term expr_prime | EPSILON

term -> factor term_prime

term_prime -> STAR factor term_prime | EPSILON

factor -> LEFT_PAREN expr RIGHT_PAREN | NUMBER

首先有 First(factor)={LEFT_PAREN, NUMBER}, 由于 factor 出现在 term->factor term_prime, 因此 First(factor)是 First(term)的子集, 同理 First(term)也是 First(expr)的子集,同理 First(expr)是 First(stmt)的子集。

从表达式中可以观察到 First(factor)={LEFT_PAREN, NUMBER}, term的推导中直接跟着 factor,所以 First(term)=First(factor) = {LEFT_PAREN, NUMBER}. expr 的推导中直接跟着 term,同时它又可以直接推导到 EPSILON,

因此First(expr) = {LEFT_PAREN, NUMBER, EPSILON}, expr_prime 在推导中后面只能直接跟着 PLUS, 所以 First(expr_prime)={PLUS}, stmt 在推导中跟着 expr,注意到 expr能推导到 EPSILON,因此 expr 后面的 SEMICOLON 也属于First(stmt),
因此有First(stmt)={LEFT_PAREN,NUMBER,EPSILON,SEMICOLON}, 对应 term_prime 来说,它在推导中直接抵达 STAR,因此有 First(term_prime)={STAR}。

除了 First 集合,我们还需要了解另一种集合叫 Follow 集合。 所谓 Follow 集合就是给定某个非终结符,我们把所以在推导表达式中能直接跟着该符号的终结符找出来形成一个集合。我们看具体例子:

1,compound_stmt -> LEFT_BRACKET stmt_list RIGHT_BRACKET,

2,stmt_list -> stmt_list stmt

3,stmt -> expr sEMICOLON

从第一个表达式看到 RIGHT_BRACKET 跟在 stmt_list 后面,因此它属于集合 Follow(stmt_list)。 下面我们看一个推导过程:

compund_stmt -> LEFT_BRACKET stmt_list RIGHT_BRACKET, 使用表达式 2 替换其中的 stmt_list 就有:compund_stmt -> LEFT_BRACKET stmt_list stmt RIGHT_BRACKET

于是乎 RIGHT_BRACKET 也能在表达式中跟在 stmt 后面因此它也属于集合 Follow(stmt)。 如果使用表达式 3 去替换此时的 stmt 就有:

compound_stmt -> LEFT_BRACKET stmt_list expr SEMICOLON RIGHT_BRACKET,这意味着 SEIMICOLON, RIGHT_BRACKET 属于 Follow(expr)。

我们看看如何计算前面表达式中非终结符的 Follow 集合。 首先从表达式 stmt -> expr SEMICOLON, factor -> LEFT_PAREN expr RIGHT_PAREN 可以看到 SEMICOLON, RIGHT_PAREN 都直接跟在 expr 后面,
因此有 Follow(expr)={RIGHT_PAREN,SEMICOLON} , 从表达式 expr_prime -> PLUS term expr_prime 可以看出,所有出现在 expr_prime 能直达的终结符也必然跟在 term 的后面,因此 First(expr_prime)属于 Follow(term)。
根据表达式 term_prime -> STAR factor term_prime ,任何能出现在 term_prime 能直达终结符也必然跟在 factor 后面,因此 STAR 也属于 Follow(term_prime),于是经过一轮分析我们就有:

Follow(stmt) ={},
Follow(expr) = {SEMI, RIGHT_PAREN}
Follow(expr_prime) = {}
Follow(term) = {PLUS}
Follow(term_prime)={}
Follow(factor)={STAR}

其实一轮分析还不够,我们需要返回运用前面的分析过程直到没有任何非终结符对应的 Follow 集合发生变化为止。 综合来说寻找 Follow 集合的步骤如下:

1,如果 s 是推导表达式中的起始符号,也就是第一个表达式箭头左边的符号,那么 EOF(end of input)这个符号先加入 Follow(s)。

2,对于表达式 s -> … a b … ,其中 a 是非终结符,b 是终结符或非终结符,那么 First(b)属于 Follow(a)。

3,对于表达式 s -> … a b c … ,其中 a 是非终结符,b 是可以推导为 EPSILON 的非终结符,那么 Follow(a)就包含 First(b)和 First©。

4,对于表达式 s -> … a,其中 a 是最右边的非终结符,那么 Follow(s)是 Follow(a)的子集。

5,对于表达式 s -> … a b1 b2 … bn,其中 b1, b2…bn 对应可以推导到 EPSILON 的非终结符,那么 Follow(s)是 Follow(a)的子集。

在前面我们构造的解析跳转表中,最顶部一行对应所有终结符,最左边一列对应非终结符,然后表中的格子对应表达式编号,我们先从解析堆栈拿到当前要解析的非终结符,然后从输入中读入终结符,接着从跳转表中查询要使用的推导表达式。我们看一个具体例子,假设有如下表达式:

1, terminal -> PERKIN_ELEMER pk

2, terminal -> ADM3 adm

3, terminal -> dec_term

4, dec_term -> VT_52

5, dec_term -> VT_100

然后它对应如下解析跳转表:

[外链图片转存中…(img-K7olmHw2-1715056118298)]

根据以上信息我们得出每个表达式对应的 Select 集合如下:

Select(1) = {PERKIN_ELMER}

Select(2) = {ADM3}

Select(3) = {VT_52, VT_100}

Select(4) = {VT52}

Select(5) = {VT_100}

其中 Select(3)表示当当前输入的终结符是 VT_52, VT_100 ,解析堆栈顶部的非终结符是表达式 3 箭头左边的非终结符 terminal 时,选择表达式 3. 这里跟我们前面一节不同的是,集合针对的是表达式的编号,而不是表达式的非终结符。对于 LL(1)语法来说,
如果多个表达式箭头左边的非终结符一样,那么表达式对应的 Select 集合必须不同,要不然语法解析就无法进行,因为给定同一个非终结符,那么对应当前输入的终结符,它就可以有多个表达式可以选择,于是语法解析就不知道该选哪一个好。

我们看看生成 Select 集合的基本步骤:
1,如果一个表达式它右边所有的非终结符都可以推导出 EPSILON 或者它右边就是 EPSILON,那么我们称该表达式为 Nullable。

2,对非 nullable 的表达式,假设它有如下形式 s -> a1 a2 …an b … ,其中 s 是非终结符,a1…an 是一系列可以推导到 EPSILON 的非终结符集,b 是一个终结符或者是不能推导到 EPSILON 的非终结符,假设该表达式的编号为n,
那么 Select(n) = {First(a1), …, First(an), First(b)}, 如果 a1,…an,中包含不能推导到 EPSILON 的非终结符,那么 Select(n)={First(b)}

3, 对于 nullable 的表达式 s -> a1, a2, … an,也就是 a1, a2…an 能推导到 EPSILON,假设其编号为 n,那么 Select(n)={First(a1), First(a2),…,First(an), Follow(s)}

下面我们给出自动化创建解析跳转表的步骤:

1, 将跳转表 parse_table的每个格子设置为 error,

2, 遍历每个表达式,假设当前表达式编号为 n, lhs 为表达式箭头左边的非终结符, token 为 Select(n)中的终结符,那么有parse_table[lhs][token] = n

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/7457.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java代码基础算法练习-年龄问题-2024.05.07

数学家维纳智力早熟,11岁就上了大学。一次,他参加某个重要会议,年轻的脸孔引人注目。于是有人询问他的年龄,他回答说:“我年龄的立方是个4位数。我年龄的4次方是个6位数。这10 个数字正好包含了从0到9这10个数字&#…

mac安装linux的centos strem9 虚拟机解压rar文件报错

背景:解压rar文件需要再linux上安装unrar工具 yum install unrar直接安装的后解压报错,如图 解决办法: 下载:wget https://www.rarlab.com/rar/rarlinux-x64-6.0.2.tar.gz 安装: tar -zxvf rarlinux-x64-6.0.2.tar.gz cd rar …

水力发电乙级资质标准全解读从人员到设备

企业资质要求 独立法人资格:企业必须具有独立的企业法人资格,这是参与市场活动的基础。社会信誉:企业应具有良好的社会信誉,反映其商业道德和市场表现。注册资本:注册资本不少于100万元人民币,确保企业有足…

分销体系搭建sop,如何快速建立分销体系

坐标:厦门,我是易创客运营肖琳 深耕社交新零售行业10年,主要提供新零售系统工具及顶层商业模式设计、全案策划运营陪跑等。 今天为大家介绍分销体系搭建 SOP。 分销是什么? 分销是一种店铺利用客户推广带来流量与销量的营销工具。分销商通过…

Puppeteer的基本使用及多目标同时访问

文章目录 一、安装 puppeteer 并更改默认缓存路径1、更改 Puppeteer 用于安装浏览器的默认缓存目录2、安装 puppeteer3、项目结构目录 二、基本使用1、启动浏览器并访问目标网站2、生成截图3、生成 PDF 文件4、获取目标网站 html 结构并解析5、拦截请求6、执行 JavaScript7、同…

大模型最新消息

最新消息如下: 大语言模型服务的多样化:互联网上出现了许多免费的大语言模型服务,如OpenAI的ChatGPT、Google的Gemini、Anthropic的Claude、Meta的Llama等。这些服务的推出使得大语言模型的应用更加广泛和便捷。软银和苹果的AI新动向&#x…

IEEE(TOP),CCF推荐,5本毕业神刊,最快7天录用!指标优秀

本期盘点计算机领域超顺快刊,涵盖IEEE1区TOP、CCF推荐SCIE,期刊指标优秀,审稿周期短,质量稳定,有意向作者请看下文: IEEE旗下1区(TOP) 1 期刊简介 ✅出版社:IEEE ✅影…

《安富莱嵌入式周报》第336期:开源计算器,交流欧姆表,高性能开源BLDC控制器,Matlab2024a,操作系统漏洞排名,微软开源MS-DOS V4.0

周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 本周更新一期视频教程: BSP视频教程第30期:UDS ISO14229统一诊断服务CAN总线专题,常…

Golang | Leetcode Golang题解之第68题文本左右对齐

题目: 题解: // blank 返回长度为 n 的由空格组成的字符串 func blank(n int) string {return strings.Repeat(" ", n) }func fullJustify(words []string, maxWidth int) (ans []string) {right, n : 0, len(words)for {left : right // 当前…

简述 BIO 、NIO 模型

BIO : 同步阻塞I/O(Block IO) 服务器实现模式为每一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,此处可以通过线程池机制进行优化。 impo…

工业光源环形系列一高均匀条形光源特点

产品特点 ◆可以根据检测需求随意调整照射角度: ◆可以根据检测需求选择光源颜色: ◆多个条形光源可以自由组合: ◆使用贴片灯珠,均匀性更好。

VMP 简单源码分析(.net)

虚拟机 获取CPU的型号 实现了一个指令集解释器,每个操作码对应一个特定的处理函数,用于执行相应的指令操作。在执行字节码时,解释器会根据操作码查找并调用相应的处理函数来执行指令。 截获异常 先由虚拟机处理 处理不了再抛出异常 priva…

第五节 内联框架强化练习

建立左右常用框架结构说明 1、添加动态面板如下: 2、添加树元件 3、添加树节点(右键->添加子节点 右键->点添加节点) 4、添加交互页面 5、添加单击交互跳转事件到内联框架中(注意当前内联框架与当前面板要处在同级目录) 6、添加交互跳…

外贸尾货全新变现玩法,冷门暴利项目,单月轻松3W+

有些朋友可能不太了解什么是外贸尾货。实际上,当我们国内的厂家进行外贸出口业务时,生产的商品并不总是能够完全销售到国外。为了避免出现瑕疵品,厂家通常会生产超出订单数量的产品,以确保产品质量和满足出口需求。例如&#xff0…

uniapp实现下拉刷新效果-uniapp原生接口

onPullDownRefresh | uni-app官网 1、需要在 pages.json 里,找到的当前页面的pages节点,并在 style 选项中开启 enablePullDownRefresh 2、生命周期中添加onPullDownRefresh,下拉时获取数据 3、处理完数据后,停止下拉效果stopPul…

web前端学习笔记7-iconfont使用

7. iconfont的使用流程 字体图标使用较多的是阿里巴巴iconfont图标库,它是阿里巴巴体验团队推出的图标库和图标管理平台,提供了大量免费和可定制的矢量图标,以满足网页设计、平面设计、UI设计、应用程序开发和其他创意项目的需求。 官方网站:https://www.iconfont.cn/ 使用…

MYSQL RR隔离级别下无索引更新是否表锁?

最近在MYSQL菜鸟群有群友提问,说他看了某个公众号里面文章说 "MYSQL RR隔离级别下无索引更新会导致表锁! " 他表示疑惑,而且不仅是他,还有很多个她在不同的群里同样表示疑惑! 下面是群友的截图 是啊 MYSQL 以及进化到了8.3.0版本了,普遍都使用5.7和8.0版本.而且还…

深入理解 GMP:使用GNU多精度库进行高精度计算

目录标题 1. GMP库简介2. GMP库的核心功能3. 安装GMP库在Unix-like系统上:在macOS上:在Windows上: 4. GMP的优势和应用5. gmp-6.2.1特性和优化安装GMP 6.2.1使用GMP 6.2.1进行编程 6. 总结 高精度计算在科学研究、金融分析、加密算法以及任何…

Navicat for MySQL Mac:数据库管理与开发的理想工具

Navicat for MySQL Mac是一款功能强大的数据库管理与开发工具,专为Mac用户设计,旨在提供高效、便捷的数据库操作体验。 它支持创建、管理和维护MySQL和MariaDB数据库,通过直观的图形界面,用户可以轻松进行数据库连接、查询、编辑和…

提升开发者效率的必备工具

目录 前言1. Git2. Docker3. Postman4. Apipost5. PyCharm6. IntelliJ IDEA7. Everything8. Sublime Text9. 截图工具(Snipaste)10. Markdown 在线编辑器11. Xmind 思维导图12. 在线流程图制作工具(如 ProcessOn)结语 前言 在快节…