构造LR预测分析表:FIRST与FOLLOW集

1. FIRST 集

顾名思义,“第一个” + “集合”,也就是

FIRST(A) 表示 A 所能推导出的串的首终结符构成的集合

举个例:

有文法:A ——> aB
那么 FIRST(A) = {a},因为A ——> a...

那么如何求解呢?分三种情况:

  • 能推出空串:

    本文中我用 ‘’ 表示空串

    A ——> B | ''
    那么 '' 应该被加入到 FIRST(A) 中注:
    如果有A ——> BB ——> ''
    那么 A 也能推出空串,也就是说:
    '' 应该被加入到 FIRST(A) 中。
    
  • 产生式体的首字符为非终结符:

    A ——> B
    A 能推导出的串显然依赖于 B,因此
    FIRST(B) 应该被加入到 FIRST(A) 中
    或者说 FIRST(A) = FIRST(B)
    
  • 产生式体的首字符为终结符:

    A ——> aB
    无论 B 能推导出什么,显然 
    A 能推导出的串的首字符一定是 a;
    因此 a 应该被加入到 FIRST(A) 中。'这也可以换一种理解方式'FIRST(A) = FIRST(aB) =
    FIRST(a) = { a }
    

来个综合的例子:

(0) E ——> TG
(1) G ——> +TG | ''
(2) T ——> FU 
(3) U ——> *FU | ''
(4) F ——> (E) | id

对于(1)式,由于 E 的产生式体的首字符为非终结符 T,因此有 FIRST(E) = FIRST(T)
同理(2)式有 FIRST(T) = FIRST(F),也就是

FIRST(E) = FIRST(T) = FIRST(F)

那么看 F 的产生式(4)式,由于其产生式体首字符都是终结符,因此 FIRST(F) = { (, id },也就是说

FIRST(E) = FIRST(T) = FIRST(F) = { (, id }

现在还剩(1)、(3)式,两个产生式形式基本相同:产生式体首字符是终结符,能推导出空串,因此:

FIRST(G) = { +, ‘’ }
FIRST(U) = { *, ‘’ }

FIRST集计算完毕。


2. FOLLOW 集

**“跟在后面的” + “集合”,也就是

FOLLOW(A) 表示能出现在 A 的后面的第一个终结符集合

比如

A ——> Ba
对于 B 而言,在这个产生式中,
B 后面紧跟的第一个终结符为 a,
因此 a 应加入 FOLLOW(B)

求解方式分四种:

  • 应将 $ 加入开始符号的 FOLLOW 集

    $ 表示输入结束标记字符

    S ——> A
    A ——> a
    那么 $ 应加入 FOLLOW(S)
    
  • 后面紧跟着终结符

    S ——> Aa
    那么 a 应加入 FOLLOW(A)
    
  • 后面紧跟着非终结符

    S ——> AB
    由于 A 后面是 B,那么
    FIRST(B) 应加入 FOLLOW(A)
  • 产生式体末尾是非终结符

    S ——> ABC
    C ——> ''1. FOLLOW(S) 应加入到 FOLLOW(C) 中;由于 S ——> ABC,那么 S 等价于就是 ABC,
    显然 FOLLOW(S) 应加入到 FOLLOW(C); 
    但注意 FOLLOW(S) 不一定等于 FOLLOW(C)"一种理解方式":
    S 和 C 都是 S 文法定义的语言的某语法成分,
    但是 S 显然比 C 更大;
    如果 C 只出现在 S 的末尾,
    那么 FOLLOW(C) = FOLLOW(S);
    但如果 C 还出现在了 S 的中间,比如S ——> ACBC
    那么可能有 FOLLOW(S) < FOLLOW(B) 2. 如果 C 能推出空串 (或者 '' 属于 FIRST(C))
    那么 FOLLOW(S) 应加入 FOLLOW(B) 中。当 C ——> '' 时,那么 S 等价于 AB,因此 
    FOLLOW(B) 应加入到 FOLLOW(A) 中。3. 依次类推,直到出现第一个不能推出空串的非终结符
    

同样的例子:

(0) E ——> TG
(1) G ——> +TG | ''
(2) T ——> FU 
(3) U ——> *FU | ''
(4) F ——> (E) | id

FOLLOW集 的分析比 FIRST集 复杂得多,一不小心就容易漏掉个别情况。对此我建议采用以下方法进行求解:

  • 先将 $ 加入 FOLLOW(开始符号)
  • 分析产生式右部
  • 分析产生式整体
  • 从 (0) - (4) 依次分析,如果有 FOLLOW集 改变,执行依次更新操作
  • 将 $ 加入 FOLLOW(开始符号)

    FOLLOW(E) = { $ }

  • 分析产生式(0)的右部

    TG
    

    由于 T 后面紧跟的是非终结符 G,因此 FIRST(G) = { +, ‘’ } 应加入 FOLLOW(T)

    FOLLOW(T) = { + }

  • 分析产生式 (0) 的整体

    E ——> TG
    

    由于 E 相当于 TG,因此 FOLLOW(E) 应加入 FOLLOW(G)

    FOLLOW(G) = { $ }
    为了避免忘记,你自己在分析时做相应的标记,方便后续的分析

    此外,由于 G 能推出空串,因此 E 也相当于 T,所以 FOLLOW(E) 应加入 FOLLOW(T)

    FOLLOW(T) = { $, + }

  • 分析产生式(1)

    +TG | ''
    空串在 FOLLOW集 中没有分析的必要,因此只需要分析:
    +TG
    

    由于 “+TG” 与 “TG” 在形式上基本相同(G都紧跟在T后面,并且G都在末尾),因此(1)的分析结果与(0)的分析结果相同,跳过

    你可以自行验证

  • 分析(2)的右部

    FU 
    

    与(0)的右部分析类似:FIRST(U) = { *, ‘’ } 应加入 FOLLOW(F)

    FOLLOW(F) = { * }

  • 分析(2)的整体

    T ——> FU 
    

    与(0)的整体分析类似:FOLLOW(T) 应加入 FOLLOW(U),FOLLOW(T) 应加入 FOLLOW(F)

    FOLLOW(U) = { $, + }
    FOLLOW(F) = { $, +, * }

  • 分析(3)
    分析结果同(2),跳过

  • 分析(4)的右部

    (E) | id
    'id' 只有终结符,因此不参与分析 
    

    由于 E 后面为终结符 ),因此加入 FOLLOW(E)

    **FOLLOW(E) = { $, ) }

    这里你会发现个问题,我们第一个求解的是 FOLLOW(E),而 FOLLOW(E) 又会被加入到其他的 FOLLOW集 中(比如 FOLLOW(T) 等),那么这时候就需要更新对应的 FOLLOW集,这也是为什么要记录 “谁加入谁的原因”。 但是此时还没分析完,还有一步:分析(4)的整体,因此在分析完一遍后再进行更新操作

  • 分析(4)的整体

    F ——> (E) | id
    

    由于产生式右部的末尾都是终结符,因此没有分析的必要,跳过

  • 更新操作:

    • FOLLOW(E) 应加入 FOLLOW(G)
    • FOLLOW(E) 应加入 FOLLOW(T)
    • FOLLOW(T) 应加入 FOLLOW(U)
    • FOLLOW(T) 应加入 FOLLOW(F)

    因此最后的结果为

    FOLLOW(E) = { $, ) }
    FOLLOW(G) = { $, ) }
    FOLLOW(T) = { $, ), + }
    FOLLOW(U) = { $, ), + }
    FOLLOW(F) = { $, ), +, * }


3. 预测分析表

为了更好地理解此部分,你应该知道如何使用 预测分析表 进行 LR分析

先看两个例子:

现有文法:S ——> aA S ——> ''
那么 FIRST(S) = { a, '' }FOLLOW(S) = { $ }
1. 如果现在输入为 a...,需要使用 S 进行分析,那么应该使用 S ——> aA 进行语法分析2. 如果现在输入结束了,相当于输入为 $,需要使用 S 进行分析,由于 '' 属于 FIRST(S),并且 $ 属于 FOLLOW(S),因此因使用S ——> ''进行语法分析
'可以这么理解':因为使用 S 去进行分析,但是输入却是 FOLLOW(S),也就是说,没有遇到 S 能推导出的首终结符 (FIRST(S)),遇到的是 S 后面'紧跟'的首终结符(FOLLOW(S))那么 S 应该被'忽略',也就是S 应该换为空串,同时由于 S 可以推空串,因此使用 S ——> ''进行语法分析3. 如果输入为 b,既不属于 FIRST(S),也不属于 FOLLOW(S),因此语法分析错误
现有文法:S ——> aA
那么 FIRST(S) = { a }FOLLOW(S) = { $ }
1. 如果现在输入字符为 a ...并且需要使用 S 进行分析,那么你就知道应该用 S ——> aA去进行语法分析2. 如果输入字符为 $需要用 S 进行分析,虽然 $ 属于 FOLLOW(S),但是 S 不能推空串,因此语法分析错误3. 如果输入为 c ...需要使用 S 进行分析,由于 FIRST(S) 中没有 c因此语法分析错误

看了上面两个例子,相信的你对如何构造预测分析表有了基本的理解。下面使用之前的例子以及求出的 FIRST集 与 FOLLOW集 来构造 预测分析表。

基本思路:对文法的每一个产生式的右部 (非终结符),针对不同的终结符,使用对应的 FIRST、FOLLOW集 进行构造


  • 文法

    (0) E ——> TG
    (1) G ——> +TG | ''
    (2) T ——> FU 
    (3) U ——> *FU | ''
    (4) F ——> (E) | id
    
  • FIRST集

    FIRST(E) = FIRST(T) = FIRST(F) = { (, id }
    FIRST(G) = { +, ‘’ }
    FIRST(U) = { *, ‘’ }

  • FOLLOW集

    FOLLOW(E) = FOLLOW(G) = { $, ) }
    FOLLOW(T) = FOLLOW(U) = { $, ), + }
    FOLLOW(F) = { $, ), +, * }

规定:预测分析表横轴为终结符,纵轴为非终结符,中间的元素为指定的产生式(比如 表[S][$] = S —> ‘’,表示当 S 遇到输入字符 $ 时应使用 S —> ‘’ 来进行语法分析)

$
SS —> ‘’

算法:对于每个产生式:A —> B

  • 对于 FIRST(B) 的每个终结符 a,将 A —> B 加入 表[A][a]
  • 如果 ‘’ 属于 FIRST(B),对于 FOLLOW(B) 中的每个终结符 b,将 A —> ‘’ 加入 表[A][b]
  • 其他都是空,表示语法分析错误

  • 先找出所有的终结符

    { id, (, ), +, *, $ }

  • (0) E —> TG
    首先有

    FIRST(E) = { (, id }
    FOLLOW(E) = { $, ) }

    因为 E 与 TG 等效(E —> TG),那么 FIRST(E) = FIRST(TG) = { (, id },因此对于 FIRST(TG) 中的每一个终结符 a,将 E ——> TG 加入 表[E][a](即 表[ E ][ ( ]、表[ E ][ id ])
    故有

    id()+*$
    EE —> TGE —> TG

    由于不存在 E —> ‘’ ,所以不需要考虑 FOLLOW(E)

  • (1) G ——> +TG | ‘’
    首先有

    FIRST(G) = { +, ‘’ }
    FOLLOW(G) = { $, ) }

    同理由于 FIRST(+TG) = { + },因此将 G —> +TG 加入 表[ G ][ + ];
    现在 ‘’ 属于 FIRST(G)了,需要考虑 FOLLOW(G):
    **对于 FOLLOW(G) 中的每一个终结符 b,将 G —> ‘’ 加入 表[ G ] [ b ](也就是 表[ G ][ $ ]、表[ G ][ ) ])

    id()+*$
    GG —> ‘’G —> +TGG —> ‘’
  • 其余的自行分析
    最后结果为

    id()+*$
    EE —> TGE —> TG
    GG —> ‘’G —> +TGG —> ‘’
    TT—> FUT —> FU
    UU —> ‘’U —> ‘’U —> *FUU —> ‘’
    FF —> idF —> (E)

最后

本文一部分来源于课本,一部分为自己的理解,如有错误,欢迎指正。

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

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

相关文章

概率论与数理统计_下_科学出版社

contents 前言第5章 大数定律与中心极限定理独立同分布中心极限定理 第6章 数理统计的基本概念6.1 总体与样本6.2 经验分布与频率直方图6.3 统计量6.4 正态总体抽样分布定理6.4.1 卡方分布、t 分布、F 分布6.4.2 正态总体抽样分布基本定理 第7章 参数估计7.1 点估计7.1.1 矩估计…

Java列表转树形结构工具

不废话&#xff0c;直接上代码 一、工具函数 可以直接使用list2tree()实现列表转树形结构 package com.server.utils.tree;import org.springframework.beans.BeanUtils;import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import ja…

上海-灵曼科技(面经)

上海-灵曼科技 hr电话面 个人简介 个人信息的询问 是否知道芋道框架 技术面 算法题 14. 最长公共前缀&#xff08;写出来即可&#xff09; 聊一下Docker Docker核心概念总结Docker实战 聊一下AOP Spring AOP详解 聊一下JWT JWT 基础概念详解JWT 身份认证优缺点分析 Spri…

在数据库中,什么是主码、候选码、主属性、非主属性?

在数据库中&#xff0c;主码、候选码、主属性和非主属性是几个重要的概念&#xff0c;它们对于理解数据库的结构和数据的完整性至关重要。以下是对这些概念的详细解释&#xff1a; 一、主码&#xff08;Primary Key&#xff09; 定义&#xff1a;主码&#xff0c;也被称为主键…

使用React复刻ThreeJS官网示例——keyframes动画

最近在看three.js相关的东西&#xff0c;想着学习一下threejs给的examples。源码是用html结合js写的&#xff0c;恰好最近也在学习react&#xff0c;就用react框架学习一下。 本文参考的是threeJs给的第一个示例 three.js examples (threejs.org) 一、下载threeJS源码 通常我们…

【接口自动化测试】第四节.实现项目核心业务的单接口自动化测试

文章目录 前言一、登录单接口自动化测试 1.1 登录单接口文档信息 1.2 登录成功 1.3 登录失败&#xff08;用户名为空&#xff09;二、数据驱动的实现 2.1 json文件实现数据驱动三、课程添加单接口自动化测试 3.1 课程添加单接口文档信息 3.2 课程…

vue怎么动态设置类名和样式?

动态类名 对象语法 使用对象语法绑定动态类名&#xff1a; <template><div><button click"toggleClass">Toggle Class</button><div :class"{active: isActive, inactive: !isActive}">This divs class changes dynamica…

Promethuse-监控 Etcd

一、思路 Prometheus监控Etcd集群&#xff0c;是没有对应的exporter&#xff0c;而 由CoreOS公司开发的Operator&#xff0c;用来扩展 Kubernetes API&#xff0c;特定的应用程序控制器&#xff0c;它用来创建、配置和管理复杂的有状态应用&#xff0c;如数据库、缓存和监控系…

大数据面试题之数据库(2)

数据库中存储引擎MvlSAM与InnoDB的区别 Mylsam适用于什么场景? InnoDB和Mvlsam针对读写场景? MySQL Innodb实现了哪个隔离级别? InnoDB数据引擎的特点 InnoDB用什么索引 Hash索引缺点 数据库索引的类型&#xff0c;各有什么优缺点? MySQL的索引有哪些?索引…

软件性能测试有哪几种测试方法?专业性能测试报告出具

软件性能测试是指对软件系统在特定负载条件下的性能进行评估和验证的过程&#xff0c;目的是确保软件在正常使用的情况下能够满足用户的要求&#xff0c;并在稳定的性能水平下运行&#xff0c;在软件开发过程中起到了至关重要的作用&#xff0c;可以确保软件产品的质量和可靠性…

java.lang.UnsatisfiedLinkError: XXX: 无法打开共享对象文件: 没有那个文件或目录

一、问题描述 在服务器上运行Jar包&#xff0c;出现&#xff1a; Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.UnsatisfiedLinkError: /usr/local/jdk-11.0.23/lib…

【Android面试八股文】你是怎么保证Android设备的时间与服务器时间同步的?(使用NTP和TrueTime方案)

文章目录 一、网络时间协议(NTP)二、使用网络时间协议(NTP)2.1 使用系统提供的 NTP 服务器2.2 使用TrueTime2.2.1 引入TrueTime库2.2.2 初始化 TrueTime2.2.3 用法2.2.4 使用 TrueTime 获取时间2.2.4 自动更新时间2.2.5 注意事项二. 使用 HTTP 请求获取服务器时间2.1. 发送…

【unity实战】使用Unity实现动作游戏的攻击 连击 轻重攻击和打击感

最终效果 文章目录 最终效果前言素材下载&#xff1a;玩家移动跳跃控制攻击动画配置轻攻击重攻击 攻击时禁止移动和攻击移动补偿敌人击退和播放受击动画受击特效攻击停顿和屏幕震动局部顿帧&#xff08;补充&#xff09;参考源码完结 前言 注意本文为自己的学习记录笔记&#…

Android平台崩溃和 ANR 问题进行符号化解析、解析崩溃日志的内存地址

使用Android Logcat Stacktrace Utility | Android Logcat | 1.2.3 1.设置so库路径 2.打开Stacktrace Utility工具 3.在Original粘贴报错内存地址 4.点击Resolve Stacktraces,就会解析出内存地址 如果是红色,解析失败了,缺少原生so库,可以在第一步添加so库文件再次尝试…

nginx的重定向rewrite

nginx的重定向(rewrite) location匹配 location匹配的就是后面的URI location匹配的分类和优先级* 1、精确匹配 location/ 对字符串进行完全匹配&#xff0c;必须完全符合,后面内容要写全 2、正则匹配 ^~ 以 xxx为开头 ~区分大小写的匹配 ~*不区分大小写 !~ :区分大小写…

c语言回顾-内存操作函数

目录 前言 1.memcpy 函数 1.1函数介绍 1.2与strcpy的区别 1.3memcpy的模拟 2.memmove 函数 2.1函数介绍和使用 2.2函数的模拟 3.memset函数 3.1函数介绍 3.2函数的模拟 4.memcmp函数 4.1函数的使用 4.2函数的模拟 结束语 前言 在动态内存的章节中小编详细讲解了动…

代码随想录算法训练营第69天:图论7[1]

代码随想录算法训练营第69天&#xff1a;图论7 109. 冗余连接II 卡码网题目链接&#xff08;ACM模式&#xff09;(opens new window) 题目描述 有向树指满足以下条件的有向图。该树只有一个根节点&#xff0c;所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节…

C++多进程下使用文件锁互斥执行压缩进程

文章目录 0. 引言1. 解决方案2. 文件锁相比信号量的优势3. 示例代码compress_log.cpp4. 流程图5. 总结 0. 引言 在多进程环境中&#xff0c;每个进程都会生成自己的日志文件&#xff0c;并独立进行gzip压缩。尽管每个进程压缩的频率和时间可能不同&#xff0c;但由于系统的运行…

【Arduino】ESP8266开发环境配置(图文)

ESP8266与ESP32开发很类似&#xff0c;相当于是低配版本的ESP32&#xff0c;其同样具有无线网络连接能力&#xff0c;功能强大&#xff0c;而且价格比ESP32更具有优势。接下来我们就来设置一下ESP8266的开发环境。 使用Arduino开发平台软件&#xff0c;选择首选项进行设置。 h…

ASP.NET Core 6.0 使用 Action过滤器

Action过滤器 在ASP.NET Core中&#xff0c;Action过滤器用于在执行Action方法之前或之后执行逻辑。你可以创建自定义的Action过滤器来实现这一点。 继承 ActionFilterAttribute 类&#xff1a; [TypeFilter(typeof(CustomAllActionResultFilterAttribute))]public IActionRe…