CLASS 12 · 2026-03-16 · 11-13 递归与解析

文法与分析

文法是用一组产生式规则定义字符串集合的简洁工具。把文法交给解析器生成器(parser generator),就能自动得到一棵具体语法树;再把它翻译成抽象语法树,程序就可以直接处理结构化的数据。

什么是文法?

在 6.102 中,文法(grammar)是一种形式化记法,用来描述某个语言中所有合法字符串的集合。它由以下要素组成:

文法:一组产生式规则,定义了哪些字符串属于该语言。每个产生式把一个非终结符映射到终结符与非终结符的一个序列。

文法的运算符

为了让文法写得更紧凑,我们使用几种常用运算符:

例如,一个简化算术表达式文法可以写成:

expr    = number ('+' number)*
number  = digit+
digit   = [0-9]

解析树

把文法应用到具体字符串上,就得到一棵解析树(parse tree)。树的内部节点是非终结符,叶子节点是终结符,整个结构展示了字符串如何由文法推导出来。

解析树:表示一个字符串如何由某条文法生成的树形结构,记录了每一步产生式的应用。

正则表达式

正则表达式是正则文法的紧凑记法。它擅长描述词法层面的模式,比如标识符、数字、邮箱地址。但正则表达式不能处理任意嵌套的结构(比如匹配括号的表达式),因为正则文法没有「栈记忆」。

// 正则表达式示例:匹配简化邮箱
const email = /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/;
console.log(email.test("ada@mit.edu")); // true

上下文无关文法

用完整的文法(而不仅是正则表达式)可以表达的语言,称为上下文无关语言(context-free languages)。它们由上下文无关文法(CFG)定义,能处理递归嵌套——这正是编程语言语法的基础。

解析器生成器

解析器生成器(parser generator):读入一文法,自动生成一个解析器——一个把字符串转换为解析树(或 AST)的程序。

6.102 使用 ParserLib。它接受一个文法字符串,编译出一个可复用的解析器对象。

生成解析器

import { Parser } from 'parserlib';

const grammar = `
  expr = number ('+' number)* ;
    number = digit+ ;
     digit = [0-9] ;
`;
const parser = new Parser(grammar);
const tree = parser.parse("12+34+5");

构造抽象语法树

解析器输出的是具体语法树(parse tree),它忠实地反映文法,但包含很多与求值无关的细节(括号、分隔符)。我们通常把它翻译成更精简的抽象语法树(AST)。

// 把解析树翻译成 AST
interface Expr { evaluate(): number; }
class Num implements Expr { constructor(public n: number){} evaluate(){return this.n;} }
class Add implements Expr {
  constructor(public left: Expr, public right: Expr){}
  evaluate(){ return this.left.evaluate() + this.right.evaluate(); }
}

错误处理

当字符串不符合文法时,ParserLib 会抛出 ParseError。健壮的程序应该捕获它并向用户给出清晰的错误位置和原因。

左递归与 ParserLib 的限制

ParserLib 生成的是自顶向下递归下降(top-down recursive descent)解析器。如果文法中存在左递归(产生式右侧以自身开头),解析器会无限循环。例如:

// 左递归(ParserLib 不支持)
expr = expr '+' number ;  // ← 无限循环!

// 改写为右递归或循环形式
expr = number ('+' number)* ;

因此使用 ParserLib 时,必须把左递归改写为等价的右递归或重复形式。

关键要点

  • 文法用产生式、非终结符、终结符和运算符定义字符串集合。
  • 正则正则表达式是正则文法的紧凑记法,不能表达任意嵌套。
  • 生成器解析器生成器读文法自动生成解析器,6.102 使用 ParserLib。
  • AST把具体语法树翻译成抽象语法树,便于后续处理。
  • 限制ParserLib 不支持左递归,需改写文法。