package edu.parser.QL.typechecker; import edu.exceptions.TypeCheckException; import edu.parser.QL.nodes.expression.*; import edu.parser.QL.nodes.question.Question; import edu.parser.QL.nodes.type.Number; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * Created by Steven Kok on 12/03/2015. */ public class TypeCheckerExpressionValidator implements ExpressionVisitor<Expression> { private final Set<QLIdentifier> identifiers; private Map<QLIdentifier, Question> questions; public TypeCheckerExpressionValidator(Map<QLIdentifier, Question> questions) { this.questions = questions; this.identifiers = new HashSet<>(); } @Override public Expression visit(Addition addition) { return visitArithmeticExpression(addition); } @Override public Expression visit(And and) { return visitLogicalExpression(and); } @Override public Expression visit(Or or) { return visitLogicalExpression(or); } private Expression visitLogicalExpression(BinaryExpression expression) { if (expression.getLeft().hasBooleanOperands() && expression.getRight().hasBooleanOperands()) { visit(expression.getLeft()); visit(expression.getRight()); return expression; } else { throw new TypeCheckException(String.format(TypeChecker.EXPRESSION_EXPECTS_BOOLEAN, expression.getClass().getSimpleName(), expression.toString())); } } @Override public Expression visit(Equal equal) { visit(equal.getLeft()); visit(equal.getRight()); return equal; } @Override public Expression visit(GreaterOrEqual greaterOrEqual) { return visitArithmeticExpression(greaterOrEqual); } @Override public Expression visit(GreaterThan greaterThan) { return visitArithmeticExpression(greaterThan); } @Override public Expression visit(LessOrEqual lessOrEqual) { return visitArithmeticExpression(lessOrEqual); } @Override public Expression visit(LessThan lessThan) { return visitArithmeticExpression(lessThan); } @Override public Expression visit(Multiplication multiplication) { return visitArithmeticExpression(multiplication); } @Override public Expression visit(Division division) { return visitArithmeticExpression(division); } @Override public Expression visit(edu.parser.QL.nodes.type.Boolean aBoolean) { return aBoolean; } @Override public Expression visit(Number number) { return number; } @Override public Expression visit(Expression expression) { return (Expression) expression.accept(this); } @Override public Expression visit(Not not) { if (!not.getOperand().hasBooleanOperands()) { throw new TypeCheckException(String.format(TypeChecker.EXPRESSION_EXPECTS_BOOLEAN, not.getClass().getSimpleName(), not.toString())); } return visit(not.getOperand()); } @Override public Expression visit(NotEqual notEqual) { return notEqual; } private Expression visitArithmeticExpression(BinaryExpression expression) { if (expression.getLeft().hasBooleanOperands() || expression.getRight().hasBooleanOperands()) { throw new TypeCheckException(String.format(TypeChecker.EXPRESSION_EXPECTS_NON_BOOLEAN, expression.getClass().getSimpleName(), expression.toString())); } visit(expression.getLeft()); visit(expression.getRight()); return expression; } @Override public Expression visit(QLIdentifier qlIdentifier) { identifiers.add(qlIdentifier); // may overwrite existing items checkReferenceToUndefinedQuestions(); return qlIdentifier; } // implicitly checks for cyclic dependencies of questions private void checkReferenceToUndefinedQuestions() { String undefinedReferences = identifiers .stream() .filter(identifier -> !questions.containsKey(identifier)) .map(QLIdentifier::toString) .collect(Collectors.joining(", ")); if (!undefinedReferences.isEmpty()) { throw new TypeCheckException("Invalid reference to undefined question: " + undefinedReferences); } } }