We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
本文为博客迁移过来,原文链接: 一步步实现极简编译器 —— 了解编译器原理:2018-1-14
今天在看 babel 的时候,无意中被引到一个外链
关于编译器的优秀/简单的教程,请查看 the-super-tiny-compiler ,同时它也从宏观角度上解释了 Babel 本身是如何工作的。
觉得挺感兴趣的,加上代码也不多,就跟着思路自己理解敲了一遍。
本文主要是帮助理解编译器的原理 不做过多的其他扩展
一般简单的编译器可以由以下几部分组成:
tokens
babel 初始阶段并没有做任何事,基本上等于 const babel = code=> code; 先 tokenizer, parser 解析代码,再 transformer 的时候,完全不改动原来的 ast
const babel = code=> code
接下来以最简单的编译器组成 一个环节一个环节走下去
词法分析器其实可以理解为简单的将文本切割,然后将有价值的按照相邻同等类型的 文本组合一起输出。 ps:无价值指对代码生成没有影响的部分,比如js里面非文本 一个空格和一百个空格对编译器来说是没有区别的
实现思路:
current
(
)
token
function tokenizer(input) { let current = 0; const tokens = []; while (current < input.length) { let char = input[current]; if (char === "(" || char === ")") { tokens.push({ type: 'paren', value: char }); current++; continue; } const WHITESPACE = /\s/; if (WHITESPACE.test(char)) { current++; continue; } const LETTERS = /[a-z]/i; if (LETTERS.test(char)) { let name = ""; while (LETTERS.test(char)) { name += char; current++; char = input[current]; } tokens.push({ type: 'name', value: name }); continue; } const NUMBERS = /[0-9]/; if (NUMBERS.test(char)) { let numbers = ""; while(NUMBERS.test(char)) { numbers += char; current++; char = input[current]; } tokens.push({ type: 'number', value: numbers }); continue; } if (char === '"') { let string = ''; current++; char = input[current]; while(char !== '"') { string += char; current++; char = input[current]; } tokens.push({ type: 'string', value: string }); current++; continue; } throw new TypeError('不知道你输入的是什么鬼东西 ' + char); } return tokens; }
const input = '(add 2 (subtract 4 2 "djwaqp"))'; tokenizer(input); // 输出 /* [{ 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: 'string', value: 'djwaqp' }, { type: 'paren', value: ')' }, { type: 'paren', value: ')' }]; */
语法剖析器就是把 tokens 解析,转化为抽象语法树(AST)🌲🌲🌲,方便后续的处理。
[{ type: 'paren', value: '(' }, ...] => { type: 'Program', body: [...] }
{type: 'Program' , body: [...]}
token.type
number
{type: 'NumberLiteral', value, }
string
{type: 'StringLiteral', value, }
paren (
paren )
function parser(tokens) { let current = 0; const ast = { type: 'Program', body: [] }; 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; } throw new TypeError(token.type); } while(current < tokens.length) { ast.body.push(walk()); } return ast; }
input: [{ 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: 'string', value: 'djwaqp' }, { type: 'paren', value: ')' }, { type: 'paren', value: ')' }] => output: { "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" }, { "type": "StringLiteral", "value": "djwaqp" }] }] }] }
transformer 顾名思义,为转换部分,最复杂 也最常用。 .babelrc 添加的插件,也只是在这个环节进行操作,将原本的 ast( es6 ) 转换为目标的 newAst (es5)。
.babelrc
ast { type: 'Program', body: [...] } => newAst { type: 'Program', body: [...] }
{ type: 'Program', body: [...] }
ast
_context
newAst.body
Program
CallExpression
4
function traverser(node, visitor) { function traverseArray(nodeArr, parent) { nodeArr.forEach(child => traverseNode(child, parent)); } function traverseNode(node, parent) { const methods = visitor[node.type]; if (methods && methods.enter) { methods.enter(node, parent); } switch (node.type) { case 'Program': traverseArray(node.body, node); break; case 'CallExpression': traverseArray(node.params, node); break; case 'NumberLiteral': case 'StringLiteral': break; default: throw new TypeError(node.type); } if (methods && methods.exit) { methods.exit(node, parent); } } traverseNode(node, null); } function transformer(ast) { const newAst = { type: 'Program', body: [] }; ast._context = newAst.body; traverser(ast, { NumberLiteral: { enter(node, parent) { parent._context.push({ type: 'NumberLiteral', value: node.value }); } }, StringLiteral: { enter(node, parent) { parent._context.push({ type: 'StringLiteral', value: node.value, }); } }, CallExpression: { enter(node, parent) { let expression = { type: 'CallExpression', callee: { type: 'Identifier', name: node.name }, arguments: [] } node._context = expression.arguments; if (parent.type !== 'CallExpression') { expression = { type: 'ExpressionStatement', expression: expression } } parent._context.push(expression); } } }); return newAst; }
input: { "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" }, { "type": "StringLiteral", "value": "djwaqp" }] }] }] } => output: { "type": "Program", "body": [{ "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "ADD" }, "arguments": [{ "type": "NumberLiteral", "value": "2" }, { "type": "CallExpression", "callee": { "type": "Identifier", "name": "subtract" }, "arguments": [{ "type": "NumberLiteral", "value": "4" }, { "type": "NumberLiteral", "value": "2" }, { "type": "StringLiteral", "value": "djwaqp" }] }] } }] }
最后一步就是 codeGenerator, 用 newAst 递归调用,根据 node 与一系列规则去生成一个 string。
codeGenerator
newAst
node
newAst { type: 'Program', body: [...] } => call(argumentsA, ...argumentsN);
ExpressionStatement
Identifier
NumberLiteral
StringLiteral
function codeGenerator(node) { switch (node.type) { case 'Program': return node.body.map(codeGenerator).join('\n'); case 'ExpressionStatement': return codeGenerator(node.expression) + ';'; case 'CallExpression': return (codeGenerator(node.callee) +'(' + node.arguments.map(codeGenerator).join(', ') + ')'); case 'Identifier': return node.name; case 'NumberLiteral': return node.value; case 'StringLiteral': return '"' + node.value + '"'; default: throw new TypeError(node.type); } }
input: { "type": "Program", "body": [{ "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "ADD" }, "arguments": [{ "type": "NumberLiteral", "value": "2" }, { "type": "CallExpression", "callee": { "type": "Identifier", "name": "subtract" }, "arguments": [{ "type": "NumberLiteral", "value": "4" }, { "type": "NumberLiteral", "value": "2" }, { "type": "StringLiteral", "value": "djwaqp" }] }] } }] } => output: add(2, subtract(4, 2, "djwaqp"));
至此,编译器所需的几个步骤: 词法分析,解析,转换,生成都已经完成。
function compiler(input) { const tokens = tokenizer(input); const ast = parser(tokens); const newAst = transformer(ast); const output = codeGenerator(newAst); return output; } const input = '(add 2 (subtract 4 2 "djwaqp"))'; const output = compilter(input); // 'add(2, subtract(4, 2, "djwaqp"));'
babel 的工作原理可以理解成就是一个简单的编译器:分析 => 转换 => 生成代码
分析
转换
生成代码
bable 的篇幅太多,下面直接给出 demo源码。 具体可以看 官方插件手册 babel-handbook
我们写的babel 插件,都是在转换的部分运行
const t = require('babel-types'); module.exports= function() { // plugin contents return { visitor: { // visitor contents BinaryExpression(path, state) { // 如果操作符不是 === 则返回 if (path.node.operator !== '===') { return; } // 操作符的左边替换为 sebmck, 右边替换为 dork path.node.left = t.identifier('"sebmck"'); path.node.right = t.identifier('"dork"'); } } }; };
function demo() { return 1===2; }
安装相关依赖:npm i babel-cli babel-types --save-dev
npm i babel-cli babel-types --save-dev
运行脚本: babel src/ -d build/
babel src/ -d build/
function demo() { return "sebmck" === "dork"; }
最后再推一波关于学习中看到的好网站
源码 esprima 解析语法树🌲 AST Explorer ast名词解释 babel-plugin-handbook
The text was updated successfully, but these errors were encountered:
No branches or pull requests
welcome
今天在看 babel 的时候,无意中被引到一个外链
觉得挺感兴趣的,加上代码也不多,就跟着思路自己理解敲了一遍。
本文主要是帮助理解编译器的原理 不做过多的其他扩展
编译器的基本组成
一般简单的编译器可以由以下几部分组成:
tokens
接下来以最简单的编译器组成 一个环节一个环节走下去
tokenizer 词法分析器
词法分析器其实可以理解为简单的将文本切割,然后将有价值的按照相邻同等类型的 文本组合一起输出。
ps:无价值指对代码生成没有影响的部分,比如js里面非文本 一个空格和一百个空格对编译器来说是没有区别的
实现思路:
current
tokens
current
的值做 分类型处理(
)
token
并返回parser
语法剖析器就是把
tokens
解析,转化为抽象语法树(AST)🌲🌲🌲,方便后续的处理。实现思路:
current
对tokens
进行遍历,每一项token
进行分析处理{type: 'Program' , body: [...]}
token.type
进行相应的归类处理:number
: 直接返回{type: 'NumberLiteral', value, }
string
: 直接返回{type: 'StringLiteral', value, }
paren (
: 对下一个进行递归,直到出现paren )
transformer
transformer 顾名思义,为转换部分,最复杂 也最常用。
.babelrc
添加的插件,也只是在这个环节进行操作,将原本的 ast( es6 ) 转换为目标的 newAst (es5)。实现思路:
{ type: 'Program', body: [...] }
ast
上建一个引用_context
到newAst.body
;_context
Program
或CallExpression
对子级进行 递归4
处理codeGenerator
最后一步就是
codeGenerator
, 用newAst
递归调用,根据node
与一系列规则去生成一个 string。实现思路:
Program
=> 对 node.body 进行递归ExpressionStatement
=> 对 node.expression 进行处理CallExpression
=> 对 node.callee 与 node.arguments 进行处理Identifier
&&NumberLiteral
&&StringLiteral
直接返回对应的字段compiler
至此,编译器所需的几个步骤: 词法分析,解析,转换,生成都已经完成。
babel
babel 的工作原理可以理解成就是一个简单的编译器:
分析
=>转换
=>生成代码
bable 的篇幅太多,下面直接给出 demo源码。
具体可以看 官方插件手册 babel-handbook
我们写的babel 插件,都是在转换的部分运行
安装相关依赖:
npm i babel-cli babel-types --save-dev
运行脚本:
babel src/ -d build/
最后再推一波关于学习中看到的好网站
源码
esprima 解析语法树🌲
AST Explorer
ast名词解释
babel-plugin-handbook
The text was updated successfully, but these errors were encountered: