diff --git a/src/bindings/lua/hammer.lua b/src/bindings/lua/hammer.lua
index 6c82afcdb3f86d50c7d0ddfe8646c10ce429ff21..568d7e9d7e78d0287d4807f9df0cb0e4c67cfaba 100644
--- a/src/bindings/lua/hammer.lua
+++ b/src/bindings/lua/hammer.lua
@@ -140,16 +140,32 @@ local function append(a, ...)
   return helper(a, select('#', ...), ...)
 end
 
--- local parser
 local mt = {
   __index = {
     parse = function(p, str) return h.h_parse(p, str, #str) end,
   },
 }
--- parser = ffi.metatype("HParser", mt)
-
 local hammer = {}
 hammer.parser = ffi.metatype("HParser", mt)
+
+local counted_array
+local arr_mt = {
+  __index = function(table, key)
+    return table.elements[key]
+  end,
+  __len = function(table) return table.used end,
+  __ipairs = function(table)
+    local i, n = 0, #table
+    return function()
+      i = i + 1
+      if i <= n then
+        return i, table.elements[i]
+      end
+    end
+  end
+}
+counted_array = ffi.metatype("HCountedArray", arr_mt) 
+
 function hammer.token(str)
   return h.h_token(str, #str)
 end
diff --git a/src/bindings/lua/test.lua b/src/bindings/lua/test.lua
index f5b77e5d948d6831b047602c73115ada5725dae1..7b0b8cfa573a94444a824fd42db1235be67e41ac 100644
--- a/src/bindings/lua/test.lua
+++ b/src/bindings/lua/test.lua
@@ -13,7 +13,7 @@ describe("Combinator tests", function()
     local parser = hammer.token("95" .. string.char(0xa2))
     it("parses a token", function()
       local ret = parser:parse("95" .. string.char(0xa2))
-      assert.are.same(ret.ast.bytes, "95" .. string.char(0xa2))
+      assert.are.same("95" .. string.char(0xa2), ret.ast.bytes)
     end)
     it("does not parse an incomplete token", function()
       local ret = parser:parse("95")
@@ -25,7 +25,7 @@ describe("Combinator tests", function()
     local parser = hammer.ch(0xa2)
     it("parses a matching char", function()
       local ret = parser:parse(string.char(0xa2))
-      assert.are.same(ret.ast.uint, string.char(0xa2))
+      assert.are.same(string.char(0xa2), ret.ast.uint)
     end)
     it("rejects a non-matching char", function()
       local ret = parser:parse(string.char(0xa3))
@@ -37,7 +37,7 @@ describe("Combinator tests", function()
     local parser = hammer.ch_range("a", "c")
     it("parses a char in the range", function()
       local ret = parser:parse("b")
-      assert.are.same(ret.ast.uint, "b")
+      assert.are.same("b", ret.ast.uint)
     end)
     it("rejects a char outside the range", function()
       local ret = parser:parse("d")
@@ -49,7 +49,7 @@ describe("Combinator tests", function()
     local parser = hammer.int64()
     it("parses a valid 64-bit int", function()
       local ret = parser:parse(string.char(0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00))
-      assert.are.same(ret.ast.sint, -0x200000000)
+      assert.are.same(-0x200000000, ret.ast.sint)
     end)
     it("does not parse an invalid 64-bit int", function()
       local ret = parser:parse(string.char(0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00))
@@ -61,7 +61,7 @@ describe("Combinator tests", function()
     local parser = hammer.int32()
     it("parses a valid 32-bit int", function()
       local ret = parser:parse(string.char(0xff, 0xfe, 0x00, 0x00))
-      assert.are.same(ret.ast.sint, -0x20000)
+      assert.are.same(-0x20000, ret.ast.sint)
     end)
     it("does not parse an invalid 32-bit int", function()
       local ret = parser:parse(string.char(0xff, 0xfe, 0x00))
@@ -73,7 +73,7 @@ describe("Combinator tests", function()
     local parser = hammer.int16()
     it("parses a valid 16-bit int", function()
       local ret = parser:parse(string.char(0xfe, 0x00))
-      assert.are.same(ret.ast.sint, -0x200)
+      assert.are.same(-0x200, ret.ast.sint)
     end)
     it("does not parse an invalid 16-bit int", function()
       local ret = parser:parse(string.char(0xfe))
@@ -85,7 +85,7 @@ describe("Combinator tests", function()
     local parser = hammer.int8()
     it("parses a valid 8-bit int", function()
       local ret = parser:parse(string.char(0x88))
-      assert.are.same(ret.ast.sint, -0x78)
+      assert.are.same(-0x78, ret.ast.sint)
     end)
     it("does not parse an invalid 8-bit int", function()
       local ret = parser:parse("")
@@ -97,7 +97,7 @@ describe("Combinator tests", function()
     local parser = hammer.uint64()
     it("parses a valid 64-bit unsigned int", function()
       local ret = parser:parse(string.char(0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00))
-      assert.are.same(ret.ast.uint, 0x200000000)
+      assert.are.same(0x200000000, ret.ast.uint)
     end)
     it("does not parse an invalid 64-bit unsigned int", function()
       local ret = parser:parse(string.char(0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00))
@@ -109,7 +109,7 @@ describe("Combinator tests", function()
     local parser = hammer.uint32()
     it("parses a valid 32-bit unsigned int", function()
       local ret = parser:parse(string.char(0x00, 0x02, 0x00, 0x00))
-      assert.are.same(ret.ast.uint, 0x20000)
+      assert.are.same(0x20000, ret.ast.uint)
     end)
     it("does not parse an invalid 32-bit unsigned int", function()
       local ret = parser:parse(string.char(0x00, 0x02, 0x00))
@@ -121,7 +121,7 @@ describe("Combinator tests", function()
     local parser = hammer.uint16()
     it("parses a valid 16-bit unsigned int", function()
       local ret = parser:parse(string.char(0x02, 0x00))
-      assert.are.same(ret.ast.uint, 0x200)
+      assert.are.same(0x200, ret.ast.uint)
     end)
     it("does not parse an invalid 16-bit unsigned int", function()
       local ret = parser:parse(string.char(0x02))
@@ -133,7 +133,7 @@ describe("Combinator tests", function()
     local parser = hammer.uint8()
     it("parses a valid 8-bit unsigned int", function()
       local ret = parser:parse(string.char(0x78))
-      assert.are.same(ret.ast.uint, 0x78)
+      assert.are.same(0x78, ret.ast.uint)
     end)
     it("does not parse an invalid 8=bit unsigned int", function()
       local ret = parser:parse("")
@@ -145,7 +145,7 @@ describe("Combinator tests", function()
     local parser = hammer.int_range(hammer.uint8(), 3, 10)
     it("parses a value in the range", function()
       local ret = parser:parse(string.char(0x05))
-      assert.are.same(ret.ast.uint, 5)
+      assert.are.same(5, ret.ast.uint)
     end)
     it("does not parse a value outside the range", function()
       local ret = parser:parse(string.char(0xb))
@@ -158,19 +158,19 @@ describe("Combinator tests", function()
     local parser2 = hammer.whitespace(hammer.end_p())
     it("parses a string with no whitespace", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("parses a string with a leading space", function()
       local ret = parser:parse(" a")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("parses a string with leading spaces", function()
       local ret = parser:parse("  a")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("parses a string with a leading tab", function()
       local ret = parser:parse("\ta")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("does not parse a string with a leading underscore", function()
       local ret = parser:parse("_a")
@@ -178,11 +178,11 @@ describe("Combinator tests", function()
     end)
     it("parses an empty string", function()
       local ret = parser2:parse("")
-      assert.are.same(ret.ast, nil)
+      assert.are.same(nil, ret.ast)
     end)
     it("parses a whitespace-only string", function()
       local ret = parser2:parse("  ")
-      assert.are.same(ret.ast, nil)
+      assert.are.same(nil, ret.ast)
     end)
     it("does not parse a string with leading whitespace and a trailing character", function()
       local ret = parser2:parse("  x")
@@ -194,7 +194,7 @@ describe("Combinator tests", function()
     local parser = hammer.left(hammer.ch("a"), hammer.ch(" "))
     it("parses the leftmost character", function()
       local ret = parser:parse("a ")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("does not parse a string that is too short", function()
       local ret = parser:parse("a")
@@ -214,7 +214,7 @@ describe("Combinator tests", function()
     local parser = hammer.right(hammer.ch(" "), hammer.ch("a"))
     it("parses the rightmost character", function()
       local ret = parser:parse(" a")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("does not parse a string that starts with the wrong character", function()
       local ret = parser:parse("a")
@@ -234,7 +234,7 @@ describe("Combinator tests", function()
     local parser = hammer.middle(hammer.ch(" "), hammer.ch("a"), hammer.ch(" "))
     it("parses the middle character", function()
       local ret = parser:parse(" a ")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("does not parse a string that is too short", function()
       local ret = parser:parse("a")
@@ -262,7 +262,7 @@ describe("Combinator tests", function()
 
   describe("Semantic action tests", function()
     local function upcase(result, user_data)
-      local chars = result.ast.seq.elements
+      local chars = result.ast.seq
       local ret = ""
       for i, v in ipairs(chars)
         do ret = ret .. string.char(v.uint):upper()
@@ -272,14 +272,14 @@ describe("Combinator tests", function()
     local parser = hammer.action(hammer.sequence(hammer.choice(hammer.ch("a"), hammer.ch("A")), hammer.choice(hammer.ch("b"), hammer.ch("B"))), upcase, nil)
     it("converts a lowercase 'ab' to uppercase", function()
       local ret = parser:parse("ab")
-      assert.are.same(ret.ast.seq, {"A", "B"})
+      assert.are.same({"A", "B"}, ret.ast.seq)
     end)
     it("accepts an uppercase 'AB' unchanged", function()
       local ret = parser:parse("AB")
-      assert.are.same(ret.ast.seq, {"A", "B"})
+      assert.are.same({"A", "B"}, ret.ast.seq)
     end)
     it("rejects strings that don't match the underlying parser", function()
-      local ret = parser:parse()
+      local ret = parser:parse("XX")
       assert.is_falsy(ret)
     end)
   end)
@@ -288,7 +288,7 @@ describe("Combinator tests", function()
     local parser = hammer.in_({"a", "b", "c"})
     it("parses a character that is in the included set", function()
       local ret = parser:parse("b")
-      assert.are.same(ret.ast.uint, "b")
+      assert.are.same("b", ret.ast.uint)
     end)
     it("does not parse a character that is not in the included set", function()
       local ret = parser:parse("d")
@@ -300,7 +300,7 @@ describe("Combinator tests", function()
     local parser = hammer.not_in({"a", "b", "c"})
     it("parses a character that is not in the excluded set", function()
       local ret = parser:parse("d")
-      assert.are.same(ret.ast.uint, "d")
+      assert.are.same("d", ret.ast.uint)
     end)
     it("does not parse a character that is in the excluded set", function()
       local ret = parser:parse("a")
@@ -312,7 +312,7 @@ describe("Combinator tests", function()
     local parser = hammer.sequence(hammer.ch("a"), hammer.end_p())
     it("parses a string that ends where it is expected to", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
     end)
     it("does not parse a string that is too long", function()
       local ret = parser:parse("aa")
@@ -333,7 +333,7 @@ describe("Combinator tests", function()
     local parser2 = hammer.sequence(hammer.ch("a"), hammer.whitespace(hammer.ch("b")))
     it("parses a string matching the sequence", function()
       local ret = parser:parse("ab")
-      assert.are.same(ret.ast.seq, {"a", "b"})
+      assert.are.same({"a", "b"}, ret.ast.seq)
     end)
     it("does not parse a string that is too short", function()
       local ret = parser:parse("a")
@@ -345,14 +345,14 @@ describe("Combinator tests", function()
     end)
     it("parses a whitespace-optional string with no whitespace", function()
       local ret = parser2:parse("ab")
-      assert.are.same(ret.ast.seq, {"a", "b"})
+      assert.are.same({"a", "b"}, ret.ast.seq)
     end)
     -- it("parses a whitespace-optional string containing whitespace", function()
     --   local ret = parser:parse("a b")
-    --   assert.are.same(ret.ast.seq, {"a", "b"}) -- this is the line that segfaults
+    --   assert.are.same({"a", "b"}, ret.ast.seq) -- this is the line that segfaults
     --   print("in sequence")
     --   ret = parser:parse("a  b")
-    --   assert.are.same(ret.ast.seq, {"a", "b"})
+    --   assert.are.same({"a", "b"}, ret.ast.seq)
     -- end)
   end)
 
@@ -360,9 +360,9 @@ describe("Combinator tests", function()
     local parser = hammer.choice(hammer.ch("a"), hammer.ch("b"))
     it("parses a character in the choice set", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
       ret = parser:parse("b")
-      assert.are.same(ret.ast.uint, "b")
+      assert.are.same("b", ret.ast.uint)
     end)
     it("does not parse a character not in the choice set", function()
       local ret = parser:parse("c")
@@ -375,9 +375,9 @@ describe("Combinator tests", function()
     local parser2 = hammer.butnot(hammer.ch_range("0", "9"), hammer.ch("6"))
     it("succeeds when 'a' matches but 'ab' doesn't", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
       ret = parser:parse("aa")
-      assert.are.same(ret.ast.uint, "a")
+      assert.are.same("a", ret.ast.uint)
     end)
     it("fails when p2's result is longer than p1's", function()
       local ret = parser:parse("ab")
@@ -393,7 +393,7 @@ describe("Combinator tests", function()
     local parser = hammer.difference(hammer.token("ab"), hammer.ch("a"))
     it("succeeds when 'ab' matches and its result is longer than the result for 'a'", function()
       local ret = parser:parse("ab")
-      assert.are.same(ret.ast.bytes, "ab")
+      assert.are.same("ab", ret.ast.bytes)
     end)
     it("fails if 'ab' doesn't match", function()
       local ret = parser:parse("a")
@@ -405,11 +405,11 @@ describe("Combinator tests", function()
     local parser = hammer.xor(hammer.ch_range("0", "6"), hammer.ch_range("5", "9"))
     it("parses a value only in the first range", function()
       local ret = parser:parse("0")
-      assert.are.same(ret.ast.uint, "0")
+      assert.are.same("0", ret.ast.uint)
     end)
     it("parses a value only in the second range", function()
       local ret = parser:parse("9")
-      assert.are.same(ret.ast.uint, "9")
+      assert.are.same("9", ret.ast.uint)
     end)
     it("does not parse a value inside both ranges", function()
       local ret = parser:parse("5")
@@ -425,17 +425,17 @@ describe("Combinator tests", function()
     local parser = hammer.many(hammer.choice(hammer.ch("a"), hammer.ch("b")))
     it("parses an empty string", function()
       local ret = parser:parse("")
-      assert.are.same(ret.ast.seq, {})
+      assert.are.same({}, ret.ast.seq)
     end)
     it("parses a single repetition of the pattern", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
       ret = parser:parse("b")
-      assert.are.same(ret.ast.seq, {"b"})
+      assert.are.same({"b"}, ret.ast.seq)
     end)
     it("parses multiple repetitions of the pattern", function()
       local ret = parser:parse("aabbaba")
-      assert.are.same(ret.ast.seq, {"a", "a", "b", "b", "a", "b", "a"})
+      assert.are.same({"a", "a", "b", "b", "a", "b", "a"}, ret.ast.seq)
     end)
   end)
 
@@ -447,13 +447,13 @@ describe("Combinator tests", function()
     end)
     it("parses a single repetition of the pattern", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
       ret = parser:parse("b")
-      assert.are.same(ret.ast.seq, {"b"})
+      assert.are.same({"b"}, ret.ast.seq)
     end)
     it("parses multiple repetitions of the pattern", function()
       local ret = parser:parse("aabbaba")
-      assert.are.same(ret.ast.seq, {"a", "a", "b", "b", "a", "b", "a"})
+      assert.are.same({"a", "a", "b", "b", "a", "b", "a"}, ret.ast.seq)
     end)
     it("does not parse a string that does not start with one of the patterns to repeat", function()
       local ret = parser:parse("daabbabadef")
@@ -469,7 +469,7 @@ describe("Combinator tests", function()
     end)
     it("parses a string containing the correct number of repetitions", function()
       local ret = parser:parse("abdef")
-      assert.are.same(ret.ast.seq, {"a", "b"})
+      assert.are.same({"a", "b"}, ret.ast.seq)
     end)
     it("does not parse a string that does not start with a character in the repetition set", function()
       local ret = parser:parse("dabdef")
@@ -481,17 +481,17 @@ describe("Combinator tests", function()
     local parser = hammer.sequence(hammer.ch("a"), hammer.optional(hammer.choice(hammer.ch("b"), hammer.ch("c"))), hammer.ch("d"))
     it("parses a string containing either optional character", function()
       local ret = parser:parse("abd")
-      assert.are.same(ret.ast.seq, {"a", "b", "d"})
+      assert.are.same({"a", "b", "d"}, ret.ast.seq)
       ret = parser:parse("acd")
-      assert.are.same(ret.ast.seq, {"a", "c", "d"})
+      assert.are.same({"a", "c", "d"}, ret.ast.seq)
     end)
     it("parses a string missing one of the optional characters", function()
       local ret = parser:parse("ad")
-      assert.are.same(ret.ast.seq, {"a", {}, "d"})
+      assert.are.same({"a", {}, "d"}, ret.ast.seq)
     end)
     it("does not parse a string containing a character not among the optional ones", function()
       local ret = parser:parse("aed")
-      assert.is_falsy(ret)
+      assert.is_falsy(ret.ast)
     end)
   end)
 
@@ -499,7 +499,7 @@ describe("Combinator tests", function()
     local parser = hammer.sequence(hammer.ch("a"), hammer.ignore(hammer.ch("b")), hammer.ch("c"))
     it("parses a string containing the pattern to ignore, and leaves that pattern out of the result", function()
       local ret = parser:parse("abc")
-      assert.are.same(ret.ast.seq, {"a", "c"})
+      assert.are.same({"a", "c"}, ret.ast.seq)
     end)
     it("does not parse a string not containing the pattern to ignore", function()
       local ret = parser:parse("ac")
@@ -511,23 +511,23 @@ describe("Combinator tests", function()
     local parser = hammer.sepBy(hammer.choice(hammer.ch("1"), hammer.ch("2"), hammer.ch("3")), hammer.ch(","))
     it("parses an ordered list", function()
       local ret = parser:parse("1,2,3")
-      assert.are.same(ret.ast.seq, {"1", "2", "3"})
+      assert.are.same({"1", "2", "3"}, ret.ast.seq)
     end)
     it("parses an unordered list", function()
       local ret = parser:parse("1,3,2")
-      assert.are.same(ret.ast.seq, {"1", "3", "2"})
+      assert.are.same({"1", "3", "2"}, ret.ast.seq)
     end)
     it("parses a list not containing all options", function()
       local ret = parser:parse("1,3")
-      assert.are.same(ret.ast.seq, {"1", "3"})
+      assert.are.same({"1", "3"}, ret.ast.seq)
     end)
     it("parses a unary list", function()
       local ret = parser:parse("3")
-      assert.are.same(ret.ast.seq, {"3"})
+      assert.are.same({"3"}, ret.ast.seq)
     end)
     it("parses an empty list", function()
       local ret = parser:parse("")
-      assert.are.same(ret.ast.seq, {})
+      assert.are.same({}, ret.ast.seq)
     end)
   end)
 
@@ -535,20 +535,20 @@ describe("Combinator tests", function()
     local parser = hammer.sepBy1(hammer.choice(hammer.ch("1"), hammer.ch("2"), hammer.ch("3")), hammer.ch(","))
     it("parses an ordered list", function()
       local ret = parser:parse("1,2,3")
-      assert.are.same(ret.ast.seq, {"1", "2", "3"})
+      assert.are.same({"1", "2", "3"}, ret.ast.seq)
     end)
     it("parses an unordered list", function()
       local ret = parser:parse("1,3,2")
-      assert.are.same(ret.ast.seq, {"1", "3", "2"})
+      assert.are.same({"1", "3", "2"}, ret.ast.seq)
     end)
     it("parses a list not containing all options", function()
       local ret = parser:parse("1,3")
-      assert.are.same(ret.ast.seq, {"1", "3"})
+      assert.are.same({"1", "3"}, ret.ast.seq)
     end)
     -- it("parses a unary list", function()
     --   local ret = parser:parse("3")
     --   print("in sepBy1")
-    --   assert.are.same(ret.ast.seq, {"3"}) -- this line also segfaults
+    --   assert.are.same({"3"}, ret.ast.seq) -- this line also segfaults
     -- end)
     it("does not parse an empty list", function()
       local ret = parser:parse("")
@@ -562,15 +562,15 @@ describe("Combinator tests", function()
     local parser3 = hammer.sequence(hammer.ch("a"), hammer.epsilon_p())
     it("parses an empty string between two characters", function()
       local ret = parser:parse("ab")
-      assert.are.same(ret.ast.seq, {"a", "b"})
+      assert.are.same({"a", "b"}, ret.ast.seq)
     end)
     it("parses an empty string before a character", function()
       local ret = parser2:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
     end)
     it("parses a ", function()
       local ret = parser3:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
     end)
   end)
 
@@ -581,9 +581,10 @@ describe("Combinator tests", function()
     local parser = hammer.attr_bool(hammer.many1(hammer.choice(hammer.ch("a"), hammer.ch("b"))), equals)
     it("parses successfully when both characters are the same (i.e., the validation function succeeds)", function()
       local ret = parser:parse("aa")
-      assert.are.same(ret.ast.seq, {"a", "a"})
+      assert.are.same({"a", "a"}, ret.ast.seq)
+      print("in attr_bool")
       ret = parser:parse("bb")
-      assert.are.same(ret.ast.seq, {"b", "b"})
+      assert.are.same({"b", "b"}, ret.ast.seq)
     end)
     it("does not parse successfully when the characters are different (i.e., the validation function fails)", function()
       local ret = parser:parse("ab")
@@ -597,7 +598,7 @@ describe("Combinator tests", function()
     local parser3 = hammer.sequence(hammer.ch("1"), hammer.and_(hammer.ch("2")))
     it("parses successfully when the lookahead matches the next character to parse", function()
       local ret = parser:parse("0")
-      assert.are.same(ret.ast.seq, {"0"})
+      assert.are.same({"0"}, ret.ast.seq)
     end)
     it("does not parse successfully when the lookahead does not match the next character to parse", function()
       local ret = parser2:parse("0")
@@ -605,7 +606,7 @@ describe("Combinator tests", function()
     end)
     it("parses successfully when the lookahead is there", function()
       local ret = parser3:parse("12")
-      assert.are.same(ret.ast.seq, {"1"})
+      assert.are.same({"1"}, ret.ast.seq)
     end)
   end)
 
@@ -614,7 +615,7 @@ describe("Combinator tests", function()
     local parser2 = hammer.sequence(hammer.ch("a"), hammer.choice(hammer.sequence(hammer.ch("+"), hammer.not_(hammer.ch("+"))), hammer.token("++")), hammer.ch("b"))
     it("parses a single plus correctly in the 'choice' example", function()
       local ret = parser:parse("a+b")
-      assert.are.same(ret.ast.seq, {"a", "+", "b"})
+      assert.are.same({"a", "+", "b"}, ret.ast.seq)
     end)
     it("does not parse a double plus correctly in the 'choice' example", function()
       local ret = parser:parse("a++b")
@@ -622,11 +623,11 @@ describe("Combinator tests", function()
     end)
     it("parses a single plus correctly in the 'not' example", function()
       local ret = parser2:parse("a+b")
-      assert.are.same(ret.ast.seq, {"a", {"+"}, "b"})
+      assert.are.same({"a", {"+"}, "b"}, ret.ast.seq)
     end)
     it("parses a double plus correctly in the 'not' example", function()
       local ret = parser2:parse("a++b")
-      assert.are.same(ret.ast.seq, {"a", "++", "b"})
+      assert.are.same({"a", "++", "b"}, ret.ast.seq)
     end)
   end)
 
@@ -635,15 +636,15 @@ describe("Combinator tests", function()
     hammer.bind_indirect(parser, hammer.choice(hammer.sequence(parser, hammer.ch("a")), hammer.ch("a")))
     it("parses the base case", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
     end)
     it("parses one level of recursion", function()
       local ret = parser:parse("aa")
-      assert.are.same(ret.ast.seq, {"a", "a"})
+      assert.are.same({"a", "a"}, ret.ast.seq)
     end)
     it("parses two levels of recursion", function()
       local ret = parser:parse("aaa")
-      assert.are.same(ret.ast.seq, {{"a", "a"}, "a"})
+      assert.are.same({{"a", "a"}, "a"}, ret.ast.seq)
     end)
   end)
 
@@ -652,15 +653,15 @@ describe("Combinator tests", function()
     hammer.bind_indirect(parser, hammer.choice(hammer.sequence(hammer.ch("a"), parser), hammer.epsilon_p()))
     it("parses the base case", function()
       local ret = parser:parse("a")
-      assert.are.same(ret.ast.seq, {"a"})
+      assert.are.same({"a"}, ret.ast.seq)
     end)
     it("parses one level of recursion", function()
       local ret = parser:parse("aa")
-      assert.are.same(ret.ast.seq, {"a", {"a"}})
+      assert.are.same({"a", {"a"}}, ret.ast.seq)
     end)
     it("parses two levels of recursion", function()
       local ret = parser:parse("aaa")
-      assert.are.same(ret.ast.seq, {"a", {"a", {"a"}}})
+      assert.are.same({"a", {"a", {"a"}}}, ret.ast.seq)
     end)
   end)
 
@@ -682,25 +683,25 @@ describe("Combinator tests", function()
     local parser8 = hammer.with_endianness(lb, u5)
     it("parses big-endian cases", function()
       local ret = parser1:parse("abcd")
-      assert.are.same(ret.ast.uint, 0x61626364)
+      assert.are.same(0x61626364, ret.ast.uint)
       ret = parser2:parse("abcd")
-      assert.are.same(ret.ast.uint, 0xc)
+      assert.are.same(0xc, ret.ast.uint)
     end)
     it("parses little-endian cases", function()
       local ret = parser3:parse("abcd")
-      assert.are.same(ret.ast.uint, 0x61626364)
+      assert.are.same(0x61626364, ret.ast.uint)
       ret = parser4:parse("abcd")
-      assert.are.same(ret.ast.uint, 0xc)
+      assert.are.same(0xc, ret.ast.uint)
     end)
     it("parses mixed-endian cases", function()
       local ret = parser5:parse("abcd")
-      assert.are.same(ret.ast.uint, 0x61626364)
+      assert.are.same(0x61626364, ret.ast.uint)
       ret = parser6:parse("abcd")
-      assert.are.same(ret.ast.uint, 0x1)
+      assert.are.same(0x1, ret.ast.uint)
       ret = parser7:parse("abcd")
-      assert.are.same(ret.ast.uint, 0x64636261)
+      assert.are.same(0x64636261, ret.ast.uint)
       ret = parser8:parse("abcd")
-      assert.are.same(ret.ast.uint, 0xc)
+      assert.are.same(0xc, ret.ast.uint)
     end)
   end)
 
@@ -708,11 +709,11 @@ describe("Combinator tests", function()
     local parser = hammer.sequence(hammer.put_value(hammer.uint8(), "size"), hammer.token("foo"), hammer.length_value(hammer.get_value("size"), hammer.uint8()))
     it("parses a string that has enough bytes for the specified length", function()
       local ret = parser:parse(string.char(0x06) .. "fooabcdef")
-      assert.are.same(ret.ast.seq.elements[1].bytes, "foo")
-      assert.are.same(ret.ast.seq.elements[2].seq, {0x61, 0x62, 0x63, 0x64, 0x65, 0x66})
+      assert.are.same("foo", ret.ast.seq[2])
+      assert.are.same({0x61, 0x62, 0x63, 0x64, 0x65, 0x66}, ret.ast.seq[3])
     end)
     it("does not parse a string that does not have enough bytes for the specified length", function()
-      local ret = parser:parse()
+      local ret = parser:parse(string.char(0x06) .. "fooabcde")
       assert.is_falsy(ret)
     end)
   end)
@@ -721,17 +722,17 @@ describe("Combinator tests", function()
     local parser = hammer.permutation(hammer.ch("a"), hammer.ch("b"), hammer.ch("c"))
     it("parses a permutation of 'abc'", function()
       local ret = parser:parse("abc")
-      assert.are.same(ret.ast.seq, {"a", "b", "c"})
+      assert.are.same({"a", "b", "c"}, ret.ast.seq)
       ret = parser:parse("acb")
-      assert.are.same(ret.ast.seq, {"a", "c", "b"})
+      assert.are.same({"a", "c", "b"}, ret.ast.seq)
       ret = parser:parse("bac")
-      assert.are.same(ret.ast.seq, {"b", "a", "c"})
+      assert.are.same({"b", "a", "c"}, ret.ast.seq)
       ret = parser:parse("bca")
-      assert.are.same(ret.ast.seq, {"b", "c", "a"})
+      assert.are.same({"b", "c", "a"}, ret.ast.seq)
       ret = parser:parse("cab")
-      assert.are.same(ret.ast.seq, {"c", "a", "b"})
+      assert.are.same({"c", "a", "b"}, ret.ast.seq)
       ret = parser:parse("cba")
-      assert.are.same(ret.ast.seq, {"c", "b", "a"})
+      assert.are.same({"c", "b", "a"}, ret.ast.seq)
     end)
     it("does not parse a string that is not a permutation of 'abc'", function()
       local ret = parser:parse("a")
@@ -744,21 +745,21 @@ describe("Combinator tests", function()
     parser = hammer.permutation(hammer.ch("a"), hammer.ch("b"), hammer.optional(hammer.ch("c")))
     it("parses a string that is a permutation of 'ab[c]'", function()
       local ret = parser:parse("abc")
-      assert.are.same(ret.ast.seq, {"a", "b", "c"})
+      assert.are.same({"a", "b", "c"}, ret.ast.seq)
       ret = parser:parse("acb")
-      assert.are.same(ret.ast.seq, {"a", "c", "b"})
+      assert.are.same({"a", "c", "b"}, ret.ast.seq)
       ret = parser:parse("bac")
-      assert.are.same(ret.ast.seq, {"b", "a", "c"})
+      assert.are.same({"b", "a", "c"}, ret.ast.seq)
       ret = parser:parse("bca")
-      assert.are.same(ret.ast.seq, {"b", "c", "a"})
+      assert.are.same({"b", "c", "a"}, ret.ast.seq)
       ret = parser:parse("cab")
-      assert.are.same(ret.ast.seq, {"c", "a", "b"})
+      assert.are.same({"c", "a", "b"}, ret.ast.seq)
       ret = parser:parse("cba")
-      assert.are.same(ret.ast.seq, {"c", "b", "a"})
+      assert.are.same({"c", "b", "a"}, ret.ast.seq)
       ret = parser:parse("ab")
-      assert.are.same(ret.ast.seq, {"a", "b"})
+      assert.are.same({"a", "b"}, ret.ast.seq)
       ret = parser:parse("ba")
-      assert.are.same(ret.ast.seq, {"b", "a"})
+      assert.are.same({"b", "a"}, ret.ast.seq)
     end)
     it("does not parse a string that is not a permutation of 'ab[c]'", function()
       local ret = parser:parse("a")
@@ -781,21 +782,21 @@ describe("Combinator tests", function()
     parser = hammer.permutation(hammer.optional(hammer.ch("c")), hammer.ch("a"), hammer.ch("b"))
     it("parses a string that is a permutation of '[c]ab'", function()
       local ret = parser:parse("abc")
-      assert.are.same(ret.ast.seq, {"a", "b", "c"})
+      assert.are.same({"a", "b", "c"}, ret.ast.seq)
       ret = parser:parse("acb")
-      assert.are.same(ret.ast.seq, {"a", "c", "b"})
+      assert.are.same({"a", "c", "b"}, ret.ast.seq)
       ret = parser:parse("bac")
-      assert.are.same(ret.ast.seq, {"b", "a", "c"})
+      assert.are.same({"b", "a", "c"}, ret.ast.seq)
       ret = parser:parse("bca")
-      assert.are.same(ret.ast.seq, {"b", "c", "a"})
+      assert.are.same({"b", "c", "a"}, ret.ast.seq)
       ret = parser:parse("cab")
-      assert.are.same(ret.ast.seq, {"c", "a", "b"})
+      assert.are.same({"c", "a", "b"}, ret.ast.seq)
       ret = parser:parse("cba")
-      assert.are.same(ret.ast.seq, {"c", "b", "a"})
+      assert.are.same({"c", "b", "a"}, ret.ast.seq)
       ret = parser:parse("ab")
-      assert.are.same(ret.ast.seq, {"a", "b"})
+      assert.are.same({"a", "b"}, ret.ast.seq)
       ret = parser:parse("ba")
-      assert.are.same(ret.ast.seq, {"b", "a"})
+      assert.are.same({"b", "a"}, ret.ast.seq)
     end)
     it("does not parse a string that is not a permutation of '[c]ab'", function()
       local ret = parser:parse("a")
@@ -820,7 +821,7 @@ describe("Combinator tests", function()
   -- describe("Monadic binding tests", function()
   --   local function continuation(allocator, result, env)
   --     local val = 0
-  --     for k, v in result.seq.elements
+  --     for k, v in result.seq
   --       do val = val*10 + v->uint - 48
   --     end
   --     if val > 26 then