Skip to content

Commit 064e100

Browse files
authored
Fix named function arguments conflicts (SkriptLang#8350)
1 parent 6e10d6b commit 064e100

5 files changed

Lines changed: 220 additions & 201 deletions

File tree

src/main/java/org/skriptlang/skript/common/function/FunctionArgumentParser.java

Lines changed: 33 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
package org.skriptlang.skript.common.function;
22

3+
import ch.njol.skript.lang.ParseContext;
4+
import ch.njol.skript.lang.SkriptParser;
35
import org.skriptlang.skript.common.function.FunctionReference.Argument;
46
import org.skriptlang.skript.common.function.FunctionReference.ArgumentType;
57

68
import java.util.ArrayList;
79
import java.util.List;
10+
import java.util.regex.Matcher;
11+
import java.util.regex.Pattern;
812

913
/**
1014
* Parses the arguments of a function reference.
1115
*/
1216
final class FunctionArgumentParser {
1317

18+
private static final Pattern PART_PATTERN = Pattern.compile("(?:\\s*(?<name>[_a-zA-Z0-9]+):)?(?<value>.+)");
19+
1420
/**
1521
* The input string.
1622
*/
@@ -38,192 +44,50 @@ public FunctionArgumentParser(String args) {
3844
*/
3945
private int index = 0;
4046

41-
/**
42-
* The current character.
43-
*/
44-
private char c;
45-
46-
/**
47-
* Whether the current argument being parsed starts with a name declaration.
48-
*/
49-
private boolean nameFound = false;
50-
51-
/**
52-
* A builder which keeps track of the name part of an argument.
53-
* <p>
54-
* This builder may contain a part of the expression at the start of parsing an argument,
55-
* when it is unclear whether we are currently parsing a name or not. On realization that
56-
* this argument does not have a name, its contents are cleared.
57-
* </p>
58-
*/
59-
private final StringBuilder namePart = new StringBuilder();
60-
61-
/**
62-
* A builder which keeps track of the expression part of an argument.
63-
* <p>
64-
* This builder may contain a part of the name at the start of parsing an argument,
65-
* when it is unclear whether we are currently parsing a name or not. On realization that
66-
* this argument has a name, its contents are cleared.
67-
* </p>
68-
*/
69-
private final StringBuilder exprPart = new StringBuilder();
70-
71-
/**
72-
* Whether we are currently in a string or not.
73-
* <p>
74-
* To avoid parsing a comma in a string as the start of a new argument, we keep track of whether we're
75-
* in a string or not to ignore commas found in strings.
76-
* A new argument can only start when {@code nesting == 0 && !inString}.
77-
* </p>
78-
*/
79-
private boolean inString = false;
80-
81-
/**
82-
* The level of nesting we are currently in.
83-
* <p>
84-
* The nesting level is increased when entering special expressions which may contain commas,
85-
* thereby avoiding incorrectly parsing a comma in variables or parentheses as the start of a new argument.
86-
* A new argument can only start when {@code nesting == 0 && !inString}.
87-
* </p>
88-
*/
89-
private int nesting = 0;
90-
9147
/**
9248
* Parses the input string into arguments.
93-
* <p>
94-
* For every argument, during the parsing of the first few characters, one of the following things occurs.
95-
* <ul>
96-
* <li>A legal parameter name character is encountered. The character is added to {@link #namePart} and
97-
* {@link #exprPart}.</li>
98-
* <li>An illegal parameter name character is encountered. This means that the previous data added to {@link #namePart}
99-
* cannot be a name. {@link #namePart} is cleared and the rest of the argument is parsed as the expression.</li>
100-
* <li>A colon {@code :} is encountered. When all previous characters for this argument match the requirements
101-
* for a parameter name, the name is stored in {@link #namePart} and the rest of the argument is parsed as the expression.</li>
102-
* <li>A comma {@code ,} is encountered. This means that the end of the argument has been reached. If no name was found,
103-
* the entire argument is parsed as {@link #exprPart}. If a name was found, {@link #exprPart} gets stored alongside {@link #namePart}.</li>
104-
* </ul>
105-
* </p>
10649
*/
10750
private void parse() {
10851
// if we have no args to parse, give up instantly
10952
if (args.isEmpty()) {
11053
return;
11154
}
11255

113-
while (index < args.length()) {
114-
c = args.charAt(index);
115-
116-
// first try to compile the name
117-
if (!nameFound) {
118-
// if a name matches the legal characters, update name part
119-
if (c == '_' || Character.isLetterOrDigit(c)) {
120-
namePart.append(c);
121-
exprPart.append(c);
122-
index++;
123-
continue;
124-
}
125-
126-
// then if we have a name, start parsing the second part
127-
if (nesting == 0 && c == ':' && !namePart.isEmpty()) {
128-
exprPart.setLength(0);
129-
index++;
130-
nameFound = true;
131-
continue;
132-
}
133-
134-
if (isSpecialCharacter(ArgumentType.UNNAMED)) {
135-
continue;
136-
}
137-
138-
// given that the character did not match the legal name chars, reset name
139-
namePart.setLength(0);
140-
nextExpr();
141-
continue;
56+
int next = 0;
57+
while (next < args.length()) {
58+
next = SkriptParser.next(args, next, ParseContext.DEFAULT);
59+
if (next == -1) {
60+
// if no end is found, just parse the whole passed string as an argument and pray it works
61+
index = 0;
62+
next = args.length();
14263
}
14364

144-
if (isSpecialCharacter(ArgumentType.NAMED)) {
65+
if (next < args.length() && args.charAt(next) != ',') {
14566
continue;
14667
}
14768

148-
nextExpr(); // add to expression
149-
}
150-
151-
// make sure to save the last argument
152-
if (nameFound) {
153-
save(ArgumentType.NAMED);
154-
} else {
155-
save(ArgumentType.UNNAMED);
156-
}
157-
}
69+
String part = args.substring(index, next);
70+
index = next + 1;
15871

159-
/**
160-
* Manages special character handling by updating the {@link #nesting} and {@link #inString} variables.
161-
*
162-
* @param type The type of argument that is currently being parsed.
163-
* @return True when {@link #c} is a special character, false if not.
164-
*/
165-
private boolean isSpecialCharacter(ArgumentType type) {
166-
// for strings
167-
if (!inString && c == '"') {
168-
nesting++;
169-
inString = true;
170-
nextExpr();
171-
return true;
172-
}
173-
174-
if (inString && c == '"'
175-
&& index < args.length() - 1 && args.charAt(index + 1) != '"') { // allow double string char in strings
176-
nesting--;
177-
inString = false;
178-
nextExpr();
179-
return true;
180-
}
181-
182-
if (c == '(' || c == '{') {
183-
nesting++;
184-
nextExpr();
185-
return true;
186-
}
187-
188-
if (c == ')' || c == '}') {
189-
nesting--;
190-
nextExpr();
191-
return true;
192-
}
193-
194-
if (nesting == 0 && c == ',') {
195-
save(type);
196-
return true;
197-
}
198-
199-
return false;
200-
}
201-
202-
/**
203-
* Moves the parser to the next part of the expression that is being parsed.
204-
*/
205-
private void nextExpr() {
206-
exprPart.append(c);
207-
index++;
208-
}
72+
Matcher matcher = PART_PATTERN.matcher(part);
73+
if (!matcher.matches()) {
74+
continue;
75+
}
20976

210-
/**
211-
* Saves the string parts stored in {@link #exprPart} (and optionally {@link #namePart}) as a new argument in
212-
* {@link #arguments}. Then, all data for the current argument is cleared.
213-
*
214-
* @param type The type of argument to save as.
215-
*/
216-
private void save(ArgumentType type) {
217-
if (type == ArgumentType.UNNAMED) {
218-
arguments.add(new Argument<>(ArgumentType.UNNAMED, null, exprPart.toString().trim()));
219-
} else {
220-
arguments.add(new Argument<>(ArgumentType.NAMED, namePart.toString().trim(), exprPart.toString().trim()));
77+
String name = matcher.group("name");
78+
String value = matcher.group("value");
79+
if (name == null) {
80+
arguments.add(new Argument<>(ArgumentType.UNNAMED,
81+
null,
82+
value.trim(),
83+
value));
84+
} else {
85+
arguments.add(new Argument<>(ArgumentType.NAMED,
86+
name.trim(),
87+
value.trim(),
88+
name + ":" + value));
89+
}
22190
}
222-
223-
namePart.setLength(0);
224-
exprPart.setLength(0);
225-
index++;
226-
nameFound = false;
22791
}
22892

22993
/**

src/main/java/org/skriptlang/skript/common/function/FunctionReference.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,43 @@ public String toString(@Nullable Event event, boolean debug) {
332332
* @param type The type of the argument.
333333
* @param name The name of the argument, possibly null.
334334
* @param value The value of the argument.
335+
* @param raw The raw full string of this argument.
335336
*/
336337
public record Argument<T>(
337338
ArgumentType type,
338339
String name,
339-
T value
340+
T value,
341+
@Nullable String raw
340342
) {
341343

344+
/**
345+
* Secondary constructor where raw is null.
346+
*
347+
* @param type The type of the argument.
348+
* @param name The name of the argument, possibly null.
349+
* @param value The value of the argument.
350+
*/
351+
public Argument(ArgumentType type, String name, T value) {
352+
this(type, name, value, null);
353+
}
354+
355+
@Override
356+
public boolean equals(Object o) {
357+
if (!(o instanceof Argument<?> argument)) {
358+
return false;
359+
}
360+
361+
return Objects.equals(value, argument.value) && Objects.equals(name, argument.name) && type == argument.type;
362+
}
363+
364+
@Override
365+
public int hashCode() {
366+
int result = Objects.hashCode(type);
367+
result = 31 * result + Objects.hashCode(name);
368+
result = 31 * result + Objects.hashCode(value);
369+
return result;
370+
}
371+
342372
}
343373

344374
/**

0 commit comments

Comments
 (0)