package de.skuzzle.polly.core.parser.ast.declarations.types; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import de.skuzzle.polly.core.parser.ParserProperties; import de.skuzzle.polly.core.parser.Position; import de.skuzzle.polly.core.parser.ast.Identifier; import de.skuzzle.polly.core.parser.ast.declarations.types.unification.Unifier; import de.skuzzle.polly.core.parser.ast.declarations.types.unification.Unifiers; import de.skuzzle.polly.core.parser.ast.visitor.Visitable; import de.skuzzle.polly.tools.EqualsHelper; import de.skuzzle.polly.tools.Equatable; /** * <p>Base class for type expressions. Instances of this class are primitive types for * {@link Expression Expressions}. Using type constructors, more complex type structures * can be created, for example product types, list types and mapping types.</p> * * <p>Please note that in order to be able to unify two type expressions that contain * type variables (as represented by a {@link TypeVar}) both expressions must use the * same instance for the same variable in order to be considered unifiable.</p> * * @author Simon Taddiken */ public class Type implements Visitable<TypeVisitor>, Equatable, Comparable<Type> { // XXX: static field order important! /** Primitive type for Numbers. */ public final static Type NUM = new Type(new Identifier("num"), true, true); /** Primitive type for dates. */ public final static Type DATE = new Type(new Identifier("date"), true, true) { @Override public int compareTo(Type o) { if (o == DATE || !o.isPrimitve()) return 0; return -o.compareTo(this); } }; /** Primitive type for timespans. */ public final static Type TIMESPAN = new Type(new Identifier("timespan"), true, true) { @Override public boolean isA(Type other) { return other == this || other == DATE; } @Override public int compareTo(Type o) { if (o == DATE) return 1; return o == this ? 0 : -1; } }; /** Primitive type for channels. */ public final static Type CHANNEL = new Type(new Identifier("channel"), true, true) { @Override public boolean isA(Type other) { return other == this || other == STRING; } @Override public int compareTo(Type o) { if (o == STRING) return 1; return o == this ? 0 : -1; } }; /** Primitive type for users. */ public final static Type USER = new Type(new Identifier("user"), true, true) { @Override public boolean isA(Type other) { return other == this || other == STRING; } @Override public int compareTo(Type o) { if (o == DATE) return 1; return o == this ? 0 : -1; } }; /** Primitive type for strings. */ public final static Type STRING = new Type(new Identifier("string"), true, true) { @Override public int compareTo(Type o) { if (o == STRING || !o.isPrimitve()) return 0; return -o.compareTo(this); } }; /** Void type. */ public final static Type VOID = new Type(new Identifier("void"), true, true); /** Primitive type for booleans. */ public final static Type BOOLEAN = new Type(new Identifier("boolean"), true, true); /** Primitive type for help literals. */ public final static Type HELP = new Type(new Identifier("Help"), true, true); /** Type indicating that a concrete type has not been resolved for an expression. */ public final static Type UNKNOWN = new Type(new Identifier("UNKNOWN"), true, true); private final static Map<String, Type> primitives = new HashMap<String, Type>(); static { primitives.put(NUM.getName().getId(), NUM); primitives.put(DATE.getName().getId(), DATE); primitives.put(TIMESPAN.getName().getId(), TIMESPAN); primitives.put(CHANNEL.getName().getId(), CHANNEL); primitives.put(USER.getName().getId(), USER); primitives.put(STRING.getName().getId(), STRING); primitives.put(BOOLEAN.getName().getId(), BOOLEAN); primitives.put(HELP.getName().getId(), HELP); primitives.put(VOID.getName().getId(), VOID); primitives.put(UNKNOWN.getName().getId(), UNKNOWN); } /** * Chooses the map type with the most specific signature from the given collection * of types. This requires that all types in the given collection must be unifiable * in the first place. If not, this method will throw an * {@link ASTTraversalException}. This method proceeds to compare the source types of * each map type with the next one in the collection, choosing the one that is more * specific, then going on with then next element and so on. A primitive is more * specific than another, if it extends the other. When comparing products, the * one with the most more specific entries is chosen over the other one. * * @param types Collection of {@link MapType MapTypes} from which the most specific * should be chosen. * @return The considered most specific type within the given collection, or * <code>null</code> if the collection contains a type which is not unifiable * with any of the other types. */ public static MapType getMostSpecific(Collection<MapType> types) { assert !types.isEmpty(); final Iterator<MapType> it = types.iterator(); MapType result = it.next(); while (it.hasNext()) { final MapType next = it.next(); if (!tryUnify(result.getSource(), next.getSource()) && !tryUnify(next.getSource(), result.getSource())) { return null; } result = result.compareTo(next) >= 0 ? result :next; } return result; } /** * Tries to resolve a primitive type with the given name. If no such type exists, * <code>null</code> is returned. * * @param name Name of the type to resolve. * @return The resolved type or <code>null</code> if polymorphic types are not * allowed and not type with given name exists. */ public final static Type resolve(Identifier name) { final Type t = primitives.get(name.getId()); return t; } /** * Checks whether the given type expression contains a type variable. * * @param type The root of the type graph. * @return <code>true</code> if the type expression contains a type variable. */ public final static boolean containsTypeVar(Type type) { return TypeVarFinder.containsTypeVar(type); } /** * Gets an unique name for a new type variable. * * @return Unique type variable name. */ public final static Identifier nextTypeVarName() { return new Identifier(Position.NONE, "T_" + (varIds++)); } private static int varIds = 0; /** * Creates a new {@link TypeVar} with a name different from previous invocations of * this method. * * @return A new {@link TypeVar}. */ public final static TypeVar newTypeVar() { return newTypeVar(nextTypeVarName()); } /** * Creates a {@link TypeVar} with the given name. * * @param name The name of the type variable. * @return A new {@link TypeVar}. */ public final static TypeVar newTypeVar(Identifier name) { return new TypeVar(name); } /** * Creates a {@link TypeVar} with given name. * * @param name The name of the type variable. * @return A new {@link TypeVar}. */ public final static TypeVar newTypeVar(String name) { return newTypeVar(new Identifier(name)); } /** * Tests whether the left type is an instance of the right type using unification. * * @param left The left type expression. * @param right The right type expression. * @return Whether both types are unifiable. */ public final static boolean tryUnify(Type left, Type right) { final boolean subtyping = ParserProperties.should(ParserProperties.ALLOW_SUBTYPING); return getUnifier(subtyping).tryUnify(left, right); } /** * Tests whether both types are superficially unifiable, not taking inheritance of * primitive types into account. * * @param first The left type expression. * @param second The right type expression. * @return Whether both types are unifiable. */ public final static boolean tryUnifyNoInheritance(Type first, Type second) { return getUnifier(false).tryUnify(first, second); } /** * Tests whether the left type is an instance of the right type using unification. If * successful, this method returns a {@link Substitution} for the occurring type * variables in the given type expressions. * * @param left The left type expression. * @param right The right type expression. * @return A {@link Substitution} instance if unification was successful, * <code>null</code> otherwise. */ public final static Substitution unify(Type left, Type right) { final boolean subtyping = ParserProperties.should(ParserProperties.ALLOW_SUBTYPING); return getUnifier(subtyping).unify(left, right); } private final static Unifier getUnifier(boolean subtyping) { return Unifiers.newDefault(subtyping); } private final Identifier name; private final boolean comparable; private final boolean primitve; /** * Creates a new simple type. * * @param name String representation of the type as an {@link Identifier}. * @param comparable Whether literals of this type are comparable. * @param primitive Whether this represents a primitive type. */ Type(Identifier name, boolean comparable, boolean primitive) { this.name = name; this.comparable = comparable; this.primitve = primitive; } /** * Tries to cast this type into a ProductType. This will obviously fail if this is * no product type. This method is used to avoid verbose constructs like * <code>((ProductType) x).something</code>. * @return This type as a ProductType */ public ProductType asProduct() { return (ProductType) this; } /** * Tries to cast this type into a MapType. This will obviously fail if this is * no map type. This method is used to avoid verbose constructs like * <code>((MapType) x).something</code>. * @return This type as a MapType */ public MapType asMap() { return (MapType) this; } /** * Tries to cast this type into a ListType. This will obviously fail if this is * no list type. This method is used to avoid verbose constructs like * <code>((ListType) x).something</code>. * @return This type as a ListType */ public ListType asList() { return (ListType) this; } /** * Creates a new product type which starts with this type followed by the types given * as parameter. * @param others Types to form a product with. * @return A new product type. */ public ProductType productWith(Type...others) { final Type[] types = new Type[others.length + 1]; types[0] = this; System.arraycopy(others, 0, types, 1, others.length); return new ProductType(types); } /** * Creates a mapping type expression from this one to the given target type * expression. * * @param target The target (right side) of the mapping. * @return A new mapping type expression */ public MapType mapTo(Type target) { return new MapType(this, target); } /** * Calculates inheritance compatibility for primitive types. Returns * <code>true</code> if this type is a sub type of the given. The result of this * method is influenced by the parser setting * {@link ParserProperties#ALLOW_SUBTYPING}. * * @param other Type to check. * @return Whether this type is a sub type of the given other one. */ public boolean isA(Type other) { return other == this; } /** * Creates a mapping type from the given type expression to this one. * * @param source The source (left side) of the mapping. * @return A new mapping type expression. */ public MapType mapFrom(Type source) { return new MapType(source, this); } /** * Returns a new {@link ListType} expression with this type as a sub type. * @return A new {@link ListType} expression. */ public ListType listOf() { return new ListType(this); } /** * Applies the given substitution to this type expression. This will create a new * type expression where all type variables are substituted by the rules implemented * in {@link Substitution#getSubstitute(TypeVar)}. Every other type expression * (except primitive types) will be replaced with new instances of the same kind. * * @param s The substitution to apply. * @return A new type expression. */ public Type subst(Substitution s) { return this; } /** * Gets whether this is a primitive type. * * @return Whether this is a primitive type. */ public final boolean isPrimitve() { return this.primitve; } /** * Gets the name of this type. * * @return The type's name. */ public Identifier getName() { return this.name; } /** * Gets whether literals of this type have a nature order. * * @return Whether literals of this type have a nature order. */ public boolean isComparable() { return this.comparable; } @Override public String toString() { return this.getName().getId(); } @Override public boolean visit(TypeVisitor visitor) { return visitor.visit(this); } @Override public int hashCode() { // hashcode by identity is explicitly wanted with types. return super.hashCode(); } @Override public final boolean equals(Object obj) { return EqualsHelper.testEquality(this, obj); } @Override public Class<?> getEquivalenceClass() { return Type.class; } @Override public boolean actualEquals(Equatable o) { // Types are equal if they can be unified. final Type other = (Type) o; return tryUnify(this, other); } @Override public int compareTo(Type o) { return 0; } }