package ql.semantics;
import ql.ast.expression.*;
import ql.ast.type.*;
import ql.semantics.errors.Error;
import ql.semantics.errors.Messages;
/**
* Created by bore on 16/03/15.
*/
public class TypeDeducer extends DefaultExprVisitor<Type>
{
private final static BoolType boolType = new BoolType();
private final static StrType strType = new StrType();
private final static IntType intType = new IntType();
private final static DecType decType = new DecType();
private final static UndefType undefType = new UndefType();
private final Questions questions;
private final Messages messages;
public static Type deduceType(Expr e, Questions questions, Messages messages)
{
TypeDeducer deducer = new TypeDeducer(questions, messages);
return e.accept(deducer);
}
private TypeDeducer(Questions questions, Messages messages)
{
this.questions = questions;
this.messages = messages;
}
@Override
public Type visitBinary(BinaryExpr e)
{
return this.computeTypeOfBinaryExpr(e);
}
@Override
public Type visitUnary(UnaryExpr e)
{
return this.computeTypeOfUnaryExpr(e);
}
@Override
public Type visit(BoolExpr n)
{
return boolType;
}
@Override
public Type visit(IntExpr n)
{
return intType;
}
@Override
public Type visit(DecExpr n)
{
return decType;
}
@Override
public Type visit(StrExpr n)
{
return strType;
}
@Override
public Type visit(Ident n)
{
if (this.isIdentUndeclared(n))
{
this.messages.add(ql.semantics.errors.Error.undeclaredIdentifier(n.getId(), n.getLineNumber()));
return undefType;
}
return this.questions.getType(n.getId());
}
@Override
public Type visitDefault(Expr e)
{
throw new IllegalStateException("Expr is unhandled");
}
private boolean isIdentUndeclared(Ident id)
{
return !(this.questions.contains(id.getId()));
}
// 1. Check if the operands are defined
// 2. Check if the operands are of the allowed types
// 3. Check if the operands are of the same type, e.g. no 1=="string"
private Type computeTypeOfBinaryExpr(BinaryExpr e)
{
Type left = e.getLeft().accept(this);
Type right = e.getRight().accept(this);
if (left.isUndef() || right.isUndef())
{
// error is already logged, so just propagate it upwards
return undefType;
}
if (this.isChildTypeIncompatibleWithExpr(e, left))
{
this.addIncorrectTypeError(e, left);
return undefType;
}
if (this.isChildTypeIncompatibleWithExpr(e, right))
{
this.addIncorrectTypeError(e, right);
return undefType;
}
Type leftPromoted = left.promoteTo(right);
Type rightPromoted = right.promoteTo(left);
if (this.areChildTypesInconsistent(leftPromoted, rightPromoted))
{
this.messages.add(Error.typeMismatch(e.getClass().getSimpleName(), left.getTitle(),
right.getTitle(), e.getLineNumber()));
return undefType;
}
return e.getReturnType(leftPromoted);
}
// 1. Check if the operand is defined
// 2. Check if the operand is of the correct type
private Type computeTypeOfUnaryExpr(UnaryExpr e)
{
Type operand = e.getOperand().accept(this);
if (operand.isUndef())
{
// error is already logged, so just propagate it upwards
return undefType;
}
if (this.isChildTypeIncompatibleWithExpr(e, operand))
{
this.addIncorrectTypeError(e, operand);
return undefType;
}
return e.getReturnType(operand);
}
private void addIncorrectTypeError(NaryExpr e, Type childType)
{
this.messages.add(Error.incorrectTypes(e.getClass().getSimpleName(), childType.getTitle(), e.getLineNumber()));
}
private boolean isChildTypeIncompatibleWithExpr(NaryExpr e, Type childType)
{
return !(e.isTypeCompatibleWithExpr(childType));
}
private boolean areChildTypesInconsistent(Type left, Type right)
{
return !(left.equals(right));
}
}