正则一把梭解析 Java 代码

Google 了一下,强大的 JavaScript 生态里竟然找不到一个纯 JS 实现的 Java 解析器,所有的我能找到的解析器都是通过启动一个 Java 进程来完成解析,然后再对输出结果进行后续处理。大概是因为 Java 的语法实在是复杂吧🤣。

为了实现一个在线查看 Jar 包内容的功能,我希望在浏览器中解析 Jar 包,并分析依赖关系,所以需要使用纯 JS 实现对 Java 代码的解析。

目标

  • 快:在 web 的世界里,我们的一帧最多只有 33ms 的时间,所以第一个要求就是快。
  • 准:至少要保证类型和依赖关系的解析的准确性,对于其他细节,其实也不用特别精确。

思路

  1. 基于 PEG 的解析,这是最靠谱最正统的解析方式,比如 Babel 对 JavaScript 的解析就是基于此。

    但是我并不能列举出 Java 所有的语法细节,所以也无法写出一个靠谱的 PEG 解析器。

    而且 PEG 在这种场景下有两个弊端:

    • 过于严谨,因而
    • 容错能力差,一处错误可能导致后面所有的语法都不能识别。
  2. 逐字解析,参考 highlight.js 的实现方式。

    这种方式也需要对 Java 语法有足够的了解,实现起来也有一定的难度。

    我的第一个版本就是用了这种方式,最后发现总有一些边缘情况考虑不周,导致识别失败。

    而实际上,我对解析的要求没有那么高,一些特殊语法原样展示就可以了,不用完全识别出来。

  3. 正则一把梭。

    这其实是一种取巧的方法,模拟一个不那么了解 Java 的人的角度,来从 Java 代码中获取关键信息。

    跟逐字解析的方式不太一样的地方在于,我们从大到小的提取信息,先用正则提取大块的内容,再逐渐细分,直到识别不出来为止。这样可以最大程度地保留关键信息,而且有很强的容错能力。

所以,最后我选择了正则一把梭。

suo

实现

  1. 为了防止误匹配,第一步要把注释提取出去,因为注释里可能包含代码,干扰识别。

    所以我直接正则匹配所有的 /* ... */,替换成占位符,这样就可以确保后面的匹配不会命中注释中的内容,提高准确率。

  2. 正则提取所有的 import,存下来到最后用于解析所有的依赖关系。
  3. 正则识别每个文件导出的类型,一般都是这种形式:

    public interface XxxInterface {
      boolean sayHi();
    }
  4. 识别完导出类型之后,根据当前类型下的典型的语法提取出关键信息,比如 interface 的方法名,class 的属性名。

    这里要用一个比较通用的形式,避免处理细节,比如 interface 中提取出任何的 type methodName(params),然后在后面的步骤中再做细分。

  5. 解析数据类型,从大到小。

    举个例子:对于一个复杂的类型 A<B<C>>,我们先提取最大的部分,得到泛型 A 和具体类型 B<C>,然后再进一步解析 B<C>

  6. 最后把所有解析出来的信息汇总,所有的类都存到一个池子里,然后将所有用到的数据类型补全,即完成了一次粗糙的在线解析。

jar-parser

不足

这毕竟是一个取巧的方式,对于复杂的 Java 代码还是无能为力的,不过处理仅仅用于定义接口的 Jar 包基本可以满足需求。

目前对于 Jar 包中存在复杂代码,或是嵌套定义的时候,还是可能会存在问题,以后有机会再继续改进。

最后附上项目地址:https://github.com/gera2ld/jar-parser


© 2020