领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多Groovy 语言是创建领域特定语言 (DSL) 的一个优秀平台。一个好的 DSL 可以使程序更简洁、更具表达力,并提高程序员的生产力。但是,到目前为止,Groovy-Eclipse 的编辑器中没有直接支持这些 DSL。当大量使用 DSL 时,标准 IDE 功能(如内容辅助、搜索、悬停和导航)会失去其价值。一段时间以来,一直可以通过编写 Eclipse 插件来扩展 Groovy-Eclipse,但这是一种重量级的方法,需要具备 Eclipse API 的特定知识。现在,Groovy-Eclipse 支持DSL 描述符 (DSLD),在 Groovy-Eclipse 中支持自定义 DSL 将变得更加容易。
3.m + 2.yd + 2.mi - 1.km
这是一个简单且富有表现力的 DSL,但是当您将其输入 Groovy-Eclipse 中的 Groovy 编辑器时(为简洁起见,假设$url
在其他地方定义)
[caption id="attachment_8774" align="aligncenter" width="179"][/caption]
您会看到下划线并且没有悬停提示,这意味着编辑器无法静态解析 DSL 的表达式。使用 DSLD,可以教会编辑器这些自定义 DSL 背后的一些语义,并提供悬停提示的文档
[caption id="attachment_8775" align="aligncenter" width="683"][/caption]
要为距离 DSL 创建 DSL 描述符,您只需将一个文件添加到 Groovy 项目中,该文件具有.dsld 文件扩展名,并包含以下内容
currentType( subType( Number ) ).accept {
property name:"m", type:"Distance",
doc: """A <code>meter</code> from <a href="$url">$url</a>"""
}
此脚本表示每当编辑器中当前正在评估的类型是java.lang.Number
的子类型时,都向其添加类型为Distance
的'm
'属性。currentType(subType(Number))
部分称为切点,包含对property
的调用的代码块称为贡献块。稍后将详细介绍这些概念。
上面的脚本片段不是完整的 DSLD。它仅添加了'm
'属性。要完成实现,您可以利用 Groovy 语法的全部功能
currentType( subType( Number ) ).accept {
[ m: "meter", yd: "yard", cm: "centimerter", mi: "mile", km: "kilometer"].each {
property name:it.key, type:"Distance",
doc: """A <code>${it.value}</code> from <a href="$url">$url</a>"""
}
}
这个简单的例子表明,一个相对较小的脚本可以创建一些强大的 DSL 支持。
DSLD 增强了 Groovy-Eclipse 的类型推断引擎,该引擎在编辑过程中在后台运行。DSLD 由 IDE 评估,并根据需要由推断引擎查询。
DSLD 脚本包含一组切点,每个切点都与一个或多个贡献块相关联。切点大致描述了需要增强类型推断的位置(即哪些上下文中的哪些类型),而贡献块描述了增强方式(即应添加哪些属性和方法)。
提供了许多切点,并在DSLD 文档中用示例进行了详细描述。随着我们开始了解人们将如何创建脚本以及他们需要进行哪些操作,可用切点的集合可能会在未来版本的 DSLD 中扩展。
贡献块是 Groovy 代码块,通过accept
方法与切点相关联。您可以在贡献块内部执行的两个主要操作是property
(我们之前已介绍过)和method
(将方法添加到贡献块中正在分析的类型)。
术语切点借鉴自面向方面编程 (AOP)。实际上,DSLD可以被认为是一种 AOP 语言。DSLD 与AspectJ等典型 AOP 语言的主要区别在于,DSLD 对正在编辑的程序的抽象语法树进行操作,而像 AspectJ 这样的语言则对已编译程序的 Java 字节码进行操作。
在Codehaus 上的 wiki上提供了完整的 DSLD 文档。在这里,我将简要介绍如何开始使用 DSLD。开始使用
http://dist.codehaus.org/groovy/distributions/greclipse/snapshot/e3.6/
currentType(subType('groovy.lang.GroovyObject')).accept {
property name : 'newProp', type : String,
provider : 'Sample DSL',
doc : 'This is a sample. You should see this in content assist for all GroovyObjects:<pre>newProp</pre>'
}
在 DSLD 中,您应该会看到特定于 DSLD 的内容辅助和悬停提示(这来自步骤 2 中添加的元 DSLD 脚本)。它看起来像这样:
this.newProp
您应该会看到newProp
正确突出显示,并且悬停将显示来自 DSLD 的文档,它应该看起来像这样:您可以从 Groovy -> DSLD 首选项页面查看和管理工作区中的所有 DSLD:
在这里,您可以启用/禁用各个脚本,以及选择要编辑的脚本。
重要提示:由于在实现 DSLD 时查找和修复错误可能会有点隐晦,因此强烈建议您执行以下操作
脚本的编译时和运行时问题将在这两个位置之一显示。
对于更大的示例,让我们看看 Grails 框架。Grails 约束 DSL提供了一种声明性方式来验证 Grails 域类。它清晰简洁,但是如果没有对此 DSL 的直接编辑支持,Grails 程序员将依赖于外部文档,并且可能在运行时才会意识到语法错误。我们可以创建一个 DSLD 来解决此问题
// only available in STS 2.7.0 and above
supportsVersion(grailsTooling:"2.7.0")
// a generic grails artifact is a class that is in a grails project, is not a script and is in one of the 'grails-app' folders
def grailsArtifact = { String folder ->
sourceFolderOfCurrentType("grails-app/" + folder) &
nature("com.springsource.sts.grails.core.nature") & (~isScript())
}
// define the various kinds of grails artifacts
def domainClass = grailsArtifact("domain")
// we only require domainClass, but we can also reference other kinds of artifacts here
def controllerClass = grailsArtifact("controllers")
def serviceClass = grailsArtifact("services")
def taglibClass = grailsArtifact("taglib")
// constraints
// The constraints DSL is only applicable inside of the static "constraints" field declaration
inClosure() & (domainClass & enclosingField(name("constraints") & isStatic()) &
(bind(props : properties()) & // 'bind' props to the collection of properties in the domain class
currentTypeIsEnclosingType())).accept {
provider = "Grails Constraints DSL" // this value will appear in content assist
// for each non-static property, there are numerous constraints "methods" that are available
// define them all here
for (prop in props) {
if (prop.isStatic()) {
continue
}
if (prop.type == ClassHelper.STRING_TYPE) {
method isStatic: true, name: prop.name, params: [blank:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [creditCard:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [email:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [url:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [matches:String], useNamedArgs:true
} else if (prop.type.name == Date.name) {
method isStatic: true, name: prop.name, params: [max:Date], useNamedArgs:true
method isStatic: true, name: prop.name, params: [min:Date], useNamedArgs:true
} else if (ClassHelper.isNumberType(prop.type)) {
method isStatic: true, name: prop.name, params: [max:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [min:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [scale:Number], useNamedArgs:true
} else if (prop.type.implementsInterface(ClassHelper.LIST_TYPE)) {
method isStatic: true, name: prop.name, params: [maxSize:Number], useNamedArgs:true
method isStatic: true, name: prop.name, params: [minSize:Number], useNamedArgs:true
}
method isStatic: true, name: prop.name, params: [unique:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [size:Integer], useNamedArgs:true
method isStatic: true, name: prop.name, params: [notEqual:Object], useNamedArgs:true
method isStatic: true, name: prop.name, params: [nullable:Boolean], useNamedArgs:true
method isStatic: true, name: prop.name, params: [range:Range], useNamedArgs:true
method isStatic: true, name: prop.name, params: [inList:List], useNamedArgs:true
}
}
如果复制上面的 DSLD 脚本并将其添加到 Grails 项目中的 DSLD 文件中,则将向 STS 教授约束语言。例如,在以下简单的域类中,您在约束块内获得以下内容辅助:
可以调整上述脚本以添加自定义文档。
即使大多数 Groovy 和 Grails 用户没有实现自己的 DSL,他们也会使用 DSL(在Grails、Gaelyk中,通过构建器等)。因此,即使大多数 STS 用户不会创建自己的 DSLD,他们也将从其他人创建的 DSLD 中受益。我们将与库和 DSL 开发人员紧密合作,为 Groovy 生态系统的不同部分创建通用的 DSLD。
您可以预期在即将发布的 Groovy-Eclipse 版本中,对流行的基于 Groovy 的框架的支持将显著增加。
DSLD 语言的核心实现现已可用,但随着我们更多地了解用户需要什么以及他们想要支持哪种 DSL,我们将对其进行调整。我们将实现更多切点,扩展文档,并努力在 Groovy-Eclipse 本身中提供一些标准的 DSLD。
请尝试此处或wiki上介绍的一些 DSLD,并向我们提供关于此博客文章的反馈,在我们的问题跟踪器上,或在Groovy-Eclipse 邮件列表上。