com.synflow.cx.internal.validation.ExpressionValidator.java Source code

Java tutorial

Introduction

Here is the source code for com.synflow.cx.internal.validation.ExpressionValidator.java

Source

/*******************************************************************************
 * Copyright (c) 2014 Synflow SAS.
 * 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:
 *    Matthieu Wipliez - initial API and implementation and/or initial documentation
 *******************************************************************************/
package com.synflow.cx.internal.validation;

import static com.synflow.cx.CxConstants.PROP_AVAILABLE;
import static com.synflow.cx.CxConstants.PROP_READ;
import static com.synflow.cx.validation.IssueCodes.ERR_AVAILABLE;
import static com.synflow.cx.validation.IssueCodes.ERR_LOCAL_NOT_INITIALIZED;
import static com.synflow.cx.validation.IssueCodes.ERR_MULTIPLE_READS;
import static com.synflow.cx.validation.IssueCodes.ERR_NO_SIDE_EFFECTS;
import static com.synflow.cx.validation.IssueCodes.ERR_TYPE_MISMATCH;
import static com.synflow.models.util.SwitchUtil.check;
import static org.eclipse.xtext.validation.CheckType.NORMAL;

import java.util.List;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.validation.AbstractDeclarativeValidator;
import org.eclipse.xtext.validation.Check;
import org.eclipse.xtext.validation.EValidatorRegistrar;

import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Multiset;
import com.google.common.collect.Multiset.Entry;
import com.google.inject.Inject;
import com.synflow.cx.CxUtil;
import com.synflow.cx.cx.Branch;
import com.synflow.cx.cx.CxExpression;
import com.synflow.cx.cx.CxPackage.Literals;
import com.synflow.cx.cx.ExpressionVariable;
import com.synflow.cx.cx.StatementAssign;
import com.synflow.cx.cx.StatementLoop;
import com.synflow.cx.cx.StatementVariable;
import com.synflow.cx.cx.Task;
import com.synflow.cx.cx.VarRef;
import com.synflow.cx.cx.Variable;
import com.synflow.cx.instantiation.IInstantiator;
import com.synflow.cx.internal.services.BoolCxSwitch;
import com.synflow.models.dpn.Entity;
import com.synflow.models.dpn.InterfaceType;
import com.synflow.models.dpn.Port;
import com.synflow.models.util.Executable;

/**
 * This class defines a validator for Cx expressions.
 * 
 * @author Matthieu Wipliez
 * 
 */
public class ExpressionValidator extends AbstractDeclarativeValidator {

    /**
     * This class defines a visitor that returns true when a function called is not constant.
     * 
     * @author Matthieu Wipliez
     * 
     */
    private static class ConstantCallSwitch extends BoolCxSwitch {

        @Override
        public Boolean caseExpressionVariable(ExpressionVariable expr) {
            Variable variable = expr.getSource().getVariable();
            if (CxUtil.isFunctionNotConstant(variable)) {
                return true;
            }

            return super.caseExpressionVariable(expr);
        }

    }

    @Inject
    private IInstantiator instantiator;

    @Check
    public void checkCondition(Branch stmt) {
        checkFunctionCalls(stmt.getCondition());
    }

    @Check
    public void checkCondition(StatementLoop stmt) {
        checkFunctionCalls(stmt.getCondition());
    }

    private void checkFunctionCalls(CxExpression condition) {
        if (check(new ConstantCallSwitch(), condition)) {
            error("Scheduling: this expression cannot call functions with side effects", condition, null,
                    ERR_NO_SIDE_EFFECTS);
        }
    }

    @Check
    public void checkLocalVariableUse(ExpressionVariable expr) {
        Variable variable = expr.getSource().getVariable();
        if (!CxUtil.isLocal(variable)) {
            return;
        }

        boolean isArray = !variable.getDimensions().isEmpty();
        if (!isArray && !variable.isInitialized()) {
            error("The local variable '" + variable.getName() + "' may not have been initialized", expr,
                    Literals.EXPRESSION_VARIABLE__SOURCE, ERR_LOCAL_NOT_INITIALIZED);
        }
    }

    @Check(NORMAL)
    public void checkMultipleReads(final CxExpression expr) {
        // Checks that there are at most one read per port in the expression. Otherwise indicate an
        // error.
        Task task = EcoreUtil2.getContainerOfType(expr, Task.class);
        if (task == null) {
            return;
        }

        instantiator.forEachMapping(task, new Executable<Entity>() {
            @Override
            public void exec(Entity entity) {
                Multiset<Port> portsRead = LinkedHashMultiset.create();
                Multiset<Port> portsAvailable = LinkedHashMultiset.create();
                computePortSets(entity, portsAvailable, portsRead, expr);

                boolean hasMultipleReads = false;
                for (Entry<Port> entry : portsRead.entrySet()) {
                    hasMultipleReads |= entry.getCount() > 1;
                }

                if (hasMultipleReads) {
                    error("Port error: cannot have more than one read per port in expression", expr, null,
                            ERR_MULTIPLE_READS);
                }
            }
        });
    }

    @Check
    public void checkPort(ExpressionVariable expr) {
        Variable variable = expr.getSource().getVariable();
        if (CxUtil.isPort(variable) && CxUtil.isInput(variable)) {
            // checks that the given reference to a port variable has the proper semantics.
            checkPortExpression(expr);
        }
    }

    /**
     * Checks the given expression that refers to an input port.
     * 
     * @param expr
     *            an expression variable
     */
    private void checkPortExpression(ExpressionVariable expr) {
        String prop = expr.getProperty();
        if (PROP_AVAILABLE.equals(prop)) {
            Variable variable = expr.getSource().getVariable();
            if (CxUtil.isPort(variable)) {
                InterfaceType iface = CxUtil.getInterface(variable);
                if (!iface.isSync()) {
                    error("Port error: '" + PROP_AVAILABLE + "' can only be used on 'sync' ports", expr, null,
                            ERR_AVAILABLE);
                }
            }

            EObject cter = CxUtil.getTarget(expr);
            if (!(cter instanceof Branch || cter instanceof StatementLoop)) {
                error("Port error: '" + PROP_AVAILABLE
                        + "' can only be used in the condition of if/for/while statements", expr, null,
                        ERR_AVAILABLE);
            }
        } else if (!PROP_READ.equals(prop)) {
            error("Port error: an input port can only be used with the '" + PROP_AVAILABLE + "' or '" + PROP_READ
                    + "' function", expr, null, ERR_TYPE_MISMATCH);
            return;
        }

        if (!expr.getIndexes().isEmpty()) {
            error("Port error: an input port cannot be used with indexes", expr, null, ERR_TYPE_MISMATCH);
        }

        if (!expr.getParameters().isEmpty()) {
            error("Port error: the '" + prop + "' function does not accept arguments", expr, null,
                    ERR_TYPE_MISMATCH);
        }
    }

    /**
     * Computes the two port sets: one containing ports that are available, the other one containing
     * ports that are read.
     * 
     * @param available
     *            a set in which ports available are put
     * @param read
     *            a set in which ports read are put
     * @param condition
     *            the condition to visit
     */
    public void computePortSets(Entity entity, Multiset<Port> available, Multiset<Port> read,
            CxExpression condition) {
        List<ExpressionVariable> exprs;
        if (condition == null) {
            return;
        }

        exprs = EcoreUtil2.eAllOfType(condition, ExpressionVariable.class);
        for (ExpressionVariable expr : exprs) {
            VarRef ref = expr.getSource();
            Variable variable = ref.getVariable();
            if (CxUtil.isPort(variable)) {
                Port port = instantiator.getPort(entity, ref);
                String prop = expr.getProperty();
                if (PROP_AVAILABLE.equals(prop)) {
                    available.add(port);
                } else if (PROP_READ.equals(prop)) {
                    read.add(port);
                }
            }
        }
    }

    @Override
    public void register(EValidatorRegistrar registrar) {
        // do nothing: packages are already registered by CxJavaValidator
    }

    @Check
    public void setInitialized(StatementAssign stmt) {
        Variable variable = stmt.getTarget().getSource().getVariable();

        // set variable as defined if the assignment has a value
        // MUST NOT USE variable.setDefined EVER BECAUSE IT WILL CLEAR THE INSTANTIATOR'S CACHE
        // don't use setInitialized(value != null)
        // because once a value has been defined, it must not be un-defined
        if (CxUtil.isLocal(variable) && stmt.getValue() != null) {
            setInitialized(variable);
        }
    }

    @Check
    public void setInitialized(StatementVariable stmt) {
        // set the 'defined' flag for each variable that has a value
        for (Variable variable : stmt.getVariables()) {
            if (variable.getValue() != null) {
                // MUST NOT USE variable.setDefined EVER
                setInitialized(variable);
            }
        }
    }

    /**
     * Sets the 'initialized' field of variable to <code>true</code> without notifying adapters.
     * Necessary so that resources stay cached.
     * 
     * @param variable
     *            a variable
     */
    private void setInitialized(Variable variable) {
        boolean deliver = variable.eDeliver();
        variable.eSetDeliver(false);
        variable.setInitialized(true);
        variable.eSetDeliver(deliver);
    }

}