= GEP-2: AST 构建器支持 .元数据 **** [horizontal,options="compact"] *编号*:: GEP-2 *标题*:: AST 构建器支持 *版本*:: 8 *类型*:: 特性 *目标*:: Groovy 1.7 *状态*:: 最终 *评论*:: 已包含在 Groovy 中,但部分 _被宏取代_ *负责人*:: Hamlet D'Arcy *创建日期*:: 2009-04-01 *最后修改日期* :: 2018-10-12 **** == 摘要 Groovy 1.6 引入了执行局部和全局 AST (抽象语法树) 转换的能力,允许用户在 Groovy 代码编译时读取和修改其 AST。在 Groovy 中读取 AST 中的信息相对容易。核心库提供了一个强类型的访问器,称为 GroovyCodeVisitor。可以通过 ASTNode 子类型提供的 API 读取和修改节点。编写新的 AST 节点则不那么简单。从源代码生成的 AST 并不总是显而易见的,并且使用构造函数调用来生成节点树可能会很冗长。本 GEP 提出一个 ASTBuilder 对象,允许用户轻松创建 AST。ASTBuilder 对象允许从以下方式创建 AST: * 包含 Groovy 源代码的字符串 * 包含 Groovy 源代码的闭包 * 包含 AST 创建 DSL 的闭包 所有三种方法共享相同的 API:实例化一个构建器对象并调用一个 build* 方法。 == 方法 === 从字符串创建 ASTNode 最简单的实现方法是为 AST 构建器提供一个 API,该 API 接受一个 String 并返回 List: ``` def builder = new AstBuilder() Liststatements = builder.buildFromString( CompilePhase.CONVERSION, true, """ println "Hello World" """ ) ``` * phase 参数告诉构建器从哪个阶段返回 AST。此参数是可选的,默认的 CompilePhase 是 CLASS_GENERATION。这为常见情况提供了更清晰的 API,之所以选择 CLASS_GENERATION 是因为在后期阶段有更多的类型可用。 * "statementsOnly" 布尔参数是一个可选参数,它告诉构建器丢弃生成的顶级 Script ClassNode。默认为 true。 * 最后一个 String 参数是输入 * 构建器返回 List上述示例生成以下 AST: ``` BlockStatement -> ExpressionStatement -> MethodCallExpression -> VariableExpression -> ConstantExpression -> ArgumentListExpression -> ConstantExpression ``` ==== 替代方案 * 曾考虑为该功能提供某种 AST 模板。考虑以下示例: ``` def astTemplate = builder.buildAst ( "println $txt" ).head() def constant = builder.buildAst ( "To be, or not to be: that is the question" ).head() def methodCallExpression = astTemplate.apply(txt: constant) // 方法调用表达式不包含 println "To be ... " ``` 这种模板方法增加了可能不会使用的复杂性。它重载了 GString 的 $ 运算符,因为它在这里仅与 ASTNode 类型的对象一起使用,但在 GStrings 中通常与任何 Object 类型一起使用。此外,模板方法可能会造成优先级混淆。考虑 source = "$expr * y",然后 $expr 绑定到 "x+a"。结果是 "x + a * y",这可能是无意的。目前,AST 构建器不包含此类功能。 === 从代码块创建 ASTNode 从代码块创建 AST 是一个有用的 API。 ``` AstBuilder builder = new AstBuilder() def statementBlock = builder.buildFromCode (CompilePhase.CONVERSION, true) { println "Hello World" } ``` * 在 Groovy 源代码中表达 Groovy 源代码似乎是最自然的编写方式(而不是将 Groovy 源代码放入字符串中)。 * 某些 IDE 支持自然可用(高亮显示等),但 IDE 警告对于变量作用域规则会产生误导 * "ASTNode from String" 中的相同问题和规则适用于此版本的 phase 和 statementsOnly 属性 * 提供了与 String 构建器类似的 API,除了 code 属性接受在 Closure 上下文中合法的任何代码块。 * 从闭包转换为 AST 是通过全局编译器转换执行的。这要求 AstBuilder 引用是强类型的,以便触发全局注解。 上述示例生成以下 AST: ``` BlockStatement -> ExpressionStatement -> MethodCallExpression -> VariableExpression -> ConstantExpression -> ArgumentListExpression -> ConstantExpression ``` ==== 替代方案 如果使用 @ASTSource 注解,那么让用户在构建器之外重用该注解将非常容易。考虑以下示例: ``` @AstSource(CompilePhase.CONVERSION) Listsource = { println "compiled on: ${new Date()}" } ``` 这个选项似乎很有帮助;然而,目前还不支持局部变量上的注解。此方法将不予实现。 === 从伪规范创建 ASTNode 有条件地构建 AST,例如插入 if 语句或循环,在基于字符串或代码的构建器中不容易实现。考虑这个示例: ``` def builder = new AstBuilder() Liststatements = builder.buildFromSpec { methodCall { variable "this" constant "println" argumentList { if (locale == "US") constant "Hello" if (locale == "FR") constant "Bonjour" else constant "Ni hao" } } } ``` 这个库类在几个方面很有用: * 在 AST 构建器中使用条件或循环可能会很常见 * 在任何其他方法中都难以创建 Field 或 Method 引用 * 仅使用 @Newify 注解并不能充分改进语法 * 这种结构减轻了区分 Statement 和 Expression 的需要,因为这些词已从方法名称中删除 * 这种方法不需要 phase 或 statementsOnly 属性 * 许多表达式接受 ClassNode 类型,该类型包装了一个 Class。ClassNode 的语法是只传递一个 Class 实例,构建器会自动将其包装在 ClassNode 中。 ==== 问题 * ASTNode 子类型上的构造函数参数列表可能很长,这种方法消除了 IDE 帮助的可能性。这是构建器所需付出的代价,计划在 1.7 中实现的构建器元数据功能可能会缓解这个问题。 * 从伪规范创建 AST 的类应该实现,使其不创建当前 AST 类型的镜像类层次结构。这将迫使对 AST 类型的所有更改都在两个地方执行:一次在 ASTNode 子类中,一次在此构建器中。如果这不可能,那么至少 AST 层次结构不会频繁更改。 * 几种 ASTNode 类型具有相同类型的构造函数签名:最常见的是 (Expression, Expression, Expression)。这意味着 DSL 中的参数是顺序相关的,并且以错误的顺序指定参数不会创建异常,但会在运行时导致截然不同的结果。这在邮件列表中有充分的文档。 * 指定 Parameter 对象的语法在邮件列表中有文档。 * 少数 ASTNode 类型与语言关键字存在命名冲突。例如,ClassExpression 类型不能缩写为“class”,IfStatement 不能缩减为“if”。这在邮件列表中有充分的文档。 * 参数具有默认值并可以是可变参数。需要提出一个合适的语法。 * 有时需要在 DSL 中切换构造函数参数的顺序。例如,考虑 SwitchStatement(Expression expression, ListcaseStatements, Expression defaultStatement)。DSL 的当前语法对参数施加了一种可变参数的刚性:列表只是通过重复元素隐含的。因此,将 SwitchStatement 的中间参数设为列表是有问题的,因为转换构造函数的自然方式是使其变为 (Expression expression, CaseStatement... caseStatements, Expression default),这是不可能的。这在邮件列表中有充分的文档。 ==== 替代方案 Template Haskell 和 Boo 为 AST 构建语句提供了特殊语法。准引用(或牛津引用)可用于触发 AST 构建操作: ``` ConstantExpression exp = [| "Hello World" |] ``` 这些语言还提供了一个拼接运算符 $(...),用于将 AST 转换回代码。这不属于 AstBuilder 的工作范围。 == 参考资料和有用链接 https://marc.info/?l=groovy-dev&m=123880759618095&w=2[groovy-dev: Groovy AST 构建器讨论] https://marc.info/?l=groovy-dev&m=124187435114834&w=2[groovy-dev: GEP-2 AST 构建器“从规范”的几个问题] https://en.wikipedia.org/wiki/Template_Haskell[Template Haskell] https://web.archive.org/web/20090213045341/http://blogs.codehaus.org:80/people/bamboo/archives/001593_boo_meta_methods.html[Boo 元方法] https://github.com/cython/cython/wiki/enhancements-metaprogramming[Cython 元编程提案] 作者 Martin C Martin - 包含一些用例的精彩介绍 === JIRA 问题:此功能取决于允许局部变量上的注解:https://issues.apache.org/jira/browse/GROOVY-3481[GROOVY-3481] == 更新历史 7 (2009-06-17):: 从 Codehaus wiki 提取的版本 8 (2018-10-11):: 添加了关于宏的评论

GEP-2