Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » TMF (Xtext) » [Xtext 2.1.1][SOLVED] create custom loop scope(How to use a XExpression's return type as type of a local variable?)
[Xtext 2.1.1][SOLVED] create custom loop scope [message #757391] Fri, 18 November 2011 08:27 Go to next message
Max Goltzsche is currently offline Max GoltzscheFriend
Messages: 40
Registered: November 2011
Member
Hey,

In the view modelling part of my language I am working on a for loop that only contains my custom grammar rule ViewElement as "eachExpression". ViewElement itself can contain other loops and ViewElements which can contain JvmOperations with XExpressions in which the for loop's local variable should be accessible.

Sample language usage:
	view myView(de.algorythm.sth.MyEntity e) {
		for a : e.children {
			text a.name

			for b : e.childrenB
				button action=b.doSth() label="do"
		}
	}


To realise it I've build the following grammar extract:
CtrlLoop:
	(name=ValidID ':')? 'for' (variable=JvmFormalParameter ':' iterable=XExpression)
		(=>each=ViewElement);


A JvmType is inferred from every View (containing ViewElements) as boundary class. Each XExpression in a ViewElement's attribute is the body of a JvmOperation.
Such JvmOperation's scope has to contain all parent loop variables.

The following method in my IJvmModelInferrer tries to enforce an Iterable return type in the iterable XExpression:
	def protected dispatch void inferBoundaryMethods(CtrlLoop expr, JvmGenericType jvmType) {
		if (expr.iterable != null && expr.variable != null) {
			val varType = expr.variable.parameterType
			
			val returnType = if (varType == null || varType.type == null)
					expr.newTypeRef(typeof(Iterable))
				else
					expr.newTypeRef(typeof(Iterable), varType)
			
			expr.inferBoundaryMethod(jvmType, "retrieve" + expr.name.toFirstUpper + "Iterable", returnType, expr.iterable, scope)
		}
		
		expr.proceedInferViewElements(jvmType)
	}

But you can also write a JvmFormalParameter without explicit type declaration so that the type is derived from an XExpression. That's what I do in my ScopeProvider's method createVarDescription every time retrieving the scope of a XExpression inside my CtrlLoop:
class CrudslScopeProvider extends XbaseScopeProvider {
	
	@Inject extension IJvmModelAssociations
		
	override protected IScope createLocalVarScopeForJvmOperation(JvmOperation context, IScope parentScope) {
		val scope = super.createLocalVarScopeForJvmOperation(context, parentScope)
		val crudslCtx = context.primarySourceElement

		if (crudslCtx instanceof ENamedElement)
			return createLoopVarScope(crudslCtx as ENamedElement, scope)
		
		return scope
	}
	
	/** returns a JvmFeatureScope of the loop variables of all parent loops */
	def private createLoopVarScope(ENamedElement element, IScope parentScope) {
		val Collection<IValidatedEObjectDescription> descriptions = newLinkedList()
		var ENamedElement e = element
		
		while ((e = e.parentLoop) != null) {
			val loop = e as CtrlLoop
			val v = loop.variable
			
			if (v.name != null)
				descriptions.add(v.createVarDescription(loop.iterable))
		}
		
		return if (descriptions.empty)
			parentScope
		else
			new JvmFeatureScope(parentScope, "JvmFormalParameter", descriptions)
	}
	
	def private parentLoop(ENamedElement expr) {
		EcoreUtil2::getContainerOfType(expr.eContainer, typeof(CtrlLoop))
	}
	
	def private createVarDescription(JvmFormalParameter variable, XExpression expr) {
		if (variable.parameterType == null) {
			val exprType = typeProvider.getType(expr, true)
			
			if (exprType != null)
				try {
					val pExprType = exprType as JvmParameterizedTypeReference
			
					variable.parameterType = pExprType.arguments.head
				} catch(ClassCastException e) {}
		}
		
		variable.createLocalVarDescription
	}
}

Now the plugin works, partially:
If I include a mistake in the iterable expression of a CtrlLoop the loop variable's type is null, of course. But if I then correct the iterable expression no more "problems" are listed in eclipse but the text editor does not display the right scope for my local loop var and underlines its usage red. From this point on, I can only get rid of the red underlined screen by closing and reopening my custom language file, although I have removed all errors.
Somehow, my loop's scope is calculated several times. one time its type is null. next time it is the wanted type but in the end the variable's type is handled as null.

The model is inferred, firstly, then scopes are calculated. Am I right?
Scope calculation runs once from top to bottom, normally?!
I am a little confused.
Please help!!!

#1 What am I doing wrong? What do I misunderstand?

#2 Is there a better way to do this?


EDIT: A better approach would be to handle the loop variable as parameter of every JvmOperation created from a XExpression inside a loop but typeProvider.getType(expr, true) is null then. I would have to evaluate all iterable expression's return types after everything is initialized. But after the first expression in the first view is evaluated it's inferred JvmOperation would be available in the second expression (or not)... but this is not neccessary... I may outsource every loop in an own "virtual" JvmType extending my view's JvmType and then evaluate all views after every controller and entity (which I could access in the expressions) is inferred and do not write an own ScopeProvider?!
But am I able to get the type of a XExpression via typeProvider.getType(expr, true) while inferring the jvm model? Scoping runs afterwards and only after this is done a XExpression's type can be calculated?!

regards,
Max

[Updated on: Sun, 20 November 2011 09:45]

Report message to a moderator

Re: [Xtext 2.1.1] create custom loop scope [message #757578 is a reply to message #757391] Sat, 19 November 2011 20:07 Go to previous messageGo to next message
Max Goltzsche is currently offline Max GoltzscheFriend
Messages: 40
Registered: November 2011
Member
Any ideas? *push*
Re: [Xtext 2.1.1] create custom loop scope [message #757596 is a reply to message #757578] Sun, 20 November 2011 02:06 Go to previous messageGo to next message
Max Goltzsche is currently offline Max GoltzscheFriend
Messages: 40
Registered: November 2011
Member
I think it is a bug.
Finally, I realised that getType(expr) can also be used in the jvminterferrer, so that it is not neccessary to implement a scopeprovider but to have all loop vars in a stack while inferring and add them to every JvmOperation which is created for an expression inside a view - works well, but the error does not disappear, anyway.
A clean call also does not help. Only closing and reopening the window or completely changing the loop expression before changing it back helps. Else the editor marks local loop var access as error while it is not displayed in the "Problems" tab and is no error.

[Updated on: Sun, 20 November 2011 02:59]

Report message to a moderator

Re: [Xtext 2.1.1] create custom loop scope [message #757602 is a reply to message #757596] Sun, 20 November 2011 09:20 Go to previous message
Max Goltzsche is currently offline Max GoltzscheFriend
Messages: 40
Registered: November 2011
Member
The biggest bug always sits in front of the computer.
My first mistake was that the loop variable's JvmTypeReference somtimes was a proxy with type null and I didn't update the type because I (and therefore my code) "thought" the variable's type was already declared.
My other mistake was I didn't cloneWithProxies the evaluted JvmTypeReference what lead to null types at other places because an ecore model's object can only have one parent.

Now, I don't need a ScopeProvider any more but a little helper class InferredViewData to hold the local var stack and minimize the argument count of the recursive inferBoundaryMethods() method.
Here is my last solution:

Dsl syntax extract:
View:
	'view' name=ValidID '(' (args+=JvmFormalParameter (',' args+=JvmFormalParameter)*)? ')' '{'
		elements+=ViewElement*
	'}';

ViewElement returns ecore::ENamedElement:
	CtrlBlock | Button | ...

CtrlLoop:
	(name=ValidID ':')? 'for' declaredParam=JvmFormalParameter ':' iterable=XExpression
		eachElement=ViewElement;

JvmModelInferrer extract:
class CrudslJvmModelInferrer implements IJvmModelInferrer {

	@Inject extension JvmTypesBuilder typeBuilder
	
	@Inject extension TypeReferences typeReferences
	
	@Inject extension ITypeProvider typeProvider
	
	@Inject extension IQualifiedNameProvider


	def infer ...

	def protected initView(View view) {
		val jvmType = view.toClass(view.fullyQualifiedName, null)
		val inferredViewData = new InferredViewData(jvmType, typeBuilder, typeProvider, typeReferences)
		
		jvmType.members += view.toConstructor(view.name) [
			for (a : view.args)
				parameters += a.toParameter(a.name, a.parameterType)
		]
		
		for (a : view.args)
			jvmType.members += view.toField(a.name, a.parameterType)
		
		view.elements.inferBoundaryMethods(inferredViewData)
	}
	
	def protected dispatch void inferBoundaryMethods(CtrlLoop loop, InferredViewData view) {
		val param = loop.declaredParam
		val isParamTypeDeclared = 
		
		if (loop.iterable != null) {
			// define iterable expression's expected type as detailed as possible
			val expectedType = if (param == null || param.parameterType == null || param.parameterType.type == null)
					// enforce some iterable return type
					loop.newTypeRef(typeof(Iterable))
				else
					// use declared loop var type
					loop.newTypeRef(typeof(Iterable), param.parameterType)
			
			// create JvmOperation of view: has to run before getType() call to be able to access view properties
			view.createMethod(loop, "Iterable", loop.iterable, expectedType)
			
			// evaluate iterable expression's type
			val exprType = loop.iterable.getType(true)
			
			// declare local loop var type inferred from interable expression type
			if (exprType != null)
				try {
					val pExprType = exprType as JvmParameterizedTypeReference
					
					if (param != null && (param.parameterType == null || param.parameterType.type == null))
						param.parameterType = EcoreUtil2::cloneWithProxies(pExprType.arguments.head)
				} catch(ClassCastException e) { }
		}

		if (param != null)
			view.pushLocalVar(param)

		loop.eachElement.inferBoundaryMethods(view)
		
		if (param != null)
			view.popLocalVar()
	}

	// ... {more dispatched methods}
}


InferredViewData:
public class InferredViewData {
	
	final private JvmGenericType type;
	final private Stack<JvmFormalParameter> localVarScope;
	final private JvmTypesBuilder typeBuilder;
	final private ITypeProvider typeProvider;
	final private TypeReferences typeReferences;
	private int count;
	
	public InferredViewData(final JvmGenericType type, 
			final JvmTypesBuilder typeBuilder, final ITypeProvider typeProvider,
			final TypeReferences typeReferences) {
		this.type = type;
		this.localVarScope = new Stack<JvmFormalParameter>();
		this.typeBuilder = typeBuilder;
		this.typeProvider = typeProvider;
		this.typeReferences = typeReferences;
	}
	
	public void pushLocalVar(final JvmFormalParameter localVar) {
		localVarScope.push(localVar);
	}
	
	public void popLocalVar() {
		localVarScope.pop();
	}
	
	public void createMethod(final ENamedElement viewElement, String suffix, final XExpression expr, final JvmTypeReference returnType) {
		final Set<String> uniqueVarNames = new HashSet<String>(); 
		final Collection<JvmFormalParameter> currentLocalVarScope = new LinkedList<JvmFormalParameter>();
		
		for (JvmFormalParameter a : localVarScope)
			if (uniqueVarNames.add(a.getName()))
				currentLocalVarScope.add(a);
		
		final Procedure1<JvmOperation> initializer = new Procedure1<JvmOperation>() {
			public void apply(final JvmOperation op) {
				typeBuilder.setBody(op, expr);
				
				for (JvmFormalParameter p : currentLocalVarScope)
					op.getParameters().add(EcoreUtil2.cloneWithProxies(p));
			}
		};
		
		String elementName = viewElement.getName();
		elementName = elementName == null ? "Expr" + count++ : elementName.substring(0, 1).toUpperCase() + elementName.substring(1); 
		
		JvmOperation exprFunction = typeBuilder.toMethod(viewElement, "retrieve" + elementName + suffix, returnType, initializer);

		type.getMembers().add(exprFunction);
	}
}


Code with this dsl looks like:
view testView(de.algorythm.test.MyEntity e) {
	text e.name
	
	for a : e.children {
		text a.name
		
		for b : e.childrenB {
			text b.name.substring(0, 2)
		}
	}
	
	button "click me!" action = e.testMethode("asdf")
}


Now, in a view expression I can also use other already inferred methods but it doesn't really work because for some reason the return type of the method is not correct. In every case I do not want the user call other expressions declared in the view.
Is there a simple way to forbid calls of member methods in such JvmOperations?

regards,
Max
Previous Topic:How to add attributes to an abstract component
Next Topic:Grammar rules ordering
Goto Forum:
  


Current Time: Thu Mar 28 23:53:23 GMT 2024

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

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

Back to the top