您不需要在每次归约时都构建一个节点,并且您构建的节点不需要包含每个被归约的符号。减少的符号也不需要以与解析相同的顺序出现。
在许多情况下,AST 是完整解析树的简化,对应于上述内容。
简单示例,对于表达式语法,使用类似 yacc 的解析器生成器:
expr: term { $$ = $1; /* see below */ }
| expr '+' term { $$ = new_sum_node($1, $3); }
term: factor { $$ = $1; /* see below */ }
| term '*' factor { $$ = new_product_node($1, $3); }
factor: '(' expr ')' { $$ = $2; /* See below */ }
| ID { $$ = new_variable_node($1); }
| NUMBER { $$ = new_literal_node($1); }
AST 被构建为非终结符的语义值。这些函数new_*_node
应返回指定类型的新分配节点。(这里我们假设有一些机制可以从指针推断出它是什么类型的节点。例如,可以使用子类型和虚函数。)
在 Yacc(和类似的解析器生成器)中,每个符号(终端或非终端)都有一个关联的“值”,每个产生式都有一个关联的动作,当产生式减少时执行。产生式的动作可以分配被减少的非终结符的“值”。该动作可以利用右侧组件的“值”,因为每个都是终端(其值由扫描仪设置)或已经减少的非终端。实际上,这是一个S 属性语法。
上面的一些减少根本没有出现在 AST 中。特别是,单位缩减 (expr:term
和term:factor
) 仅通过右侧的 AST。类似地,括号减少term:'(' expr ')'
只是通过 AST 的expr
,结果括号有效地从 AST 中消失。(这在所有语言中都不正确;在某些语言中,明显冗余的括号实际上会影响语义,您需要创建一个 AST 节点来记录事实。)
在 Yacc 中,$$ = $1
如果未指定任何操作,则默认减少操作,并且由于大多数单位减少从 AST 中消除,因此它们通常会在没有减少操作的情况下显示。出于教学目的,我将它们明确表示;在实践中,你不应该那样做。