diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index e4109da4..4796765c 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -481,9 +481,9 @@ public class Expression } - public void addLazyFunction(String name, int num_params, TriFunction, LazyValue> fun) + public void addLazyFunction(String name, int numParams, TriFunction, LazyValue> fun) { - functions.put(name, new AbstractLazyFunction(num_params, name) + functions.put(name, new AbstractLazyFunction(numParams, name) { @Override public boolean pure() { @@ -499,8 +499,11 @@ public class Expression public LazyValue lazyEval(Context c, Context.Type i, Expression e, Tokenizer.Token t, List lazyParams) { ILazyFunction.checkInterrupts(); - if (num_params >= 0 && lazyParams.size() != num_params) - throw new InternalExpressionException("'"+name+"' requires "+num_params+" parameters, "+lazyParams.size()+" provided."); + if (numParams >= 0 && lazyParams.size() != numParams) + { + String error = "Function '"+name+"' requires "+numParams+" arguments, got "+lazyParams.size()+". "; + throw new InternalExpressionException(error + (fun instanceof Fluff.UsageProvider up ? up.getUsage() : "")); + } try { diff --git a/src/main/java/carpet/script/Fluff.java b/src/main/java/carpet/script/Fluff.java index d2e07fdc..ab2e45ba 100644 --- a/src/main/java/carpet/script/Fluff.java +++ b/src/main/java/carpet/script/Fluff.java @@ -30,6 +30,8 @@ public abstract class Fluff @FunctionalInterface public interface SexFunction { R apply(A a, B b, C c, D d, E e, F f);} + public interface UsageProvider { String getUsage();} + public interface EvalNode { /** diff --git a/src/main/java/carpet/script/annotation/AnnotationParser.java b/src/main/java/carpet/script/annotation/AnnotationParser.java index a2c759c6..2e8907dd 100644 --- a/src/main/java/carpet/script/annotation/AnnotationParser.java +++ b/src/main/java/carpet/script/annotation/AnnotationParser.java @@ -21,6 +21,7 @@ import carpet.script.Context; import carpet.script.Expression; import carpet.script.Fluff.AbstractLazyFunction; import carpet.script.Fluff.TriFunction; +import carpet.script.Fluff.UsageProvider; import carpet.script.exception.InternalExpressionException; import carpet.script.LazyValue; import carpet.script.value.Value; @@ -73,6 +74,7 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList; */ public final class AnnotationParser { + static final int UNDEFINED_PARAMS = -2; private static final List functionList = new ArrayList<>(); /** @@ -90,8 +92,8 @@ public final class AnnotationParser * {@link InternalExpressionException}). * Basically, it's fine as long as you don't add a {@code throws} declaration to your methods. *
  • Varargs (or effectively varargs) annotated methods must explicitly declare a maximum number of parameters to ingest in the {@link ScarpetFunction} - * annotation. They can still declare an unlimited amount by setting that maximum to {@code -1}. "Effectively varargs" means a function that has - * a parameter using/requiring a {@link ValueConverter} that has declared {@link ValueConverter#consumesVariableArgs()}.
  • + * annotation. They can still declare an unlimited amount by setting that maximum to {@link ScarpetFunction#UNLIMITED_PARAMS}. "Effectively varargs" + * means a function that has at least a parameter requiring a {@link ValueConverter} that has declared {@link ValueConverter#consumesVariableArgs()}. *
  • Annotated methods must not have a parameter with generics as the varargs parameter. This is just because it was painful for me (altrisi) and * didn't want to support it. Those will crash with a {@code ClassCastException}
  • * @@ -141,7 +143,7 @@ public final class AnnotationParser expr.addLazyFunction(function.name, function.scarpetParamCount, function); } - private static class ParsedFunction implements TriFunction, LazyValue> + private static class ParsedFunction implements TriFunction, LazyValue>, UsageProvider { private final String name; private final boolean isMethodVarArgs; @@ -186,14 +188,14 @@ public final class AnnotationParser if (this.isEffectivelyVarArgs) { maxParams = method.getAnnotation(ScarpetFunction.class).maxParams(); - if (maxParams == -2) - throw new IllegalArgumentException("No maximum number of params specified for " + name + ", use -1 for unlimited. " + if (maxParams == UNDEFINED_PARAMS) + throw new IllegalArgumentException("No maximum number of params specified for " + name + ", use ScarpetFunction.UNLIMITED_PARAMS for unlimited. " + "Provided in " + instance.getClass()); + if (maxParams == ScarpetFunction.UNLIMITED_PARAMS) + maxParams = Integer.MAX_VALUE; if (maxParams < this.minParams) throw new IllegalArgumentException("Provided maximum number of params for " + name + " is smaller than method's param count." + "Provided in " + instance.getClass()); - if (maxParams == -1) - maxParams = Integer.MAX_VALUE; } this.maxParams = maxParams; @@ -293,7 +295,8 @@ public final class AnnotationParser return params; } - private String getUsage() + @Override + public String getUsage() { // Possibility: More descriptive messages using param.getName()? Would need changing gradle setup to keep those StringBuilder builder = new StringBuilder("Usage: '"); diff --git a/src/main/java/carpet/script/annotation/ScarpetFunction.java b/src/main/java/carpet/script/annotation/ScarpetFunction.java index 466bda7e..edb980fd 100644 --- a/src/main/java/carpet/script/annotation/ScarpetFunction.java +++ b/src/main/java/carpet/script/annotation/ScarpetFunction.java @@ -51,6 +51,11 @@ import carpet.script.value.Value; @Retention(RUNTIME) public @interface ScarpetFunction { + /** + *

    Used to define that this {@link ScarpetFunction} can accept an unlimited number of parameters

    + */ + public static final int UNLIMITED_PARAMS = -1; + /** *

    If the function can accept a variable number of parameters, either by declaring its last parameter as a varargs parameter or by having one * of their parameters use a converter that consumes a variable number of arguments, this must define the maximum number of parameters this @@ -65,11 +70,11 @@ public @interface ScarpetFunction * consider that those can take either a single triple of values or 3 independent values, that would be counted in the maximum number of * parameters.

    * - *

    Use -1 to specify an unlimited number of parameters.

    + *

    Use {@link ScarpetFunction#UNLIMITED_PARAMS} to allow an unlimited number of parameters.

    * * @return The maximum number of parameters this function can accept */ - int maxParams() default -2; + int maxParams() default AnnotationParser.UNDEFINED_PARAMS; /** *

    Defines the Context Type that will be used when evaluating arguments to annotated methods.

    diff --git a/src/main/java/carpet/script/annotation/ValueConverter.java b/src/main/java/carpet/script/annotation/ValueConverter.java index 2a5e50c8..ed1a1812 100644 --- a/src/main/java/carpet/script/annotation/ValueConverter.java +++ b/src/main/java/carpet/script/annotation/ValueConverter.java @@ -162,13 +162,13 @@ public interface ValueConverter * constraints as {@link #convert(Value)}

    * @param valueIterator An {@link Iterator} holding the {@link Value} to convert in next position * @param context The {@link Context} this function has been called with. This was used when this passed lazy values instead in order to - * evaluate, now it's used to get the context. It is now ignored by the default implementation - * @param theLazyT The {@code t} that the original function was called with. It is ignored by the default implementation. + * evaluate, now it's used to get the context + * @param contextType The {@link Context.Type} that the original function was called with * @return The next {@link Value} (s) converted to the type {@code } of this {@link ValueConverter} * @implNote This method's default implementation runs the {@link #convert(Value)} function in the next {@link Value} ignoring {@link Context} and * {@code theLazyT}. */ - default public R checkAndConvert(Iterator valueIterator, Context context, Context.Type theLazyT) + default public R checkAndConvert(Iterator valueIterator, Context context, Context.Type contextType) { if (!valueIterator.hasNext()) return null;