diff --git a/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java b/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java index b58353db..c62cca17 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java +++ b/src/main/java/io/github/syst3ms/skriptparser/effects/EffChange.java @@ -90,7 +90,7 @@ public boolean init(Expression[] expressions, int matchedPattern, ParseContex } else if (!changed.acceptsChange(mode, changeWith)) { var type = TypeManager.getByClassExact(changeWith.getReturnType()); assert type.isPresent(); - String changeTypeName = type.get().withIndefiniteArticle(!changeWith.isSingle()); + String changeTypeName = type.get().withIndefiniteArticle(changeWith.isSingle()); switch (mode) { case SET: logger.error(changedString + " cannot be set to " + changeTypeName, ErrorType.SEMANTIC_ERROR); diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java index 9c343b52..5d530b18 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Expression.java @@ -2,12 +2,15 @@ import io.github.syst3ms.skriptparser.expressions.ExprLoopValue; import io.github.syst3ms.skriptparser.lang.base.ConvertedExpression; +import io.github.syst3ms.skriptparser.parsing.ParseContext; import io.github.syst3ms.skriptparser.parsing.ParserState; import io.github.syst3ms.skriptparser.parsing.SkriptParserException; import io.github.syst3ms.skriptparser.parsing.SkriptRuntimeException; import io.github.syst3ms.skriptparser.registration.SyntaxManager; import io.github.syst3ms.skriptparser.sections.SecLoop; +import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.ChangeMode; +import io.github.syst3ms.skriptparser.types.changers.Changer; import io.github.syst3ms.skriptparser.types.conversions.Converters; import java.util.ArrayList; @@ -74,7 +77,7 @@ default boolean isSingle() { } /** - * @return the return type of this expression. By default, this is defined on registration, but, like {@linkplain #isSingle()}, can be overriden. + * @return the return type of this expression. By default, this is defined on registration, but, like {@linkplain #isSingle()}, can be overridden. */ default Class getReturnType() { return SyntaxManager.getExpressionExact(this) @@ -93,7 +96,10 @@ default Class getReturnType() { * {@link ChangeMode#DELETE} or {@link ChangeMode#RESET}, then an empty array should be returned. */ default Optional[]> acceptsChange(ChangeMode mode) { - return Optional.empty(); + return TypeManager.getByClassExact(getReturnType()) + .orElseThrow(() -> new SkriptParserException("Couldn't find a type corresponding to the class '" + getReturnType().getName() + "'")) + .getDefaultChanger() + .map(ch -> ch.acceptsChange(mode)); } /** @@ -128,12 +134,18 @@ default boolean acceptsChange(ChangeMode mode, Class needle, boolean isSingle } /** - * Changes this expression with the given values according to the given mode + * Changes this expression with the given values according to the given mode. * @param ctx the event - * @param changeMode the mode of change + * @param mode the mode of change * @param changeWith the values to change this Expression with */ - default void change(TriggerContext ctx, ChangeMode changeMode, Object[] changeWith) { /* Nothing */ } + @SuppressWarnings("unchecked") + default void change(TriggerContext ctx, ChangeMode mode, Object[] changeWith) { + TypeManager.getByClassExact(getReturnType()) + .orElseThrow(() -> new SkriptParserException("Couldn't find a type corresponding to the class '" + getReturnType().getName() + "'")) + .getDefaultChanger() + .ifPresent(ch -> ((Changer) ch).change(getArray(ctx), mode, changeWith)); + } /** * @param ctx the event @@ -152,7 +164,7 @@ default Stream stream(TriggerContext ctx) { } /** - * Converts this expression from it's current type ({@link T}) to another type, using + * Converts this expression from its current type ({@link T}) to another type, using * {@linkplain Converters converters}. * @param to the class of the type to convert this Expression to * @param the type to convert this Expression to @@ -232,6 +244,14 @@ static boolean check(T[] all, Predicate predicate, boolean negate return negated != and; } + static boolean initialize(Expression expression, Expression[] expressions, int matchedPattern, ParseContext parseContext) { + try { + return expression.init(expressions, matchedPattern, parseContext); + } catch (Exception ignored) { + return true; + } + } + @SuppressWarnings("unchecked") static List getMatchingSections(ParserState parserState, Class sectionClass) { diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleExpression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleExpression.java index da236469..940ae5c6 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleExpression.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleExpression.java @@ -1,9 +1,7 @@ package io.github.syst3ms.skriptparser.lang; import io.github.syst3ms.skriptparser.parsing.ParseContext; -import io.github.syst3ms.skriptparser.types.TypeManager; import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.Nullable; import java.util.function.Function; @@ -15,18 +13,13 @@ public class SimpleExpression implements Expression { private final Class returnType; private final boolean isSingle; private final Function function; - @Nullable - private final String representation; + private final String toString; - public SimpleExpression(Class returnType, boolean isSingle, Function function) { - this(returnType, isSingle, function, null); - } - - public SimpleExpression(Class returnType, boolean isSingle, Function function, @Nullable String representation) { + public SimpleExpression(Class returnType, boolean isSingle, Function function, String toString) { this.returnType = returnType; this.isSingle = isSingle; this.function = function; - this.representation = representation; + this.toString = toString; } @Override @@ -51,11 +44,6 @@ public Class getReturnType() { @Override public String toString(TriggerContext ctx, boolean debug) { - // If the representation is not given, the default TypeManager#toString will be used to convert each value separately - if (representation == null) { - return TypeManager.toString((Object[]) function.apply(ctx)); - } else { - return representation; - } + return toString; } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleLiteral.java b/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleLiteral.java index 4f7ebc0e..fbf3e97e 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleLiteral.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/SimpleLiteral.java @@ -21,14 +21,15 @@ public class SimpleLiteral implements Literal { private final T[] values; private boolean isAndList = true; - private Class returnType; + private final Class returnType; - public SimpleLiteral(T[] values) { - this.values = values; + public SimpleLiteral(T... values) { + this((Class) values.getClass().getComponentType(), values); } @SafeVarargs - public SimpleLiteral(Class c, T... values) { + public SimpleLiteral(Class returnType, T... values) { + this.returnType = returnType; this.values = Arrays.copyOf(values, values.length); } @@ -60,11 +61,7 @@ public boolean isSingle() { } public Class getReturnType() { - if (returnType != null) { - return returnType; - } else { - return returnType = (Class) values.getClass().getComponentType(); - } + return returnType; } @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..740eeb79 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/Variable.java @@ -193,7 +193,7 @@ public void change(TriggerContext ctx, ChangeMode mode, Object[] changeWith) thr if (changer.map(ch -> ch.acceptsChange(ChangeMode.RESET)).isPresent()) { var one = (Object[]) Array.newInstance(o.getClass(), 1); one[0] = o; - changer.ifPresent(ch -> ((Changer) ch).change(one, new Object[0], ChangeMode.RESET)); + changer.ifPresent(ch -> ((Changer) ch).change(one, ChangeMode.RESET, new Object[0])); } } break; @@ -291,7 +291,7 @@ public void change(TriggerContext ctx, ChangeMode mode, Object[] changeWith) thr if (d2 != null) l.add(d2); } - ((Changer) changer.get()).change(one, l.toArray(), mode); + ((Changer) changer.get()).change(one, mode, l.toArray()); } break; } diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/base/EventExpression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/base/EventExpression.java new file mode 100644 index 00000000..8b40e2d0 --- /dev/null +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/base/EventExpression.java @@ -0,0 +1,90 @@ +package io.github.syst3ms.skriptparser.lang.base; + +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.log.ErrorType; +import io.github.syst3ms.skriptparser.parsing.ParseContext; +import io.github.syst3ms.skriptparser.parsing.SkriptRuntimeException; +import io.github.syst3ms.skriptparser.registration.context.ContextValue; +import io.github.syst3ms.skriptparser.registration.context.ContextValues; +import io.github.syst3ms.skriptparser.types.PatternType; +import io.github.syst3ms.skriptparser.types.Type; +import io.github.syst3ms.skriptparser.types.TypeManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * One can use this as a utility class for a {@linkplain Type#getDefaultExpression() default expression}. + * This expression class holds a reference to a specific {@link ContextValue} based on the required + * return type and whether or not the result must be a single value. A runtime exception will be thrown + * if such context value does not exist. + *
+ * Note that the {@linkplain ContextValue.State state} of the referenced context value must be 'present'. + * @param the Expression's type + * @author Mwexim + */ +public class EventExpression implements Expression { + private final PatternType returnType; + private final List> contextValues = new ArrayList<>(); + + public EventExpression(Class returnType, boolean isSingle) { + this.returnType = new PatternType<>(TypeManager.getByClassExact(returnType).orElseThrow(IllegalArgumentException::new), isSingle); + } + + @Override + public boolean init(Expression[] expressions, int matchedPattern, ParseContext parseContext) { + parseContext.getParserState().getCurrentContexts().stream() + .map(ctx -> ContextValues.getContextValues(ctx).stream() + .filter(info -> returnType.getType().getTypeClass().isAssignableFrom(info.getReturnType().getType().getTypeClass())) + .filter(info -> !returnType.isSingle() || info.getReturnType().isSingle()) + .filter(info -> info.getState() == ContextValue.State.PRESENT) + .collect(Collectors.toList())) + .map(List.class::cast) + .forEach(contextValues::addAll); + if (contextValues.size() == 0) { + parseContext.getLogger().error("Couldn't find " + + returnType.getType().withIndefiniteArticle(returnType.isSingle()) + + " as default expression in this event", + ErrorType.NO_MATCH); + return false; + } + return true; + } + + @SuppressWarnings("unchecked") + @Override + public T[] getValues(TriggerContext ctx) { + var info = (ContextValue) contextValues.stream() + .filter(val -> val.getContext().isAssignableFrom(ctx.getClass())) + .findFirst() + .orElseThrow(() -> new SkriptRuntimeException("Couldn't find any context value corresponding to the class '" + ctx.getClass().getName() + "'")); + return info.getFunction().apply(ctx); + } + + @Override + public boolean isSingle() { + return returnType.isSingle(); + } + + @Override + public Class getReturnType() { + return returnType.getType().getTypeClass(); + } + + @SuppressWarnings("unchecked") + @Override + public String toString(TriggerContext ctx, boolean debug) { + var info = (ContextValue) contextValues.stream() + .filter(val -> val.getContext().isAssignableFrom(ctx.getClass())) + .findFirst().orElse(null); + if (info != null) { + return new String[] {"past ", "", "future "}[info.getState().ordinal()] + + (info.getUsage().isCorrect(true) ? "" : "context-") + + info.getReturnType().getType().withIndefiniteArticle(returnType.isSingle()); + } else { + return TypeManager.NULL_REPRESENTATION; + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/syst3ms/skriptparser/lang/base/RestrictedExpression.java b/src/main/java/io/github/syst3ms/skriptparser/lang/base/RestrictedExpression.java index 9d6bb562..3b1bca33 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/lang/base/RestrictedExpression.java +++ b/src/main/java/io/github/syst3ms/skriptparser/lang/base/RestrictedExpression.java @@ -15,7 +15,7 @@ * It is possible to specify the error message that should be shown if the restrictions aren't followed. * * This class shouldn't be used for expressions that should only work with specific {@link TriggerContext}s. - * For this purpose, use {@link ParseContext#getParserState()} in conjuction with {@link ParserState#getCurrentContexts()}. + * For this purpose, use {@link ParseContext#getParserState()} in conjunction with {@link ParserState#getCurrentContexts()}. * @param the return type * @see ParserState#getCurrentContexts() */ diff --git a/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java b/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java index 86a5a2c6..59cf7dbf 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java +++ b/src/main/java/io/github/syst3ms/skriptparser/parsing/SyntaxParser.java @@ -335,7 +335,7 @@ public static Optional> parseBooleanExpression(Str } recentContextValues.acknowledge(info); - return Optional.of(new ContextExpression<>((ContextValue) info, value, alone)); + return Optional.of(new ContextExpression<>((ContextValue) info, value, alone)); } } return Optional.empty(); diff --git a/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java b/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java index e0fc1f34..96b58b68 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java +++ b/src/main/java/io/github/syst3ms/skriptparser/pattern/ExpressionElement.java @@ -5,9 +5,7 @@ import io.github.syst3ms.skriptparser.lang.Variable; import io.github.syst3ms.skriptparser.lang.base.ConditionalExpression; import io.github.syst3ms.skriptparser.log.ErrorType; -import io.github.syst3ms.skriptparser.log.SkriptLogger; import io.github.syst3ms.skriptparser.parsing.MatchContext; -import io.github.syst3ms.skriptparser.parsing.ParserState; import io.github.syst3ms.skriptparser.parsing.SyntaxParser; import io.github.syst3ms.skriptparser.types.PatternType; import io.github.syst3ms.skriptparser.util.StringUtils; @@ -46,6 +44,8 @@ public ExpressionElement(List> types, Acceptance acceptance, bool public int match(String s, int index, MatchContext context) { var typeArray = types.toArray(new PatternType[0]); if (index >= s.length()) { + if (typeArray.length >= 1) + getDefaultExpression(typeArray[0], context).ifPresent(context::addExpression); return -1; } var logger = context.getLogger(); @@ -69,7 +69,7 @@ public int match(String s, int index, MatchContext context) { return -1; } var toParse = s.substring(index).strip(); - var expression = parse(toParse, typeArray, context.getParserState(), logger); + var expression = parse(toParse, typeArray, context); if (expression.isPresent()) { context.addExpression(expression.get()); return index + toParse.length(); @@ -79,7 +79,7 @@ public int match(String s, int index, MatchContext context) { var i = StringUtils.indexOfIgnoreCase(s, text, index); while (i != -1) { var toParse = s.substring(index, i).strip(); - var expression = parse(toParse, typeArray, context.getParserState(), logger); + var expression = parse(toParse, typeArray, context); if (expression.isPresent()) { context.addExpression(expression.get()); return index + toParse.length(); @@ -96,7 +96,7 @@ public int match(String s, int index, MatchContext context) { var toParse = s.substring(index, i); if (toParse.length() == context.getOriginalPattern().length()) continue; - var expression = parse(toParse, typeArray, context.getParserState(), logger); + var expression = parse(toParse, typeArray, context); if (expression.isPresent()) { context.addExpression(expression.get()); return index + toParse.length(); @@ -117,7 +117,7 @@ public int match(String s, int index, MatchContext context) { var i = StringUtils.indexOfIgnoreCase(s, split, index); if (i != -1) { var toParse = s.substring(index, i); - var expression = parse(toParse, typeArray, context.getParserState(), logger); + var expression = parse(toParse, typeArray, context); if (expression.isPresent()) { context.addExpression(expression.get()); return index + toParse.length(); @@ -136,7 +136,7 @@ public int match(String s, int index, MatchContext context) { var i = StringUtils.indexOfIgnoreCase(s, split, index); if (i != -1) { var toParse = s.substring(index, i); - var expression = parse(toParse, typeArray, context.getParserState(), logger); + var expression = parse(toParse, typeArray, context); if (expression.isPresent()) { context.addExpression(expression.get()); return index + toParse.length(); @@ -177,7 +177,8 @@ private List splitAtSpaces(String s) { } @SuppressWarnings("unchecked") - private Optional> parse(String s, PatternType[] types, ParserState parserState, SkriptLogger logger) { + private Optional> parse(String s, PatternType[] types, MatchContext context) { + var logger = context.getLogger(); for (var type : types) { Optional> expression; logger.recurse(); @@ -186,11 +187,11 @@ private Optional> parse(String s, PatternT expression = (Optional>) SyntaxParser.parseBooleanExpression( s, acceptsConditional ? SyntaxParser.MAYBE_CONDITIONAL : SyntaxParser.NOT_CONDITIONAL, - parserState, + context.getParserState(), logger ); } else { - expression = SyntaxParser.parseExpression(s, (PatternType) type, parserState, logger); + expression = SyntaxParser.parseExpression(s, (PatternType) type, context.getParserState(), logger); } logger.callback(); if (expression.isEmpty()) @@ -217,14 +218,35 @@ private Optional> parse(String s, PatternT return false; } break; + default: + throw new IllegalStateException(); } return true; }); + if (expression.isEmpty()) { + expression = getDefaultExpression(type, context); + } return expression; } return Optional.empty(); } + @SuppressWarnings("unchecked") + private Optional> getDefaultExpression(PatternType type, MatchContext context) { + if (nullable) { + return type.getType().getDefaultExpression() + .filter(expr -> !type.isSingle() || type.isSingle() && expr.isSingle()) + .filter(expr -> Expression.initialize( + expr, + context.getParsedExpressions().toArray(new Expression[0]), + context.getPatternIndex(), + context.toParseResult())) + .map(expr -> (Expression) expr); + } else { + return Optional.empty(); + } + } + @Override public boolean equals(Object obj) { if (this == obj) @@ -233,7 +255,7 @@ public boolean equals(Object obj) { return false; } else { var e = (ExpressionElement) obj; - return types.equals(e.types) && acceptance == e.acceptance && acceptsConditional == e.acceptsConditional; + return types.equals(e.types) && acceptance == e.acceptance && nullable == e.nullable && acceptsConditional == e.acceptsConditional; } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/pattern/PatternParser.java b/src/main/java/io/github/syst3ms/skriptparser/pattern/PatternParser.java index 9ab4b082..b0cba087 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/pattern/PatternParser.java +++ b/src/main/java/io/github/syst3ms/skriptparser/pattern/PatternParser.java @@ -18,7 +18,7 @@ */ public class PatternParser { private static final Pattern PARSE_MARK_PATTERN = Pattern.compile("(([A-Za-z0-9]+)?):(.*)"); - private static final Pattern VARIABLE_PATTERN = Pattern.compile("(-)?([*^~])?(=)?(?[\\w/]+)?"); + private static final Pattern VARIABLE_PATTERN = Pattern.compile("([*^~])?(=)?(?[\\w/]+)?(\\?)?"); /** * Parses a pattern and returns a {@link PatternElement}. This method can be called by itself, for example when parsing group constructs. @@ -194,14 +194,14 @@ private static Optional parsePattern(String pattern, S var s = pattern.substring(i + 1, nextIndex); i = nextIndex; var m = VARIABLE_PATTERN.matcher(s); + if (!m.matches()) { logger.error("Invalid expression element (index " + initialPos + ") : '" + s + "'", ErrorType.MALFORMED_INPUT); return Optional.empty(); } else { - var nullable = m.group(1) != null; var acceptance = ExpressionElement.Acceptance.ALL; - if (m.group(2) != null) { - var acc = m.group(2); + if (m.group(1) != null) { + var acc = m.group(1); if (acc.equals("~")) { acceptance = ExpressionElement.Acceptance.EXPRESSIONS_ONLY; } else if (acc.equals("^")) { @@ -221,10 +221,17 @@ private static Optional parsePattern(String pattern, S } patternTypes.add(t.get()); } - var acceptConditional = m.group(3) != null; + var acceptConditional = m.group(2) != null; + var nullable = m.group(4) != null; if (acceptConditional && patternTypes.stream().noneMatch(t -> t.getType().getTypeClass() == Boolean.class)) { logger.error("Can't use the '=' flag on non-boolean types (index " + initialPos + ")", ErrorType.SEMANTIC_ERROR); return Optional.empty(); + } else if (nullable && patternTypes.size() > 1) { + logger.error("Can't use the '?' flag with multiple type possibilities (index " + initialPos + ")", ErrorType.SEMANTIC_ERROR); + return Optional.empty(); + } else if (nullable && patternTypes.get(0).getType().getDefaultExpression().isEmpty()) { + logger.error("Can't use the '?' flag if the type doesn't have a default expression (index " + initialPos + ")", ErrorType.SEMANTIC_ERROR); + return Optional.empty(); } elements.add(new ExpressionElement(patternTypes, acceptance, nullable, acceptConditional)); } 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..92b354ac 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/DefaultRegistration.java @@ -1,6 +1,7 @@ package io.github.syst3ms.skriptparser.registration; import io.github.syst3ms.skriptparser.Parser; +import io.github.syst3ms.skriptparser.lang.SimpleLiteral; import io.github.syst3ms.skriptparser.types.Type; import io.github.syst3ms.skriptparser.types.TypeManager; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; @@ -157,7 +158,6 @@ public Class getRelativeType() { return null; } }) - .toStringFunction(String::valueOf) .register(); registration.newType(Type.class, "type", "type@s") @@ -167,10 +167,10 @@ public Class getRelativeType() { registration.newType(Color.class, "color", "color@s") .literalParser(s -> Color.ofLiteral(s).orElse(null)) - .toStringFunction(Color::toString) .register(); registration.newType(Duration.class, "duration", "duration@s") + .defaultExpression(new SimpleLiteral<>(Duration.ZERO)) .literalParser(s -> TimeUtils.parseDuration(s).orElse(null)) .toStringFunction(TimeUtils::toStringDuration) .arithmetic(new Arithmetic() { @@ -197,7 +197,6 @@ public Class getRelativeType() { .register(); registration.newType(SkriptDate.class, "date", "date@s") - .toStringFunction(SkriptDate::toString) .arithmetic(new Arithmetic() { @Override public Duration difference(SkriptDate first, SkriptDate second) { @@ -223,7 +222,6 @@ public Class getRelativeType() { registration.newType(Time.class, "time", "time@s") .literalParser(s -> Time.parse(s).orElse(null)) - .toStringFunction(Time::toString) .arithmetic(new Arithmetic() { @Override public Duration difference(Time first, Time second) { 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..35b294f0 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/SkriptRegistration.java @@ -558,6 +558,8 @@ public class TypeRegistrar implements Registrar { private final Class c; private final String baseName; private final String pattern; + @Nullable + private Expression defaultExpression; private Function toStringFunction = o -> Objects.toString(o, TypeManager.NULL_REPRESENTATION); @Nullable private Function literalParser; @@ -572,6 +574,18 @@ public TypeRegistrar(Class c, String baseName, String pattern) { this.pattern = pattern; } + /** + * The default expression will be used when an optional part in a pattern, + * with an {@linkplain ExpressionElement}, explicitly allows the expression + * to be set to a default one if the optional part was not used or matched. + * @param defaultExpression the default expression of this type + * @return the registrar + */ + public TypeRegistrar defaultExpression(Expression defaultExpression) { + this.defaultExpression = defaultExpression; + return this; + } + /** * @param literalParser a function interpreting a string as an instance of the type * @return the registrar @@ -614,7 +628,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, defaultChanger, arithmetic, defaultExpression)); } } diff --git a/src/main/java/io/github/syst3ms/skriptparser/registration/context/ContextValues.java b/src/main/java/io/github/syst3ms/skriptparser/registration/context/ContextValues.java index 3e06feff..32ecd5fb 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/registration/context/ContextValues.java +++ b/src/main/java/io/github/syst3ms/skriptparser/registration/context/ContextValues.java @@ -10,7 +10,7 @@ import java.util.stream.Collectors; public class ContextValues { - private static final List> contextValues = new ArrayList<>(); + private static final List> contextValues = new ArrayList<>(); public static void register(SkriptRegistration reg) { contextValues.addAll(reg.getContextValues()); @@ -20,7 +20,7 @@ public static void register(SkriptRegistration reg) { * Returns an unmodifiable list with all the registered context values. * @return a list with all context values */ - public static List> getContextValues() { + public static List> getContextValues() { return Collections.unmodifiableList(contextValues); } @@ -30,7 +30,7 @@ public static void register(SkriptRegistration reg) { * @param ctx the context class * @return a list with the applicable context values */ - public static List> getContextValues(Class ctx) { + public static List> getContextValues(Class ctx) { return contextValues.stream() .filter(val -> val.getContext().isAssignableFrom(ctx)) .filter(val -> !CollectionUtils.contains(val.getExcluded(), val.getContext())) diff --git a/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java b/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java index db44052a..675d696d 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java +++ b/src/main/java/io/github/syst3ms/skriptparser/sections/SecConditional.java @@ -122,7 +122,6 @@ public boolean checkFinishing(Predicate finishingTest, */ public void setFallingClause(SecConditional conditional) { fallingClause = conditional; - //fallingClause.setParent(this); } /** 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..57929a64 100644 --- a/src/main/java/io/github/syst3ms/skriptparser/types/Type.java +++ b/src/main/java/io/github/syst3ms/skriptparser/types/Type.java @@ -1,5 +1,7 @@ package io.github.syst3ms.skriptparser.types; +import io.github.syst3ms.skriptparser.lang.Expression; +import io.github.syst3ms.skriptparser.pattern.ExpressionElement; import io.github.syst3ms.skriptparser.types.changers.Arithmetic; import io.github.syst3ms.skriptparser.types.changers.Changer; import io.github.syst3ms.skriptparser.util.StringUtils; @@ -18,6 +20,8 @@ public class Type { private final Class typeClass; private final String baseName; private final String[] pluralForms; + @Nullable + private final Expression defaultExpression; private final Function toStringFunction; @Nullable private final Function literalParser; @@ -84,7 +88,7 @@ public Type(Class typeClass, String pattern, @Nullable Function literalParser, Function toStringFunction) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, null); + this(typeClass, baseName, pattern, literalParser, toStringFunction, null, null); } public Type(Class typeClass, @@ -92,8 +96,9 @@ public Type(Class typeClass, String pattern, @Nullable Function literalParser, Function toStringFunction, - @Nullable Changer defaultChanger) { - this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, null); + @Nullable Changer defaultChanger, + @Nullable Arithmetic arithmetic) { + this(typeClass, baseName, pattern, literalParser, toStringFunction, defaultChanger, arithmetic, null); } @SuppressWarnings("unchecked") @@ -103,7 +108,8 @@ public Type(Class typeClass, @Nullable Function literalParser, Function toStringFunction, @Nullable Changer defaultChanger, - @Nullable Arithmetic arithmetic) { + @Nullable Arithmetic arithmetic, + @Nullable Expression defaultExpression) { this.typeClass = typeClass; this.baseName = baseName; this.literalParser = literalParser; @@ -111,6 +117,7 @@ public Type(Class typeClass, this.pluralForms = StringUtils.getForms(pattern.strip()); this.defaultChanger = defaultChanger; this.arithmetic = arithmetic; + this.defaultExpression = defaultExpression; } public Class getTypeClass() { @@ -141,13 +148,23 @@ public Optional> getDefaultChanger() { return Optional.ofNullable(arithmetic); } + /** + * The default expression will be used when an optional part in a pattern, + * with an {@linkplain ExpressionElement}, explicitly allows the expression + * to be set to a default one if the optional part was not used or matched. + * @return the default expression + */ + public Optional> getDefaultExpression() { + return Optional.ofNullable(defaultExpression); + } + /** * Adds a proper English indefinite article to this type and applies the correct form. - * @param plural whether this Type is plural or not + * @param isSingle whether this Type is single or not * @return the applied form of this Type */ - public String withIndefiniteArticle(boolean plural) { - return StringUtils.withIndefiniteArticle(pluralForms[plural ? 1 : 0], plural); + public String withIndefiniteArticle(boolean isSingle) { + return StringUtils.withIndefiniteArticle(pluralForms[isSingle ? 0 : 1], isSingle); } @Override 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..f134305a 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 @@ -19,8 +19,8 @@ public interface Changer { /** * Changes the implementing object * @param toChange the current values - * @param changeWith the values to change with * @param mode the change mode + * @param changeWith the values to change with */ - void change(T[] toChange, Object[] changeWith, ChangeMode mode); + void change(T[] toChange, ChangeMode mode, Object[] changeWith); } diff --git a/src/test/java/io/github/syst3ms/skriptparser/TestRegistration.java b/src/test/java/io/github/syst3ms/skriptparser/TestRegistration.java index a3e6f8cb..f2bfc3a1 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/TestRegistration.java +++ b/src/test/java/io/github/syst3ms/skriptparser/TestRegistration.java @@ -1,5 +1,6 @@ package io.github.syst3ms.skriptparser; +import io.github.syst3ms.skriptparser.lang.base.EventExpression; import io.github.syst3ms.skriptparser.registration.DefaultRegistration; import io.github.syst3ms.skriptparser.registration.SkriptRegistration; import io.github.syst3ms.skriptparser.util.FileUtils; @@ -21,7 +22,9 @@ public static void register() { } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); } + DefaultRegistration.register(); + try { FileUtils.loadClasses( Path.of("build/classes/java/main"), @@ -40,6 +43,12 @@ public static void register() { } catch (IOException e) { e.printStackTrace(); } + + // This overrides the default String type + Parser.getMainRegistration().newType(String.class, "string", "string@s") + .defaultExpression(new EventExpression<>(String.class, true)) + .register(); + Parser.getMainRegistration().register(); /* diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/PatternParserTest.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/PatternParserTest.java index a80c4773..9d405930 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/parsing/PatternParserTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/parsing/PatternParserTest.java @@ -168,11 +168,21 @@ public void testParsePattern() { TypeManager.getPatternType("strings").orElseThrow(AssertionError::new) ), ExpressionElement.Acceptance.LITERALS_ONLY, - true, + false, false ), parsePattern("%*number/strings%", logger) ); + assertEqualsOptional( + new ExpressionElement( + Collections.singletonList(TypeManager.getPatternType("durations").orElseThrow(AssertionError::new)), + ExpressionElement.Acceptance.VARIABLES_ONLY, + true, + false + ), + parsePattern("%^durations?%", logger) + ); + assertOptionalEmpty(parsePattern("%number/duration/date?%", logger)); // Multiple types cannot be nullable // Failing patterns assertOptionalEmpty(parsePattern("(unclosed", logger)); diff --git a/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java b/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java index 005ea955..a19fa878 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/parsing/SyntaxParserTest.java @@ -1,10 +1,17 @@ package io.github.syst3ms.skriptparser.parsing; import io.github.syst3ms.skriptparser.TestRegistration; +import io.github.syst3ms.skriptparser.lang.SimpleLiteral; +import io.github.syst3ms.skriptparser.lang.TriggerContext; +import io.github.syst3ms.skriptparser.lang.base.EventExpression; import io.github.syst3ms.skriptparser.log.LogEntry; import io.github.syst3ms.skriptparser.log.LogType; +import io.github.syst3ms.skriptparser.log.SkriptLogger; +import io.github.syst3ms.skriptparser.pattern.PatternParser; import io.github.syst3ms.skriptparser.registration.SkriptAddon; +import io.github.syst3ms.skriptparser.syntax.TestContext; import io.github.syst3ms.skriptparser.variables.Variables; +import org.junit.Test; import org.junit.jupiter.api.DynamicContainer; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.DynamicTest; @@ -13,6 +20,7 @@ import java.io.File; import java.net.URL; +import java.time.Duration; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -36,8 +44,39 @@ public class SyntaxParserTest { private static final List errorsFound = new ArrayList<>(); + @Test + public void testDefaultExpressions() { + var parserState = new ParserState(); + parserState.setCurrentContexts(Set.of(TestContext.class, TestContext.SubTestContext.class)); + /* + * Duration type with SimpleLiteral as default expression + * Also checks if non-single types accept single default expressions. + */ + var pattern = PatternParser.parsePattern("look for default expression [%durations?%]", new SkriptLogger()).orElseThrow(AssertionError::new); + var context = new MatchContext(pattern, parserState, new SkriptLogger()); + assert pattern.match("look for default expression", 0, context) != -1 : "pattern didn't match"; + + var expressions = context.getParsedExpressions(); + assert expressions.size() == 1 : "expected exactly one (default) expression, found " + expressions.size(); + assert expressions.get(0) instanceof SimpleLiteral : "didn't find default expression of type SimpleLiteral"; + assert expressions.get(0).getSingle(TriggerContext.DUMMY).orElseThrow(AssertionError::new).equals(Duration.ZERO) : "default expression is not equal to Duration#ZERO constant"; + + /* + * String type with EventExpression as default expression + */ + pattern = PatternParser.parsePattern("another default expression [%string?%]", new SkriptLogger()).orElseThrow(AssertionError::new); + context = new MatchContext(pattern, parserState, new SkriptLogger()); + assert pattern.match("another default expression", 0, context) != -1 : "pattern didn't match"; + + expressions = context.getParsedExpressions(); + assert expressions.size() == 1 : "expected exactly one (default) expression, found " + expressions.size(); + assert expressions.get(0) instanceof EventExpression : "didn't find default expression of type EventExpression"; + // We use TestContext to eliminate all other context values that purposefully have been declared with SubTestContext + assert expressions.get(0).getSingle(new TestContext()).orElseThrow(AssertionError::new).equals("This works as well") : "default expression is not equal to 'This works as well'"; + } + @TestFactory - public Iterator syntaxTest() { + public Iterator testSyntaxClasses() { String[] folders = {"effects", "expressions", "literals", "sections", "tags", "general"}; ArrayList containerList = new ArrayList<>(); for (String folder : folders) { diff --git a/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java b/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java index 4480f16a..65107b72 100644 --- a/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java +++ b/src/test/java/io/github/syst3ms/skriptparser/syntax/EvtTest.java @@ -5,7 +5,7 @@ import io.github.syst3ms.skriptparser.lang.SkriptEvent; import io.github.syst3ms.skriptparser.lang.TriggerContext; import io.github.syst3ms.skriptparser.parsing.ParseContext; -import io.github.syst3ms.skriptparser.registration.context.ContextValue; +import io.github.syst3ms.skriptparser.registration.context.ContextValue.State; import io.github.syst3ms.skriptparser.registration.context.ContextValue.Usage; import io.github.syst3ms.skriptparser.syntax.TestContext.SubTestContext; @@ -25,7 +25,7 @@ public class EvtTest extends SkriptEvent { static { Parser.getMainRegistration() .newEvent(EvtTest.class, "*test [[only] when %=boolean%]") - .setHandledContexts(SubTestContext.class) + .setHandledContexts(TestContext.class, SubTestContext.class) .register(); Parser.getMainRegistration() .newContextValue(SubTestContext.class, String.class, true, "test", __ -> new String[] {"Hello World!"}) @@ -38,9 +38,10 @@ public class EvtTest extends SkriptEvent { Parser.getMainRegistration() .newContextValue(TestContext.class, String.class, true, "[some] pattern value", ctx -> new String[] {ctx.patternValue()}) .setUsage(Usage.ALONE_ONLY) - .setState(ContextValue.State.PAST) + .setState(State.PAST) .register(); Parser.getMainRegistration().addContextType(SubTestContext.class, Duration.class, SubTestContext::oneDay); + Parser.getMainRegistration().addContextType(TestContext.class, String.class, __ -> "This works as well"); } private Expression condition;