Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return API #6118

Merged
merged 24 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
906412e
Return API
UnderscoreTud Oct 12, 2023
b6df369
Re-add `Functions.currentFunction` field for backward compatibility
UnderscoreTud Oct 12, 2023
cd7ec47
Update EffReturn's description
UnderscoreTud Oct 12, 2023
b900f48
Fix tests
UnderscoreTud Oct 13, 2023
f559dad
Fix javadocs
UnderscoreTud Oct 16, 2023
f6a53fb
Use the proper term. 'queue' -> 'stack'
UnderscoreTud Oct 16, 2023
8486ceb
Merge branch 'dev/feature' into feature/return-api
UnderscoreTud May 10, 2024
ad2f0b1
Add missing import
UnderscoreTud May 10, 2024
1413626
Only exit upto the returnable section when returning rather than the …
UnderscoreTud May 10, 2024
911e5c9
Requested Changes
UnderscoreTud May 10, 2024
4e3a466
change `function` to `trigger`
UnderscoreTud May 13, 2024
0056f44
Make TriggerSection#setReturnValues and TriggerSection#resetReturnVal…
UnderscoreTud May 13, 2024
3b269c4
Merge branch 'dev/feature' into feature/return-api
UnderscoreTud May 13, 2024
f09b0c1
Replace `ReturnData` with a new interface
UnderscoreTud May 14, 2024
8cded0e
Requested Changes
UnderscoreTud May 14, 2024
bcdc5db
Requested Changes
UnderscoreTud May 15, 2024
a79571b
Requested Changes
UnderscoreTud May 15, 2024
6a65ffe
Merge branch 'dev/feature' into feature/return-api
UnderscoreTud May 25, 2024
5d75baf
Add tests
UnderscoreTud May 25, 2024
6990bcb
Merge branch 'dev/feature' into feature/return-api
UnderscoreTud Jun 1, 2024
c6c98a9
Use parser instance backups
UnderscoreTud Jun 1, 2024
82f0d23
Merge branch 'dev/feature' into feature/return-api
UnderscoreTud Jun 11, 2024
bc31aaa
Make ReturnHandler#returnValueType return a Class object rather than …
UnderscoreTud Jun 14, 2024
d7d2275
Merge remote-tracking branch 'origin/feature/return-api' into feature…
UnderscoreTud Jun 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 53 additions & 34 deletions src/main/java/ch/njol/skript/effects/EffReturn.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,14 @@
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.TriggerItem;
import ch.njol.skript.lang.TriggerSection;
import ch.njol.skript.lang.function.FunctionEvent;
import ch.njol.skript.lang.function.Functions;
import ch.njol.skript.lang.function.ScriptFunction;
import ch.njol.skript.log.RetainingLogHandler;
import ch.njol.skript.log.SkriptLogger;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;

@Name("Return")
@Description("Makes a function return a value")
@Description("Makes a trigger (e.g. a function) return a value")
@Examples({
"function double(i: number) :: number:",
"\treturn 2 * {_i}",
Expand All @@ -50,70 +47,64 @@
})
@Since("2.2, INSERT VERSION (returns aliases)")
public class EffReturn extends Effect {

static {
Skript.registerEffect(EffReturn.class, "return %objects%");
}

@SuppressWarnings("NotNullFieldNotInitialized")
private ScriptFunction<?> function;

private TriggerSection section;
@SuppressWarnings("NotNullFieldNotInitialized")
private Expression<?> value;

@SuppressWarnings("unchecked")

@Override
@SuppressWarnings("unchecked")
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
ScriptFunction<?> f = Functions.currentFunction;
if (f == null) {
Skript.error("The return statement can only be used in a function");
ReturnData data = getParser().getCurrentReturnData();
if (data == null) {
Skript.error("The return statement cannot be used here");
return false;
}

if (!isDelayed.isFalse()) {
Skript.error("A return statement after a delay is useless, as the calling trigger will resume when the delay starts (and won't get any returned value)");
return false;
}
function = f;
ClassInfo<?> returnType = function.getReturnType();

section = data.getSection();
ClassInfo<?> returnType = data.getReturnType();
if (returnType == null) {
Skript.error("This function doesn't return any value. Please use 'stop' or 'exit' if you want to stop the function.");
Skript.error("This trigger doesn't return any value. Please use 'stop' or 'exit' if you want to stop the function.");
return false;
}

RetainingLogHandler log = SkriptLogger.startRetainingLog();
Expression<?> convertedExpr;
try {
convertedExpr = exprs[0].getConvertedExpression(returnType.getC());
if (convertedExpr == null) {
log.printErrors("This function is declared to return " + returnType.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type.");
log.printErrors("This trigger is declared to return " + returnType.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type.");
return false;
}
log.printLog();
} finally {
log.stop();
}
if (f.isSingle() && !convertedExpr.isSingle()) {
Skript.error("This function is defined to only return a single " + returnType.toString() + ", but this return statement can return multiple values.");

if (data.isSingle() && !convertedExpr.isSingle()) {
Skript.error("This trigger is defined to only return a single " + returnType + ", but this return statement can return multiple values.");
return false;
}
value = convertedExpr;

return true;
}

@Override
@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
protected TriggerItem walk(Event event) {
debug(event, false);
if (event instanceof FunctionEvent) {
((ScriptFunction) function).setReturnValue(value.getArray(event));
} else {
assert false : event;
}
section.setReturnValue(value.getArray(event));

TriggerSection parent = getParent();
while (parent != null) {
Expand All @@ -125,15 +116,43 @@ protected TriggerItem walk(Event event) {

return null;
}

@Override
protected void execute(Event event) {
assert false;
}

@Override
public String toString(@Nullable Event event, boolean debug) {
return "return " + value.toString(event, debug);
}


public static class ReturnData {
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved

private final TriggerSection section;
@Nullable
private final ClassInfo<?> returnType;
private final boolean single;

public ReturnData(TriggerSection section, @Nullable ClassInfo<?> returnType, boolean single) {
this.section = section;
this.returnType = returnType;
this.single = single;
}

public TriggerSection getSection() {
return section;
}

@Nullable
public ClassInfo<?> getReturnType() {
return returnType;
}

public boolean isSingle() {
return single;
}

}

}
27 changes: 26 additions & 1 deletion src/main/java/ch/njol/skript/lang/Section.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.Skript;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.effects.EffReturn.ReturnData;
import ch.njol.skript.lang.SkriptParser.ParseResult;
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.util.Kleenean;
Expand All @@ -29,15 +31,18 @@
import org.skriptlang.skript.lang.structure.Structure;

import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;

/**
* A section that can decide what it does with its contents, as code isn't parsed by default.
* <br><br>
* In most cases though, a section should load its code through one of the following loading methods:
* {@link #loadCode(SectionNode)}, {@link #loadCode(SectionNode, String, Class[])}, {@link #loadOptionalCode(SectionNode)}
* {@link #loadCode(SectionNode)}, {@link #loadCode(SectionNode, String, Class[])}, {@link #loadOptionalCode(SectionNode)},
* {@link #loadReturnableCode(SectionNode, ClassInfo, boolean)}
* <br><br>
* Every section must override the {@link TriggerSection#walk(Event)} method. In this method, you can determine whether
* or not the section should run. If you have stored a {@link Trigger} from {@link #loadCode(SectionNode, String, Class[])}, you
Expand Down Expand Up @@ -135,12 +140,14 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, @Nullable
Structure previousStructure = parser.getCurrentStructure();
List<TriggerSection> previousSections = parser.getCurrentSections();
Kleenean previousDelay = parser.getHasDelayBefore();
Deque<ReturnData> previousReturnQueue = parser.getReturnStack();

parser.setCurrentEvent(name, events);
SkriptEvent skriptEvent = new SectionSkriptEvent(name, this);
parser.setCurrentStructure(skriptEvent);
parser.setCurrentSections(new ArrayList<>());
parser.setHasDelayBefore(Kleenean.FALSE);
parser.setReturnStack(new LinkedList<>());
List<TriggerItem> triggerItems = ScriptLoader.loadItems(sectionNode);

if (afterLoading != null)
Expand All @@ -151,6 +158,7 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, @Nullable
parser.setCurrentStructure(previousStructure);
parser.setCurrentSections(previousSections);
parser.setHasDelayBefore(previousDelay);
parser.setReturnStack(previousReturnQueue);

return new Trigger(parser.getCurrentScript(), name, skriptEvent, triggerItems);
}
Expand All @@ -171,6 +179,23 @@ protected void loadOptionalCode(SectionNode sectionNode) {
getParser().setHasDelayBefore(Kleenean.UNKNOWN);
}

/**
* Loads the code using {@link Section#loadCode(SectionNode)}.
* <br>
* This method also pushes the current trigger into the return stack,
* and pops it once it's done loading.
* @see ParserInstance#getReturnStack()
* @see ParserInstance#pushReturnData(ReturnData)
*/
protected void loadReturnableCode(SectionNode sectionNode, @Nullable ClassInfo<?> returnType, boolean single) {
try {
getParser().pushReturnData(new ReturnData(this, returnType, single));
loadCode(sectionNode);
} finally {
getParser().popReturnData();
}
}

@SuppressWarnings({"unchecked", "rawtypes"})
@Nullable
public static Section parse(String expr, @Nullable String defaultError, SectionNode sectionNode, List<TriggerItem> triggerItems) {
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/ch/njol/skript/lang/Trigger.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.eclipse.jdt.annotation.Nullable;

import java.util.List;
import java.util.function.Function;

public class Trigger extends TriggerSection {

Expand All @@ -36,11 +37,15 @@ public class Trigger extends TriggerSection {
private String debugLabel;

public Trigger(@Nullable Script script, String name, SkriptEvent event, List<TriggerItem> items) {
super(items);
this(script, name, event, trigger -> items);
}

public Trigger(@Nullable Script script, String name, SkriptEvent event, Function<Trigger, List<TriggerItem>> function) {
this.script = script;
this.name = name;
this.event = event;
this.debugLabel = "unknown trigger";
setTriggerItems(function.apply(this));
}

/**
Expand Down
32 changes: 30 additions & 2 deletions src/main/java/ch/njol/skript/lang/TriggerSection.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@

import java.util.List;

import ch.njol.util.coll.CollectionUtils;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.lang.parser.ParserInstance;
import org.jetbrains.annotations.ApiStatus;

/**
* Represents a section of a trigger, e.g. a conditional or a loop
Expand All @@ -36,7 +38,10 @@ public abstract class TriggerSection extends TriggerItem {
protected TriggerItem first = null;
@Nullable
protected TriggerItem last = null;
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved


private boolean returnValueSet = false;
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved
private Object @Nullable [] returnValue;

/**
* Reserved for new Trigger(...)
*/
Expand Down Expand Up @@ -93,7 +98,30 @@ public TriggerSection setParent(@Nullable TriggerSection parent) {
super.setParent(parent);
return this;
}


public Object @Nullable [] getReturnValue() {
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved
return returnValue;
}

public <T> T @Nullable [] getReturnValue(Class<T> expectedType) {
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved
return CollectionUtils.arrayType(expectedType).cast(getReturnValue());
}

/**
* Should only be called by {@link ch.njol.skript.effects.EffReturn}.
*/
@ApiStatus.Internal
public final void setReturnValue(Object @Nullable [] returnValue) {
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved
assert !returnValueSet;
returnValueSet = true;
this.returnValue = returnValue;
}

public final void resetReturnValue() {
returnValueSet = false;
returnValue = null;
}

@Override
protected final boolean run(Event e) {
throw new UnsupportedOperationException();
Expand Down
27 changes: 14 additions & 13 deletions src/main/java/ch/njol/skript/lang/function/ScriptFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package ch.njol.skript.lang.function;

import ch.njol.skript.lang.parser.ParserInstance;
import org.skriptlang.skript.lang.script.Script;
import org.eclipse.jdt.annotation.Nullable;

Expand All @@ -37,32 +38,33 @@ public class ScriptFunction<T> extends Function<T> {

public ScriptFunction(Signature<T> sign, Script script, SectionNode node) {
super(sign);


ParserInstance parser = ParserInstance.get();
Functions.currentFunction = this;
try {
trigger = new Trigger(
script,
"function " + sign.getName(),
new SimpleEvent(),
ScriptLoader.loadItems(node)
trigger -> {
parser.pushReturnData(trigger, getReturnType(), isSingle());
return ScriptLoader.loadItems(node);
}
);
trigger.setLineNumber(node.getLine());
} finally {
Functions.currentFunction = null;
parser.popReturnData();
}
trigger.setLineNumber(node.getLine());
}

private boolean returnValueSet = false;
@Nullable
private T[] returnValue = null;

/**
* Should only be called by {@link EffReturn}.
* @deprecated Use {@link ch.njol.skript.lang.TriggerSection#setReturnValue(Object[])}
*/
@Deprecated
public final void setReturnValue(final @Nullable T[] value) {
assert !returnValueSet;
returnValueSet = true;
returnValue = value;
trigger.setReturnValue(value);
}

// REMIND track possible types of local variables (including undefined variables) (consider functions, commands, and EffChange) - maybe make a general interface for this purpose
Expand All @@ -84,13 +86,12 @@ public T[] execute(final FunctionEvent<?> e, final Object[][] params) {
}

trigger.execute(e);
return returnValue;
return getReturnType() == null ? null : trigger.getReturnValue(getReturnType().getC());
}

@Override
public boolean resetReturnValue() {
returnValue = null;
returnValueSet = false;
trigger.resetReturnValue();
return true;
}

Expand Down
Loading