package nl.uva.softwcons.ql.validation.type; import static nl.uva.softwcons.ql.ast.type.BooleanType.BOOLEAN_TYPE; import static nl.uva.softwcons.ql.ast.type.NumberType.NUMBER_TYPE; import static nl.uva.softwcons.ql.ast.type.StringType.STRING_TYPE; import static nl.uva.softwcons.ql.ast.type.UndefinedType.UNDEFINED_TYPE; import java.util.Arrays; import java.util.List; import nl.uva.softwcons.ql.ast.expression.ExpressionVisitor; import nl.uva.softwcons.ql.ast.expression.binary.BinaryExpression; import nl.uva.softwcons.ql.ast.expression.binary.arithmetic.Addition; import nl.uva.softwcons.ql.ast.expression.binary.arithmetic.Division; import nl.uva.softwcons.ql.ast.expression.binary.arithmetic.Multiplication; import nl.uva.softwcons.ql.ast.expression.binary.arithmetic.Subtraction; import nl.uva.softwcons.ql.ast.expression.binary.comparison.Equal; import nl.uva.softwcons.ql.ast.expression.binary.comparison.GreaterOrEqual; import nl.uva.softwcons.ql.ast.expression.binary.comparison.GreaterThan; import nl.uva.softwcons.ql.ast.expression.binary.comparison.LowerOrEqual; import nl.uva.softwcons.ql.ast.expression.binary.comparison.LowerThan; import nl.uva.softwcons.ql.ast.expression.binary.comparison.NotEqual; import nl.uva.softwcons.ql.ast.expression.binary.logical.And; import nl.uva.softwcons.ql.ast.expression.binary.logical.Or; import nl.uva.softwcons.ql.ast.expression.identifier.Identifier; import nl.uva.softwcons.ql.ast.expression.literal.BooleanLiteral; import nl.uva.softwcons.ql.ast.expression.literal.NumberLiteral; import nl.uva.softwcons.ql.ast.expression.literal.StringLiteral; import nl.uva.softwcons.ql.ast.expression.unary.logical.Not; import nl.uva.softwcons.ql.ast.form.Form; import nl.uva.softwcons.ql.ast.form.FormVisitor; import nl.uva.softwcons.ql.ast.statement.ComputedQuestion; import nl.uva.softwcons.ql.ast.statement.Conditional; import nl.uva.softwcons.ql.ast.statement.Question; import nl.uva.softwcons.ql.ast.statement.StatementVisitor; import nl.uva.softwcons.ql.ast.type.Type; import nl.uva.softwcons.ql.validation.Checker; import nl.uva.softwcons.ql.validation.Error; import nl.uva.softwcons.ql.validation.type.error.InvalidConditionType; import nl.uva.softwcons.ql.validation.type.error.InvalidOperatorTypes; import nl.uva.softwcons.ql.validation.type.error.InvalidQuestionExpressionType; import nl.uva.softwcons.ql.validation.type.error.UndefinedReference; public final class TypeChecker extends Checker implements FormVisitor<List<Error>>, StatementVisitor<Void>, ExpressionVisitor<Type> { private final Environment env; public static List<Error> check(final Form form) { return form.accept(new TypeChecker()); } private TypeChecker() { this.env = new Environment(); } @Override public List<Error> visit(final Form form) { form.getStatements().forEach(st -> st.accept(this)); return this.getErrors(); } @Override public Void visit(final ComputedQuestion computedQuestion) { this.env.defineVariable(computedQuestion.getId(), computedQuestion.getType()); final Type questionExpressionType = computedQuestion.getExpression().accept(this); if (questionExpressionType != computedQuestion.getType()) { this.addError(new InvalidQuestionExpressionType(computedQuestion.getLineInfo())); } return null; } @Override public Void visit(final Question question) { this.env.defineVariable(question.getId(), question.getType()); return null; } @Override public Void visit(final Conditional conditional) { final Type conditionExprType = conditional.getExpression().accept(this); if (conditionExprType != BOOLEAN_TYPE) { this.addError(new InvalidConditionType(conditional.getLineInfo())); } conditional.getQuestions().forEach(q -> q.accept(this)); return null; } @Override public Type visit(final Addition expr) { resolveAndValidateBinaryExpressionType(expr, NUMBER_TYPE); return NUMBER_TYPE; } @Override public Type visit(final Division expr) { resolveAndValidateBinaryExpressionType(expr, NUMBER_TYPE); return NUMBER_TYPE; } @Override public Type visit(final Multiplication expr) { resolveAndValidateBinaryExpressionType(expr, NUMBER_TYPE); return NUMBER_TYPE; } @Override public Type visit(final Subtraction expr) { resolveAndValidateBinaryExpressionType(expr, NUMBER_TYPE); return NUMBER_TYPE; } @Override public Type visit(final Equal expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final NotEqual expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final GreaterOrEqual expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final GreaterThan expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final LowerOrEqual expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final LowerThan expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final And expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final Or expr) { resolveAndValidateBinaryExpressionType(expr, BOOLEAN_TYPE); return BOOLEAN_TYPE; } @Override public Type visit(final Not expr) { final Type expressionType = visitUnaryOperand(expr); if (expressionType != BOOLEAN_TYPE) { this.addError(new InvalidOperatorTypes(expr.getLineInfo())); } return BOOLEAN_TYPE; } @Override public Type visit(final Identifier questionId) { final Type variableType = this.env.resolveVariable(questionId); if (variableType == UNDEFINED_TYPE) { this.addError(new UndefinedReference(questionId.getLineInfo())); } return variableType; } @Override public Type visit(final BooleanLiteral expr) { return BOOLEAN_TYPE; } @Override public Type visit(final StringLiteral expr) { return STRING_TYPE; } @Override public Type visit(final NumberLiteral expr) { return NUMBER_TYPE; } /** * Resolves the type of the given expression recursively and returns it, * adding an error to the list of currently found errors in it is not in the * "allowedTypes" parameter. * * @param expr * A binary expression whose type is resolved and checked * @param allowedTypes * The list of allowed types for the given binary expression * @return The type of the given expression after it is resolved */ private Type resolveAndValidateBinaryExpressionType(final BinaryExpression expr, final Type... allowedTypes) { final Type nodeType = expr.resolveType(visitLeftOperand(expr), visitRightOperand(expr)); if (!Arrays.asList(allowedTypes).contains(nodeType)) { this.addError(new InvalidOperatorTypes(expr.getLineInfo())); } return nodeType; } }