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 13 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
95 changes: 60 additions & 35 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,90 +47,118 @@
})
@Since("2.2, 2.8.0 (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 trigger.");
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved
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.setReturnValues(value.getArray(event));

TriggerSection parent = getParent();
while (parent != null) {
while (parent != null && parent != section) {
if (parent instanceof SectionExitHandler)
((SectionExitHandler) parent).exit(event);

parent = parent.getParent();
}

if (section instanceof SectionExitHandler)
((SectionExitHandler) section).exit(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);
}


/**
* Used to define the return data of a trigger.
*/
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;
}

}

}
44 changes: 43 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 * the section should run. If you have stored a {@link Trigger} from {@link #loadCode(SectionNode, String, Class[])}, you
* should not run it with this event passed in this walk method.
Expand Down Expand Up @@ -134,12 +139,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> previousReturnStack = 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 @@ -150,6 +157,7 @@ protected final Trigger loadCode(SectionNode sectionNode, String name, @Nullable
parser.setCurrentStructure(previousStructure);
parser.setCurrentSections(previousSections);
parser.setHasDelayBefore(previousDelay);
parser.setReturnStack(previousReturnStack);

return new Trigger(parser.getCurrentScript(), name, skriptEvent, triggerItems);
}
Expand All @@ -170,6 +178,40 @@ 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 onto 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();
}
}

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

@Nullable
@SuppressWarnings({"unchecked", "rawtypes"})
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>> loadItems) {
this.script = script;
this.name = name;
this.event = event;
this.debugLabel = "unknown trigger";
setTriggerItems(loadItems.apply(this));
}

/**
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/ch/njol/skript/lang/TriggerSection.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import ch.njol.skript.lang.parser.ParserInstance;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;
import org.skriptlang.skript.lang.converter.Converters;

import java.util.List;

Expand All @@ -34,6 +35,9 @@ public abstract class TriggerSection extends TriggerItem {
@Nullable
protected TriggerItem first, last;

private boolean returnValueSet;
private Object @Nullable [] returnValue;

/**
* Reserved for new Trigger(...)
*/
Expand Down Expand Up @@ -91,6 +95,30 @@ public TriggerSection setParent(@Nullable TriggerSection parent) {
return this;
}

public Object @Nullable [] getReturnValues() {
return returnValue;
}

/**
* Returns the return values of the trigger execution, converting them to the specified type.
* @param expectedType the type to convert to.
* @return the return values. May be null if no return values were provided.
*/
public <T> T @Nullable [] getReturnValues(Class<T> expectedType) {
return Converters.convert(getReturnValues(), expectedType);
}

public void setReturnValues(Object @Nullable [] returnValue) {
assert !returnValueSet;
returnValueSet = true;
this.returnValue = returnValue;
}

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

@Override
protected final boolean run(Event event) {
throw new UnsupportedOperationException();
Expand Down
Loading
Loading