From 3cd421ee25a71338b1ff251900f5f2199bf67e83 Mon Sep 17 00:00:00 2001 From: Moderocky Date: Tue, 3 Sep 2024 12:54:38 +0100 Subject: [PATCH] Pick first. --- .../ch/njol/skript/registrations/Feature.java | 1 + .../java/ch/njol/skript/sections/SecFor.java | 197 ++++++++++++++++++ .../java/ch/njol/skript/sections/SecLoop.java | 40 ++-- 3 files changed, 218 insertions(+), 20 deletions(-) create mode 100644 src/main/java/ch/njol/skript/sections/SecFor.java diff --git a/src/main/java/ch/njol/skript/registrations/Feature.java b/src/main/java/ch/njol/skript/registrations/Feature.java index 7b9f19b1d7f..baf7e2c9114 100644 --- a/src/main/java/ch/njol/skript/registrations/Feature.java +++ b/src/main/java/ch/njol/skript/registrations/Feature.java @@ -29,6 +29,7 @@ * Experimental feature toggles as provided by Skript itself. */ public enum Feature implements Experiment { + FOR_EACH_LOOPS("for loop", LifeCycle.EXPERIMENTAL, "for [each] [loop[s]]") ; private final String codeName; diff --git a/src/main/java/ch/njol/skript/sections/SecFor.java b/src/main/java/ch/njol/skript/sections/SecFor.java new file mode 100644 index 00000000000..b4d99b95584 --- /dev/null +++ b/src/main/java/ch/njol/skript/sections/SecFor.java @@ -0,0 +1,197 @@ +/** + * This file is part of Skript. + * + * Skript is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Skript is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Skript. If not, see . + * + * Copyright Peter Güttinger, SkriptLang team and contributors + */ +package ch.njol.skript.sections; + +import ch.njol.skript.Skript; +import ch.njol.skript.SkriptAPIException; +import ch.njol.skript.classes.Changer; +import ch.njol.skript.config.SectionNode; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.TriggerItem; +import ch.njol.skript.lang.Variable; +import ch.njol.skript.lang.util.ContainerExpression; +import ch.njol.skript.util.Container; +import ch.njol.skript.util.Container.ContainerType; +import ch.njol.skript.util.LiteralUtils; +import ch.njol.util.Kleenean; +import org.bukkit.event.Event; +import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.experiment.Feature; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +@Name("For Each Loop (Experimental)") +@Description({ + "A specialised loop section run for each element in a list.", + "Unlike the basic loop, this is designed for extracting the key & value from pairs.", + "The loop element's key/index and value can be stored in a variable for convenience." +}) +@Examples({ + "for each {_player} in players:", + "\tsend \"Hello %{_player}%!\" to {_player}", + "", + "for each {_item} in {list of items::*}:", + "\tbroadcast {_item}'s name", + "", + "for each key {_index} in {list of items::*}:", + "\tbroadcast {_index}", + "", + "for each key {_index} and value {_value} in {list of items::*}:", + "\tbroadcast \"%{_index}% = %{_value}%\"", + "", + "for each {_index} = {_value} in {my list::*}:", + "\tbroadcast \"%{_index}% = %{_value}%\"", +}) +@Since("INSERT VERSION") +public class SecFor extends SecLoop { + + static { + Skript.registerSection(SecFor.class, + "for [each] [value] %~object% in %objects%", + "for [each] (key|index) %~object% in %objects%", + "for [each] [key|index] %~object% (=|and) [value] %~object% in %objects%" + ); + } + + private @Nullable Expression keyStore, valueStore; + private boolean isVariable; + + @Override + @SuppressWarnings("unchecked") + public boolean init(Expression[] exprs, + int matchedPattern, + Kleenean isDelayed, + ParseResult parseResult, + SectionNode sectionNode, + List triggerItems) { + if (!this.hasExperiment(Feature.FOR_EACH_LOOPS)) + return false; + // + switch (matchedPattern) { + case 0: + this.valueStore = exprs[0]; + this.expression = LiteralUtils.defendExpression(exprs[1]); + break; + case 1: + this.keyStore = exprs[0]; + this.expression = LiteralUtils.defendExpression(exprs[1]); + break; + default: + this.keyStore = exprs[0]; + this.valueStore = exprs[1]; + this.expression = LiteralUtils.defendExpression(exprs[2]); + } + this.isVariable = expression instanceof Variable; + // + // + if (!(keyStore instanceof Variable || keyStore == null)) { + Skript.error("The 'key' input for a for-loop must be a variable to store the value."); + return false; + } + if (!(valueStore instanceof Variable || valueStore == null)) { + Skript.error("The 'value' input for a for-loop must be a variable to store the value."); + return false; + } + if (!LiteralUtils.canInitSafely(expression)) { + Skript.error("Can't understand this loop: '" + parseResult.expr + "'"); + return false; + } + if (Container.class.isAssignableFrom(expression.getReturnType())) { + ContainerType type = expression.getReturnType().getAnnotation(ContainerType.class); + if (type == null) + throw new SkriptAPIException(expression.getReturnType().getName() + " implements Container but is missing the required @ContainerType annotation"); + this.expression = new ContainerExpression((Expression>) expression, type.value()); + } + if (expression.isSingle()) { + Skript.error("Can't loop '" + expression + "' because it's only a single value"); + return false; + } + // + this.loadOptionalCode(sectionNode); + super.setNext(this); + return true; + } + + @Override + @Nullable + protected TriggerItem walk(Event event) { + // + Iterator iter = super.iteratorMap.get(event); + if (iter == null) { + iter = this.isVariable + ? ((Variable) super.expression).variablesIterator(event) + : super.expression.iterator(event); + if (iter != null) { + if (iter.hasNext()) + super.iteratorMap.put(event, iter); + else + iter = null; + } + } + // + if (iter == null || !iter.hasNext()) { + this.exit(event); + this.debug(event, false); + return actualNext; + } else { + Object next = iter.next(); + super.current.put(event, next); + super.currentLoopCounter.put(event, (currentLoopCounter.getOrDefault(event, 0L)) + 1); + // + if (next instanceof Map.Entry) { + @SuppressWarnings("unchecked") Map.Entry entry = (Map.Entry) next; + if (keyStore != null) + this.keyStore.change(event, new Object[]{entry.getKey()}, Changer.ChangeMode.SET); + if (valueStore != null) + this.valueStore.change(event, new Object[]{entry.getValue()}, Changer.ChangeMode.SET); + } else { + if (keyStore != null) + this.keyStore.change(event, new Object[]{this.getLoopCounter(event)}, Changer.ChangeMode.SET); + if (valueStore != null) + this.valueStore.change(event, new Object[]{next}, Changer.ChangeMode.SET); + } + // + return this.walk(event, true); + } + } + + @Override + public String toString(@Nullable Event event, boolean debug) { + if (keyStore != null && valueStore != null) { + return "for each key " + keyStore.toString(event, debug) + + " and value " + valueStore.toString(event, debug) + " in " + + super.expression.toString(event, debug); + } + else if (keyStore != null) { + return "for each key " + keyStore.toString(event, debug) + " in " + + super.expression.toString(event, debug); + } + assert valueStore != null: "How did we get here?"; + return "for each value " + valueStore.toString(event, debug) + " in " + + super.expression.toString(event, debug); + } + +} diff --git a/src/main/java/ch/njol/skript/sections/SecLoop.java b/src/main/java/ch/njol/skript/sections/SecLoop.java index 8f0682d2bea..a753a513354 100644 --- a/src/main/java/ch/njol/skript/sections/SecLoop.java +++ b/src/main/java/ch/njol/skript/sections/SecLoop.java @@ -36,7 +36,8 @@ import ch.njol.skript.util.LiteralUtils; import ch.njol.util.Kleenean; import org.bukkit.event.Event; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import java.util.Iterator; import java.util.List; @@ -86,14 +87,13 @@ public class SecLoop extends LoopSection { Skript.registerSection(SecLoop.class, "loop %objects%"); } - @SuppressWarnings("NotNullFieldNotInitialized") - private Expression expr; + protected @UnknownNullability Expression expression; - private final transient Map current = new WeakHashMap<>(); - private final transient Map> currentIter = new WeakHashMap<>(); + protected final transient Map current = new WeakHashMap<>(); + protected final transient Map> iteratorMap = new WeakHashMap<>(); @Nullable - private TriggerItem actualNext; + protected TriggerItem actualNext; @Override @SuppressWarnings("unchecked") @@ -103,21 +103,21 @@ public boolean init(Expression[] exprs, ParseResult parseResult, SectionNode sectionNode, List triggerItems) { - expr = LiteralUtils.defendExpression(exprs[0]); - if (!LiteralUtils.canInitSafely(expr)) { + expression = LiteralUtils.defendExpression(exprs[0]); + if (!LiteralUtils.canInitSafely(expression)) { Skript.error("Can't understand this loop: '" + parseResult.expr.substring(5) + "'"); return false; } - if (Container.class.isAssignableFrom(expr.getReturnType())) { - ContainerType type = expr.getReturnType().getAnnotation(ContainerType.class); + if (Container.class.isAssignableFrom(expression.getReturnType())) { + ContainerType type = expression.getReturnType().getAnnotation(ContainerType.class); if (type == null) - throw new SkriptAPIException(expr.getReturnType().getName() + " implements Container but is missing the required @ContainerType annotation"); - expr = new ContainerExpression((Expression>) expr, type.value()); + throw new SkriptAPIException(expression.getReturnType().getName() + " implements Container but is missing the required @ContainerType annotation"); + expression = new ContainerExpression((Expression>) expression, type.value()); } - if (expr.isSingle()) { - Skript.error("Can't loop '" + expr + "' because it's only a single value"); + if (expression.isSingle()) { + Skript.error("Can't loop '" + expression + "' because it's only a single value"); return false; } @@ -130,12 +130,12 @@ public boolean init(Expression[] exprs, @Override @Nullable protected TriggerItem walk(Event event) { - Iterator iter = currentIter.get(event); + Iterator iter = iteratorMap.get(event); if (iter == null) { - iter = expr instanceof Variable ? ((Variable) expr).variablesIterator(event) : expr.iterator(event); + iter = expression instanceof Variable ? ((Variable) expression).variablesIterator(event) : expression.iterator(event); if (iter != null) { if (iter.hasNext()) - currentIter.put(event, iter); + iteratorMap.put(event, iter); else iter = null; } @@ -153,7 +153,7 @@ protected TriggerItem walk(Event event) { @Override public String toString(@Nullable Event event, boolean debug) { - return "loop " + expr.toString(event, debug); + return "loop " + expression.toString(event, debug); } @Nullable @@ -162,7 +162,7 @@ public Object getCurrent(Event event) { } public Expression getLoopedExpression() { - return expr; + return expression; } @Override @@ -180,7 +180,7 @@ public TriggerItem getActualNext() { @Override public void exit(Event event) { current.remove(event); - currentIter.remove(event); + iteratorMap.remove(event); super.exit(event); }