为什么antlr用于模板引擎不是个好主意
我在发布 jfinal 3.0 的时候认为 antlr 用于 "模板引擎" 并不是个好主意,两年多时间过去了,我的观点更进一步:认为 antlr 在多数 “非模板引擎” 的场景下使用也不是个好主意。
在发布 jfinal 3.0 的时候谈到 antlr,只言片语信息量太少,引起了部分人的误解,今天就来稍稍展开聊一聊。
一、antlr 生成 Parser 难于调试、难于阅读
首先现场直观来感受一下 jfinal 手写 Parser 与使用 antlr 生成的 parser 的对比,下面是为 jfinal enjoy 模板引擎手写的 parser:
https://gitee.com/jfinal/jfinal/blob/master/src/main/java/com/jfinal/template/stat/Parser.java
空行 + 注释 + java 代码一共 278 行,干净利落,人类轻松阅读。更重要的是其用到的 Recursive Descent 算法简洁可靠,功能强大,随手可得。了解这个算法原理的同学几个小时就可以手撸一个自己的 Parser 出来。
再来看一下 antlr 为模板引擎生成的 parse:
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlParser.java
空行 + 注释 + java 代码一共 3746 行,这里一定要注意看第 3923 行的 String _serializedATN 变量,这个是其 parser 运行时所依靠的核心,人类完全无法阅读,也根本无法调试。
这还不算完,生成这个 parser 需要先学习 antlr 的语法定义规则,然后写一个语法定义规则文件,该规则文件描述语法结构:
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlParser.g4
这还没完,要用上 antlr 还得学会、用好、创建这些东西:
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlParser.interp
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlParser.tokens
要熟练掌握以上这套规则和语法描述,并精准无误地用于具体项目并不容易,学习成本比使用现成的 Recursive Descent 算法做的 Parser要高得多,而且生成出来的东西不可阅读、无法调试。
那么到这里总该完事了吧?仍然没有,词法分析 lexer 还要再搞一次上面这一类的事情,学习成本再提升一倍:
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlLexer.g4
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlLexer.interp
https://gitee.com/xiandafu/beetl/blob/master/src/main/java/org/beetl/core/parser/BeetlLexer.java
特别注意看一下 BeetlLexer.java 第 156 行定义的 String _serializedATN 变量,用 antlr 生成的 lexer 同样是不可读,不可调试的。
那么请问,整完 lexer 这套东西,总该完了吧?还是没有。几套规则定义文件外加两个生成的 java 文件还是跑不起来,必须要引入一个 329KB 的运行时依赖:antlr4-runtime.jar。
就 parser 这点破事,引入一个 329KB 体量 runtime。要知道 jfinal 用这么大体量连 AOP + ORM + template engine 的事全干完了。我真不知道这个 jar 包里面在做什么?
上述这些事,在 jfinal enjoy 模板引擎里头手撸一个 parser、lexer 完事了,现成的、成熟的算法拿来即用,parser、lexer 的原理、作用、写法在大学本科阶段是必学的,有些学校还会为此作为一个课程设计的作业。
当你花时间学习 antlr 这套规则的时候,我早就手工撸完 parser 了。
这里要说明一下 jfinal enjoy 独创的 DLRD 算法。jfinal enjoy 的 parser 是针对模板文件的特征,基于传统 Recursive Descent Parser 做了改进,做成了 Double Layer Recursive Descent 算法,将指令级的 parser 与表达式级的 parser 划分在两个独立的层次,属于独创。每一层与传统的 Recursive Descent 算法原理类似,对左递归、二义性等问题的处理有所改进。
即便其他人没有 jfinal enjoy 的算法创新,仅仅使用传统的 Recursive Descent Parser,我前面的表述依然成立。
罗总很严谨,这段的标题我用了“难于调试”、“难于阅读”,是为了不排除少数天才有这个能力,但对于绝大部分人类来说是不可调试、不可阅读的。
二、antlr 生成的 parser 对变化响应慢
当你用 antlr 做出来的东西在语法、词法层面需要加点东西或者改点东西的时候,过程如下:
1: 修改语法、词法涉及的规则 .g4 描述文件
2: 修改语法、词法涉及的 .tokens、.interp 文件
3: 运行 antlr 的生成器,重新生成 Parser、Lexer
4: 开始写自己的代码
首先,注意上面的工作流程,其中前三步都是在用特定规则的描述语言在表示语法、词法,然后 antlr 将其转化成不可读、不可调试的 parser 代码。
如果生成出来的东西有 bug,你很难从生成的 parser 再回头去找规则描述文件中的错误和原因,因为描述文件是不能被调试的,它不是 java 代码,根本不能运行。
其次,上面这个工作流程表明,在对词法、语法进行迭代的过程中,你要首先干很多别的事情,然后才可以开始真正写代码,这个重复、麻烦且容易出错的过程会阻碍了模板引擎的改进。
jfinal enjoy 在迭代的过程中就改过语法,例如后来添加的 #switch 指令,enjoy 不使用 antlr 方案,基本上只需要在 Parser 中添加一个 case 分支就完事了:
https://gitee.com/jfinal/jfinal/blob/master/src/main/java/com/jfinal/template/stat/Parser.java
注意看第 218 行代码,根本就不需要学习、折腾 antlr 方案下的那一套东西。
三、至于那些用了 antlr 的项目
首先我并不认为某些比较知名的项目用了 antlr 就证明它是个好方案,我个人的思维习惯之一就是普遍怀疑,哪怕你是权威。只有这样才有可能站在前人的肩膀上做得更好,走得更远。
其次,antlr 被人们使用,我认为出于如下几个原因:
1: 非计算机专业的人
2: 计算机专业,但大学基础没学好,不知道有现成的 parser 算法、源码可用
3: 盲目跟风的人。早期的几个知名模板引擎都用的 ANTLR、jflex、javacc 这一类,后来者跟风模仿
主流的程序语言,如 java、C#、C++ 都是手写 parser,不可能去用 antlr 这种东西,综上所有信息量,我认为 antlr 在多数 “非模板引擎” 的场景下使用也不是个好主意。
罗总在博客中谈到 “Parser, 根本就不是拿来给人读的, 也不是用来让人直接"细致打磨" 这个我完全不赞同。
jfinal enjoy 多次新增、改进过语法,例如新增 #switch、#case 指令,再例如修正过空合并安全操作符与其它操作符混合使用时的 bug,这种情况下不可读、不可调试是绝对不行的。
如果 antlr 生成的 parser 出现上述类似的 bug,你无法通过调试的办法找到原因,只能硬着头皮去看那几个用文本文件书写的描述规则,该描述文件不能运行、更不能调试。
java、C++ 这种语言为啥要自己写 parser 不使用 antlr? 新增、修改语法、处理 bug 都必须要可读、可调试
四、回到 jfinal 3.0 发布时新闻中的部分观点
有了前面的信息量,再回看两年前我发布 jfinal 3.0 时谈到的 antlr 所用的一些词以及观点。我相信 antlr 是可靠、稳固的,但我不相信使用者制定的那套描述文件也是可靠稳固的。进而不相信生成的 parser 代码是可靠的,“飘摇不安” 就指向这里。
antlr 那套语法描述规则并不比现成的 parser 算法容易,如果你能精准用好那套规则,早就更能用好现成的 parser 源码了,根本用不着 antlr。
最后我们做个有趣的事情:
我能够感觉到,罗总发的这些东西具有鲜明的立场,都还是站在 beetl 那边,先有立场,再找各种各样的理由支持自己的立场。即便是对 antlr 不熟悉,但为了 beetl 的立场,宁愿花时间去研究 antlr 来证明自己的立场。
如果罗总不这么认为的话,我们来做个有趣的实验:
1: 罗总将这几天的事件当成是一场辩论,假如前几天罗总是反方,从现在开始,罗总变正方,站在 jfinal 立场
2: 辩论是公正的,所以我则站在反方,与你展开辩论
3: 辩论形式还是以罗总写博客为开头,我在作为反方回复
4: 辩论的基本原则是以自己胜出为目标,所以要彻底交换你原来的立场
上面这招是向阿里巴巴前 CEO 卫哲学来的,他在自己的商业会议上先是将企业家分成正反两方对某个论题展开辩论,大家吵得不可开交。
然后,将正反两方互换立场,你猜什么事情发生了? 原先自己捍卫的立场在交换身份为对方原来的立场辩护以后,照样理由充分,争得面红耳赤,难分高下。
这招可以证明,人们往往更多是为了立场而战,而我自己也因为这招,一直怀揣着一个反方的思脉,所以能不断从对手那里学到东西,也能更好体察对方的观点
我的这个建议是觉得罗总在自己的立场下,会不断找到无数种理由继续下去,但永远没有结果。不如换个角度,站在对方的视角看看这个世界。
以上是 为什么antlr用于模板引擎不是个好主意 的全部内容, 来源链接: utcz.com/z/511946.html