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 15 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
68 changes: 33 additions & 35 deletions src/main/java/ch/njol/skript/effects/EffReturn.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,21 @@
import ch.njol.skript.doc.Since;
import ch.njol.skript.lang.Effect;
import ch.njol.skript.lang.Expression;
import ch.njol.skript.lang.ReturnHandler;
import ch.njol.skript.lang.ReturnHandler.ReturnHandlerStack;
import ch.njol.skript.lang.SectionExitHandler;
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.lang.parser.ParserInstance;
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 +50,88 @@
})
@Since("2.2, 2.8.0 (returns aliases)")
public class EffReturn extends Effect {

static {
Skript.registerEffect(EffReturn.class, "return %objects%");
ParserInstance.registerData(ReturnHandlerStack.class, ReturnHandlerStack::new);
}

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

private ReturnHandler<?> handler;
@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");
handler = getParser().getData(ReturnHandlerStack.class).getCurrentHandler();
if (handler == 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();

ClassInfo<?> returnType = handler.returnValueType();
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 (handler.singleReturnValue() && !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;
}
//noinspection rawtypes,unchecked
((ReturnHandler) handler).returnValues(value.getArray(event));

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

parent = parent.getParent();
}

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

}
205 changes: 205 additions & 0 deletions src/main/java/ch/njol/skript/lang/ReturnHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/**
* 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.lang;

import ch.njol.skript.ScriptLoader;
import ch.njol.skript.SkriptAPIException;
import ch.njol.skript.classes.ClassInfo;
import ch.njol.skript.config.SectionNode;
import ch.njol.skript.lang.parser.ParserInstance;
import ch.njol.skript.lang.util.SimpleEvent;
import ch.njol.util.Kleenean;
import org.bukkit.event.Event;
import org.eclipse.jdt.annotation.Nullable;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import org.skriptlang.skript.lang.structure.Structure;

import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

public interface ReturnHandler<T> {
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved

/**
* Loads the code in the given {@link SectionNode} using the same logic as
* {@link Section#loadCode(SectionNode)} and pushes the section onto the
* return handler stack
* <br>
* <b>This method may only be called by a {@link Section}</b>
* @throws SkriptAPIException if this return handler is not a {@link Section}
*/
@NonExtendable
default void loadReturnableSectionCode(SectionNode node) {
if (!(this instanceof Section))
throw new SkriptAPIException("loadReturnableSectionCode called on a non-section object");
ParserInstance parser = ParserInstance.get();
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);
stack.push(this);
Section section = (Section) this;
try {
section.loadCode(node);
} finally {
stack.pop();
}
}

/**
* Loads the code in the given {@link SectionNode} using the same logic as
* {@link Section#loadCode(SectionNode, String, Class[])} and pushes the section onto the
* return handler stack
* <br>
* <b>This method may only be called by a {@link Section}</b>
* @param node the section node
* @param name the name of the event(s) being used
* @param events the event(s) during the section's execution
* @return a returnable trigger containing the loaded section.
* This should be stored and used to run the section one or more times
* @throws SkriptAPIException if this return handler is not a {@link Section}
*/
@NonExtendable
default ReturnableTrigger<T> loadReturnableSectionCode(SectionNode node, String name, Class<? extends Event>[] events) {
if (!(this instanceof Section))
throw new SkriptAPIException("loadReturnableSectionCode called on a non-section object");
ParserInstance parser = ParserInstance.get();
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);

SkriptEvent skriptEvent = new SectionSkriptEvent(name, (Section) this);
String previousName = parser.getCurrentEventName();
Class<? extends Event>[] previousEvents = parser.getCurrentEvents();
Structure previousStructure = parser.getCurrentStructure();
List<TriggerSection> previousSections = parser.getCurrentSections();
Kleenean previousDelay = parser.getHasDelayBefore();

parser.setCurrentEvent(name, events);
parser.setCurrentStructure(skriptEvent);
parser.setCurrentSections(new ArrayList<>());
parser.setHasDelayBefore(Kleenean.FALSE);
try {
return new ReturnableTrigger<>(
this,
parser.getCurrentScript(),
name,
skriptEvent,
trigger -> {
stack.push(trigger);
return ScriptLoader.loadItems(node);
}
);
} finally {
stack.pop();
parser.setCurrentEvent(previousName, previousEvents);
parser.setCurrentStructure(previousStructure);
parser.setCurrentSections(previousSections);
parser.setHasDelayBefore(previousDelay);
}
}

/**
* Loads the code in the given {@link SectionNode} into a {@link ReturnableTrigger}.
* <br>
* This is a general method to load a section node without extra logic
* done to the {@link ParserInstance}
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved
* @param node the section node to load
* @param name the name of the trigger
* @param event the {@link SkriptEvent} of the trigger
* @return a returnable trigger containing the loaded section node
*/
@NonExtendable
default ReturnableTrigger<T> loadReturnableTrigger(SectionNode node, String name, SkriptEvent event) {
ParserInstance parser = ParserInstance.get();
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);
try {
return new ReturnableTrigger<T>(
this,
parser.getCurrentScript(),
name,
event,
trigger -> {
stack.push(trigger);
return ScriptLoader.loadItems(node);
}
);
} finally {
stack.pop();
}
}

/**
* @param values the values to return
*/
void returnValues(T @Nullable [] values);

/**
* @return whether this return handler may accept multiple return values
*/
boolean singleReturnValue();

/**
* The return type of this return handler, or null if it can't
* accept return values in this context (e.g. a function without a return type).
* @return the return type
*/
@Nullable ClassInfo<T> returnValueType();
UnderscoreTud marked this conversation as resolved.
Show resolved Hide resolved

class ReturnHandlerStack extends ParserInstance.Data {

private final Deque<ReturnHandler<?>> stack = new LinkedList<>();

public ReturnHandlerStack(ParserInstance parserInstance) {
super(parserInstance);
}

public Deque<ReturnHandler<?>> getStack() {
return stack;
}

/**
* Retrieves the current {@link ReturnHandler}
* @return the return data
*/
public @Nullable ReturnHandler<?> getCurrentHandler() {
return stack.peek();
}

/**
* Pushes the current return handler onto the return stack.
* <br>
* <b>Note: After the trigger finished loading,
* {@link ReturnHandlerStack#pop()} <u>MUST</u> be called</b>
* @param handler the return handler
* @see ReturnHandlerStack#pop()
*/
public void push(ReturnHandler<?> handler) {
stack.push(handler);
}

/**
* Pops the current handler off the return stack.
* Should be called after the trigger has finished loading.
* @return the popped return data
* @see ReturnHandlerStack#push(ReturnHandler)
*/
public ReturnHandler<?> pop() {
return stack.pop();
}

}

}
Loading
Loading