十五分钟入门——进阶
在开发出自己的DSL之后,随之而来的问题便是如何自定义语言中语法的行为。因此,有几个简单的教程来描述自定义的DSL中常见的情况。这些教程之间没有关系,每个都基于第3章中的域建模教程。其语法看起来如下:
package java.lang {
datatype String
}
package my.company.blog {
import java.lang.*
import my.company.common.*
entity Blog {
title: String
many posts: Post
}
entity HasAuthor {
author: String
}
entity Post extends HasAuthor {
title: String
content: String
many comments: Comment
}
entity Comment extends HasAuthor {
content: String
}
}
grammar org.eclipse.xtext.example.Domainmodel with
org.eclipse.xtext.common.Terminals
generate domainmodel "http://www.eclipse.org/xtext/example/Domainmodel"
Domainmodel:
(elements += AbstractElement)*
;
PackageDeclaration:
'package' name = QualifiedName '{'
(elements += AbstractElement)*
'}'
;
AbstractElement:
PackageDeclaration | Type | Import
;
QualifiedName:
ID ('.' ID)*
;
Import:
'import' importedNamespace = QualifiedNameWithWildcard
;
QualifiedNameWithWildcard:
QualifiedName '.*'?
;
Type:
DataType | Entity
;
DataType:
'datatype' name=ID
;
Entity:
'entity' name = ID
('extends' superType = [Entity | QualifiedName])?
'{'
(features += Feature)*
'}'
;
Feature:
(many ?= 'many')? name = ID ':' type = [Type | QualifiedName]
;
使用Xtent编写代码生成器
为相应的语法生成了Xtext编辑器之后,在您定义的语言项目运行时回插入一个代码生成器,现在来看一下Xtend是如何将您的代码集成到Eclipse中。
在本教程中,将会根据域建模DSL中的entity来生成相应的Java Bean。对于每个Entity,会生成一个Java类,同时,每个Feature将会作为该类的私有成员变量,同时会提供改成员变量的getter和setter方法。为了简单起见,这里使用完整的命名。
package my.company.blog;
public class HasAuthor {
private java.lang.String author;
public java.lang.String getAuthor() {
return author;
}
public void setAuthor(java.lang.String author) {
this.author = author;
}
}
package org.eclipse.xtext.example.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
class DomainmodelGenerator implements IGenerator {
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
}
}
1. 首先,需要对资源中的内容进行遍历来找出所对应的entity,因此,对嵌套的元素进行遍历。Xtend可以简化这个过程,类ResourceExtensions提供了功能非常强大的方法allContentsIterable(),我们可以静态import。
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.* //译者添加,如果这里有错误,点击项目中的MANIFEST.MF,在Required Plug-ins中添加org.eclipse.xtext.xtend2.lib库
class DomainmodelGenerator implements IGenerator {
..
}
然后,我们通过类型来对其中的所有的对象进行遍历和过滤来找出所有的Entities。
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
//译者注:做了修改,原来为:resource.allContentsIterable,其中allContentsIterable已经Deprecated了
for(e: resource.allContents.toIterable.filter(typeof(Entity))) {
}
}
2. 现在来回答一个问题:我们如何定义每个Entity所对用的Java类的文件名。由于Java支持这种模式,因此可以基于Entity的限定名称来进行命名。可以通过一种特殊的服务来获得适用于所有语言的限定命名。幸运的是,Xtend提供了相应的功能,只需简单的在生成器中inject IQuali edNameProvider即可。
@Inject extension IQualifiedNameProvider nameProvider
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
//译者注:做了修改,原来为:resource.allContentsIterable,其中allContentsIterable已经Deprecated了
for(e: resource.allContents.toIterable.filter(typeof(Entity))) {
fsa.generateFile(
e.fullyQualifiedName.toString.replace(".", "/") + ".java",
e.compile)
}
}
def compile(Entity e) '''
package «e.eContainer.fullyQualifiedName»;
public class «e.name» {
}
'''
4. 上面的模板是Java Bean生成器的大体框架,不过还没有完成。如果在Package中不含有Entity时,编译会失败。需要进行一些小的修改,需要在IF表达式中来加入package-declaration:
def compile(Entity e) '''
«IF e.eContainer != null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» {
}
'''
def compile(Entity e) '''
«IF e.eContainer != null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» «IF e.superType != null
»extends «e.superType.fullyQualifiedName» «ENDIF»{
}
'''
5. 尽管此时编译不会有任何问题,但缺少了相应的Java属性,需要加入所声明的Feature。
为了加入属性,需要创建另一个Xtent函数来将某个属性加入到Java代码中。
def compile(Feature f) '''
private «f.type.fullyQualifiedName» «f.name»;
public «f.type.fullyQualifiedName» get«f.name.toFirstUpper»() {
return «f.name»;
}
public void set«f.name.toFirstUpper»(«f.type.fullyQualifiedName» «f.name») {
this.«f.name» = «f.name»;
}
'''
def compile(Entity e) '''
«IF e.eContainer != null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» «IF e.superType != null
»extends «e.superType.fullyQualifiedName» «ENDIF»{
«FOR f:e.features»
«f.compile»
«ENDFOR»
}
'''
最终的代码生成器看起来像下面的样子。现在,可以尝试一下,运行一个新的Eclipse应用(Xtext项目中的 Run As -> Eclipse Application),创建一个Java项目,并创建一个 dmodle项目。现在,在该项目中创建一个新的源程序文件夹 src-gen,最后会找到Entity并生成相应的Java代码。
package org.eclipse.xtext.example.generator
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.* //译者添加,如果这里有错误,点击项目中的MANIFEST.MF,在Required Plug-ins中添加org.eclipse.xtext.xtend2.lib库
import org.eclipse.xtext.example.domainmodel.*
import org.eclipse.xtext.naming.IQualifiedNameProvider
import com.google.inject.Inject
class DomainmodelGenerator implements IGenerator {
@Inject extension IQualifiedNameProvider nameProvider
override void doGenerate(Resource resource, IFileSystemAccess fsa) {
//译者注:做了修改,原来为:resource.allContentsIterable,其中allContentsIterable已经Deprecated了
for(resource.allContents.toIterable.filter(typeof(Entity))) {
fsa.generateFile(
e.fullyQualifiedName.toString.replace(".", "/") + ".java",
e.compile)
}
}
def compile(Entity e) '''
«IF e.eContainer != null»
package «e.eContainer.fullyQualifiedName»;
«ENDIF»
public class «e.name» «IF e.superType != null
»extends «e.superType.fullyQualifiedName» «ENDIF»{
«FOR f:e.features»
«f.compile»
«ENDFOR»
}
'''
def compile(Feature f) '''
private «f.type.fullyQualifiedName» «f.name»;
public «f.type.fullyQualifiedName» get«f.name.toFirstUpper»() {
return «f.name»;
}
public void set«f.name.toFirstUpper»(«f.type.fullyQualifiedName» «f.name») {
this.«f.name» = «f.name»;
}
'''
}
对语言进行单元测试
自动化测试对于软件产品的可维护性和质量来说是非常重要的,这就是为何强烈建议为你的语言编写单元测试的原因。Xtext项目向导会创建相应的单元测试项目,其简化了通过Junit4来对Eclipse和UI进行测试的复杂性。
本教程关注与对解析器以及链接Domainmodel的测试,其使用Xtend来编写测试用例。
1. 首先,需要创建一个新的Xtend类。因此,选择test插件的src文件夹,然后在上下文菜单中选择New -> Xtend Class,起个有意义的名字,输入package名,然后点击完成。测试基础结构的核心是XtextRunner和语言规范IInjectorProvider。需要使用Annotation:
@InjectWith(typeof(DomainmodelInjectorProvider))
@RunWith(typeof(XtextRunner))
class ParserTest {
}
@Inject
ParseHelper<Domainmodel> parser
@Test
def void parseDomainmodel() {
val model = parser.parse(
"entity MyEntity {
parent: MyEntity
}")
val entity = model.elements.head as Entity
assertSame(entity, entity.features.head.type)
}
3. 在保存Xtend文件之后,来运行一下测试用例。到xtend-gen文件夹中选择生成的java类,然后选择从上下文菜单中选择Run As -> JUnit Test。
创建自定义的验证规则
DSL的一大优点是可以静态的对域特定约束进行检查,这是通过静态分析来实现的。因为这是一个常用的功能,因此Xtext提供了强大的规则验证功能。
在本次教学中,我们需要确保Entity的命名首字母为大写,同时每个Entity的继承关系中所有的Feature的命名不同。在package org.eclipse.xtext.example.validation中找到DomainmodelJavaValidator类,可以在插件中找到。仅需要几行代码便可以完成对约束的定义:
译者注:需要import org.eclipse.xtext.validation.Check和相应的Entity、Fature所在的包
@Check
public void checkNameStartsWithCapital(Entity entity) {
if (!Character.isUpperCase(entity.getName().charAt(0))) {
warning("Name should start with a capital",
DomainmodelPackage.Literals.TYPE__NAME);
}
}
@Check
public void checkFeatureNameIsUnique(Feature f) {
Entity superEntity = ((Entity) f.eContainer()).getSuperType();
while(superEntity != null) {
for(Feature other: superEntity.getFeatures()) {
if (f.getName().equals(other.getName())) {
error("Feature names have to be unique",
DomainmodelPackage.Literals.FEATURE__NAME);
return;
}
}
superEntity = superEntity.getSuperType();
}
}
[转载请标明出处http://blog.csdn.net/donhao/article/details/7183867]
英文原文地址:http://www.eclipse.org/Xtext/documentation/2_1_0/030-tutorial-next-steps.php