写自己的语言从来没有像现在这样容易(2016)

2020-07-03 23:28:50

随着编程的成熟和向更高级别的抽象转移,它变得越来越多地是关于组装预制的构建块,而不是编写您自己的新的、新颖的解决方案。当我使用开源操作系统、编程语言、数据库、数据库、网络连接库、Web服务器、Web框架、验证库、API库、支付服务库创建Web应用程序(这是我日常工作的主要部分)时,您就会明白了这一点。我编写的代码越来越多地变成胶水代码,简单地按照正确的顺序将我的构建块绑定在一起,就像更复杂的IFTTT或文本乐高积木(Lego)。

不足为奇的是,这个构建块集合正在扩大,以涵盖越来越复杂的编程领域。今天,用构建Web应用程序的方式构建一种简单的编程语言是可能的。

传统上,前端由一个词法分析器和一个解析器组成,前者用于根据语法规则将原始源代码转换为令牌,后者用于将令牌转换为抽象语法树或表示操作的AST嵌套块。

后端是编译器,它执行类型检查、优化以及最终将AST转换为低级表示等步骤。如果您正在编写一种解释型语言,那么这个较低级别可能是字节码,而不是可执行文件,以及读取和执行字节码指令的相应解释器。例如,Python是一种解释型语言,其中.py表示原始源代码文件,.pyc表示编译后的字节码。当第一次运行.py文件时,Python将构建可以在下次运行的abytecode编译的等价物(无论如何,直到您的源代码更改为止)。相反,如果您重新编写编译语言,您的后端将构建可由操作系统直接运行的本机可执行文件。

因此,一旦您有了语法,就可以插入到前端,并且可以用后端编译ResultingAST,那么还有什么要做的呢?嗯,不要忘记编写标准库-定义您的内置类型、类(如果您有的话),以及在它们上操作的所有标准函数。这一步至少要做同样多的工作。

如果您现在正在编写一种语言,那么我们上面定义的许多部分已经为您准备好了。您的后端和前端都可以由现有部件构建。

有大量的词法分析器和解析器可用-例如,Lexx和Yacc是历史悠久的词法分析和解析器组合,已经产生了无数的现代解释。RPly(RPython Lexx/yacc)是我目前在这方面选择的工具。如果您使用词法分析器和解析器,留给您的工作就是编写语法规则、表示AST的对象,以及告诉解析器如何将语法转换为AST的简单函数。定义语法和AST很有趣!如果你的语法很简单,这一步可能也不会那么难。

对于后端来说,LLVM是这项工作最广为人知和最受推崇的工具。LLVM支持C和C++编译器Clang以及其他知名语言,如Rust和Swift。LLVM的美妙之处在于它的中间表示,即IR。IR看起来很像汇编语言,或者真的很难看的C语言,但是如果你准备把你的语言翻译成LLVM IR,LLVM会帮你优化和编译成原始的可执行文件!这是件大事。它甚至可以在解释模式下运行以进行调试,或者在您的解释器中用作即时(JIT)编译器,如果这是您需要的。

幸运的是,你不需要手写IR。因为LLVM有一个C接口,所以有许多语言的LLVM库可以帮助您创建IR。llvm-lite是我发现的最成熟的Python绑定,也是我目前使用的绑定。本质上,使用llvm-lite意味着您可以请求它为您创建一个新的函数定义,例如,它将负责生成正确的IR语法,并为您返回一个更高级别的接口来管理内部的变量和代码块。

因此,有了这几个部分的组合,编写语言的许多最棘手的部分就可以完全外包了。

让我给你们举一个具体的例子。我将使用我目前用Python编写的玩具语言来演示它们是如何结合在一起的:

我用Python编写了一个标记列表(本质上是一组字符串),并将它们提供给RPly的词法分析器。

我创建了一个Python类来表示每种类型的AST节点或代码块(函数、IF语句等)。

我用Python编写了一组语法规则,告诉它如何将令牌转换为AST实例,并将它们提供给RPly的解析器。

我编写了一个编译器类,它可以使用llvm-lite将每个AST类型转换为LLVM IR。

瞧,这是一个我可以运行的工作程序。如果没有标准库,它不能做很多事情,但是如果我为一些二元操作(如加法和减法)定义语法和AST,我就可以编译简单的算术并返回或打印结果。

我想说的是,一旦你决定了你的语言的语法,你写的大部分代码都是胶水,把一个库的输出转化为另一个库的输入。当然,这个胶水编解码器变得相当复杂,但是对于现代语言开发人员来说,已经为您做了很多事情,并且已经准备好插入。如果你对一门语言有什么想法,那么尝试一下就再容易不过了。

顶上