From d5358a724ba5d5abe809c02f55769f7f94deed64 Mon Sep 17 00:00:00 2001
From: Dan Hirsch <thequux@thequux.com>
Date: Sat, 12 May 2012 00:40:54 +0100
Subject: [PATCH] Made sequence and choice variadic, and added test suite
 helper functions

---
 src/Makefile     |  3 ++
 src/hammer.c     | 87 ++++++++++++++++++++++++++++++++++--------------
 src/hammer.h     |  6 ++--
 src/internal.h   |  1 +
 src/test_suite.h | 38 ++++++++++++++++++---
 5 files changed, 102 insertions(+), 33 deletions(-)

diff --git a/src/Makefile b/src/Makefile
index 670a4daf..99bebd70 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -18,3 +18,6 @@ libhammer.a: bitreader.o hammer.o pprint.o
 
 bitreader.o: test_suite.h
 hammer.o: hammer.h
+
+test: test_suite
+	./test_suite -v
diff --git a/src/hammer.c b/src/hammer.c
index ec010aca..e4addbae 100644
--- a/src/hammer.c
+++ b/src/hammer.c
@@ -19,6 +19,7 @@
 #include "internal.h"
 #include <assert.h>
 #include <string.h>
+#include <stdarg.h>
 
 parse_state_t* from(parse_state_t *ps, const size_t index) {
   parse_state_t *ret = g_new(parse_state_t, 1);
@@ -71,7 +72,10 @@ parse_result_t* do_parse(const parser_t* parser, parse_state_t *state) {
   } else {
     // It doesn't exist... run the 
     parse_result_t *res;
-    res = parser->fn(parser->env, state);
+    if (parser)
+      res = parser->fn(parser->env, state);
+    else
+      res = NULL;
     if (state->input_stream.overrun)
       res = NULL; // overrun is always failure.
     // update the cache
@@ -229,10 +233,27 @@ static parse_result_t* parse_sequence(void *env, parse_state_t *state) {
   return make_result(tok);
 }
 
-const parser_t* sequence(const parser_t* p_array[]) { 
-  size_t len = sizeof(p_array) / sizeof(parser_t*);
+const parser_t* sequence(const parser_t *p, ...) {
+  va_list ap;
+  size_t len = 0;
+  const parser_t *arg;
+  va_start(ap, p);
+  do {
+    len++;
+    arg = va_arg(ap, const parser_t *);
+  } while (arg);
+  va_end(ap);
   sequence_t *s = g_new(sequence_t, 1);
-  s->p_array = (const parser_t**)p_array; s->len = len;
+  s->p_array = g_new(const parser_t *, len);
+
+  va_start(ap, p);
+  s->p_array[0] = p;
+  for (size_t i = 1; i < len; i++) {
+    s->p_array[i] = va_arg(ap, const parser_t *);
+  } while (arg);
+  va_end(ap);
+
+  s->len = len;
   parser_t *ret = g_new(parser_t, 1);
   ret->fn = parse_sequence; ret->env = (void*)s;
   return ret;
@@ -252,10 +273,28 @@ static parse_result_t* parse_choice(void *env, parse_state_t *state) {
   return NULL;
 }
 
-const parser_t* choice(const parser_t* p_array[]) { 
-  size_t len = sizeof(p_array) / sizeof(parser_t*);
+const parser_t* choice(const parser_t* p, ...) {
+  va_list ap;
+  size_t len = 0;
   sequence_t *s = g_new(sequence_t, 1);
-  s->p_array = (const parser_t**)p_array; s->len = len;
+
+  const parser_t *arg;
+  va_start(ap, p);
+  do {
+    len++;
+    arg = va_arg(ap, const parser_t *);
+  } while (arg);
+  va_end(ap);
+  s->p_array = g_new(const parser_t *, len);
+
+  va_start(ap, p);
+  s->p_array[0] = p;
+  for (size_t i = 1; i < len; i++) {
+    s->p_array[i] = va_arg(ap, const parser_t *);
+  } while (arg);
+  va_end(ap);
+
+  s->len = len;
   parser_t *ret = g_new(parser_t, 1);
   ret->fn = parse_choice; ret->env = (void*)s;
   return ret;
@@ -479,6 +518,7 @@ static void test_range(void) {
   g_check_failed(ret2);
 }
 
+#if 0
 static void test_int64(void) {
   uint8_t test1[8] = { 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00 };
   uint8_t test2[7] = { 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00 };
@@ -578,6 +618,8 @@ static void test_float32(void) {
   g_check_cmpfloat(ret1->ast->flt, ==, 1);
   g_check_failed(ret2);
 }
+#endif
+
 
 static void test_whitespace(void) {
   uint8_t test1[1] = { 'a' };
@@ -620,8 +662,7 @@ static void test_not_in(void) {
 static void test_end_p(void) {
   uint8_t test1[1] = { 'a' };
   uint8_t test2[2] = { 'a', 'a' };
-  const parser_t *p_array[2] = { ch('a'), end_p() };
-  const parser_t *end_p_ = sequence(p_array);
+  const parser_t *end_p_ = sequence(ch('a'), end_p(), NULL);
   parse_result_t *ret1 = parse(end_p_, test1, 1);
   parse_result_t *ret2 = parse(end_p_, test2, 2);
   g_check_cmpint(ret1->ast->uint, ==, 'a');
@@ -636,22 +677,16 @@ static void test_nothing_p(void) {
 }
 
 static void test_sequence(void) {
-  uint8_t test1[2] = { 'a', 'b' };
-  uint8_t test2[1] = { 'a' };
-  uint8_t test3[1] = { 'b' };
-  uint8_t test4[3] = { 'a', ' ', 'b' };
-  uint8_t test5[4] = { 'a', ' ', ' ', 'b' };
-  uint8_t test6[2] = { 'a', 'b' };
-  const parser_t *s1[2] = { ch('a'), ch('b') };
-  const parser_t *s2[2] = { ch('a'), whitespace(ch('b')) };
-  const parser_t *sequence_1 = sequence(s1);
-  const parser_t *sequence_2 = sequence(s2);
-  parse_result_t *ret1 = parse(sequence_1, test1, 2);
-  parse_result_t *ret2 = parse(sequence_1, test2, 1);
-  parse_result_t *ret3 = parse(sequence_1, test3, 1);
-  parse_result_t *ret4 = parse(sequence_2, test4, 3);
-  parse_result_t *ret5 = parse(sequence_2, test5, 4);
-  parse_result_t *ret6 = parse(sequence_2, test6, 2);
+  const parser_t *sequence_1 = sequence(ch('a'), ch('b'), NULL);
+  const parser_t *sequence_2 = sequence(ch('a'), whitespace(ch('b')), NULL);
+
+  g_check_parse_ok(sequence_1, "ab", 2, "(<41> <42>)");
+  g_check_parse_failed(sequence_1, "a", 1);
+  g_check_parse_failed(sequence_1, "b", 1);
+  g_check_parse_ok(sequence_2, "ab", 2, "(<41> <42>)");
+  g_check_parse_ok(sequence_2, "a b", 3, "(<41> <42>)");
+  g_check_parse_ok(sequence_2, "a  b", 4, "(<41> <42>)");
+  
   //g_check_cmpseq(ret1->ast->
 }
 
@@ -727,6 +762,7 @@ void register_parser_tests(void) {
   g_test_add_func("/core/parser/token", test_token);
   g_test_add_func("/core/parser/ch", test_ch);
   g_test_add_func("/core/parser/range", test_range);
+#if 0
   g_test_add_func("/core/parser/int64", test_int64);
   g_test_add_func("/core/parser/int32", test_int32);
   g_test_add_func("/core/parser/int16", test_int16);
@@ -737,6 +773,7 @@ void register_parser_tests(void) {
   g_test_add_func("/core/parser/uint8", test_uint8);
   g_test_add_func("/core/parser/float64", test_float64);
   g_test_add_func("/core/parser/float32", test_float32);
+#endif
   g_test_add_func("/core/parser/whitespace", test_whitespace);
   g_test_add_func("/core/parser/action", test_action);
   g_test_add_func("/core/parser/left_factor_action", test_left_factor_action);
diff --git a/src/hammer.h b/src/hammer.h
index 1b84861e..a6445a89 100644
--- a/src/hammer.h
+++ b/src/hammer.h
@@ -147,11 +147,11 @@ const parser_t* end_p();
 /* This parser always fails. */
 const parser_t* nothing_p();
 
-/* Given an array of parsers, p_array, apply each parser in order. The parse succeeds only if all parsers succeed. */
-const parser_t* sequence(const parser_t* p_array[]);
+/* Given an null-terminated list of parsers, apply each parser in order. The parse succeeds only if all parsers succeed. */
+const parser_t* sequence(const parser_t* p, ...) __attribute__((sentinel));
 
 /* Given an array of parsers, p_array, apply each parser in order. The first parser to succeed is the result; if no parsers succeed, the parse fails. */
-const parser_t* choice(const parser_t* p_array[]);
+const parser_t* choice(const parser_t* p, ...) __attribute__((sentinel));
 
 /* Given two parsers, p1 and p2, this parser succeeds in the following cases: 
  * - if p1 succeeds and p2 fails
diff --git a/src/internal.h b/src/internal.h
index 6659599a..771a0ee4 100644
--- a/src/internal.h
+++ b/src/internal.h
@@ -36,4 +36,5 @@ parse_result_t* do_parse(const parser_t* parser, parse_state_t *state);
 void put_cached(parse_state_t *ps, const parser_t *p, parse_result_t *cached);
 guint djbhash(const uint8_t *buf, size_t len);
 char* write_result_unamb(const parsed_token_t* tok);
+void pprint(const parsed_token_t* tok, int indent, int delta);
 #endif // #ifndef HAMMER_INTERNAL__H
diff --git a/src/test_suite.h b/src/test_suite.h
index 711f9ebe..3c01d3fe 100644
--- a/src/test_suite.h
+++ b/src/test_suite.h
@@ -15,7 +15,7 @@
 
 #define g_check_bytes(len, n1, op, n2) {	\
     const uint8_t *_n1 = (n1);			\
-    uint8_t *_n2 = (n2);			\
+    const uint8_t *_n2 = (n2);			\
     if (!(memcmp(_n1, _n2, len) op 0)) {	\
       g_test_message("Check failed: (%s)",    	\
 		     #n1 " " #op " " #n2);	\
@@ -23,10 +23,18 @@
     }						\
   }
 
-#define g_check_cmpseq(n1, op, n2) {            \
-    GSequence *seq = (n1);                      \
-    
-    
+#define g_check_string(n1, op, n2) {			\
+    const char *_n1 = (n1);				\
+    const char *_n2 = (n2);				\
+    if (!(strcmp(_n1, _n2) op 0)) {		\
+      g_test_message("Check failed: (%s) (%s %s %s)",	\
+		     #n1 " " #op " " #n2,		\
+		     _n1, #op, _n2);			\
+      g_test_fail();					\
+    }							\
+  }
+
+// TODO: replace uses of this with g_check_parse_failed
 #define g_check_failed(res) {		        \
     const parse_result_t *result = (res);       \
     if (NULL != result) {                       \
@@ -35,6 +43,26 @@
     }                                           \
   }
 
+#define g_check_parse_failed(parser, input, inp_len) {	 		\
+    const parse_result_t *result = parse(parser, (const uint8_t*)input, inp_len); \
+    if (NULL != result) {						\
+      g_test_message("Check failed: shouldn't have succeeded, but did"); \
+      g_test_fail();							\
+    }									\
+  }
+
+#define g_check_parse_ok(parser, input, inp_len, result) {		\
+    parse_result_t *res = parse(parser, (const uint8_t*)input, inp_len); \
+    if (!res) {								\
+      g_test_message("Parse failed");					\
+      g_test_fail();							\
+    } else {								\
+      char* cres = write_result_unamb(res->ast);			\
+      g_check_string(cres, ==, result);					\
+    }									\
+  }
+
+
 #define g_check_cmpint(n1, op, n2) g_check_inttype("%d", int, n1, op, n2)
 #define g_check_cmplong(n1, op, n2) g_check_inttype("%ld", long, n1, op, n2)
 #define g_check_cmplonglong(n1, op, n2) g_check_inttype("%lld", long long, n1, op, n2)
-- 
GitLab