Skip to content

Commit 1743000

Browse files
Return API (#6118)
1 parent d073e28 commit 1743000

File tree

7 files changed

+473
-70
lines changed

7 files changed

+473
-70
lines changed

src/main/java/ch/njol/skript/effects/EffReturn.java

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,22 @@
2626
import ch.njol.skript.doc.Since;
2727
import ch.njol.skript.lang.Effect;
2828
import ch.njol.skript.lang.Expression;
29+
import ch.njol.skript.lang.ReturnHandler;
30+
import ch.njol.skript.lang.ReturnHandler.ReturnHandlerStack;
2931
import ch.njol.skript.lang.SectionExitHandler;
3032
import ch.njol.skript.lang.SkriptParser.ParseResult;
3133
import ch.njol.skript.lang.TriggerItem;
3234
import ch.njol.skript.lang.TriggerSection;
33-
import ch.njol.skript.lang.function.FunctionEvent;
34-
import ch.njol.skript.lang.function.Functions;
35-
import ch.njol.skript.lang.function.ScriptFunction;
35+
import ch.njol.skript.lang.parser.ParserInstance;
3636
import ch.njol.skript.log.RetainingLogHandler;
3737
import ch.njol.skript.log.SkriptLogger;
38+
import ch.njol.skript.registrations.Classes;
3839
import ch.njol.util.Kleenean;
3940
import org.bukkit.event.Event;
4041
import org.eclipse.jdt.annotation.Nullable;
4142

4243
@Name("Return")
43-
@Description("Makes a function return a value")
44+
@Description("Makes a trigger (e.g. a function) return a value")
4445
@Examples({
4546
"function double(i: number) :: number:",
4647
"\treturn 2 * {_i}",
@@ -50,90 +51,89 @@
5051
})
5152
@Since("2.2, 2.8.0 (returns aliases)")
5253
public class EffReturn extends Effect {
53-
54+
5455
static {
5556
Skript.registerEffect(EffReturn.class, "return %objects%");
57+
ParserInstance.registerData(ReturnHandlerStack.class, ReturnHandlerStack::new);
5658
}
57-
59+
5860
@SuppressWarnings("NotNullFieldNotInitialized")
59-
private ScriptFunction<?> function;
60-
61+
private ReturnHandler<?> handler;
6162
@SuppressWarnings("NotNullFieldNotInitialized")
6263
private Expression<?> value;
63-
64-
@SuppressWarnings("unchecked")
64+
6565
@Override
66+
@SuppressWarnings("unchecked")
6667
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
67-
ScriptFunction<?> f = Functions.currentFunction;
68-
if (f == null) {
69-
Skript.error("The return statement can only be used in a function");
68+
handler = getParser().getData(ReturnHandlerStack.class).getCurrentHandler();
69+
if (handler == null) {
70+
Skript.error("The return statement cannot be used here");
7071
return false;
7172
}
72-
73+
7374
if (!isDelayed.isFalse()) {
7475
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)");
7576
return false;
7677
}
77-
78-
function = f;
79-
ClassInfo<?> returnType = function.getReturnType();
78+
79+
Class<?> returnType = handler.returnValueType();
8080
if (returnType == null) {
81-
Skript.error("This function doesn't return any value. Please use 'stop' or 'exit' if you want to stop the function.");
81+
Skript.error(handler + " doesn't return any value. Please use 'stop' or 'exit' if you want to stop the trigger.");
8282
return false;
8383
}
84-
84+
8585
RetainingLogHandler log = SkriptLogger.startRetainingLog();
8686
Expression<?> convertedExpr;
8787
try {
88-
convertedExpr = exprs[0].getConvertedExpression(returnType.getC());
88+
convertedExpr = exprs[0].getConvertedExpression(returnType);
8989
if (convertedExpr == null) {
90-
log.printErrors("This function is declared to return " + returnType.getName().withIndefiniteArticle() + ", but " + exprs[0].toString(null, false) + " is not of that type.");
90+
String typeName = Classes.getSuperClassInfo(returnType).getName().withIndefiniteArticle();
91+
log.printErrors(handler + " is declared to return " + typeName + ", but " + exprs[0].toString(null, false) + " is not of that type.");
9192
return false;
9293
}
9394
log.printLog();
9495
} finally {
9596
log.stop();
9697
}
97-
98-
if (f.isSingle() && !convertedExpr.isSingle()) {
99-
Skript.error("This function is defined to only return a single " + returnType.toString() + ", but this return statement can return multiple values.");
98+
99+
if (handler.isSingleReturnValue() && !convertedExpr.isSingle()) {
100+
Skript.error(handler + " is defined to only return a single " + returnType + ", but this return statement can return multiple values.");
100101
return false;
101102
}
102103
value = convertedExpr;
103-
104+
104105
return true;
105106
}
106-
107+
107108
@Override
108109
@Nullable
109-
@SuppressWarnings({"unchecked", "rawtypes"})
110110
protected TriggerItem walk(Event event) {
111111
debug(event, false);
112-
if (event instanceof FunctionEvent) {
113-
((ScriptFunction) function).setReturnValue(value.getArray(event));
114-
} else {
115-
assert false : event;
116-
}
112+
//noinspection rawtypes,unchecked
113+
((ReturnHandler) handler).returnValues(value.getArray(event));
117114

118115
TriggerSection parent = getParent();
119-
while (parent != null) {
116+
while (parent != null && parent != handler) {
120117
if (parent instanceof SectionExitHandler)
121118
((SectionExitHandler) parent).exit(event);
122119

123120
parent = parent.getParent();
124121
}
125122

123+
if (handler instanceof SectionExitHandler)
124+
((SectionExitHandler) handler).exit(event);
125+
126126
return null;
127127
}
128-
128+
129129
@Override
130130
protected void execute(Event event) {
131131
assert false;
132132
}
133-
133+
134134
@Override
135135
public String toString(@Nullable Event event, boolean debug) {
136136
return "return " + value.toString(event, debug);
137137
}
138-
138+
139139
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* This file is part of Skript.
3+
*
4+
* Skript is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Skript is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
* Copyright Peter Güttinger, SkriptLang team and contributors
18+
*/
19+
package ch.njol.skript.lang;
20+
21+
import ch.njol.skript.ScriptLoader;
22+
import ch.njol.skript.SkriptAPIException;
23+
import ch.njol.skript.config.SectionNode;
24+
import ch.njol.skript.lang.parser.ParserInstance;
25+
import org.bukkit.event.Event;
26+
import org.jetbrains.annotations.ApiStatus.NonExtendable;
27+
import org.jetbrains.annotations.Nullable;
28+
29+
import java.util.Deque;
30+
import java.util.LinkedList;
31+
32+
public interface ReturnHandler<T> {
33+
34+
/**
35+
* Loads the code in the given {@link SectionNode} using the same logic as
36+
* {@link Section#loadCode(SectionNode)} and pushes the section onto the
37+
* return handler stack
38+
* <br>
39+
* <b>This method may only be called by a {@link Section}</b>
40+
* @throws SkriptAPIException if this return handler is not a {@link Section}
41+
*/
42+
@NonExtendable
43+
default void loadReturnableSectionCode(SectionNode node) {
44+
if (!(this instanceof Section))
45+
throw new SkriptAPIException("loadReturnableSectionCode called on a non-section object");
46+
ParserInstance parser = ParserInstance.get();
47+
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);
48+
stack.push(this);
49+
Section section = (Section) this;
50+
try {
51+
section.loadCode(node);
52+
} finally {
53+
stack.pop();
54+
}
55+
}
56+
57+
/**
58+
* Loads the code in the given {@link SectionNode} using the same logic as
59+
* {@link Section#loadCode(SectionNode, String, Class[])} and pushes the section onto the
60+
* return handler stack
61+
* <br>
62+
* <b>This method may only be called by a {@link Section}</b>
63+
* @param node the section node
64+
* @param name the name of the event(s) being used
65+
* @param events the event(s) during the section's execution
66+
* @return a returnable trigger containing the loaded section.
67+
* This should be stored and used to run the section one or more times
68+
* @throws SkriptAPIException if this return handler is not a {@link Section}
69+
*/
70+
@NonExtendable
71+
default ReturnableTrigger<T> loadReturnableSectionCode(SectionNode node, String name, Class<? extends Event>[] events) {
72+
if (!(this instanceof Section))
73+
throw new SkriptAPIException("loadReturnableSectionCode called on a non-section object");
74+
ParserInstance parser = ParserInstance.get();
75+
ParserInstance.Backup parserBackup = parser.backup();
76+
parser.reset();
77+
78+
parser.setCurrentEvent(name, events);
79+
SkriptEvent skriptEvent = new SectionSkriptEvent(name, (Section) this);
80+
parser.setCurrentStructure(skriptEvent);
81+
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);
82+
83+
try {
84+
return new ReturnableTrigger<>(
85+
this,
86+
parser.getCurrentScript(),
87+
name,
88+
skriptEvent,
89+
trigger -> {
90+
stack.push(trigger);
91+
return ScriptLoader.loadItems(node);
92+
}
93+
);
94+
} finally {
95+
stack.pop();
96+
parser.restoreBackup(parserBackup);
97+
}
98+
}
99+
100+
/**
101+
* Loads the code in the given {@link SectionNode} into a {@link ReturnableTrigger}.
102+
* <br>
103+
* This is a general method to load a section node without extra logic
104+
* done to the {@link ParserInstance}.
105+
* The calling code is expected to manage the {@code ParserInstance} accordingly, which may vary depending on
106+
* where the code being loaded is located and what state the {@code ParserInstance} is in.
107+
* @param node the section node to load
108+
* @param name the name of the trigger
109+
* @param event the {@link SkriptEvent} of the trigger
110+
* @return a returnable trigger containing the loaded section node
111+
*/
112+
@NonExtendable
113+
default ReturnableTrigger<T> loadReturnableTrigger(SectionNode node, String name, SkriptEvent event) {
114+
ParserInstance parser = ParserInstance.get();
115+
ReturnHandlerStack stack = parser.getData(ReturnHandlerStack.class);
116+
try {
117+
return new ReturnableTrigger<T>(
118+
this,
119+
parser.getCurrentScript(),
120+
name,
121+
event,
122+
trigger -> {
123+
stack.push(trigger);
124+
return ScriptLoader.loadItems(node);
125+
}
126+
);
127+
} finally {
128+
stack.pop();
129+
}
130+
}
131+
132+
/**
133+
* @param values the values to return
134+
*/
135+
void returnValues(T @Nullable [] values);
136+
137+
/**
138+
* @return whether this return handler may accept multiple return values
139+
*/
140+
boolean isSingleReturnValue();
141+
142+
/**
143+
* The return type of this return handler, or null if it can't
144+
* accept return values in this context (e.g. a function without a return type).
145+
*
146+
* @return the return type
147+
*/
148+
@Nullable Class<? extends T> returnValueType();
149+
150+
class ReturnHandlerStack extends ParserInstance.Data {
151+
152+
private final Deque<ReturnHandler<?>> stack = new LinkedList<>();
153+
154+
public ReturnHandlerStack(ParserInstance parserInstance) {
155+
super(parserInstance);
156+
}
157+
158+
public Deque<ReturnHandler<?>> getStack() {
159+
return stack;
160+
}
161+
162+
/**
163+
* Retrieves the current {@link ReturnHandler}
164+
* @return the return data
165+
*/
166+
public @Nullable ReturnHandler<?> getCurrentHandler() {
167+
return stack.peek();
168+
}
169+
170+
/**
171+
* Pushes the current return handler onto the return stack.
172+
* <br>
173+
* <b>Note: After the trigger finished loading,
174+
* {@link ReturnHandlerStack#pop()} <u>MUST</u> be called</b>
175+
* @param handler the return handler
176+
* @see ReturnHandlerStack#pop()
177+
*/
178+
public void push(ReturnHandler<?> handler) {
179+
stack.push(handler);
180+
}
181+
182+
/**
183+
* Pops the current handler off the return stack.
184+
* Should be called after the trigger has finished loading.
185+
* @return the popped return data
186+
* @see ReturnHandlerStack#push(ReturnHandler)
187+
*/
188+
public ReturnHandler<?> pop() {
189+
return stack.pop();
190+
}
191+
192+
}
193+
194+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* This file is part of Skript.
3+
*
4+
* Skript is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* Skript is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with Skript. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
* Copyright Peter Güttinger, SkriptLang team and contributors
18+
*/
19+
package ch.njol.skript.lang;
20+
21+
import org.eclipse.jdt.annotation.Nullable;
22+
import org.skriptlang.skript.lang.script.Script;
23+
24+
import java.util.Collections;
25+
import java.util.List;
26+
import java.util.function.Function;
27+
28+
public class ReturnableTrigger<T> extends Trigger implements ReturnHandler<T> {
29+
30+
private final ReturnHandler<T> handler;
31+
32+
public ReturnableTrigger(ReturnHandler<T> handler, @Nullable Script script, String name, SkriptEvent event, Function<ReturnHandler<T>, List<TriggerItem>> loadItems) {
33+
super(script, name, event, Collections.emptyList());
34+
this.handler = handler;
35+
setTriggerItems(loadItems.apply(this));
36+
}
37+
38+
@Override
39+
public void returnValues(T @Nullable [] values) {
40+
handler.returnValues(values);
41+
}
42+
43+
@Override
44+
public boolean isSingleReturnValue() {
45+
return handler.isSingleReturnValue();
46+
}
47+
48+
@Override
49+
public @Nullable Class<? extends T> returnValueType() {
50+
return handler.returnValueType();
51+
}
52+
53+
}

0 commit comments

Comments
 (0)