diff --git a/src/main/java/ch/njol/skript/doc/Documentation.java b/src/main/java/ch/njol/skript/doc/Documentation.java
index 850f56b0bb6..787af40e330 100644
--- a/src/main/java/ch/njol/skript/doc/Documentation.java
+++ b/src/main/java/ch/njol/skript/doc/Documentation.java
@@ -9,7 +9,6 @@
import org.skriptlang.skript.common.function.DefaultFunction;
import ch.njol.skript.lang.function.Functions;
import ch.njol.skript.lang.function.JavaFunction;
-import ch.njol.skript.lang.function.Parameter;
import ch.njol.skript.registrations.Classes;
import ch.njol.skript.util.Utils;
import ch.njol.util.NonNullPair;
@@ -18,6 +17,7 @@
import ch.njol.util.coll.iterator.IteratorIterable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.common.function.Parameter;
import java.io.*;
import java.util.ArrayList;
@@ -389,8 +389,8 @@ private static void insertFunction(PrintWriter pw, ch.njol.skript.lang.function.
}
StringBuilder params = new StringBuilder();
- for (Parameter> p : func.getParameters()) {
- if (params.length() != 0)
+ for (Parameter> p : func.getSignature().parameters().values()) {
+ if (!params.isEmpty())
params.append(", ");
params.append(p.toString());
}
diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java
index a8426bd55d1..26b2ad528b2 100644
--- a/src/main/java/ch/njol/skript/lang/SkriptParser.java
+++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java
@@ -15,7 +15,6 @@
import ch.njol.skript.lang.function.FunctionReference;
import ch.njol.skript.lang.function.FunctionRegistry;
import ch.njol.skript.lang.function.Functions;
-import ch.njol.skript.lang.function.Signature;
import ch.njol.skript.lang.parser.DefaultValueData;
import ch.njol.skript.lang.parser.ParseStackOverflowException;
import ch.njol.skript.lang.parser.ParserInstance;
@@ -46,26 +45,18 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.common.function.FunctionReferenceParser;
import org.skriptlang.skript.lang.converter.Converters;
import org.skriptlang.skript.lang.experiment.ExperimentSet;
import org.skriptlang.skript.lang.experiment.ExperimentalSyntax;
-import org.skriptlang.skript.lang.script.Script;
import org.skriptlang.skript.lang.script.ScriptWarning;
import org.skriptlang.skript.registration.SyntaxInfo;
import org.skriptlang.skript.registration.SyntaxRegistry;
import java.lang.reflect.Array;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.EnumMap;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
+import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
@@ -491,11 +482,10 @@ private static boolean checkExperimentalSyntax(T eleme
log.printError();
return null;
}
- FunctionReference functionReference = parseFunction(types);
+ org.skriptlang.skript.common.function.FunctionReference functionReference = parseFunctionReference();
if (functionReference != null) {
log.printLog();
- //noinspection rawtypes
- return new ExprFunctionCall(functionReference);
+ return new ExprFunctionCall<>(functionReference, types);
} else if (log.hasError()) {
log.printError();
return null;
@@ -647,10 +637,9 @@ private static boolean checkExperimentalSyntax(T eleme
}
// If it wasn't variable, do same for function call
- FunctionReference> functionReference = parseFunction(types);
+ org.skriptlang.skript.common.function.FunctionReference> functionReference = parseFunctionReference();
if (functionReference != null) {
-
- if (onlySingular && !functionReference.isSingle()) {
+ if (onlySingular && !functionReference.single()) {
Skript.error("'" + expr + "' can only be a single "
+ Classes.toString(Stream.of(exprInfo.classes).map(classInfo -> classInfo.getName().toString()).toArray(), false)
+ ", not more.");
@@ -659,7 +648,7 @@ private static boolean checkExperimentalSyntax(T eleme
}
log.printLog();
- return new ExprFunctionCall<>(functionReference);
+ return new ExprFunctionCall<>(functionReference, types);
} else if (log.hasError()) {
log.printError();
return null;
@@ -879,12 +868,7 @@ private boolean checkAcceptedType(Class> clazz, Class> ... types) {
private final static String MULTIPLE_AND_OR = "List has multiple 'and' or 'or', will default to 'and'. Use brackets if you want to define multiple lists.";
private final static String MISSING_AND_OR = "List is missing 'and' or 'or', defaulting to 'and'";
- private boolean suppressMissingAndOrWarnings = SkriptConfig.disableMissingAndOrWarnings.value();
-
- private SkriptParser suppressMissingAndOrWarnings() {
- suppressMissingAndOrWarnings = true;
- return this;
- }
+ private final boolean suppressMissingAndOrWarnings = SkriptConfig.disableMissingAndOrWarnings.value();
@SafeVarargs
public final @Nullable Expression extends T> parseExpression(Class extends T>... types) {
@@ -1157,11 +1141,28 @@ private record OrderedExprInfo(ExprInfo[] infos) { }
* Function parsing
*/
+
+ /**
+ * Attempts to parse {@link SkriptParser#expr} as a function reference.
+ *
+ * @param The return type of the function.
+ * @return A {@link FunctionReference} if a function is found, or {@code null} if none is found.
+ */
+ public org.skriptlang.skript.common.function.FunctionReference parseFunctionReference() {
+ if (context != ParseContext.DEFAULT && context != ParseContext.EVENT) {
+ return null;
+ }
+
+ return new FunctionReferenceParser(context, flags).parseFunctionReference(expr);
+ }
+
private final static Pattern FUNCTION_CALL_PATTERN = Pattern.compile("(" + Functions.functionNamePattern + ")\\((.*)\\)");
/**
- * @param types The required return type or null if it is not used (e.g. when calling a void function)
- * @return The parsed function, or null if the given expression is not a function call or is an invalid function call (check for an error to differentiate these two)
+ * Attempts to parse {@link SkriptParser#expr} as a function reference.
+ *
+ * @param The return type of the function.
+ * @return A {@link FunctionReference} if a function is found, or {@code null} if none is found.
*/
@SuppressWarnings("unchecked")
public @Nullable FunctionReference parseFunction(@Nullable Class extends T>... types) {
@@ -1192,8 +1193,7 @@ private record OrderedExprInfo(ExprInfo[] infos) { }
return null;
}
- SkriptParser skriptParser = new SkriptParser(args, flags | PARSE_LITERALS, context)
- .suppressMissingAndOrWarnings();
+ SkriptParser skriptParser = new SkriptParser(args, flags | PARSE_LITERALS, context);
Expression>[] params = args.isEmpty() ? new Expression[0] : null;
String namespace = null;
@@ -1235,7 +1235,7 @@ record SignatureData(ClassInfo> classInfo, boolean plural) { }
boolean trySinglePlural = false;
for (var signature : signatures) {
trySingle |= signature.getMinParameters() == 1 || signature.getMaxParameters() == 1;
- trySinglePlural |= trySingle && !signature.getParameter(0).isSingleValue();
+ trySinglePlural |= trySingle && !signature.getParameter(0).single();
for (int i = 0; i < signature.getMaxParameters(); i++) {
if (signatureDatas.size() <= i) {
signatureDatas.add(new ArrayList<>());
@@ -1564,22 +1564,24 @@ public static int next(String expr, int startIndex, ParseContext context) {
return startIndex + 1;
int index;
- switch (expr.charAt(startIndex)) {
- case '"':
+ return switch (expr.charAt(startIndex)) {
+ case '"' -> {
index = nextQuote(expr, startIndex + 1);
- return index < 0 ? -1 : index + 1;
- case '{':
+ yield index < 0 ? -1 : index + 1;
+ }
+ case '{' -> {
index = VariableString.nextVariableBracket(expr, startIndex + 1);
- return index < 0 ? -1 : index + 1;
- case '(':
+ yield index < 0 ? -1 : index + 1;
+ }
+ case '(' -> {
for (index = startIndex + 1; index >= 0 && index < exprLength; index = next(expr, index, context)) {
if (expr.charAt(index) == ')')
- return index + 1;
+ yield index + 1;
}
- return -1;
- default:
- return startIndex + 1;
- }
+ yield -1;
+ }
+ default -> startIndex + 1;
+ };
}
/**
@@ -1784,18 +1786,4 @@ private static ParserInstance getParser() {
ParserInstance.registerData(DefaultValueData.class, DefaultValueData::new);
}
- /**
- * @deprecated due to bad naming conventions,
- * use {@link #LIST_SPLIT_PATTERN} instead.
- */
- @Deprecated(since = "2.7.0", forRemoval = true)
- public final static Pattern listSplitPattern = LIST_SPLIT_PATTERN;
-
- /**
- * @deprecated due to bad naming conventions,
- * use {@link #WILDCARD} instead.
- */
- @Deprecated(since = "2.8.0", forRemoval = true)
- public final static String wildcard = WILDCARD;
-
}
diff --git a/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java b/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java
index 4fc4d553c2f..cbd4ded6d84 100644
--- a/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java
+++ b/src/main/java/ch/njol/skript/lang/function/DynamicFunctionReference.java
@@ -10,6 +10,7 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.UnknownNullability;
+import org.skriptlang.skript.common.function.Parameter;
import org.skriptlang.skript.lang.script.Script;
import org.skriptlang.skript.util.Executable;
import org.skriptlang.skript.util.Validated;
@@ -44,7 +45,7 @@ public DynamicFunctionReference(Function extends Result> function) {
this.function = new WeakReference<>(function);
this.name = function.getName();
this.signature = function.getSignature();
- @Nullable File file = ScriptLoader.getScriptFromName(signature.script);
+ @Nullable File file = ScriptLoader.getScriptFromName(signature.namespace());
this.source = file != null ? ScriptLoader.getScript(file) : null;
}
@@ -69,7 +70,7 @@ public DynamicFunctionReference(@NotNull String name, @Nullable Script source) {
this.function = new WeakReference<>(function);
if (resolved) {
this.signature = function.getSignature();
- @Nullable File file = ScriptLoader.getScriptFromName(signature.script);
+ @Nullable File file = ScriptLoader.getScriptFromName(signature.namespace());
this.source = file != null ? ScriptLoader.getScript(file) : null;
} else {
this.signature = null;
@@ -90,8 +91,8 @@ public DynamicFunctionReference(@NotNull String name, @Nullable Script source) {
public boolean isSingle(Expression>... arguments) {
if (!resolved)
return true;
- return signature.contract != null
- ? signature.contract.isSingle(arguments)
+ return signature.getContract() != null
+ ? signature.getContract().isSingle(arguments)
: signature.isSingle();
}
@@ -99,8 +100,8 @@ public boolean isSingle(Expression>... arguments) {
public @Nullable Class> getReturnType(Expression>... arguments) {
if (!resolved)
return Object.class;
- if (signature.contract != null)
- return signature.contract.getReturnType(arguments);
+ if (signature.getContract() != null)
+ return signature.getContract().getReturnType(arguments);
Function extends Result> function = this.function.get();
if (function != null && function.getReturnType() != null)
return function.getReturnType().getC();
@@ -162,7 +163,7 @@ public String toString() {
this.checkedInputs.put(input, null); // failure case
if (signature == null)
return null;
- boolean varArgs = signature.getMaxParameters() == 1 && !signature.getParameter(0).single;
+ boolean varArgs = signature.getMaxParameters() == 1 && !signature.parameters().firstEntry().getValue().single();
Expression>[] parameters = input.parameters();
// Too many parameters
if (parameters.length > signature.getMaxParameters() && !varArgs)
@@ -174,12 +175,19 @@ else if (parameters.length < signature.getMinParameters())
// Check parameter types
for (int i = 0; i < parameters.length; i++) {
- Parameter> parameter = signature.parameters[varArgs ? 0 : i];
- //noinspection unchecked
- Expression> expression = parameters[i].getConvertedExpression(parameter.type());
+ Parameter> parameter = signature.parameters().values().toArray(new Parameter>[0])[varArgs ? 0 : i];
+
+ Class> target;
+ if (parameter.type().isArray()) {
+ target = parameter.type().componentType();
+ } else {
+ target = parameter.type();
+ }
+
+ Expression> expression = parameters[i].getConvertedExpression(target);
if (expression == null) {
return null;
- } else if (parameter.single && !expression.isSingle()) {
+ } else if (parameter.single() && !expression.isSingle()) {
return null;
}
checked[i] = expression;
diff --git a/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java
index 10dd821b017..0a4c2939e2a 100644
--- a/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java
+++ b/src/main/java/ch/njol/skript/lang/function/EffFunctionCall.java
@@ -1,49 +1,46 @@
package ch.njol.skript.lang.function;
-import org.bukkit.event.Event;
-import org.jetbrains.annotations.Nullable;
-
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ParseContext;
import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.util.Kleenean;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.common.function.FunctionReference;
-/**
- * @author Peter Güttinger
- */
public class EffFunctionCall extends Effect {
-
- private final FunctionReference> function;
-
- public EffFunctionCall(final FunctionReference> function) {
- this.function = function;
+
+ private final FunctionReference> reference;
+
+ public EffFunctionCall(FunctionReference> reference) {
+ this.reference = reference;
}
-
- @Nullable
+
public static EffFunctionCall parse(final String line) {
- final FunctionReference> function = new SkriptParser(line, SkriptParser.ALL_FLAGS, ParseContext.DEFAULT).parseFunction((Class>[]) null);
+ FunctionReference> function = new SkriptParser(line, SkriptParser.ALL_FLAGS, ParseContext.DEFAULT).parseFunctionReference();
if (function != null)
return new EffFunctionCall(function);
return null;
}
-
+
@Override
protected void execute(final Event event) {
- function.execute(event);
- function.resetReturnValue(); // Function might have return value that we're ignoring
+ reference.execute(event);
+ if (reference.function() != null)
+ reference.function().resetReturnValue(); // Function might have return value that we're ignoring
}
-
+
@Override
- public String toString(@Nullable final Event event, final boolean debug) {
- return function.toString(event, debug);
+ public String toString(@Nullable Event event, boolean debug) {
+ return reference.toString(event, debug);
}
-
+
@Override
- public boolean init(final Expression>[] exprs, final int matchedPattern, final Kleenean isDelayed, final ParseResult parseResult) {
+ public boolean init(Expression>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
assert false;
return false;
}
-
+
}
diff --git a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java
index 9ba3c82798e..05fe30f8c5b 100644
--- a/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java
+++ b/src/main/java/ch/njol/skript/lang/function/ExprFunctionCall.java
@@ -2,8 +2,6 @@
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.KeyProviderExpression;
-import ch.njol.skript.lang.KeyedValue;
-import ch.njol.skript.lang.KeyedValue.UnzippedKeyValues;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.util.SimpleExpression;
import ch.njol.skript.util.Utils;
@@ -13,31 +11,37 @@
import org.bukkit.event.Event;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.common.function.FunctionReference;
import org.skriptlang.skript.lang.converter.Converters;
import java.lang.reflect.Array;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.WeakHashMap;
public class ExprFunctionCall extends SimpleExpression implements KeyProviderExpression {
- private final FunctionReference> function;
+ private final FunctionReference> reference;
private final Class extends T>[] returnTypes;
private final Class returnType;
private final Map cache = new WeakHashMap<>();
- public ExprFunctionCall(FunctionReference function) {
- this(function, function.returnTypes);
- }
-
@SuppressWarnings("unchecked")
- public ExprFunctionCall(FunctionReference> function, Class extends T>[] expectedReturnTypes) {
- this.function = function;
- Class> functionReturnType = function.getReturnType();
- assert functionReturnType != null;
- if (CollectionUtils.containsSuperclass(expectedReturnTypes, functionReturnType)) {
+ public ExprFunctionCall(FunctionReference> reference, Class extends T>[] expectedReturnTypes) {
+ this.reference = reference;
+
+ Class> functionReturnType = reference.signature().returnType();
+ Class> returnType;
+ if (functionReturnType != null && functionReturnType.isArray()) {
+ returnType = functionReturnType.componentType();
+ } else {
+ returnType = functionReturnType;
+ }
+
+ if (CollectionUtils.containsSuperclass(expectedReturnTypes, returnType)) {
// Function returns expected type already
- this.returnTypes = new Class[] {functionReturnType};
- this.returnType = (Class) functionReturnType;
+ this.returnTypes = new Class[] {returnType};
+ this.returnType = (Class) returnType;
} else {
// Return value needs to be converted
this.returnTypes = expectedReturnTypes;
@@ -47,9 +51,15 @@ public ExprFunctionCall(FunctionReference> function, Class extends T>[] expe
@Override
protected T @Nullable [] get(Event event) {
- Object[] values = function.execute(event);
- String[] keys = function.returnedKeys();
- function.resetReturnValue();
+ Object[] values;
+ if (reference.single()) {
+ values = new Object[] { reference.execute(event) };
+ } else {
+ values = (Object[]) reference.execute(event);
+ }
+
+ String[] keys = reference.function().returnedKeys();
+ reference.function().resetReturnValue();
//noinspection unchecked
T[] convertedValues = (T[]) Array.newInstance(returnType, values != null ? values.length : 0);
@@ -90,15 +100,23 @@ public boolean areKeysRecommended() {
public @Nullable Expression extends R> getConvertedExpression(Class... to) {
if (CollectionUtils.containsSuperclass(to, getReturnType()))
return (Expression extends R>) this;
- assert function.getReturnType() != null;
- if (Converters.converterExists(function.getReturnType(), to))
- return new ExprFunctionCall<>(function, to);
+
+ Class> returns = reference.signature().returnType();
+ Class> converterType;
+ if (returns != null && returns.isArray()) {
+ converterType = returns.componentType();
+ } else {
+ converterType = returns;
+ }
+
+ if (Converters.converterExists(converterType, to))
+ return new ExprFunctionCall<>(reference, to);
return null;
}
@Override
public boolean isSingle() {
- return function.isSingle();
+ return reference.single();
}
@Override
@@ -118,7 +136,7 @@ public boolean isLoopOf(String input) {
@Override
public String toString(@Nullable Event event, boolean debug) {
- return function.toString(event, debug);
+ return reference.toString(event, debug);
}
@Override
diff --git a/src/main/java/ch/njol/skript/lang/function/Function.java b/src/main/java/ch/njol/skript/lang/function/Function.java
index 3734f1e925e..ffdb0dcc419 100644
--- a/src/main/java/ch/njol/skript/lang/function/Function.java
+++ b/src/main/java/ch/njol/skript/lang/function/Function.java
@@ -2,6 +2,7 @@
import ch.njol.skript.SkriptConfig;
import ch.njol.skript.classes.ClassInfo;
+import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.KeyProviderExpression;
import ch.njol.skript.lang.KeyedValue;
import ch.njol.util.coll.CollectionUtils;
@@ -9,9 +10,13 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.common.function.DefaultFunction;
+import org.skriptlang.skript.common.function.FunctionArguments;
import org.skriptlang.skript.common.function.Parameter.Modifier;
+import org.skriptlang.skript.common.function.ScriptParameter;
import java.util.Arrays;
+import java.util.Objects;
+import java.util.SequencedMap;
/**
* Functions can be called using arguments.
@@ -47,32 +52,47 @@ public String getName() {
return sign.getName();
}
- public Parameter>[] getParameters() {
+ /**
+ * @deprecated Use {@link Signature#parameters()} instead.
+ */
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
+ public org.skriptlang.skript.common.function.Parameter>[] getParameters() {
return sign.getParameters();
}
- @SuppressWarnings("null")
- public Parameter> getParameter(int index) {
- return getParameters()[index];
+ /**
+ * @deprecated Use {@link Signature#getParameter(String)}} instead.
+ */
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
+ public org.skriptlang.skript.common.function.Parameter> getParameter(int index) {
+ return sign.getParameter(index);
}
public boolean isSingle() {
return sign.isSingle();
}
+ /**
+ * @deprecated Use {@link #type()} instead.
+ */
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public @Nullable ClassInfo getReturnType() {
return sign.getReturnType();
}
+ /**
+ * @return The return type of this signature. Returns null for no return type.
+ */
+ public Class type() {
+ return sign.returnType();
+ }
+
// FIXME what happens with a delay in a function?
/**
- * Executes this function with given parameter.
- * @param params Function parameters. Must contain at least
- * {@link Signature#getMinParameters()} elements and at most
- * {@link Signature#getMaxParameters()} elements.
- * @return The result(s) of this function
+ * @deprecated Use {@link #execute(FunctionEvent, FunctionArguments)} instead.
*/
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public final T @Nullable [] execute(Object[][] params) {
FunctionEvent extends T> event = new FunctionEvent<>(this);
@@ -82,38 +102,44 @@ public boolean isSingle() {
Bukkit.getPluginManager().callEvent(event);
// Parameters taken by the function.
- Parameter>[] parameters = sign.getParameters();
+ SequencedMap> parameters = sign.parameters();
- if (params.length > parameters.length) {
+ if (params.length > parameters.size()) {
// Too many parameters, should have failed to parse
assert false : params.length;
return null;
}
// If given less that max amount of parameters, pad remaining with nulls
- Object[][] parameterValues = params.length < parameters.length ? Arrays.copyOf(params, parameters.length) : params;
+ Object[][] parameterValues = params.length < parameters.size() ? Arrays.copyOf(params, parameters.size()) : params;
+ int i = 0;
// Execute parameters or default value expressions
- for (int i = 0; i < parameters.length; i++) {
- Parameter> parameter = parameters[i];
+ for (org.skriptlang.skript.common.function.Parameter> parameter : parameters.values()) {
Object[] parameterValue = parameter.hasModifier(Modifier.KEYED) ? convertToKeyed(parameterValues[i]) : parameterValues[i];
+ Expression> def;
+ if (parameter instanceof Parameter> script) {
+ def = script.def;
+ } else if (parameter instanceof ScriptParameter> script) {
+ def = script.defaultValue();
+ } else {
+ def = null;
+ }
+
// see https://github.com/SkriptLang/Skript/pull/8135
- if ((parameterValues[i] == null || parameterValues[i].length == 0)
- && parameter.keyed
- && parameter.def != null
- ) {
- Object[] defaultValue = parameter.def.getArray(event);
+ if ((parameterValues[i] == null || parameterValues[i].length == 0) && parameter.hasModifier(Modifier.KEYED) && def != null) {
+ Object[] defaultValue = def.getArray(event);
if (defaultValue.length == 1) {
parameterValue = KeyedValue.zip(defaultValue, null);
} else {
parameterValue = defaultValue;
}
- } else if (!(this instanceof DefaultFunction>) && parameterValue == null) { // Go for default value
- assert parameter.def != null; // Should've been parse error
- Object[] defaultValue = parameter.def.getArray(event);
- if (parameter.hasModifier(Modifier.KEYED) && KeyProviderExpression.areKeysRecommended(parameter.def)) {
- String[] keys = ((KeyProviderExpression>) parameter.def).getArrayKeys(event);
+ } else if (parameterValue == null) { // Go for default value
+ assert def != null; // Should've been parse error
+ Object[] defaultValue = def.getArray(event);
+ if (parameter.hasModifier(Modifier.KEYED) && KeyProviderExpression.areKeysRecommended(def)) {
+ String[] keys = ((KeyProviderExpression>) def).getArrayKeys(event);
parameterValue = KeyedValue.zip(defaultValue, keys);
} else {
parameterValue = defaultValue;
@@ -126,9 +152,10 @@ public boolean isSingle() {
* really have a concept of nulls, it was changed. The config
* option may be removed in future.
*/
- if (!(this instanceof DefaultFunction>) && !executeWithNulls && parameterValue.length == 0)
+ if (!(this instanceof DefaultFunction>) && !executeWithNulls && parameterValue != null && parameterValue.length == 0)
return null;
parameterValues[i] = parameterValue;
+ i++;
}
// Execute function contents
@@ -157,15 +184,9 @@ public boolean isSingle() {
}
/**
- * Executes this function with given parameters. Usually, using
- * {@link #execute(Object[][])} is better; it handles optional and keyed arguments
- * and function event creation automatically.
- * @param event Associated function event. This is usually created by Skript.
- * @param params Function parameters.
- * There must be {@link Signature#getMaxParameters()} amount of them, and
- * you need to manually handle default values.
- * @return Function return value(s).
+ * @deprecated Use {@link #execute(FunctionEvent, FunctionArguments)} instead.
*/
+ @Deprecated(since = "INSERT VERSION", forRemoval = true)
public abstract T @Nullable [] execute(FunctionEvent> event, Object[][] params);
/**
@@ -185,7 +206,7 @@ public boolean isSingle() {
@Override
public String toString() {
- return (sign.local ? "local " : "") + "function " + sign.getName();
+ return (sign.isLocal() ? "local " : "") + "function " + sign.getName();
}
}
diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionEvent.java b/src/main/java/ch/njol/skript/lang/function/FunctionEvent.java
index 6e17b1410d1..a4de260d8d4 100644
--- a/src/main/java/ch/njol/skript/lang/function/FunctionEvent.java
+++ b/src/main/java/ch/njol/skript/lang/function/FunctionEvent.java
@@ -13,7 +13,11 @@ public final class FunctionEvent extends Event {
public FunctionEvent(Function extends T> function) {
this.function = function;
}
-
+
+ public FunctionEvent(org.skriptlang.skript.common.function.Function extends T> function) {
+ this.function = (Function extends T>) function;
+ }
+
public Function extends T> getFunction() {
return function;
}
diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java
index 478d2cd0bac..631f18afb33 100644
--- a/src/main/java/ch/njol/skript/lang/function/FunctionReference.java
+++ b/src/main/java/ch/njol/skript/lang/function/FunctionReference.java
@@ -4,10 +4,12 @@
import ch.njol.skript.SkriptAPIException;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.config.Node;
-import ch.njol.skript.lang.*;
+import ch.njol.skript.lang.Expression;
+import ch.njol.skript.lang.KeyProviderExpression;
+import ch.njol.skript.lang.KeyedValue;
+import ch.njol.skript.lang.SkriptParser;
import ch.njol.skript.lang.function.FunctionRegistry.Retrieval;
import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult;
-import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.skript.registrations.Classes;
@@ -16,16 +18,20 @@
import ch.njol.util.StringUtils;
import org.bukkit.event.Event;
import org.jetbrains.annotations.Nullable;
-import org.skriptlang.skript.lang.converter.Converters;
+import org.skriptlang.skript.common.function.FunctionReference.Argument;
+import org.skriptlang.skript.common.function.FunctionReference.ArgumentType;
+import org.skriptlang.skript.common.function.Parameter;
import org.skriptlang.skript.common.function.Parameter.Modifier;
+import org.skriptlang.skript.lang.converter.Converters;
import org.skriptlang.skript.util.Executable;
import java.util.*;
import java.util.stream.Collectors;
/**
- * Reference to a {@link Function Skript function}.
+ * @deprecated Use {@link org.skriptlang.skript.common.function.FunctionReference} instead.
*/
+@Deprecated(forRemoval = true, since = "INSERT VERSION")
public class FunctionReference implements Contract, Executable {
private static final String AMBIGUOUS_ERROR =
@@ -160,8 +166,8 @@ public boolean validateFunction(boolean first) {
// Validate that return types are what caller expects they are
Class extends T>[] returnTypes = this.returnTypes;
if (returnTypes != null) {
- ClassInfo> rt = sign.returnType;
- if (rt == null) {
+ Class> returnType = sign.returnType();
+ if (returnType == null) {
if (first) {
Skript.error("The function '" + stringified + "' doesn't return any value.");
} else {
@@ -171,9 +177,10 @@ public boolean validateFunction(boolean first) {
}
return false;
}
- if (!Converters.converterExists(rt.getC(), returnTypes)) {
+
+ if (!Converters.converterExists(returnType, returnTypes)) {
if (first) {
- Skript.error("The returned value of the function '" + stringified + "', " + sign.returnType + ", is " + SkriptParser.notOfType(returnTypes) + ".");
+ Skript.error("The returned value of the function '" + stringified + "', " + returnType + ", is " + SkriptParser.notOfType(returnTypes) + ".");
} else {
Skript.error("The function '" + stringified + "' was redefined with a different, incompatible return type, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
@@ -182,8 +189,8 @@ public boolean validateFunction(boolean first) {
return false;
}
if (first) {
- single = sign.single;
- } else if (single && !sign.single) {
+ single = sign.isSingle();
+ } else if (single && !sign.isSingle()) {
Skript.error("The function '" + functionName + "' was redefined with a different, incompatible return type, but is still used in other script(s)."
+ " These will continue to use the old version of the function until Skript restarts.");
function = previousFunction;
@@ -192,7 +199,7 @@ public boolean validateFunction(boolean first) {
}
// Validate parameter count
- singleListParam = sign.getMaxParameters() == 1 && !sign.getParameter(0).single;
+ singleListParam = sign.getMaxParameters() == 1 && !sign.parameters().entrySet().iterator().next().getValue().single();
if (!singleListParam) { // Check that parameter count is within allowed range
// Too many parameters
if (parameters.length > sign.getMaxParameters()) {
@@ -229,17 +236,24 @@ public boolean validateFunction(boolean first) {
// Check parameter types
for (int i = 0; i < parameters.length; i++) {
- Parameter> p = sign.parameters[singleListParam ? 0 : i];
+ Parameter> parameter = sign.parameters().values().toArray(new Parameter>[0])[singleListParam ? 0 : i];
RetainingLogHandler log = SkriptLogger.startRetainingLog();
try {
+ Class> target;
+ if (parameter.type().isArray()) {
+ target = parameter.type().componentType();
+ } else {
+ target = parameter.type();
+ }
+
//noinspection unchecked
- Expression> e = parameters[i].getConvertedExpression(p.type());
- if (e == null) {
+ Expression> expr = parameters[i].getConvertedExpression(target);
+ if (expr == null) {
if (first) {
if (LiteralUtils.hasUnparsedLiteral(parameters[i])) {
Skript.error("Can't understand this expression: " + parameters[i].toString());
} else {
- String type = Classes.toString(getClassInfo(p.type()));
+ String type = Classes.toString(getClassInfo(target));
Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + stringified + "' is not of the required type " + type + "."
+ " Check the correct order of the arguments and put lists into parentheses if appropriate (e.g. 'give(player, (iron ore and gold ore))')."
@@ -251,7 +265,7 @@ public boolean validateFunction(boolean first) {
function = previousFunction;
}
return false;
- } else if (p.single && !e.isSingle()) {
+ } else if (parameter.single() && !expr.isSingle()) {
if (first) {
Skript.error("The " + StringUtils.fancyOrderNumber(i + 1) + " argument given to the function '" + functionName + "' is plural, "
+ "but a single argument was expected");
@@ -262,7 +276,7 @@ public boolean validateFunction(boolean first) {
}
return false;
}
- parameters[i] = e;
+ parameters[i] = expr;
} finally {
log.printLog();
}
@@ -270,7 +284,13 @@ public boolean validateFunction(boolean first) {
//noinspection unchecked
signature = (Signature extends T>) sign;
- sign.calls.add(this);
+
+ //noinspection unchecked
+ Argument>[] stream = (Argument>[]) Arrays.stream(parameters)
+ .map(it -> new Argument<>(ArgumentType.UNNAMED, null, it))
+ .toArray(Argument[]::new);
+
+ sign.calls().add(new org.skriptlang.skript.common.function.FunctionReference<>(script, functionName, signature, stream));
Contract contract = sign.getContract();
if (contract != null)
@@ -387,10 +407,14 @@ public boolean resetReturnValue() {
// Prepare parameter values for calling
Object[][] params = new Object[singleListParam ? 1 : parameters.length][];
if (singleListParam && parameters.length > 1) { // All parameters to one list
- params[0] = evaluateSingleListParameter(parameters, event, function.getParameter(0).hasModifier(Modifier.KEYED));
+ params[0] = evaluateSingleListParameter(parameters, event, function.getSignature().parameters()
+ .entrySet().iterator().next().getValue().modifiers().contains(Modifier.KEYED));
} else { // Use parameters in normal way
+ Parameter>[] values = function.getSignature().parameters()
+ .values().toArray(new Parameter>[0]);
+
for (int i = 0; i < parameters.length; i++)
- params[i] = evaluateParameter(parameters[i], event, function.getParameter(i).hasModifier(Modifier.KEYED));
+ params[i] = evaluateParameter(parameters[i], event, values[i].modifiers().contains(Modifier.KEYED));
}
// Execute the function
@@ -467,8 +491,7 @@ public boolean isSingle(Expression>... arguments) {
if (signature == null)
throw new SkriptAPIException("Signature of function is null when return type is asked!");
- ClassInfo extends T> ret = signature.returnType;
- return ret == null ? null : ret.getC();
+ return signature.returnType();
}
/**
diff --git a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java
index 0a5296577fb..7d2d7e4cad8 100644
--- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java
+++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java
@@ -4,16 +4,18 @@
import ch.njol.skript.SkriptAPIException;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
+import org.skriptlang.skript.common.function.Parameter;
+import org.skriptlang.skript.common.function.Parameter.Modifier;
import org.skriptlang.skript.lang.converter.Converters;
import org.skriptlang.skript.util.Registry;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
@@ -40,7 +42,7 @@ public static FunctionRegistry getRegistry() {
* The pattern for a valid function name.
* Functions must start with a letter or underscore and can only contain letters, numbers, and underscores.
*/
- final static String FUNCTION_NAME_PATTERN = "[\\p{IsAlphabetic}_][\\p{IsAlphabetic}\\d_]*";
+ final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile("[A-z_][A-z_0-9]*");
/**
* The namespace for registered global functions.
@@ -148,7 +150,7 @@ public void register(@Nullable String namespace, @NotNull Function> function)
Skript.debug("Registering function '%s'", function.getName());
String name = function.getName();
- if (!name.matches(FUNCTION_NAME_PATTERN)) {
+ if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) {
throw new SkriptAPIException("Invalid function name '" + name + "'");
}
@@ -212,7 +214,7 @@ private boolean signatureExists(@NotNull NamespaceIdentifier namespace, @NotNull
* The result of attempting to retrieve a function.
* Depending on the type, a {@link Retrieval} will feature different data.
*/
- enum RetrievalResult {
+ public enum RetrievalResult {
/**
* The specified function or signature has not been registered.
@@ -258,7 +260,7 @@ enum RetrievalResult {
* @param retrieved The function or signature that was found if {@code result} is {@code EXACT}.
* @param conflictingArgs The conflicting arguments if {@code result} is {@code AMBIGUOUS}.
*/
- record Retrieval(
+ public record Retrieval(
@NotNull RetrievalResult result,
T retrieved,
Class>[][] conflictingArgs
@@ -407,31 +409,24 @@ Retrieval> getExactSignature(
public @Unmodifiable @NotNull Set> getSignatures(@Nullable String namespace, @NotNull String name) {
Preconditions.checkNotNull(name, "name cannot be null");
- ImmutableSet.Builder> setBuilder = ImmutableSet.builder();
-
- // obtain all global functions of "name"
- Namespace globalNamespace = namespaces.get(GLOBAL_NAMESPACE);
- Set globalIdentifiers = globalNamespace.identifiers.get(name);
- if (globalIdentifiers != null) {
- for (FunctionIdentifier identifier : globalIdentifiers) {
- setBuilder.add(globalNamespace.signatures.get(identifier));
- }
- }
+ Map> total = new HashMap<>();
// obtain all local functions of "name"
if (namespace != null) {
- Namespace localNamespace = namespaces.get(new NamespaceIdentifier(namespace));
- if (localNamespace != null) {
- Set localIdentifiers = localNamespace.identifiers.get(name);
- if (localIdentifiers != null) {
- for (FunctionIdentifier identifier : localIdentifiers) {
- setBuilder.add(localNamespace.signatures.get(identifier));
- }
- }
+ Namespace local = namespaces.getOrDefault(new NamespaceIdentifier(namespace), new Namespace());
+
+ for (FunctionIdentifier identifier : local.identifiers.getOrDefault(name, Collections.emptySet())) {
+ total.putIfAbsent(identifier, local.signatures.get(identifier));
}
}
- return setBuilder.build();
+ // obtain all global functions of "name"
+ Namespace global = namespaces.getOrDefault(GLOBAL_NAMESPACE, new Namespace());
+ for (FunctionIdentifier identifier : global.identifiers.getOrDefault(name, Collections.emptySet())) {
+ total.putIfAbsent(identifier, global.signatures.get(identifier));
+ }
+
+ return Set.copyOf(total.values());
}
/**
@@ -505,10 +500,13 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp
&& candidate.args.length == 1
&& candidate.args[0].isArray()) {
// if a function has single list value param, check all types
-
// make sure all types in the passed array are valid for the array parameter
Class> arrayType = candidate.args[0].componentType();
for (Class> arrayArg : provided.args) {
+ if (arrayArg.isArray()) {
+ arrayArg = arrayArg.componentType();
+ }
+
if (!Converters.converterExists(arrayArg, arrayType)) {
continue candidates;
}
@@ -527,6 +525,13 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp
// if the types of the provided arguments do not match the candidate arguments, skip
for (int i = 0; i < provided.args.length; i++) {
// allows single passed values to still match array type in candidate (e.g. clamp)
+ Class> providedType;
+ if (provided.args[i].isArray()) {
+ providedType = provided.args[i].componentType();
+ } else {
+ providedType = provided.args[i];
+ }
+
Class> candidateType;
if (candidate.args[i].isArray()) {
candidateType = candidate.args[i].componentType();
@@ -540,7 +545,7 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp
continue candidates;
}
} else {
- if (!Converters.converterExists(providedArg, candidateType)) {
+ if (!Converters.converterExists(providedType, candidateType)) {
continue candidates;
}
}
@@ -595,7 +600,7 @@ public void remove(@NotNull Signature> signature) {
Namespace namespace;
if (signature.isLocal()) {
- namespace = namespaces.get(new NamespaceIdentifier(signature.script));
+ namespace = namespaces.get(new NamespaceIdentifier(signature.namespace()));
} else {
namespace = namespaces.get(GLOBAL_NAMESPACE);
}
@@ -707,22 +712,17 @@ static FunctionIdentifier of(@NotNull String name, boolean local, @NotNull Class
static FunctionIdentifier of(@NotNull Signature> signature) {
Preconditions.checkNotNull(signature, "signature cannot be null");
- Parameter>[] signatureParams = signature.parameters;
+ Parameter>[] signatureParams = signature.parameters().values().toArray(new Parameter>[0]);
Class>[] parameters = new Class[signatureParams.length];
int optionalArgs = 0;
for (int i = 0; i < signatureParams.length; i++) {
Parameter> param = signatureParams[i];
- if (param.isOptional()) {
+ if (param.modifiers().contains(Modifier.OPTIONAL)) {
optionalArgs++;
}
- Class> type = param.type();
- if (param.isSingleValue()) {
- parameters[i] = type;
- } else {
- parameters[i] = type.arrayType();
- }
+ parameters[i] = param.type();
}
return new FunctionIdentifier(signature.getName(), signature.isLocal(),
@@ -731,7 +731,7 @@ static FunctionIdentifier of(@NotNull Signature> signature) {
@Override
public int hashCode() {
- return Objects.hash(name, local, Arrays.hashCode(args));
+ return Objects.hash(name, Arrays.hashCode(args));
}
@Override
@@ -748,10 +748,6 @@ public boolean equals(Object obj) {
return false;
}
- if (local != other.local) {
- return false;
- }
-
for (int i = 0; i < args.length; i++) {
if (args[i] != other.args[i]) {
return false;
diff --git a/src/main/java/ch/njol/skript/lang/function/Functions.java b/src/main/java/ch/njol/skript/lang/function/Functions.java
index bcd3570a6e3..88ed4aed612 100644
--- a/src/main/java/ch/njol/skript/lang/function/Functions.java
+++ b/src/main/java/ch/njol/skript/lang/function/Functions.java
@@ -8,11 +8,12 @@
import ch.njol.skript.lang.function.FunctionRegistry.Retrieval;
import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult;
import ch.njol.skript.registrations.Classes;
-import ch.njol.skript.util.Utils;
-import ch.njol.util.NonNullPair;
+import ch.njol.skript.structures.StructFunction;
import ch.njol.util.StringUtils;
import org.jetbrains.annotations.Nullable;
import org.skriptlang.skript.common.function.DefaultFunction;
+import org.skriptlang.skript.common.function.FunctionReference;
+import org.skriptlang.skript.common.function.Parameter;
import org.skriptlang.skript.lang.script.Script;
import java.util.*;
@@ -52,7 +53,7 @@ private Functions() {}
*/
private static final Map globalFunctions = new HashMap<>();
- static boolean callFunctionEvents = false;
+ public static boolean callFunctionEvents = false;
/**
* Registers a {@link DefaultFunction}.
@@ -94,7 +95,7 @@ public static JavaFunction> registerFunction(JavaFunction> function) {
return function;
}
- public final static String functionNamePattern = "[\\p{IsAlphabetic}_][\\p{IsAlphabetic}\\p{IsDigit}_]*";
+ public final static String functionNamePattern = "[\\p{IsAlphabetic}_][\\p{IsAlphabetic}\\d_]*";
/**
* Loads a script function from given node.
@@ -105,7 +106,7 @@ public static JavaFunction> registerFunction(JavaFunction> function) {
* @return Script function, or null if something went wrong.
*/
public static @Nullable Function> loadFunction(Script script, SectionNode node, Signature> signature) {
- String name = signature.name;
+ String name = signature.getName();
Namespace namespace = getScriptNamespace(script.getConfig().getFileName());
if (namespace == null) {
namespace = globalFunctions.get(name);
@@ -113,12 +114,20 @@ public static JavaFunction> registerFunction(JavaFunction> function) {
return null; // Probably duplicate signature; reported before
}
- Parameter>[] params = signature.parameters;
- ClassInfo> c = signature.returnType;
+ Parameter>[] params = signature.parameters().values().toArray(new Parameter>[0]);
- if (Skript.debug() || node.debug())
- Skript.debug((signature.local ? "local " : "") + "function " + name + "(" + StringUtils.join(params, ", ") + ")"
+ if (Skript.debug() || node.debug()) {
+ Class> returnType = signature.returnType();
+ ClassInfo> c;
+ if (returnType != null && returnType.isArray()) {
+ c = Classes.getExactClassInfo(returnType.componentType());
+ } else {
+ c = Classes.getExactClassInfo(returnType);
+ }
+
+ Skript.debug((signature.isLocal() ? "local " : "") + "function " + name + "(" + StringUtils.join(params, ", ") + ")"
+ (c != null ? " :: " + (signature.isSingle() ? c.getName().getSingular() : c.getName().getPlural()) : "") + ":");
+ }
Function> function;
try {
@@ -132,7 +141,7 @@ public static JavaFunction> registerFunction(JavaFunction> function) {
return null;
}
- if (namespace.getFunction(signature.name) == null) {
+ if (namespace.getFunction(signature.getName()) == null) {
namespace.addFunction(function);
}
@@ -145,41 +154,12 @@ public static JavaFunction> registerFunction(JavaFunction> function) {
return function;
}
-
/**
- * Parses the signature from the given arguments.
- * @param script Script file name (might be used for some checks).
- * @param name The name of the function.
- * @param args The parameters of the function. See {@link Parameter#parse(String)}
- * @param returnType The return type of the function
- * @param local If the signature of function is local.
- * @return Parsed signature or null if something went wrong.
- * @see Functions#registerSignature(Signature)
+ * @deprecated Use {@link StructFunction.FunctionParser#parse(String, String, String, String, boolean)} instead.
*/
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public static @Nullable Signature> parseSignature(String script, String name, String args, @Nullable String returnType, boolean local) {
- List> parameters = Parameter.parse(args);
- if (parameters == null)
- return null;
-
- // Parse return type if one exists
- ClassInfo> returnClass;
- boolean singleReturn;
- if (returnType == null) {
- returnClass = null;
- singleReturn = false; // Ignored, nothing is returned
- } else {
- returnClass = Classes.getClassInfoFromUserInput(returnType);
- NonNullPair p = Utils.getEnglishPlural(returnType);
- singleReturn = !p.getSecond();
- if (returnClass == null)
- returnClass = Classes.getClassInfoFromUserInput(p.getFirst());
- if (returnClass == null) {
- Skript.error("Cannot recognise the type '" + returnType + "'");
- return null;
- }
- }
- //noinspection unchecked
- return new Signature<>(script, name, parameters.toArray(new Parameter[0]), local, (ClassInfo
+ */
private static final Pattern SIGNATURE_PATTERN =
- Pattern.compile("^(?:local )?function (" + Functions.functionNamePattern + ")\\((.*?)\\)(?:\\s*(?:::| returns )\\s*(.+))?$");
+ Pattern.compile("^(?:local )?function (?" + Functions.functionNamePattern + ")\\((?.*?)\\)(?:\\s*(?:->|::| returns )\\s*(?.+))?$");
private static final AtomicBoolean VALIDATE_FUNCTIONS = new AtomicBoolean();
static {
@@ -53,7 +89,6 @@ public class StructFunction extends Structure {
);
}
- @SuppressWarnings("NotNullFieldNotInitialized")
private SectionNode source;
@Nullable
private Signature> signature;
@@ -82,9 +117,9 @@ public boolean preLoad() {
// parse signature
getParser().setCurrentEvent((local ? "local " : "") + "function", FunctionEvent.class);
- signature = Functions.parseSignature(
+ signature = FunctionParser.parse(
getParser().getCurrentScript().getConfig().getFileName(),
- matcher.group(1), matcher.group(2), matcher.group(3), local
+ matcher.group("name"), matcher.group("args"), matcher.group("returns"), local
);
getParser().deleteCurrentEvent();
@@ -134,4 +169,139 @@ public String toString(@Nullable Event event, boolean debug) {
return (local ? "local " : "") + "function";
}
+ public static class FunctionParser {
+
+ /**
+ * Parses the signature from the given arguments.
+ *
+ * @param script Script file name (might be used for some checks).
+ * @param name The name of the function.
+ * @param args The parameters of the function.
+ * @param returns The return type of the function
+ * @param local If the signature of function is local.
+ * @return Parsed signature or null if something went wrong.
+ * @see Functions#registerSignature(Signature)
+ */
+ public static @Nullable Signature> parse(String script, String name, String args, @Nullable String returns, boolean local) {
+ SequencedMap> parameters = parseParameters(args);
+ if (parameters == null)
+ return null;
+
+ // Parse return type if one exists
+ ClassInfo> returnClass;
+ Class> returnType = null;
+
+ if (returns != null) {
+ returnClass = Classes.getClassInfoFromUserInput(returns);
+ PluralResult result = Utils.isPlural(returns);
+
+ if (returnClass == null)
+ returnClass = Classes.getClassInfoFromUserInput(result.updated());
+
+ if (returnClass == null) {
+ Skript.error("Cannot recognise the type '" + returns + "'");
+ return null;
+ }
+
+ if (result.plural()) {
+ returnType = returnClass.getC().arrayType();
+ } else {
+ returnType = returnClass.getC();
+ }
+
+ return new Signature<>(script, name, parameters, returnType, local);
+ }
+
+ return new Signature<>(script, name, parameters, returnType, local);
+ }
+
+ /**
+ * Represents the pattern used for the parameter definition in a script function declaration.
+ *
+ * The first group specifies the name of the parameter. The name may contain any characters
+ * but a colon, parenthesis, curly braces, double quotes, or a comma. Then, after a colon,
+ * the type is specified in the {@code type} group. If a default value is present, this is specified
+ * with the least amount of tokens as possible.
+ *
+ */
+ private final static Pattern SCRIPT_PARAMETER_PATTERN =
+ Pattern.compile("^\\s*(?[^:(){}\",]+?)\\s*:\\s*(?[a-zA-Z ]+?)\\s*(?:\\s*=\\s*(?.+))?\\s*$");
+
+ private static SequencedMap> parseParameters(String args) {
+ SequencedMap> params = new LinkedHashMap<>();
+
+ boolean caseInsensitive = SkriptConfig.caseInsensitiveVariables.value();
+
+ if (args.isEmpty()) // Zero-argument function
+ return params;
+
+ int j = 0;
+ for (int i = 0; i <= args.length(); i = SkriptParser.next(args, i, ParseContext.DEFAULT)) {
+ if (i == -1) {
+ Skript.error("Invalid text/variables/parentheses in the arguments of this function");
+ return null;
+ }
+
+ if (i != args.length() && args.charAt(i) != ',') {
+ continue;
+ }
+
+ String arg = args.substring(j, i);
+
+ // One or more arguments for this function
+ Matcher n = SCRIPT_PARAMETER_PATTERN.matcher(arg);
+ if (!n.matches()) {
+ Skript.error("The " + StringUtils.fancyOrderNumber(params.size() + 1) + " argument's definition is invalid. It should look like 'name: type' or 'name: type = default value'.");
+ return null;
+ }
+
+ String paramName = n.group("name");
+ // for comparing without affecting the original name, in case the config option for case insensitivity changes.
+ String lowerParamName = paramName.toLowerCase(Locale.ENGLISH);
+ for (String otherName : params.keySet()) {
+ // only force lowercase if we don't care about case in variables
+ otherName = caseInsensitive ? otherName.toLowerCase(Locale.ENGLISH) : otherName;
+ if (otherName.equals(caseInsensitive ? lowerParamName : paramName)) {
+ Skript.error("Each argument's name must be unique, but the name '" + paramName + "' occurs at least twice.");
+ return null;
+ }
+ }
+
+ ClassInfo> c = Classes.getClassInfoFromUserInput(n.group("type"));
+ PluralResult result = Utils.isPlural(n.group("type"));
+
+ if (c == null)
+ c = Classes.getClassInfoFromUserInput(result.updated());
+
+ if (c == null) {
+ Skript.error("Cannot recognise the type '%s'", n.group("type"));
+ return null;
+ }
+
+ String variableName = paramName.endsWith("*") ? paramName.substring(0, paramName.length() - 3) +
+ (!result.plural() ? "::1" : "") : paramName;
+
+ Class> type;
+ if (result.plural()) {
+ type = c.getC().arrayType();
+ } else {
+ type = c.getC();
+ }
+
+ Parameter> parameter = ScriptParameter.parse(variableName, type, n.group("def"));
+
+ if (parameter == null)
+ return null;
+
+ params.put(variableName, parameter);
+
+ j = i + 1;
+ if (i == args.length())
+ break;
+ }
+ return params;
+ }
+
+ }
+
}
diff --git a/src/main/java/ch/njol/skript/util/LiteralUtils.java b/src/main/java/ch/njol/skript/util/LiteralUtils.java
index d2ba137886a..e53963f7819 100644
--- a/src/main/java/ch/njol/skript/util/LiteralUtils.java
+++ b/src/main/java/ch/njol/skript/util/LiteralUtils.java
@@ -68,8 +68,8 @@ public static boolean hasUnparsedLiteral(Expression> expr) {
* @return Whether or not the passed expressions contain {@link UnparsedLiteral} objects
*/
public static boolean canInitSafely(Expression>... expressions) {
- for (int i = 0; i < expressions.length; i++) {
- if (expressions[i] == null || hasUnparsedLiteral(expressions[i])) {
+ for (Expression> expression : expressions) {
+ if (expression == null || hasUnparsedLiteral(expression)) {
return false;
}
}
diff --git a/src/main/java/ch/njol/skript/util/Utils.java b/src/main/java/ch/njol/skript/util/Utils.java
index 1bbb8e5626a..2ff288d0bb9 100644
--- a/src/main/java/ch/njol/skript/util/Utils.java
+++ b/src/main/java/ch/njol/skript/util/Utils.java
@@ -8,6 +8,7 @@
import ch.njol.util.Pair;
import ch.njol.util.StringUtils;
import ch.njol.util.coll.CollectionUtils;
+import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
@@ -243,9 +244,9 @@ public static File getFile(Plugin plugin) {
}
/**
- * @param word trimmed string
- * @return Pair of singular string + boolean whether it was plural
+ * @deprecated Use {@link #isPlural(String)} instead.
*/
+ @Deprecated(forRemoval = true, since = "INSERT VERSION")
public static NonNullPair getEnglishPlural(String word) {
assert word != null;
if (word.isEmpty())
@@ -273,10 +274,60 @@ public static NonNullPair getEnglishPlural(String word) {
return new NonNullPair<>(word, false);
}
+ public record PluralResult(String updated, boolean plural) {
+
+ }
+
+ /**
+ * Returns whether a word is plural. If it is, {@code updated} contains the single variant of the word.
+ * Otherwise, {@code updated == word}.
+ *
+ * @param word The word to check.
+ * @return A pair with the updated word and a boolean indicating whether it was plural.
+ */
+ public static PluralResult isPlural(String word) {
+ Preconditions.checkNotNull(word, "word cannot be null");
+
+ if (word.isEmpty()) {
+ return new PluralResult("", false);
+ }
+
+ if (couldBeSingular(word)) {
+ return new PluralResult(word, false);
+ }
+
+ for (WordEnding ending : plurals) {
+ if (ending.isCompleteWord()) {
+ // Complete words shouldn't be used as partial pieces
+ if (word.length() != ending.plural().length()) {
+ continue;
+ }
+ }
+
+ if (word.endsWith(ending.plural())) {
+ return new PluralResult(
+ word.substring(0, word.length() - ending.plural().length()) + ending.singular(),
+ true
+ );
+ }
+
+ if (word.endsWith(ending.plural().toUpperCase(Locale.ENGLISH))) {
+ return new PluralResult(
+ word.substring(0, word.length() - ending.plural().length())
+ + ending.singular().toUpperCase(Locale.ENGLISH),
+ true
+ );
+ }
+ }
+
+ return new PluralResult(word, false);
+ }
+
private static boolean couldBeSingular(String word) {
- for (final WordEnding ending : plurals) {
+ for (WordEnding ending : plurals) {
if (ending.singular().isBlank())
continue;
+
if (ending.isCompleteWord() && ending.singular().length() != word.length())
continue; // Skip complete words
@@ -724,8 +775,8 @@ public static Class highestDenominator(Class<
assert classes.length > 0;
Class> chosen = classes[0];
outer:
- for (final Class> checking : classes) {
- assert checking != null && !checking.isArray() && !checking.isPrimitive() : checking;
+ for (Class> checking : classes) {
+ assert !checking.isArray() && !checking.isPrimitive() : "%s has no super".formatted(checking.getSimpleName());
if (chosen.isAssignableFrom(checking))
continue;
Class> superType = checking;
diff --git a/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java b/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java
index 2a89f4bd0e2..585daf80802 100644
--- a/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java
+++ b/src/main/java/org/skriptlang/skript/common/function/DefaultFunctionImpl.java
@@ -3,6 +3,7 @@
import ch.njol.skript.lang.function.FunctionEvent;
import ch.njol.skript.lang.function.Signature;
import com.google.common.base.Preconditions;
+import org.enginehub.piston.converter.MapArgumentConverter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -16,7 +17,7 @@
final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function implements DefaultFunction {
private final SkriptAddon source;
- private final Parameter>[] parameters;
+ private final SequencedMap> parameters;
private final Function execute;
private final List description;
@@ -27,14 +28,15 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function
DefaultFunctionImpl(
SkriptAddon source,
- String name, Parameter>[] parameters,
+ String name,
+ SequencedMap> parameters,
Class returnType, boolean single,
@Nullable ch.njol.skript.util.Contract contract,
Function execute,
String[] description, String[] since, String[] examples,
String[] keywords, String[] requires
) {
- super(new Signature<>(null, name, parameters, returnType, single, contract));
+ super(new Signature<>(null, name, parameters.values().toArray(new Parameter[0]), returnType, single, contract));
Preconditions.checkNotNull(source, "source cannot be null");
Preconditions.checkNotNull(name, "name cannot be null");
@@ -56,10 +58,11 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function
public T @Nullable [] execute(FunctionEvent> event, Object[][] params) {
Map args = new LinkedHashMap<>();
- int length = Math.min(parameters.length, params.length);
+ int length = Math.min(parameters.size(), params.length);
+ Parameter>[] arrayParams = parameters.values().toArray(new Parameter[0]);
for (int i = 0; i < length; i++) {
Object[] arg = params[i];
- Parameter> parameter = parameters[i];
+ Parameter> parameter = arrayParams[i];
if (arg == null || arg.length == 0) {
if (parameter.hasModifier(Modifier.OPTIONAL)) {
@@ -100,6 +103,17 @@ final class DefaultFunctionImpl extends ch.njol.skript.lang.function.Function
}
}
+ @Override
+ public T execute(FunctionEvent> event, FunctionArguments arguments) {
+ for (String name : arguments.names()) {
+ if (arguments.get(name) == null && !parameters.get(name).hasModifier(Modifier.OPTIONAL)) {
+ return null;
+ }
+ }
+
+ return execute.apply(arguments);
+ }
+
@Override
public boolean resetReturnValue() {
return true;
@@ -150,7 +164,7 @@ static class BuilderImpl implements DefaultFunctionImpl.Builder {
private final SkriptAddon source;
private final String name;
private final Class returnType;
- private final Map> parameters = new LinkedHashMap<>();
+ private final SequencedMap> parameters = new LinkedHashMap<>();
private ch.njol.skript.util.Contract contract = null;
@@ -236,7 +250,7 @@ public Builder parameter(@NotNull String name, @NotNull Class> type, Modifi
public DefaultFunction build(@NotNull Function execute) {
Preconditions.checkNotNull(execute, "execute cannot be null");
- return new DefaultFunctionImpl<>(source, name, parameters.values().toArray(new Parameter[0]),
+ return new DefaultFunctionImpl<>(source, name, parameters,
returnType, !returnType.isArray(), contract, execute,
description, since, examples, keywords, requires);
}
diff --git a/src/main/java/org/skriptlang/skript/common/function/Function.java b/src/main/java/org/skriptlang/skript/common/function/Function.java
index 4b121d92943..a7f56afb48f 100644
--- a/src/main/java/org/skriptlang/skript/common/function/Function.java
+++ b/src/main/java/org/skriptlang/skript/common/function/Function.java
@@ -1,9 +1,11 @@
package org.skriptlang.skript.common.function;
+import ch.njol.skript.lang.function.FunctionEvent;
import org.jetbrains.annotations.ApiStatus.Experimental;
import org.jetbrains.annotations.ApiStatus.Internal;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
/**
* Represents a function implementation.
@@ -16,9 +18,22 @@
@Experimental
public interface Function {
+ /**
+ * Executes this function with the given parameters.
+ *
+ * @param event The event that is associated with this function execution.
+ * @param arguments The arguments to execute the function with.
+ * @return The return value.
+ */
+ T execute(FunctionEvent> event, FunctionArguments arguments);
+
/**
* @return The signature belonging to this function.
*/
@NotNull Signature signature();
+ boolean resetReturnValue();
+
+ @NotNull String @Nullable [] returnedKeys();
+
}
diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionArgumentParser.java b/src/main/java/org/skriptlang/skript/common/function/FunctionArgumentParser.java
new file mode 100644
index 00000000000..4d884235d9d
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/common/function/FunctionArgumentParser.java
@@ -0,0 +1,239 @@
+package org.skriptlang.skript.common.function;
+
+import org.skriptlang.skript.common.function.FunctionReference.Argument;
+import org.skriptlang.skript.common.function.FunctionReference.ArgumentType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Parses the arguments of a function reference.
+ */
+final class FunctionArgumentParser {
+
+ /**
+ * The input string.
+ */
+ private final String args;
+
+ /**
+ * The list of arguments that have been found so far.
+ */
+ private final List> arguments = new ArrayList<>();
+
+ /**
+ * Constructs a new function argument parser based on the
+ * input string and instantly calculates the result.
+ *
+ * @param args The input string.
+ */
+ public FunctionArgumentParser(String args) {
+ this.args = args;
+
+ parse();
+ }
+
+ /**
+ * The char index.
+ */
+ private int index = 0;
+
+ /**
+ * The current character.
+ */
+ private char c;
+
+ /**
+ * Whether the current argument being parsed starts with a name declaration.
+ */
+ private boolean nameFound = false;
+
+ /**
+ * A builder which keeps track of the name part of an argument.
+ *
+ * This builder may contain a part of the expression at the start of parsing an argument,
+ * when it is unclear whether we are currently parsing a name or not. On realization that
+ * this argument does not have a name, its contents are cleared.
+ *
+ */
+ private final StringBuilder namePart = new StringBuilder();
+
+ /**
+ * A builder which keeps track of the expression part of an argument.
+ *
+ * This builder may contain a part of the name at the start of parsing an argument,
+ * when it is unclear whether we are currently parsing a name or not. On realization that
+ * this argument has a name, its contents are cleared.
+ *
+ */
+ private final StringBuilder exprPart = new StringBuilder();
+
+ /**
+ * Whether we are currently in a string or not.
+ *
+ * To avoid parsing a comma in a string as the start of a new argument, we keep track of whether we're
+ * in a string or not to ignore commas found in strings.
+ * A new argument can only start when {@code nesting == 0 && !inString}.
+ *
+ */
+ private boolean inString = false;
+
+ /**
+ * The level of nesting we are currently in.
+ *
+ * The nesting level is increased when entering special expressions which may contain commas,
+ * thereby avoiding incorrectly parsing a comma in variables or parentheses as the start of a new argument.
+ * A new argument can only start when {@code nesting == 0 && !inString}.
+ *
+ */
+ private int nesting = 0;
+
+ /**
+ * Parses the input string into arguments.
+ *
+ * For every argument, during the parsing of the first few characters, one of the following things occurs.
+ *
+ *
A legal parameter name character is encountered. The character is added to {@link #namePart} and
+ * {@link #exprPart}.
+ *
An illegal parameter name character is encountered. This means that the previous data added to {@link #namePart}
+ * cannot be a name. {@link #namePart} is cleared and the rest of the argument is parsed as the expression.
+ *
A colon {@code :} is encountered. When all previous characters for this argument match the requirements
+ * for a parameter name, the name is stored in {@link #namePart} and the rest of the argument is parsed as the expression.
+ *
A comma {@code ,} is encountered. This means that the end of the argument has been reached. If no name was found,
+ * the entire argument is parsed as {@link #exprPart}. If a name was found, {@link #exprPart} gets stored alongside {@link #namePart}.
+ *
+ *
+ */
+ private void parse() {
+ // if we have no args to parse, give up instantly
+ if (args.isEmpty()) {
+ return;
+ }
+
+ while (index < args.length()) {
+ c = args.charAt(index);
+
+ // first try to compile the name
+ if (!nameFound) {
+ // if a name matches the legal characters, update name part
+ if (c == '_' || Character.isLetterOrDigit(c)) {
+ namePart.append(c);
+ exprPart.append(c);
+ index++;
+ continue;
+ }
+
+ // then if we have a name, start parsing the second part
+ if (nesting == 0 && c == ':' && !namePart.isEmpty()) {
+ exprPart.setLength(0);
+ index++;
+ nameFound = true;
+ continue;
+ }
+
+ if (isSpecialCharacter(ArgumentType.UNNAMED)) {
+ continue;
+ }
+
+ // given that the character did not match the legal name chars, reset name
+ namePart.setLength(0);
+ nextExpr();
+ continue;
+ }
+
+ if (isSpecialCharacter(ArgumentType.NAMED)) {
+ continue;
+ }
+
+ nextExpr(); // add to expression
+ }
+
+ // make sure to save the last argument
+ if (nameFound) {
+ save(ArgumentType.NAMED);
+ } else {
+ save(ArgumentType.UNNAMED);
+ }
+ }
+
+ /**
+ * Manages special character handling by updating the {@link #nesting} and {@link #inString} variables.
+ *
+ * @param type The type of argument that is currently being parsed.
+ * @return True when {@link #c} is a special character, false if not.
+ */
+ private boolean isSpecialCharacter(ArgumentType type) {
+ // for strings
+ if (!inString && c == '"') {
+ nesting++;
+ inString = true;
+ nextExpr();
+ return true;
+ }
+
+ if (inString && c == '"'
+ && index < args.length() - 1 && args.charAt(index + 1) != '"') { // allow double string char in strings
+ nesting--;
+ inString = false;
+ nextExpr();
+ return true;
+ }
+
+ if (c == '(' || c == '{') {
+ nesting++;
+ nextExpr();
+ return true;
+ }
+
+ if (c == ')' || c == '}') {
+ nesting--;
+ nextExpr();
+ return true;
+ }
+
+ if (nesting == 0 && c == ',') {
+ save(type);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Moves the parser to the next part of the expression that is being parsed.
+ */
+ private void nextExpr() {
+ exprPart.append(c);
+ index++;
+ }
+
+ /**
+ * Saves the string parts stored in {@link #exprPart} (and optionally {@link #namePart}) as a new argument in
+ * {@link #arguments}. Then, all data for the current argument is cleared.
+ *
+ * @param type The type of argument to save as.
+ */
+ private void save(ArgumentType type) {
+ if (type == ArgumentType.UNNAMED) {
+ arguments.add(new Argument<>(ArgumentType.UNNAMED, null, exprPart.toString().trim()));
+ } else {
+ arguments.add(new Argument<>(ArgumentType.NAMED, namePart.toString().trim(), exprPart.toString().trim()));
+ }
+
+ namePart.setLength(0);
+ exprPart.setLength(0);
+ index++;
+ nameFound = false;
+ }
+
+ /**
+ * Returns all arguments.
+ *
+ * @return All arguments.
+ */
+ public Argument[] getArguments() {
+ //noinspection unchecked
+ return (Argument[]) arguments.toArray(new Argument[0]);
+ }
+
+}
diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java b/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java
new file mode 100644
index 00000000000..4b8b5ab110e
--- /dev/null
+++ b/src/main/java/org/skriptlang/skript/common/function/FunctionReference.java
@@ -0,0 +1,351 @@
+package org.skriptlang.skript.common.function;
+
+import ch.njol.skript.Skript;
+import ch.njol.skript.classes.ClassInfo;
+import ch.njol.skript.expressions.ExprBlockSound.SoundType;
+import ch.njol.skript.expressions.ExprKeyed;
+import ch.njol.skript.lang.*;
+import ch.njol.skript.lang.function.*;
+import ch.njol.skript.lang.function.FunctionRegistry.Retrieval;
+import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult;
+import ch.njol.skript.localization.Language;
+import ch.njol.skript.registrations.Classes;
+import ch.njol.skript.util.LiteralUtils;
+import com.google.common.base.Preconditions;
+import org.bukkit.Bukkit;
+import org.bukkit.event.Event;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.skriptlang.skript.common.function.Parameter.Modifier;
+
+import java.util.*;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+
+/**
+ * A reference to a {@link Function} found in a script.
+ *
+ * @param The return type of this reference.
+ */
+public final class FunctionReference implements Debuggable {
+
+ private final String namespace;
+ private final String name;
+ private final Signature signature;
+ private final Argument>[] arguments;
+
+ private Function cachedFunction;
+ private LinkedHashMap cachedArguments;
+
+ private record ArgInfo(Expression> expression, Class> type, Set modifiers) {
+
+ }
+
+ public FunctionReference(@Nullable String namespace,
+ @NotNull String name,
+ @NotNull Signature signature,
+ @NotNull Argument>[] arguments) {
+ Preconditions.checkNotNull(name, "name cannot be null");
+ Preconditions.checkNotNull(signature, "signature cannot be null");
+ Preconditions.checkNotNull(arguments, "arguments cannot be null");
+
+ this.namespace = namespace;
+ this.name = name;
+ this.signature = signature;
+ this.arguments = arguments;
+ }
+
+ /**
+ * Validates this function reference.
+ *
+ * @return True if this is a valid function reference, false if not.
+ */
+ public boolean validate() {
+ if (signature == null) {
+ return false;
+ }
+
+ if (cachedArguments == null) {
+ cachedArguments = new LinkedHashMap<>();
+
+ // mixing arguments is only allowed when the order of arguments matches param order
+ boolean mix = Arrays.stream(arguments)
+ .map(it -> it.type)
+ .collect(Collectors.toSet()).size() == ArgumentType.values().length;
+
+ // get the target params of the function
+ SequencedMap> targetParameters = new LinkedHashMap<>(signature.parameters());
+
+ for (Argument> argument : arguments) {
+ Parameter> target;
+ if (argument.type == ArgumentType.NAMED) {
+ target = targetParameters.get(argument.name);
+ } else {
+ Entry> first = targetParameters.entrySet().iterator().next();
+
+ if (first == null) {
+ return false;
+ }
+
+ target = first.getValue();
+ }
+
+ if (target == null) {
+ return false;
+ }
+
+ // try to parse value in the argument
+ Class> conversionTarget;
+ if (target.type().isArray()) {
+ conversionTarget = target.type().componentType();
+ } else {
+ conversionTarget = target.type();
+ }
+
+ //noinspection unchecked
+ Expression> converted = argument.value.getConvertedExpression(conversionTarget);
+
+ // failed to parse value
+ if (!validateArgument(target, argument.value, converted)) {
+ return false;
+ }
+
+ if (mix && !targetParameters.entrySet().iterator().next().getKey().equals(target.name())) {
+ Skript.error(Language.get("functions.mixing named and unnamed arguments"));
+ return false;
+ }
+
+ // all good
+ cachedArguments.put(target.name(), new ArgInfo(converted, target.type(), target.modifiers()));
+ targetParameters.remove(target.name());
+ }
+ }
+
+ signature.addCall(this);
+
+ return true;
+ }
+
+ private boolean validateArgument(Parameter> target, Expression> original, Expression> converted) {
+ if (converted == null) {
+ if (LiteralUtils.hasUnparsedLiteral(original)) {
+ Skript.error("Can't understand this expression: %s", original);
+ } else {
+ Skript.error("Expected type %s for argument '%s', but %s is of type %s.",
+ getName(target.type(), target.single()), target.name(), original, getName(original.getReturnType(), original.isSingle()));
+ }
+ return false;
+ }
+
+ if (target.single() && !converted.isSingle()) {
+ Skript.error("Expected type %s for argument '%s', but %s is of type %s.",
+ getName(target.type(), target.single()), target.name(), converted, getName(converted.getReturnType(), converted.isSingle()));
+ return false;
+ }
+
+ return true;
+ }
+
+ private String getName(Class> clazz, boolean single) {
+ if (single) {
+ return Classes.getSuperClassInfo(clazz).getName().getSingular();
+ } else {
+ if (clazz.isArray()) {
+ return Classes.getSuperClassInfo(clazz.componentType()).getName().getPlural();
+ }
+ return Classes.getSuperClassInfo(clazz).getName().getPlural();
+ }
+ }
+
+ /**
+ * Executes the function referred to by this reference.
+ *
+ * @param event The event to use for execution.
+ * @return The return value of the function.
+ */
+ public T execute(Event event) {
+ if (!validate()) {
+ Skript.error("Failed to verify function %s before execution.", name);
+ return null;
+ }
+
+ LinkedHashMap args = new LinkedHashMap<>();
+ cachedArguments.forEach((k, v) -> {
+ if (v.modifiers().contains(Modifier.KEYED)) {
+ args.put(k, evaluateKeyed(v.expression(), event));
+ return;
+ }
+
+ if (!v.type().isArray()) {
+ args.put(k, v.expression().getSingle(event));
+ } else {
+ args.put(k, v.expression().getArray(event));
+ }
+ });
+
+ Function function = function();
+ FunctionEvent> fnEvent = new FunctionEvent<>(function);
+
+ if (Functions.callFunctionEvents)
+ Bukkit.getPluginManager().callEvent(fnEvent);
+
+ return function.execute(fnEvent, new FunctionArgumentsImpl(args));
+ }
+
+ private KeyedValue>[] evaluateKeyed(Expression> expression, Event event) {
+ if (expression instanceof ExpressionList> list) {
+ return evaluateSingleListParameter(list.getExpressions(), event);
+ }
+ return evaluateParameter(expression, event);
+ }
+
+ private KeyedValue>[] evaluateSingleListParameter(Expression>[] parameters, Event event) {
+ List