Skip to content

41. 整合实践:构建完整解析器

我们已经走过了一段漫长而充实的旅程。从一个最基础的词法分析器开始,我们一步步地为 mini-acorn 添加了表达式解析、语句解析、函数、类、模块,最后甚至实现了插件化、Source Map 和错误恢复等高级功能。在之前的章节中,为了聚焦于特定的知识点,我们常常将代码拆分或简化。

现在,是时候将所有的碎片整合起来,看一看我们亲手打造的 mini-acorn 解析器的完整形态了。

本章将作为全书的总结和代码附录。我们将回顾最终的项目结构,并提供核心文件的完整代码清单。这不仅能帮助你巩固所学的全部知识,也为你提供了一个可以作为未来项目起点的、完整可运行的 mini-acorn 实现。

最终项目结构

一个典型的、组织良好的解析器项目结构如下:

mini-acorn/
├── src/
│   ├── tokentype.js      # 定义所有 Token 类型
│   ├── token.js          # Token 类的实现
│   ├── lexer.js          # 词法分析器
│   ├── parser.js         # 语法分析器 (核心)
│   ├── scope.js          # 作用域与符号表管理
│   └── index.js          # 主入口,导出公共 API
├── plugins/              # 插件目录
│   └── ...
├── test/                 # 测试文件
│   └── ...
├── package.json
└── ...
  • tokentype.js: 所有 Token 的“字典”,定义了它们的类型、关键字信息等。
  • token.js: Token 对象的实现,用于承载词法分析的结果。
  • lexer.js: 负责将源代码字符串转化为 Token 流。
  • parser.js: 核心文件,实现了语法分析器 Parser 类,负责将 Token 流构建为 AST。
  • scope.js: 提供了作用域栈和符号表管理的功能。
  • index.js: 项目的公共 API 入口,通常会导出一个 parse 函数,方便外部调用。

核心代码清单

以下是 mini-acorn 几个核心文件的最终整合代码。这份代码融合了我们之前所有章节的知识点。

src/tokentype.js

javascript
export class TokenType {
  constructor(label, conf = {}) {
    this.label = label;
    this.keyword = conf.keyword;
    this.beforeExpr = !!conf.beforeExpr;
    this.startsExpr = !!conf.startsExpr;
    this.isLoop = !!conf.isLoop;
    this.isAssign = !!conf.isAssign;
    this.prefix = !!conf.prefix;
    this.postfix = !!conf.postfix;
    this.binop = conf.binop || null;
  }
}

const keywords = new Map();

function createKeyword(name, options = {}) {
  options.keyword = name;
  const token = new TokenType(name, options);
  keywords.set(name, token);
  return token;
}

export const types = {
  num: new TokenType("num", { startsExpr: true }),
  string: new TokenType("string", { startsExpr: true }),
  name: new TokenType("name", { startsExpr: true }),
  eof: new TokenType("eof"),

  // 标点
  bracketL: new TokenType("[", { beforeExpr: true, startsExpr: true }),
  bracketR: new TokenType("]"),
  braceL: new TokenType("{", { beforeExpr: true, startsExpr: true }),
  braceR: new TokenType("}"),
  parenL: new TokenType("(", { beforeExpr: true, startsExpr: true }),
  parenR: new TokenType(")"),
  comma: new TokenType(",", { beforeExpr: true }),
  semi: new TokenType(";", { beforeExpr: true }),
  colon: new TokenType(":", { beforeExpr: true }),
  dot: new TokenType("."),
  question: new TokenType("?", { beforeExpr: true }),
  arrow: new TokenType("=>", { beforeExpr: true }),
  ellipsis: new TokenType("..."),
  backQuote: new TokenType("`"),
  dollarBraceL: new TokenType("${`, { beforeExpr: true, startsExpr: true }),

  // 操作符
  eq: new TokenType("=", { isAssign: true }),
  assign: new TokenType("_=", { isAssign: true }),
  incDec: new TokenType("++/--", { prefix: true, postfix: true, startsExpr: true }),
  prefix: new TokenType("!/~", { beforeExpr: true, prefix: true, startsExpr: true }),
  logicalOR: new TokenType("||", { beforeExpr: true, binop: 1 }),
  logicalAND: new TokenType("&&", { beforeExpr: true, binop: 2 }),
  bitwiseOR: new TokenType("|", { beforeExpr: true, binop: 3 }),
  bitwiseXOR: new TokenType("^", { beforeExpr: true, binop: 4 }),
  bitwiseAND: new TokenType("&", { beforeExpr: true, binop: 5 }),
  equality: new TokenType("==/!=", { beforeExpr: true, binop: 6 }),
  relational: new TokenType("</>", { beforeExpr: true, binop: 7 }),
  bitShift: new TokenType("<</>>", { beforeExpr: true, binop: 8 }),
  plusMin: new TokenType("+/-", { beforeExpr: true, binop: 9, prefix: true, startsExpr: true }),
  modulo: new TokenType("%", { beforeExpr: true, binop: 10 }),
  star: new TokenType("*", { beforeExpr: true, binop: 10 }),
  slash: new TokenType("/", { beforeExpr: true, binop: 10 }),
  starstar: new TokenType("**", { beforeExpr: true }),

  // 关键字
  _break: createKeyword("break"),
  _case: createKeyword("case", { beforeExpr: true }),
  _catch: createKeyword("catch"),
  _continue: createKeyword("continue"),
  _debugger: createKeyword("debugger"),
  _default: createKeyword("default", { beforeExpr: true }),
  _do: createKeyword("do", { isLoop: true, beforeExpr: true }),
  _else: createKeyword("else", { beforeExpr: true }),
  _finally: createKeyword("finally"),
  _for: createKeyword("for", { isLoop: true }),
  _function: createKeyword("function", { startsExpr: true }),
  _if: createKeyword("if"),
  _return: createKeyword("return", { beforeExpr: true }),
  _switch: createKeyword("switch"),
  _throw: createKeyword("throw", { beforeExpr: true }),
  _try: createKeyword("try"),
  _var: createKeyword("var"),
  _const: createKeyword("const"),
  _let: createKeyword("let"),
  _while: createKeyword("while", { isLoop: true }),
  _with: createKeyword("with"),
  _new: createKeyword("new", { beforeExpr: true, startsExpr: true }),
  _this: createKeyword("this", { startsExpr: true }),
  _super: createKeyword("super", { startsExpr: true }),
  _class: createKeyword("class", { startsExpr: true }),
  _extends: createKeyword("extends", { beforeExpr: true }),
  _export: createKeyword("export"),
  _import: createKeyword("import", { startsExpr: true }),
  _null: createKeyword("null", { startsExpr: true }),
  _true: createKeyword("true", { startsExpr: true }),
  _false: createKeyword("false", { startsExpr: true }),
  _in: createKeyword("in", { beforeExpr: true, binop: 7 }),
  _instanceof: createKeyword("instanceof", { beforeExpr: true, binop: 7 }),
  _typeof: createKeyword("typeof", { beforeExpr: true, prefix: true, startsExpr: true }),
  _void: createKeyword("void", { beforeExpr: true, prefix: true, startsExpr: true }),
  _delete: createKeyword("delete", { beforeExpr: true, prefix: true, startsExpr: true }),
};

src/index.js

javascript
import { Parser } from './parser';

export function parse(input, options) {
  return new Parser(input, options).parse();
}

export { Parser };

src/parser.js (骨架)

由于 parser.js 文件在整合了所有功能后会非常庞大,这里我们只展示它的整体结构和骨架。你可以将之前章节中 pp.(即 Parser.prototype)上的所有方法填充到这个类中,形成最终的完整版本。

javascript
import { Lexer } from './lexer';
import { types as tt } from './tokentype';

export class Parser extends Lexer {
  constructor(input, options) {
    super(input, options);
  }

  parse() {
    const node = this.startNode();
    this.nextToken();
    return this.parseTopLevel(node);
  }

  // --- 核心方法 ---
  parseTopLevel(node) { /* ... */ }
  parseStatement(declaration, topLevel) { /* ... */ }
  parseExpression() { /* ... */ }
  parseExprAtom() { /* ... */ }
  parseExprOps() { /* ... */ }
  parseMaybeUnary() { /* ... */ }
  parseBlock() { /* ... */ }
  parseVarStatement(node, kind) { /* ... */ }
  parseFunction(node, isStatement) { /* ... */ }
  // ... 此处省略了我们在全书中实现的几十个解析方法

  // --- 辅助方法 ---
  next() { /* ... */ }
  eat(type) { /* ... */ }
  expect(type) { /* ... */ }
  startNode() { /* ... */ }
  finishNode(node, type) { /* ... */ }
  raise(pos, message) { /* ... */ }
  unexpected(pos) { /* ... */ }
}

最后的思考:从这里走向何方?

手握一个自己构建的、完整的解析器,你已经拥有了打开编译器世界大门的钥匙。这不仅仅是一段代码,更是一种思维方式和一套解决问题的工具。

  • 深入源码: 你现在已经完全有能力去阅读 Acorn、Babel Parser (Babylon) 甚至 V8 解析器的源码了。你会发现,虽然它们的实现更复杂、优化更极致,但其核心思想与我们的 mini-acorn 是一脉相承的。

  • 构建自己的工具: 是否想过做一个自己的 JSX 方言?或者一种领域特定语言(DSL)?现在,你可以基于 mini-acorn 进行扩展,实现你的想法。

  • 贡献开源: 当你发现 Babel 或 ESLint 的某个解析 Bug 时,你不再是束手无策。你可以尝试定位问题,甚至提交一个 Pull Request,因为你已经懂得了它们是如何工作的。

结语

《mini-acorn.js 解析器实战》的旅程到此正式结束。我们从最基础的 Token 开始,最终构建了一个功能强大、结构清晰的现代 JavaScript 解析器。希望这段旅程不仅让你收获了知识,更点燃了你对技术底层原理的探索热情。

编程世界的大门永远向那些充满好奇、勇于探索的人敞开。愿你在未来的学习和工作中,能够利用在这里学到的知识,创造出更多有价值、有意思的东西。

感谢你的坚持与陪伴。再会!

41. 整合实践:构建完整解析器 has loaded