前言
本篇内容主要由 [https://github.com/jamiebuilds/the-super-tiny-compiler]中的注释翻译而来,该项目实现了一款包含编译器核心组成的极简的编译器。希望能够给想要初步了解编译过程的同学提供到一些帮助。
概要
- 本篇和大家一起学习写一款超级简单轻量,去掉注释只有不到200行代码的编译器。
- 该编译器将类 lisp 语法函数调用 编译为 类C语言函数调用
- 如果不熟悉上述的两种语法的其中任意一种,下面给出了简单的介绍
- 例如有两个函数 add 和 subtract 他们用对应的语言分别实如余下:
- 本篇要实现编译的全部语法如上所示。虽然既不涵盖完整的lisp语法和c语法,但是足够展示一个现代编译器需要的主要组成部分
编译器组成
大部分的编译器可以粗略的划分为3个阶段: 解析 Parsing,翻译 Transformation,代码生成Code Generation
- 解析 获取原始代码并将其转化为一个更抽象的代码表示
- 翻译 用抽象的代码表示为编译器想要完成的操作做准备
- 代码生成 将翻译过的抽象表示转化为新的要编译的代码
解析 Parsing
解析过程通常被分为两个部分: 词法分析,语法分析
- 词法分析 获取原始代码 ,且将代码分割为一个一个词[token]
由这些词构成的词组用来描述语法,他们可以是数字,文本,标点符号,运算符等等 - 语法分析 获取词组[tokens]且将他们重新格式化为一个表示形式,该表示形式描述语法的每个部分及其相互之间的关系。这称为中间表示或抽象语法树。
抽象语法树(简称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: ')' }, ]
抽象语法树如下
{ type: 'Program', body: [{ type: 'CallExpression', name: 'add', params: [{ type: 'NumberLiteral', value: '2', }, { type: 'CallExpression', name: 'subtract', params: [{ type: 'NumberLiteral', value: '4', }, { type: 'NumberLiteral', value: '2', }] }] }] }
翻译
获得抽象语法树后下一个阶段就是翻译转换。同样,这只需要从最后一步中提取AST并对其进行更改。它可以用同一种语言操纵AST,也可以将AST翻译成一种全新的语言。
让我们看看如何转换AST。
你可能会注意到我们的AST中有看起来非常相似的元素。这些对象具有类型属性。每个节点都称为AST节点。这些节点定义了描述树的一个独立部分的属性。
我们有一个数字节点 "NumberLiteral"
{ type: 'NumberLiteral', value: '2',}
或者一个调用表达式节
{ type: 'CallExpression', name: 'subtract', params: [...nested nodes here...], }
点转换AST时,我们可以通过添加/删除/替换属性来操纵节点,可以添加新节点,删除节点,也可以不使用现有的AST直接基于它创建一个全新的AST。
由于我们定位的是新语言,因此我们将专注于创建特定于目标语言的全新AST。
遍历
为了浏览所有这些节点