一个超级超级小的编译器的实现,真正代码大约只有200行

在github 上看到一个超级小的编译器实现,看了一下具体实现,大概了解了编译器的实现逻辑,这里分享给大家。 今天,我们将一起编写一个编译器。不过,这不是任何普通的编译器……这是一个超级超级小的编译器!这个编译器小到,如果你把所有注释都删掉,真正的代码只有大约200行。 我们即将将一些类似LISP的函数调用编译成一些类似C的函数调用。 如果你对这两种语言都不太熟悉,我会快速介绍一下。 假如我们有两个函数 add 和 subtract,它们的写法如下: LISP C 2 + 2 (add 2 2) add(2, 2) 4 - 2 (subtract 4 2) subtract(4, 2) 2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2)) 很简单,对吧? 那很好,因为这正是我们要编译的内容。虽然这既不完全符合LISP也不完全符合C的语法,但它足以演示现代编译器的许多主要部分。 大多数编译器分为三个主要阶段:解析、转换和代码生成。 解析,即将原始代码转换成更抽象的代码表示形式。 转换,即对这种抽象表示进行操作,完成编译器想要完成的任何任务。 代码生成,即将转换后的代码表示转换成新的代码。 核心截断 解析 解析通常分为两个阶段:词法分析和句法分析。 词法分析,将原始代码分割成所谓的tokens,这一过程由一个称为 tokenizer(或lexer)的工具完成。 Tokens是描述语法的孤立部分的一系列小对象。他们可以是数字、标签、标点、操作符等。 句法分析,将 tokens 重新组织成一种表示语法各部分及其相互关系的形式。这种表示称为中间表示或抽象语法树(AST)。 抽象语法树(AST)是一个深度嵌套的对象,它以易于操作的方式表示代码,并提供大量信息。 对于如下语法: (add 2 (subtract 4 2)) Tokens可能如下所示: [ { type: 'paren', value: '(' }, { type: 'name', value: 'add' }, { type: 'number', value: '2' }, { type: 'paren', value: '(' }, { type: 'name', value: 'subtract' }, { type: 'number', value: '4' }, { type: 'number', value: '2' }, { type: 'paren', value: ')' }, { type: 'paren', value: ')' }, ] 而抽象语法树(AST)可能如下所示:...

April 8, 2024 · 15 min · fisherdaddy