From f4808c2d5d830cfd057b3b39e9576cf19ceec93d Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Sun, 8 Mar 2026 13:35:28 -0700 Subject: [PATCH 1/7] FileParser - add multi line comments --- .../io/github/syst3ms/skriptparser/file/FileParser.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/file/FileParser.java b/src/main/java/io/github/syst3ms/skriptparser/file/FileParser.java index 6e709ba2..6b6dbfc1 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/file/FileParser.java +++ b/src/main/java/io/github/syst3ms/skriptparser/file/FileParser.java @@ -30,11 +30,18 @@ public class FileParser { */ public static List parseFileLines(String fileName, List lines, int expectedIndentation, int lastLine, SkriptLogger logger) { List elements = new ArrayList<>(); + boolean multiLineComment = false; for (var i = 0; i < lines.size(); i++) { var line = lines.get(i); + + // Multi line comment sections + if (!line.isBlank() && line.startsWith("###")) { + multiLineComment = !multiLineComment; + } + String content = removeComments(line); - if (content.isEmpty()) { + if (multiLineComment || content.isEmpty()) { elements.add(new VoidElement(fileName, lastLine + i, expectedIndentation)); continue; } From 9012976f65b62a8a1d41a4405eea71e0ee3e3d54 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 10 Mar 2026 11:55:55 -0700 Subject: [PATCH 2/7] Fix expression parsing with space-separated expressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes a bug where patterns with two expressions separated only by whitespace or optional elements would fail to parse. The issue was in ExpressionElement.java at line 135-137, where the code incorrectly skipped processing when the next expression was the last element in the pattern. This caused patterns like: - "claude test %number% [of] %number%" - "location %direction% [of] %location%" To fail when parsing inputs like: - "claude test 1 2" - "location above {_loc}" The fix removes the incorrect boundary check. When an expression is the last element, getPossibleInputs() correctly returns the end-of-line marker "\0", which is properly handled by the existing code. Includes unit test (ClaudeDebugTest) that verifies both space-separated and optional text cases work correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../pattern/ExpressionElement.java | 97 ++++++++++++++++--- .../syst3ms/skriptparser/ClaudeDebugTest.java | 56 +++++++++++ 2 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java diff --git a/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java b/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java index 098f1084..1c1781db 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java +++ b/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java @@ -106,23 +106,93 @@ public int match(String s, int index, MatchContext context) { } } else { assert possibleInput instanceof ExpressionElement; - var nextPossibleInputs = PatternElement.getPossibleInputs(flattened.subList(context.getPatternIndex() + 1, flattened.size())); + // Find the index of the next expression element in the flattened list + // We need to find the first ExpressionElement after the current position + var expressionIndex = -1; + for (var i = possibilityIndex + 1; i < flattened.size(); i++) { + var elem = flattened.get(i); + // Skip optional groups and look inside them + if (elem instanceof OptionalGroup) { + var inner = PatternElement.flatten(((OptionalGroup) elem).getElement()); + if (inner.stream().anyMatch(e -> e instanceof ExpressionElement)) { + continue; // Skip optional groups containing expressions + } + } else if (elem instanceof ExpressionElement) { + expressionIndex = i; + break; + } + } + if (expressionIndex == -1) { + continue; + } + // When the expression is the last element, nextPossibleInputs will contain "\0" (end of line) + // which we handle below, so we should NOT skip this case! + var nextPossibleInputs = PatternElement.getPossibleInputs(flattened.subList(expressionIndex + 1, flattened.size())); if (nextPossibleInputs.stream().anyMatch(pe -> !(pe instanceof TextElement))) { continue; } for (var nextPossibleInput : nextPossibleInputs) { var text = ((TextElement) nextPossibleInput).getText(); - if (text.equals("")) { + if (text.equals("\0")) { + // End of line marker - parse the rest and we're done var rest = s.substring(index); var splits = splitAtSpaces(rest); - for (var split : splits) { - var i = StringUtils.indexOfIgnoreCase(s, split, index); - if (i != -1) { - var toParse = s.substring(index, i); - var expression = parse(toParse, typeArray, context.getParserState(), logger); - if (expression.isPresent()) { - context.addExpression(expression.get()); - return index + toParse.length(); + if (splits.isEmpty()) { + return -1; + } + // Try parsing progressively larger prefixes + for (var splitCount = 1; splitCount < splits.size(); splitCount++) { + var endIndex = index; + for (var j = 0; j < splitCount; j++) { + var splitIndex = s.indexOf(splits.get(j), endIndex); + if (splitIndex == -1) { + break; + } + endIndex = splitIndex + splits.get(j).length(); + } + while (endIndex < s.length() && Character.isWhitespace(s.charAt(endIndex))) { + endIndex++; + } + if (endIndex > index) { + var toParse = s.substring(index, endIndex).strip(); + if (!toParse.isEmpty()) { + var expression = parse(toParse, typeArray, context.getParserState(), logger); + if (expression.isPresent()) { + context.addExpression(expression.get()); + return endIndex; + } + } + } + } + return -1; + } else if (text.isEmpty() || text.isBlank()) { + var rest = s.substring(index); + var splits = splitAtSpaces(rest); + if (splits.isEmpty()) { + return -1; + } + // Try parsing progressively larger prefixes (first 1 token, then first 2 tokens, etc.) + for (var splitCount = 1; splitCount < splits.size(); splitCount++) { + var endIndex = index; + for (var j = 0; j < splitCount; j++) { + var splitIndex = s.indexOf(splits.get(j), endIndex); + if (splitIndex == -1) { + break; + } + endIndex = splitIndex + splits.get(j).length(); + } + // Find the start of the next token (skip whitespace) + while (endIndex < s.length() && Character.isWhitespace(s.charAt(endIndex))) { + endIndex++; + } + if (endIndex > index) { + var toParse = s.substring(index, endIndex).strip(); + if (!toParse.isEmpty()) { + var expression = parse(toParse, typeArray, context.getParserState(), logger); + if (expression.isPresent()) { + context.addExpression(expression.get()); + return endIndex; + } } } } @@ -137,11 +207,14 @@ public int match(String s, int index, MatchContext context) { for (var split : splits) { var i = StringUtils.indexOfIgnoreCase(s, split, index); if (i != -1) { - var toParse = s.substring(index, i); + var toParse = s.substring(index, i).strip(); + if (toParse.isEmpty()) { + continue; + } var expression = parse(toParse, typeArray, context.getParserState(), logger); if (expression.isPresent()) { context.addExpression(expression.get()); - return index + toParse.length(); + return i; } } } diff --git a/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java b/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java new file mode 100644 index 00000000..faeb97d4 --- /dev/null +++ b/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java @@ -0,0 +1,56 @@ +package io.github.syst3ms.skriptparser; + +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.parsing.ParserState; +import io.github.syst3ms.skriptparser.parsing.SyntaxParser; +import io.github.syst3ms.skriptparser.types.PatternType; +import io.github.syst3ms.skriptparser.types.TypeManager; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class ClaudeDebugTest { + static { + TestRegistration.register(); + } + + @Test + public void testClaudePattern() { + var logger = new SkriptLogger(true); + var parserState = new ParserState(); + + // Test case 1: with space between numbers + String input1 = "claude test 1 2"; + var type = TypeManager.getByClass(String.class).orElseThrow(); + var patternType = new PatternType<>(type, true); + + System.out.println("Testing: " + input1); + var result1 = SyntaxParser.parseExpression(input1, patternType, parserState, logger); + + if (result1.isEmpty()) { + System.out.println("FAILED to parse: " + input1); + fail("Failed to parse: " + input1); + } else { + System.out.println("SUCCESS: " + input1); + Expression expr = result1.get(); + System.out.println(" Parsed expression: " + expr.getClass().getSimpleName()); + } + + // Test case 2: with "of" between numbers + logger.clearErrors(); + logger.clearLogs(); + String input2 = "claude test 1 of 2"; + System.out.println("\nTesting: " + input2); + var result2 = SyntaxParser.parseExpression(input2, patternType, parserState, logger); + + if (result2.isEmpty()) { + System.out.println("FAILED to parse: " + input2); + fail("Failed to parse: " + input2); + } else { + System.out.println("SUCCESS: " + input2); + Expression expr = result2.get(); + System.out.println(" Parsed expression: " + expr.getClass().getSimpleName()); + } + } +} From 6ba4e779689128d5f7a1c40135f1efc1763b2293 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 10 Mar 2026 11:58:51 -0700 Subject: [PATCH 3/7] ExprClaudeTest - add for testing --- .../expressions/ExprClaudeTest.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/io/github/syst3ms/skriptparser/expressions/ExprClaudeTest.java diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprClaudeTest.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprClaudeTest.java new file mode 100644 index 00000000..e122ae82 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprClaudeTest.java @@ -0,0 +1,40 @@ +package io.github.syst3ms.skriptparser.expressions; + +import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.parsing.ParseContext; + +public class ExprClaudeTest implements Expression { + + static { + Parser.getMainRegistration().newExpression(ExprClaudeTest.class, String.class, + true, "claude test %number% [of] %number%") + .noDoc() + .register(); + } + + private Expression first, second; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + first = (Expression) expressions[0]; + second = (Expression) expressions[1]; + return true; + } + + @Override + public String[] getValues(TriggerContext ctx) { + Number number = this.first.getSingle(ctx).orElse(null); + Number other = this.second.getSingle(ctx).orElse(null); + if (number != null && other != null) { + return new String[]{"claude test " + number + " " + other}; + } + return new String[]{}; + } + + @Override + public String toString(TriggerContext ctx, boolean debug) { + return ""; + } +} From 780fbac5da45bec5fd3bc5fc43263f62a82e656f Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 10 Mar 2026 12:01:13 -0700 Subject: [PATCH 4/7] ClaudeDebugTest - checkstyle --- .../java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java b/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java index faeb97d4..f7ab31ff 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/ClaudeDebugTest.java @@ -8,7 +8,7 @@ import io.github.syst3ms.skriptparser.types.TypeManager; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.fail; public class ClaudeDebugTest { static { From ad99ca2f80b521f1f711591468c66bf07e13168b Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 10 Mar 2026 12:25:59 -0700 Subject: [PATCH 5/7] Fix a bug with errors being cut off in weird spaces --- .../skriptparser/parsing/SyntaxParser.java | 2 +- .../pattern/ExpressionElement.java | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java index c0362edc..6953a2cd 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java @@ -356,7 +356,7 @@ private static Optional> matchExpressionIn */ public static Optional> parseListLiteral(String s, PatternType expectedType, ParserState parserState, SkriptLogger logger) { assert !expectedType.isSingle(); - if (!s.contains(",") && !s.contains("and") && !s.contains("nor") && !s.contains("or")) + if (!s.contains(",") && !s.matches(".*\\b(?:and|n?or)\\b.*")) return Optional.empty(); List parts = new ArrayList<>(); var m = LIST_SPLIT_PATTERN.matcher(s); diff --git a/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java b/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java index 1c1781db..7f7f6f36 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java +++ b/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java @@ -78,7 +78,7 @@ public int match(String s, int index, MatchContext context) { } return -1; } - var i = StringUtils.indexOfIgnoreCase(s, text, index); + var i = findTextWithBoundary(s, text.strip(), index); while (i != -1) { var toParse = s.substring(index, i).strip(); var expression = parse(toParse, typeArray, context.getParserState(), logger); @@ -86,7 +86,7 @@ public int match(String s, int index, MatchContext context) { context.addExpression(expression.get()); return index + toParse.length(); } - i = StringUtils.indexOfIgnoreCase(s, text, i + 1); + i = findTextWithBoundary(s, text.strip(), i + 1); } } else if (possibleInput instanceof RegexGroup) { var m = ((RegexGroup) possibleInput).getPattern().matcher(s).region(index, s.length()); @@ -225,6 +225,36 @@ public int match(String s, int index, MatchContext context) { return -1; } + /** + * Finds the index of text in a string, respecting word boundaries for keywords like "or", "and", "nor". + * @param s the string to search in + * @param text the text to find + * @param start the starting index + * @return the index where text was found, or -1 if not found + */ + private int findTextWithBoundary(String s, String text, int start) { + if (text.isEmpty()) { + return -1; + } + // Check if this is a keyword that needs word boundary checking + var lowerText = text.toLowerCase(); + var needsBoundaryCheck = lowerText.equals("or") || lowerText.equals("and") || lowerText.equals("nor"); + + var i = StringUtils.indexOfIgnoreCase(s, text, start); + while (i != -1 && needsBoundaryCheck) { + // Check word boundaries + var beforeIsWordChar = i > 0 && Character.isLetterOrDigit(s.charAt(i - 1)); + var afterIsWordChar = (i + text.length() < s.length()) && Character.isLetterOrDigit(s.charAt(i + text.length())); + + if (!beforeIsWordChar && !afterIsWordChar) { + return i; // Valid word boundary match + } + // Try next occurrence + i = StringUtils.indexOfIgnoreCase(s, text, i + 1); + } + return i; + } + private List splitAtSpaces(String s) { List split = new ArrayList<>(); var sb = new StringBuilder(); From 826c262964e2f7178f5980930ab61dff9d97f0c2 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 10 Mar 2026 12:34:14 -0700 Subject: [PATCH 6/7] Variable - better toString --- .../java/io/github/syst3ms/skriptparser/lang/Variable.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java b/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java index 1a4b68fb..d240187f 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java @@ -424,6 +424,11 @@ public boolean isIndexLoop(String s) { @Override public String toString(TriggerContext ctx, boolean debug) { + // When in debug mode or using a dummy context, show the variable name instead of its value + if (debug || ctx == TriggerContext.DUMMY) { + String l = this.local ? "_" : ""; + return "{" + l + name.toString(ctx) + "}"; + } return TypeManager.toString(getValues(ctx)); } From 0c7b077e2db3a6c6a6b2c565a59c76d1f1487879 Mon Sep 17 00:00:00 2001 From: ShaneBeee Date: Tue, 10 Mar 2026 12:34:35 -0700 Subject: [PATCH 7/7] ExpressionList - fix not being able to delete more than 1 var at a time --- .../skriptparser/lang/ExpressionList.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/ExpressionList.java b/src/main/java/io/github/syst3ms/skriptparser/lang/ExpressionList.java index eedb88f1..6bb03155 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/ExpressionList.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/ExpressionList.java @@ -1,6 +1,7 @@ package io.github.syst3ms.skriptparser.lang; import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.changers.ChangeMode; import io.github.syst3ms.skriptparser.util.ClassUtils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; @@ -209,4 +210,25 @@ public Expression getSource() { return source != null ? source : this; } + @Override + public Optional[]> acceptsChange(ChangeMode mode) { + // Check if all expressions in the list accept this change mode + for (var expr : expressions) { + if (expr.acceptsChange(mode).isEmpty()) { + return Optional.empty(); + } + } + // If all expressions accept the change, return the accepted types from the first expression + // (assuming all expressions have compatible types) + return expressions[0].acceptsChange(mode); + } + + @Override + public void change(TriggerContext ctx, ChangeMode mode, Object[] changeWith) throws UnsupportedOperationException { + // Apply the change to all expressions in the list + for (var expr : expressions) { + expr.change(ctx, mode, changeWith); + } + } + }