/* * Copyright (c) 2010-2011, IETR/INSA of Rennes * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the IETR/INSA of Rennes nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ package net.sf.orcc.cal.validation; import static net.sf.orcc.cal.cal.CalPackage.eINSTANCE; import java.util.Iterator; import java.util.List; import net.sf.orcc.cal.CalDiagnostic; import net.sf.orcc.cal.cal.AstAction; import net.sf.orcc.cal.cal.AstExpression; import net.sf.orcc.cal.cal.AstPort; import net.sf.orcc.cal.cal.AstProcedure; import net.sf.orcc.cal.cal.AstTypeInt; import net.sf.orcc.cal.cal.AstTypeList; import net.sf.orcc.cal.cal.AstTypeUint; import net.sf.orcc.cal.cal.CalFactory; import net.sf.orcc.cal.cal.ExpressionBinary; import net.sf.orcc.cal.cal.ExpressionCall; import net.sf.orcc.cal.cal.ExpressionElsif; import net.sf.orcc.cal.cal.ExpressionIf; import net.sf.orcc.cal.cal.ExpressionIndex; import net.sf.orcc.cal.cal.ExpressionUnary; import net.sf.orcc.cal.cal.Function; import net.sf.orcc.cal.cal.Guard; import net.sf.orcc.cal.cal.OutputPattern; import net.sf.orcc.cal.cal.StatementAssign; import net.sf.orcc.cal.cal.StatementCall; import net.sf.orcc.cal.cal.StatementElsif; import net.sf.orcc.cal.cal.StatementIf; import net.sf.orcc.cal.cal.StatementWhile; import net.sf.orcc.cal.cal.Variable; import net.sf.orcc.cal.cal.VariableReference; import net.sf.orcc.cal.services.Evaluator; import net.sf.orcc.cal.services.Typer; import net.sf.orcc.cal.util.Util; import net.sf.orcc.ir.IrFactory; import net.sf.orcc.ir.OpBinary; import net.sf.orcc.ir.OpUnary; import net.sf.orcc.ir.Type; import net.sf.orcc.ir.TypeList; import net.sf.orcc.ir.util.TypePrinter; import net.sf.orcc.ir.util.TypeUtil; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.xtext.validation.Check; import org.eclipse.xtext.validation.CheckType; /** * This class describes a validator that performs type checking for an RVC-CAL * actor/unit. The checks tagged as "normal" are only performed when the file is * saved and before code generation. * * @author Matthieu Wipliez * */ public class TypeValidator extends AbstractCalValidator { private final TypePrinter printer; public TypeValidator() { printer = new TypePrinter(); } private void checkActionGuard(Guard guard) { if (guard == null) return; int index = 0; for (AstExpression guardExpression : guard.getExpressions()) { Type type = Typer.getType(guardExpression); if (!TypeUtil.isConvertibleTo(type, IrFactory.eINSTANCE.createTypeBool())) { error("Type mismatch: cannot convert from " + print(type) + " to bool", guard, eINSTANCE.getGuard_Expressions(), index, CalDiagnostic.ERROR_TYPE_CONVERSION); } index++; } } /** * Checks the token expressions are correctly typed. * * @param outputs * the output patterns of an action */ private void checkActionOutputs(List<OutputPattern> outputs) { for (OutputPattern pattern : outputs) { AstPort port = pattern.getPort(); Type portType = Typer.getType(port); AstExpression astRepeat = pattern.getRepeat(); if (astRepeat == null) { List<AstExpression> values = pattern.getValues(); int index = 0; for (AstExpression value : values) { Type type = Typer.getType(value); if (!TypeUtil.isConvertibleTo(type, portType)) { error("this expression must be of type " + print(portType), pattern, eINSTANCE.getOutputPattern_Values(), index); } index++; } } else { int repeat = Evaluator.getIntValue(astRepeat); if (repeat != 1) { // each value is supposed to be a list List<AstExpression> values = pattern.getValues(); int index = 0; for (AstExpression value : values) { Type type = Typer.getType(value); if (type.isList()) { TypeList typeList = (TypeList) type; Type lub = TypeUtil.getLub(portType, typeList.getType()); if (lub != null && typeList.getSize() >= repeat) { continue; } } error("Type mismatch: expected " + print(portType) + "[" + repeat + "]", pattern, eINSTANCE.getOutputPattern_Values(), index); index++; } } } } } @Check(CheckType.NORMAL) public void checkAstAction(AstAction action) { checkActionGuard(action.getGuard()); checkActionOutputs(action.getOutputs()); } @Check(CheckType.NORMAL) public void checkExpressionBinary(ExpressionBinary expression) { OpBinary op = OpBinary.getOperator(expression.getOperator()); checkTypeBinary(op, Typer.getType(expression.getLeft()), Typer.getType(expression.getRight()), expression, eINSTANCE.getExpressionBinary_Operator()); } @Check(CheckType.NORMAL) public void checkExpressionCall(ExpressionCall call) { Function function = call.getFunction(); String name = function.getName(); List<AstExpression> parameters = call.getParameters(); if (function.getParameters().size() != parameters.size()) { error("function " + name + " takes " + function.getParameters().size() + " arguments.", call, eINSTANCE.getExpressionCall_Function()); return; } Iterator<Variable> itFormal = function.getParameters().iterator(); Iterator<AstExpression> itActual = parameters.iterator(); int index = 0; while (itFormal.hasNext() && itActual.hasNext()) { Type formalType = Typer.getType(itFormal.next()); AstExpression expression = itActual.next(); Type actualType = Typer.getType(expression); // check types if (!TypeUtil.isConvertibleTo(actualType, formalType)) { error("Type mismatch: cannot convert from " + print(actualType) + " to " + print(formalType), call, eINSTANCE.getExpressionCall_Parameters(), index, CalDiagnostic.ERROR_TYPE_CONVERSION); } index++; } } @Check(CheckType.NORMAL) public void checkExpressionElsif(ExpressionElsif expression) { Type type = Typer.getType(expression.getCondition()); if (type == null || !type.isBool()) { error("Cannot convert " + type + " to bool", expression, eINSTANCE.getExpressionElsif_Condition()); } } @Check(CheckType.NORMAL) public void checkExpressionIf(ExpressionIf expression) { Type type = Typer.getType(expression.getCondition()); if (type == null || !type.isBool()) { error("Cannot convert " + print(type) + " to bool", expression, eINSTANCE.getExpressionIf_Condition()); } Type typeThen = Typer.getType(expression.getThen()); type = typeThen; int index = 0; for (ExpressionElsif elsif : expression.getElsifs()) { Type typeElsif = Typer.getType(elsif); type = TypeUtil.getLub(type, typeElsif); if (type == null) { error("Type mismatch: cannot convert " + print(typeElsif) + " to " + print(typeThen), expression, eINSTANCE.getExpressionIf_Elsifs(), index, CalDiagnostic.ERROR_TYPE_CONVERSION); } index++; } Type typeElse = Typer.getType(expression.getElse()); type = TypeUtil.getLub(type, typeElse); if (type == null) { error("Type mismatch: cannot convert " + print(typeElse) + " to " + print(type), expression, eINSTANCE.getExpressionIf_Else(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } @Check(CheckType.NORMAL) public void checkExpressionIndex(ExpressionIndex expression) { Variable variable = expression.getSource().getVariable(); Type type = Typer.getType(variable); List<AstExpression> indexes = expression.getIndexes(); int errorIdx = 0; for (AstExpression index : indexes) { Type subType = Typer.getType(index); if (type != null && type.isList()) { if (subType.isInt() || subType.isUint()) { type = ((TypeList) type).getType(); } else { error("index must be an integer", expression, eINSTANCE.getExpressionIndex_Indexes(), errorIdx); } } else { error("Cannot convert " + print(type) + " to List", expression, eINSTANCE.getExpressionIndex_Source()); } errorIdx++; } } @Check(CheckType.NORMAL) public void checkExpressionUnary(ExpressionUnary expression) { OpUnary op = OpUnary.getOperator(expression.getUnaryOperator()); Type type = Typer.getType(expression.getExpression()); if (type == null) { return; } switch (op) { case BITNOT: if (!(type.isInt() || type.isUint())) { error("Cannot convert " + print(type) + " to int/uint", expression, eINSTANCE.getExpressionUnary_Expression(), -1); } break; case LOGIC_NOT: if (!type.isBool()) { error("Cannot convert " + print(type) + " to boolean", expression, eINSTANCE.getExpressionUnary_Expression(), -1); } break; case MINUS: if (!type.isUint() && !type.isInt() && !type.isFloat()) { error("Cannot convert " + print(type) + " to int or float", expression, eINSTANCE.getExpressionUnary_Expression(), -1); } break; case NUM_ELTS: if (!type.isList()) { error("Cannot convert " + print(type) + " to List", expression, eINSTANCE.getExpressionUnary_Expression()); } break; default: error("Unknown unary operator", expression, eINSTANCE.getExpressionUnary_Expression()); } } @Check(CheckType.NORMAL) public void checkFunction(final Function function) { if (!Util.hasAnnotation("native", function.getAnnotations())) { checkReturnType(function); } } private void checkReturnType(Function function) { Type returnType = Typer.getType(function); Type expressionType = Typer.getType(function.getExpression()); if (!TypeUtil.isConvertibleTo(expressionType, returnType) && expressionType != null) { error("Type mismatch: cannot convert from " + print(expressionType) + " to " + print(returnType), function, eINSTANCE.getFunction_Expression(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } @Check(CheckType.NORMAL) public void checkStatementAssign(StatementAssign assign) { Variable variable = assign.getTarget().getVariable(); if (!Util.isAssignable(variable)) { error("The variable " + variable.getName() + " is not assignable", eINSTANCE.getStatementAssign_Target(), CalDiagnostic.ERROR_CONSTANT_ASSIGNATION); } // create expression ExpressionIndex expression = CalFactory.eINSTANCE .createExpressionIndex(); // set reference VariableReference reference = CalFactory.eINSTANCE .createVariableReference(); reference.setVariable(variable); expression.setSource(reference); // copy indexes expression.getIndexes().addAll(EcoreUtil.copyAll(assign.getIndexes())); // check types Type targetType = Typer.getType(expression); AstExpression value = assign.getValue(); if (value != null) { Type type = Typer.getType(value); if (!TypeUtil.isConvertibleTo(type, targetType) && type != null) { error("Type mismatch: cannot convert from " + print(type) + " to " + print(targetType), assign, eINSTANCE.getStatementAssign_Value(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } } @Check(CheckType.NORMAL) public void checkStatementCall(StatementCall call) { AstProcedure procedure = call.getProcedure(); String name = procedure.getName(); List<AstExpression> arguments = call.getArguments(); if (procedure.eContainer() == null) { if ("print".equals(name) || "println".equals(name)) { if (arguments.size() > 1) { error("built-in procedure " + name + " takes at most one expression", call, eINSTANCE.getStatementCall_Procedure()); } } return; } if (procedure.getParameters().size() != arguments.size()) { error("procedure " + name + " takes " + procedure.getParameters().size() + " arguments.", call, eINSTANCE.getStatementCall_Procedure()); return; } Iterator<Variable> itFormal = procedure.getParameters().iterator(); Iterator<AstExpression> itActual = arguments.iterator(); int index = 0; while (itFormal.hasNext() && itActual.hasNext()) { Type formalType = Typer.getType(itFormal.next()); AstExpression expression = itActual.next(); Type actualType = Typer.getType(expression); // check types if (!TypeUtil.isConvertibleTo(actualType, formalType) && actualType != null) { error("Type mismatch: cannot convert from " + print(actualType) + " to " + print(formalType), call, eINSTANCE.getStatementCall_Arguments(), index, CalDiagnostic.ERROR_TYPE_CONVERSION); } index++; } } @Check(CheckType.NORMAL) public void checkStatementElsif(StatementElsif elsIf) { Type type = Typer.getType(elsIf.getCondition()); if (!TypeUtil.isConvertibleTo(type, IrFactory.eINSTANCE.createTypeBool()) && type != null) { error("Type mismatch: cannot convert from " + print(type) + " to bool", elsIf, eINSTANCE.getStatementElsif_Condition(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } @Check(CheckType.NORMAL) public void checkStatementIf(StatementIf stmtIf) { Type type = Typer.getType(stmtIf.getCondition()); if (!TypeUtil.isConvertibleTo(type, IrFactory.eINSTANCE.createTypeBool()) && type != null) { error("Type mismatch: cannot convert from " + print(type) + " to bool", stmtIf, eINSTANCE.getStatementIf_Condition(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } @Check(CheckType.NORMAL) public void checkStatementWhile(StatementWhile stmtWhile) { Type type = Typer.getType(stmtWhile.getCondition()); if (!TypeUtil.isConvertibleTo(type, IrFactory.eINSTANCE.createTypeBool()) && type != null) { error("Type mismatch: cannot convert from " + print(type) + " to bool", stmtWhile, eINSTANCE.getStatementWhile_Condition(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } /** * Check that the type of a binary expression whose left operand has type t1 * and right operand has type t2, and whose operator is given, is correct. * * @param op * operator * @param t1 * type of the first operand * @param t2 * type of the second operand * @param source * source object * @param feature * feature */ private void checkTypeBinary(OpBinary op, Type t1, Type t2, EObject source, EStructuralFeature feature) { if (t1 == null || t2 == null) { return; } if (t1.isList() || t2.isList()) { error("Binary operations on Lists are not supported", source, feature); } switch (op) { case BITAND: if (!t1.isInt() && !t1.isUint()) { error("Cannot convert " + print(t1) + " to int/uint", source, feature); } if (!t2.isInt() && !t2.isUint()) { error("Cannot convert " + print(t2) + " to int/uint", source, feature); } break; case BITOR: case BITXOR: if (!t1.isInt() && !t1.isUint()) { error("Cannot convert " + print(t1) + " to int/uint", source, feature); } if (!t2.isInt() && !t2.isUint()) { error("Cannot convert " + print(t2) + " to int/uint", source, feature); } break; case TIMES: if (!t1.isInt() && !t1.isUint() && !t1.isFloat()) { error("Cannot convert " + print(t1) + " to int/uint/float", source, feature); } if (!t2.isInt() && !t2.isUint() && !t2.isFloat()) { error("Cannot convert " + print(t2) + " to int/uint/float", source, feature); } break; case MINUS: if (!t1.isInt() && !t1.isUint() && !t1.isFloat()) { error("Cannot convert " + print(t1) + " to int/uint/float", source, feature); } if (!t2.isInt() && !t2.isUint() && !t2.isFloat()) { error("Cannot convert " + print(t2) + " to int/uint/float", source, feature); } break; case PLUS: if (t1.isBool() && !t2.isString() || !t1.isString() && t2.isBool()) { error("Addition is not defined for booleans", source, feature); } break; case DIV: case DIV_INT: case SHIFT_RIGHT: if (!t1.isInt() && !t1.isUint() && !t1.isFloat()) { error("Cannot convert " + print(t1) + " to int/uint/float", source, feature); } if (!t2.isInt() && !t2.isUint() && !t2.isFloat()) { error("Cannot convert " + print(t2) + " to int/uint/float", source, feature); } break; case MOD: if (!t1.isInt() && !t1.isUint()) { error("Cannot convert " + print(t1) + " to int/uint", source, feature); } if (!t2.isInt() && !t2.isUint()) { error("Cannot convert " + print(t2) + " to int/uint", source, feature); } break; case SHIFT_LEFT: if (!t1.isInt() && !t1.isUint()) { error("Cannot convert " + print(t1) + " to int/uint", source, feature); } if (!t2.isInt() && !t2.isUint()) { error("Cannot convert " + print(t2) + " to int/uint", source, feature); } break; case EQ: case GE: case GT: case LE: case LT: case NE: Type type = TypeUtil.getLub(t1, t2); if (type == null) { error("Incompatible operand types " + print(t1) + " and " + print(t2), source, feature); } break; case EXP: error("Operator ** not implemented", source, feature); break; case LOGIC_AND: case LOGIC_OR: if (!t1.isBool()) { error("Cannot convert " + print(t1) + " to bool", source, feature); } if (!t2.isBool()) { error("Cannot convert " + print(t2) + " to bool", source, feature); } break; default: break; } } @Check(CheckType.NORMAL) public void checkTypeInt(AstTypeInt type) { AstExpression size = type.getSize(); if (size != null) { int value = Evaluator.getIntValue(size); if (value <= 0) { error("This size must evaluate to a compile-time " + "constant greater than zero", type, eINSTANCE.getAstTypeInt_Size(), CalDiagnostic.ERROR_TYPE_SYNTAX); } } } @Check(CheckType.NORMAL) public void checkTypeList(AstTypeList type) { AstExpression size = type.getSize(); if (size != null) { int value = Evaluator.getIntValue(size); if (value <= 0) { error("This size must evaluate to a compile-time " + "constant greater than zero", type, eINSTANCE.getAstTypeList_Size()); } } } @Check(CheckType.NORMAL) public void checkTypeUint(AstTypeUint type) { AstExpression size = type.getSize(); if (size != null) { int value = Evaluator.getIntValue(size); if (value <= 0) { error("This size must evaluate to a compile-time " + "constant greater than zero", type, eINSTANCE.getAstTypeUint_Size()); } } } @Check(CheckType.NORMAL) public void checkVariable(Variable variable) { AstExpression value = variable.getValue(); if (value != null) { // check types Type targetType = Typer.getType(variable); Type type = Typer.getType(value); if (!TypeUtil.isConvertibleTo(type, targetType)) { error("Type mismatch: cannot convert from " + print(type) + " to " + print(targetType), variable, eINSTANCE.getVariable_Value(), CalDiagnostic.ERROR_TYPE_CONVERSION); } } } private String print(Type type) { if (type == null) { return "null"; } return printer.doSwitch(type); } }