/*
* 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.
*/
/*
* AutoburnLogic.java
* Creation date: Dec 20, 2002
* By: Ken Wong
*/
package org.openquark.gems.client;
import java.util.ArrayList;
import java.util.List;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeChecker.TypeCheckInfo;
import org.openquark.cal.services.GemEntity;
/**
* A class that holds all of the autoburn logic that the tabletop uses.
* This was refactored out from the original version in the TableTop.
* @author Ken Wong
* Creation Date: Dec 20th 2002
*/
public class AutoburnLogic {
/** The maximum burn depth which will be considered.
* If there are n possible pieces which can be considered for burning, a burn combination will be considered if
* the number of burns is (<= MAX_BURN_DEPTH) or (>= (n - MAX_BURN_DEPTH)) */
public static final int MAX_BURN_DEPTH = 2;
/**
* Autoburn action enum pattern.
* Creation date: (12/04/01 12:00:43 PM)
* @author Edward Lam
*/
static final class AutoburnAction {
static final AutoburnAction NOTHING = new AutoburnAction(); // last autoburn: nothing happened
static final AutoburnAction BURNED = new AutoburnAction(); // last autoburn: burned
static final AutoburnAction MULTIPLE = new AutoburnAction();// last autoburn: nothing happened because ambiguous
static final AutoburnAction IMPOSSIBLE = new AutoburnAction();// last autoburn: nothing happened because not possible
static final AutoburnAction UNBURNED = new AutoburnAction();// last autoburn: unburned
/**
* Constructor for an autoburn action
* Creation date: (03/07/2002 6:09:00 PM)
*/
private AutoburnAction() {
}
}
/**
* Burn status enum pattern.
* Creation date: (04/01/2002 11:56:43 AM)
* @author Edward Lam
*/
static final class BurnStatus {
private final String typeString;
private BurnStatus(String s) {
typeString = s;
}
@Override
public String toString() {
return typeString;
}
/** Not burnt. */
public static final BurnStatus NOT_BURNT = new BurnStatus("NOT_BURNT");
/** Automatically burnt. */
public static final BurnStatus AUTOMATICALLY_BURNT = new BurnStatus("AUTOMATICALLY_BURNT");
/** Manually burnt. */
public static final BurnStatus MANUALLY_BURNT = new BurnStatus("MANUALLY_BURNT");
}
/**
* Enum pattern used to summarize how a gem can be connected to another gem, allowing for the
* possibility of autoburning.
* @author Edward Lam
*/
public static final class AutoburnUnifyStatus {
private final String typeString;
private AutoburnUnifyStatus(String s) {
typeString = s;
}
@Override
public String toString() {
return typeString;
}
/**
* Returns true if this unify status indicates that two gems are always connectable.
* Always connectable means that the autoburn logic will be able to connect the gems without
* requiring manual user intervention. This is true when autoburning is either unambiguous or
* not necessary and also in the case where burning is ambiguous but the connection can be made
* without burning. In that last case the burn logic would simply make the connection without burning.
* @return true if gems are connectable automatically, false otherwise
*/
public boolean isAutoConnectable() {
return (this == UNAMBIGUOUS ||
this == UNAMBIGUOUS_NOT_NECESSARY ||
this == AMBIGUOUS_NOT_NECESSARY ||
this == NOT_NECESSARY);
}
/**
* Returns true if this unify status indicates that the possible autoburn combinations are unambiguous.
* This means there is only one possible combination that can be burned and therefore autoburning is
* possible without requiring user interaction.
* @return true if unambiguous autoburning is possible, false otherwise
*/
public boolean isUnambiguous() {
return (this == UNAMBIGUOUS ||
this == UNAMBIGUOUS_NOT_NECESSARY);
}
/**
* Returns true if this unify status indicates that two gems are connectable without burning.
* @return true if gems can be connected without burning, false otherwise
*/
public boolean isConnectableWithoutBurning() {
return (this != AMBIGUOUS &&
this != UNAMBIGUOUS &&
this != NOT_POSSIBLE);
}
/**
* Returns true if this unify status indicates that two gems are connectable using burning.
* @return true if gems can be connected using burning, false otherwise
*/
public boolean isConnectableWithBurning() {
return (this != NOT_NECESSARY &&
this != NOT_POSSIBLE);
}
/** Unifies without burning and there are is no way to unify with burning. */
public static final AutoburnUnifyStatus NOT_NECESSARY = new AutoburnUnifyStatus("NOT_NECESSARY");
/** Unification cannot be enabled by burning and is not possible without burning. */
public static final AutoburnUnifyStatus NOT_POSSIBLE = new AutoburnUnifyStatus("NOT_POSSIBLE");
/** It's ambiguous which inputs to burn to unify and it is impossible to unify without burning. */
public static final AutoburnUnifyStatus AMBIGUOUS = new AutoburnUnifyStatus("AMBIGUOUS");
/** It's unambiguous which inputs to burn to unify and it is impossible to unify without burning. */
public static final AutoburnUnifyStatus UNAMBIGUOUS = new AutoburnUnifyStatus("UNAMBIGUOUS");
/** It's ambiguous which inputs to burn to unify and you can also unify without burning. */
public static final AutoburnUnifyStatus AMBIGUOUS_NOT_NECESSARY = new AutoburnUnifyStatus("AMBIGUOUS_NOT_NECESSARY");
/** It's umambiguous which inputs to burn to unify and you can also unify without burning. */
public static final AutoburnUnifyStatus UNAMBIGUOUS_NOT_NECESSARY = new AutoburnUnifyStatus("UNAMBIGUOUS_NOT_NECESSARY");
}
/**
* A helper class to hold information about a single burn combination.
* This holds information about which inputs to burn as well as the type closeness for this burning combination.
* Note that the arguments are represented as integer indices relative to the arguments of the gem and not the gem tree.
* @author Richard Webster
*/
public static class BurnCombination {
/** An array of indexes of the arguments to burn. */
private final int[] inputsToBurn;
/** The type closeness associated with this burning combination. */
private final int burnTypeCloseness;
/**
* BurnCombination constructor.
*/
BurnCombination(int[] inputsToBurn, int burnTypeCloseness) {
this.inputsToBurn = inputsToBurn;
this.burnTypeCloseness = burnTypeCloseness;
}
/**
* Returns the type closeness of this burning combination.
*/
public int getBurnTypeCloseness() {
return burnTypeCloseness;
}
/**
* Returns the argument indexes to burn.
* Each int is the index of an arg to burn with respect to the gem.
*/
public int[] getInputsToBurn() {
return inputsToBurn;
}
}
/**
* A helper class to hold the status of a query as to whether a particular connection between gems can be made,
* including the possibility of various sorts of burning.
* @author Bo Ilic
*/
public static class AutoburnInfo {
private final transient AutoburnUnifyStatus status;
/**
* Each element of this list is a BurnCombinations which contains information about
* which arguments should be burnt and the type closeness.
*/
private final List<BurnCombination> burnCombinations;
/**
* every possible configuration of burnings (e.g. for a gem with 2 arguments, can burn none of the arguments,
* the first argument only, the second argument only, or both arguments) has an associated type closeness for its
* unification with the destination type. This is the maximum value and represents the "best possible" connection.
*/
private final int maxTypeCloseness;
/**
* the type closeness when no burning is attempted.
*/
private final int noBurnTypeCloseness;
private AutoburnInfo (AutoburnUnifyStatus status, List<BurnCombination> burnCombinations, int maxTypeCloseness, int noBurnTypeCloseness) {
if (status == null) {
throw new NullPointerException();
}
if (status == AutoburnLogic.AutoburnUnifyStatus.NOT_POSSIBLE) {
if (burnCombinations != null || maxTypeCloseness != -1) {
throw new IllegalArgumentException();
}
} else if (status == AutoburnLogic.AutoburnUnifyStatus.NOT_NECESSARY) {
if (burnCombinations.size() != 0) {
throw new IllegalArgumentException();
}
} else {
if (burnCombinations.size() < 1) {
throw new IllegalArgumentException();
}
}
//todoBI unfortunately, the case where *no* arguments are burnt is not represented as the empty burn combination.
//i.e. the possibilities for burning a 2 argument gem could be:
//[[], [0], [1], [0,1]] but instead are listed as [[0], [1], [0,1]]
//if this were so however, the status value could be calculated from burnCombinations.
//As it is, we can't distinguish e.g. [[], [0]] from [[0]].
this.status = status;
this.burnCombinations = burnCombinations;
this.maxTypeCloseness = maxTypeCloseness;
this.noBurnTypeCloseness = noBurnTypeCloseness;
}
static private AutoburnInfo makeNoUnificationPossibleAutoburnInfo() {
return new AutoburnInfo(AutoburnUnifyStatus.NOT_POSSIBLE, null, -1, -1);
}
/**
* @return a summary of the type of unifications possible (including the possibility of burning).
*/
public AutoburnUnifyStatus getAutoburnUnifyStatus() {
return status;
}
/**
* @return each element of this list is a BurnCombination which indicate which arguments should be burnt.
*/
public List<BurnCombination> getBurnCombinations() {
return burnCombinations;
}
/**
* @return every possible configuration of burnings (e.g. for a gem with 2 arguments, can burn none of the arguments,
* the first argument only, the second argument only, or both arguments) has an associated type closeness for its
* unification with the destination type. This is the maximum value and represents the "best possible" connection.
*/
public int getMaxTypeCloseness() {
return maxTypeCloseness;
}
/**
* @return the type closeness for the case where no burning occurs. This is used by clients to see if the no burning
* situation is the most "natural" and we shouldn't bother presenting autoburn possibilities.
*/
public int getNoBurnTypeCloseness() {
return noBurnTypeCloseness;
}
}
/**
* Return whether autoburning will result in a unification.
* @param destType The destination type to unify with
* @param burnEntity on which to attempt "autoburning"
* @param info the info to use
* @return AutoburnLogic.AutoburnInfo autoburnable result
*/
public static AutoburnInfo getAutoburnInfo(TypeExpr destType, GemEntity burnEntity, TypeCheckInfo info) {
// see if we have to do anything
TypeExpr entityType;
if (burnEntity == null || (entityType = burnEntity.getTypeExpr()) == null) {
return AutoburnInfo.makeNoUnificationPossibleAutoburnInfo();
}
return getAutoburnInfoWorker(destType, entityType.getTypePieces(), null, info, null);
}
/**
* Return whether autoburning will result in a unification.
* @param destType The destination type to unify with
* @param sourcePieces the type pieces of the entity on which to attempt "autoburning"
* @param info the info to use
* @return AutoburnLogic.AutoburnInfo autoburnable result
*/
public static AutoburnInfo getAutoburnInfo(TypeExpr destType, TypeExpr[] sourcePieces, TypeCheckInfo info) {
// see if we have to do anything
if (sourcePieces == null || sourcePieces.length == 0) {
return AutoburnInfo.makeNoUnificationPossibleAutoburnInfo();
}
return getAutoburnInfoWorker(destType, sourcePieces, null, info, null);
}
/**
* Return whether autoburning will result in a unification.
* Only inputs on the given gem will be considered for burning.
* @param destType The destination type to unify with.
* @param gem The gem on which to attempt "autoburning".
* @param info the typeCheck info to use.
* @return AutoburnLogic.AutoburnInfo autoburnable result.
*/
public static AutoburnInfo getAutoburnInfo(TypeExpr destType, Gem gem, TypeCheckInfo info) {
// see if we got a real type
if (destType == null) {
return AutoburnInfo.makeNoUnificationPossibleAutoburnInfo();
}
int[] burnableArgIndices;
List<TypeExpr> sourceTypeList = new ArrayList<TypeExpr>(); // input types, then output type.
TypeExpr outType = (gem instanceof CollectorGem) ? ((CollectorGem)gem).getCollectingPart().getType() : gem.getOutputPart().getType();
TypeExpr[] outTypePieces = outType.getTypePieces();
// A collector gem's collecting part is not burnable.
if (gem instanceof CollectorGem && !((CollectorGem)gem).isConnected()) {
burnableArgIndices = new int[0];
} else {
// declare the burnt arg indices array
burnableArgIndices = new int[gem.getNInputs()];
if (burnableArgIndices.length != 0) {
// get the unbound input parts of the gem and keep track of which ones are burnable
int unburnedCount = 0;
int burnedCount = 0;
for (int i = 0, n = gem.getNInputs(); i < n; i++) {
Gem.PartInput input = gem.getInputPart(i);
if (!input.isConnected()) {
if (input.isBurnt()) {
sourceTypeList.add(outTypePieces[burnedCount]);
burnedCount++;
} else {
sourceTypeList.add(input.getType());
burnableArgIndices[unburnedCount] = sourceTypeList.size() - 1;
unburnedCount++;
}
}
}
// Calculate what the output type would be if none of the arguments were burned.
TypeExpr newOutType = outTypePieces[outTypePieces.length-1];
for (int i = outTypePieces.length-2; i >= burnedCount; i--) {
newOutType = TypeExpr.makeFunType(outTypePieces[i], newOutType);
}
outType = newOutType;
// Assign to a new trimmed down array
int[] newIndices = new int[unburnedCount];
System.arraycopy(burnableArgIndices, 0, newIndices, 0, unburnedCount);
burnableArgIndices = newIndices;
}
}
// Add the output type to the source types.
sourceTypeList.add(outType);
return getAutoburnInfoWorker(destType, sourceTypeList.toArray(new TypeExpr[0]), burnableArgIndices, info, gem);
}
/**
* Translate from an array of indices of the burnable inputs to an array of indices of all inputs to the gem
* @param unboundArgsToBurn An array of indices relative to the burnable (ie unburned, unbound) inputs of the gem
* @param sourceGem The gem that these are the inputs of
* @return An array of indices of inputs relative to ALL inputs of the gem, corresponding to the contents of unburnedArgsToBurn.
*/
private static int[] translateBurnCombo (int[] unboundArgsToBurn, Gem sourceGem) {
int[] argsToBurn = new int[unboundArgsToBurn.length];
// loop indices:
// i - index of the current input
// j - counter to keep track of how many unbound and unburned inputs we have passed
// k - index of the next element of unboudnArgsToBurn to check
for (int i = 0, j = 0, k = 0; i < sourceGem.getNInputs() && k < unboundArgsToBurn.length; i++) {
Gem.PartInput currentInput = sourceGem.getInputPart(i);
if (currentInput.isConnected() || currentInput.isBurnt()) {
// Skip over unburnable inputs
continue;
}
// This is the j'th burnable input
// Check to see if this argument should be burned, and if so, add its index relative to the list of all inputs
// to the argsToBurn array
if (unboundArgsToBurn[k] == j) {
argsToBurn[k] = i;
k++;
}
j++;
}
return argsToBurn;
}
/**
* Create the first burn combination of size numBurns.
* Note: We assume that numBurns <= numBurnable.
* @return An array of burn positions from zero up to (numBurns-1).
*/
private static int[] getFirstCombination(int numBurns) {
int[] burnArray = new int[numBurns];
for (int i = 0; i < numBurns; i++) {
burnArray[i] = i;
}
return burnArray;
}
/**
* Works along with getFirstCombination to iterate over all burn combinations of a fixed size.
* For example, the following code: <br>
* <code>
* boolean isValidCombo = true;<br>
* for(int[] array = getFirstCombination(3); isValidCombo; isValidCombo = getNextCombination(array, 5)) {...}
* </code><br>
* would genereate the following sequence: <br>
* <ol>
* <li>[0, 1, 2]
* <li>[0, 1, 3]
* <li>[0, 1, 4]
* <li>[0, 2, 3]
* <li>[0, 2, 4]
* <li>[0, 3, 4]
* <li>[1, 2, 3]
* <li>[1, 2, 4]
* <li>[1, 3, 4]
* <li>[2, 3, 4]
* </ol>
*
* For fixed values of numBurnable and numBurns, this algorithm will iterate over
* (numBurnable CHOOSE numBurns) arrays.
*
* @param burnArray An array of burn positions representing the current burn combination.
* Upon return this array will have the next burn combiantion stored in it if possible.
* @param numBurnable The number of burnable inputs.
* @return true if the iteration succeeded, false if there are no more valid combinations
*/
private static boolean getNextCombination(int[] burnArray, int numBurnable){
if (burnArray.length == 0) {
// There is only one combination for a burn size of zero: []
return false;
}
// The algorithm we use is analagous to a counting algorithm in a numbering system with radix
// numBurnable where each digit is represented by an element of burnArray, with the added restriction
// that the digits always form a strictly increasing sequence. Hence our strategy is to iterate over
// the possible combinations in "numerical" order.
// We start by incrmenting burnArray[n-1] (ie. the rightmost digit) and then checking to see if
// burnArray[n-1] is still less than numBurnable.
// If so, we have a valid combination. If not, we continue on to burnArray[n-2] (the next least significant digit)
// and try again. Note that for i < (n-1), it must actually be the case that burnArray[i] < numBurnable-((n-i),
// since the (n-1)-i elements that follow burnArray[i] must be strictly increasing and all less than numBurnable.
final int n = burnArray.length-1;
int i = n;
while (i >= 0) {
burnArray[i]++;
if (burnArray[i] >= (numBurnable - (n - i))) {
i--;
} else {
break;
}
}
// At this point, i is the first index for which the value can be safely incremented.
// If i < 0, this means we have already counted all the valid combinations, so we are done.
if (i < 0) {
return false;
}
// Otherwise we make set the elements in positions [i+1..n] to be
// the smallest possible increasing sequence.
for (; i < n; i++) {
burnArray[i+1] = burnArray[i] + 1;
}
return true;
}
/**
* Note that the types of combinations considered is subject to MAX_BURN_DEPTH.
*
* TODOEL: it seems like it would be better if all of the input types in source type pieces were burnable, and it were up to the
* caller to figure out how the resulting indices mapped onto its arguments.
* @param destType for example, if connecting to the first argument of the map gem, this would be a -> b
* @param sourceTypePieces a list of the relevant source type pieces (ie inputs and output).
* This includes the burnable and already burnt inputs in proper order, as well as the output piece (which should appear at the end).
* It does NOT include any inputs that are already bound.
* For example, in the following case:
*
* Int --\
* | \
* bound ---------- \
* | |- a
* Boolean (burned) >- /
* | /
* Double --/
*
* This would be [Int, Boolean, Double, a]
*
* @param unburnedArguments This is an array of indices into sourceTypePieces that should be considered for burning sites.
* This can be null, in which case *every* argument is interpreted as a possible burn site i.e. equivalent to
* a value of [0, 1, 2, ..., sourceTypePieces.length - 1].
* For example, if the take gem were the source, and it had no arguments burnt and none bound, this would be [0, 1].
* If its 0th argument were burnt this would be [1]. If both arguments were burnt, this would be [].
* Note that since these are indices into sourceTypePieces, if one or more of the arguments are bound, they are not counted.
* So, for example, if the first argument of take was bound to another gem and the second one was unburned, this would be [0].
* In the situation illustrated above, this would be [0, 2].
* @param info the typeCheck info to use
* @param sourceGem the source gem, if one exists. Should be null if otherwise.
*/
private static AutoburnInfo getAutoburnInfoWorker(TypeExpr destType, TypeExpr[] sourceTypePieces, int[] unburnedArguments, TypeCheckInfo info, Gem sourceGem) {
// Possible improvements:
// Check matchability of the nth argument burn to the nth dest type piece.
// Use the fact that some clients do not want to know all burn combos in the ambiguous case.
// Use type piece identity (eg. source type pieces which are the same type var..). Hash for gem types already tried.
// Group types when calculating (eg. if try burning one Double, trying to burn the other double will give the same result).
// Specialize as burns are applied (eg. makeTransform is Typeable a => a -> a. If one a is specialized, so is the other).
// initialize our return type
AutoburnUnifyStatus unificationStatus = AutoburnUnifyStatus.NOT_POSSIBLE;
int numUnburned;
if (unburnedArguments == null) {
//all positions are burnable
numUnburned = sourceTypePieces.length - 1;
unburnedArguments = new int[numUnburned];
for (int i = 0; i < numUnburned; i++) {
unburnedArguments[i] = i;
}
} else {
//only some positions are burnable. This corresponds to a gem graph where some arguments have been already burnt
//by the user, or where some of the arguments do not correspond to arguments in the root gem.
numUnburned = unburnedArguments.length;
}
TypeExpr outputType = sourceTypePieces[sourceTypePieces.length - 1];
boolean doNotTryBurning = false;
if (!destType.isFunctionType()) {
//Don't try burning if the destination type isn't a function.
//This is a not a logically required restriction. For example, any burnt gem can be connected to the Prelude.id gem.
//A less trivial example is that Prelude.sin with argument burnt can be connected to the Prelude.typeOf gem. This is because
//Double -> Double can unify with Typeable a => a.
//However, it is not a situation that the end user would "likely" want to see in the list and we found that it results in
//many rare situations.
doNotTryBurning = true;
} else {
//If the destination result type is a type constructor, perform a basic check against the source result type.
//The fact used here:
//a. for 2 types to unify, their result types must unify.
//b. burning doesn't change the result type (in the technical sense of CAL, and not in the sense of the output type displayed
// as the result of a gem in the GemCutter. For example, burning the first argument of take gem changes the gem to
// in the GemCutter to display as a 1 argument gem with output type Int -> [a], but the overall type of the burnt take gem is
// is [a] -> (Int -> [a]) which is just [a] -> Int -> [a] (-> is right associative) and has result type [a], as with the unburnt
// take gem.
TypeExpr destResultTypeExpr = destType.getResultType();
if (destResultTypeExpr.rootTypeVar() == null && !TypeExpr.canUnifyType(destResultTypeExpr, outputType.getResultType(), info.getModuleTypeInfo())) {
//only do the check if the destination result type is a type constructor or record type.
//the reason for this is that if it is a parameteric type, we are likely to succeed here...
doNotTryBurning = true;
}
}
int maxTypeCloseness = -1;
//the type closeness with no burning
int noBurnTypeCloseness = Integer.MIN_VALUE;
// The lower bound on the number of burns in the upper range.
int upperRangeMinBurns = Math.max(numUnburned - MAX_BURN_DEPTH, MAX_BURN_DEPTH+1);
// List of all burn combinations for which unification can take place.
List<BurnCombination> burnCombos = new ArrayList<BurnCombination>();
// Our combination generator will create all combinations of a fixed size.
// So, we iterate over all possible sizes.
// However, we skip over sizes between MAX_BURN_DEPTH and upperRangeMinBurns
// so that our algorithm runs in polynomial ( specifically O(n^MAX_BURN_DEPTH) ) time
// rather than exponential ( O(2^n) ).
for (int numBurns = 0; numBurns <= numUnburned; numBurns++) {
if (doNotTryBurning && numBurns > 0) {
break;
}
// If necessary, skip from MAX_BURN_DEPTH to upperRangeMinBurns.
if (numBurns == MAX_BURN_DEPTH + 1) {
numBurns = upperRangeMinBurns;
}
// Iterate through all the combinations of burning inputs of size numBurns.
boolean isValidCombo = true;
for (int[] burnComboArray = getFirstCombination(numBurns); isValidCombo; isValidCombo = getNextCombination(burnComboArray, numUnburned)) {
// calculate what the corresponding output type would be.
// Note that we are assuming that the elements of burnComboArray are in
// ascending order.
int currentSourceTypePiece = sourceTypePieces.length - 2;
int nextUnburnedArg = numUnburned - 1;
int nextArgToBurn = numBurns - 1;
// Loop over currentSourceTypePieces.
// These represent all the burned and unburned arguments.
TypeExpr newOutputType = outputType;
while (currentSourceTypePiece >= 0) {
boolean shouldBurn = false;
if (nextUnburnedArg >= 0 && currentSourceTypePiece == unburnedArguments[nextUnburnedArg]) {
// This is an unburned argument.
if (nextArgToBurn >= 0 && nextUnburnedArg == burnComboArray[nextArgToBurn]) {
// This is a burnable argument that we want to try burning
shouldBurn = true;
nextArgToBurn--;
}
nextUnburnedArg--;
} else {
// This is a burned argument.
shouldBurn = true;
}
if (shouldBurn) {
// We need to add the corresponding type to the output type
TypeExpr argType = sourceTypePieces[currentSourceTypePiece];
newOutputType = TypeExpr.makeFunType(argType, newOutputType);
}
currentSourceTypePiece--;
}
// Would the resulting output type unify with the type we want?
int typeCloseness = TypeExpr.getTypeCloseness(destType, newOutputType, info.getModuleTypeInfo());
if (numBurns == 0) {
assert (noBurnTypeCloseness == Integer.MIN_VALUE);
noBurnTypeCloseness = typeCloseness;
}
if (typeCloseness >= 0) {
if (typeCloseness > maxTypeCloseness) {
maxTypeCloseness = typeCloseness;
}
boolean autoburnable = false;
if (numBurns == 0) {
// We didn't have to autoburn.
unificationStatus = AutoburnUnifyStatus.NOT_NECESSARY;
} else if (unificationStatus == AutoburnUnifyStatus.NOT_POSSIBLE) {
// We have our first valid combo and it is not possible to unify without burning.
autoburnable = true;
unificationStatus = AutoburnUnifyStatus.UNAMBIGUOUS;
} else if (unificationStatus == AutoburnUnifyStatus.NOT_NECESSARY) {
// We have our first valid combo and it is possible to unify without burning.
autoburnable = true;
unificationStatus = AutoburnUnifyStatus.UNAMBIGUOUS_NOT_NECESSARY;
} else {
// We got > 1 valid combos.
autoburnable = true;
if (unificationStatus == AutoburnUnifyStatus.UNAMBIGUOUS) {
unificationStatus = AutoburnUnifyStatus.AMBIGUOUS;
} else if (unificationStatus == AutoburnUnifyStatus.UNAMBIGUOUS_NOT_NECESSARY) {
unificationStatus = AutoburnUnifyStatus.AMBIGUOUS_NOT_NECESSARY;
}
}
// If unify can take place, copy the array and store it in burn combos.
if (autoburnable) {
int[] autoburnableCombo;
if (sourceGem != null) {
autoburnableCombo = translateBurnCombo(burnComboArray, sourceGem);
} else {
autoburnableCombo = new int[numBurns];
System.arraycopy(burnComboArray, 0, autoburnableCombo, 0, numBurns);
}
burnCombos.add(new BurnCombination(autoburnableCombo, typeCloseness));
}
}
}
}
if (unificationStatus == AutoburnUnifyStatus.NOT_POSSIBLE) {
burnCombos = null;
}
return new AutoburnInfo(unificationStatus, burnCombos, maxTypeCloseness, noBurnTypeCloseness);
}
}