我一直在阅读有关解释器/编译器如何工作的一些内容,而我感到困惑的一个领域是AST和CST之间的区别。我的理解是解析器生成一个CST,将它交给语义分析器,将其转换为AST。但是,我的理解是语义分析器只是确保遵循规则。我真的不明白为什么它会实际做出任何改变,使其变得抽象而不是具体。
有没有关于语义分析器的东西,或者AST和CST之间的差异有点人为?
答案 0 :(得分:54)
具体的语法树完全以解析的形式表示源文本。通常,它符合定义源语言的无上下文语法。
然而,具体的语法和树有很多东西是使源文本明确可解析所必需的,但却没有实际意义。例如,要实现运算符优先级,您的CFG通常具有多个级别的表达式组件(术语,因子等),运算符将它们连接到不同的级别(您添加术语以获取表达式,术语由可选乘以的因子组成)等)。但是,要实际解释或编译语言,您不需要这样做;您只需要具有运算符和操作数的Expression节点。抽象语法树是将具体语法树简化为实际需要表示程序含义的结果。该树具有更简单的定义,因此在执行的后期阶段更容易处理。
您通常不需要实际构建具体的语法树。您的YACC(或Antlr,或Menhir,或其他......)语法中的动作例程可以直接构建抽象语法树,因此具体语法树仅作为表示源文本的解析结构的概念实体存在。
答案 1 :(得分:28)
具体语法树与语法规则所说的语法相匹配。 抽象语法树的目的是“简单”表示“语法树”中必不可少的内容。
AST恕我直言的实际值是它比CST 小,因此处理时间更短。 (你可能会说,谁在乎呢?但我使用的是一个工具 数以千万计的节点同时存在!)。
大多数支持构建语法树的解析器生成器都坚持要求您在假设树节点比CST“更简单”的情况下亲自指定它们的构建方式(并且,它们通常是正确的,如同程序员非常懒惰)。可以说,这意味着你必须编写更少的树访问者函数,这也是有价值的,因为它最大限度地减少了工程能量。当你有3500条规则(例如,对于COBOL)时,这很重要。而这种“更简单”的方法导致了“小”的良好属性。
但是有这样的AST会产生一个不存在的问题:它与语法不匹配,现在你必须在心理上跟踪它们。当3500规则语法有1500个AST节点时,这很重要。如果语法发展(他们总是这样做!),现在你有两套巨大的东西要保持同步。
另一种解决方案是让解析器只为您构建CST节点并使用它们。构建语法时这是一个巨大的优势:没有必要发明1500个特殊的AST节点来模拟3500语法规则。试想一下树与语法同构。从语法工程师的角度来看,这是完全无脑的,这使他能够专注于正确理解语法,并将其扼杀在心中。可以说,您必须编写更多节点访问者规则,但这可以进行管理。稍后会详细介绍。
我们对DMS Software Reengineering Toolkit的处理是根据(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 generates most of the prettyprinter automatically因为它可以同时访问: - }
底线:ASTs适用于小型,包括物理和概念。 CST的自动AST构造提供了两者,并且可以避免跟踪两个不同集合的问题。
答案 2 :(得分:20)
This blog post可能会有所帮助。
在我看来,AST“抛弃”了许多不会对语义有贡献的中间语法/结构信息。例如,你不在乎3是一个原子是一个术语是一个因素是......当你实现取幂表达式时,你只关心它是3
。
答案 3 :(得分:17)
这是基于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中不存在parens,因为关联可以从树结构中派生。
修改
有关详细说明,请参阅Compilers and Compiler Generators pg。 23
答案 4 :(得分:9)
具体语法树遵循语言语法规则。在语法中,“表达式列表”通常用两个规则定义
按照字面意思,这两条规则给出了程序中出现的任何表达式列表的梳形。
抽象语法树采用便于进一步操作的形式。它以对理解程序含义的人有意义的方式表示事物,而不仅仅是它们的编写方式。上面的表达式列表(可以是函数的参数列表)可以方便地表示为表达式的向量,因为静态分析最好使表达式的总数明确可用并且能够通过其访问每个表达式索引。
答案 5 :(得分:1)
具体语法树包含所有信息,如多余的括号,空格和注释,抽象语法树从这些信息中抽象出来。
注意:很有趣,当你实现一个重构引擎时,你的AST将再次包含所有具体信息,但你会继续将它称为AST,因为它已经成为该领域的标准术语(所以人们可以说它早就失去了原有的含义。)
答案 6 :(得分:1)
这是一个没有区别的差异。
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进行重构而不是同时维护各种树或编写额外的推理引擎。程序员很懒惰是有充分理由的。实际上,他们通过ASTs中的词法分析存储了甚至行和列信息,以便进行错误报告。非常抽象。
答案 7 :(得分:1)
简单来说,AST只包含代码的语义,Parse tree / CST还包含有关代码编写方式的信息。
答案 8 :(得分:-1)
CST(具体语法树)是语法的树形表示(应该如何编写程序的规则)。 根据编译器体系结构,Parser可以使用它来生成AST。
AST(抽象语法树)是Parsed源的树表示,由编译器的Parser部分生成。它存储有关令牌+语法的信息。
根据编译器的体系结构,CST可用于生成AST。可以说CST演变成AST。或者,AST是一个更丰富的CST。
可在此链接中找到更多解释:http://eli.thegreenplace.net/2009/02/16/abstract-vs-concrete-syntax-trees#id6