文法与分析
文法是用一组产生式规则定义字符串集合的简洁工具。把文法交给解析器生成器(parser generator),就能自动得到一棵具体语法树;再把它翻译成抽象语法树,程序就可以直接处理结构化的数据。
什么是文法?
在 6.102 中,文法(grammar)是一种形式化记法,用来描述某个语言中所有合法字符串的集合。它由以下要素组成:
- 非终结符(nonterminal):可以被进一步展开的抽象符号。
- 终结符(terminal):语言中实际出现的字符或词法单元。
- 产生式(production):把非终结符重写为终结符与非终结符序列的规则。
- 起始符号:推导的起点。
文法:一组产生式规则,定义了哪些字符串属于该语言。每个产生式把一个非终结符映射到终结符与非终结符的一个序列。
文法的运算符
为了让文法写得更紧凑,我们使用几种常用运算符:
- 重复
*:匹配零次或多次。 - 连接(空格):顺序匹配两个部分。
- 并集
|:匹配左边或右边。 - 此外常用
?(零次或一次)、+(一次或多次)、{n}(恰好 n 次)、[...](字符类)、[^...](否定字符类)。
例如,一个简化算术表达式文法可以写成:
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 不支持左递归,需改写文法。