XText hook for model transformation? [message #1751792] |
Sat, 14 January 2017 06:58 |
|
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 #1751795 is a reply to message #1751794] |
Sat, 14 January 2017 07:40 |
|
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 |
|
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 #1751798 is a reply to message #1751797] |
Sat, 14 January 2017 07:58 |
|
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 |
|
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
}
}
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
|
|
|
|
Re: XText hook for model transformation? [message #1751801 is a reply to message #1751800] |
Sat, 14 January 2017 08:35 |
|
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)
}
}
Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
|
|
|
Re: XText hook for model transformation? [message #1751802 is a reply to message #1751800] |
Sat, 14 January 2017 08:39 |
|
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
I will experiment with your suggestion.
|
|
|
|
Re: XText hook for model transformation? [message #1751842 is a reply to message #1751803] |
Mon, 16 January 2017 07:35 |
|
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 #1751856 is a reply to message #1751845] |
Mon, 16 January 2017 10:15 |
|
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?
|
|
|
|
Powered by
FUDForum. Page generated in 0.03784 seconds