/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * KindExpr.java * Creation date: (January 8, 2001) * By: Bo Ilic */ package org.openquark.cal.compiler; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.openquark.cal.internal.serialization.ModuleSerializationTags; import org.openquark.cal.internal.serialization.RecordInputStream; import org.openquark.cal.internal.serialization.RecordOutputStream; import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo; /** * The internal representation of kind expressions. Each type constructor, type class and type * expression in a CAL program has an associated kind. * * <p> * Kind inference is the process of determining the kind of user-supplied type expressions, and is used * to verify that type expressions are correct. User-supplied type expressions occur * <ol> * <li> in function type declarations and expression type signatures * <li> in algebraic data declarations * <li> in type class definitions * </ol> * * <p> * This is analogous to an expression having an associated type, * with type inference being used to determine that the expression is correct. For example, Double * and Boolean have kind *, while Maybe has kind * -> * and Prelude.Function has kind * -> * -> *. * * <p> * Thus, for example, type expressions such as "Int Double", "Function Char Int Double" will result * in kind errors. However, depending on the context, an type constructor can correctly be used in an * undersaturated form. For example, List, Maybe and "Map k" are instances of the Functor type class. * * <p> * Creation date: (January 8, 2001) * @author Bo Ilic */ abstract class KindExpr { static final KindConstant STAR = new KindConstant(); /** * The array of possible record tags used in calls to {@link RecordInputStream#findRecord(short[])} by * the {@link #load} method. */ private static final short[] KIND_EXPR_RECORD_TAGS = new short[]{ ModuleSerializationTags.KIND_EXPR_KIND_CONSTANT, ModuleSerializationTags.KIND_EXPR_KIND_FUNCTION }; /** * A kind variable. It is uninstantiated when the instance field is null and instantiated * otherwise. An instantiated kind variable behaves like its instantiation. */ static final class KindVar extends KindExpr { private KindExpr instance; KindVar() { instance = null; } KindExpr getInstance() { return instance; } void setInstance(KindExpr instance) { if (this.instance != null) { throw new IllegalStateException("KindExpr.setInstance: Programming Error- can't set the instance of an instantiated kind variable."); } this.instance = instance; } /** * Whether an uninstantiated kind variable occurs in a kind expression. * For example, "k" occurs in "* -> k -> *". We do not allow unifications where * a kind variable is specialized to a kind expression in which the variable * itself occurs since this leads to infinite kinds. */ boolean occursInKindExpr(KindExpr kindExpr) { kindExpr = kindExpr.prune(); if (kindExpr instanceof KindVar) { return this == kindExpr; } else if (kindExpr instanceof KindFunction) { return occursInKindExpr(((KindFunction) kindExpr).getDomain()) || occursInKindExpr(((KindFunction) kindExpr).getCodomain()); } else if (kindExpr instanceof KindConstant) { return false; } throw new IllegalArgumentException("Programming error"); } /** {@inheritDoc} */ @Override int getNArguments() { if (instance != null) { return instance.getNArguments(); } return 0; } /** {@inheritDoc} */ @Override boolean isSimpleKindChain() { if (instance != null) { return instance.isSimpleKindChain(); } return false; } /** {@inheritDoc} */ @Override KindExpr prune() { if (instance != null) { return instance.prune(); } return this; } /** {@inheritDoc} */ @Override boolean containsKindVars() { return true; } @Override String toString(Map<KindVar, Integer> kindVarToIndexMap) { if (instance != null) { return instance.toString(kindVarToIndexMap); } Object indexObject = kindVarToIndexMap.get(this); int index; if (indexObject == null) { index = kindVarToIndexMap.size() + 1; kindVarToIndexMap.put(this, Integer.valueOf(index)); } else { index = ((Integer) indexObject).intValue(); } return indexToVarName(index); } private static String indexToVarName(int index) { if (index > 0 && index <= 26) { return String.valueOf((char) ('a' + index - 1)); } return "a" + String.valueOf(index); } @Override final void write (RecordOutputStream s) throws IOException { throw new IOException("we should not be serializing kind variables- they are transient during type checking."); } } /** * A singleton. KindConstant is the kind *. */ static final class KindConstant extends KindExpr { private static final int serializationSchema = 0; /** should only be used to construct the KindExpr.STAR. */ private KindConstant() { } /** {@inheritDoc} */ @Override int getNArguments() { return 0; } /** {@inheritDoc} */ @Override boolean isSimpleKindChain() { return true; } /** {@inheritDoc} */ @Override KindExpr prune() { return this; } /** {@inheritDoc} */ @Override boolean containsKindVars() { return false; } @Override String toString(Map<KindVar, Integer> kindVarToIndexMap) { return "*"; } @Override final void write (RecordOutputStream s) throws IOException { s.startRecord(ModuleSerializationTags.KIND_EXPR_KIND_CONSTANT, serializationSchema); s.endRecord(); } static final KindConstant load (RecordInputStream s, int schema, ModuleName moduleName, CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "KindExpr.KindConstant", msgLogger); s.skipRestOfRecord(); return KindExpr.STAR; } } /** * KindFunction is the kind k1 -> k2 where k1 and k2 are kinds. */ static final class KindFunction extends KindExpr { private static final int serializationSchema = 0; private KindExpr domain; private KindExpr codomain; KindFunction(KindExpr domain, KindExpr codomain) { if (domain == null || codomain == null) { throw new NullPointerException(); } this.domain = domain; this.codomain = codomain; } KindExpr getDomain() { return domain; } KindExpr getCodomain() { return codomain; } /** {@inheritDoc} */ @Override int getNArguments() { return 1 + codomain.getNArguments(); } /** {@inheritDoc} */ @Override boolean isSimpleKindChain() { return (domain.prune() instanceof KindConstant) && codomain.isSimpleKindChain(); } /** {@inheritDoc} */ @Override KindExpr prune() { return this; } /** {@inheritDoc} */ @Override boolean containsKindVars() { return domain.containsKindVars() || codomain.containsKindVars(); } @Override String toString(Map<KindVar, Integer> kindVarToIndexMap) { StringBuilder buf = new StringBuilder(); String domainString = domain.toString(kindVarToIndexMap); if (domainString.length() == 1) { buf.append(domainString); } else { buf.append("(").append(domainString).append(")"); } buf.append(" -> "); //parentheses around the codomain are not necessary since -> is right associative. buf.append(codomain.toString(kindVarToIndexMap)); return buf.toString(); } @Override final void write (RecordOutputStream s) throws IOException { s.startRecord(ModuleSerializationTags.KIND_EXPR_KIND_FUNCTION, serializationSchema); domain.write(s); codomain.write(s); s.endRecord(); } /** * Load an instance of KindFunction from the RecordInputStream. * Read position will be after the KindFunction record header. * @param s * @param schema * @param moduleName the name of the module being loaded * @param msgLogger the logger to which to log deserialization messages. * @return a KindFunction instance. * @throws IOException */ final static KindFunction load (RecordInputStream s, int schema, ModuleName moduleName, CompilerMessageLogger msgLogger) throws IOException { DeserializationHelper.checkSerializationSchema(schema, serializationSchema, moduleName, "KindExpr.KindFunction", msgLogger); KindExpr ke1 = KindExpr.load(s, moduleName, msgLogger); KindExpr ke2 = KindExpr.load(s, moduleName, msgLogger); s.skipRestOfRecord(); return new KindFunction(ke1, ke2); } } /** * Converts all the kind variables in a kind expression to the default binding *. e.g the * kind expression (k1 -> *) -> k2 is converted to (* -> *) -> *. The returned KindExpr * will have no kind variables in it (instantiated or not). this kindExpr may be pruned, * but will otherwise not be altered. */ KindExpr bindKindVariablesToConstant() { KindExpr kindExpr = this.prune(); if (kindExpr instanceof KindVar) { //will be uninstantiated because of the pruning return STAR; } else if (kindExpr instanceof KindConstant) { return kindExpr; } else if (kindExpr instanceof KindFunction) { if (kindExpr.containsKindVars()) { KindExpr domainKindExpr = ((KindFunction) kindExpr).getDomain().bindKindVariablesToConstant(); KindExpr codomainKindExpr = ((KindFunction) kindExpr).getCodomain().bindKindVariablesToConstant(); return new KindExpr.KindFunction(domainKindExpr, codomainKindExpr); } else { //no kind variables, so don't need to make a copy. return kindExpr; } } throw new IllegalStateException("Programming error. Unrecognized KindExpr subtype."); } /** * A helper function to create kind expressions of the form *->...->*->*. * Creation date: (3/7/01 1:36:36 PM) * @return KindExpr * @param chainLength int the number of *'s in the chain */ static KindExpr makeKindChain(int chainLength) { if (chainLength < 0) { throw new IllegalArgumentException("KindExpr.makeKindChain: negative chainLength."); } KindExpr kindExpr = KindExpr.STAR; while (--chainLength > 0) { kindExpr = new KindExpr.KindFunction(KindExpr.STAR, kindExpr); } return kindExpr; } /** * @return int the number of type arguments taken by the kind. For example, for the kind * * -> * -> * this is 2, for the kind (* -> *) -> (* -> *) -> *, this is also 2. */ abstract int getNArguments(); /** * @return boolean true if this KindExpr is of the form * -> * -> * ... -> *. In other words, * if this is the kind of a TypeConsApp of arity n, then all type variable arguments have kind *. * For example, will return false for (* -> *) -> * -> *. */ abstract boolean isSimpleKindChain(); /** * Eliminate redundant instantiated kind variables at the top of this KindExpr. * The result of prune is always a non-instantiated kind variable, a kind * constant or a kind function. * <p> * This function has no side-effects on the invocation object. * <p> * Creation date: (1/24/01 3:27:07 PM) * @return KindExpr */ abstract KindExpr prune(); /** * @return true if this KindExpr contains a KindVar, whether instantiated or not. */ abstract boolean containsKindVars(); /** * Returns a string representation of this KindExpr, where kind variable addresses are * replaced by lower case letters: a, b, c, ..., z, a1, a2, a3, ... * <p> * Since kinds are an internal implementation technique, this is mainly intended for * debugging purposes, in contrast to the analogous method in the TypeExpr class. * <p> * Creation date: (1/24/01 4:26:11 PM) * @return String */ @Override public String toString() { return toString(new HashMap<KindVar, Integer>()); } /** * Insert the method's description here. * Creation date: (1/24/01 4:24:29 PM) * @return String * @param typeVarToIndexMap */ abstract String toString(Map<KindVar, Integer> typeVarToIndexMap); /** * Write the object to a record output stream. * @param s * @throws IOException */ abstract void write (RecordOutputStream s) throws IOException; /** * Read an instance of one of the concrete derived classes from * the RecordInputStream. * @param s * @param moduleName the name of the module being loaded * @param msgLogger the logger to which to log deserialization messages. * @return and instance of KindExpr. * @throws IOException */ static KindExpr load (RecordInputStream s, ModuleName moduleName, CompilerMessageLogger msgLogger) throws IOException { RecordHeaderInfo rhi = s.findRecord(KIND_EXPR_RECORD_TAGS); switch (rhi.getRecordTag()) { case ModuleSerializationTags.KIND_EXPR_KIND_CONSTANT: return KindConstant.load(s, rhi.getSchema(), moduleName, msgLogger); case ModuleSerializationTags.KIND_EXPR_KIND_FUNCTION: return KindFunction.load(s, rhi.getSchema(), moduleName, msgLogger); default: throw new IOException ("Unexpected record tag while loading KindExpr: " + rhi.getRecordTag()); } } /** * Unify two kind expressions. What this means is to find the most general kind to which both * kindExpr1 and kindExpr2 can be specialized to. Unification fails when trying to a kind constant * to a kind function (such as * and *->*) or when trying to instantiate a kind variable to a term * containing that kind variable (like a and a->b, where a circular structure would be built.) * As a general point of interest, this process is essentially the same as unification in Prolog. * * <p> * An important point is that this function has side-effects on its KindExpr arguments, including * potentially when the unification fails in a TypeException. * * <p> * Creation date: (1/24/01 5:49:01 PM) * @param kindExpr1 * @param kindExpr2 */ static void unifyKind(KindExpr kindExpr1, KindExpr kindExpr2) throws TypeException { kindExpr1 = kindExpr1.prune(); kindExpr2 = kindExpr2.prune(); if (kindExpr1 instanceof KindVar) { KindExpr.KindVar kindVar = (KindVar) kindExpr1; if (kindVar.occursInKindExpr(kindExpr2)) { if (kindVar != kindExpr2) { throw new TypeException("Kind clash: attempt to create an infinite kind"); } } else { kindVar.setInstance(kindExpr2); } } else if (kindExpr1 instanceof KindConstant) { if (kindExpr2 instanceof KindVar) { unifyKind(kindExpr2, kindExpr1); } else if (kindExpr2 instanceof KindFunction) { throw new TypeException("Kinding clash"); } else if (kindExpr2 instanceof KindConstant) { //do nothing if kindExpr2 instanceof KindConstant } else { throw new IllegalStateException(); } } else if (kindExpr1 instanceof KindFunction) { if (kindExpr2 instanceof KindVar || kindExpr2 instanceof KindConstant) { unifyKind(kindExpr2, kindExpr1); } else { //kindExpr2 instanceof KindFunction unifyKind(((KindFunction) kindExpr1).getDomain(), ((KindFunction) kindExpr2).getDomain()); unifyKind(((KindFunction) kindExpr1).getCodomain(), ((KindFunction) kindExpr2).getCodomain()); } } else { throw new IllegalStateException(); } } /** * In certain cases there are multiple options as to how to break up a KindExpr into argument kinds and * return kind. * For example, if KindExpr is (* -> *) -> (* -> * -> *) -> *, then getKindPieces(2) returns * [* -> *, * -> * -> *, *] * and getKindPieces(1) returns [* -> *, (* -> * -> *) -> *] * * @param arity must be less than or equal to this.getNArguments() * @return kind pieces array of length arity + 1. The return kind is the final element of the array. */ final KindExpr[] getKindPieces(int arity) { if (arity < 0 || arity > getNArguments()) { throw new IllegalArgumentException("arity arguments must be non-negative and less than or equal to KindExpr.getNArguments()"); } KindExpr[] kindPieces = new KindExpr[arity + 1]; KindExpr kindExpr = this.prune(); for (int i = 0; i < arity; ++i) { if (kindExpr instanceof KindFunction) { KindFunction kindFunction = (KindFunction)kindExpr; kindPieces[i] = kindFunction.getDomain().prune(); kindExpr = kindFunction.getCodomain().prune(); } else { throw new IllegalStateException(); } } kindPieces[arity] = kindExpr; return kindPieces; } }