From 125d1159a4e9c454994f001702027bc164098537 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:54:00 +0200 Subject: [PATCH 01/14] init commit --- .../njol/skript/sections/ExprSecFunction.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/main/java/ch/njol/skript/sections/ExprSecFunction.java diff --git a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java new file mode 100644 index 00000000000..da173c322da --- /dev/null +++ b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java @@ -0,0 +1,53 @@ +package ch.njol.skript.sections; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Name; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +@Name("Function Section") +@Description(""" + Runs a function with the specified arguments. + """) +public class ExprSecFunction extends SectionExpression { + + static { + Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "<.+> with argument[s]"); + } + + @Override + public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems) { + return false; + } + + @Override + protected Object @Nullable [] get(Event event) { + return new Object[0]; + } + + @Override + public boolean isSingle() { + return false; + } + + @Override + public Class getReturnType() { + return null; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return ""; + } +} From 18ba81590984211954c481c4714b089a41d0c1a6 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Wed, 6 Aug 2025 22:19:58 +0200 Subject: [PATCH 02/14] use FunctionRegistry --- .../lang/function/FunctionRegistry.java | 17 +- .../njol/skript/sections/ExprSecFunction.java | 198 ++++++++++++++---- 2 files changed, 171 insertions(+), 44 deletions(-) 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 32d8f9409b1..ff422e22b77 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -4,21 +4,20 @@ import ch.njol.skript.SkriptAPIException; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; -import org.jetbrains.annotations.UnmodifiableView; +import org.jetbrains.annotations.*; 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; /** * A registry for functions. */ -final class FunctionRegistry implements Registry> { +@ApiStatus.Internal +public final class FunctionRegistry implements Registry> { private static FunctionRegistry registry; @@ -38,7 +37,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. @@ -147,7 +146,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 + "'"); } @@ -211,7 +210,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. @@ -257,7 +256,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 diff --git a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java index da173c322da..a353ee466e3 100644 --- a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java +++ b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java @@ -1,53 +1,181 @@ package ch.njol.skript.sections; import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; +import ch.njol.skript.config.SimpleNode; import ch.njol.skript.doc.Description; import ch.njol.skript.doc.Name; import ch.njol.skript.expressions.base.SectionExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.*; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.function.Function; +import ch.njol.skript.lang.function.FunctionRegistry; +import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; +import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; +import ch.njol.skript.lang.function.Parameter; +import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.skript.registrations.Classes; import ch.njol.util.Kleenean; +import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; -import java.util.List; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; @Name("Function Section") @Description(""" - Runs a function with the specified arguments. - """) + Runs a function with the specified arguments. + """) public class ExprSecFunction extends SectionExpression { - static { - Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "<.+> with argument[s]"); - } - - @Override - public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, - @Nullable SectionNode node, @Nullable List triggerItems) { - return false; - } - - @Override - protected Object @Nullable [] get(Event event) { - return new Object[0]; - } - - @Override - public boolean isSingle() { - return false; - } - - @Override - public Class getReturnType() { - return null; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return ""; - } + private static final String AMBIGUOUS_ERROR = + "Skript cannot determine which function named '%s' to call. " + + "The following functions were matched: %s. " + + "Try clarifying the type of the arguments using the 'value within' expression."; + + /** + * The pattern for a valid function name. + * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. + */ + private final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile("[A-z_][A-z_0-9]*"); + + /** + * The pattern for an argument that can be passed in the children of this section. + */ + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); + + static { + Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "function <.+> with argument[s]"); + } + + private Function function; + private final LinkedHashMap> arguments = new LinkedHashMap<>(); + + @Override + public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems) { + if (node == null) { + Skript.error("A section must follow this expression."); + return false; + } else if (node.isEmpty()) { + Skript.error("A function section must contain code."); + return false; + } + + for (Node n : node) { + if (!(n instanceof SimpleNode) || n.getKey() == null) { + Skript.error("Invalid argument declaration for a function section: ", n.getKey()); + return false; + } + + Matcher matcher = ARGUMENT_PATTERN.matcher(n.getKey()); + if (!matcher.matches()) { + Skript.error("Invalid argument declaration for a function section: ", n.getKey()); + return false; + } + + String parameterName = matcher.group("name"); + String value = matcher.group("value"); + + Expression expression = new SkriptParser(value, SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) + .parseExpression(Object.class); + + if (expression == null) { + Skript.error("Invalid argument in argument declaration for a function section: ", value); + return false; + } + + arguments.put(parameterName, expression); + } + + String namespace = ParserInstance.get().getCurrentScript().getConfig().getFileName(); + String name = result.regexes.get(0).group(); + + if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { + Skript.error("The function %s() does not exist.".formatted(name)); + return false; + } + + Class[] types = arguments.values().stream().map(Expression::getReturnType).toArray(Class[]::new); + + Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, types); + if (retrieval.result() == RetrievalResult.NOT_REGISTERED) { + Skript.error("The function %s() does not exist.".formatted(name)); + return false; + } else if (retrieval.result() == RetrievalResult.AMBIGUOUS) { + List conflicts = new ArrayList<>(); + for (Class[] classes : retrieval.conflictingArgs()) { + conflicts.add("%s(%s)".formatted(name, Arrays.stream(classes) + .map(Classes::getExactClassInfo) + .filter(Objects::nonNull) + .map(ClassInfo::getCodeName) + .collect(Collectors.joining(", ")))); + } + + Skript.error(AMBIGUOUS_ERROR.formatted(name, StringUtils.join(conflicts, ",", " and "))); + return false; + } + + function = retrieval.retrieved(); + + LinkedHashMap> parameters = Arrays.stream(function.getParameters()).collect(Collectors.toMap( + Parameter::getName, + p -> p, + (a, b) -> b, + LinkedHashMap::new + )); + + for (String key : arguments.keySet()) { + arguments.computeIfPresent(key, (s, expression) -> { + Class c = parameters.get(s).getType().getC(); + + //noinspection unchecked + return expression.getConvertedExpression(c); + }); + } + + return true; + } + + @Override + protected Object @Nullable [] get(Event event) { + Object[][] args = new Object[arguments.size()][]; + int i = 0; + for (Expression value : arguments.values()) { + args[i] = value.getArray(event); + i++; + } + + return function.execute(args); + } + + @Override + public boolean isSingle() { + return function.isSingle(); + } + + @Override + public boolean isSectionOnly() { + return true; + } + + @Override + public Class getReturnType() { + return function.getReturnType() != null ? function.getReturnType().getC() : null; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("run function") + .append(function.getName()) + .append("with arguments") + .toString(); + } + } From e1dcf5d2c3217602c2ada9207552306ac97d69c1 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Wed, 6 Aug 2025 23:40:21 +0200 Subject: [PATCH 03/14] init commit --- .../lang/function/FunctionRegistry.java | 43 +++++- .../njol/skript/sections/ExprSecFunction.java | 129 ++++++++++++------ .../syntaxes/sections/ExprSecFunction.sk | 34 +++++ 3 files changed, 162 insertions(+), 44 deletions(-) create mode 100644 src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk 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 ff422e22b77..eac08ddb747 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -394,6 +394,37 @@ Retrieval> getExactSignature( return attempt; } + /** + * Gets every signature with the name {@code name}. + * This includes global functions and, if {@code namespace} is not null, functions under that namespace (if valid). + * @param namespace The additional namespace to obtain signatures from. + * Usually represents the path of the script this function is registered in. + * @param name The name of the signature(s) to obtain. + * @return A list of all signatures named {@code name}. + */ + public @Unmodifiable @NotNull Set> getSignatures(@Nullable String namespace, @NotNull String name) { + Preconditions.checkNotNull(name, "name cannot be null"); + + Map> total = new HashMap<>(); + + // obtain all local functions of "name" + if (namespace != null) { + 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)); + } + } + + // 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()); + } + /** * Gets the signature for a function with the given name and arguments. * @@ -469,6 +500,9 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp // 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; } @@ -494,13 +528,20 @@ private Retrieval> getSignature(@NotNull NamespaceIdentifier namesp candidateType = candidate.args[i]; } + Class providedType; + if (provided.args[i].isArray()) { + providedType = provided.args[i].componentType(); + } else { + providedType = provided.args[i]; + } + Class providedArg = provided.args[i]; if (exact) { if (providedArg != candidateType) { continue candidates; } } else { - if (!Converters.converterExists(providedArg, candidateType)) { + if (!Converters.converterExists(providedType, candidateType)) { continue candidates; } } diff --git a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java index a353ee466e3..41be68ef914 100644 --- a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java +++ b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java @@ -1,7 +1,6 @@ package ch.njol.skript.sections; import ch.njol.skript.Skript; -import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.config.Node; import ch.njol.skript.config.SectionNode; import ch.njol.skript.config.SimpleNode; @@ -15,14 +14,16 @@ import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; import ch.njol.skript.lang.function.Parameter; +import ch.njol.skript.lang.function.Signature; import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; -import ch.njol.util.StringUtils; import org.bukkit.event.Event; import org.jetbrains.annotations.Nullable; import java.util.*; +import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -54,7 +55,7 @@ public class ExprSecFunction extends SectionExpression { } private Function function; - private final LinkedHashMap> arguments = new LinkedHashMap<>(); + private LinkedHashMap> arguments = null; @Override public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, @@ -67,6 +68,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, return false; } + LinkedHashMap args = new LinkedHashMap<>(); for (Node n : node) { if (!(n instanceof SimpleNode) || n.getKey() == null) { Skript.error("Invalid argument declaration for a function section: ", n.getKey()); @@ -79,18 +81,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, return false; } - String parameterName = matcher.group("name"); - String value = matcher.group("value"); - - Expression expression = new SkriptParser(value, SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) - .parseExpression(Object.class); - - if (expression == null) { - Skript.error("Invalid argument in argument declaration for a function section: ", value); - return false; - } - - arguments.put(parameterName, expression); + args.put(matcher.group("name"), matcher.group("value")); } String namespace = ParserInstance.get().getCurrentScript().getConfig().getFileName(); @@ -101,45 +92,97 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, return false; } - Class[] types = arguments.values().stream().map(Expression::getReturnType).toArray(Class[]::new); + // todo use FunctionParser + function = findFunction(namespace, name, args); - Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, types); - if (retrieval.result() == RetrievalResult.NOT_REGISTERED) { - Skript.error("The function %s() does not exist.".formatted(name)); + if (function == null || arguments == null || arguments.isEmpty()) { + doesNotExist(name, args); return false; - } else if (retrieval.result() == RetrievalResult.AMBIGUOUS) { - List conflicts = new ArrayList<>(); - for (Class[] classes : retrieval.conflictingArgs()) { - conflicts.add("%s(%s)".formatted(name, Arrays.stream(classes) - .map(Classes::getExactClassInfo) - .filter(Objects::nonNull) - .map(ClassInfo::getCodeName) - .collect(Collectors.joining(", ")))); + } + + return true; + } + + /** + * Attempts to find the function to execute given the arguments. + * + * @param namespace The current script. + * @param name The name of the function. + * @param args The passed arguments. + * @return The function given the arguments, or null if no function is found. + */ + private Function findFunction(String namespace, String name, LinkedHashMap args) { + signatures: + for (Signature signature : FunctionRegistry.getRegistry().getSignatures(namespace, name)) { + LinkedHashMap> arguments = new LinkedHashMap<>(); + + LinkedHashMap> parameters = Arrays.stream(signature.getParameters()) + .collect(Collectors.toMap(Parameter::getName, p -> p, (a, b) -> b, LinkedHashMap::new)); + for (Entry entry : args.entrySet()) { + Parameter parameter = parameters.get(entry.getKey()); + + if (parameter == null) { + continue signatures; + } + + //noinspection unchecked + Expression expression = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) + .parseExpression(parameter.getType().getC()); + + if (expression == null) { + continue signatures; + } + + arguments.put(entry.getKey(), expression); } - Skript.error(AMBIGUOUS_ERROR.formatted(name, StringUtils.join(conflicts, ",", " and "))); - return false; + Class[] signatureArgs = Arrays.stream(signature.getParameters()) + .map(it -> { + if (it.isSingleValue()) { + return it.getType().getC(); + } else { + return it.getType().getC().arrayType(); + } + }) + .toArray(Class[]::new); + + Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, signatureArgs); + if (retrieval.result() == RetrievalResult.EXACT) { + this.arguments = arguments; + return retrieval.retrieved(); + } } - function = retrieval.retrieved(); + return null; + } + + /** + * Prints the error for when a function does not exist. + * + * @param name The function name. + * @param arguments The passed arguments to the function call. + */ + private void doesNotExist(String name, LinkedHashMap arguments) { + StringJoiner joiner = new StringJoiner(", "); - LinkedHashMap> parameters = Arrays.stream(function.getParameters()).collect(Collectors.toMap( - Parameter::getName, - p -> p, - (a, b) -> b, - LinkedHashMap::new - )); + for (Map.Entry entry : arguments.entrySet()) { + SkriptParser parser = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT); - for (String key : arguments.keySet()) { - arguments.computeIfPresent(key, (s, expression) -> { - Class c = parameters.get(s).getType().getC(); + Expression expression = LiteralUtils.defendExpression(parser.parseExpression(Object.class)); - //noinspection unchecked - return expression.getConvertedExpression(c); - }); + if (expression == null) { + joiner.add("?"); + continue; + } + + if (expression.isSingle()) { + joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getSingular()); + } else { + joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getPlural()); + } } - return true; + Skript.error("The function %s(%s) does not exist.", name, joiner); } @Override diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk new file mode 100644 index 00000000000..4021a27b834 --- /dev/null +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -0,0 +1,34 @@ +local function esf(x: int):: int: + return 1 + +#local function esf(x: string):: int: +# return 2 + +#local function esf(x: ints):: int: +# return 3 + +local function esf_two(x: int, y: int):: int: + return 4 + +test "function section": + set {_x} to function esf with arguments: + x set to 1 + assert {_x} = 1 + + #set {_x} to function esf with arguments: + # x set to "hey" + #assert {_x} = 2 + + #set {_x} to function esf with arguments: + # x set to 1 and 2 + #assert {_x} = 3 + + parse: + function esf with arguments: + x set to firework + assert first element of last parse logs is set + + #parse: + # function esf with arguments: + # x set to {_y} + #assert first element of last parse logs is set From d82ac87c8ac9a7f81324943329ece4af2afbdc52 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:53:31 +0200 Subject: [PATCH 04/14] requested changes --- .../njol/skript/sections/ExprSecFunction.java | 66 +++++++++++++------ .../syntaxes/sections/ExprSecFunction.sk | 29 +++++++- 2 files changed, 72 insertions(+), 23 deletions(-) diff --git a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java index 41be68ef914..6b957dbaa4b 100644 --- a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java +++ b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java @@ -5,7 +5,9 @@ import ch.njol.skript.config.SectionNode; import ch.njol.skript.config.SimpleNode; import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; import ch.njol.skript.expressions.base.SectionExpression; import ch.njol.skript.lang.*; import ch.njol.skript.lang.SkriptParser.ParseResult; @@ -32,13 +34,19 @@ @Description(""" Runs a function with the specified arguments. """) +@Example(""" + local function multiply(x: number, y: number) returns number: + return {_x} * {_y} + + set {_x} to function multiply with arguments: + x as 2 + y as 3 + + broadcast "%{_x}%" # returns 6 + """) +@Since("INSERT VERSION") public class ExprSecFunction extends SectionExpression { - private static final String AMBIGUOUS_ERROR = - "Skript cannot determine which function named '%s' to call. " + - "The following functions were matched: %s. " + - "Try clarifying the type of the arguments using the 'value within' expression."; - /** * The pattern for a valid function name. * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. @@ -48,10 +56,10 @@ public class ExprSecFunction extends SectionExpression { /** * The pattern for an argument that can be passed in the children of this section. */ - private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(?:the )?argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); static { - Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "function <.+> with argument[s]"); + Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "[the] function <.+> with [the] arg[ument][s]"); } private Function function; @@ -60,11 +68,10 @@ public class ExprSecFunction extends SectionExpression { @Override public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, @Nullable SectionNode node, @Nullable List triggerItems) { - if (node == null) { - Skript.error("A section must follow this expression."); - return false; - } else if (node.isEmpty()) { - Skript.error("A function section must contain code."); + assert node != null; + + if (node.isEmpty()) { + Skript.error("A function section must contain arguments."); return false; } @@ -88,7 +95,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, String name = result.regexes.get(0).group(); if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { - Skript.error("The function %s() does not exist.".formatted(name)); + Skript.error("The function %s does not exist.", name); return false; } @@ -100,6 +107,11 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, return false; } + if (function.getReturnType() == null) { + Skript.error("The function %s does not return anything.", name); + return false; + } + return true; } @@ -129,7 +141,7 @@ private Function findFunction(String namespace, String name, LinkedHashMap expression = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) .parseExpression(parameter.getType().getC()); - if (expression == null) { + if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { continue signatures; } @@ -170,8 +182,8 @@ private void doesNotExist(String name, LinkedHashMap arguments) Expression expression = LiteralUtils.defendExpression(parser.parseExpression(Object.class)); - if (expression == null) { - joiner.add("?"); + if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { + joiner.add(entry.getKey() + ": ?"); continue; } @@ -187,14 +199,28 @@ private void doesNotExist(String name, LinkedHashMap arguments) @Override protected Object @Nullable [] get(Event event) { - Object[][] args = new Object[arguments.size()][]; + if (function == null) { + return null; + } + + Object[][] args = new Object[function.getParameters().length][]; int i = 0; - for (Expression value : arguments.values()) { - args[i] = value.getArray(event); + for (Parameter value : function.getParameters()) { + Expression expression = arguments.get(value.getName()); + + if (expression == null) { + return null; + } + + args[i] = expression.getArray(event); i++; } - return function.execute(args); + try { + return function.execute(args); + } finally { + function.resetReturnValue(); + } } @Override diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index 4021a27b834..8aeb6664c1e 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -10,6 +10,9 @@ local function esf(x: int):: int: local function esf_two(x: int, y: int):: int: return 4 +local function esf_void(x: int): + stop + test "function section": set {_x} to function esf with arguments: x set to 1 @@ -24,11 +27,31 @@ test "function section": #assert {_x} = 3 parse: - function esf with arguments: + set {_x} to function esf with arguments: x set to firework - assert first element of last parse logs is set + assert first element of last parse logs contains "The function esf(x: item type) does not exist" #parse: - # function esf with arguments: + # set {_x} to function esf with arguments: # x set to {_y} #assert first element of last parse logs is set + + parse: + set {_x} to function esf with arguments: + x set to agadsfg + assert first element of last parse logs contains "The function esf(x: ?) does not exist" + + set {_x} to function esf_two with arguments: + x set to 1 + y set to 2 + assert {_x} = 4 + + set {_x} to function esf_two with arguments: + y set to 2 + x set to 1 + assert {_x} = 4 + + parse: + set {_x} to function esf_void with arguments: + x set to 1 + assert first element of last parse logs contains "The function esf_void does not return anything" From 29dd1a1617cc6f934b315c2de3cfb5de69868083 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Thu, 7 Aug 2025 15:22:00 +0200 Subject: [PATCH 05/14] fix variables --- .../njol/skript/sections/ExprSecFunction.java | 333 +++++++++--------- .../syntaxes/sections/ExprSecFunction.sk | 19 +- 2 files changed, 181 insertions(+), 171 deletions(-) diff --git a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java index 6b957dbaa4b..bffb6ad0c6c 100644 --- a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java +++ b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java @@ -32,8 +32,8 @@ @Name("Function Section") @Description(""" - Runs a function with the specified arguments. - """) + Runs a function with the specified arguments. + """) @Example(""" local function multiply(x: number, y: number) returns number: return {_x} * {_y} @@ -47,165 +47,166 @@ local function multiply(x: number, y: number) returns number: @Since("INSERT VERSION") public class ExprSecFunction extends SectionExpression { - /** - * The pattern for a valid function name. - * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. - */ - private final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile("[A-z_][A-z_0-9]*"); + /** + * The pattern for a valid function name. + * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. + */ + private final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile("[A-z_][A-z_0-9]*"); - /** - * The pattern for an argument that can be passed in the children of this section. - */ - private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(?:the )?argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); + /** + * The pattern for an argument that can be passed in the children of this section. + */ + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(?:the )?argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); - static { - Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "[the] function <.+> with [the] arg[ument][s]"); - } + static { + Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "[the] function <.+> with [the] arg[ument][s]"); + } - private Function function; - private LinkedHashMap> arguments = null; + private Function function; + private LinkedHashMap> arguments = null; - @Override - public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, - @Nullable SectionNode node, @Nullable List triggerItems) { + @Override + public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems) { assert node != null; - if (node.isEmpty()) { - Skript.error("A function section must contain arguments."); - return false; - } + if (node.isEmpty()) { + Skript.error("A function section must contain arguments."); + return false; + } - LinkedHashMap args = new LinkedHashMap<>(); - for (Node n : node) { - if (!(n instanceof SimpleNode) || n.getKey() == null) { - Skript.error("Invalid argument declaration for a function section: ", n.getKey()); - return false; - } + LinkedHashMap args = new LinkedHashMap<>(); + for (Node n : node) { + if (!(n instanceof SimpleNode) || n.getKey() == null) { + Skript.error("Invalid argument declaration for a function section: ", n.getKey()); + return false; + } - Matcher matcher = ARGUMENT_PATTERN.matcher(n.getKey()); - if (!matcher.matches()) { - Skript.error("Invalid argument declaration for a function section: ", n.getKey()); - return false; - } + Matcher matcher = ARGUMENT_PATTERN.matcher(n.getKey()); + if (!matcher.matches()) { + Skript.error("Invalid argument declaration for a function section: ", n.getKey()); + return false; + } - args.put(matcher.group("name"), matcher.group("value")); - } + args.put(matcher.group("name"), matcher.group("value")); + } - String namespace = ParserInstance.get().getCurrentScript().getConfig().getFileName(); - String name = result.regexes.get(0).group(); + String namespace = ParserInstance.get().getCurrentScript().getConfig().getFileName(); + String name = result.regexes.get(0).group(); - if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { - Skript.error("The function %s does not exist.", name); - return false; - } + if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { + Skript.error("The function %s does not exist.", name); + return false; + } - // todo use FunctionParser - function = findFunction(namespace, name, args); + // todo use FunctionParser + function = findFunction(namespace, name, args); - if (function == null || arguments == null || arguments.isEmpty()) { - doesNotExist(name, args); - return false; - } + if (function == null || arguments == null || arguments.isEmpty()) { + doesNotExist(name, args); + return false; + } if (function.getReturnType() == null) { Skript.error("The function %s does not return anything.", name); return false; } - return true; - } - - /** - * Attempts to find the function to execute given the arguments. - * - * @param namespace The current script. - * @param name The name of the function. - * @param args The passed arguments. - * @return The function given the arguments, or null if no function is found. - */ - private Function findFunction(String namespace, String name, LinkedHashMap args) { - signatures: - for (Signature signature : FunctionRegistry.getRegistry().getSignatures(namespace, name)) { - LinkedHashMap> arguments = new LinkedHashMap<>(); - - LinkedHashMap> parameters = Arrays.stream(signature.getParameters()) - .collect(Collectors.toMap(Parameter::getName, p -> p, (a, b) -> b, LinkedHashMap::new)); - for (Entry entry : args.entrySet()) { - Parameter parameter = parameters.get(entry.getKey()); - - if (parameter == null) { - continue signatures; - } - - //noinspection unchecked - Expression expression = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) - .parseExpression(parameter.getType().getC()); - - if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { - continue signatures; - } - - arguments.put(entry.getKey(), expression); - } - - Class[] signatureArgs = Arrays.stream(signature.getParameters()) - .map(it -> { - if (it.isSingleValue()) { - return it.getType().getC(); - } else { - return it.getType().getC().arrayType(); - } - }) - .toArray(Class[]::new); - - Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, signatureArgs); - if (retrieval.result() == RetrievalResult.EXACT) { - this.arguments = arguments; - return retrieval.retrieved(); - } - } - - return null; - } - - /** - * Prints the error for when a function does not exist. - * - * @param name The function name. - * @param arguments The passed arguments to the function call. - */ - private void doesNotExist(String name, LinkedHashMap arguments) { - StringJoiner joiner = new StringJoiner(", "); - - for (Map.Entry entry : arguments.entrySet()) { - SkriptParser parser = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT); - - Expression expression = LiteralUtils.defendExpression(parser.parseExpression(Object.class)); - - if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { - joiner.add(entry.getKey() + ": ?"); - continue; - } - - if (expression.isSingle()) { - joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getSingular()); - } else { - joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getPlural()); - } - } - - Skript.error("The function %s(%s) does not exist.", name, joiner); - } - - @Override - protected Object @Nullable [] get(Event event) { + return true; + } + + /** + * Attempts to find the function to execute given the arguments. + * + * @param namespace The current script. + * @param name The name of the function. + * @param args The passed arguments. + * @return The function given the arguments, or null if no function is found. + */ + private Function findFunction(String namespace, String name, LinkedHashMap args) { + signatures: + for (Signature signature : FunctionRegistry.getRegistry().getSignatures(namespace, name)) { + LinkedHashMap> arguments = new LinkedHashMap<>(); + + LinkedHashMap> parameters = Arrays.stream(signature.getParameters()) + .collect(Collectors.toMap(Parameter::getName, p -> p, (a, b) -> b, LinkedHashMap::new)); + for (Entry entry : args.entrySet()) { + Parameter parameter = parameters.get(entry.getKey()); + + if (parameter == null) { + continue signatures; + } + + //noinspection unchecked + Expression expression = LiteralUtils.defendExpression( + new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) + .parseExpression(parameter.getType().getC())); + + if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { + continue signatures; + } + + arguments.put(entry.getKey(), expression); + } + + Class[] signatureArgs = Arrays.stream(signature.getParameters()) + .map(it -> { + if (it.isSingleValue()) { + return it.getType().getC(); + } else { + return it.getType().getC().arrayType(); + } + }) + .toArray(Class[]::new); + + Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, signatureArgs); + if (retrieval.result() == RetrievalResult.EXACT) { + this.arguments = arguments; + return retrieval.retrieved(); + } + } + + return null; + } + + /** + * Prints the error for when a function does not exist. + * + * @param name The function name. + * @param arguments The passed arguments to the function call. + */ + private void doesNotExist(String name, LinkedHashMap arguments) { + StringJoiner joiner = new StringJoiner(", "); + + for (Map.Entry entry : arguments.entrySet()) { + SkriptParser parser = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT); + + Expression expression = LiteralUtils.defendExpression(parser.parseExpression(Object.class)); + + if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { + joiner.add(entry.getKey() + ": ?"); + continue; + } + + if (expression.isSingle()) { + joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getSingular()); + } else { + joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getPlural()); + } + } + + Skript.error("The function %s(%s) does not exist.", name, joiner); + } + + @Override + protected Object @Nullable [] get(Event event) { if (function == null) { return null; } - Object[][] args = new Object[function.getParameters().length][]; - int i = 0; - for (Parameter value : function.getParameters()) { + Object[][] args = new Object[function.getParameters().length][]; + int i = 0; + for (Parameter value : function.getParameters()) { Expression expression = arguments.get(value.getName()); if (expression == null) { @@ -213,38 +214,38 @@ private void doesNotExist(String name, LinkedHashMap arguments) } args[i] = expression.getArray(event); - i++; - } + i++; + } - try { + try { return function.execute(args); } finally { function.resetReturnValue(); } - } - - @Override - public boolean isSingle() { - return function.isSingle(); - } - - @Override - public boolean isSectionOnly() { - return true; - } - - @Override - public Class getReturnType() { - return function.getReturnType() != null ? function.getReturnType().getC() : null; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return new SyntaxStringBuilder(event, debug) - .append("run function") - .append(function.getName()) - .append("with arguments") - .toString(); - } + } + + @Override + public boolean isSingle() { + return function.isSingle(); + } + + @Override + public boolean isSectionOnly() { + return true; + } + + @Override + public Class getReturnType() { + return function.getReturnType() != null ? function.getReturnType().getC() : null; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return new SyntaxStringBuilder(event, debug) + .append("run function") + .append(function.getName()) + .append("with arguments") + .toString(); + } } diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index 8aeb6664c1e..4a971e8de7a 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -4,7 +4,7 @@ local function esf(x: int):: int: #local function esf(x: string):: int: # return 2 -#local function esf(x: ints):: int: +#local function esf(x: objects):: int: # return 3 local function esf_two(x: int, y: int):: int: @@ -26,16 +26,25 @@ test "function section": # x set to 1 and 2 #assert {_x} = 3 - parse: - set {_x} to function esf with arguments: - x set to firework - assert first element of last parse logs contains "The function esf(x: item type) does not exist" + #set {_x} to function esf with arguments: + # x set to 1, 2, 3, {_a}, {_b::*} + #assert {_x} = 3 #parse: # set {_x} to function esf with arguments: # x set to {_y} #assert first element of last parse logs is set + set {_y} to 3 + set {_x} to function esf with arguments: + x set to {_y} + assert {_x} = 1 + + parse: + set {_x} to function esf with arguments: + x set to firework + assert first element of last parse logs contains "The function esf(x: item type) does not exist" + parse: set {_x} to function esf with arguments: x set to agadsfg From b9bb5f53d5e314d9c818646125493ec3b4ad2293 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:46:49 +0100 Subject: [PATCH 06/14] Optimize imports --- .../java/ch/njol/skript/lang/function/FunctionRegistry.java | 2 -- 1 file changed, 2 deletions(-) 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 be9177644cb..b26a33a2c58 100644 --- a/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java +++ b/src/main/java/ch/njol/skript/lang/function/FunctionRegistry.java @@ -5,8 +5,6 @@ import ch.njol.skript.util.Utils; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; -import org.jetbrains.annotations.*; -import com.google.common.collect.ImmutableSet; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; From 1ec8b04a67932e435de2f6df36c64d58ecae1b19 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:52:52 +0100 Subject: [PATCH 07/14] Update to use overloading --- .../syntaxes/sections/ExprSecFunction.sk | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index 4a971e8de7a..13df0ecf467 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -1,11 +1,11 @@ local function esf(x: int):: int: return 1 -#local function esf(x: string):: int: -# return 2 +local function esf(x: string):: int: + return 2 -#local function esf(x: objects):: int: -# return 3 +local function esf(x: objects):: int: + return 3 local function esf_two(x: int, y: int):: int: return 4 @@ -18,36 +18,36 @@ test "function section": x set to 1 assert {_x} = 1 - #set {_x} to function esf with arguments: - # x set to "hey" - #assert {_x} = 2 + set {_x} to function esf with arguments: + x set to "hey" + assert {_x} = 2 - #set {_x} to function esf with arguments: - # x set to 1 and 2 - #assert {_x} = 3 + set {_x} to function esf with arguments: + x set to 1 and 2 + assert {_x} = 3 - #set {_x} to function esf with arguments: - # x set to 1, 2, 3, {_a}, {_b::*} - #assert {_x} = 3 + set {_x} to function esf with arguments: + x set to 1, 2, 3, {_a}, {_b::*} + assert {_x} = 3 - #parse: - # set {_x} to function esf with arguments: - # x set to {_y} - #assert first element of last parse logs is set + parse: + set {_x} to function esf with arguments: + x set to {_y} + assert first element of last parse logs is set set {_y} to 3 set {_x} to function esf with arguments: - x set to {_y} + x set to integer within {_y} assert {_x} = 1 parse: - set {_x} to function esf with arguments: + set {_x} to function esf_two with arguments: x set to firework assert first element of last parse logs contains "The function esf(x: item type) does not exist" parse: set {_x} to function esf with arguments: - x set to agadsfg + x set to agasgasfgadsfg assert first element of last parse logs contains "The function esf(x: ?) does not exist" set {_x} to function esf_two with arguments: From 51f53ebefcdddf51b117965f04761dac17521fad Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:53:09 +0100 Subject: [PATCH 08/14] Move to common module, use FunctionReferenceParser --- .../njol/skript/sections/ExprSecFunction.java | 251 ------------------ .../skript/common/CommonModule.java | 3 +- .../common/sections/ExprSecFunction.java | 206 ++++++++++++++ 3 files changed, 208 insertions(+), 252 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/sections/ExprSecFunction.java create mode 100644 src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java diff --git a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java b/src/main/java/ch/njol/skript/sections/ExprSecFunction.java deleted file mode 100644 index bffb6ad0c6c..00000000000 --- a/src/main/java/ch/njol/skript/sections/ExprSecFunction.java +++ /dev/null @@ -1,251 +0,0 @@ -package ch.njol.skript.sections; - -import ch.njol.skript.Skript; -import ch.njol.skript.config.Node; -import ch.njol.skript.config.SectionNode; -import ch.njol.skript.config.SimpleNode; -import ch.njol.skript.doc.Description; -import ch.njol.skript.doc.Example; -import ch.njol.skript.doc.Name; -import ch.njol.skript.doc.Since; -import ch.njol.skript.expressions.base.SectionExpression; -import ch.njol.skript.lang.*; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.function.Function; -import ch.njol.skript.lang.function.FunctionRegistry; -import ch.njol.skript.lang.function.FunctionRegistry.Retrieval; -import ch.njol.skript.lang.function.FunctionRegistry.RetrievalResult; -import ch.njol.skript.lang.function.Parameter; -import ch.njol.skript.lang.function.Signature; -import ch.njol.skript.lang.parser.ParserInstance; -import ch.njol.skript.registrations.Classes; -import ch.njol.skript.util.LiteralUtils; -import ch.njol.util.Kleenean; -import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.Map.Entry; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -@Name("Function Section") -@Description(""" - Runs a function with the specified arguments. - """) -@Example(""" - local function multiply(x: number, y: number) returns number: - return {_x} * {_y} - - set {_x} to function multiply with arguments: - x as 2 - y as 3 - - broadcast "%{_x}%" # returns 6 - """) -@Since("INSERT VERSION") -public class ExprSecFunction extends SectionExpression { - - /** - * The pattern for a valid function name. - * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. - */ - private final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile("[A-z_][A-z_0-9]*"); - - /** - * The pattern for an argument that can be passed in the children of this section. - */ - private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(?:the )?argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); - - static { - Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "[the] function <.+> with [the] arg[ument][s]"); - } - - private Function function; - private LinkedHashMap> arguments = null; - - @Override - public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, - @Nullable SectionNode node, @Nullable List triggerItems) { - assert node != null; - - if (node.isEmpty()) { - Skript.error("A function section must contain arguments."); - return false; - } - - LinkedHashMap args = new LinkedHashMap<>(); - for (Node n : node) { - if (!(n instanceof SimpleNode) || n.getKey() == null) { - Skript.error("Invalid argument declaration for a function section: ", n.getKey()); - return false; - } - - Matcher matcher = ARGUMENT_PATTERN.matcher(n.getKey()); - if (!matcher.matches()) { - Skript.error("Invalid argument declaration for a function section: ", n.getKey()); - return false; - } - - args.put(matcher.group("name"), matcher.group("value")); - } - - String namespace = ParserInstance.get().getCurrentScript().getConfig().getFileName(); - String name = result.regexes.get(0).group(); - - if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { - Skript.error("The function %s does not exist.", name); - return false; - } - - // todo use FunctionParser - function = findFunction(namespace, name, args); - - if (function == null || arguments == null || arguments.isEmpty()) { - doesNotExist(name, args); - return false; - } - - if (function.getReturnType() == null) { - Skript.error("The function %s does not return anything.", name); - return false; - } - - return true; - } - - /** - * Attempts to find the function to execute given the arguments. - * - * @param namespace The current script. - * @param name The name of the function. - * @param args The passed arguments. - * @return The function given the arguments, or null if no function is found. - */ - private Function findFunction(String namespace, String name, LinkedHashMap args) { - signatures: - for (Signature signature : FunctionRegistry.getRegistry().getSignatures(namespace, name)) { - LinkedHashMap> arguments = new LinkedHashMap<>(); - - LinkedHashMap> parameters = Arrays.stream(signature.getParameters()) - .collect(Collectors.toMap(Parameter::getName, p -> p, (a, b) -> b, LinkedHashMap::new)); - for (Entry entry : args.entrySet()) { - Parameter parameter = parameters.get(entry.getKey()); - - if (parameter == null) { - continue signatures; - } - - //noinspection unchecked - Expression expression = LiteralUtils.defendExpression( - new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT) - .parseExpression(parameter.getType().getC())); - - if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { - continue signatures; - } - - arguments.put(entry.getKey(), expression); - } - - Class[] signatureArgs = Arrays.stream(signature.getParameters()) - .map(it -> { - if (it.isSingleValue()) { - return it.getType().getC(); - } else { - return it.getType().getC().arrayType(); - } - }) - .toArray(Class[]::new); - - Retrieval> retrieval = FunctionRegistry.getRegistry().getFunction(namespace, name, signatureArgs); - if (retrieval.result() == RetrievalResult.EXACT) { - this.arguments = arguments; - return retrieval.retrieved(); - } - } - - return null; - } - - /** - * Prints the error for when a function does not exist. - * - * @param name The function name. - * @param arguments The passed arguments to the function call. - */ - private void doesNotExist(String name, LinkedHashMap arguments) { - StringJoiner joiner = new StringJoiner(", "); - - for (Map.Entry entry : arguments.entrySet()) { - SkriptParser parser = new SkriptParser(entry.getValue(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT); - - Expression expression = LiteralUtils.defendExpression(parser.parseExpression(Object.class)); - - if (expression == null || LiteralUtils.hasUnparsedLiteral(expression)) { - joiner.add(entry.getKey() + ": ?"); - continue; - } - - if (expression.isSingle()) { - joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getSingular()); - } else { - joiner.add(entry.getKey() + ": " + Classes.getSuperClassInfo(expression.getReturnType()).getName().getPlural()); - } - } - - Skript.error("The function %s(%s) does not exist.", name, joiner); - } - - @Override - protected Object @Nullable [] get(Event event) { - if (function == null) { - return null; - } - - Object[][] args = new Object[function.getParameters().length][]; - int i = 0; - for (Parameter value : function.getParameters()) { - Expression expression = arguments.get(value.getName()); - - if (expression == null) { - return null; - } - - args[i] = expression.getArray(event); - i++; - } - - try { - return function.execute(args); - } finally { - function.resetReturnValue(); - } - } - - @Override - public boolean isSingle() { - return function.isSingle(); - } - - @Override - public boolean isSectionOnly() { - return true; - } - - @Override - public Class getReturnType() { - return function.getReturnType() != null ? function.getReturnType().getC() : null; - } - - @Override - public String toString(@Nullable Event event, boolean debug) { - return new SyntaxStringBuilder(event, debug) - .append("run function") - .append(function.getName()) - .append("with arguments") - .toString(); - } - -} diff --git a/src/main/java/org/skriptlang/skript/common/CommonModule.java b/src/main/java/org/skriptlang/skript/common/CommonModule.java index 22de43efd99..12f64e2f962 100644 --- a/src/main/java/org/skriptlang/skript/common/CommonModule.java +++ b/src/main/java/org/skriptlang/skript/common/CommonModule.java @@ -11,7 +11,8 @@ public class CommonModule implements AddonModule { @Override public void load(SkriptAddon addon) { try { - Skript.getAddonInstance().loadClasses("org.skriptlang.skript.common", "expressions"); + Skript.getAddonInstance().loadClasses("org.skriptlang.skript.common", + "expressions", "sections"); } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java new file mode 100644 index 00000000000..4bbb2668ca9 --- /dev/null +++ b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java @@ -0,0 +1,206 @@ +package org.skriptlang.skript.common.sections; + +import ch.njol.skript.Skript; +import ch.njol.skript.config.Node; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.config.SimpleNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Example; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SectionExpression; +import ch.njol.skript.lang.*; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.localization.Noun; +import ch.njol.skript.log.ParseLogHandler; +import ch.njol.skript.log.SkriptLogger; +import ch.njol.skript.registrations.Classes; +import ch.njol.skript.util.LiteralUtils; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.common.function.FunctionReference; +import org.skriptlang.skript.common.function.FunctionReference.Argument; +import org.skriptlang.skript.common.function.FunctionReference.ArgumentType; +import org.skriptlang.skript.common.function.FunctionReferenceParser; + +import java.util.ArrayList; +import java.util.List; +import java.util.StringJoiner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Name("Function Section") +@Description(""" + Runs a function with the specified arguments. + """) +@Example(""" + local function multiply(x: number, y: number) returns number: + return {_x} * {_y} + + set {_x} to function multiply with arguments: + x as 2 + y as 3 + + broadcast "%{_x}%" # returns 6 + """) +@Since("INSERT VERSION") +public class ExprSecFunction extends SectionExpression { + + /** + * The pattern for a valid function name. + * Functions must start with a letter or underscore and can only contain letters, numbers, and underscores. + */ + private final static Pattern FUNCTION_NAME_PATTERN = Pattern.compile(Functions.functionNamePattern); + + /** + * The pattern for an argument that can be passed in the children of this section. + */ + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(?:the )?argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); + + static { + Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "[the] function <.+> with [the] arg[ument][s]"); + } + + private FunctionReference reference; + private final List> arguments = new ArrayList<>(); + + @Override + public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems) { + assert node != null; + + if (node.isEmpty()) { + Skript.error("A function section must contain arguments."); + return false; + } + + for (Node child : node) { + if (!(child instanceof SimpleNode) || child.getKey() == null) { + Skript.error("Invalid argument declaration for a function section: ", child.getKey()); + return false; + } + + Matcher matcher = ARGUMENT_PATTERN.matcher(child.getKey()); + if (!matcher.matches()) { + Skript.error("Invalid argument declaration for a function section: ", child.getKey()); + return false; + } + + arguments.add(new Argument<>(ArgumentType.NAMED, matcher.group("name"), matcher.group("value"))); + } + + String name = result.regexes.getFirst().group(); + if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { + Skript.error("The function %s does not exist.", name); + return false; + } + + FunctionReferenceParser parser = new FunctionReferenceParser(ParseContext.DEFAULT, SkriptParser.PARSE_EXPRESSIONS); + //noinspection unchecked + Argument[] array = (Argument[]) arguments.toArray(new Argument[0]); + try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) { + reference = parser.parseFunctionReference(name, array, log); + } + + if (reference == null || this.arguments.isEmpty()) { + doesNotExist(name); + return false; + } + + if (reference.signature().returnType() == null) { + Skript.error("The function %s does not return anything.", name); + return false; + } + + return true; + } + + /** + * Prints the error for when a function does not exist. + * + * @param name The function name. + */ + private void doesNotExist(String name) { + StringJoiner joiner = new StringJoiner(", "); + + for (Argument argument : arguments) { + SkriptParser parser = new SkriptParser(argument.value(), SkriptParser.ALL_FLAGS, ParseContext.DEFAULT); + + Expression expression = LiteralUtils.defendExpression(parser.parseExpression(Object.class)); + if (!LiteralUtils.canInitSafely(expression)) { + joiner.add(argument.name() + ": ?"); + continue; + } + + Noun className = Classes.getSuperClassInfo(expression.getReturnType()).getName(); + if (expression.isSingle()) { + joiner.add(argument.name() + ": " + className.getSingular()); + } else { + joiner.add(argument.name() + ": " + className.getPlural()); + } + } + + Skript.error("The function %s(%s) does not exist.", name, joiner); + } + + @Override + protected Object @Nullable [] get(Event event) { + if (reference == null) { + return null; + } + + Class returnType = reference.signature().returnType(); + if (returnType == null) { + return null; + } + + Object result = reference.execute(event); + if (result == null) { + return null; + } + + reference.function().resetReturnValue(); + + if (result.getClass().isArray()) { + return (Object[]) result; + } else { + return new Object[] { result }; + } + } + + @Override + public boolean isSingle() { + return reference.isSingle(); + } + + @Override + public boolean isSectionOnly() { + return true; + } + + @Override + public Class getReturnType() { + return reference.signature().returnType() != null ? Utils.getComponentType(reference.signature().returnType()) : null; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug) + .append("run function") + .append(reference.name()); + + if (arguments.size() > 1) { + builder.append("with arguments"); + } else { + builder.append("with argument"); + } + + arguments.forEach(argument -> builder.append(argument.name() + ": " + argument.value() + ", ")); + + return builder.toString(); + } + +} From 4360e91ac9e7ab782ffb7a12f8faf8072a642dd0 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:05:02 +0100 Subject: [PATCH 09/14] Update test function name --- src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index 13df0ecf467..3880aa36db1 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -43,7 +43,7 @@ test "function section": parse: set {_x} to function esf_two with arguments: x set to firework - assert first element of last parse logs contains "The function esf(x: item type) does not exist" + assert first element of last parse logs contains "The function esf_two(x: item type) does not exist" parse: set {_x} to function esf with arguments: From 62a5448cab088e2100941b0aa6b5a652128662ef Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:12:40 +0100 Subject: [PATCH 10/14] Update test error --- .../skriptlang/skript/common/sections/ExprSecFunction.java | 6 +++--- src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java index 4bbb2668ca9..a9f2b29aaee 100644 --- a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java +++ b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java @@ -94,7 +94,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, String name = result.regexes.getFirst().group(); if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { - Skript.error("The function %s does not exist.", name); + Skript.error("The function '%s' does not exist.", name); return false; } @@ -111,7 +111,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, } if (reference.signature().returnType() == null) { - Skript.error("The function %s does not return anything.", name); + Skript.error("The function '%s' does not return anything.", name); return false; } @@ -143,7 +143,7 @@ private void doesNotExist(String name) { } } - Skript.error("The function %s(%s) does not exist.", name, joiner); + Skript.error("The function '%s(%s)' does not exist.", name, joiner); } @Override diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index 3880aa36db1..d5d8202f3a9 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -48,7 +48,7 @@ test "function section": parse: set {_x} to function esf with arguments: x set to agasgasfgadsfg - assert first element of last parse logs contains "The function esf(x: ?) does not exist" + assert first element of last parse logs contains "Can't understand this expression: 'agasgasfgadsfg'" set {_x} to function esf_two with arguments: x set to 1 From bb8a5a4dbbaeb9649f97262bccf579f24ac55aea Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:53:20 +0100 Subject: [PATCH 11/14] Translations and standardize errors --- .../function/FunctionReferenceParser.java | 2 +- .../common/sections/ExprSecFunction.java | 11 +++++----- src/main/resources/lang/english.lang | 3 +++ .../syntaxes/sections/ExprSecFunction.sk | 4 ++-- .../syntaxes/structures/StructFunction.sk | 20 +++++++++---------- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java b/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java index fe25f85ea02..c3e43ce1749 100644 --- a/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java @@ -423,7 +423,7 @@ private void doesNotExist(String name, FunctionReference.Argument[] argu } } - Skript.error("The function %s(%s) does not exist.", name, joiner); + Skript.error(Language.get("functions.does not exist"), "%s(%s)".formatted(name, joiner)); } /** diff --git a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java index a9f2b29aaee..d9db7495ce8 100644 --- a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java +++ b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java @@ -12,6 +12,7 @@ import ch.njol.skript.lang.*; import ch.njol.skript.lang.SkriptParser.ParseResult; import ch.njol.skript.lang.function.Functions; +import ch.njol.skript.localization.Language; import ch.njol.skript.localization.Noun; import ch.njol.skript.log.ParseLogHandler; import ch.njol.skript.log.SkriptLogger; @@ -79,13 +80,13 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, for (Node child : node) { if (!(child instanceof SimpleNode) || child.getKey() == null) { - Skript.error("Invalid argument declaration for a function section: ", child.getKey()); + Skript.error(Language.get("functions.invalid argument in section"), child.getKey()); return false; } Matcher matcher = ARGUMENT_PATTERN.matcher(child.getKey()); if (!matcher.matches()) { - Skript.error("Invalid argument declaration for a function section: ", child.getKey()); + Skript.error(Language.get("functions.invalid argument in section"), child.getKey()); return false; } @@ -94,7 +95,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, String name = result.regexes.getFirst().group(); if (!FUNCTION_NAME_PATTERN.matcher(name).matches()) { - Skript.error("The function '%s' does not exist.", name); + Skript.error(Language.get("functions.does not exist"), name); return false; } @@ -111,7 +112,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, } if (reference.signature().returnType() == null) { - Skript.error("The function '%s' does not return anything.", name); + Skript.error(Language.get("functions.does not return"), name); return false; } @@ -143,7 +144,7 @@ private void doesNotExist(String name) { } } - Skript.error("The function '%s(%s)' does not exist.", name, joiner); + Skript.error(Language.get("functions.does not exist"), "%s(%s)".formatted(name, joiner)); } @Override diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index 1fd9e7c8257..7c56754775b 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -238,3 +238,6 @@ functions: ambiguous function call: Cannot determine which function named '%s' to call: '%s'. Try clarifying the type of the arguments using the 'value within' expression. already assigned value to parameter: A value has already been assigned to parameter '%s'. mixing named and unnamed arguments: Mixing named and unnamed arguments is not allowed unless the order of the arguments matches the order of the parameters. + does not exist: The function '%s' does not exist. + does not return: The function '%s' does not return anything. + invalid argument in section: Invalid argument declaration for a function section: %s diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index d5d8202f3a9..b91ec693947 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -43,7 +43,7 @@ test "function section": parse: set {_x} to function esf_two with arguments: x set to firework - assert first element of last parse logs contains "The function esf_two(x: item type) does not exist" + assert first element of last parse logs contains "The function 'esf_two(x: item type)' does not exist" parse: set {_x} to function esf with arguments: @@ -63,4 +63,4 @@ test "function section": parse: set {_x} to function esf_void with arguments: x set to 1 - assert first element of last parse logs contains "The function esf_void does not return anything" + assert first element of last parse logs contains "The function 'esf_void' does not return anything" diff --git a/src/test/skript/tests/syntaxes/structures/StructFunction.sk b/src/test/skript/tests/syntaxes/structures/StructFunction.sk index 9e668902abe..432c5ea1e0a 100644 --- a/src/test/skript/tests/syntaxes/structures/StructFunction.sk +++ b/src/test/skript/tests/syntaxes/structures/StructFunction.sk @@ -62,7 +62,7 @@ local function argument_test_list(xs: ints): test "function structure arguments": parse: argument_test(1) - assert last parse logs contain "The function argument_test(integer) does not exist" + assert last parse logs contain "The function 'argument_test(integer)' does not exist" parse: argument_test(1, 2) @@ -70,7 +70,7 @@ test "function structure arguments": parse: argument_test(1, 2, 3) - assert last parse logs contain "The function argument_test(integer, integer, integer) does not exist" + assert last parse logs contain "The function 'argument_test(integer, integer, integer)' does not exist" parse: argument_test_list((1, 2, 3)) @@ -78,7 +78,7 @@ test "function structure arguments": parse: argument_test_list(1, (2, 3)) - assert last parse logs contain "The function argument_test_list(integer, integers) does not exist" + assert last parse logs contain "The function 'argument_test_list(integer, integers)' does not exist" local function argument_default(x: int, y: int = 2) -> int: return {_x} + {_y} @@ -112,7 +112,7 @@ test "named function arguments": parse: assert nfa(a: 8, c: adghadhaherta, b: 2) = 7 - assert first element of last parse logs contains "The function nfa(a: integer, c: ?, b: integer) does not exist" + assert first element of last parse logs contains "The function 'nfa(a: integer, c: ?, b: integer)' does not exist" parse: assert nfa(8, c: 3, b: 2) = 7 @@ -134,11 +134,11 @@ test "named function arguments": parse: nfa(a: 1, b: 2, d: 2) - assert first element of last parse logs contains "The function nfa(a: integer, b: integer, d: integer) does not exist" + assert first element of last parse logs contains "The function 'nfa(a: integer, b: integer, d: integer)' does not exist" parse: nfa(a: 1, d: 2, b: 2) - assert first element of last parse logs contains "The function nfa(a: integer, d: integer, b: integer) does not exist" + assert first element of last parse logs contains "The function 'nfa(a: integer, d: integer, b: integer)' does not exist" parse: nfa(a: 1, a: 2, c: 2) @@ -154,19 +154,19 @@ test "named function arguments with single list params": parse: nfa_incorrect_list(ns: 1, n: 2, 3) - assert first element of last parse logs contains "The function nfa_incorrect_list(ns: integer, n: integer, integer) does not exist" + assert first element of last parse logs contains "The function 'nfa_incorrect_list(ns: integer, n: integer, integer)' does not exist" parse: nfa_incorrect_list(wrong: (1, 2, 3)) - assert first element of last parse logs contains "The function nfa_incorrect_list(wrong: integers) does not exist" + assert first element of last parse logs contains "The function 'nfa_incorrect_list(wrong: integers)' does not exist" parse: nfa_incorrect_list(ns: (1, (2, 3))) - assert first element of last parse logs contains "The function nfa_incorrect_list(ns: integers) does not exist" + assert first element of last parse logs contains "The function 'nfa_incorrect_list(ns: integers)' does not exist" parse: nfa_incorrect_list(ns: 1, 2, 3) - assert first element of last parse logs contains "The function nfa_incorrect_list(ns: integer, integer, integer) does not exist" + assert first element of last parse logs contains "The function 'nfa_incorrect_list(ns: integer, integer, integer)' does not exist" parse: nfa_incorrect_list(ns: 1, ns: 2, ns: 3) From 0d44bfba1a3f1553226903de3f4e835bfbca73bf Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Wed, 28 Jan 2026 16:56:14 +0100 Subject: [PATCH 12/14] Minor changes --- .../skript/common/sections/ExprSecFunction.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java index d9db7495ce8..29c140fe83e 100644 --- a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java +++ b/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java @@ -41,9 +41,9 @@ local function multiply(x: number, y: number) returns number: return {_x} * {_y} - set {_x} to function multiply with arguments: - x as 2 - y as 3 + set {_x} to result of function multiply with arguments: + x set to 2 + y set to 3 broadcast "%{_x}%" # returns 6 """) @@ -59,10 +59,11 @@ public class ExprSecFunction extends SectionExpression { /** * The pattern for an argument that can be passed in the children of this section. */ - private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(?:the )?argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); + private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); static { - Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, "[the] function <.+> with [the] arg[ument][s]"); + Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, + "[the] result[s] of [running|executing] function <.+> with [the] arg[ument][s]"); } private FunctionReference reference; From a3d964becf1da64e013cb6ec6f9fc7b760b0c2bf Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Sun, 19 Apr 2026 14:42:17 +0200 Subject: [PATCH 13/14] Merge ExprResult --- .../njol/skript/expressions/ExprResult.java | 111 --------------- .../skript/common/CommonModule.java | 18 ++- .../sections/ExprSecFunction.java | 131 ++++++++++++++---- .../function/FunctionReferenceParser.java | 4 +- src/main/resources/lang/english.lang | 1 - .../tests/syntaxes/expressions/ExprResult.sk | 57 -------- .../syntaxes/sections/ExprSecFunction.sk | 84 +++++++++-- .../syntaxes/structures/StructFunction.sk | 2 +- 8 files changed, 187 insertions(+), 221 deletions(-) delete mode 100644 src/main/java/ch/njol/skript/expressions/ExprResult.java rename src/main/java/org/skriptlang/skript/common/{ => elements}/sections/ExprSecFunction.java (60%) delete mode 100644 src/test/skript/tests/syntaxes/expressions/ExprResult.sk diff --git a/src/main/java/ch/njol/skript/expressions/ExprResult.java b/src/main/java/ch/njol/skript/expressions/ExprResult.java deleted file mode 100644 index 58545049769..00000000000 --- a/src/main/java/ch/njol/skript/expressions/ExprResult.java +++ /dev/null @@ -1,111 +0,0 @@ -package ch.njol.skript.expressions; - -import ch.njol.skript.Skript; -import ch.njol.skript.classes.Changer.ChangeMode; -import ch.njol.skript.doc.*; -import ch.njol.skript.expressions.base.PropertyExpression; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.ExpressionList; -import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.function.DynamicFunctionReference; -import ch.njol.skript.util.LiteralUtils; -import ch.njol.util.Kleenean; -import org.bukkit.event.Event; -import org.jetbrains.annotations.Nullable; -import ch.njol.skript.registrations.experiments.ReflectionExperimentSyntax; -import org.skriptlang.skript.util.Executable; - -@Name("Result") -@Description({ - "Runs something (like a function) and returns its result.", - "If the thing is expected to return multiple values, use 'results' instead of 'result'." -}) -@Example("set {_function} to the function named \"myFunction\"") -@Example("set {_result} to the result of {_function}") -@Example("set {_list::*} to the results of {_function}") -@Example("set {_result} to the result of {_function} with arguments 13 and true") -@Since("2.10") -@Keywords({"run", "result", "execute", "function", "reflection"}) -public class ExprResult extends PropertyExpression, Object> implements ReflectionExperimentSyntax { - - static { - Skript.registerExpression(ExprResult.class, Object.class, ExpressionType.COMBINED, - "[the] result[plural:s] of [running|executing] %executable% [arguments:with arg[ument]s %-objects%]"); - } - - private Expression arguments; - private boolean hasArguments, isPlural; - private DynamicFunctionReference.Input input; - - @Override - public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult result) { - //noinspection unchecked - this.setExpr((Expression>) expressions[0]); - this.hasArguments = result.hasTag("arguments"); - this.isPlural = result.hasTag("plural"); - if (hasArguments) { - this.arguments = LiteralUtils.defendExpression(expressions[1]); - Expression[] arguments; - if (this.arguments instanceof ExpressionList list) { - arguments = list.getExpressions(); - } else { - arguments = new Expression[] {this.arguments}; - } - this.input = new DynamicFunctionReference.Input(arguments); - return LiteralUtils.canInitSafely(this.arguments); - } else { - this.input = new DynamicFunctionReference.Input(); - } - return true; - } - - @Override - protected Object[] get(Event event, Executable[] source) { - for (Executable task : source) { - Object[] arguments; - //noinspection rawtypes - if (task instanceof DynamicFunctionReference reference) { - Expression validated = reference.validate(input); - if (validated == null) - return new Object[0]; - arguments = validated.getArray(event); - } else if (hasArguments) { - arguments = this.arguments.getArray(event); - } else { - arguments = new Object[0]; - } - Object execute = task.execute(event, arguments); - if (execute instanceof Object[] results) - return results; - return new Object[] {execute}; - } - return new Object[0]; - } - - @Override - public Class @Nullable [] acceptChange(ChangeMode mode) { - return null; - } - - @Override - public Class getReturnType() { - return Object.class; - } - - @Override - public boolean isSingle() { - return !isPlural; - } - - @Override - public String toString(@Nullable Event event, final boolean debug) { - String text = "the result" + (isPlural ? "s" : "") + " of " + getExpr().toString(event, debug); - if (hasArguments) - text += " with arguments " + arguments.toString(event, debug); - return text; - } - - - -} diff --git a/src/main/java/org/skriptlang/skript/common/CommonModule.java b/src/main/java/org/skriptlang/skript/common/CommonModule.java index c3569dc0bc1..cbb894afac7 100644 --- a/src/main/java/org/skriptlang/skript/common/CommonModule.java +++ b/src/main/java/org/skriptlang/skript/common/CommonModule.java @@ -4,9 +4,14 @@ import org.skriptlang.skript.addon.AddonModule; import org.skriptlang.skript.addon.HierarchicalAddonModule; import org.skriptlang.skript.addon.SkriptAddon; -import org.skriptlang.skript.common.elements.expressions.*; +import org.skriptlang.skript.common.elements.expressions.ExprColorFromHexCode; +import org.skriptlang.skript.common.elements.expressions.ExprHexCode; +import org.skriptlang.skript.common.elements.expressions.ExprRecursiveSize; +import org.skriptlang.skript.common.elements.sections.ExprSecFunction; import org.skriptlang.skript.common.properties.PropertiesModule; -import org.skriptlang.skript.common.types.*; +import org.skriptlang.skript.common.types.QuaternionClassInfo; +import org.skriptlang.skript.common.types.QueueClassInfo; +import org.skriptlang.skript.common.types.ScriptClassInfo; import java.util.List; @@ -15,7 +20,7 @@ public class CommonModule extends HierarchicalAddonModule { @Override public Iterable children() { return List.of( - new PropertiesModule(this) + new PropertiesModule(this) ); } @@ -30,9 +35,10 @@ protected void initSelf(SkriptAddon addon) { @Override protected void loadSelf(SkriptAddon addon) { register(addon, - ExprColorFromHexCode::register, - ExprHexCode::register, - ExprRecursiveSize::register + ExprColorFromHexCode::register, + ExprHexCode::register, + ExprRecursiveSize::register, + ExprSecFunction::register ); } diff --git a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java b/src/main/java/org/skriptlang/skript/common/elements/sections/ExprSecFunction.java similarity index 60% rename from src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java rename to src/main/java/org/skriptlang/skript/common/elements/sections/ExprSecFunction.java index 29c140fe83e..e637d6f0b54 100644 --- a/src/main/java/org/skriptlang/skript/common/sections/ExprSecFunction.java +++ b/src/main/java/org/skriptlang/skript/common/elements/sections/ExprSecFunction.java @@ -1,4 +1,4 @@ -package org.skriptlang.skript.common.sections; +package org.skriptlang.skript.common.elements.sections; import ch.njol.skript.Skript; import ch.njol.skript.config.Node; @@ -26,6 +26,9 @@ import org.skriptlang.skript.common.function.FunctionReference.Argument; import org.skriptlang.skript.common.function.FunctionReference.ArgumentType; import org.skriptlang.skript.common.function.FunctionReferenceParser; +import org.skriptlang.skript.registration.SyntaxInfo; +import org.skriptlang.skript.registration.SyntaxRegistry; +import org.skriptlang.skript.util.Executable; import java.util.ArrayList; import java.util.List; @@ -35,18 +38,24 @@ @Name("Function Section") @Description(""" - Runs a function with the specified arguments. - """) + Runs a function with the specified arguments. + """) @Example(""" - local function multiply(x: number, y: number) returns number: - return {_x} * {_y} - - set {_x} to result of function multiply with arguments: - x set to 2 - y set to 3 - - broadcast "%{_x}%" # returns 6 - """) + local function multiply(x: number, y: number) returns number: + return {_x} * {_y} + + set {_x} to result of function multiply with arguments: + x set to 2 + y set to 3 + + broadcast "%{_x}%" # returns 6 + """) +@Example(""" + set {_function} to the function named "myFunction" + set {_result} to the result of {_function} + set {_list::*} to the results of {_function} + set {_result} to the result of {_function} with arguments 13 and true + """) @Since("INSERT VERSION") public class ExprSecFunction extends SectionExpression { @@ -61,17 +70,45 @@ public class ExprSecFunction extends SectionExpression { */ private static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:argument )?(?%s) set to (?.+)".formatted(FUNCTION_NAME_PATTERN.toString())); - static { - Skript.registerExpression(ExprSecFunction.class, Object.class, ExpressionType.SIMPLE, - "[the] result[s] of [running|executing] function <.+> with [the] arg[ument][s]"); + public static void register(SyntaxRegistry syntaxRegistry) { + syntaxRegistry.register(SyntaxRegistry.EXPRESSION, SyntaxInfo.Expression.builder(ExprSecFunction.class, Object.class) + .supplier(ExprSecFunction::new) + .addPattern("[the] result[plural:s] of [running|executing] %executable% [arguments:with arg[ument]s %-objects%]") + .addPattern("[the] result[s] of [running|executing] function <.+> with [the] arg[ument][s]") + .build()); } + private boolean usesExecutable; + private Expression> executable; + private Expression[] executableArguments = null; + private FunctionReference reference; private final List> arguments = new ArrayList<>(); @Override - public boolean init(Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, - @Nullable SectionNode node, @Nullable List triggerItems) { + public boolean init( + Expression[] expressions, int pattern, Kleenean delayed, ParseResult result, + @Nullable SectionNode node, @Nullable List triggerItems + ) { + usesExecutable = pattern == 0; + + if (usesExecutable) { + //noinspection unchecked + executable = (Expression>) expressions[0]; + if (result.hasTag("arguments")) { + Expression expression = LiteralUtils.defendExpression(expressions[1]); + if (expression instanceof ExpressionList list) { + executableArguments = list.getExpressions(); + } else { + executableArguments = new Expression[]{expression}; + } + + return LiteralUtils.canInitSafely(executableArguments); + } + + return true; + } + assert node != null; if (node.isEmpty()) { @@ -123,7 +160,7 @@ public boolean init(Expression[] expressions, int pattern, Kleenean delayed, /** * Prints the error for when a function does not exist. * - * @param name The function name. + * @param name The function name. */ private void doesNotExist(String name) { StringJoiner joiner = new StringJoiner(", "); @@ -150,6 +187,29 @@ private void doesNotExist(String name) { @Override protected Object @Nullable [] get(Event event) { + if (usesExecutable) { + Executable executable = this.executable.getSingle(event); + if (executable == null) { + return null; + } + + Object result; + if (executableArguments == null) { + result = executable.execute(event); + } else { + Object[] arguments = new Object[executableArguments.length]; + for (int i = 0; i < arguments.length; i++) { + arguments[i] = executableArguments[i].getArray(event); + } + + result = executable.execute(event, arguments); + } + if (result instanceof Object[] results) { + return results; + } + return new Object[]{result}; + } + if (reference == null) { return null; } @@ -169,38 +229,49 @@ private void doesNotExist(String name) { if (result.getClass().isArray()) { return (Object[]) result; } else { - return new Object[] { result }; + return new Object[]{result}; } } @Override public boolean isSingle() { - return reference.isSingle(); - } - - @Override - public boolean isSectionOnly() { - return true; + return usesExecutable ? executable.isSingle() : reference.isSingle(); } @Override public Class getReturnType() { - return reference.signature().returnType() != null ? Utils.getComponentType(reference.signature().returnType()) : null; + return usesExecutable ? executable.getReturnType() : (reference.signature().returnType() != null ? Utils.getComponentType(reference.signature().returnType()) : null); } @Override public String toString(@Nullable Event event, boolean debug) { SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug) - .append("run function") - .append(reference.name()); + .append("the result of function"); + + if (usesExecutable) { + builder.append(executable.toString(event, debug)); + } else { + builder.append(reference.name()); + } if (arguments.size() > 1) { builder.append("with arguments"); - } else { + } else if (arguments.size() == 1) { builder.append("with argument"); + } else { + return builder.toString(); } - arguments.forEach(argument -> builder.append(argument.name() + ": " + argument.value() + ", ")); + if (usesExecutable) { + StringJoiner joiner = new StringJoiner(", "); + for (Expression argument : executableArguments) { + joiner.add(argument.toString(event, debug)); + } + + builder.append(joiner); + } else { + arguments.forEach(argument -> builder.append(argument.name() + ": " + argument.value() + ", ")); + } return builder.toString(); } diff --git a/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java b/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java index f5d93fd7ef1..5af390b12f5 100644 --- a/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java +++ b/src/main/java/org/skriptlang/skript/common/function/FunctionReferenceParser.java @@ -44,7 +44,7 @@ public record FunctionReferenceParser(ParseContext context, int flags) { private static final ArgsMessage UNEXPECTED_ARGUMENT = new ArgsMessage("functions.unexpected argument"); private static final ArgsMessage INVALID_ARGUMENT = new ArgsMessage("functions.invalid argument"); - private static final ArgsMessage UNKNOWN_FUNCTION = new ArgsMessage("functions.unknown function"); + private static final ArgsMessage DOES_NOT_EXIST = new ArgsMessage("functions.does not exist"); private static final ArgsMessage POTENTIAL_SIGNATURE = new ArgsMessage("functions.potential signature"); /** @@ -539,7 +539,7 @@ private void doesNotExist(String name, FunctionReference.Argument[] argu if (intended.isPresent()) { possibleMatch = " " + POTENTIAL_SIGNATURE.toString(intended.get().toString(false, false)); } - Skript.error(UNKNOWN_FUNCTION.toString(name, joiner) + possibleMatch); + Skript.error(DOES_NOT_EXIST.toString("%s(%s)".formatted(name, joiner)) + possibleMatch); } /** diff --git a/src/main/resources/lang/english.lang b/src/main/resources/lang/english.lang index ff74487ca2b..c52ab48a8a6 100644 --- a/src/main/resources/lang/english.lang +++ b/src/main/resources/lang/english.lang @@ -243,5 +243,4 @@ functions: invalid argument in section: Invalid argument declaration for a function section: %s invalid argument: Can't understand the argument for the parameter '%s' with type '%s': %s. unexpected argument: The argument named '%s' is unexpected. - unknown function: The function %s(%s) does not exist. potential signature: Did you mean to use the function '%s'? diff --git a/src/test/skript/tests/syntaxes/expressions/ExprResult.sk b/src/test/skript/tests/syntaxes/expressions/ExprResult.sk deleted file mode 100644 index 8eb8ad6ca8a..00000000000 --- a/src/test/skript/tests/syntaxes/expressions/ExprResult.sk +++ /dev/null @@ -1,57 +0,0 @@ -options: - path: "../../../../../../src/test/skript/tests/syntaxes/expressions/" - misc: "../../../../../../src/test/skript/tests/misc/" - - # Princess test script is in another castle, Mario! - # paths are relativised to the /scripts/ directory - # but we are loading these scripts from the test folder :( - -using script reflection - -function testExprResult0() :: boolean: - return true - -local function testExprResult1() :: boolean: - return true - -test "result of functions": - set {_function} to function "testExprResult0()" - set {_result} to result of {_function} - assert {_result} exists with "function didn't run" - assert {_result} is true with "function didn't return correctly" - delete {_result} - - set {_function} to function "testExprResult1()" - set {_result} to result of {_function} - assert {_result} exists with "function didn't run" - assert {_result} is true with "function didn't return correctly" - delete {_result} - -test "result of external functions": - set {_script} to the script named {@misc} + "dummy.sk" - set {_function} to function "testDummy()" from {_script} - assert {_function} exists with "function wasn't found" - set {_result} to result of {_function} - assert {_result} exists with "function didn't run" - assert {_result} is true with "function didn't return correctly" - delete {_result} - - set {_script} to the script named {@path} + "ExprFunction.sk" - - set {_function} to function "testExprFunction1()" from {_script} - assert {_function} exists with "function wasn't found" - set {_result} to result of {_function} - assert {_result} exists with "function didn't run" - assert {_result} is true with "function didn't return correctly" - delete {_result} - - set {_function} to function "testExprFunction2()" from {_script} - assert {_function} exists with "function wasn't found" - set {_result} to result of {_function} with arguments "hello" and 0 - assert {_result} exists with "function didn't run" - assert {_result} is false with "function didn't return correctly" - set {_result} to result of {_function} with arguments "hello" and 3 - assert {_result} is false with "function didn't return correctly" - set {_result} to result of {_function} with arguments "hello" and 5 - assert {_result} is true with "function didn't return correctly" - delete {_result} diff --git a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk index b91ec693947..45d7ce7e411 100644 --- a/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk +++ b/src/test/skript/tests/syntaxes/sections/ExprSecFunction.sk @@ -13,54 +13,112 @@ local function esf_two(x: int, y: int):: int: local function esf_void(x: int): stop -test "function section": - set {_x} to function esf with arguments: +test "function section result": + set {_x} to result of function esf with arguments: x set to 1 assert {_x} = 1 - set {_x} to function esf with arguments: + set {_x} to result of function esf with arguments: x set to "hey" assert {_x} = 2 - set {_x} to function esf with arguments: + set {_x} to result of function esf with arguments: x set to 1 and 2 assert {_x} = 3 - set {_x} to function esf with arguments: + set {_x} to result of function esf with arguments: x set to 1, 2, 3, {_a}, {_b::*} assert {_x} = 3 parse: - set {_x} to function esf with arguments: + set {_x} to result of function esf with arguments: x set to {_y} assert first element of last parse logs is set set {_y} to 3 - set {_x} to function esf with arguments: + set {_x} to result of function esf with arguments: x set to integer within {_y} assert {_x} = 1 parse: - set {_x} to function esf_two with arguments: + set {_x} to result of function esf_two with arguments: x set to firework assert first element of last parse logs contains "The function 'esf_two(x: item type)' does not exist" parse: - set {_x} to function esf with arguments: + set {_x} to result of function esf with arguments: x set to agasgasfgadsfg - assert first element of last parse logs contains "Can't understand this expression: 'agasgasfgadsfg'" + assert first element of last parse logs contains "Can't understand the argument for the parameter" - set {_x} to function esf_two with arguments: + set {_x} to result of function esf_two with arguments: x set to 1 y set to 2 assert {_x} = 4 - set {_x} to function esf_two with arguments: + set {_x} to result of function esf_two with arguments: y set to 2 x set to 1 assert {_x} = 4 parse: - set {_x} to function esf_void with arguments: + set {_x} to result of function esf_void with arguments: x set to 1 assert first element of last parse logs contains "The function 'esf_void' does not return anything" + +options: + path: "../../../../../../src/test/skript/tests/syntaxes/expressions/" + misc: "../../../../../../src/test/skript/tests/misc/" + + # Princess test script is in another castle, Mario! + # paths are relativised to the /scripts/ directory + # but we are loading these scripts from the test folder :( + +using script reflection + +function testExprResult0() :: boolean: + return true + +local function testExprResult1() :: boolean: + return true + +test "result of functions": + set {_function} to function "testExprResult0()" + set {_result} to result of {_function} + assert {_result} exists with "function didn't run" + assert {_result} is true with "function didn't return correctly" + delete {_result} + + set {_function} to function "testExprResult1()" + set {_result} to result of {_function} + assert {_result} exists with "function didn't run" + assert {_result} is true with "function didn't return correctly" + delete {_result} + +test "result of external functions": + set {_script} to the script named {@misc} + "dummy.sk" + set {_function} to function "testDummy()" from {_script} + assert {_function} exists with "function wasn't found" + set {_result} to result of {_function} + assert {_result} exists with "function didn't run" + assert {_result} is true with "function didn't return correctly" + delete {_result} + + set {_script} to the script named {@path} + "ExprFunction.sk" + + set {_function} to function "testExprFunction1()" from {_script} + assert {_function} exists with "function wasn't found" + set {_result} to result of {_function} + assert {_result} exists with "function didn't run" + assert {_result} is true with "function didn't return correctly" + delete {_result} + + set {_function} to function "testExprFunction2()" from {_script} + assert {_function} exists with "function wasn't found" + set {_result} to result of {_function} with arguments "hello" and 0 + assert {_result} exists with "function didn't run" + assert {_result} is false with "function didn't return correctly" + set {_result} to result of {_function} with arguments "hello" and 3 + assert {_result} is false with "function didn't return correctly" + set {_result} to result of {_function} with arguments "hello" and 5 + assert {_result} is true with "function didn't return correctly" + delete {_result} diff --git a/src/test/skript/tests/syntaxes/structures/StructFunction.sk b/src/test/skript/tests/syntaxes/structures/StructFunction.sk index eff7bfd6c9f..757cb9e0964 100644 --- a/src/test/skript/tests/syntaxes/structures/StructFunction.sk +++ b/src/test/skript/tests/syntaxes/structures/StructFunction.sk @@ -82,7 +82,7 @@ test "function structure arguments": parse: argument_test(1 if {_x} is 1, else 2, 3) - assert last parse logs is "The function argument_test(?, ?, integer) does not exist. Did you mean to use the function 'local argument_test(x: integer, y: integer)'?" + assert first element of last parse logs contains "The function 'argument_test(?, ?, integer)' does not exist" parse: argument_test((1 if {_x} is 1, else 2), 3) From 7bacf96a4e1a5341e9e0e88f446a40d06f731408 Mon Sep 17 00:00:00 2001 From: Efnilite <35348263+Efnilite@users.noreply.github.com> Date: Wed, 12 Aug 2026 00:08:57 +0200 Subject: [PATCH 14/14] merge with dev/feature --- src/main/java/org/skriptlang/skript/common/CommonModule.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/skriptlang/skript/common/CommonModule.java b/src/main/java/org/skriptlang/skript/common/CommonModule.java index 34d324d0d01..7b224a40931 100644 --- a/src/main/java/org/skriptlang/skript/common/CommonModule.java +++ b/src/main/java/org/skriptlang/skript/common/CommonModule.java @@ -7,6 +7,7 @@ import org.skriptlang.skript.common.elements.expressions.ExprColorFromHexCode; import org.skriptlang.skript.common.elements.expressions.ExprHexCode; import org.skriptlang.skript.common.elements.expressions.ExprRecursiveSize; +import org.skriptlang.skript.common.elements.expressions.ExprReplace; import org.skriptlang.skript.common.elements.sections.ExprSecFunction; import org.skriptlang.skript.common.properties.PropertiesModule; import org.skriptlang.skript.common.types.QuaternionClassInfo;