Skip to content

Commit

Permalink
Return API (#6118)
Browse files Browse the repository at this point in the history
  • Loading branch information
UnderscoreTud authored Jun 14, 2024
1 parent d073e28 commit 1743000
Show file tree
Hide file tree
Showing 7 changed files with 473 additions and 70 deletions.
72 changes: 36 additions & 36 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,22 @@
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.skript.registrations.Classes;
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 +51,89 @@
})
@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();

Class<?> 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(handler + " doesn't return any value. Please use 'stop' or 'exit' if you want to stop the trigger.");
return false;
}

RetainingLogHandler log = SkriptLogger.startRetainingLog();
Expression<?> convertedExpr;
try {
convertedExpr = exprs[0].getConvertedExpression(returnType.getC());
convertedExpr = exprs[0].getConvertedExpression(returnType);
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.");
String typeName = Classes.getSuperClassInfo(returnType).getName().withIndefiniteArticle();
log.printErrors(handler + " is declared to return " + typeName + ", 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.isSingleReturnValue() && !convertedExpr.isSingle()) {
Skript.error(handler + " 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);
}

}
194 changes: 194 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,194 @@
/**
* 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.config.SectionNode;
import ch.njol.skript.lang.parser.ParserInstance;
import org.bukkit.event.Event;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import org.jetbrains.annotations.Nullable;

import java.util.Deque;
import java.util.LinkedList;

public interface ReturnHandler<T> {

/**
* 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();
ParserInstance.Backup parserBackup = parser.backup();
parser.reset();

parser.setCurrentEvent(name, events);
SkriptEvent skriptEvent = new SectionSkriptEvent(name, (Section) this);
parser.setCurrentStructure(skriptEvent);
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);

try {
return new ReturnableTrigger<>(
this,
parser.getCurrentScript(),
name,
skriptEvent,
trigger -> {
stack.push(trigger);
return ScriptLoader.loadItems(node);
}
);
} finally {
stack.pop();
parser.restoreBackup(parserBackup);
}
}

/**
* 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}.
* The calling code is expected to manage the {@code ParserInstance} accordingly, which may vary depending on
* where the code being loaded is located and what state the {@code ParserInstance} is in.
* @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 isSingleReturnValue();

/**
* 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 Class<? extends T> returnValueType();

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();
}

}

}
53 changes: 53 additions & 0 deletions src/main/java/ch/njol/skript/lang/ReturnableTrigger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* 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 org.eclipse.jdt.annotation.Nullable;
import org.skriptlang.skript.lang.script.Script;

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

public class ReturnableTrigger<T> extends Trigger implements ReturnHandler<T> {

private final ReturnHandler<T> handler;

public ReturnableTrigger(ReturnHandler<T> handler, @Nullable Script script, String name, SkriptEvent event, Function<ReturnHandler<T>, List<TriggerItem>> loadItems) {
super(script, name, event, Collections.emptyList());
this.handler = handler;
setTriggerItems(loadItems.apply(this));
}

@Override
public void returnValues(T @Nullable [] values) {
handler.returnValues(values);
}

@Override
public boolean isSingleReturnValue() {
return handler.isSingleReturnValue();
}

@Override
public @Nullable Class<? extends T> returnValueType() {
return handler.returnValueType();
}

}
Loading

0 comments on commit 1743000

Please sign in to comment.