适合自我沉淀的人生感悟句子 沉淀自己的经典句子

国学综合

适合自我沉淀的人生感悟句子 沉淀自己的经典句子

妖皇太子围观:℉更新时间:05-22 11:04

你现在阅读的是一篇关于适合自我沉淀的人生感悟句子的文章,里面有丰富多彩的内容,还有给你准备沉淀自己的经典句子和适合自我沉淀的人生感悟句子的精彩内容哦。

适合自我沉淀的人生感悟句子 沉淀自己的经典句子

适合自我沉淀的人生感悟句子 沉淀自己的经典句子

看过这样一个故事:

一个书生做事向来浮躁,经常为未达成之小事,心力交瘁,痛苦不堪。

有一天,他找到一个智者诉说自己的痛苦,智者听后把他带到一间破败陈旧的小屋里,屋内的桌上放着一杯水。

智者对书生说:“你看这杯水放在这里很久,每天都有灰尘落入杯中,但这水依然清澈透亮,你可知原因?”

书生仔细观察了一番说:“因为灰尘,都沉淀在杯底。”

智者点点头,说道:“人生就如同这杯水,懂得沉淀,才能让水变得澄澈。”

书生听后豁然开朗,懂得了自己的痛苦不过是容忍自扰,心不澄净所致。

图片来自网络侵删

漫漫人生路,总有起起伏伏,没有人可以一帆风顺,当我们的能力不足以支撑我们的野心的时候,要学会沉淀自己。

人生,需要沉淀

人生是需要沉淀的,需要有足够的时间去反思。如果人生走得太匆忙,没有时间去思考人生的意义,你的一生只能像是昙花一现,在世界上很难留下痕迹。当你能停下自己的脚步,会发现生活如此不同。当你感到生活很艰辛时,不妨静下心来,慢慢提升自己,直至到达梦想的彼岸。

婚姻,需要沉淀

有人说婚姻是爱情的坟墓,是爱情的围城,我认为婚姻是爱情的归宿。爱情使人生丰富,婚姻使爱情升华。没有经历过爱情的人生是不完整的,没有经历过婚姻的爱情是不深刻的。

好的婚姻需要时间的沉淀,两个人之间彼此包容,接受对方的不完美,夫妻双方朝着共同的目标前行,爱情之船才能稳稳地向前航行。

人脉,需要沉淀

在这个世界上没有孤立存在的人,每个人都处在错综复杂的关系中。人脉是事业成功的保障。松下幸之助说:“一个人的成功就是他人际关系的成功。”没有人脉,只能是一分耕耘,一分收获,但若加上人脉,我们将是一分耕耘,数倍收获。

“穷在闹市无人问,富在深山有远亲”。当你能力不足时,不要谈人脉,人脉是一种等价的交换,对方在你身上任何价值都获得不了,这种关系不会长久,所以维护人脉的同时也要不断地提升自己。

正如梭罗所说:“我要活得深刻,把生命的精华吸个干净;我要坚毅地生活,摆脱所有没有生命力的人和事……这样,当我死时,才会发现:我没有白活过。"

我们不能决定生命的长度,但是我们可以决定生命的宽度和深度,静下心来沉淀自己,老天不会辜负一个有能力的人,时间可以证明一切。

英语句子分析器(当我们输入一条SQL查询语句时)

作者 | 丁奇

出处 | 极客时间《MySQL 实战 45 讲》专栏

我们经常说,看一个事儿千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样能够帮助你从高维度理解问题。同样,对于 MySQL 的学习也是这样。平时我们使用数据库,看到的通常都是一个整体。比如,你有个最简单的表,表里只有一个 ID 字段,在执行下面这个查询语句时:

mysql> select * from T where ID=10;

我们看到的只是输入一条语句,返回一个结果,却不知道这条语句在 MySQL 内部的执行过程。

所以今天我想和你一起把 MySQL 拆解一下,看看里面都有哪些“零件”,希望借由这个拆解过程,让你对 MySQL 有更深入的理解。这样当我们碰到 MySQL 的一些异常或者问题时,就能够直戳本质,更为快速地定位并解决问题。

下面我给出的是 MySQL 的基本架构示意图,从中你可以清楚地看到 SQL 语句在 MySQL 的各个功能模块中的执行过程。

MySQL 的逻辑架构图

大体来说,MySQL 可以分为 Server 层和存储引擎层两部分。

Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。

而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

也就是说,你执行 create table 建表的时候,如果不指定引擎类型,默认使用的就是 InnoDB。不过,你也可以通过指定存储引擎的类型来选择别的引擎,比如在 create table 语句中使用 engine=memory, 来指定使用内存引擎创建表。不同存储引擎的表数据存取方式不同,支持的功能也不同,在后面的文章中,我们会讨论到引擎的选择。

从图中不难看出,不同的存储引擎共用一个 Server 层,也就是从连接器到执行器的部分。你可以先对每个组件的名字有个印象,接下来我会结合开头提到的那条 SQL 语句,带你走一遍整个执行流程,依次看下每个组件的作用。

连接器

第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:

mysql -h$ip -P$port -u$user -p

输完命令之后,你就需要在交互对话里面输入密码。虽然密码也可以直接跟在 -p 后面写在命令行中,但这样可能会导致你的密码泄露。如果你连的是生产服务器,强烈建议你不要这么做。

连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。

这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。

连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。

客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。

如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。

数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。

建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。

但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。

怎么解决这个问题呢?你可以考虑以下两种方案。

  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  2. 如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

查询缓存

连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。

MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。

如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。

但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利。

查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

好在 MySQL 也提供了这种“按需使用”的方式。你可以将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:

mysql> select SQL_CACHE * from T where ID=10;

需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

分析器

如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。

分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。

MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。

做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。

如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。

mysql> elect * from t where ID=1;ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1

一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。

优化器

经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 join:

mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;

这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。

优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等等,没关系,我会在后面的文章中单独展开说明优化器的内容。

执行器

MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。

开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示。

mysql> select * from T where ID=10;ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。

比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:

  1. 调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

至此,这个语句就执行完成了。

对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。

你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。

在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此 引擎扫描行数跟 rows_examined 并不是完全相同的。我们后面会专门有一篇文章来讲存储引擎的内部机制,里面会有详细的说明。

感谢亲人的句子(大年初一感恩我生命中的每一位家人朋友)

我们这一生,会遇见很多人,

他们充实我们的生活,

他们丰富我们的生命,

给了我们温暖和感动。

遇见他们,是我们的福气。

今天是大年初一,

在此感恩出现在我生命里的每个人,

并且祝福他们:

新年快乐,虎年好运!

1、感恩父亲母亲

世上有两个人,

为我们付出,毫无保留,

把我们生养,操劳一生。

总是把最好的给我们,

担心给我们的不够好不够多。

出门在外,他们牵肠挂肚,

逢年过节,他们忙里忙外,

他们就是父母!

感恩父母,

给我们生命,把我们养育,

为我们遮风挡雨,耗尽心血。

在他们的呵护照顾下,

我们成为最幸福的人。

和他们的缘分只有一场,

好好珍惜,及时尽孝!

2、感恩兄弟姐妹

血脉相连,手足情深,

一起成长,彼此帮衬。

小时候虽然打打闹闹,

但是长大了相互依靠。

关系亲密,割舍不断。

感恩,兄弟姐妹,

这些年来,

把我照顾,给我帮助,

你们是除了父母外,

对我最好,和我最亲的人。

此生有你们,足矣!

3、感恩知心爱人

人海茫茫,有幸相遇,

成为伴侣,相守一生。

分开了格外想念,

见面时更加依恋。

朝夕相处,同床共枕,

彼此成为对方最重要的人。

感恩知心爱人,

相伴身旁,患难与共。

无论富裕贫穷,都要忠诚,

不管风光落魄,都别改变。

愿此生顺利终老,

恩恩爱爱,不分不散。

4、感恩真心朋友

有一种人,

与我们毫无血缘,

却情同手足家人。

你知道我的小秘密,

我了解你的臭脾气。

伤心难过时,都会向她倾诉,

迷惑不解时,都会和他探讨,

一起哭过笑过闹过,

这种人,就是真心朋友。

感恩真心朋友,

对我包容理解,给我关心温暖,

把我帮助照顾,为我解决烦忧。

这一年我们相伴,

新一年我们继续!

5、感恩陌生人

陌生人,

和我们素不相识,

却向我们伸出援手。

是他们的举手之劳,

温暖了我们的心。

虽然只是小小的举动,

却给了我们莫大的帮助。

感恩陌生人,

你们的善良,我记在心里,

你们的帮助,我不会忘记。

今生能遇到你们,

是我的幸运。

愿你们一世安好,

祝你们健康平安。

6感恩辛苦的自己

这世上有一个人,

从出生开始跟随你,

陪你经历风雨坎坷,

陪你面对挫折打击,

陪你熬过苦楚困境,

知道你的脾气,了解你的全部,

也是真正懂你爱你的人,

这个人就是自己。

感恩辛苦的自己,

一路风雨,砥砺前进,

再累再难,无所畏惧。

即使受到伤害,遭遇不顺,

依然保持乐观,面带微笑。

愿辛苦的自己,在以后的日子里:

幸福快乐,一切顺利!

新的一年,新的开始,

我要感恩生命中的每一个人,

是你们让我感受温暖,

是你们带我走出困难,

是你们给我呵护照顾。

大年初一,新年第一天,

真诚的祝福你们:

身体健康,平安喜乐,

生活安逸,万事胜意!

英语句子分析器(手写一个词法分析器)

前言

最近大部分时间都在撸 Python,其中也会涉及到将数据库表转换为 Python 中 ORM 框架的 Model,但我们并没有找到一个合适的工具来做这个意义不大的”体力活“,所以每次新建表后大家都是根据自己的表结构手写一遍 Model。

一两张表还好,一旦 10 几张表都要写一遍时那痛苦只有自己知道;这时程序员的 slogan 再次印证:一切毫无意义的体力劳动终将被计算机取代。

intellij plugin

既然没有现成的工具那就自己写一个吧,演示效果如下:

考虑到我们主要是用 PyCharm 开发,正好 jetbrains 也提供了 SDK 用于开发插件,所以 UI 层面可以不用额外考虑了。

使用流程很简单,只需要导入 DDL 语句就可以生成 Python 所需要的 Model 代码。

例如导入以下 DDL:

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userName` varchar(20) DEFAULT NULL COMMENT '用户名', `password` varchar(100) DEFAULT NULL COMMENT '密码', `roleId` int(11) DEFAULT NULL COMMENT '角色ID', PRIMARY KEY (`id`), ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8

便会生成对应的 Python 代码:

class User(db.Model): __tablename__ = 'user' id = db.Column(db.Integer, primary_key=True, autoincrement=True) userName = db.Column(db.String) # 用户名 password = db.Column(db.String) # 密码 roleId = db.Column(db.Integer) # 角色ID

词法解析

仔细对比源文件及目标代码会很容易找出规律,无非就是解析出表名、字段、及字段的属性(是否为主键、类型、长度),最后再转换为 Python 所需要的模板即可。

在我动手之前我认为是非常简单的,无非就是解析字符串,但实际上手后发现不是那么回事;主要是有以下几个问题:

  1. 如何识别出表名称?
  2. 同样的如何识别出字段名称,同时还得关联上该字段的类型、长度、注释。
  3. 如何识别出主键?

总结一句话,如何通过一系列规则识别出一段字符串中的关键信息,这同样也是 MySQL Server 所做的事情。

在开始真正解析 DDL 之前,先来看下一段简单的脚本如何解析:

x = 20

按照我们平时开发的经验,这条语句分为以下几部分:

所以我们对这段脚本的解析结果应当为:

VAR xGE =VAL 100

这个解析过程在编译原理中称为”词法解析“,可能大家听到 编译原理这几个字就头大(我也是);对于刚才那段脚本我们可以编写一个非常简单的词法解析器生成这样的结果。

状态迁移

再开始之前先捋一下思路,可以看到上文的结果中通过 VAR 表示变量、 GE 表示赋值符号 ”=“、 VAL 表示赋值结果,现在需要重点记住这三个状态。

在依次读取字符解析时,程序就是在这几个状态中来回切换,如下图:

  1. 默认为初始状态。
  2. 当字符为字母时进入 VAR 状态。
  3. 当字符为 ”=“ 符号时进入 GE 状态。

同理,当不满足这几个状态时候又会回到初始从而再次确认新的状态。

光看图有点抽象,直接来看核心代码:

public class Result{ public TokenType tokenType ; public StringBuilder text = new StringBuilder(); }

首先定义了一个结果类,收集最终的解析结果;其中的 TokenType 就对应了图中的三种状态,简单的用枚举值来表示。

public enum TokenType { INIT, VAR, GE, VAL}

首先对应到第一张图:初始化状态。

需要对当前解析的字符定义一个 TokenType:

和图中描述的流程一致,判断当前字符给定一个状态即可。

接着对应到第二张图:状态之间的转换。

会根据不同的状态进入不同的 case,在不同的 case 中判断是否应当跳转到其他状态(进入 INIT 状态后会重新生成状态)。

举个例子:x=20:

首选会进入 VAR 状态,接着下一个字符为空格,自然在 38 行中重新进入初始状态,导致再次确定下一个字符 = 进入 GE 状态。

当脚本为 ab=30: 第一个字符为 a 也是进入 VAR 状态,第二个字符为 b,依然为字母,所以进入 36 行,状态不会改变,同时将 b 这个字符追加进来;后续步骤就和上一个例子一致了。

多说无益,建议大家自己跑一下单测就会明白:https://github.com/crossoverJie/sqlalchemy-transfer/blob/master/src/test/java/top/crossoverjie/plugin/core/lab/TestLexerTest.java

DDL 解析

简单的解析完成后来看看 DDL 这样的脚本应当如何解析:

CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `userName` varchar(20) DEFAULT NULL COMMENT '用户名', `password` varchar(100) DEFAULT NULL COMMENT '密码', `roleId` int(11) DEFAULT NULL COMMENT '角色ID', PRIMARY KEY (`id`), ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8

原理类似,首先还是要看出规律(也就是语法):

根据我们需要解析的数据种类,我这里定义了这个枚举:

然后在初始化类型时进行判断赋值:

由于需要解析的数据不少,所以这里的判断条件自然也就多了。

递归解析

针对于 DDL 的语法规则,我们这里还有需要有特殊处理的地方;比如解析具体字段信息时如何关联起来?

举个例子:

`userName` varchar(20) DEFAULT NULL COMMENT '用户名',`password` varchar(100) DEFAULT NULL COMMENT '密码',

这里我们解析出来的数据得有一个映射关系:

所以我们只能一个字段的全部信息解析完成并且关联好之后才能解析下一个字段。

于是这里我采用了递归的方式进行解析(不一定是最好的,欢迎大家提出更优的方案)。

} else if (value == '`' && pStatus == Status.BASE_INIT) { result.tokenType = DDLTokenType.FI; result.text.append(value);}

当当前字符为 ”`“ 符号时,将状态置为 "FI"(FieldInfo),同时当解析到为 "," 符号时便进入递归处理。

可以理解为将这一段字符串单独提取出来处理:

`userName` varchar(20) DEFAULT NULL COMMENT '用户名',

接着再将这段字符递归调用当前方法再次进行解析,这时便按照字段名称、类型、长度、注释的规则解析即可。

同时既然存在递归,还需要将子递归的数据关联起来,所以我在返回结果中新增了一个 pid 的字段,这个也容易理解。

默认值为 0,一旦递归后便自增 +1,保证每次递归的数据都是唯一的。

用同样的方法在解析主键时也是先将整个字符串提取出来:

PRIMARY KEY (`id`)

只不过是 "P" 打头 ")" 结尾。

} else if (value == 'P' && pStatus == Status.BASE_INIT) { result.tokenType = DDLTokenType.P_K; result.text.append(value);}

也是将整段字符串递归解析,再递归的过程中进行状态切换 P_K--->P_K_V 最终获取到主键。

所以通过对刚才那段 DDL 解析得到的结果如下:

这样每个字段也通过了 pid 进行了区分关联。

所以现在只需要对这个词法解析器进行封装,便可以提供一个简单的 API 来获取表中的数据了。

总结

到此整个词法解析器的全部内容都已经完成了,虽然实现的是一个小功能,但我自己花的时间可不少,其中光复习编译原理就让人头疼。

但这还只是整个编译语言知识点的冰山一角,后续还有语法、语义、中间、目标代码等一系列内容,都是一个比一个难啃。

其实我相信大多数人和我想法一样,这个东西太底层而且枯燥,真正从事这方面工作的也都是凤毛麟角,所以花这时间干啥呢?

所以我也决定这个弄完后就弃坑啦。


哈哈,开个玩笑,或许有生之年自己也能实现一门编程语言,当老了和儿子吹牛时也能有点资本。

本文所有源码及插件地址:

https://github.com/crossoverJie/sqlalchemy-transfer

英语句子分析器(使用)

原文地址:https://python.plainenglish.io/introduction-to-creating-interpreter-using-python-c2a9a6820aa0

原文作者:Umangshrestha

译文出自:掘金翻译计划

本文永久链接:https://github.com/xitu/gold-miner/blob/master/article/2023/introduction-to-creating-interpreter-using-python.md

译者:jaredliw

计算机只能理解机器码。归根结底,编程语言只是一串文字,目的是为了让人类更容易编写他们想让计算机做的事情。真正的魔法是由编译器和解释器完成,它们弥合了两者之间的差距。解释器逐行读取代码并将其转换为机器码。

在本文中,我们将设计一个可以执行算术运算的解释器。

我们不会重新造轮子。文章将使用由 David M. Beazley 开发的词法解析器 —— PLY(Python Lex-Yacc(https://github.com/dabeaz/ply))。

PLY 可以通过以下方式下载:

$ pip install ply

我们将粗略地浏览一下创建解释器所需的基础知识。欲了解更多,请参阅这个 GitHub 仓库(https://github.com/dabeaz/ply)。

标记(Token)

标记是为解释器提供有意义信息的最小字符单位。标记包含一对名称和属性值。

让我们从创建标记名称列表开始。这是一个必要的步骤。

tokens = ( # 数据类型 "NUM", "FLOAT", # 算术运算 "PLUS", "MINUS", "MUL", "DIV", # 括号 "LPAREN", "RPAREN",)

词法分析器(Lexer)

将语句转换为标记的过程称为标记化或词法分析。执行词法分析的程序是词法分析器。

# 标记的正则表达t_PLUS = r"\+"t_MINUS = r"\-"t_MUL = r"\*"t_DIV = r"/"t_LPAREN = r"\("t_RPAREN = r"\)"t_POW = r"\^"# 忽略空格和制表符t_ignore = " \t"# 为每个规则添加动作def t_FLOAT(t): r"""\d+\.\d+""" t.value = float(t.value) return tdef t_NUM(t): r"""\d+""" t.value = int(t.value) return t# 未定义规则字符的错误处理def t_error(t): # 此处的 t.value 包含未标记的其余输入 print(f"keyword not found: {t.value[0]}\nline {t.lineno}") t.lexer.skip(1)# 如果遇到 \n 则将其设为新的一行def t_newline(t): r"""\n+""" t.lexer.lineno += t.value.count("\n")

为导入词法分析器,我们将使用:

import ply.lex as lex

t_ 是一个特殊的前缀,表示定义标记的规则。每条词法规则都是用正则表达式制作的,与 Python 中的 re 模块兼容。正则表达式能够根据规则扫描输入并搜索符合的符号串。正则表达式定义的文法称为正则文法。正则文法定义的语言则称为正则语言。

定义好了规则,我们将构建词法分析器。

data = 'a = 2 +(10 -8)/1.0'lexer = lex.lex()lexer.input(data)while tok := lexer.token(): print(tok)

为了传递输入字符串,我们使用 lexer.input(data)。lexer.token() 将返回下一个 LexToken 实例,最后返回 None。根据上述规则,代码 2 + ( 10 -8)/1.0 的标记将是:

紫色字符代表的是标记的名称,其后是标记的具体内容。

巴科斯-诺尔范式(Backus-Naur Form,BNF)

大多数编程语言都可以用上下文无关文法来编写。它比常规语言更复杂。对于上下文无关文法,我们用上下文无关语法,它是描述语言中所有可能语法的规则集。BNF 是一种定义语法的方式,它描述了编程语言的语法。让我们看看例子:

symbol : alternative1 | alternative2 …

根据产生式,: 的左侧被替换为右侧的其中一个值替换。右侧的值由 | 分隔(可理解为 symbol 定义为 alternative1 或 alternative2或…… 等等)。对于我们的这个算术解释器,语法规格如下:

expression : expression '+' expression | expression '-' expression | expression '/' expression | expression '*' expression | expression '^' expression | +expression | -expression | ( expression ) | NUM | FLOAT

输入的标记是诸如 NUM、FLOAT、+、-、*、/ 之类的符号,称作终端(无法继续分解或产生其他符号的字符)。一个表达式由终端和规则集组成,例如 expression 则称为非终端。有关 BNF 的更多信息,请参阅此处(https://isaaccomputerscience.org/concepts/dsa_toc_bnf)。

解析器(Parser)

我们将使用 YACC(Yet Another Compiler Compiler) 作为解析器生成器。导入模块:import ply.yacc as yacc。

from operator import (add, sub, mul, truediv, pow)# 我们的解释器支持的运算符列表ops = { "+": add, "-": sub, "*": mul, "/": truediv, "^": pow,}def p_expression(p): """expression : expression PLUS expression | expression MINUS expression | expression DIV expression | expression MUL expression | expression POW expression""" if (p[2], p[3]) == ("/", 0): # 如果除以 0,则将“INF”(无限)作为值 p[0] = float("INF") else: p[0] = ops[p[2]](p[1], p[3])def p_expression_uplus_or_expr(p): """expression : PLUS expression %prec UPLUS | LPAREN expression RPAREN""" p[0] = p[2]def p_expression_uminus(p): """expression : MINUS expression %prec UMINUS""" p[0] = -p[2]def p_expression_num(p): """expression : NUM | FLOAT""" p[0] = p[1]# 语法错误时的规则def p_error(p): print(f"Syntax error in {p.value}")

在文档字符串中,我们将添加适当的语法规范。p 列表中的的元素与语法符号一一对应,如下所示:

expression : expression PLUS expressionp[0] p[1] p[2] p[3]

在上文中,%prec UPLUS 和 %prec UMINUS 是用来表示自定义运算的。%prec 即是 precedence 的缩写。在符号中本来没有 UPLUS 和 UMINUS 这个说法(在本文中这两个自定义运算表示一元正号和符号,其实 UPLUS 和 UMINUS 只是个名字,想取什么就取什么)。之后,我们可以添加基于表达式的规则。YACC 允许为每个令牌分配优先级。我们可以使用以下方法设置它:

precedence = ( ("left", "PLUS", "MINUS"), ("left", "MUL", "DIV"), ("left", "POW"), ("right", "UPLUS", "UMINUS"))

在优先级声明中,标记按优先级从低到高的顺序排列。PLUS 和 MINUS 优先级相同并且具有左结合性(运算从左至右执行)。MUL 和 DIV 的优先级高于 PLUS 和 MINUS,也具有左结合性。POW 亦是如此,不过优先级更高。UPLUS 和 UMINUS 则是具有右结合性(运算从右至左执行)。

要解析输入我们将使用:

parser = yacc.yacc()result = parser.parse(data)print(result)

完整代码如下:

###################################### 引入模块 ######################################from logging import (basicConfig, INFO, getLogger)from operator import (add, sub, mul, truediv, pow)import ply.lex as leximport ply.yacc as yacc# 我们的解释器支持的运算符列表ops = { "+": add, "-": sub, "*": mul, "/": truediv, "^": pow,}###################################### 标记集 ######################################tokens = ( # 数据类型 "NUM", "FLOAT", # 算术运算 "PLUS", "MINUS", "MUL", "DIV", "POW", # 括号 "LPAREN", "RPAREN",)###################################### 标记的正则表达式 ######################################t_PLUS = r"\+"t_MINUS = r"\-"t_MUL = r"\*"t_DIV = r"/"t_LPAREN = r"\("t_RPAREN = r"\)"t_POW = r"\^"# 忽略空格和制表符t_ignore = " \t"# 为每个规则添加动作def t_FLOAT(t): r"""\d+\.\d+""" t.value = float(t.value) return tdef t_NUM(t): r"""\d+""" t.value = int(t.value) return t# 未定义规则字符的错误处理def t_error(t): # 此处的 t.value 包含未标记的其余输入 print(f"keyword not found: {t.value[0]}\nline {t.lineno}") t.lexer.skip(1)# 如果看到 \n 则将其设为新的一行def t_newline(t): r"""\n+""" t.lexer.lineno += t.value.count("\n")###################################### 设置符号优先级 ######################################precedence = ( ("left", "PLUS", "MINUS"), ("left", "MUL", "DIV"), ("left", "POW"), ("right", "UPLUS", "UMINUS"))###################################### 书写 BNF 规则 ######################################def p_expression(p): """expression : expression PLUS expression | expression MINUS expression | expression DIV expression | expression MUL expression | expression POW expression""" if (p[2], p[3]) == ("/", 0): # 如果除以 0,则将“INF”(无限)作为值 p[0] = float("INF") else: p[0] = ops[p[2]](p[1], p[3])def p_expression_uplus_or_expr(p): """expression : PLUS expression %prec UPLUS | LPAREN expression RPAREN""" p[0] = p[2]def p_expression_uminus(p): """expression : MINUS expression %prec UMINUS""" p[0] = -p[2]def p_expression_num(p): """expression : NUM | FLOAT""" p[0] = p[1]# 语法错误时的规则def p_error(p): print(f"Syntax error in {p.value}")###################################### 主程式 ######################################if __name__ == "__main__": basicConfig(level=INFO, filename="logs.txt") lexer = lex.lex() parser = yacc.yacc() while True: try: result = parser.parse( input(">>>"), debug=getLogger()) print(result) except AttributeError: print("invalid syntax")

结论

由于这个话题的体积庞大,这篇文章并不能将事物完全的解释清楚,但我希望你能很好地理解文中涵盖的表层知识。我很快会发布关于这个话题的其他文章。谢谢你,祝你有个美好的一天。

Python猫注:原作者还写了系列的几篇文章,感兴趣的同学可查阅原作者博客(https://umangshrestha09.medium.com)。

如果你觉得本文有帮助的话,请点赞关注一下博主吧,支持我发布更多优质的好文章!

以上内容是关于适合自我沉淀的人生感悟句子和沉淀自己的经典句子的内容,小编幸苦为你编辑整理,喜欢的请点赞收藏把。

标签:沉淀自己的经典句子

标题:适合自我沉淀的人生感悟句子 沉淀自己的经典句子

链接:http://m.zhaichaow.cn/z/1617406.html