Xtext——4. 十五分钟入门——进阶

十五分钟入门——进阶

        在开发出自己的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;
    }
}

    首先,在 org.eclipse.xtext.example.generator中找到 DomainmodelGenerator.xtend文件,Xtend类用来为你的建模来生成代码。

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) {
    }
}

    让实现更加有意义。其生成策略是:从资源中找到所有的entity,并为其生成相应代码。

    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

    这样就允许询问某个Entity的命名,其直接将类型转换成了文件名:

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)
    }
}

    3. 下一步是为Entity编写实际的模板代码。现在,不存在方法  Entity.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» {
    }
'
''

    此外,如果Entity存在超类,那么也需要使用IF表达式来完成:

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»;
        }
    '
''
}

   如果你想玩转Xtend,可以将Xtent教程添加到工作区中。选择 New -> Example -> Xtend Tutorial,看一下Xtend的特性。作为一个小的练习,可以实现某个Feature的多个属性,或者强制命名转换,例如,命名应该以下划线开头。

对语言进行单元测试

    自动化测试对于软件产品的可维护性和质量来说是非常重要的,这就是为何强烈建议为你的语言编写单元测试的原因。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 {
}

    2. 测试Xteng的测试用例非常直接。 org.eclipse.xtext.junit4.util.ParseHelper可以将任意字符串洁洗衣 一个 Domainmodel。该模型自身可以被遍历和前向检查。静态引入的Assert可以让测试用例看起来更为简洁。

@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是指建议框架把该方法是为一个验证规则。如果命名以小写字母开头,将会在 Entity的地方有个警告标示,每个方法的名字都会进行判断。第二个验证非常直接,其遍历 Entity的继承结构,然后查找已经被使用的命名:

@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();
    }
}

    在同一个Entity中定义的不同Feature,Xtext框架会自动进行验证,因此无需对其进行检查两次。

[转载请标明出处http://blog.csdn.net/donhao/article/details/7183867]

英文原文地址:http://www.eclipse.org/Xtext/documentation/2_1_0/030-tutorial-next-steps.php


4.1. 使用Xtent编写代码生成器

你可能感兴趣的:(java,eclipse,单元测试,测试,JUnit,deprecated)