diff --git a/src/main/java/ch/njol/skript/effects/EffSort.java b/src/main/java/ch/njol/skript/effects/EffSort.java new file mode 100644 index 00000000000..40061777d50 --- /dev/null +++ b/src/main/java/ch/njol/skript/effects/EffSort.java @@ -0,0 +1,167 @@ +/** + * 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.effects; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Keywords; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.ExprInput; +import ch.njol.skript.expressions.ExprSortedList; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.InputSource; +import ch.njol.skript.lang.ParseContext; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.util.Kleenean; +import ch.njol.util.Pair; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +@Name("Sort") +@Description({ + "Sorts a list variable using either the natural ordering of the contents or the results of the given expression.", + "Be warned, this will overwrite the indices of the list variable." +}) +@Examples({ + "set {_words::*} to \"pineapple\", \"banana\", \"yoghurt\", and \"apple\"", + "sort {_words::*} # alphabetical sort", + "sort {_words::*} by length of input # shortest to longest", + "sort {_words::*} based on {tastiness::%input%} # sort based on custom value" +}) +@Since("INSERT VERSION") +@Keywords("input") +public class EffSort extends Effect implements InputSource { + + static { + Skript.registerEffect(EffSort.class, "sort %~objects% [(by|based on) <.+>]"); + if (!ParserInstance.isRegistered(InputData.class)) + ParserInstance.registerData(InputData.class, InputData::new); + } + + @Nullable + private Expression mappingExpr; + @Nullable + private String unparsedExpression; + private Variable unsortedObjects; + + private Set> dependentInputs = new HashSet<>(); + + @Nullable + private Object currentValue; + @UnknownNullability + private String currentIndex; + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + if (expressions[0].isSingle() || !(expressions[0] instanceof Variable)) { + Skript.error("You can only sort list variables!"); + return false; + } + unsortedObjects = (Variable) expressions[0]; + + if (!parseResult.regexes.isEmpty()) { + unparsedExpression = parseResult.regexes.get(0).group(); + assert unparsedExpression != null; + InputData inputData = getParser().getData(InputData.class); + InputSource originalSource = inputData.getSource(); + inputData.setSource(this); + mappingExpr = new SkriptParser(unparsedExpression, SkriptParser.PARSE_EXPRESSIONS, ParseContext.DEFAULT) + .parseExpression(Object.class); + inputData.setSource(originalSource); + return mappingExpr != null && mappingExpr.isSingle(); + } + return true; + } + + @Override + protected void execute(Event event) { + Object[] sorted; + if (mappingExpr == null) { + try { + sorted = unsortedObjects.stream(event) + .sorted(ExprSortedList::compare) + .toArray(); + } catch (IllegalArgumentException | ClassCastException e) { + return; + } + } else { + Map valueToMappedValue = new LinkedHashMap<>(); + for (Iterator> it = unsortedObjects.variablesIterator(event); it.hasNext(); ) { + Pair pair = it.next(); + currentIndex = pair.getKey(); + currentValue = pair.getValue(); + Object mappedValue = mappingExpr.getSingle(event); + if (mappedValue == null) + return; + valueToMappedValue.put(currentValue, mappedValue); + } + try { + sorted = valueToMappedValue.entrySet().stream() + .sorted(Map.Entry.comparingByValue(ExprSortedList::compare)) + .map(Map.Entry::getKey) + .toArray(); + } catch (IllegalArgumentException | ClassCastException e) { + return; + } + } + + unsortedObjects.change(event, sorted, ChangeMode.SET); + } + + @Override + public Set> getDependentInputs() { + return dependentInputs; + } + + @Override + public @Nullable Object getCurrentValue() { + return currentValue; + } + + @Override + public boolean hasIndices() { + return true; + } + + @Override + public @UnknownNullability String getCurrentIndex() { + return currentIndex; + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + return "sort" + unsortedObjects.toString(event, debug) + + (mappingExpr == null ? "" : " by " + mappingExpr.toString(event, debug)); + } + +} diff --git a/src/main/java/ch/njol/skript/expressions/ExprFilter.java b/src/main/java/ch/njol/skript/expressions/ExprFilter.java index 239b720e418..a9747bbc3fc 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprFilter.java +++ b/src/main/java/ch/njol/skript/expressions/ExprFilter.java @@ -27,83 +27,97 @@ import ch.njol.skript.lang.Condition; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionType; -import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.InputSource; import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.lang.parser.ParserInstance; import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Pair; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.skriptlang.skript.lang.converter.Converters; import ch.njol.skript.util.LiteralUtils; -import ch.njol.skript.util.Utils; import ch.njol.util.Kleenean; -import ch.njol.util.coll.iterator.ArrayIterator; import com.google.common.collect.Iterators; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import java.lang.reflect.Array; -import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; -import java.util.List; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.regex.Pattern; +import java.util.stream.StreamSupport; @Name("Filter") -@Description("Filters a list based on a condition. " + - "For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', " + - "only \"something\" would be broadcast as it is the only string that matched the condition.") +@Description({ + "Filters a list based on a condition. ", + "For example, if you ran 'broadcast \"something\" and \"something else\" where [string input is \"something\"]', ", + "only \"something\" would be broadcast as it is the only string that matched the condition." +}) @Examples("send \"congrats on being staff!\" to all players where [player input has permission \"staff\"]") @Since("2.2-dev36") @SuppressWarnings({"null", "unchecked"}) -public class ExprFilter extends SimpleExpression { - - @Nullable - private static ExprFilter parsing; +public class ExprFilter extends SimpleExpression implements InputSource { static { Skript.registerExpression(ExprFilter.class, Object.class, ExpressionType.COMBINED, "%objects% (where|that match) \\[<.+>\\]"); + if (!ParserInstance.isRegistered(InputData.class)) + ParserInstance.registerData(InputData.class, InputData::new); } - private Object current; - private List> children = new ArrayList<>(); - private Condition condition; - private String rawCond; - private Expression objects; + private Condition filterCondition; + private String unparsedCondition; + private Expression unfilteredObjects; + private Set> dependentInputs = new HashSet<>(); @Nullable - public static ExprFilter getParsing() { - return parsing; - } + private Object currentFilterValue; + @Nullable + private String currentFilterIndex; @Override public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - try { - parsing = this; - objects = LiteralUtils.defendExpression(exprs[0]); - if (objects.isSingle()) - return false; - rawCond = parseResult.regexes.get(0).group(); - condition = Condition.parse(rawCond, "Can't understand this condition: " + rawCond); - } finally { - parsing = null; - } - return condition != null && LiteralUtils.canInitSafely(objects); + unfilteredObjects = LiteralUtils.defendExpression(exprs[0]); + if (unfilteredObjects.isSingle() || !LiteralUtils.canInitSafely(unfilteredObjects)) + return false; + unparsedCondition = parseResult.regexes.get(0).group(); + InputData inputData = getParser().getData(InputData.class); + InputSource originalSource = inputData.getSource(); + inputData.setSource(this); + filterCondition = Condition.parse(unparsedCondition, "Can't understand this condition: " + unparsedCondition); + inputData.setSource(originalSource); + return filterCondition != null; } - @NonNull + @NotNull @Override public Iterator iterator(Event event) { - Iterator objIterator = this.objects.iterator(event); - if (objIterator == null) + if (unfilteredObjects instanceof Variable) { + Iterator> variableIterator = ((Variable) unfilteredObjects).variablesIterator(event); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(variableIterator, Spliterator.ORDERED), false) + .filter(pair -> { + currentFilterValue = pair.getValue(); + currentFilterIndex = pair.getKey(); + return filterCondition.check(event); + }) + .map(Pair::getValue) + .iterator(); + } + + // clear current index just to be safe + currentFilterIndex = null; + + Iterator unfilteredObjectIterator = unfilteredObjects.iterator(event); + if (unfilteredObjectIterator == null) return Collections.emptyIterator(); - try { - return Iterators.filter(objIterator, object -> { - current = object; - return condition.check(event); - }); - } finally { - current = null; - } + return Iterators.filter(unfilteredObjectIterator, candidateObject -> { + currentFilterValue = candidateObject; + return filterCondition.check(event); + }); } @Override @@ -115,148 +129,64 @@ protected Object[] get(Event event) { } } - public Object getCurrent() { - return current; - } - - private void addChild(ExprInput child) { - children.add(child); - } - - private void removeChild(ExprInput child) { - children.remove(child); + @Override + public boolean isSingle() { + return false; } @Override public Class getReturnType() { - return objects.getReturnType(); + return unfilteredObjects.getReturnType(); } - @Override - public boolean isSingle() { - return objects.isSingle(); - } @Override public String toString(Event event, boolean debug) { - return String.format("%s where [%s]", objects.toString(event, debug), rawCond); + return unfilteredObjects.toString(event, debug) + " that match [" + unparsedCondition + "]"; } - @Override - public boolean isLoopOf(String s) { - for (ExprInput child : children) { // if they used player input, let's assume loop-player is valid - if (child.getClassInfo() == null || child.getClassInfo().getUserInputPatterns() == null) - continue; + private boolean matchesAnySpecifiedTypes(String candidateString) { + for (ExprInput dependentInput : dependentInputs) { + ClassInfo specifiedType = dependentInput.getSpecifiedType(); + if (specifiedType == null) + return false; + Pattern[] specifiedTypePatterns = specifiedType.getUserInputPatterns(); + if (specifiedTypePatterns == null) + return false; - for (Pattern pattern : child.getClassInfo().getUserInputPatterns()) { - if (pattern.matcher(s).matches()) + for (Pattern typePattern : specifiedTypePatterns) { + if (typePattern.matcher(candidateString).matches()) { return true; + } } } - return objects.isLoopOf(s); // nothing matched, so we'll rely on the object expression's logic + return false; } - @Name("Filter Input") - @Description("Represents the input in a filter expression. " + - "For example, if you ran 'broadcast \"something\" and \"something else\" where [input is \"something\"]" + - "the condition would be checked twice, using \"something\" and \"something else\" as the inputs.") - @Examples("send \"congrats on being staff!\" to all players where [input has permission \"staff\"]") - @Since("2.2-dev36") - public static class ExprInput extends SimpleExpression { - - static { - Skript.registerExpression(ExprInput.class, Object.class, ExpressionType.COMBINED, - "input", - "%*classinfo% input" - ); - } - - @Nullable - private final ExprInput source; - private final Class[] types; - private final Class superType; - @SuppressWarnings("NotNullFieldNotInitialized") - private ExprFilter parent; - @Nullable - private ClassInfo inputType; - - public ExprInput() { - this(null, (Class) Object.class); - } - - public ExprInput(@Nullable ExprInput source, Class... types) { - this.source = source; - if (source != null) { - this.parent = source.parent; - this.inputType = source.inputType; - parent.removeChild(source); - parent.addChild(this); - } - - this.types = types; - this.superType = (Class) Utils.getSuperType(types); - } - - @Override - public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { - parent = ExprFilter.getParsing(); - - if (parent == null) - return false; - - parent.addChild(this); - inputType = matchedPattern == 0 ? null : ((Literal>) exprs[0]).getSingle(); - return true; - } - - @Override - protected T[] get(Event event) { - Object current = parent.getCurrent(); - if (inputType != null && !inputType.getC().isInstance(current)) { - return null; - } - - try { - return Converters.convert(new Object[]{current}, types, superType); - } catch (ClassCastException e1) { - return (T[]) Array.newInstance(superType, 0); - } - } - - public void setParent(ExprFilter parent) { - this.parent = parent; - } - - @Override - public Expression getConvertedExpression(Class... to) { - return new ExprInput<>(this, to); - } - @Override - public Expression getSource() { - return source == null ? this : source; - } - - @Override - public Class getReturnType() { - return superType; - } + @Override + public boolean isLoopOf(String candidateString) { + return unfilteredObjects.isLoopOf(candidateString) || matchesAnySpecifiedTypes(candidateString); + } - @Nullable - private ClassInfo getClassInfo() { - return inputType; - } + public Set> getDependentInputs() { + return dependentInputs; + } - @Override - public boolean isSingle() { - return true; - } + @Nullable + public Object getCurrentValue() { + return currentFilterValue; + } - @Override - public String toString(Event event, boolean debug) { - return inputType == null ? "input" : inputType.getCodeName() + " input"; - } + @Override + public boolean hasIndices() { + return unfilteredObjects instanceof Variable; + } + @Override + @UnknownNullability + public String getCurrentIndex() { + return currentFilterIndex; } } diff --git a/src/main/java/ch/njol/skript/expressions/ExprInput.java b/src/main/java/ch/njol/skript/expressions/ExprInput.java new file mode 100644 index 00000000000..334fc79cd93 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprInput.java @@ -0,0 +1,170 @@ +/** + * 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.expressions; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionType; +import ch.njol.skript.lang.InputSource; +import ch.njol.skript.lang.InputSource.InputData; +import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.skript.registrations.DefaultClasses; +import ch.njol.skript.util.ClassInfoReference; +import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; +import org.skriptlang.skript.lang.converter.Converters; + +import java.lang.reflect.Array; +import java.util.Set; + +@Name("Input") +@Description({ + "Represents the input in a filter expression or sort effect.", + "For example, if you ran 'broadcast \"something\" and \"something else\" where [input is \"something\"]", + "the condition would be checked twice, using \"something\" and \"something else\" as the inputs.", + "The 'input index' pattern can be used when acting on a variable to access the index of the input." +}) +@Examples({ + "send \"congrats on being staff!\" to all players where [input has permission \"staff\"]", + "sort {_list::*} based on length of input index" +}) +@Since("2.2-dev36, INSERT_VERSION (input index)") +public class ExprInput extends SimpleExpression { + + static { + Skript.registerExpression(ExprInput.class, Object.class, ExpressionType.COMBINED, + "input", + "%*classinfo% input", + "input index" + ); + } + + @Nullable + private final ExprInput source; + private final Class[] types; + private final Class superType; + + private InputSource inputSource; + + @Nullable + private ClassInfo specifiedType; + private boolean isIndex = false; + + public ExprInput() { + this(null, (Class) Object.class); + } + + public ExprInput(@Nullable ExprInput source, Class... types) { + this.source = source; + if (source != null) { + isIndex = source.isIndex; + specifiedType = source.specifiedType; + inputSource = source.inputSource; + Set> dependentInputs = inputSource.getDependentInputs(); + dependentInputs.remove(this.source); + dependentInputs.add(this); + } + this.types = types; + this.superType = (Class) Utils.getSuperType(types); + } + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + inputSource = getParser().getData(InputData.class).getSource(); + if (inputSource == null) + return false; + switch (matchedPattern) { + case 1: + ClassInfoReference classInfoReference = ((Literal) ClassInfoReference.wrap((Expression>) exprs[0])).getSingle(); + if (classInfoReference.isPlural().isTrue()) { + Skript.error("An input can only be a single value! Please use a singular type (for example: players input -> player input)."); + return false; + } + specifiedType = classInfoReference.getClassInfo(); + break; + case 2: + if (!inputSource.hasIndices()) { + Skript.error("You cannot use 'input index' on lists without indices!"); + return false; + } + specifiedType = DefaultClasses.STRING; + isIndex = true; + break; + default: + specifiedType = null; + } + return true; + } + + @Override + protected T[] get(Event event) { + Object currentValue = isIndex ? inputSource.getCurrentIndex() : inputSource.getCurrentValue(); + if (currentValue == null || (specifiedType != null && !specifiedType.getC().isInstance(currentValue))) + return (T[]) Array.newInstance(superType, 0); + + try { + return Converters.convert(new Object[]{currentValue}, types, superType); + } catch (ClassCastException exception) { + return (T[]) Array.newInstance(superType, 0); + } + } + + @Override + public Expression getConvertedExpression(Class... to) { + return new ExprInput<>(this, to); + } + + @Override + public Expression getSource() { + return source == null ? this : source; + } + + @Override + public boolean isSingle() { + return true; + } + + @Override + public Class getReturnType() { + return superType; + } + + @Nullable + public ClassInfo getSpecifiedType() { + return specifiedType; + } + + + @Override + public String toString(Event event, boolean debug) { + if (isIndex) + return "input index"; + return specifiedType == null ? "input" : specifiedType.getCodeName() + " input"; + } + +} diff --git a/src/main/java/ch/njol/skript/lang/InputSource.java b/src/main/java/ch/njol/skript/lang/InputSource.java new file mode 100644 index 00000000000..df541a0e40f --- /dev/null +++ b/src/main/java/ch/njol/skript/lang/InputSource.java @@ -0,0 +1,101 @@ +/** + * 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.lang; + +import ch.njol.skript.expressions.ExprInput; +import ch.njol.skript.lang.parser.ParserInstance; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; + +import java.util.Set; + +/** + * An InputSource represents a syntax that can provide a + * value for {@link ExprInput} to use. + *
+ * @see ch.njol.skript.expressions.ExprFilter + * @see ch.njol.skript.effects.EffSort + */ +public interface InputSource { + + /** + * @return A mutable {@link Set} of {@link ExprInput}s that depend on this source. + */ + Set> getDependentInputs(); + + /** + * @return The current value that {@link ExprInput} should use. + */ + @Nullable Object getCurrentValue(); + + /** + * {@link InputSource}s that can supply indices along with values should override this + * method to indicate their ability. + * + * @return Whether this source can return indices. + */ + default boolean hasIndices() { + return false; + } + + /** + * This should only be used by {@link InputSource}s that return true for {@link InputSource#hasIndices()}. + * + * @return The current value's index. + */ + default @UnknownNullability String getCurrentIndex() { + return null; + } + + /** + * A {@link ch.njol.skript.lang.parser.ParserInstance.Data} used for + * linking {@link InputSource}s and {@link ExprInput}s. + */ + class InputData extends ParserInstance.Data { + + @Nullable + private InputSource source; + + public InputData(ParserInstance parserInstance) { + super(parserInstance); + } + + /** + * {@link InputSource} should call this during init() to declare that they are the current source for future + * {@link ExprInput}s, and then reset it to its previous value once out of scope. + * + * @param source the source of information. + */ + public void setSource(@Nullable InputSource source) { + this.source = source; + } + + /** + * ExprInput should use this to get the information source, and then call + * {@link InputSource#getCurrentValue()} to get the current value of the source. + * + * @return the source of information. + */ + @Nullable + public InputSource getSource() { + return source; + } + + } +} diff --git a/src/test/skript/tests/syntaxes/effects/EffSort.sk b/src/test/skript/tests/syntaxes/effects/EffSort.sk new file mode 100644 index 00000000000..4d832cd61fa --- /dev/null +++ b/src/test/skript/tests/syntaxes/effects/EffSort.sk @@ -0,0 +1,47 @@ +test "sorting": + set {_numbers::*} to shuffled integers from 1 to 50 + sort {_numbers::*} + assert {_numbers::*} is integers from 1 to 50 with "improper sorting of numbers" + + set {_numbers::*} to shuffled integers from 1 to 5 + sort {_numbers::*} by input * 20 + 4 - 3 # linear transformations don't affect order + assert {_numbers::*} is integers from 1 to 5 with "improper custom sorting of numbers" + + set {_numbers::*} to shuffled integers from 1 to 5 + set {_pre-sort-numbers::*} to {_numbers::*} + sort {_numbers::*} by "%input%" parsed as time # map expression returns null + assert {_numbers::*} is {_pre-sort-numbers::*} with "Invalid sorting expression adjusted list" + + set {_numbers::*} to shuffled integers from 1 to 5 + set {_pre-sort-numbers::*} to {_numbers::*} + sort {_numbers::*} by {_} + assert {_numbers::*} is {_pre-sort-numbers::*} with "Invalid sorting expression adjusted list" + + set {_numbers::*} to {_} + sort {_numbers::*} by input + 3 + assert {_numbers::*} is not set with "Invalid sorting of unset list" + + set {_chars::*} to shuffled characters between "a" and "f" + sort {_chars::*} + assert {_chars::*} is characters between "a" and "f" with "improper sorting of chars" + + set {_chars::*} to shuffled characters between "a" and "f" + sort {_chars::*} based on codepoint of input + assert {_chars::*} is characters between "a" and "f" with "improper custom sorting of chars" + + set {_mixed::*} to shuffled (characters between "a" and "f", integers from 1 to 5) + set {_pre-sort-mixed::*} to {_mixed::*} + sort {_mixed::*} + assert {_mixed::*} is {_pre-sort-mixed::*} with "incomparable mixed list was adjusted" + + set {_mixed::*} to shuffled (characters between "a" and "f", integers from 1 to 5) + sort {_mixed::*} by "%input%" + assert {_mixed::*} is 1, 2, 3, 4, 5, and characters between "a" and "f" with "improper custom sorting of mixed list" + + set {_list::x} to 1 + set {_list::aa} to 2 + set {_list::bxs} to 3 + set {_list::zysa} to 4 + set {_list::aaaaa} to 5 + sort {_list::*} by length of input index + assert {_list::*} is integers from 1 to 5 with "improper custom sorting based on index" diff --git a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk index b1f1738f20d..bc195b894c8 100644 --- a/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk +++ b/src/test/skript/tests/syntaxes/expressions/ExprFilter.sk @@ -3,4 +3,15 @@ test "where filter": assert first element of ({_list::*} where [string input is "foo"]) is "foo" with "ExprFilter filtered incorrectly" assert {_list::*} where [number input is set] is not set with "ExprFilter provided input value when classinfo did not match" assert first element of ({_list::*} where [input is "foo"]) is "foo" with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "bar"]) is "bar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "bar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "bar"]) is "bar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "bar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert first element of ({_list::*} where [input is "foobar"]) is "foobar" with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "foobar"]) is 1 with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is "foo" or "bar"]) is 2 with "ExprFilter filtered object input incorrectly" + assert size of ({_list::*} where [input is set]) is 3 with "ExprFilter filtered object input incorrectly" assert {_list::*} where [false is true] is not set with "ExprFilter returned objects with false condition" + assert ({_list::*} where [input is (("foo" and "bar") where [input is "bar"])]) is "bar" with "Failed filter with filter within condition" + assert (({_list::*} where [input is "foo"]) where [input is "foo"]) is "foo" with "Failed chained filters" + assert {_list::*} where [input index is "2" or "3"] is "bar" and "foobar" with "Failed input index filter"