Skip to content

Commit 8a2a4e9

Browse files
authored
Merge pull request #19 from SkriptDev/dev/feature
Dev/feature
2 parents 24d2c81 + 0c7b077 commit 8a2a4e9

File tree

7 files changed

+249
-16
lines changed

7 files changed

+249
-16
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.github.syst3ms.skriptparser.expressions;
2+
3+
import io.github.syst3ms.skriptparser.Parser;
4+
import io.github.syst3ms.skriptparser.lang.Expression;
5+
import io.github.syst3ms.skriptparser.lang.TriggerContext;
6+
import io.github.syst3ms.skriptparser.parsing.ParseContext;
7+
8+
public class ExprClaudeTest implements Expression<String> {
9+
10+
static {
11+
Parser.getMainRegistration().newExpression(ExprClaudeTest.class, String.class,
12+
true, "claude test %number% [of] %number%")
13+
.noDoc()
14+
.register();
15+
}
16+
17+
private Expression<Number> first, second;
18+
19+
@Override
20+
public boolean init(Expression<?>[] expressions, int matchedPattern, ParseContext parseContext) {
21+
first = (Expression<Number>) expressions[0];
22+
second = (Expression<Number>) expressions[1];
23+
return true;
24+
}
25+
26+
@Override
27+
public String[] getValues(TriggerContext ctx) {
28+
Number number = this.first.getSingle(ctx).orElse(null);
29+
Number other = this.second.getSingle(ctx).orElse(null);
30+
if (number != null && other != null) {
31+
return new String[]{"claude test " + number + " " + other};
32+
}
33+
return new String[]{};
34+
}
35+
36+
@Override
37+
public String toString(TriggerContext ctx, boolean debug) {
38+
return "";
39+
}
40+
}

src/main/java/io/github/syst3ms/skriptparser/file/FileParser.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ public class FileParser {
3030
*/
3131
public static List<FileElement> parseFileLines(String fileName, List<String> lines, int expectedIndentation, int lastLine, SkriptLogger logger) {
3232
List<FileElement> elements = new ArrayList<>();
33+
boolean multiLineComment = false;
3334
for (var i = 0; i < lines.size(); i++) {
3435
var line = lines.get(i);
36+
37+
// Multi line comment sections
38+
if (!line.isBlank() && line.startsWith("###")) {
39+
multiLineComment = !multiLineComment;
40+
}
41+
3542
String content = removeComments(line);
3643

37-
if (content.isEmpty()) {
44+
if (multiLineComment || content.isEmpty()) {
3845
elements.add(new VoidElement(fileName, lastLine + i, expectedIndentation));
3946
continue;
4047
}

src/main/java/io/github/syst3ms/skriptparser/lang/ExpressionList.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.syst3ms.skriptparser.lang;
22

33
import io.github.syst3ms.skriptparser.parsing.ParseContext;
4+
import io.github.syst3ms.skriptparser.types.changers.ChangeMode;
45
import io.github.syst3ms.skriptparser.util.ClassUtils;
56
import org.jetbrains.annotations.Contract;
67
import org.jetbrains.annotations.Nullable;
@@ -209,4 +210,25 @@ public Expression<?> getSource() {
209210
return source != null ? source : this;
210211
}
211212

213+
@Override
214+
public Optional<Class<?>[]> acceptsChange(ChangeMode mode) {
215+
// Check if all expressions in the list accept this change mode
216+
for (var expr : expressions) {
217+
if (expr.acceptsChange(mode).isEmpty()) {
218+
return Optional.empty();
219+
}
220+
}
221+
// If all expressions accept the change, return the accepted types from the first expression
222+
// (assuming all expressions have compatible types)
223+
return expressions[0].acceptsChange(mode);
224+
}
225+
226+
@Override
227+
public void change(TriggerContext ctx, ChangeMode mode, Object[] changeWith) throws UnsupportedOperationException {
228+
// Apply the change to all expressions in the list
229+
for (var expr : expressions) {
230+
expr.change(ctx, mode, changeWith);
231+
}
232+
}
233+
212234
}

src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,11 @@ public boolean isIndexLoop(String s) {
424424

425425
@Override
426426
public String toString(TriggerContext ctx, boolean debug) {
427+
// When in debug mode or using a dummy context, show the variable name instead of its value
428+
if (debug || ctx == TriggerContext.DUMMY) {
429+
String l = this.local ? "_" : "";
430+
return "{" + l + name.toString(ctx) + "}";
431+
}
427432
return TypeManager.toString(getValues(ctx));
428433
}
429434

src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ private static <T> Optional<? extends Expression<? extends T>> matchExpressionIn
356356
*/
357357
public static <T> Optional<? extends Expression<? extends T>> parseListLiteral(String s, PatternType<T> expectedType, ParserState parserState, SkriptLogger logger) {
358358
assert !expectedType.isSingle();
359-
if (!s.contains(",") && !s.contains("and") && !s.contains("nor") && !s.contains("or"))
359+
if (!s.contains(",") && !s.matches(".*\\b(?:and|n?or)\\b.*"))
360360
return Optional.empty();
361361
List<String> parts = new ArrayList<>();
362362
var m = LIST_SPLIT_PATTERN.matcher(s);

src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java

Lines changed: 117 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@ public int match(String s, int index, MatchContext context) {
7878
}
7979
return -1;
8080
}
81-
var i = StringUtils.indexOfIgnoreCase(s, text, index);
81+
var i = findTextWithBoundary(s, text.strip(), index);
8282
while (i != -1) {
8383
var toParse = s.substring(index, i).strip();
8484
var expression = parse(toParse, typeArray, context.getParserState(), logger);
8585
if (expression.isPresent()) {
8686
context.addExpression(expression.get());
8787
return index + toParse.length();
8888
}
89-
i = StringUtils.indexOfIgnoreCase(s, text, i + 1);
89+
i = findTextWithBoundary(s, text.strip(), i + 1);
9090
}
9191
} else if (possibleInput instanceof RegexGroup) {
9292
var m = ((RegexGroup) possibleInput).getPattern().matcher(s).region(index, s.length());
@@ -106,23 +106,93 @@ public int match(String s, int index, MatchContext context) {
106106
}
107107
} else {
108108
assert possibleInput instanceof ExpressionElement;
109-
var nextPossibleInputs = PatternElement.getPossibleInputs(flattened.subList(context.getPatternIndex() + 1, flattened.size()));
109+
// Find the index of the next expression element in the flattened list
110+
// We need to find the first ExpressionElement after the current position
111+
var expressionIndex = -1;
112+
for (var i = possibilityIndex + 1; i < flattened.size(); i++) {
113+
var elem = flattened.get(i);
114+
// Skip optional groups and look inside them
115+
if (elem instanceof OptionalGroup) {
116+
var inner = PatternElement.flatten(((OptionalGroup) elem).getElement());
117+
if (inner.stream().anyMatch(e -> e instanceof ExpressionElement)) {
118+
continue; // Skip optional groups containing expressions
119+
}
120+
} else if (elem instanceof ExpressionElement) {
121+
expressionIndex = i;
122+
break;
123+
}
124+
}
125+
if (expressionIndex == -1) {
126+
continue;
127+
}
128+
// When the expression is the last element, nextPossibleInputs will contain "\0" (end of line)
129+
// which we handle below, so we should NOT skip this case!
130+
var nextPossibleInputs = PatternElement.getPossibleInputs(flattened.subList(expressionIndex + 1, flattened.size()));
110131
if (nextPossibleInputs.stream().anyMatch(pe -> !(pe instanceof TextElement))) {
111132
continue;
112133
}
113134
for (var nextPossibleInput : nextPossibleInputs) {
114135
var text = ((TextElement) nextPossibleInput).getText();
115-
if (text.equals("")) {
136+
if (text.equals("\0")) {
137+
// End of line marker - parse the rest and we're done
116138
var rest = s.substring(index);
117139
var splits = splitAtSpaces(rest);
118-
for (var split : splits) {
119-
var i = StringUtils.indexOfIgnoreCase(s, split, index);
120-
if (i != -1) {
121-
var toParse = s.substring(index, i);
122-
var expression = parse(toParse, typeArray, context.getParserState(), logger);
123-
if (expression.isPresent()) {
124-
context.addExpression(expression.get());
125-
return index + toParse.length();
140+
if (splits.isEmpty()) {
141+
return -1;
142+
}
143+
// Try parsing progressively larger prefixes
144+
for (var splitCount = 1; splitCount < splits.size(); splitCount++) {
145+
var endIndex = index;
146+
for (var j = 0; j < splitCount; j++) {
147+
var splitIndex = s.indexOf(splits.get(j), endIndex);
148+
if (splitIndex == -1) {
149+
break;
150+
}
151+
endIndex = splitIndex + splits.get(j).length();
152+
}
153+
while (endIndex < s.length() && Character.isWhitespace(s.charAt(endIndex))) {
154+
endIndex++;
155+
}
156+
if (endIndex > index) {
157+
var toParse = s.substring(index, endIndex).strip();
158+
if (!toParse.isEmpty()) {
159+
var expression = parse(toParse, typeArray, context.getParserState(), logger);
160+
if (expression.isPresent()) {
161+
context.addExpression(expression.get());
162+
return endIndex;
163+
}
164+
}
165+
}
166+
}
167+
return -1;
168+
} else if (text.isEmpty() || text.isBlank()) {
169+
var rest = s.substring(index);
170+
var splits = splitAtSpaces(rest);
171+
if (splits.isEmpty()) {
172+
return -1;
173+
}
174+
// Try parsing progressively larger prefixes (first 1 token, then first 2 tokens, etc.)
175+
for (var splitCount = 1; splitCount < splits.size(); splitCount++) {
176+
var endIndex = index;
177+
for (var j = 0; j < splitCount; j++) {
178+
var splitIndex = s.indexOf(splits.get(j), endIndex);
179+
if (splitIndex == -1) {
180+
break;
181+
}
182+
endIndex = splitIndex + splits.get(j).length();
183+
}
184+
// Find the start of the next token (skip whitespace)
185+
while (endIndex < s.length() && Character.isWhitespace(s.charAt(endIndex))) {
186+
endIndex++;
187+
}
188+
if (endIndex > index) {
189+
var toParse = s.substring(index, endIndex).strip();
190+
if (!toParse.isEmpty()) {
191+
var expression = parse(toParse, typeArray, context.getParserState(), logger);
192+
if (expression.isPresent()) {
193+
context.addExpression(expression.get());
194+
return endIndex;
195+
}
126196
}
127197
}
128198
}
@@ -137,11 +207,14 @@ public int match(String s, int index, MatchContext context) {
137207
for (var split : splits) {
138208
var i = StringUtils.indexOfIgnoreCase(s, split, index);
139209
if (i != -1) {
140-
var toParse = s.substring(index, i);
210+
var toParse = s.substring(index, i).strip();
211+
if (toParse.isEmpty()) {
212+
continue;
213+
}
141214
var expression = parse(toParse, typeArray, context.getParserState(), logger);
142215
if (expression.isPresent()) {
143216
context.addExpression(expression.get());
144-
return index + toParse.length();
217+
return i;
145218
}
146219
}
147220
}
@@ -152,6 +225,36 @@ public int match(String s, int index, MatchContext context) {
152225
return -1;
153226
}
154227

228+
/**
229+
* Finds the index of text in a string, respecting word boundaries for keywords like "or", "and", "nor".
230+
* @param s the string to search in
231+
* @param text the text to find
232+
* @param start the starting index
233+
* @return the index where text was found, or -1 if not found
234+
*/
235+
private int findTextWithBoundary(String s, String text, int start) {
236+
if (text.isEmpty()) {
237+
return -1;
238+
}
239+
// Check if this is a keyword that needs word boundary checking
240+
var lowerText = text.toLowerCase();
241+
var needsBoundaryCheck = lowerText.equals("or") || lowerText.equals("and") || lowerText.equals("nor");
242+
243+
var i = StringUtils.indexOfIgnoreCase(s, text, start);
244+
while (i != -1 && needsBoundaryCheck) {
245+
// Check word boundaries
246+
var beforeIsWordChar = i > 0 && Character.isLetterOrDigit(s.charAt(i - 1));
247+
var afterIsWordChar = (i + text.length() < s.length()) && Character.isLetterOrDigit(s.charAt(i + text.length()));
248+
249+
if (!beforeIsWordChar && !afterIsWordChar) {
250+
return i; // Valid word boundary match
251+
}
252+
// Try next occurrence
253+
i = StringUtils.indexOfIgnoreCase(s, text, i + 1);
254+
}
255+
return i;
256+
}
257+
155258
private List<String> splitAtSpaces(String s) {
156259
List<String> split = new ArrayList<>();
157260
var sb = new StringBuilder();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.github.syst3ms.skriptparser;
2+
3+
import io.github.syst3ms.skriptparser.lang.Expression;
4+
import io.github.syst3ms.skriptparser.log.SkriptLogger;
5+
import io.github.syst3ms.skriptparser.parsing.ParserState;
6+
import io.github.syst3ms.skriptparser.parsing.SyntaxParser;
7+
import io.github.syst3ms.skriptparser.types.PatternType;
8+
import io.github.syst3ms.skriptparser.types.TypeManager;
9+
import org.junit.Test;
10+
11+
import static org.junit.Assert.fail;
12+
13+
public class ClaudeDebugTest {
14+
static {
15+
TestRegistration.register();
16+
}
17+
18+
@Test
19+
public void testClaudePattern() {
20+
var logger = new SkriptLogger(true);
21+
var parserState = new ParserState();
22+
23+
// Test case 1: with space between numbers
24+
String input1 = "claude test 1 2";
25+
var type = TypeManager.getByClass(String.class).orElseThrow();
26+
var patternType = new PatternType<>(type, true);
27+
28+
System.out.println("Testing: " + input1);
29+
var result1 = SyntaxParser.parseExpression(input1, patternType, parserState, logger);
30+
31+
if (result1.isEmpty()) {
32+
System.out.println("FAILED to parse: " + input1);
33+
fail("Failed to parse: " + input1);
34+
} else {
35+
System.out.println("SUCCESS: " + input1);
36+
Expression<?> expr = result1.get();
37+
System.out.println(" Parsed expression: " + expr.getClass().getSimpleName());
38+
}
39+
40+
// Test case 2: with "of" between numbers
41+
logger.clearErrors();
42+
logger.clearLogs();
43+
String input2 = "claude test 1 of 2";
44+
System.out.println("\nTesting: " + input2);
45+
var result2 = SyntaxParser.parseExpression(input2, patternType, parserState, logger);
46+
47+
if (result2.isEmpty()) {
48+
System.out.println("FAILED to parse: " + input2);
49+
fail("Failed to parse: " + input2);
50+
} else {
51+
System.out.println("SUCCESS: " + input2);
52+
Expression<?> expr = result2.get();
53+
System.out.println(" Parsed expression: " + expr.getClass().getSimpleName());
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)