forked from Mwexim/skript-parser
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathExpression.java
More file actions
281 lines (257 loc) · 10.9 KB
/
Expression.java
File metadata and controls
281 lines (257 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package io.github.syst3ms.skriptparser.lang;
import io.github.syst3ms.skriptparser.expressions.ExprLoopValue;
import io.github.syst3ms.skriptparser.lang.base.ConvertedExpression;
import io.github.syst3ms.skriptparser.parsing.ParserState;
import io.github.syst3ms.skriptparser.parsing.SkriptParserException;
import io.github.syst3ms.skriptparser.parsing.SkriptRuntimeException;
import io.github.syst3ms.skriptparser.registration.SyntaxManager;
import io.github.syst3ms.skriptparser.sections.SecLoop;
import io.github.syst3ms.skriptparser.types.changers.ChangeMode;
import io.github.syst3ms.skriptparser.types.conversions.Converters;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* An expression, i.e a {@link SyntaxElement} representing a value with some type.
*
* @param <T> the type of value this expression returns
*/
public interface Expression<T> extends SyntaxElement {
/**
* Checks an array of elements against a given predicate
*
* @param all the array to check
* @param predicate the predicate
* @param negated whether the result should be inverted
* @param and whether all elements of the array should match the predicate, or only one
* @param <T> the type of the elements to check
* @return whether the elements match the given predicate
*/
static <T> boolean check(T[] all, Predicate<? super T> predicate, boolean negated, boolean and) {
boolean hasElement = false;
for (var t : all) {
if (t == null)
continue;
hasElement = true;
boolean b = predicate.test(t);
if (and && !b)
return negated;
if (!and && b)
return !negated;
}
if (!hasElement)
return negated;
return negated != and;
}
@SuppressWarnings("unchecked")
static <S extends CodeSection> List<? extends S> getMatchingSections(ParserState parserState,
Class<? extends S> sectionClass) {
List<S> result = new ArrayList<>();
for (var section : parserState.getCurrentSections()) {
if (sectionClass.isAssignableFrom(section.getClass())) {
result.add((S) section);
}
}
return result;
}
static <S extends CodeSection> Optional<? extends S> getLinkedSection(ParserState parserState,
Class<? extends S> sectionClass) {
return getLinkedSection(parserState, sectionClass, l -> l.stream().findFirst());
}
static <S extends CodeSection> Optional<? extends S> getLinkedSection(ParserState parserState,
Class<? extends S> sectionClass,
Function<? super List<? extends S>, Optional<? extends S>> selector) {
return selector.apply(getMatchingSections(parserState, sectionClass));
}
/**
* Retrieves all values of this Expression, accounting for possible modifiers.
* This means that if this is an {@linkplain #isAndList() or-list}, it will choose
* a random value to return.
*
* @param ctx the event
* @return an array of the values
* @see #getArray(TriggerContext)
*/
T[] getValues(TriggerContext ctx);
/**
* Retrieves all values of this Expressions, without accounting for possible modifiers.
* This means that if this is an {@linkplain #isAndList() or-list}, it will still
* return all possible values.
*
* @param ctx the event
* @return an array of the raw values
*/
@SuppressWarnings("unchecked")
default T[] getArray(TriggerContext ctx) {
T[] values = getValues(ctx);
if (values == null) return (T[]) Array.newInstance(getReturnType(), 0);
return values;
}
/**
* Gets a single value out of this Expression
*
* @param ctx the event
* @return the single value of this Expression, or empty if it has no value
* @throws SkriptRuntimeException if the expression returns more than one value
*/
default Optional<? extends T> getSingle(TriggerContext ctx) {
var values = getValues(ctx);
if (values == null) {
return Optional.empty();
}
if (values.length == 0) {
return Optional.empty();
} else if (values.length > 1) {
throw new SkriptRuntimeException("Can't call getSingle on an expression that returns multiple values!");
} else {
return Optional.ofNullable(values[0]);
}
}
/**
* @return whether this expression returns a single value. By default, this is defined on registration, but it can
* be overridden.
*/
default boolean isSingle() {
return SyntaxManager.getExpressionExact(this)
.orElseThrow(() -> new SkriptParserException("Unregistered expression class: " + getClass().getName()))
.getReturnType()
.isSingle();
}
/**
* @return the return type of this expression. By default, this is defined on registration, but, like {@linkplain #isSingle()}, can be overriden.
*/
default Class<? extends T> getReturnType() {
return SyntaxManager.getExpressionExact(this)
.orElseThrow(() -> new SkriptParserException("Unregistered expression class: " + getClass().getName()))
.getReturnType()
.getType()
.getTypeClass();
}
/**
* Determines whether this expression can be changed according to a specific {@link ChangeMode}, and what type
* of values it can be changed with.
*
* @param mode the mode this Expression would be changed with
* @return an array of classes describing what types this Expression can be changed with, empty if it
* shouldn't be changed with the given {@linkplain ChangeMode change mode}. If the change mode is
* {@link ChangeMode#DELETE} or {@link ChangeMode#RESET}, then an empty array should be returned.
*/
default Optional<Class<?>[]> acceptsChange(ChangeMode mode) {
return Optional.empty();
}
/**
* Determines whether this expression can be changed to a specific {@link ChangeMode} and type class.
*
* @param mode the mode this Expression would be changed with
* @param changeWith the Expression you want to change with
* @return whether or not this Expression should be changed with the given {@linkplain ChangeMode change mode} and class.
*/
default boolean acceptsChange(ChangeMode mode, Expression<?> changeWith) {
return acceptsChange(mode, changeWith.getReturnType(), changeWith.isSingle());
}
/**
* Determines whether this expression can be changed to a specific {@link ChangeMode} and type class.
*
* @param mode the mode this Expression would be changed with
* @param needle the type class of the instance this Expression would be changed with
* @param isSingle whether or not the instance this Expression would be changed with is single
* @return whether or not this Expression should be changed with the given {@linkplain ChangeMode change mode} and class.
*/
default boolean acceptsChange(ChangeMode mode, Class<?> needle, boolean isSingle) {
if (mode == ChangeMode.DELETE || mode == ChangeMode.RESET)
throw new UnsupportedOperationException();
for (var haystack : acceptsChange(mode).orElse(new Class[0])) {
if (!haystack.isArray() && !isSingle)
continue;
if (haystack.isArray())
haystack = haystack.getComponentType();
if (needle == Object.class || haystack.isAssignableFrom(needle))
return true;
}
return false;
}
/**
* Changes this expression with the given values according to the given mode
*
* @param ctx the event
* @param changeMode the mode of change
* @param changeWith the values to change this Expression with
*/
default void change(TriggerContext ctx, ChangeMode changeMode, Object[] changeWith) { /* Nothing */ }
/**
* @param ctx the event
* @return an iterator of the values of this expression
*/
default Iterator<? extends T> iterator(TriggerContext ctx) {
return Arrays.asList(getValues(ctx)).iterator();
}
/**
* @param ctx the event
* @return a stream of the values of this expression
*/
default Stream<? extends T> stream(TriggerContext ctx) {
T[] values = getValues(ctx);
if (values != null && values.length > 0) {
return Arrays.stream(values);
}
return Stream.empty();
}
/**
* Converts this expression from it's current type ({@link T}) to another type, using
* {@linkplain Converters converters}.
*
* @param to the class of the type to convert this Expression to
* @param <C> the type to convert this Expression to
* @return a converted Expression, or {@code null} if it couldn't be converted
*/
default <C> Optional<? extends Expression<C>> convertExpression(Class<C> to) {
return ConvertedExpression.newInstance(this, to);
}
/**
* When this expression is looped, returns what the loop reference (as in {@literal loop-<reference>})
* should be in order to describe each element of the values of this expression.
*
* @param s the loop reference
* @return whether the given reference describes this expression's elements. By default, returns {@code true} if
* the parameter is {@code "value"}
* @see ExprLoopValue
* @see SecLoop
*/
default boolean isLoopOf(String s) {
return s.equals("value");
}
default boolean isAndList() {
return true;
}
default void setAndList(boolean isAndList) { /* Nothing*/ }
default Expression<?> getSource() {
return this;
}
/**
* Checks this expression against the given {@link Predicate}
*
* @param ctx the event
* @param predicate the predicate
* @return whether the expression matches the predicate
*/
default boolean check(TriggerContext ctx, Predicate<? super T> predicate) {
return check(ctx, predicate, false);
}
/**
* Checks this expression against the given {@link Predicate}
*
* @param ctx the event
* @param predicate the predicate
* @param negated whether the result should be inverted
* @return whether the expression matches the predicate
*/
default boolean check(TriggerContext ctx, Predicate<? super T> predicate, boolean negated) {
return check(getArray(ctx), predicate, negated, isAndList());
}
}