package qls.ast.visitor.typechecker; import java.util.ArrayList; import java.util.List; import ql.ast.Expression; import ql.ast.QLType; import ql.ast.expression.Identifier; import ql.ast.type.QLForm; import ql.ast.visitor.TypeVisitor; import ql.ast.visitor.typechecker.TypeEnvironment; import ql.errorhandling.ErrorEnvironment; import qls.ast.Statement; import qls.ast.expression.literal.BooleanLiteral; import qls.ast.expression.literal.FloatLiteral; import qls.ast.expression.literal.IntegerLiteral; import qls.ast.expression.literal.StringLiteral; import qls.ast.statement.DefaultWidget; import qls.ast.statement.Page; import qls.ast.statement.Question; import qls.ast.statement.Stylesheet; import qls.ast.statement.widget.styling.property.Color; import qls.ast.statement.widget.styling.property.Font; import qls.ast.statement.widget.styling.property.FontSize; import qls.ast.statement.widget.styling.property.Height; import qls.ast.statement.widget.styling.property.Width; import qls.ast.statement.widget.type.parameterised.Dropdown; import qls.ast.statement.widget.type.parameterised.RadioButton; import qls.ast.statement.widget.type.parameterised.Slider; import qls.ast.visitor.ExpressionVisitor; import qls.ast.visitor.StatementVisitor; import qls.errorhandling.error.DuplicateIdentifierError; import qls.errorhandling.error.IllegalPropertyValueError; import qls.errorhandling.error.IllegalWidgetValueError; import qls.errorhandling.error.IncompatibleWidgetError; import qls.errorhandling.error.MissingIdentifiersError; import qls.errorhandling.error.NullQuestionError; import qls.errorhandling.error.PageIdentifierError; import qls.errorhandling.error.StylesheetIdentifierError; public class TypeChecker extends StatementVisitor<Void> implements ExpressionVisitor<QLType>, TypeVisitor<Void> { private ErrorEnvironment errorEnvironment; private TypeEnvironment typeEnvironment; private List<Identifier> processedIdentifiers; private TypeChecker(TypeEnvironment typeEnvironment) { super.setExpressionVisitor(this); super.setTypeVisitor(this); this.typeEnvironment = typeEnvironment; errorEnvironment = new ErrorEnvironment(); processedIdentifiers = new ArrayList<Identifier>(); } public ErrorEnvironment getErrorEnvironment() { return errorEnvironment; } /** * Entry point, static type checks the supplied tree */ public static ErrorEnvironment check(Statement tree, TypeEnvironment typeEnvironment) { TypeChecker typeChecker = new TypeChecker(typeEnvironment); tree.accept(typeChecker); return typeChecker.getErrorEnvironment(); } public static ErrorEnvironment check(Expression tree, TypeEnvironment typeEnvironment) { TypeChecker typeChecker = new TypeChecker(typeEnvironment); tree.accept(typeChecker); return typeChecker.getErrorEnvironment(); } public static ErrorEnvironment check(QLType tree, TypeEnvironment typeEnvironment) { TypeChecker typeChecker = new TypeChecker(typeEnvironment); tree.accept(typeChecker); return typeChecker.getErrorEnvironment(); } @Override public Void visit(DefaultWidget defaultNode) { if(!defaultNode.getWidget().isCompatibleWith(defaultNode.getType())) { errorEnvironment.addError(new IncompatibleWidgetError(defaultNode, defaultNode.getType(), defaultNode.getWidget())); } return super.visit(defaultNode); } @Override public QLType visit(Identifier identifierNode) { if(processedIdentifiers.contains(identifierNode)) { errorEnvironment.addError(new DuplicateIdentifierError(identifierNode)); } processedIdentifiers.add(identifierNode); return typeEnvironment.resolve(identifierNode); } @Override public Void visit(Page pageNode) { QLType resolvedType = pageNode.getIdentifier().accept(this); if(resolvedType != null) { errorEnvironment.addError(new PageIdentifierError(pageNode.getIdentifier(), resolvedType)); } return pageNode.getStatements().accept(this); } @Override public Void visit(Question questionNode) { QLType identifierType = questionNode.getIdentifier().accept(this); if(identifierType == null || identifierType == new QLForm()) { errorEnvironment.addError(new NullQuestionError(questionNode.getIdentifier())); } else if(!questionNode.hasCompatibleWidget(identifierType)) { errorEnvironment.addError(new IncompatibleWidgetError(questionNode, identifierType, questionNode.getWidget())); } return questionNode.getWidget().accept(this); } @Override public Void visit(Stylesheet stylesheetNode) { QLType resolvedType = stylesheetNode.getIdentifier().accept(this); if(resolvedType == null || !resolvedType.equals(new QLForm())) { errorEnvironment.addError(new StylesheetIdentifierError(stylesheetNode, resolvedType)); } stylesheetNode.getPages().accept(this); if(!processedIdentifiers.containsAll(typeEnvironment.getIdentifiers())) { errorEnvironment.addError(new MissingIdentifiersError(stylesheetNode, processedIdentifiers, typeEnvironment.getIdentifiers())); } return null; } @Override public Void visit(Dropdown dropdownNode) { if(!dropdownNode.hasCompatibleValues()) { errorEnvironment.addError(new IllegalWidgetValueError(dropdownNode, dropdownNode.getCompatibleValueType())); } return super.visit(dropdownNode); } @Override public Void visit(RadioButton radioButtonNode) { if(!radioButtonNode.hasCompatibleValues()) { errorEnvironment.addError(new IllegalWidgetValueError(radioButtonNode, radioButtonNode.getCompatibleValueType())); } return super.visit(radioButtonNode); } @Override public Void visit(Slider sliderNode) { if(!sliderNode.hasCompatibleValues()) { errorEnvironment.addError(new IllegalWidgetValueError(sliderNode, sliderNode.getCompatibleValueType())); } return super.visit(sliderNode); } @Override public QLType visit(BooleanLiteral booleanLiteral) { return booleanLiteral.getType(); } @Override public QLType visit(FloatLiteral floatLiteral) { return floatLiteral.getType(); } @Override public QLType visit(IntegerLiteral integerLiteral) { return integerLiteral.getType(); } @Override public QLType visit(StringLiteral stringLiteral) { return stringLiteral.getType(); } /********************* * STYLE PROPERTIES ** *********************/ @Override public Void visit(Color color) { QLType valueType = color.getLiteral().accept(this); if(!color.isCompatibleWith(valueType)) { errorEnvironment.addError(new IllegalPropertyValueError(color, valueType)); } return null; } @Override public Void visit(Width width) { QLType valueType = width.getLiteral().accept(this); if(!width.isCompatibleWith(valueType)) { errorEnvironment.addError(new IllegalPropertyValueError(width, valueType)); } return null; } @Override public Void visit(Height height) { QLType valueType = height.getLiteral().accept(this); if(!height.isCompatibleWith(valueType)) { errorEnvironment.addError(new IllegalPropertyValueError(height, valueType)); } return null; } @Override public Void visit(Font font) { QLType valueType = font.getLiteral().accept(this); if(!font.isCompatibleWith(valueType)) { errorEnvironment.addError(new IllegalPropertyValueError(font, valueType)); } return null; } @Override public Void visit(FontSize fontSize) { QLType valueType = fontSize.getLiteral().accept(this); if(!fontSize.isCompatibleWith(valueType)) { errorEnvironment.addError(new IllegalPropertyValueError(fontSize, valueType)); } return null; } }