编译原理笔记15:自下而上语法分析(2)LR 分析基础、LR 分析表、LR(0) 分析表

news2024/11/16 12:44:27

目录

    • LR分析
      • LR 分析的特点:
      • LR 分析表
        • 格局与动作
          • 改变格局的动作的含义:
          • 转移表含义:
      • LR(k) 文法
    • LR(0) 项目和 LR(0) 项目集规范族
      • LR(0) 分析表构造步骤:
        • 活前缀
        • 构造 LR(0) 分析器的关键:为 G 构造一个识别它的所有活前缀的 DFA
          • 状态转换图:
        • LR(0) 项目
          • 项目的意义
      • 由文法的 LR(0) 项目构造识别活前缀的 NFA

LR分析

LR 分析的特点:

  1. 采用最一般的无回溯移进-规约方法;
  2. 适用于几乎所有程序设计语言;
  3. 能及时发现错误;
  4. 分析表复杂,难以手工构造。

LR 分析表和驱动器是 LR 分析的核心。

以下讨论会基于下面的文法进行:

E → E-T | T		(1)(2)
T → T*F | F		(3)(4)
F → -F | id		(5)(6)

上面的文法也能够说明 LR 的适用范围更广:

  • 产生式可以左递归(预测分析不支持)
  • 同一个符号( - )可以既是一元的也是二元的(算符优先分析不支持)

LR 分析表

分析表分为两个部分,分别是【动作表(Action)】和【转移表(goto)】。

  • 两个表的行首是分析器状态编号,其对应着识别活前缀的 DFA 的状态;
  • 动作表的列首包含终结符和结束符;
  • 转移表的列首是文法中的非终结符,转移表中的数字是状态号。

在这里插入图片描述

  • 分析表中:
    • s : Shift
    • r : Reduce
    • acc : Accept
    • 空白 : 报错
  • 转移表中的数字对应行首的状态编号

格局与动作

  • 开始格局:(#0, ω#, 移进?)
    • 0: 对应分析表左上角的 0 ,其是识别活前缀的 DFA 的初态,任意语法分析都应该从这里开始;
    • ω:全部的输入序列;
    • 改变格局的动作:一般来说刚开始都是要移进的,除非是一开始就查表查到空白的位置然后出错
  • 结束格局:(#0E1, #, 接收)
    • #0E1:# 和 0 是配对的,E 和 1 是配对的。E 后面的 1 是通过查询分析表得到的(0 状态遇到 E ,通过查转移表可知对应的是状态编号 1);
    • 剩余输入为空,所以只有一个 # ;
    • 接收(acc):当前栈顶状态编号为 1 ,剩余输入序列中读到 # ,故根据查表可知这个 action[s, 1] 对应着动作表中的 acc ,也就是要执行 acc 这个改变格局的动作了。
    • 把整个文法的符号序列都规约为文法的开始符号 E ,就可以执行接收动作了。
  • 出错格局:(#δ, ω’# , 报错)
    • 根据栈顶的状态号和读写头指向的下一终结符(也就是剩余输入的第一个符号)读到了空白,则会到达出错格局。
改变格局的动作的含义:
  1. action[s, a] = si:根据当前栈顶状态 s 和当前读到的终结符 a 去查表的 s 行 a 列,确定改变格局的动作。s 意味着要进行移进(Shift),并在移进后转向到 i 状态(将 i 入栈,压在刚刚移进的符号的上面,与刚刚移进的符号配成一对);
  2. action[s, a] = rj:用第 j 个产生式的左部替换掉栈中的句柄(Reduce);
  3. action[s, a] = acc:接收
  4. action[s, a] = 空白:报错
转移表含义:
  • goto[s, A] = s’ :指示了非终结符的状态转移。s 表示的是次栈顶的状态,A 表示非终结符。

    当我们刚刚执行一个 action[s, a] = rj 时,栈顶的终结符会被弹出并替换为一个非终结符,此时该终结符位于栈顶,与该非终结符配对的状态需要根据栈顶终结符和次栈顶状态来通过查找 goto 表确定,找到后再将该状态值压入栈顶。

完整的规约动作由 action[s, a] = rj 和 goto[s, A] = s’ 两个步骤组成

LR 分析器的工作过程其实可以看作是栈的内容和剩余输入序列所组成的二元组的变化过程(改变格局的动作,也是基于这两个东西去查找分析表确定的),分析开始时的二元组为(#0, a0a1…an#),中间每一步的结果就可以表示为(#0X1s1X2s2…Xmsm, aiai+1…an#),栈中的全是文法符号和状态的组合构成的序列。该序列就是一个活前缀,该前缀由 a0~ai-1 规约而成。

  • 移进:当要对 ai 进行移进时,就要先将 ai 入栈,然后根据 sm 和 ai 去查分析表确定下一状态 s,确定后将 s 入栈,读写头指向下一个符号;
  • 规约:每次规约后,都要查找 goto 表,生成与栈顶配对的状态编号
    • 若 action[sm, ai] = 规约A→β,则说明栈顶一段的符号已经形成了 β 文法符号序列,可以进行规约了。
    • 规约需要先将整个 β 出栈(因符号和状态编号配对,故总计需要弹出 2|β| 次。全部弹出完成后暴露出状态 sm-|β|),A 入栈。
    • 此时通过次栈顶 sm-|β| 和栈顶 A 值查找 goto 表,将结果 goto[ sm-|β|, A] = s 入栈。这个 s 就是和 A 配对的状态。

LR 分析算法:

在这里插入图片描述

以对 id–id*id 为例,进行举例分析如下:

在这里插入图片描述

这时肯定就会有同学说:

“啊~~整挺好,这个 r 后面的值是规约用到的产生式编号,但我要怎么知道规约的时候该用哪个产生式对句柄进行规约呢?”

问得好,这是个很关键的问题,但这是为什么呢?这个到下一篇文章中会介绍。

LR(k) 文法

若文法 G 的分析表中不含多重定义的条目,则该文法为 LR(k) 文法,对应分析器即 LR(k) 分析器,对应语言即 LR(k) 语言。

其中,L:从左到右扫描,R:逆序是最右推导,k:为了确定下一动作会向前扫描的终结符个数——当确定栈顶是某个产生式的右部时,要再向后扫描输入序列 k 个符号,才能决定是否用该产生式及逆行规约。一般 k<=1 ,k=1 则简称为 LR

这个规约过程中向后扫描终结符,是为了解决【规约-规约冲突】的问题——当规约栈顶可用的产生式的选择超过一个时,可以通过扫描输入序列中后面的一个终结符来辅助决定使用哪个产生式进行规约

(解决这个问题,就要用到这几个右部对应的左部非终结符的 FOLLOW 集合进行判断)

LR分析器是一类分析器。根据分析表构造的不同,可以分为 LR(0)、SLR(1)、LALR(1)、LR(1) 分析器。这几个分析器的功能和构造难度都依次递增。一般不构造 k>1 的分析器(因为太复杂惹)

LR(0) 项目和 LR(0) 项目集规范族

LR(0) 分析表构造步骤:

  1. 构造可识别文法 G 中所有活前缀的 DFA;
  2. 根据 DFA 构造 LR(0) 分析表

该 DFA 的构造也有两种方式,一种是先构造 NFA 再基于此改为 DFA,另一种则是直接构造 DFA 。

本文剩余部分将先讲解如何构造 NFA

活前缀

文法 G 中,若在符号序列 α 右边增添零或多个终结符后,能够形成一个右句型且 α 不含该句型句柄之后的任何符号,则 α 为 G 的活前缀。

  • 在推导的过程中,若每次直接推导均替换句型中最右边的非终结符,则称为最右推导。由最右推导产生的句型被称为右句型;
  • 句柄,是一个句型的最左直接短语;
  • LR 分析的任何时刻,栈中的序列都是一个活前缀。如果输入的串语法结构正确,则把输入串中的剩余部分匹配到这个活前缀的后面就能够形成一个右句型;
  • 活前缀其实就是一个符号串,一个文法会有很多的活前缀;
  • 只要保证已经扫描过的输入序列都可以规约成一个活前缀,则到目前为止的分析就是正确的。

我觉得,这个活前缀,其实就是说【仍然灵活的前缀】。活前缀首先是一个前缀,然后它又是具有灵活性的(能够被塑造、向着不同的方向变化的)

举例说明活前缀:

在这里插入图片描述

构造 LR(0) 分析器的关键:为 G 构造一个识别它的所有活前缀的 DFA

步骤:先构造识别活前缀的 NFA,再通过确定化、最小化转为 DFA。

这个 NFA 就是用来识别所有语法 G 的活前缀的,其从初态到终态路径上的标记就是活前缀

状态转换图:

其实就是从产生式直接翻译过来的

在这里插入图片描述

产生式 E → E + T 说明了 E 其实无非就是【 E 推导出来的符号序列(被规约规约就变成了 E )】连接上一个加号,再连接上一个【 T 推导出来的符号序列】。

图中的 0 状态表示状态转换图的初态,后面接着的几个状态,要说明的是:在 0 状态开始,期望看到 E+T 推导出来的输入序列。当自动机跳到 0 状态后面连接的 1 状态,表示已经看到了 E 推导出来的输入序列,我们在 1 状态这里期望从 1 开始看到 +T 推导出来的输入序列(也就是读写头将要读的那些符号,应该可以经过规约后变成 +T )。

也就是说,识别活前缀就是要在 NFA 的状态中记录下【我看到了产生式的哪个部分、没有看到哪个部分】,即,识别活前缀的 NFA 状态中应该包含并体现产生式的信息(直接放在状态转移上即可)、已经看到和还没有看到的信息(接下来会提到,这个 ”进度“ 是通过在产生式右部序列中插入一个圆点来表现的,通过圆点的位置标记我们已经看到了的序列的进度)

LR(0) 项目

NFA 的每个状态都应该能够记录 **从初态到当前状态,已经看到了哪个产生式右部的多少内容。**并基于此来判断下一步该做什么,即:若分析器处于当前状态,则下一步要选择的动作是移进还是规约,如果是规约,那么规约时要用到哪个产生式。

产生式右部加入的点 【 . 】 在右部的位置表示一个 NFA 的状态(这样一个加圆点的产生式就叫做一个【LR(0)项目】)。我们接下来都会用一个 LR(0) 项目表示一个 NFA 状态

一个 LR(0) 项目(简称为项目) 是一个产生式,其右部的某个位置有一个点 “ . ” 。特殊的,对于 A → ε 仅有一个项目 A → .

这样的一个项目就代表 NFA 的一个状态,点前面的表示已经看到的产生式部分,后面的表示没有看到的产生式部分。

比如产生式 A → XYZ 共有四个项目: A → .XYZ 、A → X.YZ 、 A → XY.Z 、 A → XYZ.

项目的意义

项目说明分析过程中每个时刻已经看到了产生式的多大一部分,比如:

  • A → .XYZ 说明希望从后面的输入串中可以看到【能够从 XYZ 推出的符号串】,如果想要看到它则在此处需要移进
  • A → X.YZ 说明已经从输入串中看到了【可以从 X 推出的符号串】,希望进一步能看到【可以从 YZ 推出的符号串】,如果想要看到则此处需要继续移进
  • A → XYZ. 说明当前栈顶已经形成句柄了,此时可以进行规约。

比如下面这个例子:

在这里插入图片描述

我们接下来的学习路线大概是这样的:

文法 G => 文法 G 的 LR(0) 项目 => 构造识别 G 的所有活前缀的 NFA => 构造识别 G 的所有活前缀的 DFA => 为文法 G 构造 LR(0) 分析表

和词法分析中构造 NFA 的路数比较接近。

由文法的 LR(0) 项目构造识别活前缀的 NFA

  1. 每个 LR(0) 项目都是 NFA 的一个状态

  2. Σ:文法符号和 ε

  3. 初态:左边为文法开始符号且圆点在右部最左端的项目

  4. 终态:NFA 的所有状态(是的,每个状态都是终态,这个和词法分析 NFA 不同)

  5. 状态转移关系 move:若状态 i、j 出自同一个产生式,且状态 j 的圆点只落后状态 i 的圆点一个位置(比如状态 i 为 A → X.YZ,状态 j 为 A → XY.Z),则从 i 画一条标记为 Y 的边到状态 j;

    若状态 i 圆点后面是非终结符,比如 i:X → α.Aβ,则从 i 画 ε 边到所有 A → .γ 的状态

下面给出一个例子,文法 G:

S' → E
E → aA | bB
A → cA | d
B → cB | d

(该文法其实是【拓广文法】,原文法的开始符号就是 E ,我们在这里引入一个新的开始符号 S’ ,关于【拓广文法】后面会再提到,这里只需要知道:文法经过拓广后才能够构造 NFA 即可)

其项目:

1  S' → .E
2  S' → E.
3  E → .aA
4  E → a.A
5  E → aA.
6  A → .cA
7  A → c.A
8  A → cA.
9  A → .d
10 A → d.
11 E → .bB
12 E → b.B
13 E → bB.
14 B → .cB
15 B → c.B
16 B → cB.
17 B → .d
18 B → d.

方法:

在这里插入图片描述

所有的状态都是 NFA 的终态,红圈圈上的是【句柄识别态】,处在该状态时,圆点在整个产生式的最右端,语法分析处于这些状态的时候意味着已经【看到了产生式的完整右部】,自然也就可以进行规约了。

所有的状态都是 NFA 的终态,只要能够在 NFA 中任意找到一条路径,那这条路径的序列就是一个活前缀

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

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

相关文章

STM32G0+EMW3080+阿里云飞燕平台实现单片机WiFi智能联网功能(三)EMW3080完成配网,EMW3080连接到阿里云飞平台

项目描述&#xff1a;该系列记录了STM32G0EMW3080实现单片机智能联网功能项目的从零开始一步步的实现过程&#xff1b;硬件环境&#xff1a;单片机为STM32G030C8T6&#xff1b;物联网模块为EMW3080V2-P&#xff1b;网联网模块的开发板为MXKit开发套件&#xff0c;具体型号为XCH…

尝试解决一次跨域问题

上文出现跨域问题&#xff1b; 下面根据网上资料看一下设置360浏览器允许&#xff1b; 我的360浏览器安装目录如下&#xff1b; 根据资料&#xff1b;在360浏览器的启动命令后加上参数&#xff0c;加上之后如下&#xff0c; C:\Users\Administrator\AppData\Local\360ChromeX\C…

编译原理笔记14:自下而上语法分析(1)短语、句柄,规约,移进规约分析器的工作模式

目录 基本方法短语、句柄&#xff0c;规范规约&#xff0c;剪句柄短语、直接短语和句柄规范规约&#xff08;最左规约&#xff09;例&#xff1a; 移进-规约移进规约分析器的工作模式移进规约分析例&#xff1a; 基本方法 从句子 ω 开始&#xff0c;从左到右扫描 ω&#xff0…

实现自动驾驶的难点有哪些?

摘要&#xff1a; 这里主要介绍了传感器融合&#xff08;SF&#xff09;对目标方面的几类任务&#xff0c;除目标之外&#xff0c;传感器融合还能做很多其他对环境建模的任务&#xff0c;例如道路特征的描述、占用栅格地图以及可通行区域表示等等。 从车道保持到高阶自动驾驶功…

【从零开始学习JAVA | 第二篇】JAVA综合练习 (1)

目录 前言&#xff1a; 1.买飞机票 2.找质数&#xff1a; 3.开发验证码&#xff1a; 4.数组元素的复制&#xff1a; 5.评委打分&#xff1a; 6.数字加密&#xff1a; 前言&#xff1a; 本篇将起到总结的作用&#xff0c;利用具体案例来带我们复习JAVA的基础内容&#xff…

CountDownLatch源码

介绍 CountDownLatch是依赖AQS完成的线程阻塞和唤醒&#xff0c;利用AQS的共享锁完成锁的多线程获取和释放。 CountDownLatch 使用了共享锁模式。CountDownLatch 使用一个内部类 Sync来实现CountDownLatch的同步控制&#xff0c;而Sync是AQS的一个实现类&#xff0c;它使用AQ…

安装Node,环境配置详细教程及使用

安装Node&#xff0c;环境配置详细教程及使用 一.下载 https://nodejs.org/en/download 一般现在windows电脑都选64位&#xff0c;如果是其他系统或者需要更低的版本可以按照自己的需求进行下载安装&#xff0c;过程都大差不多 二.安装 node.js的安装过程一般都不需要勾选什…

2023/6/21总结

JS 解绑事件 对象.on事件 对象.on事件null 如果是 addEventListener方式必须使用 对象.removeEventListener () 匿名函数无法解绑 mouseover和mouseout会有冒泡效果mouseenter和mouseleave没有冒泡效果 事件委托&#xff1a; 事件委托是利用事件流的特征解决一…

第九章 ShuffleNetv1网络详解

系列文章目录 第一章 AlexNet网络详解 第二章 VGG网络详解 第三章 GoogLeNet网络详解 第四章 ResNet网络详解 第五章 ResNeXt网络详解 第六章 MobileNetv1网络详解 第七章 MobileNetv2网络详解 第八章 MobileNetv3网络详解 第九章 ShuffleNetv1网络详解 第十章…

【从零开始学习JAVA | 第五篇】This关键字详解

目录 前言&#xff1a; This关键字&#xff1a; 作用&#xff1a; 本质&#xff1a; 总结&#xff1a; 前言&#xff1a; 相信大家在进入JAVA面向对象编程篇章以后&#xff0c;多多少少都见过This关键字&#xff0c;而他的指向很多人总是傻傻搞不清楚&#xff0c;今天我们…

chatgpt赋能python:Python编程语言的词汇量有多少?

Python编程语言的词汇量有多少&#xff1f; Python编程语言是一种广泛使用的高级编程语言&#xff0c;被广泛用于数据科学、机器学习、人工智能、Web开发、游戏开发和其他许多领域。由于Python的简单易学以及丰富的库&#xff0c;越来越多的人开始使用Python编程语言进行编程。…

【MongoDB】四、MongoDB副本集的部署

【MongoDB】四、MongoDB副本集的部署 实验目的实验内容实验步骤实验小结 实验目的 能够通过部署副本集理解副本集机制&#xff0c;从而解决大数据项目中数据丢失的问题 实验内容 环境准备&#xff1a;根据表中的信息完成3台MongoDB服务器的部署&#xff08;XXX是姓名拼音首字母…

C语言指针初阶+进阶(看这一篇就够了)

目录 本章重点 1. 指针是什么 2. 指针和指针类型 3. 野指针 4. 指针运算 5. 指针和数组 6. 二级指针 7. 指针数组 8. 字符指针 9.数组指针 10. 指针数组 11数组传参和指针传参 12. 函数指针 13. 函数指针数组 14. 指向函数指针数组的指针 15. 回调函数 16 指针和数组面试题的解…

【从零开始学习JAVA | 第三篇】类与对象 和 封装

目录 前言&#xff1a; 类与对象&#xff1a; 封装&#xff1a; 总结&#xff1a; 前言&#xff1a; 从本篇开始&#xff0c;我们就要以面向对象编程思想来进行学习了&#xff0c;今天我们学习的内容是类与对象&#xff0c;这是JAVA中的重要知识&#xff0c;让我们一起来进…

docker 项目部署 后端/前端

1.前端部署 2.后端部署 问题一&#xff1a;build出问题 ERROR: failed to solve: failed to compute cache key: failed to calculate checksum of ref 668a7264-5d0b-45a6-b547-fa8fff014bda::g00ukurq2ipxuvrrz8rnpyskp: "/swagger2-demo-0.0.1-SNAPSHOT.jar": …

next.js博客搭建_react-markdown渲染内容(第三步)

文章目录 ⭐前言⭐引入react-markdown&#x1f496; 使用markdown渲染&#x1f496; 文章内容布局&#x1f496; react-syntax-highlighter代码高亮 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本期给大家分享next项目中使用react-markdown渲染内容。 该系列的往期…

考研C语言第八章

结构体定义&#xff0c;初始化&#xff0c;结构体数组 结构体对齐 这个东西看着像数据库里面属性的定义&#xff0c;也像java里面的类的定义 #include <stdio.h> #include <string.h> #include <stdlib.h>struct student{int num;char name[20];char sex;i…

chatgpt赋能python:Python桌面编程:探索图形用户界面

Python桌面编程&#xff1a;探索图形用户界面 Python是一种广受欢迎的高级编程语言&#xff0c;被广泛应用于数据科学、人工智能、Web开发和自动化。但是&#xff0c;随着越来越多的应用程序向图形用户界面&#xff08;GUI&#xff09;转移&#xff0c;Python的桌面编程能力也…

datagrip 连接 phoenix

jar替换完后尽量重启datagrip. 然后重新连接即可. 不重启貌似报错... 效果:

chatgpt赋能python:Python更新界面:让你的应用更美观、更易用

Python更新界面&#xff1a;让你的应用更美观、更易用 Python是一门强大的编程语言&#xff0c;广泛应用于软件开发、数据分析、机器学习等领域。其中&#xff0c;Python的GUI开发能力也非常出色。Python支持多种GUI库&#xff0c;如Tkinter、PyQt、wxPython等&#xff0c;可以…