package ql.semantics;
import ql.ast.form.Form;
import ql.ast.form.FormVisitor;
import ql.ast.statement.*;
import ql.ast.type.*;
import ql.semantics.errors.Error;
import ql.semantics.errors.Messages;
import ql.semantics.errors.Warning;
import java.util.*;
import java.util.stream.Collectors;
/**
* Created by bore on 13/02/15.
*/
public class TypeChecker implements FormVisitor<Void>, StatVisitor<Void>
{
private final Questions questions;
private final QuestionDependencies questionDependencies;
private final LabelMap labels;
private final Messages messages;
public static Messages check(Form f)
{
Questions questions = QuestionCollector.collect(f);
QuestionDependencies dependencies = QuestionDependenciesBuilder.build(f);
TypeChecker typeChecker = new TypeChecker(questions, dependencies);
typeChecker.checkForIdentDuplication();
f.accept(typeChecker);
typeChecker.checkForCyclicDependencies();
typeChecker.checkForLabelDuplication();
return typeChecker.messages;
}
private TypeChecker(Questions questions, QuestionDependencies dependencies)
{
this.questions = questions;
this.questionDependencies = dependencies;
this.labels = new LabelMap();
this.messages = new Messages();
}
@Override
public Void visit(Form form)
{
for (Statement statement : form.getBody())
{
statement.accept(this);
}
return null;
}
@Override
public Void visit(IfCondition condition)
{
Type inferredType = TypeDeducer.deduceType(condition.getCondition(), this.questions, this.messages);
if (this.isTypeAllowedInCond(inferredType))
{
this.messages.add(Error.ifConditionShouldBeBoolean(condition.getLineNumber()));
}
for (Statement statement : condition.getBody())
{
statement.accept(this);
}
return null;
}
private boolean isTypeAllowedInCond(Type type)
{
return !type.isBool();
}
@Override
public Void visit(Question q)
{
this.labels.registerLabel(q);
return null;
}
@Override
public Void visit(CalculatedQuestion q)
{
this.labels.registerLabel(q);
Type defined = q.getType();
Type inferredType = TypeDeducer.deduceType(q.getCalculation(), questions, this.messages);
Type assigned = inferredType.promoteTo(defined);
if (this.areTypesMismatched(defined, assigned))
{
this.messages.add(Error.identifierDefEvalMismatch(q.getId(), defined.getTitle(), assigned.getTitle(),
q.getLineNumber()));
}
return null;
}
private boolean areTypesMismatched(Type defined, Type assigned)
{
return this.isTypeDeclared(defined) &&
this.isTypeDeclared(assigned) &&
!(defined.equals(assigned));
}
private boolean isTypeDeclared(Type type)
{
return !type.isUndef();
}
private void checkForCyclicDependencies()
{
if (this.questionDependencies.containsCycle())
{
Identifiers cyclicIds = this.questionDependencies.getCycleIds();
this.messages.add(Error.cyclicQuestions(cyclicIds.toString()));
}
}
private void checkForLabelDuplication()
{
LabelDuplicates duplicates = this.labels.getLabelDuplicatesSet();
for (Identifiers d : duplicates)
{
this.messages.add(Warning.labelDuplication(d.toString()));
}
}
private void checkForIdentDuplication()
{
for (String id : this.questions)
{
List<Question> lq = this.questions.getQuestionsById(id);
if (this.isIdentDuplicate(lq))
{
this.addDuplicationError(id, lq);
}
}
}
private List<Integer> getSortedLineNumbers(List<Question> qs)
{
List<Integer> result = new ArrayList<>();
for (Question q : qs)
{
result.add(q.getLineNumber());
}
Collections.sort(result);
return result;
}
private boolean isIdentDuplicate(List<Question> lq)
{
return lq.size() > 1;
}
private void addDuplicationError(String id, List<Question> lq)
{
List<String> lines = this.getSortedLineNumbers(lq)
.stream()
.map(Object::toString)
.collect(Collectors.toList());
Error error = Error.identifierAlreadyDeclared(id, lines);
this.messages.add(error);
}
}