去年,和公司的大佬讨论了一系列关于代码的代码化,还记录了一些笔记。在那以后,我开始了各类尝试:如何将代码转变化代码。原先有一些思路,然后过了一年以后,慢慢地练习,又有了一些新的收获。html
咱们想要作的事情是:把任意的 A 语言转换为任意的 B 语言(PS:这里的任意 A 和任意 B 语言都是主流语言)。如此一来,咱们即可以:前端
统一语言模型,即对不一样的比编程语言进行抽象,使用同一套数据结构描述编程语言。java
在我使用了 Golang + Antlr 实现了 Coca 以后,我意识到这是一条可行的方案。可是,因为 Coca 的架构和用途所限,外加之 Antlr 对于 Java 的支持远比 Go 要好,我并无继续在 Coca 上实施这个方案。git
因而乎,我开始了第二个尝试,使用 Kotlin + Antlr 来实现对不一样语言的模型统一,也就是个人另一个开源项目 Chapi。可是呢,随着不断的尝试,我发现了其中的难度和工做量比较大:github
从难度上来讲,咱们能够看出技术难度主要是在步骤 1 和步骤 2。而步骤 3 呢,则是一个很是繁琐、工做量巨大的体力活。咱们还须要熟悉不一样的编程语言,并一一解析对应的字段,才能转换每个语言。正则表达式
所以,我尝试创建起了 Chapi 的社区,而后手把手带领一群人干活。尽管,对于不一样的语言我已经创建起了统一的编写模式:TDD + Tasking。彷佛,不少人对于 AST 有点担忧,所以参与的人很是少。因此,对于其它语言的支持就不了了之。算法
相关资源:编程
与此同时,哪怕有足够的人,Antlr 并不是一个完美的答案。在编写不一样语言的支持时,我依旧遇到一系列的 Antlr 语法不支持的问题。如 JavaScript 的 Import,Java 的一些 Lambda 问题……。换句话来讲,Antlr 官方只是维护这么一个库,真实的效果就不得而知了。后端
因而,我就回到了一条老路上,使用正则——固然不会本身写了。在那篇《编程语言的 IDE 支持》中,我提到了基于正则表达式来实现语法分析,其中介绍了两个编辑器的实现方式:api
因此,咱们选择了 VSCode 做为了语法解析背后的语言。在这种模式之下:
所以,我和个人同事从几个前开始编写:github.com/phodal/scie… —— 一个基于 TextMate 语法高亮的库。
在咱们粗糙地完成了 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,用于转换统一模型到不一样的语言。
编译器的核心数据结构是被编译程序的中间形式。 —— 《编译器设计》
理论上,经过上述的两种方式,咱们就能够直接生成不一样领域的模型。可是呢,为了调试方便,能够建立一个中间语言来做为它们的承载物,可让咱们实现更有意思的事情,去统一进行编译器优化——固然,我是瞎说的。
随后项目的缘由,我研究了一小段时间的 Proguard + D8 和 Android R8 的实现上。它们两作的事情是类似的,将 .class
字节码,编译优化,再转换成 Android 手机上的 dex。固然了,转换为 Aot 就是一个更有意思的话题了(虽然我也不熟悉)。可是呢,这期间涉及到了一系列的中间状态:java -> .class -> .dex -> odex -> .oat
。即从 Java 代码到 JVM 虚拟机字节码 -> Dalvik 虚拟机字节码 -> 优化事后的 Dalvik 字节码 -> ART 机器码。
而咱们再回过到来看,编码语言自己也是一种中间表示,由于机器运行的是靠机器码。即,那句经典的话:代码是写给人看的。
对于有的编译器来讲 ,它们可能有惟一的 IR(中间表示,Intermediate representation),也可能会有一系列的 IR。最多见的一些实现,即是咱们看到的那些使用 LLVM 做为后端的语言,它们能够生成中间形式的 LLVM IR。一样的对于咱们想作到的事来讲,咱们能够设计一个相似于 LLVM IR 的高级中间表示,用于承载语言的设计。
因为项目涉及到一丁点的代码优化,因此我还阅读了一下那本《高级编译器设计与实现》,书中引入了 ICAN 这个中间语言。嗯,这就是已经被论证的结果了,再也不须要我去论证它的必要性。因此下一步就是:
自举,在计算机科学中,它是一种用于生成自编译编译器的技术,即便用打算编译的源编程语言编写的编译器。
在业内,人们每每往把自举定义在编译器领域中。可是呢,它能够在更多的领域被应用。例如 Java 的构建工具,Gradle 使用 Gradle 来构建本身 —— 固然与编程语言相比,这事要相对容易一些。
而人的自举就是把本身替换便,让工具作了本身的事,让别人作得了本身的事。因此,咱们就须要 Charj 来作本身所能作的事情。
终于回到了正题上了,在有了上面的几步以后,咱们就能:
这即是从任意语言转换为任意语言的想法和思路。因而乎,我和个人同事们开始设计一个中间语言:Charj。
固然了,开发一个语言的目的主要是为了锻炼本身的能力,不管是抽象能力,仍是算法能力等等。在这个漫长的人生里, 它将会变得有意义。之后,请叫我 Charj 语言做者。PS:你也能够是 Charj 语言做者。
回过头来看,事实上应该是这样的,我已经尝试造了各式各样的工具,从各种的编辑器到各种的命令行工具。而在学习了 Rust 以后,我研究了 JVM、编辑器底层,也正在逐一尝试建立平常所使用的工具。而在上一年里,由于编写重构工具 Coca,再到随后的转换为统一语言模型的 Chapi。对于编译器前端,我已经有了至关丰富的经验。天然而然的,创造一个语言就成了下一个方向。
从本义上来讲,Char 是更适合 Charj 的定义的,可是 Char(仓颉)的商标已经被注册了。退而求其次,我只好叫 Charj,能够引申为中英混合式的:字符(Char)集(Ji),又或者是字符(Char)集(姬)。又或者是『字符 J』 —— 至于 J 是什么意思,我还没想清楚。咱们能够再定义,再取一个新的名字。
Charj 使用的是 Rust 为主的语言编写的。Rust 的自举已经证实了:Rust 用于开发编程语言是没有问题的。固然了,主要缘由还在于让我 C++,还不如让我写 Haskell。
Charj lang 如今的工做分为两部分:
尽管从理论上来讲,Charj 不必定须要编译 + 可运行,可是为了自举,咱们须要它们。因而,咱们在后端采用了 LLVM,前端使用的是 Rust 里的 LR(1)解析器生成器 lalrpop 。
GitHub:github.com/charj-lang/…
当前已经有一个简单的语言插件,固然只有基本的高亮和跳转功能。若是你有必定的 IDEA 插件开发经验,也能够来咱们一块儿搞搞。
GitHub:github.com/charj-lang/…
Scie(Simple Code Identify Engine)是一个基于正则表达式的通用语言转换器。主要开发工做基本已经完成了,可是有几个问题须要解决:
GitHub:github.com/charj-lang/…
Charj Poet 是一个是用于生成 Charj 代码的 Rust API。计划等语法设计完,再进一步完善。
GitHub:github.com/charj-lang/…
两部分:
简陋和粗糙的官网:charj-lang.org/
此时此刻,虽然我翻过几本编译相关的书籍,我也并不是一个编译原理相关的专家。因此,若是你也有兴趣,欢迎来加入咱们。