diff --git a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java index 6e17db006da..70e6cdc038a 100644 --- a/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/BukkitClasses.java @@ -153,7 +153,7 @@ public Entity parse(final String s, final ParseContext context) { @Override public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -536,7 +536,7 @@ protected boolean canBeInstantiated() { @Nullable public World parse(final String s, final ParseContext context) { // REMIND allow shortcuts '[over]world', 'nether' and '[the_]end' (server.properties: 'level-name=world') // inconsistent with 'world is "..."' - if (context == ParseContext.COMMAND || context == ParseContext.CONFIG) + if (context == ParseContext.COMMAND || context == ParseContext.PARSE || context == ParseContext.CONFIG) return Bukkit.getWorld(s); final Matcher m = parsePattern.matcher(s); if (m.matches()) @@ -673,7 +673,7 @@ public String toVariableNameString(final Inventory i) { @Override @Nullable public Player parse(String s, ParseContext context) { - if (context == ParseContext.COMMAND) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (s.isEmpty()) return null; if (UUID_PATTERN.matcher(s).matches()) @@ -693,7 +693,7 @@ public Player parse(String s, ParseContext context) { @Override public boolean canParse(final ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override @@ -733,7 +733,7 @@ public String getDebugMessage(final Player p) { @Nullable @SuppressWarnings("deprecation") public OfflinePlayer parse(final String s, final ParseContext context) { - if (context == ParseContext.COMMAND) { + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) { if (UUID_PATTERN.matcher(s).matches()) return Bukkit.getOfflinePlayer(UUID.fromString(s)); else if (!SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) @@ -746,7 +746,7 @@ else if (!SkriptConfig.playerNameRegexPattern.value().matcher(s).matches()) @Override public boolean canParse(ParseContext context) { - return context == ParseContext.COMMAND; + return context == ParseContext.COMMAND || context == ParseContext.PARSE; } @Override diff --git a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java index 1f463e87361..237b5db732c 100644 --- a/src/main/java/ch/njol/skript/classes/data/JavaClasses.java +++ b/src/main/java/ch/njol/skript/classes/data/JavaClasses.java @@ -576,6 +576,7 @@ public String parse(String s, ParseContext context) { return Utils.replaceChatStyles("" + s.substring(1, s.length() - 1).replace("\"\"", "\"")); return null; case COMMAND: + case PARSE: return s; } assert false; diff --git a/src/main/java/ch/njol/skript/effects/EffChange.java b/src/main/java/ch/njol/skript/effects/EffChange.java index 6b16e36071f..9c355191ab2 100644 --- a/src/main/java/ch/njol/skript/effects/EffChange.java +++ b/src/main/java/ch/njol/skript/effects/EffChange.java @@ -21,7 +21,12 @@ import java.util.Arrays; import java.util.logging.Level; -import org.skriptlang.skript.lang.script.Script; +import ch.njol.skript.expressions.ExprParse; +import ch.njol.skript.lang.Effect; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.ExpressionList; +import ch.njol.skript.lang.SkriptParser; +import ch.njol.skript.lang.Variable; import org.skriptlang.skript.lang.script.ScriptWarning; import org.bukkit.event.Event; import org.eclipse.jdt.annotation.Nullable; @@ -35,11 +40,7 @@ import ch.njol.skript.doc.Examples; import ch.njol.skript.doc.Name; import ch.njol.skript.doc.Since; -import ch.njol.skript.lang.Effect; -import ch.njol.skript.lang.Expression; -import ch.njol.skript.lang.SkriptParser; import ch.njol.skript.lang.SkriptParser.ParseResult; -import ch.njol.skript.lang.Variable; import ch.njol.skript.log.CountingLogHandler; import ch.njol.skript.log.ErrorQuality; import ch.njol.skript.log.ParseLogHandler; @@ -254,7 +255,18 @@ else if (mode == ChangeMode.SET) Skript.error("only one " + Classes.getSuperClassInfo(x).getName() + " can be " + (mode == ChangeMode.ADD ? "added to" : "removed from") + " " + changed + ", not more", ErrorQuality.SEMANTIC_ERROR); return false; } - + + if (changed instanceof Variable && !changed.isSingle() && mode == ChangeMode.SET) { + if (ch instanceof ExprParse) { + ((ExprParse) ch).flatten = false; + } else if (ch instanceof ExpressionList) { + for (Expression expression : ((ExpressionList) ch).getExpressions()) { + if (expression instanceof ExprParse) + ((ExprParse) expression).flatten = false; + } + } + } + if (changed instanceof Variable && !((Variable) changed).isLocal() && (mode == ChangeMode.SET || ((Variable) changed).isList() && mode == ChangeMode.ADD)) { final ClassInfo ci = Classes.getSuperClassInfo(ch.getReturnType()); if (ci.getC() != Object.class && ci.getSerializer() == null && ci.getSerializeAs() == null && !SkriptConfig.disableObjectCannotBeSavedWarnings.value()) { diff --git a/src/main/java/ch/njol/skript/expressions/ExprParse.java b/src/main/java/ch/njol/skript/expressions/ExprParse.java index 0cf481c7bdf..bea0909a304 100644 --- a/src/main/java/ch/njol/skript/expressions/ExprParse.java +++ b/src/main/java/ch/njol/skript/expressions/ExprParse.java @@ -48,6 +48,9 @@ import org.eclipse.jdt.annotation.Nullable; import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; @Name("Parse") @Description({"Parses text as a given type, or as a given pattern.", @@ -88,8 +91,9 @@ public class ExprParse extends SimpleExpression { @Nullable private SkriptPattern pattern; @Nullable - private boolean[] patternExpressionPlurals; - private boolean patternHasSingleExpression = false; + private NonNullPair, Boolean>[] patternExpressions; + private boolean single = true; + public boolean flatten = true; @Nullable private ClassInfo classInfo; @@ -106,7 +110,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return false; } - NonNullPair p = SkriptParser.validatePattern(pattern); + NonNullPair, Boolean>[]> p = SkriptParser.validatePattern(pattern); if (p == null) { // Errored in validatePattern already return false; @@ -114,7 +118,15 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye // Make all types in the pattern plural pattern = p.getFirst(); - patternExpressionPlurals = p.getSecond(); + patternExpressions = p.getSecond(); + + // Check if all the types can actually parse + for (NonNullPair, Boolean> patternExpression : patternExpressions) { + if (!canParse(patternExpression.getFirst())) + return false; + if (patternExpression.getSecond()) + single = false; + } // Escape '¦' and ':' (used for parser tags/marks) pattern = escapeParseTags(pattern); @@ -128,8 +140,9 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye return false; } - // If the pattern contains at most 1 type, this expression is single - this.patternHasSingleExpression = this.pattern.countNonNullTypes() <= 1; + // If the pattern contains at most 1 type, and this type is single, this expression is single + if (single) + this.single = this.pattern.countNonNullTypes() <= 1; } else { classInfo = ((Literal>) exprs[1]).getSingle(); @@ -139,11 +152,7 @@ public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelaye } // Make sure the ClassInfo has a parser - Parser parser = classInfo.getParser(); - if (parser == null || !parser.canParse(ParseContext.COMMAND)) { // TODO special parse context? - Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); - return false; - } + return canParse(classInfo); } return true; } @@ -165,21 +174,36 @@ protected Object[] get(Event event) { assert parser != null; // checked in init() // Parse and return value - Object value = parser.parse(text, ParseContext.COMMAND); + Object value = parser.parse(text, ParseContext.PARSE); if (value != null) { Object[] valueArray = (Object[]) Array.newInstance(classInfo.getC(), 1); valueArray[0] = value; return valueArray; } } else { - assert pattern != null && patternExpressionPlurals != null; + assert pattern != null && patternExpressions != null; - MatchResult matchResult = pattern.match(text, SkriptParser.PARSE_LITERALS, ParseContext.COMMAND); + MatchResult matchResult = pattern.match(text, SkriptParser.PARSE_LITERALS, ParseContext.PARSE); if (matchResult != null) { Expression[] exprs = matchResult.getExpressions(); - assert patternExpressionPlurals.length == exprs.length; + assert patternExpressions.length == exprs.length; + + if (flatten) { + List values = new ArrayList<>(); + for (int i = 0; i < exprs.length; i++) { + if (exprs[i] != null) { + if (patternExpressions[i].getSecond()) { + values.addAll(Arrays.asList(exprs[i].getArray(null))); + continue; + } + values.add(exprs[i].getSingle(null)); + } + } + return values.toArray(); + } + int nonNullExprCount = 0; for (Expression expr : exprs) { if (expr != null) // Ignore missing optional parts @@ -192,8 +216,7 @@ protected Object[] get(Event event) { for (int i = 0; i < exprs.length; i++) { if (exprs[i] != null) { //noinspection DataFlowIssue - values[valueIndex] = patternExpressionPlurals[i] ? exprs[i].getArray(null) : exprs[i].getSingle(null); - + values[valueIndex] = patternExpressions[i].getSecond() ? exprs[i].getArray(null) : exprs[i].getSingle(null); valueIndex++; } } @@ -221,17 +244,28 @@ protected Object[] get(Event event) { @Override public boolean isSingle() { - return pattern == null || patternHasSingleExpression; + return single; } @Override public Class getReturnType() { - return classInfo != null ? classInfo.getC() : Object.class; + if (classInfo != null) + return classInfo.getC(); + return patternExpressions.length == 1 ? patternExpressions[0].getFirst().getC() : Object.class; } @Override - public String toString(@Nullable Event e, boolean debug) { - return text.toString(e, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); + public String toString(@Nullable Event event, boolean debug) { + return text.toString(event, debug) + " parsed as " + (classInfo != null ? classInfo.toString(Language.F_INDEFINITE_ARTICLE) : pattern); + } + + private static boolean canParse(ClassInfo classInfo) { + Parser parser = classInfo.getParser(); + if (parser == null || !parser.canParse(ParseContext.PARSE)) { + Skript.error("Text cannot be parsed as " + classInfo.getName().withIndefiniteArticle()); + return false; + } + return true; } /** diff --git a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java index 1377337ede1..cb068ae1328 100644 --- a/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java +++ b/src/main/java/ch/njol/skript/hooks/regions/classes/Region.java @@ -66,6 +66,7 @@ public Region parse(String s, final ParseContext context) { quoted = true; break; case COMMAND: + case PARSE: case CONFIG: quoted = false; break; diff --git a/src/main/java/ch/njol/skript/lang/ParseContext.java b/src/main/java/ch/njol/skript/lang/ParseContext.java index e3e648a4f23..7532d16859e 100644 --- a/src/main/java/ch/njol/skript/lang/ParseContext.java +++ b/src/main/java/ch/njol/skript/lang/ParseContext.java @@ -36,6 +36,10 @@ public enum ParseContext { * Only used for parsing arguments of commands */ COMMAND, + /** + * Used for parsing text in {@link ch.njol.skript.expressions.ExprParse} + */ + PARSE, /** * Used for parsing values from a config */ diff --git a/src/main/java/ch/njol/skript/lang/SkriptParser.java b/src/main/java/ch/njol/skript/lang/SkriptParser.java index d5f8ab99732..7f783e7d824 100644 --- a/src/main/java/ch/njol/skript/lang/SkriptParser.java +++ b/src/main/java/ch/njol/skript/lang/SkriptParser.java @@ -317,7 +317,10 @@ private final Expression parseSingleExpr(final boolean allowUnp assert types.length == 1 || !CollectionUtils.contains(types, Object.class); if (expr.isEmpty()) return null; - if (context != ParseContext.COMMAND && expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) + if (context != ParseContext.COMMAND && + context != ParseContext.PARSE && + expr.startsWith("(") && expr.endsWith(")") && + next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, types); final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { @@ -410,7 +413,10 @@ private final Expression parseSingleExpr(final boolean allowUnparsedLiteral, return null; // Command special parsing - if (context != ParseContext.COMMAND && expr.startsWith("(") && expr.endsWith(")") && next(expr, 0, context) == expr.length()) + if (context != ParseContext.COMMAND && + context != ParseContext.PARSE && + expr.startsWith("(") && expr.endsWith(")") && + next(expr, 0, context) == expr.length()) return new SkriptParser(this, "" + expr.substring(1, expr.length() - 1)).parseSingleExpr(allowUnparsedLiteral, error, vi); final ParseLogHandler log = SkriptLogger.startParseLogHandler(); try { @@ -658,7 +664,7 @@ public final Expression parseExpression(final Class parseExpression(final ExprInfo vi) { } } if (i != expr.length()) { - assert i == -1 && context != ParseContext.COMMAND : i + "; " + expr; + assert i == -1 && context != ParseContext.COMMAND && context != ParseContext.PARSE : i + "; " + expr; log.printError("Invalid brackets/variables/text in '" + expr + "'", ErrorQuality.NOT_AN_EXPRESSION); return null; } @@ -1159,7 +1165,7 @@ public static String notOfType(final ClassInfo... cs) { /** * Returns the next character in the expression, skipping strings, * variables and parentheses - * (unless {@code context} is {@link ParseContext#COMMAND}). + * (unless {@code context} is {@link ParseContext#COMMAND} or {@link ParseContext#PARSE}). * * @param expr The expression to traverse. * @param startIndex The index to start at. @@ -1176,7 +1182,7 @@ public static int next(String expr, int startIndex, ParseContext context) { if (startIndex >= exprLength) return -1; - if (context == ParseContext.COMMAND) + if (context == ParseContext.COMMAND || context == ParseContext.PARSE) return startIndex + 1; int j; @@ -1201,7 +1207,8 @@ public static int next(String expr, int startIndex, ParseContext context) { /** * Returns the next occurrence of the needle in the haystack. * Similar to {@link #next(String, int, ParseContext)}, this method skips - * strings, variables and parentheses (unless context is {@link ParseContext#COMMAND}). + * strings, variables and parentheses (unless context is {@link ParseContext#COMMAND} + * or {@link ParseContext#PARSE}). * * @param haystack The string to search in. * @param needle The string to search for. @@ -1214,7 +1221,7 @@ public static int next(String expr, int startIndex, ParseContext context) { public static int nextOccurrence(String haystack, String needle, int startIndex, ParseContext parseContext, boolean caseSensitive) { if (startIndex < 0) throw new StringIndexOutOfBoundsException(startIndex); - if (parseContext == ParseContext.COMMAND) + if (parseContext == ParseContext.COMMAND || parseContext == ParseContext.PARSE) return haystack.indexOf(needle, startIndex); int haystackLength = haystack.length(); @@ -1285,11 +1292,11 @@ private ParseResult parse_i(String pattern, int i, int j) { * @return The pattern with %codenames% and a boolean array that contains whether the expressions are plural or not */ @Nullable - public static NonNullPair validatePattern(final String pattern) { - final List ps = new ArrayList<>(); + public static NonNullPair, Boolean>[]> validatePattern(final String pattern) { + final List, Boolean>> pairs = new ArrayList<>(); int groupLevel = 0, optionalLevel = 0; final Deque groups = new LinkedList<>(); - final StringBuilder b = new StringBuilder(pattern.length()); + final StringBuilder stringBuilder = new StringBuilder(pattern.length()); int last = 0; for (int i = 0; i < pattern.length(); i++) { final char c = pattern.charAt(i); @@ -1332,13 +1339,13 @@ public static NonNullPair validatePattern(final String patter final int j = pattern.indexOf('%', i + 1); if (j == -1) return error("Missing end sign '%' of expression. Escape the percent sign to match a literal '%': '\\%'"); - final NonNullPair p = Utils.getEnglishPlural("" + pattern.substring(i + 1, j)); - final ClassInfo ci = Classes.getClassInfoFromUserInput(p.getFirst()); - if (ci == null) - return error("The type '" + p.getFirst() + "' could not be found. Please check your spelling or escape the percent signs if you want to match literal %s: \"\\%not an expression\\%\""); - ps.add(p.getSecond()); - b.append(pattern.substring(last, i + 1)); - b.append(Utils.toEnglishPlural(ci.getCodeName(), p.getSecond())); + final NonNullPair pair = Utils.getEnglishPlural("" + pattern.substring(i + 1, j)); + final ClassInfo classInfo = Classes.getClassInfoFromUserInput(pair.getFirst()); + if (classInfo == null) + return error("The type '" + pair.getFirst() + "' could not be found. Please check your spelling or escape the percent signs if you want to match literal %s: \"\\%not an expression\\%\""); + pairs.add(new NonNullPair<>(classInfo, pair.getSecond())); + stringBuilder.append(pattern, last, i + 1); + stringBuilder.append(Utils.toEnglishPlural(classInfo.getCodeName(), pair.getSecond())); last = j; i = j; } else if (c == '\\') { @@ -1347,15 +1354,13 @@ public static NonNullPair validatePattern(final String patter i++; } } - b.append(pattern.substring(last)); - final boolean[] plurals = new boolean[ps.size()]; - for (int i = 0; i < plurals.length; i++) - plurals[i] = ps.get(i); - return new NonNullPair<>("" + b.toString(), plurals); + stringBuilder.append(pattern.substring(last)); + //noinspection unchecked + return new NonNullPair<>(stringBuilder.toString(), pairs.toArray(new NonNullPair[0])); } @Nullable - private static NonNullPair error(final String error) { + private static NonNullPair, Boolean>[]> error(final String error) { Skript.error("Invalid pattern: " + error); return null; } diff --git a/src/main/java/ch/njol/skript/lang/Variable.java b/src/main/java/ch/njol/skript/lang/Variable.java index 0b3fc39aa1e..619418a1743 100644 --- a/src/main/java/ch/njol/skript/lang/Variable.java +++ b/src/main/java/ch/njol/skript/lang/Variable.java @@ -542,7 +542,7 @@ public void change(Event e, @Nullable Object[] delta, ChangeMode mode) throws Un for (Object d : delta) { if (d instanceof Object[]) { for (int j = 0; j < ((Object[]) d).length; j++) { - setIndex(e, "" + i + SEPARATOR + j, ((Object[]) d)[j]); + setIndex(e, "" + i + SEPARATOR + (j + 1), ((Object[]) d)[j]); } } else { setIndex(e, "" + i, d); diff --git a/src/main/java/ch/njol/skript/registrations/Classes.java b/src/main/java/ch/njol/skript/registrations/Classes.java index a2958b4cd04..fdb5ba2544c 100644 --- a/src/main/java/ch/njol/skript/registrations/Classes.java +++ b/src/main/java/ch/njol/skript/registrations/Classes.java @@ -500,7 +500,7 @@ public static T parse(final String s, final Class c, final ParseContext c return t; } for (final ConverterInfo conv : Converters.getConverterInfos()) { - if (context == ParseContext.COMMAND && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0) + if ((context == ParseContext.COMMAND || context == ParseContext.PARSE) && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0) continue; if (c.isAssignableFrom(conv.getTo())) { log.clear(); diff --git a/src/test/skript/tests/syntaxes/expressions/ExprParse.sk b/src/test/skript/tests/syntaxes/expressions/ExprParse.sk new file mode 100644 index 00000000000..4476ecf4512 --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprParse.sk @@ -0,0 +1,16 @@ +test "ExprParse": + assert "1" parsed as "%integer%" where [1 is 1] exists to fail with "Parsing as one single expression isn't single" + assert "1, 2" parsed as "%integers%" where [1 is 1] exists with "Parsing as one plural expression is single" + assert "1, 2" parsed as "%integer%, %integer%" where [1 is 1] exists with "Parsing as multiple expression is single" + + assert "hello" parsed as "%object%" exists to fail with "Parsing as a type that can't be parsed should fail" + assert "hello" parsed as object exists to fail with "Parsing as a type that can't be parsed should fail" + + assert "1" parsed as integer is 1 with "Failed parsing" + assert "1" parsed as "%integer%" is 1 with "Failed parsing with one single expression" + assert "1, 2" parsed as "%integers%" is 1 or 2 with "Failed parsing with one plural expression" + assert "1, 2" parsed as "%integer%, %integer%" is 1 or 2 with "Failed parsing with two single expressions" + + set {_parse::*} to "1, 2" parsed as "%integers%" + assert {_parse::1::*} is 1 or 2 with "Setting list to plural expression in parsing doesn't create sublist" + assert indices of {_parse::1::*} is "1" or "2" with "Sublist doesn't start from 1"