// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz) // All rights reserved. // // This software may be modified and distributed under the terms // of the BSD license. See the LICENSE file for details. package wyil.util.type; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Comparator; import java.util.HashMap; import wyautl_old.lang.*; import wybs.lang.NameID; import wyil.lang.Type; import wyil.util.TypeSystem; /** * Contains various important algorithms for manipulating types. These were * split out from wyil.lang.Type, as that class is already quite large. The most * important algorithms contained in this class are: * <ul> * <li> * <p> * <b>Type Intersection</b>. Here, the intersection of two types is calculated. * This is necessary for determining the updated type after a runtime type test. * Essentially, we intersect the original type with the tested type to produce * something new. * </p> * </li> * <li> * <p> * <b>Type Simplification</b>. Here, a number of simplification rules are * provided which reduce non-sensical forms into something sensible. For * example, <code>int|int</code> is a non-sensical form, and should be * simplified to just <code>int</code>. * </p> * </li> * </ul> * <b>NOTE:</b> the algorithms contained in this file are ugly and difficult to * follow. Do not touch them unless you absolutely know what you're doing! * * @author David J. Pearce * */ public final class TypeAlgorithms { /** * The data comparator is used in the type canonicalisation process. It is * used to compare the supplementary data of states in automata * representing types. Supplementary data is used for record kinds and */ public static final Comparator<Automaton.State> DATA_COMPARATOR = new Comparator<Automaton.State>() { @Override public int compare(Automaton.State s1, Automaton.State s2) { // PRE-CONDITION s1.kind == s2.kind if(s1.kind == TypeSystem.K_RECORD) { TypeSystem.RecordState fields1 = (TypeSystem.RecordState) s1.data; TypeSystem.RecordState fields2 = (TypeSystem.RecordState) s2.data; int fields1_size = fields1.size(); int fields2_size = fields2.size(); if (fields1_size < fields2_size || (!fields1.isOpen && fields2.isOpen)) { return -1; } else if (fields1_size > fields2_size || (fields1.isOpen && !fields2.isOpen)) { return 1; } // ASSERT: fields1_size == fields2_size for(int i=0;i!=fields1_size;++i) { String str1 = fields1.get(i); String str2 = fields2.get(i); int c = str1.compareTo(str2); if(c != 0) { return c; } } return 0; } else if(s1.kind == TypeSystem.K_NOMINAL){ NameID nid1 = (NameID) s1.data; NameID nid2 = (NameID) s2.data; return nid1.toString().compareTo(nid2.toString()); } else if(s1.kind == TypeSystem.K_ARRAY){ Boolean nid1 = (Boolean) s1.data; Boolean nid2 = (Boolean) s2.data; return nid1.toString().compareTo(nid2.toString()); } else if(s1.kind == TypeSystem.K_FUNCTION || s1.kind == TypeSystem.K_METHOD) { TypeSystem.FunctionOrMethodState s1Data = (TypeSystem.FunctionOrMethodState) s1.data; TypeSystem.FunctionOrMethodState s2Data = (TypeSystem.FunctionOrMethodState) s2.data; return s1Data.compareTo(s2Data); } else { String str1 = (String) s1.data; String str2 = (String) s2.data; return str1.compareTo(str2); } } }; /** * <p> * Contractive types are types which cannot accept value because they have * an <i>unterminated cycle</i>. An unterminated cycle has no leaf nodes * terminating it. For example, <code>X<{X field}></code> is contractive, * where as <code>X<{null|X field}></code> is not. * </p> * * <p> * This method returns true if the type is contractive, or contains a * contractive subcomponent. For example, <code>null|X<{X field}></code> is * considered contracted. * </p> * * @param type --- type to test for contractivity. * @return */ public static boolean isContractive(Automaton automaton) { BitSet contractives = new BitSet(automaton.size()); // TODO: optimise away the need to initialise the contractives contractives.set(0,automaton.size(),true); // initially all nodes are considered contracive. return findContractives(automaton,contractives); } private static boolean findContractives(Automaton automaton, BitSet contractives) { boolean changed = true; boolean contractive = false; while(changed) { changed=false; contractive = false; for(int i=0;i!=automaton.size();++i) { boolean oldVal = contractives.get(i); boolean newVal = isContractive(i,contractives,automaton); if(oldVal && !newVal) { contractives.set(i,newVal); changed = true; } contractive |= newVal; } } return contractive; } private static boolean isContractive(int index, BitSet contractives, Automaton automaton) { Automaton.State state = automaton.states[index]; int[] children = state.children; if(children.length == 0) { return false; } switch(state.kind) { case TypeSystem.K_ARRAY: case TypeSystem.K_FUNCTION: case TypeSystem.K_METHOD: return false; } if(state.deterministic) { for(int child : children) { if(child == index || contractives.get(child)) { return true; } } return false; } else { boolean r = true; for(int child : children) { if(child == index) { return true; } r &= contractives.get(child); } return r; } } /** * <p> * This simplification rule removes spurious components by repeated * application of various rewrite rules. The following provides * representatives of the main rules considered. * </p> * <ul> * <li><code>{void f}</code> => <code>void</code>.</li> * <li><code>T | void</code> => <code>T</code>.</li> * <li><code>T | any</code> => <code>any</code>.</li> * <li><code>X<T | X></code> => <code>T</code>.</li> * <li><code>!!T</code> => <code>T</code>.</li> * <li><code>!any</code> => <code>void</code>.</li> * <li><code>!void</code> => <code>any</code>.</li> * <li><code>!(T_1 | T_2)</code> => <code>!T_1 & !T_2</code>.</li> * <li><code>(T_1 | T_2) | T_3</code> => <code>(T_1 | T_2 | T_3)</code>.</li> * <li><code>T_1 | T_2</code> where <code>T_1 :> T_2</code> => * <code>T_1</code>. * <code>void</code>. * </ul> * <p> * <b>NOTE:</b> applications of this rewrite rule may leave states which are * unreachable from the root. Therefore, the resulting automaton should be * extracted after rewriting to eliminate such states. * </p> * */ /** * Type inhabitation labels used by the simplification algorithm. */ private static enum Inhabitation { /** * Indicates that the simplification algorithm has not yet labeled a state * with a value. */ UNLABELED, /** * Type is uninhabited. Equivalent to void. */ NONE, /** * May have some values inhabiting the type. Since we don't precisely * track inhabitation, a type that has this label does not necessarily * have any values, but we may be unable to prove it is uninhabited so we * may choose to assume that it is inhabited to be safe. */ SOME, /** * Type is inhabited by all values. Equivalent to any. */ ALL } /** * Get the inhabitation of a state during simplification. Some states have a * fixed, known inhabitation. Others are either UNLABELED or SOME, depending * on the value of the corresponding bit in the set of flags. * * @param index * --- index of state being worked on. * @param automaton * --- automaton containing state being worked on. * @param inhabitationFlags * --- flags tracking inhabitation for some of the states * @return The inhabitation of the state. */ private static Inhabitation getStateInhabitation(int index, Automaton automaton, BitSet inhabitationFlags) { Automaton.State state = automaton.states[index]; switch (state.kind) { // Some states have known, fixed inhabitation. case TypeSystem.K_VOID: return Inhabitation.NONE; case TypeSystem.K_ANY: return Inhabitation.ALL; case TypeSystem.K_NULL: case TypeSystem.K_BOOL: case TypeSystem.K_BYTE: case TypeSystem.K_INT: case TypeSystem.K_FUNCTION: case TypeSystem.K_NOMINAL: case TypeSystem.K_META: return Inhabitation.SOME; // Other states have their inhabitation labels stored as a flag. case TypeSystem.K_NEGATION: case TypeSystem.K_UNION : case TypeSystem.K_ARRAY: case TypeSystem.K_REFERENCE: case TypeSystem.K_RECORD: case TypeSystem.K_METHOD: if (inhabitationFlags.get(index)) { return Inhabitation.SOME; } else { return Inhabitation.UNLABELED; } default: throw new IllegalArgumentException("Unknown kind: " + state.kind); } } /** * Set the inhabitation of a state during simplification. This method might * do several things: * * 1. If the inhabitation is set to its existing value, then nothing happens * and false is returned. * 2. If the inhabitation is set to NONE or ALL then the state's kind will be * changed to K_VOID or K_ANY and true is returned. * 3. If the inhabitation is set to SOME or UNLABELED, and the state's kind * supports it, then a flag will be set or clared in the inhabitationFlags * parameter. True will be returned. * 4. If none of these actions are possible then a state is being set to * an unsupported inhabitation label (e.g. K_INT set to K_UNLABELED) and * an IllegalArgumentExceptionw will be thrown. * * @param index * --- index of state being worked on. * @param automaton * --- automaton containing state being worked on. * @param inhabitationFlags * --- flags tracking inhabitation for some of the states * @param newValue * --- the new inhabitation value to set the state to * @return Whether or not the value was changed. */ private static boolean setStateInhabitation(int index, Automaton automaton, BitSet inhabitationFlags, Inhabitation newValue) { Inhabitation existingValue = getStateInhabitation(index, automaton, inhabitationFlags); if (newValue == existingValue) { return false; } if (newValue == Inhabitation.NONE) { automaton.states[index] = new Automaton.State(TypeSystem.K_VOID); } else if (newValue == Inhabitation.ALL) { automaton.states[index] = new Automaton.State(TypeSystem.K_ANY); } else if (newValue == Inhabitation.SOME || newValue == Inhabitation.UNLABELED) { int existingKind = automaton.states[index].kind; switch (existingKind) { case TypeSystem.K_NEGATION: case TypeSystem.K_UNION : case TypeSystem.K_ARRAY: case TypeSystem.K_REFERENCE: case TypeSystem.K_RECORD: case TypeSystem.K_FUNCTION: case TypeSystem.K_METHOD: inhabitationFlags.set(index, newValue == Inhabitation.SOME); break; default: throw new IllegalArgumentException("Cannot label state with kind " + existingKind + " with inhabitation label " + newValue); } } else { throw new IllegalArgumentException("Unknown inhabitation label " + newValue); } return true; } /** * Analyse an automaton and try to produce a version that is simpler but * still equivalent. * * @param automaton * --- automaton to simplify. */ public static void simplify(Automaton automaton) { // Use a BitSet to track the inhabitation label of some of the states in // the automaton. A bit is available for each state but it is ignored for // states whose kind has an implicit inhabitation label, e.g. K_VOID has // a label of NONE, K_ANY has a label of ALL, K_INT has a label of SOME, // etc, but K_RECORD needs a bit to track whether it is UNLABELED or has // a label of SOME. // // A clear bit indicates a state is UNLABELED, a set bit indicates it // has SOME inhabitants. This bit has no meaning for states that already // have implicit inhabitation information. BitSet lastInhabitationFlags = null; BitSet inhabitationFlags = null; while (true) { // Swap inhabitation flags. { BitSet temp = inhabitationFlags; lastInhabitationFlags = inhabitationFlags; lastInhabitationFlags = temp; if (inhabitationFlags == null) { inhabitationFlags = new BitSet(automaton.size()); } else { inhabitationFlags.clear(); } } // Don't assume any prior knowledge about inhabitation. Previous simplifications // may have assumed states had SOME inhabitants but these could be NONE after // further simplification, i.e. because !!SOME => SOME using our rules, but this // is only because we're imprecise in propagating inhabitation through negation. inhabitationFlags.clear(); // Perform an initial simplification on the automaton. boolean simplified = simplifyInner(automaton, inhabitationFlags); // Break if not simplified or inhabitaitonFlags non-null and equal if (!simplified && lastInhabitationFlags != null && lastInhabitationFlags.equals(inhabitationFlags)) { // No simplification and inhabitation flags unchanged: breaking out of simplification loop break; } // Found simplification and/or inhabitation flags changes: continuing with simplification loop // Now that simplification has occurred once, all possibly inhabited states should // have a label. Anything unlabeled can be considered uninhabited. boolean labelingChanged = markUnlabeledAsUninhabited(automaton, inhabitationFlags); if (!labelingChanged && lastInhabitationFlags != null) { // No uninhabited labeling changes: breaking out of simplification loop break; } // Found labeling changes: continuing with simplification loop } } /** * Simplifies each state in an automaton until a fixpoint is reached. * * @param automaton * --- automaton being simplified. * @param inhabitationFlags * --- flags tracking inhabitation for some of the states */ private static boolean simplifyInner(Automaton automaton, BitSet inhabitationFlags) { // Flag indicating whether any simplifications have been performed by this // method. boolean anySimplificationPerformed = false; // Flag indicating whether or not to loop again. This will be false when // no simplifications are made to any state. boolean loopAgain = true; while (loopAgain) { loopAgain = false; // Try to simplify each state and track whether or not any simplifications // have occurred. for(int i=0;i!=automaton.size();++i) { boolean stateSimplified = simplifyState(i,automaton, inhabitationFlags); loopAgain |= stateSimplified; anySimplificationPerformed |= stateSimplified; } } return anySimplificationPerformed; } /** * This method is called after an initial simplification pass. Any states * that have an UNLABELED inhabitation must not be inhabited, so can be set * to NONE. * * @param automaton * --- automaton being simplified. * @param inhabitationFlags * --- flags tracking inhabitation for some of the states * @return True if the labels were changed by this method, false otherwise. */ private static boolean markUnlabeledAsUninhabited(Automaton automaton, BitSet inhabitationFlags) { boolean labelingChanged = false; for(int i=0;i!=automaton.size();++i) { if (getStateInhabitation(i, automaton, inhabitationFlags) == Inhabitation.UNLABELED) { // Mark unlabeled state as uninhabited setStateInhabitation(i, automaton, inhabitationFlags, Inhabitation.NONE); labelingChanged = true; } } return labelingChanged; } // FIXME: This method is not called from anywhere. private static boolean simplifyContractives(Automaton automaton) { BitSet contractives = new BitSet(automaton.size()); // initially all nodes are considered contractive. // TODO: optimise away the need to initialise the contractives contractives.set(0,automaton.size(),true); boolean changed = findContractives(automaton, contractives); for (int i = contractives.nextSetBit(0); i >= 0; i = contractives .nextSetBit(i + 1)) { automaton.states[i] = new Automaton.State(TypeSystem.K_VOID); } return changed; } /** * Attempts to simplify a single state in automaton. * * @param index * --- the index of the state being worked on. * @param automaton * --- automaton being simplified. * @param inhabitationFlags * --- flags tracking inhabitation for some of the states * @return True if this method modified the state, false if the state is * still the same. */ private static boolean simplifyState(int index, Automaton automaton, BitSet inhabitationFlags) { Automaton.State state = automaton.states[index]; switch (state.kind) { case TypeSystem.K_VOID: case TypeSystem.K_ANY: case TypeSystem.K_NULL: case TypeSystem.K_BOOL: case TypeSystem.K_BYTE: case TypeSystem.K_INT: case TypeSystem.K_NOMINAL: case TypeSystem.K_FUNCTION: case TypeSystem.K_META: return false; case TypeSystem.K_NEGATION: return simplifyNegation(index, state, automaton, inhabitationFlags); case TypeSystem.K_UNION : return simplifyUnion(index, state, automaton, inhabitationFlags); case TypeSystem.K_REFERENCE: return simplifyReference(index, state, automaton, inhabitationFlags); case TypeSystem.K_ARRAY: // void[] // All non-empty lists and sets contain the empty list or set return setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.SOME); // list/set type of form [T+] so fall through to simplify like other compounds. case TypeSystem.K_RECORD: case TypeSystem.K_METHOD: return simplifyCompound(index, state, automaton, inhabitationFlags); default: throw new IllegalArgumentException("Can't simplify state with kind: " + state.kind); } } private static boolean simplifyNegation(int index, Automaton.State state, Automaton automaton, BitSet inhabitationFlags) { // Rewrite !!X => X Automaton.State child = automaton.states[state.children[0]]; if(child.kind == TypeSystem.K_NEGATION) { // bypass node int childchildIndex = child.children[0]; Inhabitation childchildInhabitation = getStateInhabitation(childchildIndex, automaton, inhabitationFlags); Automaton.State childchild = automaton.states[childchildIndex]; // Replace double negation of X with just X automaton.states[index] = new Automaton.State(childchild); setStateInhabitation(index, automaton, inhabitationFlags, childchildInhabitation); return true; } // Set inhabitation label based on child's label Inhabitation childInhabitation = getStateInhabitation(state.children[0], automaton, inhabitationFlags); Inhabitation inhabitation; if (childInhabitation == Inhabitation.ALL) { inhabitation = Inhabitation.NONE; // Negation of ALL is NONE } else if (childInhabitation == Inhabitation.NONE) { inhabitation = Inhabitation.ALL; // Negation of ALL is SOME } else { // Approximate negation of SOME is SOME, leave UNLABELED if child is UNLABELED inhabitation = childInhabitation; } return setStateInhabitation(index, automaton, inhabitationFlags, inhabitation); } private static boolean simplifyReference(int index, Automaton.State state, Automaton automaton, BitSet inhabitationFlags) { // Look at child inhabitation to work out reference inhabitation Inhabitation childInhabitation = getStateInhabitation(state.children[0], automaton, inhabitationFlags); if (childInhabitation == Inhabitation.ALL || childInhabitation == Inhabitation.SOME) { // Use SOME for the reference even if the child is ALL. This is necessary to prevent // the constant Reference.T_REF_ANY from be normalised directly into any, losing its // reference-ness. We could possibly use ALL if we added a way to construct a reference // without normalising it completely. return setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.SOME); } else if (childInhabitation == Inhabitation.NONE) { return setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.NONE); } else { return false; } } private static boolean simplifyCompound(int index, Automaton.State state, Automaton automaton, BitSet inhabitationFlags) { int kind = state.kind; int[] children = state.children; // Skip some children if the compound is a function int numChildrenToCheck = children.length; if (state.kind == TypeSystem.K_FUNCTION) { // Only check function parameters for now // TODO: Work out how to handle function return types properly numChildrenToCheck = (Integer) state.data; } // Scan compound's children boolean allChildrenInhabited = true; for(int i=0;i<numChildrenToCheck;++i) { Automaton.State child = automaton.states[children[i]]; Inhabitation childInhabitation = getStateInhabitation(children[i], automaton, inhabitationFlags); if (childInhabitation == Inhabitation.NONE) { return setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.NONE); } else if (childInhabitation == Inhabitation.UNLABELED) { // If a child is labeled then we do not know if all the children are inhabited allChildrenInhabited = false; } // else ALL or SOME: leave allChildrenInhabited as it is } // Consider compound inhabited if all children are inhabited if (allChildrenInhabited) { return setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.SOME); } return false; } /** * This method is responsible for the following rewrite rules: * <ul> * <li><code>T | void</code> => <code>T</code>.</li> * <li><code>T | any</code> => <code>any</code>.</li> * <li><code>X<T | X></code> => <code>T</code>.</li> * <li><code>(T_1 | T_2) | T_3</code> => <code>(T_1 | T_2 | T_3)</code>.</li> * <li><code>T_1 | T_2</code> where <code>T_1 :> T_2</code> => <code>T_1</code>. * </ul> * * @param index * --- index of state being worked on. * @param state * --- state being worked on. * @param automaton * --- automaton containing state being worked on. * @return */ private static boolean simplifyUnion(int index, Automaton.State state, Automaton automaton, BitSet inhabitationFlags) { return simplifyUnion_1(index, state, automaton, inhabitationFlags) || simplifyUnion_2(index, state, automaton); } /** * This method applies the following rewrite rules: * <ul> * <li><code>T | void</code> => <code>T</code>.</li> * <li><code>T | any</code> => <code>any</code>.</li> * <li><code>X<T | X></code> => <code>T</code>.</li> * <li><code>(T_1 | T_2) | T_3</code> => <code>(T_1 | T_2 | T_3)</code>.</li> * </ul> * * @param index * --- index of state being worked on. * @param state * --- state being worked on. * @param automaton * --- automaton containing state being worked on. * @return */ private static boolean simplifyUnion_1(int index, Automaton.State state, Automaton automaton, BitSet inhabitationFlags) { int[] children = state.children; boolean changed = false; // Scan over all children, flattening nested unions, removing uninhabited // children, checking if all children are inhabited, etc. boolean anyChildrenInhabited = false; for (int i = 0; i < children.length; ++i) { int iChild = children[i]; if (iChild == index) { // Contractive case: union contains itself. We can remove this child. state.children = removeIndex(i, children); changed = true; } else { Automaton.State child = automaton.states[iChild]; // If a child of a union is a union, flatten them together. if (child.kind == TypeSystem.K_UNION) { // TODO: Optimise by inserting in-place and continuing loop? flattenChildren(index, state, automaton); return true; } // Now check inhabitation of children switch (getStateInhabitation(iChild, automaton, inhabitationFlags)) { case ALL: // If a child is ALL then we can replace the union with ALL return setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.ALL); case NONE: // If a child is NONE then it can be removed from the union children = removeIndex(i, children); state.children = children; changed = true; continue; case SOME: // If a child is SOME then that means the union can be SOME anyChildrenInhabited = true; // Unlabeled children are ignored } } } // Perform more simplifications now that all children have been examined if (children.length == 0) { // Union with no children is void. This can happen in the case of a // union which has only itself as a child. automaton.states[index] = new Automaton.State(TypeSystem.K_VOID); changed = true; } else if (children.length == 1) { // Union with only 1 child is equivalent to that child: bypass the union // altogether. int child = children[0]; automaton.states[index] = new Automaton.State(automaton.states[child]); setStateInhabitation(index, automaton, inhabitationFlags, getStateInhabitation(child, automaton, inhabitationFlags)); changed = true; } else if (anyChildrenInhabited) { // Union with at least one SOME child is SOME itself. changed |= setStateInhabitation(index, automaton, inhabitationFlags, Inhabitation.SOME); } return changed; } /** * This method applies the following rewrite rules: * <ul> * <li><code>T_1 | T_2</code> where <code>T_1 :> T_2</code> => <code>T_1</code>. * </ul> * * @param index * --- index of state being worked on. * @param state * --- state being worked on. * @param automaton * --- automaton containing state being worked on. * @return */ private static boolean simplifyUnion_2(int index, Automaton.State state, Automaton automaton) { boolean changed = false; int[] children = state.children; for (int i = 0; i < children.length; ++i) { int iChild = children[i]; // check whether this child is subsumed boolean subsumed = false; for (int j = 0; j < children.length; ++j) { int jChild = children[j]; if (i != j && isSubtype(jChild, iChild, automaton) && (!isSubtype(iChild, jChild, automaton) || i > j)) { // Found a child that's a subtype of another child; it can be // subsumed into that other child. subsumed = true; } } if (subsumed) { // Remove the subsumed child. children = removeIndex(i--, children); state.children = children; changed = true; } } if (children.length == 1) { // bypass this node altogether int child = children[0]; automaton.states[index] = new Automaton.State(automaton.states[child]); changed = true; } return changed; } private static boolean isSubtype(int fromIndex, int toIndex, Automaton automaton) { SubtypeOperator op = new SubtypeOperator(automaton,automaton,LifetimeRelation.EMPTY); return op.isSubtype(fromIndex, toIndex); } private final static class IntersectionPoint { public final int fromIndex; public final boolean fromSign; public final int toIndex; public final boolean toSign; public IntersectionPoint(int fromIndex, boolean fromSign, int toIndex, boolean toSign) { this.fromIndex = fromIndex; this.fromSign = fromSign; this.toIndex = toIndex; this.toSign = toSign; } @Override public boolean equals(Object o) { if(o instanceof IntersectionPoint) { IntersectionPoint ip = (IntersectionPoint) o; return fromIndex == ip.fromIndex && fromSign == ip.fromSign && toIndex == ip.toIndex && toSign == ip.toSign; } return false; } @Override public String toString() { return "(" + (fromSign ? '+' : '-') + fromIndex + "&" + (toSign ? '+' : '-') + fromIndex + ")"; } @Override public int hashCode() { return fromIndex + toIndex; } } private static Automaton intersect(boolean fromSign, Automaton from, boolean toSign, Automaton to) { HashMap<IntersectionPoint,Integer> allocations = new HashMap(); ArrayList<Automaton.State> nstates = new ArrayList(); intersect(0,fromSign,from,0,toSign,to,allocations,nstates); return new Automaton(nstates.toArray(new Automaton.State[nstates.size()])); } private static int intersect(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { // first, check whether we have determined this state already IntersectionPoint ip = new IntersectionPoint(fromIndex,fromSign,toIndex,toSign); Integer allocation = allocations.get(ip); if(allocation != null) { return allocation;} // looks like we haven't, so proceed to determine the new state. int myIndex = states.size(); allocations.put(ip,myIndex); Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; // now, dispatch for the appropriate case. if(fromState.kind == toState.kind) { return intersectSameKind(fromIndex, fromSign, from, toIndex, toSign, to, allocations, states); } else { return intersectDifferentKind(fromIndex, fromSign, from, toIndex, toSign, to, allocations, states); } } /** * The following method intersects two nodes which have different kinds. A * precondition is that space has already been allocated in states for the * resulting node. * * @param fromIndex * --- index of state in from position * @param fromSign * --- index of state in from position * @param from * --- automaton in the from position (i.e. containing state at * fromIndex). * @param toIndex * --- index of state in to position * @param toSign * --- index of state in to position * @param to * --- automaton in the to position (i.e. containing state at * toIndex). * @param allocations * --- mapping of intersection points to their index in states * @param states * --- list of states which constitute the new automaton being * constructed/ * @return */ private static int intersectDifferentKind(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // allocate space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; Automaton.State myState = null; // using invert helps reduce the number of cases to consider. int fromKind = invert(fromState.kind,fromSign); int toKind = invert(toState.kind,toSign); // TODO: tidy this mess up if(fromKind == TypeSystem.K_VOID || toKind == TypeSystem.K_VOID) { myState = new Automaton.State(TypeSystem.K_VOID); } else if(fromKind == TypeSystem.K_UNION) { // (T1 | T2) & T3 => (T1&T3) | (T2&T3) int[] fromChildren = fromState.children; int[] myChildren = new int[fromChildren.length]; for(int i=0;i!=fromChildren.length;++i) { int fromChild = fromChildren[i]; myChildren[i] = intersect(fromChild,fromSign,from,toIndex,toSign,to,allocations,states); } myState = new Automaton.State(TypeSystem.K_UNION,false,myChildren); } else if(toKind == TypeSystem.K_UNION) { int[] toChildren = toState.children; int[] myChildren = new int[toChildren.length]; for(int i=0;i!=toChildren.length;++i) { int toChild = toChildren[i]; myChildren[i] = intersect(fromIndex, fromSign, from, toChild, toSign, to, allocations, states); } myState = new Automaton.State(TypeSystem.K_UNION,false,myChildren); } else if (fromKind == TypeSystem.K_INTERSECTION) { // !(T1 | T2) & T3 => (!T1&T3) & (!T2&T3) // => !(!(!T1&T3)|!(!T2&T3)) int[] fromChildren = fromState.children; int[] myChildren = new int[fromChildren.length]; for (int i = 0; i != fromChildren.length; ++i) { int fromChild = fromChildren[i]; int tmpChild = intersect(fromChild, fromSign, from, toIndex, toSign, to, allocations, states); myChildren[i] = states.size(); states.add(new Automaton.State(TypeSystem.K_NEGATION, true, tmpChild)); } states.add(new Automaton.State(TypeSystem.K_UNION, false, myChildren)); myState = new Automaton.State(TypeSystem.K_NEGATION, true, states.size() - 1); } else if (toKind == TypeSystem.K_INTERSECTION) { int[] toChildren = toState.children; int[] myChildren = new int[toChildren.length]; for (int i = 0; i != toChildren.length; ++i) { int toChild = toChildren[i]; int tmpChild = intersect(fromIndex, fromSign, from, toChild, toSign, to, allocations, states); myChildren[i] = states.size(); states.add(new Automaton.State(TypeSystem.K_NEGATION, true, tmpChild)); } states.add(new Automaton.State(TypeSystem.K_UNION, false, myChildren)); myState = new Automaton.State(TypeSystem.K_NEGATION, true, states.size() - 1); } else if(fromKind == TypeSystem.K_NEGATION) { states.remove(states.size()-1); int fromChild = fromState.children[0]; return intersect(fromChild,!fromSign,from,toIndex,toSign,to,allocations,states); } else if(toKind == TypeSystem.K_NEGATION) { states.remove(states.size()-1); int toChild = toState.children[0]; return intersect(fromIndex,fromSign,from,toChild,!toSign,to,allocations,states); } else if(fromKind == TypeSystem.K_ANY) { states.remove(states.size()-1); if(!toSign) { states.add(new Automaton.State(TypeSystem.K_NEGATION,states.size()+1)); } Automata.extractOnto(toIndex,to,states); return myIndex; } else if(toKind == TypeSystem.K_ANY) { states.remove(states.size()-1); if(!fromSign) { states.add(new Automaton.State(TypeSystem.K_NEGATION,states.size()+1)); } Automata.extractOnto(fromIndex,from,states); return myIndex; } else if(fromSign && toSign) { myState = new Automaton.State(TypeSystem.K_VOID); } else if(fromSign) { states.remove(states.size()-1); Automata.extractOnto(fromIndex,from,states); return myIndex; } else if(toSign) { states.remove(states.size()-1); Automata.extractOnto(toIndex,to,states); return myIndex; } else { int childIndex = states.size(); states.add(null); int nFromChild = states.size(); Automata.extractOnto(fromIndex,from,states); int nToChild = states.size(); Automata.extractOnto(toIndex,to,states); states.set(childIndex,new Automaton.State(TypeSystem.K_UNION, nFromChild, nToChild)); myState = new Automaton.State(TypeSystem.K_NEGATION, childIndex); } states.set(myIndex, myState); return myIndex; } /** * The following method intersects two nodes which have identical kind. * * @param fromIndex * --- index of state in from position * @param fromSign * --- sign of state in from position * @param from * --- automaton in the from position (i.e. containing state at * fromIndex). * @param toIndex * --- index of state in to position * @param toSign * --- sign of state in to position * @param to * --- automaton in the to position (i.e. containing state at * toIndex). * @param allocations * --- mapping of intersection points to their index in states * @param states * --- list of states which constitute the new automaton being * constructed/ * @return */ private static int intersectSameKind(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; switch(fromState.kind) { case TypeSystem.K_VOID: return intersectVoid(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); case TypeSystem.K_ANY: return intersectVoid(fromIndex,!fromSign,from,toIndex,!toSign,to,allocations,states); case TypeSystem.K_RECORD: return intersectRecords(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); case TypeSystem.K_NOMINAL: return intersectNominals(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); case TypeSystem.K_ARRAY: return intersectArrays(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); case TypeSystem.K_REFERENCE: return intersectCompounds(fromIndex,fromSign,from,toIndex,toSign,to,fromState.data,allocations,states); case TypeSystem.K_NEGATION: return intersectNegations(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); case TypeSystem.K_UNION: return intersectUnions(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); case TypeSystem.K_FUNCTION: case TypeSystem.K_METHOD: return intersectFunctionsOrMethods(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); default: { return intersectPrimitives(fromIndex,fromSign,from,toIndex,toSign,to,allocations,states); } } } // ================================================================================== // Primitives // ================================================================================== private static int intersectVoid(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State myState; if(!fromSign && !toSign) { myState = new Automaton.State(TypeSystem.K_ANY); } else { // void & void => void // void & !void => void myState = new Automaton.State(TypeSystem.K_VOID); } states.add(myState); return states.size()-1; } private static int intersectPrimitives(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State myState; if(fromSign && toSign) { // e.g. INT & INT => INT myState = new Automaton.State(fromState.kind); } else if(fromSign || toSign) { // e.g. !INT & INT => INT myState = new Automaton.State(TypeSystem.K_VOID); } else { // e.g. !INT & !INT => !INT int myIndex = states.size(); states.add(new Automaton.State(TypeSystem.K_NEGATION,myIndex+1)); states.add(new Automaton.State(fromState.kind)); return myIndex; } states.add(myState); return states.size()-1; } // ================================================================================== // Negations // ================================================================================== private static int intersectNegations(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; int fromChild = fromState.children[0]; int toChild = toState.children[0]; return intersect(fromChild,!fromSign,from,toChild,!toSign,to,allocations,states); } // ================================================================================== // Nominals // ================================================================================== private static int intersectNominals(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; if(!fromState.data.equals(toState.data)) { int myIndex = states.size(); if(fromSign && toSign) { states.add(new Automaton.State(TypeSystem.K_VOID)); return myIndex; } else if(fromSign || toSign) { Automata.extractOnto(fromIndex,from,states); return myIndex; } } return intersectCompounds(fromIndex, fromSign, from, toIndex, toSign, to, fromState.data, allocations, states); } // ================================================================================== // Unions // ================================================================================== private static int intersectUnions(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states){ if (!fromSign && !toSign) { return intersectUnionsNegNeg(fromIndex, from, toIndex, to,allocations,states); } else { return intersectUnionsPosNeg(fromIndex, fromSign, from, toIndex, toSign, to, allocations,states); } } private static int intersectUnionsPosNeg(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { // (T1|T2) & !(T3|T4) => (T1&!(T3|T4)) | (T2&!(T3|T4)) Automaton.State fromState = from.states[fromIndex]; int myIndex = states.size(); states.add(null); // reserve space for me int[] fromChildren = fromState.children; int[] newChildren = new int[fromChildren.length]; for (int i = 0; i != fromChildren.length; ++i) { int fromChild = fromChildren[i]; newChildren[i] = intersect(fromChild, fromSign, from, toIndex, toSign, to, allocations, states); } Automaton.State myState = new Automaton.State(TypeSystem.K_UNION, false, newChildren); states.set(myIndex,myState); return myIndex; } private static int intersectUnionsNegNeg(int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states){ // !(T1|T2) & !(T3|T4) => !T1 & !T2 & !T3 & !T4 => !(T1|T2|T3|T4) int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[fromIndex]; int[] fromChildren = fromState.children; int[] toChildren = toState.children; int[] newChildren = new int[fromChildren.length+toChildren.length]; int childIndex = states.size(); states.add(new Automaton.State(TypeSystem.K_UNION, false, newChildren)); for (int i = 0; i != fromChildren.length; ++i) { int fromChild = fromChildren[i]; newChildren[i] = states.size(); Automata.extractOnto(fromChild,from,states); } for (int i = 0; i != toChildren.length; ++i) { int toChild = toChildren[i]; newChildren[i+fromChildren.length] = states.size(); Automata.extractOnto(toChild,to,states); } Automaton.State myState = new Automaton.State(TypeSystem.K_NEGATION, true, childIndex); states.set(myIndex,myState); return myIndex; } // ================================================================================== // Sets and Lists // ================================================================================== private static int intersectArrays(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; // pos-pos // [T1] & [T2] => [T1&T2] // [T1+] & [T2] => [T1&T2+] // [T1] & [T2+] => [T1&T2+] // [T1+] & [T2+] => [T1&T2+] // pos-neg // [T1] & ![T2] => [T1&!T2+] // [T1+] & ![T2] => [T1&!T2+] // [T1] & ![T2+] => [T1&!T2] // [T1+] & ![T2+] => [T1&!T2+] boolean fromNonEmpty = (Boolean) fromState.data; boolean toNonEmpty = (Boolean) toState.data; Object myData = null; if(fromSign && toSign) { myData = fromNonEmpty | toNonEmpty; } else if(fromSign) { myData = fromNonEmpty | !toNonEmpty; } else if(toSign) { myData = !fromNonEmpty | toNonEmpty; } return intersectCompounds(fromIndex,fromSign,from,toIndex,toSign,to,myData,allocations,states); } // ================================================================================== // Dictionaries, Processes, Records (closed), Sets and Lists // ================================================================================== private static int intersectCompounds(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, Object myData, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { if (fromSign == toSign) { if (fromSign) { return intersectCompoundsPosPos(fromIndex, from, toIndex, to, myData,allocations, states); } else { return intersectCompoundsNegNeg(fromIndex, from, toIndex, to, allocations, states); } } else if (fromSign) { return intersectCompoundsPosNeg(fromIndex, from, toIndex, to, myData,allocations, states); } else { return intersectCompoundsPosNeg(toIndex, to, fromIndex, from, myData,allocations, states); } } private static int intersectCompoundsPosPos(int fromIndex, Automaton from, int toIndex, Automaton to,Object myData, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; int[] myChildren = contiguousZipIntersection(fromState.children, from, toState.children, to, allocations, states); Automaton.State myState = new Automaton.State(fromState.kind, myData, true, myChildren); states.set(myIndex,myState); return myIndex; } private static int intersectCompoundsPosNeg(int fromIndex, Automaton from, int toIndex, Automaton to,Object myData, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; int[] myChildren = contiguousDistributeIntersection(fromState, true, from, toState, false, to, myData, allocations, states); Automaton.State myState = new Automaton.State(TypeSystem.K_UNION, null, false, myChildren); states.set(myIndex,myState); return myIndex; } private static int intersectCompoundsNegNeg(int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // reserve space for me // e.g. ![int] & ![real] => !([int]|[real]) int fromChild = states.size(); Automata.extractOnto(fromIndex,from,states); int toChild = states.size(); Automata.extractOnto(toIndex,to,states); states.add(new Automaton.State(TypeSystem.K_UNION,false,fromChild,toChild)); Automaton.State myState = new Automaton.State(TypeSystem.K_NEGATION,states.size()-1); states.set(myIndex,myState); return myIndex; } // ================================================================================== // Functions and Methods // ================================================================================== private static int intersectFunctionsOrMethods(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; if(fromState.children.length != toState.children.length) { if(fromSign && toSign) { int myIndex = states.size(); states.add(new Automaton.State(TypeSystem.K_VOID)); return myIndex; } else if(fromSign || toSign) { int myIndex = states.size(); Automata.extractOnto(fromIndex,from,states); return myIndex; } } // it's fair to say that something doesn't feel right about this? if(fromSign == toSign) { if(fromSign) { return intersectFunctionOrMethodsHelper(fromIndex,from,toIndex,toSign,to,allocations,states); } else { return intersectCompounds(fromIndex,fromSign,from,toIndex,toSign,to,null,allocations,states); } } else if(fromSign) { return intersectFunctionOrMethodsHelper(fromIndex,from,toIndex,false,to,allocations,states); } else { return intersectFunctionOrMethodsHelper(toIndex,to,fromIndex,false,from,allocations,states); } } private static int intersectFunctionOrMethodsHelper(int fromIndex, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { // int(int) & any(real) => int(int|real) int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; int[] fromChildren = fromState.children; int[] toChildren = toState.children; int[] myChildren = new int[fromChildren.length]; // return value is co-variant (i.e. normal, like e.g. list elements) myChildren[0] = intersect(fromChildren[0], true, from, toChildren[0], toSign, to, allocations, states); // throws clause is co-variant (i.e. normal, like e.g. list elements) myChildren[1] = intersect(fromChildren[1], true, from, toChildren[1], toSign, to, allocations, states); if(toSign) { // parameter values are harder, since they are contra-variant. for(int i=2;i<fromChildren.length;++i) { int fromChild = fromChildren[i]; int toChild = toChildren[i]; int[] childChildren = new int[2]; myChildren[i] = states.size(); states.add(new Automaton.State(TypeSystem.K_UNION,null,false,childChildren)); childChildren[0] = states.size(); Automata.extractOnto(fromChild,from,states); childChildren[1] = states.size(); Automata.extractOnto(toChild,to,states); } } else { // parameter values are harder, since they are contra-variant. for(int i=2;i<fromChildren.length;++i) { int fromChild = fromChildren[i]; int toChild = toChildren[i]; int[] childChildren = new int[2]; myChildren[i] = states.size(); states.add(new Automaton.State(TypeSystem.K_UNION,null,false,childChildren)); childChildren[0] = states.size(); Automata.extractOnto(fromChild,from,states); childChildren[1] = states.size(); states.add(new Automaton.State(TypeSystem.K_NEGATION,null,false,states.size()+1)); Automata.extractOnto(toChild,to,states); } } Automaton.State myState = new Automaton.State(fromState.kind, fromState.data, true, myChildren); states.set(myIndex,myState); return myIndex; } // ================================================================================== // Records // ================================================================================== private static int intersectRecords(int fromIndex, boolean fromSign, Automaton from, int toIndex, boolean toSign, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { if(fromSign == toSign) { if(fromSign) { return intersectRecordsPosPos(fromIndex,from,toIndex,to,allocations,states); } else { return intersectCompoundsNegNeg(fromIndex,from,toIndex,to,allocations,states); } } else if(fromSign) { return intersectRecordsPosNeg(fromIndex,from,toIndex,to,allocations,states); } else { return intersectRecordsPosNeg(toIndex,to,fromIndex,from,allocations,states); } } private static int intersectRecordsPosPos(int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; TypeSystem.RecordState fromData = (TypeSystem.RecordState) fromState.data; TypeSystem.RecordState toData = (TypeSystem.RecordState) toState.data; TypeSystem.RecordState myData; int myIndex = states.size(); states.add(null); // reserve space int[] myChildren; if(fromData.isOpen == toData.isOpen) { if(fromData.isOpen) { // open open myData = new TypeSystem.RecordState(true); setUnion(fromData,toData,myData); myChildren = nonContiguousZipIntersection(myData,fromState,from,toState,to,allocations,states); } else { // closed closed if(!fromData.equals(toData)) { // e.g. {int f,...} & {int g} states.set(myIndex,new Automaton.State(TypeSystem.K_VOID)); return myIndex; } else { myData = fromData; myChildren = contiguousZipIntersection(fromState.children, from, toState.children, to, allocations, states); } } } else if(fromData.isOpen) { // open closed if(!isSubset(fromData,toData)) { // e.g. {int f,...} & {int g} states.set(myIndex,new Automaton.State(TypeSystem.K_VOID)); return myIndex; } else { myData = toData; myChildren = nonContiguousZipIntersection(toData,fromState,from,toState,to,allocations,states); } } else { // assert toData.isOpen if(!isSubset(toData,fromData)) { // e.g. {int f} & {int g,...} states.set(myIndex,new Automaton.State(TypeSystem.K_VOID)); return myIndex; } else { myData = fromData; myChildren = nonContiguousZipIntersection(fromData,fromState,from,toState,to,allocations,states); } } Automaton.State myState = new Automaton.State(fromState.kind, myData, true, myChildren); states.set(myIndex,myState); return myIndex; } private static int intersectRecordsPosNeg(int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; TypeSystem.RecordState fromData = (TypeSystem.RecordState) fromState.data; TypeSystem.RecordState toData = (TypeSystem.RecordState) toState.data; if (fromData.isOpen && toData.isOpen) { return intersectPosNegOpenOpen(fromIndex,from,toIndex,to,allocations,states); } else if(toData.isOpen) { return intersectPosNegClosedOpen(fromIndex,from,toIndex,to,allocations,states); } else if(fromData.isOpen) { return intersectNegPosClosedOpen(toIndex,to,fromIndex,from,allocations,states); } else { // fall through to general case for compounds if (!fromData.equals(toData)) { // e.g. {int f} & !{int g} => {int f} int myIndex = states.size(); Automata.extractOnto(fromIndex,from,states); return myIndex; } else { return intersectCompoundsPosNeg(fromIndex,from,toIndex,to,fromState.data,allocations,states); } } } /** * Intersect two automaton representing open records, where the first is * positive and the second negative. For example: * * <pre> * {T1 f, T2 g, ...} & !{T3 g, T4 h, ...} => {T1 f, T2&!T3 g, ...} | {T1 f, T2 g, void h, ...} * </pre> * * This is a fairly ticky case! * * @param fromIndex * --- index of state in from position * @param from * --- automaton in the from position (i.e. containing state at * fromIndex). * @param toIndex * --- index of state in to position * @param to * --- automaton in the to position (i.e. containing state at * toIndex). * @param allocations * --- mapping of intersection points to their index in states * @param states * --- list of states which constitute the new automaton being * constructed. * @return */ private static int intersectPosNegOpenOpen(int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; TypeSystem.RecordState fromData = (TypeSystem.RecordState) fromState.data; TypeSystem.RecordState toData = (TypeSystem.RecordState) toState.data; // if(isSubset(toData,fromData)) { // // no possible intersection so back out // states.set(myIndex,new Automaton.State(TypeSystem.K_VOID)); // return myIndex; // tad ugly perhaps // } // finally, distribute over those fields present in the open record // (which are a subset of those in the closed record). int[] myChildren = nonContiguousDistributeIntersection(fromState, true, from, toState, false, to, fromData, allocations, states); Automaton.State myState = new Automaton.State(TypeSystem.K_UNION, null, false, myChildren); states.set(myIndex,myState); return myIndex; } /** * Intersect two automaton representing records, where the first is positive * and closed and the second negative and open. For example: * * <pre> * {T1 f, T2 g} & !{T3 g, T4 h, ...} => {T1 f, T2 g} * {T1 f, T2 g} & !{T3 f, T4 g, ...} => {T1 & !T3 f, T2 g} | {T1 f, T2 & !T4 g} * {T1 f, T2 g, T3 h} & !{T4 f, T5 g, ...} => {T1 & !T4 f, T2 g, T3 h} | {T1 f, T2 & !T5 g, T3 h} * </pre> * * In this case, the result must include exactly those fields in the closed * record. If the open record contains a field not present * in the closed record then it does not intersect with the closed record * and we're fine. Finally, in the case they have exactly the same fields * then we have to distribute. * * @param fromIndex * --- index of state in from position * @param from * --- automaton in the from position (i.e. containing state at * fromIndex). * @param toIndex * --- index of state in to position * @param to * --- automaton in the to position (i.e. containing state at * toIndex). * @param allocations * --- mapping of intersection points to their index in states * @param states * --- list of states which constitute the new automaton being * constructed. * @return */ private static int intersectPosNegClosedOpen( int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; TypeSystem.RecordState fromData = (TypeSystem.RecordState) fromState.data; TypeSystem.RecordState toData = (TypeSystem.RecordState) toState.data; // check whether the open record contains a field not present in // the closed record. If so, then there is no intersection between them // and we can just return the closed record. if(!isSubset(toData,fromData)) { // no possible intersection so back out myIndex = states.size(); Automata.extractOnto(toIndex,to,states); return myIndex; // tad ugly perhaps } // finally, distribute over those fields present in the open record // (which are a subset of those in the closed record). int[] myChildren = nonContiguousDistributeIntersection(fromState, true, from, toState, false, to, fromData, allocations, states); Automaton.State myState = new Automaton.State(TypeSystem.K_UNION, null, false, myChildren); states.set(myIndex,myState); return myIndex; } /** * Intersect two automaton representing records, where the first is negative * and closed and the second positive and open. For example: * * <pre> * !{int f} & {int f,...} => {int f, ...+} * {T1 f, T2 g, ...} & !{T3 g, T4 h} => {T1 f, T2 g} * {T1 f, T2 g, ...} & !{T3 f, T4 g} => {T1&!T3 f, T2 g,...} | {T1 f, T2&!T4 g,...} * {T1 f, T2 g, ...} & !{T3 f, T4 g, T5 h} => {T1&!T3 f, T2 g, ...} | * {T1 f, T2&!T4 g, ...} | {T1 f, T2 g, void h, ...} | * {T1 f, T2 g, !T5 h, ...} * </pre> * * Here, <code>{T1 f, T2 g, void h, ...}</code> represents a record which * doesn't have a field h. This use of void only really makes sense in the * context of open records. * * @param fromIndex * --- index of state in from position * @param from * --- automaton in the from position (i.e. containing state at * fromIndex). * @param toIndex * --- index of state in to position * @param to * --- automaton in the to position (i.e. containing state at * toIndex). * @param allocations * --- mapping of intersection points to their index in states * @param states * --- list of states which constitute the new automaton being * constructed. * @return */ private static int intersectNegPosClosedOpen( int fromIndex, Automaton from, int toIndex, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int myIndex = states.size(); states.add(null); // reserve space for me Automaton.State fromState = from.states[fromIndex]; Automaton.State toState = to.states[toIndex]; TypeSystem.RecordState fromData = (TypeSystem.RecordState) fromState.data; TypeSystem.RecordState toData = (TypeSystem.RecordState) toState.data; // check whether the open record contains a field not present in // the closed record. If so, then there is no intersection between them // and we can just return the closed record. if(!isSubset(toData,fromData)) { // no possible intersection so back out myIndex = states.size(); Automata.extractOnto(toIndex,to,states); return myIndex; // tad ugly perhaps } // finally, distribute over those fields present in the open record // (which are a subset of those in the closed record). TypeSystem.RecordState myData = new TypeSystem.RecordState(false,fromData); int[] myChildren = nonContiguousDistributeIntersection(fromState, false, from, toState, true, to, myData, allocations, states); myChildren = Arrays.copyOf(myChildren, myChildren.length+1); myChildren[myChildren.length-1] = states.size(); Automata.extractOnto(toIndex,to,states); // FIXME: at this point, the extract type must be made larger Automaton.State myState = new Automaton.State(TypeSystem.K_UNION, null, false, myChildren); states.set(myIndex,myState); return myIndex; } /** * Check whether one sorted lists is a subset or another. * * @param subset --- list to check as subset * @param superset --- list to check as superset * @return */ private static boolean isSubset(ArrayList<String> subset, ArrayList<String> superset) { int subsetSize = subset.size(); int supersetSize = superset.size(); if(subsetSize > supersetSize) { return false; } int fi=0,ti=0; while(fi < subsetSize && ti < supersetSize) { String fn = subset.get(fi); String tn = superset.get(ti); int c = fn.compareTo(tn); if(c > 0) { ++ti; continue; } else if(c == 0){ ++ti; ++fi; continue; } return false; } return fi == subsetSize; } /** * Compute the union of two sorted ArrayLists. The resulting list is sorted, * and does not contain any duplicate labels. * * @param lhs * @param rhs * @return */ private static void setUnion(ArrayList<String> lhs, ArrayList<String> rhs, ArrayList<String> result) { int li = 0; int ri = 0; int lhsSize = lhs.size(); int rhsSize = rhs.size(); while(li < lhsSize && ri < rhsSize) { String ln = lhs.get(li); String rn = rhs.get(ri); int c = ln.compareTo(rn); if(c < 0) { result.add(ln); li++; } else if(c == 0) { result.add(ln); li++; ri++; } else { result.add(rn); ri++; } } while(li < lhsSize) { result.add(lhs.get(li++)); } while(ri < rhsSize) { result.add(rhs.get(ri++)); } } /** * Intersect the <code>fromChildren</code> with the <code>toChildren</code> * in a one-one fashion. For example, * <code>[T1,T2] & [T3,T4] => [T1&T3,T2&T4]</code>. * * @param fromChildren * @param from * @param toChildren * @param to * @param allocations * @param states * @return */ private static int[] contiguousZipIntersection(int[] fromChildren, Automaton from, int[] toChildren, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int fromChildrenLength = fromChildren.length; int[] myChildren = new int[fromChildrenLength]; for(int i=0;i!=fromChildrenLength;++i) { int fromChild = fromChildren[i]; int toChild = toChildren[i]; myChildren[i] = intersect(fromChild, true, from, toChild, true, to, allocations, states); } return myChildren; } /** * Similar to a zip intersection, except that the element labels are * non-contiguous. For example: * * <pre> * [T1 f,T2 h] & [T3 g,T4 h] => [T1 f,T3 g,T2&T4 h] * </pre> * * @param fromChildren * @param from * @param toChildren * @param to * @param allocations * @param states * @return */ private static int[] nonContiguousZipIntersection( ArrayList<String> myLabels, Automaton.State fromState, Automaton from, Automaton.State toState, Automaton to, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { ArrayList<String> fromLabels = (ArrayList) fromState.data; ArrayList<String> toLabels = (ArrayList) toState.data; int[] fromChildren = fromState.children; int[] toChildren = toState.children; int[] myChildren = new int[myLabels.size()]; int fi = 0, ti = 0, mi = 0; while (fi < fromLabels.size() && ti < toLabels.size()) { int fromChild = fromChildren[fi]; int toChild = toChildren[ti]; String fn = fromLabels.get(fi); String tn = toLabels.get(ti); int c = fn.compareTo(tn); if (c == 0) { c = intersect(fromChild, true, from, toChild,true, to, allocations, states); fi++;ti++; } else if (c < 0) { c = states.size(); Automata.extractOnto(fromChild,from,states); fi++; } else { c = states.size(); Automata.extractOnto(toChild,to,states); ti++; } myChildren[mi++] = c; } while (fi < fromLabels.size()) { int fromChild = fromChildren[fi++]; myChildren[mi++] = states.size(); Automata.extractOnto(fromChild,from,states); } while (ti < toLabels.size()) { int toChild = toChildren[ti++]; myChildren[mi++] = states.size(); Automata.extractOnto(toChild,to,states); } return myChildren; } /** * Distribute the intersections of <code>fromState.Children</code> over * <code>toState.Children</code>. For example, * <code>[T1,T2] & [T3,T4] => [T1&T3,T2] | [T1,T2&T4]</code>. * * @param fromChildren * @param from * @param toChildren * @param to * @param allocations * @param states * @return */ private static int[] contiguousDistributeIntersection(Automaton.State fromState, boolean fromSign, Automaton from, Automaton.State toState, boolean toSign, Automaton to, Object myData, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { int[] fromChildren = fromState.children; int[] toChildren = toState.children; // First, allocate every fromChildren since each one will be used in one // of the generated components. int fromChildrenLength = fromChildren.length; int[] tmpChildren = extractChildren(fromChildren,from,states); // Second, generate the new components (one for each element in // fromChildren). int[] myChildren = new int[fromChildrenLength]; for(int i=0;i!=fromChildrenLength;++i) { int[] myChildChildren = new int[fromChildren.length]; System.arraycopy(tmpChildren, 0, myChildChildren, 0, fromChildrenLength); myChildChildren[i] = intersect(fromChildren[i], fromSign, from, toChildren[i], toSign, to, allocations, states); myChildren[i] = states.size(); states.add(new Automaton.State(fromState.kind, myData, true, myChildChildren)); } return myChildren; } /** * Similar to a contiguous distribute intersection, except that the element * labels are non-contiguous. For example: * * <pre> * [T1 f, T2 g] & [T3 g] => [void f,T2 g]|[T1 f,T2&T3 g] * [T1 f, T2 g] & [T3 g, T4 h] => [void f,T2 g]|[T1 f,T2&T3 g] * </pre> * * <b>NOTE</b> current precondition is that toState.data is a subset of * fromState.data * * @param fromChildren * @param from * @param toChildren * @param to * @param allocations * @param states * @return */ private static int[] nonContiguousDistributeIntersection(Automaton.State fromState, boolean fromSign, Automaton from, Automaton.State toState, boolean toSign, Automaton to, Object myData, HashMap<IntersectionPoint, Integer> allocations, ArrayList<Automaton.State> states) { ArrayList<String> fromLabels = (ArrayList) fromState.data; ArrayList<String> toLabels = (ArrayList) toState.data; int[] fromChildren = fromState.children; int[] toChildren = toState.children; int[] tmpChildren = extractChildren(fromChildren,from,states); // Second, generate the new components (one for each element in // fromChildren). int[] myChildren = new int[fromChildren.length]; for(int fi=0,ti=0;fi!=fromChildren.length;++fi,++ti) { int[] myChildChildren = new int[fromChildren.length]; System.arraycopy(tmpChildren, 0, myChildChildren, 0, fromChildren.length); if(ti < toLabels.size()) { String fn = fromLabels.get(fi); String tn = toLabels.get(ti); int c = fn.compareTo(tn); if(c == 0) { myChildChildren[fi] = intersect(fromChildren[fi], fromSign, from, toChildren[fi], toSign, to, allocations, states); ti++; } else if(c < 0) { myChildChildren[fi] = states.size(); states.add(new Automaton.State(TypeSystem.K_VOID)); } // precondition protects against other case } else { myChildChildren[fi] = states.size(); states.add(new Automaton.State(TypeSystem.K_VOID)); } myChildren[fi] = states.size(); states.add(new Automaton.State(fromState.kind, myData, true, myChildChildren)); } return myChildren; } /** * Extract a given array of states onto the given list of states, returning * an array of indices to the copied states. * * @param children * --- states to be extracted * @param automaton * --- automata from which to extract states. * @param states * --- heap onto which to place the new states. * @return */ private static int[] extractChildren(int[] children, Automaton automaton, ArrayList<Automaton.State> states) { int childrenLength = children.length; int[] nchildren = new int[childrenLength]; for(int i=0;i!=childrenLength;++i) { nchildren[i] = states.size(); Automata.extractOnto(children[i],automaton,states); } return nchildren; } private static int invert(int kind, boolean sign) { if(sign) { return kind; } switch(kind) { case TypeSystem.K_ANY: return TypeSystem.K_VOID; case TypeSystem.K_VOID: return TypeSystem.K_ANY; case TypeSystem.K_UNION: return TypeSystem.K_INTERSECTION; default: return kind; } } private static int[] removeIndex(int index, int[] children) { int[] nchildren = new int[children.length - 1]; for (int j = 0; j < children.length; ++j) { if (j < index) { nchildren[j] = children[j]; } else if (j > index) { nchildren[j - 1] = children[j]; } } return nchildren; } /** * This rule flattens children which have the same kind as the given state. * * @param index * @param state * @param automaton * @return */ private static boolean flattenChildren(int index, Automaton.State state, Automaton automaton) { ArrayList<Integer> nchildren = new ArrayList<Integer>(); int[] children = state.children; final int kind = state.kind; for (int i = 0; i < children.length; ++i) { int iChild = children[i]; Automaton.State child = automaton.states[iChild]; if (child.kind == kind) { for (int c : child.children) { nchildren.add(c); } } else { nchildren.add(iChild); } } children = new int[nchildren.size()]; for (int i = 0; i < children.length; ++i) { children[i] = nchildren.get(i); } automaton.states[index] = new Automaton.State(kind, false, children); return true; } }