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 03:27  |
Eclipse User |
|
|
|
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 04:45] by Moderator
|
|
| | |
Re: [Xtext 2.1.1] create custom loop scope [message #757602 is a reply to message #757596] |
Sun, 20 November 2011 04:20  |
Eclipse User |
|
|
|
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
|
|
|
Goto Forum:
Current Time: Wed Jul 23 22:28:12 EDT 2025
Powered by FUDForum. Page generated in 0.05978 seconds
|