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 extends Container>>) 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 extends Container>>) expr, type.value());
+ throw new SkriptAPIException(expression.getReturnType().getName() + " implements Container but is missing the required @ContainerType annotation");
+ expression = new ContainerExpression((Expression extends Container>>) 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);
}
diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/Feature.java b/src/main/java/org/skriptlang/skript/lang/experiment/Feature.java
index 66363ded879..f071548b660 100644
--- a/src/main/java/org/skriptlang/skript/lang/experiment/Feature.java
+++ b/src/main/java/org/skriptlang/skript/lang/experiment/Feature.java
@@ -28,6 +28,7 @@
* @author moderocky
*/
public enum Feature implements Experiment {
+ FOR_EACH_LOOPS("for loop", LifeCycle.EXPERIMENTAL, "for [each] [loop[s]]")
;
private final String codeName;