手写一个词法分析器

手写一个词法分析器

前言

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

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

intellij plugin

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

1-min.gif

考虑到我们主要是用 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 所需要的模板即可。

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

如何识别出表名称?

同样的如何识别出字段名称,同时还得关联上该字段的类型、长度、注释。

如何识别出主键?

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

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

x = 20

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

x 表示变量

= 表示赋值符号

20 表示赋值结果

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

VAR x GE = VAL 100

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

状态迁移

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

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

手写一个词法分析器

默认为初始状态。

当字符为字母时进入 VAR 状态。

当字符为 ”=“ 符号时进入 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 解析

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsfpfs.html