Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » TMF (Xtext) » XText hook for model transformation?
XText hook for model transformation? [message #1751792] Sat, 14 January 2017 06:58 Go to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
This is a general question about the XText workflow.

The language I am developing contains a concept of "tag" similar in syntax and functionality to Java annotations and more specifically Xtend active annotations. I use these tags to mark language elements (class, property, method, etc.) so that the code generator generate boilerplate code automagically.

The example below is one of the code generator unit-test:

@Test
def void passingTest() {
    '''
    class TestCase {
        methods {
            @Test shouldPass {
                assert(true)
            }
        }
    }
    '''
    .generate
    .runTest('TestCase')
}


The inner @Test tag marks the "shouldPass" method and is supposed to transform the AST by inserting an ancestor to the 'TestCase' class. The problem I have is that I implemented this insertion mechanism in the code generator, only to realize that the validation, which happens before generation, fails. It fails because the "assert" call refers to a method of the class to be inserted which is not yet part of the AST when the validation happens.

I feel I need to hook the insertion step after parsing but before linking.

How can I achieve that?
Re: XText hook for model transformation? [message #1751794 is a reply to message #1751792] Sat, 14 January 2017 07:08 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
Can you please Share

A minimal reproducing Grammar, the Generator, and the complete Test class.
From the Skippet you Shared so far it is Not clear what you are doing and what the error is

Do. you use xbase?
Do. you do m2m though iderivredstatecomputer
Hör do. you validate


Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com

[Updated on: Sat, 14 January 2017 07:33]

Report message to a moderator

Re: XText hook for model transformation? [message #1751795 is a reply to message #1751794] Sat, 14 January 2017 07:40 Go to previous messageGo to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
The grammar would be:

grammar com.septentrio.infrastructure.flax.Flax with org.eclipse.xtext.common.Terminals

generate language "http://www.septentrio.com/infrastructure/flax/Flax"

Flax:
    ('package' name=QualifiedName)?
    (imports+=Import)*
    (entities+=Entity)*;

Import:
    'import' importedNamespace=QualifiedNameWithWildcard;

QualifiedName:
    ID ('.' ID)*;

QualifiedNameWithWildcard:
    QualifiedName '.*'?;

Entity:
    Tag|Class;

Tag:
    'tag' name=ID
    ( '{' properties+=TagProperty* '}' )?;

TagProperty:
    name=ID;

TagReference:
    '@' tag=[Tag]
    ( '(' ( properties+=[TagProperty|QualifiedName] ','? )* ')' )?;

Class:
    tagrefs+=TagReference*
	'class' name=ID
	( 'extends' (parents+=[Class|QualifiedName]','?)+ )?
	( '{' sections+=Section* '}' )?;

Section:
    PropertySection|MethodSection;

PropertySection:
    'properties' '{' properties+=Property* '}';

MethodSection:
    'methods' '{' methods+=Method* '}';

Member:
    Property|Method;

Property:
    tagrefs+=TagReference*
    class=[Class|QualifiedName] name=ID;

Method:
    tagrefs+=TagReference*
    class=[Class|QualifiedName]? name=ID
    ( '(' (parameters+=Parameter','?)* ')' )?
    body=Block;

Block:
    '{' (expressions+=Expression)* '}';

Parameter:
    class=[Class|QualifiedName] name=ID;

Symbol:
    Entity|Parameter|Member;

Expression:
    Primary ( {Selection.operand=current} levels+=SelectionLevel )*;

/*
 * The duplication of the 'Parameters' fragment together
 * with the syntactic predicates is messy. It would be
 * better to have one single rule for both and replace
 * the cross-reference with a regular ID terminal rule.
 */

Primary returns Expression:
    '(' Expression ')' |
    {Boolean} value=('true'|'false') |
    {Integer} value=INT |
    {This} 'this' |
    {Metadata} '?' class=[Class|QualifiedName] |
    {SymbolReference} symbol=[Symbol] => Parameters?;

SelectionLevel returns Expression:
    {MemberSelection} '.' member=[Member] => Parameters?;

fragment Parameters:
    parameterized ?= '(' (parameters+=Expression','?)* ')';


The generator would be:

package com.septentrio.infrastructure.flax.generator

import com.septentrio.infrastructure.flax.helper.FlaxHelper
import com.septentrio.infrastructure.flax.language.Class
import com.septentrio.infrastructure.flax.language.MethodSection
import com.septentrio.infrastructure.flax.language.Method
import com.septentrio.infrastructure.flax.language.Block
import com.septentrio.infrastructure.flax.language.Boolean
import com.septentrio.infrastructure.flax.language.Integer
import com.septentrio.infrastructure.flax.language.Metadata
import com.septentrio.infrastructure.flax.language.SymbolReference
import com.septentrio.infrastructure.flax.language.Selection
import com.septentrio.infrastructure.flax.language.MemberSelection
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.naming.IQualifiedNameProvider
import org.eclipse.xtext.generator.AbstractGenerator
import org.eclipse.xtext.generator.IFileSystemAccess2
import org.eclipse.xtext.generator.IGeneratorContext
import com.google.inject.Inject
import org.eclipse.emf.ecore.EObject
import java.util.ArrayDeque
import java.util.Map
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import com.septentrio.infrastructure.flax.language.Member
import com.septentrio.infrastructure.flax.test.Exception

class FlaxMatlabGenerator extends AbstractFlaxGenerator {
    private Logger logger = LoggerFactory.getLogger('FlaxGenerator')
    @Inject extension FlaxHelper
    @Inject extension IQualifiedNameProvider
    override def String generate(EObject it, FlaxScope scope) {
        logger.debug('''BODY: «it»''')
        switch it {
            case scope.isAssigned(it):
                scope.getVariableFor(it)
            Class:
                '''
                classdef «name» «generateAncestry»
                    «FOR section : sections»
                        methods «IF isTestCase»(Test)«ENDIF»
                            «IF section instanceof MethodSection»
                                «FOR method : section.methods»
                                    «method.generate(scope)»
                                «ENDFOR»
                            «ENDIF»
                        end
                    «ENDFOR»
                end
                '''
            Method:
                '''
                function «name»(this«IF !parameters.empty», «parameters.map[generate(scope)].join(', ')»«ENDIF»)
                    «body.generateInChildScope»
                end
                '''
            Block:
                '''
                «getPreambleFor(scope)»
                «FOR expression : expressions»
                    «expression.generate(scope)»;
                «ENDFOR»
                '''
            Selection:
                operand.generate(scope) + levels.map[generate(scope)].join
            Boolean:
                String.valueOf(value)
            Integer:
                String.valueOf(value)
            Metadata:
                '''flax.lang.Class.fromName('«class_.fullyQualifiedName»')'''
            SymbolReference: {
                switch symbol {
                    Class:
                        symbol.name
                    Member:
                        if (symbol.name.equals('assertInstanceOf') && containingClass.isTestCase)
                            '''this.flax__assertInstanceOf''' // circumvent sealed method
                        else
                            '''this.«symbol.name»'''
                    default:
                        throw new Exception('Unhandled symbol type')
                } +
                '''«IF isParameterized»(«parameters.map[generate(scope)].join(', ')»)«ENDIF»'''
            }
            MemberSelection:
                '''.«member.name»«IF isParameterized»(«parameters.map[generate(scope)].join(', ')»)«ENDIF»'''        }
    }
    private def String getPreambleFor(EObject it, FlaxScope scope) {
        logger.debug('''HEADER: «it»''')
        switch it {
            Block:
                '''
                «FOR expression: expressions»
                    «expression.getPreambleFor(scope)»
                «ENDFOR»
                '''
            SymbolReference:
                '''
                «IF symbol instanceof Class»
                    «generateVariableAssignment(scope)»
                «ENDIF»
                «IF parameterized»
                    «FOR parameter: parameters»
                        «parameter.getPreambleFor(scope)»
                    «ENDFOR»
                «ENDIF»
                '''
            Selection:
                '''
                «operand.getPreambleFor(scope)»
                «FOR level: levels»
                    «level.getPreambleFor(scope)»
                «ENDFOR»
                '''
            MemberSelection:
                '''
                «IF parameterized»
                    «FOR parameter: parameters»
                        «parameter.getPreambleFor(scope)»
                    «ENDFOR»
                «ENDIF»
                '''
            Metadata:
                generateVariableAssignment(scope)
        }
    }
    private def generateAncestry(Class it) {
        if (!parents.isEmpty)
            '''< «parents.map[fullyQualifiedName].join(' & ')»'''
        else if (isTestCase)
            '< flax.test.TestCase'
        else
            '< flax.lang.Object'
    }
    private def generateVariableAssignment(EObject it, FlaxScope scope) {
        val rhs = generate(scope)
        val lhs = scope.newVariableFor(it)
        '''«lhs» = «rhs»;'''.toString
    }
}

abstract class AbstractFlaxGenerator extends AbstractGenerator {
    private Logger logger = LoggerFactory.getLogger('AbstractFlaxGenerator')
    @Inject extension FlaxHelper
    val scopes = new ArrayDeque<FlaxScope>
    final override def void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) {
        for(e:resource.allContents.toIterable)
            switch e {
                Class:
                    fsa.generateFile(e.name+".m",e.generateInChildScope)
            }
    }
    final def generateInChildScope(EObject context) {
        val scope = new FlaxScope(this)
        scopes.push(scope)
        val text = generate(context, scope)
        scopes.pop
        return text
    }
    final def isTestCase(Class it) {
        val testTag = containingFlax.tags.findFirst[name == 'Test']
        logger.debug('''testTag = «testTag»''')
        logger.debug('''          tagrefs = «methods.map[tagrefs].flatten»''')
        logger.debug('''          «testTag == isSameOrChildOf('flax.test.TestCase')»''')
        logger.debug('''          «testTag == methods.map[tagrefs].flatten.exists[tag == testTag]»''')
        switch it {
            case isSameOrChildOf('flax.test.TestCase'):
                true
            case methods.map[tagrefs].flatten.exists[tag == testTag]:
                true
            default:
                false
        }
    }
    abstract def String generate(EObject context, FlaxScope scope)
}

class FlaxScope {
    private Logger logger = LoggerFactory.getLogger('FlaxScope')
    private AbstractFlaxGenerator generator
    private extension Map<EObject, String> variables = newHashMap
    private int count = 0
    new (AbstractFlaxGenerator generator) {
        this.generator = generator
    }
    def newVariableFor(EObject it) {
        switch put(it, '''v«count++»''') {
            case null: {
                logger.debug('''NEW VARIABLE «get(it)» for «it»''')
                get(it)
            }
            default:
                throw new Exception('')
        }
    }
    def isAssigned(EObject it) {
        containsKey(it)
    }
    def getVariableFor(EObject it) {
        get(it)
    }
}


And the stripped generator test class:

package com.septentrio.infrastructure.flax.tests

import com.septentrio.infrastructure.flax.helper.FlaxLibrary
import com.septentrio.infrastructure.flax.test.AssertionFailedException
import matlabcontrol.MatlabProxyFactory
import matlabcontrol.MatlabProxyFactoryOptions
import matlabcontrol.MatlabProxy
import matlabcontrol.MatlabConnectionException
import org.eclipse.xtext.validation.CheckMode
import org.eclipse.xtext.validation.IResourceValidator
import org.eclipse.xtext.generator.GeneratorContext
import org.eclipse.xtext.generator.GeneratorDelegate
import org.eclipse.xtext.generator.JavaIoFileSystemAccess
import org.eclipse.xtext.util.CancelIndicator
import org.eclipse.xtext.junit4.InjectWith
import org.eclipse.xtext.junit4.XtextRunner
import org.eclipse.xtext.junit4.TemporaryFolder
import org.eclipse.xtext.junit4.util.ResourceHelper
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.BeforeClass
import org.junit.AfterClass
import org.junit.Assert
import com.google.inject.Inject
import java.nio.file.Paths

@RunWith(XtextRunner)
@InjectWith(FlaxInjectorProvider)
class FlaxGeneratorTest {
    static private TemporaryFolder matlabFolder
    static private MatlabProxy proxy
    static private String matlabPath
    @Inject private GeneratorDelegate generator
    @Inject private JavaIoFileSystemAccess access
    @Inject private IResourceValidator validator
    @Inject extension ResourceHelper resourceHelper
    @Inject extension FlaxLibrary
    @BeforeClass
    def static void setup() throws MatlabConnectionException {
        matlabFolder = new TemporaryFolder()
        proxy = new MatlabProxyFactory(
            new MatlabProxyFactoryOptions.Builder()
                .setUsePreviouslyControlledSession(true)
                .setMatlabStartingDirectory(matlabFolder.getRoot)
                .setProxyTimeout(60000) // 1 minute
                .build
            ).getProxy
        matlabPath = 'path'.evalAsString
    }
    @AfterClass
    def static void teardown() {
        // proxy.exit()
    }

    /*
     * ----------------------------------------------------
     * flax.test package generator test
     * ----------------------------------------------------
     */

    @Test
    def void passingTest() {
        '''
        class TestCase {
            methods {
                @Test shouldPass {
                    assert(true)
                }
            }
        }
        '''
        .generate
        .runTest('TestCase')
    }

    /*
     * ----------------------------------------------------
     * Matlab specific code
     * ----------------------------------------------------
     */

    def private MatlabProxy generate(CharSequence text) {
        val resource = text.resource
        resource.resourceSet.loadFlaxLibrary
        val issues = validator.validate(resource, CheckMode.ALL, CancelIndicator.NullImpl);
        if (!issues.isEmpty) {
            val message = new StringBuilder()
                .append('Validation failed with the following issues:')
                .append(new StringBuilder() => [issues.forEach(issue|it.append('\n'+issue))])
                .toString
            Assert.fail(message)
        }
        generator.generate(
                resource,
                access => [outputPath = matlabFolder.getRoot.getAbsolutePath],
                new GeneratorContext() => [cancelIndicator = CancelIndicator.NullImpl]
        )
        'clear all'.eval
        'path'.feval(matlabPath)
        'cd'.feval(matlabFolder.root.toString)
        /*
         * ../build/classes/test/ ...
         * ../build/resources/matlab/ ...
         */
        val jarLocation = class.protectionDomain.codeSource.location
        val jarPath = Paths.get(jarLocation.toURI)
        val matlabLibraryRootPath = jarPath.resolve('../../resources/main/matlab/')
        'addpath'.feval(matlabLibraryRootPath.toString)
        return proxy
    }
    def private runTest(MatlabProxy proxy, String name) {
        '''result = runtests('«name»');'''.eval
        if ('numel(result)'.evalAsDouble == 0)
            throw new Exception('''Test «name» execution ended unexpectdly''')
        else if ('result.Failed'.evalAsBoolean == true) {
            val report = 'result.Details.DiagnosticRecord.Report'.evalAsString
            switch 'result.Details.DiagnosticRecord.Event'.evalAsString {
                case 'AssertionFailed':
                    throw new AssertionFailedException(report)
                default:
                    throw new Exception(report)
            }
        }
    }
    static private def eval(CharSequence text) {
        text.toString.eval
    }
    static private def eval(String text) {
        proxy.eval(text)
    }
    static private def evalAsDouble(String expression) {
        (expression.returningEval(1).get(0) as double[]).get(0)
    }
    static private def evalAsBoolean(String expression) {
        (expression.returningEval(1).get(0) as boolean[]).get(0)
    }
    static private def evalAsString(String expression) {
        expression.returningEval(1).get(0) as String
    }
    static private def returningEval(String string, int nargout) {
        proxy.returningEval(string, nargout)
    }
    static private def feval(String functionName, Object ... args) {
        proxy.feval(functionName, args)
    }
}


The error I see happens in the validation phase:

java.lang.AssertionError: Validation failed with the following issues:
ERROR:Couldn't resolve reference to Symbol 'assert'. (__synthetic0.flax line : 4 column : 13)

	at org.junit.Assert.fail(Assert.java:91)
	at com.septentrio.infrastructure.flax.tests.FlaxGeneratorTest.generate(FlaxGeneratorTest.java:335)
	at com.septentrio.infrastructure.flax.tests.FlaxGeneratorTest.passingTest(FlaxGeneratorTest.java:119)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
	at org.eclipse.xtext.junit4.XtextRunner$1.evaluate(XtextRunner.java:49)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
	at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)


The "assert" symbol is a reference to a method defined in the flax.lang.TestCase class:

package flax.test

import flax.lang.Class

tag Test

class TestCase {
    methods {
        assert(boolean actual) {}
        assertInstanceOf(Class actual) {}
    }
}


As you can see this class is not declared as an ancestor of the "TestCase" class definition of the unit test above. If added manually, the error message disappear, which is expected.

Does it make any sense?
Re: XText hook for model transformation? [message #1751796 is a reply to message #1751795] Sat, 14 January 2017 07:44 Go to previous messageGo to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
To answer you last questions:

I don't use XBase because I generate code to Matlab, C++ and Python.
I don't do any model-2-model through IDeriredStateComputer.
Validation is done manually in the beginning of the AbstractFlaxGenerator class (see snippet).
Re: XText hook for model transformation? [message #1751797 is a reply to message #1751795] Sat, 14 January 2017 07:50 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
such m2m can be done with IDerivedStateComputer. Inside you may use synthethiclinkingsupport to create an artifical Proxy for the superclass
(I am Not sure of that will work Since i have Never Tried that)



Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com
Re: XText hook for model transformation? [message #1751798 is a reply to message #1751797] Sat, 14 January 2017 07:58 Go to previous messageGo to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
Wouldn't it be simpler to just bind a custom parser that has some extra steps?

Something along these lines:

package com.septentrio.infrastructure.flax

import java.io.Reader
import org.eclipse.xtext.parser.IParseResult
import com.septentrio.infrastructure.flax.parser.antlr.FlaxParser
import com.septentrio.infrastructure.flax.test.Exception

class FlaxRichParser extends FlaxParser {
    override IParseResult doParse(Reader reader) {
        super.doParse(reader).transform
    }
    private def IParseResult transform(IParseResult result) {
        ...
    }
}


Can I modify an IParseResult instance?
Re: XText hook for model transformation? [message #1751799 is a reply to message #1751798] Sat, 14 January 2017 08:19 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
i dont know that. but the following seems to work

Model:
	entities+=Entity*
	refs+=MemberReference*
;


Entity:
	"entity" name=ID "{"
		members+=Member*
	"}"
;

Member:
	name=ID
;

MemberReference:
	("e" entity=[Entity])? "m" member=[Member] ";"
;


entity BaseClass {
	a
	b
	c
}

entity X {
	x
	y
	z
}

entity Other {
	
} 
m a;
m x;


class MyDslScopeProvider extends AbstractMyDslScopeProvider {
	
	override getScope(EObject context, EReference reference) {
		if (reference == MyDslPackage.Literals.MEMBER_REFERENCE__MEMBER && context instanceof MemberReference) {
			return Scopes.scopeFor((context as MemberReference).getEntity().getMembers());
		}
		super.getScope(context, reference)
	}

}


public class MyDslDerivedStateComputer implements IDerivedStateComputer {
	
	@Inject
	private SyntheticLinkingSupport syntheticLinkingSupport;

	@Override
	public void installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) {
		if (!resource.getContents().isEmpty()) {
			Model m = (Model) resource.getContents().get(0);
			for (MemberReference ref : m.getRefs()) {
				if (NodeModelUtils.findNodesForFeature(ref, MyDslPackage.Literals.MEMBER_REFERENCE__ENTITY).isEmpty()) {
					syntheticLinkingSupport.createAndSetProxy(ref, MyDslPackage.Literals.MEMBER_REFERENCE__ENTITY, "BaseClass");
				}
			}
				
		}
	}

	@Override
	public void discardDerivedState(DerivedStateAwareResource resource) {
		if (!resource.getContents().isEmpty()) {
			Model m = (Model) resource.getContents().get(0);
			for (MemberReference ref : m.getRefs()) {
				if (NodeModelUtils.findNodesForFeature(ref, MyDslPackage.Literals.MEMBER_REFERENCE__ENTITY).isEmpty()) {
					ref.setEntity(null);
				}
			}
				
		}
		
	}

}


class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
	
	def Class<? extends IDerivedStateComputer> bindIDerivedStateComputer() {
		MyDslDerivedStateComputer
	}
	
	def Class<? extends Manager> bindIResourceDescription$Manager() {
		DerivedStateAwareResourceDescriptionManager
	}
	
	override Class<? extends XtextResource> bindXtextResource() {
		DerivedStateAwareResource
	}
}




Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com
Re: XText hook for model transformation? [message #1751800 is a reply to message #1751799] Sat, 14 January 2017 08:26 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
p.s. alternatively you could simply fix the scoping for the members ;D

Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com
Re: XText hook for model transformation? [message #1751801 is a reply to message #1751800] Sat, 14 January 2017 08:35 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
e.g.

	
	override getScope(EObject context, EReference reference) {
		if (reference == MyDslPackage.Literals.MEMBER_REFERENCE__MEMBER && context instanceof MemberReference) {
			val e = (context as MemberReference).getEntity()
			if (e != null) {
				return Scopes.scopeFor(e.getMembers());
			} else {
				val namespaceResolvers = #[new ImportNormalizer(QualifiedName.create("BaseClass"), true, false)];
				val parent = delegateGetScope(context,reference)
				return new ImportScope(namespaceResolvers, parent, null, MyDslPackage.Literals.MEMBER, false)
			}
			
		}
		super.getScope(context, reference)
	}

}


Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com
Re: XText hook for model transformation? [message #1751802 is a reply to message #1751800] Sat, 14 January 2017 08:39 Go to previous messageGo to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
Yes indeed I could fix the scoping, which is actually what Lorenzo suggests in his book. In my case that would mean having extra logic in different places (scope provider and each code generator) which are actually very similar. I'm a bit of a freak when I smell code duplication so I prefer to find a clean solution. Additionally I intend to do more complicated model transformation in the future, so definitely not looking at a doomed-to-be-unmaintainable source tree Smile

I will experiment with your suggestion.
Re: XText hook for model transformation? [message #1751803 is a reply to message #1751802] Sat, 14 January 2017 08:42 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
http://xtextcasts.org/episodes/18-model-optimization my give you some further reading

Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com
Re: XText hook for model transformation? [message #1751842 is a reply to message #1751803] Mon, 16 January 2017 07:35 Go to previous messageGo to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
I played around and injected the following DerivedStateComputer implementation:

class FlaxDerivedStateComputer implements IDerivedStateComputer {
    static val private CLASS__PARENTS = LanguagePackage.Literals.CLASS__PARENTS
    static val logger = LoggerFactory.getLogger('FlaxDerivedStateComputer')
    @Inject private extension SyntheticLinkingSupport syntheticLinkingSupport
    @Inject private extension FlaxHelper
    @Inject private extension IQualifiedNameProvider
    override installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) {
        if (!resource.contents.isEmpty)
            for (class: resource.model.classes)
                if(class.containsTag('flax.test.Test'))
                    if(!class.isSameOrChildOf('flax.test.TestCase'))
                        class.createAndSetProxy(CLASS__PARENTS, 'flax.test.TestCase')
    }
    override discardDerivedState(DerivedStateAwareResource resource) {
        if(!resource.contents.isEmpty)
            for(class: resource.model.classes)
                if(class.containsTag('flax.test.Test'))
                    if(!class.isSameOrChildOf('flax.test.TestCase'))
                        class.createAndSetProxy(CLASS__PARENTS, StringUtils.EMPTY)
    }
    private def getModel(Resource it) {
        contents.get(0) as Flax
    }
    private def getMethodsTags(Class it) {
        methods.map[tagrefs].flatten.map[tag]
    }
    private def containsTag(Class it, String tagName) {
        methodsTags.exists[fullyQualifiedName.toString == tagName]
    }
    private def isSameOrChildOf(Class it, String className) {
        ancestors.exists[fullyQualifiedName.toString == className]
    }
}


And it works fine as long as I replace the CLASS__PARENTS feature entirely with something new. Apparently I cannot mutate the multi-valued feature (class.parents = <something> is not possible) to, for example, extends it with a single reference. I assume I need to go via the SyntheticLinkingSupport.createAndSetProxy method which only support string input. This means that I need to use the syntax of my language at this point, which feels odd. I would rather use a syntax agnostic method, where for example I would simply extends the multi-valued feature CLASS__PARENTS with an EReference object built from scratch. I could not find such method.

Any idea to make this cleaner?
Re: XText hook for model transformation? [message #1751845 is a reply to message #1751842] Mon, 16 January 2017 07:57 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 10786
Registered: July 2009
Senior Member
i dont get that. that should work with a multi value as well, you might need to digg into the helper and maybe do some copy & paste.
unfortunately i dont have the time to digg into that


Need professional support for Xtext, Xpand, EMF?
Go to: http://xtext.itemis.com
Twitter : @chrdietrich
Blog : christiandietrich.wordpress.com

[Updated on: Mon, 16 January 2017 07:58]

Report message to a moderator

Re: XText hook for model transformation? [message #1751856 is a reply to message #1751845] Mon, 16 January 2017 10:15 Go to previous messageGo to next message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
Ideas are clearer after a coffee.

This works fine:

override installDerivedState(DerivedStateAwareResource resource, boolean preLinkingPhase) {
        if (!resource.contents.isEmpty)
            for (class: resource.model.classes)
                if(class.containsTag('flax.test.Test'))
                    if(!class.isSameOrChildOf('flax.test.TestCase'))
                        class.parents.add(class.getLibraryClass('flax.test.TestCase'))
    }


Next question:

How can I differentiate a TestCase parent that was added by installDerivedState from one that was parsed? Is there a way to attach information on-the-fly to EMF nodes?
Re: XText hook for model transformation? [message #1751858 is a reply to message #1751856] Mon, 16 January 2017 10:21 Go to previous message
Eric Salemi is currently offline Eric SalemiFriend
Messages: 34
Registered: September 2016
Location: Belgium
Member
I think I'm getting it:

Semantic elements that were parsed have nodes attached to them. Like in your example I should remove TestCase ancestors that have no nodes attached.
Previous Topic:Reference across multiple files not working
Next Topic:Differences in URI values
Goto Forum:
  


Current Time: Mon Jun 26 02:07:24 GMT 2017

Powered by FUDForum. Page generated in 0.03351 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software