Skip to content

Commit

Permalink
Add for loop section.
Browse files Browse the repository at this point in the history
  • Loading branch information
Moderocky committed Apr 13, 2024
1 parent 705f6a6 commit 6513ec2
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 20 deletions.
197 changes: 197 additions & 0 deletions src/main/java/ch/njol/skript/sections/SecFor.java
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*
* 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<TriggerItem> triggerItems) {
if (!this.hasExperiment(Feature.FOR_EACH_LOOPS))
return false;
//<editor-fold desc="Set the key/value expressions based on the pattern" defaultstate="collapsed">
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;
//</editor-fold>
//<editor-fold desc="Check our input expressions are safe/correct" defaultstate="collapsed">
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;
}
//</editor-fold>
this.loadOptionalCode(sectionNode);
super.setNext(this);
return true;
}

@Override
@Nullable
protected TriggerItem walk(Event event) {
//<editor-fold desc="Get the iterator (duplicate of SecLoop)" defaultstate="collapsed">
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;
}
}
//</editor-fold>
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);
//<editor-fold desc="Store the loop index/value in the variables" defaultstate="collapsed">
if (next instanceof Map.Entry) {
@SuppressWarnings("unchecked") Map.Entry<String, Object> entry = (Map.Entry<String, Object>) 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);
}
//</editor-fold>
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);
}

}
40 changes: 20 additions & 20 deletions src/main/java/ch/njol/skript/sections/SecLoop.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<Event, Object> current = new WeakHashMap<>();
private final transient Map<Event, Iterator<?>> currentIter = new WeakHashMap<>();
protected final transient Map<Event, Object> current = new WeakHashMap<>();
protected final transient Map<Event, Iterator<?>> iteratorMap = new WeakHashMap<>();

@Nullable
private TriggerItem actualNext;
protected TriggerItem actualNext;

@Override
@SuppressWarnings("unchecked")
Expand All @@ -103,21 +103,21 @@ public boolean init(Expression<?>[] exprs,
ParseResult parseResult,
SectionNode sectionNode,
List<TriggerItem> 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;
}

Expand All @@ -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;
}
Expand All @@ -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
Expand All @@ -162,7 +162,7 @@ public Object getCurrent(Event event) {
}

public Expression<?> getLoopedExpression() {
return expr;
return expression;
}

@Override
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 6513ec2

Please sign in to comment.