/******************************************************************************* | |
* Copyright (c) 2008 The University of York. | |
* This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License 2.0 | |
* which is available at https://www.eclipse.org/legal/epl-2.0/ | |
* | |
* Contributors: | |
* Dimitrios Kolovos - initial API and implementation | |
******************************************************************************/ | |
package org.eclipse.epsilon.eol.dom; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Iterator; | |
import java.util.WeakHashMap; | |
import java.util.List; | |
import java.util.Map; | |
import org.eclipse.epsilon.common.module.IModule; | |
import org.eclipse.epsilon.common.parse.AST; | |
import org.eclipse.epsilon.eol.compile.context.EolCompilationContext; | |
import org.eclipse.epsilon.eol.exceptions.EolIllegalReturnException; | |
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException; | |
import org.eclipse.epsilon.eol.execute.Return; | |
import org.eclipse.epsilon.eol.execute.context.FrameStack; | |
import org.eclipse.epsilon.eol.execute.context.FrameType; | |
import org.eclipse.epsilon.eol.execute.context.IEolContext; | |
import org.eclipse.epsilon.eol.execute.context.Variable; | |
import org.eclipse.epsilon.eol.execute.operations.contributors.IterableOperationContributor; | |
import org.eclipse.epsilon.eol.parse.EolParser; | |
import org.eclipse.epsilon.eol.types.EolAnyType; | |
import org.eclipse.epsilon.eol.types.EolNoType; | |
import org.eclipse.epsilon.eol.types.EolType; | |
public class Operation extends AnnotatableModuleElement implements ICompilableModuleElement { | |
protected NameExpression nameExpression; | |
protected TypeExpression contextTypeExpression; | |
protected TypeExpression returnTypeExpression; | |
protected EolType contextType; | |
protected EolType returnType; | |
protected StatementBlock body; | |
protected List<Parameter> formalParameters; | |
protected boolean isCached; | |
// If this operation cannot be cached, it will stay null, to save some memory. | |
// Note that this cache needs to be thread-safe! | |
protected Map<Object, Object> cache; | |
//TODO: Add guards to helpers | |
public Operation() { | |
} | |
@Override | |
public void build(AST cst, IModule module) { | |
super.build(cst, module); | |
AST nameAst = null; | |
if (cst.getFirstChild().getType() == EolParser.TYPE) { | |
AST contextTypeExpressionAst = cst.getFirstChild(); | |
contextTypeExpression = (TypeExpression) module.createAst(contextTypeExpressionAst, this); | |
nameAst = contextTypeExpressionAst.getNextSibling(); | |
} | |
else { | |
nameAst = cst.getFirstChild(); | |
} | |
AST paramListAst = null; | |
AST returnAst = null; | |
AST bodyAst = null; | |
if (nameAst.getNextSibling().getType() == EolParser.PARAMLIST) { | |
paramListAst = nameAst.getNextSibling(); | |
} | |
if (paramListAst != null) { // helper with parameters | |
if (paramListAst.getNextSibling().getType() == EolParser.TYPE) { // with return type | |
returnAst = paramListAst.getNextSibling(); | |
bodyAst = returnAst.getNextSibling(); | |
} | |
else { // without return type | |
bodyAst = paramListAst.getNextSibling(); | |
} | |
} | |
else { // helper without parameters | |
if (nameAst.getNextSibling().getType() == EolParser.TYPE) { //with return type | |
returnAst = nameAst.getNextSibling(); | |
bodyAst = returnAst.getNextSibling(); | |
} | |
else { // without return type | |
bodyAst = nameAst.getNextSibling(); | |
} | |
} | |
this.nameExpression = (NameExpression) module.createAst(nameAst, this); | |
this.returnTypeExpression = (TypeExpression) module.createAst(returnAst, this); | |
this.body = (StatementBlock) module.createAst(bodyAst, this); | |
if (paramListAst != null) { | |
List<AST> paramListAstChildren = paramListAst.getChildren(); | |
formalParameters = new ArrayList<>(paramListAstChildren.size()); | |
for (AST formalParameterAst : paramListAst.getChildren()) { | |
formalParameters.add((Parameter) module.createAst(formalParameterAst, this)); | |
} | |
} | |
else { | |
formalParameters = Collections.emptyList(); | |
} | |
if ((isCached = hasAnnotation("cached") && formalParameters.isEmpty()) == true) { | |
this.cache = Collections.synchronizedMap(new WeakHashMap<>()); | |
} | |
} | |
@Override | |
public void compile(EolCompilationContext context) { | |
EolType contextType = EolNoType.Instance; | |
if (contextTypeExpression != null) { | |
contextTypeExpression.compile(context); | |
contextType = contextTypeExpression.getCompilationType(); | |
} | |
context.getFrameStack().enterLocal(FrameType.PROTECTED, this, new Variable("self", contextType)); | |
for (Parameter parameter : formalParameters) { | |
parameter.compile(context); | |
} | |
body.compile(context); | |
context.getFrameStack().leaveLocal(this); | |
} | |
public void clearCache() { | |
if (isCached()) { | |
cache.clear(); | |
} | |
// This is important for EUnit, as it ensures that the cached | |
// type information will be re-evaluated for the reloaded models | |
contextType = returnType = null; | |
for (Parameter formalParameter : formalParameters) { | |
formalParameter.clearCache(); | |
} | |
} | |
@Override | |
public String toString() { | |
String contextTypeName = ""; | |
String returnTypeName = ""; | |
if (contextTypeExpression != null) { | |
contextTypeName = " - " + contextTypeExpression.getName();// + "."; | |
} | |
if (returnTypeExpression != null) { | |
returnTypeName = " : " + returnTypeExpression.getName(); | |
} | |
return getName() + "(" + new IterableOperationContributor(formalParameters).concat(", ") + ")" + returnTypeName + contextTypeName; | |
} | |
public boolean isCached() { | |
return isCached; | |
} | |
public Object execute(Object self, List<?> parameterValues, IEolContext context) throws EolRuntimeException { | |
return execute(self, parameterValues, context, true); | |
} | |
public Object execute(Object self, List<?> parameterValues, IEolContext context, boolean inNewStackFrame) throws EolRuntimeException { | |
final int paramSize = formalParameters.size(); | |
assert paramSize > 0 ? paramSize == parameterValues.size() : true; | |
boolean cacheContainsSelf = isCached && cache.containsKey(self); | |
if (cacheContainsSelf) { | |
return cache.get(self); | |
} | |
FrameStack scope = context.getFrameStack(); | |
if (inNewStackFrame) { | |
scope.enterLocal(FrameType.PROTECTED, this, Variable.createReadOnlyVariable("self", self)); | |
} | |
Iterator<?> parameterValuesIter = parameterValues.iterator(); | |
for (Parameter fp : formalParameters) { | |
scope.put(new Variable(fp.getName(), parameterValuesIter.next(), fp.getType(context))); | |
} | |
evaluatePreConditions(context); | |
Object result = Return.getValue(executeBody(context)); | |
checkResultType(result, context); | |
evaluatePostConditions(context, result); | |
if (inNewStackFrame) { | |
scope.leaveLocal(this); | |
} | |
// cache.containsKey(self) == false; | |
if (isCached && !cacheContainsSelf) { | |
cache.put(self, result); | |
} | |
return result; | |
} | |
protected Object executeBody(IEolContext context) throws EolRuntimeException { | |
return context.getExecutorFactory().execute(this.getBody(), context); | |
} | |
protected void evaluatePreConditions(IEolContext context) throws EolRuntimeException { | |
for (Annotation annotation : getAnnotations("pre")) { | |
if (!(annotation instanceof ExecutableAnnotation)) continue; | |
Object satisfied = ((ExecutableAnnotation)annotation).getValue(context); | |
if (satisfied instanceof Boolean) { | |
if (!(boolean) satisfied) { | |
throw new EolRuntimeException("Pre-condition not satisfied", annotation); | |
} | |
} | |
else { | |
throw new EolIllegalReturnException("Boolean", satisfied, annotation, context); | |
} | |
} | |
} | |
protected void checkResultType(Object result, IEolContext context) throws EolRuntimeException { | |
if (returnTypeExpression != null && result != null) { | |
if (returnType == null) { | |
returnType = (EolType) context.getExecutorFactory().execute(returnTypeExpression, context); | |
} | |
if (!returnType.isKind(result)) { | |
throw new EolRuntimeException(getName() + " is expected to return a " + returnType.getName() + ", but returned a " + result.getClass().getCanonicalName()); | |
} | |
} | |
} | |
protected void evaluatePostConditions(IEolContext context, Object result) throws EolRuntimeException { | |
context.getFrameStack().put(Variable.createReadOnlyVariable("_result", result)); | |
for (Annotation annotation : getAnnotations("post")) { | |
if (!(annotation instanceof ExecutableAnnotation)) continue; | |
Object satisfied = ((ExecutableAnnotation) annotation).getValue(context); | |
if (satisfied instanceof Boolean) { | |
if (!(boolean) satisfied) { | |
// We can simply use the frame stack here, as we created a frame for the post-condition | |
// in order to isolate the _result variable. | |
throw new EolRuntimeException( | |
"Post-condition not satisfied: _result was " | |
+ context.getPrettyPrinterManager().print(result)); | |
} | |
} | |
else { | |
throw new EolIllegalReturnException("Boolean", satisfied, annotation, context); | |
} | |
} | |
} | |
public EolType getReturnType(IEolContext context) throws EolRuntimeException{ | |
if (returnType == null) { | |
if (returnTypeExpression != null) { | |
returnType = (EolType) context.getExecutorFactory().execute(returnTypeExpression, context); | |
} | |
else { | |
returnType = EolAnyType.Instance; | |
} | |
} | |
return returnType; | |
} | |
public EolType getContextType(IEolContext context) throws EolRuntimeException { | |
if (contextType == null) { | |
if (contextTypeExpression != null) { | |
contextType = (EolType) context.getExecutorFactory().execute(contextTypeExpression, context); | |
} | |
else { | |
contextType = EolNoType.Instance; | |
} | |
} | |
return contextType; | |
} | |
public String getName() { | |
return nameExpression.getName(); | |
} | |
public List<Parameter> getFormalParameters() { | |
return formalParameters; | |
} | |
public StatementBlock getBody() { | |
return body; | |
} | |
public void setBody(StatementBlock body) { | |
this.body = body; | |
} | |
public NameExpression getNameExpression() { | |
return nameExpression; | |
} | |
public void setNameExpression(NameExpression nameExpression) { | |
this.nameExpression = nameExpression; | |
} | |
public TypeExpression getContextTypeExpression() { | |
return contextTypeExpression; | |
} | |
public void setContextTypeExpression(TypeExpression contextTypeExpression) { | |
this.contextTypeExpression = contextTypeExpression; | |
} | |
public TypeExpression getReturnTypeExpression() { | |
return returnTypeExpression; | |
} | |
public void setReturnTypeExpression(TypeExpression returnTypeExpression) { | |
this.returnTypeExpression = returnTypeExpression; | |
} | |
} |