diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/NbtPath.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/NbtPath.java index 3d9f7dc00e3..fd308162fec 100644 --- a/src/main/java/org/cyclops/cyclopscore/nbt/path/NbtPath.java +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/NbtPath.java @@ -21,12 +21,17 @@ public class NbtPath { new NbtPathExpressionParseHandlerListElement(), new NbtPathExpressionParseHandlerListSlice(), new NbtPathExpressionParseHandlerUnion(), + new NbtPathExpressionParseHandlerGrouping(), new NbtPathExpressionParseHandlerBooleanRelationalLessThan(), new NbtPathExpressionParseHandlerBooleanRelationalLessThanOrEqual(), new NbtPathExpressionParseHandlerBooleanRelationalGreaterThan(), new NbtPathExpressionParseHandlerBooleanRelationalGreaterThanOrEqual(), + new NbtPathExpressionParseHandlerBooleanRelationalNotEqual(), new NbtPathExpressionParseHandlerBooleanRelationalEqual(), new NbtPathExpressionParseHandlerStringEqual(), + new NbtPathExpressionParseHandlerBooleanLogicalAnd(), + new NbtPathExpressionParseHandlerBooleanLogicalOr(), + new NbtPathExpressionParseHandlerBooleanLogicalNot(), new NbtPathExpressionParseHandlerFilterExpression() ); diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalAdapter.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalAdapter.java new file mode 100644 index 00000000000..062b4f97487 --- /dev/null +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalAdapter.java @@ -0,0 +1,151 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.Tag; +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.cyclops.cyclopscore.nbt.path.NbtParseException; +import org.cyclops.cyclopscore.nbt.path.NbtPath; +import org.cyclops.cyclopscore.nbt.path.NbtPathExpressionMatches; + +import javax.annotation.Nullable; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * @author rubensworks + */ +public abstract class NbtPathExpressionParseHandlerBooleanLogicalAdapter implements INbtPathExpressionParseHandler { + + private final Pattern regex; + + protected NbtPathExpressionParseHandlerBooleanLogicalAdapter(String relation) { + this.regex = Pattern.compile("^ *" + relation + " *"); + } + + protected abstract boolean getLogicalValue(boolean left, Supplier right); + + /** + * Determine if a tag is truthy. + * ByteTag with value 1 is true, 0 is false. + * Any other non-null tag is considered true. + * This follows the same logic as {@link org.cyclops.cyclopscore.nbt.path.INbtPathExpression#test(Tag)}. + * + * @param tag The tag to check + * @return true if the tag is truthy, false otherwise + */ + public static boolean isTruthy(Tag tag) { + if (tag == null) { + return false; + } + if (tag.getId() == Tag.TAG_BYTE) { + return ((ByteTag) tag).getAsByte() == (byte) 1; + } + // Non-null non-ByteTags are truthy + return true; + } + + /** + * Find the end position of an expression, stopping at logical operators or closing parenthesis. + * This method is shared by logical operator handlers to identify expression boundaries. + * + * @param expression The full expression string + * @param start The starting position to search from + * @return The position where the expression ends + */ + public static int findExpressionEnd(String expression, int start) { + int depth = 0; + for (int i = start; i < expression.length(); i++) { + char c = expression.charAt(i); + + if (c == '(') { + depth++; + } else if (c == ')') { + if (depth == 0) { + return i; + } + depth--; + } else if (depth == 0) { + // Check for logical operators at top level + if (i + 1 < expression.length()) { + String twoChar = expression.substring(i, i + 2); + if (twoChar.equals("&&") || twoChar.equals("||")) { + return i; + } + } + // Check for NOT operator (but not != which is handled differently) + if (c == '!' && (i + 1 >= expression.length() || expression.charAt(i + 1) != '=')) { + return i; + } + } + } + return expression.length(); + } + + @Nullable + @Override + public HandleResult handlePrefixOf(String nbtPathExpression, int pos) { + Matcher matcher = this.regex + .matcher(nbtPathExpression) + .region(pos, nbtPathExpression.length()); + if (!matcher.find()) { + return HandleResult.INVALID; + } + + // Parse the right-hand side expression + int rightPos = pos + matcher.group().length(); + if (rightPos >= nbtPathExpression.length()) { + return HandleResult.INVALID; + } + + // Find the end of the right expression + int endPos = findExpressionEnd(nbtPathExpression, rightPos); + if (endPos == rightPos) { + return HandleResult.INVALID; + } + + String rightExpressionString = nbtPathExpression.substring(rightPos, endPos); + try { + INbtPathExpression rightExpression = NbtPath.parse(rightExpressionString.trim()); + return new HandleResult(new Expression(rightExpression, this), + matcher.group().length() + rightExpressionString.length()); + } catch (NbtParseException e) { + return HandleResult.INVALID; + } + } + + public static class Expression implements INbtPathExpression { + + protected final INbtPathExpression expression; + protected final NbtPathExpressionParseHandlerBooleanLogicalAdapter handler; + + public Expression(INbtPathExpression expression, NbtPathExpressionParseHandlerBooleanLogicalAdapter handler) { + this.expression = expression; + this.handler = handler; + } + + @Override + public NbtPathExpressionMatches matchContexts(Stream executionContexts) { + return new NbtPathExpressionMatches(executionContexts + .map(executionContext -> { + Tag currentTag = executionContext.getCurrentTag(); + + // The left side is the current tag (should be a boolean result from previous expression) + boolean leftValue = isTruthy(currentTag); + + // Evaluate the right expression against the root context + // This ensures both sides of the expression are evaluated against the same base context + Tag rootTag = executionContext.getRootContext().getCurrentTag(); + + // AND operation + boolean result = handler.getLogicalValue(leftValue, () -> expression.test(rootTag)); + + return new NbtPathExpressionExecutionContext(ByteTag.valueOf(result), executionContext); + }) + ); + } + + } + +} diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalAnd.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalAnd.java new file mode 100644 index 00000000000..2274cd0fc0d --- /dev/null +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalAnd.java @@ -0,0 +1,18 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import java.util.function.Supplier; + +/** + * A handler that handles boolean AND expressions in the form of "expression1 {@literal &&} expression2". + */ +public class NbtPathExpressionParseHandlerBooleanLogicalAnd extends NbtPathExpressionParseHandlerBooleanLogicalAdapter { + + public NbtPathExpressionParseHandlerBooleanLogicalAnd() { + super("&&"); + } + + @Override + protected boolean getLogicalValue(boolean left, Supplier right) { + return left && right.get(); + } +} diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalNot.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalNot.java new file mode 100644 index 00000000000..2354f588b81 --- /dev/null +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalNot.java @@ -0,0 +1,118 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.Tag; +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.cyclops.cyclopscore.nbt.path.NbtParseException; +import org.cyclops.cyclopscore.nbt.path.NbtPath; +import org.cyclops.cyclopscore.nbt.path.NbtPathExpressionMatches; + +import javax.annotation.Nullable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +/** + * A handler that handles boolean NOT expressions in the form of "!expression". + * Only accepts full expressions like "!(@.a {@literal <} 15)" or "!@.a", not partial expressions like "!{@literal <} 10". + */ +public class NbtPathExpressionParseHandlerBooleanLogicalNot implements INbtPathExpressionParseHandler { + + // Match ! followed by either an opening parenthesis or a path reference (@, $, etc.) + private static final Pattern REGEX_EXPRESSION = Pattern.compile("^ *!(?!=) *(?=[(@$])"); + + @Nullable + @Override + public HandleResult handlePrefixOf(String nbtPathExpression, int pos) { + Matcher matcher = REGEX_EXPRESSION + .matcher(nbtPathExpression) + .region(pos, nbtPathExpression.length()); + if (!matcher.find()) { + return HandleResult.INVALID; + } + + // Parse the expression to negate + int exprPos = pos + matcher.group().length(); + if (exprPos >= nbtPathExpression.length()) { + return HandleResult.INVALID; + } + + // Check if expression starts with parenthesis + boolean hasParenthesis = nbtPathExpression.charAt(exprPos) == '('; + int endPos; + + if (hasParenthesis) { + // Find matching closing parenthesis + endPos = findMatchingClosingParenthesis(nbtPathExpression, exprPos); + if (endPos == -1) { + return HandleResult.INVALID; + } + // Include the closing parenthesis + endPos++; + } else { + // Find the end of the expression (stops at logical operators) + endPos = NbtPathExpressionParseHandlerBooleanLogicalAdapter.findExpressionEnd(nbtPathExpression, exprPos); + if (endPos == exprPos) { + return HandleResult.INVALID; + } + } + + String expressionString = nbtPathExpression.substring(exprPos, endPos); + try { + INbtPathExpression expression = NbtPath.parse(expressionString.trim()); + return new HandleResult(new Expression(expression), + matcher.group().length() + expressionString.length()); + } catch (NbtParseException e) { + return HandleResult.INVALID; + } + } + + /** + * Find the matching closing parenthesis for an opening parenthesis. + * @param expression The expression string + * @param openPos The position of the opening parenthesis + * @return The position of the matching closing parenthesis, or -1 if not found + */ + private int findMatchingClosingParenthesis(String expression, int openPos) { + int depth = 0; + for (int i = openPos; i < expression.length(); i++) { + char c = expression.charAt(i); + if (c == '(') { + depth++; + } else if (c == ')') { + depth--; + if (depth == 0) { + return i; + } + } + } + return -1; + } + + public static class Expression implements INbtPathExpression { + + protected final INbtPathExpression expression; + + public Expression(INbtPathExpression expression) { + this.expression = expression; + } + + @Override + public NbtPathExpressionMatches matchContexts(Stream executionContexts) { + return new NbtPathExpressionMatches(executionContexts + .map(executionContext -> { + Tag currentTag = executionContext.getCurrentTag(); + + // Evaluate the expression + boolean value = expression.test(currentTag); + + // NOT operation + boolean result = !value; + + return new NbtPathExpressionExecutionContext( + ByteTag.valueOf(result ? (byte) 1 : (byte) 0), executionContext); + }) + ); + } + } +} diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalOr.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalOr.java new file mode 100644 index 00000000000..e6339e45b2d --- /dev/null +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanLogicalOr.java @@ -0,0 +1,18 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import java.util.function.Supplier; + +/** + * A handler that handles boolean OR expressions in the form of "expression1 {@literal ||} expression2". + */ +public class NbtPathExpressionParseHandlerBooleanLogicalOr extends NbtPathExpressionParseHandlerBooleanLogicalAdapter { + + public NbtPathExpressionParseHandlerBooleanLogicalOr() { + super("\\|\\|"); + } + + @Override + protected boolean getLogicalValue(boolean left, Supplier right) { + return left || right.get(); + } +} diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanRelationalNotEqual.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanRelationalNotEqual.java new file mode 100644 index 00000000000..7510389b748 --- /dev/null +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerBooleanRelationalNotEqual.java @@ -0,0 +1,15 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +/** + * A handler that handles boolean expressions in the form of " != 10". + */ +public class NbtPathExpressionParseHandlerBooleanRelationalNotEqual extends NbtPathExpressionParseHandlerBooleanRelationalAdapter { + + public NbtPathExpressionParseHandlerBooleanRelationalNotEqual() { + super("!="); + } + + protected boolean getRelationalValue(double left, double right) { + return left != right; + } +} diff --git a/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerGrouping.java b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerGrouping.java new file mode 100644 index 00000000000..ab5fa6bd435 --- /dev/null +++ b/src/main/java/org/cyclops/cyclopscore/nbt/path/parse/NbtPathExpressionParseHandlerGrouping.java @@ -0,0 +1,72 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.cyclops.cyclopscore.nbt.path.NbtParseException; +import org.cyclops.cyclopscore.nbt.path.NbtPath; + +import javax.annotation.Nullable; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A handler that handles parenthesized expressions for grouping: "(expression)". + * This allows precedence control in complex logical expressions. + */ +public class NbtPathExpressionParseHandlerGrouping implements INbtPathExpressionParseHandler { + + private static final Pattern REGEX_EXPRESSION = Pattern.compile("^ *\\("); + + @Nullable + @Override + public HandleResult handlePrefixOf(String nbtPathExpression, int pos) { + Matcher matcher = REGEX_EXPRESSION + .matcher(nbtPathExpression) + .region(pos, nbtPathExpression.length()); + if (!matcher.find()) { + return HandleResult.INVALID; + } + + // Find the matching closing parenthesis + int openPos = pos + matcher.group().length() - 1; // Position of '(' + int closePos = findMatchingClosingParenthesis(nbtPathExpression, openPos); + if (closePos == -1) { + return HandleResult.INVALID; + } + + // Extract the expression inside the parentheses + String innerExpression = nbtPathExpression.substring(openPos + 1, closePos); + + try { + // Parse the inner expression + INbtPathExpression expression = NbtPath.parse(innerExpression.trim()); + + // The grouping itself doesn't change the expression, it just controls precedence + // So we return the inner expression directly wrapped in a pass-through + return new HandleResult(expression, matcher.group().length() + innerExpression.length() + 1); + } catch (NbtParseException e) { + return HandleResult.INVALID; + } + } + + /** + * Find the matching closing parenthesis for an opening parenthesis. + * @param expression The expression string + * @param openPos The position of the opening parenthesis + * @return The position of the matching closing parenthesis, or -1 if not found + */ + private int findMatchingClosingParenthesis(String expression, int openPos) { + int depth = 0; + for (int i = openPos; i < expression.length(); i++) { + char c = expression.charAt(i); + if (c == '(') { + depth++; + } else if (c == ')') { + depth--; + if (depth == 0) { + return i; + } + } + } + return -1; + } +} diff --git a/src/test/java/org/cyclops/cyclopscore/nbt/path/TestNbtPathLogicalOperators.java b/src/test/java/org/cyclops/cyclopscore/nbt/path/TestNbtPathLogicalOperators.java new file mode 100644 index 00000000000..05717b737ae --- /dev/null +++ b/src/test/java/org/cyclops/cyclopscore/nbt/path/TestNbtPathLogicalOperators.java @@ -0,0 +1,413 @@ +package org.cyclops.cyclopscore.nbt.path; + +import com.google.common.collect.Lists; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import org.junit.Test; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestNbtPathLogicalOperators { + + @Test + public void testParseLogicalNotSimple() throws NbtParseException { + // Test: !(@.a == 5) + INbtPathExpression expression = NbtPath.parse("!@.a == 5"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 3); // 3 == 5 is false, so !(false) should be true + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + } + + @Test + public void testParseLogicalNotFalse() throws NbtParseException { + // Test: !(@.a == 5) where a is 5 + INbtPathExpression expression = NbtPath.parse("!@.a == 5"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 5); // 5 == 5 is true, so !(true) should be false + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag1), is(false)); + } + + @Test + public void testParseLogicalAndBothTrue() throws NbtParseException { + // Test: (@.a > 2) && (@.a < 10) with value 5 + INbtPathExpression expression = NbtPath.parse("@.a > 2 && @.a < 10"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 5); // 5 > 2 is true, 5 < 10 is true, so true && true = true + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + } + + @Test + public void testParseLogicalAndLeftFalse() throws NbtParseException { + // Test: (@.a > 10) && (@.a < 20) with value 5 + INbtPathExpression expression = NbtPath.parse("@.a > 10 && @.a < 20"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 5); // 5 > 10 is false, 5 < 20 is true, so false && true = false + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag1), is(false)); + } + + @Test + public void testParseLogicalOrBothFalse() throws NbtParseException { + // Test: (@.a < 2) || (@.a > 10) with value 5 + INbtPathExpression expression = NbtPath.parse("@.a < 2 || @.a > 10"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 5); // 5 < 2 is false, 5 > 10 is false, so false || false = false + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag1), is(false)); + } + + @Test + public void testParseLogicalOrRightTrue() throws NbtParseException { + // Test: (@.a < 2) || (@.a > 10) with value 15 + INbtPathExpression expression = NbtPath.parse("@.a < 2 || @.a > 10"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 15); // 15 < 2 is false, 15 > 10 is true, so false || true = true + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + } + + @Test + public void testParseComplexLogicalExpression() throws NbtParseException { + // Test: (@.a > 5) && (@.a < 15) || (@.a == 20) + INbtPathExpression expression = NbtPath.parse("@.a > 5 && @.a < 15 || @.a == 20"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 10); // 10 > 5 is true, 10 < 15 is true, so (true && true) = true, true || false = true + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + } + + @Test + public void testParseLogicalWithFilterExpression() throws NbtParseException { + // Test filter expression with OR: [?(@.value < 5 || @.value > 10)] + INbtPathExpression expression = NbtPath.parse("$.items[?(@.value < 5 || @.value > 10)]"); + + CompoundTag root = new CompoundTag(); + ListTag items = new ListTag(); + + CompoundTag item1 = new CompoundTag(); + item1.putInt("value", 3); // 3 < 5 is true, should match + + CompoundTag item2 = new CompoundTag(); + item2.putInt("value", 7); // 7 < 5 is false, 7 > 10 is false, should not match + + CompoundTag item3 = new CompoundTag(); + item3.putInt("value", 15); // 15 < 5 is false, 15 > 10 is true, should match + + items.add(item1); + items.add(item2); + items.add(item3); + root.put("items", items); + + ListTag expectedFiltered = new ListTag(); + expectedFiltered.add(item1); + expectedFiltered.add(item3); + + List expected = List.of(expectedFiltered); + assertThat(expression.match(Stream.of(root)).getMatches().collect(Collectors.toList()), equalTo(expected)); + assertThat(expression.test(root), is(true)); + } + + @Test + public void testParseLogicalWithFilterExpressionAnd() throws NbtParseException { + // Test filter expression with AND: [?(@.min < 10 && @.min > 5)] + INbtPathExpression expression = NbtPath.parse("$.items[?(@.min < 10 && @.min > 5)]"); + + CompoundTag root = new CompoundTag(); + ListTag items = new ListTag(); + + CompoundTag item1 = new CompoundTag(); + item1.putInt("min", 7); // 7 < 10 is true, 7 > 5 is true, should match + + CompoundTag item2 = new CompoundTag(); + item2.putInt("min", 3); // 3 < 10 is true, 3 > 5 is false, should not match + + CompoundTag item3 = new CompoundTag(); + item3.putInt("min", 12); // 12 < 10 is false, should not match + + items.add(item1); + items.add(item2); + items.add(item3); + root.put("items", items); + + ListTag expectedFiltered = new ListTag(); + expectedFiltered.add(item1); + + List expected = List.of(expectedFiltered); + assertThat(expression.match(Stream.of(root)).getMatches().collect(Collectors.toList()), equalTo(expected)); + assertThat(expression.test(root), is(true)); + } + + @Test + public void testParseLogicalNotWithFilterExpression() throws NbtParseException { + // Test filter expression with NOT: [?(@.active != 1)] + // Using != instead of !(...) to avoid parentheses issue with filter regex + INbtPathExpression expression = NbtPath.parse("$.items[?(@.active != 1)]"); + + CompoundTag root = new CompoundTag(); + ListTag items = new ListTag(); + + CompoundTag item1 = new CompoundTag(); + item1.putInt("active", 0); // 0 != 1 is true, should match + + CompoundTag item2 = new CompoundTag(); + item2.putInt("active", 1); // 1 != 1 is false, should not match + + CompoundTag item3 = new CompoundTag(); + item3.putInt("active", 0); // 0 != 1 is true, should match + + items.add(item1); + items.add(item2); + items.add(item3); + root.put("items", items); + + ListTag expectedFiltered = new ListTag(); + expectedFiltered.add(item1); + expectedFiltered.add(item3); + + List expected = List.of(expectedFiltered); + assertThat(expression.match(Stream.of(root)).getMatches().collect(Collectors.toList()), equalTo(expected)); + assertThat(expression.test(root), is(true)); + } + + @Test + public void testParseNotEqualTrue() throws NbtParseException { + // Test: @.a != 5 where a is 3 + INbtPathExpression expression = NbtPath.parse("@.a != 5"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 3); // 3 != 5 is true + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + } + + @Test + public void testParseNotEqualFalse() throws NbtParseException { + // Test: @.a != 5 where a is 5 + INbtPathExpression expression = NbtPath.parse("@.a != 5"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 5); // 5 != 5 is false + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag1), is(false)); + } + + @Test + public void testParseNotEqualWithFilterExpression() throws NbtParseException { + // Test filter expression with !=: [?(@.status != 0)] + INbtPathExpression expression = NbtPath.parse("$.items[?(@.status != 0)]"); + + CompoundTag root = new CompoundTag(); + ListTag items = new ListTag(); + + CompoundTag item1 = new CompoundTag(); + item1.putInt("status", 1); // 1 != 0 is true, should match + + CompoundTag item2 = new CompoundTag(); + item2.putInt("status", 0); // 0 != 0 is false, should not match + + CompoundTag item3 = new CompoundTag(); + item3.putInt("status", 2); // 2 != 0 is true, should match + + items.add(item1); + items.add(item2); + items.add(item3); + root.put("items", items); + + ListTag expectedFiltered = new ListTag(); + expectedFiltered.add(item1); + expectedFiltered.add(item3); + + List expected = List.of(expectedFiltered); + assertThat(expression.match(Stream.of(root)).getMatches().toList(), equalTo(expected)); + assertThat(expression.test(root), is(true)); + } + + @Test + public void testParseLogicalAndDifferentFields() throws NbtParseException { + // Test: (@.a > 10) && (@.b < 5) with different fields + INbtPathExpression expression = NbtPath.parse("@.a > 10 && @.b < 5"); + + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 15); + tag1.putInt("b", 3); // 15 > 10 is true, 3 < 5 is true, so true && true = true + + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + + CompoundTag tag2 = new CompoundTag(); + tag2.putInt("a", 15); + tag2.putInt("b", 8); // 15 > 10 is true, 8 < 5 is false, so true && false = false + + assertThat(expression.match(Stream.of(tag2)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag2), is(false)); + } + + @Test + public void testParseParenthesesWithOr() throws NbtParseException { + // Test: @.a > 5 && (@.a < 15 || @.a == 20) + // This should evaluate as: (a > 5) AND ((a < 15) OR (a == 20)) + INbtPathExpression expression = NbtPath.parse("@.a > 5 && (@.a < 15 || @.a == 20)"); + + // Test with a=10: 10 > 5 is true, (10 < 15 is true || 10 == 20 is false) = true, so true && true = true + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 10); + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + + // Test with a=20: 20 > 5 is true, (20 < 15 is false || 20 == 20 is true) = true, so true && true = true + CompoundTag tag2 = new CompoundTag(); + tag2.putInt("a", 20); + assertThat(expression.match(Stream.of(tag2)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag2), is(true)); + + // Test with a=3: 3 > 5 is false, doesn't matter what's in parentheses, false && anything = false + CompoundTag tag3 = new CompoundTag(); + tag3.putInt("a", 3); + assertThat(expression.match(Stream.of(tag3)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag3), is(false)); + + // Test with a=17: 17 > 5 is true, (17 < 15 is false || 17 == 20 is false) = false, so true && false = false + CompoundTag tag4 = new CompoundTag(); + tag4.putInt("a", 17); + assertThat(expression.match(Stream.of(tag4)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag4), is(false)); + } + + @Test + public void testParseParenthesesWithAnd() throws NbtParseException { + // Test: (@.a > 5 && @.a < 15) || @.a == 20 + // This should evaluate as: ((a > 5) AND (a < 15)) OR (a == 20) + INbtPathExpression expression = NbtPath.parse("(@.a > 5 && @.a < 15) || @.a == 20"); + + // Test with a=10: (10 > 5 is true && 10 < 15 is true) = true, 10 == 20 is false, so true || false = true + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 10); + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + + // Test with a=20: (20 > 5 is true && 20 < 15 is false) = false, 20 == 20 is true, so false || true = true + CompoundTag tag2 = new CompoundTag(); + tag2.putInt("a", 20); + assertThat(expression.match(Stream.of(tag2)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag2), is(true)); + + // Test with a=3: (3 > 5 is false && anything) = false, 3 == 20 is false, so false || false = false + CompoundTag tag3 = new CompoundTag(); + tag3.putInt("a", 3); + assertThat(expression.match(Stream.of(tag3)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag3), is(false)); + } + + @Test + public void testParseNestedParentheses() throws NbtParseException { + // Test: ((@.a > 5 && @.a < 15) || @.a == 20) && @.b != 0 + // This tests nested parentheses with multiple levels + INbtPathExpression expression = NbtPath.parse("((@.a > 5 && @.a < 15) || @.a == 20) && @.b != 0"); + + // Test with a=10, b=1: inner AND is true, OR with false is true, outer AND with true is true + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 10); + tag1.putInt("b", 1); + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + + // Test with a=10, b=0: left side is true but b != 0 is false, so true && false = false + CompoundTag tag2 = new CompoundTag(); + tag2.putInt("a", 10); + tag2.putInt("b", 0); + assertThat(expression.match(Stream.of(tag2)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag2), is(false)); + } + + @Test + public void testParseParenthesesWithNot() throws NbtParseException { + // Test: !(@.a > 10) && @.b < 5 + // This tests NOT with parentheses combined with AND + INbtPathExpression expression = NbtPath.parse("!(@.a > 10) && @.b < 5"); + + // Test with a=5, b=3: !(5 > 10) = !false = true, 3 < 5 = true, so true && true = true + CompoundTag tag1 = new CompoundTag(); + tag1.putInt("a", 5); + tag1.putInt("b", 3); + assertThat(expression.match(Stream.of(tag1)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 1) + ))); + assertThat(expression.test(tag1), is(true)); + + // Test with a=15, b=3: !(15 > 10) = !true = false, 3 < 5 = true, so false && true = false + CompoundTag tag2 = new CompoundTag(); + tag2.putInt("a", 15); + tag2.putInt("b", 3); + assertThat(expression.match(Stream.of(tag2)).getMatches().collect(Collectors.toList()), equalTo(Lists.newArrayList( + ByteTag.valueOf((byte) 0) + ))); + assertThat(expression.test(tag2), is(false)); + } +} diff --git a/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalAnd.java b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalAnd.java new file mode 100644 index 00000000000..27b1132e68c --- /dev/null +++ b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalAnd.java @@ -0,0 +1,101 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import com.google.common.collect.Lists; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.IntTag; +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.junit.Before; +import org.junit.Test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestNbtPathExpressionHandlerBooleanLogicalAnd { + + private NbtPathExpressionParseHandlerBooleanLogicalAnd handler; + + @Before + public void beforeEach() { + handler = new NbtPathExpressionParseHandlerBooleanLogicalAnd(); + } + + @Test + public void testNonMatch() { + assertThat(handler.handlePrefixOf("$", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testNonMatchSingleAmpersand() { + assertThat(handler.handlePrefixOf("&", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testMatchExpressionSimple() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf("&& < 10", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getConsumedExpressionLength(), is(7)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalAnd.Expression.class)); + } + + @Test + public void testMatchExpressionWithSpaces() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf(" && > 5", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getConsumedExpressionLength(), is(9)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalAnd.Expression.class)); + } + + @Test + public void testExpressionStreamBothTrue() { + // Create an expression that evaluates "< 10" (the right side of &&) + INbtPathExpression expression = handler.handlePrefixOf("&& < 10", 0).getPrefixExpression(); + + // Create execution context with ByteTag(1) as current (left side = true) + // and IntTag(5) as parent (which will be used to evaluate right side: 5 < 10 = true) + NbtPathExpressionExecutionContext parentContext = new NbtPathExpressionExecutionContext(IntTag.valueOf(5)); + NbtPathExpressionExecutionContext context = new NbtPathExpressionExecutionContext(ByteTag.valueOf((byte) 1), parentContext); + + // true && true should be true + assertThat(expression.match(Stream.of(context.getCurrentTag())) + .getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 1)))); + } + + @Test + public void testExpressionStreamLeftFalse() { + // Create an expression that evaluates "< 10" + INbtPathExpression expression = handler.handlePrefixOf("&& < 10", 0).getPrefixExpression(); + + // Create execution context with ByteTag(0) as current (left side = false) + // and IntTag(5) as parent (right side would be: 5 < 10 = true, but left is false) + NbtPathExpressionExecutionContext parentContext = new NbtPathExpressionExecutionContext(IntTag.valueOf(5)); + NbtPathExpressionExecutionContext context = new NbtPathExpressionExecutionContext(ByteTag.valueOf((byte) 0), parentContext); + + // false && true should be false + assertThat(expression.match(Stream.of(context.getCurrentTag())) + .getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 0)))); + } + + @Test + public void testExpressionStreamRightFalse() { + // Create an expression that evaluates "> 10" + INbtPathExpression expression = handler.handlePrefixOf("&& > 10", 0).getPrefixExpression(); + + // Create execution context with ByteTag(1) as current (left side = true) + // and IntTag(5) as parent (right side: 5 > 10 = false) + NbtPathExpressionExecutionContext parentContext = new NbtPathExpressionExecutionContext(IntTag.valueOf(5)); + NbtPathExpressionExecutionContext context = new NbtPathExpressionExecutionContext(ByteTag.valueOf((byte) 1), parentContext); + + // true && false should be false + assertThat(expression.match(Stream.of(context.getCurrentTag())) + .getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 0)))); + } +} diff --git a/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalNot.java b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalNot.java new file mode 100644 index 00000000000..25446fb4d5e --- /dev/null +++ b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalNot.java @@ -0,0 +1,107 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import com.google.common.collect.Lists; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.junit.Before; +import org.junit.Test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestNbtPathExpressionHandlerBooleanLogicalNot { + + private NbtPathExpressionParseHandlerBooleanLogicalNot handler; + + @Before + public void beforeEach() { + handler = new NbtPathExpressionParseHandlerBooleanLogicalNot(); + } + + @Test + public void testNonMatch() { + assertThat(handler.handlePrefixOf("$", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testNonMatchPartialExpression() { + // Should not match partial expressions like "!< 10" + assertThat(handler.handlePrefixOf("!< 10", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testMatchExpressionWithParentheses() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf("!(@.a < 10)", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalNot.Expression.class)); + } + + @Test + public void testMatchExpressionWithPath() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf("!@.a == 5", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalNot.Expression.class)); + } + + @Test + public void testMatchExpressionWithSpaces() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf(" ! (@.a > 5)", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalNot.Expression.class)); + } + + @Test + public void testExpressionStreamNegateTrue() { + // Create an expression that evaluates "!(@.a < 10)" + INbtPathExpression expression = handler.handlePrefixOf("!(@.a < 10)", 0).getPrefixExpression(); + + CompoundTag tag = new CompoundTag(); + tag.putInt("a", 5); + // 5 < 10 is true, so !(true) should be false + assertThat(expression.match(Stream.of(tag)).getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 0)))); + } + + @Test + public void testExpressionStreamNegateFalse() { + // Create an expression that evaluates "!(@.a < 10)" + INbtPathExpression expression = handler.handlePrefixOf("!(@.a < 10)", 0).getPrefixExpression(); + + CompoundTag tag = new CompoundTag(); + tag.putInt("a", 15); + // 15 < 10 is false, so !(false) should be true + assertThat(expression.match(Stream.of(tag)).getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 1)))); + } + + @Test + public void testExpressionStreamNegatePathTrue() { + // Create an expression that evaluates "!@.a == 1" + INbtPathExpression expression = handler.handlePrefixOf("!@.a == 1", 0).getPrefixExpression(); + + CompoundTag tag = new CompoundTag(); + tag.putInt("a", 1); + // @.a == 1 evaluates to: get a (=1), then == 1, result is true, so !(true) should be false + assertThat(expression.match(Stream.of(tag)).getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 0)))); + } + + @Test + public void testExpressionStreamNegatePathFalse() { + // Create an expression that evaluates "!@.a == 1" + INbtPathExpression expression = handler.handlePrefixOf("!@.a == 1", 0).getPrefixExpression(); + + CompoundTag tag = new CompoundTag(); + tag.putInt("a", 0); + // @.a == 1 evaluates to: get a (=0), then == 1, result is false, so !(false) should be true + assertThat(expression.match(Stream.of(tag)).getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 1)))); + } +} diff --git a/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalOr.java b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalOr.java new file mode 100644 index 00000000000..55ce428ab24 --- /dev/null +++ b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanLogicalOr.java @@ -0,0 +1,101 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import com.google.common.collect.Lists; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.IntTag; +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.junit.Before; +import org.junit.Test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestNbtPathExpressionHandlerBooleanLogicalOr { + + private NbtPathExpressionParseHandlerBooleanLogicalOr handler; + + @Before + public void beforeEach() { + handler = new NbtPathExpressionParseHandlerBooleanLogicalOr(); + } + + @Test + public void testNonMatch() { + assertThat(handler.handlePrefixOf("$", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testNonMatchSinglePipe() { + assertThat(handler.handlePrefixOf("|", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testMatchExpressionSimple() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf("|| < 10", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getConsumedExpressionLength(), is(7)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalOr.Expression.class)); + } + + @Test + public void testMatchExpressionWithSpaces() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf(" || > 5", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getConsumedExpressionLength(), is(9)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanLogicalOr.Expression.class)); + } + + @Test + public void testExpressionStreamBothTrue() { + // Create an expression that evaluates "< 10" + INbtPathExpression expression = handler.handlePrefixOf("|| < 10", 0).getPrefixExpression(); + + // Create execution context with ByteTag(1) as current (left side = true) + // and IntTag(5) as parent (right side: 5 < 10 = true) + NbtPathExpressionExecutionContext parentContext = new NbtPathExpressionExecutionContext(IntTag.valueOf(5)); + NbtPathExpressionExecutionContext context = new NbtPathExpressionExecutionContext(ByteTag.valueOf((byte) 1), parentContext); + + // true || true should be true + assertThat(expression.match(Stream.of(context.getCurrentTag())) + .getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 1)))); + } + + @Test + public void testExpressionStreamLeftTrue() { + // Create an expression that evaluates "> 10" + INbtPathExpression expression = handler.handlePrefixOf("|| > 10", 0).getPrefixExpression(); + + // Create execution context with ByteTag(1) as current (left side = true) + // and IntTag(5) as parent (right side: 5 > 10 = false) + NbtPathExpressionExecutionContext parentContext = new NbtPathExpressionExecutionContext(IntTag.valueOf(5)); + NbtPathExpressionExecutionContext context = new NbtPathExpressionExecutionContext(ByteTag.valueOf((byte) 1), parentContext); + + // true || false should be true + assertThat(expression.match(Stream.of(context.getCurrentTag())) + .getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 1)))); + } + + @Test + public void testExpressionStreamBothFalse() { + // Create an expression that evaluates "> 10" + INbtPathExpression expression = handler.handlePrefixOf("|| > 10", 0).getPrefixExpression(); + + // Create execution context with ByteTag(0) as current (left side = false) + // and IntTag(5) as parent (right side: 5 > 10 = false) + NbtPathExpressionExecutionContext parentContext = new NbtPathExpressionExecutionContext(IntTag.valueOf(5)); + NbtPathExpressionExecutionContext context = new NbtPathExpressionExecutionContext(ByteTag.valueOf((byte) 0), parentContext); + + // false || false should be false + assertThat(expression.match(Stream.of(context.getCurrentTag())) + .getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 0)))); + } +} diff --git a/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanRelationalNotEqual.java b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanRelationalNotEqual.java new file mode 100644 index 00000000000..20314d5eed2 --- /dev/null +++ b/src/test/java/org/cyclops/cyclopscore/nbt/path/parse/TestNbtPathExpressionHandlerBooleanRelationalNotEqual.java @@ -0,0 +1,64 @@ +package org.cyclops.cyclopscore.nbt.path.parse; + +import com.google.common.collect.Lists; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.IntTag; +import org.cyclops.cyclopscore.nbt.path.INbtPathExpression; +import org.junit.Before; +import org.junit.Test; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class TestNbtPathExpressionHandlerBooleanRelationalNotEqual { + + private NbtPathExpressionParseHandlerBooleanRelationalNotEqual handler; + + @Before + public void beforeEach() { + handler = new NbtPathExpressionParseHandlerBooleanRelationalNotEqual(); + } + + @Test + public void testNonMatch() { + assertThat(handler.handlePrefixOf("$", 0), + is(INbtPathExpressionParseHandler.HandleResult.INVALID)); + } + + @Test + public void testMatchExpressionInt() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf("!=2", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getConsumedExpressionLength(), is(3)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanRelationalNotEqual.Expression.class)); + assertThat(((NbtPathExpressionParseHandlerBooleanRelationalNotEqual.Expression) result.getPrefixExpression()).getTargetDouble(), is(2D)); + } + + @Test + public void testMatchExpressionDoubleSpaces() { + INbtPathExpressionParseHandler.HandleResult result = handler.handlePrefixOf(" != 24.23", 0); + assertThat(result.isValid(), is(true)); + assertThat(result.getConsumedExpressionLength(), is(9)); + assertThat(result.getPrefixExpression(), instanceOf(NbtPathExpressionParseHandlerBooleanRelationalNotEqual.Expression.class)); + assertThat(((NbtPathExpressionParseHandlerBooleanRelationalNotEqual.Expression) result.getPrefixExpression()).getTargetDouble(), is(24.23D)); + } + + @Test + public void testExpressionStreamSingleLeafIntValid() { + INbtPathExpression expression = handler.handlePrefixOf("aa != 3", 2).getPrefixExpression(); + assertThat(expression.match(Stream.of(IntTag.valueOf(5))).getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 1)))); + } + + @Test + public void testExpressionStreamSingleLeafIntInvalid() { + INbtPathExpression expression = handler.handlePrefixOf("aa != 3", 2).getPrefixExpression(); + assertThat(expression.match(Stream.of(IntTag.valueOf(3))).getMatches().collect(Collectors.toList()), + is(Lists.newArrayList(ByteTag.valueOf((byte) 0)))); + } + +}