From 5b040f344357cf08cc2ea6e632955d88503c66e1 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Sun, 17 Aug 2025 17:27:20 -0400 Subject: [PATCH 01/17] Create PatternConflictsTest.java --- .../tests/syntaxes/PatternConflictsTest.java | 278 ++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java new file mode 100644 index 00000000000..93f9d34b3af --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java @@ -0,0 +1,278 @@ +package org.skriptlang.skript.test.tests.syntaxes; + +import ch.njol.skript.Skript; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.util.StringUtils; +import org.junit.Test; +import org.skriptlang.skript.registration.SyntaxInfo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class PatternConflictsTest extends SkriptJUnitTest { + + private static final Map>> REGISTERED_PATTERNS = new HashMap<>(); + + @Test + public void test() { + Skript.adminBroadcast(StringUtils.join( + new PatternParser("[all [of the]|the] entities [of %-world%]" + .replaceAll("%.+%", "%*%") + .replaceAll("[a-zA-Z0-9]+:", "") + .replaceAll(":", "")).getCombinations(), + "\n" + )); + /* + all entities + all entities of %world% + all of the entities + all of the entities of %world% + the entities + the entities of %world% + entities + entities of %world% + */ + + Skript.adminBroadcast(StringUtils.join( + new PatternParser("[all [of the]|the] [:typed] entities [of %-world%]" + .replaceAll("%.+%", "%*%") + .replaceAll("[a-zA-Z0-9]+:", "") + .replaceAll(":", "")).getCombinations(), + "\n" + )); + /* + all typed entities + all typed entities of %world% + all entities + all entities of %world% + all of the typed entities + all of the typed entities of %world% + all of the entities + all of the entities of %world% + the typed entities + the typed entities of %world% + the entities + the entities of %world% + typed entities + typed entities of %world% + entities + entities of %world% + */ + } + + public void testPatterns() { + + + Collection> elements = Skript.instance().syntaxRegistry().elements(); + for (SyntaxInfo syntaxInfo : elements) { + Collection patterns = syntaxInfo.patterns(); + Class elementClass = syntaxInfo.type(); + + for (String pattern : patterns) { + PatternParser parser = new PatternParser(pattern + .replaceAll("%.+%", "%*%") + .replaceAll("[a-zA-Z0-9]+:", "") + .replaceAll(":", "")); + } + } + } + + private static class PatternParser { + + private final String pattern; + private final int startIndex; + private final boolean isGroup; + private boolean isOptional; + private int endIndex = 0; + private final List combinations = new ArrayList<>(); + + private PatternParser(String pattern) { + this.pattern = pattern; + startIndex = 0; + isGroup = false; + init(); + } + + private PatternParser(String pattern, int startIndex, boolean isOptional) { + this.pattern = pattern; + this.startIndex = startIndex; + this.isOptional = isOptional; + isGroup = true; + init(); + } + + private void init() { + List segments = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + boolean hasSelector = false; + for (int i = startIndex; i < pattern.length(); i++) { + char c = pattern.charAt(i); + if (c == '(') { + segments.add(builder.toString()); + builder = new StringBuilder(); + PatternParser group = new PatternParser(pattern, i + 1, false); + i = group.getEndIndex(); + segments.add(group); + } else if (c == '[') { + segments.add(builder.toString()); + builder = new StringBuilder(); + PatternParser group = new PatternParser(pattern, i + 1, true); + i = group.getEndIndex(); + segments.add(group); + } else if (isGroup && ((!isOptional && c == ')') || (isOptional && c == ']'))) { + endIndex = i; + break; + } else { + if (c == '|') + hasSelector = true; + builder.append(c); + } + } + if (!builder.isEmpty()) { + segments.add(builder.toString()); + } + + if (isGroup && hasSelector) { + List choices = new ArrayList<>(); + for (int i = 0; i < segments.size(); i++) { + Object segment = segments.get(i); + if (segment instanceof String string) { + List split = new ArrayList<>(Arrays.stream(string.split("\\|")).toList()); + if (!string.startsWith("|") && i > 0 && choices.get(choices.size() - 1) instanceof PatternParser group) { + choices.remove(choices.size() - 1); + String suffix = split.get(0); + choices.add(new PatternStringGroup(group, suffix)); + split.remove(0); + } + if (!string.endsWith("|") && i < segments.size() - 1 && segments.get(i + 1) instanceof PatternParser group) { + String prefix = split.get(split.size() - 1); + choices.add(new PatternStringGroup(prefix, group)); + i++; + split.remove(split.size() - 1); + } + choices.addAll(split); + } else { + choices.add(segment); + } + } + combinations.addAll(new PatternChoices(choices).getCombinations()); + if (isOptional) + combinations.add(""); + } else { + for (Object segment : segments) { + if (segment instanceof String string) { + apply(List.of(string)); + } else if (segment instanceof PatternParser parser) { + apply(parser.getCombinations()); + } + } + if (isGroup && isOptional) + combinations.add(""); + } + } + + private void apply(List strings) { + if (combinations.isEmpty()) { + combinations.addAll(strings); + return; + } + List copy = List.copyOf(combinations); + combinations.clear(); + for (String base : copy) { + for (String add : strings) { + String combined = combine(base.trim(), add.trim()); + if (!combinations.contains(combined)) + combinations.add(combined); + } + } + } + + private int getEndIndex() { + assert isGroup; + return endIndex; + } + + private List getCombinations() { + return combinations; + } + + } + + private static class PatternChoices { + + private final List choices; + private final List combinations = new ArrayList<>(); + + private PatternChoices(List choices) { + this.choices = choices; + for (Object choice : choices) { + if (choice instanceof String string) { + combinations.add(string); + } else if (choice instanceof PatternParser parser) { + combinations.addAll(parser.getCombinations()); + } else if (choice instanceof PatternStringGroup stringGroup) { + combinations.addAll(stringGroup.getCombinations()); + } + } + } + + private List getCombinations() { + return combinations; + } + + } + + private static class PatternStringGroup { + + private final boolean isPrefix; + private final String string; + private final PatternParser group; + private final List combinations = new ArrayList<>(); + + private PatternStringGroup(String string, PatternParser group) { + this.string = string; + this.group = group; + isPrefix = true; + init(); + } + + private PatternStringGroup(PatternParser group, String string) { + this.group = group; + this.string = string; + isPrefix = false; + init(); + } + + private void init() { + List combos = group.getCombinations(); + if (isPrefix) { + for (String combo : combos) { + combinations.add(combine(string, combo)); + } + } else { + for (String combo : combos) { + combinations.add(combine(combo, string)); + } + } + } + + private List getCombinations() { + return combinations; + } + + } + + private static String combine(String first, String last) { + if (first.endsWith(" ") && last.startsWith(" ")) { + return first + last.substring(1, last.length() - 1); + } else if (!first.endsWith(" ") && !last.startsWith(" ")) { + return first + " " + last; + } + return first + last; + } + +} From 0d6d12eba63cd33154d67acf240940a158369e67 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Mon, 18 Aug 2025 13:20:15 -0400 Subject: [PATCH 02/17] Draft --- gradle.properties | 2 +- .../skript/lang/util/PatternParser.java | 192 ++++++++++++ .../test/tests/lang/PatternParserTest.java | 102 +++++++ .../tests/syntaxes/PatternConflictsTest.java | 278 ------------------ 4 files changed, 295 insertions(+), 279 deletions(-) create mode 100644 src/main/java/org/skriptlang/skript/lang/util/PatternParser.java create mode 100644 src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java delete mode 100644 src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java diff --git a/gradle.properties b/gradle.properties index 55586f5bc20..ea1c84fb58e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Done to increase the memory available to gradle. # Ensure encoding is consistent across systems. -org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4G -Dfile.encoding=UTF-8 org.gradle.parallel=true groupid=ch.njol diff --git a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java new file mode 100644 index 00000000000..bf8cb411f9e --- /dev/null +++ b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java @@ -0,0 +1,192 @@ +package org.skriptlang.skript.lang.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +/** + * Parser used to grab all combinations of a pattern + */ +public class PatternParser { + + private final String pattern; + private final int startIndex; + private final boolean isGroup; + private boolean isOptional; + private int endIndex = -1; + private final LinkedHashSet combinations = new LinkedHashSet<>(); + + public PatternParser(String pattern) { + this.pattern = pattern; + startIndex = 0; + isGroup = false; + init(); + } + + public PatternParser(String pattern, int startIndex, boolean isOptional) { + this.pattern = pattern; + this.startIndex = startIndex; + this.isOptional = isOptional; + isGroup = true; + init(); + } + + private void init() { + List segments = new ArrayList<>(); + StringBuilder builder = new StringBuilder(); + boolean hasSelector = false; + for (int i = startIndex; i < pattern.length(); i++) { + char c = pattern.charAt(i); + if (c == '(' && (i == 0 || pattern.charAt(i - 1) != '\\')) { + segments.add(builder.toString()); + builder = new StringBuilder(); + PatternParser group = new PatternParser(pattern, i + 1, false); + i = group.getEndIndex(); + segments.add(group); + } else if (c == '[' && (i == 0 || pattern.charAt(i - 1) != '\\')) { + segments.add(builder.toString()); + builder = new StringBuilder(); + PatternParser group = new PatternParser(pattern, i + 1, true); + i = group.getEndIndex(); + segments.add(group); + } else if (isGroup && ((!isOptional && c == ')') || (isOptional && c == ']'))) { + endIndex = i; + break; + } else { + if (c == '|') + hasSelector = true; + builder.append(c); + } + } + if (isGroup && endIndex == -1) { + String closing = isOptional ? "]" : ")"; + throw new RuntimeException("Could not find closing '" + closing + "': " + pattern.substring(0, startIndex)); + } + if (!builder.isEmpty()) { + segments.add(builder.toString()); + } + + if (isGroup && hasSelector) { + List choices = new ArrayList<>(); + List current = new ArrayList<>(); + for (int i = 0; i < segments.size(); i++) { + Object segment = segments.get(i); + if (segment instanceof String string) { + if (string.contains("|")) { + List split = new ArrayList<>(Arrays.stream(string.split("\\|")).toList()); + if (!string.startsWith("|")) { + String first = split.remove(0); + current.add(first); + } + choices.addAll(combineChoices(current)); + current.clear(); + + if (!split.isEmpty()) { + current.add(split.remove(split.size() - 1)); + if (!split.isEmpty()) { + choices.addAll(split); + } + } + } else { + current.add(string); + } + } else { + current.add(segment); + } + } + if (!current.isEmpty()) { + choices.addAll(combineChoices(current)); + } + for (Object choice : choices) { + if (choice instanceof String string) { + combinations.add(string); + } else if (choice instanceof PatternParser parser) { + combinations.addAll(parser.getCombinations()); + } + } + if (isOptional) + combinations.add(""); + } else { + for (Object segment : segments) { + if (segment instanceof String string) { + apply(Set.of(string)); + } else if (segment instanceof PatternParser parser) { + apply(parser.getCombinations()); + } + } + if (isGroup && isOptional) + combinations.add(""); + } + } + + private void apply(Set strings) { + if (combinations.isEmpty()) { + combinations.addAll(strings); + return; + } + Set newCombinations = new HashSet<>(); + for (String base : combinations) { + for (String add : strings) { + newCombinations.add(combine(base, add)); + } + } + combinations.clear(); + combinations.addAll(newCombinations); + } + + private int getEndIndex() { + assert isGroup; + return endIndex; + } + + private String combine(String first, String second) { + if (first.isEmpty()) { + return second.trim(); + } else if (second.isEmpty()) { + return first.trim(); + } else if (first.endsWith(" ") && second.startsWith(" ")) { + return first + second.substring(1); + } else if (!first.endsWith(" ") && !second.startsWith(" ")) { + return first + " " + second; + } + return first + second; + } + + private Set combineChoices(List choices) { + if (choices.isEmpty()) + return Collections.emptySet(); + Set combinations = new HashSet<>(); + Object first = choices.remove(0); + if (first instanceof String string) { + combinations.add(string); + } else if (first instanceof PatternParser parser) { + combinations = parser.getCombinations(); + } + for (Object choice : choices) { + Set newCombinations = new HashSet<>(); + Set current = new HashSet<>(); + if (choice instanceof String string) { + current.add(string); + } else if (choice instanceof PatternParser parser) { + current = parser.getCombinations(); + } + for (String base : combinations) { + for (String add : current) { + newCombinations.add(combine(base, add)); + } + } + combinations.clear(); + combinations.addAll(newCombinations); + } + return combinations; + } + + public Set getCombinations() { + return combinations; + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java new file mode 100644 index 00000000000..9ff4b310d82 --- /dev/null +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java @@ -0,0 +1,102 @@ +package org.skriptlang.skript.test.tests.lang; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.test.runner.SkriptJUnitTest; +import ch.njol.util.StringUtils; +import org.junit.Assert; +import org.junit.Test; +import org.skriptlang.skript.lang.util.PatternParser; +import org.skriptlang.skript.registration.SyntaxInfo; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class PatternParserTest extends SkriptJUnitTest { + + private static String regexPattern(String pattern) { + return pattern.replaceAll("%\\S+%", "%*%") + .replaceAll("[a-zA-Z0-9]+:", "") + .replaceAll(":", "") + .replaceAll("[0-9]+¦", ""); + } + + @Test + public void test() { + Assert.assertEquals( + new PatternParser(regexPattern("[all [of the]|the] entities [of %-world%]")).getCombinations(), + Set.of( + "all entities", "all entities of %*%", + "all of the entities", "all of the entities of %*%", + "the entities", "the entities of %*%", + "entities", "entities of %*%" + ) + ); + + Assert.assertEquals( + new PatternParser(regexPattern("[all [of the]|the] [:typed] entities [of %-world%]")).getCombinations(), + Set.of( + "all typed entities", "all typed entities of %*%", + "all entities", "all entities of %*%", + "all of the typed entities", "all of the typed entities of %*%", + "all of the entities", "all of the entities of %*%", + "the typed entities", "the typed entities of %*%", + "the entities", "the entities of %*%", + "typed entities", "typed entities of %*%", + "entities", "entities of %*%" + ) + ); + + } + + @Test + public void testPatterns() { + Map>> registeredPatterns = new HashMap<>(); + Set hasMultiple = new HashSet<>(); + + Collection> elements = Skript.instance().syntaxRegistry().elements(); + Skript.adminBroadcast("Total elements: " + elements.size()); + int elementCounter = 0; + int patternCounter = 0; + int combinationCounter = 0; + for (SyntaxInfo syntaxInfo : elements) { + Collection patterns = syntaxInfo.patterns(); + Class elementClass = syntaxInfo.type(); + + elementCounter++; + Skript.adminBroadcast("Element Counter: " + elementCounter); + for (String pattern : patterns) { + patternCounter++; + Skript.adminBroadcast("Pattern Counter: " + patternCounter); + Skript.adminBroadcast("Pattern: " + pattern); + PatternParser parser = new PatternParser(regexPattern(pattern)); + for (String combination : parser.getCombinations()) { + combinationCounter++; + Skript.adminBroadcast("Combination Counter: " + combinationCounter); + registeredPatterns.computeIfAbsent(combination, set -> new HashSet<>()).add(elementClass); + if (registeredPatterns.get(combination).size() > 2) + hasMultiple.add(combination); + } + } + } + + if (hasMultiple.isEmpty()) + return; + + List errors = new ArrayList<>(); + for (String string : hasMultiple) { + List names = registeredPatterns.get(string).stream() + .map(Class::getCanonicalName) + .toList(); + String error = "The pattern '" + string + "' conflicts in: " + StringUtils.join(names, ", ", ", and "); + errors.add(error); + } + throw new SkriptAPIException(StringUtils.join(errors, "\n")); + } + +} diff --git a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java b/src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java deleted file mode 100644 index 93f9d34b3af..00000000000 --- a/src/test/java/org/skriptlang/skript/test/tests/syntaxes/PatternConflictsTest.java +++ /dev/null @@ -1,278 +0,0 @@ -package org.skriptlang.skript.test.tests.syntaxes; - -import ch.njol.skript.Skript; -import ch.njol.skript.test.runner.SkriptJUnitTest; -import ch.njol.util.StringUtils; -import org.junit.Test; -import org.skriptlang.skript.registration.SyntaxInfo; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class PatternConflictsTest extends SkriptJUnitTest { - - private static final Map>> REGISTERED_PATTERNS = new HashMap<>(); - - @Test - public void test() { - Skript.adminBroadcast(StringUtils.join( - new PatternParser("[all [of the]|the] entities [of %-world%]" - .replaceAll("%.+%", "%*%") - .replaceAll("[a-zA-Z0-9]+:", "") - .replaceAll(":", "")).getCombinations(), - "\n" - )); - /* - all entities - all entities of %world% - all of the entities - all of the entities of %world% - the entities - the entities of %world% - entities - entities of %world% - */ - - Skript.adminBroadcast(StringUtils.join( - new PatternParser("[all [of the]|the] [:typed] entities [of %-world%]" - .replaceAll("%.+%", "%*%") - .replaceAll("[a-zA-Z0-9]+:", "") - .replaceAll(":", "")).getCombinations(), - "\n" - )); - /* - all typed entities - all typed entities of %world% - all entities - all entities of %world% - all of the typed entities - all of the typed entities of %world% - all of the entities - all of the entities of %world% - the typed entities - the typed entities of %world% - the entities - the entities of %world% - typed entities - typed entities of %world% - entities - entities of %world% - */ - } - - public void testPatterns() { - - - Collection> elements = Skript.instance().syntaxRegistry().elements(); - for (SyntaxInfo syntaxInfo : elements) { - Collection patterns = syntaxInfo.patterns(); - Class elementClass = syntaxInfo.type(); - - for (String pattern : patterns) { - PatternParser parser = new PatternParser(pattern - .replaceAll("%.+%", "%*%") - .replaceAll("[a-zA-Z0-9]+:", "") - .replaceAll(":", "")); - } - } - } - - private static class PatternParser { - - private final String pattern; - private final int startIndex; - private final boolean isGroup; - private boolean isOptional; - private int endIndex = 0; - private final List combinations = new ArrayList<>(); - - private PatternParser(String pattern) { - this.pattern = pattern; - startIndex = 0; - isGroup = false; - init(); - } - - private PatternParser(String pattern, int startIndex, boolean isOptional) { - this.pattern = pattern; - this.startIndex = startIndex; - this.isOptional = isOptional; - isGroup = true; - init(); - } - - private void init() { - List segments = new ArrayList<>(); - StringBuilder builder = new StringBuilder(); - boolean hasSelector = false; - for (int i = startIndex; i < pattern.length(); i++) { - char c = pattern.charAt(i); - if (c == '(') { - segments.add(builder.toString()); - builder = new StringBuilder(); - PatternParser group = new PatternParser(pattern, i + 1, false); - i = group.getEndIndex(); - segments.add(group); - } else if (c == '[') { - segments.add(builder.toString()); - builder = new StringBuilder(); - PatternParser group = new PatternParser(pattern, i + 1, true); - i = group.getEndIndex(); - segments.add(group); - } else if (isGroup && ((!isOptional && c == ')') || (isOptional && c == ']'))) { - endIndex = i; - break; - } else { - if (c == '|') - hasSelector = true; - builder.append(c); - } - } - if (!builder.isEmpty()) { - segments.add(builder.toString()); - } - - if (isGroup && hasSelector) { - List choices = new ArrayList<>(); - for (int i = 0; i < segments.size(); i++) { - Object segment = segments.get(i); - if (segment instanceof String string) { - List split = new ArrayList<>(Arrays.stream(string.split("\\|")).toList()); - if (!string.startsWith("|") && i > 0 && choices.get(choices.size() - 1) instanceof PatternParser group) { - choices.remove(choices.size() - 1); - String suffix = split.get(0); - choices.add(new PatternStringGroup(group, suffix)); - split.remove(0); - } - if (!string.endsWith("|") && i < segments.size() - 1 && segments.get(i + 1) instanceof PatternParser group) { - String prefix = split.get(split.size() - 1); - choices.add(new PatternStringGroup(prefix, group)); - i++; - split.remove(split.size() - 1); - } - choices.addAll(split); - } else { - choices.add(segment); - } - } - combinations.addAll(new PatternChoices(choices).getCombinations()); - if (isOptional) - combinations.add(""); - } else { - for (Object segment : segments) { - if (segment instanceof String string) { - apply(List.of(string)); - } else if (segment instanceof PatternParser parser) { - apply(parser.getCombinations()); - } - } - if (isGroup && isOptional) - combinations.add(""); - } - } - - private void apply(List strings) { - if (combinations.isEmpty()) { - combinations.addAll(strings); - return; - } - List copy = List.copyOf(combinations); - combinations.clear(); - for (String base : copy) { - for (String add : strings) { - String combined = combine(base.trim(), add.trim()); - if (!combinations.contains(combined)) - combinations.add(combined); - } - } - } - - private int getEndIndex() { - assert isGroup; - return endIndex; - } - - private List getCombinations() { - return combinations; - } - - } - - private static class PatternChoices { - - private final List choices; - private final List combinations = new ArrayList<>(); - - private PatternChoices(List choices) { - this.choices = choices; - for (Object choice : choices) { - if (choice instanceof String string) { - combinations.add(string); - } else if (choice instanceof PatternParser parser) { - combinations.addAll(parser.getCombinations()); - } else if (choice instanceof PatternStringGroup stringGroup) { - combinations.addAll(stringGroup.getCombinations()); - } - } - } - - private List getCombinations() { - return combinations; - } - - } - - private static class PatternStringGroup { - - private final boolean isPrefix; - private final String string; - private final PatternParser group; - private final List combinations = new ArrayList<>(); - - private PatternStringGroup(String string, PatternParser group) { - this.string = string; - this.group = group; - isPrefix = true; - init(); - } - - private PatternStringGroup(PatternParser group, String string) { - this.group = group; - this.string = string; - isPrefix = false; - init(); - } - - private void init() { - List combos = group.getCombinations(); - if (isPrefix) { - for (String combo : combos) { - combinations.add(combine(string, combo)); - } - } else { - for (String combo : combos) { - combinations.add(combine(combo, string)); - } - } - } - - private List getCombinations() { - return combinations; - } - - } - - private static String combine(String first, String last) { - if (first.endsWith(" ") && last.startsWith(" ")) { - return first + last.substring(1, last.length() - 1); - } else if (!first.endsWith(" ") && !last.startsWith(" ")) { - return first + " " + last; - } - return first + last; - } - -} From 0ddb78a82cf463db97b3ba84dec823b6193fea17 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 10:02:24 -0400 Subject: [PATCH 03/17] Update --- .../skript/lang/util/PatternParser.java | 32 +- .../test/tests/lang/PatternParserTest.java | 279 +++++++++++++++++- 2 files changed, 298 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java index bf8cb411f9e..1ae58403bcc 100644 --- a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java +++ b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java @@ -39,6 +39,7 @@ private void init() { List segments = new ArrayList<>(); StringBuilder builder = new StringBuilder(); boolean hasSelector = false; + boolean optionalChoice = false; for (int i = startIndex; i < pattern.length(); i++) { char c = pattern.charAt(i); if (c == '(' && (i == 0 || pattern.charAt(i - 1) != '\\')) { @@ -54,11 +55,16 @@ private void init() { i = group.getEndIndex(); segments.add(group); } else if (isGroup && ((!isOptional && c == ')') || (isOptional && c == ']'))) { + if (pattern.charAt(i - 1) == '|') + optionalChoice = true; endIndex = i; break; } else { - if (c == '|') + if (c == '|') { hasSelector = true; + if (i == startIndex) + optionalChoice = true; + } builder.append(c); } } @@ -86,7 +92,9 @@ private void init() { current.clear(); if (!split.isEmpty()) { - current.add(split.remove(split.size() - 1)); + if (!string.endsWith("|")) { + current.add(split.remove(split.size() - 1)); + } if (!split.isEmpty()) { choices.addAll(split); } @@ -108,8 +116,11 @@ private void init() { combinations.addAll(parser.getCombinations()); } } - if (isOptional) + if (isOptional || optionalChoice) { combinations.add(""); + } else { + combinations.remove(""); + } } else { for (Object segment : segments) { if (segment instanceof String string) { @@ -118,8 +129,13 @@ private void init() { apply(parser.getCombinations()); } } - if (isGroup && isOptional) - combinations.add(""); + if (isGroup) { + if (isOptional) { + combinations.add(""); + } else { + combinations.remove(""); + } + } } } @@ -145,13 +161,11 @@ private int getEndIndex() { private String combine(String first, String second) { if (first.isEmpty()) { - return second.trim(); + return second.stripLeading(); } else if (second.isEmpty()) { - return first.trim(); + return first.stripTrailing(); } else if (first.endsWith(" ") && second.startsWith(" ")) { return first + second.substring(1); - } else if (!first.endsWith(" ") && !second.startsWith(" ")) { - return first + " " + second; } return first + second; } diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java index 9ff4b310d82..39b5f24c0c4 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java @@ -20,15 +20,34 @@ public class PatternParserTest extends SkriptJUnitTest { private static String regexPattern(String pattern) { - return pattern.replaceAll("%\\S+%", "%*%") - .replaceAll("[a-zA-Z0-9]+:", "") - .replaceAll(":", "") - .replaceAll("[0-9]+¦", ""); + return pattern.replaceAll("%[^%()\\[\\]|*]+%", "%*%") // Replaces expressions, except literals + .replaceAll("[a-zA-Z0-9]+:", "") // Replaces parse tags with leading ID 'any:' + .replaceAll(":", "") // Replaces ':' for parse tags with trailing ID ':any' + .replaceAll("[0-9]+¦", ""); // Replaces parse marks '1¦' + } + + private static void compare(Set got, Set expect) { + if (!expect.equals(got)) { + Set gotCopy = new HashSet<>(Set.copyOf(got)); + gotCopy.removeAll(expect); + if (!gotCopy.isEmpty()) { + Assert.fail("Unexpected combinations: " + got); + } + Set expectCopy = new HashSet<>(Set.copyOf(expect)); + expectCopy.removeAll(got); + if (!expectCopy.isEmpty()) { + Assert.fail("Combinations not found: " + expectCopy); + } + } } @Test public void test() { Assert.assertEquals( + regexPattern("[all [of the]|the] entities [of %-world%]"), + "[all [of the]|the] entities [of %*%]" + ); + compare( new PatternParser(regexPattern("[all [of the]|the] entities [of %-world%]")).getCombinations(), Set.of( "all entities", "all entities of %*%", @@ -39,6 +58,10 @@ public void test() { ); Assert.assertEquals( + regexPattern("[all [of the]|the] [:typed] entities [of %-world%]"), + "[all [of the]|the] [typed] entities [of %*%]" + ); + compare( new PatternParser(regexPattern("[all [of the]|the] [:typed] entities [of %-world%]")).getCombinations(), Set.of( "all typed entities", "all typed entities of %*%", @@ -52,6 +75,253 @@ public void test() { ) ); + Assert.assertEquals( + regexPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]"), + "stop (all sound[s]|sound[s] %*%) [(in [the]|from) %*%] [(from playing to|for) %*%]" + ); + compare( + new PatternParser(regexPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]")) + .getCombinations(), + Set.of( + "stop all sound", "stop all sound in %*%", "stop all sound in %*% from playing to %*%", "stop all sound in %*% for %*%", + "stop all sound in the %*%", "stop all sound in the %*% from playing to %*%", "stop all sound in the %*% for %*%", + "stop all sound from %*%", "stop all sound from %*% from playing to %*%", "stop all sound from %*% for %*%", + "stop all sound from playing to %*%", "stop all sound for %*%", + + "stop all sounds", "stop all sounds in %*%", "stop all sounds in %*% from playing to %*%", "stop all sounds in %*% for %*%", + "stop all sounds in the %*%", "stop all sounds in the %*% from playing to %*%", "stop all sounds in the %*% for %*%", + "stop all sounds from %*%", "stop all sounds from %*% from playing to %*%", "stop all sounds from %*% for %*%", + "stop all sounds from playing to %*%", "stop all sounds for %*%", + + "stop sound %*%", "stop sound %*% in %*%", "stop sound %*% in %*% from playing to %*%", "stop sound %*% in %*% for %*%", + "stop sound %*% in the %*%", "stop sound %*% in the %*% from playing to %*%", "stop sound %*% in the %*% for %*%", + "stop sound %*% from %*%", "stop sound %*% from %*% from playing to %*%", "stop sound %*% from %*% for %*%", + "stop sound %*% from playing to %*%", "stop sound %*% for %*%", + + "stop sounds %*%", "stop sounds %*% in %*%", "stop sounds %*% in %*% from playing to %*%", "stop sounds %*% in %*% for %*%", + "stop sounds %*% in the %*%", "stop sounds %*% in the %*% from playing to %*%", "stop sounds %*% in the %*% for %*%", + "stop sounds %*% from %*%", "stop sounds %*% from %*% from playing to %*%", "stop sounds %*% from %*% for %*%", + "stop sounds %*% from playing to %*%", "stop sounds %*% for %*%" + ) + ); + + Assert.assertEquals( + regexPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%"), + "[the] [(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %*%] of %*%" + ); + compare( + new PatternParser(regexPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%")) + .getCombinations(), + Set.of( + "the tall fall damage sound of %*%", "the tall fall damage sound from %*% of %*%", + "the tall fall damage sound from a height %*% of %*%", "the tall fall damage sound from a height of %*% of %*%", + "the tall fall damage sound from height %*% of %*%", "the tall fall damage sound from height of %*% of %*%", + + "the tall fall damage sounds of %*%", "the tall fall damage sounds from %*% of %*%", + "the tall fall damage sounds from a height %*% of %*%", "the tall fall damage sounds from a height of %*% of %*%", + "the tall fall damage sounds from height %*% of %*%", "the tall fall damage sounds from height of %*% of %*%", + + "the high fall damage sound of %*%", "the high fall damage sound from %*% of %*%", + "the high fall damage sound from a height %*% of %*%", "the high fall damage sound from a height of %*% of %*%", + "the high fall damage sound from height %*% of %*%", "the high fall damage sound from height of %*% of %*%", + + "the high fall damage sounds of %*%", "the high fall damage sounds from %*% of %*%", + "the high fall damage sounds from a height %*% of %*%", "the high fall damage sounds from a height of %*% of %*%", + "the high fall damage sounds from height %*% of %*%", "the high fall damage sounds from height of %*% of %*%", + + "the low fall damage sound of %*%", "the low fall damage sound from %*% of %*%", + "the low fall damage sound from a height %*% of %*%", "the low fall damage sound from a height of %*% of %*%", + "the low fall damage sound from height %*% of %*%", "the low fall damage sound from height of %*% of %*%", + + "the low fall damage sounds of %*%", "the low fall damage sounds from %*% of %*%", + "the low fall damage sounds from a height %*% of %*%", "the low fall damage sounds from a height of %*% of %*%", + "the low fall damage sounds from height %*% of %*%", "the low fall damage sounds from height of %*% of %*%", + + "the normal fall damage sound of %*%", "the normal fall damage sound from %*% of %*%", + "the normal fall damage sound from a height %*% of %*%", "the normal fall damage sound from a height of %*% of %*%", + "the normal fall damage sound from height %*% of %*%", "the normal fall damage sound from height of %*% of %*%", + + "the normal fall damage sounds of %*%", "the normal fall damage sounds from %*% of %*%", + "the normal fall damage sounds from a height %*% of %*%", "the normal fall damage sounds from a height of %*% of %*%", + "the normal fall damage sounds from height %*% of %*%", "the normal fall damage sounds from height of %*% of %*%", + + "the fall damage sound of %*%", "the fall damage sound from %*% of %*%", + "the fall damage sound from a height %*% of %*%", "the fall damage sound from a height of %*% of %*%", + "the fall damage sound from height %*% of %*%", "the fall damage sound from height of %*% of %*%", + + "the fall damage sounds of %*%", "the fall damage sounds from %*% of %*%", + "the fall damage sounds from a height %*% of %*%", "the fall damage sounds from a height of %*% of %*%", + "the fall damage sounds from height %*% of %*%", "the fall damage sounds from height of %*% of %*%", + + "tall fall damage sound of %*%", "tall fall damage sound from %*% of %*%", + "tall fall damage sound from a height %*% of %*%", "tall fall damage sound from a height of %*% of %*%", + "tall fall damage sound from height %*% of %*%", "tall fall damage sound from height of %*% of %*%", + + "tall fall damage sounds of %*%", "tall fall damage sounds from %*% of %*%", + "tall fall damage sounds from a height %*% of %*%", "tall fall damage sounds from a height of %*% of %*%", + "tall fall damage sounds from height %*% of %*%", "tall fall damage sounds from height of %*% of %*%", + + "high fall damage sound of %*%", "high fall damage sound from %*% of %*%", + "high fall damage sound from a height %*% of %*%", "high fall damage sound from a height of %*% of %*%", + "high fall damage sound from height %*% of %*%", "high fall damage sound from height of %*% of %*%", + + "high fall damage sounds of %*%", "high fall damage sounds from %*% of %*%", + "high fall damage sounds from a height %*% of %*%", "high fall damage sounds from a height of %*% of %*%", + "high fall damage sounds from height %*% of %*%", "high fall damage sounds from height of %*% of %*%", + + "low fall damage sound of %*%", "low fall damage sound from %*% of %*%", + "low fall damage sound from a height %*% of %*%", "low fall damage sound from a height of %*% of %*%", + "low fall damage sound from height %*% of %*%", "low fall damage sound from height of %*% of %*%", + + "low fall damage sounds of %*%", "low fall damage sounds from %*% of %*%", + "low fall damage sounds from a height %*% of %*%", "low fall damage sounds from a height of %*% of %*%", + "low fall damage sounds from height %*% of %*%", "low fall damage sounds from height of %*% of %*%", + + "normal fall damage sound of %*%", "normal fall damage sound from %*% of %*%", + "normal fall damage sound from a height %*% of %*%", "normal fall damage sound from a height of %*% of %*%", + "normal fall damage sound from height %*% of %*%", "normal fall damage sound from height of %*% of %*%", + + "normal fall damage sounds of %*%", "normal fall damage sounds from %*% of %*%", + "normal fall damage sounds from a height %*% of %*%", "normal fall damage sounds from a height of %*% of %*%", + "normal fall damage sounds from height %*% of %*%", "normal fall damage sounds from height of %*% of %*%", + + "fall damage sound of %*%", "fall damage sound from %*% of %*%", + "fall damage sound from a height %*% of %*%", "fall damage sound from a height of %*% of %*%", + "fall damage sound from height %*% of %*%", "fall damage sound from height of %*% of %*%", + + "fall damage sounds of %*%", "fall damage sounds from %*% of %*%", + "fall damage sounds from a height %*% of %*%", "fall damage sounds from a height of %*% of %*%", + "fall damage sounds from height %*% of %*%", "fall damage sounds from height of %*% of %*%" + ) + ); + + Assert.assertEquals( + regexPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]"), + "[on] [uncancelled|cancelled|(any|all)] <.+> [with priority ((lowest|low|normal|high|highest|monitor))]" + ); + compare( + new PatternParser(regexPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]")) + .getCombinations(), + Set.of( + "on <.+>", "on <.+> with priority lowest", "on <.+> with priority low", + "on <.+> with priority normal", "on <.+> with priority high", + "on <.+> with priority highest", "on <.+> with priority monitor", + + "on uncancelled <.+>", "on uncancelled <.+> with priority lowest", "on uncancelled <.+> with priority low", + "on uncancelled <.+> with priority normal", "on uncancelled <.+> with priority high", + "on uncancelled <.+> with priority highest", "on uncancelled <.+> with priority monitor", + + "on cancelled <.+>", "on cancelled <.+> with priority lowest", "on cancelled <.+> with priority low", + "on cancelled <.+> with priority normal", "on cancelled <.+> with priority high", + "on cancelled <.+> with priority highest", "on cancelled <.+> with priority monitor", + + "on any <.+>", "on any <.+> with priority lowest", "on any <.+> with priority low", + "on any <.+> with priority normal", "on any <.+> with priority high", + "on any <.+> with priority highest", "on any <.+> with priority monitor", + + "on all <.+>", "on all <.+> with priority lowest", "on all <.+> with priority low", + "on all <.+> with priority normal", "on all <.+> with priority high", + "on all <.+> with priority highest", "on all <.+> with priority monitor", + + "<.+>", "<.+> with priority lowest", "<.+> with priority low", + "<.+> with priority normal", "<.+> with priority high", + "<.+> with priority highest", "<.+> with priority monitor", + + "uncancelled <.+>", "uncancelled <.+> with priority lowest", "uncancelled <.+> with priority low", + "uncancelled <.+> with priority normal", "uncancelled <.+> with priority high", + "uncancelled <.+> with priority highest", "uncancelled <.+> with priority monitor", + + "cancelled <.+>", "cancelled <.+> with priority lowest", "cancelled <.+> with priority low", + "cancelled <.+> with priority normal", "cancelled <.+> with priority high", + "cancelled <.+> with priority highest", "cancelled <.+> with priority monitor", + + "any <.+>", "any <.+> with priority lowest", "any <.+> with priority low", + "any <.+> with priority normal", "any <.+> with priority high", + "any <.+> with priority highest", "any <.+> with priority monitor", + + "all <.+>", "all <.+> with priority lowest", "all <.+> with priority low", + "all <.+> with priority normal", "all <.+> with priority high", + "all <.+> with priority highest", "all <.+> with priority monitor" + ) + ); + + Assert.assertEquals( + regexPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%"), + "(open|show) (((crafting [table]|workbench)|chest|anvil|hopper|dropper|dispenser) (view|window|inventory|)|%*%) (to|for) %*%" + ); + compare( + new PatternParser(regexPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%")) + .getCombinations(), + Set.of( + "open crafting to %*%", "open crafting view to %*%", "open crafting window to %*%", + "open crafting inventory to %*%", "open crafting for %*%", "open crafting view for %*%", + "open crafting window for %*%", "open crafting inventory for %*%", + + "open crafting table to %*%", "open crafting table view to %*%", "open crafting table window to %*%", + "open crafting table inventory to %*%", "open crafting table for %*%", "open crafting table view for %*%", + "open crafting table window for %*%", "open crafting table inventory for %*%", + + "open workbench to %*%", "open workbench view to %*%", "open workbench window to %*%", + "open workbench inventory to %*%", "open workbench for %*%", "open workbench view for %*%", + "open workbench window for %*%", "open workbench inventory for %*%", + + "open chest to %*%", "open chest view to %*%", "open chest window to %*%", + "open chest inventory to %*%", "open chest for %*%", "open chest view for %*%", + "open chest window for %*%", "open chest inventory for %*%", + + "open anvil to %*%", "open anvil view to %*%", "open anvil window to %*%", + "open anvil inventory to %*%", "open anvil for %*%", "open anvil view for %*%", + "open anvil window for %*%", "open anvil inventory for %*%", + + "open hopper to %*%", "open hopper view to %*%", "open hopper window to %*%", + "open hopper inventory to %*%", "open hopper for %*%", "open hopper view for %*%", + "open hopper window for %*%", "open hopper inventory for %*%", + + "open dropper to %*%", "open dropper view to %*%", "open dropper window to %*%", + "open dropper inventory to %*%", "open dropper for %*%", "open dropper view for %*%", + "open dropper window for %*%", "open dropper inventory for %*%", + + "open dispenser to %*%", "open dispenser view to %*%", "open dispenser window to %*%", + "open dispenser inventory to %*%", "open dispenser for %*%", "open dispenser view for %*%", + "open dispenser window for %*%", "open dispenser inventory for %*%", + + "open %*% to %*%", "open %*% for %*%", + + "show crafting to %*%", "show crafting view to %*%", "show crafting window to %*%", + "show crafting inventory to %*%", "show crafting for %*%", "show crafting view for %*%", + "show crafting window for %*%", "show crafting inventory for %*%", + + "show crafting table to %*%", "show crafting table view to %*%", "show crafting table window to %*%", + "show crafting table inventory to %*%", "show crafting table for %*%", "show crafting table view for %*%", + "show crafting table window for %*%", "show crafting table inventory for %*%", + + "show workbench to %*%", "show workbench view to %*%", "show workbench window to %*%", + "show workbench inventory to %*%", "show workbench for %*%", "show workbench view for %*%", + "show workbench window for %*%", "show workbench inventory for %*%", + + "show chest to %*%", "show chest view to %*%", "show chest window to %*%", + "show chest inventory to %*%", "show chest for %*%", "show chest view for %*%", + "show chest window for %*%", "show chest inventory for %*%", + + "show anvil to %*%", "show anvil view to %*%", "show anvil window to %*%", + "show anvil inventory to %*%", "show anvil for %*%", "show anvil view for %*%", + "show anvil window for %*%", "show anvil inventory for %*%", + + "show hopper to %*%", "show hopper view to %*%", "show hopper window to %*%", + "show hopper inventory to %*%", "show hopper for %*%", "show hopper view for %*%", + "show hopper window for %*%", "show hopper inventory for %*%", + + "show dropper to %*%", "show dropper view to %*%", "show dropper window to %*%", + "show dropper inventory to %*%", "show dropper for %*%", "show dropper view for %*%", + "show dropper window for %*%", "show dropper inventory for %*%", + + "show dispenser to %*%", "show dispenser view to %*%", "show dispenser window to %*%", + "show dispenser inventory to %*%", "show dispenser for %*%", "show dispenser view for %*%", + "show dispenser window for %*%", "show dispenser inventory for %*%", + + "show %*% to %*%", "show %*% for %*%" + ) + ); } @Test @@ -85,6 +355,7 @@ public void testPatterns() { } } + hasMultiple.remove("<.+>"); // Remove regex if (hasMultiple.isEmpty()) return; From 8fbcbe90f344978694005b64fb2810980136a3b6 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 10:02:42 -0400 Subject: [PATCH 04/17] Update PatternParserTest.java --- .../skriptlang/skript/test/tests/lang/PatternParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java index 39b5f24c0c4..a31b763d177 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java @@ -31,7 +31,7 @@ private static void compare(Set got, Set expect) { Set gotCopy = new HashSet<>(Set.copyOf(got)); gotCopy.removeAll(expect); if (!gotCopy.isEmpty()) { - Assert.fail("Unexpected combinations: " + got); + Assert.fail("Unexpected combinations: " + gotCopy); } Set expectCopy = new HashSet<>(Set.copyOf(expect)); expectCopy.removeAll(got); From 7bc6f2d4ae3ed65d973437c452d1d0f8147c4f44 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 10:13:13 -0400 Subject: [PATCH 05/17] Revert --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ea1c84fb58e..55586f5bc20 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Done to increase the memory available to gradle. # Ensure encoding is consistent across systems. -org.gradle.jvmargs=-Xmx4G -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx1G -Dfile.encoding=UTF-8 org.gradle.parallel=true groupid=ch.njol From a8ac3bd9b13e5420c5dc6689127fa224b1efc1e1 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 10:23:35 -0400 Subject: [PATCH 06/17] Update PatternParserTest.java --- .../skriptlang/skript/test/tests/lang/PatternParserTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java index a31b763d177..9a0fd9837d9 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java @@ -349,7 +349,7 @@ public void testPatterns() { combinationCounter++; Skript.adminBroadcast("Combination Counter: " + combinationCounter); registeredPatterns.computeIfAbsent(combination, set -> new HashSet<>()).add(elementClass); - if (registeredPatterns.get(combination).size() > 2) + if (registeredPatterns.get(combination).size() > 1) hasMultiple.add(combination); } } From 52f9c4a01f64971da9fae7edb5f11f5e34b3f522 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 10:39:10 -0400 Subject: [PATCH 07/17] Update PatternParserTest.java --- .../skript/test/tests/lang/PatternParserTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java index 9a0fd9837d9..4b59d9d7f83 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java @@ -330,7 +330,7 @@ public void testPatterns() { Set hasMultiple = new HashSet<>(); Collection> elements = Skript.instance().syntaxRegistry().elements(); - Skript.adminBroadcast("Total elements: " + elements.size()); + Skript.debug("Total elements: " + elements.size()); int elementCounter = 0; int patternCounter = 0; int combinationCounter = 0; @@ -339,15 +339,15 @@ public void testPatterns() { Class elementClass = syntaxInfo.type(); elementCounter++; - Skript.adminBroadcast("Element Counter: " + elementCounter); + Skript.debug("Element Counter: " + elementCounter); for (String pattern : patterns) { patternCounter++; - Skript.adminBroadcast("Pattern Counter: " + patternCounter); - Skript.adminBroadcast("Pattern: " + pattern); + Skript.debug("Pattern Counter: " + patternCounter); + Skript.debug("Pattern: " + pattern); PatternParser parser = new PatternParser(regexPattern(pattern)); for (String combination : parser.getCombinations()) { combinationCounter++; - Skript.adminBroadcast("Combination Counter: " + combinationCounter); + Skript.debug("Combination Counter: " + combinationCounter); registeredPatterns.computeIfAbsent(combination, set -> new HashSet<>()).add(elementClass); if (registeredPatterns.get(combination).size() > 1) hasMultiple.add(combination); From cec0b2597d855b4f5650ed9391625aabaeb3a329 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 12:55:35 -0400 Subject: [PATCH 08/17] Filtering + Exclusions --- .../skript/lang/util/PatternParser.java | 52 +++- .../test/tests/lang/PatternParserTest.java | 227 +++++++++++++++--- 2 files changed, 246 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java index 1ae58403bcc..78f520585e8 100644 --- a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java +++ b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java @@ -20,6 +20,10 @@ public class PatternParser { private int endIndex = -1; private final LinkedHashSet combinations = new LinkedHashSet<>(); + /** + * Constructs a new {@link PatternParser} to retrieve all combinations of {@code pattern}. + * @param pattern The pattern to get combinations from. + */ public PatternParser(String pattern) { this.pattern = pattern; startIndex = 0; @@ -27,6 +31,12 @@ public PatternParser(String pattern) { init(); } + /** + * Constructs a new {@link PatternParser} designed for parsing a group. + * @param pattern The full pattern to get combinations from. + * @param startIndex The character index after the opening group bracket '(' or '[' + * @param isOptional Whether the group is optional '[' or not '(' + */ public PatternParser(String pattern, int startIndex, boolean isOptional) { this.pattern = pattern; this.startIndex = startIndex; @@ -35,6 +45,9 @@ public PatternParser(String pattern, int startIndex, boolean isOptional) { init(); } + /** + * Initializes this {@link PatternParser} by iterating through every character and group, and constructing all combinations + */ private void init() { List segments = new ArrayList<>(); StringBuilder builder = new StringBuilder(); @@ -43,14 +56,18 @@ private void init() { for (int i = startIndex; i < pattern.length(); i++) { char c = pattern.charAt(i); if (c == '(' && (i == 0 || pattern.charAt(i - 1) != '\\')) { - segments.add(builder.toString()); - builder = new StringBuilder(); + if (!builder.isEmpty()) { + segments.add(builder.toString()); + builder = new StringBuilder(); + } PatternParser group = new PatternParser(pattern, i + 1, false); i = group.getEndIndex(); segments.add(group); } else if (c == '[' && (i == 0 || pattern.charAt(i - 1) != '\\')) { - segments.add(builder.toString()); - builder = new StringBuilder(); + if (!builder.isEmpty()) { + segments.add(builder.toString()); + builder = new StringBuilder(); + } PatternParser group = new PatternParser(pattern, i + 1, true); i = group.getEndIndex(); segments.add(group); @@ -83,6 +100,8 @@ private void init() { Object segment = segments.get(i); if (segment instanceof String string) { if (string.contains("|")) { + if (string.contains("||")) + optionalChoice = true; List split = new ArrayList<>(Arrays.stream(string.split("\\|")).toList()); if (!string.startsWith("|")) { String first = split.remove(0); @@ -139,6 +158,10 @@ private void init() { } } + /** + * Applies all new combinations to previous combinations. + * @param strings The new combinations to apply. + */ private void apply(Set strings) { if (combinations.isEmpty()) { combinations.addAll(strings); @@ -154,22 +177,37 @@ private void apply(Set strings) { combinations.addAll(newCombinations); } + /** + * Gets the ending index for this {@link PatternParser} that correlates to the closing group bracket. + * @return The index of the closing group bracket. + */ private int getEndIndex() { assert isGroup; return endIndex; } + /** + * Joins {@code first} and {@code second} together stripping out unnecessary spaces. + * @param first + * @param second + * @return + */ private String combine(String first, String second) { if (first.isEmpty()) { return second.stripLeading(); } else if (second.isEmpty()) { return first.stripTrailing(); } else if (first.endsWith(" ") && second.startsWith(" ")) { - return first + second.substring(1); + return first + second.stripLeading(); } return first + second; } + /** + * Similar to {@link #apply(Set)} but applies only the combinations from {@code choices}. + * @param choices The combinations to apply together. + * @return The resulting combinations. + */ private Set combineChoices(List choices) { if (choices.isEmpty()) return Collections.emptySet(); @@ -199,6 +237,10 @@ private Set combineChoices(List choices) { return combinations; } + /** + * Gets the final product of all combinations from this {@link PatternParser} + * @return The combinations. + */ public Set getCombinations() { return combinations; } diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java index 4b59d9d7f83..b43750dd069 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java +++ b/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java @@ -2,10 +2,17 @@ import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.expressions.ExprScripts; +import ch.njol.skript.expressions.ExprScriptsOld; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.Section; +import ch.njol.skript.lang.Statement; import ch.njol.skript.test.runner.SkriptJUnitTest; import ch.njol.util.StringUtils; +import org.jetbrains.annotations.Nullable; import org.junit.Assert; import org.junit.Test; +import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.util.PatternParser; import org.skriptlang.skript.registration.SyntaxInfo; @@ -16,10 +23,11 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; public class PatternParserTest extends SkriptJUnitTest { - private static String regexPattern(String pattern) { + private static String cleanPattern(String pattern) { return pattern.replaceAll("%[^%()\\[\\]|*]+%", "%*%") // Replaces expressions, except literals .replaceAll("[a-zA-Z0-9]+:", "") // Replaces parse tags with leading ID 'any:' .replaceAll(":", "") // Replaces ':' for parse tags with trailing ID ':any' @@ -44,11 +52,11 @@ private static void compare(Set got, Set expect) { @Test public void test() { Assert.assertEquals( - regexPattern("[all [of the]|the] entities [of %-world%]"), + cleanPattern("[all [of the]|the] entities [of %-world%]"), "[all [of the]|the] entities [of %*%]" ); compare( - new PatternParser(regexPattern("[all [of the]|the] entities [of %-world%]")).getCombinations(), + new PatternParser(cleanPattern("[all [of the]|the] entities [of %-world%]")).getCombinations(), Set.of( "all entities", "all entities of %*%", "all of the entities", "all of the entities of %*%", @@ -58,11 +66,11 @@ public void test() { ); Assert.assertEquals( - regexPattern("[all [of the]|the] [:typed] entities [of %-world%]"), + cleanPattern("[all [of the]|the] [:typed] entities [of %-world%]"), "[all [of the]|the] [typed] entities [of %*%]" ); compare( - new PatternParser(regexPattern("[all [of the]|the] [:typed] entities [of %-world%]")).getCombinations(), + new PatternParser(cleanPattern("[all [of the]|the] [:typed] entities [of %-world%]")).getCombinations(), Set.of( "all typed entities", "all typed entities of %*%", "all entities", "all entities of %*%", @@ -76,11 +84,11 @@ public void test() { ); Assert.assertEquals( - regexPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]"), + cleanPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]"), "stop (all sound[s]|sound[s] %*%) [(in [the]|from) %*%] [(from playing to|for) %*%]" ); compare( - new PatternParser(regexPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]")) + new PatternParser(cleanPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]")) .getCombinations(), Set.of( "stop all sound", "stop all sound in %*%", "stop all sound in %*% from playing to %*%", "stop all sound in %*% for %*%", @@ -106,11 +114,11 @@ public void test() { ); Assert.assertEquals( - regexPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%"), + cleanPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%"), "[the] [(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %*%] of %*%" ); compare( - new PatternParser(regexPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%")) + new PatternParser(cleanPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%")) .getCombinations(), Set.of( "the tall fall damage sound of %*%", "the tall fall damage sound from %*% of %*%", @@ -196,11 +204,11 @@ public void test() { ); Assert.assertEquals( - regexPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]"), + cleanPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]"), "[on] [uncancelled|cancelled|(any|all)] <.+> [with priority ((lowest|low|normal|high|highest|monitor))]" ); compare( - new PatternParser(regexPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]")) + new PatternParser(cleanPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]")) .getCombinations(), Set.of( "on <.+>", "on <.+> with priority lowest", "on <.+> with priority low", @@ -246,11 +254,11 @@ public void test() { ); Assert.assertEquals( - regexPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%"), + cleanPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%"), "(open|show) (((crafting [table]|workbench)|chest|anvil|hopper|dropper|dispenser) (view|window|inventory|)|%*%) (to|for) %*%" ); compare( - new PatternParser(regexPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%")) + new PatternParser(cleanPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%")) .getCombinations(), Set.of( "open crafting to %*%", "open crafting view to %*%", "open crafting window to %*%", @@ -324,50 +332,213 @@ public void test() { ); } + private enum ElementType { + STRUCTURE, STATEMENT, EXPRESSION; + + private static ElementType getType(Class elementClass) { + if (Structure.class.isAssignableFrom(elementClass)) { + return STRUCTURE; + } else if (Statement.class.isAssignableFrom(elementClass) || Section.class.isAssignableFrom(elementClass)) { + return STATEMENT; + } else if (Expression.class.isAssignableFrom(elementClass)) { + return EXPRESSION; + } + throw new IllegalStateException("The class '" + elementClass.getSimpleName() + "' does not fall into a type"); + } + } + + /** + * Record for a logged pattern combination mainly for ensuring if it truly conflicts. + * @param combination The logged pattern combination. + * @param pattern The pattern the combination came from. + * @param elementClass The {@link Class} the pattern is registered to. + * @param elementType The {@link ElementType} of the {@code elementClass}. + */ + private record Combination(String combination, String pattern, Class elementClass, ElementType elementType) { + + /** + * Whether this {@link Combination} truly conflicts with another {@link Combination}. + * @param other The other {@link Combination}. + * @return {@code true} if it conflicts, otherwise {@code false}. + */ + private boolean conflicts(Combination other) { + return combination.equals(other.combination) + && elementType.equals(other.elementType) + && !elementClass.equals(other.elementClass); + } + + } + + /** + * Manual exclusion + */ + private static class Exclusion { + + private final Set> classes; + private final @Nullable String patternCombination; + + /** + * Constructs a new {@link Exclusion} that will exclude any conflicting combination + * as long as the only classes involved in the confliction are {@code classes}. + * @param classes The {@link Class}es to check for. + */ + private Exclusion(Class... classes) { + this(null, classes); + } + + /** + * Constructs a new {@link Exclusion} that will exclude the conflicting {@code patternCombination} + * as long as the only classes involved in the confliction are {@code classes}. + * @param patternCombination The restricted combination. + * @param classes The {@link Class}es to check for. + */ + private Exclusion(@Nullable String patternCombination, Class... classes) { + this.patternCombination = patternCombination; + this.classes = Set.of(classes); + } + + /** + * Whether this {@link Exclusion} excludes the confliction by checking if the {@link Class}es from + * {@code combinations} are only {@link #classes}. + * @param combinations The {@link Combination}s to check. + * @return {@code true} if the confliction can be excluded, otherwise {@code false}. + */ + private boolean exclude(Set combinations) { + if (combinations.isEmpty()) + return false; + Set> combinationClasses = combinations.stream() + .map(Combination::elementClass) + .collect(Collectors.toSet()); + return combinationClasses.equals(classes); + } + + } + + /** + * Whether the info messages from the process of {@link #testPatterns()} should be debugged + * via {@link Skript#debug(String)}. + */ + public static boolean DEBUG = false; + + /** + * Whether the info messages from the process of {@link #testPatterns()} should be broadcasted + * via {@link Skript#adminBroadcast(String)}. + */ + public static boolean BROADCAST = false; + private static final Set EXCLUSIONS = new HashSet<>(); + + static { + EXCLUSIONS.add(new Exclusion(ExprScriptsOld.class, ExprScripts.class)); + } + + private void info(String message) { + if (DEBUG) + Skript.debug(message); + if (BROADCAST) + Skript.adminBroadcast(message); + } + @Test public void testPatterns() { - Map>> registeredPatterns = new HashMap<>(); + Map> registeredPatterns = new HashMap<>(); Set hasMultiple = new HashSet<>(); Collection> elements = Skript.instance().syntaxRegistry().elements(); - Skript.debug("Total elements: " + elements.size()); + info("Total elements: " + elements.size()); int elementCounter = 0; int patternCounter = 0; int combinationCounter = 0; for (SyntaxInfo syntaxInfo : elements) { Collection patterns = syntaxInfo.patterns(); Class elementClass = syntaxInfo.type(); + ElementType elementType = ElementType.getType(elementClass); elementCounter++; - Skript.debug("Element Counter: " + elementCounter); + info("Element Counter: " + elementCounter); for (String pattern : patterns) { patternCounter++; - Skript.debug("Pattern Counter: " + patternCounter); - Skript.debug("Pattern: " + pattern); - PatternParser parser = new PatternParser(regexPattern(pattern)); - for (String combination : parser.getCombinations()) { + info("Pattern Counter: " + patternCounter); + info("Pattern: " + pattern); + PatternParser parser = new PatternParser(cleanPattern(pattern)); + for (String patternCombination : parser.getCombinations()) { combinationCounter++; - Skript.debug("Combination Counter: " + combinationCounter); - registeredPatterns.computeIfAbsent(combination, set -> new HashSet<>()).add(elementClass); - if (registeredPatterns.get(combination).size() > 1) - hasMultiple.add(combination); + info("Combination Counter: " + combinationCounter); + Combination combination = new Combination(patternCombination, pattern, elementClass, elementType); + registeredPatterns.computeIfAbsent(patternCombination, set -> new HashSet<>()).add(combination); + if (registeredPatterns.get(patternCombination).size() > 1) + hasMultiple.add(patternCombination); + } + } + } + + if (hasMultiple.isEmpty()) + return; + + // Filter out combinations that can't conflict due to different element types + Set filteredMultiple = new HashSet<>(); + for (String string : hasMultiple) { + Set combinations = registeredPatterns.get(string); + Set filteredCombinations = new HashSet<>(); + for (Combination first : combinations) { + boolean conflicts = false; + for (Combination second : combinations) { + if (filteredCombinations.contains(second)) + continue; + if (first.conflicts(second)) { + conflicts = true; + break; + } + } + if (!conflicts) { + info("Filtered Combination: " + first); + filteredCombinations.add(first); } } + combinations.removeAll(filteredCombinations); + if (combinations.size() <= 1) { + info("Filtered Confliction: " + string); + filteredMultiple.add(string); + } } + hasMultiple.removeAll(filteredMultiple); + if (hasMultiple.isEmpty()) + return; - hasMultiple.remove("<.+>"); // Remove regex + // Check exclusions + Set excluded = new HashSet<>(); + for (Exclusion exclusion : EXCLUSIONS) { + if (exclusion.patternCombination != null) { + if (!hasMultiple.contains(exclusion.patternCombination)) + continue; + if (exclusion.exclude(registeredPatterns.get(exclusion.patternCombination))) { + info("Excluded: " + exclusion.patternCombination); + excluded.add(exclusion.patternCombination); + } + } else { + for (String string : hasMultiple) { + if (excluded.contains(string)) + continue; + if (exclusion.exclude(registeredPatterns.get(string))) { + info("Excluded: " + string); + excluded.add(string); + } + } + } + } + hasMultiple.removeAll(excluded); if (hasMultiple.isEmpty()) return; List errors = new ArrayList<>(); for (String string : hasMultiple) { List names = registeredPatterns.get(string).stream() - .map(Class::getCanonicalName) + .map(combination -> "Class: " + combination.elementClass.getSimpleName() + " - Pattern: " + combination.pattern) .toList(); - String error = "The pattern '" + string + "' conflicts in: " + StringUtils.join(names, ", ", ", and "); + String error = "The pattern combination '" + string + "' conflicts in: \n\t\t\t" + StringUtils.join(names, "\n\t\t\t"); errors.add(error); } - throw new SkriptAPIException(StringUtils.join(errors, "\n")); + errors.add("Total Conflictions: " + errors.size()); + throw new SkriptAPIException(StringUtils.join(errors, "\n\t")); } } From c94941d2c91c5994dcdbd53ccfbd0b2d994dfa1e Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 20:12:43 -0400 Subject: [PATCH 09/17] PatternElement Usage --- .../skript/patterns/ChoicePatternElement.java | 10 + .../skript/patterns/GroupPatternElement.java | 7 + .../patterns/LiteralPatternElement.java | 7 + .../patterns/OptionalPatternElement.java | 9 + .../patterns/ParseTagPatternElement.java | 14 + .../njol/skript/patterns/PatternCompiler.java | 2 +- .../njol/skript/patterns/PatternElement.java | 51 ++++ .../skript/patterns/RegexPatternElement.java | 7 + .../skript/patterns/TypePatternElement.java | 18 ++ .../skript/lang/util/PatternParser.java | 248 ------------------ .../patterns/PatternConflictsTest.java} | 131 ++++----- 11 files changed, 192 insertions(+), 312 deletions(-) delete mode 100644 src/main/java/org/skriptlang/skript/lang/util/PatternParser.java rename src/test/java/{org/skriptlang/skript/test/tests/lang/PatternParserTest.java => ch/njol/skript/patterns/PatternConflictsTest.java} (86%) diff --git a/src/main/java/ch/njol/skript/patterns/ChoicePatternElement.java b/src/main/java/ch/njol/skript/patterns/ChoicePatternElement.java index 9494a14a521..537c00f9959 100644 --- a/src/main/java/ch/njol/skript/patterns/ChoicePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/ChoicePatternElement.java @@ -3,7 +3,9 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; /** @@ -55,4 +57,12 @@ public String toString() { .map(PatternElement::toFullString) .collect(Collectors.joining("|")); } + + @Override + public Set getCombinations(boolean clean) { + Set combinations = new HashSet<>(); + patternElements.forEach(patternElement -> combinations.addAll(patternElement.getAllCombinations(clean))); + return combinations; + } + } diff --git a/src/main/java/ch/njol/skript/patterns/GroupPatternElement.java b/src/main/java/ch/njol/skript/patterns/GroupPatternElement.java index 6d2437634a5..db7cf4cabec 100644 --- a/src/main/java/ch/njol/skript/patterns/GroupPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/GroupPatternElement.java @@ -2,6 +2,8 @@ import org.jetbrains.annotations.Nullable; +import java.util.Set; + /** * A {@link PatternElement} that represents a group, for example {@code (test)}. */ @@ -34,4 +36,9 @@ public String toString() { return "(" + patternElement + ")"; } + @Override + public Set getCombinations(boolean clean) { + return patternElement.getAllCombinations(clean); + } + } diff --git a/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java b/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java index 6a18fa9bfa8..5ee18bd5e77 100644 --- a/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/LiteralPatternElement.java @@ -2,7 +2,9 @@ import org.jetbrains.annotations.Nullable; +import java.util.HashSet; import java.util.Locale; +import java.util.Set; /** * A {@link PatternElement} that contains a literal string to be matched, for example {@code hello world}. @@ -52,4 +54,9 @@ public String toString() { return new String(literal); } + @Override + public Set getCombinations(boolean clean) { + return new HashSet<>(Set.of(toString())); + } + } diff --git a/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java b/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java index 0ce91a75951..84699a7eea1 100644 --- a/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/OptionalPatternElement.java @@ -2,6 +2,8 @@ import org.jetbrains.annotations.Nullable; +import java.util.Set; + /** * A {@link PatternElement} that contains an optional part, for example {@code [hello world]}. */ @@ -37,4 +39,11 @@ public String toString() { return "[" + patternElement.toFullString() + "]"; } + @Override + public Set getCombinations(boolean clean) { + Set combinations = patternElement.getAllCombinations(clean); + combinations.add(""); + return combinations; + } + } diff --git a/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java b/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java index 187129b805a..bedb7598883 100644 --- a/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/ParseTagPatternElement.java @@ -2,7 +2,9 @@ import org.jetbrains.annotations.Nullable; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * A {@link PatternElement} that applies a parse mark when matched. @@ -82,4 +84,16 @@ public String toString() { } } + /** + * {@inheritDoc} + * @param clean Whether the parse mark/tag should be excluded. + */ + @Override + public Set getCombinations(boolean clean) { + Set combinations = new HashSet<>(); + if (!clean) + combinations.add(toString()); + return combinations; + } + } diff --git a/src/main/java/ch/njol/skript/patterns/PatternCompiler.java b/src/main/java/ch/njol/skript/patterns/PatternCompiler.java index dcc9e088c07..92eebf8dd50 100644 --- a/src/main/java/ch/njol/skript/patterns/PatternCompiler.java +++ b/src/main/java/ch/njol/skript/patterns/PatternCompiler.java @@ -45,7 +45,7 @@ public static SkriptPattern compile(String pattern) throws MalformedPatternExcep * {@link TypePatternElement} should be initiated with. * @return The first link of the {@link PatternElement} chain */ - private static PatternElement compile(String pattern, AtomicInteger expressionOffset) { + static PatternElement compile(String pattern, AtomicInteger expressionOffset) { StringBuilder literalBuilder = new StringBuilder(); PatternElement first = null; diff --git a/src/main/java/ch/njol/skript/patterns/PatternElement.java b/src/main/java/ch/njol/skript/patterns/PatternElement.java index c67069b1236..0c4afe92a4b 100644 --- a/src/main/java/ch/njol/skript/patterns/PatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/PatternElement.java @@ -2,6 +2,9 @@ import org.jetbrains.annotations.Nullable; +import java.util.HashSet; +import java.util.Set; + public abstract class PatternElement { @Nullable @@ -48,4 +51,52 @@ public String toFullString() { return stringBuilder.toString(); } + /** + * Gets the combinations available to this {@link PatternElement}. + * @param clean Whether unnecessary data should be excluded from the combinations. + * @return The combinations. + */ + public abstract Set getCombinations(boolean clean); + + /** + * Gets all combinations available to this {@link PatternElement} and linked {@link PatternElement}s. + * @param clean Whether unnecessary data should be excluded from the combinations. + * @return The combinations. + */ + public final Set getAllCombinations(boolean clean) { + Set combinations = getCombinations(clean); + if (combinations.isEmpty()) + combinations.add(""); + PatternElement next = this; + while ((next = next.originalNext) != null) { + Set newCombinations = new HashSet<>(); + Set nextCombinations = next.getCombinations(clean); + if (nextCombinations.isEmpty()) + continue; + for (String base : combinations) { + for (String add : nextCombinations) { + newCombinations.add(combineCombination(base, add)); + } + } + combinations.clear(); + combinations.addAll(newCombinations); + } + return combinations; + } + + /** + * Helper method for appropriately combining two strings together. + * @return The resulting string. + */ + private String combineCombination(String first, String second) { + if (first.isEmpty()) { + return second.stripLeading(); + } else if (second.isEmpty()) { + return first.stripTrailing(); + } else if (first.endsWith(" ") && second.startsWith(" ")) { + return first + second.stripLeading(); + } + return first + second; + } + } diff --git a/src/main/java/ch/njol/skript/patterns/RegexPatternElement.java b/src/main/java/ch/njol/skript/patterns/RegexPatternElement.java index 502703dc032..feb55f335a0 100644 --- a/src/main/java/ch/njol/skript/patterns/RegexPatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/RegexPatternElement.java @@ -5,6 +5,8 @@ import ch.njol.skript.log.SkriptLogger; import org.jetbrains.annotations.Nullable; +import java.util.HashSet; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -58,4 +60,9 @@ public String toString() { return "<" + pattern + ">"; } + @Override + public Set getCombinations(boolean clean) { + return new HashSet<>(Set.of(toString())); + } + } diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index c337648d52b..917c2cfac25 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -17,6 +17,9 @@ import ch.njol.util.NonNullPair; import org.jetbrains.annotations.Nullable; +import java.util.HashSet; +import java.util.Set; + /** * A {@link PatternElement} that contains a type to be matched with an expressions, for example {@code %number%}. */ @@ -263,4 +266,19 @@ public ExprInfo getExprInfo() { return exprInfo; } + /** + * {@inheritDoc} + * @param clean Whether this type should be replaced with {@code %*%} if it's literal. + */ + @Override + public Set getCombinations(boolean clean) { + Set combinations = new HashSet<>(); + if (flagMask == 2 && !clean) { + combinations.add(toString()); + } else { + combinations.add("%*%"); + } + return combinations; + } + } diff --git a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java b/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java deleted file mode 100644 index 78f520585e8..00000000000 --- a/src/main/java/org/skriptlang/skript/lang/util/PatternParser.java +++ /dev/null @@ -1,248 +0,0 @@ -package org.skriptlang.skript.lang.util; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Parser used to grab all combinations of a pattern - */ -public class PatternParser { - - private final String pattern; - private final int startIndex; - private final boolean isGroup; - private boolean isOptional; - private int endIndex = -1; - private final LinkedHashSet combinations = new LinkedHashSet<>(); - - /** - * Constructs a new {@link PatternParser} to retrieve all combinations of {@code pattern}. - * @param pattern The pattern to get combinations from. - */ - public PatternParser(String pattern) { - this.pattern = pattern; - startIndex = 0; - isGroup = false; - init(); - } - - /** - * Constructs a new {@link PatternParser} designed for parsing a group. - * @param pattern The full pattern to get combinations from. - * @param startIndex The character index after the opening group bracket '(' or '[' - * @param isOptional Whether the group is optional '[' or not '(' - */ - public PatternParser(String pattern, int startIndex, boolean isOptional) { - this.pattern = pattern; - this.startIndex = startIndex; - this.isOptional = isOptional; - isGroup = true; - init(); - } - - /** - * Initializes this {@link PatternParser} by iterating through every character and group, and constructing all combinations - */ - private void init() { - List segments = new ArrayList<>(); - StringBuilder builder = new StringBuilder(); - boolean hasSelector = false; - boolean optionalChoice = false; - for (int i = startIndex; i < pattern.length(); i++) { - char c = pattern.charAt(i); - if (c == '(' && (i == 0 || pattern.charAt(i - 1) != '\\')) { - if (!builder.isEmpty()) { - segments.add(builder.toString()); - builder = new StringBuilder(); - } - PatternParser group = new PatternParser(pattern, i + 1, false); - i = group.getEndIndex(); - segments.add(group); - } else if (c == '[' && (i == 0 || pattern.charAt(i - 1) != '\\')) { - if (!builder.isEmpty()) { - segments.add(builder.toString()); - builder = new StringBuilder(); - } - PatternParser group = new PatternParser(pattern, i + 1, true); - i = group.getEndIndex(); - segments.add(group); - } else if (isGroup && ((!isOptional && c == ')') || (isOptional && c == ']'))) { - if (pattern.charAt(i - 1) == '|') - optionalChoice = true; - endIndex = i; - break; - } else { - if (c == '|') { - hasSelector = true; - if (i == startIndex) - optionalChoice = true; - } - builder.append(c); - } - } - if (isGroup && endIndex == -1) { - String closing = isOptional ? "]" : ")"; - throw new RuntimeException("Could not find closing '" + closing + "': " + pattern.substring(0, startIndex)); - } - if (!builder.isEmpty()) { - segments.add(builder.toString()); - } - - if (isGroup && hasSelector) { - List choices = new ArrayList<>(); - List current = new ArrayList<>(); - for (int i = 0; i < segments.size(); i++) { - Object segment = segments.get(i); - if (segment instanceof String string) { - if (string.contains("|")) { - if (string.contains("||")) - optionalChoice = true; - List split = new ArrayList<>(Arrays.stream(string.split("\\|")).toList()); - if (!string.startsWith("|")) { - String first = split.remove(0); - current.add(first); - } - choices.addAll(combineChoices(current)); - current.clear(); - - if (!split.isEmpty()) { - if (!string.endsWith("|")) { - current.add(split.remove(split.size() - 1)); - } - if (!split.isEmpty()) { - choices.addAll(split); - } - } - } else { - current.add(string); - } - } else { - current.add(segment); - } - } - if (!current.isEmpty()) { - choices.addAll(combineChoices(current)); - } - for (Object choice : choices) { - if (choice instanceof String string) { - combinations.add(string); - } else if (choice instanceof PatternParser parser) { - combinations.addAll(parser.getCombinations()); - } - } - if (isOptional || optionalChoice) { - combinations.add(""); - } else { - combinations.remove(""); - } - } else { - for (Object segment : segments) { - if (segment instanceof String string) { - apply(Set.of(string)); - } else if (segment instanceof PatternParser parser) { - apply(parser.getCombinations()); - } - } - if (isGroup) { - if (isOptional) { - combinations.add(""); - } else { - combinations.remove(""); - } - } - } - } - - /** - * Applies all new combinations to previous combinations. - * @param strings The new combinations to apply. - */ - private void apply(Set strings) { - if (combinations.isEmpty()) { - combinations.addAll(strings); - return; - } - Set newCombinations = new HashSet<>(); - for (String base : combinations) { - for (String add : strings) { - newCombinations.add(combine(base, add)); - } - } - combinations.clear(); - combinations.addAll(newCombinations); - } - - /** - * Gets the ending index for this {@link PatternParser} that correlates to the closing group bracket. - * @return The index of the closing group bracket. - */ - private int getEndIndex() { - assert isGroup; - return endIndex; - } - - /** - * Joins {@code first} and {@code second} together stripping out unnecessary spaces. - * @param first - * @param second - * @return - */ - private String combine(String first, String second) { - if (first.isEmpty()) { - return second.stripLeading(); - } else if (second.isEmpty()) { - return first.stripTrailing(); - } else if (first.endsWith(" ") && second.startsWith(" ")) { - return first + second.stripLeading(); - } - return first + second; - } - - /** - * Similar to {@link #apply(Set)} but applies only the combinations from {@code choices}. - * @param choices The combinations to apply together. - * @return The resulting combinations. - */ - private Set combineChoices(List choices) { - if (choices.isEmpty()) - return Collections.emptySet(); - Set combinations = new HashSet<>(); - Object first = choices.remove(0); - if (first instanceof String string) { - combinations.add(string); - } else if (first instanceof PatternParser parser) { - combinations = parser.getCombinations(); - } - for (Object choice : choices) { - Set newCombinations = new HashSet<>(); - Set current = new HashSet<>(); - if (choice instanceof String string) { - current.add(string); - } else if (choice instanceof PatternParser parser) { - current = parser.getCombinations(); - } - for (String base : combinations) { - for (String add : current) { - newCombinations.add(combine(base, add)); - } - } - combinations.clear(); - combinations.addAll(newCombinations); - } - return combinations; - } - - /** - * Gets the final product of all combinations from this {@link PatternParser} - * @return The combinations. - */ - public Set getCombinations() { - return combinations; - } - -} diff --git a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java similarity index 86% rename from src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java rename to src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index b43750dd069..142681a2062 100644 --- a/src/test/java/org/skriptlang/skript/test/tests/lang/PatternParserTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -1,9 +1,14 @@ -package org.skriptlang.skript.test.tests.lang; +package ch.njol.skript.patterns; import ch.njol.skript.Skript; import ch.njol.skript.SkriptAPIException; -import ch.njol.skript.expressions.ExprScripts; -import ch.njol.skript.expressions.ExprScriptsOld; +import ch.njol.skript.conditions.CondCompare; +import ch.njol.skript.conditions.CondDate; +import ch.njol.skript.conditions.CondIsLoaded; +import ch.njol.skript.conditions.CondScriptLoaded; +import ch.njol.skript.effects.EffScriptFile; +import ch.njol.skript.effects.EffWorldLoad; +import ch.njol.skript.expressions.*; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.Section; import ch.njol.skript.lang.Statement; @@ -13,9 +18,9 @@ import org.junit.Assert; import org.junit.Test; import org.skriptlang.skript.lang.structure.Structure; -import org.skriptlang.skript.lang.util.PatternParser; import org.skriptlang.skript.registration.SyntaxInfo; +import java.lang.annotation.ElementType; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -23,40 +28,36 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; -public class PatternParserTest extends SkriptJUnitTest { - - private static String cleanPattern(String pattern) { - return pattern.replaceAll("%[^%()\\[\\]|*]+%", "%*%") // Replaces expressions, except literals - .replaceAll("[a-zA-Z0-9]+:", "") // Replaces parse tags with leading ID 'any:' - .replaceAll(":", "") // Replaces ':' for parse tags with trailing ID ':any' - .replaceAll("[0-9]+¦", ""); // Replaces parse marks '1¦' - } +public class PatternConflictsTest extends SkriptJUnitTest { private static void compare(Set got, Set expect) { - if (!expect.equals(got)) { - Set gotCopy = new HashSet<>(Set.copyOf(got)); - gotCopy.removeAll(expect); - if (!gotCopy.isEmpty()) { - Assert.fail("Unexpected combinations: " + gotCopy); - } - Set expectCopy = new HashSet<>(Set.copyOf(expect)); - expectCopy.removeAll(got); - if (!expectCopy.isEmpty()) { - Assert.fail("Combinations not found: " + expectCopy); - } + if (expect.equals(got)) + return; + + Set gotCopy = new HashSet<>(Set.copyOf(got)); + gotCopy.removeAll(expect); + if (!gotCopy.isEmpty()) { + Assert.fail("Unexpected combinations: " + gotCopy); } + Set expectCopy = new HashSet<>(Set.copyOf(expect)); + expectCopy.removeAll(got); + if (!expectCopy.isEmpty()) { + Skript.adminBroadcast(StringUtils.join(got, "\n")); + Assert.fail("Combinations not found: " + expectCopy); + } + } + + private Set getCombinations(String pattern) { + return PatternCompiler.compile(pattern, new AtomicInteger()).getAllCombinations(true); } @Test public void test() { - Assert.assertEquals( - cleanPattern("[all [of the]|the] entities [of %-world%]"), - "[all [of the]|the] entities [of %*%]" - ); compare( - new PatternParser(cleanPattern("[all [of the]|the] entities [of %-world%]")).getCombinations(), + getCombinations("[all [of the]|the] entities [of %-world%]"), Set.of( "all entities", "all entities of %*%", "all of the entities", "all of the entities of %*%", @@ -65,12 +66,8 @@ public void test() { ) ); - Assert.assertEquals( - cleanPattern("[all [of the]|the] [:typed] entities [of %-world%]"), - "[all [of the]|the] [typed] entities [of %*%]" - ); compare( - new PatternParser(cleanPattern("[all [of the]|the] [:typed] entities [of %-world%]")).getCombinations(), + getCombinations("[all [of the]|the] [:typed] entities [of %-world%]"), Set.of( "all typed entities", "all typed entities of %*%", "all entities", "all entities of %*%", @@ -83,13 +80,8 @@ public void test() { ) ); - Assert.assertEquals( - cleanPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]"), - "stop (all sound[s]|sound[s] %*%) [(in [the]|from) %*%] [(from playing to|for) %*%]" - ); compare( - new PatternParser(cleanPattern("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]")) - .getCombinations(), + getCombinations("stop (all:all sound[s]|sound[s] %-strings%) [(in [the]|from) %-soundcategory%] [(from playing to|for) %players%]"), Set.of( "stop all sound", "stop all sound in %*%", "stop all sound in %*% from playing to %*%", "stop all sound in %*% for %*%", "stop all sound in the %*%", "stop all sound in the %*% from playing to %*%", "stop all sound in the %*% for %*%", @@ -113,13 +105,8 @@ public void test() { ) ); - Assert.assertEquals( - cleanPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%"), - "[the] [(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %*%] of %*%" - ); compare( - new PatternParser(cleanPattern("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%")) - .getCombinations(), + getCombinations("[the] [high:(tall|high)|(low|normal)] fall damage sound[s] [from [[a] height [of]] %-number%] of %livingentities%"), Set.of( "the tall fall damage sound of %*%", "the tall fall damage sound from %*% of %*%", "the tall fall damage sound from a height %*% of %*%", "the tall fall damage sound from a height of %*% of %*%", @@ -203,13 +190,8 @@ public void test() { ) ); - Assert.assertEquals( - cleanPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]"), - "[on] [uncancelled|cancelled|(any|all)] <.+> [with priority ((lowest|low|normal|high|highest|monitor))]" - ); compare( - new PatternParser(cleanPattern("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]")) - .getCombinations(), + getCombinations("[on] [:uncancelled|:cancelled|any:(any|all)] <.+> [priority:with priority (:(lowest|low|normal|high|highest|monitor))]"), Set.of( "on <.+>", "on <.+> with priority lowest", "on <.+> with priority low", "on <.+> with priority normal", "on <.+> with priority high", @@ -253,13 +235,8 @@ public void test() { ) ); - Assert.assertEquals( - cleanPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%"), - "(open|show) (((crafting [table]|workbench)|chest|anvil|hopper|dropper|dispenser) (view|window|inventory|)|%*%) (to|for) %*%" - ); compare( - new PatternParser(cleanPattern("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%")) - .getCombinations(), + getCombinations("(open|show) ((0¦(crafting [table]|workbench)|1¦chest|2¦anvil|3¦hopper|4¦dropper|5¦dispenser) (view|window|inventory|)|%-inventory/inventorytype%) (to|for) %players%"), Set.of( "open crafting to %*%", "open crafting view to %*%", "open crafting window to %*%", "open crafting inventory to %*%", "open crafting for %*%", "open crafting view for %*%", @@ -332,9 +309,17 @@ public void test() { ); } + /** + * Enum to determine what element type a class falls into. + */ private enum ElementType { STRUCTURE, STATEMENT, EXPRESSION; + /** + * Gets the {@link ElementType} that {@code elementClass} falls into. + * @param elementClass The {@link Class} to check. + * @return The {@link ElementType}. + */ private static ElementType getType(Class elementClass) { if (Structure.class.isAssignableFrom(elementClass)) { return STRUCTURE; @@ -369,9 +354,6 @@ private boolean conflicts(Combination other) { } - /** - * Manual exclusion - */ private static class Exclusion { private final Set> classes; @@ -425,10 +407,33 @@ private boolean exclude(Set combinations) { * via {@link Skript#adminBroadcast(String)}. */ public static boolean BROADCAST = false; + private static final Set EXCLUSIONS = new HashSet<>(); - static { + private void registerExclusions() { + // Usage of these depend on an Experiment being enabled EXCLUSIONS.add(new Exclusion(ExprScriptsOld.class, ExprScripts.class)); + + // Intentional - Sovde + EXCLUSIONS.add(new Exclusion("vector from %*%", ExprVectorOfLocation.class, ExprVectorFromDirection.class)); + + // TODO + // Only 1 conflict + EXCLUSIONS.add(new Exclusion("formatted %*%", ExprFormatDate.class, ExprColoured.class)); + EXCLUSIONS.add(new Exclusion("unload %*%", EffScriptFile.class, EffWorldLoad.class)); + EXCLUSIONS.add(new Exclusion("the %*% of %*%", ExprArmorSlot.class, ExprEntities.class)); + EXCLUSIONS.add(new Exclusion("%*% of %*%", ExprArmorSlot.class, ExprEntities.class, ExprXOf.class)); + + // More than 1 conflict + EXCLUSIONS.add(new Exclusion(CondScriptLoaded.class, CondIsLoaded.class)); + EXCLUSIONS.add(new Exclusion(CondDate.class, CondCompare.class)); + EXCLUSIONS.add(new Exclusion(ExprNewBannerPattern.class, ExprFireworkEffect.class)); + EXCLUSIONS.add(new Exclusion(ExprEntitySound.class, ExprBlockSound.class)); + EXCLUSIONS.add(new Exclusion(ExprInventoryAction.class, ExprClicked.class)); + EXCLUSIONS.add(new Exclusion(ExprEnchantmentLevel.class, ExprPotionEffectTier.class)); + EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprItemsIn.class)); + EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprSets.class)); + EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprValueWithin.class)); } private void info(String message) { @@ -459,8 +464,7 @@ public void testPatterns() { patternCounter++; info("Pattern Counter: " + patternCounter); info("Pattern: " + pattern); - PatternParser parser = new PatternParser(cleanPattern(pattern)); - for (String patternCombination : parser.getCombinations()) { + for (String patternCombination : getCombinations(pattern)) { combinationCounter++; info("Combination Counter: " + combinationCounter); Combination combination = new Combination(patternCombination, pattern, elementClass, elementType); @@ -505,6 +509,7 @@ public void testPatterns() { return; // Check exclusions + registerExclusions(); Set excluded = new HashSet<>(); for (Exclusion exclusion : EXCLUSIONS) { if (exclusion.patternCombination != null) { From 733f83808f2607d159d7db510ed496157479c344 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 19 Aug 2025 20:22:34 -0400 Subject: [PATCH 10/17] Fix TypePatternElement --- src/main/java/ch/njol/skript/patterns/TypePatternElement.java | 2 +- src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index 917c2cfac25..3de8f4abdd3 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -273,7 +273,7 @@ public ExprInfo getExprInfo() { @Override public Set getCombinations(boolean clean) { Set combinations = new HashSet<>(); - if (flagMask == 2 && !clean) { + if (!clean || flagMask == 2) { combinations.add(toString()); } else { combinations.add("%*%"); diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index 142681a2062..38fc465601a 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -20,7 +20,6 @@ import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.registration.SyntaxInfo; -import java.lang.annotation.ElementType; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; From d275fe989e7f026642175e0471ff15045f7f4300 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Wed, 20 Aug 2025 07:11:19 -0400 Subject: [PATCH 11/17] Partial Changes --- .../njol/skript/patterns/PatternElement.java | 3 +- .../skript/patterns/PatternConflictsTest.java | 57 +++++++++++-------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/PatternElement.java b/src/main/java/ch/njol/skript/patterns/PatternElement.java index 0c4afe92a4b..d9bc1209789 100644 --- a/src/main/java/ch/njol/skript/patterns/PatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/PatternElement.java @@ -78,8 +78,7 @@ public final Set getAllCombinations(boolean clean) { newCombinations.add(combineCombination(base, add)); } } - combinations.clear(); - combinations.addAll(newCombinations); + combinations = newCombinations; } return combinations; } diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index 38fc465601a..f323db486d8 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -26,6 +26,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; @@ -360,7 +361,7 @@ private static class Exclusion { /** * Constructs a new {@link Exclusion} that will exclude any conflicting combination - * as long as the only classes involved in the confliction are {@code classes}. + * as long as the only classes involved in the conflict are {@code classes}. * @param classes The {@link Class}es to check for. */ private Exclusion(Class... classes) { @@ -369,7 +370,7 @@ private Exclusion(Class... classes) { /** * Constructs a new {@link Exclusion} that will exclude the conflicting {@code patternCombination} - * as long as the only classes involved in the confliction are {@code classes}. + * as long as the only classes involved in the conflict are {@code classes}. * @param patternCombination The restricted combination. * @param classes The {@link Class}es to check for. */ @@ -379,7 +380,7 @@ private Exclusion(@Nullable String patternCombination, Class... classes) { } /** - * Whether this {@link Exclusion} excludes the confliction by checking if the {@link Class}es from + * Whether this {@link Exclusion} excludes the conflict by checking if the {@link Class}es from * {@code combinations} are only {@link #classes}. * @param combinations The {@link Combination}s to check. * @return {@code true} if the confliction can be excluded, otherwise {@code false}. @@ -423,16 +424,24 @@ private void registerExclusions() { EXCLUSIONS.add(new Exclusion("the %*% of %*%", ExprArmorSlot.class, ExprEntities.class)); EXCLUSIONS.add(new Exclusion("%*% of %*%", ExprArmorSlot.class, ExprEntities.class, ExprXOf.class)); - // More than 1 conflict - EXCLUSIONS.add(new Exclusion(CondScriptLoaded.class, CondIsLoaded.class)); - EXCLUSIONS.add(new Exclusion(CondDate.class, CondCompare.class)); + // 2 EXCLUSIONS.add(new Exclusion(ExprNewBannerPattern.class, ExprFireworkEffect.class)); - EXCLUSIONS.add(new Exclusion(ExprEntitySound.class, ExprBlockSound.class)); EXCLUSIONS.add(new Exclusion(ExprInventoryAction.class, ExprClicked.class)); + EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprValueWithin.class)); + + // 4 + EXCLUSIONS.add(new Exclusion(ExprEntitySound.class, ExprBlockSound.class)); EXCLUSIONS.add(new Exclusion(ExprEnchantmentLevel.class, ExprPotionEffectTier.class)); - EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprItemsIn.class)); + + // 5 EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprSets.class)); - EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprValueWithin.class)); + + // 6 + EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprItemsIn.class)); + + // 8 + EXCLUSIONS.add(new Exclusion(CondScriptLoaded.class, CondIsLoaded.class)); + EXCLUSIONS.add(new Exclusion(CondDate.class, CondCompare.class)); } private void info(String message) { @@ -445,7 +454,6 @@ private void info(String message) { @Test public void testPatterns() { Map> registeredPatterns = new HashMap<>(); - Set hasMultiple = new HashSet<>(); Collection> elements = Skript.instance().syntaxRegistry().elements(); info("Total elements: " + elements.size()); @@ -468,24 +476,22 @@ public void testPatterns() { info("Combination Counter: " + combinationCounter); Combination combination = new Combination(patternCombination, pattern, elementClass, elementType); registeredPatterns.computeIfAbsent(patternCombination, set -> new HashSet<>()).add(combination); - if (registeredPatterns.get(patternCombination).size() > 1) - hasMultiple.add(patternCombination); } } } - if (hasMultiple.isEmpty()) - return; - - // Filter out combinations that can't conflict due to different element types - Set filteredMultiple = new HashSet<>(); - for (String string : hasMultiple) { - Set combinations = registeredPatterns.get(string); - Set filteredCombinations = new HashSet<>(); + Set hasMultiple = new HashSet<>(); + for (Entry> entry : registeredPatterns.entrySet()) { + Set combinations = entry.getValue(); + if (combinations.size() <= 1) + continue; + String string = entry.getKey(); + Set filtered = new HashSet<>(); + // Filter out combinations that can't conflict due to different element types for (Combination first : combinations) { boolean conflicts = false; for (Combination second : combinations) { - if (filteredCombinations.contains(second)) + if (filtered.contains(second)) continue; if (first.conflicts(second)) { conflicts = true; @@ -494,16 +500,17 @@ public void testPatterns() { } if (!conflicts) { info("Filtered Combination: " + first); - filteredCombinations.add(first); + filtered.add(first); } } - combinations.removeAll(filteredCombinations); + combinations.removeAll(filtered); if (combinations.size() <= 1) { info("Filtered Confliction: " + string); - filteredMultiple.add(string); + continue; } + hasMultiple.add(string); } - hasMultiple.removeAll(filteredMultiple); + if (hasMultiple.isEmpty()) return; From aab610f66b43e486abdad5e89ea40ac4e9efcf55 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Wed, 20 Aug 2025 16:12:53 -0400 Subject: [PATCH 12/17] Update PatternConflictsTest.java --- .../skript/patterns/PatternConflictsTest.java | 99 +++++++++++-------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index f323db486d8..7d9215a86ce 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -328,7 +329,7 @@ private static ElementType getType(Class elementClass) { } else if (Expression.class.isAssignableFrom(elementClass)) { return EXPRESSION; } - throw new IllegalStateException("The class '" + elementClass.getSimpleName() + "' does not fall into a type"); + throw new IllegalArgumentException("The class '" + elementClass.getSimpleName() + "' does not fall into a type"); } } @@ -352,6 +353,15 @@ private boolean conflicts(Combination other) { && !elementClass.equals(other.elementClass); } + @Override + public boolean equals(Object object) { + if (!(object instanceof Combination other)) + return false; + return combination.equals(other.combination) + && elementType.equals(other.elementType) + && elementClass.equals(other.elementClass); + } + } private static class Exclusion { @@ -383,7 +393,7 @@ private Exclusion(@Nullable String patternCombination, Class... classes) { * Whether this {@link Exclusion} excludes the conflict by checking if the {@link Class}es from * {@code combinations} are only {@link #classes}. * @param combinations The {@link Combination}s to check. - * @return {@code true} if the confliction can be excluded, otherwise {@code false}. + * @return {@code true} if the conflict can be excluded, otherwise {@code false}. */ private boolean exclude(Set combinations) { if (combinations.isEmpty()) @@ -403,7 +413,7 @@ private boolean exclude(Set combinations) { public static boolean DEBUG = false; /** - * Whether the info messages from the process of {@link #testPatterns()} should be broadcasted + * Whether the info messages from the process of {@link #testPatterns()} should broadcast * via {@link Skript#adminBroadcast(String)}. */ public static boolean BROADCAST = false; @@ -456,8 +466,7 @@ public void testPatterns() { Map> registeredPatterns = new HashMap<>(); Collection> elements = Skript.instance().syntaxRegistry().elements(); - info("Total elements: " + elements.size()); - int elementCounter = 0; + info("Total Elements: " + elements.size()); int patternCounter = 0; int combinationCounter = 0; for (SyntaxInfo syntaxInfo : elements) { @@ -465,78 +474,86 @@ public void testPatterns() { Class elementClass = syntaxInfo.type(); ElementType elementType = ElementType.getType(elementClass); - elementCounter++; - info("Element Counter: " + elementCounter); for (String pattern : patterns) { patternCounter++; - info("Pattern Counter: " + patternCounter); - info("Pattern: " + pattern); for (String patternCombination : getCombinations(pattern)) { combinationCounter++; - info("Combination Counter: " + combinationCounter); Combination combination = new Combination(patternCombination, pattern, elementClass, elementType); registeredPatterns.computeIfAbsent(patternCombination, set -> new HashSet<>()).add(combination); } } } + info("Total Patterns: " + patternCounter); + info("Total Combinations: " + combinationCounter); Set hasMultiple = new HashSet<>(); + int filterCombinationCounter = 0; + int filterConflictCounter = 0; for (Entry> entry : registeredPatterns.entrySet()) { Set combinations = entry.getValue(); - if (combinations.size() <= 1) + int size = combinations.size(); + if (size <= 1) continue; - String string = entry.getKey(); - Set filtered = new HashSet<>(); // Filter out combinations that can't conflict due to different element types - for (Combination first : combinations) { - boolean conflicts = false; - for (Combination second : combinations) { - if (filtered.contains(second)) - continue; - if (first.conflicts(second)) { - conflicts = true; - break; + List list = new ArrayList<>(combinations); + boolean[] hasConflict = new boolean[size]; + for (int firstIndex = 0; firstIndex < size - 1; firstIndex++) { + if (hasConflict[firstIndex]) + continue; + for (int secondIndex = firstIndex + 1; secondIndex < size; secondIndex++) { + if (list.get(firstIndex).conflicts(list.get(secondIndex))) { + hasConflict[firstIndex] = true; + hasConflict[secondIndex] = true; } } - if (!conflicts) { - info("Filtered Combination: " + first); - filtered.add(first); + } + + Set nonConflicting = new HashSet<>(); + for (int index = 0; index < size; index++) { + if (!hasConflict[index]) { + nonConflicting.add(list.get(index)); + filterCombinationCounter++; } } - combinations.removeAll(filtered); + + combinations.removeAll(nonConflicting); if (combinations.size() <= 1) { - info("Filtered Confliction: " + string); - continue; + filterConflictCounter++; + } else { + hasMultiple.add(entry.getKey()); } - hasMultiple.add(string); } + info("Total Filtered Combinations: " + filterCombinationCounter); + info("Total Filtered Conflicts: " + filterConflictCounter); if (hasMultiple.isEmpty()) return; // Check exclusions registerExclusions(); - Set excluded = new HashSet<>(); + int excludedCounter = 0; for (Exclusion exclusion : EXCLUSIONS) { - if (exclusion.patternCombination != null) { - if (!hasMultiple.contains(exclusion.patternCombination)) + if (hasMultiple.isEmpty()) + break; + String exclusionPattern = exclusion.patternCombination; + if (exclusionPattern != null) { + if (!hasMultiple.contains(exclusionPattern)) continue; - if (exclusion.exclude(registeredPatterns.get(exclusion.patternCombination))) { - info("Excluded: " + exclusion.patternCombination); - excluded.add(exclusion.patternCombination); + if (exclusion.exclude(registeredPatterns.get(exclusionPattern))) { + hasMultiple.remove(exclusionPattern); + excludedCounter++; } } else { - for (String string : hasMultiple) { - if (excluded.contains(string)) - continue; + for (Iterator iterator = hasMultiple.iterator(); iterator.hasNext();) { + String string = iterator.next(); if (exclusion.exclude(registeredPatterns.get(string))) { - info("Excluded: " + string); - excluded.add(string); + iterator.remove(); + excludedCounter++; } } } } - hasMultiple.removeAll(excluded); + info("Total Excluded: " + excludedCounter); if (hasMultiple.isEmpty()) return; @@ -548,7 +565,7 @@ public void testPatterns() { String error = "The pattern combination '" + string + "' conflicts in: \n\t\t\t" + StringUtils.join(names, "\n\t\t\t"); errors.add(error); } - errors.add("Total Conflictions: " + errors.size()); + errors.add("Total Conflicts: " + errors.size()); throw new SkriptAPIException(StringUtils.join(errors, "\n\t")); } From 2fbb59b11193b59f682b3d63457ff33d67a01d48 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Wed, 20 Aug 2025 19:26:45 -0400 Subject: [PATCH 13/17] Update PatternConflictsTest.java --- src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index 7d9215a86ce..5d81ff756b7 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -46,7 +46,6 @@ private static void compare(Set got, Set expect) { Set expectCopy = new HashSet<>(Set.copyOf(expect)); expectCopy.removeAll(got); if (!expectCopy.isEmpty()) { - Skript.adminBroadcast(StringUtils.join(got, "\n")); Assert.fail("Combinations not found: " + expectCopy); } } From cce1e19e6b284f97a179af64d23a096852222133 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Thu, 21 Aug 2025 07:36:48 -0400 Subject: [PATCH 14/17] Update PatternConflictsTest.java --- .../java/ch/njol/skript/patterns/PatternConflictsTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index 5d81ff756b7..10e061e73bc 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -507,15 +507,13 @@ public void testPatterns() { } } - Set nonConflicting = new HashSet<>(); for (int index = 0; index < size; index++) { if (!hasConflict[index]) { - nonConflicting.add(list.get(index)); + combinations.remove(list.get(index)); filterCombinationCounter++; } } - combinations.removeAll(nonConflicting); if (combinations.size() <= 1) { filterConflictCounter++; } else { From 13a79e76360bd6b4c5191782c21f8324806a9d89 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Thu, 21 Aug 2025 14:53:26 -0400 Subject: [PATCH 15/17] Update PatternConflictsTest.java --- .../skript/patterns/PatternConflictsTest.java | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index 10e061e73bc..971ec55f9e0 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -394,7 +394,7 @@ private Exclusion(@Nullable String patternCombination, Class... classes) { * @param combinations The {@link Combination}s to check. * @return {@code true} if the conflict can be excluded, otherwise {@code false}. */ - private boolean exclude(Set combinations) { + private boolean exclude(List combinations) { if (combinations.isEmpty()) return false; Set> combinationClasses = combinations.stream() @@ -462,7 +462,7 @@ private void info(String message) { @Test public void testPatterns() { - Map> registeredPatterns = new HashMap<>(); + Map> registeredPatterns = new HashMap<>(); Collection> elements = Skript.instance().syntaxRegistry().elements(); info("Total Elements: " + elements.size()); @@ -478,7 +478,7 @@ public void testPatterns() { for (String patternCombination : getCombinations(pattern)) { combinationCounter++; Combination combination = new Combination(patternCombination, pattern, elementClass, elementType); - registeredPatterns.computeIfAbsent(patternCombination, set -> new HashSet<>()).add(combination); + registeredPatterns.computeIfAbsent(patternCombination, list -> new ArrayList<>()).add(combination); } } } @@ -488,30 +488,32 @@ public void testPatterns() { Set hasMultiple = new HashSet<>(); int filterCombinationCounter = 0; int filterConflictCounter = 0; - for (Entry> entry : registeredPatterns.entrySet()) { - Set combinations = entry.getValue(); + for (Entry> entry : registeredPatterns.entrySet()) { + List combinations = entry.getValue(); int size = combinations.size(); if (size <= 1) continue; // Filter out combinations that can't conflict due to different element types - List list = new ArrayList<>(combinations); boolean[] hasConflict = new boolean[size]; for (int firstIndex = 0; firstIndex < size - 1; firstIndex++) { if (hasConflict[firstIndex]) continue; for (int secondIndex = firstIndex + 1; secondIndex < size; secondIndex++) { - if (list.get(firstIndex).conflicts(list.get(secondIndex))) { + if (combinations.get(firstIndex).conflicts(combinations.get(secondIndex))) { hasConflict[firstIndex] = true; hasConflict[secondIndex] = true; } } } - for (int index = 0; index < size; index++) { + int index = 0; + for (Iterator iterator = combinations.iterator(); iterator.hasNext();) { + Combination combination = iterator.next(); if (!hasConflict[index]) { - combinations.remove(list.get(index)); filterCombinationCounter++; + iterator.remove(); } + index++; } if (combinations.size() <= 1) { From 78c18ce38ed98826605b2a4b74c42b3b2b112339 Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Fri, 22 Aug 2025 17:06:56 -0400 Subject: [PATCH 16/17] Requested Changes --- .../njol/skript/patterns/PatternElement.java | 4 +- .../skript/patterns/PatternConflictsTest.java | 72 +++++++++---------- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/PatternElement.java b/src/main/java/ch/njol/skript/patterns/PatternElement.java index d9bc1209789..f1e8a19c53c 100644 --- a/src/main/java/ch/njol/skript/patterns/PatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/PatternElement.java @@ -60,7 +60,7 @@ public String toFullString() { /** * Gets all combinations available to this {@link PatternElement} and linked {@link PatternElement}s. - * @param clean Whether unnecessary data should be excluded from the combinations. + * @param clean Whether unnecessary data, determined by each implementation, should be excluded from the combinations. * @return The combinations. */ public final Set getAllCombinations(boolean clean) { @@ -87,7 +87,7 @@ public final Set getAllCombinations(boolean clean) { * Helper method for appropriately combining two strings together. * @return The resulting string. */ - private String combineCombination(String first, String second) { + private static String combineCombination(String first, String second) { if (first.isEmpty()) { return second.stripLeading(); } else if (second.isEmpty()) { diff --git a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java index 971ec55f9e0..640ea2649a5 100644 --- a/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java +++ b/src/test/java/ch/njol/skript/patterns/PatternConflictsTest.java @@ -38,19 +38,19 @@ private static void compare(Set got, Set expect) { if (expect.equals(got)) return; - Set gotCopy = new HashSet<>(Set.copyOf(got)); + Set gotCopy = new HashSet<>(got); gotCopy.removeAll(expect); if (!gotCopy.isEmpty()) { Assert.fail("Unexpected combinations: " + gotCopy); } - Set expectCopy = new HashSet<>(Set.copyOf(expect)); + Set expectCopy = new HashSet<>(expect); expectCopy.removeAll(got); if (!expectCopy.isEmpty()) { Assert.fail("Combinations not found: " + expectCopy); } } - private Set getCombinations(String pattern) { + private static Set getCombinations(String pattern) { return PatternCompiler.compile(pattern, new AtomicInteger()).getAllCombinations(true); } @@ -406,65 +406,60 @@ private boolean exclude(List combinations) { } /** - * Whether the info messages from the process of {@link #testPatterns()} should be debugged - * via {@link Skript#debug(String)}. - */ - public static boolean DEBUG = false; - - /** - * Whether the info messages from the process of {@link #testPatterns()} should broadcast + * Whether the info messages from the process of {@link #testConflicts()} should broadcast * via {@link Skript#adminBroadcast(String)}. */ public static boolean BROADCAST = false; private static final Set EXCLUSIONS = new HashSet<>(); - private void registerExclusions() { + static { // Usage of these depend on an Experiment being enabled EXCLUSIONS.add(new Exclusion(ExprScriptsOld.class, ExprScripts.class)); // Intentional - Sovde EXCLUSIONS.add(new Exclusion("vector from %*%", ExprVectorOfLocation.class, ExprVectorFromDirection.class)); - // TODO - // Only 1 conflict + // TODO - Fix these conflicts + // Exclusions by amount of conflicts + // 1 conflict EXCLUSIONS.add(new Exclusion("formatted %*%", ExprFormatDate.class, ExprColoured.class)); EXCLUSIONS.add(new Exclusion("unload %*%", EffScriptFile.class, EffWorldLoad.class)); EXCLUSIONS.add(new Exclusion("the %*% of %*%", ExprArmorSlot.class, ExprEntities.class)); EXCLUSIONS.add(new Exclusion("%*% of %*%", ExprArmorSlot.class, ExprEntities.class, ExprXOf.class)); - // 2 + // 2 conflicts EXCLUSIONS.add(new Exclusion(ExprNewBannerPattern.class, ExprFireworkEffect.class)); EXCLUSIONS.add(new Exclusion(ExprInventoryAction.class, ExprClicked.class)); EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprValueWithin.class)); - // 4 + // 4 conflicts EXCLUSIONS.add(new Exclusion(ExprEntitySound.class, ExprBlockSound.class)); EXCLUSIONS.add(new Exclusion(ExprEnchantmentLevel.class, ExprPotionEffectTier.class)); - // 5 + // 5 conflicts EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprSets.class)); - // 6 + // 6 conflicts EXCLUSIONS.add(new Exclusion(ExprEntities.class, ExprItemsIn.class)); - // 8 + // 8 conflicts EXCLUSIONS.add(new Exclusion(CondScriptLoaded.class, CondIsLoaded.class)); EXCLUSIONS.add(new Exclusion(CondDate.class, CondCompare.class)); } private void info(String message) { - if (DEBUG) - Skript.debug(message); + Skript.debug(message); if (BROADCAST) Skript.adminBroadcast(message); } @Test - public void testPatterns() { - Map> registeredPatterns = new HashMap<>(); + public void testConflicts() { + Map> registeredCombinations = new HashMap<>(); Collection> elements = Skript.instance().syntaxRegistry().elements(); + info("Running Conflicts Test"); info("Total Elements: " + elements.size()); int patternCounter = 0; int combinationCounter = 0; @@ -478,17 +473,17 @@ public void testPatterns() { for (String patternCombination : getCombinations(pattern)) { combinationCounter++; Combination combination = new Combination(patternCombination, pattern, elementClass, elementType); - registeredPatterns.computeIfAbsent(patternCombination, list -> new ArrayList<>()).add(combination); + registeredCombinations.computeIfAbsent(patternCombination, list -> new ArrayList<>()).add(combination); } } } info("Total Patterns: " + patternCounter); info("Total Combinations: " + combinationCounter); - Set hasMultiple = new HashSet<>(); + Set conflicts = new HashSet<>(); int filterCombinationCounter = 0; int filterConflictCounter = 0; - for (Entry> entry : registeredPatterns.entrySet()) { + for (Entry> entry : registeredCombinations.entrySet()) { List combinations = entry.getValue(); int size = combinations.size(); if (size <= 1) @@ -508,7 +503,7 @@ public void testPatterns() { int index = 0; for (Iterator iterator = combinations.iterator(); iterator.hasNext();) { - Combination combination = iterator.next(); + iterator.next(); if (!hasConflict[index]) { filterCombinationCounter++; iterator.remove(); @@ -519,46 +514,45 @@ public void testPatterns() { if (combinations.size() <= 1) { filterConflictCounter++; } else { - hasMultiple.add(entry.getKey()); + conflicts.add(entry.getKey()); } } info("Total Filtered Combinations: " + filterCombinationCounter); info("Total Filtered Conflicts: " + filterConflictCounter); - if (hasMultiple.isEmpty()) + if (conflicts.isEmpty()) return; // Check exclusions - registerExclusions(); int excludedCounter = 0; for (Exclusion exclusion : EXCLUSIONS) { - if (hasMultiple.isEmpty()) + if (conflicts.isEmpty()) break; String exclusionPattern = exclusion.patternCombination; if (exclusionPattern != null) { - if (!hasMultiple.contains(exclusionPattern)) + if (!conflicts.contains(exclusionPattern)) continue; - if (exclusion.exclude(registeredPatterns.get(exclusionPattern))) { - hasMultiple.remove(exclusionPattern); + if (exclusion.exclude(registeredCombinations.get(exclusionPattern))) { + conflicts.remove(exclusionPattern); excludedCounter++; } } else { - for (Iterator iterator = hasMultiple.iterator(); iterator.hasNext();) { + for (Iterator iterator = conflicts.iterator(); iterator.hasNext();) { String string = iterator.next(); - if (exclusion.exclude(registeredPatterns.get(string))) { + if (exclusion.exclude(registeredCombinations.get(string))) { iterator.remove(); excludedCounter++; } } } } - info("Total Excluded: " + excludedCounter); - if (hasMultiple.isEmpty()) + info("Total Excluded Conflicts: " + excludedCounter); + if (conflicts.isEmpty()) return; List errors = new ArrayList<>(); - for (String string : hasMultiple) { - List names = registeredPatterns.get(string).stream() + for (String string : conflicts) { + List names = registeredCombinations.get(string).stream() .map(combination -> "Class: " + combination.elementClass.getSimpleName() + " - Pattern: " + combination.pattern) .toList(); String error = "The pattern combination '" + string + "' conflicts in: \n\t\t\t" + StringUtils.join(names, "\n\t\t\t"); From 1865c418779366b2b6aa462fa87552ac1aa5f2da Mon Sep 17 00:00:00 2001 From: SirSmurfy2 Date: Tue, 9 Sep 2025 18:53:26 -0400 Subject: [PATCH 17/17] Pickles Changes --- src/main/java/ch/njol/skript/patterns/PatternElement.java | 4 ++-- src/main/java/ch/njol/skript/patterns/TypePatternElement.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/njol/skript/patterns/PatternElement.java b/src/main/java/ch/njol/skript/patterns/PatternElement.java index f1e8a19c53c..c02dfdcb49b 100644 --- a/src/main/java/ch/njol/skript/patterns/PatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/PatternElement.java @@ -53,7 +53,7 @@ public String toFullString() { /** * Gets the combinations available to this {@link PatternElement}. - * @param clean Whether unnecessary data should be excluded from the combinations. + * @param clean Whether unnecessary data, determined by each implementation, should be excluded from the combinations. * @return The combinations. */ public abstract Set getCombinations(boolean clean); @@ -88,7 +88,7 @@ public final Set getAllCombinations(boolean clean) { * @return The resulting string. */ private static String combineCombination(String first, String second) { - if (first.isEmpty()) { + if (first.isBlank()) { return second.stripLeading(); } else if (second.isEmpty()) { return first.stripTrailing(); diff --git a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java index 3de8f4abdd3..e3b7e452ec8 100644 --- a/src/main/java/ch/njol/skript/patterns/TypePatternElement.java +++ b/src/main/java/ch/njol/skript/patterns/TypePatternElement.java @@ -268,7 +268,7 @@ public ExprInfo getExprInfo() { /** * {@inheritDoc} - * @param clean Whether this type should be replaced with {@code %*%} if it's literal. + * @param clean Whether this type should be replaced with {@code %*%} if it's not literal. */ @Override public Set getCombinations(boolean clean) {