dslxtextxtend

Is there some way to test my grammar which refer to EAttributes?


I am stuck at one point as I am unable to test my grammar using Junit test cases. Below is my complete grammar.

ExpressionModel:
    expression=Expression;

Expression:
    Comparison;

Comparison returns Expression:
    Primary ({Comparison.left=current} op=("=" | "!=" | ">=" | "<=" | ">" | "<")right=Primary)* ;

Primary returns Expression:
    '(' Expression ')' |
    {Not} "!" expression=Primary |
    Atomic;

Atomic returns Expression:
    {IntConstant} value=INT |
    {StringConstant} value=STRING |
    {BoolConstant} value=('true' | 'false') |
    {VariableRef} variable=[ecore::EAttribute|QualifiedName];

QualifiedName:
    ID ('.' ID)*;

If i test my code generator for this grammar by launching an eclipse instance, all i have to do is create an ".ecore" file in the src folder and another file for my grammar and i am easily able to access my variable created in ".ecore" file.

What i mean to say is after launching an eclipse instance, i created a "test.ecore" file having a class "vars" and an EAttribute "alpha" which is of EString type and i created another file "testModel.dsl" now i am easily able to access "vars.alpha" inside this file. Can anyone please help me how to perform same steps if i want to test my code generator without launching an eclipse instance. It would be of great help to me.

I am trying to test below test case-->

@RunWith(XtextRunner)
@InjectWith(ExtendedMyDslInjectorProvider)
class ExpressionDSLCompilationTest {
@Inject extension CompilationTestHelper
@Inject extension ParseHelper
@Inject extension ReflectExtensions
@Inject extension IQualifiedNameProvider
@Inject Provider<XtextResourceSet> resourceSetProvider

@Test
def void ReturnVariable() {

    val fooPackage = EcoreFactory::eINSTANCE.createEPackage
    fooPackage.name = "foo"
    fooPackage.nsPrefix = "foo"
    fooPackage.nsURI = "http://foo"

    val fooClass = EcoreFactory::eINSTANCE.createEClass
    fooClass.name = "vars"
    fooPackage.EClassifiers.add(fooClass)

    val fooattr = EcoreFactory::eINSTANCE.createEAttribute
    fooattr.name = "alpha"
    fooattr.EType = EcorePackage::eINSTANCE.EString
    val resourceset = resourceSetProvider.get
    val resource = resourceset.createResource(URI.createURI("hiTest.ecore"))

    fooClass.EStructuralFeatures.add(attr)

    resource.contents.add(fooPackage)
    // val model = '''foo.vars.alpha'''.parse(resourceset)
    '''foo.vars.alpha'''.compile [
        getCompiledClass.newInstance => [
            assertEquals(
                "foo.vars.alpha",
                it.invoke("generateCodeForExpression")
            )
        ]
    ]
}

class ExtendedMyDslInjectorProvider extends ExpressionDSLInjectorProvider {

    override internalCreateInjector() {
        EcoreSupportStandaloneSetup.setup
        return super.internalCreateInjector
    }
}

I have already followed steps mentioned in https://www.eclipse.org/forums/index.php/t/1081785/ but this is of no use as it is giving me null pointer exception when i run my test case. Any help will be appreciated.

I am adding a piece of my code generator and highlighting the piece where it is giving error. Hope it will be enough.

class ExpressionDSLGenerator extends AbstractGenerator {
    @Inject extension IQualifiedNameProvider

    /*Generate Java Code with the name of java file as same as that of ".mexpression" file*/
    override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
        var str = ""
        for (e : resource.allContents.toIterable.filter(ExpressionModel)) {

            str += e.checkCompileForFunctionsOrExpresssion
        }
        fsa.generateFile('''«resource.URI.lastSegment.substring(0,resource.URI.lastSegment.indexOf("."))».java''', str)
    }

    /*Generates the body of .Java File having class and single checkExpression
     Method for Expressions and Functions Separately */
    def String checkCompileForFunctionsOrExpresssion(ExpressionModel model) {
            '''
public class «model.eResource.URI.lastSegment.substring(0,model.eResource.URI.lastSegment.indexOf("."))»{
    
    public «getExpressionReturnType(model)» generateCodeForExpression() {
        return «getExpressionReturnBody(model.expression)»;
    }
}
'''
        }
    def getExpressionReturnType(ExpressionModel model) {

    /*If expression is not a comparison and just a single variable or value, we must return value's data type*/
        if (model.eContents.size < 2) {
            model.expression.getValueReturnType
        } /* Return boolean since it will be a comparison*/ else {
            "boolean"
        }
    }
    
    // Utility method to get return type of an expression
    def getValueReturnType(Expression e) {
        if (e.isInt) {
            "int"
        } else if (e.isString) {
            "String"
        } else if (e.isVariable) {
            e.variableReturnsType
        } else {
            "boolean"
        }
    }
    
    // Utility method to set return type on the basis of variable's return type
    def getVariableReturnsType(Expression e) {
        switch (e) {
            VariableRef: {
                <part giving error:-->e.variable.EType is coming to be null, hence null pointer exception>**e.variable.EType.name.equals("EString") ? "String" : e.variable.EType.name.equals(
                    "EInt") ? "int" : "boolean"**</part giving error>
            }
        }
    }
// Utility method to get return body of an expression
    def String getExpressionReturnBody(Expression e) {
        switch (e) {
            VariableRef: {
                getVariableReturn(e.variable)
            }
            IntConstant: {
                e.value.intConstantReturn
            }
            BoolConstant: {
                e.value.booleanConstantReturn
            }
            StringConstant: {
                e.value.stringConstantReturn
            }
            Not: {
                e.expression.notExpressionReturn
            }
            Comparison: {
                val left = e.left.getExpressionReturnBody as String
                val right = e.right.getExpressionReturnBody as String
                if (e.left.isString) {
                    getStringCompareBody(left, right, e.op)
                } else if (e.left.isBoolean) {
                    getBoolCompareBody(left, right, e.op)
                } else if (e.left.isVariable) {
                    getVariableReturnsBody(e.left, left, right, e.op)
                } else {
                    getOthersCompareBody(left, right, e.op)
                }
            }
        }

    }
// return variable's full name 
    def getVariableReturn(EAttribute e) {
        e.fullyQualifiedName + ""
    }
// return integer value
    def getIntConstantReturn(int value) {
        value + ""
    }
// return boolean value
    def getBooleanConstantReturn(String value) {
        Boolean::parseBoolean(value) + ""
    }

    // return string value
    def getStringConstantReturn(String value) {
        "\"" + value + "\""
    }
// return not value of the given expression
    def getNotExpressionReturn(Expression value) {
        "!(" + value.getExpressionReturnBody + ")"
    }
// Utility method to check if Expression is a String
    def isString(Expression e) {
        switch (e) {
            StringConstant: {
                true
            }
            default: {
                false
            }
        }
    }

    // Utility method to check if Expression is a boolean
    def isBoolean(Expression e) {
        switch (e) {
            BoolConstant: {
                true
            }
            default: {
                false
            }
        }
    }

    // Utility method to check if Expression is a variable
    def isVariable(Expression e) {
        switch (e) {
            VariableRef: {
                true
            }
            default: {
                false
            }
        }
    }
// return body of comparison expression for string
    def getStringCompareBody(String left, String right, String op) {
        switch (op) {
            case '=': "(" + left + ".equals(" + right + "))"
            case '!=': "!(" + left + ".equals(" + right + "))"
            default: false + ""
        }
    }

    // return body of comparison expression for boolean
    def getBoolCompareBody(String left, String right, String op) {
        switch (op) {
            case '=': "(" + left + "==" + right + ")"
            case '!=': "(" + left + "!=" + right + ")"
            default: false + ""
        }
    }

    // return body of comparison expression for other's
    def getOthersCompareBody(String left, String right, String op) {
        switch (op) {
            case '<': "(" + left + "<" + right + ")"
            case '>': "(" + left + ">" + right + ")"
            case '>=': "(" + left + ">=" + right + ")"
            case '<=': "(" + left + "<=" + right + ")"
            case '=': "(" + left + "==" + right + ")"
            case '!=': "!(" + left + "==" + right + ")"
            default: false + ""
        }

    }
// body for variable type
    def getVariableReturnsBody(Expression e, String left, String right, String operator) {
        switch (e) {
            VariableRef: {
                e.variable.EType.name.equals("EString")
                    ? getStringCompareBody(left, right, operator) : e.variable.EType.name.equals(
                    "EBoolean") ? getBoolCompareBody(left, right, operator) : getOthersCompareBody(left, right,
                    operator)
            }
        }
    }
}

Solution

  • @Inject extension ValidationTestHelper h
    ...
    val model = '''foo.vars.alpha'''.parse(resourceset)
    model.assertNoErrors
    

    which is the adaption of the forum snippet to your context works perfectly fine

    if you want to use CompilationTestHelper then you have to customize it for the resourcetset to add the resource there

    e.g.

    val model = '''foo.vars.alpha'''.parse(resourceset)
    model.assertNoErrors
    
    compile(resourceset) [
        getCompiledClass.newInstance => [
            assertEquals(
                "foo.vars.alpha",
                it.invoke("generateCodeForExpression")
            )
        ]
    ]