去年,和公司的大佬探讨了一系列对于代码的代码化,还记录了一些笔记。在那之后,我开始了各种尝试:如何将代码转变动代码。原先有一些思路,而后过了一年之后,缓缓地练习,又有了一些新的播种。
咱们想要做的事件是:把 任意的 A 语言转换为 任意的 B 语言(PS:这里的任意 A 和任意 B 语言都是支流语言)。如此一来,咱们便能够:
- 疾速重写任何的零碎。
- 与编程语言无关的领域建模。
- 产生一个更弱小的 DSL。
- 创立新的语言。
引子 0:对立语言模型
对立语言模型,即对不同的比编程语言进行形象,应用同一套数据结构形容编程语言。
在我应用了 Golang + Antlr 实现了 Coca 之后,我意识到这是一条可行的计划。然而,因为 Coca 的架构和用处所限,外加之 Antlr 对于 Java 的反对远比 Go 要好,我并没有持续在 Coca 上施行这个计划。
于是乎,我开始了第二个尝试,应用 Kotlin + Antlr 来实现对不同语言的模型对立,也就是我的另外一个开源我的项目 Chapi。然而呢,随着一直的尝试,我发现了其中的难度和工作量比拟大:
- 编写不同语言的语法解析。社区上曾经有大量的成熟的轮子,其中最闻名的就是 Antlr 相干的语法解析。官网保护的代码仓库(grammars-v4)蕴含了大量的 Antlr 语法解析案例,能够找到市面上一些支流的和非主流的实现。
- 设计对立语言模型。即设计出一套能兼容不同语言的语言模式。当然了,这是一个继续欠缺的过程,会随着更多语言的退出,变得更加残缺和简单。
- 解析不同语言。即依据不同语言的语法个性,转换为上述的模型。
从难度上来说,咱们能够看出技术难度次要是在步骤 1 和步骤 2。而步骤 3 呢,则是一个十分繁琐、工作量微小的体力活。咱们还须要相熟不同的编程语言,并一一解析对应的字段,能力转换每一个语言。
因而,我尝试建设起了 Chapi 的社区,而后手把手率领一群人干活。只管,对于不同的语言我曾经建设起了对立的编写模式:TDD + Tasking。仿佛,很多人对于 AST 有点放心,因而参加的人非常少。所以,对于其它语言的反对就不了了之。
相干资源:
- 具体的设计能够参考我写的那一篇:《如何为代码建模?》
- 具体的实现能够参照:https://github.com/phodal/chapi
引子 1:语法高亮的背地
与此同时,哪怕有足够的人,Antlr 并非一个完满的答案。在编写不同语言的反对时,我仍旧遇到一系列的 Antlr 语法不反对的问题。如 JavaScript 的 Import,Java 的一些 Lambda 问题……。换句话来说,Antlr 官网只是保护这么一个库,实在的成果就不得而知了。
于是,我就回到了一条老路上,应用正则——当然不会本人写了。在那篇《编程语言的 IDE 反对》中,我提到了 基于正则表达式来实现语法分析,其中介绍了两个编辑器的实现形式:
- Sublime Text 基于 YAML 模式的正则匹配形式:Sublime Syntax files
- Textmate、VS Code 基于 JSON 的正则匹配形式:Language Grammars
所以,咱们抉择了 VSCode 作为了语法解析背地的语言。在这种模式之下:
- 咱们有一个成熟稳固的语言解析工具,并且也有一个微小的团队在保护它们。
- 它的社区是十分宏大的,通过大量的重复晋升。
因而,我和我的共事从几个前开始编写:https://github.com/phodal/scie/ —— 一个基于 TextMate 语法高亮的库。
引子 2:代码生成与 JavaPoet
在咱们毛糙地实现了 Scie 之后,我开始思考着下一步:如何从 A 语言转换为 B 语言的时候,我从 JavaPoet 获取到了一些灵感。JavaPoet 是一个用来生成 .java
源文件的 Java API。如下是一个简略的 JavaPoet 代码示例:
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
也就是说,咱们能够写一个 API,以将某语言转换为 B 语言的源码。而要实现任意语言的转换,那么咱们就须要实现一个 DSL:用于 形容不同语言与对立模型的差别 。起初,我意识到我还须要另外一个 DSL,用于 转换对立模型到不同的语言。
引子 3:两头示意的演变
编译器的外围数据结构是被编译程序的两头模式。——《编译器设计》
实践上,通过上述的两种形式,咱们就能够间接生成不同畛域的模型。然而呢,为了调试不便,能够创立一个两头语言来作为它们的承载物,能够让咱们实现更有意思的事件,去对立进行编译器优化——当然,我是瞎说的。
随后我的项目的起因,我钻研了一小段时间的 Proguard + D8 和 Android R8 的实现上。它们两做的事件是类似的,将 .class
字节码,编译优化,再转换成 Android 手机上的 dex。当然了,转换为 Aot 就是一个更有意思的话题了(尽管我也不相熟)。然而呢,这期间波及到了一系列的中间状态:java -> .class -> .dex -> odex -> .oat
。即从 Java 代码到 JVM 虚拟机字节码 -> Dalvik 虚拟机字节码 -> 优化过后的 Dalvik 字节码 -> ART 机器码。
而咱们再回过到来看,编码语言自身也是一种两头示意,因为机器运行的是靠机器码。即,那句经典的话:代码是写给人看的。
引子 4:DSL 的 DSL
对于有的编译器来说,它们可能有惟一的 IR(两头示意,Intermediate representation),也可能会有一系列的 IR。最常见的一些实现,便是咱们看到的那些应用 LLVM 作为后端的语言,它们能够生成两头模式的 LLVM IR。同样的对于咱们想做到的事来说,咱们能够设计一个相似于 LLVM IR 的高级两头示意,用于承载语言的设计。
因为我的项目波及到一丁点的代码优化,所以我还浏览了一下那本《高级编译器设计与实现》,书中引入了 ICAN 这个两头语言。嗯,这就是曾经被论证的后果了,不再须要我去论证它的必要性。所以下一步就是:
自举,在计算机科学中,它是一种用于生成自编译编译器的技术,即应用打算编译的源编程语言编写的编译器。
在业内,人们往往往把自举定义在编译器畛域中。然而呢,它能够在更多的畛域被利用。例如 Java 的构建工具,Gradle 应用 Gradle 来构建本人 —— 当然与编程语言相比,这事要绝对容易一些。
而人的自举就是把本人替换便,让工具做了本人的事,让他人做得了本人的事。所以,咱们就须要 Charj 来做本人所能做的事件。
Charj Lang
终于回到了正题上了,在有了下面的几步之后,咱们就能:
- 通过正则表达式,解析、生成不同语言的语法树。
- 编写 Poet API 将上述的语法树,转换为某一特定语言源码。
- 设计某一两头语言,用来作为 A 语言转换为 C 语言的载体。
- 实现 A 语言到 C 语言,又或者 C 语言到 A 语言的自在转换。
这便是从任意语言转换为任意语言的想法和思路。于是乎,我和我的共事们开始设计一个两头语言:Charj。
当然了,开发一个语言的目标次要是为了锤炼本人的能力,不论是形象能力,还是算法能力等等。在这个漫长的人生里,它将会变得有意义。当前,请叫我 Charj 语言作者。PS:你也能够是 Charj 语言作者。
回过头来看,事实上应该是这样的,我曾经尝试造了各式各样的工具,从各类的编辑器到各类的命令行工具。而在学习了 Rust 之后,我钻研了 JVM、编辑器底层,也正在逐个尝试创立日常所应用的工具。而在上一年里,因为编写重构工具 Coca,再到随后的转换为对立语言模型的 Chapi。对于编译器前端,我曾经有了相当丰盛的教训。自然而然的,发明一个语言就成了下一个方向。
为什么叫 Charj?
从转义上来说,Char 是更适宜 Charj 的定义的,然而 Char(仓颉)的商标曾经被注册了。退而求其次,我只好叫 Charj,能够引伸为中英混合式的:字符(Char)集(Ji),又或者是字符(Char)集(姬)。又或者是『字符 J』—— 至于 J 是什么意思,我还没想分明。咱们能够再定义,再取一个新的名字。
Charj 停顿
Charj 应用的是 Rust 为主的语言编写的。Rust 的自举曾经证实了:Rust 用于开发编程语言是没有问题的。当然了,次要起因还在于让我 C++,还不如让我写 Haskell。
Charj Lang(设计中)
Charj lang 当初的工作分为两局部:
- 欠缺语法设计
- 编译器的流程设计
只管从实践上来说,Charj 不肯定须要编译 + 可运行,然而为了自举,咱们须要它们。于是,咱们在后端采纳了 LLVM,前端应用的是 Rust 里的 LR(1)解析器生成器 lalrpop。
GitHub:https://github.com/charj-lang/charj
Charj IDE(开发中)
以后曾经有一个简略的语言插件,当然只有根本的高亮和跳转性能。如果你有肯定的 IDEA 插件开发教训,也能够来咱们一起搞搞。
GitHub:https://github.com/charj-lang/intellij-charj
Scie
Scie(Simple Code Identify Engine)是一个基于正则表达式的通用语言转换器。次要开发工作根本曾经实现了,然而有几个问题须要解决:
- 效率优化
- 调用 Oniguruma FFI 时会随机出错。
GitHub:https://github.com/charj-lang/scie
Charj Poet(开发中)
Charj Poet 是一个是用于生成 Charj 代码的 Rust API。打算等语法设计完,再进一步欠缺。
GitHub:https://github.com/charj-lang/charj-poet
Poet DSL(待定)
两局部:
- 即设计一个新的 DSL,来形容不同语言转换为 Charj Lang 的 DSL。
- 即设计一个新的 DSL,来形容 Charj Lang 转换为不同语言的 DSL。
官网
简陋和毛糙的官网:https://charj-lang.org/
其它
此时此刻,尽管我翻过几本编译相干的书籍,我也并非一个编译原理相干的专家。所以,如果你也有趣味,欢送来退出咱们。