From 3d38292413f5c80964ac68c981707edfbf785edb Mon Sep 17 00:00:00 2001 From: Jared Davis Date: Wed, 10 Jun 2026 19:03:22 -0400 Subject: [PATCH 1/3] Throw exception instead of returning null And improve wording per discussion with @ctrueden. Signed-off-by: Curtis Rueden --- .../eval/AbstractStandardEvaluator.java | 71 +++++++++++-------- .../eval/AbstractStandardEvaluatorTest.java | 3 +- 2 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java b/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java index f3d7226..96fb6b4 100644 --- a/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java +++ b/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java @@ -110,14 +110,25 @@ public Object function(final Object a, final Object b) { } // NB: Unknown function type. - return null; + throw new UnsupportedOperationException("function " + a + " was not found"); + } + + private static Object throwUnsupportedImplementation(final String op) { + throw new UnsupportedOperationException("Operation " + op + " is not implemented"); } + private static Object throwUnsupportedParameters(String op, Object a, Object b) { + throw new UnsupportedOperationException("Operation " + op + " did not complete with parameters A= [" + a + "] type " + type(a) + " B = [" + b + "] type " + type(b)); + } + private static String type(Object o) { + return o == null ? "" : o.getClass().getSimpleName(); + } + // -- dot -- @Override public Object dot(final Object a, final Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("dot"); } // -- groups -- @@ -142,12 +153,12 @@ public Object braces(final Object... args) { @Override public Object transpose(final Object a) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("transpose"); } @Override public Object dotTranspose(final Object a) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("dotTranspose"); } @Override @@ -156,7 +167,7 @@ public Object pow(final Object a, final Object b) { if (isD(av) && isD(bv)) return pow(d(av), d(bv)); if (isBI(av) && isI(bv)) return pow(bi(av), i(bv)); if (isBD(av) && isI(bv)) return pow(bd(av), i(bv)); - return null; + return throwUnsupportedParameters("pow", av, bv); } public double pow(final double av, final double bv) { return Math.pow(av, bv); } public BigInteger pow(final BigInteger av, final int bv) { return av.pow(bv); } @@ -164,7 +175,7 @@ public Object pow(final Object a, final Object b) { @Override public Object dotPow(final Object a, final Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("dotPow"); } // -- unary -- @@ -206,7 +217,7 @@ public Object complement(final Object a) { final Object av = value(a); if (isI(av)) return complement(i(av)); if (isL(av)) return complement(l(av)); - return null; + return throwUnsupportedParameters("complement", av, null); } public int complement(final int a) { return ~a; } public long complement(final long a) { return ~a; } @@ -226,7 +237,7 @@ public Object mul(final Object a, final Object b) { if (isD(av) && isD(bv)) return mul(d(av), d(bv)); if (isBI(av) && isBI(bv)) return mul(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return mul(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("mul", av, bv); } public int mul(final int av, final int bv) { return av * bv; } public long mul(final long av, final long bv) { return av * bv; } @@ -248,7 +259,7 @@ public Object div(final Object a, final Object b) { if (isD(av) && isD(bv)) return div(d(av), d(bv)); if (isBI(av) && isBI(bv)) return div(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return div(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("div", av, bv); } public int div(final int av, final int bv) { return av / bv; } public long div(final long av, final long bv) { return av / bv; } @@ -270,7 +281,7 @@ public Object mod(final Object a, final Object b) { if (isD(av) && isD(bv)) return mod(d(av), d(bv)); if (isBI(av) && isBI(bv)) return mod(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return mod(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("mod", av, bv); } public int mod(final int av, final int bv) { return av % bv; } public long mod(final long av, final long bv) { return av % bv; } @@ -285,22 +296,22 @@ public BigDecimal mod(final BigDecimal av, final BigDecimal bv) { @Override public Object rightDiv(final Object a, final Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("rightDiv"); } @Override public Object dotMul(Object a, Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("dotMul"); } @Override public Object dotDiv(final Object a, final Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("dotDiv"); } @Override public Object dotRightDiv(final Object a, final Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("dotRightDiv"); } // -- additive -- @@ -315,7 +326,7 @@ public Object add(final Object a, final Object b) { if (isD(av) && isD(bv)) return add(d(av), d(bv)); if (isBI(av) && isBI(bv)) return add(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return add(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("add", av, bv); } public String add(final String av, final String bv) { return av + bv; } public int add(final int av, final int bv) { return av + bv; } @@ -338,7 +349,7 @@ public Object sub(final Object a, final Object b) { if (isD(av) && isD(bv)) return sub(d(av), d(bv)); if (isBI(av) && isBI(bv)) return sub(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return sub(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("sub", av, bv); } public int sub(final int av, final int bv) { return av - bv; } public long sub(final long av, final long bv) { return av - bv; } @@ -359,7 +370,7 @@ public Object leftShift(final Object a, final Object b) { if (isI(av) && isI(bv)) return leftShift(i(av), i(bv)); if (isL(av) && isL(bv)) return leftShift(l(av), l(bv)); if (isBI(av) && isI(bv)) return leftShift(bi(av), i(bv)); - return null; + return throwUnsupportedParameters("leftShift", av, bv); } public int leftShift(final int av, final int bv) { return av << bv; } public long leftShift(final long av, final long bv) { return av << bv; } @@ -373,7 +384,7 @@ public Object rightShift(final Object a, final Object b) { if (isI(av) && isI(bv)) return rightShift(i(av), i(bv)); if (isL(av) && isL(bv)) return rightShift(l(av), l(bv)); if (isBI(av) && isI(bv)) return rightShift(bi(av), i(bv)); - return null; + return throwUnsupportedParameters("rightShift", av, bv); } public int rightShift(final int av, final int bv) { return av >> bv; } public long rightShift(final long av, final long bv) { return av >> bv; } @@ -386,7 +397,7 @@ public Object unsignedRightShift(final Object a, final Object b) { final Object av = value(a), bv = value(b); if (isI(av) && isI(bv)) return unsignedRightShift(i(av), i(bv)); if (isL(av) && isL(bv)) return unsignedRightShift(l(av), l(bv)); - return null; + return throwUnsupportedParameters("unsignedRightShift", av, bv); } public int unsignedRightShift(final int av, final int bv) { return av >>> bv; } public long unsignedRightShift(final long av, final long bv) { return av >>> bv; } @@ -404,7 +415,7 @@ public Object lessThan(final Object a, final Object b) { if (isD(av) && isD(bv)) return lessThan(d(av), d(bv)); if (isBI(av) && isBI(bv)) return lessThan(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return lessThan(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("lessThan", av, bv); } public boolean lessThan(final Comparable av, final T bv) { return av.compareTo(bv) < 0; @@ -421,7 +432,7 @@ public Object greaterThan(final Object a, final Object b) { if (isD(av) && isD(bv)) return greaterThan(d(av), d(bv)); if (isBI(av) && isBI(bv)) return greaterThan(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return greaterThan(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("greaterThan", av, bv); } public boolean greaterThan(final Comparable av, final T bv) { return av.compareTo(bv) > 0; @@ -438,7 +449,7 @@ public Object lessThanOrEqual(final Object a, final Object b) { if (isD(av) && isD(bv)) return lessThanOrEqual(d(av), d(bv)); if (isBI(av) && isBI(bv)) return lessThanOrEqual(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return lessThanOrEqual(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("lessThanOrEqual", av, bv); } public boolean lessThanOrEqual(final Comparable av, final T bv) { return av.compareTo(bv) <= 0; @@ -455,7 +466,7 @@ public Object greaterThanOrEqual(final Object a, final Object b) { if (isD(av) && isD(bv)) return greaterThanOrEqual(d(av), d(bv)); if (isBI(av) && isBI(bv)) return greaterThanOrEqual(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return greaterThanOrEqual(bd(av), bd(bv)); - return null; + return throwUnsupportedParameters("greaterThanOrEqual", av, bv); } public boolean greaterThanOrEqual(final Comparable av, final T bv) { return av.compareTo(bv) >= 0; @@ -463,7 +474,7 @@ public boolean greaterThanOrEqual(final Comparable av, final T bv) { @Override public Object instanceOf(final Object av, final Object bv) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("instanceOf"); } // -- equality -- @@ -486,7 +497,7 @@ public Object bitwiseAnd(final Object a, final Object b) { if (isI(av) && isI(bv)) return bitwiseAnd(i(av), i(bv)); if (isL(av) && isL(bv)) return bitwiseAnd(l(av), l(bv)); if (isBI(av) && isBI(bv)) return bitwiseAnd(bi(av), bi(bv)); - return null; + return throwUnsupportedParameters("bitwiseAnd", av, bv); } public int bitwiseAnd(final int av, final int bv) { return av & bv; } public long bitwiseAnd(final long av, final long bv) { return av & bv; } @@ -500,7 +511,7 @@ public Object bitwiseOr(final Object a, final Object b) { if (isI(av) && isI(bv)) return bitwiseOr(i(av), i(bv)); if (isL(av) && isL(bv)) return bitwiseOr(l(av), l(bv)); if (isBI(av) && isBI(bv)) return bitwiseOr(bi(av), bi(bv)); - return null; + return throwUnsupportedParameters("bitwiseOr", av, bv); } public int bitwiseOr(final int av, final int bv) { return av | bv; } public long bitwiseOr(final long av, final long bv) { return av | bv; } @@ -514,7 +525,7 @@ public BigInteger bitwiseOr(final BigInteger av, final BigInteger bv) { public Object logicalAnd(final Object a, final Object b) { final Object av = value(a), bv = value(b); if (isBool(av) && isBool(bv)) return logicalAnd(bool(av), bool(bv)); - return null; + return throwUnsupportedParameters("logicalAnd", av, bv); } public boolean logicalAnd(final boolean av, final boolean bv) { return av && bv; } @@ -522,7 +533,7 @@ public Object logicalAnd(final Object a, final Object b) { public Object logicalOr(final Object a, final Object b) { final Object av = value(a), bv = value(b); if (isBool(av) && isBool(bv)) return logicalOr(bool(av), bool(bv)); - return null; + return throwUnsupportedParameters("logicalOr", av, bv); } public boolean logicalOr(final boolean av, final boolean bv) { return av || bv; } @@ -530,12 +541,12 @@ public Object logicalOr(final Object a, final Object b) { @Override public Object question(final Object a, final Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("question"); } @Override public Object colon(Object a, Object b) { - throw new UnsupportedOperationException(); + return throwUnsupportedImplementation("colon"); } // -- Helper methods - type matching -- diff --git a/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java b/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java index 1aa2755..3aa2933 100644 --- a/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java +++ b/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java @@ -31,7 +31,6 @@ package org.scijava.parsington.eval; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -109,7 +108,7 @@ public void testFunction() { assertEquals("b", e.function(v, Arrays.asList(1))); assertEquals("c", e.function(v, Arrays.asList(2))); - assertNull(e.function(o(0), o(1))); + assertThrows(UnsupportedOperationException.class, () ->e.function(o(0), o(1))); } // -- dot -- From 3c8ca522ee2e8895d033e53b59d0921f70214462 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 17 Jul 2026 14:04:39 -0500 Subject: [PATCH 2/3] Tidy up exception formatting logic * Put private methods with the rest of the helpers. * Shorten the helper method names. * Distinguish between unary and binary param type failures. * Make type+value stringification logic more DRY. Finally, remove the static keyword from the helper methods. Yeah, they don't use any object instance state and can be static, but it's an extra keyword that makes the code more verbose, for not much concrete maintainability benefit. --- .../eval/AbstractStandardEvaluator.java | 88 ++++++++++--------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java b/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java index 96fb6b4..d68f508 100644 --- a/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java +++ b/src/main/java/org/scijava/parsington/eval/AbstractStandardEvaluator.java @@ -113,22 +113,11 @@ public Object function(final Object a, final Object b) { throw new UnsupportedOperationException("function " + a + " was not found"); } - private static Object throwUnsupportedImplementation(final String op) { - throw new UnsupportedOperationException("Operation " + op + " is not implemented"); - } - private static Object throwUnsupportedParameters(String op, Object a, Object b) { - throw new UnsupportedOperationException("Operation " + op + " did not complete with parameters A= [" + a + "] type " + type(a) + " B = [" + b + "] type " + type(b)); - } - private static String type(Object o) { - return o == null ? "" : o.getClass().getSimpleName(); - } - - // -- dot -- @Override public Object dot(final Object a, final Object b) { - return throwUnsupportedImplementation("dot"); + return throwUnimplementedOp("dot"); } // -- groups -- @@ -153,12 +142,12 @@ public Object braces(final Object... args) { @Override public Object transpose(final Object a) { - return throwUnsupportedImplementation("transpose"); + return throwUnimplementedOp("transpose"); } @Override public Object dotTranspose(final Object a) { - return throwUnsupportedImplementation("dotTranspose"); + return throwUnimplementedOp("dotTranspose"); } @Override @@ -167,7 +156,7 @@ public Object pow(final Object a, final Object b) { if (isD(av) && isD(bv)) return pow(d(av), d(bv)); if (isBI(av) && isI(bv)) return pow(bi(av), i(bv)); if (isBD(av) && isI(bv)) return pow(bd(av), i(bv)); - return throwUnsupportedParameters("pow", av, bv); + return throwUnsupportedTypes("pow", av, bv); } public double pow(final double av, final double bv) { return Math.pow(av, bv); } public BigInteger pow(final BigInteger av, final int bv) { return av.pow(bv); } @@ -175,7 +164,7 @@ public Object pow(final Object a, final Object b) { @Override public Object dotPow(final Object a, final Object b) { - return throwUnsupportedImplementation("dotPow"); + return throwUnimplementedOp("dotPow"); } // -- unary -- @@ -217,7 +206,7 @@ public Object complement(final Object a) { final Object av = value(a); if (isI(av)) return complement(i(av)); if (isL(av)) return complement(l(av)); - return throwUnsupportedParameters("complement", av, null); + return throwUnsupportedTypes("complement", av); } public int complement(final int a) { return ~a; } public long complement(final long a) { return ~a; } @@ -237,7 +226,7 @@ public Object mul(final Object a, final Object b) { if (isD(av) && isD(bv)) return mul(d(av), d(bv)); if (isBI(av) && isBI(bv)) return mul(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return mul(bd(av), bd(bv)); - return throwUnsupportedParameters("mul", av, bv); + return throwUnsupportedTypes("mul", av, bv); } public int mul(final int av, final int bv) { return av * bv; } public long mul(final long av, final long bv) { return av * bv; } @@ -259,7 +248,7 @@ public Object div(final Object a, final Object b) { if (isD(av) && isD(bv)) return div(d(av), d(bv)); if (isBI(av) && isBI(bv)) return div(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return div(bd(av), bd(bv)); - return throwUnsupportedParameters("div", av, bv); + return throwUnsupportedTypes("div", av, bv); } public int div(final int av, final int bv) { return av / bv; } public long div(final long av, final long bv) { return av / bv; } @@ -281,7 +270,7 @@ public Object mod(final Object a, final Object b) { if (isD(av) && isD(bv)) return mod(d(av), d(bv)); if (isBI(av) && isBI(bv)) return mod(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return mod(bd(av), bd(bv)); - return throwUnsupportedParameters("mod", av, bv); + return throwUnsupportedTypes("mod", av, bv); } public int mod(final int av, final int bv) { return av % bv; } public long mod(final long av, final long bv) { return av % bv; } @@ -296,22 +285,22 @@ public BigDecimal mod(final BigDecimal av, final BigDecimal bv) { @Override public Object rightDiv(final Object a, final Object b) { - return throwUnsupportedImplementation("rightDiv"); + return throwUnimplementedOp("rightDiv"); } @Override public Object dotMul(Object a, Object b) { - return throwUnsupportedImplementation("dotMul"); + return throwUnimplementedOp("dotMul"); } @Override public Object dotDiv(final Object a, final Object b) { - return throwUnsupportedImplementation("dotDiv"); + return throwUnimplementedOp("dotDiv"); } @Override public Object dotRightDiv(final Object a, final Object b) { - return throwUnsupportedImplementation("dotRightDiv"); + return throwUnimplementedOp("dotRightDiv"); } // -- additive -- @@ -326,7 +315,7 @@ public Object add(final Object a, final Object b) { if (isD(av) && isD(bv)) return add(d(av), d(bv)); if (isBI(av) && isBI(bv)) return add(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return add(bd(av), bd(bv)); - return throwUnsupportedParameters("add", av, bv); + return throwUnsupportedTypes("add", av, bv); } public String add(final String av, final String bv) { return av + bv; } public int add(final int av, final int bv) { return av + bv; } @@ -349,7 +338,7 @@ public Object sub(final Object a, final Object b) { if (isD(av) && isD(bv)) return sub(d(av), d(bv)); if (isBI(av) && isBI(bv)) return sub(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return sub(bd(av), bd(bv)); - return throwUnsupportedParameters("sub", av, bv); + return throwUnsupportedTypes("sub", av, bv); } public int sub(final int av, final int bv) { return av - bv; } public long sub(final long av, final long bv) { return av - bv; } @@ -370,7 +359,7 @@ public Object leftShift(final Object a, final Object b) { if (isI(av) && isI(bv)) return leftShift(i(av), i(bv)); if (isL(av) && isL(bv)) return leftShift(l(av), l(bv)); if (isBI(av) && isI(bv)) return leftShift(bi(av), i(bv)); - return throwUnsupportedParameters("leftShift", av, bv); + return throwUnsupportedTypes("leftShift", av, bv); } public int leftShift(final int av, final int bv) { return av << bv; } public long leftShift(final long av, final long bv) { return av << bv; } @@ -384,7 +373,7 @@ public Object rightShift(final Object a, final Object b) { if (isI(av) && isI(bv)) return rightShift(i(av), i(bv)); if (isL(av) && isL(bv)) return rightShift(l(av), l(bv)); if (isBI(av) && isI(bv)) return rightShift(bi(av), i(bv)); - return throwUnsupportedParameters("rightShift", av, bv); + return throwUnsupportedTypes("rightShift", av, bv); } public int rightShift(final int av, final int bv) { return av >> bv; } public long rightShift(final long av, final long bv) { return av >> bv; } @@ -397,7 +386,7 @@ public Object unsignedRightShift(final Object a, final Object b) { final Object av = value(a), bv = value(b); if (isI(av) && isI(bv)) return unsignedRightShift(i(av), i(bv)); if (isL(av) && isL(bv)) return unsignedRightShift(l(av), l(bv)); - return throwUnsupportedParameters("unsignedRightShift", av, bv); + return throwUnsupportedTypes("unsignedRightShift", av, bv); } public int unsignedRightShift(final int av, final int bv) { return av >>> bv; } public long unsignedRightShift(final long av, final long bv) { return av >>> bv; } @@ -415,7 +404,7 @@ public Object lessThan(final Object a, final Object b) { if (isD(av) && isD(bv)) return lessThan(d(av), d(bv)); if (isBI(av) && isBI(bv)) return lessThan(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return lessThan(bd(av), bd(bv)); - return throwUnsupportedParameters("lessThan", av, bv); + return throwUnsupportedTypes("lessThan", av, bv); } public boolean lessThan(final Comparable av, final T bv) { return av.compareTo(bv) < 0; @@ -432,7 +421,7 @@ public Object greaterThan(final Object a, final Object b) { if (isD(av) && isD(bv)) return greaterThan(d(av), d(bv)); if (isBI(av) && isBI(bv)) return greaterThan(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return greaterThan(bd(av), bd(bv)); - return throwUnsupportedParameters("greaterThan", av, bv); + return throwUnsupportedTypes("greaterThan", av, bv); } public boolean greaterThan(final Comparable av, final T bv) { return av.compareTo(bv) > 0; @@ -449,7 +438,7 @@ public Object lessThanOrEqual(final Object a, final Object b) { if (isD(av) && isD(bv)) return lessThanOrEqual(d(av), d(bv)); if (isBI(av) && isBI(bv)) return lessThanOrEqual(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return lessThanOrEqual(bd(av), bd(bv)); - return throwUnsupportedParameters("lessThanOrEqual", av, bv); + return throwUnsupportedTypes("lessThanOrEqual", av, bv); } public boolean lessThanOrEqual(final Comparable av, final T bv) { return av.compareTo(bv) <= 0; @@ -466,7 +455,7 @@ public Object greaterThanOrEqual(final Object a, final Object b) { if (isD(av) && isD(bv)) return greaterThanOrEqual(d(av), d(bv)); if (isBI(av) && isBI(bv)) return greaterThanOrEqual(bi(av), bi(bv)); if (isBD(av) && isBD(bv)) return greaterThanOrEqual(bd(av), bd(bv)); - return throwUnsupportedParameters("greaterThanOrEqual", av, bv); + return throwUnsupportedTypes("greaterThanOrEqual", av, bv); } public boolean greaterThanOrEqual(final Comparable av, final T bv) { return av.compareTo(bv) >= 0; @@ -474,7 +463,7 @@ public boolean greaterThanOrEqual(final Comparable av, final T bv) { @Override public Object instanceOf(final Object av, final Object bv) { - return throwUnsupportedImplementation("instanceOf"); + return throwUnimplementedOp("instanceOf"); } // -- equality -- @@ -497,7 +486,7 @@ public Object bitwiseAnd(final Object a, final Object b) { if (isI(av) && isI(bv)) return bitwiseAnd(i(av), i(bv)); if (isL(av) && isL(bv)) return bitwiseAnd(l(av), l(bv)); if (isBI(av) && isBI(bv)) return bitwiseAnd(bi(av), bi(bv)); - return throwUnsupportedParameters("bitwiseAnd", av, bv); + return throwUnsupportedTypes("bitwiseAnd", av, bv); } public int bitwiseAnd(final int av, final int bv) { return av & bv; } public long bitwiseAnd(final long av, final long bv) { return av & bv; } @@ -511,7 +500,7 @@ public Object bitwiseOr(final Object a, final Object b) { if (isI(av) && isI(bv)) return bitwiseOr(i(av), i(bv)); if (isL(av) && isL(bv)) return bitwiseOr(l(av), l(bv)); if (isBI(av) && isBI(bv)) return bitwiseOr(bi(av), bi(bv)); - return throwUnsupportedParameters("bitwiseOr", av, bv); + return throwUnsupportedTypes("bitwiseOr", av, bv); } public int bitwiseOr(final int av, final int bv) { return av | bv; } public long bitwiseOr(final long av, final long bv) { return av | bv; } @@ -525,7 +514,7 @@ public BigInteger bitwiseOr(final BigInteger av, final BigInteger bv) { public Object logicalAnd(final Object a, final Object b) { final Object av = value(a), bv = value(b); if (isBool(av) && isBool(bv)) return logicalAnd(bool(av), bool(bv)); - return throwUnsupportedParameters("logicalAnd", av, bv); + return throwUnsupportedTypes("logicalAnd", av, bv); } public boolean logicalAnd(final boolean av, final boolean bv) { return av && bv; } @@ -533,7 +522,7 @@ public Object logicalAnd(final Object a, final Object b) { public Object logicalOr(final Object a, final Object b) { final Object av = value(a), bv = value(b); if (isBool(av) && isBool(bv)) return logicalOr(bool(av), bool(bv)); - return throwUnsupportedParameters("logicalOr", av, bv); + return throwUnsupportedTypes("logicalOr", av, bv); } public boolean logicalOr(final boolean av, final boolean bv) { return av || bv; } @@ -541,12 +530,12 @@ public Object logicalOr(final Object a, final Object b) { @Override public Object question(final Object a, final Object b) { - return throwUnsupportedImplementation("question"); + return throwUnimplementedOp("question"); } @Override public Object colon(Object a, Object b) { - return throwUnsupportedImplementation("colon"); + return throwUnimplementedOp("colon"); } // -- Helper methods - type matching -- @@ -639,4 +628,23 @@ private Object callFunction(final String name, final Object b) { return null; } + // -- Helper methods - exception formatting -- + + private Object throwUnimplementedOp(final String op) { + throw new UnsupportedOperationException("Operation " + op + " is not implemented"); + } + + private Object throwUnsupportedTypes(String op, Object a) { + throw new UnsupportedOperationException("Invalid parameter type for unary operation " + op + ": " + typeAndValue(a)); + } + + private Object throwUnsupportedTypes(String op, Object a, Object + b) { + throw new UnsupportedOperationException("Invalid parameter types for binary operation " + op + ": A = " + typeAndValue(a) + ", B = " + typeAndValue(b)); + } + + private String typeAndValue(Object o) { + return o == null ? "" : o.getClass().getSimpleName() + " [" + o + "]"; + } + } From db96ba8b393c0ed07cd66cbde150de0f84d04790 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 17 Jul 2026 15:36:38 -0500 Subject: [PATCH 3/3] Beef up evaluator tests around expected exceptions --- .../eval/AbstractStandardEvaluatorTest.java | 84 +++++++++++++++---- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java b/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java index 3aa2933..579a4e8 100644 --- a/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java +++ b/src/test/java/org/scijava/parsington/eval/AbstractStandardEvaluatorTest.java @@ -41,6 +41,8 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -108,7 +110,9 @@ public void testFunction() { assertEquals("b", e.function(v, Arrays.asList(1))); assertEquals("c", e.function(v, Arrays.asList(2))); - assertThrows(UnsupportedOperationException.class, () ->e.function(o(0), o(1))); + // test invalid function + String m = assertThrows(UnsupportedOperationException.class, () -> e.function("foo", "bar")).getMessage(); + assertEquals("function foo was not found", m); } // -- dot -- @@ -116,7 +120,7 @@ public void testFunction() { /** Tests {@link StandardEvaluator#dot(Object, Object)}. */ @Test public void testDot() { - assertThrows(UnsupportedOperationException.class, () -> e.dot(o(0), o(1))); + assertUnimplemented(e::dot); } // -- groups -- @@ -159,13 +163,13 @@ public void testBraces() { /** Tests {@link StandardEvaluator#transpose(Object)}. */ @Test public void testTranspose() { - assertThrows(UnsupportedOperationException.class, () -> e.transpose(o(0))); + assertUnimplemented(e::transpose); } /** Tests {@link StandardEvaluator#dotTranspose(Object)}. */ @Test public void testDotTranspose() { - assertThrows(UnsupportedOperationException.class, () -> e.dotTranspose(o(0))); + assertUnimplemented(e::dotTranspose); } /** Tests {@link StandardEvaluator#pow(Object, Object)}. */ @@ -179,7 +183,7 @@ public void testPow() { /** Tests {@link StandardEvaluator#dotPow(Object, Object)}. */ @Test public void testDotPow() { - assertThrows(UnsupportedOperationException.class, () -> e.dotPow(o(0), o(0))); + assertUnimplemented(e::dotPow); } // -- postfix -- @@ -318,19 +322,19 @@ public void testMod() { /** Tests {@link StandardEvaluator#rightDiv(Object, Object)}. */ @Test public void testRightDiv() { - assertThrows(UnsupportedOperationException.class, () -> e.rightDiv(o(0), o(0))); + assertUnimplemented(e::rightDiv); } /** Tests {@link StandardEvaluator#dotDiv(Object, Object)}. */ @Test public void testDotDiv() { - assertThrows(UnsupportedOperationException.class, () -> e.dotDiv(o(0), o(0))); + assertUnimplemented(e::dotDiv); } /** Tests {@link StandardEvaluator#dotRightDiv(Object, Object)}. */ @Test public void testDotRightDiv() { - assertThrows(UnsupportedOperationException.class, () -> e.dotRightDiv(o(0), o(0))); + assertUnimplemented(e::dotRightDiv); } // -- additive -- @@ -345,6 +349,7 @@ public void testAdd() { assertNumber(3.6d, e.add(o(1.5d), o(2.1d))); assertNumber(bi(10), e.add(o(bi(4)), o(bi(6)))); assertNumber(bd(3.6), e.add(o(bd(1.5)), o(bd(2.1)))); + assertBadTypes(e::add, 123, "456"); } /** Tests {@link StandardEvaluator#sub(Object, Object)}. */ @@ -356,6 +361,7 @@ public void testSub() { assertNumber(1.5d, e.sub(o(3.6d), o(2.1d))); assertNumber(bi(4), e.sub(o(bi(10)), o(bi(6)))); assertNumber(bd(1.5), e.sub(o(bd(3.6)), o(bd(2.1)))); + assertBadTypes(e::sub, 456, "123"); } // -- shift -- @@ -366,6 +372,7 @@ public void testLeftShift() { assertNumber(0xafebabe0, e.leftShift(o(0xcafebabe), o(4))); assertNumber(0xdcafebeefbabe000L, e.leftShift(o(0xdeadcafebeefbabeL), o(12))); assertNumber(bi(7296), e.leftShift(o(bi(57)), o(7))); + assertBadTypes(e::leftShift, "1", "2"); } /** Tests {@link StandardEvaluator#rightShift(Object, Object)}. */ @@ -374,6 +381,7 @@ public void testRightShift() { assertNumber(0xfcafebab, e.rightShift(o(0xcafebabe), o(4))); assertNumber(0xfffdeadcafebeefbL, e.rightShift(o(0xdeadcafebeefbabeL), o(12))); assertNumber(bi(278), e.rightShift(o(bi(8920)), o(5))); + assertBadTypes(e::rightShift, "3", "4"); } /** Tests {@link StandardEvaluator#unsignedRightShift(Object, Object)}. */ @@ -381,6 +389,7 @@ public void testRightShift() { public void testUnsignedRightShift() { assertNumber(0x0cafebab, e.unsignedRightShift(o(0xcafebabe), o(4))); assertNumber(0x000deadcafebeefbL, e.unsignedRightShift(o(0xdeadcafebeefbabeL), o(12))); + assertBadTypes(e::unsignedRightShift, "5", "6"); } // -- relational -- @@ -419,6 +428,8 @@ public void testLessThan() { assertSame(true, e.lessThan(o(bd(17)), o(bd(18)))); assertSame(false, e.lessThan(o(bd(17)), o(bd(17)))); assertSame(false, e.lessThan(o(bd(17)), o(bd(16)))); + + assertBadTypes(e::lessThan, "7", 8); } /** Tests {@link StandardEvaluator#greaterThan(Object, Object)}. */ @@ -455,6 +466,8 @@ public void testGreaterThan() { assertSame(false, e.greaterThan(o(bd(17)), o(bd(18)))); assertSame(false, e.greaterThan(o(bd(17)), o(bd(17)))); assertSame(true, e.greaterThan(o(bd(17)), o(bd(16)))); + + assertBadTypes(e::greaterThan, "9", 10); } /** Tests {@link StandardEvaluator#lessThanOrEqual(Object, Object)}. */ @@ -491,6 +504,8 @@ public void testLessThanOrEqual() { assertSame(true, e.lessThanOrEqual(o(bd(17)), o(bd(18)))); assertSame(true, e.lessThanOrEqual(o(bd(17)), o(bd(17)))); assertSame(false, e.lessThanOrEqual(o(bd(17)), o(bd(16)))); + + assertBadTypes(e::lessThanOrEqual, "11", 12); } /** Tests {@link StandardEvaluator#greaterThanOrEqual(Object, Object)}. */ @@ -527,12 +542,14 @@ public void testGreaterThanOrEqual() { assertSame(false, e.greaterThanOrEqual(o(bd(17)), o(bd(18)))); assertSame(true, e.greaterThanOrEqual(o(bd(17)), o(bd(17)))); assertSame(true, e.greaterThanOrEqual(o(bd(17)), o(bd(16)))); + + assertBadTypes(e::greaterThanOrEqual, "13", 14); } /** Tests {@link StandardEvaluator#instanceOf(Object, Object)}. */ @Test public void testInstanceOf() { - assertThrows(UnsupportedOperationException.class, () -> e.instanceOf(o(0), o(0))); + assertUnimplemented(e::instanceOf); } // -- equality -- @@ -571,6 +588,9 @@ public void testEqual() { assertSame(false, e.equal(o(bd(17)), o(bd(18)))); assertSame(true, e.equal(o(bd(17)), o(bd(17)))); assertSame(false, e.equal(o(bd(17)), o(bd(16)))); + + // NB: Objects#equals supports objects of any type, + // so there is no assertBadTypes check here. } /** Tests {@link StandardEvaluator#notEqual(Object, Object)}. */ @@ -607,6 +627,9 @@ public void testNotEqual() { assertSame(true, e.notEqual(o(bd(17)), o(bd(18)))); assertSame(false, e.notEqual(o(bd(17)), o(bd(17)))); assertSame(true, e.notEqual(o(bd(17)), o(bd(16)))); + + // NB: Objects#equals supports objects of any type, + // so there is no assertBadTypes check here. } // -- bitwise -- @@ -617,6 +640,7 @@ public void testBitwiseAnd() { assertNumber(0xcaacbaae, e.bitwiseAnd(o(0xcafebabe), o(0xdeadbeef))); assertNumber(0L, e.bitwiseAnd(o(0x0d0e0a0d0c0a0f0eL), o(0xb0e0e0f0b0a0b0e0L))); assertNumber(bi(0xcaacbaae), e.bitwiseAnd(o(bi(0xcafebabe)), o(bi(0xdeadbeef)))); + assertBadTypes(e::bitwiseAnd, "19", 20); } /** Tests {@link StandardEvaluator#bitwiseOr(Object, Object)}. */ @@ -625,6 +649,7 @@ public void testBitwiseOr() { assertNumber(0xdeffbeff, e.bitwiseOr(o(0xcafebabe), o(0xdeadbeef))); assertNumber(0xbdeeeafdbcaabfeeL, e.bitwiseOr(o(0x0d0e0a0d0c0a0f0eL), o(0xb0e0e0f0b0a0b0e0L))); assertNumber(bi(0xdeffbeff), e.bitwiseOr(o(bi(0xcafebabe)), o(bi(0xdeadbeef)))); + assertBadTypes(e::bitwiseOr, "21", 22); } // -- logical -- @@ -636,6 +661,7 @@ public void testLogicalAnd() { assertSame(false, e.logicalAnd(o(false), o(true))); assertSame(false, e.logicalAnd(o(true), o(false))); assertSame(true, e.logicalAnd(o(true), o(true))); + assertBadTypes(e::logicalAnd, 23, 24); } /** Tests {@link StandardEvaluator#logicalOr(Object, Object)}. */ @@ -645,6 +671,7 @@ public void testLogicalOr() { assertSame(true, e.logicalOr(o(false), o(true))); assertSame(true, e.logicalOr(o(true), o(false))); assertSame(true, e.logicalOr(o(true), o(true))); + assertBadTypes(e::logicalOr, 25, 26); } // -- ternary -- @@ -652,13 +679,13 @@ public void testLogicalOr() { /** Tests {@link StandardEvaluator#question(Object, Object)}. */ @Test public void testQuestion() { - assertThrows(UnsupportedOperationException.class, () -> e.question(o(0), o(0))); + assertUnimplemented(e::question); } /** Tests {@link StandardEvaluator#colon(Object, Object)}. */ @Test public void testColon() { - assertThrows(UnsupportedOperationException.class, () -> e.colon(o(0), o(0))); + assertUnimplemented(e::colon); } // -- assignment -- @@ -675,6 +702,8 @@ public void testAssign() { assertAssigned(4d, v, e.assign(v, 4d)); assertAssigned(bi(5), v, e.assign(v, bi(5))); assertAssigned(bd(6), v, e.assign(v, bd(6))); + String m = assertThrows(IllegalArgumentException.class, () -> e.assign(27, "28")).getMessage(); + assertTrue(m.startsWith("Not a variable: "), "Unexpected exception message: " + m); } /** Tests {@link StandardEvaluator#powAssign(Object, Object)}. */ @@ -684,12 +713,13 @@ public void testPowAssign() { e.set(v, 2.5d); assertAssigned(15.625d, v, e.powAssign(v, 3)); e.set(v, bi(5)); assertAssigned(bi(15625), v, e.powAssign(v, 6)); e.set(v, bd(2.5)); assertAssigned(bd(15.625), v, e.powAssign(v, 3)); + assertBadTypes(e::powAssign, 29, "30"); } /** Tests {@link StandardEvaluator#dotPowAssign(Object, Object)}. */ @Test public void testDotPowAssign() { - // NB: Nothing to test; dotPow is unimplemented. + assertUnimplemented(e::dotPowAssign); } /** Tests {@link StandardEvaluator#mulAssign(Object, Object)}. */ @@ -702,6 +732,7 @@ public void testMulAssign() { e.set(v, 2.5d); assertAssigned(8.75d, v, e.mulAssign(v, 3.5d)); e.set(v, bi(4)); assertAssigned(bi(24), v, e.mulAssign(v, bi(6))); e.set(v, bd(2.5)); assertAssigned(bd(8.75), v, e.mulAssign(v, bd(3.5))); + assertBadTypes(e::mulAssign, 31, "32"); } /** Tests {@link StandardEvaluator#divAssign(Object, Object)}. */ @@ -714,6 +745,7 @@ public void testDivAssign() { e.set(v, 8.75d); assertAssigned(2.5d, v, e.divAssign(v, 3.5d)); e.set(v, bi(27)); assertAssigned(bi(4), v, e.divAssign(v, bi(6))); e.set(v, bd(8.75)); assertAssigned(bd(2.5), v, e.divAssign(v, bd(3.5))); + assertBadTypes(e::divAssign, 33, "34"); } /** Tests {@link StandardEvaluator#modAssign(Object, Object)}. */ @@ -726,24 +758,25 @@ public void testModAssign() { e.set(v, 8.75d); assertAssigned(1.75d, v, e.modAssign(v, 3.5d)); e.set(v, bi(27)); assertAssigned(bi(3), v, e.modAssign(v, bi(6))); e.set(v, bd(8.75)); assertAssigned(bd(1.75), v, e.modAssign(v, bd(3.5))); + assertBadTypes(e::modAssign, 35, "36"); } /** Tests {@link StandardEvaluator#rightDivAssign(Object, Object)}. */ @Test public void testRightDivAssign() { - // NB: Nothing to test; rightDiv is unimplemented. + assertUnimplemented(e::rightDiv); } /** Tests {@link StandardEvaluator#dotDivAssign(Object, Object)}. */ @Test public void testDotDivAssign() { - // NB: Nothing to test; dotDiv is unimplemented. + assertUnimplemented(e::dotDivAssign); } /** Tests {@link StandardEvaluator#dotRightDivAssign(Object, Object)}. */ @Test public void testDotRightDivAssign() { - // NB: Nothing to test; dotRightDiv is unimplemented. + assertUnimplemented(e::dotRightDiv); } /** Tests {@link StandardEvaluator#addAssign(Object, Object)}. */ @@ -758,6 +791,7 @@ public void testAddAssign() { e.set(v, 1.5d); assertAssigned(3.6d, v, e.addAssign(v, 2.1d)); e.set(v, bi(4)); assertAssigned(bi(10), v, e.addAssign(v, bi(6))); e.set(v, bd(1.5)); assertAssigned(bd(3.6), v, e.addAssign(v, bd(2.1))); + assertBadTypes(e::addAssign, 37, "38"); } /** Tests {@link StandardEvaluator#subAssign(Object, Object)}. */ @@ -770,6 +804,7 @@ public void testSubAssign() { e.set(v, 3.6d); assertAssigned(1.5d, v, e.subAssign(v, 2.1d)); e.set(v, bi(10)); assertAssigned(bi(4), v, e.subAssign(v, bi(6))); e.set(v, bd(3.6)); assertAssigned(bd(1.5), v, e.subAssign(v, bd(2.1))); + assertBadTypes(e::subAssign, 39, "40"); } /** Tests {@link StandardEvaluator#andAssign(Object, Object)}. */ @@ -779,6 +814,7 @@ public void testAndAssign() { e.set(v, 0xcafebabe); assertAssigned(0xcaacbaae, v, e.andAssign(v, 0xdeadbeef)); e.set(v, 0x0d0e0a0d0c0a0f0eL); assertAssigned(0L, v, e.andAssign(v, 0xb0e0e0f0b0a0b0e0L)); e.set(v, bi(0xcafebabeL)); assertAssigned(bi(0xcaacbaaeL), v, e.andAssign(v, bi(0xdeadbeefL))); + assertBadTypes(e::andAssign, 41, "42"); } /** Tests {@link StandardEvaluator#orAssign(Object, Object)}. */ @@ -788,6 +824,7 @@ public void testOrAssign() { e.set(v, 0xcafebabe); assertAssigned(0xdeffbeff, v, e.orAssign(v, 0xdeadbeef)); e.set(v, 0x0d0e0a0d0c0a0f0eL); assertAssigned(0xbdeeeafdbcaabfeeL, v, e.orAssign(v, 0xb0e0e0f0b0a0b0e0L)); e.set(v, bi(0xcafebabeL)); assertAssigned(bi(0xdeffbeffL), v, e.orAssign(v, bi(0xdeadbeefL))); + assertBadTypes(e::orAssign, 43, "44"); } /** Tests {@link StandardEvaluator#leftShiftAssign(Object, Object)}. */ @@ -797,6 +834,7 @@ public void testLeftShiftAssign() { e.set(v, 0xcafebabe); assertAssigned(0xafebabe0, v, e.leftShiftAssign(v, 4)); e.set(v, 0xdeadcafebeefbabeL); assertAssigned(0xdcafebeefbabe000L, v, e.leftShiftAssign(v, 12)); e.set(v, bi(57)); assertAssigned(bi(7296), v, e.leftShiftAssign(v, 7)); + assertBadTypes(e::leftShiftAssign, 45, "46"); } /** Tests {@link StandardEvaluator#rightShiftAssign(Object, Object)}. */ @@ -806,6 +844,7 @@ public void testRightShiftAssign() { e.set(v, 0xcafebabe); assertAssigned(0xfcafebab, v, e.rightShiftAssign(v, 4)); e.set(v, 0xdeadcafebeefbabeL); assertAssigned(0xfffdeadcafebeefbL, v, e.rightShiftAssign(v, 12)); e.set(v, bi(8920)); assertAssigned(bi(278), v, e.rightShiftAssign(v, 5)); + assertBadTypes(e::rightShiftAssign, 47, "48"); } /** Tests {@link StandardEvaluator#unsignedRightShiftAssign(Object, Object)}. */ @@ -814,6 +853,7 @@ public void testUnsignedRightShiftAssign() { final Variable v = new Variable("v"); e.set(v, 0xcafebabe); assertAssigned(0x0cafebab, v, e.unsignedRightShiftAssign(v, 4)); e.set(v, 0xdeadcafebeefbabeL); assertAssigned(0x000deadcafebeefbL, v, e.unsignedRightShiftAssign(v, 12)); + assertBadTypes(e::unsignedRightShiftAssign, 49, "50"); } // -- Helper methods -- @@ -831,4 +871,18 @@ private void assertAssigned(final Object expected, final Variable v, assertEquals(expected, e.get(v)); } + private void assertUnimplemented(Consumer op) { + assertUnimplemented((a, b) -> op.accept(a)); + } + + private void assertUnimplemented(BiConsumer op) { + String m = assertThrows(UnsupportedOperationException.class, () -> op.accept(0, 1)).getMessage(); + assertTrue(m.endsWith(" is not implemented"), "Unexpected exception message: " + m); + } + + private void assertBadTypes(BiConsumer op, A a, B b) { + String m = assertThrows(UnsupportedOperationException.class, () -> op.accept(a, b)).getMessage(); + assertTrue(m.startsWith("Invalid parameter type"), "Unexpected exception message: " + m); + } + }