答案:该javaScript编译器将lisp风格函数调用转换为C风格,通过四步实现:词法分析将输入拆为词元;语法分析构建AST;转换器修改AST结构;代码生成器输出目标字符串。示例输入(add 2 (subtract 4 2))被正确转为add(2, subtract(4, 2)),展示了编译器核心流程:解析→转换→生成,帮助理解AST在Babel等工具中的作用。

要实现一个简单的 javascript 编译器,我们不需要从零造轮子,但可以借助 AST(抽象语法树)来理解编译过程的核心步骤:词法分析、语法分析、转换和代码生成。下面是一个极简的“编译器”,它将类似 Lisp 风格的函数调用转换为 C 语言风格的函数调用。
例如:
输入(Lisp 风格):
(add 2 (subtract 4 2))
立即学习“Java免费学习笔记(深入)”;
输出(C 风格):
add(2, subtract(4, 2))
1. 词法分析(Tokenizer)
将输入字符串拆分为“词元”(tokens),比如括号、标识符、数字等。
function tokenizer(input) { let current = 0; const tokens = []; while (current < input.length) { let char = input[current]; if (char === '(') { tokens.push({ type: 'paren', value: '(' }); current++; continue; } if (char === ')') { tokens.push({ type: 'paren', value: ')' }); current++; continue; } // 跳过空白字符 if (/s/.test(char)) { current++; continue; } // 匹配数字(支持多位) if (/[0-9]/.test(char)) { let value = ''; while (/[0-9]/.test(char)) { value += char; char = input[++current]; } tokens.push({ type: 'number', value }); continue; } // 匹配字母(用于函数名如 add, subtract) if (/[a-z]/i.test(char)) { let value = ''; while (/[a-z]/i.test(char)) { value += char; char = input[++current]; } tokens.push({ type: 'name', value }); continue; } throw new TypeError('未知字符: ' + char); } return tokens; }
2. 语法分析(Parser)
将词元列表转换为抽象语法树(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 === 'paren' && token.value === '(') { token = tokens[++current]; // 跳过 '(' // 下一个是函数名 let node = { type: 'CallExpression', name: token.value, params: [], }; token = tokens[++current]; // 跳过函数名 // 处理参数,直到遇到右括号 while (token.type !== 'paren' || token.value !== ')') { node.params.push(walk()); token = tokens[current]; } current++; // 跳过 ')' return node; } throw new TypeError('意外的 token: ' + token.value); } // 构建根节点 const ast = { type: 'Program', body: [], }; while (current < tokens.length) { ast.body.push(walk()); } return ast; }
3. 转换(transformer)
遍历 AST 并生成新的 AST 结构(目标结构)。
function transformer(ast) { const newAst = { type: 'Program', body: [], }; ast._context = newAst.body; function traverse(node, parent) { if (node.type === 'NumberLiteral') { parent._context.push({ type: 'NumberLiteral', value: node.value, }); } if (node.type === 'CallExpression') { let expression = { type: 'CallExpression', callee: { type: 'Identifier', name: node.name, }, arguments: [], }; node._context = expression.arguments; parent._context.push(expression); } if (node.type === 'Program') { node.body.forEach(child => { traverse(child, node); }); } if (node.type === 'CallExpression') { node.params.forEach(child => { traverse(child, node); }); } } traverse(ast, null); return newAst; }
4. 代码生成(Code Generator)
将新 AST 转换为目标代码字符串。
function codeGenerator(node) { if (node.type === 'Program') { return node.body.map(codeGenerator).join('n'); } if (node.type === 'Identifier') { return node.name; } if (node.type === 'NumberLiteral') { return node.value; } if (node.type === 'CallExpression') { const args = node.arguments.map(codeGenerator).join(', '); return `${codeGenerator(node.callee)}(${args})`; } throw new TypeError('未支持的节点类型: ' + node.type); }
把所有部分组合起来:
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))'; console.log(compiler(input)); // 输出: add(2, subtract(4, 2)) </font>
基本上就这些。这个简单编译器展示了现代编译器(如 Babel、typescript)的基本流程:解析 → 转换 → 生成。虽然功能极简,但它帮助你理解 AST 的作用和编译原理的核心思想。不复杂但容易忽略细节,比如上下文管理和递归遍历。掌握这些,再看 babel 插件或自定义 DSL 就会清晰很多。


