“抽象语法树(AST)是抽象语法的一种树形表示。每个节点标记一个语法构造,按顺序排列的子节点表示该构造的直接组成部分;标识符与字面量等原子构造通常成为叶节点。若抽象语法含 [ e ::= n…”
形式陈述 ​
抽象语法用构造子说明表达式由哪些部分组成,而不把空格、注释、括号拼写等表面细节当作表达式的核心结构。设
这定义了由上述构造子有限次生成的最小项集合
例如 let x = e1 in e2。本条目约定它把
具体文本通过词法分析与解析得到抽象项;一个表达式也可以携带源码位置等辅助信息,但这些信息是否影响项的相等,应与核心语法分开规定。抽象语法树是表示这种项结构的一种数据结构,抽象语法本身则是语言所允许的数学对象。
直觉
读 1 + (2 * 3) 时,括号帮我们判断先做哪一步。解析完成后,只要保存“根是加法,右孩子是乘法”,就已经保留了组合关系,不必再把左右括号当成执行对象保存。因此 1 + (2 * 3) 与 1+2*3 在通常优先级下可以得到相同抽象项。
“抽象”也不表示任意忽略差别。1 + 2 * 3 与 (1 + 2) * 3 的组合顺序不同,必须得到不同的项;
因此,抽象语法保留的是后续规则需要辨认的结构。类型检查器要知道某处是不是函数调用,解释器要知道先求哪个子表达式,优化器要知道变量绑定在哪里;这些信息不能因为最终显示结果相同就被随意合并。
例子与边界
同一套语法贯穿解析与求值 ​
表达式 1 + 2 * 3 对应
这个项的根是加法,乘法与它的两个参数整体构成右侧子项。let x = 2 in x + 3 的主体则是
设环境
这里假设所需的自由变量都有绑定;let 的作用域约定。语言的求值意义由这些规则补上,并非由树形天然决定。
绑定使名字比较变得不够 ​
自由变量集合也按结构递归计算。字面量没有自由变量,
例如 let x = y in x + z 的自由变量为 let w = y in w + z,二者
若要把其中自由的 let x = x in x + z:赋值右边的新 let 的作用域内,所以仍自由。若替换的是自由的 let w = y in w + x,否则会把新放入的自由
树、共享表示与语法糖 ​
AST 常以不可变树实现;相同子项也可以共享存储,形成 DAG。只要解释为展开后的项,共享可以减少内存而不改变这个抽象对象。但“共享表示”不自动等于“只求值一次”:在有副作用的语言里,增加缓存或改变求值次数可能改变程序行为,必须另行证明转换正确。
处理绑定时,de Bruijn 索引按绑定距离表示变量,高阶抽象语法借用宿主语言的绑定机制。它们是在解决表示和替换问题,不是允许省略作用域定义。宏展开或语法糖消解还可能把源语言 AST 转成另一套核心语言项;两个层次应有各自的构造子与明确的转换函数。
推论与应用
构造子决定递归与归纳的分支 ​
要定义表达式大小,可以给字面量和变量记 let 也记
变量绑定、操作语义和类型系统都沿着这个结构定义判断。以“自由变量均在环境中有值,则上述整数表达式可以求值”为例,let 情形需要先为
编译器的每个阶段还应标明自己接收哪一层语法。源码到 AST 的解析主要解决表面结构,AST 到核心语言的消解处理语法糖,后续优化再证明所需的语义保持。把这些转换混成一次“去掉多余符号”,会掩盖绑定、求值次序和错误行为是否被保留的问题。
参考资料
- Robert Harper,Practical Foundations for Programming Languages,第 2 版,作者页面,第 1–2 章:抽象语法、绑定结构与归纳定义。
- Benjamin C. Pierce,Types and Programming Languages,第 3–5 章:项、操作语义、变量绑定及捕获规避替换。
- Andrew W. Appel,Modern Compiler Implementation in ML,第 3–4 章:具体语法分析与抽象语法表示的接口。