From d33214f656d0bb7108094d8b82ce9955213272b8 Mon Sep 17 00:00:00 2001
From: "Meredith L. Patterson" <mlp@thesmartpolitenerd.com>
Date: Wed, 8 Jan 2014 17:40:40 +0100
Subject: [PATCH] All java tests pass

---
 lib/test-suite                                |  28 +-
 lib/tsgenjava.pl                              |  44 +--
 src/bindings/java/.gitignore                  |   2 +
 .../com/upstandinghackers/hammer/Hammer.java  |  21 +-
 .../upstandinghackers/hammer/ParsedToken.java |  58 +++-
 .../com/upstandinghackers/hammer/Parser.java  |   5 +-
 ...com_upstandinghackers_hammer_ParsedToken.c |  69 ++++-
 .../java/native/src/test/java/HammerTest.java | 279 ++++++++----------
 .../native/src/test/java/TestSupport.java     |  63 ++++
 9 files changed, 353 insertions(+), 216 deletions(-)
 create mode 100644 src/bindings/java/.gitignore
 create mode 100644 src/bindings/java/native/src/test/java/TestSupport.java

diff --git a/lib/test-suite b/lib/test-suite
index 6c15b3d6..7f00b8e3 100644
--- a/lib/test-suite
+++ b/lib/test-suite
@@ -19,7 +19,7 @@
 token {
   parser token("95\xa2");
   test "95\xa2" --> "95\xa2";
-  test "95\xa2" --> fail;
+  test "95\xa3" --> fail;
 }
 
 ch {
@@ -87,7 +87,7 @@ uint8 {
 }
 
 int_range {
-  parser int_range(uint8(), 0x3, 0x10);
+  parser int_range(uint8(), 0x3, 0xa);
   test <05> --> u0x05;
   test <0b> --> fail;
 }
@@ -299,17 +299,17 @@ rightrec {
   test "aa" --> ['a',['a']];
   test "aaa" --> ['a',['a',['a']]];
 }
-
-ambiguous {
-  subparser $d = ch('d');
-  subparser $p = ch('+');
-  subparser $e = choice(sequence($e, $p, $e), $d);
-  # TODO: implement action/h_act_flatten
-  parser $e;
-  
-  test "d" --> 'd';
-  test "d+d" --> ['d','+','d'];
-  test "d+d+d" --> [['d','+','d'],'+','d'];
-}
+## Only for GLR
+#ambiguous {
+#  subparser $d = ch('d');
+#  subparser $p = ch('+');
+#  subparser $e = choice(sequence($e, $p, $e), $d);
+#  # TODO: implement action/h_act_flatten
+#  parser $e;
+#  
+#  test "d" --> 'd';
+#  test "d+d" --> ['d','+','d'];
+#  test "d+d+d" --> [['d','+','d'],'+','d'];
+#}
 
 
diff --git a/lib/tsgenjava.pl b/lib/tsgenjava.pl
index b8b0af52..8caa5561 100644
--- a/lib/tsgenjava.pl
+++ b/lib/tsgenjava.pl
@@ -132,15 +132,15 @@ pp_test_elem(init, test(_,_)) --> !.
 pp_test_elem(decl, testFail(_)) --> !.
 pp_test_elem(init, testFail(_)) --> !.
 pp_test_elem(exec, test(Str, Result)) -->
-    !, indent(2),
-    "Assert.assertTrue(handle(parser.parse(", pp_parser(string(Str)),
-    ").getAst(), ",
+    !, indent(3),
+    "checkParseOK(parser, ", pp_parser(string(Str)),
+    ", ",
     pp_parse_result(Result),
-    "));\n".
+    ");\n".
 pp_test_elem(exec, testFail(Str)) -->
-    !, indent(2),
-    "Assert.assertNull(parser.parse(", pp_parser(string(Str)),
-    "));\n".
+    !, indent(3),
+    "checkParseFail(parser, ", pp_parser(string(Str)),
+    ");\n".
 
 % pp_test_elem(_, _) --> !.
 
@@ -171,8 +171,11 @@ pp_parser_bigint_str(num(Num)) --> !,
     pp_hexnum_guts(RNum).
 
 pp_parse_result(char(C)) --> !,
-    pp_parser(char(C)),
-    ".getBytes()[0]".
+    "new BigInteger(\"",
+    pp_parser_bigint_str(num(C)),
+    "\", 16)".
+    %pp_parser(char(C)),
+        %".getBytes()[0]".
 pp_parse_result(seq(Args)) --> !,
     "new Object[]{ ", pp_result_seq(Args), "}".
 pp_parse_result(none) --> !,
@@ -220,30 +223,11 @@ pp_test_suite(Suite) -->
     "import java.util.Arrays;\n",
     "import org.testng.annotations.*;\n",
     "import org.testng.Assert;\n\n",
-    "public class HammerTest {\n\n",
+    "public class HammerTest extends TestSupport {\n\n",
     indent(1), "static {\n",
     indent(2), "System.loadLibrary(\"hammer-java\");\n",
     indent(1), "}\n\n",
-    indent(1), "private boolean handle(ParsedToken p, Object known) {\n",
-    indent(2), "switch (p.getTokenType()) {\n",
-    indent(2), "case BYTES:\n",
-    indent(3), "return Arrays.toString(p.getBytesValue()).equals((String)known);\n",
-    indent(2), "case SINT:\n",
-    indent(3), "return ((Long)p.getSIntValue()).equals(known);\n",
-    indent(2), "case UINT:\n",
-    indent(3), "return ((Long)p.getUIntValue()).equals(known);\n",
-    indent(2), "case SEQUENCE:\n",
-    indent(3), "int i=0;\n",
-    indent(3), "for (ParsedToken tok : p.getSeqValue()) {\n",
-    indent(4), "if (!handle(tok, ((Object[])known)[i]))\n",
-    indent(5), "return false;\n",
-    indent(4), "++i;\n",
-    indent(3), "}\n",
-    indent(3), "return true;\n",
-    indent(2), "default:\n",
-    indent(3), "return false;\n",
-    indent(2), "}\n",
-    indent(1), "}\n\n", 
+
     pp_test_cases(Suite),
     "}\n".
 
diff --git a/src/bindings/java/.gitignore b/src/bindings/java/.gitignore
new file mode 100644
index 00000000..c44dfcd2
--- /dev/null
+++ b/src/bindings/java/.gitignore
@@ -0,0 +1,2 @@
+java/target
+native/linux/target
diff --git a/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Hammer.java b/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Hammer.java
index 8cff9b30..e73cd232 100644
--- a/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Hammer.java
+++ b/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Hammer.java
@@ -33,24 +33,37 @@ public class Hammer
         }
     }
 
+  private static byte[] toRawBytes(String input) {
+    byte[] bytes = new byte[input.length()];
+    for (int i = 0; i < input.length(); i++)
+      bytes[i] = (byte)input.charAt(i);
+    return bytes;
+  }
+  
     public static native ParseResult parse(Parser parser, byte[] input, int length);
     public static ParseResult parse(Parser parser, String input) {
-	return parse(parser, input.getBytes(), input.length());
+      return parse(parser, Hammer.toRawBytes(input), input.length());
     }
     public static native Parser token(byte[] str, int length);
     public static Parser token(String str) {
-	return token(str.getBytes(), str.length());
+      return token(Hammer.toRawBytes(str), str.length());
     }
     public static native Parser ch(byte c);
     public static Parser ch(String s) {
-	return ch(s.getBytes()[0]);
+      return ch((byte)s.charAt(0));
     }
     public static Parser ch(int c) {
 	return ch((byte)c);
     }
+    public static Parser ch(char c) {
+	return ch((byte)c);
+    }
     public static native Parser chRange(byte from, byte to);
     public static Parser chRange(String from, String to) {
-	return chRange(from.getBytes()[0], to.getBytes()[0]);
+      return chRange(from.charAt(0), to.charAt(0));
+    }
+    public static Parser chRange(char from, char to) {
+	return chRange((byte)from, (byte)to);
     }
     public static Parser chRange(int from, int to) {
 	return chRange((byte)from, (byte)to);
diff --git a/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/ParsedToken.java b/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/ParsedToken.java
index efbc8ed7..6767980b 100644
--- a/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/ParsedToken.java
+++ b/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/ParsedToken.java
@@ -1,4 +1,5 @@
 package com.upstandinghackers.hammer;
+import java.math.BigInteger;
 
 public class ParsedToken
 {
@@ -14,8 +15,8 @@ public class ParsedToken
     public native int getIndex();
     public native byte getBitOffset();
     public native byte[] getBytesValue();
-    public native long getSIntValue();
-    public native long getUIntValue();
+    public native BigInteger getSIntValue();
+    public native BigInteger getUIntValue();
     public native double getDoubleValue();
     public native float getFloatValue();
     public native ParsedToken[] getSeqValue();
@@ -25,8 +26,8 @@ public class ParsedToken
     native void setIndex(int index);
     native void setBitOffset(byte offset);
     native void setBytesValue(byte[] value);
-    native void setSIntValue(long value);
-    native void setUIntValue(long value);
+  native void setSIntValue(long value); // TODO: Change these to take a biginteger
+  native void setUIntValue(long value); // TODO: Change these to take a biginteger
     native void setDoubleValue(double value);
     native void setFloatValue(float value);
     native void setSeqValue(ParsedToken value[]);
@@ -37,4 +38,53 @@ public class ParsedToken
 
     private long inner;
     ParsedToken(long inner) {this.inner=inner;}
+
+  private void write(StringBuilder b) {
+    switch (getTokenType()) {
+    case BYTES:
+      byte[] bytes = getBytesValue();
+      for (int i = 0; i < bytes.length; i++) {
+        b.append(i == 0 ? "<" : ".");
+        String byteStr = Integer.toHexString(((int)bytes[i] + 256)%256);
+        if (byteStr.length() < 2) {
+          b.append("0");
+        }
+        b.append(byteStr);
+      }
+      b.append(">");
+      break;
+    case SINT:
+      b.append("s");
+      b.append(getSIntValue());
+      break;
+    case UINT:
+      b.append("u");
+      b.append(getUIntValue());
+      break;
+    case SEQUENCE:
+      boolean first = true;
+      b.append("[");
+      for (ParsedToken tok : getSeqValue()) {
+        if (!first)
+          b.append(' ');
+        first = false;
+        if (tok == null)
+          // I don't think this can ever be the case
+          b.append("null");
+        else
+          tok.write(b);
+      }
+      b.append("]");
+      break;
+    case NONE:
+      b.append("NONE");
+    }
+  }
+  
+  public String toString() {
+    StringBuilder b = new StringBuilder();
+    write(b);
+    return b.toString();
+  }
 }
+  
diff --git a/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Parser.java b/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Parser.java
index eb5f89bb..519e635e 100644
--- a/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Parser.java
+++ b/src/bindings/java/java/src/main/java/com/upstandinghackers/hammer/Parser.java
@@ -6,7 +6,10 @@ public class Parser
     public native void free();
     public long getInner() {return this.inner;}
     public ParseResult parse(String input) {
-	return Hammer.parse(this, input.getBytes(), input.length());
+      byte[] bytes = new byte[input.length()];
+      for (int i = 0; i < input.length(); i++)
+        bytes[i] = (byte)input.charAt(i);
+      return Hammer.parse(this, bytes, bytes.length);
     }
     public ParseResult parse(byte[] input, int length) {
 	return Hammer.parse(this, input, length);
diff --git a/src/bindings/java/native/src/main/native/com_upstandinghackers_hammer_ParsedToken.c b/src/bindings/java/native/src/main/native/com_upstandinghackers_hammer_ParsedToken.c
index e863738a..f69856c1 100644
--- a/src/bindings/java/native/src/main/native/com_upstandinghackers_hammer_ParsedToken.c
+++ b/src/bindings/java/native/src/main/native/com_upstandinghackers_hammer_ParsedToken.c
@@ -1,6 +1,22 @@
 #include "jhammer.h"
 #include "com_upstandinghackers_hammer_ParsedToken.h"
 
+#ifdef __GNUC__
+#ifndef BRANCH_LIKELY
+#define BRANCH_LIKELY(cond) __builtin_expect((cond),1)
+#endif
+#ifndef BRANCH_UNLIKELY
+#define BRANCH_UNLIKELY(cond) __builtin_expect((cond),0)
+#endif
+#else /* unknown compiler */
+#ifndef BRANCH_LIKELY
+#define BRANCH_LIKELY(cond) (cond)
+#endif
+#ifndef BRANCH_UNLIKELY
+#define BRANCH_UNLIKELY(cond) (cond)
+#endif
+#endif
+
 #define HPT_UNWRAP(env, this) HParsedToken *inner = unwrap_parsed_token(env, this); assert(inner!=NULL)
 
 HParsedToken *unwrap_parsed_token(JNIEnv *env, jobject obj)
@@ -45,19 +61,64 @@ JNIEXPORT jbyteArray JNICALL Java_com_upstandinghackers_hammer_ParsedToken_getBy
     (*env)->SetByteArrayRegion(env, outArray, (jsize) 0, (jsize)(inner->bytes.len), (jbyte *)(inner->bytes.token));
     return outArray; 
 }
+#if 0
+static jchar* format_number(uint64_t number, int length, int signedp, int *tlen, int had_neg) {
+  if (BRANCH_UNLIKELY(signedp && (number & 0x8000000000000000UL /* sign bit */) && length == 0)) {
+    jchar* ret = format_number((~number) + 1, 1, 0, tlen, 1);
+    ret[0] = '-';
+    return ret;
+  } else {
+    // no negative sign possible
+    if (BRANH_UNLIKELY(number == 0 && (length - had_neg) > 0)) {
+      *tlen = length;
+      return (jchar*)malloc(sizeof(jchar) * *tlen);
+    }
+    const jchar hexchars = {'0','1','2','3','4','5','6','7',
+                            '8','9','A','B','C','D','E','F'};
+    jchar* ret - format_number(number >> 4, length + 1, 0, tlen, had_neg);
+    ret[*tlen - length - 1 + had_neg] = hexchars[number & 0xF];
+    return ret;
+  }
+}    
+#endif
+static jobject make_bignum(JNIEnv *env, uint64_t num, int signedp) {
+  jbyte bytebuf[9];
+  for (int i = 0; i < 8; i++) {
+    bytebuf[i+1] = (num >> (8 * (7 - i))) & 0xFF;
+  }
+
+  bytebuf[0] = (signedp && (num & (1LL << 63))) ? 0xFF : 0;
+  jbyteArray byteArray = (*env)->NewByteArray(env, 9);
+  (*env)->SetByteArrayRegion(env, byteArray, (jsize)0, (jsize)9, bytebuf);
+
+  printf("Formatted %s 0x%016lx as [", signedp ? "signed" : "unsigned", num);
+  for (int i = 0; i < 9; i++)
+    printf("%02hhx%s", bytebuf[i], (i == 8) ? "]\n":".");
+  /*
+    int buflen = 0;
+    jchar* numbuf = format_number(num, 0, signedp, *buflen, 0);
+    jstring numstr = env->NewString(env, numbuf, buflen);
+  */
+  jclass BigNum;
+  FIND_CLASS(BigNum, env, "java/math/BigInteger");
+  jmethodID bignum_ctor = (*env)->GetMethodID(env, BigNum, "<init>", "([B)V");
+  jobject ret = (*env)->NewObject(env, BigNum, bignum_ctor, byteArray);
+  return ret;
+  
+}
 
-JNIEXPORT jlong JNICALL Java_com_upstandinghackers_hammer_ParsedToken_getSIntValue
+JNIEXPORT jobject JNICALL Java_com_upstandinghackers_hammer_ParsedToken_getSIntValue
   (JNIEnv *env, jobject this)
 {
     HPT_UNWRAP(env, this);
-    return (jlong) (inner->sint);
+    return make_bignum(env, inner->uint, 1);
 }
 
-JNIEXPORT jlong JNICALL Java_com_upstandinghackers_hammer_ParsedToken_getUIntValue
+JNIEXPORT jobject JNICALL Java_com_upstandinghackers_hammer_ParsedToken_getUIntValue
   (JNIEnv *env, jobject this)
 {
     HPT_UNWRAP(env, this);
-    return (jlong) (inner->uint);
+    return make_bignum(env, inner->uint, 0);
 }
 
 JNIEXPORT jdouble JNICALL Java_com_upstandinghackers_hammer_ParsedToken_getDoubleValue
diff --git a/src/bindings/java/native/src/test/java/HammerTest.java b/src/bindings/java/native/src/test/java/HammerTest.java
index d9b3ac07..81f7253f 100644
--- a/src/bindings/java/native/src/test/java/HammerTest.java
+++ b/src/bindings/java/native/src/test/java/HammerTest.java
@@ -5,322 +5,297 @@ import java.util.Arrays;
 import org.testng.annotations.*;
 import org.testng.Assert;
 
-public class HammerTest {
+public class HammerTest extends TestSupport {
 
     static {
         System.loadLibrary("hammer-java");
     }
 
-    private boolean handle(ParsedToken p, Object known) {
-	System.out.println("Known: " + known);
-        switch (p.getTokenType()) {
-        case BYTES:
-	    System.out.println("Parsed: " + Arrays.toString(p.getBytesValue()));
-            return Arrays.toString(p.getBytesValue()).equals((String)known);
-        case SINT:
-	    System.out.println("Parsed: " + p.getSIntValue());
-            return ((Long)p.getSIntValue()).equals(known);
-        case UINT:
-	    System.out.println("Parsed: " + p.getUIntValue());
-            return ((Long)p.getUIntValue()).equals(known);
-        case SEQUENCE:
-            int i=0;
-            for (ParsedToken tok : p.getSeqValue()) {
-                if (!handle(tok, ((Object[])known)[i]))
-                    return false;
-                ++i;
-            }
-            return true;
-        default:
-            return false;
-        }
-    }
-
     @Test
     public void TestToken() {
         Parser parser;
         parser = Hammer.token("95\u00a2");
-        Assert.assertTrue(handle(parser.parse("95\u00a2").getAst(), "95\u00a2"));
-        Assert.assertNull(parser.parse("95\u00a2"));
+            checkParseOK(parser, "95\u00a2", "95\u00a2");
+            checkParseFail(parser, "95\u00a3");
     }
     @Test
     public void TestCh() {
         Parser parser;
         parser = Hammer.ch(0xa2);
-        Assert.assertTrue(handle(parser.parse("\u00a2").getAst(), "\u00a2".getBytes()[0]));
-        Assert.assertNull(parser.parse("\u00a3"));
+            checkParseOK(parser, "\u00a2", new BigInteger("a2", 16));
+            checkParseFail(parser, "\u00a3");
     }
     @Test
     public void TestChRange() {
         Parser parser;
         parser = Hammer.chRange(0x61, 0x63);
-        Assert.assertTrue(handle(parser.parse("b").getAst(), "b".getBytes()[0]));
-        Assert.assertNull(parser.parse("d"));
+            checkParseOK(parser, "b", new BigInteger("62", 16));
+            checkParseFail(parser, "d");
     }
     @Test
     public void TestInt64() {
         Parser parser;
         parser = Hammer.int64();
-        Assert.assertTrue(handle(parser.parse("\u00ff\u00ff\u00ff\u00fe\u0000\u0000\u0000\u0000").getAst(), new BigInteger("-200000000", 16)));
-        Assert.assertNull(parser.parse("\u00ff\u00ff\u00ff\u00fe\u0000\u0000\u0000"));
+            checkParseOK(parser, "\u00ff\u00ff\u00ff\u00fe\u0000\u0000\u0000\u0000", new BigInteger("-200000000", 16));
+            checkParseFail(parser, "\u00ff\u00ff\u00ff\u00fe\u0000\u0000\u0000");
     }
     @Test
     public void TestInt32() {
         Parser parser;
         parser = Hammer.int32();
-        Assert.assertTrue(handle(parser.parse("\u00ff\u00fe\u0000\u0000").getAst(), new BigInteger("-20000", 16)));
-        Assert.assertNull(parser.parse("\u00ff\u00fe\u0000"));
-        Assert.assertTrue(handle(parser.parse("\u0000\u0002\u0000\u0000").getAst(), new BigInteger("20000", 16)));
-        Assert.assertNull(parser.parse("\u0000\u0002\u0000"));
+            checkParseOK(parser, "\u00ff\u00fe\u0000\u0000", new BigInteger("-20000", 16));
+            checkParseFail(parser, "\u00ff\u00fe\u0000");
+            checkParseOK(parser, "\u0000\u0002\u0000\u0000", new BigInteger("20000", 16));
+            checkParseFail(parser, "\u0000\u0002\u0000");
     }
     @Test
     public void TestInt16() {
         Parser parser;
         parser = Hammer.int16();
-        Assert.assertTrue(handle(parser.parse("\u00fe\u0000").getAst(), new BigInteger("-200", 16)));
-        Assert.assertNull(parser.parse("\u00fe"));
-        Assert.assertTrue(handle(parser.parse("\u0002\u0000").getAst(), new BigInteger("200", 16)));
-        Assert.assertNull(parser.parse("\u0002"));
+            checkParseOK(parser, "\u00fe\u0000", new BigInteger("-200", 16));
+            checkParseFail(parser, "\u00fe");
+            checkParseOK(parser, "\u0002\u0000", new BigInteger("200", 16));
+            checkParseFail(parser, "\u0002");
     }
     @Test
     public void TestInt8() {
         Parser parser;
         parser = Hammer.int8();
-        Assert.assertTrue(handle(parser.parse("\u0088").getAst(), new BigInteger("-78", 16)));
-        Assert.assertNull(parser.parse(""));
+            checkParseOK(parser, "\u0088", new BigInteger("-78", 16));
+            checkParseFail(parser, "");
     }
     @Test
     public void TestUint64() {
         Parser parser;
         parser = Hammer.uint64();
-        Assert.assertTrue(handle(parser.parse("\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000").getAst(), new BigInteger("200000000", 16)));
-        Assert.assertNull(parser.parse("\u0000\u0000\u0000\u0002\u0000\u0000\u0000"));
+            checkParseOK(parser, "\u0000\u0000\u0000\u0002\u0000\u0000\u0000\u0000", new BigInteger("200000000", 16));
+            checkParseFail(parser, "\u0000\u0000\u0000\u0002\u0000\u0000\u0000");
     }
     @Test
     public void TestUint32() {
         Parser parser;
         parser = Hammer.uint32();
-        Assert.assertTrue(handle(parser.parse("\u0000\u0002\u0000\u0000").getAst(), new BigInteger("20000", 16)));
-        Assert.assertNull(parser.parse("\u0000\u0002\u0000"));
+            checkParseOK(parser, "\u0000\u0002\u0000\u0000", new BigInteger("20000", 16));
+            checkParseFail(parser, "\u0000\u0002\u0000");
     }
     @Test
     public void TestUint16() {
         Parser parser;
         parser = Hammer.uint16();
-        Assert.assertTrue(handle(parser.parse("\u0002\u0000").getAst(), new BigInteger("200", 16)));
-        Assert.assertNull(parser.parse("\u0002"));
+            checkParseOK(parser, "\u0002\u0000", new BigInteger("200", 16));
+            checkParseFail(parser, "\u0002");
     }
     @Test
     public void TestUint8() {
         Parser parser;
         parser = Hammer.uint8();
-        Assert.assertTrue(handle(parser.parse("x").getAst(), new BigInteger("78", 16)));
-        Assert.assertNull(parser.parse(""));
+            checkParseOK(parser, "x", new BigInteger("78", 16));
+            checkParseFail(parser, "");
     }
     @Test
     public void TestIntRange() {
         Parser parser;
-        parser = Hammer.intRange(Hammer.uint8(), 0x3, 0x10);
-        Assert.assertTrue(handle(parser.parse("\u0005").getAst(), new BigInteger("5", 16)));
-        Assert.assertNull(parser.parse("\u000b"));
+        parser = Hammer.intRange(Hammer.uint8(), 0x3, 0xa);
+            checkParseOK(parser, "\u0005", new BigInteger("5", 16));
+            checkParseFail(parser, "\u000b");
     }
     @Test
     public void TestWhitespace() {
         Parser parser;
         parser = Hammer.whitespace(Hammer.ch(0x61));
-        Assert.assertTrue(handle(parser.parse("a").getAst(), "a".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse(" a").getAst(), "a".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse("  a").getAst(), "a".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse("\u0009a").getAst(), "a".getBytes()[0]));
-        Assert.assertNull(parser.parse("_a"));
+            checkParseOK(parser, "a", new BigInteger("61", 16));
+            checkParseOK(parser, " a", new BigInteger("61", 16));
+            checkParseOK(parser, "  a", new BigInteger("61", 16));
+            checkParseOK(parser, "\u0009a", new BigInteger("61", 16));
+            checkParseFail(parser, "_a");
         parser = Hammer.whitespace(Hammer.endP());
-        Assert.assertTrue(handle(parser.parse("").getAst(), null));
-        Assert.assertTrue(handle(parser.parse("  ").getAst(), null));
-        Assert.assertNull(parser.parse("  x"));
+            checkParseOK(parser, "", null);
+            checkParseOK(parser, "  ", null);
+            checkParseFail(parser, "  x");
     }
     @Test
     public void TestLeft() {
         Parser parser;
         parser = Hammer.left(Hammer.ch(0x61), Hammer.ch(0x20));
-        Assert.assertTrue(handle(parser.parse("a ").getAst(), "a".getBytes()[0]));
-        Assert.assertNull(parser.parse("a"));
-        Assert.assertNull(parser.parse(" "));
-        Assert.assertNull(parser.parse("ba"));
+            checkParseOK(parser, "a ", new BigInteger("61", 16));
+            checkParseFail(parser, "a");
+            checkParseFail(parser, " ");
+            checkParseFail(parser, "ba");
     }
     @Test
     public void TestMiddle() {
         Parser parser;
         parser = Hammer.middle(Hammer.ch(" "), Hammer.ch("a"), Hammer.ch(" "));
-        Assert.assertTrue(handle(parser.parse(" a ").getAst(), "a".getBytes()[0]));
-        Assert.assertNull(parser.parse("a"));
-        Assert.assertNull(parser.parse(" a"));
-        Assert.assertNull(parser.parse("a "));
-        Assert.assertNull(parser.parse(" b "));
-        Assert.assertNull(parser.parse("ba "));
-        Assert.assertNull(parser.parse(" ab"));
+            checkParseOK(parser, " a ", new BigInteger("61", 16));
+            checkParseFail(parser, "a");
+            checkParseFail(parser, " a");
+            checkParseFail(parser, "a ");
+            checkParseFail(parser, " b ");
+            checkParseFail(parser, "ba ");
+            checkParseFail(parser, " ab");
     }
     @Test
     public void TestIn() {
         Parser parser;
         parser = Hammer.in("abc");
-        Assert.assertTrue(handle(parser.parse("b").getAst(), "b".getBytes()[0]));
-        Assert.assertNull(parser.parse("d"));
+            checkParseOK(parser, "b", new BigInteger("62", 16));
+            checkParseFail(parser, "d");
     }
     @Test
     public void TestNotIn() {
         Parser parser;
         parser = Hammer.notIn("abc");
-        Assert.assertTrue(handle(parser.parse("d").getAst(), "d".getBytes()[0]));
-        Assert.assertNull(parser.parse("a"));
+            checkParseOK(parser, "d", new BigInteger("64", 16));
+            checkParseFail(parser, "a");
     }
     @Test
     public void TestEndP() {
         Parser parser;
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.endP());
-        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
-        Assert.assertNull(parser.parse("aa"));
+            checkParseOK(parser, "a", new Object[]{ new BigInteger("61", 16)});
+            checkParseFail(parser, "aa");
     }
     @Test
     public void TestNothingP() {
         Parser parser;
         parser = Hammer.nothingP();
-        Assert.assertNull(parser.parse("a"));
+            checkParseFail(parser, "a");
     }
     @Test
     public void TestSequence() {
         Parser parser;
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.ch("b"));
-        Assert.assertTrue(handle(parser.parse("ab").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
-        Assert.assertNull(parser.parse("a"));
-        Assert.assertNull(parser.parse("b"));
+            checkParseOK(parser, "ab", new Object[]{ new BigInteger("61", 16), new BigInteger("62", 16)});
+            checkParseFail(parser, "a");
+            checkParseFail(parser, "b");
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.whitespace(Hammer.ch("b")));
-        Assert.assertTrue(handle(parser.parse("ab").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("a b").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("a  b").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
+            checkParseOK(parser, "ab", new Object[]{ new BigInteger("61", 16), new BigInteger("62", 16)});
+            checkParseOK(parser, "a b", new Object[]{ new BigInteger("61", 16), new BigInteger("62", 16)});
+            checkParseOK(parser, "a  b", new Object[]{ new BigInteger("61", 16), new BigInteger("62", 16)});
     }
     @Test
     public void TestChoice() {
         Parser parser;
         parser = Hammer.choice(Hammer.ch("a"), Hammer.ch("b"));
-        Assert.assertTrue(handle(parser.parse("a").getAst(), "a".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse("b").getAst(), "b".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse("ab").getAst(), "a".getBytes()[0]));
-        Assert.assertNull(parser.parse("c"));
+            checkParseOK(parser, "a", new BigInteger("61", 16));
+            checkParseOK(parser, "b", new BigInteger("62", 16));
+            checkParseOK(parser, "ab", new BigInteger("61", 16));
+            checkParseFail(parser, "c");
     }
     @Test
     public void TestButnot() {
         Parser parser;
         parser = Hammer.butnot(Hammer.ch("a"), Hammer.token("ab"));
-        Assert.assertTrue(handle(parser.parse("a").getAst(), "a".getBytes()[0]));
-        Assert.assertNull(parser.parse("ab"));
-        Assert.assertTrue(handle(parser.parse("aa").getAst(), "a".getBytes()[0]));
+            checkParseOK(parser, "a", new BigInteger("61", 16));
+            checkParseFail(parser, "ab");
+            checkParseOK(parser, "aa", new BigInteger("61", 16));
         parser = Hammer.butnot(Hammer.chRange("0", "9"), Hammer.ch("6"));
-        Assert.assertTrue(handle(parser.parse("5").getAst(), "5".getBytes()[0]));
-        Assert.assertNull(parser.parse("6"));
+            checkParseOK(parser, "5", new BigInteger("35", 16));
+            checkParseFail(parser, "6");
     }
     @Test
     public void TestDifference() {
         Parser parser;
         parser = Hammer.difference(Hammer.token("ab"), Hammer.ch("a"));
-        Assert.assertTrue(handle(parser.parse("ab").getAst(), "ab"));
-        Assert.assertNull(parser.parse("a"));
+            checkParseOK(parser, "ab", "ab");
+            checkParseFail(parser, "a");
     }
     @Test
     public void TestXor() {
         Parser parser;
         parser = Hammer.xor(Hammer.chRange("0", "6"), Hammer.chRange("5", "9"));
-        Assert.assertTrue(handle(parser.parse("0").getAst(), "0".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse("9").getAst(), "9".getBytes()[0]));
-        Assert.assertNull(parser.parse("5"));
-        Assert.assertNull(parser.parse("a"));
+            checkParseOK(parser, "0", new BigInteger("30", 16));
+            checkParseOK(parser, "9", new BigInteger("39", 16));
+            checkParseFail(parser, "5");
+            checkParseFail(parser, "a");
     }
     @Test
     public void TestMany() {
         Parser parser;
         parser = Hammer.many(Hammer.choice(Hammer.ch("a"), Hammer.ch("b")));
-        Assert.assertTrue(handle(parser.parse("").getAst(), new Object[]{ }));
-        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("b").getAst(), new Object[]{ "b".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("aabbaba").getAst(), new Object[]{ "a".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0]}));
+            checkParseOK(parser, "", new Object[]{ });
+            checkParseOK(parser, "a", new Object[]{ new BigInteger("61", 16)});
+            checkParseOK(parser, "b", new Object[]{ new BigInteger("62", 16)});
+            checkParseOK(parser, "aabbaba", new Object[]{ new BigInteger("61", 16), new BigInteger("61", 16), new BigInteger("62", 16), new BigInteger("62", 16), new BigInteger("61", 16), new BigInteger("62", 16), new BigInteger("61", 16)});
     }
     @Test
     public void TestMany1() {
         Parser parser;
         parser = Hammer.many1(Hammer.choice(Hammer.ch("a"), Hammer.ch("b")));
-        Assert.assertNull(parser.parse(""));
-        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("b").getAst(), new Object[]{ "b".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("aabbaba").getAst(), new Object[]{ "a".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0], "b".getBytes()[0], "a".getBytes()[0]}));
-        Assert.assertNull(parser.parse("daabbabadef"));
+            checkParseFail(parser, "");
+            checkParseOK(parser, "a", new Object[]{ new BigInteger("61", 16)});
+            checkParseOK(parser, "b", new Object[]{ new BigInteger("62", 16)});
+            checkParseOK(parser, "aabbaba", new Object[]{ new BigInteger("61", 16), new BigInteger("61", 16), new BigInteger("62", 16), new BigInteger("62", 16), new BigInteger("61", 16), new BigInteger("62", 16), new BigInteger("61", 16)});
+            checkParseFail(parser, "daabbabadef");
     }
     @Test
     public void TestRepeatN() {
         Parser parser;
         parser = Hammer.repeatN(Hammer.choice(Hammer.ch("a"), Hammer.ch("b")), 0x2);
-        Assert.assertNull(parser.parse("adef"));
-        Assert.assertTrue(handle(parser.parse("abdef").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0]}));
-        Assert.assertNull(parser.parse("dabdef"));
+            checkParseFail(parser, "adef");
+            checkParseOK(parser, "abdef", new Object[]{ new BigInteger("61", 16), new BigInteger("62", 16)});
+            checkParseFail(parser, "dabdef");
     }
     @Test
     public void TestOptional() {
         Parser parser;
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.optional(Hammer.choice(Hammer.ch("b"), Hammer.ch("c"))), Hammer.ch("d"));
-        Assert.assertTrue(handle(parser.parse("abd").getAst(), new Object[]{ "a".getBytes()[0], "b".getBytes()[0], "d".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("acd").getAst(), new Object[]{ "a".getBytes()[0], "c".getBytes()[0], "d".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("ad").getAst(), new Object[]{ "a".getBytes()[0], null, "d".getBytes()[0]}));
-        Assert.assertNull(parser.parse("aed"));
-        Assert.assertNull(parser.parse("ab"));
-        Assert.assertNull(parser.parse("ac"));
+            checkParseOK(parser, "abd", new Object[]{ new BigInteger("61", 16), new BigInteger("62", 16), new BigInteger("64", 16)});
+            checkParseOK(parser, "acd", new Object[]{ new BigInteger("61", 16), new BigInteger("63", 16), new BigInteger("64", 16)});
+            checkParseOK(parser, "ad", new Object[]{ new BigInteger("61", 16), null, new BigInteger("64", 16)});
+            checkParseFail(parser, "aed");
+            checkParseFail(parser, "ab");
+            checkParseFail(parser, "ac");
     }
     @Test
     public void TestIgnore() {
         Parser parser;
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.ignore(Hammer.ch("b")), Hammer.ch("c"));
-        Assert.assertTrue(handle(parser.parse("abc").getAst(), new Object[]{ "a".getBytes()[0], "c".getBytes()[0]}));
-        Assert.assertNull(parser.parse("ac"));
+            checkParseOK(parser, "abc", new Object[]{ new BigInteger("61", 16), new BigInteger("63", 16)});
+            checkParseFail(parser, "ac");
     }
     @Test
     public void TestSepBy() {
         Parser parser;
         parser = Hammer.sepBy(Hammer.choice(Hammer.ch("1"), Hammer.ch("2"), Hammer.ch("3")), Hammer.ch(","));
-        Assert.assertTrue(handle(parser.parse("1,2,3").getAst(), new Object[]{ "1".getBytes()[0], "2".getBytes()[0], "3".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("1,3,2").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0], "2".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("1,3").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("3").getAst(), new Object[]{ "3".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("").getAst(), new Object[]{ }));
+            checkParseOK(parser, "1,2,3", new Object[]{ new BigInteger("31", 16), new BigInteger("32", 16), new BigInteger("33", 16)});
+            checkParseOK(parser, "1,3,2", new Object[]{ new BigInteger("31", 16), new BigInteger("33", 16), new BigInteger("32", 16)});
+            checkParseOK(parser, "1,3", new Object[]{ new BigInteger("31", 16), new BigInteger("33", 16)});
+            checkParseOK(parser, "3", new Object[]{ new BigInteger("33", 16)});
+            checkParseOK(parser, "", new Object[]{ });
     }
     @Test
     public void TestSepBy1() {
         Parser parser;
         parser = Hammer.sepBy1(Hammer.choice(Hammer.ch("1"), Hammer.ch("2"), Hammer.ch("3")), Hammer.ch(","));
-        Assert.assertTrue(handle(parser.parse("1,2,3").getAst(), new Object[]{ "1".getBytes()[0], "2".getBytes()[0], "3".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("1,3,2").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0], "2".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("1,3").getAst(), new Object[]{ "1".getBytes()[0], "3".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("3").getAst(), new Object[]{ "3".getBytes()[0]}));
-        Assert.assertNull(parser.parse(""));
+            checkParseOK(parser, "1,2,3", new Object[]{ new BigInteger("31", 16), new BigInteger("32", 16), new BigInteger("33", 16)});
+            checkParseOK(parser, "1,3,2", new Object[]{ new BigInteger("31", 16), new BigInteger("33", 16), new BigInteger("32", 16)});
+            checkParseOK(parser, "1,3", new Object[]{ new BigInteger("31", 16), new BigInteger("33", 16)});
+            checkParseOK(parser, "3", new Object[]{ new BigInteger("33", 16)});
+            checkParseFail(parser, "");
     }
     @Test
     public void TestAnd() {
         Parser parser;
         parser = Hammer.sequence(Hammer.and(Hammer.ch("0")), Hammer.ch("0"));
-        Assert.assertTrue(handle(parser.parse("0").getAst(), new Object[]{ "0".getBytes()[0]}));
-        Assert.assertNull(parser.parse("1"));
+            checkParseOK(parser, "0", new Object[]{ new BigInteger("30", 16)});
+            checkParseFail(parser, "1");
         parser = Hammer.sequence(Hammer.and(Hammer.ch("0")), Hammer.ch("1"));
-        Assert.assertNull(parser.parse("0"));
-        Assert.assertNull(parser.parse("1"));
+            checkParseFail(parser, "0");
+            checkParseFail(parser, "1");
         parser = Hammer.sequence(Hammer.ch("1"), Hammer.and(Hammer.ch("2")));
-        Assert.assertTrue(handle(parser.parse("12").getAst(), new Object[]{ "1".getBytes()[0]}));
-        Assert.assertNull(parser.parse("13"));
+            checkParseOK(parser, "12", new Object[]{ new BigInteger("31", 16)});
+            checkParseFail(parser, "13");
     }
     @Test
     public void TestNot() {
         Parser parser;
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.choice(Hammer.token("+"), Hammer.token("++")), Hammer.ch("b"));
-        Assert.assertTrue(handle(parser.parse("a+b").getAst(), new Object[]{ "a".getBytes()[0], "+", "b".getBytes()[0]}));
-        Assert.assertNull(parser.parse("a++b"));
+            checkParseOK(parser, "a+b", new Object[]{ new BigInteger("61", 16), "+", new BigInteger("62", 16)});
+            checkParseFail(parser, "a++b");
         parser = Hammer.sequence(Hammer.ch("a"), Hammer.choice(Hammer.sequence(Hammer.token("+"), Hammer.not(Hammer.ch("+"))), Hammer.token("++")), Hammer.ch("b"));
-        Assert.assertTrue(handle(parser.parse("a+b").getAst(), new Object[]{ "a".getBytes()[0], new Object[]{ "+"}, "b".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("a++b").getAst(), new Object[]{ "a".getBytes()[0], "++", "b".getBytes()[0]}));
+            checkParseOK(parser, "a+b", new Object[]{ new BigInteger("61", 16), new Object[]{ "+"}, new BigInteger("62", 16)});
+            checkParseOK(parser, "a++b", new Object[]{ new BigInteger("61", 16), "++", new BigInteger("62", 16)});
     }
     @Test
     public void TestRightrec() {
@@ -328,22 +303,8 @@ public class HammerTest {
         Parser sp_rr = Hammer.indirect();
         sp_rr.bindIndirect(Hammer.choice(Hammer.sequence(Hammer.ch("a"), sp_rr), Hammer.epsilonP()));
         parser = sp_rr;
-        Assert.assertTrue(handle(parser.parse("a").getAst(), new Object[]{ "a".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("aa").getAst(), new Object[]{ "a".getBytes()[0], new Object[]{ "a".getBytes()[0]}}));
-        Assert.assertTrue(handle(parser.parse("aaa").getAst(), new Object[]{ "a".getBytes()[0], new Object[]{ "a".getBytes()[0], new Object[]{ "a".getBytes()[0]}}}));
-    }
-    @Test
-    public void TestAmbiguous() {
-        Parser parser;
-        Parser sp_d = Hammer.indirect();
-        Parser sp_p = Hammer.indirect();
-        Parser sp_e = Hammer.indirect();
-        sp_d.bindIndirect(Hammer.ch("d"));
-        sp_p.bindIndirect(Hammer.ch("+"));
-        sp_e.bindIndirect(Hammer.choice(Hammer.sequence(sp_e, sp_p, sp_e), sp_d));
-        parser = sp_e;
-        Assert.assertTrue(handle(parser.parse("d").getAst(), "d".getBytes()[0]));
-        Assert.assertTrue(handle(parser.parse("d+d").getAst(), new Object[]{ "d".getBytes()[0], "+".getBytes()[0], "d".getBytes()[0]}));
-        Assert.assertTrue(handle(parser.parse("d+d+d").getAst(), new Object[]{ new Object[]{ "d".getBytes()[0], "+".getBytes()[0], "d".getBytes()[0]}, "+".getBytes()[0], "d".getBytes()[0]}));
+            checkParseOK(parser, "a", new Object[]{ new BigInteger("61", 16)});
+            checkParseOK(parser, "aa", new Object[]{ new BigInteger("61", 16), new Object[]{ new BigInteger("61", 16)}});
+            checkParseOK(parser, "aaa", new Object[]{ new BigInteger("61", 16), new Object[]{ new BigInteger("61", 16), new Object[]{ new BigInteger("61", 16)}}});
     }
 }
diff --git a/src/bindings/java/native/src/test/java/TestSupport.java b/src/bindings/java/native/src/test/java/TestSupport.java
new file mode 100644
index 00000000..d3b1a037
--- /dev/null
+++ b/src/bindings/java/native/src/test/java/TestSupport.java
@@ -0,0 +1,63 @@
+package com.upstandinghackers.hammer;
+
+import java.util.Arrays;
+import java.math.BigInteger;
+import org.testng.Assert;
+
+public class TestSupport {
+  protected boolean deepEqual(ParsedToken p, Object known) {
+    //if (known != null || p == null)
+    //  throw new RuntimeException("Actually got here!");
+    if (p == null) {
+      return known == null;
+    }
+    System.err.println("Saw token of type: " + p.getTokenType());
+    System.err.println("Arg of type: " + (known == null ? "null" : known.getClass().toString()));
+    switch (p.getTokenType()) {
+    case BYTES:
+      
+      byte[] tmp_byte_buffer = p.getBytesValue();
+      String knowns = (String)known;
+      for (int i = 0; i < tmp_byte_buffer.length; i++) {
+        System.err.println("Comparing char " + i);
+        if (tmp_byte_buffer[i] != (byte)knowns.charAt(i)) {
+          System.err.println("Compare failed at char " + i + ": " + (byte)knowns.charAt(i) + " != " + tmp_byte_buffer[i]);
+          return false;
+        }
+      }
+      System.err.println("Compare succeeded");
+      return true;
+    case SINT:
+      return p.getSIntValue().equals(known);
+    case UINT:
+      return p.getUIntValue().equals(known);
+    case SEQUENCE:
+      int i=0;
+      for (ParsedToken tok : p.getSeqValue()) {
+        if (!deepEqual(tok, ((Object[])known)[i]))
+          return false;
+        ++i;
+      }
+      return true;
+    case NONE:
+      return known == null;
+    default:
+      System.err.println("Unkown token type " + p.getTokenType());
+      return false;
+    }
+  }
+
+  public void checkParseOK(Parser p, String input, Object expected) {
+    ParseResult res = p.parse(input);
+    if (res == null)
+      Assert.fail("parse failed");
+    if (!deepEqual(res.getAst(), expected))
+      Assert.fail("Parse does not match expected; got " + res.getAst().toString());
+  }
+
+  public void checkParseFail(Parser p, String input) {
+    ParseResult res = p.parse(input);
+    if (res != null)
+      Assert.fail("Parse unexpectedly succeeded; got " + res.getAst().toString());
+  }
+}
-- 
GitLab