blob: 783f090de2aba126258ce4c3f2bd69b677ac2bc5 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2008-2018 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/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.eol.dom;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.epsilon.common.module.IModule;
import org.eclipse.epsilon.common.module.ModuleElement;
import org.eclipse.epsilon.common.parse.AST;
import org.eclipse.epsilon.common.util.StringUtil;
import org.eclipse.epsilon.eol.compile.context.IEolCompilationContext;
import org.eclipse.epsilon.eol.exceptions.EolIllegalOperationException;
import org.eclipse.epsilon.eol.exceptions.EolNullPointerException;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.epsilon.eol.execute.context.FrameType;
import org.eclipse.epsilon.eol.execute.context.IEolContext;
import org.eclipse.epsilon.eol.execute.operations.AbstractOperation;
import org.eclipse.epsilon.eol.execute.operations.declarative.*;
import org.eclipse.epsilon.eol.models.IModel;
import org.eclipse.epsilon.eol.parse.EolParser;
import org.eclipse.epsilon.eol.types.*;
public class FirstOrderOperationCallExpression extends FeatureCallExpression {
protected List<Parameter> parameters = new ArrayList<>(2);
protected List<Expression> expressions = new ArrayList<>(3);
public FirstOrderOperationCallExpression() {}
public FirstOrderOperationCallExpression(Expression targetExpression, NameExpression nameExpression, Parameter parameter, Expression lambdaExpression) {
this.targetExpression = targetExpression;
this.nameExpression = nameExpression;
this.parameters.add(parameter);
this.expressions.add(lambdaExpression);
}
@Override
public void build(AST cst, IModule module) {
super.build(cst, module);
AST lambdaParamsContainer, exprAst;
if (cst.getType() != EolParser.NAME && cst.getFirstChild().getType() != EolParser.PARAMLIST) {
targetExpression = (Expression) module.createAst(cst.getFirstChild(), this);
exprAst = cst.getSecondChild();
nameExpression = (NameExpression) module.createAst(exprAst, this);
}
else {
nameExpression = new NameExpression(cst.getText());
nameExpression.setRegion(cst.getRegion());
nameExpression.setUri(cst.getUri());
nameExpression.setModule(cst.getModule());
exprAst = cst;
}
lambdaParamsContainer = exprAst != null ? exprAst.getFirstChild() : null;
if (lambdaParamsContainer != null && lambdaParamsContainer.getType() == EolParser.PARAMLIST) {
for (AST ast : lambdaParamsContainer.getChildren()) {
parameters.add((Parameter) module.createAst(ast, this));
}
}
if (exprAst != null) for (AST ast : exprAst.getChildren()) {
if (parameters.isEmpty() || ast != lambdaParamsContainer) {
ModuleElement resolved = module.createAst(ast, this);
if (resolved instanceof Expression) {
expressions.add((Expression) resolved);
}
}
}
}
@Override
public Object execute(IEolContext context) throws EolRuntimeException {
Object target = EolNoType.Instance;
if (targetExpression != null) {
target = context.getExecutorFactory().execute(targetExpression, context);
}
else if (!parameters.isEmpty()) {
EolType iterator = parameters.get(0).getType(context);
if (iterator instanceof EolModelElementType) {
target = ((EolModelElementType) iterator).getAllOfKind();
}
}
String operationName = nameExpression.getName();
if (target == null) {
if (isNullSafe()) {
return null;
}
else {
throw new EolNullPointerException(operationName, targetExpression);
}
}
IModel owningModel = context.getModelRepository().getOwningModel(target);
AbstractOperation operation = getAbstractOperation(target, operationName, owningModel, context);
replaceWithDelegateOperation(SelectBasedOperation.class, SelectOperation.class, operation, target, operationName, owningModel, context);
replaceWithDelegateOperation(CollectBasedOperation.class, CollectOperation.class, operation, target, operationName, owningModel, context);
return operation.execute(target, nameExpression, parameters, expressions, context);
}
/**
* @since 1.6
*/
@Override
protected AbstractOperation getOperationFromContext(Object target, String name, IModel owningModel, IEolContext context) throws EolIllegalOperationException {
return context.getOperationFactory().getOptimisedOperation(name, target, owningModel, context);
}
/**
*
* @param <O>
* @param <D>
* @param delegateClass
* @param originalClass
* @param operation
* @param target
* @param originalOpName
* @param owningModel
* @param context
* @throws EolIllegalOperationException
* @since 1.6
*/
@SuppressWarnings("unchecked")
private <O extends FirstOrderOperation, D extends DelegateBasedOperation<O>>
void replaceWithDelegateOperation(Class<D> delegateClass, Class<O> originalClass,
AbstractOperation operation, Object target, String originalOpName, IModel owningModel, IEolContext context) throws EolIllegalOperationException {
if (delegateClass.isInstance(operation)) {
D dbo = (D) operation;
if (dbo.getDelegateOperation().getClass().equals(originalClass)) {
String delegateOpName = originalClass.getSimpleName();
delegateOpName = delegateOpName.substring(0, delegateOpName.indexOf("Operation"));
if (originalOpName.startsWith("parallel") && !StringUtil.firstToLower(delegateOpName).startsWith("parallel")) {
delegateOpName = "parallel" + delegateOpName;
}
else if (originalOpName.startsWith("sequential") && !StringUtil.firstToLower(delegateOpName).startsWith("sequential")) {
delegateOpName = "sequential" + delegateOpName;
}
else {
delegateOpName = StringUtil.firstToLower(delegateOpName);
}
O delegateOp = (O) getAbstractOperation(target, delegateOpName, owningModel, context);
if (!delegateOp.getClass().equals(originalClass)) {
dbo.setDelegateOperation(delegateOp);
}
}
}
}
@Override
public void compile(IEolCompilationContext context) {
targetExpression.compile(context);
EolType contextType = null;
if (targetExpression.getResolvedType() instanceof EolCollectionType) {
contextType = ((EolCollectionType) targetExpression.getResolvedType()).getContentType();
}
else if (targetExpression.getResolvedType() == EolAnyType.Instance) {
contextType = targetExpression.getResolvedType();
}
String name = nameExpression.getName();
if (name.startsWith("sequential")) name = name.substring(10);
else if (name.startsWith("parallel")) name = name.substring(8);
if (contextType != null) {
context.getFrameStack().enterLocal(FrameType.UNPROTECTED, this);
Parameter parameter = parameters.get(0);
parameter.compile(context, false);
if (parameter.isExplicitlyTyped()) {
//TODO: Check that the type of the parameter is a subtype of the type of the collection
contextType = parameter.getCompilationType();
}
context.getFrameStack().put(parameter.getName(), contextType);
Expression expression = expressions.get(0);
expression.compile(context);
context.getFrameStack().leaveLocal(this);
if (StringUtil.isOneOf(name, "select", "reject", "rejectOne", "closure", "sortBy")) {
resolvedType = new EolCollectionType("Sequence", contextType);
}
else if (name.equals("selectOne")) {
resolvedType = contextType;
}
else if (name.equals("collect")) {
resolvedType = new EolCollectionType("Sequence", expression.getResolvedType());
}
else if (StringUtil.isOneOf(name, "exists", "forAll", "one", "none", "nMatch, atLeastNMatch, atMostNMatch")) {
resolvedType = EolPrimitiveType.Boolean;
}
else if (name.equals("aggregate")) {
if (expressions.size() == 2) {
Expression valueExpression = expressions.get(1);
valueExpression.compile(context);
resolvedType = new EolMapType(expression.getResolvedType(), valueExpression.getResolvedType());
}
else {
context.addErrorMarker(nameExpression, "Aggregate requires a key and a value expression");
}
}
else if (name.equals("mapBy")) {
resolvedType = new EolMapType(expression.getResolvedType(), new EolCollectionType("Sequence", contextType));
}
else if (name.equals("sortBy")) {
resolvedType = new EolCollectionType("Sequence", contextType);
}
if (StringUtil.isOneOf(name,
"select", "selectOne", "reject", "rejectOne", "exists", "one", "none", "forAll", "closure") &&
expression.getResolvedType().isNot(EolPrimitiveType.Boolean)) {
context.addErrorMarker(expression, "Expression should return a Boolean but returns a " + expression.getResolvedType().getName() + " instead");
}
}
else {
context.addErrorMarker(nameExpression, "Operation " + name + " only applies to collections");
}
}
public List<Parameter> getParameters() {
return parameters;
}
public List<Expression> getExpressions() {
return expressions;
}
public void accept(IEolVisitor visitor) {
visitor.visit(this);
}
}