Skip to content

Commit cda2816

Browse files
committed
make ^ support nbt (properly)
1 parent f56d52c commit cda2816

8 files changed

Lines changed: 349 additions & 130 deletions

File tree

worldedit-core/src/main/java/com/sk89q/util/StringUtil.java

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import java.util.Locale;
2626
import java.util.Map;
27+
import java.util.Arrays;
2728
import java.util.regex.Pattern;
2829

2930
/**
@@ -340,30 +341,36 @@ public static List<String> parseListInQuotes(String[] input, char delimiter, cha
340341
if (quotes != quoteClose.length) {
341342
throw new Error("Mismatched quoteOpen and quoteClose lengths");
342343
}
344+
345+
int[] nestingDepths = new int[quotes];
343346
for (String split : input) {
344-
boolean quoteHandled = false;
345-
for (int i = 0; i < quotes; i++) {
346-
if (split.indexOf(quoteOpen[i]) != -1 && split.indexOf(quoteClose[i]) == -1) {
347-
buffer.append(split).append(delimiter);
348-
quoteHandled = true;
349-
break;
350-
} else if (split.indexOf(quoteClose[i]) != -1 && split.indexOf(quoteOpen[i]) == -1) {
351-
buffer.append(split);
352-
parsableBlocks.add(buffer.toString());
353-
buffer = new StringBuilder();
354-
quoteHandled = true;
355-
break;
356-
}
357-
}
358-
if (!quoteHandled) {
359-
if (buffer.length() == 0) {
360-
parsableBlocks.add(split);
361-
} else {
362-
buffer.append(split).append(delimiter);
363-
}
347+
split.chars()
348+
.forEach(ch -> {
349+
for (int i = 0; i < quoteOpen.length; i++) {
350+
char openQuote = quoteOpen[i];
351+
if (openQuote == ch) {
352+
nestingDepths[i]++;
353+
}
354+
}
355+
for (int i = 0; i < quoteClose.length; i++) {
356+
char closeQuote = quoteClose[i];
357+
if (closeQuote == ch) {
358+
nestingDepths[i]--;
359+
}
360+
}
361+
});
362+
363+
if (Arrays.stream(nestingDepths).allMatch(i -> i == 0)) {
364+
//all quotes closed after this split
365+
buffer.append(split);
366+
parsableBlocks.add(buffer.toString());
367+
buffer = new StringBuilder();
368+
} else {
369+
//ongoing quoting
370+
buffer.append(split).append(delimiter);
364371
}
365372
}
366-
if (appendLeftover && buffer.length() != 0) {
373+
if (appendLeftover && !buffer.isEmpty()) {
367374
parsableBlocks.add(buffer.delete(buffer.length() - 1, buffer.length()).toString());
368375
}
369376

worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser;
2626
import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser;
2727
import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser;
28-
import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser;
28+
import com.sk89q.worldedit.extension.factory.parser.pattern.PartiallyApplyingPatternParser;
2929
import com.sk89q.worldedit.function.pattern.Pattern;
3030
import com.sk89q.worldedit.internal.registry.AbstractFactory;
3131

@@ -51,7 +51,7 @@ public PatternFactory(WorldEdit worldEdit) {
5151

5252
// individual patterns
5353
register(new ClipboardPatternParser(worldEdit));
54-
register(new TypeOrStateApplyingPatternParser(worldEdit));
54+
register(new PartiallyApplyingPatternParser(worldEdit));
5555
register(new RandomStatePatternParser(worldEdit));
5656
register(new BlockCategoryPatternParser(worldEdit));
5757
}
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* WorldEdit, a Minecraft world manipulation toolkit
3+
* Copyright (C) sk89q <http://www.sk89q.com>
4+
* Copyright (C) WorldEdit team and contributors
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
*/
19+
20+
package com.sk89q.worldedit.extension.factory.parser.pattern;
21+
22+
import com.sk89q.worldedit.WorldEdit;
23+
import com.sk89q.worldedit.extension.input.InputParseException;
24+
import com.sk89q.worldedit.extension.input.NoMatchException;
25+
import com.sk89q.worldedit.extension.input.ParserContext;
26+
import com.sk89q.worldedit.extent.Extent;
27+
import com.sk89q.worldedit.extent.buffer.ExtentBuffer;
28+
import com.sk89q.worldedit.function.pattern.*;
29+
import com.sk89q.worldedit.internal.registry.InputParser;
30+
import com.sk89q.worldedit.util.formatting.text.TextComponent;
31+
import com.sk89q.worldedit.util.formatting.text.TranslatableComponent;
32+
import org.enginehub.linbus.format.snbt.LinStringIO;
33+
import org.enginehub.linbus.stream.exception.NbtParseException;
34+
import org.enginehub.linbus.tree.LinCompoundTag;
35+
import org.jetbrains.annotations.NotNull;
36+
37+
import java.util.ArrayList;
38+
import java.util.HashMap;
39+
import java.util.List;
40+
import java.util.Map;
41+
import java.util.stream.Stream;
42+
43+
44+
public class PartiallyApplyingPatternParser extends InputParser<Pattern> {
45+
46+
boolean compatibilityMode = false;
47+
48+
public PartiallyApplyingPatternParser(WorldEdit worldEdit) {
49+
super(worldEdit);
50+
}
51+
52+
protected PartiallyApplyingPatternParser(WorldEdit worldEdit, boolean compatibilityMode) {
53+
super(worldEdit);
54+
this.compatibilityMode = compatibilityMode;
55+
}
56+
57+
@Override
58+
public Stream<String> getSuggestions(String input, ParserContext context) {
59+
if (input.isEmpty()) {
60+
return Stream.of("^");
61+
}
62+
if (!input.startsWith("^")) {
63+
return Stream.empty();
64+
}
65+
input = input.substring(1);
66+
67+
if (input.isEmpty()) {
68+
//define properties, nbt or a type
69+
return Stream.concat(
70+
Stream.of("^[", "^{", "^{,"),
71+
worldEdit.getPatternFactory().getSuggestions(input, context)
72+
.stream()
73+
.map(s -> "^" + s)
74+
);
75+
}
76+
77+
PartiallyApplyingComponents components = split(input);
78+
79+
if (!components.nbt().isEmpty()) {
80+
if (!components.type().isEmpty() && !components.properties().isEmpty()) {
81+
//all of them are defined, we suggest like we would without ^
82+
return worldEdit.getPatternFactory().getSuggestions(input, context)
83+
.stream()
84+
.map(s -> "^" + s);
85+
}
86+
if (!components.type().isEmpty()) {
87+
//type and nbt. We currently don't support nbt hints, so nothing to suggest
88+
return Stream.empty();
89+
}
90+
if (!components.properties().isEmpty()) {
91+
//properties and nbt. We can't figure out possible nbt without type
92+
return Stream.empty();
93+
}
94+
}
95+
96+
if (!components.properties().isEmpty()) {
97+
if (!components.type().isEmpty()) {
98+
//type and properties are defined, we suggest like we would without ^
99+
return worldEdit.getPatternFactory().getSuggestions(input, context)
100+
.stream()
101+
.map(s -> "^" + s);
102+
}
103+
return Stream.empty(); // without knowing a type, we can't really suggest states
104+
}
105+
//only type is defined, we suggest like we would without ^
106+
return worldEdit.getPatternFactory().getSuggestions(input, context)
107+
.stream()
108+
.map(s -> "^" + s);
109+
}
110+
111+
private @NotNull PartiallyApplyingComponents split(String input) {
112+
String type;
113+
String properties = "";
114+
//default as delete NBT retains previous behaviour
115+
String nbt = compatibilityMode ? "{=}" : "";
116+
117+
int startProperties = input.indexOf('[');
118+
int startNbt = input.indexOf('{');
119+
if (startNbt >= 0 && startNbt < startProperties) {
120+
startProperties = -1;
121+
}
122+
123+
if (startProperties >= 0 && startNbt >= 0) {
124+
//properties and nbt and maybe type
125+
type = input.substring(0, startProperties);
126+
properties = input.substring(startProperties, startNbt);
127+
nbt = input.substring(startNbt);
128+
} else if (startProperties >= 0) {
129+
//properties and maybe type
130+
type = input.substring(0, startProperties);
131+
properties = input.substring(startProperties);
132+
} else if (startNbt >= 0) {
133+
//nbt and maybe type
134+
type = input.substring(0, startNbt);
135+
nbt = input.substring(startNbt);
136+
} else {
137+
type = input;
138+
}
139+
return new PartiallyApplyingComponents(type, properties, nbt);
140+
}
141+
142+
private record PartiallyApplyingComponents(String type, String properties, String nbt) {
143+
}
144+
145+
@Override
146+
public Pattern parseFromInput(String input, ParserContext context) throws InputParseException {
147+
if (!input.startsWith("^")) {
148+
return null;
149+
}
150+
Extent extent = context.requireExtent();
151+
input = input.substring(1);
152+
153+
if (input.isEmpty()) {
154+
throw new NoMatchException(TranslatableComponent.of("worldedit.error.unknown-block", TextComponent.of(input)));
155+
}
156+
157+
PartiallyApplyingComponents components = split(input);
158+
159+
List<ExtendPatternFactory> extendPatternFactories = new ArrayList<>();
160+
161+
if (!components.nbt().isEmpty()) {
162+
extendPatternFactories
163+
.add(getNbtApplyingPatternFactory(input, components.nbt()));
164+
}
165+
if (!components.type().isEmpty()) {
166+
extendPatternFactories
167+
.add(getTypeApplyingPatternFactory(context, components.type()));
168+
}
169+
if (!components.properties().isEmpty()) {
170+
extendPatternFactories
171+
.add(getStateApplyingPatternFactory(components));
172+
}
173+
174+
if (extendPatternFactories.size() > 1) {
175+
Extent buffer = new ExtentBuffer(extent);
176+
Pattern[] patterns = extendPatternFactories.stream()
177+
.map(factory -> factory.forExtend(buffer))
178+
.toArray(Pattern[]::new);
179+
return new ExtentBufferedCompositePattern(buffer, patterns);
180+
}
181+
182+
return extendPatternFactories.getFirst().forExtend(extent);
183+
184+
}
185+
186+
private @NotNull ExtendPatternFactory getTypeApplyingPatternFactory(ParserContext context, String type) throws InputParseException {
187+
Pattern pattern = worldEdit.getPatternFactory().parseFromInput(type, context);
188+
return ext -> new TypeApplyingPattern(ext, pattern);
189+
}
190+
191+
private static @NotNull ExtendPatternFactory getStateApplyingPatternFactory(PartiallyApplyingComponents components) throws InputParseException {
192+
String properties = components.properties();
193+
if (!properties.endsWith("]")) {
194+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbracket"));
195+
}
196+
String propertiesWithoutBrackets = properties.substring(1, properties.length() - 1);
197+
final String[] states = propertiesWithoutBrackets.split(",", 0);
198+
Map<String, String> statesToSet = new HashMap<>();
199+
for (String state : states) {
200+
if (state.isEmpty()) {
201+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-state"));
202+
}
203+
String[] propVal = state.split("=", 2);
204+
if (propVal.length != 2) {
205+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-equals-separator"));
206+
}
207+
final String prop = propVal[0];
208+
if (prop.isEmpty()) {
209+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-property"));
210+
}
211+
final String value = propVal[1];
212+
if (value.isEmpty()) {
213+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.empty-value"));
214+
}
215+
if (statesToSet.put(prop, value) != null) {
216+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.duplicate-property", TextComponent.of(prop)));
217+
}
218+
}
219+
return ext -> new StateApplyingPattern(ext, statesToSet);
220+
}
221+
222+
private static @NotNull ExtendPatternFactory getNbtApplyingPatternFactory(String input, String nbt) throws InputParseException {
223+
if (!nbt.endsWith("}")) {
224+
throw new InputParseException(TranslatableComponent.of("worldedit.error.parser.missing-rbrace"));
225+
}
226+
if (nbt.equals("{}")) {
227+
return (ext) -> new NBTApplyingPattern(ext, null);
228+
}
229+
boolean merge = true;
230+
if (nbt.startsWith("{=")) {
231+
merge = false;
232+
nbt = "{" + nbt.substring(2);
233+
}
234+
LinCompoundTag tag;
235+
try {
236+
if (nbt.equals("{}")) {
237+
tag = LinCompoundTag.builder().build();
238+
} else {
239+
tag = LinStringIO.readFromStringUsing(nbt, LinCompoundTag::readFrom);
240+
}
241+
} catch (NbtParseException e) {
242+
throw new NoMatchException(TranslatableComponent.of(
243+
"worldedit.error.parser.invalid-nbt",
244+
TextComponent.of("^" + input),
245+
TextComponent.of(e.getMessage())
246+
));
247+
}
248+
if (merge) {
249+
return (ext) -> new NBTMergingPattern(ext, tag.value());
250+
} else {
251+
return (ext) -> new NBTApplyingPattern(ext, tag);
252+
}
253+
}
254+
255+
private interface ExtendPatternFactory {
256+
Pattern forExtend(Extent e);
257+
}
258+
259+
}

0 commit comments

Comments
 (0)