手写一个PrattParser基本运算解析器2: PrattParser概述

news2025/3/15 4:13:33

点击查看 基于Swift的PrattParser项目


解析器概述

由于编译原理内容太过于枯燥, 所以当时我就在想能不能写一个编译过程, 这时候就在B站上看到了熊爷的技术去魅篇 - PrattParser解析器.

解析器主要的工作是把一系列的标记转换为树的表示形式. 例如线性代码 a = 1 + 1 * 3 的转换过程如下所示.

想要实现如下的转换过程, 我们一般需要实现解析器, 而想要实现解析器一般有两种方式.

  • 使用 Parser generator (所有验证大部分来自CharGPT, 了解就好)

  • 手工实现解析器

使用 Parser generator 进行上面的转换过程, 用一种DSL (例如 BNF ) 来描述你的语法, 这时候, a = 1 + 1 * 3 会变成什么样呢? 如下所示.

statement   ::= expression_statement | assignment_statement
expression_statement   ::= expression
assignment_statement  ::= identifier '=' expression
expression   ::= additive_expression
additive_expression   ::= multiplicative_expression ( ('+' | '-') multiplicative_expression )*
multiplicative_expression   ::= primary_expression ( ('*' | '/') primary_expression )*
primary_expression   ::= number | identifier | '(' expression ')'
number   ::= digit+
digit   ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
identifier   ::= letter (letter | digit)*
letter   ::= 'a' | 'b' | 'c' | ... | 'y' | 'z' | 'A' | 'B' | 'C' | ... | 'Y' | 'Z'

看到这样的转换结果, 谁脑子不是嗡嗡的? 这还是最简单的赋值过程.

所以 BNF 虽然可以完成转换过程, 它的缺点也是很明显.

  • 歧义性

    BNF 描述的语法规则可能存在歧义。如果有多个不同的推导路径可以生成同一个句子,则称为文法歧义。在设计语言或编译器时,歧义性可能导致解析困难或语言不一致。

  • 缺乏语义信息

    BNF 只关注语法结构,而忽略了语义信息。它无法直接描述语义规则和操作。语义的处理往往需要通过其他方式来表示和处理。

  • 粒度较粗

    BNF 是一种上下文无关语法表示方法,它主要关注语法结构的形式化描述,而对于上下文相关的语法规则,如特定的运算顺序、关键字的语义等,BNF 的表达能力较弱。

  • 可读性较差

    BNF 使用了一些特殊的符号和记号,对于非专业人士来说,对于语法规则的理解可能具有一定的难度。此外,复杂的语法规则可能导致 BNF 的表示变得冗长和难以阅读。

  • 不适合描述部分语言特性

    BNF 对于一些编程语言中的特定特性,如正则表达式、回溯和优先级等的描述相对困难。对于一些复杂的语法规则,BNF 可能无法直接表示或需要引入扩展规则。

但是, 瑕不掩瑜, 尽管 BNF 存在这些缺点,但它仍然是一种被广泛应用于语言设计、编译器构建和形式语言的描述方法,它在建立语法基础和形式化验证方面仍然具有重要的作用。

对于手工实现解析器, 这里主要说的就是PrattParser解析器, 就是接下来, 我们一起看一下PrattParser解析器的相关内容.


PrattParser解析器概述

PrattParser解析器主要是对编译前端工作: 词法分析语法分析语义分析中间代码生成 这几个部分的一个实践.

每一个过程, 我们会在后面的章节中进行探讨, 我们先聊一下 PrattParser解析器 生成 AST语法树 的理论支撑.

首先, 当一段代码通过 词法分析器 之后成为一个个Token词法单元, 这个过程看似简单, 实际上也是很简单, 枚举所有的符号, 移除所有的空格, 换行等, 就完成了这一过程. 具体如下所示.

PrattParser解析器 中, 认为所有的词法单元Token只有两种类型 前序单元中序单元.

在基础运算中, 我们可以认为 + - * / 是中序单元, 因为这种符号关联左边符号Token, 比如加法, 必然要有 a + b.

1 所有的数字都是前序单元, 同时还有一个特殊的前序单元 那就是 - 号, 也就是 - 号既可以是中序单元也可以是前序单元. +9 这种形式暂不讨论…

那么, PrattParser解析器 如何解决运算符的优先级的呢? 比如 -1 + 14 * 3 - 6 正确的应该是 (-1 + (14 * 3))- 6 ✅, 而不是 (-1 + 14) * 3)- 6 ❎, 也就是生成的AST语法树的过程如下图所示.

实际上, PrattParser解析器 解决运算符的优先级问题, 就是预设了每一种算数运算符的优先级. 优先级的定义也是根据数学运算中 先乘除后加减 的原则来的. 具体如下表所示.

符号单元优先级
NUM0
+1
-1
*2
/2

哪怕有这个表, 我们也是云里雾里的, 我们接下来就用最简单的示例 1 + 2 * 3 来演示 PrattParser解析器 是如何运用优先级生成 AST语法树的.


PrattParser解析器与AST语法树

对于 1 + 2 * 3 这个数学表达式来说. 我们需要通过 递归 构建整个AST语法树.

首先, 然后是通过 词法分析器 我们首先拆分成如下的 词法单元Token : 1 + 2 * 3 EOF

然后, 我们读取第一个词法单元 1. 并形成前序节点. 我们初始化当前表达式优先级为0(NUM).

然后我们读取下一个词法单元 +, 这时候, 我们发现 + 的优先级(1)要高于左边的优先级, 所以, 1+ 号吸引, 成为其左节点. 这时候整体优先级变成 1 .

+ 这个中序节点有了左节点, 那就需要去寻找它的右节点, 这时候找到了第二个数字词法单元 2, 本来到这里就结束了. 但是紧接着发现 2 后面跟的是 乘法词法单元 *, 如下图所示.

乘法词法单元 *优先级为 2, 比当前 + 号的优先级高, 所以它就会对数字词法单元 2更有吸引力, 所以 2 成为 * 的左节点. 而加号+ 的右节点的寻找过程只能继续等待了.

这时候, 加号+ 的右节点的寻找过程已经在暂停的路上(PS: 其实上是递归过程的递过程, 还没有进入归过程呢.), 我们要先解决乘法* 的右节点问题. 然后, 我们继续按照上面的思路寻找右节点, 发现了数字词法单元3, 再往后一个单元就是结束词法单元EOF了.

不用说了, 碰到结束词法单元EOF, 我们就直接构建根节点为乘法*的AST语法树.

然后, 对于 加号节点+ 的右节点寻找过程来说, 也进行了递归的归过程了, 这时候整个乘法节点AST语法树就成了加号节点+ 的右节点了.

至此, 对于表达式 1 + 2 * 3 的AST语法树构建过程就完成了.


PrattParser节点升级

这时候, 我们再次拓展一下, 如果 1 + 2 * 3 + 5 会怎么样呢? 我们接着上一模块继续说, 基于第一个加法+, 整个PrattParser 仍然会往后寻找. 这时候会找到第二个加号+, 要考虑现在的优先级是最开始的优先级0, 所以加号的优先级高于0, 会继续执行.

第一个加号 + 的AST语法树成为第二个加号 + 的左节点.

已发炮制, 继续查找第二个加号 + 的右节点.

最终, 1 + 2 * 3 + 5 的整体AST语法树就构建好了.


PrattParser的AST语法树优先级总结

结论只有这样的两点.

  • 如果当前节点比先前节点的优先级高, 那么它会抢夺先前节点的右节点, 并把自己成为先前节点的右节点.

  • 如果当前节点不比先前节点的优先级高, 那么它把先前节点当成自己的左节点.


总结

这一篇博客主要是说了以下 PrattParser解析器 的工作原理, 优先级等方面, 下一篇博客, 我会就着代码来逐步分析 PrattParser解析器 的实现过程. 如果有任何问题, 欢迎留言. 欢迎持续关注骚栋.


点击查看 基于Swift的PrattParser项目


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

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

相关文章

React 路由学习总结 react-router-dom6+react-router-dom5

开题 单页面应用和多页面应用 SPA:单页面应用程序,整个应用中只有一个页面(index.html) MPA:多页面应用程序,整个应用中有很多页面(*.html) react路由 现在的前端应用大多都是SPA单页面应用程序,也就是一个HTML页面的…

Compose Material3 新增垂直分隔符(VerticalDivider)解析与疑惑

前言 谷歌在 7 月 28 日发布了 Compose Material3 1.2.0-alpha04 版本,在该版本新增(修改)了两个组件,垂直分隔符和分段按钮: Experimental Segmented Button API. Dividers now have a parameter to control orienta…

TwinCAT3 ADS与C++通讯

文章目录 一 ADS简介1.1 ADS通讯定义1.2 ADS通讯实现 二 上位机程序编写(Visual Studio 2019)2.1 启动VS2019,新建MFC项目2.2 添加ADS通讯链接库2.3 在程序中引入头文件 一 ADS简介 1.1 ADS通讯定义 ADS(Advanced Design System&#xff09…

从0开始编写BP,自适应学习率的BP神经网络,不使用MATLAB工具箱,纯手写matlab代码,以BP分类为例...

与上篇文章不同,仔细读了上篇文章的小伙伴应该知道,BP神经网络是有一个学习率的,而这个学习率很大程度上决定着神经网络的效果。这里采用自适应学习率,实现纯手写BP神经网络。 编程时,激活函数选择Sigmoid函数&#xf…

【计算机毕设选题推荐】网络在线考试系统SpringBoot+SSM+Vue

前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 项目名 网络在线考试系统 技术栈 SpringBootSSMVueMySQLMaven 文章目录 一、网络在线考试系统-环境…

SLAM从入门到精通(dwa算法)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 要说搜路算法,这个大家都比较好理解。毕竟从一个地点走到另外一个地点,这个都是直觉上可以感受到的事情。但是这条道路上机…

行情分析——加密货币市场大盘走势(10.17)

大饼昨日在受到假消息美国证券交易委员会(SEC)通过大饼ETF后迅速上涨,一度上涨到30000,而很快回落到28000附近。从MACD日线来看,现在完全进入多头趋势,同时大饼再次进入蓝色上涨趋势线,目前按照…

李宏毅机器学习笔记-半监督学习

半监督学习,一般应用于少量带标签的数据(数量R)和大量未带标签数据的场景(数量U),一般来说,U>>R。 半监督学习一般可以分为2种情况,一种是transductive learning,…

使用秋云 ucharts echarts 高性能跨全端图表组件 流程

1. 2. // 引入 import qiunDataCharts from ../../uni_modules/qiun-data-charts/components/qiun-data-charts/qiun-data-charts.vue // 注册 components:{qiunDataCharts },// 页面中使用 <qiun-data-charts type"line" :opts"opts" :chartData"…

04 MIT线性代数-矩阵的LU分解 Factorization into A=LU

目的: 从矩阵的角度理解高斯消元法, 完成LU分解得到ALU 1.矩阵乘积的逆矩阵 Inverse of a product 2.矩阵乘积的转置 Transpose of a product 3.转置矩阵的逆矩阵 Inverse of a transpose 4.矩阵的LU分解 U为上三角阵(Upper triangular matrix), L为下三角阵(Lower triangular…

pycharm远程连接miniconda完整过程,以及遇到的问题解决

问题1&#xff1a;no-zero exit code(126) env: ‘/home/user2/miniconda3/envs/ihan/bin/python3’: Too many levels of symbolic links Python interpreter process exited with a non-zero exit code 126 因为选择的新建导致太多软连接&#xff0c;先在服务器上建好虚拟环…

【微信小程序】数字化会议OA系统之首页搭建(附源码)

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《微信小程序开发实战》。&#x1f3af;&#x1f3a…

eNSP笔记①

关闭范文信息&#xff1a;undo terminal monitor VRP三种试图 "<>"表示用户视图&#xff0c;系统默认的状态。主要用于查询设备基础信息或者状态等&#xff0c;也可以执行保存(save)。 “[]” 表示系统视图&#xff0c;在用户视图下输入system-view进入状态…

Kafka序列化反序列化解析、kafka schema

Kafka序列化反序列化解析、kafka schema。 kafka有自己的rpc协议,即nio bytebuf中的数据格式,详见之前的kafka相关介绍的文章。这里我们来看一下大家常用,有时又疑惑的序列化反序列化,对应rpc协议中的records,kafka叫Serdes,实际上也是字面上的意思serialize and deseri…

Qt扫盲-QDataStream 序列化和反序列化理论

QDataStream 序列化和反序列化理论 一、概述二、QDataStream 概述三、版本控制四、读取和写入原始二进制数据五、读写Qt集合类六、读写其他Qt类七、使用读事务八、Qt支持的序列化类型 一、概述 序列化&#xff1a; 指的是将一个内存对象转化成一串字节数据&#xff08;存储在一…

FSDP(Fully Sharded Data Parallel)

完全分片数据并行 (FSDP) &#xff0c;它将AI 模型的参数分片到数据并行工作器上&#xff0c;并且可以选择将部分训练计算卸载到 CPU。顾名思义&#xff0c;FSDP 是一种数据并行训练算法。尽管参数被分片到不同的GPU&#xff0c;但每个微批次数据的计算仍然是每个 GPU Worker 本…

C++ - 一些特殊类的设计

前言 我们在日常写项目的过程当中&#xff0c;肯定会遇到各种各样的需求&#xff0c;那么也就要求我们要写各种各样的类。本篇博客当中&#xff0c;就一些常用的特殊类进行介绍和实现。 不能被拷贝的类 关于实例化类拷贝&#xff08;对象的拷贝&#xff09;一般就是两个场景&…

6.DApp-用Web3实现前端与智能合约的交互

题记 用Web3实现前端与智能合约的交互&#xff0c;以下是操作流程和代码。 准备ganache环境 文章地址&#xff1a;4.DApp-MetaMask怎么连接本地Ganache-CSDN博客 准备智能合约 文章地址&#xff1a; 2.DApp-编写和运行solidity智能合约-CSDN博客 编写index.html文件 <!…

简单测试一下 展锐的 UDX710 性能

最近在接触 联通5G CPE VN007 &#xff0c;发现使用的是 展锐的Unisoc UDX710 CPU&#xff0c;正好简单的测试一下这颗CPU CPU信息 UDX710 是一颗 双核 ARM Cortex-A55 处理器&#xff0c;主频高达 1.35GHz processor : 0 BogoMIPS : 52.00 Features : fp…

ARM资源记录《AI嵌入式系统:算法优化与实现》第八章(暂时用不到)

1.CMSIS的代码 书里给的5&#xff0c;https://github.com/ARM-software/CMSIS_5 现在有6了&#xff0c;https://github.com/ARM-software/CMSIS_6 这是官网的书&#xff0c;介绍cmsis函数的https://arm-software.github.io/CMSIS_5/Core/html/index.html 2.CMSIS介绍 Cort…