package uva.ql.interpreter.typecheck;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import uva.ql.ast.Node;
import uva.ql.ast.Form;
import uva.ql.ast.Prog;
import uva.ql.ast.expressions.BinaryExpression;
import uva.ql.ast.expressions.Expression;
import uva.ql.ast.expressions.literals.BooleanLiteral;
import uva.ql.ast.expressions.literals.MoneyLiteral;
import uva.ql.ast.expressions.literals.Identifier;
import uva.ql.ast.expressions.literals.IntLiteral;
import uva.ql.ast.expressions.literals.StringLiteral;
import uva.ql.ast.expressions.logic.And;
import uva.ql.ast.expressions.logic.Equal;
import uva.ql.ast.expressions.logic.Greater;
import uva.ql.ast.expressions.logic.Greater_Eq;
import uva.ql.ast.expressions.logic.Less;
import uva.ql.ast.expressions.logic.Less_Eq;
import uva.ql.ast.expressions.logic.NotEqual;
import uva.ql.ast.expressions.logic.Or;
import uva.ql.ast.expressions.math.Addition;
import uva.ql.ast.expressions.math.Division;
import uva.ql.ast.expressions.math.Exponentiation;
import uva.ql.ast.expressions.math.Multiplication;
import uva.ql.ast.expressions.math.Substraction;
import uva.ql.ast.statements.Assign;
import uva.ql.ast.statements.IfStatement;
import uva.ql.ast.statements.Question;
import uva.ql.ast.statements.Statement;
import uva.ql.ast.type.Type;
import uva.ql.ast.type.TypeBoolean;
import uva.ql.ast.type.TypeInteger;
import uva.ql.ast.type.TypeMoney;
import uva.ql.ast.type.TypeString;
import uva.ql.ast.visitor.ExpressionVisitor;
import uva.ql.ast.visitor.StatementVisitor;
import uva.ql.ast.visitor.TypeVisitor;
import uva.ql.interpreter.typecheck.depedency.DependencyExpressionVisitor;
import uva.ql.interpreter.typecheck.depedency.DependencyHelper;
import uva.ql.interpreter.typecheck.error.IssueList;
import uva.ql.interpreter.typecheck.error.IssueObject;
import uva.ql.interpreter.typecheck.error.IssueType;
import uva.ql.interpreter.typecheck.table.DependencyTable;
import uva.ql.interpreter.typecheck.table.LabelTable;
import uva.ql.interpreter.typecheck.table.SymbolTable;
public class TypeCheckVisitor implements ExpressionVisitor<Expression>, StatementVisitor<Void>, TypeVisitor<Type>{
private final IssueList issueList = new IssueList();
private final SymbolTable symbolTable = new SymbolTable();
private final LabelTable labelTable = new LabelTable();
private final DependencyExpressionVisitor dependencyVisitor = new DependencyExpressionVisitor();
private DependencyTable dependencyTable = new DependencyTable();
/* Type Check Methods */
public boolean hasErrors(){
return this.issueList.hasErrors();
}
public List<IssueObject> errorList(){
return issueList.errorList();
}
public List<IssueObject> getErrorOfType(IssueType.ERROR type){
return this.issueList.getErrorOfType(type);
}
public void printIssues(){
this.issueList.printIssues();
}
private boolean isDuplicateQuestionSameType(Question question){
String questionIdentifier = question.getQuestionIdentifier().evaluate().getValue();
Type questionType = question.getQuestionType();
return this.symbolTable.typeEqualsTo(questionIdentifier, questionType);
}
private boolean isDuplicateQuestionDifferentType(Question question){
String questionIdentifier = question.getQuestionIdentifier().evaluate().getValue();
Type questionType = question.getQuestionType();
if (this.symbolTable.keyExists(questionIdentifier)){
return !this.symbolTable.retrieveType(questionIdentifier).equals(questionType);
}
return false;
}
private boolean referenceToUndefinedQuestion(Identifier identifier){
return !symbolTable.keyExists(identifier.evaluate().getValue());
}
private boolean duplicateLabelCheck(Question question){
return this.labelTable.keyExists(question.getQuestionLabel().evaluate().getValue().replaceAll("\\s+",""));
}
private boolean expressionOfType(List<Type> possibleTypes, List<Type> acceptedTypes){
for (Type type : acceptedTypes){
if (!possibleTypes.contains(type)){
return false;
}
}
return true;
}
private void cyclicDependencies(){
DependencyHelper helper = new DependencyHelper();
dependencyTable = helper.populateDependencyTable(dependencyTable);
Set<String> cycles = helper.getCycles(dependencyTable);
if (!cycles.isEmpty()){
this.addCyclicIssues(cycles);
}
}
private void addCyclicIssues(Set<String> cycles){
for (String _identifier: cycles){
IssueObject issue = new IssueObject(IssueType.ERROR.CYCLIC_DEPENDANCIES, _identifier, null);
this.issueList.putIssue(issue);
}
}
/* Type Check Visitor */
@Override
public Void visitProg(Prog prog) {
prog.getForm().accept(this);
this.cyclicDependencies();
return null;
}
@Override
public Void visitForm(Form form) {
this.visitStatements(form.getFormStatements());
return null;
}
private Object visitStatements(List<Statement> statements){
for (Statement statement : statements){
statement.accept(this);
}
return null;
}
@Override
public Void visitASTNode(Node node) {
return null;
}
@Override
public Void visitStatement(Statement statement) {
statement.accept(this);
return null;
}
@Override
public Void visitIfStatement(IfStatement ifStatement) {
if (ifStatement.hasBooleanCondition()){
this.visitBinaryLogical((BinaryExpression)ifStatement.getIfStatementExpression());
}
else {
IssueObject issue = new IssueObject(IssueType.ERROR.CONDITION_NOT_BOOLEAN, ifStatement.getIfStatementExpression(),ifStatement.getCodeLine());
this.issueList.putIssue(issue);
}
this.visitStatements(ifStatement.getStatements());
return null;
}
@Override
public Void visitSimpleQuestion(Question question) {
this.visitQuestion(question);
return null;
}
@Override
public Void visitComputedQuestion(Question question) {
dependencyVisitor.visitExpression(question.getQuestionExpression());
dependencyTable.putIdentifierSet(question.getQuestionIdentifierValue(), dependencyVisitor.getIdentifierSet());
this.visitQuestion(question);
question.getQuestionExpression().accept(this);
return null;
}
private Object visitQuestion(Question question){
String questionIdentifier = question.getQuestionIdentifierValue();
String questionLabel = question.getQuestionLabelText();
if (this.isDuplicateQuestionDifferentType(question)){
IssueObject issue = new IssueObject(IssueType.ERROR.DUPLICATE_DIFFERENT_TYPE, question, question.getCodeLine());
this.issueList.putIssue(issue);
}
if (isDuplicateQuestionSameType(question)){
IssueObject issue = new IssueObject(IssueType.ERROR.DUPLICATE_SAME_TYPE, question, question.getCodeLine());
this.issueList.putIssue(issue);
}
if (duplicateLabelCheck(question)){
IssueObject issue = new IssueObject(IssueType.WARNING.DUPLICATE_QUESTION_LABEL, question, question.getCodeLine());
this.issueList.putIssue(issue);
}
this.labelTable.putValue(questionLabel.replaceAll("\\s+",""), question.getCodeLine());
this.symbolTable.putValue(questionIdentifier, question.getQuestionType());
return null;
}
@Override
public Void visitAssign(Assign assign) {
assign.getAssignIdentifier().accept(this);
assign.getAssignExpression().accept(this);
return null;
}
@Override
public Expression visitBinaryExpression(BinaryExpression expression) {
expression.accept(this);
return null;
}
private Expression visitBinaryNumerical(BinaryExpression binaryExpression){
Expression left = binaryExpression.getLeftExpr();
Expression right = binaryExpression.getRightExpr();
List<Type> acceptedTypes = Arrays.asList(new TypeMoney(), new TypeInteger());
boolean leftIsNumerical = this.expressionOfType(left.possibleReturnTypes(), acceptedTypes);
boolean rightIsNumerical = this.expressionOfType(right.possibleReturnTypes(), acceptedTypes);
if (!leftIsNumerical){
this.identifierExpressionValid(left, acceptedTypes);
}
if (!rightIsNumerical){
this.identifierExpressionValid(right, acceptedTypes);
}
left.accept(this);
right.accept(this);
return null;
}
private Expression visitBinaryLogical(BinaryExpression binaryExpression){
Expression left = binaryExpression.getLeftExpr();
Expression right = binaryExpression.getRightExpr();
List<Type> leftType = this.getExpressionType(left);
List<Type> rightType = this.getExpressionType(right);
if (Collections.disjoint(leftType, rightType)){
IssueObject issue = new IssueObject(IssueType.ERROR.INVALID_OPERANDS_LOGICAL, binaryExpression, binaryExpression.getLinesOfCode());
this.issueList.putIssue(issue);
}
left.accept(this);
right.accept(this);
return null;
}
private void identifierExpressionValid(Expression expression, List<Type> acceptedTypes){
IssueObject issue = new IssueObject(IssueType.ERROR.INVALID_OPERANDS_MATH, expression, expression.getLinesOfCode());
if (expression.isIdentifier()){
Identifier identifier = (Identifier)expression;
Type type = this.symbolTable.retrieveType(identifier.getValue());
if (type != null){
if (!type.typeDoesConfirm(acceptedTypes)){
this.issueList.putIssue(issue);
}
}
}
else {
this.issueList.putIssue(issue);
}
}
private List<Type> getExpressionType(Expression expression){
if (expression.possibleReturnTypes().contains(new TypeString())){
Identifier identifier = (Identifier)expression;
Type type = this.symbolTable.retrieveType(identifier.getValue());
return Arrays.asList(type);
}
return expression.possibleReturnTypes();
}
@Override
public Expression visitExpression(Expression expression) {
return expression.accept(this);
}
@Override
public Expression visitExponentiation(Exponentiation exponentiation) {
return this.visitBinaryNumerical(exponentiation);
}
@Override
public Expression visitAddition(Addition addition) {
return this.visitBinaryNumerical(addition);
}
@Override
public Expression visitSubstraction(Substraction substraction) {
return this.visitBinaryNumerical(substraction);
}
@Override
public Expression visitMultiplication(Multiplication multipllication) {
return this.visitBinaryNumerical(multipllication);
}
@Override
public Expression visitDivision(Division division) {
return this.visitBinaryNumerical(division);
}
@Override
public Expression visitAnd(And and) {
return this.visitBinaryLogical(and);
}
@Override
public Expression visitOr(Or or) {
return this.visitBinaryLogical(or);
}
@Override
public Expression visitEqual(Equal equal) {
return this.visitBinaryLogical(equal);
}
@Override
public Expression visitNotEqual(NotEqual notEqual) {
return this.visitBinaryLogical(notEqual);
}
@Override
public Expression visitGreaterEqual(Greater_Eq greaterEqual) {
return this.visitBinaryLogical(greaterEqual);
}
@Override
public Expression visitGreater(Greater greater) {
return this.visitBinaryLogical(greater);
}
@Override
public Expression visitLessEqual(Less_Eq lessEqual) {
return this.visitBinaryLogical(lessEqual);
}
@Override
public Expression visitLess(Less less) {
return this.visitBinaryLogical(less);
}
@Override
public Identifier visitIdentifier(Identifier identifier) {
if (this.referenceToUndefinedQuestion(identifier)){
IssueObject issue = new IssueObject(IssueType.ERROR.REFERENCE_UNDEFINED, identifier, identifier.getLinesOfCode());
this.issueList.putIssue(issue);
}
return identifier;
}
@Override
public BooleanLiteral visitBooleanLiteral(BooleanLiteral booleanLiteral) {
return booleanLiteral;
}
@Override
public MoneyLiteral visitMoneyLiteral(MoneyLiteral moneyLiteral) {
return moneyLiteral;
}
@Override
public IntLiteral visitIntLiteral(IntLiteral intLiteral) {
return intLiteral;
}
@Override
public StringLiteral visitStringLiteral(StringLiteral stringLiteral) {
return stringLiteral;
}
@Override
public TypeBoolean visitTypeBoolean(TypeBoolean booleanType) {
return booleanType;
}
@Override
public TypeInteger visitTypeInteger(TypeInteger integerType) {
return integerType;
}
@Override
public TypeMoney visitTypeMoney(TypeMoney moneyType) {
return moneyType;
}
@Override
public TypeString visitTypeString(TypeString stringType) {
return stringType;
}
}