在日常工作中我们很少能接触到编译相关,但是确是一个很重要的一个知识。作者@ jamiebuilds通过一个简单的案例用最小的实现方式展现了一个简单demo的编译器。https://github.com/jamiebuilds/the-super-tiny-compiler。
super-tiny-compiler 主要是将lisp语言的函数调用转换成C语言(你也可以理解JS的函数调用)
Aim | 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)) |
编译分为哪几个阶段(Stage)?
- 词法解析
parse
,将原始的代码经过词法分析转成抽象树 - 转换器
Transformation
,将抽象树转换成编译器需要的结构 - 代码生成
Code Generation
,将转换过的抽象树转换成目标代码
tokenizer
将代码拆解成一个一个的token
。
(add 2 (subtract 4 2))
转换成:
[
{ type: 'paren', value: '(' },
{ type: 'string', value: 'add' }
// ...
]
parser
接收一个tokens,将tokens转换成AST。
function parser(tokens) {
let current = 0;
function walk() {
// 匹配左括号
let token = tokens[current];
if (token.type === 'number') {
current++;
return {
type: 'NumberLiteral',
value: token.value,
};
}
if (token.type === 'string') {
current++;
return {
type: 'StringLiteral',
value: token.value,
};
}
if (
token.type === 'paren' &&
token.value === '('
) {
token = tokens[++current];
let node = {
type: 'CallExpression',
name: token.value,
params: [],
};
token = tokens[++current];
while (
(token.type !== 'paren') ||
(token.type === 'paren' && token.value !== ')')
) {
node.params.push(walk());
token = tokens[current];
}
current++;
return node
}
}
let ast = {
type: 'Program',
body: [],
};
while (current < tokens.length) {
ast.body.push(walk());
}
return ast;
}
transformer
将旧AST转换成与之相代码结构对应的新AST