package nl.uva.sc.encoders.ql.validation; import static nl.uva.sc.encoders.ql.message.Messages.getString; import static nl.uva.sc.encoders.ql.validation.ValidationMessage.Type.ERROR; import static nl.uva.sc.encoders.ql.validation.ValidationMessage.Type.WARNING; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import nl.uva.sc.encoders.ql.ast.Questionnaire; import nl.uva.sc.encoders.ql.ast.TextLocation; import nl.uva.sc.encoders.ql.ast.expression.BinaryExpression; import nl.uva.sc.encoders.ql.ast.expression.BracedExpression; import nl.uva.sc.encoders.ql.ast.expression.Expression; import nl.uva.sc.encoders.ql.ast.expression.LiteralExpression; import nl.uva.sc.encoders.ql.ast.expression.NameExpression; import nl.uva.sc.encoders.ql.ast.expression.UnaryExpression; import nl.uva.sc.encoders.ql.ast.operator.BinaryOperator; import nl.uva.sc.encoders.ql.ast.operator.UnaryOperator; import nl.uva.sc.encoders.ql.ast.statement.ConditionalBlock; import nl.uva.sc.encoders.ql.ast.statement.Question; import nl.uva.sc.encoders.ql.ast.statement.Statement; import nl.uva.sc.encoders.ql.ast.type.BooleanType; import nl.uva.sc.encoders.ql.ast.type.DataType; import nl.uva.sc.encoders.ql.ast.type.TypeMap; import nl.uva.sc.encoders.ql.visitor.ExpressionVisitor; import nl.uva.sc.encoders.ql.visitor.StatementVisitor; public class TypeChecker implements ExpressionVisitor<List<TypeValidation>>, StatementVisitor<List<TypeValidation>> { private static final String BOOLEAN_CONDITION = "booleanCondition"; private static final String DUPLICATE_LABEL = "duplicateLabel"; private static final String REFERENCE_BEFORE_STATED = "referenceBeforeStated"; private static final String UNDEFINED_QUESTION = "undefinedQuestion"; private static final String UNSUPPORTED_TYPES_FOR_BINARY_OPERATOR = "unsupportedTypesForBinaryOperator"; private static final String UNSUPPORTED_TYPES_FOR_UNARY_OPERATOR = "unsupportedTypesForUnaryOperator"; private static final String COMPUTED_TYPE_DOES_NOT_MATCH_QUESTION_TYPE = "computedTypeDoesNotMatchQuestionType"; private final TypeMap typeMap = new TypeMap(); private final Set<String> questionLabels = new HashSet<>(); private final Questionnaire questionnaire; public TypeChecker(Questionnaire questionnaire) { this.questionnaire = questionnaire; } public List<TypeValidation> checkTypes() { List<TypeValidation> validations = new ArrayList<>(); List<Statement> statements = questionnaire.getStatements(); for (Statement statement : statements) { validations.addAll(statement.accept(this)); } return validations; } @Override public List<TypeValidation> visit(BracedExpression bracedExpression) { Expression innerExpression = bracedExpression.getExpression(); return innerExpression.accept(this); } @Override public List<TypeValidation> visit(NameExpression nameExpression) { List<TypeValidation> validations = new ArrayList<>(); String name = nameExpression.getName(); if (!questionnaire.containsQuestion(name)) { String validationMessage = getString(UNDEFINED_QUESTION, name); TextLocation textLocation = nameExpression.getTextLocation(); validations.add(new TypeValidation(validationMessage, textLocation, ERROR)); } else { if (!typeMap.containsKey(name)) { String validationMessage = getString(REFERENCE_BEFORE_STATED, name); TextLocation textLocation = nameExpression.getTextLocation(); validations.add(new TypeValidation(validationMessage, textLocation, ERROR)); } } return validations; } @Override public List<TypeValidation> visit(UnaryExpression unaryExpression) { List<TypeValidation> validations = new ArrayList<>(); Expression expression = unaryExpression.getExpression(); validations.addAll(expression.accept(this)); UnaryOperator operator = unaryExpression.getOperator(); DataType dataType = expression.getType(typeMap); if (!operator.supports(dataType)) { String validationMessage = getString(UNSUPPORTED_TYPES_FOR_UNARY_OPERATOR, operator.toString(), dataType); TextLocation textLocation = unaryExpression.getTextLocation(); validations.add(new TypeValidation(validationMessage, textLocation, ERROR)); } return validations; } @Override public List<TypeValidation> visit(BinaryExpression binaryExpression) { Expression leftHand = binaryExpression.getLeftHand(); Expression rightHand = binaryExpression.getRightHand(); DataType leftHandDataType = leftHand.getType(typeMap); DataType rightHandDataType = rightHand.getType(typeMap); List<TypeValidation> validations = new ArrayList<>(); List<TypeValidation> leftHandValidations = leftHand.accept(this); List<TypeValidation> rightHandValidations = rightHand.accept(this); validations.addAll(leftHandValidations); validations.addAll(rightHandValidations); if (leftHandValidations.isEmpty() && rightHandValidations.isEmpty()) { BinaryOperator operator = binaryExpression.getOperator(); TextLocation textLocation = binaryExpression.getTextLocation(); if (!operator.supports(leftHandDataType, rightHandDataType)) { String validationMessage = getString(UNSUPPORTED_TYPES_FOR_BINARY_OPERATOR, operator.toString(), leftHandDataType, rightHandDataType); validations.add(new TypeValidation(validationMessage, textLocation, ERROR)); } } return validations; } @Override public List<TypeValidation> visit(ConditionalBlock conditionalBlock) { List<TypeValidation> validations = new ArrayList<>(); Expression condition = conditionalBlock.getCondition(); List<TypeValidation> conditionValidations = condition.accept(this); validations.addAll(conditionValidations); if (conditionValidations.isEmpty()) { DataType dataType = condition.getType(typeMap); if (!(dataType instanceof BooleanType)) { TextLocation textLocation = condition.getTextLocation(); String validationMessage = getString(BOOLEAN_CONDITION, dataType); validations.add(new TypeValidation(validationMessage, textLocation, ERROR)); } } visitQuestions(conditionalBlock.getQuestions()); return validations; } private void visitQuestions(List<Question> questions) { for (Question question : questions) { visit(question); } } @Override public List<TypeValidation> visit(Question question) { typeMap.put(question.getName(), question.getDataType()); List<TypeValidation> validations = new ArrayList<>(); validations.addAll(checkForDuplicateLabel(question)); validations.addAll(checkDataTypes(question)); return validations; } private List<TypeValidation> checkDataTypes(Question question) { List<TypeValidation> validations = new ArrayList<>(); Expression computed = question.getComputed(); if (computed != null) { List<TypeValidation> computedValidations = computed.accept(this); validations.addAll(computedValidations); if (computedValidations.isEmpty()) { DataType computedType = computed.getType(typeMap); DataType questionType = question.getDataType(); if (!computedType.equals(questionType)) { TextLocation textLocation = computed.getTextLocation(); String validationMessage = getString(COMPUTED_TYPE_DOES_NOT_MATCH_QUESTION_TYPE, computedType, questionType); validations.add(new TypeValidation(validationMessage, textLocation, ERROR)); } } } return validations; } private List<TypeValidation> checkForDuplicateLabel(Question question) { List<TypeValidation> validations = new ArrayList<>(); String label = question.getQuestionLabel(); boolean added = questionLabels.add(label); if (!added) { String validationMessage = getString(DUPLICATE_LABEL, label); TextLocation textLocation = question.getTextLocation(); validations.add(new TypeValidation(validationMessage, textLocation, WARNING)); } return validations; } @Override public List<TypeValidation> visit(LiteralExpression literalExpression) { return Collections.emptyList(); } }