Skip to content

Commit fc51c90

Browse files
authored
Add Indices of X in List (SkriptLang#7323)
* fix test * change test * Clean up the example * Changes * Tidy up error message * Fix bug * Tiny error * Fix null return values * Allow non-variable lists and add position syntax * Fix indentation in test * Changes * Fix bug * ok. * Clean up the class * Changes * Return Integer or String when value is null * fix * Changes * Merge ExprIndicesOfX with ExprIndicesOf (renamed the class for clarity) * Clean up * Fix pattern * Fix example * Changes * Clear up the description * Add positions of x from position and clean-up * Add positions of x from position and clean-up * Remove positions of x in y from z * Changes * turd changes * oops i forgot * Change the pattern & return nothing for not found string indices * Fix tests * Slight improvement in the getStringPositions() method * Fix example * Fix tests (lol)
1 parent 5178ac2 commit fc51c90

3 files changed

Lines changed: 289 additions & 71 deletions

File tree

src/main/java/ch/njol/skript/expressions/ExprIndexOf.java

Lines changed: 0 additions & 71 deletions
This file was deleted.
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package ch.njol.skript.expressions;
2+
3+
import ch.njol.skript.lang.SyntaxStringBuilder;
4+
import ch.njol.skript.lang.Variable;
5+
import ch.njol.skript.util.LiteralUtils;
6+
import ch.njol.util.Pair;
7+
import org.bukkit.event.Event;
8+
import org.jetbrains.annotations.Nullable;
9+
10+
import ch.njol.skript.Skript;
11+
import ch.njol.skript.doc.Description;
12+
import ch.njol.skript.doc.Examples;
13+
import ch.njol.skript.doc.Name;
14+
import ch.njol.skript.doc.Since;
15+
import ch.njol.skript.lang.Expression;
16+
import ch.njol.skript.lang.ExpressionType;
17+
import ch.njol.skript.lang.SkriptParser.ParseResult;
18+
import ch.njol.skript.lang.util.SimpleExpression;
19+
import ch.njol.util.Kleenean;
20+
21+
import java.lang.reflect.Array;
22+
import java.util.*;
23+
24+
@Name("Indices Of")
25+
@Description({
26+
"Get the first, last or all positions of a character (or text) in another text using "
27+
+ "'positions of %text% in %text%'. Nothing is returned when the value does not occur in the text. "
28+
+ "Positions range from 1 to the <a href='#ExprIndicesOf'>length</a> of the text (inclusive).",
29+
"",
30+
"Using 'indices/positions of %objects% in %objects%', you can get the indices or positions of "
31+
+ "a list where the value at that index is the provided value. "
32+
+ "Indices are only supported for variable lists and will return the string indices of the given value. "
33+
+ "Positions can be used with any list and will return "
34+
+ "the numerical position of the value in the list, counting up from 1. "
35+
+ "As well, nothing is returned if the value is not found in the list."
36+
})
37+
@Examples({
38+
"set {_first} to the first position of \"@\" in the text argument",
39+
"if {_s} contains \"abc\":",
40+
"\tset {_s} to the first (position of \"abc\" in {_s} + 3) characters of {_s}",
41+
"\t# removes everything after the first \"abc\" from {_s}",
42+
"",
43+
"set {_list::*} to 1, 2, 3, 1, 2, 3",
44+
"set {_indices::*} to indices of the value 1 in {_list::*}",
45+
"# {_indices::*} is now \"1\" and \"4\"",
46+
"",
47+
"set {_indices::*} to all indices of the value 2 in {_list::*}",
48+
"# {_indices::*} is now \"2\" and \"5\"",
49+
"",
50+
"set {_positions::*} to all positions of the value 3 in {_list::*}",
51+
"# {_positions::*} is now 3 and 6",
52+
"",
53+
"set {_otherlist::bar} to 100",
54+
"set {_otherlist::hello} to \"hi\"",
55+
"set {_otherlist::burb} to 100",
56+
"set {_otherlist::tud} to \"hi\"",
57+
"set {_otherlist::foo} to 100",
58+
"",
59+
"set {_indices::*} to the first index of the value 100 in {_otherlist::*}",
60+
"# {_indices::*} is now \"bar\"",
61+
"set {_indices::*} to the last index of the value 100 in {_otherlist::*}",
62+
"# {_indices::*} is now \"foo\"",
63+
"",
64+
"set {_positions::*} to all positions of the value 100 in {_otherlist::*}",
65+
"# {_positions::*} is now 1, 3 and 5",
66+
"set {_positions::*} to all positions of the value \"hi\" in {_otherlist::*}",
67+
"# {_positions::*} is now 2 and 4"
68+
})
69+
@Since("2.1, INSERT VERSION (indices, positions of list)")
70+
public class ExprIndicesOf extends SimpleExpression<Object> {
71+
72+
static {
73+
Skript.registerExpression(ExprIndicesOf.class, Object.class, ExpressionType.COMBINED,
74+
"[the] [1:first|2:last|3:all] (position[mult:s]|mult:indices|index[mult:es]) of [[the] value] %string% in %string%",
75+
"[the] [1:first|2:last|3:all] position[mult:s] of [[the] value] %object% in %~objects%",
76+
"[the] [1:first|2:last|3:all] (mult:indices|index[mult:es]) of [[the] value] %object% in %~objects%"
77+
);
78+
}
79+
80+
private IndexType indexType;
81+
private boolean position, string;
82+
private Expression<?> value, objects;
83+
84+
@Override
85+
public boolean init(Expression<?>[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) {
86+
if (exprs[1].isSingle() && (matchedPattern > 0)) {
87+
Skript.error("'" + exprs[1] + "' can only ever have one value at most, "
88+
+ "thus the 'indices of x in list' expression has no effect.");
89+
return false;
90+
}
91+
92+
if (!(exprs[1] instanceof Variable<?>) && matchedPattern == 2) {
93+
Skript.error("'" + exprs[1] + "' is not a list variable. "
94+
+ "You can only get the indices of a list variable.");
95+
return false;
96+
}
97+
98+
indexType = IndexType.values()[parseResult.mark == 0 ? 0 : parseResult.mark - 1];
99+
if (parseResult.mark == 0 && parseResult.hasTag("mult"))
100+
indexType = IndexType.ALL;
101+
102+
position = matchedPattern <= 1;
103+
string = matchedPattern == 0;
104+
value = LiteralUtils.defendExpression(exprs[0]);
105+
objects = exprs[1];
106+
107+
return LiteralUtils.canInitSafely(value);
108+
}
109+
110+
@Override
111+
protected Object @Nullable [] get(Event event) {
112+
Object value = this.value.getSingle(event);
113+
if (value == null)
114+
return (Object[]) Array.newInstance(getReturnType(), 0);
115+
116+
if (this.position) {
117+
if (string) {
118+
String haystack = (String) objects.getSingle(event);
119+
if (haystack == null)
120+
return new Long[0];
121+
122+
return getStringPositions(haystack, (String) value);
123+
}
124+
125+
return getListPositions(objects, value, event);
126+
}
127+
128+
assert objects instanceof Variable<?>;
129+
130+
return getVariableIndices((Variable<?>) objects, value, event);
131+
}
132+
133+
private Long[] getStringPositions(String haystack, String needle) {
134+
List<Long> positions = new ArrayList<>();
135+
long position = haystack.indexOf(needle);
136+
137+
if (position == -1)
138+
return new Long[0];
139+
140+
if (indexType == IndexType.ALL) {
141+
while (position != -1) {
142+
positions.add(position + 1);
143+
position = haystack.indexOf(needle, (int) position + 1);
144+
}
145+
return positions.toArray(new Long[0]);
146+
}
147+
148+
if (indexType == IndexType.LAST)
149+
position = haystack.lastIndexOf(needle);
150+
151+
return new Long[]{position + 1};
152+
}
153+
154+
private Long[] getListPositions(Expression<?> list, Object value, Event event) {
155+
Iterator<?> iterator = list.iterator(event);
156+
if (iterator == null)
157+
return new Long[0];
158+
159+
List<Long> positions = new ArrayList<>();
160+
161+
long position = 0;
162+
while (iterator.hasNext()) {
163+
position++;
164+
165+
if (!iterator.next().equals(value))
166+
continue;
167+
168+
if (indexType == IndexType.FIRST)
169+
return new Long[]{position};
170+
171+
positions.add(position);
172+
}
173+
174+
if (indexType == IndexType.LAST)
175+
return new Long[]{positions.get(positions.size() - 1)};
176+
177+
return positions.toArray(Long[]::new);
178+
}
179+
180+
private String[] getVariableIndices(Variable<?> variable, Object value, Event event) {
181+
Iterator<Pair<String, Object>> iterator = variable.variablesIterator(event);
182+
if (iterator == null)
183+
return new String[0];
184+
185+
List<String> indices = new ArrayList<>();
186+
187+
while (iterator.hasNext()) {
188+
var pair = iterator.next();
189+
190+
Object pairValue = pair.getValue();
191+
// when {foo::1::bar} is set, the value of {foo::1} is a map with a null key that holds the value of {foo::1}
192+
if (pairValue instanceof Map<?, ?> map)
193+
pairValue = map.get(null);
194+
195+
if (pairValue.equals(value)) {
196+
if (indexType == IndexType.FIRST)
197+
return new String[]{pair.getKey()};
198+
199+
indices.add(pair.getKey());
200+
}
201+
}
202+
203+
if (indices.isEmpty())
204+
return new String[0];
205+
206+
if (indexType == IndexType.LAST)
207+
return new String[]{indices.get(indices.size() - 1)};
208+
209+
return indices.toArray(String[]::new);
210+
}
211+
212+
@Override
213+
public boolean isSingle() {
214+
return indexType == IndexType.FIRST || indexType == IndexType.LAST;
215+
}
216+
217+
@Override
218+
public Class<?> getReturnType() {
219+
if (position)
220+
return Long.class;
221+
return String.class;
222+
}
223+
224+
@Override
225+
public String toString(@Nullable Event event, boolean debug) {
226+
SyntaxStringBuilder builder = new SyntaxStringBuilder(event, debug);
227+
228+
builder.append(indexType.name().toLowerCase(Locale.ENGLISH));
229+
if (position) {
230+
builder.append("positions");
231+
} else {
232+
builder.append("indices");
233+
}
234+
builder.append("of value", value, "in", objects);
235+
236+
return builder.toString();
237+
}
238+
239+
private enum IndexType {
240+
FIRST, LAST, ALL
241+
}
242+
243+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
test "index of":
2+
set {_list::*} to 1, 2, 3, 1, 2 and 3
3+
set {_indices::*} to indices of the value 1 in {_list::*}
4+
assert {_indices::*} is "1" and "4" with "indices of 1 failed"
5+
6+
set {_indices::*} to the first index of the value 3 in {_list::*}
7+
assert {_indices::*} is "3" with "first index of 3 failed"
8+
9+
set {_indices::*} to the last index of the value 3 in {_list::*}
10+
assert {_indices::*} is "6" with "last index of 3 failed"
11+
12+
set {_indices::*} to the position of the value 3 in {_list::*}
13+
assert {_indices::*} is 3 with "first position of 3 failed"
14+
15+
set {_indices::*} to the last position of the value 3 in {_list::*}
16+
assert {_indices::*} is 6 with "last position of 3 failed"
17+
18+
set {_otherlist::burb} to test-location
19+
set {_otherlist::_DJ8U3f;} to test-location
20+
set {_otherlist::;'w20} to test-location
21+
set {_otherList::breh} to 2
22+
set {_otherList::quatro} to 4
23+
24+
set {_indices::*} to all indices of the value test-location in {_otherlist::*}
25+
assert {_indices::*} is ";'w20", "_dj8u3f;" and "burb" with "indices of test-location with symbols failed"
26+
27+
set {_indices::*} to all positions of the value test-location in {_otherList::*}
28+
assert {_indices::*} is 1, 2 and 4 with "positions of test-location failed"
29+
30+
set {_indices::*} to all positions of "b" in "abcabcabcabc"
31+
assert {_indices::*} is 2, 5, 8 and 11 with "positions of char failed"
32+
33+
set {_indices::*} to all positions of "abc" in "abcabcabcabc"
34+
assert {_indices::*} is 1, 4, 7 and 10 with "positions of string failed"
35+
36+
set {_index} to first position of "c" in "abraham lincoln"
37+
assert {_index} is 12 with "first position of char failed"
38+
39+
set {_index} to the position of "ham" in "abraham lincoln"
40+
assert {_index} is 5 with "first position of string failed"
41+
42+
set {_position} to position of "abc" in "nothing"
43+
assert {_position} is not set with "position of string is set"
44+
45+
set {_position::*} to all positions of "nothing" in "abc"
46+
assert {_position::*} is not set with "all positions of string is set"

0 commit comments

Comments
 (0)