package typing; import java.util.Set; import java.util.TreeSet; /** * Represents a type environment, which assigns a type to * every identifier contained within the type environment. * * @author Benedikt Meurer * @version $Id$ */ public final class Environment { /** * Allocates a new type environment with the specified <code>parent</code>, * and the new <code>type</code> for <code>identifier</code>. * * @param parent the parent environment. * @param identifier the identifier for <code>type</code>. * @param type the {@link Type} that should be set for <code>identifier</code>. * * @throws IllegalArgumentException if an invalid value is specified for any * of the parameters. */ public Environment(Environment parent, String identifier, Type type) { // verify that a valid environment is provided if (parent == null) throw new IllegalArgumentException("You must specify a valid parent environment"); // verify that a valid identifier is provided if (identifier == null || identifier.length() == 0) throw new IllegalArgumentException("The empty string is not a valid identifier"); // verify that a valid type is provided if (type == null) throw new IllegalArgumentException("You must specify a valid type"); // initialize attributes this.parent = parent; this.identifier = identifier; this.type = type; } /** * Generates the polymorphic closure for the monomorphic * type <code>tau</code> in the type environment. * * @param tau a mono morphic type. * * @return the polymorphic closure for <code>tau</code>. */ PolyType closure(MonoType tau) { // determine the free type variables for tau Set<String> freeTau = tau.free(); // determine the free type variable for gamma Set<String> freeGamma = free(); // determine the set of free variables in // tau without the ones in gamma TreeSet<String> free = new TreeSet<String>(); for (String name : freeTau) if (!freeGamma.contains(name)) free.add(name); // generate a new polymorphic type return new PolyType(free, tau); } /** * Extends this type environment with an entry for the pair * (<code>identifer</code>,<code>type</code>) and returns the * new {@link Environment}. * * @param identifier the identifier for <code>type</code>. * @param type the {@link Type} that should be set for <code>identifier</code>. * * @return the extended environment. * * @throws IllegalArgumentException if an invalid value is specified for any * of the parameters. */ public Environment extend(String identifier, Type type) { return new Environment(this, identifier, type); } /** * Returns all free type variables in this type environment, * that is the union of all free type variables for the types * within this type environment. * * @return the set of free type variables in this type * environment. */ public Set<String> free() { // check if this is the empty environment if (this == EMPTY_ENVIRONMENT) return Type.EMPTY_SET; // determine the free type variables for the parent Set<String> freeParent = this.parent.free(); // determine the free type variables for this type Set<String> freeType = this.type.free(); if (freeType == Type.EMPTY_SET) return freeParent; // merge the sets TreeSet<String> free = new TreeSet<String>(); free.addAll(freeParent); free.addAll(freeType); return free; } /** * Looks up the type for the <code>identifier</code> in * this type environment. * * @param identifier the name of an identifier * * @return the type for <code>identifier</code>. * * @throws IllegalArgumentException if the <code>identifier</code> is not * a valid name. * @throws UnknownIdentifierException if the <code>identifier</code> is not * present in this type environment. * * @see #identifiers() */ public Type get(String identifier) throws UnknownIdentifierException { // verify that a valid identifier is provided if (identifier == null || identifier.length() == 0) throw new IllegalArgumentException("The empty string is not a valid identifier"); // determine the type for identifier Type type = getInternal(identifier); // check if no type was found if (type == null) throw new UnknownIdentifierException(this, identifier); // return the type return type; } /** * Returns the set of identifiers, which are present in this * type environment. For each of the identifiers, a type is * registered in the type environment. * * @return the set of identifiers in the type environment. * * @see #get(String) */ public Set<String> identifiers() { // determine the set of identifiers in the environment TreeSet<String> identifiers = new TreeSet<String>(); for (Environment env = this; env.parent != null; env = env.parent) identifiers.add(env.identifier); return identifiers; } /** * Applies the <code>substitution</code> to all types in * the environment and returns the resulting environment. * * The <code>typeVariableAllocator</code> can be used to * allocate new type variables required for renaming type * variables when substituting below a polymorphic type. * * @param substitution the {@link Substitution}. * @param typeVariableAllocator a {@link TypeVariableAllocator}, which can * be used to allocate new type variables. * * @return the resulting {@link Environment}. */ Environment substitute(Substitution substitution, TypeVariableAllocator typeVariableAllocator) { // nothing to substitute in the empty environment if (this == EMPTY_ENVIRONMENT) return this; // apply the substitution to the parent Environment parent = this.parent.substitute(substitution, typeVariableAllocator); // check if we have a polymorphic type Type type = this.type; if (type instanceof PolyType) { PolyType polyType = (PolyType)type; // determine a substitution for the bound rename Substitution renamer = Substitution.EMPTY_SUBSTITUTION; for (String name : polyType.getQuantifiedVariables()) { // check if the substitution contains the name as free type variable if (substitution.containsFreeTypeVariable(name)) { renamer = new Substitution(name, typeVariableAllocator.allocateTypeVariable(), renamer); } } // apply the renamer to avoid collision type = type.substitute(renamer); } // apply the substitution to the type type = type.substitute(substitution); // check anything changed if (parent != this.parent || type != this.type) return new Environment(parent, this.identifier, type); else return this; } /** * Returns <code>true</code> if <code>obj</code> is an * instance of {@link Environment}, the set of identifiers * in this environment and <code>obj</code> is the same and * for each identifier the types are equal. * * @param obj another environment object. * * @return <code>true</code> if <code>obj</code> is an * environment and the types are the same. * * @see #get(String) * @see #identifiers() * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (obj instanceof Environment) { Environment env = (Environment)obj; // determine the sets of identifiers and compare them Set<String> identifiers = identifiers(); if (!identifiers.equals(env.identifiers())) return false; // verify that the types for all identifiers are the same for (String identifier : identifiers) { if (!getInternal(identifier).equals(env.getInternal(identifier))) return false; } // the environments are the same return true; } else { return false; } } /** * Returns the string representation for the type environment, * which contains the type for each identifier in the environment. * * @return the string representation for the type environment. * * @see #get(String) * @see #identifiers() * @see java.lang.Object#toString() */ @Override public String toString() { // construct the string for the type environment StringBuilder builder = new StringBuilder(128); builder.append('['); // add all identifiers for the environment for (Environment env = this; env != EMPTY_ENVIRONMENT; env = env.parent) { if (env != this) builder.append(", "); builder.append(env.identifier); builder.append(" : "); builder.append(env.type); } // finalize the string builder.append(']'); return builder.toString(); } /** * The empty type environment, shared by all non-empty type environment * as absolute parent. */ public static final Environment EMPTY_ENVIRONMENT = new Environment(); private Environment() { } private Type getInternal(String identifier) { if (this == EMPTY_ENVIRONMENT) return null; else if (this.identifier.equals(identifier)) return this.type; else return this.parent.getInternal(identifier); } // member attributes private Environment parent; private String identifier; private Type type; }