Implicitly add method call to generated interface. [message #1754650] |
Tue, 21 February 2017 10:15  |
Eclipse User |
|
|
|
Hi,
I'am developing a DSL which relies on Xbase and allows some simple implementations for methods. In my JVMModelInferrer I created a player attribute of type I<RoleName>Player. Thus, I can refer to it without explicit declaration.
However, whenever I invoke a method on this player Attribute I would like to add it to the generated Interface implicitly. Unfortunately, I've no idea how to hook into Xbase inside the JvmModelInferrer in order to retrieve method names and attributes.
CollSpec:
importSection=XImportSection?
module = Module;
Module:
'module' name=QualifiedName
collaboration = Collaboration;
Collaboration:
'collaboration' name=ValidID '{'
(
(roles+=CoordinatorRole) &
(roles+=NonCoordinatorRole+)
)
'}'
;
CoordinatorRole:
'coordinator' 'role' Role
;
NonCoordinatorRole:
'role' Role
;
fragment Role:
name=ValidID '{'
features+=Feature*
'}'
;
...
Feature:
Property | Operation;
Property:
name=ValidID ':' type=JvmTypeReference;
Operation:
(isPlayerQualifierSet?='playable') 'op' name=ValidID
'('(params+=FullJvmFormalParameter
(',' params+=FullJvmFormalParameter)*)?')'
':' type=JvmTypeReference
body=XBlockExpression?;
In the following example I'd like to add the doSomething Method of role B to IBPlayer interface.
collaboration Test {
coordinator role A {
}
role B {
test:String
playable op bar() :String {
player.doSomething(String test)
}
}
}
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754656 is a reply to message #1754651] |
Tue, 21 February 2017 11:21   |
Eclipse User |
|
|
|
Thanks for your reply!
Christian Dietrich wrote on Tue, 21 February 2017 10:20can you please post the expected java code as well.
For each Role a class and an interface is generated.
The class gets the following method:
public String bar() {
player.performBRoleDoSomething() // something like getPlayer().doSomething() would be preferred actually.
return null;
}
and the interface gets the performBRoleDoSomething() method.
Christian Dietrich wrote on Tue, 21 February 2017 10:20is the interface inferred or is it a existing class?
both, here an excerpt of my Inferer:
def dispatch void infer(NonCoordinatorRole role, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase, CollSpec collSpec){
acceptor.accept(role.toClass('''«collSpec.module.fullyQualifiedName».«role.name»Role''')) [
documentation = role.documentation
if(!isPreIndexingPhase) {
superTypes += AbstractRole.typeRef
var playerRef = typesFactory.createJvmField()
playerRef.type = typeRef('''«collSpec.module.fullyQualifiedName».I«role.name»RolePlayer''')
playerRef.simpleName = 'player'
playerRef.visibility = JvmVisibility.PRIVATE
playerRef.type
members += playerRef
}
for(feature : role.features) {
switch feature {
Property : {
members += feature.toField(feature.name, feature.type)
members += feature.toGetter(feature.name, feature.type)
members += feature.toSetter(feature.name, feature.type)
}
Operation : {
members += feature.toMethod(feature.name, feature.type) [
documentation = feature.documentation
for(p : feature.params) {
parameters += p.toParameter(p.name, p.parameterType)
}
body = feature.body
]
}
}
}
]
}
def void inferPlayerInterface(Role role, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase, CollSpec collSpec) {
acceptor.accept(role.toClass('''«collSpec.module.fullyQualifiedName».I«role.name»RolePlayer''')) [
setInterface = true
for (feature : role.features) {
if (feature instanceof Operation) {
if (feature.isPlayerQualifierSet) {
members += feature.toMethod('''perform«role.name»Role«feature.name.toFirstUpper»''', feature.type) [
documentation = feature.documentation
for(p : feature.params) {
parameters += p.toParameter(p.name, p.parameterType)
}
// body = feature.body
]
}
}
}
]
}
Christian Dietrich wrote on Tue, 21 February 2017 10:20you want to be ablte to call a method that does not exist? so let it drop from the sky?
Actually yes, it shall be dispatched at run time. So it does not drop from the sky but the complementing implementation is provided somewhere else. In the end there should be no fixed player attribute but for the moment this would be ok.
[Updated on: Tue, 21 February 2017 11:24] by Moderator Report message to a moderator
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754663 is a reply to message #1754661] |
Tue, 21 February 2017 11:44   |
Eclipse User |
|
|
|
This is what I mean with implicit definition. This is also the reason for generating the interface. The DSL is the first step of a 2-step development strategy. In the second phase the interface needs to be implemented by a class.
By writing player.doSomething() i want to require that a corresponding player really provides this method.
Return types for such methods are not of interest so far. Thus I currently don't care about them.
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754667 is a reply to message #1754666] |
Tue, 21 February 2017 12:13   |
Eclipse User |
|
|
|
Okay, this is basically what I expected, but where can i hook into that?
Traversing the feature.body seems a little bit tricky because almost everything is an XExpression and I assume that not everything allows such a method call. Which model elements of XExpression are the ones to take care of?
Thanks in advance.
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754686 is a reply to message #1754670] |
Tue, 21 February 2017 14:42   |
Eclipse User |
|
|
|
While having a look at XMemberFeatureCall in Xbase Definition I thought about a special rule, which could simplify parsing. Something like
PlayerFeatureCall returns xBase::XExpression:
{PlayerFeatureCall}
'player'
((".")
feature=[types::JvmIdentifiableElement|IdOrSuper] (
=>explicitOperationCall?='('
(
memberCallArguments+=XExpression (',' memberCallArguments+=XExpression)*
)?
')')?
);
Unfortunately, this code snippet seems to be wrong - when typing player.doSomething( ) it is not recognized as a special rule.
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754717 is a reply to message #1754692] |
Tue, 21 February 2017 18:39   |
Eclipse User |
|
|
|
Christian Dietrich wrote on Tue, 21 February 2017 15:32id simply use nodemodel and have a look at the text for the stuff at the beginning of the rule and see if it is player
So still traversing XExpressions? Or did I get something wrong? What do you mean with "beginning of the rule and see if it is player".
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754934 is a reply to message #1754718] |
Fri, 24 February 2017 09:22   |
Eclipse User |
|
|
|
What is the right place to traverse the tree? Right now I'm doing this in the JvmModelInferrer. The problem is that I can find them, but they are not added to the interface - so at least the new interface is not generated. A reason for this might be, that the IDE (IntelliJ in my case) tries to resolve types before and cannot find them off cause, thus it interrupts the building process - but I'm not sure about this.
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754937 is a reply to message #1754935] |
Fri, 24 February 2017 09:34   |
Eclipse User |
|
|
|
Sure.
In the JvmInferrer I start with inferring the root element.
def dispatch void infer(CollSpec collSpec, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
infer(collSpec.module.collaboration,acceptor,isPreIndexingPhase,collSpec)
collSpec.module.collaboration.roles.forEach [
infer(it,acceptor,isPreIndexingPhase,collSpec)
]
collSpec.module.collaboration.roles.forEach [
inferPlayerInterface(it,acceptor,isPreIndexingPhase,collSpec)
]
}
The interface is generated like this:
def void inferPlayerInterface(Role role, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase, CollSpec collSpec) {
acceptor.accept(role.toClass('''«collSpec.module.fullyQualifiedName».I«role.name»RolePlayer''')) [
setInterface = true
for(feature : role.features) {
if(feature instanceof Operation) {
if(feature.isPlayerQualifierSet) {
members += feature.toMethod('''perform«role.name»Role«feature.name.toFirstUpper»''', feature.type) [
setDefault = false
documentation = feature.documentation
for(p : feature.params) {
parameters += p.toParameter(p.name, p.parameterType)
}
]
}
var playerReferences = feature?.body?.findPlayerReferences
for(featureCall : playerReferences) {
println(featureCall)
var op = typesFactory.createJvmOperation
members += featureCall.toMethod('''perform«role.name»Role«feature.name.toFirstUpper»«featureCall.feature.simpleName.toFirstUpper»''',if(featureCall.explicitReturnType) featureCall.returnType else Void.typeRef) [
setDefault = false
setAbstract = true
]
}
}
}
]
}
The method findPlayerReferences returns the correct references - I added them as a specific modeling element to my grammar:
PlayerFeatureCall returns xBase::XExpression:
(=>'player' '.' {PlayerFeatureCall}
//('<' typeArguments+=JvmArgumentTypeReference (',' typeArguments+=JvmArgumentTypeReference)* '>')?
feature=[types::JvmIdentifiableElement|IdOrSuper] (
=>explicitOperationCall?='('
(
//memberCallArguments+=XShortClosure
//|
memberCallArguments+=XExpression (',' memberCallArguments+=XExpression)*
)?
')')?
(=>explicitReturnType?='returns' returnType=JvmTypeReference)?
//memberCallArguments+=XClosure?
);
XPrimaryExpression returns xBase::XExpression:
XConstructorCall |
XBlockExpression |
XSwitchExpression |
XSynchronizedExpression |
XFeatureCall |
XLiteral |
XIfExpression |
XForLoopExpression |
XBasicForLoopExpression |
XWhileExpression |
XDoWhileExpression |
XThrowExpression |
XReturnExpression |
XTryCatchFinallyExpression |
XParenthesizedExpression |
PlayerFeatureCall
;
|
|
|
|
Re: Implicitly add method call to generated interface. [message #1754939 is a reply to message #1754938] |
Fri, 24 February 2017 09:43   |
Eclipse User |
|
|
|
the findPlayerReferences call traverses the body, which is an XExpression.
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XExpression expr) {
return newArrayList
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(Void expr) {
return newArrayList
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XBlockExpression expr) {
var result = newArrayList
for(el : expr.expressions) {
result.addAll(el.findPlayerReferences)
}
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XClosure expr) {
var result = newArrayList
result.addAll(expr.expression.findPlayerReferences)
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XIfExpression expr) {
var result = newArrayList
result.addAll(expr.getIf.findPlayerReferences)
result.addAll(expr.getThen.findPlayerReferences)
result.addAll(expr.getElse.findPlayerReferences)
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XForLoopExpression expr) {
var result = newArrayList
result.addAll(expr.forExpression.findPlayerReferences)
result.addAll(expr.eachExpression.findPlayerReferences)
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XBasicForLoopExpression expr) {
var result = newArrayList
result.addAll(expr.eachExpression.findPlayerReferences)
result.addAll(expr.expression.findPlayerReferences)
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XSwitchExpression expr) {
var result = newArrayList
result.addAll(expr.getSwitch.findPlayerReferences)
result.addAll(expr.getDefault.findPlayerReferences)
for(el : expr.cases) {
result.addAll(el.findPlayerReferences)
}
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XCasePart expr) {
var result = newArrayList
result.addAll(expr.getCase.findPlayerReferences)
result.addAll(expr.getThen.findPlayerReferences)
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XAbstractWhileExpression expr) {
var result = newArrayList
result.addAll(expr.getPredicate.findPlayerReferences)
result.addAll(expr.getBody.findPlayerReferences)
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XMemberFeatureCall expr) {
var result = newArrayList
result.addAll(expr.memberCallTarget.findPlayerReferences)
result.addAll(expr.implicitFirstArgument.findPlayerReferences)
result.addAll(expr.implicitReceiver.findPlayerReferences)
result.addAll(expr.actualReceiver.findPlayerReferences)
for(el : expr.memberCallArguments) {
result.addAll(el.findPlayerReferences)
}
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(PlayerFeatureCall expr) {
var result = newArrayList
result.add(expr)
for(el : expr.memberCallArguments) {
result.addAll(el.findPlayerReferences)
}
result
}
private def dispatch List<PlayerFeatureCall> findPlayerReferences(XAbstractFeatureCall expr) {
var result = newArrayList
result.addAll(expr.implicitFirstArgument.findPlayerReferences)
result.addAll(expr.implicitReceiver.findPlayerReferences)
result.addAll(expr.actualReceiver.findPlayerReferences)
for(el : expr.actualArguments) {
result.addAll(el.findPlayerReferences)
}
result
}
Otherwise I don't know what you mean by node model.
|
|
|
Re: Implicitly add method call to generated interface. [message #1754941 is a reply to message #1754938] |
Fri, 24 February 2017 10:32   |
Eclipse User |
|
|
|
AST !== NodeModel
Model:
elements+=Element*
;
Element:
"element" name=ID "{"
operation=Operation
"}"
;
Operation:
"op" name=ID "("")" body=XBlockExpression
;
/*
* generated by Xtext 2.11.0.RC2
*/
package org.xtext.example.mydsl.jvmmodel
import com.google.inject.Inject
import org.eclipse.xtext.Action
import org.eclipse.xtext.nodemodel.ICompositeNode
import org.eclipse.xtext.nodemodel.util.NodeModelUtils
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.example.mydsl.myDsl.Element
import org.xtext.example.mydsl.services.MyDslGrammarAccess
import org.eclipse.xtext.RuleCall
/**
* <p>Infers a JVM model from the source model.</p>
*
* <p>The JVM model should contain all elements that would appear in the Java code
* which is generated from the source model. Other models link against the JVM model rather than the source model.</p>
*/
class MyDslJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
@Inject extension MyDslGrammarAccess
def dispatch void infer(Element element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toInterface("demo.I" + element.name + "Player", [])) [
if (element.operation !== null) {
val node = NodeModelUtils.findActualNodeFor(element.operation.body)
// search the nodemodel. this is just an example. make sure you catch all cases
if (node !== null) {
for (e : node.asTreeIterable) {
val ge = e.grammarElement
if (ge instanceof Action) {
if (ge === XMemberFeatureCallAccess.XMemberFeatureCallMemberCallTargetAction_1_1_0_0_0) {
if (e instanceof ICompositeNode) {
for (ex : e.children) {
val gex = ex.grammarElement
if (gex instanceof RuleCall) {
if (gex.rule === XMemberFeatureCallRule) {
val text = ex.text.trim
if ("player".equals(text)) {
var next = ex.nextSibling
while (next !== null) {
if (next.grammarElement ==
XMemberFeatureCallAccess.
featureJvmIdentifiableElementCrossReference_1_1_2_0) {
val ftext = next.text.trim
members +=
element.toMethod(ftext, Void.TYPE.typeRef)[]
next = null
} else {
next = next.nextSibling
}
}
}
}
}
}
}
}
}
}
}
}
]
acceptor.accept(element.toClass("demo." + element.name)) [
members += element.toField("player", ("demo.I" + element.name + "Player").typeRef)[]
if (element.operation !== null) {
members += element.operation.toMethod(element.operation.name, Void.TYPE.typeRef) [
body = element.operation.body
]
}
]
}
}
|
|
|
Re: Implicitly add method call to generated interface. [message #1754942 is a reply to message #1754941] |
Fri, 24 February 2017 10:39   |
Eclipse User |
|
|
|
p.s.
you might start traversing the ast as you do,
but you may never follow cross references but use NodeModelUtils to get the node and text e.g
class MyDslJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
@Inject extension MyDslGrammarAccess
def dispatch void infer(Element element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toInterface("demo.I" + element.name + "Player", [])) [
if (element.operation !== null) {
for (XMemberFeatureCall c : EcoreUtil2.getAllContentsOfType(element.operation.body,
XMemberFeatureCall)) {
val node = NodeModelUtils.findNodesForFeature(c,
XbasePackage.Literals.XMEMBER_FEATURE_CALL__MEMBER_CALL_TARGET).head
if (node !== null) {
val text = node.text.trim
if ("player" == text) {
val node2 = NodeModelUtils.findNodesForFeature(c,
XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE).head
if (node2 !== null) {
val text2 = node2.text.trim
members+= element.toMethod(text2, Void.TYPE.typeRef)[]
}
}
}
}
}
]
acceptor.accept(element.toClass("demo." + element.name)) [
members += element.toField("player", ("demo.I" + element.name + "Player").typeRef)[]
if (element.operation !== null) {
members += element.operation.toMethod(element.operation.name, Void.TYPE.typeRef) [
body = element.operation.body
]
}
]
}
}
|
|
|
Re: Implicitly add method call to generated interface. [message #1754955 is a reply to message #1754942] |
Fri, 24 February 2017 13:46   |
Eclipse User |
|
|
|
Thanks! This works perfect but one issue remains. I need to replace the defined method names like doSomething with more complex ones, e.g., performBRoleDoSomething. Currently I receive an Issue, that doSomething is undefined for the type I...Player. Where do I have to hook in to translate the method name to resolve this properly?
|
|
|
|
Powered by
FUDForum. Page generated in 0.05343 seconds