diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index aba4c91..e78392b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -39,5 +39,5 @@ jobs: uses: SkriptLang/skript-test-action@v1.0 with: test_script_directory: src/test/scripts - skript_repo_ref: 2.8.2 + skript_repo_ref: dev/patch extra_plugins_directory: extra-plugins/ diff --git a/src/main/java/com/btk5h/skriptmirror/skript/CondAcceptsChange.java b/src/main/java/com/btk5h/skriptmirror/skript/CondAcceptsChange.java new file mode 100644 index 0000000..f805433 --- /dev/null +++ b/src/main/java/com/btk5h/skriptmirror/skript/CondAcceptsChange.java @@ -0,0 +1,118 @@ +package com.btk5h.skriptmirror.skript; + +import ch.njol.skript.Skript; +import ch.njol.skript.classes.Changer.ChangeMode; +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.util.Patterns; +import ch.njol.util.Kleenean; +import com.btk5h.skriptmirror.util.ClassInfoReference; +import com.btk5h.skriptmirror.util.SkriptUtil; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + + +public class CondAcceptsChange extends Condition { + + private static final Patterns PATTERNS = new Patterns<>(new Object[][] { + {"%classinfo% can be added to %expressions%", ChangeMode.ADD}, + {"%classinfo% (can't|cannot) be added to %expressions%", ChangeMode.ADD}, + {"%expressions% can be set to %classinfo%", ChangeMode.SET}, + {"%expressions% (can't|cannot) be set to %classinfo%", ChangeMode.SET}, + {"%classinfo% can be removed from %expressions%", ChangeMode.REMOVE}, + {"%classinfo% (can't|cannot) be removed from %expressions%", ChangeMode.REMOVE}, + {"all %classinfo% can be removed from %expressions%", ChangeMode.REMOVE_ALL}, + {"all %classinfo% (can't|cannot) be removed from %expressions%", ChangeMode.REMOVE_ALL}, + {"%expressions% can be deleted", ChangeMode.DELETE}, + {"%expressions% (can't|cannot) be deleted", ChangeMode.DELETE}, + {"%expressions% can be reset", ChangeMode.RESET}, + {"%expressions% (can't|cannot) be reset", ChangeMode.RESET} + }); + + static { + Skript.registerCondition(CondAcceptsChange.class, PATTERNS.getPatterns()); + } + + private ChangeMode desiredChangeMode; + private boolean desiredTypeIsPlural; + private Expression desiredType; + private Expression> expressions; + + @SuppressWarnings("unchecked") + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + setNegated((matchedPattern % 2) != 0); + desiredChangeMode = PATTERNS.getInfo(matchedPattern); + Expression desiredType = null; + switch (desiredChangeMode) { + case ADD: + case REMOVE: + case REMOVE_ALL: + desiredType = exprs[0]; + expressions = (Expression>) exprs[1]; + break; + case SET: + expressions = (Expression>) exprs[0]; + desiredType = exprs[1]; + break; + case RESET: + case DELETE: + expressions = (Expression>) exprs[0]; + } + if (desiredType != null) { + this.desiredType = SkriptUtil.wrapClassInfoExpression((Expression>) desiredType); + } + return SkriptUtil.canInitSafely(desiredType); + } + + @Override + public boolean check(Event event) { + if (desiredChangeMode == ChangeMode.DELETE || desiredChangeMode == ChangeMode.RESET) + //noinspection ConstantValue + return expressions.check(event, expressions -> expressions.acceptChange(desiredChangeMode) != null, isNegated()); + ClassInfoReference desiredType = this.desiredType.getSingle(event); + if (desiredType == null) + return false; + return expressions.check(event, expression -> acceptsChange(expression, desiredChangeMode, desiredType), isNegated()); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + String expressionsString = expressions.toString(event, debug); + String desiredTypesString = desiredType == null ? null : desiredType.toString(event, debug); + switch (desiredChangeMode) { + case ADD: + return desiredTypesString + " can be added to " + expressionsString; + case SET: + return expressionsString + " can be set to " + desiredTypesString; + case RESET: + return expressionsString + " can be reset"; + case DELETE: + return expressionsString + " can be deleted"; + case REMOVE: + return desiredTypesString + " can be removed from " + expressionsString; + case REMOVE_ALL: + return "all " + desiredTypesString + " can be removed from " + expressionsString; + default: + throw new IllegalStateException(); + } + } + + private boolean acceptsChange(Expression expression, ChangeMode desiredChangeMode, ClassInfoReference desiredType) { + Class[] acceptableTypes = expression.acceptChange(desiredChangeMode); + //noinspection ConstantValue + if (acceptableTypes != null) { + for (Class acceptableType : acceptableTypes) { + if (acceptableType.isArray() + && acceptableType.getComponentType().isAssignableFrom(desiredType.getClassInfo().getC())) { + return true; + } else if (desiredType.isPlural() && acceptableType.isAssignableFrom(desiredType.getClassInfo().getC())) + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/btk5h/skriptmirror/skript/Types.java b/src/main/java/com/btk5h/skriptmirror/skript/Types.java index 8110068..608c9d6 100644 --- a/src/main/java/com/btk5h/skriptmirror/skript/Types.java +++ b/src/main/java/com/btk5h/skriptmirror/skript/Types.java @@ -3,6 +3,7 @@ import ch.njol.skript.classes.ClassInfo; import ch.njol.skript.classes.Parser; import ch.njol.skript.classes.Serializer; +import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ParseContext; import ch.njol.skript.registrations.Classes; import ch.njol.yggdrasil.Fields; @@ -214,6 +215,31 @@ public String getVariableNamePattern() { Classes.registerClass(new ClassInfo<>(Section.class, "section") .user("sections?") ); + + Classes.registerClass(new ClassInfo<>(Expression.class, "expression") + .user("expressions?") + .parser(new Parser>() { + @Override + public boolean canParse(ParseContext context) { + return false; + } + + @Override + public String getDebugMessage(Expression expression) { + return expression.toString(null, true); + } + + @Override + public String toString(Expression expression, int i) { + return expression.toString(null, false); + } + + @Override + public String toVariableNameString(Expression expression) { + return toString(expression, 0); + } + })); + } } diff --git a/src/main/java/com/btk5h/skriptmirror/util/ClassInfoReference.java b/src/main/java/com/btk5h/skriptmirror/util/ClassInfoReference.java new file mode 100644 index 0000000..c0d57ba --- /dev/null +++ b/src/main/java/com/btk5h/skriptmirror/util/ClassInfoReference.java @@ -0,0 +1,51 @@ +package com.btk5h.skriptmirror.util; + +import ch.njol.skript.classes.ClassInfo; +import ch.njol.skript.expressions.base.WrapperExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.eclipse.jdt.annotation.Nullable; + +import java.util.Objects; + +public class ClassInfoReference { + + private ClassInfo classInfo; + private boolean plural; + private boolean specific; + + /** + * Creates a non-specific ClassInfoReference + * @param classInfo the classinfo referenced + */ + public ClassInfoReference(ClassInfo classInfo) { + this.classInfo = classInfo; + } + + /** + * Creates a specific ClassInfoReference + * @param classInfo the classinfo referenced + * @param plural whether the reference to the classinfo is plural + */ + public ClassInfoReference(ClassInfo classInfo, boolean plural) { + this.classInfo = classInfo; + this.plural = plural; + this.specific = true; + } + + public ClassInfo getClassInfo() { + return classInfo; + } + + public boolean isSpecific() { + return specific; + } + + public boolean isPlural() { + return plural; + } + +} diff --git a/src/main/java/com/btk5h/skriptmirror/util/SkriptUtil.java b/src/main/java/com/btk5h/skriptmirror/util/SkriptUtil.java index 17df4f7..7507f179 100644 --- a/src/main/java/com/btk5h/skriptmirror/util/SkriptUtil.java +++ b/src/main/java/com/btk5h/skriptmirror/util/SkriptUtil.java @@ -9,17 +9,21 @@ import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionList; import ch.njol.skript.lang.Literal; +import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.TriggerItem; import ch.njol.skript.lang.UnparsedLiteral; import ch.njol.skript.lang.parser.ParserInstance; +import ch.njol.skript.lang.util.SimpleExpression; import ch.njol.skript.log.RetainingLogHandler; import ch.njol.skript.log.SkriptLogger; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.DefaultClasses; import ch.njol.skript.util.Utils; +import ch.njol.util.Kleenean; import ch.njol.util.NonNullPair; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import org.skriptlang.skript.lang.script.Script; import java.io.File; @@ -120,6 +124,23 @@ public static Script getCurrentScript() { .orElse(null); } + /** + * Gets the UnparsedLiteral an expression was converted from + * @param expression the expression to get the UnparsedLiteral of + * @return the source UnparsedLiteral or null if there is no such source + */ + public static UnparsedLiteral getSourceUnparsedLiteral(Expression expression) { + Expression sourceExpression = expression.getSource(); + while (!(sourceExpression instanceof UnparsedLiteral)) { + Expression nextSourceExpression = sourceExpression.getSource(); + if (nextSourceExpression == sourceExpression) { + return null; + } + sourceExpression = nextSourceExpression; + } + return (UnparsedLiteral) sourceExpression; + } + /** * Gets the {@link ClassInfo} by first converting the given string to a singular. * Returns {@code Object.class}'s if no {@link ClassInfo} can be found for the given type. @@ -143,7 +164,7 @@ public static ClassInfo getUserClassInfo(String name) { } /** - * @return the pair of the {@link ClassInfo} in the given string, and whether is is singular. + * @return the pair of the {@link ClassInfo} in the given string, and whether it is singular. */ public static NonNullPair, Boolean> getUserClassInfoAndPlural(String name) { NonNullPair wordData = Utils.getEnglishPlural(name); @@ -152,6 +173,71 @@ public static NonNullPair, Boolean> getUserClassInfoAndPlural(Strin return new NonNullPair<>(ci, wordData.getSecond()); } + /** + * Wraps a ClassInfo expression into a ClassInfoReference expression + * If possible, specific attributes of ClassInfoReference may be filled in. + * @param sourceExpression the ClassInfo expression + * @return the ClassInfoReference expression + */ + public static Expression wrapClassInfoExpression(Expression> sourceExpression) { + ClassInfoReference parsedReference = null; + if (sourceExpression instanceof Literal) { + UnparsedLiteral sourceUnparsedLiteral = getSourceUnparsedLiteral(sourceExpression); + if (sourceUnparsedLiteral == null) { + return null; + } + boolean plural = Utils.getEnglishPlural(sourceUnparsedLiteral.getData()).getSecond(); + ClassInfo classInfo = ((Literal>) sourceExpression).getSingle(); + if (classInfo != null) { + parsedReference = new ClassInfoReference(classInfo, plural); + } + } + ClassInfoReference finalParsedReference = parsedReference; + return new SimpleExpression() { + + @Override + public boolean init(Expression[] expressions, int matchedPattern, Kleenean isDelayed, SkriptParser.ParseResult parseResult) { + return sourceExpression.init(expressions, matchedPattern, isDelayed, parseResult); + } + + @Override + protected ClassInfoReference[] get(Event event) { + if (finalParsedReference != null) { + return new ClassInfoReference[] { finalParsedReference }; + } else if (isSingle()) { + ClassInfo classInfo = sourceExpression.getSingle(event); + if (classInfo == null) { + return new ClassInfoReference[0]; + } + return new ClassInfoReference[] { new ClassInfoReference(classInfo) }; + } else { + return sourceExpression.stream(event) + .filter(Objects::nonNull) + .map(ClassInfoReference::new) + .toArray(ClassInfoReference[]::new); + } + } + + public Class getReturnType() { + return ClassInfoReference.class; + } + + @Override + public boolean isSingle() { + return sourceExpression.isSingle(); + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (debug) { + return sourceExpression.toString(event, true) + " (wrapped by ClassInfoReference)"; + } + return sourceExpression.toString(event, false); + } + + }; + } + /** * @return the singular form of the given string type, * converted back to plural if it was plural in the first place. @@ -166,7 +252,7 @@ public static String replaceUserInputPatterns(String name) { /** * {@return} a {@link Function} to get a {@link Expression}'s value, - * using {@link Expression#getSingle(Event)} if {@link Expression#getSingle(Event)} + * using {@link Expression#getSingle(Event)} if {@link Expression#isSingle()} * returns {@code true}, otherwise returning {@link Expression#getArray(Event)}. */ public static Function, Object> unwrapWithEvent(Event e) { diff --git a/src/test/scripts/CondChange.sk b/src/test/scripts/CondChange.sk new file mode 100644 index 0000000..bbb970d --- /dev/null +++ b/src/test/scripts/CondChange.sk @@ -0,0 +1,33 @@ +test "CondChange - set": + assert raw ({_v}) can be set to a string with "a variable should be settable to a string" + assert raw (location of (random player out of all players)) can't be set to a string with "the location expression shouldn't be settable to a string" + assert raw (name of {_v}) can be set to a string with "the name expression should be settable to a string" + assert raw (name of {_v}) can't be set to an integer with "the name expression shouldn't be settable to an integer" + assert raw (name of {_v}) can't be set to strings with "the name expression shouldn't be settable to multiple strings" + assert raw ({_v::*}) can be set to strings with "a list variable should be settable to multiple strings" + assert raw ({_v}) can't be set to strings with "a non-list variable should not be settable to multiple strings" + +test "CondChange - reset": + assert raw (uppercase "") can't be reset with "the uppercase expression shouldn't be resettable" + assert raw (portal cooldown of {_v}) can be reset with "the portal cooldown expression should be resettable" + +test "CondChange - delete": + assert raw ({_v}) can be deleted with "a variable should be deletable" + assert raw (uppercase "") can't be deleted with "the uppercase expression shouldn't be deletable" + +test "CondChange - add": + assert an integer can be added to raw ({_v}) with "an integer should be addable to a variable" + assert integers can be added to raw ({_v}) with "multiple integers should be addable to a variable" + assert a string can't be added to raw (all players) with "a string shouldn't be addable to all players" + +test "CondChange - remove": + assert a player can be removed from raw ({_v::*}) with "a player should be removable from a variable" + assert players can be removed from raw ({_v::*}) with "multiple players should be removable from a variable" + assert a player can't be removed from raw (block at {_v}) with "a player shouldn't be removable the block expression" + assert a integer can be removed from raw (durability of {_v}) with "an integer should be removable from the durability expression" + +test "CondChange - remove all": + assert all player can be removed from raw ({_v::*}) with "all integer should be removable from a variable" + assert all players can be removed from raw ({_v::*}) with "all integers should be removable from a variable" + assert all strings can't be removed from raw (name of {_v}) with "all strings shouldn't be removable from the name expression" + assert all players can't be removed from raw (name of {_v}) with "all players shouldn't be removable from the name expression"