From e48ff3f92d4cbc542e865518c7b0843fc5ccd54e Mon Sep 17 00:00:00 2001 From: TimePath Date: Tue, 8 Mar 2016 21:59:34 +1100 Subject: [PATCH] Basic JSON parser --- qcsrc/lib/_all.inc | 1 + qcsrc/lib/iter.qh | 5 ++ qcsrc/lib/json.qc | 186 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 qcsrc/lib/json.qc diff --git a/qcsrc/lib/_all.inc b/qcsrc/lib/_all.inc index 0fee57ec1..fb7122fd8 100644 --- a/qcsrc/lib/_all.inc +++ b/qcsrc/lib/_all.inc @@ -72,6 +72,7 @@ void isnt_bool( float this) { print(ftos(this)); } #include "functional.qh" #include "i18n.qh" #include "iter.qh" +#include "json.qc" #include "lazy.qh" #include "linkedlist.qh" #include "log.qh" diff --git a/qcsrc/lib/iter.qh b/qcsrc/lib/iter.qh index fae834b49..c21d02121 100644 --- a/qcsrc/lib/iter.qh +++ b/qcsrc/lib/iter.qh @@ -55,6 +55,11 @@ } MACRO_END #define STRING_ITERATOR_GET(this) str2chr(this##_s, this##_i++) +#define STRING_ITERATOR_PEEK(this) str2chr(this##_s, this##_i) +#define STRING_ITERATOR_NEXT(this) MACRO_BEGIN ++this##_i; MACRO_END +#define STRING_ITERATOR_UNGET(this) MACRO_BEGIN --this##_i; MACRO_END +#define STRING_ITERATOR_SAVE(this) this##_i +#define STRING_ITERATOR_LOAD(this, n) MACRO_BEGIN this##_i = n; MACRO_END #define FOREACH_CHAR(s, cond, body) \ MACRO_BEGIN \ diff --git a/qcsrc/lib/json.qc b/qcsrc/lib/json.qc new file mode 100644 index 000000000..0f2f44c4e --- /dev/null +++ b/qcsrc/lib/json.qc @@ -0,0 +1,186 @@ +#include "test.qh" + +STRING_ITERATOR(_json, string_null, 0); + +/** parse a json object */ +bool _json_parse_object(); + bool _json_parse_members(); + bool _json_parse_pair(); +bool _json_parse_array(); + bool _json_parse_elements(); +bool _json_parse_value(); + bool _json_parse_true(); + bool _json_parse_false(); + bool _json_parse_null(); +bool _json_parse_string(); +bool _json_parse_number(); + bool _json_parse_int(); + +#define JSON_BEGIN() int __i = STRING_ITERATOR_SAVE(_json) +#define JSON_FAIL(reason) goto fail +#define JSON_END() \ + return true; \ +:fail \ + STRING_ITERATOR_LOAD(_json, __i); \ + return false; + +bool _json_parse_object() { + JSON_BEGIN(); + if (STRING_ITERATOR_GET(_json) != '{') JSON_FAIL("expected '{'"); + LOG_INFO(">> object\n"); + _json_parse_members(); + if (STRING_ITERATOR_GET(_json) != '}') JSON_FAIL("expected '}'"); + LOG_INFO("<< object\n"); + JSON_END(); +} + + bool _json_parse_members() { + JSON_BEGIN(); + if (!_json_parse_pair()) JSON_FAIL("expected pair"); + if (STRING_ITERATOR_PEEK(_json) == ',') { + STRING_ITERATOR_NEXT(_json); + if (!_json_parse_members()) JSON_FAIL("expected pair"); + } + JSON_END(); + } + + bool _json_parse_pair() { + JSON_BEGIN(); + if (!_json_parse_string()) JSON_FAIL("expected string"); + if (STRING_ITERATOR_GET(_json) != ':') JSON_FAIL("expected ':'"); + if (!_json_parse_value()) JSON_FAIL("expected value"); + JSON_END(); + } + +bool _json_parse_array() { + JSON_BEGIN(); + if (STRING_ITERATOR_GET(_json) != '[') JSON_FAIL("expected '['"); + LOG_INFO(">> array\n"); + _json_parse_elements(); + if (STRING_ITERATOR_GET(_json) != ']') JSON_FAIL("expected ']'"); + LOG_INFO("<< array\n"); + JSON_END(); +} + + bool _json_parse_elements() { + JSON_BEGIN(); + if (!_json_parse_value()) JSON_FAIL("expected value"); + if (STRING_ITERATOR_PEEK(_json) == ',') { + STRING_ITERATOR_NEXT(_json); + if (!_json_parse_elements()) JSON_FAIL("expected value"); + } + JSON_END(); + } + +bool _json_parse_value() { + JSON_BEGIN(); + if (!(_json_parse_string() + || _json_parse_number() + || _json_parse_object() + || _json_parse_array() + || _json_parse_true() + || _json_parse_false() + || _json_parse_null())) JSON_FAIL("expected value"); + JSON_END(); +} + + bool _json_parse_true() { + JSON_BEGIN(); + if (!(STRING_ITERATOR_GET(_json) == 't' + && STRING_ITERATOR_GET(_json) == 'r' + && STRING_ITERATOR_GET(_json) == 'u' + && STRING_ITERATOR_GET(_json) == 'e')) + JSON_FAIL("expected 'true'"); + LOG_INFO(">> bool (true)\n"); + JSON_END(); + } + + bool _json_parse_false() { + JSON_BEGIN(); + if (!(STRING_ITERATOR_GET(_json) == 'f' + && STRING_ITERATOR_GET(_json) == 'a' + && STRING_ITERATOR_GET(_json) == 'l' + && STRING_ITERATOR_GET(_json) == 's' + && STRING_ITERATOR_GET(_json) == 'e')) + JSON_FAIL("expected 'false'"); + LOG_INFO(">> bool (false)\n"); + JSON_END(); + } + + bool _json_parse_null() { + JSON_BEGIN(); + if (!(STRING_ITERATOR_GET(_json) == 'n' + && STRING_ITERATOR_GET(_json) == 'u' + && STRING_ITERATOR_GET(_json) == 'l' + && STRING_ITERATOR_GET(_json) == 'l')) + JSON_FAIL("expected 'null'"); + LOG_INFO(">> null\n"); + JSON_END(); + } + +bool _json_parse_string() { + JSON_BEGIN(); + if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected opening '\"'"); + string s = ""; + for (int c; (c = STRING_ITERATOR_GET(_json)); ) { + if (c == '"') { + STRING_ITERATOR_UNGET(_json); + break; + } else if (c == '\\') { + string esc; + switch (STRING_ITERATOR_GET(_json)) { + default: + JSON_FAIL("expected ( '\"' | '\\' | 'n' | 't' )"); + case '"': esc = "\""; break; + case '\\': esc = "\\"; break; + case 'n': esc = "\n"; break; + case 't': esc = "\t"; break; + } + s = strcat(s, esc); + } else { + s = strcat(s, chr2str(c)); + } + } + if (STRING_ITERATOR_GET(_json) != '"') JSON_FAIL("expected closing '\"'"); + LOG_INFOF(">> string ('%s')\n", s); + JSON_END(); +} + +bool _json_parse_number() { + JSON_BEGIN(); + if (!_json_parse_int()) JSON_FAIL("expected int"); + JSON_END(); +} + + bool _json_parse_int() { + JSON_BEGIN(); + string s = ""; + for (int c; (c = STRING_ITERATOR_GET(_json)); ) { + if (!(c >= '0' && c <= '9')) { + STRING_ITERATOR_UNGET(_json); + break; + } + if (s == "" && c == '0') JSON_FAIL("expected [1-9]"); + s = strcat(s, chr2str(c)); + } + if (s == "") JSON_FAIL("expected int"); + int i = stof(s); + LOG_INFOF(">> int (%d)\n", i); + JSON_END(); + } + +bool json_parse(string in) { + // TODO: remove insignificant whitespace + STRING_ITERATOR_SET(_json, in, 0); + return _json_parse_object(); +} + +#undef JSON_BEGIN +#undef JSON_FAIL +#undef JSON_END + +TEST(json, Parse) +{ + EXPECT_EQ(true, json_parse("{\"string\":\"string\",\"int\":123,\"bool\":true,\"null\":null,\"obj\":{\"arr\":[1,2,3]}}")); + SUCCEED(); +} -- 2.39.2