package org.elixir_lang.psi.scope.call_definition_clause;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.ResolveState;
import gnu.trove.THashMap;
import org.apache.commons.lang.math.IntRange;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
import static org.elixir_lang.psi.call.name.Module.KERNEL_SPECIAL_FORMS;
import static org.elixir_lang.psi.scope.CallDefinitionClause.MODULAR_CANONICAL_NAME;
/**
* While an arity range for a normal function or macro is represented as an
* {@link org.apache.commons.lang.math.IntRange}, some special forms have no fixed arity when used because although
* defined in {@code Kernel.SpecialForms} as 1-arity, that one argument is effectively a splat of all the arguments, so
* there needs to be a way to represent that half-open interval.
*/
class ArityInterval {
/*
* CONSTANTS
*/
private static final ArityInterval ONE = new ArityInterval(1);
private static final Map<String, ArityInterval> KERNEL_SPECIAL_FORM_ARITY_INTERVAL_BY_NAME =
new THashMap<String, ArityInterval>();
static {
KERNEL_SPECIAL_FORM_ARITY_INTERVAL_BY_NAME.put("__aliases__", ONE);
KERNEL_SPECIAL_FORM_ARITY_INTERVAL_BY_NAME.put("__block__", ONE);
KERNEL_SPECIAL_FORM_ARITY_INTERVAL_BY_NAME.put("for", ONE);
KERNEL_SPECIAL_FORM_ARITY_INTERVAL_BY_NAME.put("with", ONE);
}
/*
* Static Methods
*/
static ArityInterval arityInterval(@NotNull Pair<String, IntRange> nameArityRange,
@NotNull ResolveState resolveState) {
@Nullable String modularCanonicalName = resolveState.get(MODULAR_CANONICAL_NAME);
IntRange arityRange = nameArityRange.second;
int arityRangeMinimum = arityRange.getMinimumInteger();
int arityRangeMaximum = arityRange.getMaximumInteger();
ArityInterval arityInterval = null;
if (modularCanonicalName != null && modularCanonicalName.equals(KERNEL_SPECIAL_FORMS)) {
String name = nameArityRange.first;
arityInterval = KERNEL_SPECIAL_FORM_ARITY_INTERVAL_BY_NAME.get(name);
}
if (arityInterval == null) {
arityInterval = new ArityInterval(arityRangeMinimum, arityRangeMaximum);
}
return arityInterval;
}
/*
* Fields
*/
@NotNull
private final Integer minimum;
@Nullable
private final Integer maximum;
/*
* Constructors
*/
/**
* Unlike {@link org.apache.commons.lang.math.IntRange#IntRange(int)}}, where the argument becomes the minimum AND
* the maximum, here the argument is ONLY the minimum and the interval has an infinite maximum.
*
* @param minimum minimum arity
*/
private ArityInterval(int minimum) {
this.minimum = minimum;
this.maximum = null;
}
private ArityInterval(int minimum, int maximum) {
this.minimum = minimum;
this.maximum = maximum;
}
/*
* Instance Methods
*/
boolean containsInteger(int candidate) {
return minimum <= candidate && (maximum == null || candidate <= maximum);
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("ArityInterval(").append(minimum);
if (maximum != null) {
stringBuilder.append(", ").append(maximum);
}
stringBuilder.append(")");
return stringBuilder.toString();
}
}