我一直在阅读一些口译/编译器的工作方式,而令我感到困惑的一个方面是AST和CST之间的区别。我的理解是,解析器生成CST,然后将其交给语义分析器,后者将其转换为AST。但是,我的理解是语义分析器只是确保遵守规则。我真的不明白为什么它会进行任何更改以使其抽象而不是具体。
关于语义分析器,我是否缺少某些东西,或者AST和CST之间的区别有些人为?
我一直在阅读一些口译/编译器的工作方式,而令我感到困惑的一个方面是AST和CST之间的区别。我的理解是,解析器生成CST,然后将其交给语义分析器,后者将其转换为AST。但是,我的理解是语义分析器只是确保遵守规则。我真的不明白为什么它会进行任何更改以使其抽象而不是具体。
关于语义分析器,我是否缺少某些东西,或者AST和CST之间的区别有些人为?
Answers:
一个具体的语法树完全以已解析的形式表示源文本。通常,它符合定义源语言的上下文无关语法。
但是,具体的语法和树有很多事情对于使源文本能够明确地进行解析是必不可少的,但对实际含义没有帮助。例如,要实现运算符优先级,您的CFG通常具有多个级别的表达式组件(项,因子等),而运算符将它们连接在不同的级别(您添加项以获取表达式,项由可选倍数的因子组成)等)。但是,要真正解释或编译语言,则不需要此方法。您只需要具有运算符和操作数的Expression节点。抽象语法树是将具体语法树简化为代表程序含义所需的实际操作的结果。该树的定义要简单得多,因此在以后的执行阶段更易于处理。
您通常不需要实际构建具体的语法树。YACC(或Antlr,Menhir或其他任何东西)语法中的动作例程可以直接构建抽象语法树,因此具体语法树仅作为表示源文本的语法结构的概念实体存在。
一个具体的语法树与语法规则所说的语法相匹配。抽象语法树的目的是对“语法树”中的基本内容进行“简单”表示。
AST IMHO的真正价值在于它比CST小,因此处理时间更少。(您可能会说,谁在乎?但是我使用的工具可以同时容纳数千万个节点!)。
大多数支持生成语法树的解析器生成器都坚持要求您亲自指定它们的生成方式,前提是您的树节点将比CST更“简单”(因为程序员很漂亮,因此它们通常是正确的)懒)。可以说这意味着您不必编写更少的树访问器功能代码,这也很有价值,因为它可以最大程度地减少工程精力。当您有3500条规则(例如,对于COBOL)时,这很重要。这种“简单”的特性导致了“小”的良好特性。
但是拥有这样的AST会带来一个不存在的问题:它与语法不匹配,现在您必须在脑海中追踪这两个问题。而且当3500个规则语法中有1500个AST节点时,这很重要。而且,如果语法不断发展(它们总是会做的!),那么您现在有两套庞大的东西可以保持同步。
另一个解决方案是让解析器为您简单地构建CST节点并使用它们。在构建语法时,这是一个巨大的优势:无需发明1500个特殊的AST节点来建模3500个语法规则。只需考虑树与语法是同构的。从语法工程师的角度来看,这是完全没有头脑的,这使他可以专注于正确地编写语法并将其深深扎根于自己的内心。可以说,您必须编写更多的节点访问者规则,但是可以对其进行管理。稍后对此进行更多讨论。
我们该怎么做 DMS软件再造工具套件是根据(GLR)解析过程的结果自动构建CST。然后,DMS出于空间效率的原因,通过消除无值的终端(关键字,标点符号),语义上无用的一元产品,并为语法规则对形成列表,自动构建一个“压缩的” CST,这些列表包括:
L = e ;
L = L e ;
L2 = e2 ;
L2 = L2 ',' e2 ;
以及此类形式的各种变体。您根据语法规则和虚拟CST进行思考;该工具在压缩的表示形式上运行。轻松动脑,运行时更快/更小。
值得注意的是,以这种方式构建的压缩CST看起来很像是您可能手工设计的AST(请参见示例链接)。特别是,压缩的CST不会携带任何只是具体语法的节点。有点尴尬:例如,虽然在表达式子语法中经典找到的'('和')'的具体节点不在树中,但“括号节点”确实出现在压缩的CST中,必须进行处理。真正的AST不会有这个。似乎不必为指定AST构造而付出的便利,这似乎是一个很小的代价。树的文档始终可用且正确:语法就是文档。
我们如何避免“额外访客”?我们并不完全是,但是DMS提供了一个AST库,该库可以遍历AST并透明地处理CST和AST之间的差异。DMS还提供了一个“属性语法”评估器(AGE),该评估器是一种用于在树上上下传递经过计算的节点的值的方法。AGE处理所有的树表示问题,因此工具工程师只担心直接在语法规则本身上有效地编写计算。最后,DMS还提供了“表面语法”模式,该模式允许语法中的代码片段用于查找特定类型的子树,而无需了解所涉及的大多数节点类型。
另一个答案之一是,如果您要构建可以重新生成源代码的工具,则AST必须与CST相匹配。这不是真的,但是如果您具有CST节点,则重新生成源要容易得多。 DMS会自动生成大多数prettyprinter,因为它可以同时访问这两个:-}
底线:AST适用于物理上和概念上的小型。CST的自动AST构造提供了两者,并且可以避免跟踪两个不同的集合的问题。
EDIT 2015年3月: 链接到以这种方式构建的CST与“ AST”示例
这基于Terrence Parr的Expression Evaluator语法。
此示例的语法:
grammar Expr002;
options
{
output=AST;
ASTLabelType=CommonTree; // type of $stat.tree ref etc...
}
prog : ( stat )+ ;
stat : expr NEWLINE -> expr
| ID '=' expr NEWLINE -> ^('=' ID expr)
| NEWLINE ->
;
expr : multExpr (( '+'^ | '-'^ ) multExpr)*
;
multExpr
: atom ('*'^ atom)*
;
atom : INT
| ID
| '('! expr ')'!
;
ID : ('a'..'z' | 'A'..'Z' )+ ;
INT : '0'..'9'+ ;
NEWLINE : '\r'? '\n' ;
WS : ( ' ' | '\t' )+ { skip(); } ;
输入项
x=1
y=2
3*(x+y)
解析树
解析树是输入的具体表示。解析树保留输入的所有信息。空框代表空格,即行尾。
AST
AST是输入的抽象表示。请注意,因为关联是从树结构派生的,所以AST中不存在括号。
编辑
有关详细的解释,请参见编译器和编译器生成器pg。23
在具体语法树如下语言的语法规则。在语法中,通常用两个规则定义“表达式列表”
从字面上遵循,这两个规则为程序中出现的任何表达式列表赋予了梳状形状。
该抽象语法树是这便于进一步的操作形式。它以一种对理解程序含义的人有意义的方式代表事物,而不仅仅是理解程序的编写方式。上面的表达式列表(可以是函数的参数列表)可以方便地表示为表达式的向量,因为对于静态分析而言,最好具有明确可用的表达式总数,并能够通过其表达式访问每个表达式指数。
这是没有区别的区别。
AST通常被解释为一种通过丢弃词汇内容来近似编程语言表达语义的方法。例如,在无上下文语法中,您可以编写以下EBNF规则
term: atom (('*' | '/') term )*
而在AST情况下,您只能使用mul_rule和div_rule来表示正确的算术运算。
那些规则不能首先引入语法吗?当然。您可以通过将上述规则分解为用于模仿上述AST节点的更具体的规则来重写上述紧凑和抽象的规则:
term: mul_rule | div_rule
mul_rule: atom ('*' term)*
div_rule: atom ('/' term)*
现在,当您考虑自上而下的解析时,第二个术语会在mul_rule和div_rule之间引入FIRST / FIRST冲突,这是LL(1)解析器无法处理的。第一个规则形式是第二个规则形式的左因子版本,可有效消除结构。您必须在此处使用LL(1)支付一定的奖励。
因此,AST是一种临时补充,用于修复语法和解析器的不足。CST-> AST转换是一种重构动作。当在语法树中存储其他逗号或冒号时,不会有人打扰。相反,有些作者将它们改装为AST,因为他们喜欢使用AST进行重构,而不是同时维护各种树或编写其他推理引擎。程序员懒惰是有充分理由的。实际上,它们将词法分析收集的偶数行和列信息存储在AST中,用于错误报告。确实非常抽象。
CST(具体语法树)是语法(程序编写规则)的树形表示。根据编译器体系结构,解析器可以使用它来生成AST。
AST(抽象语法树)是已解析源的树表示,由编译器的解析器部分生成。它存储有关令牌+语法的信息。
根据您的编译器的体系结构,CST可用于生成AST。可以说CST演变为AST。或者,AST是更丰富的CST。
可以在此链接上找到更多的解释:http : //eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees#id6