From a65814128888e38207c6bd199ce95f3973144c22 Mon Sep 17 00:00:00 2001 From: Mwexim Date: Tue, 14 Feb 2023 22:20:53 +0100 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Type.Attribute:=20new=20registr?= =?UTF-8?q?ation=20system=20for=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../expressions/CondExprIsDivisible.java | 2 +- .../expressions/CondExprIsPrime.java | 6 +- .../expressions/CondExprIsSet.java | 2 +- .../expressions/ExprDifference.java | 86 ++++------------ .../skriptparser/expressions/ExprRange.java | 32 ++++-- .../syst3ms/skriptparser/lang/Variable.java | 2 +- .../lang/properties/PropertyConditional.java | 63 ++++++------ .../lang/properties/PropertyExpression.java | 28 ++++-- .../registration/DefaultRegistration.java | 89 ++++++++--------- .../registration/SkriptRegistration.java | 64 ++++++++---- .../skriptparser/sections/SecLoop.java | 47 +++------ .../syst3ms/skriptparser/types/Type.java | 97 ++++++------------- .../{changers => attributes}/Arithmetic.java | 6 +- .../skriptparser/types/attributes/Range.java | 22 +++++ .../types/attributes/package-info.java | 4 + .../skriptparser/types/changers/Changer.java | 3 +- .../skriptparser/types/ranges/RangeInfo.java | 32 ------ .../skriptparser/types/ranges/Ranges.java | 30 ------ .../resources/expressions/ExprDifference.txt | 22 ++++- src/test/resources/expressions/ExprRange.txt | 9 +- 20 files changed, 289 insertions(+), 357 deletions(-) rename src/main/java/io/github/syst3ms/skriptparser/types/{changers => attributes}/Arithmetic.java (63%) create mode 100644 src/main/java/io/github/syst3ms/skriptparser/types/attributes/Range.java create mode 100644 src/main/java/io/github/syst3ms/skriptparser/types/attributes/package-info.java delete mode 100644 src/main/java/io/github/syst3ms/skriptparser/types/ranges/RangeInfo.java delete mode 100644 src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java index ba2ae11a..bf2aa7cf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsDivisible.java @@ -42,7 +42,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex @Override public boolean check(TriggerContext ctx) { - return getPerformer().check( + return getOwner().check( ctx, performer -> divider.getSingle(ctx) .filter(__ -> BigDecimalMath.isIntValue(BigDecimalMath.getBigDecimal(performer))) diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsPrime.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsPrime.java index 9e1e1de5..620a804f 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsPrime.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsPrime.java @@ -21,13 +21,13 @@ public class CondExprIsPrime extends PropertyConditional { static { Parser.getMainRegistration().newPropertyConditional(CondExprIsPrime.class, "numbers", ConditionalType.BE, "[a] prime [number[s]]") - .addData(PROPERTY_IDENTIFIER, "prime") + .setPropertyName("prime") .register(); } @Override - public boolean check(Number performer) { - var bd = BigDecimalMath.getBigDecimal(performer); + public boolean check(Number owner) { + var bd = BigDecimalMath.getBigDecimal(owner); return bd.signum() != -1 && BigDecimalMath.isIntValue(bd) && NumberMath.isPrime(BigDecimalMath.getBigInteger(bd)); diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java index 5d2ef4e6..e3afc4b0 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/CondExprIsSet.java @@ -24,7 +24,7 @@ public class CondExprIsSet extends PropertyConditional { } @Override - public boolean check(Object performer) { + public boolean check(Object owner) { return true; } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java index a9feb210..623135d7 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java @@ -2,20 +2,18 @@ import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.lang.Expression; -import io.github.syst3ms.skriptparser.lang.Literal; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.lang.Variable; import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; -import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Arithmetic; import io.github.syst3ms.skriptparser.util.ClassUtils; import io.github.syst3ms.skriptparser.util.DoubleOptional; import org.jetbrains.annotations.Nullable; -import java.util.Optional; - /** * The difference between two values. * Note that only values that can be checked for difference are allowed @@ -37,8 +35,6 @@ public class ExprDifference implements Expression { } private Expression first, second; - private boolean variablePresent; - @SuppressWarnings("rawtypes") @Nullable private Arithmetic arithmetic; @@ -46,65 +42,21 @@ public class ExprDifference implements Expression { public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { first = expressions[0]; second = expressions[1]; - Optional> type; - - if (first instanceof Literal && second instanceof Literal) { - type = TypeManager.getByClass( - ClassUtils.getCommonSuperclass(first.getReturnType(), second.getReturnType()) - ); - } else if (first instanceof Variable && second instanceof Variable) { - variablePresent = true; - type = TypeManager.getByClassExact(Object.class); - } else { - // If the values are Literals, we want to use that to convert them as much as possible. - if (first instanceof Literal) { - var firstConverted = first.convertExpression(second.getReturnType()); - if (firstConverted.isEmpty()) - return false; - first = firstConverted.get(); - } else if (second instanceof Literal) { - var secondConverted = second.convertExpression(first.getReturnType()); - if (secondConverted.isEmpty()) - return false; - second = secondConverted.get(); - } + if (first instanceof Variable || second instanceof Variable) + return true; - // If one value is a Variable, we do not know its type at parse-time. - if (first instanceof Variable) { - variablePresent = true; - first = first.convertExpression(second.getReturnType()).orElseThrow(); - type = TypeManager.getByClass(second.getReturnType()); - } else if (second instanceof Variable) { - variablePresent = true; - second = second.convertExpression(first.getReturnType()).orElseThrow(); - type = TypeManager.getByClass(first.getReturnType()); - } else { - type = TypeManager.getByClass( - ClassUtils.getCommonSuperclass(first.getReturnType(), second.getReturnType()) - ); - } - } - - assert type.isPresent(); - if (!variablePresent && type.get().getArithmetic().isEmpty()) { - var firstType = TypeManager.getByClass(first.getReturnType()); - var secondType = TypeManager.getByClass(second.getReturnType()); - assert firstType.isPresent() && secondType.isPresent(); + arithmetic = TypeManager.getByClass(ClassUtils.getCommonSuperclass(first.getReturnType(), second.getReturnType())).flatMap(Type::getArithmetic).orElse(null); + if (arithmetic == null) { + SkriptLogger logger = parseContext.getLogger(); parseContext.getLogger().error( - "Cannot compare these two values" - + " (types '" - + firstType.get().getBaseName() - + "' and '" - + secondType.get().getBaseName() - + "' are inconvertible)", + "Cannot get the difference between " + + first.toString(TriggerContext.DUMMY, logger.isDebug()) + + " and " + + second.toString(TriggerContext.DUMMY, logger.isDebug()), ErrorType.SEMANTIC_ERROR ); return false; } - if (!variablePresent) { - assert type.get().getArithmetic().isPresent(); - arithmetic = type.get().getArithmetic().get(); - } return true; } @@ -113,17 +65,15 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex public Object[] getValues(TriggerContext ctx) { return DoubleOptional.ofOptional(first.getSingle(ctx), second.getSingle(ctx)) .mapToOptional((f, s) -> { - // The arithmetic field isn't initialized here. - if (variablePresent) { - assert f.getClass() == s.getClass(); - var type = TypeManager.getByClass(f.getClass()).orElseThrow(); - var variableMath = type.getArithmetic(); - if (variableMath.isEmpty()) + // If variables are used, the arithmetic field is not initialised. + if (arithmetic == null) { + arithmetic = TypeManager.getByClass(ClassUtils.getCommonSuperclass(f.getClass(), s.getClass())) + .flatMap(Type::getArithmetic) + .orElse(null); + // If it's still null, then no difference can be found sadly... + if (arithmetic == null) return null; - arithmetic = variableMath.get(); } - - assert arithmetic != null; return new Object[] {arithmetic.difference(f, s)}; }) .orElse(new Object[0]); diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java index 9628ac94..c2523029 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java @@ -3,19 +3,19 @@ import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.Variable; import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; +import io.github.syst3ms.skriptparser.types.attributes.Range; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; import io.github.syst3ms.skriptparser.types.comparisons.Relation; -import io.github.syst3ms.skriptparser.types.ranges.RangeInfo; -import io.github.syst3ms.skriptparser.types.ranges.Ranges; import io.github.syst3ms.skriptparser.util.ClassUtils; import io.github.syst3ms.skriptparser.util.CollectionUtils; import io.github.syst3ms.skriptparser.util.DoubleOptional; -import java.util.function.BiFunction; - /** * Returns a range of values between two endpoints. Types supported by default are integers and characters (length 1 strings). * @@ -35,13 +35,16 @@ public class ExprRange implements Expression { } private Expression from, to; - private RangeInfo range; + private Range range; @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { from = expressions[0]; to = expressions[1]; - range = Ranges.getRange(ClassUtils.getCommonSuperclass(from.getReturnType(), to.getReturnType())).orElse(null); + if (from instanceof Variable || to instanceof Variable) + return true; + + range = TypeManager.getByClass(ClassUtils.getCommonSuperclass(from.getReturnType(), to.getReturnType())).flatMap(Type::getRange).orElse(null); if (range == null) { SkriptLogger logger = parseContext.getLogger(); logger.error( @@ -60,13 +63,22 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex public Object[] getValues(TriggerContext ctx) { return DoubleOptional.ofOptional(from.getSingle(ctx), to.getSingle(ctx)) .mapToOptional((f, t) -> { + if (range == null) { + range = TypeManager.getByClass(ClassUtils.getCommonSuperclass(f.getClass(), t.getClass())) + .flatMap(Type::getRange) + .orElse(null); + // If it's still null, then no range can be found sadly... + if (range == null) + return null; + } + // This is safe... right? if (Comparators.compare(f, t) == Relation.GREATER) { return CollectionUtils.reverseArray( - (Object[]) ((BiFunction) this.range.getFunction()).apply(t, f) + ((Range) this.range).apply(t, f) ); } else { - return (Object[]) ((BiFunction) this.range.getFunction()).apply(f, t); + return ((Range) this.range).apply(f, t); } }) .orElse(new Object[0]); @@ -74,7 +86,9 @@ public Object[] getValues(TriggerContext ctx) { @Override public Class getReturnType() { - return range.getTo(); + return range != null + ? range.getRelativeType() + : ClassUtils.getCommonSuperclass(from.getReturnType(), to.getReturnType()); } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java b/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java index 93e16a38..a3c12985 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java @@ -4,7 +4,7 @@ import io.github.syst3ms.skriptparser.parsing.SkriptRuntimeException; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; -import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.ChangeMode; import io.github.syst3ms.skriptparser.types.changers.Changer; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyConditional.java b/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyConditional.java index 0167cc47..1d94a964 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyConditional.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyConditional.java @@ -16,28 +16,27 @@ *
  • {@code something has something}
  • * * The plural and negated forms are also supported. - * - * The gains of using this class: + * The advantages of using this class: *
      - *
    • There is a useful {@link #toString(TriggerContext, boolean, String) toString()} - * method and it works well with the plural and negated forms. + *
    • There is a useful {@link #toString(TriggerContext, boolean, String)} method + * and it works well with the plural and negated forms. In a + * lot of cases, you won't even need to override it. * It is implemented by default.
    • *
    • Registration is very straightforward.
    • - *
    • The performer expression is automatically checked for nullity.
    • + *
    • The owner expression is automatically checked for nullity.
    • *
    - * @param

    the type of the performer in this condition + * @param the type of the owner in this condition * @author Mwexim */ -public abstract class PropertyConditional

    extends ConditionalExpression { - public static final String PROPERTY_IDENTIFIER = "property"; +public abstract class PropertyConditional extends ConditionalExpression { public static final String CONDITIONAL_TYPE_IDENTIFIER = "conditionalType"; - private Expression

    performer; + private Expression owner; /** * This default {@code init()} implementation automatically properly sets the performer in this condition, - * which can be accessed using {@link #getPerformer()}. If this implementation is overridden for one reason - * or another, it must call {@link #setPerformer(Expression)} properly. + * which can be accessed using {@link #getOwner()}. If this implementation is overridden for one reason + * or another, it must call {@link #setOwner(Expression)} properly. * @param expressions an array of expressions representing all the expressions that are being passed * to this syntax element. * @param matchedPattern the index of the pattern that was successfully matched. It corresponds to the order of @@ -49,36 +48,44 @@ public abstract class PropertyConditional

    extends ConditionalExpression { @SuppressWarnings("unchecked") @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { - setPerformer((Expression

    ) expressions[0]); + setOwner((Expression) expressions[0]); setNegated(matchedPattern == 1); return true; } @Override public boolean check(TriggerContext ctx) { - return getPerformer().check(ctx, this::check, isNegated()); + return getOwner().check(ctx, this::check, isNegated()); } /** - * Tests this condition for each individual performer. Negated conditions are taken care of + * Tests this condition for each individual owner. Negated conditions are taken care of * automatically, so one must not account for it in here. - * @param performer the performer - * @return whether the conditions is true for this performer + * @param owner the owner + * @return whether the conditions are true for this owner */ - public boolean check(P performer) { + public boolean check(O owner) { throw new UnsupportedOperationException("Override #check(P) if you are planning to use the default functionality."); } + /** + * This is the string representation of this property conditional. If this method is + * not overridden, it will default to the property name that was registered in the pattern. + * @return the property name + */ + protected String getPropertyName() { + return SyntaxManager.getExpressionExact(this) + .orElseThrow(() -> new SkriptParserException("Unregistered property class: " + getClass().getName())) + .getData(PropertyExpression.PROPERTY_NAME_IDENTIFIER, String.class); + } + @Override public String toString(TriggerContext ctx, boolean debug) { - var property = SyntaxManager.getExpressionExact(this) - .orElseThrow(() -> new SkriptParserException("Unregistered property class: " + getClass().getName())) - .getData(PROPERTY_IDENTIFIER, String.class); - return toString(ctx, debug, property); + return toString(ctx, debug, getPropertyName()); } protected String toString(TriggerContext ctx, boolean debug, String property) { - var performer = getPerformer(); + var performer = getOwner(); var conditionalType = SyntaxManager.getExpressionExact(this) .orElseThrow(() -> new SkriptParserException("Unregistered property class: " + getClass().getName())) .getData(CONDITIONAL_TYPE_IDENTIFIER, ConditionalType.class); @@ -98,16 +105,16 @@ protected String toString(TriggerContext ctx, boolean debug, String property) { } } - public Expression

    getPerformer() { - return performer; + public Expression getOwner() { + return owner; } - public void setPerformer(Expression

    performer) { - this.performer = performer; + public void setOwner(Expression owner) { + this.owner = owner; } - public static String[] composePatterns(String performer, ConditionalType conditionalType, String property) { - var type = performer.startsWith("*") ? performer.substring(1) : "%" + performer + "%"; + public static String[] composePatterns(String owner, ConditionalType conditionalType, String property) { + var type = owner.startsWith("*") ? owner.substring(1) : "%" + owner + "%"; switch (conditionalType) { case BE: return new String[] { diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java index 1c0fd3b2..afabc126 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/properties/PropertyExpression.java @@ -11,13 +11,13 @@ /** * A base class for expressions that contain general properties. - * In English, one can express properties in many different ways: + * In English, one can express properties in multiple ways: *

      *
    • Mwexim's book
    • *
    • the book of Mwexim
    • *
    - * This utility class acknowledges how useful and common such 'property expressions' are, and provides a simple way - * to implement them. + * This utility class acknowledges how useful and common such 'property expressions' are, and provides + * a simple way to implement them. * The class also provides default implementations of {@link #init(Expression[], int, ParseContext)} * and {@link #getValues(TriggerContext)}. Their default functionality is specified below. * @param The returned type of this expression. @@ -25,7 +25,7 @@ * @author Mwexim */ public abstract class PropertyExpression implements Expression { - public static final String PROPERTY_IDENTIFIER = "property"; + public static final String PROPERTY_NAME_IDENTIFIER = "propertyName"; private Expression owner; private boolean genitive; @@ -62,7 +62,7 @@ public T[] getValues(TriggerContext ctx) { } /** - * For each owner, this method will be ran individually to convert it to this particular property. + * For each owner, this method will be executed individually to convert it to this particular property. * @param owner the owner * @return the property value */ @@ -71,6 +71,17 @@ public T getProperty(O owner) { throw new UnsupportedOperationException("Override #getProperty(O) if you are planning to use the default functionality."); } + /** + * This is the string representation of this property expression. If this method is + * not overridden, it will default to the property name that was registered in the pattern.yy + * @return the property name + */ + protected String getPropertyName() { + return SyntaxManager.getExpressionExact(this) + .orElseThrow(() -> new SkriptParserException("Unregistered property class: " + getClass().getName())) + .getData(PROPERTY_NAME_IDENTIFIER, String.class); + } + @Override public boolean isSingle() { return owner.isSingle(); @@ -78,13 +89,10 @@ public boolean isSingle() { @Override public String toString(TriggerContext ctx, boolean debug) { - var property = SyntaxManager.getExpressionExact(this) - .orElseThrow(() -> new SkriptParserException("Unregistered property class: " + getClass().getName())) - .getData(PROPERTY_IDENTIFIER, String.class); - return toString(ctx, debug, property); + return toString(ctx, debug, getPropertyName()); } - protected String toString(TriggerContext ctx, boolean debug, String property) { + public String toString(TriggerContext ctx, boolean debug, String property) { return genitive ? owner.toString(ctx, debug) + "'s " + property : property + " of " + owner.toString(ctx, debug); diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index cd434eeb..cf3579cf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -3,11 +3,11 @@ import io.github.syst3ms.skriptparser.Parser; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; -import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Range; import io.github.syst3ms.skriptparser.types.comparisons.Comparator; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; import io.github.syst3ms.skriptparser.types.comparisons.Relation; -import io.github.syst3ms.skriptparser.types.ranges.Ranges; import io.github.syst3ms.skriptparser.util.SkriptDate; import io.github.syst3ms.skriptparser.util.Time; import io.github.syst3ms.skriptparser.util.TimeUtils; @@ -36,11 +36,7 @@ public static void register() { /* * Classes */ - registration.addType( - Object.class, - "object", - "object@s" - ); + registration.addType(Object.class, "object", "object@s"); registration.newType(Number.class,"number", "number@s") .literalParser(s -> { @@ -139,13 +135,47 @@ public Class getRelativeType() { return BigInteger.class; } }) + .range(new Range() { + @Override + public BigInteger[] apply(BigInteger from, BigInteger to) { + if (from.compareTo(to) > 0) { + return new BigInteger[0]; + } else { + List elements = new ArrayList<>(); + BigInteger current = from; + do { + elements.add(current); + current = current.add(BigInteger.ONE); + } while (current.compareTo(to) <= 0); + return elements.toArray(new BigInteger[0]); + } + } + + @Override + public Class getRelativeType() { + return BigInteger.class; + } + }) .register(); - registration.addType( - String.class, - "string", - "string@s" - ); + registration.newType(String.class, "string", "string@s") + .range(new Range() { + @Override + public String[] apply(String from, String to) { + if (from.length() != 1 || to.length() != 1) + return new String[0]; + char leftChar = from.charAt(0), rightChar = to.charAt(0); + return IntStream.range(leftChar, rightChar + 1) + .mapToObj(i -> Character.toString((char) i)) + .toArray(String[]::new); + } + + @Override + public Class getRelativeType() { + return String.class; + } + }) + .register(); registration.newType(Boolean.class, "boolean", "boolean@s") .literalParser(s -> { @@ -279,41 +309,6 @@ public Relation apply(Duration duration, Duration duration2) { } ); - /* - * Ranges - */ - Ranges.registerRange( - BigInteger.class, - BigInteger.class, - (l, r) -> { - if (l.compareTo(r) >= 0) { - return new BigInteger[0]; - } else { - List elements = new ArrayList<>(); - BigInteger current = l; - do { - elements.add(current); - current = current.add(BigInteger.ONE); - } while (current.compareTo(r) <= 0); - return elements.toArray(new BigInteger[0]); - } - } - ); - - // Actually a character range - Ranges.registerRange( - String.class, - String.class, - (l, r) -> { - if (l.length() != 1 || r.length() != 1) - return new String[0]; - char leftChar = l.charAt(0), rightChar = r.charAt(0); - return IntStream.range(leftChar, rightChar + 1) - .mapToObj(i -> Character.toString((char) i)) - .toArray(String[]::new); - } - ); - /* * Converters */ diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java index 81b11b12..993006bf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -33,7 +33,8 @@ import io.github.syst3ms.skriptparser.registration.tags.TagManager; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; -import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Range; import io.github.syst3ms.skriptparser.types.changers.Changer; import io.github.syst3ms.skriptparser.types.conversions.ConverterInfo; import io.github.syst3ms.skriptparser.types.conversions.Converters; @@ -132,7 +133,7 @@ public List> getTags() { } /** - * @return the addon handling this registration (may be Skript itself) + * @return the addon handling this registration (possibly Skript itself) */ public SkriptAddon getRegisterer() { return registerer; @@ -190,8 +191,8 @@ public , T> void addExpression(Class c, Class retu * @return an {@link ExpressionRegistrar} to continue the registration process */ public , T> ExpressionRegistrar newPropertyExpression(Class c, Class returnType, String owner, String property) { - return (ExpressionRegistrar) newExpression(c, returnType, false, PropertyExpression.composePatterns(owner, property)) - .addData(PropertyExpression.PROPERTY_IDENTIFIER, property); + return newExpression(c, returnType, false, PropertyExpression.composePatterns(owner, property)) + .setPropertyName(property); } /** @@ -232,8 +233,8 @@ public , T> void addExpression(Class c, Class retu */ public > ExpressionRegistrar newPropertyConditional(Class c, String performer, ConditionalType conditionalType, String property) { return (ExpressionRegistrar) newExpression(c, Boolean.class, true, PropertyConditional.composePatterns(performer, conditionalType, property)) - .addData(PropertyConditional.CONDITIONAL_TYPE_IDENTIFIER, conditionalType) - .addData(PropertyConditional.PROPERTY_IDENTIFIER, property); + .setPropertyName(property) + .addData(PropertyConditional.CONDITIONAL_TYPE_IDENTIFIER, conditionalType); } /** @@ -388,7 +389,7 @@ public void addEvent(Class c, Class the TriggerContext class @@ -403,7 +404,7 @@ public ContextValueRegistrar newContextValue * Registers a {@link ContextValue} * @param context the TriggerContext class * @param returnType the returned type of this context value - * @param isSingle whether or not the return value is single + * @param isSingle whether the return value is single * @param pattern the pattern * @param function the function that needs to be applied in order to get the context value * @param the TriggerContext class @@ -561,10 +562,7 @@ public class TypeRegistrar implements Registrar { private Function toStringFunction = o -> Objects.toString(o, TypeManager.NULL_REPRESENTATION); @Nullable private Function literalParser; - @Nullable - private Changer defaultChanger; - @Nullable - private Arithmetic arithmetic; + private final List> attributes = new ArrayList<>(); public TypeRegistrar(Class c, String baseName, String pattern) { this.c = c; @@ -591,11 +589,13 @@ public TypeRegistrar toStringFunction(Function toStringFun } /** - * @param defaultChanger a default {@link Changer} for this type + * Registers an attribute for this type. All built-in attributes have helper functions + * available. Attributes are interfaces that provide additional behavior for this type. + * @param attribute the attribute * @return the registrar */ - public TypeRegistrar defaultChanger(Changer defaultChanger) { - this.defaultChanger = defaultChanger; + public TypeRegistrar attribute(Type.Attribute attribute) { + attributes.add(attribute); return this; } @@ -604,7 +604,25 @@ public TypeRegistrar defaultChanger(Changer defaultChanger) { * @return the registrar */ public TypeRegistrar arithmetic(Arithmetic arithmetic) { - this.arithmetic = arithmetic; + attributes.add(arithmetic); + return this; + } + + /** + * @param defaultChanger a default {@link Changer} for this type + * @return the registrar + */ + public TypeRegistrar defaultChanger(Changer defaultChanger) { + attributes.add(defaultChanger); + return this; + } + + /** + * @param range a {@link Range} for this type + * @return the registrar + */ + public TypeRegistrar range(Range range) { + attributes.add(range); return this; } @@ -614,7 +632,7 @@ public TypeRegistrar arithmetic(Arithmetic arithmetic) { @Override public void register() { newTypes = true; - types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic)); + types.add(new Type<>(c, baseName, pattern, literalParser, toStringFunction, attributes)); } } @@ -682,6 +700,18 @@ public class ExpressionRegistrar, T> extends S typeCheck(); } + /** + * Set the property name of this property expression. Only useful for property expressions. + * @param property the name + * @return the registrar + * @see PropertyExpression + * @see PropertyConditional + */ + public ExpressionRegistrar setPropertyName(String property) { + addData(PropertyExpression.PROPERTY_NAME_IDENTIFIER, property); + return this; + } + /** * Adds this expression to the list of currently registered syntaxes */ diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java index 0b85ab36..3fb3c83c 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecLoop.java @@ -4,7 +4,7 @@ import io.github.syst3ms.skriptparser.file.FileSection; import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.Literal; -import io.github.syst3ms.skriptparser.lang.SimpleLiteral; +import io.github.syst3ms.skriptparser.lang.SimpleExpression; import io.github.syst3ms.skriptparser.lang.Statement; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.lang.Variable; @@ -15,7 +15,9 @@ import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.parsing.ParserState; -import io.github.syst3ms.skriptparser.types.ranges.Ranges; +import io.github.syst3ms.skriptparser.parsing.SkriptParserException; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; import org.jetbrains.annotations.Nullable; import java.math.BigInteger; @@ -43,9 +45,8 @@ public class SecLoop extends ArgumentSection implements Continuable, SelfReferen ); } - @Nullable private Expression expression; - private Expression times; + private Expression times; // For the toString(TriggerContext, boolean) method. private boolean isNumericLoop; @Nullable @@ -80,6 +81,14 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex return false; } } + expression = new SimpleExpression<>( + BigInteger.class, + false, + ctx -> TypeManager.getByClassExact(BigInteger.class) + .flatMap(Type::getRange) + .orElseThrow(() -> new SkriptParserException("Unregistered type range: BigInteger")) + .apply(BigInteger.ONE, times.getSingle(ctx).map(BigInteger.class::cast).orElse(BigInteger.ZERO)) + ); } else { expression = expressions[0]; if (expression.isSingle()) { @@ -96,16 +105,8 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex @Override public Optional walk(TriggerContext ctx) { - if (isNumericLoop && expression == null) { - // We just set the looped expression to a range from 1 to the amount of times. - // This allows the usage of 'loop-number' to get the current iteration - expression = rangeOf(ctx, times); - } - - if (iterator == null) { - assert expression != null; + if (iterator == null) iterator = expression instanceof Variable ? ((Variable) expression).variablesIterator(ctx) : expression.iterator(ctx); - } if (iterator.hasNext()) { setArguments(iterator.next()); @@ -148,26 +149,6 @@ public String toString(TriggerContext ctx, boolean debug) { * @return the expression whose values this loop is iterating over */ public Expression getLoopedExpression() { - if (isNumericLoop && expression == null) { - expression = rangeOf(TriggerContext.DUMMY, times); - } return expression; } - - /** - * Returns a SimpleLiteral containing the numbers 1 up until a certain amount, specified - * by the given expression. - * @param ctx the context - * @param size the expression - * @return the SimpleLiteral - */ - private static Expression rangeOf(TriggerContext ctx, Expression size) { - BigInteger[] range = (BigInteger[]) size.getSingle(ctx) - .filter(t -> t.compareTo(BigInteger.ZERO) > 0) - .map(t -> Ranges.getRange(BigInteger.class).orElseThrow() - .getFunction() - .apply(BigInteger.ONE, t)) // Upper bound is inclusive - .orElse(new BigInteger[0]); - return new SimpleLiteral<>(BigInteger.class, range); - } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java index 5cfc651f..a8cc9830 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java @@ -1,11 +1,13 @@ package io.github.syst3ms.skriptparser.types; -import io.github.syst3ms.skriptparser.types.changers.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Arithmetic; +import io.github.syst3ms.skriptparser.types.attributes.Range; import io.github.syst3ms.skriptparser.types.changers.Changer; import io.github.syst3ms.skriptparser.util.StringUtils; import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Function; @@ -21,26 +23,7 @@ public class Type { private final Function toStringFunction; @Nullable private final Function literalParser; - @Nullable - private final Changer defaultChanger; - @Nullable - private final Arithmetic arithmetic; - - /** - * Constructs a new Type. - * - * @param typeClass the class this type represents - * @param baseName the basic name to represent this type with. It should be more or less a lowercase version of the Java class. - * @param pattern the pattern for plural forms. It's written in Skript aliases plural format. Examples : - *
      - *
    • {@code fish} -> {@literal fish} (invariant)
    • - *
    • {@code dog¦s} -> {@literal dog} and {@literal dogs}
    • - *
    • {@code part¦y¦ies} -> {@literal party} and {@literal parties} (irregular plural)
    • - *
    - */ - public Type(Class typeClass, String baseName, String pattern) { - this(typeClass, baseName, pattern, null); - } + private final List> attributes; /** * Constructs a new Type. @@ -50,67 +33,27 @@ public Type(Class typeClass, String baseName, String pattern) { * @param pattern the pattern for plural forms. It's written in Skript aliases plural format. Examples : *
      *
    • {@code fish} -> {@literal fish} (invariant)
    • - *
    • {@code dog¦s} -> {@literal dog} and {@literal dogs}
    • - *
    • {@code part¦y¦ies} -> {@literal party} and {@literal parties} (irregular plural)
    • + *
    • {@code dog@s} -> {@literal dog} and {@literal dogs}
    • + *
    • {@code part@y@ies} -> {@literal party} and {@literal parties} (irregular plural)
    • *
    * @param literalParser the function that would parse literals for the given type. If the parser throws an exception on parsing, it will be * caught and the type will be ignored. - */ - public Type(Class typeClass, - String baseName, - String pattern, - @Nullable Function literalParser) { - this(typeClass, baseName, pattern, literalParser, Objects::toString); - } - - /** - * Constructs a new Type. - * - * @param typeClass the class this type represents - * @param baseName the basic name to represent this type with. It should be more or less a lowercase version of the Java class. - * @param pattern the pattern for plural forms. It's written in Skript aliases plural format. Examples : - *
      - *
    • {@code fish} -> {@literal fish} (invariant)
    • - *
    • {@code dog¦s} -> {@literal dog} and {@literal dogs}
    • - *
    • {@code part¦y¦ies} -> {@literal party} and {@literal parties} (irregular plural)
    • - *
    - * @param literalParser the function that would parse literals for the given type. If the parser throws an exception on parsing, it will be - * caught and the type will be ignored. - * @param toStringFunction the functions that converts an object of the type {@link T} to a {@link String}.Defaults to {@link Objects#toString} for + * @param toStringFunction the functions that converts an object of the type {@link T} to a {@link String}. Defaults to {@link Objects#toString} for * other constructors. */ - public Type(Class typeClass, - String baseName, - String pattern, - @Nullable Function literalParser, - Function toStringFunction) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, null); - } - - public Type(Class typeClass, - String baseName, - String pattern, - @Nullable Function literalParser, - Function toStringFunction, - @Nullable Changer defaultChanger) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null); - } - @SuppressWarnings("unchecked") public Type(Class typeClass, String baseName, String pattern, @Nullable Function literalParser, Function toStringFunction, - @Nullable Changer defaultChanger, - @Nullable Arithmetic arithmetic) { + List> attributes) { this.typeClass = typeClass; this.baseName = baseName; this.literalParser = literalParser; this.toStringFunction = (Function) toStringFunction; this.pluralForms = StringUtils.getForms(pattern.strip()); - this.defaultChanger = defaultChanger; - this.arithmetic = arithmetic; + this.attributes = attributes; } public Class getTypeClass() { @@ -133,12 +76,26 @@ public Function getToStringFunction() { return Optional.ofNullable(literalParser); } - public Optional> getDefaultChanger() { - return Optional.ofNullable(defaultChanger); + @SuppressWarnings("unchecked") + public > Optional getAttribute(Class attributeType) { + return (Optional) attributes.stream() + .filter(att -> attributeType.isAssignableFrom(att.getClass())) + .findFirst(); } + @SuppressWarnings("unchecked") public Optional> getArithmetic() { - return Optional.ofNullable(arithmetic); + return (Optional>) getAttribute(Arithmetic.class); + } + + @SuppressWarnings("unchecked") + public Optional> getDefaultChanger() { + return (Optional>) getAttribute(Changer.class); + } + + @SuppressWarnings("unchecked") + public Optional> getRange() { + return (Optional>) getAttribute(Range.class); } /** @@ -172,4 +129,6 @@ public boolean equals(Object obj) { public int hashCode() { return Arrays.hashCode(pluralForms); } + + public interface Attribute {} } \ No newline at end of file diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/changers/Arithmetic.java b/src/main/java/io/github/syst3ms/skriptparser/types/attributes/Arithmetic.java similarity index 63% rename from src/main/java/io/github/syst3ms/skriptparser/types/changers/Arithmetic.java rename to src/main/java/io/github/syst3ms/skriptparser/types/attributes/Arithmetic.java index df14d1cd..87bb96da 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/changers/Arithmetic.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/attributes/Arithmetic.java @@ -1,11 +1,13 @@ -package io.github.syst3ms.skriptparser.types.changers; +package io.github.syst3ms.skriptparser.types.attributes; + +import io.github.syst3ms.skriptparser.types.Type; /** * An interface describing arithmetic operations between two types * @param the first type * @param the second type */ -public interface Arithmetic { +public interface Arithmetic extends Type.Attribute { R difference(A first, A second); diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/attributes/Range.java b/src/main/java/io/github/syst3ms/skriptparser/types/attributes/Range.java new file mode 100644 index 00000000..32b35bc1 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/types/attributes/Range.java @@ -0,0 +1,22 @@ +package io.github.syst3ms.skriptparser.types.attributes; + +import io.github.syst3ms.skriptparser.types.Type; + +/** + * Information about a range function + * + * @param the type of the two endpoints + * @param the type of the range that is returned + */ +public interface Range extends Type.Attribute { + /** + * Calculates the range of values between two endpoints. If the lower bound is + * smaller than the upper bound, an empty array must be returned by convention. + * @param from the lower bound + * @param to the upper bound + * @return the range + */ + R[] apply(B from, B to); + + Class getRelativeType(); +} diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/attributes/package-info.java b/src/main/java/io/github/syst3ms/skriptparser/types/attributes/package-info.java new file mode 100644 index 00000000..bcca093a --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/types/attributes/package-info.java @@ -0,0 +1,4 @@ +@ParametersAreNonnullByDefault +package io.github.syst3ms.skriptparser.types.attributes; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/changers/Changer.java b/src/main/java/io/github/syst3ms/skriptparser/types/changers/Changer.java index 966e1226..6e56f91f 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/changers/Changer.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/changers/Changer.java @@ -2,6 +2,7 @@ import io.github.syst3ms.skriptparser.lang.Expression; import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.types.Type; /** * An interface for anything that can be changed @@ -9,7 +10,7 @@ * @see Expression#change(TriggerContext, ChangeMode, Object[]) * @see Expression#acceptsChange(ChangeMode) */ -public interface Changer { +public interface Changer extends Type.Attribute { /** * @param mode the given mode * @return the classes of the objects that the implementing object can be changed to diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/ranges/RangeInfo.java b/src/main/java/io/github/syst3ms/skriptparser/types/ranges/RangeInfo.java deleted file mode 100644 index 7d20c017..00000000 --- a/src/main/java/io/github/syst3ms/skriptparser/types/ranges/RangeInfo.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.github.syst3ms.skriptparser.types.ranges; - -import java.util.function.BiFunction; - -/** - * Information about a range function - * @param the type of the two endpoints - * @param the type of the range that is returned - */ -public class RangeInfo { - private final Class bound; - private final Class to; - private final BiFunction function; - - public RangeInfo(Class bound, Class to, BiFunction function) { - this.bound = bound; - this.to = to; - this.function = function; - } - - public Class getBound() { - return bound; - } - - public Class getTo() { - return to; - } - - public BiFunction getFunction() { - return function; - } -} diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java b/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java deleted file mode 100644 index 12438e09..00000000 --- a/src/main/java/io/github/syst3ms/skriptparser/types/ranges/Ranges.java +++ /dev/null @@ -1,30 +0,0 @@ -package io.github.syst3ms.skriptparser.types.ranges; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; - -/** - * A class handling registration of ranges - */ -public class Ranges { - private static final Map, RangeInfo> rangeMap = new HashMap<>(); - - public static void registerRange(Class bound, Class to, BiFunction function) { - rangeMap.put( - bound, - new RangeInfo<>(bound, to, function) - ); - } - - @SuppressWarnings("unchecked") - public static Optional> getRange(Class bound) { - for (var c : rangeMap.keySet()) { - if (c == bound || c.isAssignableFrom(bound)) { - return Optional.ofNullable((RangeInfo) rangeMap.get(c)); - } - } - return Optional.empty(); - } -} diff --git a/src/test/resources/expressions/ExprDifference.txt b/src/test/resources/expressions/ExprDifference.txt index fb6397ce..108fd0ae 100644 --- a/src/test/resources/expressions/ExprDifference.txt +++ b/src/test/resources/expressions/ExprDifference.txt @@ -1,6 +1,8 @@ # Author(s): # - Mwexim -# Date: 2020/12/20 +# Date: +# - 2020/12/20 +# - 2023/01/26 (rewrite) test: assert difference between -3 and 5 = 8 with "Numerical difference should be 8: %difference between -3 and 5 = 8%" @@ -10,10 +12,22 @@ test: # Variable difference set {list::1} to 3.4 set {list::2} to 8.9 - assert difference between {list::1} and {list::2} is 5.5 with "Number difference between variables should be 5.5: %difference between {list::1} and {list::2}%" - assert difference between {list::1} and 5.0 is 1.6 with "Number difference with one variable should be 1.6: %difference between {list::1} and 5%" + set {difference} to difference between {list::1} and {list::2} + assert {difference} is 5.5 with "Number difference between variables should be 5.5: %difference between {list::1} and {list::2}%" + assert difference between {list::1} and 5.0 is 1.6 with "Number difference wRith one variable should be 1.6: %difference between {list::1} and 5%" # Non-comparable types throws difference between now and 5 with "Date and number should not be comparable" # The following line does not throw at parse-time, because the type of the variable is unknown - assert difference between {list::1} and now is not set with "Date and number with one variable should not be comparable: %difference between {list::1} and now%" \ No newline at end of file + assert difference between {list::1} and now is not set with "Date and number with one variable should not be comparable: %difference between {list::1} and now%" + + # Before the rewrite, values would inconsistently be cast to each other, + # resulting in weird results when comparing numbers and integers. + set {var} to 5 + assert difference between 1.5 and {var} = 3.5 with "Difference with integer variable as second parameter should be 3.5: %difference between 1.5 and {var}%" + assert difference between {var} and 1.5 = 3.5 with "Difference with integer variable as first parameter should be 3.5: %difference between {var} and 1.5%" + set {var} to 1.5 + assert difference between 6 and {var} = 4.5 with "Difference with number variable as second parameter should be 4.5: %difference between 6 and {var}%" + assert difference between {var} and 6 = 4.5 with "Difference with number variable as first parameter should be 4.5: %difference between {var} and 6%" + + assert difference between {list::1} and now is not set with "Difference between number variable and date should not return anything." \ No newline at end of file diff --git a/src/test/resources/expressions/ExprRange.txt b/src/test/resources/expressions/ExprRange.txt index 831a7caa..599e3dd2 100644 --- a/src/test/resources/expressions/ExprRange.txt +++ b/src/test/resources/expressions/ExprRange.txt @@ -1,6 +1,8 @@ # Author(s): # - Mwexim -# Date: 2020/12/22 +# Date: +# - 2020/12/22 +# - 2023/01/26 (variable support) test: # Number @@ -10,3 +12,8 @@ test: # Character set {list::*} to range from "d" to "j" assert {list::*} = "d", "e", "f", "g", "h", "i" and "j" + + # Variable range + set {var} to 14 + set {list::*} to range from {var} to 9 + assert {list::*} = 14, 13, 12, 11, 10 and 9 From 37233d318de1f3a0fc66e95f8e2174a0786bb355 Mon Sep 17 00:00:00 2001 From: Mwexim Date: Sun, 23 Jul 2023 14:14:05 +0200 Subject: [PATCH 2/2] IntersectionType: add intersection type utility and apply to ExprDifference and ExprRange --- .../expressions/ExprDifference.java | 33 ++++--- .../skriptparser/expressions/ExprRange.java | 41 ++++---- .../registration/DefaultRegistration.java | 1 + .../skriptparser/types/TypeManager.java | 97 ++++++++++++++++++- .../types/conversions/Converters.java | 4 +- .../syst3ms/skriptparser/util/ClassUtils.java | 2 +- .../resources/expressions/ExprDifference.txt | 8 +- src/test/resources/expressions/ExprRange.txt | 4 + 8 files changed, 151 insertions(+), 39 deletions(-) diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java index 623135d7..31c88215 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprDifference.java @@ -7,10 +7,8 @@ import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParseContext; -import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.attributes.Arithmetic; -import io.github.syst3ms.skriptparser.util.ClassUtils; import io.github.syst3ms.skriptparser.util.DoubleOptional; import org.jetbrains.annotations.Nullable; @@ -36,7 +34,7 @@ public class ExprDifference implements Expression { private Expression first, second; @Nullable - private Arithmetic arithmetic; + private TypeManager.IntersectionType intersectionType; @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { @@ -45,8 +43,8 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex if (first instanceof Variable || second instanceof Variable) return true; - arithmetic = TypeManager.getByClass(ClassUtils.getCommonSuperclass(first.getReturnType(), second.getReturnType())).flatMap(Type::getArithmetic).orElse(null); - if (arithmetic == null) { + var info = TypeManager.getByIntersection(Arithmetic.class, first.getReturnType(), second.getReturnType()); + if (info.isEmpty()) { SkriptLogger logger = parseContext.getLogger(); parseContext.getLogger().error( "Cannot get the difference between " @@ -57,6 +55,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex ); return false; } + intersectionType = info.get(); return true; } @@ -66,24 +65,30 @@ public Object[] getValues(TriggerContext ctx) { return DoubleOptional.ofOptional(first.getSingle(ctx), second.getSingle(ctx)) .mapToOptional((f, s) -> { // If variables are used, the arithmetic field is not initialised. - if (arithmetic == null) { - arithmetic = TypeManager.getByClass(ClassUtils.getCommonSuperclass(f.getClass(), s.getClass())) - .flatMap(Type::getArithmetic) - .orElse(null); + if (intersectionType == null) { + var info = TypeManager.getByIntersection(Arithmetic.class, f.getClass(), s.getClass()); // If it's still null, then no difference can be found sadly... - if (arithmetic == null) + if (info.isEmpty()) return null; + intersectionType = info.get(); } - return new Object[] {arithmetic.difference(f, s)}; + + // Convert the expressions to the intersection type + var firstConverted = intersectionType.convert(f); + var secondConverted = intersectionType.convert(s); + if (firstConverted.isEmpty() || secondConverted.isEmpty()) + return null; + Arithmetic arithmetic = intersectionType.getType().getArithmetic().orElseThrow(); + return new Object[] {arithmetic.difference(firstConverted.get(), secondConverted.get())}; }) .orElse(new Object[0]); } @Override public Class getReturnType() { - return arithmetic != null - ? arithmetic.getRelativeType() - : ClassUtils.getCommonSuperclass(false, first.getReturnType(), second.getReturnType()); + return intersectionType != null + ? intersectionType.getType().getArithmetic().orElseThrow().getRelativeType() + : Object.class; } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java index c2523029..5a235f94 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java +++ b/src/main/java/io/github/syst3ms/skriptparser/expressions/ExprRange.java @@ -7,12 +7,10 @@ import io.github.syst3ms.skriptparser.log.ErrorType; import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.ParseContext; -import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.attributes.Range; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; import io.github.syst3ms.skriptparser.types.comparisons.Relation; -import io.github.syst3ms.skriptparser.util.ClassUtils; import io.github.syst3ms.skriptparser.util.CollectionUtils; import io.github.syst3ms.skriptparser.util.DoubleOptional; @@ -20,7 +18,7 @@ * Returns a range of values between two endpoints. Types supported by default are integers and characters (length 1 strings). * * @name Range - * @pattern [the] range from %object% to %object% + * @pattern [the] range from %object% [up|down]([ ]to| until) %object% * @since ALPHA * @author Syst3ms */ @@ -30,12 +28,12 @@ public class ExprRange implements Expression { ExprRange.class, Object.class, false, - "[the] range from %object% to %object%" + "[the] range from %object% [up|down]([ ]to| until) %object%" ); } private Expression from, to; - private Range range; + private TypeManager.IntersectionType intersectionType; @Override public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { @@ -44,8 +42,8 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex if (from instanceof Variable || to instanceof Variable) return true; - range = TypeManager.getByClass(ClassUtils.getCommonSuperclass(from.getReturnType(), to.getReturnType())).flatMap(Type::getRange).orElse(null); - if (range == null) { + var info = TypeManager.getByIntersection(Range.class, from.getReturnType(), to.getReturnType()); + if (info.isEmpty()) { SkriptLogger logger = parseContext.getLogger(); logger.error( "Cannot get a range between " @@ -55,6 +53,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex ErrorType.SEMANTIC_ERROR); return false; } + intersectionType = info.get(); return true; } @@ -63,22 +62,26 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex public Object[] getValues(TriggerContext ctx) { return DoubleOptional.ofOptional(from.getSingle(ctx), to.getSingle(ctx)) .mapToOptional((f, t) -> { - if (range == null) { - range = TypeManager.getByClass(ClassUtils.getCommonSuperclass(f.getClass(), t.getClass())) - .flatMap(Type::getRange) - .orElse(null); + if (intersectionType == null) { + var info = TypeManager.getByIntersection(Range.class, f.getClass(), t.getClass()); // If it's still null, then no range can be found sadly... - if (range == null) + if (info.isEmpty()) return null; + intersectionType = info.get(); } + // Convert the expressions to the intersection type + var fromConverted = intersectionType.convert(f); + var toConverted = intersectionType.convert(t); + if (fromConverted.isEmpty() || toConverted.isEmpty()) + return null; + Range range = intersectionType.getType().getRange().orElseThrow(); + // This is safe... right? if (Comparators.compare(f, t) == Relation.GREATER) { - return CollectionUtils.reverseArray( - ((Range) this.range).apply(t, f) - ); + return CollectionUtils.reverseArray(range.apply(toConverted.get(), fromConverted.get())); } else { - return ((Range) this.range).apply(f, t); + return range.apply(fromConverted.get(), toConverted.get()); } }) .orElse(new Object[0]); @@ -86,9 +89,9 @@ public Object[] getValues(TriggerContext ctx) { @Override public Class getReturnType() { - return range != null - ? range.getRelativeType() - : ClassUtils.getCommonSuperclass(from.getReturnType(), to.getReturnType()); + return intersectionType != null + ? intersectionType.getType().getRange().orElseThrow().getRelativeType() + : Object.class; } @Override diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java index 9002c21a..c1a5863b 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -8,6 +8,7 @@ import io.github.syst3ms.skriptparser.types.comparisons.Comparator; import io.github.syst3ms.skriptparser.types.comparisons.Comparators; import io.github.syst3ms.skriptparser.types.comparisons.Relation; +import io.github.syst3ms.skriptparser.util.DurationUtils; import io.github.syst3ms.skriptparser.util.SkriptDate; import io.github.syst3ms.skriptparser.util.Time; import io.github.syst3ms.skriptparser.util.color.Color; diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java index d5bf10e0..19d8e93a 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/TypeManager.java @@ -1,11 +1,20 @@ package io.github.syst3ms.skriptparser.types; +import io.github.syst3ms.skriptparser.expressions.ExprDifference; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.registration.SkriptRegistration; +import io.github.syst3ms.skriptparser.types.conversions.Converters; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; /** * Manages the registration and usage of {@link Type} @@ -20,15 +29,20 @@ public class TypeManager { * The string equivalent of an empty array */ public static final String EMPTY_REPRESENTATION = ""; + private static final List> types = new ArrayList<>(); private static final Map> nameToType = new HashMap<>(); private static final Map, Type> classToType = new LinkedHashMap<>(); // Ordering is important for stuff like number types + public static List> getTypeList() { + return types; + } + public static Map, Type> getClassToTypeMap() { return classToType; } /** - * Gets a {@link Type} by its exact name (the baseName parameter used in {@link Type#Type(Class, String, String)}) + * Gets a {@link Type} by its exact name (the baseName parameter used in {@link Type(Class, String, String)}) * @param name the name to get the Type from * @return the corresponding Type, or {@literal null} if nothing matched */ @@ -51,7 +65,6 @@ public static Optional> getByName(String name) { return Optional.empty(); } - /** * Gets a {@link Type} from its associated {@link Class}. * @param c the Class to get the Type from @@ -117,8 +130,88 @@ public static Optional> getPatternType(String name) { public static void register(SkriptRegistration reg) { for (var type : reg.getTypes()) { + types.add(type); nameToType.put(type.getBaseName(), type); classToType.put(type.getTypeClass(), type); } } + + /** + * If a certain set of types can all be converted to a single type, then that type is an intersection + * type for that set of types. Given an array of classes, this method determines that intersection type, + * or returns an empty optional if no such type was found. + * @param classes the return types + * @return an {@link IntersectionType} + */ + public static Optional getByIntersection(Class... classes) { + for (var type : types) { + var converters = Arrays.stream(classes) + .map(cls -> (Optional) Converters.getConverter(cls, type.getTypeClass())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + // Check if we didn't lose some converters by the filter + if (converters.size() == classes.length) + return Optional.of(new IntersectionType(type, (List>>) converters)); + } + return Optional.empty(); + } + + /** + * If a certain set of types can all be converted to a single type, then that type is an intersection + * type for that set of types. Given an array of classes, this method determines that intersection type, + * or returns an empty optional if no such type was found. + * In addition to this, this method will only consider types that have a certain attribute defined. + * @param attributeClass the attribute class + * @param classes the return types + * @return an {@link IntersectionType} + * @see ExprDifference#init(Expression[], int, ParseContext) + */ + public static Optional getByIntersection(Class attributeClass, + Class... classes) { + for (var type : types) { + var provided = type.getAttribute(attributeClass); + if (provided.isPresent()) { + var converters = Arrays.stream(classes) + .map(cls -> (Optional) Converters.getConverter(cls, type.getTypeClass())) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + // Check if we didn't lose some converters by the filter + if (converters.size() == classes.length) + return Optional.of(new IntersectionType(type, (List>>) converters)); + } + } + return Optional.empty(); + } + + // TODO better generics? + public static class IntersectionType { + private final Type type; + private final List>> converters; + private int currentIndex = 0; + + public IntersectionType(Type type, List>> converters) { + this.type = type; + this.converters = converters; + } + + public Type getType() { + return type; + } + + public Function> getConverter(int index) { + return (Function>) converters.get(index); + } + + /** + * Converts the next item, in the order specified in the + * {@link #getByIntersection(Class[]) intersection} method. + * @param toConvert the object to convert + * @return the converted object + */ + public Optional convert(Object toConvert) { + return ((Function>) converters.get(currentIndex++)).apply(toConvert); + } + } } \ No newline at end of file diff --git a/src/main/java/io/github/syst3ms/skriptparser/types/conversions/Converters.java b/src/main/java/io/github/syst3ms/skriptparser/types/conversions/Converters.java index 005443f8..ca2f9aa5 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/conversions/Converters.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/conversions/Converters.java @@ -246,7 +246,9 @@ public static boolean converterExists(Class from, Class... to) { */ @SuppressWarnings("unchecked") public static Optional>> getConverter(Class from, Class to) { - var p = new Pair, Class>(from, to); + if (to.isAssignableFrom(from)) + return Optional.of((Function>) val -> (Optional) Optional.of(val)); + var p = new Pair, Class>(from, to); if (convertersCache.containsKey(p)) // can contain null to denote nonexistence of a converter return Optional.ofNullable((Function>) convertersCache.get(p)); var c = getConverterInternal(from, to); diff --git a/src/main/java/io/github/syst3ms/skriptparser/util/ClassUtils.java b/src/main/java/io/github/syst3ms/skriptparser/util/ClassUtils.java index 54dc08fb..72c65310 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/util/ClassUtils.java +++ b/src/main/java/io/github/syst3ms/skriptparser/util/ClassUtils.java @@ -14,7 +14,7 @@ public static Class getCommonSuperclass(Class... cs) { /** * @param cs the array of classes - * @param interfaces whether or not to account for interfaces + * @param interfaces whether to account for interfaces * @return the nearest common superclass of the provided classes */ public static Class getCommonSuperclass(boolean interfaces, Class... cs) { diff --git a/src/test/resources/expressions/ExprDifference.txt b/src/test/resources/expressions/ExprDifference.txt index 108fd0ae..a821db7f 100644 --- a/src/test/resources/expressions/ExprDifference.txt +++ b/src/test/resources/expressions/ExprDifference.txt @@ -14,7 +14,7 @@ test: set {list::2} to 8.9 set {difference} to difference between {list::1} and {list::2} assert {difference} is 5.5 with "Number difference between variables should be 5.5: %difference between {list::1} and {list::2}%" - assert difference between {list::1} and 5.0 is 1.6 with "Number difference wRith one variable should be 1.6: %difference between {list::1} and 5%" + assert difference between {list::1} and 5.0 is 1.6 with "Number difference with one variable should be 1.6: %difference between {list::1} and 5%" # Non-comparable types throws difference between now and 5 with "Date and number should not be comparable" @@ -30,4 +30,8 @@ test: assert difference between 6 and {var} = 4.5 with "Difference with number variable as second parameter should be 4.5: %difference between 6 and {var}%" assert difference between {var} and 6 = 4.5 with "Difference with number variable as first parameter should be 4.5: %difference between {var} and 6%" - assert difference between {list::1} and now is not set with "Difference between number variable and date should not return anything." \ No newline at end of file + assert difference between {list::1} and now is not set with "Difference between number variable and date should not return anything." + + # Intersection types + compiles difference of now and 5 PM with "Difference between date and time should compile" + assert difference of 5 PM and now is less than 1 day with "Difference between time and day should be less than 1 day: %difference of 5 PM and now%" \ No newline at end of file diff --git a/src/test/resources/expressions/ExprRange.txt b/src/test/resources/expressions/ExprRange.txt index 599e3dd2..ec0d9406 100644 --- a/src/test/resources/expressions/ExprRange.txt +++ b/src/test/resources/expressions/ExprRange.txt @@ -17,3 +17,7 @@ test: set {var} to 14 set {list::*} to range from {var} to 9 assert {list::*} = 14, 13, 12, 11, 10 and 9 + + # Intersection types + set {list::*} to range from 7.4 down to 3.12345 + assert {list::*} = 7, 6, 5, 4 and 3