Browse Source

Implemented object, pair, key, identifier, string

Sergey Lyubka 11 năm trước cách đây
mục cha
commit
357a75e4e4
3 tập tin đã thay đổi với 315 bổ sung0 xóa
  1. 167 0
      frozen.c
  2. 58 0
      frozen.h
  3. 90 0
      unit_test.c

+ 167 - 0
frozen.c

@@ -0,0 +1,167 @@
+// Copyright (c) 2004-2013 Sergey Lyubka <[email protected]>
+// Copyright (c) 2013 Cesanta Software Limited
+// All rights reserved
+//
+// This library is dual-licensed: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation. For the terms of this
+// license, see <http://www.gnu.org/licenses/>.
+//
+// You are free to use this library under the terms of the GNU General
+// Public License, but WITHOUT ANY WARRANTY; without even the implied
+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// Alternatively, you can license this library under a commercial
+// license, as set out in <http://cesanta.com/products.html>.
+
+#include "frozen.h"
+
+
+#include <stdio.h>  // TODO:remove this
+
+struct frozen {
+  const char *end;
+  const char *cur;
+  struct json_token *tokens;
+  int max_tokens;
+  int num_tokens;
+};
+
+static int parse_object(struct frozen *f);
+
+#define EXPECT(cond, err_code) do { if (!(cond)) return (err_code); } while (0)
+#define TRY(expr) do { int n = expr; if (n < 0) return n; } while (0)
+#define SKIP_SPACES(f) do { skip_whitespaces(f); \
+  if (f->cur >= f->end) return JSON_STRING_INCOMPLETE; } while (0)
+#define END_OF_STRING (-1)
+
+static int is_space(int ch) {
+  return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
+};
+
+static void skip_whitespaces(struct frozen *f) {
+  while (f->cur < f->end && is_space(*f->cur)) f->cur++;
+}
+
+static int cur(struct frozen *f) {
+  skip_whitespaces(f);
+  return f->cur >= f->end ? END_OF_STRING : * (unsigned char *) f->cur;
+}
+
+static int test_and_skip(struct frozen *f, int expected) {
+  int ch = cur(f);
+  if (ch == expected) { f->cur++; return 0; }
+  return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+}
+
+static int is_alpha(int ch) {
+  return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
+}
+
+static int is_digit(int ch) {
+  return ch >= '0' && ch <= '9';
+}
+
+static int is_hex_digit(int ch) {
+  return is_digit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
+}
+
+static int get_escape_len(const char *s, int len) {
+  switch (*s) {
+    case 'u':
+      return len < 4 ? JSON_STRING_INCOMPLETE :
+        is_hex_digit(s[0]) && is_hex_digit(s[1]) &&
+        is_hex_digit(s[2]) && is_hex_digit(s[3]) ? 4 : JSON_STRING_INVALID;
+    case '"': case '\\': case '/': case 'b':
+    case 'f': case 'n': case 'r': case 't':
+      return len < 2 ? JSON_STRING_INCOMPLETE : 2;
+    default:
+      return JSON_STRING_INVALID;
+  }
+}
+
+// identifier = letter { letter | digit | '_' }
+static int parse_identifier(struct frozen *f) {
+  //printf("%s 1 [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur);
+  EXPECT(is_alpha(cur(f)), JSON_STRING_INVALID);
+  while (f->cur < f->end &&
+         (*f->cur == '_' || is_alpha(*f->cur) || is_digit(*f->cur))) {
+    f->cur++;
+  }
+  return 0;
+}
+
+// string = '"' { quoted_printable_chars } '"'
+static int parse_string(struct frozen *f) {
+  int n, ch;
+  TRY(test_and_skip(f, '"'));
+  while (++f->cur < f->end) {
+    ch = cur(f);
+    EXPECT(ch > 32 && ch < 127, JSON_STRING_INVALID);
+    if (ch == '\\') {
+      EXPECT((n = get_escape_len(f->cur + 1, f->end - f->cur)) > 0, n);
+      f->cur += n;
+    } else if (ch == '"') {
+      break;
+    };
+  }
+  return 0;
+}
+
+// value = 'null' | 'true' | 'false' | number | string | array | object
+static int parse_value(struct frozen *f) {
+  int ch = cur(f);
+  if (ch == '"') {
+    TRY(parse_string(f));
+  } else if (ch == '{') {
+    TRY(parse_object(f));
+  } else {
+    return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+  }
+  return 0;
+}
+
+// key = identifier | string
+static int parse_key(struct frozen *f) {
+  int ch = cur(f);
+  //printf("%s 1 [%.*s]\n", __func__, (int) (f->end - f->cur), f->cur);
+  if (is_alpha(ch)) {
+    TRY(parse_identifier(f));
+  } else if (ch == '"') {
+    TRY(parse_string(f));
+  } else {
+    return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID;
+  }
+  return 0;
+}
+
+// pair = key ':' value
+static int parse_pair(struct frozen *f) {
+  TRY(parse_key(f));
+  TRY(test_and_skip(f, ':'));
+  TRY(parse_value(f));
+  return 0;
+}
+
+
+// object = '{' pair { ',' pair } '}'
+static int parse_object(struct frozen *f) {
+  TRY(test_and_skip(f, '{'));
+  while (cur(f) != '}') {
+    TRY(parse_pair(f));
+  }
+  TRY(test_and_skip(f, '}'));
+  return 0;
+}
+
+// number = [ '-' ] digit { digit }
+// array = '[' [ value { ',' value } ] ']'
+// json = object
+int parse_json(const char *s, int s_len, struct json_token *arr, int arr_len) {
+  struct frozen frozen = { s + s_len, s, arr, arr_len, 0 };
+  if (s == 0 || s_len < 0) return JSON_STRING_INVALID;
+  if (s_len == 0) return JSON_STRING_INCOMPLETE;
+  TRY(parse_object(&frozen));
+  return frozen.cur - s;
+}

+ 58 - 0
frozen.h

@@ -0,0 +1,58 @@
+// Copyright (c) 2004-2013 Sergey Lyubka <[email protected]>
+// Copyright (c) 2013 Cesanta Software Limited
+// All rights reserved
+//
+// This library is dual-licensed: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation. For the terms of this
+// license, see <http://www.gnu.org/licenses/>.
+//
+// You are free to use this library under the terms of the GNU General
+// Public License, but WITHOUT ANY WARRANTY; without even the implied
+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// Alternatively, you can license this library under a commercial
+// license, as set out in <http://cesanta.com/products.html>.
+
+#ifndef FROZEN_HEADER_INCLUDED
+#define FROZEN_HEADER_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+struct json_token {
+  const char *ptr;    // Points to the beginning of the token
+  int len;            // Token length
+  int num_children;   // Number of children for array and objects
+  int type;           // Type of the token, possible values below
+
+#define JSON_TYPE_EOF     0   // End of parsed tokens marker
+#define JSON_TYPE_STRING  1
+#define JSON_TYPE_NUMERIC 2
+#define JSON_TYPE_OBJECT  3
+#define JSON_TYPE_TRUE    4
+#define JSON_TYPE_FALSE   5
+#define JSON_TYPE_NULL    6
+};
+
+// Error codes
+#define JSON_STRING_INVALID           -1
+#define JSON_STRING_INCOMPLETE        -2
+#define JSON_TOKEN_ARRAY_TOO_SMALL    -3
+#define JSON_OUTPUT_BUFFER_TOO_SMALL  -4
+
+// Parse JSON string, store tokens in tokens_array. Last token has type
+// JSON_TYPE_EOF. Return offset in json string where parsing ended,
+// or negative error code on failure
+int parse_json(const char *json_string, int json_string_length,
+               struct json_token *tokens_array, int size_of_tokens_array);
+
+int find_json_token(const struct json_token *toks, int num_toks, const char *p);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // FROZEN_HEADER_INCLUDED

+ 90 - 0
unit_test.c

@@ -0,0 +1,90 @@
+// Copyright (c) 2004-2013 Sergey Lyubka <[email protected]>
+// Copyright (c) 2013 Cesanta Software Limited
+// All rights reserved
+//
+// This library is dual-licensed: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation. For the terms of this
+// license, see <http://www.gnu.org/licenses/>.
+//
+// You are free to use this library under the terms of the GNU General
+// Public License, but WITHOUT ANY WARRANTY; without even the implied
+// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU General Public License for more details.
+//
+// Alternatively, you can license this library under a commercial
+// license, as set out in <http://cesanta.com/products.html>.
+
+#include "frozen.c"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#define FAIL(str, line) do {                    \
+  printf("Fail on line %d: [%s]\n", line, str); \
+  return str;                                   \
+} while (0)
+
+#define ASSERT(expr) do {                       \
+  static_num_tests++;                           \
+  if (!(expr)) FAIL(#expr, __LINE__);           \
+} while (0)
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define RUN_TEST(test) do { const char *msg = test(); \
+  if (msg) return msg; } while (0)
+
+static int static_num_tests = 0;
+
+#if 0
+static int cmp_token(const struct json_token *tok, const char *str) {
+  return (int) strlen(str) == tok->len && memcmp(tok->ptr, str, tok->len) == 0;
+}
+#endif
+
+static const char *test_errors(void) {
+  struct json_token ar[100];
+  int size = ARRAY_SIZE(ar);
+  static const char *invalid_tests[] = {
+    "1", "a:3", "\x01", "{:", " { 1",
+    NULL
+  };
+  static const char *incomplete_tests[] = {
+    "", " \r\n\t", "{", " { a", "{a:", "{a:\"", " { a : \"xx",
+    NULL
+  };
+  static const struct { const char *str; int expected_len; } success_tests[] = {
+    { NULL, 0 }
+  };
+  int i;
+
+  ASSERT(parse_json(NULL, 0, NULL, 0) == JSON_STRING_INVALID);
+  for (i = 0; invalid_tests[i] != NULL; i++) {
+    ASSERT(parse_json(invalid_tests[i], strlen(invalid_tests[i]),
+                      ar, size) == JSON_STRING_INVALID);
+  }
+
+  for (i = 0; incomplete_tests[i] != NULL; i++) {
+    ASSERT(parse_json(incomplete_tests[i], strlen(incomplete_tests[i]),
+                      ar, size) == JSON_STRING_INCOMPLETE);
+  }
+
+  for (i = 0; success_tests[i].str != NULL; i++) {
+    ASSERT(parse_json(success_tests[i].str, strlen(success_tests[i].str),
+                      ar, size) == success_tests[i].expected_len);
+  }
+
+  return NULL;
+}
+
+static const char *run_all_tests(void) {
+  RUN_TEST(test_errors);
+  return NULL;
+}
+
+int main(void) {
+  const char *fail_msg = run_all_tests();
+  printf("%s, tests run: %d\n", fail_msg ? "FAIL" : "PASS", static_num_tests);
+  return fail_msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE;
+}