基于Xtext扩展类Java方法调用语法的技术问询(EMF场景)
嘿,这问题我熟!要在Xtext里实现类似Java的链式调用(比如Employee.salary.getBasic()),核心是让语法支持递归的结构引用,同时还要配合语义层面的类型检查来保证正确性。我给你一步步拆解:
第一步:修改Xtext语法,支持递归链式结构
你的原始语法只能处理最多一级的属性/方法引用,我们需要把规则改成递归结构,允许在一个调用结果上继续发起新的调用。
基础版语法(支持属性链式访问)
如果你的场景是像Employee.salary.getBasic这种无括号的属性访问(对应Ecore里的EStructuralFeature),可以用下面的语法:
FeatureCall: target=FeatureTarget ('.' feature=[ecore::EStructuralFeature])+ ; FeatureTarget: classRef=[ecore::EClass] | FeatureCall ;
语法解释:
FeatureTarget是递归的核心:它既可以是初始的EClass引用(比如Employee),也可以是一个已经完成的FeatureCall(比如Employee.salary),这样就实现了链式嵌套的基础。FeatureCall要求至少包含一个'.' feature片段,所以单独的Employee不算合法调用,但Employee.salary、Employee.salary.getBasic都符合规则。
进阶版语法(兼容属性访问与方法调用)
如果要像Java一样同时支持无括号的属性访问和带括号的方法调用(比如Employee.salary.getBasic()),可以给feature添加可选的括号:
FeatureCall: target=FeatureTarget ('.' feature=[ecore::EStructuralFeature] ('(' ')')?)+ ; FeatureTarget: classRef=[ecore::EClass] | FeatureCall ;
这样Employee.salary(属性访问)和Employee.salary.getBasic()(方法调用)两种形式都能被正确解析。
第二步:语义层面保证类型安全
光有语法还不够,我们需要确保链式调用的类型合法性——比如Employee.salary返回Salary类型,那么getBasic必须是Salary类的EStructuralFeature。这需要在Scope Provider里调整作用域逻辑:
class MyDslScopeProvider extends AbstractMyDslScopeProvider { @Override public IScope getScope(EObject context, EReference reference) { // 针对FeatureCall的feature引用调整作用域 if (reference == MyDslPackage.Literals.FEATURE_CALL__FEATURE) { FeatureCall featureCall = (FeatureCall) context; EClassifier targetType = getTargetType(featureCall.getTarget()); if (targetType instanceof EClass) { // 返回目标类型的所有结构特征作为候选 return Scopes.scopeFor(((EClass) targetType).getEStructuralFeatures()); } } return super.getScope(context, reference); } // 获取目标节点的类型 private EClassifier getTargetType(FeatureTarget target) { if (target instanceof ClassReference) { return ((ClassReference) target).getClassRef(); } else if (target instanceof FeatureCall) { return ((FeatureCall) target).getFeature().getEType(); } return null; } }
这段代码的作用是:当用户输入链式调用的下一个feature时,编辑器会自动根据前一步的返回类型,只显示该类型下的合法EStructuralFeature,完全模拟Java的代码提示体验。
第三步:可选的类型验证(增强健壮性)
如果你想在编译阶段就检查链式调用的类型错误(比如调用了不存在的feature),可以在Validator里添加验证规则:
class MyDslValidator extends AbstractMyDslValidator { @Check def checkFeatureCallType(FeatureCall call) { val targetType = getTargetType(call.target) if (targetType instanceof EClass && call.feature != null) { val availableFeatures = ((EClass) targetType).eStructuralFeatures if (!availableFeatures.contains(call.feature)) { error( "Feature '" + call.feature.name + "' is not defined in type '" + targetType.name + "'", MyDslPackage.Literals.FEATURE_CALL__FEATURE ) } } } private def EClassifier getTargetType(FeatureTarget target) { // 和Scope Provider里的实现一致 if (target instanceof ClassReference) { return ((ClassReference) target).classRef } else if (target instanceof FeatureCall) { return ((FeatureCall) target).feature.eType } return null } }
这样一来,任何不符合类型规则的链式调用都会被编辑器标记为错误,提前发现问题。
内容的提问来源于stack exchange,提问作者Lucia

