Explorar o código

json_scanf implementation

PUBLISHED_FROM=84dd7cc1f6ca164f47d081084db8b32abe19b91e
Sergey Lyubka %!s(int64=9) %!d(string=hai) anos
pai
achega
a4e7d8a999
Modificáronse 4 ficheiros con 215 adicións e 6 borrados
  1. 11 2
      Makefile
  2. 141 1
      frozen.c
  3. 31 0
      frozen.h
  4. 32 3
      unit_test.c

+ 11 - 2
Makefile

@@ -1,9 +1,18 @@
 PROF = -fprofile-arcs -ftest-coverage -g -O0
 PROF = -fprofile-arcs -ftest-coverage -g -O0
-CFLAGS = -W -Wall -pedantic -O3 $(CFLAGS_EXTRA)
+CFLAGS = -W -Wall -pedantic -O3 $(PROF) $(CFLAGS_EXTRA)
 
 
-all:
+.PHONY: clean all
+
+all: c c++
+
+c: clean
+	rm -rf *.gc*
 	cc unit_test.c -o unit_test $(CFLAGS) && ./unit_test
 	cc unit_test.c -o unit_test $(CFLAGS) && ./unit_test
 	cc -m32 unit_test.c -o unit_test $(CFLAGS) && ./unit_test
 	cc -m32 unit_test.c -o unit_test $(CFLAGS) && ./unit_test
+	gcov -a unit_test.c
+
+c++: clean
+	rm -rf *.gc*
 	g++ unit_test.c -o unit_test $(CFLAGS) && ./unit_test
 	g++ unit_test.c -o unit_test $(CFLAGS) && ./unit_test
 	gcov -a unit_test.c
 	gcov -a unit_test.c
 
 

+ 141 - 1
frozen.c

@@ -311,14 +311,19 @@ static int parse_number(struct frozen *f) {
 
 
 /* array = '[' [ value { ',' value } ] ']' */
 /* array = '[' [ value { ',' value } ] ']' */
 static int parse_array(struct frozen *f) {
 static int parse_array(struct frozen *f) {
-  int ind;
+  int i = 0, ind, current_path_len;
+  char buf[20];
   TRY(test_and_skip(f, '['));
   TRY(test_and_skip(f, '['));
   {
   {
     SET_STATE(f, f->cur - 1, JSON_TYPE_ARRAY, "", 0);
     SET_STATE(f, f->cur - 1, JSON_TYPE_ARRAY, "", 0);
     TRY(capture_ptr(f, f->cur - 1, JSON_TYPE_ARRAY));
     TRY(capture_ptr(f, f->cur - 1, JSON_TYPE_ARRAY));
     ind = f->num_tokens - 1;
     ind = f->num_tokens - 1;
     while (cur(f) != ']') {
     while (cur(f) != ']') {
+      snprintf(buf, sizeof(buf), "[%d]", i);
+      i++;
+      current_path_len = append_to_path(f, buf, strlen(buf));
       TRY(parse_value(f));
       TRY(parse_value(f));
+      truncate_path(f, current_path_len);
       if (cur(f) == ',') f->cur++;
       if (cur(f) == ',') f->cur++;
     }
     }
     TRY(test_and_skip(f, ']'));
     TRY(test_and_skip(f, ']'));
@@ -759,3 +764,138 @@ int json_parse(const char *json_string, int json_string_length,
 
 
   return frozen.cur - json_string;
   return frozen.cur - json_string;
 }
 }
+
+struct scan_array_info {
+  char path[JSON_MAX_PATH_LEN];
+  struct json_token *token;
+};
+
+static void json_scanf_array_elem_cb(void *callback_data, const char *path,
+                                     const struct json_token *token) {
+  struct scan_array_info *info = (struct scan_array_info *) callback_data;
+  if (strcmp(path, info->path) == 0) {
+    *info->token = *token;
+  }
+}
+
+int json_scanf_array_elem(const char *s, int len, const char *path, int idx,
+                          struct json_token *token) {
+  struct scan_array_info info;
+  info.token = token;
+  memset(token, 0, sizeof(*token));
+  snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx);
+  json_parse(s, len, json_scanf_array_elem_cb, &info);
+  return token->len;
+}
+
+struct json_scanf_info {
+  int num_conversions;
+  char *path;
+  const char *fmt;
+  void *target;
+  void *user_data;
+};
+
+static void json_scanf_cb(void *callback_data, const char *path,
+                          const struct json_token *tok) {
+  struct json_scanf_info *info = (struct json_scanf_info *) callback_data;
+  if (strcmp(path, info->path) == 0) {
+    info->num_conversions += sscanf(tok->ptr, info->fmt, info->target);
+  }
+}
+
+static void json_scanf_cb_bool(void *callback_data, const char *path,
+                               const struct json_token *tok) {
+  struct json_scanf_info *info = (struct json_scanf_info *) callback_data;
+  if (strcmp(path, info->path) == 0) {
+    info->num_conversions++;
+    *(int *) info->target = (tok->type == JSON_TYPE_TRUE ? 1 : 0);
+  }
+}
+
+static void json_scanf_cb_str(void *callback_data, const char *path,
+                              const struct json_token *tok) {
+  struct json_scanf_info *info = (struct json_scanf_info *) callback_data;
+  if (strcmp(path, info->path) == 0) {
+    info->num_conversions++;
+    /* TODO(lsm): un-escape string */
+    *(char **) info->target = (char *) malloc(tok->len + 1);
+    if (*(char **) info->target != NULL) {
+      strncpy(*(char **) info->target, tok->ptr, tok->len);
+    }
+  }
+}
+
+static void json_scanf_cb_func(void *callback_data, const char *path,
+                               const struct json_token *tok) {
+  struct json_scanf_info *info = (struct json_scanf_info *) callback_data;
+  if (strcmp(path, info->path) == 0) {
+    union {
+      void *p;
+      json_scanner_t f;
+    } u = {info->target};
+    info->num_conversions++;
+    u.f(tok->ptr, tok->len, info->user_data);
+  }
+}
+
+int json_vscanf(const char *s, int len, const char *fmt, va_list ap) {
+  char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20];
+  int i = 0;
+  char *p = NULL;
+  struct json_scanf_info info = {0, path, fmtbuf, NULL, NULL};
+
+  while (fmt[i] != '\0') {
+    if (fmt[i] == '{') {
+      strcat(path, ".");
+      i++;
+    } else if (fmt[i] == '}') {
+      if ((p = strrchr(path, '.')) != NULL) *p = '\0';
+      i++;
+    } else if (fmt[i] == '%') {
+      info.target = va_arg(ap, void *);
+      switch (fmt[i + 1]) {
+        case 'B':
+          json_parse(s, len, json_scanf_cb_bool, &info);
+          i += 2;
+          break;
+        case 'Q':
+          json_parse(s, len, json_scanf_cb_str, &info);
+          i += 2;
+          break;
+        case 'M':
+          info.user_data = va_arg(ap, void *);
+          json_parse(s, len, json_scanf_cb_func, &info);
+          i += 2;
+          break;
+        default: {
+          const char *delims = ", \t\r\n]}";
+          int conv_len = strcspn(fmt + i + 1, delims) + 1;
+          snprintf(fmtbuf, sizeof(fmtbuf), "%.*s", conv_len, fmt + i);
+          json_parse(s, len, json_scanf_cb, &info);
+          i += conv_len;
+          i += strspn(fmt + i, delims);
+          break;
+        }
+      }
+    } else if (is_alpha(fmt[i])) {
+      const char *delims = ": \r\n\t";
+      int key_len = strcspn(&fmt[i], delims);
+      if ((p = strrchr(path, '.')) != NULL) p[1] = '\0';
+      sprintf(path + strlen(path), "%.*s", key_len, &fmt[i]);
+      i += key_len + strspn(fmt + i + key_len, delims);
+    } else {
+      i++;
+    }
+  }
+  return info.num_conversions;
+}
+
+int json_scanf(const char *str, int len, const char *fmt, ...) {
+  int result;
+  va_list ap;
+  va_start(ap, fmt);
+  result = json_vscanf(str, len, fmt, ap);
+  va_end(ap);
+  return result;
+}

+ 31 - 0
frozen.h

@@ -50,6 +50,7 @@ struct json_token {
 #define JSON_STRING_INVALID -1
 #define JSON_STRING_INVALID -1
 #define JSON_STRING_INCOMPLETE -2
 #define JSON_STRING_INCOMPLETE -2
 #define JSON_TOKEN_ARRAY_TOO_SMALL -3
 #define JSON_TOKEN_ARRAY_TOO_SMALL -3
+#define JSON_INVALID_FORMAT_STRING -4
 
 
 int parse_json(const char *json_string, int json_string_length,
 int parse_json(const char *json_string, int json_string_length,
                struct json_token *tokens_array, int size_of_tokens_array);
                struct json_token *tokens_array, int size_of_tokens_array);
@@ -124,6 +125,36 @@ int json_vprintf(struct json_out *, const char *fmt, va_list ap);
  */
  */
 int json_printf_array(struct json_out *, va_list *ap);
 int json_printf_array(struct json_out *, va_list *ap);
 
 
+/*
+ * Scan JSON string `str`, performing scanf-like conversions according to `fmt`.
+ * This is a `scanf()` - like function, with following differences:
+ *
+ * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}"
+ * 2. Order of keys in an object is irrelevant.
+ * 3. Several extra format specifiers are supported:
+ *    - %B: consumes `int *`, expects boolean `true` or `false`.
+ *    - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned
+ *       string is malloc-ed, caller must free() the string.
+ *    - %M: consumes custom scanning function pointer and
+ *       `void *user_data` parameter - see json_scanner_t definition.
+ *
+ * Return number of elements successfully scanned & converted.
+ * Negative number means scan error.
+ */
+int json_scanf(const char *str, int str_len, const char *fmt, ...);
+int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap);
+
+/* json_scanf's %M handler  */
+typedef int (*json_scanner_t)(const char *str, int len, void *user_data);
+
+/*
+ * Helper function to scan array item with given path and index.
+ * Fills `token` with the matched JSON token.
+ * Return 0 if no array element found, otherwise non-0.
+ */
+int json_scanf_array_elem(const char *s, int len, const char *path, int index,
+                          struct json_token *token);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif /* __cplusplus */
 #endif /* __cplusplus */

+ 32 - 3
unit_test.c

@@ -310,7 +310,6 @@ static const char *test_json_printf(void) {
     const char *result = "{\"a\": [-1, 777], \"b\": 37}";
     const char *result = "{\"a\": [-1, 777], \"b\": 37}";
     json_printf(&out, "{a: %M, b: %d}", json_printf_array, arr, sizeof(arr),
     json_printf(&out, "{a: %M, b: %d}", json_printf_array, arr, sizeof(arr),
                 sizeof(arr[0]), "%hd", 37);
                 sizeof(arr[0]), "%hd", 37);
-    printf("==> [%s]\n", buf);
     ASSERT(strcmp(buf, result) == 0);
     ASSERT(strcmp(buf, result) == 0);
   }
   }
 
 
@@ -318,7 +317,6 @@ static const char *test_json_printf(void) {
     struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
     struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
     const char *result = "{\"a\": \"\\\"\\\\\\r\\nя\\t\\u0002\"}";
     const char *result = "{\"a\": \"\\\"\\\\\\r\\nя\\t\\u0002\"}";
     json_printf(&out, "{a: %Q}", "\"\\\r\nя\t\x02");
     json_printf(&out, "{a: %Q}", "\"\\\r\nя\t\x02");
-    printf("==> [%s]\n", buf);
     ASSERT(strcmp(buf, result) == 0);
     ASSERT(strcmp(buf, result) == 0);
   }
   }
 
 
@@ -372,7 +370,7 @@ static void cb(void *data, const char *path, const struct json_token *token) {
 static const char *test_callback_api() {
 static const char *test_callback_api() {
   const char *s = "{\"c\":[{\"a\":9,\"b\":\"x\"}]}";
   const char *s = "{\"c\":[{\"a\":9,\"b\":\"x\"}]}";
   const char *result =
   const char *result =
-      "2->.c.a[9] 1->.c.b[x] 3->.c[{\"a\":9,\"b\":\"x\"}] "
+      "2->.c[0].a[9] 1->.c[0].b[x] 3->.c[0][{\"a\":9,\"b\":\"x\"}] "
       "7->.c[[{\"a\":9,\"b\":\"x\"}]] 3->[{\"c\":[{\"a\":9,\"b\":\"x\"}]}] ";
       "7->.c[[{\"a\":9,\"b\":\"x\"}]] 3->[{\"c\":[{\"a\":9,\"b\":\"x\"}]}] ";
   char buf[200] = "";
   char buf[200] = "";
   ASSERT(json_parse(s, strlen(s), cb, buf) == (int) strlen(s));
   ASSERT(json_parse(s, strlen(s), cb, buf) == (int) strlen(s));
@@ -380,7 +378,38 @@ static const char *test_callback_api() {
   return NULL;
   return NULL;
 }
 }
 
 
+static int scan_array(const char *str, int len, void *user_data) {
+  struct json_token t;
+  int i;
+  char *buf = (char *) user_data;
+  printf("Parsing array: %.*s\n", len, str);
+  for (i = 0; json_scanf_array_elem(str, len, ".x", i, &t) > 0; i++) {
+    sprintf(buf + strlen(buf), "%d[%.*s] ", i, t.len, t.ptr);
+  }
+  return 0;
+}
+
+static const char *test_scanf(void) {
+  char buf[100] = "";
+  int a = 0, b = 0;
+  char *d = NULL;
+  const char *str =
+      "{ a: 1234, b : true, \"c\": {x: [17, 78, -20]}, d: \"hi%20there\" }";
+
+  ASSERT(json_scanf(str, strlen(str), "{a: %d, b: %B, c: [%M], d: %Q}", &a, &b,
+                    &scan_array, buf, &d) == 4);
+  ASSERT(a == 1234);
+  ASSERT(b == 1);
+  ASSERT(strcmp(buf, "0[17] 1[78] 2[-20] ") == 0);
+  ASSERT(d != NULL);
+  ASSERT(strcmp(d, "hi%20there") == 0);
+  free(d);
+
+  return NULL;
+}
+
 static const char *run_all_tests(void) {
 static const char *run_all_tests(void) {
+  RUN_TEST(test_scanf);
   RUN_TEST(test_errors);
   RUN_TEST(test_errors);
   RUN_TEST(test_config);
   RUN_TEST(test_config);
   RUN_TEST(test_nested);
   RUN_TEST(test_nested);