package org.uva.ql.typechecker;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.uva.ql.ast.CodePosition;
import org.uva.ql.ast.expression.Expression;
import org.uva.ql.ast.expression.association.Parenthesis;
import org.uva.ql.ast.expression.binary.Addition;
import org.uva.ql.ast.expression.binary.And;
import org.uva.ql.ast.expression.binary.Binary;
import org.uva.ql.ast.expression.binary.Divide;
import org.uva.ql.ast.expression.binary.Equal;
import org.uva.ql.ast.expression.binary.Greater;
import org.uva.ql.ast.expression.binary.GreaterEqual;
import org.uva.ql.ast.expression.binary.Less;
import org.uva.ql.ast.expression.binary.LessEqual;
import org.uva.ql.ast.expression.binary.Multiply;
import org.uva.ql.ast.expression.binary.NotEqual;
import org.uva.ql.ast.expression.binary.Or;
import org.uva.ql.ast.expression.binary.Substraction;
import org.uva.ql.ast.expression.literal.BoolLiteral;
import org.uva.ql.ast.expression.literal.Identifier;
import org.uva.ql.ast.expression.literal.IntLiteral;
import org.uva.ql.ast.expression.literal.StrLiteral;
import org.uva.ql.ast.expression.unary.Negative;
import org.uva.ql.ast.expression.unary.Not;
import org.uva.ql.ast.expression.unary.Positive;
import org.uva.ql.ast.questionnaire.Form;
import org.uva.ql.ast.questionnaire.Questionnaire;
import org.uva.ql.ast.statement.Block;
import org.uva.ql.ast.statement.IfElseStatement;
import org.uva.ql.ast.statement.IfStatement;
import org.uva.ql.ast.statement.QuestionComputed;
import org.uva.ql.ast.statement.QuestionNormal;
import org.uva.ql.ast.statement.Statement;
import org.uva.ql.ast.type.BoolType;
import org.uva.ql.ast.type.IntType;
import org.uva.ql.ast.type.StrType;
import org.uva.ql.ast.type.Type;
import org.uva.ql.typechecker.dependency.DependencyList;
import org.uva.ql.visitor.ExpressionVisitor;
import org.uva.ql.visitor.QuestionnaireVisitor;
import org.uva.ql.visitor.StatementVisitor;
import org.uva.util.message.Error;
import org.uva.util.message.MessageManager;
import org.uva.util.message.Warning;
public class TypeChecker implements StatementVisitor<Boolean>, ExpressionVisitor<Boolean>,
QuestionnaireVisitor<Boolean> {
private Map<Identifier, Type> types;
private List<String> labels;
private MessageManager messageManager;
private DependencyList dependencyList;
public TypeChecker() {
types = new HashMap<Identifier, Type>();
labels = new ArrayList<String>();
messageManager = new MessageManager();
dependencyList = new DependencyList();
}
// Name-Type table
private void addType(Identifier id, Type type) {
types.put(id, type);
}
private boolean isDeclared(Identifier id) {
return types.containsKey(id);
}
public Type getType(Identifier id) {
return types.get(id);
}
public void printAll() {
Set<Identifier> keys = types.keySet();
for (Iterator<Identifier> i = keys.iterator(); i.hasNext();) {
String name = (String) i.next().toString();
String type = types.get(i.next()).toString();
System.out.println(name + " " + type);
}
}
// Label list
private void addLabel(String label) {
labels.add(label);
}
private boolean hasLabel(String label) {
return labels.contains(label);
}
// Message Management
private void addError(Error.Type type, Expression expr) {
Error error = new Error(type, expr.getPosition().getStartLine(), expr.toString());
messageManager.addError(error);
}
private void addError(Error.Type type, Expression expr, String expectType) {
Error error = new Error(type, expr.getPosition().getStartLine(), expr.toString(), expectType);
messageManager.addError(error);
}
private void addWarning(Warning warning) {
messageManager.addWarning(warning);
}
private void addWarning(Warning.Type type, QuestionNormal question) {
Warning warning = new Warning(type, question.getPosition().getStartLine(), question.getLabel().toString());
addWarning(warning);
}
public void printMessages() {
System.out.println();
System.out.println("[ERRORS] (" + messageManager.countErrors() + " items)");
messageManager.printErrors();
System.out.println();
System.out.println("[WARNINGS] (" + messageManager.countWarnings() + " items)");
messageManager.printWarnings();
}
// Checkers
private boolean checkLabel(QuestionNormal question) {
String label = question.getLabel().toString();
if (hasLabel(label)) {
addWarning(Warning.Type.DUPLICATE, question);
} else {
addLabel(label);
}
return true;
}
private boolean checkDeclaration(QuestionNormal question) {
if (isDeclared(question.getIdentifier())) {
Type thisType = question.getType();
Type expectType = getType(question.getIdentifier());
if (!thisType.isEqual(expectType)) {
addError(Error.Type.DECLARATION, question.getIdentifier());
return false;
}
} else {
addType(question.getIdentifier(), question.getType());
}
return true;
}
private boolean checkReference(Identifier identifier) {
if (!isDeclared(identifier)) {
addError(Error.Type.REFERENCE, identifier);
return false;
}
return true;
}
private boolean checkExpressionMatchType(Expression expr, Type expectType) {
if (expr.accept(this)) {
if (!expr.getType(this).isEqual(expectType)) {
addError(Error.Type.MISMATCH, expr, expectType.toString());
return false;
} else {
return true;
}
} else {
return false;
}
}
private boolean checkMatchThisLevel(Expression expr, Type expectType) {
boolean result = true;
if (!expr.getType(this).isEqual(expectType)) {
addError(Error.Type.MISMATCH, expr, expectType.toString());
result = false;
}
return result;
}
private boolean checkBinaryMatchType(Binary binary, Type expectType) {
Expression left = binary.getLeftExpression();
Expression right = binary.getRightExpression();
boolean resultLeft = checkExpressionMatchType(left, expectType);
boolean resultRight = checkExpressionMatchType(right, expectType);
return resultLeft && resultRight;
}
private boolean checkBinarySameType(Binary binary) {
Expression left = binary.getLeftExpression();
Expression right = binary.getRightExpression();
// Check inner levels then do the comparison
boolean resultL = left.accept(this);
boolean resultR = right.accept(this);
if (resultL && resultR) {
return checkExpressionMatchType(right, left.getType(this));
} else {
return false;
}
}
private boolean checkCyclicDependency() {
List<Identifier> cyclicDependentIdentifiers = dependencyList.getCyclicDependentIdentifiers();
if (cyclicDependentIdentifiers.size() != 0) {
for (Identifier identifier : cyclicDependentIdentifiers) {
addError(Error.Type.CYCLIC, identifier);
}
return false;
}
return true;
}
// Visits
@Override
public Boolean visit(Questionnaire questionnaire) {
boolean result = true;
for (Form form : questionnaire.getForms()) {
if (!form.accept(this)) {
result = false;
}
}
return result;
}
@Override
public Boolean visit(Form form) {
boolean result1 = form.getBlock().accept(this);
boolean result2 = checkCyclicDependency();
return result1 && result2;
}
@Override
public Boolean visit(Block block) {
boolean result = true;
for (Statement statement : block.getStatements()) {
if (!statement.accept(this)) {
result = false;
}
}
return result;
}
@Override
public Boolean visit(QuestionNormal question) {
boolean isValidDeclaration = checkDeclaration(question);
boolean isValidLabel = checkLabel(question);
return isValidDeclaration && isValidLabel;
}
@Override
public Boolean visit(QuestionComputed question) {
boolean isValidDeclaration = checkDeclaration(question);
boolean isValidLabel = checkLabel(question);
boolean isValidExpression = checkExpressionMatchType(question.getExpression(), question.getType());
// Build the dependency list
DependencyVisitor visitor = new DependencyVisitor();
List<Identifier> dependentIdentifiers = question.getExpression().accept(visitor);
for (Identifier identifier : dependentIdentifiers) {
dependencyList.add(identifier, question.getIdentifier());
}
return isValidDeclaration && isValidLabel && isValidExpression;
}
@Override
public Boolean visit(IfStatement ifStatement) {
boolean isValidExpression = checkExpressionMatchType(ifStatement.getExpr(),
new BoolType(ifStatement.getPosition()));
boolean isValidIfBlock = ifStatement.getIfBlock().accept(this);
return isValidExpression && isValidIfBlock;
}
@Override
public Boolean visit(IfElseStatement ifElseStatement) {
boolean isValidExpression = checkExpressionMatchType(ifElseStatement.getExpr(),
new BoolType(ifElseStatement.getPosition()));
boolean isValidIfBlock = ifElseStatement.getIfBlock().accept(this);
boolean isValidElseBlock = ifElseStatement.getElseBLock().accept(this);
return isValidExpression && isValidIfBlock && isValidElseBlock;
}
@Override
public Boolean visit(Parenthesis node) {
return node.getExpression().accept(this);
}
@Override
public Boolean visit(Not unary) {
return checkExpressionMatchType(unary.getExpression(), new BoolType(unary.getPosition()));
}
@Override
public Boolean visit(Positive unary) {
return checkExpressionMatchType(unary.getExpression(), new IntType(unary.getPosition()));
}
@Override
public Boolean visit(Negative unary) {
return checkExpressionMatchType(unary.getExpression(), new IntType(unary.getPosition()));
}
@Override
public Boolean visit(Addition binary) {
Expression left = binary.getLeftExpression();
Expression right = binary.getRightExpression();
boolean isValidLeftPart = left.accept(this);
boolean isValidRightPart = right.accept(this);
boolean result = true;
CodePosition pos = binary.getPosition();
if (isValidLeftPart && isValidRightPart) {
if (left.getType(this).isEqual(new IntType(pos)) || left.getType(this).isEqual(new StrType(pos))) {
result = checkMatchThisLevel(right, left.getType(this));
} else {
if (right.getType(this).isEqual(new BoolType(pos))) {
Error errorLeft = new Error(Error.Type.MISMATCH, left.getPosition().getStartLine(),
left.toString(), "Int|Str");
messageManager.addError(errorLeft);
Error errorRight = new Error(Error.Type.MISMATCH, right.getPosition().getStartLine(),
right.toString(), "Int|Str");
messageManager.addError(errorRight);
} else {
result = checkMatchThisLevel(left, right.getType(this));
}
result = false;
}
} else {
result = false;
}
return result;
}
@Override
public Boolean visit(Substraction binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(Multiply binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(Divide binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(Greater binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(GreaterEqual binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(Less binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(LessEqual binary) {
return checkBinaryMatchType(binary, new IntType(binary.getPosition()));
}
@Override
public Boolean visit(And binary) {
return checkBinaryMatchType(binary, new BoolType(binary.getPosition()));
}
@Override
public Boolean visit(Or binary) {
return checkBinaryMatchType(binary, new BoolType(binary.getPosition()));
}
@Override
public Boolean visit(Equal binary) {
return checkBinarySameType(binary);
}
@Override
public Boolean visit(NotEqual binary) {
return checkBinarySameType(binary);
}
@Override
public Boolean visit(Identifier node) {
return checkReference(node);
}
@Override
public Boolean visit(IntLiteral node) {
return true;
}
@Override
public Boolean visit(BoolLiteral node) {
return true;
}
@Override
public Boolean visit(StrLiteral node) {
return true;
}
}