package de.skuzzle.polly.core.parser.ast.visitor.resolving;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import de.skuzzle.polly.core.parser.ast.Identifier;
import de.skuzzle.polly.core.parser.ast.ResolvableIdentifier;
import de.skuzzle.polly.core.parser.ast.declarations.Declaration;
import de.skuzzle.polly.core.parser.ast.declarations.Namespace;
import de.skuzzle.polly.core.parser.ast.declarations.types.MapType;
import de.skuzzle.polly.core.parser.ast.declarations.types.MissingType;
import de.skuzzle.polly.core.parser.ast.declarations.types.ProductType;
import de.skuzzle.polly.core.parser.ast.declarations.types.Substitution;
import de.skuzzle.polly.core.parser.ast.declarations.types.Type;
import de.skuzzle.polly.core.parser.ast.expressions.Assignment;
import de.skuzzle.polly.core.parser.ast.expressions.Call;
import de.skuzzle.polly.core.parser.ast.expressions.Delete;
import de.skuzzle.polly.core.parser.ast.expressions.Empty;
import de.skuzzle.polly.core.parser.ast.expressions.Expression;
import de.skuzzle.polly.core.parser.ast.expressions.Inspect;
import de.skuzzle.polly.core.parser.ast.expressions.NamespaceAccess;
import de.skuzzle.polly.core.parser.ast.expressions.Native;
import de.skuzzle.polly.core.parser.ast.expressions.OperatorCall;
import de.skuzzle.polly.core.parser.ast.expressions.VarAccess;
import de.skuzzle.polly.core.parser.ast.expressions.literals.FunctionLiteral;
import de.skuzzle.polly.core.parser.ast.expressions.literals.ListLiteral;
import de.skuzzle.polly.core.parser.ast.expressions.literals.ProductLiteral;
import de.skuzzle.polly.core.parser.ast.visitor.ASTTraversalException;
import de.skuzzle.polly.core.parser.ast.visitor.DepthFirstVisitor;
import de.skuzzle.polly.core.parser.ast.visitor.Unparser;
import de.skuzzle.polly.core.parser.problems.ProblemReporter;
import de.skuzzle.polly.core.parser.problems.Problems;
import de.skuzzle.polly.core.parser.util.Combinator;
import de.skuzzle.polly.core.parser.util.Combinator.CombinationCallBack;
/**
* This visitor resolves <b>all</b> possible types for an expression and stores them in
* each expression's <i>types</i> attribute. A Second pass type resolval is needed to
* determine each expression's unique type.
*
* @author Simon Taddiken
* @see SecondPassTypeResolver
*/
class FirstPassTypeResolver extends AbstractTypeResolver {
public FirstPassTypeResolver(Namespace namespace, ProblemReporter reporter) {
super(namespace, reporter);
}
@Override
public int before(Native node) throws ASTTraversalException {
node.resolveType(this.nspace, this);
return CONTINUE;
}
@Override
public int before(Declaration node) throws ASTTraversalException {
node.getExpression().visit(this);
return CONTINUE;
}
@Override
public boolean visit(final FunctionLiteral node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
final List<Type> source = new ArrayList<Type>();
// resolve parameter types
this.enter();
final Set<String> names = new HashSet<String>();
for (final Declaration d : node.getFormal()) {
if (!names.add(d.getName().getId())) {
this.reportError(d.getName(), Problems.DUPLICATED_DECL, d.getName());
}
if (!d.visit(this)) {
return false;
}
source.add(d.getType());
this.nspace.declare(d);
}
if (!node.getBody().visit(this)) {
return false;
}
this.leave();
if (source.isEmpty()) {
source.add(Type.VOID);
}
for (final Type te : node.getBody().getTypes()) {
if (node.getBody().hasConstraint(te)) {
final Substitution constraint = node.getBody().getConstraint(te);
final Type t = new ProductType(source).mapTo(te).subst(
constraint.toSource());
node.addType(t);
} else {
node.addType(new ProductType(source).mapTo(te));
}
}
return this.after(node) == CONTINUE;
}
@Override
public int after(ListLiteral node) throws ASTTraversalException {
if (node.getContent().isEmpty()) {
this.reportError(node, Problems.EMPTY_LIST);
}
for (final Expression exp : node.getContent()) {
for (final Type t : exp.getTypes()) {
node.addType(t.listOf(), exp.getConstraint(t));
}
}
return CONTINUE;
}
@Override
public int after(final ProductLiteral node) throws ASTTraversalException {
if (node.getContent().isEmpty()) {
node.addType(new ProductType(Type.VOID));
return CONTINUE;
}
// Use combinator to create all combinations of possible types
final CombinationCallBack<Expression, Type> ccb =
new CombinationCallBack<Expression, Type>() {
@Override
public List<Type> getSubList(Expression outer) {
return outer.getTypes();
}
@Override
public void onNewCombination(List<Type> combination) {
final ProductType prod = new ProductType(combination);
final Iterator<Expression> contIt = node.getContent().iterator();
final Iterator<Type> typeIt = prod.getTypes().iterator();
// join constraints of product
Substitution s = new Substitution();
while (typeIt.hasNext()) {
final Expression nextExp = contIt.next();
final Type nextType = typeIt.next();
final Substitution constraint = nextExp.getConstraint(nextType);
s = s.join(constraint);
}
node.addType(prod, s);
}
};
Combinator.combine(node.getContent(), ccb);
return CONTINUE;
}
@Override
public int after(final Assignment node) throws ASTTraversalException {
if (node.isTemp()) {
this.reportError(node,
"Temporäre Deklarationen werden (noch?) nicht unterstützt.");
}
// deep transitive recursion check
node.getExpression().visit(new DepthFirstVisitor() {
@Override
public int before(VarAccess access) throws ASTTraversalException {
final Collection<Declaration> decls =
nspace.lookupAll(access.getIdentifier());
for (final Declaration decl : decls) {
if (decl.getName().equals(node.getName())) {
reportError(access, Problems.RECURSIVE_CALL);
return ABORT;
}
if (!decl.isNative()) {
if (!decl.getExpression().visit(this)) {
return ABORT;
}
}
}
return CONTINUE;
}
});
for (final Type t : node.getExpression().getTypes()) {
final Declaration vd = new Declaration(node.getName().getPosition(),
node.getName(), new Empty(t, node.getExpression().getPosition()));
this.nspace.declare(vd);
node.addType(t, node.getExpression().getConstraint(t));
}
return CONTINUE;
}
@Override
public boolean visit(OperatorCall node) throws ASTTraversalException {
return this.visit((Call) node);
}
@Override
public boolean visit(Call node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
// resolve parameter types
if (!node.getRhs().visit(this)) {
return false;
}
final List<Type> possibleTypes = new ArrayList<Type>(
node.getRhs().getTypes().size());
for (final Type rhsType : node.getRhs().getTypes()) {
possibleTypes.add(rhsType.mapTo(Type.newTypeVar()));
}
// resolve called function's types
if (!node.getLhs().visit(this)) {
return false;
}
boolean hasMapType = false;
for (final Type type : node.getLhs().getTypes()) {
hasMapType |= type instanceof MapType;
if (hasMapType) break;
}
if (!hasMapType) {
this.reportError(node.getLhs(), Problems.NO_FUNCTION,
Unparser.toString(node.getLhs()));
} else if (node.getLhs().getTypes().isEmpty()) {
this.reportError(node.getLhs(), Problems.UNKNOWN_FUNCTION,
Unparser.toString(node.getLhs()));
} else {
// sort out all lhs types that do not match the rhs types
for (final Type possibleLhs : possibleTypes) {
for (final Type lhs : node.getLhs().getTypes()) {
final Substitution subst = Type.unify(possibleLhs, lhs);
if (subst != null) {
// construct new type with the argument types of lhs, and
// result type of rhs
final MapType lhsMap = (MapType) lhs;
final MapType plhsMap = (MapType) possibleLhs;
final Substitution constraint = node.getRhs().getConstraint(
plhsMap.getSource());
final MapType mtc = (MapType) plhsMap.getSource().mapTo(
lhsMap.getTarget()).subst(subst);
node.addType(mtc.getTarget(), subst.join(constraint));
}
}
}
}
if (node.getTypes().isEmpty()) {
final String problem = node instanceof OperatorCall
? Problems.INCOMPATIBLE_OP
: Problems.INCOMPATIBLE_CALL;
this.reportError(node.getLhs(), problem,
Unparser.toString(node.getLhs()));
node.addType(new MissingType(new Identifier("$compatibilty")), null);
}
return this.after(node) == CONTINUE;
}
@Override
public int before(VarAccess node) throws ASTTraversalException {
final Set<Type> types = this.nspace.lookupFresh(node, this.reporter);
node.addTypes(types);
return CONTINUE;
}
@Override
public boolean visit(NamespaceAccess node) throws ASTTraversalException {
switch (this.before(node)) {
case SKIP: return true;
case ABORT: return false;
}
if (!(node.getLhs() instanceof VarAccess)) {
this.reportError(node.getLhs(), Problems.ILLEGAL_NS_ACCESS);
} else if (!(node.getRhs() instanceof VarAccess)) {
this.reportError(node.getRhs(), Problems.ILLEGAL_NS_ACCESS);
}
final VarAccess va = (VarAccess) node.getLhs();
if (!Namespace.exists(va.getIdentifier())) {
this.reportError(node.getLhs(), Problems.UNKNOWN_NS, va.getIdentifier());
}
final Namespace last = this.nspace;
this.nspace = Namespace.forName(va.getIdentifier()).derive(this.nspace);
if (!node.getRhs().visit(this)) {
return false;
}
this.nspace = last;
node.addTypes(node.getRhs().getTypes());
return this.after(node) == CONTINUE;
}
@Override
public int before(Delete node) throws ASTTraversalException {
node.addType(Type.NUM, null);
node.setUnique(Type.NUM);
return CONTINUE;
}
@Override
public int before(Inspect node) throws ASTTraversalException {
Namespace target = null;
ResolvableIdentifier var = null;
if (node.getAccess() instanceof VarAccess) {
final VarAccess va = (VarAccess) node.getAccess();
target = this.nspace;
var = va.getIdentifier();
} else if (node.getAccess() instanceof NamespaceAccess) {
final NamespaceAccess nsa = (NamespaceAccess) node.getAccess();
final VarAccess nsName = (VarAccess) nsa.getLhs();
if (!Namespace.exists(nsName.getIdentifier())) {
this.reportError(nsName, Problems.UNKNOWN_NS, nsName.getIdentifier());
}
var = ((VarAccess) nsa.getRhs()).getIdentifier();
target = Namespace.forName(nsName.getIdentifier());
} else {
throw new IllegalStateException("this should not be reachable");
}
final Collection<Declaration> decls = target.lookupAll(var);
if (decls.isEmpty()) {
this.reportError(var, Problems.UNKNOWN_VAR, var.getId());
}
node.setUnique(Type.STRING);
node.addType(Type.STRING, null);
return CONTINUE;
}
}