The question: how do i configure Xtext and Xbase in order to use in my DSL file (the one with DSL extension, ".myx") classes that are not yet generated by JvmModelInferrer?
Here is the language grammar:
grammar org.xtext.example.mydsl.MyX with org.eclipse.xtext.xbase.Xbase
generate myX "http://www.xtext.org/example/mydsl/MyX"
import "http://www.eclipse.org/xtext/xbase/Xbase" as xbase
Model:
expressions+=CommonExpression*;
CommonExpression:
Anime | AnimeResource
;
AnimeResource:
'AnimeRes' name=ID '{'
(args+=FullJvmFormalParameter)*
'}'
;
Anime:
'watch' name=ID body=XBlockExpression
;
Here is what i want to achive (test.myx):
AnimeRes Resource {
}
watch Watcher {
val someStub = Resource.create()
}
So the dsl file looks like there is a static method defined for Resource class. But in reality, there must be additional parameters that should be passed to Resource, they are purely boilerplate in my case, that's why I don't want to pass them into "create" each time. How I want the generated file look like to achieve that:
package test;
public class Model {
private int id= 0;
public static class Resource {
private int id;
public Resource(final int id) {
this.id = id;
}
}
public class ResourceCreator {
public Resource create() {
return new Resource(id /* the creator is inner non-static class */));
}
}
public ResourceCreator Resource = new ResourceCreator();
}
That way I'm kind of cheating. I have a variable that has the name of the class, and in the client code it looks like they use static method when they are really just using a builder that is named like the class. Here is the JvmModelInferrer to make similarly looking file:
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.xtext.example.mydsl.myX.Model
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.naming.QualifiedName
import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.example.mydsl.myX.AnimeResource
import org.eclipse.xtext.common.types.JvmVisibility
import org.xtext.example.mydsl.myX.Anime
class MyXJvmModelInferrer extends AbstractModelInferrer {
@Inject extension JvmTypesBuilder
def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
acceptor.accept(element.toClass(QualifiedName.create("test", "Model"))) [
for (expression : element.expressions) {
switch (expression ) {
AnimeResource: {
members += expression.toClass(expression.name) [
static = true
visibility = JvmVisibility.PUBLIC
val _members = members
expression.args.forEach [
_members += expression.toField(name, parameterType) [
static = false
visibility = JvmVisibility.PUBLIC
]
]
members += expression.toField("id", typeRef(int))
members += expression.toConstructor [
val _parameters = parameters
expression.args.forEach [
_parameters += it.toParameter(name, parameterType)
]
_parameters += expression.toParameter("id", typeRef(int))
body = '''
«FOR param : parameters»this.«param.name» = «param.name»;
«ENDFOR»
'''
]
]
members += expression.toField("id", typeRef(int))
members += element.toClass(expression.name + "Creator") [
static = false
visibility = JvmVisibility.PUBLIC
members += element.toMethod("create", typeRef(expression.name)) [
val parameters = parameters
expression.args.forEach [
parameters += it.toParameter(name, parameterType)
]
body = '''
return new «expression.name»(«FOR param : parameters»«IF parameters.indexOf(param) < parameters.size - 1», «ENDIF»«ENDFOR»id);
'''
]
]
members += expression.toField(expression.name, typeRef(expression.name + "Creator")) [
visibility = JvmVisibility.PUBLIC
initializer = '''
new «expression.name + "Creator"»()
'''
]
}
Anime: {
members += expression.toMethod(expression.name, typeRef(void)) [
body = expression.body
]
}
}
}
]
}
}
The problem that I've faced with this approach:
So it seems that some linking fails but I can not understand what I should do to fix that and what bindings I should override and how.
Any help would be appreciated.
UPD. Updated the description with compilable ModelInferrer (sorry). The problem happens when I try to use XBlockExpression of watch block to generate Java code for a method inside of Model class. So if I have such DSL file:
AnimeRes Resource {
}
watch Watcher {
val some = Resource.create()
}
AND also use the Anime branch in the Inferrer, the described problem happens. If I have the same file and do not use the Anime branch (commented out like this):
// Anime: {
// members += expression.toMethod(expression.name, typeRef(void)) [
// body = expression.body
// ]
// }
then there is no problem but I need to generate that method.
you need to use proper names for the inner types
members += element.toClass(expression.name + "Creator") [
static = false
visibility = JvmVisibility.PUBLIC
members += element.toMethod("create", typeRef("test.Model$"+expression.name)) [
val parameters = parameters
expression.args.forEach [
parameters += it.toParameter(name, parameterType)
]
body = '''
return new «expression.name»(«FOR param : parameters»«IF parameters.indexOf(param) < parameters.size - 1», «ENDIF»«ENDFOR»id);
'''
]
]
members += expression.toField(expression.name, typeRef("test.Model$"+expression.name + "Creator")) [
visibility = JvmVisibility.PUBLIC
initializer = '''
new «expression.name + "Creator"»()
'''
]