From 468bfc8163288890b5457c7db30af4f7f8e4c40e Mon Sep 17 00:00:00 2001 From: Wizzerinus Date: Sat, 7 Feb 2026 17:23:13 +0300 Subject: [PATCH 1/3] TagMatcher: fix a server crash on syntax errors --- .../gtutilities/utils/TagMatcher.java | 74 +++++++++++-------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java b/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java index 4d81269..af89d81 100644 --- a/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java +++ b/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java @@ -1,14 +1,5 @@ package net.neganote.gtutilities.utils; -import net.minecraft.core.Holder; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.material.Fluid; - -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEItemKey; -import lombok.Getter; -import org.jetbrains.annotations.Nullable; - import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -18,8 +9,23 @@ import java.util.Map; import java.util.Set; +import org.jetbrains.annotations.Nullable; + +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import lombok.Getter; +import net.minecraft.core.Holder; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.material.Fluid; + public final class TagMatcher { + private static final class InvalidTagMatcherSyntaxException extends Exception { + private InvalidTagMatcherSyntaxException(String s) { + super(s); + } + } + public static Compiled compile(@Nullable String expr) { String washed = washExpression(expr == null ? "" : expr); if (washed.isBlank()) return Compiled.EMPTY; @@ -37,7 +43,7 @@ public static Compiled compile(@Nullable String expr) { } return new Compiled(rpn, true, needsTags); - } catch (RuntimeException ex) { + } catch (InvalidTagMatcherSyntaxException ex) { return Compiled.INVALID; } } @@ -53,7 +59,11 @@ public static boolean doesItemMatch(@Nullable AEItemKey item, @Nullable Compiled if (item == null || compiled == null || !compiled.isValid()) return false; if (!compiled.needsTags()) return false; // no tags referenced => cannot match anything meaningful ItemTagCache cache = ItemTagCache.of(item); - return evalRpn(compiled.rpn, cache.tags); + try { + return evalRpn(compiled.rpn, cache.tags); + } catch (InvalidTagMatcherSyntaxException e) { + return false; + } } public static boolean doesFluidMatch(@Nullable AEFluidKey fluid, String expr) { @@ -67,7 +77,11 @@ public static boolean doesFluidMatch(@Nullable AEFluidKey fluid, @Nullable Compi if (fluid == null || compiled == null || !compiled.isValid()) return false; if (!compiled.needsTags()) return false; FluidTagCache cache = FluidTagCache.of(fluid); - return evalRpn(compiled.rpn, cache.tags); + try { + return evalRpn(compiled.rpn, cache.tags); + } catch (InvalidTagMatcherSyntaxException e) { + return false; + } } private static String washExpression(String expression) { @@ -210,7 +224,7 @@ static Token rparen() { } } - private static List tokenize(String expression) { + private static List tokenize(String expression) throws InvalidTagMatcherSyntaxException { List tokens = new ArrayList<>(); StringBuilder currentTag = new StringBuilder(); @@ -222,21 +236,21 @@ private static List tokenize(String expression) { char c = expression.charAt(i); if (c == '#') { - throw new IllegalArgumentException("Character '#' is not allowed in tag expressions (pos " + i + ")."); + throw new InvalidTagMatcherSyntaxException("Character '#' is not allowed in tag expressions (pos " + i + ")."); } if (Character.isWhitespace(c)) continue; Operator op = Operator.fromSymbol(c); if (c == '(') { - if (!expectingOperand) throw new IllegalArgumentException("Unexpected '(' at position " + i); + if (!expectingOperand) throw new InvalidTagMatcherSyntaxException("Unexpected '(' at position " + i); flushTag(currentTag, tokens); tokens.add(Token.lparen()); lp++; lastIsTag = false; } else if (c == ')') { - if (expectingOperand && lp <= 0) throw new IllegalArgumentException("Unexpected ')' at position " + i); + if (expectingOperand && lp <= 0) throw new InvalidTagMatcherSyntaxException("Unexpected ')' at position " + i); flushTag(currentTag, tokens); tokens.add(Token.rparen()); expectingOperand = false; @@ -254,13 +268,13 @@ private static List tokenize(String expression) { expectingOperand = true; } } else { - throw new IllegalArgumentException("Unexpected operator '" + c + "' at position " + i); + throw new InvalidTagMatcherSyntaxException("Unexpected operator '" + c + "' at position " + i); } lastIsTag = false; } else { if (!expectingOperand) - throw new IllegalArgumentException("Unexpected character '" + c + "' at position " + i); + throw new InvalidTagMatcherSyntaxException("Unexpected character '" + c + "' at position " + i); currentTag.append(c); lastIsTag = true; } @@ -268,12 +282,12 @@ private static List tokenize(String expression) { flushTag(currentTag, tokens); - if (tokens.isEmpty()) throw new IllegalArgumentException("Expression cannot be empty."); - if (lp > 0) throw new IllegalArgumentException("Missing ')' at the end of the expression."); + if (tokens.isEmpty()) throw new InvalidTagMatcherSyntaxException("Expression cannot be empty."); + if (lp > 0) throw new InvalidTagMatcherSyntaxException("Missing ')' at the end of the expression."); Token last = tokens.get(tokens.size() - 1); if (expectingOperand && last.type != TokenType.TAG && last.type != TokenType.RPAREN) { - throw new IllegalArgumentException("Expression ended unexpectedly."); + throw new InvalidTagMatcherSyntaxException("Expression ended unexpectedly."); } return tokens; @@ -286,7 +300,7 @@ private static void flushTag(StringBuilder currentTag, List tokens) { } } - private static Token[] toRpn(List tokens) { + private static Token[] toRpn(List tokens) throws InvalidTagMatcherSyntaxException { ArrayList out = new ArrayList<>(tokens.size()); Deque stack = new ArrayDeque<>(); @@ -321,21 +335,21 @@ private static Token[] toRpn(List tokens) { } out.add(stack.pop()); } - if (!found) throw new IllegalArgumentException("Mismatched parentheses."); + if (!found) throw new InvalidTagMatcherSyntaxException("Mismatched parentheses."); } } } while (!stack.isEmpty()) { Token top = stack.pop(); - if (top.type == TokenType.LPAREN) throw new IllegalArgumentException("Mismatched parentheses."); + if (top.type == TokenType.LPAREN) throw new InvalidTagMatcherSyntaxException("Mismatched parentheses."); out.add(top); } return out.toArray(Token[]::new); } - private static boolean evalRpn(Token[] rpn, Set actualTags) { + private static boolean evalRpn(Token[] rpn, Set actualTags) throws InvalidTagMatcherSyntaxException { if (rpn.length == 0) return false; boolean[] stack = new boolean[rpn.length]; @@ -355,28 +369,28 @@ private static boolean evalRpn(Token[] rpn, Set actualTags) { Operator op = t.op; if (op == Operator.NOT) { - if (sp < 1) throw new IllegalArgumentException("NOT needs 1 operand."); + if (sp < 1) throw new InvalidTagMatcherSyntaxException("NOT needs 1 operand."); stack[sp - 1] = !stack[sp - 1]; } else { - if (sp < 2) throw new IllegalArgumentException(op.symbol + " needs 2 operands."); + if (sp < 2) throw new InvalidTagMatcherSyntaxException(op.symbol + " needs 2 operands."); boolean right = stack[--sp]; boolean left = stack[--sp]; boolean res = switch (op) { case AND -> left && right; case OR -> left || right; case XOR -> left ^ right; - default -> throw new IllegalStateException("Unexpected op: " + op); + default -> throw new InvalidTagMatcherSyntaxException("Unexpected op: " + op); }; stack[sp++] = res; } } else { - throw new IllegalStateException("Paren token in RPN (should not happen)."); + throw new InvalidTagMatcherSyntaxException("Paren token in RPN (should not happen)."); } } if (sp == 1) return stack[0]; - throw new IllegalArgumentException("Invalid expression: stack size " + sp); + throw new InvalidTagMatcherSyntaxException("Invalid expression: stack size " + sp); } private static boolean matchesAnyGlob(String pattern, Set tags) { From a52d0a5ada31f042d11a9580ce04a331c2a7e411 Mon Sep 17 00:00:00 2001 From: Wizzerinus Date: Sat, 7 Feb 2026 19:30:54 +0300 Subject: [PATCH 2/3] Tag Stocking: show invalid syntax visually --- .../gui/widgets/MultilineTextField.java | 16 ++++- ...nlargedTagStockingInputBusPartMachine.java | 54 ++++++++++------- ...argedTagStockingInputHatchPartMachine.java | 58 ++++++++++++------- .../METagStockingInputBusPartMachine.java | 15 ++++- .../METagStockingInputHatchPartMachine.java | 56 +++++++++++------- .../gtutilities/utils/TagMatcher.java | 29 +++++++++- 6 files changed, 160 insertions(+), 68 deletions(-) diff --git a/src/main/java/net/neganote/gtutilities/common/gui/widgets/MultilineTextField.java b/src/main/java/net/neganote/gtutilities/common/gui/widgets/MultilineTextField.java index 9701092..b28b80d 100644 --- a/src/main/java/net/neganote/gtutilities/common/gui/widgets/MultilineTextField.java +++ b/src/main/java/net/neganote/gtutilities/common/gui/widgets/MultilineTextField.java @@ -32,6 +32,7 @@ public class MultilineTextField extends WidgetGroup { private final Supplier textSupplier; private final Consumer textConsumer; private final Component placeholder; + private final Supplier borderColorSupplier; private int maxLength = DEFAULT_MAX_LENGTH; @@ -50,7 +51,7 @@ public MultilineTextField( int x, int y, int width, int height, Supplier textSupplier, Consumer textConsumer) { - this(x, y, width, height, textSupplier, textConsumer, Component.empty()); + this(x, y, width, height, textSupplier, textConsumer, Component.empty(), null); } public MultilineTextField( @@ -58,10 +59,20 @@ public MultilineTextField( Supplier textSupplier, Consumer textConsumer, Component placeholder) { + this(x, y, width, height, textSupplier, textConsumer, placeholder, null); + } + + public MultilineTextField( + int x, int y, int width, int height, + Supplier textSupplier, + Consumer textConsumer, + Component placeholder, + Supplier borderColorSupplier) { super(new Position(x, y), new Size(width, height)); this.textSupplier = textSupplier; this.textConsumer = textConsumer; this.placeholder = placeholder == null ? Component.empty() : placeholder; + this.borderColorSupplier = borderColorSupplier; String init = safe(textSupplier.get()); this.lastSent = init; @@ -467,7 +478,8 @@ public void drawInBackground(net.minecraft.client.gui.GuiGraphics graphics, int int h = getSize().height; int bg = 0xFF202020; - int border = hasFocus ? 0xFFFFFFFF : 0xFF808080; + Integer suppliedBorder = borderColorSupplier != null ? borderColorSupplier.get() : null; + int border = suppliedBorder != null ? suppliedBorder : hasFocus ? 0xFFFFFFFF : 0xFF808080; graphics.fill(x0 - 1, y0 - 1, x0 + w + 1, y0 + h + 1, 0xAA000000); graphics.fill(x0, y0, x0 + w, y0 + h, bg); diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java index 59485a0..3f3e4c4 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java @@ -1,5 +1,18 @@ package net.neganote.gtutilities.integration.ae2.machine; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.jetbrains.annotations.Nullable; + import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; @@ -15,7 +28,6 @@ import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEItemList; import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEItemSlot; import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAESlot; - import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.TextTexture; import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; @@ -27,6 +39,14 @@ import com.lowdragmc.lowdraglib.utils.Position; import com.lowdragmc.lowdraglib.utils.Size; +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.ChatFormatting; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; @@ -43,23 +63,6 @@ import net.neganote.gtutilities.config.UtilConfig; import net.neganote.gtutilities.utils.TagMatcher; -import appeng.api.config.Actionable; -import appeng.api.networking.IGrid; -import appeng.api.stacks.AEItemKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.api.storage.MEStorage; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import javax.annotation.ParametersAreNonnullByDefault; - @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class MEEnlargedTagStockingInputBusPartMachine extends MEStockingBusPartMachine { @@ -75,6 +78,12 @@ public class MEEnlargedTagStockingInputBusPartMachine extends MEStockingBusPartM @DescSynced protected String blacklistExpr = ""; + @DescSynced + protected boolean whitelistBadSyntax = false; + + @DescSynced + protected boolean blacklistBadSyntax = false; + @DescSynced protected String Wltmp = ""; @@ -168,11 +177,13 @@ private void ensureCompiledUpToDate() { if (!Objects.equals(wl, wlLast)) { wlLast = wl; wlCompiled = TagMatcher.compile(wl); + whitelistBadSyntax = !wlCompiled.isValid(); decisionCache.clear(); } if (!Objects.equals(bl, blLast)) { blLast = bl; blCompiled = TagMatcher.compile(bl); + blacklistBadSyntax = !blCompiled.isValid(); decisionCache.clear(); } @@ -183,6 +194,7 @@ private void ensureCompiledUpToDate() { protected boolean isAllowed(AEItemKey key) { ensureCompiledUpToDate(); + if (whitelistBadSyntax || blacklistBadSyntax) return false; if ((wlLast == null || wlLast.isEmpty()) && (blLast == null || blLast.isEmpty())) return false; @@ -380,7 +392,8 @@ public boolean mouseWheelMove(double mouseX, double mouseY, double wheelDelta) { 7, y, 160, 25, () -> Wltmp, v -> { Wltmp = v; }, - Component.literal("Whitelist tags...")); + Component.literal("Whitelist tags..."), + () -> whitelistBadSyntax ? 0xFFFF0000 : null); group.addWidget(WLField); y += 29; @@ -388,7 +401,8 @@ public boolean mouseWheelMove(double mouseX, double mouseY, double wheelDelta) { 7, y, 160, 25, () -> Bltmp, v -> { Bltmp = v; }, - Component.literal("Blacklist tags...")); + Component.literal("Blacklist tags..."), + () -> blacklistBadSyntax ? 0xFFFF0000 : null); group.addWidget(BLField); WLField.setDirectly(whitelistExpr); diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java index 4c83e35..e843fa7 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java @@ -1,5 +1,18 @@ package net.neganote.gtutilities.integration.ae2.machine; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.jetbrains.annotations.Nullable; + import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; @@ -12,9 +25,10 @@ import com.gregtechceu.gtceu.config.ConfigHolder; import com.gregtechceu.gtceu.integration.ae2.gui.widget.AEFluidConfigWidget; import com.gregtechceu.gtceu.integration.ae2.machine.MEStockingHatchPartMachine; -import com.gregtechceu.gtceu.integration.ae2.slot.*; +import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEFluidList; +import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEFluidSlot; +import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAESlot; import com.gregtechceu.gtceu.integration.ae2.utils.AEUtil; - import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.TextTexture; import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; @@ -26,6 +40,14 @@ import com.lowdragmc.lowdraglib.utils.Position; import com.lowdragmc.lowdraglib.utils.Size; +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -40,23 +62,6 @@ import net.neganote.gtutilities.config.UtilConfig; import net.neganote.gtutilities.utils.TagMatcher; -import appeng.api.config.Actionable; -import appeng.api.networking.IGrid; -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.api.storage.MEStorage; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import org.jetbrains.annotations.Nullable; - -import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import javax.annotation.ParametersAreNonnullByDefault; - @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class MEEnlargedTagStockingInputHatchPartMachine extends MEStockingHatchPartMachine { @@ -75,6 +80,12 @@ public class MEEnlargedTagStockingInputHatchPartMachine extends MEStockingHatchP @DescSynced protected String Bltmp = ""; + @DescSynced + protected boolean whitelistBadSyntax = false; + + @DescSynced + protected boolean blacklistBadSyntax = false; + private Predicate tagAutoPullTest = ($) -> true; private transient String wlLast = null; @@ -161,11 +172,13 @@ private void ensureCompiledUpToDate() { if (!Objects.equals(wl, wlLast)) { wlLast = wl; wlCompiled = TagMatcher.compile(wl); + whitelistBadSyntax = !wlCompiled.isValid(); decisionCache.clear(); } if (!Objects.equals(bl, blLast)) { blLast = bl; blCompiled = TagMatcher.compile(bl); + blacklistBadSyntax = !blCompiled.isValid(); decisionCache.clear(); } @@ -176,6 +189,7 @@ private void ensureCompiledUpToDate() { protected boolean isAllowed(AEFluidKey key) { ensureCompiledUpToDate(); + if (whitelistBadSyntax || blacklistBadSyntax) return false; if ((wlLast == null || wlLast.isEmpty()) && (blLast == null || blLast.isEmpty())) return false; @@ -367,7 +381,8 @@ public boolean mouseWheelMove(double mouseX, double mouseY, double wheelDelta) { 7, y, 160, 25, () -> Wltmp, v -> { Wltmp = v; }, - Component.literal("Whitelist tags...")); + Component.literal("Whitelist tags..."), + () -> whitelistBadSyntax ? 0xFFFF0000 : null); group.addWidget(WLField); y += 29; @@ -375,7 +390,8 @@ public boolean mouseWheelMove(double mouseX, double mouseY, double wheelDelta) { 7, y, 160, 25, () -> Bltmp, v -> { Bltmp = v; }, - Component.literal("Blacklist tags...")); + Component.literal("Blacklist tags..."), + () -> blacklistBadSyntax ? 0xFFFF0000 : null); group.addWidget(BLField); WLField.setDirectly(whitelistExpr); diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputBusPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputBusPartMachine.java index 9eda414..8e2ba70 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputBusPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputBusPartMachine.java @@ -77,6 +77,12 @@ public class METagStockingInputBusPartMachine extends MEStockingBusPartMachine { @DescSynced protected String Bltmp = ""; + @DescSynced + protected boolean whitelistBadSyntax = false; + + @DescSynced + protected boolean blacklistBadSyntax = false; + private Predicate tagAutoPullTest = ($) -> true; private transient String wlLast = null; @@ -159,11 +165,13 @@ private void ensureCompiledUpToDate() { if (!Objects.equals(wl, wlLast)) { wlLast = wl; wlCompiled = TagMatcher.compile(wl); + whitelistBadSyntax = !wlCompiled.isValid(); decisionCache.clear(); } if (!Objects.equals(bl, blLast)) { blLast = bl; blCompiled = TagMatcher.compile(bl); + blacklistBadSyntax = !blCompiled.isValid(); decisionCache.clear(); } @@ -174,6 +182,7 @@ private void ensureCompiledUpToDate() { protected boolean isAllowed(AEItemKey key) { ensureCompiledUpToDate(); + if (whitelistBadSyntax || blacklistBadSyntax) return false; if ((wlLast == null || wlLast.isEmpty()) && (blLast == null || blLast.isEmpty())) return false; @@ -283,7 +292,8 @@ public Widget createUIWidget() { v -> { Wltmp = v; }, - Component.literal("Whitelist tags...")); + Component.literal("Whitelist tags..."), + () -> whitelistBadSyntax ? 0xFFFF0000 : null); group.addWidget(WLField); y += 29; @@ -293,7 +303,8 @@ public Widget createUIWidget() { v -> { Bltmp = v; }, - Component.literal("Blacklist tags...")); + Component.literal("Blacklist tags..."), + () -> blacklistBadSyntax ? 0xFFFF0000 : null); group.addWidget(BLField); WLField.setDirectly(whitelistExpr); diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java index 3f8ea55..80d249c 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java @@ -1,5 +1,15 @@ package net.neganote.gtutilities.integration.ae2.machine; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.function.Predicate; + +import javax.annotation.ParametersAreNonnullByDefault; + +import org.jetbrains.annotations.Nullable; + import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; @@ -13,9 +23,10 @@ import com.gregtechceu.gtceu.integration.ae2.gui.widget.AEFluidConfigWidget; import com.gregtechceu.gtceu.integration.ae2.machine.MEHatchPartMachine; import com.gregtechceu.gtceu.integration.ae2.machine.MEStockingHatchPartMachine; -import com.gregtechceu.gtceu.integration.ae2.slot.*; +import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEFluidList; +import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEFluidSlot; +import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAESlot; import com.gregtechceu.gtceu.integration.ae2.utils.AEUtil; - import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.TextTexture; import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; @@ -27,6 +38,14 @@ import com.lowdragmc.lowdraglib.utils.Position; import com.lowdragmc.lowdraglib.utils.Size; +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -39,24 +58,6 @@ import net.neganote.gtutilities.common.gui.widgets.MultilineTextField; import net.neganote.gtutilities.utils.TagMatcher; -import appeng.api.config.Actionable; -import appeng.api.networking.IGrid; -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.api.storage.MEStorage; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; -import org.jetbrains.annotations.Nullable; - -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.function.Predicate; - -import javax.annotation.ParametersAreNonnullByDefault; - @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class METagStockingInputHatchPartMachine extends MEStockingHatchPartMachine { @@ -75,6 +76,12 @@ public class METagStockingInputHatchPartMachine extends MEStockingHatchPartMachi @DescSynced protected String Bltmp = ""; + @DescSynced + protected boolean whitelistBadSyntax = false; + + @DescSynced + protected boolean blacklistBadSyntax = false; + private Predicate tagAutoPullTest = ($) -> true; private transient String wlLast = null; @@ -157,11 +164,13 @@ private void ensureCompiledUpToDate() { if (!Objects.equals(wl, wlLast)) { wlLast = wl; wlCompiled = TagMatcher.compile(wl); + whitelistBadSyntax = !wlCompiled.isValid(); decisionCache.clear(); } if (!Objects.equals(bl, blLast)) { blLast = bl; blCompiled = TagMatcher.compile(bl); + blacklistBadSyntax = !wlCompiled.isValid(); decisionCache.clear(); } @@ -172,6 +181,7 @@ private void ensureCompiledUpToDate() { protected boolean isAllowed(AEFluidKey key) { ensureCompiledUpToDate(); + if (whitelistBadSyntax || blacklistBadSyntax) return false; if ((wlLast == null || wlLast.isEmpty()) && (blLast == null || blLast.isEmpty())) return false; @@ -282,7 +292,8 @@ public Widget createUIWidget() { v -> { Wltmp = v; }, - Component.literal("Whitelist tags...")); + Component.literal("Whitelist tags..."), + () -> whitelistBadSyntax ? 0xFFFF0000 : null); group.addWidget(WLField); y += 29; @@ -292,7 +303,8 @@ public Widget createUIWidget() { v -> { Bltmp = v; }, - Component.literal("Blacklist tags...")); + Component.literal("Blacklist tags..."), + () -> blacklistBadSyntax ? 0xFFFF0000 : null); group.addWidget(BLField); WLField.setDirectly(whitelistExpr); diff --git a/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java b/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java index af89d81..07272bb 100644 --- a/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java +++ b/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java @@ -346,7 +346,34 @@ private static Token[] toRpn(List tokens) throws InvalidTagMatcherSyntaxE out.add(top); } - return out.toArray(Token[]::new); + Token[] rpn = out.toArray(Token[]::new); + validateRpnStackDepth(rpn); + return rpn; + } + + private static void validateRpnStackDepth(Token[] rpn) throws InvalidTagMatcherSyntaxException { + int sp = 0; + for (Token t : rpn) { + switch (t.type) { + case TAG: + sp++; + break; + case OPERATOR: + int requiredDepth = t.op == Operator.NOT ? 1 : 2; + sp -= requiredDepth; + if (sp < 0) { + throw new InvalidTagMatcherSyntaxException("Unexpected operator " + t.op); + } + sp++; + break; + case LPAREN: + case RPAREN: + throw new InvalidTagMatcherSyntaxException("Unexpected token: " + t.type); + } + } + if (sp != 1) { + throw new InvalidTagMatcherSyntaxException("Depth at the end should equal 1"); + } } private static boolean evalRpn(Token[] rpn, Set actualTags) throws InvalidTagMatcherSyntaxException { From 2edb9db3ca5f0d0bf1f237e3d79dbbe0ba1163f5 Mon Sep 17 00:00:00 2001 From: Wizzerinus Date: Sun, 8 Feb 2026 07:51:41 +0300 Subject: [PATCH 3/3] spotlessApply --- ...nlargedTagStockingInputBusPartMachine.java | 43 ++++++++++--------- ...argedTagStockingInputHatchPartMachine.java | 43 ++++++++++--------- .../METagStockingInputHatchPartMachine.java | 37 ++++++++-------- .../gtutilities/utils/TagMatcher.java | 25 ++++++----- 4 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java index 3f3e4c4..139d9fd 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputBusPartMachine.java @@ -1,18 +1,5 @@ package net.neganote.gtutilities.integration.ae2.machine; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import javax.annotation.ParametersAreNonnullByDefault; - -import org.jetbrains.annotations.Nullable; - import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; @@ -28,6 +15,7 @@ import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEItemList; import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEItemSlot; import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAESlot; + import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.TextTexture; import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; @@ -39,14 +27,6 @@ import com.lowdragmc.lowdraglib.utils.Position; import com.lowdragmc.lowdraglib.utils.Size; -import appeng.api.config.Actionable; -import appeng.api.networking.IGrid; -import appeng.api.stacks.AEItemKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.api.storage.MEStorage; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.ChatFormatting; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; @@ -63,6 +43,27 @@ import net.neganote.gtutilities.config.UtilConfig; import net.neganote.gtutilities.utils.TagMatcher; +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.stacks.AEItemKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javax.annotation.ParametersAreNonnullByDefault; + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class MEEnlargedTagStockingInputBusPartMachine extends MEStockingBusPartMachine { diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java index e843fa7..c21f35e 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/MEEnlargedTagStockingInputHatchPartMachine.java @@ -1,18 +1,5 @@ package net.neganote.gtutilities.integration.ae2.machine; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import javax.annotation.ParametersAreNonnullByDefault; - -import org.jetbrains.annotations.Nullable; - import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; @@ -29,6 +16,7 @@ import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEFluidSlot; import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAESlot; import com.gregtechceu.gtceu.integration.ae2.utils.AEUtil; + import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.TextTexture; import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; @@ -40,14 +28,6 @@ import com.lowdragmc.lowdraglib.utils.Position; import com.lowdragmc.lowdraglib.utils.Size; -import appeng.api.config.Actionable; -import appeng.api.networking.IGrid; -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.api.storage.MEStorage; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -62,6 +42,27 @@ import net.neganote.gtutilities.config.UtilConfig; import net.neganote.gtutilities.utils.TagMatcher; +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import javax.annotation.ParametersAreNonnullByDefault; + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class MEEnlargedTagStockingInputHatchPartMachine extends MEStockingHatchPartMachine { diff --git a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java index 80d249c..fac6d15 100644 --- a/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java +++ b/src/main/java/net/neganote/gtutilities/integration/ae2/machine/METagStockingInputHatchPartMachine.java @@ -1,15 +1,5 @@ package net.neganote.gtutilities.integration.ae2.machine; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; -import java.util.PriorityQueue; -import java.util.function.Predicate; - -import javax.annotation.ParametersAreNonnullByDefault; - -import org.jetbrains.annotations.Nullable; - import com.gregtechceu.gtceu.api.gui.GuiTextures; import com.gregtechceu.gtceu.api.gui.fancy.ConfiguratorPanel; import com.gregtechceu.gtceu.api.gui.fancy.IFancyConfiguratorButton; @@ -27,6 +17,7 @@ import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAEFluidSlot; import com.gregtechceu.gtceu.integration.ae2.slot.ExportOnlyAESlot; import com.gregtechceu.gtceu.integration.ae2.utils.AEUtil; + import com.lowdragmc.lowdraglib.gui.texture.GuiTextureGroup; import com.lowdragmc.lowdraglib.gui.texture.TextTexture; import com.lowdragmc.lowdraglib.gui.widget.LabelWidget; @@ -38,14 +29,6 @@ import com.lowdragmc.lowdraglib.utils.Position; import com.lowdragmc.lowdraglib.utils.Size; -import appeng.api.config.Actionable; -import appeng.api.networking.IGrid; -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEKey; -import appeng.api.stacks.GenericStack; -import appeng.api.storage.MEStorage; -import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2LongMap; import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.core.Direction; import net.minecraft.nbt.CompoundTag; @@ -58,6 +41,24 @@ import net.neganote.gtutilities.common.gui.widgets.MultilineTextField; import net.neganote.gtutilities.utils.TagMatcher; +import appeng.api.config.Actionable; +import appeng.api.networking.IGrid; +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEKey; +import appeng.api.stacks.GenericStack; +import appeng.api.storage.MEStorage; +import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2LongMap; +import org.jetbrains.annotations.Nullable; + +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.function.Predicate; + +import javax.annotation.ParametersAreNonnullByDefault; + @ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault public class METagStockingInputHatchPartMachine extends MEStockingHatchPartMachine { diff --git a/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java b/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java index 07272bb..ce3f040 100644 --- a/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java +++ b/src/main/java/net/neganote/gtutilities/utils/TagMatcher.java @@ -1,5 +1,14 @@ package net.neganote.gtutilities.utils; +import net.minecraft.core.Holder; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.material.Fluid; + +import appeng.api.stacks.AEFluidKey; +import appeng.api.stacks.AEItemKey; +import lombok.Getter; +import org.jetbrains.annotations.Nullable; + import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; @@ -9,18 +18,10 @@ import java.util.Map; import java.util.Set; -import org.jetbrains.annotations.Nullable; - -import appeng.api.stacks.AEFluidKey; -import appeng.api.stacks.AEItemKey; -import lombok.Getter; -import net.minecraft.core.Holder; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.material.Fluid; - public final class TagMatcher { private static final class InvalidTagMatcherSyntaxException extends Exception { + private InvalidTagMatcherSyntaxException(String s) { super(s); } @@ -236,7 +237,8 @@ private static List tokenize(String expression) throws InvalidTagMatcherS char c = expression.charAt(i); if (c == '#') { - throw new InvalidTagMatcherSyntaxException("Character '#' is not allowed in tag expressions (pos " + i + ")."); + throw new InvalidTagMatcherSyntaxException( + "Character '#' is not allowed in tag expressions (pos " + i + ")."); } if (Character.isWhitespace(c)) continue; @@ -250,7 +252,8 @@ private static List tokenize(String expression) throws InvalidTagMatcherS lastIsTag = false; } else if (c == ')') { - if (expectingOperand && lp <= 0) throw new InvalidTagMatcherSyntaxException("Unexpected ')' at position " + i); + if (expectingOperand && lp <= 0) + throw new InvalidTagMatcherSyntaxException("Unexpected ')' at position " + i); flushTag(currentTag, tokens); tokens.add(Token.rparen()); expectingOperand = false;