xtextxbase

Xtext: use generated classes in DSL file, problem of missing type


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:

the picture of editor with the shown mistake

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.


Solution

  • 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"»()
        '''
    ]