From 32e5ff693484c969668b845be04f1e83746396e9 Mon Sep 17 00:00:00 2001 From: Patrick Miller Date: Mon, 15 Apr 2024 13:34:23 -0400 Subject: [PATCH] Pattern Keyword Enhancements (#6550) Rework Keyword System Co-authored-by: Moderocky --- .../java/ch/njol/skript/patterns/Keyword.java | 148 ++++++++++++++++++ .../njol/skript/patterns/SkriptPattern.java | 29 +--- 2 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 src/main/java/ch/njol/skript/patterns/Keyword.java diff --git a/src/main/java/ch/njol/skript/patterns/Keyword.java b/src/main/java/ch/njol/skript/patterns/Keyword.java new file mode 100644 index 00000000000..91dca7d86b9 --- /dev/null +++ b/src/main/java/ch/njol/skript/patterns/Keyword.java @@ -0,0 +1,148 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.patterns; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A keyword describes a required component of a pattern. + * For example, the pattern '[the] name' has the keyword ' name' + */ +abstract class Keyword { + + /** + * Determines whether this keyword is present in a string. + * @param expr The expression to search for this keyword. + * @return Whether this keyword is present in expr. + */ + abstract boolean isPresent(String expr); + + /** + * Builds a list of keywords starting from the provided pattern element. + * @param first The pattern to build keywords from. + * @return A list of all keywords within first. + */ + public static Keyword[] buildKeywords(PatternElement first) { + List keywords = new ArrayList<>(); + PatternElement next = first; + boolean starting = true; // whether it is the start of the pattern + boolean ending = next.next == null; // whether it is the end of the pattern + while (next != null) { + if (next instanceof LiteralPatternElement) { // simple literal strings are keywords + String literal = next.toString().trim(); + while (literal.contains(" ")) + literal = literal.replace(" ", " "); + keywords.add(new SimpleKeyword(literal, starting, ending)); + } else if (next instanceof ChoicePatternElement) { // this element might contain some keywords + List choiceElements = flatten(next); + if (choiceElements.stream().allMatch(e -> e instanceof LiteralPatternElement)) { + // all elements are literals, and this is a choice, meaning one of them must be required + // thus, we build a keyword that requires one of them to be present. + List groupKeywords = choiceElements.stream() + .map(e -> { + String literal = e.toString().trim(); + while (literal.contains(" ")) + literal = literal.replace(" ", " "); + return literal; + }) + .collect(Collectors.toList()); + keywords.add(new GroupKeyword(groupKeywords, starting, ending)); + } + } else if (next instanceof GroupPatternElement) { // groups need to be unwrapped (they might contain choices) + next = ((GroupPatternElement) next).getPatternElement(); + continue; + } + starting = false; + next = next.next; + } + return keywords.toArray(new Keyword[0]); + } + + /** + * A method for flattening a pattern element. + * For example, a {@link ChoicePatternElement} wraps multiple elements. This method unwraps it. + * @param element The element to flatten. + * @return A list of all pattern elements contained within element. + */ + private static List flatten(PatternElement element) { + if (element instanceof ChoicePatternElement) { + return ((ChoicePatternElement) element).getPatternElements().stream() + .flatMap(e -> flatten(e).stream()) + .collect(Collectors.toList()); + } else if (element instanceof GroupPatternElement) { + element = ((GroupPatternElement) element).getPatternElement(); + } + return Collections.singletonList(element); + } + + /** + * A keyword implementation that requires a specific string to be present. + */ + private static final class SimpleKeyword extends Keyword { + + private final String keyword; + private final boolean starting, ending; + + SimpleKeyword(String keyword, boolean starting, boolean ending) { + this.keyword = keyword; + this.starting = starting; + this.ending = ending; + } + + @Override + public boolean isPresent(String expr) { + if (starting) + return expr.startsWith(keyword); + if (ending) + return expr.endsWith(keyword); + return expr.contains(keyword); + } + + } + + /** + * A keyword implementation that requires at least one string out of a collection of strings to be present. + */ + private static final class GroupKeyword extends Keyword { + + private final Collection keywords; + private final boolean starting, ending; + + GroupKeyword(Collection keywords, boolean starting, boolean ending) { + this.keywords = keywords; + this.starting = starting; + this.ending = ending; + } + + @Override + public boolean isPresent(String expr) { + if (starting) + return keywords.stream().anyMatch(expr::startsWith); + if (ending) + return keywords.stream().anyMatch(expr::endsWith); + return keywords.stream().anyMatch(expr::contains); + } + + } + +} diff --git a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java index f43c521b59b..40d092ca7a0 100644 --- a/src/main/java/ch/njol/skript/patterns/SkriptPattern.java +++ b/src/main/java/ch/njol/skript/patterns/SkriptPattern.java @@ -23,8 +23,6 @@ import ch.njol.skript.lang.SkriptParser; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; public class SkriptPattern { @@ -32,21 +30,22 @@ public class SkriptPattern { private final PatternElement first; private final int expressionAmount; - private final String[] keywords; + private final Keyword[] keywords; public SkriptPattern(PatternElement first, int expressionAmount) { this.first = first; this.expressionAmount = expressionAmount; - keywords = getKeywords(first); + keywords = Keyword.buildKeywords(first); } @Nullable public MatchResult match(String expr, int flags, ParseContext parseContext) { // Matching shortcut String lowerExpr = expr.toLowerCase(Locale.ENGLISH); - for (String keyword : keywords) - if (!lowerExpr.contains(keyword)) + for (Keyword keyword : keywords) { + if (!keyword.isPresent(lowerExpr)) return null; + } expr = expr.trim(); @@ -68,24 +67,6 @@ public String toString() { return first.toFullString(); } - public static String[] getKeywords(PatternElement first) { - List keywords = new ArrayList<>(); - PatternElement next = first; - while (next != null) { - if (next instanceof LiteralPatternElement) { - String literal = next.toString().trim(); - while (literal.contains(" ")) - literal = literal.replace(" ", " "); - keywords.add(literal); - } else if (next instanceof GroupPatternElement) { - next = ((GroupPatternElement) next).getPatternElement(); - continue; - } - next = next.next; - } - return keywords.toArray(new String[0]); - } - /** * @return the size of the {@link MatchResult#expressions} array * from a match.