【编译原理】理解BNF

BNF范式

下面来自百度百科:

巴科斯范式(BNF)所描述的语法是与上下文无关的。它具有语法简单,表示明确,便于语法分析和编译的特点。

源码解析使用的算法就是BNF或者其改进算法。

什么是上下文无关文法呢?

可以参考本专栏另一篇文章:【编译原理】什么是上下文无关文法?

为什么要学BNF?

因为BNF是描述上下文无关理论的一种具体方法,所以我们需要学习BNF。可以把它看做一门描述语法的编程语言,通过BNF可以实现此种文法的具体化、公式化、科学化,是实现代码解析的必要条件。

怎么学习BNF?
BNF是前端代码解析的难点之一,对于对数学不敏感的人来说,可能觉得公式很难理解。其实我们可以从计算机专业的角度来掌握它,把高大上的知识用浅显的语言表达出来。

例:四则运算的BNF,<>表示非叶子节点,|表示或者关系,:=就是等于的意思。

<expr> ::= <expr> + <term>| <expr> - <term>| <term><term> ::= <term> * <factor>| <term> / <factor>| <factor><factor> ::= ( <expr> )| Num

可见,BNF本质上就是树形分解,分解成一棵名为AST的抽象语法树。
关于AST,可以参考本专栏另一篇文章:什么是AST?


产生式

BNF中的表达式叫做产生式。产生式就是将语法的分解规则表达出来的等式。如

句子 = 主 + 谓 + 宾

将语法规则用产生式描述出来是为了便于计算,产生式可以看作是对语法的数学建模。

产生式的特点是 不断向下分解。这种特点和数据结构中的树是一样的。
通过类比,有:

  • 每个产生式就是一个子树,在写编译器时,每个子树对应一个解析函数。
  • 叶子节点叫做 终结符,非叶子节点叫做 非终结符

递归

产生式分成递归的和非递归的,如果一个产生式用自己来表示自己就会产生递归。
递归地调用自己的定义来定义自己,是无穷尽的。
比如:

  • GNU项目的命名GUN NOT UNIX;
  • A=A’b’('b’表示字符常量b)。


明明代码文本都是有限长的,为什么还要引入递归这种模型来解析,搞得很复杂?


实际的代码文本确实是有限长的。
但是,递归产生式只是定义了一个计算公式,通过这个计算公式,可以生成无穷种字符串。例如,四则运算可以用递归产生式来描述,它表示你可以写出无数个任意长的四则运算表达式,它是为了用一个公式来描述所有情况,是对实际规则的抽象与归纳。实际中,我们总不可能对每一种四则运算表达式都写一段解析代码,相反地,是所有四则运算表达式的解析都只用相同的解析程序。

递归又分为左递归和右递归,下面分别用图形的方式直观地进行描述。


左递归

左递归图解
图上图所示,产生式一直朝左侧延伸,无法结束,永远不会结束,所以叫左递归。
显然左递归无法分解出有限的叶子节点。尤其是,它永远无法得到第一个叶子节点。因为左侧在无限递归产生新的左叶子节点。 这个结论很重要,要牢记


右递归

右递归图解
与左递归相反,右递归有第一个叶子节点,没有最后一个叶子节点。


如何判断产生式有递归?

示意图如下:
产生式递归
子节点和父节点相同叫直接左递归,子节点和非父节点的祖先相同叫间接左递归


递归为什么可以消除?

因为某些左右递归可以相互转化,注意不是所有左右递归的都可以相互转化。具体是哪些呢,下面会说到,是包含终结符分支的递归表达式,终结符是转化的桥梁。


为什么只需要消除左递归?

我们需要明确第一个叶子节点的重要含义:第一个节点是语法的起点,所以第一个节点很重要,如果没有第一个叶子节点,那么就永远无法判断此语法是从哪个字符开始的。所以存在左递归的文法,是无法通过程序解析的,这样的程序无法实现

相反地,右递归有第一个叶子节点,没有最后一个叶子节点。有第一个叶子节点就可以判断语法从哪个字符开始,但是不知道语法在何时结束。

但是,在实际解析中,因为被解析的文本是有限长的,所以右递归一定会停止。除此之外,因为右递归一定有起始符号,所以在解析文本时,一旦遇到非起始符的字符串,也会停止解析。也就是说,右递归能自动保证语法的正确性,而且不会无穷递归。

综上,只有左递归需要消除。


如何消除左递归

目前我们连产生式都不会写,怎么消除?所以接下来先把消除左递归的问题放一放,先弄清楚怎么编写产生式。


怎么编写产生式

要明确一个重要规则:优先级高的产生式必然是需要先被计算的。 所以优先级越高(如乘除运算)的产生式,在BNF树中越靠近叶子节点。优先级越低(如加减运算)的产生式,在BNF树中越靠近根节点。所以优先级低的产生式一定会被分解成优先级高的产生式。

另外,产生式中不可分解的元素一定是在叶子节点,叶子节点没有子节点,无法分解。

综上:

  • BNF算法工作过程是,先向下分解到叶子节点,再从叶子节点沿着分解时的路径/调用堆栈向上反向计算。
  • 产生式优先级和在BNF树中的深度成正比。
  • 叶子节点有两种,一种是不可分解的元素,一种是优先级最高的元素。

以四则运算为例,双括号()的优先级最高,乘除运算*/的优先级其次,加减运算的优先级最低。从下面的四则运算的BNF可以看出,一个表达式是先分解成±运算,然后分解成乘除运算 */,最后再分解为Expr,这表明我们的理解是正确的。

<expr> ::= <expr> + <term>| <expr> - <term>| <term><term> ::= <term> * <factor>| <term> / <factor>| <factor><factor> ::= ( <expr> )| Num

根据以上思想,下面具体描述产生式的编写过程。

四则运算表达式编写

问:

已知Expr支持形如5+3 * (2+1)的运算表达式,即支持运算优先级,乘除高于加减,支持括号括起来的子表达式。求产生式。

答:

  • 5为Num,3为Num,(2 + 1)为含括号的子表达式(Expr)
  • Num、(Expr)优先级最高,所以做叶子节点,把他们统称为Factor,即
    Factor=Num | (Expr)
  • 次高优先级的运算为乘除运算*/,表达式称为Term,基本运算元素为上一步的Factor,有
    • 当乘除运算符个数为0时,Term = Factor
    • 当乘除运算符个数为1时,Term = Factor * Factor | Factor / Factor,将上式Term = Factor带入得 Term = Term * Factor | Term / Factor(左递归)。
    • 当乘除运算符个数大于1时,
      Term = Factor * Factor / Factor …* Factor = (Factor * Factor / Factor…) * Factor =Term * Factor

      Term = Factor * Factor / Factor…/ Factor = (Factor * Factor / Factor…) / Factor = Term / Factor。
      于是运算符个数 >= 1时,具有相同产生式。综合得出:
      Term = Term * Factor | Term / Factor | Factor
  • 最低优先级的运算为加减运算±,表达式称为Expr,基本运算元素为上一步的Term,有
    • 当加减运算符的个数为0时,Expr = Term
    • 当加减运算符的个数为1时,Expr = Term + Term | Term - Term,把上式代入得,
      Expr = Expr + Term | Expr - Term
    • 当加减运算符的个数大于1时,Expr = Term + Term - Term…+ Term = (Term + Term - Term…) + Term = Expr + Term,或 Expr = Term + Term - Term…- Term = (Term + Term - Term…) - Term = Expr - Term
      于是运算符个数 >= 1时,具有相同产生式。综合得出:
      Expr = Expr + Term | Expr - Term | Term

消除左递归

上面我们已经推导出四则运算的产生式了,但是产生式中存在之前说的左递归。具有左递归的产生式是无法用来解析代码的,所以需要消除左递归。
如何消除左递归呢?前面说过,用非递归的部分来表示左递归的部分就可以了。如果没有非递归部分,就无法消除左递归。就相当于一条路走不通,绕个弯子走另一条路。如果没有一条路可以走,就是真的无路可走。

消除直接左递归(省略号…表示无数次重复):

现有直接左递归:A = Aa | b,
即A=A…a ,右边的A用b替换掉,等价于: A = ba…a = ba…
消除了左递归,即消除A =…a,只剩下A = ba…。
此时A的产生式为:A = bAtail,Atail是除了开始符号b剩下的部分。
那剩下的部分是什么呢?Atail = a…(右递归),即Atail = aAtail。右递归是可以处理的。
综上直接左递归消除方法是:
A = Aa | b =>消除左递归 => A = bA’, A’=aA’

由此可见,终结符是转化的桥梁。

消除间接左递归:

思路就是使用变量代换将间接左递归写成直接左递归,然后消除直接左递归。具体可以参考这篇文章:语法分析之左递归消除一

编码实现

可以参考这篇文章:手把手教你做一个 C 语言编译器(4):递归下降


最后

需要做特别说明的是:以上只是在已知结论的情况下,对结论的一种理解。能够从无到有地将BNF创建并完善,绝不是一件容易的事情。



最近创建了一个公众号,定期写写文章,主要是Qt相关的。如果您觉得文章有用,可以关注一下。
在这里插入图片描述

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

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

相关文章

【GUI开发】图像处理类软件的浏览功能实现模型

图像处理软件包括但不限于&#xff1a; 图片浏览器&#xff0c;2D地图浏览器、图片编辑器等软件。 为了处理大分辨率图片&#xff0c;一般采用GDAL加载图像&#xff0c;GDAL可以动态加载图像的一部分&#xff0c;可以建立图像金字塔&#xff0c;优化加载速度。 一般的图像处理…

自动事务_JDBC进阶(二)事务编程

一、事务简介事务是用户定义的一个数据库操作序列&#xff0c;这些操作要么全做&#xff0c;要么全不做&#xff0c;是一个不可分割的工作单位。事务具有ACID特性&#xff1a;原子性&#xff08;Atomicity&#xff09; —— 原子性是指事务是一个不可分割的工作单位&#xff0c…

【精华】详解Qt中的内存管理机制

前言 内存管理&#xff0c;是对软件中内存资源的分配与释放进行有效管理的方法和理论。 众所周知&#xff0c;内存管理是软件开发的一个重要的内容。软件规模越大&#xff0c;内存管理可能出现的问题越多。如果像C语言一样手动地管理内存&#xff0c;一会给开发人员带来巨大的…

【转】2.1【MySQL】运行原理(一):查询sql的执行过程及MySQL架构分析

MySQL的发展历史和版本分支&#xff1a; 时间里程碑1996 年MySQL1.0 发布。它的历史可以追溯到 1979 年&#xff0c;作者 Monty 用 BASIC 设计的一个报表工具。1996 年 10 月3.11.1 发布。MySQL 没有 2.x 版本。2000 年ISAM 升级成 MyISAM 引擎。MySQL 开源。2003 年MySQL4.0 …

【转】2.2【MySQL】运行原理(二):InnoDB 内存结构、磁盘结构及update sql执行过程分析

前一篇讲完了查询流程&#xff0c;我们是不是再讲讲更新流程、插入流程和删除流程&#xff1f;在数据库里面&#xff0c;我们说的update操作其实包括了更新、插入和删除。如果大家有看过MyBatis的源码&#xff0c;应该知道Executor里面也只有doQuery()和doUpdate()的方法&#…

【转】2.3【MySQL】运行原理(三)InnoDB 逻辑存储结构

MySQL的存储结构分为5级&#xff1a;表空间、段、簇、页、行。 1.表空间 TableSpace 上篇【MySQL】从InnoDB的内存结构、磁盘结构到update sql执行过程分析 在磁盘结构部分就说过了&#xff0c;表空间可以看做是InnoDB 存储引擎逻辑结构的最高层&#xff0c;所有的数据都存放在…

【转】【MySQL】运行原理(四):重做日志(redo log),回滚日志(undo log),二进制日志(binlog)

MySQL中有六种日志文件&#xff0c;分别是&#xff1a;重做日志&#xff08;redo log&#xff09;、回滚日志&#xff08;undo log&#xff09;、二进制日志&#xff08;binlog&#xff09;、错误日志&#xff08;errorlog&#xff09;、慢查询日志&#xff08;slow query log&…

python 读中文乱码_python字符乱码的解决小结

引言无论学习什么程序语言&#xff0c;字符串这种数据类型总是着有非常重要。然而最近在学习python这门语言&#xff0c;想要显示中文&#xff0c;总是出现各种乱码。于是在网上查了很多资料&#xff0c;各说纷纭&#xff0c;我也尝试了许多的方法&#xff0c;有时候可以正常显…

ntnub原理怎么看_老电工由浅入深带你入门学PLC的工作原理和梯形图的编程规则...

PLC编程怎么学&#xff1f;很难吗&#xff1f;工控小白怎么入门学习PLC&#xff1f;需要为学习PLC编程做哪些准备&#xff1f;学习PLC编程时&#xff0c;前期一定要积累相关的理论知识&#xff0c;有了一定的基础&#xff0c;基础打扎实之后就是多练习了。今天推荐的重点&#…

【转】国密加密算法SM系列的C#实现方法

http://www.zhimengzhe.com/bianchengjiaocheng/Javabiancheng/22144.html 在网上搜索SM实现方法&#xff0c;按照上面网站提供方法总是出错&#xff0c;经过调试终于修改好了&#xff0c;给大家以参考&#xff0c;不走弯路了 base64修改&#xff0c;这个看需求&#xff0c;如…

1盒子刷webpad_拉宽带送的盒子也有春天:一招解放各种束缚限制

序言&#xff1a;故事要从一年多前开始说起了&#xff0c;话说...装了宽带之后&#xff0c;移动送了个电视盒子&#xff0c;一次未使用&#xff0c;角落吃灰一年多了&#xff0c;最近有个大胆的想法&#xff01;其实是被逼迫无奈&#xff0c;孩子总是喜欢拿我手机刷抖音&#x…

php udp发送和接收_63、php利用原生socket创建udp服务

1、案例函数汇总2、案例通过socket创建udp服务&#xff0c;获取对端的ip和port信息。并进行打印2.1、udp服务源码/*** Copyright(C) Iamasb* project : 3、workerman相关知识点* explain : 原生socket创建创建udp服务* filename : socket_udp.php* author : Iamasb*/// 创建udp…

【转】C#实现SM3国密加密

C#实现SM3国密加密 本文主要讲解“国密加密算法”SM系列之SM3的C#实现方法&#xff0c;加密规则请详阅国密局发布的文档。 首先需第三方Nuget包&#xff1a;Portable.BouncyCastle &#xff08;源码来自http://www.bouncycastle.org/csharp/&#xff09; 1.1常规处理 /// &l…

mq集群要建传输队列吗_面试官:消息队列这些我必问!

作者&#xff1a;mousycodersegmentfault.com/a/1190000021054802消息队列连环炮项目里怎么样使用 MQ 的&#xff1f;为什么要使用消息队列&#xff1f;消息队列有什么优点和缺点&#xff1f;kafka,activemq,rabbitmq,rocketmq 都有什么去呗&#xff1f;如何保证消息队列高可用…

【转】国密算法sm4 CBC模式加解密

一.什么是CBC模式? CBC模式的全称是Cipher Block Chaining模式&#xff08;密文分组链接模式&#xff09;&#xff0c;之所以叫这个名字&#xff0c;是因为密文分组像链条一样相互连接在一起。 在CBC模式中&#xff0c;首先将明文分组与前一个密文分组进行异或运算&#xff0c…

【转】对称加密和分组加密中的四种模式(ECB、CBC、CFB、OFB)

版权声明&#xff1a;本文为作者原创&#xff0c;如需转载&#xff0c;请注明出处https://blog.csdn.net/weixin_42940826注&#xff1a;以下图片来自于《图解密码学》&#xff0c;这本书讲的更全面细致&#xff0c;建议阅读&#xff0c;在我资源库中有此书&#xff0c;还有使用…

中发生数据丢失_如何防止Redis脑裂导致数据丢失?

所谓的脑裂&#xff0c;就是指在主从集群中&#xff0c;同时有两个主节点&#xff0c;它们都能接收写请求。而脑裂最直接的影响&#xff0c;就是客户端不知道应该往哪个主节点写入数据&#xff0c;结果就是不同的客户端会往不同的主节点上写入数据。而且&#xff0c;严重的话&a…

【转】TransactionScope事务简介

在.NET 1.0/1.1 版本我们使用SqlTransaction.处理事务 string connString ConfigurationManager.ConnectionStrings["db"].ConnectionString; using (var conn new SqlConnection(connString)) { conn.Open(); using (IDbTransaction tran conn.BeginTransact…

网络通道数2的倍数_限流笔记-通道限流(二)

在工作中的时候&#xff0c;由于我负责的一个系统需要调用很多的第3方的系统&#xff0c;可是呢&#xff0c;这些个第3方的系统的性能完全不一致&#xff0c;有的好有的坏&#xff0c;还成本都不一样&#xff0c;当然了平时把&#xff0c;直接使用成本低的就行了&#xff0c;但…

mysql题目_MySQL练习题

创建下列表并创建相关约束问题1&#xff1a;查询出成绩表&#xff0c;而且student_id 后面要有对应的学生名&#xff0c;course_id 后面要有对应的课程名.1 SELECT2 score.sid,3 score.student_id,4 student.sname,5 score.course_id,6 course.cname,7 score.number8 FROM scor…