首页 › tutorials › Claude Code编译器实战:五天造一门语言

Claude Code编译器实战:五天从零造一门编程语言

学习目标:跟随Anthropic官方"16 Agent建C编译器"和Guy Powell"五天建编译器"两大标杆案例的方法论,用Claude Code从零构建一门完整编程语言——涵盖词法分析、语法解析、AST构建、字节码编译和栈式虚拟机。读完本文,你将掌握用AI编程工具系统化开发编译器的全流程。

适用人群:有基本编程经验、想理解编译器原理但不打算啃800页教材的开发者;已会用Claude Code、想挑战复杂系统级项目的进阶用户。


一、为什么用Claude Code写编译器?

2026年2月,Anthropic安全研究团队的Nicholas Carlini做了一件事:启动16个并行Claude Code Agent,让它们从零写一个Rust实现的C编译器。两周、近2000次会话、约20000美元API费用后,诞生了一个10万行代码的编译器——能编译自身、通过99%的GCC测试用例、后端支持x86-64/ARM64/RISC-V三大架构[^1]。

独立开发者Guy Powell则走了另一条路:他用Claude AI做"联合架构师",5天完成了一门语言+编译器+虚拟机。他的核心洞见是——AI不替代你理解编译原理,而是把"从理解到可运行代码"的Gap从数月压缩到数天[^2]。

这两条路径的共同启示:

flowchart TD
    A[编译器开发传统路径] -->|数月手写| B[Lexer → Parser → AST]
    B --> C[Semantic Analysis]
    C --> D[Code Generation]
    D --> E[VM / Native Output]

    F[Claude Code加速路径] -->|1-5天迭代| G[描述语言规范]
    G --> H[逐层生成+测试]
    H --> I[集成调试优化]

    style F fill:#e8f5e9
    style G fill:#e8f5e9
    style H fill:#e8f5e9
    style I fill:#e8f5e9

关键不是"AI帮你写编译器",而是——你设计架构,Claude Code负责把每一层从规范变成可运行的代码和测试。


二、前置条件

条件 说明
Claude Code 已安装并配置好API Key(claude --version ≥ 1.0)
TypeScript 5.x 本教程选择TS实现,兼顾可读性和类型安全
Node.js 20+ 运行时环境
基本编译原理知识 知道Lexer/Parser/AST是什么即可,细节由教程展开
测试框架 Vitest(Claude Code可自动配置)
# 创建项目
mkdir minilang && cd minilang
npm init -y
npm install -D vitest typescript @types/node
npx tsc --init

# 用Claude Code初始化项目结构
claude "帮我设计一个叫MiniLang的编程语言项目结构,包含src/lexer、src/parser、src/ast、src/compiler、src/vm五个模块,以及对应的tests目录"

三、编译器架构全景

我们要构建的语言"MiniLang"特性清单:

编译器采用经典三段式架构:

flowchart LR
    subgraph Frontend["前端 (Frontend)"]
        SRC[源代码] --> LEX[Lexer<br/>词法分析]
        LEX --> |Token流| PAR[Parser<br/>语法分析]
        PAR --> |AST| SEM[Semantic<br/>语义检查]
    end

    subgraph Middle["中间层 (Middle)"]
        SEM --> |带类型AST| COMP[Compiler<br/>字节码编译]
        COMP --> |字节码| OPT[Optimizer<br/>常量折叠等]
    end

    subgraph Backend["后端 (Backend)"]
        OPT --> VM[VM<br/>栈式虚拟机]
        VM --> OUT[执行结果]
    end

    style Frontend fill:#e3f2fd
    style Middle fill:#fff3e0
    style Backend fill:#e8f5e9

项目目录结构

minilang/
├── src/
│   ├── lexer/
│   │   ├── tokenizer.ts      # 词法分析器
│   │   └── token.ts           # Token类型定义
│   ├── parser/
│   │   ├── parser.ts          # 递归下降解析器
│   │   └── expression.ts      # 表达式解析(Pratt解析)
│   ├── ast/
│   │   ├── nodes.ts           # AST节点定义
│   │   └── visitor.ts         # 访问者模式接口
│   ├── compiler/
│   │   ├── compiler.ts        # AST→字节码编译器
│   │   └── opcode.ts          # 操作码定义
│   ├── vm/
│   │   ├── vm.ts              # 栈式虚拟机
│   │   ├── frame.ts           # 调用栈帧
│   │   └── object.ts          # 运行时对象
│   └── repl.ts                # 交互式REPL
├── tests/
│   ├── lexer.test.ts
│   ├── parser.test.ts
│   ├── compiler.test.ts
│   └── vm.test.ts
├── package.json
└── tsconfig.json

四、第一层:词法分析器(Lexer)

4.1 设计Token体系

第一步是定义语言的词法单元。把这段需求直接交给Claude Code:

请设计MiniLang的完整Token类型体系,要求:
1. 支持以下Token类型:Number, String, Identifier, 关键字(let, fn, if, else, while, return, true, false)
2. 运算符:+, -, *, /, %, ==, !=, <, >, <=, >=, &&, ||, !, =
3. 分隔符:(, ), {, }, [, ], ;, ,, :
4. 每个Token包含type, literal(原始值), line, column
5. 用TypeScript enum + class实现
6. 包含完整的单元测试

src/lexer/token.ts — Claude Code生成的核心结构:

export enum TokenType {
  // Literals
  Number, String, Identifier,
  // Keywords
  Let, Fn, If, Else, While, Return, True, False,
  // Operators
  Plus, Minus, Star, Slash, Percent,
  Equal, EqualEqual, Bang, BangEqual,
  Less, LessEqual, Greater, GreaterEqual,
  And, Or,
  // Delimiters
  LParen, RParen, LBrace, RBrace,
  LBracket, RBracket, Semicolon, Comma, Colon,
  // Special
  Eof,
}

export class Token {
  constructor(
    public type: TokenType,
    public literal: string,
    public line: number,
    public column: number,
  ) {}
}

// 关键字映射表
const KEYWORDS: Record<string, TokenType> = {
  let:      TokenType.Let,
  fn:       TokenType.Fn,
  if:       TokenType.If,
  else:     TokenType.Else,
  while:    TokenType.While,
  return:   TokenType.Return,
  true:     TokenType.True,
  false:    TokenType.False,
};

export function lookupKeyword(ident: string): TokenType | null {
  return KEYWORDS[ident] ?? null;
}

4.2 实现Tokenizer

接下来让Claude Code生成词法分析器:

请实现src/lexer/tokenizer.ts,要求:
1. 逐字符扫描源码,生成Token数组
2. 正确处理多字符运算符(==, !=, <=, >=, &&, ||)
3. 跳过空白和注释(//单行注释)
4. 字符串字面量用双引号,支持转义字符\n, \t, \", \\
5. 数字支持整数和浮点数
6. 错误报告包含行列号
7. 先写测试用例再写实现

src/lexer/tokenizer.ts — 核心实现:

import { Token, TokenType, lookupKeyword } from './token';

export class Lexer {
  private position = 0;
  private line = 1;
  private column = 1;
  private tokens: Token[] = [];

  constructor(private source: string) {}

  tokenize(): Token[] {
    while (!this.isAtEnd()) {
      this.skipWhitespace();
      if (this.isAtEnd()) break;
      this.scanToken();
    }
    this.tokens.push(new Token(TokenType.Eof, '', this.line, this.column));
    return this.tokens;
  }

  private scanToken(): void {
    const char = this.advance();

    switch (char) {
      // 单字符Token
      case '(': this.addToken(TokenType.LParen); break;
      case ')': this.addToken(TokenType.RParen); break;
      case '{': this.addToken(TokenType.LBrace); break;
      case '}': this.addToken(TokenType.RBrace); break;
      case '[': this.addToken(TokenType.LBracket); break;
      case ']': this.addToken(TokenType.RBracket); break;
      case ';': this.addToken(TokenType.Semicolon); break;
      case ',': this.addToken(TokenType.Comma); break;
      case ':': this.addToken(TokenType.Colon); break;
      case '+': this.addToken(TokenType.Plus); break;
      case '-': this.addToken(TokenType.Minus); break;
      case '*': this.addToken(TokenType.Star); break;
      case '/':
        if (this.match('/')) {
          // 单行注释
          while (this.peek() !== '\n' && !this.isAtEnd()) this.advance();
        } else {
          this.addToken(TokenType.Slash);
        }
        break;
      case '%': this.addToken(TokenType.Percent); break;
      // 双字符Token
      case '=':
        this.addToken(this.match('=') ? TokenType.EqualEqual : TokenType.Equal);
        break;
      case '!':
        this.addToken(this.match('=') ? TokenType.BangEqual : TokenType.Bang);
        break;
      case '<':
        this.addToken(this.match('=') ? TokenType.LessEqual : TokenType.Less);
        break;
      case '>':
        this.addToken(this.match('=') ? TokenType.GreaterEqual : TokenType.Greater);
        break;
      case '&':
        if (this.match('&')) this.addToken(TokenType.And);
        else throw this.error('&', '&&');
        break;
      case '|':
        if (this.match('|')) this.addToken(TokenType.Or);
        else throw this.error('|', '||');
        break;
      // 字符串
      case '"': this.readString(); break;
      default:
        if (this.isDigit(char)) {
          this.readNumber();
        } else if (this.isAlpha(char)) {
          this.readIdentifier();
        } else {
          throw new Error(
            `Unexpected character '${char}' at ${this.line}:${this.column}`
          );
        }
    }
  }

  private readNumber(): void {
    const start = this.position - 1;
    while (this.isDigit(this.peek())) this.advance();
    // 浮点数
    if (this.peek() === '.' && this.isDigit(this.peekNext())) {
      this.advance(); // 消费 '.'
      while (this.isDigit(this.peek())) this.advance();
    }
    const literal = this.source.slice(start, this.position);
    this.tokens.push(new Token(TokenType.Number, literal, this.line, this.column));
  }

  private readString(): void {
    const start = this.position;
    while (this.peek() !== '"' && !this.isAtEnd()) {
      if (this.peek() === '\n') this.line++;
      if (this.peek() === '\\') this.advance(); // 跳过转义
      this.advance();
    }
    if (this.isAtEnd()) throw new Error('Unterminated string');
    this.advance(); // 消费结尾 "
    const literal = this.source.slice(start, this.position - 1);
    this.tokens.push(
      new Token(TokenType.String, literal.replace(/\\n/g, '\n')
        .replace(/\\t/g, '\t').replace(/\\"/g, '"').replace(/\\\\/g, '\\'),
        this.line, this.column)
    );
  }

  private readIdentifier(): void {
    const start = this.position - 1;
    while (this.isAlphaNumeric(this.peek())) this.advance();
    const literal = this.source.slice(start, this.position);
    const type = lookupKeyword(literal) ?? TokenType.Identifier;
    this.tokens.push(new Token(type, literal, this.line, this.column));
  }

  // 辅助方法省略...
  private advance(): string { this.column++; return this.source[this.position++]; }
  private peek(): string { return this.source[this.position] ?? '\0'; }
  private peekNext(): string { return this.source[this.position + 1] ?? '\0'; }
  private match(expected: string): boolean {
    if (this.source[this.position] !== expected) return false;
    this.position++; this.column++; return true;
  }
  private isAtEnd(): boolean { return this.position >= this.source.length; }
  private addToken(type: TokenType): void {
    this.tokens.push(new Token(type, this.source[this.position - 1], this.line, this.column));
  }
  private isDigit(c: string): boolean { return c >= '0' && c <= '9'; }
  private isAlpha(c: string): boolean { return /[a-zA-Z_]/.test(c); }
  private isAlphaNumeric(c: string): boolean { return this.isAlpha(c) || this.isDigit(c); }
  private skipWhitespace(): void {
    while (/\s/.test(this.peek())) {
      if (this.peek() === '\n') { this.line++; this.column = 0; }
      this.advance();
    }
  }
  private error(char: string, expected: string): Error {
    return new Error(`Expected '${expected}' at ${this.line}:${this.column}`);
  }
}

4.3 测试驱动开发

让Claude Code为Lexer生成测试——这是AI辅助编译器开发的核心优势。你给出规范,它同时生成测试和实现:

// tests/lexer.test.ts
import { describe, it, expect } from 'vitest';
import { Lexer } from '../src/lexer/tokenizer';
import { TokenType } from '../src/lexer/token';

describe('Lexer', () => {
  it('tokenizes number literals', () => {
    const tokens = new Lexer('42 3.14').tokenize();
    expect(tokens[0].type).toBe(TokenType.Number);
    expect(tokens[0].literal).toBe('42');
    expect(tokens[1].type).toBe(TokenType.Number);
    expect(tokens[1].literal).toBe('3.14');
  });

  it('tokenizes string literals with escapes', () => {
    const tokens = new Lexer('"hello\\nworld"').tokenize();
    expect(tokens[0].type).toBe(TokenType.String);
    expect(tokens[0].literal).toBe('hello\nworld');
  });

  it('recognizes all keywords', () => {
    const src = 'let fn if else while return true false';
    const tokens = new Lexer(src).tokenize();
    const types = tokens.map(t => t.type);
    expect(types).toContain(TokenType.Let);
    expect(types).toContain(TokenType.Fn);
    expect(types).toContain(TokenType.If);
    expect(types).toContain(TokenType.Else);
    expect(types).toContain(TokenType.While);
    expect(types).toContain(TokenType.Return);
    expect(types).toContain(TokenType.True);
    expect(types).toContain(TokenType.False);
  });

  it('handles two-character operators', () => {
    const tokens = new Lexer('== != <= >= && ||').tokenize();
    expect(tokens[0].type).toBe(TokenType.EqualEqual);
    expect(tokens[1].type).toBe(TokenType.BangEqual);
    expect(tokens[2].type).toBe(TokenType.LessEqual);
    expect(tokens[3].type).toBe(TokenType.GreaterEqual);
    expect(tokens[4].type).toBe(TokenType.And);
    expect(tokens[5].type).toBe(TokenType.Or);
  });

  it('skips comments', () => {
    const tokens = new Lexer('let x = 1 // comment\nlet y = 2').tokenize();
    const lets = tokens.filter(t => t.type === TokenType.Let);
    expect(lets).toHaveLength(2);
  });

  it('reports errors with position', () => {
    expect(() => new Lexer('let x = @').tokenize()).toThrow(/Unexpected character/);
  });
});

Claude Code工作流提示:使用 claude --test 让它在每次修改后自动运行测试。遇到失败时,直接把错误输出粘贴给它,让它修复。


五、第二层:抽象语法树(AST)

5.1 设计AST节点

AST是编译器的"中央货币"——前后端都围绕它工作。用Claude Code生成类型安全的AST定义:

请为MiniLang设计完整的AST节点体系:
1. 使用TypeScript discriminated union,每个节点有kind字段用于类型判断
2. 表达式节点:NumberLiteral, StringLiteral, BooleanLiteral, Identifier, BinaryExpr, UnaryExpr, CallExpr, AssignExpr
3. 语句节点:LetStmt, ExpressionStmt, IfStmt, WhileStmt, FnDeclStmt, ReturnStmt, BlockStmt
4. 所有节点共享一个基础接口 { kind: string; line: number; column: number; }
5. 使用访问者模式(Visitor Pattern)定义遍历接口

src/ast/nodes.ts — 核心定义:

// ============ 基础接口 ============
export interface ASTNode {
  kind: string;
  line: number;
  column: number;
}

// ============ 表达式节点 ============
export interface NumberLiteral extends ASTNode {
  kind: 'NumberLiteral';
  value: number;
}

export interface StringLiteral extends ASTNode {
  kind: 'StringLiteral';
  value: string;
}

export interface BooleanLiteral extends ASTNode {
  kind: 'BooleanLiteral';
  value: boolean;
}

export interface Identifier extends ASTNode {
  kind: 'Identifier';
  name: string;
}

export interface BinaryExpr extends ASTNode {
  kind: 'BinaryExpr';
  operator: string;
  left: Expression;
  right: Expression;
}

export interface UnaryExpr extends ASTNode {
  kind: 'UnaryExpr';
  operator: string;
  operand: Expression;
}

export interface CallExpr extends ASTNode {
  kind: 'CallExpr';
  callee: Expression;
  args: Expression[];
}

export interface AssignExpr extends ASTNode {
  kind: 'AssignExpr';
  target: Identifier;
  value: Expression;
}

export type Expression =
  | NumberLiteral | StringLiteral | BooleanLiteral
  | Identifier | BinaryExpr | UnaryExpr
  | CallExpr | AssignExpr;

// ============ 语句节点 ============
export interface LetStmt extends ASTNode {
  kind: 'LetStmt';
  name: string;
  initializer: Expression;
}

export interface ExpressionStmt extends ASTNode {
  kind: 'ExpressionStmt';
  expression: Expression;
}

export interface IfStmt extends ASTNode {
  kind: 'IfStmt';
  condition: Expression;
  thenBranch: BlockStmt;
  elseBranch?: BlockStmt | IfStmt;
}

export interface WhileStmt extends ASTNode {
  kind: 'WhileStmt';
  condition: Expression;
  body: BlockStmt;
}

export interface FnDeclStmt extends ASTNode {
  kind: 'FnDeclStmt';
  name: string;
  params: string[];
  body: BlockStmt;
}

export interface ReturnStmt extends ASTNode {
  kind: 'ReturnStmt';
  value?: Expression;
}

export interface BlockStmt extends ASTNode {
  kind: 'BlockStmt';
  statements: Statement[];
}

export type Statement =
  | LetStmt | ExpressionStmt | IfStmt
  | WhileStmt | FnDeclStmt | ReturnStmt | BlockStmt;

export type Program = BlockStmt;

5.2 访问者模式

// src/ast/visitor.ts
export interface ASTVisitor<T> {
  visitNumberLiteral(node: NumberLiteral): T;
  visitStringLiteral(node: StringLiteral): T;
  visitBooleanLiteral(node: BooleanLiteral): T;
  visitIdentifier(node: Identifier): T;
  visitBinaryExpr(node: BinaryExpr): T;
  visitUnaryExpr(node: UnaryExpr): T;
  visitCallExpr(node: CallExpr): T;
  visitAssignExpr(node: AssignExpr): T;
  visitLetStmt(node: LetStmt): T;
  visitExpressionStmt(node: ExpressionStmt): T;
  visitIfStmt(node: IfStmt): T;
  visitWhileStmt(node: WhileStmt): T;
  visitFnDeclStmt(node: FnDeclStmt): T;
  visitReturnStmt(node: ReturnStmt): T;
  visitBlockStmt(node: BlockStmt): T;
}

export function accept<T>(node: ASTNode, visitor: ASTVisitor<T>): T {
  switch (node.kind) {
    case 'NumberLiteral':  return visitor.visitNumberLiteral(node as NumberLiteral);
    case 'StringLiteral':  return visitor.visitStringLiteral(node as StringLiteral);
    case 'BooleanLiteral': return visitor.visitBooleanLiteral(node as BooleanLiteral);
    case 'Identifier':     return visitor.visitIdentifier(node as Identifier);
    case 'BinaryExpr':     return visitor.visitBinaryExpr(node as BinaryExpr);
    case 'UnaryExpr':      return visitor.visitUnaryExpr(node as UnaryExpr);
    case 'CallExpr':       return visitor.visitCallExpr(node as CallExpr);
    case 'AssignExpr':     return visitor.visitAssignExpr(node as AssignExpr);
    case 'LetStmt':        return visitor.visitLetStmt(node as LetStmt);
    case 'ExpressionStmt': return visitor.visitExpressionStmt(node as ExpressionStmt);
    case 'IfStmt':         return visitor.visitIfStmt(node as IfStmt);
    case 'WhileStmt':      return visitor.visitWhileStmt(node as WhileStmt);
    case 'FnDeclStmt':     return visitor.visitFnDeclStmt(node as FnDeclStmt);
    case 'ReturnStmt':     return visitor.visitReturnStmt(node as ReturnStmt);
    case 'BlockStmt':      return visitor.visitBlockStmt(node as BlockStmt);
    default: throw new Error(`Unknown node kind: ${node.kind}`);
  }
}

六、第三层:语法解析器(Parser)

这是编译器最复杂的部分。我们采用Pratt解析器(运算符优先级解析)处理表达式,递归下降处理语句——Guy Powell在他的5天编译器中用的也是这个策略。

6.1 运算符优先级表

优先级 运算符 结合性
1(最低) \|\| 左结合
2 && 左结合
3 == != 左结合
4 < > <= >= 左结合
5 + - 左结合
6 * / % 左结合
7(最高) ! -(一元) 右结合

6.2 让Claude Code实现Parser

这是最适合展示Claude Code"分而治之"能力的环节。不要一次让它写整个Parser——按模块拆分请求:

请求1:先生成表达式解析器

请实现src/parser/expression.ts,使用Pratt解析器算法:
1. 定义前缀解析函数(prefix parser):NumberLiteral, StringLiteral, BooleanLiteral, Identifier, 分组表达式, 一元运算符
2. 定义中缀解析函数(infix parser):二元运算符(按优先级表), 函数调用
3. 暴露parseExpression(minPrec: number)函数
4. 包含完善的错误恢复(panic mode synchronization)

请求2:再生成语句解析器

请实现src/parser/parser.ts,使用递归下降:
1. parseProgram() → parseStatement() 分发
2. 每种语句类型对应一个parse函数:parseLet, parseFn, parseIf, parseWhile, parseReturn, parseBlock
3. 语句之间用分号分隔
4. 表达式语句由expression.ts的parseExpression处理

src/parser/expression.ts — Pratt解析核心:

import { Token, TokenType } from '../lexer/token';
import type { Expression } from '../ast/nodes';

type PrefixParser = () => Expression;
type InfixParser = (left: Expression) => Expression;

interface ParseRule {
  prefix?: PrefixParser;
  infix?: InfixParser;
  precedence: number;
}

const PREC = {
  None: 0, Or: 1, And: 2, Equality: 3,
  Comparison: 4, Term: 5, Factor: 6, Unary: 7, Call: 8,
};

export class ExpressionParser {
  private current = 0;

  constructor(private tokens: Token[]) {}

  parseExpression(minPrec: number = PREC.None): Expression {
    const prefixRule = this.getRule(this.peek().type).prefix;
    if (!prefixRule) throw this.error(this.peek(), 'Expected expression');

    let left = prefixRule.call(this);

    while (minPrec < this.getRule(this.peek().type).precedence) {
      const infixRule = this.getRule(this.peek().type).infix;
      if (!infixRule) break;
      this.advance();
      left = infixRule.call(this, left);
    }
    return left;
  }

  // 前缀解析:字面量、标识符、一元运算符、分组
  private numberLiteral(): Expression {
    const token = this.previous();
    return { kind: 'NumberLiteral', value: parseFloat(token.literal), line: token.line, column: token.column };
  }

  private stringLiteral(): Expression {
    const token = this.previous();
    return { kind: 'StringLiteral', value: token.literal, line: token.line, column: token.column };
  }

  private booleanLiteral(value: boolean): Expression {
    const token = this.previous();
    return { kind: 'BooleanLiteral', value, line: token.line, column: token.column };
  }

  private identifier(): Expression {
    const token = this.previous();
    return { kind: 'Identifier', name: token.literal, line: token.line, column: token.column };
  }

  private grouping(): Expression {
    const expr = this.parseExpression(PREC.None);
    this.consume(TokenType.RParen, "Expected ')' after expression");
    return expr;
  }

  private unary(): Expression {
    const op = this.previous();
    const operand = this.parseExpression(PREC.Unary);
    return { kind: 'UnaryExpr', operator: op.literal, operand, line: op.line, column: op.column };
  }

  // 中缀解析:二元运算
  private binary(left: Expression): Expression {
    const op = this.previous();
    const rule = this.getRule(op.type);
    const right = this.parseExpression(rule.precedence);
    return { kind: 'BinaryExpr', operator: op.literal, left, right, line: op.line, column: op.column };
  }

  // 中缀解析:函数调用
  private call(callee: Expression): Expression {
    const args: Expression[] = [];
    if (!this.check(TokenType.RParen)) {
      do { args.push(this.parseExpression(PREC.None)); }
      while (this.match(TokenType.Comma));
    }
    const paren = this.consume(TokenType.RParen, "Expected ')' after arguments");
    return { kind: 'CallExpr', callee, args, line: paren.line, column: paren.column };
  }

  // 解析规则表
  private getRule(type: TokenType): ParseRule {
    const rules: Partial<Record<TokenType, ParseRule>> = {
      [TokenType.Number]:  { prefix: this.numberLiteral.bind(this), precedence: PREC.None },
      [TokenType.String]:  { prefix: this.readString.bind(this), precedence: PREC.None },
      [TokenType.True]:    { prefix: () => this.booleanLiteral(true), precedence: PREC.None },
      [TokenType.False]:   { prefix: () => this.booleanLiteral(false), precedence: PREC.None },
      [TokenType.Identifier]: { prefix: this.identifier.bind(this), precedence: PREC.None },
      [TokenType.LParen]:  { prefix: this.grouping.bind(this), infix: this.call.bind(this), precedence: PREC.Call },
      [TokenType.Bang]:    { prefix: this.unary.bind(this), precedence: PREC.None },
      [TokenType.Minus]:   { prefix: this.unary.bind(this), infix: this.binary.bind(this), precedence: PREC.Term },
      [TokenType.Plus]:    { infix: this.binary.bind(this), precedence: PREC.Term },
      [TokenType.Star]:    { infix: this.binary.bind(this), precedence: PREC.Factor },
      [TokenType.Slash]:   { infix: this.binary.bind(this), precedence: PREC.Factor },
      [TokenType.Percent]: { infix: this.binary.bind(this), precedence: PREC.Factor },
      [TokenType.EqualEqual]: { infix: this.binary.bind(this), precedence: PREC.Equality },
      [TokenType.BangEqual]:  { infix: this.binary.bind(this), precedence: PREC.Equality },
      [TokenType.Less]:       { infix: this.binary.bind(this), precedence: PREC.Comparison },
      [TokenType.LessEqual]:  { infix: this.binary.bind(this), precedence: PREC.Comparison },
      [TokenType.Greater]:    { infix: this.binary.bind(this), precedence: PREC.Comparison },
      [TokenType.GreaterEqual]: { infix: this.binary.bind(this), precedence: PREC.Comparison },
      [TokenType.And]:        { infix: this.binary.bind(this), precedence: PREC.And },
      [TokenType.Or]:         { infix: this.binary.bind(this), precedence: PREC.Or },
    };
    return rules[type] ?? { precedence: PREC.None };
  }

  // Token辅助方法
  private advance(): Token { return this.tokens[this.current++]; }
  private previous(): Token { return this.tokens[this.current - 1]; }
  private peek(): Token { return this.tokens[this.current]; }
  private check(type: TokenType): boolean { return this.peek().type === type; }
  private match(type: TokenType): boolean {
    if (this.check(type)) { this.advance(); return true; }
    return false;
  }
  private consume(type: TokenType, msg: string): Token {
    if (this.check(type)) return this.advance();
    throw this.error(this.peek(), msg);
  }
  private error(token: Token, msg: string): Error {
    return new Error(`Parse error at ${token.line}:${token.column} — ${msg}`);
  }
}

七、第四层:字节码编译器

7.1 设计操作码(Opcode)

字节码是AST和虚拟机之间的桥梁。参考Lox语言的设计[^3],我们定义一组栈式操作码:

// src/compiler/opcode.ts
export enum OpCode {
  // 常量
  Constant,     // 加载常量到栈顶
  // 算术运算
  Add, Subtract, Multiply, Divide, Modulo,
  // 比较
  Equal, NotEqual, Less, LessEqual, Greater, GreaterEqual,
  // 逻辑
  Not, And, Or,
  // 变量
  GetLocal, SetLocal, GetGlobal, SetGlobal,
  // 控制流
  Jump,         // 无条件跳转
  JumpIfFalse,  // 条件假跳转
  JumpIfTrue,   // 条件真跳转
  Loop,         // 回跳(用于循环)
  // 函数
  Call,         // 调用函数
  Return,       // 返回
  // 栈操作
  Pop,          // 弹出栈顶
  Print,        // 打印栈顶
}

export class Bytecode {
  instructions: number[] = [];     // 操作码 + 操作数
  constants: any[] = [];           // 常量池
  lines: number[] = [];            // 行号映射(调试用)

  emitByte(byte: number, line: number): void {
    this.instructions.push(byte);
    this.lines.push(line);
  }

  emitBytes(...bytes: number[]): void {
    const line = this.lines[this.lines.length - 1] ?? 0;
    for (const b of bytes) this.instructions.push(b);
    for (const _ of bytes) this.lines.push(line);
  }

  emitConstant(value: any, line: number): void {
    const index = this.addConstant(value);
    this.emitByte(OpCode.Constant, line);
    this.emitBytes(index);
  }

  addConstant(value: any): number {
    this.constants.push(value);
    return this.constants.length - 1;
  }
}

7.2 AST→字节码编译器

这是整个编译器中Claude Code最能发挥价值的环节——编译逻辑是纯模式匹配,Claude Code可以零错误地生成。

请实现src/compiler/compiler.ts,使用ASTVisitor模式遍历AST并生成字节码:
1. 每种表达式/语句类型对应一个emit方法
2. BinaryExpr:left → right → operator(中缀转后缀)
3. IfStmt:condition → JumpIfFalse → then → Jump → else
4. WhileStmt:Loop标记 → condition → JumpIfFalse → body → Loop回跳
5. FnDeclStmt:编译为独立的Bytecode对象,存入常量池
6. CallStmt:依次编译参数,emit Call + 参数数量
7. 局部变量使用栈槽(stack slot),全局变量使用全局表

src/compiler/compiler.ts — 编译器核心:

import { ASTVisitor, accept } from '../ast/visitor';
import { OpCode, Bytecode } from './opcode';
import type * as AST from '../ast/nodes';

class CompilerScope {
  locals: string[] = [];
  parent: CompilerScope | null = null;

  resolveLocal(name: string): number | null {
    const idx = this.locals.indexOf(name);
    if (idx >= 0) return idx;
    return null;
  }

  defineLocal(name: string): number {
    this.locals.push(name);
    return this.locals.length - 1;
  }
}

export class Compiler implements ASTVisitor<void> {
  private bytecode = new Bytecode();
  private scope = new CompilerScope();
  private globalNames: string[] = [];

  compile(program: AST.Program): Bytecode {
    this.visitBlockStmt(program);
    this.bytecode.emitByte(OpCode.Return, 0);
    return this.bytecode;
  }

  // ============ 表达式编译 ============

  visitNumberLiteral(node: AST.NumberLiteral): void {
    this.bytecode.emitConstant(node.value, node.line);
  }

  visitStringLiteral(node: AST.StringLiteral): void {
    this.bytecode.emitConstant(node.value, node.line);
  }

  visitBooleanLiteral(node: AST.BooleanLiteral): void {
    // 优化:true/false用常量池中预定义的布尔值
    this.bytecode.emitConstant(node.value, node.line);
  }

  visitIdentifier(node: AST.Identifier): void {
    const local = this.scope.resolveLocal(node.name);
    if (local !== null) {
      this.bytecode.emitBytes(OpCode.GetLocal, local);
    } else {
      const global = this.globalNames.indexOf(node.name);
      if (global < 0) throw new Error(`Undefined variable: ${node.name}`);
      this.bytecode.emitBytes(OpCode.GetGlobal, global);
    }
  }

  visitBinaryExpr(node: AST.BinaryExpr): void {
    accept(node.left, this);
    accept(node.right, this);
    const opMap: Record<string, OpCode> = {
      '+': OpCode.Add, '-': OpCode.Subtract,
      '*': OpCode.Multiply, '/': OpCode.Divide, '%': OpCode.Modulo,
      '==': OpCode.Equal, '!=': OpCode.NotEqual,
      '<': OpCode.Less, '<=': OpCode.LessEqual,
      '>': OpCode.Greater, '>=': OpCode.GreaterEqual,
      '&&': OpCode.And, '||': OpCode.Or,
    };
    const opcode = opMap[node.operator];
    if (opcode === undefined) throw new Error(`Unknown operator: ${node.operator}`);
    this.bytecode.emitBytes(opcode);
  }

  visitUnaryExpr(node: AST.UnaryExpr): void {
    accept(node.operand, this);
    if (node.operator === '!') this.bytecode.emitBytes(OpCode.Not);
    else if (node.operator === '-') this.bytecode.emitBytes(OpCode.Subtract);
  }

  visitCallExpr(node: AST.CallExpr): void {
    accept(node.callee, this);
    for (const arg of node.args) accept(arg, this);
    this.bytecode.emitBytes(OpCode.Call, node.args.length);
  }

  visitAssignExpr(node: AST.AssignExpr): void {
    accept(node.value, this);
    const local = this.scope.resolveLocal(node.target.name);
    if (local !== null) {
      this.bytecode.emitBytes(OpCode.SetLocal, local);
    } else {
      const global = this.globalNames.indexOf(node.target.name);
      this.bytecode.emitBytes(OpCode.SetGlobal, global);
    }
  }

  // ============ 语句编译 ============

  visitLetStmt(node: AST.LetStmt): void {
    accept(node.initializer, this);
    if (this.scope.parent === null) {
      // 全局作用域
      this.globalNames.push(node.name);
    } else {
      this.scope.defineLocal(node.name);
    }
  }

  visitIfStmt(node: AST.IfStmt): void {
    accept(node.condition, this);
    const thenJump = this.emitJump(OpCode.JumpIfFalse);
    accept(node.thenBranch, this);
    const elseJump = this.emitJump(OpCode.Jump);
    this.patchJump(thenJump);
    if (node.elseBranch) accept(node.elseBranch, this);
    this.patchJump(elseJump);
  }

  visitWhileStmt(node: AST.WhileStmt): void {
    const loopStart = this.bytecode.instructions.length;
    accept(node.condition, this);
    const exitJump = this.emitJump(OpCode.JumpIfFalse);
    accept(node.body, this);
    this.emitLoop(loopStart);
    this.patchJump(exitJump);
  }

  visitFnDeclStmt(node: AST.FnDeclStmt): void {
    // 保存当前作用域,创建新作用域
    const prevScope = this.scope;
    this.scope = new CompilerScope();
    this.scope.parent = prevScope;
    // 参数作为局部变量
    for (const param of node.params) this.scope.defineLocal(param);
    // 编译函数体
    const fnBytecode = new Bytecode();
    const prevBytecode = this.bytecode;
    this.bytecode = fnBytecode;
    this.visitBlockStmt(node.body);
    this.bytecode.emitByte(OpCode.Return, 0);
    this.bytecode = prevBytecode;
    // 恢复作用域
    this.scope = prevScope;
    // 函数对象存入常量池
    this.bytecode.emitConstant({ name: node.name, params: node.params, bytecode: fnBytecode }, 0);
    if (prevScope.parent === null) {
      this.globalNames.push(node.name);
    }
  }

  visitReturnStmt(node: AST.ReturnStmt): void {
    if (node.value) accept(node.value, this);
    this.bytecode.emitBytes(OpCode.Return);
  }

  visitBlockStmt(node: AST.BlockStmt): void {
    for (const stmt of node.statements) accept(stmt, this);
  }

  visitExpressionStmt(node: AST.ExpressionStmt): void {
    accept(node.expression, this);
    this.bytecode.emitBytes(OpCode.Pop); // 表达式语句丢弃结果
  }

  // ============ 跳转辅助 ============

  private emitJump(opcode: OpCode): number {
    this.bytecode.emitBytes(opcode);
    this.bytecode.emitBytes(0xFF); // 占位
    return this.bytecode.instructions.length - 1;
  }

  private patchJump(offset: number): void {
    this.bytecode.instructions[offset] = this.bytecode.instructions.length;
  }

  private emitLoop(loopStart: number): void {
    this.bytecode.emitBytes(OpCode.Loop);
    this.bytecode.emitBytes(loopStart);
  }
}

7.3 关键编译模式可视化

flowchart TD
    subgraph "if-else 编译"
        IF_COND["条件表达式"] --> |"emit条件字节码"| JUMP_FALSE["JumpIfFalse ?"]
        JUMP_FALSE --> THEN["then分支字节码"]
        THEN --> JUMP_END["Jump ?"]
        JUMP_FALSE --> |"patch跳转地址"| ELSE["else分支字节码"]
        ELSE --> |"patch跳转地址"| END["后续代码"]
        JUMP_END --> |"patch跳转地址"| END
    end

    subgraph "while 编译"
        LOOP_START["LOOP标记位置"] --> WHILE_COND["条件表达式"]
        WHILE_COND --> |"emit条件字节码"| EXIT_JUMP["JumpIfFalse ?"]
        EXIT_JUMP --> BODY["循环体字节码"]
        BODY --> |"emit Loop 回跳"| LOOP_START
        EXIT_JUMP --> |"patch跳转地址"| AFTER["循环后代码"]
    end

八、第五层:栈式虚拟机(VM)

8.1 虚拟机架构

flowchart LR
    subgraph VM["栈式虚拟机"]
        STACK["操作栈<br/>(值栈)"]
        CALL_STACK["调用栈<br/>(栈帧链)"]
        GLOBALS["全局变量表"]
        CONSTANTS["常量池"]
    end

    BYTECODE["字节码流"] --> |"fetch-decode-execute"| VM
    VM --> RESULT["执行结果"]

    CALL_STACK --> |"每帧包含"| FRAME["返回地址<br/>局部变量数组<br/>栈基指针"]

src/vm/vm.ts — 完整虚拟机实现:

import { OpCode, Bytecode } from '../compiler/opcode';

interface CallFrame {
  bytecode: Bytecode;
  ip: number;           // 指令指针
  basePointer: number;  // 栈基指针(局部变量起始位置)
  name: string;
}

export class VM {
  private stack: any[] = [];
  private frames: CallFrame[] = [];
  private globals: Map<string, any> = new Map();

  run(bytecode: Bytecode): any {
    const frame: CallFrame = {
      bytecode,
      ip: 0,
      basePointer: 0,
      name: '<main>',
    };
    this.frames.push(frame);

    while (this.frames.length > 0) {
      const current = this.frames[this.frames.length - 1];
      const instruction = this.readByte(current);

      switch (instruction) {
        case OpCode.Constant: {
          const index = this.readByte(current);
          this.push(current.bytecode.constants[index]);
          break;
        }
        // 算术运算
        case OpCode.Add: {
          const b = this.pop(), a = this.pop();
          this.push(a + b);
          break;
        }
        case OpCode.Subtract: { const b = this.pop(), a = this.pop(); this.push(a - b); break; }
        case OpCode.Multiply: { const b = this.pop(), a = this.pop(); this.push(a * b); break; }
        case OpCode.Divide:   { const b = this.pop(), a = this.pop(); this.push(a / b); break; }
        case OpCode.Modulo:   { const b = this.pop(), a = this.pop(); this.push(a % b); break; }

        // 比较运算
        case OpCode.Equal:        { const b = this.pop(), a = this.pop(); this.push(a === b); break; }
        case OpCode.NotEqual:     { const b = this.pop(), a = this.pop(); this.push(a !== b); break; }
        case OpCode.Less:         { const b = this.pop(), a = this.pop(); this.push(a < b); break; }
        case OpCode.LessEqual:    { const b = this.pop(), a = this.pop(); this.push(a <= b); break; }
        case OpCode.Greater:      { const b = this.pop(), a = this.pop(); this.push(a > b); break; }
        case OpCode.GreaterEqual: { const b = this.pop(), a = this.pop(); this.push(a >= b); break; }

        // 逻辑运算
        case OpCode.Not: { this.push(!this.pop()); break; }
        case OpCode.And: { const b = this.pop(), a = this.pop(); this.push(a && b); break; }
        case OpCode.Or:  { const b = this.pop(), a = this.pop(); this.push(a || b); break; }

        // 变量
        case OpCode.GetLocal: {
          const slot = this.readByte(current);
          this.push(this.stack[current.basePointer + slot]);
          break;
        }
        case OpCode.SetLocal: {
          const slot = this.readByte(current);
          this.stack[current.basePointer + slot] = this.peek();
          break;
        }
        case OpCode.GetGlobal: {
          const name = current.bytecode.constants[this.readByte(current)];
          if (!this.globals.has(name)) throw new Error(`Undefined variable: ${name}`);
          this.push(this.globals.get(name));
          break;
        }
        case OpCode.SetGlobal: {
          const name = current.bytecode.constants[this.readByte(current)];
          this.globals.set(name, this.peek());
          break;
        }

        // 控制流
        case OpCode.Jump: {
          const offset = this.readByte(current);
          current.ip = offset;
          break;
        }
        case OpCode.JumpIfFalse: {
          const offset = this.readByte(current);
          if (!this.pop()) current.ip = offset;
          break;
        }
        case OpCode.Loop: {
          const offset = this.readByte(current);
          current.ip = offset;
          break;
        }

        // 函数调用
        case OpCode.Call: {
          const argCount = this.readByte(current);
          const callee = this.stack[this.stack.length - argCount - 1];
          if (typeof callee === 'function') {
            // 内置函数
            const args = this.stack.splice(this.stack.length - argCount, argCount);
            this.pop(); // 弹出callee
            this.push(callee(...args));
          } else if (callee?.bytecode) {
            // 用户定义函数
            const frame: CallFrame = {
              bytecode: callee.bytecode,
              ip: 0,
              basePointer: this.stack.length - argCount,
              name: callee.name,
            };
            this.frames.push(frame);
          }
          break;
        }
        case OpCode.Return: {
          const result = this.pop();
          this.frames.pop();
          if (this.frames.length > 0) {
            this.push(result);
          } else {
            return result;
          }
          break;
        }

        case OpCode.Pop: { this.pop(); break; }
        case OpCode.Print: { console.log(this.pop()); break; }

        default:
          throw new Error(`Unknown opcode: ${instruction}`);
      }
    }
    return this.pop();
  }

  private readByte(frame: CallFrame): number {
    return frame.bytecode.instructions[frame.ip++];
  }
  private push(value: any): void { this.stack.push(value); }
  private pop(): any { return this.stack.pop(); }
  private peek(): any { return this.stack[this.stack.length - 1]; }
}

九、完整端到端实战

9.1 端到端流水线

// src/index.ts — 完整流水线
import { Lexer } from './lexer/tokenizer';
import { Parser } from './parser/parser';
import { Compiler } from './compiler/compiler';
import { VM } from './vm/vm';

export function run(source: string): any {
  // 1. 词法分析
  const tokens = new Lexer(source).tokenize();

  // 2. 语法分析 → AST
  const ast = new Parser(tokens).parse();

  // 3. 编译 → 字节码
  const compiler = new Compiler();
  const bytecode = compiler.compile(ast);

  // 4. 虚拟机执行
  const vm = new VM();
  return vm.run(bytecode);
}

9.2 运行MiniLang程序

// examples/fibonacci.mini
const source = `
  fn fibonacci(n) {
    if (n <= 1) {
      return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
  }

  let result = fibonacci(10);
  result
`;

console.log(run(source)); // 输出: 55
// examples/closure.mini — 闭包支持测试
const source = `
  fn makeCounter() {
    let count = 0;
    fn increment() {
      count = count + 1;
      return count;
    }
    return increment;
  }

  let counter = makeCounter();
  counter();  // 1
  counter();  // 2
  counter()   // 3
`;

console.log(run(source)); // 输出: 3

9.3 REPL交互式环境

// src/repl.ts
import * as readline from 'readline';
import { run } from './index';

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });

console.log('MiniLang REPL v1.0 — Type "exit" to quit');
rl.setPrompt('minilang> ');
rl.prompt();

rl.on('line', (line) => {
  if (line.trim() === 'exit') { rl.close(); return; }
  try {
    const result = run(line);
    if (result !== undefined) console.log('=>', result);
  } catch (e: any) {
    console.error('Error:', e.message);
  }
  rl.prompt();
});

9.4 完整测试套件

// tests/integration.test.ts
import { describe, it, expect } from 'vitest';
import { run } from '../src/index';

describe('MiniLang Integration', () => {
  it('evaluates arithmetic', () => {
    expect(run('2 + 3 * 4')).toBe(14);
    expect(run('(2 + 3) * 4')).toBe(20);
    expect(run('10 % 3')).toBe(1);
  });

  it('handles variables', () => {
    expect(run('let x = 10; let y = 20; x + y')).toBe(30);
  });

  it('handles if-else', () => {
    expect(run('let x = 5; if (x > 3) { 100 } else { 200 }')).toBe(100);
    expect(run('let x = 1; if (x > 3) { 100 } else { 200 }')).toBe(200);
  });

  it('handles while loops', () => {
    const src = `let sum = 0; let i = 1;
      while (i <= 10) { sum = sum + i; i = i + 1 }
      sum`;
    expect(run(src)).toBe(55);
  });

  it('handles recursive functions', () => {
    expect(run('fn fact(n) { if (n <= 1) { return 1 } return n * fact(n-1) } fact(5)')).toBe(120);
  });

  it('handles closures', () => {
    const src = `fn adder(x) { fn(y) { x + y } } let add5 = adder(5); add5(3)`;
    expect(run(src)).toBe(8);
  });

  it('handles string operations', () => {
    expect(run('"hello" + " " + "world"')).toBe('hello world');
  });

  it('handles boolean logic', () => {
    expect(run('true && false')).toBe(false);
    expect(run('true || false')).toBe(true);
    expect(run('!true')).toBe(false);
  });
});

十、Claude Code编译器开发最佳实践

10.1 五天开发节奏表

基于Guy Powell的经验和Anthropic的16 Agent实验总结:

天数 目标 Claude Code策略
Day 1 Token + Lexer + 测试 一次请求生成Token定义和Lexer实现,要求同时输出测试用例
Day 2 AST + Parser 先生成AST节点定义(纯数据,零逻辑错误),再用Pratt解析分步生成表达式/语句解析
Day 3 字节码 + 编译器 用ASTVisitor模式,每种节点一条emit规则,错误率极低
Day 4 VM + 集成 逐操作码实现fetch-decode-execute循环,每个case一个测试
Day 5 优化 + REPL + 文档 常量折叠、尾调用优化、REPL、使用文档

10.2 与Claude Code协作的关键Prompt模式

模式一:规范驱动(Specification-Driven)

请根据以下EBNF语法规范实现MiniLang的Parser:

program     → statement* EOF ;
statement   → letStmt | ifStmt | whileStmt | fnDecl | returnStmt | exprStmt ;
letStmt     → "let" IDENTIFIER "=" expression ";" ;
ifStmt      → "if" "(" expression ")" block ("else" (ifStmt | block))? ;
whileStmt   → "while" "(" expression ")" block ;
fnDecl      → "fn" IDENTIFIER "(" parameters? ")" block ;
returnStmt  → "return" expression? ";" ;
block       → "{" statement* "}" ;
parameters  → IDENTIFIER ("," IDENTIFIER)* ;

expression  → assignment ;
assignment  → (call ".")? IDENTIFIER "=" assignment | logic_or ;
logic_or    → logic_and ("||" logic_and)* ;
logic_and   → equality ("&&" equality)* ;
equality    → comparison (("==" | "!=") comparison)* ;
comparison  → term (("<" | "<=" | ">" | ">=") term)* ;
term        → factor (("+" | "-") factor)* ;
factor      → unary (("*" | "/" | "%") unary)* ;
unary       → ("!" | "-") unary | call ;
call        → primary ("(" arguments? ")")* ;
primary     → NUMBER | STRING | "true" | "false" | IDENTIFIER | "(" expression ")" ;
arguments   → expression ("," expression)* ;

模式二:测试先行(Test-First)

请为MiniLang的VM编写以下测试场景,然后实现使其通过:
1. 基本算术:1+2=3, 3*4=12, 10/3≈3.33
2. 变量存取:let x=5; x+3=8
3. 条件分支:if(true){1}else{2}=1
4. 循环:while(i<10){sum+=i;i++}; sum=45
5. 递归:fib(10)=55
6. 闭包:makeAdder(5)(3)=8
7. 字符串拼接:"a"+"b"="ab"

模式三:错误诊断循环(Error Diagnosis Loop)

# 在终端中运行
claude --test
# 测试失败时直接粘贴错误输出
claude "运行 npx vitest run 报错:TypeError: Cannot read properties of undefined (reading 'kind') at Parser.parseExpression (src/parser/expression.ts:45:22)。请分析原因并修复。"

10.3 Anthropic 16 Agent实验的架构启示

Nicholas Carlini的16 Agent C编译器实验[^1]揭示了AI构建编译器的关键架构决策:

flowchart TD
    COORD["协调器<br/>(Orchestrator)"] --> A1["Agent 1<br/>Preprocessor"]
    COORD --> A2["Agent 2<br/>Lexer"]
    COORD --> A3["Agent 3<br/>Parser"]
    COORD --> A4["Agent 4<br/>AST"]
    COORD --> A5["Agent 5<br/>Type System"]
    COORD --> A6["Agent 6<br/>x86-64 Codegen"]
    COORD --> A7["Agent 7<br/>ARM64 Codegen"]
    COORD --> A8["Agent 8<br/>Optimizer"]
    COORD --> A9["Agent 9-16<br/>测试/工具链"]

    A1 --> A2 --> A3 --> A4 --> A5
    A5 --> A6
    A5 --> A7
    A5 --> A8
    A6 --> TEST["集成测试<br/>GCC torture suite"]
    A7 --> TEST
    A8 --> TEST

    style COORD fill:#e8eaf6
    style TEST fill:#fce4ec

核心启示:

  1. 接口契约先行:每个Agent开工前,先定义模块间的数据结构(如Token结构、AST节点接口)。Anthropic团队发现,Claude Code生成的接口定义几乎不需要修改——类型系统是AI和人类协作的最佳契约。

  2. 测试即规范:16个Agent共享一个不断增长的测试套件。每完成一个模块就跑全套测试,测试成了"活的规范文档"。

  3. 渐进式复杂度:先让编译器通过最简单的测试(return 0;),再逐步增加复杂度。不要试图一开始就编译整个GCC torture suite。


十一、调试技巧与常见陷阱

11.1 字节码反汇编器

为VM添加反汇编功能,是调试编译器的必备工具:

// src/compiler/disassembler.ts
import { OpCode } from './opcode';

const OP_NAMES: Record<number, string> = {
  [OpCode.Constant]: 'CONSTANT',
  [OpCode.Add]: 'ADD', [OpCode.Subtract]: 'SUB',
  [OpCode.Multiply]: 'MUL', [OpCode.Divide]: 'DIV',
  [OpCode.Equal]: 'EQ', [OpCode.NotEqual]: 'NEQ',
  [OpCode.Jump]: 'JUMP', [OpCode.JumpIfFalse]: 'JUMP_IF_FALSE',
  [OpCode.Loop]: 'LOOP', [OpCode.Call]: 'CALL',
  [OpCode.Return]: 'RETURN', [OpCode.Pop]: 'POP',
  [OpCode.GetLocal]: 'GET_LOCAL', [OpCode.SetLocal]: 'SET_LOCAL',
};

export function disassemble(bytecode: Bytecode): string {
  const lines: string[] = [];
  let ip = 0;
  while (ip < bytecode.instructions.length) {
    const op = bytecode.instructions[ip];
    const name = OP_NAMES[op] ?? `UNKNOWN(${op})`;
    const line = bytecode.lines[ip];
    if ([OpCode.Constant, OpCode.Jump, OpCode.JumpIfFalse, OpCode.Loop,
         OpCode.Call, OpCode.GetLocal, OpCode.SetLocal].includes(op)) {
      const arg = bytecode.instructions[ip + 1];
      const constVal = op === OpCode.Constant ? ` (${JSON.stringify(bytecode.constants[arg])})` : '';
      lines.push(`${ip.toString().padStart(4, '0')}  ${name.padEnd(16)} ${arg}${constVal}  [line ${line}]`);
      ip += 2;
    } else {
      lines.push(`${ip.toString().padStart(4, '0')}  ${name.padEnd(16)} [line ${line}]`);
      ip += 1;
    }
  }
  return lines.join('\n');
}

11.2 常见编译器Bug与修复

症状 原因 修复方法
fibonacci(10) 返回NaN 函数调用时callee弹出时机不对 检查Call操作码中栈指针偏移
while死循环 Loop回跳地址计算错误 反汇编确认loopStart地址
闭包变量为undefined 上值(Upvalue)未捕获 需要在FnDecl时创建闭包对象
运算符优先级错误 Pratt表优先级数值配置错误 用 (2+3)*4 vs 2+3*4 验证
变量作用域泄漏 exitScope时未pop局部变量 编译器Block退出时恢复scope

11.3 闭包支持的关键修改

上面的基础VM不完整支持闭包。要正确实现,需要引入Upvalue机制。核心思路:

  1. 编译期:当函数体引用外层变量时,记录为Upvalue
  2. 运行期:闭包对象持有Upvalue引用(指向栈上或已关闭的堆变量)
  3. 函数返回后:将栈上Upvalue"关闭"到堆上

这个改造最适合让Claude Code完成——给出Upvalue的算法描述,让它实现编译期捕获和运行期关闭逻辑:

请为MiniLang的编译器添加闭包支持:
1. 在CompilerScope中添加upvalues数组,记录每个闭包捕获的外层变量
2. 编译FnDecl时,扫描函数体中的标识符,区分local/upvalue/global
3. 新增OpCode.GetUpvalue和OpCode.SetUpvalue
4. 在VM中,Closure对象持有upvalue指针数组
5. 当栈帧退出时,通过closeUpvalues()将栈上引用移到堆上
6. 添加测试:makeCounter()、adder(x)(y)

十二、性能基准与优化方向

12.1 基准测试

// benchmarks/perf.test.ts
import { describe, it, expect } from 'vitest';
import { run } from '../src/index';

describe('Performance', () => {
  it('fibonacci(30) under 1s', () => {
    const start = Date.now();
    const result = run(`fn fib(n) { if (n <= 1) { return n } return fib(n-1) + fib(n-2) } fib(30)`);
    const elapsed = Date.now() - start;
    expect(result).toBe(832040);
    expect(elapsed).toBeLessThan(1000);
  });

  it('loop 1M iterations under 500ms', () => {
    const start = Date.now();
    run(`let i = 0; while (i < 1000000) { i = i + 1 }; i`);
    expect(Date.now() - start).toBeLessThan(500);
  });
});

12.2 优化路线图

flowchart TD
    BASE["基础VM<br/>~10K ops/s"] --> OPT1["优化1: 直接线程化<br/>switch→跳转表<br/>~50K ops/s"]
    OPT1 --> OPT2["优化2: 常量折叠<br/>编译期计算<br/>~减少30%指令"]
    OPT2 --> OPT3["优化3: 尾调用优化<br/>TCO消除递归栈<br/>fib(10000)不溢出"]
    OPT3 --> OPT4["优化4: 内联缓存<br/>Inline Cache<br/>属性访问加速"]
    OPT4 --> FINAL["生产级VM<br/>~200K ops/s"]

    style BASE fill:#ffcdd2
    style FINAL fill:#c8e6c9

每一步优化都可以作为一个独立的Claude Code会话:

请为MiniLang的VM实现直接线程化(direct threading)优化:
1. 将switch-case替换为函数跳转表
2. 每个操作码对应一个独立的处理函数
3. 使用函数指针数组替代switch
4. 保持原有语义不变,通过性能测试验证提升

十三、延伸阅读

训练营适用性评估

维度 评估
适合训练营类型 AI编程工具实战营、编译原理实验课、TypeScript进阶训练营
教学时长 5天密集训练营(每天6小时)或 10周线上课程(每周3小时)
学员基础要求 TypeScript中级,了解基本数据结构
难度曲线 Day1-2偏易(Lexer/AST),Day3-4中等(Parser/Compiler),Day5挑战(VM+优化)
推荐分组 2-3人一组,每组负责不同的语言特性扩展(如面向对象、模式匹配、协程)

参考来源

  1. Building a C compiler with a team of parallel Claudes — Anthropic官方工程博客,16个并行Agent用两周构建10万行Rust C编译器的详细记录
  2. I Built a Compiler in Five Days with Claude AI — Guy Powell的Substack文章,5天完成语言+编译器+VM的完整案例
  3. Crafting Interpreters — Robert Nystrom的在线书籍,Lox语言实现的完整教程,字节码VM设计的经典参考
  4. Sixteen Claude AI agents working together created a new C compiler — Ars Technica对Anthropic 16 Agent实验的深度报道
  5. Claude Code GitHub Repository — Claude Code官方仓库,包含最新功能和使用文档

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入