Skip to content

Commit d0b30c3

Browse files
Efnilitesovdeeth
andauthored
Function package rework and named function arguments (SkriptLang#8112)
* add stuff from java funciton rework * add argument parser * update parameter * add getSignatures * update tests * implement FunctionParser * fix implementation for Param, SF, Sign * fix Function#execute * update Documentation to use new Param * fix EffFunctionCall * add "" strings to parser * minor updates * move script param parsing to StructFunction, ScriptParameter * make junit pass * update Signature to be more private * fix max({_list::*}) not selecting properly * fix errors * add types to ExprFunctionCall * fix clamp * fix keyed errors * attempt to fix keyed * deprecate, add test * minor changes * add space * fix errors * fix local/local not being differentiable * update already registered error * disallow named/unnamed out of order arg mixing * attempt to fix keyed again * fix exact matches not being matched before lists * rename function -> reference * move FunctionParser to separate class * move + improve error * add Function#returnedType * add docs * IT'S ALIVE * move argument parsing * fix java 17 support * fix java 17 support again * added more tests * added docs to FunctionArgumentParser * add errors to english.lang * requested changes * make parseFunctionReference public for multiline function arguments * make FUNCTION_NAME_PATTERN a pattern * add tests for unparsed literals * use canInitSafely * move to common.function * fix tests * first changes * further impl attempt * Revert "further impl attempt" This reverts commit f2119bb. * Revert "first changes" This reverts commit 82300ff. * rename FunctionParser * update signature pattern * add NonExtendable * requested changes * resolve merge conflicts * add implementations * resolve merge conflicts part 2 * make it run somehow * fix tests * fix missing import, fix tests * add test * fix bad import * Fix exact default values * Fix default values with script parameters * Fix tests * Update annotations, remove dead class * Fix errors in tests * requested changes * Fix value setting for single values * Fix compilation errors * Requested changes 1 * Requested changes 2 * Requested changes * Fix error * Fix error part 2 * More requested changes * Fix error message not containing '' * Requested changes * Requested changes 2 * Fix old Signature constructor using invalid isSingle logic * Add more getComponent uses * Requested changes * Add todo * Requested changes * Optimize imports, fix errors * Fix contract not being passed for DefaultFunctions * Add range to execute * Merge tud's terrible changes * Add parameter casting * Fix error 1 * Add nested structure things * bruh * move position of nested --------- Co-authored-by: sovdee <10354869+sovdeeth@users.noreply.github.com>
1 parent cffefae commit d0b30c3

34 files changed

Lines changed: 2539 additions & 727 deletions

src/main/java/ch/njol/skript/doc/Documentation.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import ch.njol.skript.lang.SkriptEventInfo;
88
import ch.njol.skript.lang.SyntaxElementInfo;
99
import ch.njol.skript.lang.function.Functions;
10-
import ch.njol.skript.lang.function.Parameter;
1110
import ch.njol.skript.registrations.Classes;
1211
import ch.njol.skript.util.Utils;
1312
import ch.njol.util.NonNullPair;
@@ -16,6 +15,7 @@
1615
import ch.njol.util.coll.iterator.IteratorIterable;
1716
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
1817
import org.jetbrains.annotations.Nullable;
18+
import org.skriptlang.skript.common.function.Parameter;
1919

2020
import java.io.*;
2121
import java.util.ArrayList;
@@ -387,8 +387,8 @@ private static void insertFunction(PrintWriter pw, ch.njol.skript.lang.function.
387387
}
388388

389389
StringBuilder params = new StringBuilder();
390-
for (Parameter<?> p : func.getParameters()) {
391-
if (params.length() != 0)
390+
for (Parameter<?> p : func.getSignature().parameters().all()) {
391+
if (!params.isEmpty())
392392
params.append(", ");
393393
params.append(p.toString());
394394
}

src/main/java/ch/njol/skript/lang/SkriptParser.java

Lines changed: 41 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import ch.njol.skript.lang.DefaultExpressionUtils.DefaultExpressionError;
1414
import ch.njol.skript.lang.function.ExprFunctionCall;
1515
import ch.njol.skript.lang.function.FunctionReference;
16-
import ch.njol.skript.lang.function.FunctionRegistry;
17-
import ch.njol.skript.lang.function.Functions;
1816
import ch.njol.skript.lang.parser.DefaultValueData;
1917
import ch.njol.skript.lang.parser.ParseStackOverflowException;
2018
import ch.njol.skript.lang.parser.ParserInstance;
@@ -45,6 +43,7 @@
4543
import org.bukkit.plugin.java.JavaPlugin;
4644
import org.jetbrains.annotations.NotNull;
4745
import org.jetbrains.annotations.Nullable;
46+
import org.skriptlang.skript.common.function.FunctionReferenceParser;
4847
import org.skriptlang.skript.lang.converter.Converters;
4948
import org.skriptlang.skript.lang.experiment.ExperimentSet;
5049
import org.skriptlang.skript.lang.experiment.ExperimentalSyntax;
@@ -58,7 +57,6 @@
5857
import java.util.Map.Entry;
5958
import java.util.concurrent.ConcurrentHashMap;
6059
import java.util.concurrent.atomic.AtomicBoolean;
61-
import java.util.function.Supplier;
6260
import java.util.logging.Level;
6361
import java.util.regex.MatchResult;
6462
import java.util.regex.Matcher;
@@ -519,11 +517,10 @@ private static <T extends SyntaxElement> boolean checkExperimentalSyntax(T eleme
519517
log.printError();
520518
return null;
521519
}
522-
FunctionReference<T> functionReference = parseFunction(types);
520+
org.skriptlang.skript.common.function.FunctionReference<T> functionReference = parseFunctionReference();
523521
if (functionReference != null) {
524522
log.printLog();
525-
//noinspection rawtypes
526-
return new ExprFunctionCall(functionReference);
523+
return new ExprFunctionCall<>(functionReference, types);
527524
} else if (log.hasError()) {
528525
log.printError();
529526
return null;
@@ -675,9 +672,8 @@ private static <T extends SyntaxElement> boolean checkExperimentalSyntax(T eleme
675672
}
676673

677674
// If it wasn't variable, do same for function call
678-
FunctionReference<?> functionReference = parseFunction(types);
675+
org.skriptlang.skript.common.function.FunctionReference<?> functionReference = parseFunctionReference();
679676
if (functionReference != null) {
680-
681677
if (onlySingular && !functionReference.isSingle()) {
682678
Skript.error("'" + expr + "' can only be a single "
683679
+ Classes.toString(Stream.of(exprInfo.classes).map(classInfo -> classInfo.getName().toString()).toArray(), false)
@@ -687,7 +683,7 @@ private static <T extends SyntaxElement> boolean checkExperimentalSyntax(T eleme
687683
}
688684

689685
log.printLog();
690-
return new ExprFunctionCall<>(functionReference);
686+
return new ExprFunctionCall<>(functionReference, types);
691687
} else if (log.hasError()) {
692688
log.printError();
693689
return null;
@@ -907,12 +903,7 @@ private boolean checkAcceptedType(Class<?> clazz, Class<?> ... types) {
907903
private final static String MULTIPLE_AND_OR = "List has multiple 'and' or 'or', will default to 'and'. Use brackets if you want to define multiple lists.";
908904
private final static String MISSING_AND_OR = "List is missing 'and' or 'or', defaulting to 'and'";
909905

910-
private boolean suppressMissingAndOrWarnings = SkriptConfig.disableMissingAndOrWarnings.value();
911-
912-
private SkriptParser suppressMissingAndOrWarnings() {
913-
suppressMissingAndOrWarnings = true;
914-
return this;
915-
}
906+
private final boolean suppressMissingAndOrWarnings = SkriptConfig.disableMissingAndOrWarnings.value();
916907

917908
@SafeVarargs
918909
public final <T> @Nullable Expression<? extends T> parseExpression(Class<? extends T>... types) {
@@ -1185,173 +1176,38 @@ private record OrderedExprInfo(ExprInfo[] infos) { }
11851176
* Function parsing
11861177
*/
11871178

1188-
private final static Pattern FUNCTION_CALL_PATTERN = Pattern.compile("(" + Functions.functionNamePattern + ")\\((.*)\\)");
1189-
1179+
11901180
/**
1191-
* @param types The required return type or null if it is not used (e.g. when calling a void function)
1192-
* @return The parsed function, or null if the given expression is not a function call or is an invalid function call (check for an error to differentiate these two)
1181+
* Attempts to parse {@link SkriptParser#expr} as a function reference.
1182+
*
1183+
* @param <T> The return type of the function.
1184+
* @return A {@link FunctionReference} if a function is found, or {@code null} if none is found.
11931185
*/
1194-
@SuppressWarnings("unchecked")
1195-
public <T> @Nullable FunctionReference<T> parseFunction(@Nullable Class<? extends T>... types) {
1196-
if (context != ParseContext.DEFAULT && context != ParseContext.EVENT)
1197-
return null;
1198-
try (ParseLogHandler log = SkriptLogger.startParseLogHandler()) {
1199-
Matcher matcher = FUNCTION_CALL_PATTERN.matcher(expr);
1200-
if (!matcher.matches()) {
1201-
log.printLog();
1202-
return null;
1203-
}
1204-
1205-
String functionName = matcher.group(1);
1206-
String args = matcher.group(2);
1207-
1208-
// Check for incorrect quotes, e.g. "myFunction() + otherFunction()" being parsed as one function
1209-
// See https://github.com/SkriptLang/Skript/issues/1532
1210-
for (int i = 0; i < args.length(); i = next(args, i, context)) {
1211-
if (i == -1) {
1212-
log.printLog();
1213-
return null;
1214-
}
1215-
}
1216-
1217-
if ((flags & PARSE_EXPRESSIONS) == 0) {
1218-
Skript.error("Functions cannot be used here (or there is a problem with your arguments).");
1219-
log.printError();
1220-
return null;
1221-
}
1222-
1223-
SkriptParser skriptParser = new SkriptParser(args, flags | PARSE_LITERALS, context)
1224-
.suppressMissingAndOrWarnings();
1225-
Expression<?>[] params = args.isEmpty() ? new Expression[0] : null;
1226-
1227-
String namespace = null;
1228-
ParserInstance parser = getParser();
1229-
if (parser.isActive()) {
1230-
namespace = parser.getCurrentScript().getConfig().getFileName();
1231-
}
1232-
1233-
if (params == null) { // there are arguments to parse
1234-
// determine signatures that could match
1235-
var signatures = FunctionRegistry.getRegistry().getSignatures(namespace, functionName).stream()
1236-
.filter(signature -> {
1237-
if (signature.getMaxParameters() == 0) { // we have arguments, but this function doesn't
1238-
return false;
1239-
}
1240-
if (types != null) { // filter signatures based on expected return type
1241-
if (signature.getReturnType() == null) {
1242-
return false;
1243-
}
1244-
Class<?> signatureType = signature.getReturnType().getC();
1245-
for (Class<?> type : types) {
1246-
//noinspection DataFlowIssue - individual elements won't be null
1247-
if (Converters.converterExists(signatureType, type)) {
1248-
return true;
1249-
}
1250-
}
1251-
return false;
1252-
}
1253-
return true;
1254-
})
1255-
.toList();
1256-
1257-
// here, we map all signatures into type/plurality collections
1258-
// for example, all possible types (and whether they are plural) for the first parameter
1259-
// will be mapped into the 0-index of both collections
1260-
record SignatureData(ClassInfo<?> classInfo, boolean plural) { }
1261-
List<List<SignatureData>> signatureDatas = new ArrayList<>();
1262-
boolean trySingle = false;
1263-
boolean trySinglePlural = false;
1264-
for (var signature : signatures) {
1265-
trySingle |= signature.getMinParameters() <= 1 || signature.getMaxParameters() == 1;
1266-
trySinglePlural |= trySingle && !signature.getParameter(0).isSingleValue();
1267-
for (int i = 0; i < signature.getMaxParameters(); i++) {
1268-
if (signatureDatas.size() <= i) {
1269-
signatureDatas.add(new ArrayList<>());
1270-
}
1271-
var parameter = signature.getParameter(i);
1272-
signatureDatas.get(i).add(new SignatureData(parameter.getType(), !parameter.isSingleValue()));
1273-
}
1274-
}
1275-
ExprInfo[] signatureInfos = new ExprInfo[signatureDatas.size()];
1276-
for (int infoIndex = 0; infoIndex < signatureInfos.length; infoIndex++) {
1277-
List<SignatureData> datas = signatureDatas.get(infoIndex);
1278-
ClassInfo<?>[] infos = new ClassInfo[datas.size()];
1279-
boolean[] isPlural = new boolean[infos.length];
1280-
for (int dataIndex = 0; dataIndex < infos.length; dataIndex++) {
1281-
SignatureData data = datas.get(dataIndex);
1282-
infos[dataIndex] = data.classInfo;
1283-
isPlural[dataIndex] = data.plural;
1284-
}
1285-
signatureInfos[infoIndex] = new ExprInfo(infos, isPlural);
1286-
}
1287-
OrderedExprInfo orderedExprInfo = new OrderedExprInfo(signatureInfos);
1288-
1289-
if (trySingle) {
1290-
params = this.getFunctionArguments(
1291-
() -> skriptParser.parseSingleExpr(true, null, orderedExprInfo.infos[0]),
1292-
args);
1293-
if (params == null && trySinglePlural) {
1294-
log.clear();
1295-
log.clearError();
1296-
try (ParseLogHandler listLog = SkriptLogger.startParseLogHandler()) {
1297-
params = this.getFunctionArguments(
1298-
() -> skriptParser.parseExpressionList(listLog, orderedExprInfo.infos[0]),
1299-
args);
1300-
}
1301-
}
1302-
}
1303-
if (params == null) {
1304-
log.clear();
1305-
log.clearError();
1306-
try (ParseLogHandler listLog = SkriptLogger.startParseLogHandler()) {
1307-
params = this.getFunctionArguments(
1308-
() -> skriptParser.parseExpressionList(listLog, orderedExprInfo),
1309-
args);
1310-
}
1311-
}
1312-
if (params == null) {
1313-
log.printError();
1314-
return null;
1315-
}
1316-
}
1317-
1318-
for (Expression<?> param : params) {
1319-
if (KeyProviderExpression.areKeysRecommended(param))
1320-
param.returnNestedStructures(true);
1321-
}
1322-
FunctionReference<T> functionReference = new FunctionReference<>(functionName, SkriptLogger.getNode(), namespace, types, params);
1323-
if (!functionReference.validateFunction(true)) {
1324-
log.printError();
1325-
return null;
1326-
}
1327-
log.printLog();
1328-
return functionReference;
1186+
public <T> org.skriptlang.skript.common.function.FunctionReference<T> parseFunctionReference() {
1187+
if (context == ParseContext.DEFAULT || context == ParseContext.EVENT) {
1188+
return new FunctionReferenceParser(context, flags).parseFunctionReference(expr);
13291189
}
1190+
return null;
13301191
}
13311192

1332-
private Expression<?> @Nullable [] getFunctionArguments(Supplier<Expression<?>> parsing, String args) {
1333-
if (args.isEmpty()) {
1334-
return new Expression[0];
1193+
/**
1194+
* @deprecated Use {@link #parseFunctionReference()} instead.
1195+
*/
1196+
@Deprecated(forRemoval = true, since = "INSERT VERSION")
1197+
public <T> @Nullable FunctionReference<T> parseFunction(@Nullable Class<? extends T>... types) {
1198+
if (context != ParseContext.DEFAULT && context != ParseContext.EVENT) {
1199+
return null;
13351200
}
1336-
1337-
Expression<?> parsedExpression = parsing.get();
1338-
if (parsedExpression == null) {
1201+
var newReference = new FunctionReferenceParser(context, flags).parseFunctionReference(expr);
1202+
if (newReference == null) {
13391203
return null;
13401204
}
13411205

1342-
Expression<?>[] params;
1343-
if (parsedExpression instanceof ExpressionList) {
1344-
if (!parsedExpression.getAnd()) {
1345-
Skript.error("Function arguments must be separated by commas and optionally an 'and', but not an 'or'."
1346-
+ " Put the 'or' into a second set of parentheses if you want to make it a single parameter, e.g. 'give(player, (sword or axe))'");
1347-
return null;
1348-
}
1349-
params = ((ExpressionList<?>) parsedExpression).getExpressions();
1350-
} else {
1351-
params = new Expression[] {parsedExpression};
1352-
}
1206+
var expressions = Arrays.stream(newReference.arguments())
1207+
.map(org.skriptlang.skript.common.function.FunctionReference.Argument::value)
1208+
.toArray(Expression[]::new);
13531209

1354-
return params;
1210+
return new FunctionReference<>(newReference.name(), null, newReference.namespace(), types, expressions);
13551211
}
13561212

13571213
/*
@@ -1596,22 +1452,24 @@ public static int next(String expr, int startIndex, ParseContext context) {
15961452
return startIndex + 1;
15971453

15981454
int index;
1599-
switch (expr.charAt(startIndex)) {
1600-
case '"':
1455+
return switch (expr.charAt(startIndex)) {
1456+
case '"' -> {
16011457
index = nextQuote(expr, startIndex + 1);
1602-
return index < 0 ? -1 : index + 1;
1603-
case '{':
1458+
yield index < 0 ? -1 : index + 1;
1459+
}
1460+
case '{' -> {
16041461
index = VariableString.nextVariableBracket(expr, startIndex + 1);
1605-
return index < 0 ? -1 : index + 1;
1606-
case '(':
1462+
yield index < 0 ? -1 : index + 1;
1463+
}
1464+
case '(' -> {
16071465
for (index = startIndex + 1; index >= 0 && index < exprLength; index = next(expr, index, context)) {
16081466
if (expr.charAt(index) == ')')
1609-
return index + 1;
1467+
yield index + 1;
16101468
}
1611-
return -1;
1612-
default:
1613-
return startIndex + 1;
1614-
}
1469+
yield -1;
1470+
}
1471+
default -> startIndex + 1;
1472+
};
16151473
}
16161474

16171475
/**
@@ -1816,18 +1674,4 @@ private static ParserInstance getParser() {
18161674
ParserInstance.registerData(DefaultValueData.class, DefaultValueData::new);
18171675
}
18181676

1819-
/**
1820-
* @deprecated due to bad naming conventions,
1821-
* use {@link #LIST_SPLIT_PATTERN} instead.
1822-
*/
1823-
@Deprecated(since = "2.7.0", forRemoval = true)
1824-
public final static Pattern listSplitPattern = LIST_SPLIT_PATTERN;
1825-
1826-
/**
1827-
* @deprecated due to bad naming conventions,
1828-
* use {@link #WILDCARD} instead.
1829-
*/
1830-
@Deprecated(since = "2.8.0", forRemoval = true)
1831-
public final static String wildcard = WILDCARD;
1832-
18331677
}

0 commit comments

Comments
 (0)