blob: b83cc5a10056ab5a4c090ce835802c38f8b6e7d5 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 Obeo.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.acceleo.query.parser;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.acceleo.query.ast.And;
import org.eclipse.acceleo.query.ast.Binding;
import org.eclipse.acceleo.query.ast.BooleanLiteral;
import org.eclipse.acceleo.query.ast.Call;
import org.eclipse.acceleo.query.ast.Conditional;
import org.eclipse.acceleo.query.ast.EnumLiteral;
import org.eclipse.acceleo.query.ast.Expression;
import org.eclipse.acceleo.query.ast.Implies;
import org.eclipse.acceleo.query.ast.IntegerLiteral;
import org.eclipse.acceleo.query.ast.Lambda;
import org.eclipse.acceleo.query.ast.Let;
import org.eclipse.acceleo.query.ast.NullLiteral;
import org.eclipse.acceleo.query.ast.Or;
import org.eclipse.acceleo.query.ast.RealLiteral;
import org.eclipse.acceleo.query.ast.SequenceInExtensionLiteral;
import org.eclipse.acceleo.query.ast.SetInExtensionLiteral;
import org.eclipse.acceleo.query.ast.StringLiteral;
import org.eclipse.acceleo.query.ast.TypeLiteral;
import org.eclipse.acceleo.query.ast.TypeSetLiteral;
import org.eclipse.acceleo.query.ast.VarRef;
import org.eclipse.acceleo.query.ast.util.AstSwitch;
import org.eclipse.acceleo.query.runtime.EvaluationResult;
import org.eclipse.acceleo.query.runtime.IReadOnlyQueryEnvironment;
import org.eclipse.acceleo.query.runtime.impl.EvaluationServices;
import org.eclipse.acceleo.query.runtime.impl.LambdaValue;
import org.eclipse.acceleo.query.runtime.impl.Nothing;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
/**
* Evaluates the asts.
*
* @author <a href="mailto:romain.guider@obeo.fr">Romain Guider</a>
*/
public class AstEvaluator extends AstSwitch<Object> {
/**
* Message used to report bad predicate typing runtime detection.
*/
private static final String BAD_PREDICATE_TYPE_MSG = "Conditional's predicate must evaluate to a boolean value but was %s instead.";
/**
* Variable definitions used during evaluation.
*/
private final Deque<Map<String, Object>> variablesStack = new ArrayDeque<Map<String, Object>>();
/**
* The evaluation services.
*/
private final EvaluationServices services;
/** Aggregated status of an evaluation. */
private Diagnostic diagnostic;
/**
* Creates a new {@link AstEvaluator} instance given an {@link IReadOnlyQueryEnvironment} instance.
*
* @param queryEnv
* the environment used to evaluate
* @deprecated use {@link #AstEvaluator(EvaluationServices)}
*/
public AstEvaluator(IReadOnlyQueryEnvironment queryEnv) {
this(new EvaluationServices(queryEnv));
}
/**
* Creates a new {@link AstEvaluator} instance given an {@link EvaluationServices} instance.
*
* @param services
* the {@link EvaluationServices} used to evaluate
*/
public AstEvaluator(EvaluationServices services) {
this.services = services;
}
/**
* Pushes the given variables into the stack.
*
* @param variables
* the variables to push
*/
protected void pushVariables(Map<String, Object> variables) {
variablesStack.addLast(variables);
}
/**
* Peeks the last {@link #pushVariables(Map) pushed} variables from the stack.
*
* @return the last {@link #pushVariables(Map) pushed} variables from the stack
*/
protected Map<String, Object> peekVariables() {
return variablesStack.peekLast();
}
/**
* Pops the last {@link #pushVariables(Map) pushed} variables from the stack.
*
* @return the last {@link #pushVariables(Map) pushed} variables from the stack
*/
protected Map<String, Object> popVariables() {
return variablesStack.removeLast();
}
/**
* Evaluates the specified expression given the specified variable definitions.
*
* @param varDefinitions
* the variable definitions
* @param ast
* the ast to evaluate.
* @return the evaluation of the specified ast.
*/
public EvaluationResult eval(Map<String, Object> varDefinitions, Expression ast) {
pushVariables(varDefinitions);
diagnostic = new BasicDiagnostic();
final Object result = doSwitch(ast);
popVariables();
return new EvaluationResult(result, diagnostic);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseBooleanLiteral(org.eclipse.acceleo.query.ast.BooleanLiteral)
*/
@Override
public Object caseBooleanLiteral(BooleanLiteral object) {
return object.isValue();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseIntegerLiteral(org.eclipse.acceleo.query.ast.IntegerLiteral)
*/
@Override
public Object caseIntegerLiteral(IntegerLiteral object) {
return object.getValue();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseRealLiteral(org.eclipse.acceleo.query.ast.RealLiteral)
*/
@Override
public Object caseRealLiteral(RealLiteral object) {
return object.getValue();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseStringLiteral(org.eclipse.acceleo.query.ast.StringLiteral)
*/
@Override
public Object caseStringLiteral(StringLiteral object) {
return object.getValue();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseTypeLiteral(org.eclipse.acceleo.query.ast.TypeLiteral)
*/
@Override
public Object caseTypeLiteral(TypeLiteral object) {
return object.getValue();
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseCall(org.eclipse.acceleo.query.ast.Call)
*/
@Override
public Object caseCall(Call object) {
final Object result;
List<Expression> exprArgs = object.getArguments();
final int argc = exprArgs.size();
final Object[] args = new Object[argc];
int i = 0;
for (Expression arg : exprArgs) {
args[i++] = doSwitch(arg);
}
// call the service.
switch (object.getType()) {
case CALLSERVICE:
result = services.call(object.getServiceName(), args, diagnostic);
break;
case CALLORAPPLY:
result = services.callOrApply(object.getServiceName(), args, diagnostic);
break;
case COLLECTIONCALL:
result = services.collectionServiceCall(object.getServiceName(), args, diagnostic);
break;
default:
throw new UnsupportedOperationException("should never happen");
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseAnd(org.eclipse.acceleo.query.ast.And)
*/
@Override
public Object caseAnd(And object) {
final Object result;
final Object[] args = new Object[2];
args[0] = doSwitch(object.getArguments().get(0));
if (args[0] != null && args[0].getClass() == Boolean.class && Boolean.FALSE.equals(args[0])) {
result = Boolean.FALSE;
} else {
args[1] = doSwitch(object.getArguments().get(1));
result = services.call(object.getServiceName(), args, diagnostic);
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseOr(org.eclipse.acceleo.query.ast.Or)
*/
@Override
public Object caseOr(Or object) {
final Object result;
final Object[] args = new Object[2];
args[0] = doSwitch(object.getArguments().get(0));
if (args[0] != null && args[0].getClass() == Boolean.class && Boolean.TRUE.equals(args[0])) {
result = Boolean.TRUE;
} else {
args[1] = doSwitch(object.getArguments().get(1));
result = services.call(object.getServiceName(), args, diagnostic);
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseImplies(org.eclipse.acceleo.query.ast.Implies)
*/
@Override
public Object caseImplies(Implies object) {
final Object result;
final Object[] args = new Object[2];
args[0] = doSwitch(object.getArguments().get(0));
if (args[0] != null && args[0].getClass() == Boolean.class && Boolean.FALSE.equals(args[0])) {
result = Boolean.TRUE;
} else {
args[1] = doSwitch(object.getArguments().get(1));
result = services.call(object.getServiceName(), args, diagnostic);
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseVarRef(org.eclipse.acceleo.query.ast.VarRef)
*/
@Override
public Object caseVarRef(VarRef object) {
return services.getVariableValue(peekVariables(), object.getVariableName(), diagnostic);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseLambda(org.eclipse.acceleo.query.ast.Lambda)
*/
@Override
public Object caseLambda(Lambda object) {
return new LambdaValue(object, new HashMap<String, Object>(peekVariables()), this, diagnostic);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseNullLiteral(org.eclipse.acceleo.query.ast.NullLiteral)
*/
@Override
public Object caseNullLiteral(NullLiteral object) {
return null;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseEnumLiteral(org.eclipse.acceleo.query.ast.EnumLiteral)
*/
@Override
public Object caseEnumLiteral(EnumLiteral object) {
final Object result;
if (object.getLiteral() != null) {
result = object.getLiteral().getInstance();
} else {
final Nothing nothing = new Nothing("Invalid enum literal.");
Diagnostic diag = new BasicDiagnostic(Diagnostic.ERROR, AstBuilderListener.PLUGIN_ID, 0, nothing
.getMessage(), new Object[] {object });
((BasicDiagnostic)diagnostic).add(diag);
result = nothing;
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseSetInExtensionLiteral(org.eclipse.acceleo.query.ast.SetInExtensionLiteral)
*/
@Override
public Object caseSetInExtensionLiteral(SetInExtensionLiteral object) {
final Set<Object> result = new LinkedHashSet<Object>();
for (Expression expression : object.getValues()) {
result.add(doSwitch(expression));
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseSequenceInExtensionLiteral(org.eclipse.acceleo.query.ast.SequenceInExtensionLiteral)
*/
@Override
public Object caseSequenceInExtensionLiteral(SequenceInExtensionLiteral object) {
final List<Object> result = new ArrayList<Object>();
for (Expression expression : object.getValues()) {
result.add(doSwitch(expression));
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseConditional(org.eclipse.acceleo.query.ast.Conditional)
*/
@Override
public Object caseConditional(Conditional object) {
Object selector = this.doSwitch(object.getPredicate());
Object result;
if (selector instanceof Boolean) {
if ((Boolean)selector) {
result = this.doSwitch(object.getTrueBranch());
} else {
result = this.doSwitch(object.getFalseBranch());
}
} else {
Nothing nothing = new Nothing(String.format(BAD_PREDICATE_TYPE_MSG, selector));
Diagnostic diag = new BasicDiagnostic(Diagnostic.WARNING, AstBuilderListener.PLUGIN_ID, 0, nothing
.getMessage(), new Object[] {object.getPredicate() });
((BasicDiagnostic)diagnostic).add(diag);
result = nothing;
}
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseLet(org.eclipse.acceleo.query.ast.Let)
*/
@Override
public Object caseLet(Let object) {
Map<String, Object> letEnv = new HashMap<String, Object>(peekVariables());
for (Binding binding : object.getBindings()) {
letEnv.put(binding.getName(), doSwitch(binding.getValue()));
}
pushVariables(letEnv);
Object result = doSwitch(object.getBody());
popVariables();
return result;
}
/**
* {@inheritDoc}
*
* @see org.eclipse.acceleo.query.ast.util.AstSwitch#caseTypeSetLiteral(org.eclipse.acceleo.query.ast.TypeSetLiteral)
*/
@Override
public Object caseTypeSetLiteral(TypeSetLiteral object) {
final Set<Object> result = new LinkedHashSet<Object>(object.getTypes().size());
for (TypeLiteral type : object.getTypes()) {
result.add(doSwitch(type));
}
return result;
}
}