手写编译器

  • code -> tokenizer -> tokens
  • tokens -> parser -> ast
  • ast -> transformer -> newAst
  • newAst -> generator -> output ```javascript // 第一步:代码分析阶段。parseing // 代码分析包括 词法分析 lexical analysis【就是tokenizer阶段】 和 语法分析 syntactic analysis // (add 2 (subtract 4 2)) => [{type: “parent”, value: “(“}, //…] function tokenizer(code) { // 用于定位当前代码中的位置 let current = 0; // tokens用于保存截取的token let tokens = []; while (current < code.length) { // char保存 code中current位置的字符 let char = code[current]; //@1: 判断当前是否为 ‘(‘ if (char === “(“) {
    1. // 如果是左括号,就向 tokens.push新的{type: "parent", value: "("}
    2. tokens.push({
    3. type: "parent",
    4. value: "(",
    5. });
    6. current++;
    7. continue;
    } //@2: 判断当前是否为 ‘)’ if (char === “)”) {
    1. // 如果是左括号,就向 tokens.push新的{type: "parent", value: "("}
    2. tokens.push({
    3. type: "parent",
    4. value: ")",
    5. });
    6. current++;
    7. continue;
    } //@3: 检查空格符,如果是空格,就使用continue,结束本次循环,直接进入下次循环 const WHITESPACE = /\s/; if (WHITESPACE.test(char)) {
    1. current++;
    2. continue;
    } //@4: 检查数字 const NUMBERS = /[0-9]/; if (NUMBERS.test(char)) {
    1. // value作为存储多个数字的中间物
    2. let value = "";
    3. while (NUMBERS.test(char)) {
    4. value += char;
    5. char = code[++current];
    6. }
    7. tokens.push({ type: "number", value: value });
    8. continue;
    } // 接下来处理有双引号包括起来的文本 (concat “aaa” “bbb”) //@5: 首先 检查 “ 双引号 if (char === ‘“‘) {
    1. let value = "";
    2. // 跳过左侧的双引号,不把它加入到token中
    3. char = code[++current];
    4. // 遍历字符,直到第二个双引号
    5. while (char !== '"') {
    6. value += char;
    7. char = code[++current];
    8. }
    9. char = code[++current];
    10. tokens.push({ type: "string", value: value });
    11. continue;
    } //@6: 检查 a-z 字符类型,处理非双引号包裹的字符,该类字符具有特殊含义的函数 (add 2 5) let LETTERS = /[a-z]/i; if (LETTERS.test(char)) {
    1. let value = "";
    2. while (LETTERS.test(char)) {
    3. value += char;
    4. char = code[++current];
    5. }
    6. tokens.push({ type: "name", value: value });
    7. continue;
    } // 到这里还没被匹配到的字符,就抛出异常,并退出程序 throw new TypeError(“Invalid character” + char); } return tokens; } // 词法分析:接收tokens数组,然后将其转换为AST // [{type: “parent”, value: “(“}] => {type: “Program”, body: […]} function parser(tokens) { let current = 0; // 创建递归函数,这里面要深度优先进行遍历 function walk() { let token = tokens[current]; // token的类型为number if (token.type === “number”) {
    1. current++;
    2. return {
    3. type: "NumberLiteral",
    4. value: token.value,
    5. };
    } // token的类型为string, if (token.type === “string”) {
    1. current++;
    2. return {
    3. type: "StringLiteral",
    4. value: token.value,
    5. };
    } // 处理CallExpressions,当遇到一个左括号时就开始处理 if (token.type === “parent” && token.value === “(“) {
    1. // 递增current变量,直接跳过左括号,在AST中并不关注它
    2. token = tokens[++current];
    3. // 创建节点node,类型为CallExpression,并将name设置为token的value,因为左括号后的下一个token就是函数名
    4. let node = {
    5. type: "CallExpression",
    6. name: token.value,
    7. params: [],
    8. };
    9. // 递增current,跳过刚才的函数名token
    10. token = tokens[++current];
    11. // 创建while循环,直到token是右括号时停止循环
    12. while (
    13. token.type !== "parent" ||
    14. (token.type === "parent" && token.value !== ")")
    15. ) {
    16. // 调用walk 函数,并将它返回的节点 加入node.params
    17. node.params.push(walk());
    18. token = tokens[current];
    19. }
    20. // 递增current,跳过右括号
    21. current++;
    22. return node;
    } throw new TypeError(“Invalid token type: “ + token.type); } let ast = { type: “Program”, body: [], }; while (current < tokens.length) { ast.body.push(walk()); } return ast; } // 经过词法分析和语法分析,接着定义traverser函数,它接收AST和visitor[定义的是转换规则]作为参数 function traverser(ast, visitor) { function traverseArray(array, visitor) { array.forEach((child) => {
    1. traverseNode(child, visitor);
    }); } function traverseNode(node, parent) { //首先判断node节点的类型,在visitor中是否存在对应的方法 let methods = visitor[node.type]; // 如果存在 enter 方法,则调用它,并以 node和parent作为参数 if (methods && methods.enter) {
    1. methods.enter(node, parent);
    } // 按照节点类型分别处理 switch (node.type) {
    1. case "Program":
    2. traverseArray(node.body, node);
    3. break;
    4. case "CallExpression":
    5. traverseArray(node.params, node);
    6. break;
    7. case "NumberLiteral":
    8. case "StringLiteral":
    9. break;
    10. default:
    11. throw new Error("unknown node type : " + node.type);
    } if (methods && methods.exit) {
    1. methods.exit(node, parent);
    } } traverseNode(ast, null); } // 第二步:代码转换 transformation function transformer(ast) { // 创建新的AST语法结构 let newAst = { type: “Program”, body: [], }; // _context是旧的AST上对新AST的引用 ast._context = newAst.body; // 调用traverser函数,并传入AST和visitor traverser(ast, { NumberLiteral: {
    1. // 使用enter方法访问
    2. enter(node, parent) {
    3. parent._context.push({
    4. type: "NumberLiteral",
    5. value: node.value,
    6. });
    7. },
    }, StringLiteral: {
    1. enter(node, parent) {
    2. parent._context.push({
    3. type: "StringLiteral",
    4. value: node.value,
    5. });
    6. },
    }, CallExpression: {
    1. enter(node, parent) {
    2. let expression = {
    3. type: "CallExpression",
    4. callee: {
    5. type: "Identifier",
    6. name: node.name,
    7. },
    8. arguments: [],
    9. };
    10. node._context = expression.arguments;
    11. // 判断父节点是否为CallExpression,如果不是
    12. if (parent.type !== "CallExpression") {
    13. // 这样设置的原因:在js中顶层CallExpression实际是声明
    14. expression = {
    15. type: "ExpressionStatement",
    16. expression: expression,
    17. };
    18. }
    19. parent._context.push(expression);
    20. },
    }, }); return newAst; }

// 第三步:代码生成 function codeGenerator(node) { switch (node.type) { // 遇到Program节点,使用codeGenerator遍历body中的每个节点,并对得到的数组以换行符为参数进行 join 操作 case “Program”: return node.body.map(codeGenerator).join(“\n”); // 遇到ExpressionStatement类型节点,则使用codeGenerator 处理node.expression 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 Error(node.type + “Error”); } }

// 最后创建compiler编译函数 function compiler(node) { let tokens = tokenizer(node); let ast = parser(tokens); let newAst = transformer(ast, tokens); let output = codeGenerator(newAst); return output; }

let str = “(add 2 (subtract 4 2) (plus 8 10))”; console.dir(compiler(str), { depth: null, });

  1. [链接](https://github.com/shenshuai89/the-super-tiny-compiler)
  2. <a name="Ykqr7"></a>
  3. ## 借助npm包实现编译器
  4. 安装包`esprima、estraverse、escodegen`<br />`yarn add esprima、estraverse、escodegen -D`
  5. ```javascript
  6. const esprima = require("esprima");
  7. const estraverse = require("estraverse");
  8. const escodegen = require("escodegen");
  9. const source = "async function foo(){}";
  10. let ast = esprima.parse(source);
  11. console.log(ast, "ast---------");
  12. let indent = 0;
  13. const padding = () => " ".repeat(indent);
  14. // 第二个参数是visitor,表示怎么定义新的语法规范
  15. estraverse.traverse(ast, {
  16. enter(node) {
  17. console.log(padding() + "enter " + node.type);
  18. if (node.type === "FunctionDeclaration") {
  19. //如果是函数,就把函数名转为大写形式
  20. node.id.name = node.id.name.toUpperCase();
  21. }
  22. indent += 2;
  23. },
  24. leave(node) {
  25. indent -= 2;
  26. console.log(padding() + "leave " + node.type);
  27. },
  28. });
  29. let newCode = escodegen.generate(ast);
  30. console.log("newCode", newCode);