我目前正在为我的Xtext dsl实现交叉引用。 dsl文件可以包含多个XImportSection,在某些特殊情况下,XImportSection不一定包含所有import语句。这意味着我需要自定义“XImportSectionNamespaceScopeProvider”来查找/构建正确的XimportSection。在实现过程中,我发现了编辑器的意外行为和/或一些验证。
我使用以下dsl代码剪切来测试我的实现:
delta MyDelta {
adds {
package my.pkg;
import java.util.List;
public class MyClass
implements List
{
}
}
modifies my.pkg.MyClass { // (1)
adds import java.util.ArrayList;
adds superclass ArrayList<String>;
}
}
dsl源代码由以下语法规则描述(不完整!):
AddsUnit:
{AddsUnit} 'adds' '{' unit=JavaCompilationUnit? '}';
ModifiesUnit:
'modifies' unit=[ClassOrInterface|QualifiedName] '{'
modifiesPackage=ModifiesPackage?
modifiesImports+=ModifiesImport*
modifiesSuperclass=ModifiesInheritance?
'}';
JavaCompilationUnit:
=> (annotations+=Annotation*
'package' name=QualifiedName EOL)?
importSection=XImportSection?
typeDeclarations+=ClassOrInterfaceDeclaration;
ClassOrInterfaceDeclaration:
annotations+=Annotation* modifiers+=Modifier* classOrInterface=ClassOrInterface;
ClassOrInterface: // (2a)
ClassDeclaration | InterfaceDeclaration | EnumDeclaration | AnnotationTypeDeclaration;
ClassDeclaration: // (2b)
'class' name=QualifiedName typeParameters=TypeParameters?
('extends' superClass=JvmTypeReference)?
('implements' interfaces=Typelist)?
body=ClassBody;
为了提供更好的工具支持,ModifiesUnit
引用了已修改的类。这种特定于Xtext的实现可以实现对类的超链接。
我目前正在开发定制的XImportSectionScopeProvider,它为ModifiesUnit
提供了所有命名空间范围。默认实现包含方法protected List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase)
,假设源文件中只有一个类类元素。但对于我的语言,可能会有一个以上。出于这个原因,我必须自定义它。
我现在的想法是以下实现(使用Xtend编程语言):
override List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase) {
switch (context) {
ModifiesUnit: context.buildImportSection
default: // ... anything else
}
}
在我开始这项工作之前,参考工作正常,没有任何意外的事情发生。我现在的目标是为ModifiesUnit
构建一个自定义的XImportSection,Xbase使用它来解析对JVM类型的引用。为此,我需要一份引用的ClassOrInterface
的XImportSection副本。要访问XImportSection,我首先调用ModifiesUnit.getUnit()
。执行此调用后,编辑器会直接显示意外行为。导致错误的最小实现看起来像这样:
def XImportSection buildImportSection(ModifiesUnit u) {
val ci = u.unit // Since this expression is executed, the error occurs!
// ...
}
在这里,我不知道内部会发生什么!但它计算错误。编辑器在(1)处显示限定名称的下列错误:“检测到循环链接:ModifiesUnit.unit-&gt; ModifiesUnit.unit”。
我的问题是:这是什么意思?为什么Xtext显示此错误?如果我访问引用的对象,为什么会出现?
我在那里也发现了一件奇怪的事情:在我的第一个方法中,我的代码扔了一个NullPointerException
。好的,我试图通过打印对象ci
找出原因。结果是:
org.deltaj.scoping.deltaJ.impl.ClassOrInterfaceImpl@4642f064 (eProxyURI: platform:/resource/Test/src/My.dj#xtextLink_::0.0.0.1.1::0::/2)
org.deltaj.scoping.deltaJ.impl.ClassDeclarationImpl@1c70366 (name: MyClass)
好吧,似乎这个方法执行了两次,Xtext在第一次和第二次执行之间解析了代理。只要收到的对象是正确的一次,对我来说就没问题了。我用if-instanceof语句处理它。
但为什么我在那里得到两个参考?它是否依赖于ParserRule ClassOrInterface(2a),它只是ClassDeclaration(2b)的抽象超规则?但是为什么Xtext无法解析ClassOrInterface的引用?
答案 0 :(得分:1)
好的,现在我找到了解决问题的方法。在我试验我的实现时,我看到“Problems”视图stil包含未解析的引用。这是重新思考我的实现所做的事情的原因。起初,我决定直接构建返回的列表List<ImportNormalizer
,而不是构建XImportSection
,然后将其转换为此列表。在实现这一过程中,我注意到我只为ModifiesUnit
元素构建了范围,而不是在ModifiesUnit
范围内需要范围的元素。这是循环链接错误的原因。现在,我只在需要时才构建列表。结果是不再发生循环链接错误,并且在问题视图中没有任何错误地正确解析了对JVM类型的所有引用。
我的实现现在看起来像这样:
class DeltaJXImportSectionNamespaceScopeProvider extends XImportSectionNamespaceScopeProvider {
override List<ImportNormalizer> internalGetImportedNamespaceResolvers(EObject context, boolean ignoreCase) {
// A scope will only be provided for elements which really need a scope. A scope is only necessary for elements
// which are siblings of a JavaCompilationUnit or a ModifiesUnit.
if (context.checkElement) { // (1)
return Collections.emptyList
}
// Finding the container which contains the import section
val container = context.jvmUnit // (2)
// For a non null container create the import normalizer list depending of returned element. If the container is
// null, no scope is needed.
return if (container != null) { // (3)
switch (container) {
JavaCompilationUnit: container.provideJcuImportNormalizerList(ignoreCase)
ModifiesUnit: container.provideMcuImportNormalizerList(ignoreCase)
}
} else {
Collections.emptyList
}
}
// Iterates upwards through the AST until a ModifiesUnit or a JavaCompilationUnit is found. (2)
def EObject jvmUnit(EObject o) {
switch (o) {
ModifiesUnit: o
JavaCompilationUnit: o
default: o.eContainer.jvmUnit
}
}
// Creates the list with all imports of a JCU (3a)
def List<ImportNormalizer> provideJcuImportNormalizerList(JavaCompilationUnit jcu, boolean ignoreCase) {
val is = jcu.importSection
return if (is != null) {
is.getImportedNamespaceResolvers(ignoreCase)
} else {
Collections.emptyList
}
}
// Creates the list of all imports of a ModifiesUnit. This implementation is similar to
// getImportedNamespaceResolvers(XImportSection, boolean) // (3b)
def List<ImportNormalizer> provideMcuImportNormalizerList(ModifiesUnit mu, boolean ignoreCase) {
val List<ImportNormalizer> result = Lists.newArrayList
result.addAll((mu.unit.jvmUnit as JavaCompilationUnit).provideJcuImportNormalizerList(ignoreCase))
for (imp : mu.modifiesImports) {
if (imp instanceof AddsImport) {
val decl = imp.importDeclaration
if (!decl.static) {
result.add(decl.transform(ignoreCase))
}
}
}
result
}
// Creates an ImportNormalizer for a given XImportSection
def ImportNormalizer transform(XImportDeclaration decl, boolean ignoreCase) {
var value = decl.importedNamespace
if (value == null) {
value = decl.importedTypeName
}
return value.createImportedNamespaceResolver(ignoreCase)
}
// Determines whether an element needs to be processed. (1)
def checkElement(EObject o) {
return o instanceof DeltaJUnit || o instanceof Delta || o instanceof AddsUnit || o instanceof ModifiesUnit ||
o instanceof RemovesUnit
}
}
可以看出,对于正确范围不需要名称空间的元素将被忽略(1)。
对于可能需要正确范围的命名空间的每个元素,确定直接包含导入的n-father元素(2)。
使用正确的父元素,可以为JCU(3a)和MU(3b)计算命名空间列表(3)。