Skip to content

Commit 748ce2e

Browse files
RuntimeErrorCatcher + Runtime Elements (#7823)
* Initial Commit * Fix Java 17 * Consumer Usage * Update SecRuntime.java * Requested Changes * Update RuntimeErrorCatcher.java * Additional Changes * Update SecCatchErrors.java * Update SecCatchErrors.java * Requested Changes * Update SecCatchErrors.java
1 parent e36f264 commit 748ce2e

8 files changed

Lines changed: 317 additions & 10 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package ch.njol.skript.expressions;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.doc.Description;
5+
import ch.njol.skript.doc.Example;
6+
import ch.njol.skript.doc.Name;
7+
import ch.njol.skript.doc.Since;
8+
import ch.njol.skript.lang.Expression;
9+
import ch.njol.skript.lang.ExpressionType;
10+
import ch.njol.skript.lang.SkriptParser.ParseResult;
11+
import ch.njol.skript.lang.util.SimpleExpression;
12+
import ch.njol.util.Kleenean;
13+
import org.bukkit.event.Event;
14+
import org.jetbrains.annotations.Nullable;
15+
16+
@Name("Last Caught Errors")
17+
@Description("Gets the last caught runtime errors from a 'catch runtime errors' section.")
18+
@Example("""
19+
catch runtime errors:
20+
set worldborder center of {_border} to location(0, 0, NaN value)
21+
if last caught runtime errors contains "Your location can't have a NaN value as one of its components":
22+
set worldborder center of {_border} to location(0, 0, 0)
23+
""")
24+
@Since("INSERT VERSION")
25+
public class ExprCaughtErrors extends SimpleExpression<String> {
26+
27+
static {
28+
Skript.registerExpression(ExprCaughtErrors.class, String.class, ExpressionType.SIMPLE,
29+
"[the] last caught [run[ ]time] errors");
30+
}
31+
32+
public static String[] lastErrors;
33+
34+
@Override
35+
public boolean init(Expression<?>[] expressions, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
36+
return true;
37+
}
38+
39+
@Override
40+
protected String @Nullable [] get(Event event) {
41+
return lastErrors;
42+
}
43+
44+
@Override
45+
public boolean isSingle() {
46+
return false;
47+
}
48+
49+
@Override
50+
public Class<? extends String> getReturnType() {
51+
return String.class;
52+
}
53+
54+
@Override
55+
public String toString(@Nullable Event event, boolean debug) {
56+
return "last caught runtime errors";
57+
}
58+
59+
}

src/main/java/ch/njol/skript/registrations/Feature.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public enum Feature implements Experiment {
1515
QUEUES("queues", LifeCycle.EXPERIMENTAL),
1616
FOR_EACH_LOOPS("for loop", LifeCycle.EXPERIMENTAL, "for [each] loop[s]"),
1717
SCRIPT_REFLECTION("reflection", LifeCycle.EXPERIMENTAL, "[script] reflection"),
18+
CATCH_ERRORS("catch runtime errors", LifeCycle.EXPERIMENTAL, "error catching [section]")
1819
;
1920

2021
private final String codeName;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package ch.njol.skript.sections;
2+
3+
import ch.njol.skript.Skript;
4+
import ch.njol.skript.config.SectionNode;
5+
import ch.njol.skript.doc.Description;
6+
import ch.njol.skript.doc.Example;
7+
import ch.njol.skript.doc.Name;
8+
import ch.njol.skript.doc.Since;
9+
import ch.njol.skript.expressions.ExprCaughtErrors;
10+
import ch.njol.skript.lang.Expression;
11+
import ch.njol.skript.lang.Section;
12+
import ch.njol.skript.lang.SkriptParser.ParseResult;
13+
import ch.njol.skript.lang.TriggerItem;
14+
import ch.njol.skript.lang.parser.ParserInstance;
15+
import ch.njol.skript.registrations.Feature;
16+
import ch.njol.util.Kleenean;
17+
import org.bukkit.event.Event;
18+
import org.jetbrains.annotations.Nullable;
19+
import org.skriptlang.skript.lang.experiment.ExperimentSet;
20+
import org.skriptlang.skript.lang.experiment.ExperimentalSyntax;
21+
import org.skriptlang.skript.log.runtime.RuntimeError;
22+
import org.skriptlang.skript.log.runtime.RuntimeErrorCatcher;
23+
24+
import java.util.List;
25+
26+
@Name("Catch Runtime Errors")
27+
@Description("Catch any runtime errors produced by code within the section. This is an in progress feature.")
28+
@Example("""
29+
catch runtime errors:
30+
set worldborder center of {_border} to location(0, 0, NaN value)
31+
if last caught runtime errors contains "Your location can't have a NaN value as one of its components":
32+
set worldborder center of {_border} to location(0, 0, 0)
33+
""")
34+
@Since("INSERT VERSION")
35+
public class SecCatchErrors extends Section implements ExperimentalSyntax {
36+
37+
static {
38+
Skript.registerSection(SecCatchErrors.class, "catch [run[ ]time] error[s]");
39+
}
40+
41+
@Override
42+
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult, SectionNode sectionNode, List<TriggerItem> triggerItems) {
43+
if (sectionNode.isEmpty()) {
44+
Skript.error("A catch errors section must contain code.");
45+
return false;
46+
}
47+
ParserInstance parser = getParser();
48+
Kleenean previousDelay = parser.getHasDelayBefore();
49+
parser.setHasDelayBefore(Kleenean.FALSE);
50+
loadCode(sectionNode);
51+
if (parser.getHasDelayBefore().isTrue()) {
52+
Skript.error("Delays can't be used within a catch errors section.");
53+
return false;
54+
}
55+
parser.setHasDelayBefore(previousDelay);
56+
return true;
57+
}
58+
59+
@Override
60+
public boolean isSatisfiedBy(ExperimentSet experimentSet) {
61+
return experimentSet.hasExperiment(Feature.CATCH_ERRORS);
62+
}
63+
64+
@Override
65+
protected @Nullable TriggerItem walk(Event event) {
66+
RuntimeErrorCatcher catcher = new RuntimeErrorCatcher().start();
67+
last.setNext(null);
68+
TriggerItem.walk(first, event);
69+
ExprCaughtErrors.lastErrors = catcher.getCachedErrors().stream().map(RuntimeError::error).toArray(String[]::new);
70+
catcher.clearCachedErrors()
71+
.clearCachedFrames()
72+
.stop();
73+
return walk(event, false);
74+
}
75+
76+
@Override
77+
public String toString(@Nullable Event event, boolean debug) {
78+
return "catch runtime errors";
79+
}
80+
81+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package org.skriptlang.skript.log.runtime;
2+
3+
import ch.njol.skript.log.SkriptLogger;
4+
import org.jetbrains.annotations.UnmodifiableView;
5+
import org.skriptlang.skript.log.runtime.Frame.FrameOutput;
6+
7+
import java.util.ArrayList;
8+
import java.util.Collections;
9+
import java.util.List;
10+
import java.util.Map.Entry;
11+
import java.util.logging.Level;
12+
13+
/**
14+
* A {@link RuntimeErrorConsumer} to be used in {@link RuntimeErrorManager} to catch {@link RuntimeError}s.
15+
* This should always be used with {@link #start()} and {@link #stop()}.
16+
*/
17+
public class RuntimeErrorCatcher implements RuntimeErrorConsumer {
18+
19+
private List<RuntimeErrorConsumer> storedConsumers = new ArrayList<>();
20+
21+
private final List<RuntimeError> cachedErrors = new ArrayList<>();
22+
23+
private final List<Entry<FrameOutput, Level>> cachedFrames = new ArrayList<>();
24+
25+
public RuntimeErrorCatcher() {}
26+
27+
/**
28+
* Gets the {@link RuntimeErrorManager}.
29+
*/
30+
private RuntimeErrorManager getManager() {
31+
return RuntimeErrorManager.getInstance();
32+
}
33+
34+
/**
35+
* Starts this {@link RuntimeErrorCatcher}, removing all {@link RuntimeErrorConsumer}s from {@link RuntimeErrorManager}
36+
* and storing them in {@link #storedConsumers}.
37+
* Makes this {@link RuntimeErrorCatcher} the only {@link RuntimeErrorConsumer} in {@link RuntimeErrorManager}
38+
* to catch {@link RuntimeError}s.
39+
* @return This {@link RuntimeErrorCatcher}
40+
*/
41+
public RuntimeErrorCatcher start() {
42+
storedConsumers = getManager().removeAllConsumers();
43+
getManager().addConsumer(this);
44+
return this;
45+
}
46+
47+
/**
48+
* Stops this {@link RuntimeErrorCatcher}, removing from {@link RuntimeErrorManager} and restoring the previous
49+
* {@link RuntimeErrorConsumer}s from {@link #storedConsumers}.
50+
* Prints all cached {@link RuntimeError}s, {@link #cachedErrors}, and cached {@link FrameOutput}s, {@link #cachedFrames}.
51+
*/
52+
public void stop() {
53+
if (!getManager().removeConsumer(this)) {
54+
SkriptLogger.LOGGER.severe("[Skript] A 'RuntimeErrorCatcher' was stopped incorrectly.");
55+
return;
56+
}
57+
getManager().addConsumers(storedConsumers.toArray(RuntimeErrorConsumer[]::new));
58+
for (RuntimeError runtimeError : cachedErrors)
59+
storedConsumers.forEach(consumer -> consumer.printError(runtimeError));
60+
for (Entry<FrameOutput, Level> entry : cachedFrames)
61+
storedConsumers.forEach(consumer -> consumer.printFrameOutput(entry.getKey(), entry.getValue()));
62+
}
63+
64+
/**
65+
* Gets all the cached {@link RuntimeError}s.
66+
*/
67+
public @UnmodifiableView List<RuntimeError> getCachedErrors() {
68+
return Collections.unmodifiableList(cachedErrors);
69+
}
70+
71+
/**
72+
* Gets all cached {@link FrameOutput}s stored with its corresponding {@link Level} in an {@link Entry}
73+
*/
74+
public @UnmodifiableView List<Entry<FrameOutput, Level>> getCachedFrames() {
75+
return Collections.unmodifiableList(cachedFrames);
76+
}
77+
78+
/**
79+
* Clear all cached {@link RuntimeError}s.
80+
*/
81+
public RuntimeErrorCatcher clearCachedErrors() {
82+
cachedErrors.clear();
83+
return this;
84+
}
85+
86+
/**
87+
* Clears all cached {@link FrameOutput}s.
88+
*/
89+
public RuntimeErrorCatcher clearCachedFrames() {
90+
cachedFrames.clear();
91+
return this;
92+
}
93+
94+
@Override
95+
public void printError(RuntimeError error) {
96+
cachedErrors.add(error);
97+
}
98+
99+
@Override
100+
public void printFrameOutput(FrameOutput output, Level level) {
101+
cachedFrames.add(new Entry<FrameOutput, Level>() {
102+
@Override
103+
public FrameOutput getKey() {
104+
return output;
105+
}
106+
107+
@Override
108+
public Level getValue() {
109+
return level;
110+
}
111+
112+
@Override
113+
public Level setValue(Level value) {
114+
return null;
115+
}
116+
});
117+
}
118+
119+
}

src/main/java/org/skriptlang/skript/log/runtime/RuntimeErrorManager.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import java.io.Closeable;
1212
import java.util.ArrayList;
13+
import java.util.Arrays;
1314
import java.util.List;
1415
import java.util.logging.Level;
1516

@@ -132,13 +133,37 @@ public void addConsumer(RuntimeErrorConsumer consumer) {
132133
}
133134
}
134135

136+
/**
137+
* Adds multiple {@link RuntimeErrorConsumer}s that will receive the emitted errors and frame output data.
138+
* Consumers will be maintained when the manager is refreshed.
139+
* @param newConsumers The {@link RuntimeErrorConsumer}s to add.
140+
*/
141+
public void addConsumers(RuntimeErrorConsumer... newConsumers) {
142+
synchronized (consumers) {
143+
consumers.addAll(Arrays.asList(newConsumers));
144+
}
145+
}
146+
135147
/**
136148
* Removes a {@link RuntimeErrorConsumer} from the tracked list.
137149
* @param consumer The consumer to remove.
150+
* @return {@code true} If the {@code consumer} was removed.
151+
*/
152+
public boolean removeConsumer(RuntimeErrorConsumer consumer) {
153+
synchronized (consumers) {
154+
return consumers.remove(consumer);
155+
}
156+
}
157+
158+
/**
159+
* Removes all {@link RuntimeErrorConsumer}s that receive emitted errors and frame output data.
160+
* @return All {@link RuntimeErrorConsumer}s removed.
138161
*/
139-
public void removeConsumer(RuntimeErrorConsumer consumer) {
162+
public List<RuntimeErrorConsumer> removeAllConsumers() {
140163
synchronized (consumers) {
141-
consumers.remove(consumer);
164+
List<RuntimeErrorConsumer> currentConsumers = List.copyOf(consumers);
165+
consumers.clear();
166+
return currentConsumers;
142167
}
143168
}
144169

src/test/skript/tests/syntaxes/effects/EffWorldBorderExpand.sk

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using error catching
2+
13
test "worldborder expand":
24
set {_border} to worldborder of world("world")
35

@@ -35,9 +37,13 @@ test "worldborder expand":
3537
assert worldborder radius of {_border} is 100 with "Growing border by None changed the radius"
3638
shrink {_border} to {_None}
3739
assert worldborder radius of {_border} is 100 with "Shrinking border to None changed the radius"
38-
shrink {_border} by NaN value
40+
catch runtime errors:
41+
shrink {_border} by NaN value
42+
assert last caught runtime errors contains "You can't shrink a world border by NaN." with "Shrinking by NaN did not throw error"
3943
assert worldborder radius of {_border} is 100 with "Shrinking border by NaN changed the radius"
40-
grow {_border} to NaN value
44+
catch runtime errors:
45+
grow {_border} to NaN value
46+
assert last caught runtime errors contains "You can't grow a world border to NaN." with "Growing by NaN did not throw error"
4147
assert worldborder radius of {_border} is 100 with "Growing border to NaN changed the radius"
4248

4349
# infinite values

src/test/skript/tests/syntaxes/expressions/ExprWorldBorderCenter.sk

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using error catching
2+
13
test "worldborder center":
24
set {_border} to worldborder of world("world")
35
assert worldborder center of {_border} is location(0, 0, 0, "world") with "default border center is not (0,0)"
@@ -19,9 +21,13 @@ test "worldborder center":
1921
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "setting location of border to None moved border"
2022
set worldborder center of {_border} to location({_None}, 0, {_None})
2123
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "using a location with None components changed the border center"
22-
set worldborder center of {_border} to location(NaN value, 0, 1)
24+
catch runtime errors:
25+
set worldborder center of {_border} to location(NaN value, 0, 1)
26+
assert last caught runtime errors contains "Your location can't have a NaN value as one of its components" with "x-coord NaN value did not throw error"
2327
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "using a location with x-coord NaN changed the border center"
24-
set worldborder center of {_border} to location(2, 0, NaN value)
28+
catch runtime errors:
29+
set worldborder center of {_border} to location(2, 0, NaN value)
30+
assert last caught runtime errors contains "Your location can't have a NaN value as one of its components" with "z-coord NaN value did not throw error"
2531
assert worldborder center of {_border} is location(-4.5, 0, -34.2, "world") with "using a location with z-coord NaN changed the border center"
2632
set worldborder center of {_border} to location(infinity value, 0, infinity value)
2733
assert worldborder center of {_border} is location(29999984, 0, 29999984, "world") with "border center coords were not rounded correctly when using +infinity"

src/test/skript/tests/syntaxes/expressions/ExprWorldBorderDamageAmount.sk

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using error catching
2+
13
test "worldborder damage amount":
24
set {_border} to worldborder of world("world")
35
assert worldborder damage amount of {_border} is 0.2 with "Default worldborder damage amount is not 0.2"
@@ -9,11 +11,17 @@ test "worldborder damage amount":
911
assert worldborder damage amount of {_border} is 1.5 with "Failed to set worldborder damage amount to a float"
1012
set worldborder damage amount of {_border} to {_None}
1113
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to None changed the damage amount"
12-
set worldborder damage amount of {_border} to NaN value
14+
catch runtime errors:
15+
set worldborder damage amount of {_border} to NaN value
16+
assert last caught runtime errors contains "NaN is not a valid world border damage amount" with "NaN damage value did not throw error"
1317
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to NaN value changed the damage amount"
14-
set worldborder damage amount of {_border} to infinity value
18+
catch runtime errors:
19+
set worldborder damage amount of {_border} to infinity value
20+
assert last caught runtime errors contains "World border damage amount cannot be infinite" with "Infinity damage value did not throw error"
1521
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to infinity changed the damage amount"
16-
set worldborder damage amount of {_border} to -infinity value
22+
catch runtime errors:
23+
set worldborder damage amount of {_border} to -infinity value
24+
assert last caught runtime errors contains "World border damage amount cannot be infinite" with "Negative infinity damage value did not throw error"
1725
assert worldborder damage amount of {_border} is 1.5 with "Setting worldborder damage amount to -infinity changed the damage amount"
1826

1927
# add tests
@@ -27,7 +35,9 @@ test "worldborder damage amount":
2735
assert worldborder damage amount of {_border} is 1.5 with "Failed adding negative integer to damage amount"
2836
add {_None} to worldborder damage amount of {_border}
2937
assert worldborder damage amount of {_border} is 1.5 with "Adding None to worldborder damage amount changed the damage amount"
30-
add NaN value to worldborder damage amount of {_border}
38+
catch runtime errors:
39+
add NaN value to worldborder damage amount of {_border}
40+
assert last caught runtime errors contains "NaN is not a valid world border damage amount" with "Adding NaN damage value did not throw error"
3141
assert worldborder damage amount of {_border} is 1.5 with "Adding NaN value to worldborder damage amount changed the damage amount"
3242
add infinity value to worldborder damage amount of {_border}
3343
assert worldborder damage amount of {_border} is 1.5 with "Adding infinity to worldborder damage amount changed the damage amount"

0 commit comments

Comments
 (0)