diff --git a/.gitmodules b/.gitmodules index f8eb19b2..190c9319 100644 --- a/.gitmodules +++ b/.gitmodules @@ -125,3 +125,6 @@ [submodule "thirdparty/facil.io"] path = thirdparty/facil.io url = https://github.com/boazsegev/facil.io.git +[submodule "thirdparty/centijson"] + path = thirdparty/centijson + url = https://github.com/mity/centijson diff --git a/src/cjsonlibs/centijson_all.c b/src/cjsonlibs/centijson_all.c new file mode 100644 index 00000000..01d5de8f --- /dev/null +++ b/src/cjsonlibs/centijson_all.c @@ -0,0 +1,6 @@ +#include "../memorystat.h" +#include "./centijson_renaming.h" + +#include "centijson/src/json.c" +#include "centijson/src/json-dom.c" +#include "centijson/src/value.c" diff --git a/src/cjsonlibs/centijson_renaming.h b/src/cjsonlibs/centijson_renaming.h new file mode 100644 index 00000000..ff87d490 --- /dev/null +++ b/src/cjsonlibs/centijson_renaming.h @@ -0,0 +1,156 @@ +#pragma once + +// Renaming to prevent using same name in other C libraries + +// centijson/src/json.h: +#define JSON_NULL CENTIJSON_NULL +#define JSON_FALSE CENTIJSON_FALSE +#define JSON_TRUE CENTIJSON_TRUE +#define JSON_NUMBER CENTIJSON_NUMBER +#define JSON_STRING CENTIJSON_STRING +#define JSON_KEY CENTIJSON_KEY +#define JSON_ARRAY_BEG CENTIJSON_ARRAY_BEG +#define JSON_ARRAY_END CENTIJSON_ARRAY_END +#define JSON_OBJECT_BEG CENTIJSON_OBJECT_BEG +#define JSON_OBJECT_END CENTIJSON_OBJECT_END +#define JSON_TYPE CENTIJSON_TYPE +#define JSON_ERR_SUCCESS CENTIJSON_ERR_SUCCESS +#define JSON_ERR_INTERNAL CENTIJSON_ERR_INTERNAL +#define JSON_ERR_OUTOFMEMORY CENTIJSON_ERR_OUTOFMEMORY +#define JSON_ERR_SYNTAX CENTIJSON_ERR_SYNTAX +#define JSON_ERR_BADCLOSER CENTIJSON_ERR_BADCLOSER +#define JSON_ERR_BADROOTTYPE CENTIJSON_ERR_BADROOTTYPE +#define JSON_ERR_EXPECTEDVALUE CENTIJSON_ERR_EXPECTEDVALUE +#define JSON_ERR_EXPECTEDKEY CENTIJSON_ERR_EXPECTEDKEY +#define JSON_ERR_EXPECTEDVALUEORCLOSER CENTIJSON_ERR_EXPECTEDVALUEORCLOSER +#define JSON_ERR_EXPECTEDKEYORCLOSER CENTIJSON_ERR_EXPECTEDKEYORCLOSER +#define JSON_ERR_EXPECTEDCOLON CENTIJSON_ERR_EXPECTEDCOLON +#define JSON_ERR_EXPECTEDCOMMAORCLOSER CENTIJSON_ERR_EXPECTEDCOMMAORCLOSER +#define JSON_ERR_EXPECTEDEOF CENTIJSON_ERR_EXPECTEDEOF +#define JSON_ERR_MAXTOTALLEN CENTIJSON_ERR_MAXTOTALLEN +#define JSON_ERR_MAXTOTALVALUES CENTIJSON_ERR_MAXTOTALVALUES +#define JSON_ERR_MAXNESTINGLEVEL CENTIJSON_ERR_MAXNESTINGLEVEL +#define JSON_ERR_MAXNUMBERLEN CENTIJSON_ERR_MAXNUMBERLEN +#define JSON_ERR_MAXSTRINGLEN CENTIJSON_ERR_MAXSTRINGLEN +#define JSON_ERR_MAXKEYLEN CENTIJSON_ERR_MAXKEYLEN +#define JSON_ERR_UNCLOSEDSTRING CENTIJSON_ERR_UNCLOSEDSTRING +#define JSON_ERR_UNESCAPEDCONTROL CENTIJSON_ERR_UNESCAPEDCONTROL +#define JSON_ERR_INVALIDESCAPE CENTIJSON_ERR_INVALIDESCAPE +#define JSON_ERR_INVALIDUTF8 CENTIJSON_ERR_INVALIDUTF8 +#define JSON_NONULLASROOT CENTIJSON_NONULLASROOT +#define JSON_NOBOOLASROOT CENTIJSON_NOBOOLASROOT +#define JSON_NONUMBERASROOT CENTIJSON_NONUMBERASROOT +#define JSON_NOSTRINGASROOT CENTIJSON_NOSTRINGASROOT +#define JSON_NOARRAYASROOT CENTIJSON_NOARRAYASROOT +#define JSON_NOOBJECTASROOT CENTIJSON_NOOBJECTASROOT +#define JSON_NOSCALARROOT CENTIJSON_NOSCALARROOT +#define JSON_NOVECTORROOT CENTIJSON_NOVECTORROOT +#define JSON_IGNOREILLUTF8KEY CENTIJSON_IGNOREILLUTF8KEY +#define JSON_FIXILLUTF8KEY CENTIJSON_IGNOREILLUTF8KEY +#define JSON_IGNOREILLUTF8VALUE CENTIJSON_IGNOREILLUTF8VALUE +#define JSON_FIXILLUTF8VALUE CENTIJSON_FIXILLUTF8VALUE +#define JSON_CONFIG CENTIJSON_CONFIG +#define JSON_INPUT_POS CENTIJSON_INPUT_POS +#define JSON_CALLBACKS CENTIJSON_CALLBACKS +#define JSON_PARSER CENTIJSON_PARSER +#define json_init centijson_init +#define json_feed centijson_feed +#define json_fini centijson_fini +#define json_parse centijson_parse +#define json_analyze_number centijson_analyze_number +#define json_number_to_int32 centijson_number_to_int32 +#define json_number_to_uint32 centijson_number_to_uint32 +#define json_number_to_int64 centijson_number_to_int64 +#define json_number_to_uint64 centijson_number_to_uint64 +#define json_number_to_double centijson_number_to_double +#define json_dump_int32 centijson_dump_int32 +#define json_dump_uint32 centijson_dump_uint32 +#define json_dump_int64 centijson_dump_int64 +#define json_dump_uint64 centijson_dump_uint64 +#define json_dump_double centijson_dump_double +#define json_dump_string centijson_dump_string + +// centijson/src/json-dom.h: +#define JSON_DOM_ERR_DUPKEY CENTIJSON_DOM_ERR_DUPKEY +#define JSON_DOM_DUPKEY_ABORT CENTIJSON_DOM_DUPKEY_ABORT +#define JSON_DOM_DUPKEY_USEFIRST CENTIJSON_DOM_DUPKEY_USEFIRST +#define JSON_DOM_DUPKEY_USELAST CENTIJSON_DOM_DUPKEY_USELAST +#define JSON_DOM_DUPKEY_MASK CENTIJSON_DOM_DUPKEY_MASK +#define JSON_DOM_MAINTAINDICTORDER CENTIJSON_DOM_MAINTAINDICTORDER +#define json_dom_init centijson_dom_init +#define json_dom_feed centijson_dom_feed +#define json_dom_fini centijson_dom_fini +#define json_dom_parse centijson_dom_parse +#define JSON_DOM_DUMP_MINIMIZE CENTIJSON_DOM_DUMP_MINIMIZE +#define JSON_DOM_DUMP_FORCECLRF CENTIJSON_DOM_DUMP_FORCECLRF +#define JSON_DOM_DUMP_INDENTWITHSPACES CENTIJSON_DOM_DUMP_INDENTWITHSPACES +#define JSON_DOM_DUMP_PREFERDICTORDER CENTIJSON_DOM_DUMP_PREFERDICTORDER +#define json_dom_dump centijson_dom_dump + +// centijson/src/value.h: +#define VALUE CENTIVALUE +#define VALUE_TYPE CENTIVALUE_TYPE +#define VALUE_NULL CENTIVALUE_NULL +#define VALUE_BOOL CENTIVALUE_BOOL +#define VALUE_INT32 CENTIVALUE_INT32 +#define VALUE_UINT32 CENTIVALUE_UINT32 +#define VALUE_INT64 CENTIVALUE_INT64 +#define VALUE_UINT64 CENTIVALUE_UINT64 +#define VALUE_FLOAT CENTIVALUE_FLOAT +#define VALUE_DOUBLE CENTIVALUE_DOUBLE +#define VALUE_STRING CENTIVALUE_STRING +#define VALUE_ARRAY CENTIVALUE_ARRAY +#define VALUE_DICT CENTIVALUE_DICT +#define value_fini centivalue_fini +#define value_type centivalue_type +#define value_is_compatible centivalue_is_compatible +#define value_is_new centivalue_is_new +#define value_path centivalue_path +#define value_build_path centivalue_build_path +#define VALUE_NULL_INITIALIZER CENTIVALUE_NULL_INITIALIZER +#define value_init_null centivalue_init_null +#define value_init_bool centivalue_init_bool +#define value_bool centivalue_bool +#define value_init_int32 centivalue_init_int32 +#define value_init_uint32 centivalue_init_uint32 +#define value_init_int64 centivalue_init_int64 +#define value_init_uint64 centivalue_init_uint64 +#define value_init_float centivalue_init_float +#define value_init_double centivalue_init_double +#define value_int32 centivalue_int32 +#define value_uint32 centivalue_uint32 +#define value_int64 centivalue_int64 +#define value_uint64 centivalue_uint64 +#define value_float centivalue_float +#define value_double centivalue_double +#define value_init_string_ centivalue_init_string_ +#define value_init_string centivalue_init_string +#define value_string centivalue_string +#define value_string_length centivalue_string_length +#define value_init_array centivalue_init_array +#define value_array_size centivalue_array_size +#define value_array_get centivalue_array_get +#define value_array_get_all centivalue_array_get_all +#define value_array_append centivalue_array_append +#define value_array_insert centivalue_array_insert +#define value_array_remove centivalue_array_remove +#define value_array_remove_range centivalue_array_remove_range +#define value_array_clean centivalue_array_clean +#define VALUE_DICT_MAINTAINORDER CENTIVALUE_DICT_MAINTAINORDER +#define value_init_dict centivalue_init_dict +#define value_init_dict_ex centivalue_init_dict_ex +#define value_dict_flags centivalue_dict_flags +#define value_dict_size centivalue_dict_size +#define value_dict_keys_sorted centivalue_dict_keys_sorted +#define value_dict_keys_ordered centivalue_dict_keys_ordered +#define value_dict_get_ centivalue_dict_get_ +#define value_dict_get centivalue_dict_get +#define value_dict_add_ centivalue_dict_add_ +#define value_dict_add centivalue_dict_add +#define value_dict_get_or_add_ centivalue_dict_get_or_add_ +#define value_dict_get_or_add centivalue_dict_get_or_add +#define value_dict_remove_ centivalue_dict_remove_ +#define value_dict_remove centivalue_dict_remove +#define value_dict_walk_ordered centivalue_dict_walk_ordered +#define value_dict_walk_sorted centivalue_dict_walk_sorted +#define value_dict_clean centivalue_dict_clean diff --git a/src/tests/centijsontest.cpp b/src/tests/centijsontest.cpp new file mode 100644 index 00000000..23469755 --- /dev/null +++ b/src/tests/centijsontest.cpp @@ -0,0 +1,349 @@ +#include "../test.h" +#include "../cjsonlibs/centijson_renaming.h" +#include "centijson/src/json.h" +#include "centijson/src/json-dom.h" +#include "centijson/src/value.h" + +#include + +static void GenStat(Stat* s, VALUE* v) { + switch (value_type(v)) { + case VALUE_DICT: + { + size_t count = value_dict_size(v); + + const VALUE* keys[count]; + value_dict_keys_sorted(v, keys, count); + + for (size_t i = 0; i < count; i++) { + const char* name = value_string(keys[i]); + GenStat(s, value_dict_get(v, name)); + s->stringLength += value_string_length(keys[i]); + } + s->objectCount++; + s->memberCount += count; + s->stringCount += count; + } + break; + + case VALUE_ARRAY: + { + size_t count = value_array_size(v); + for (size_t i = 0; i < count; i++) + GenStat(s, value_array_get(v, i)); + s->arrayCount++; + s->elementCount += count; + } + break; + + case VALUE_STRING: + s->stringCount++; + s->stringLength += value_string_length(v); + break; + + case VALUE_INT32: + case VALUE_UINT32: + case VALUE_INT64: + case VALUE_UINT64: + case VALUE_FLOAT: + case VALUE_DOUBLE: + s->numberCount++; + break; + + case VALUE_BOOL: + if (value_bool(v)) + s->trueCount++; + else + s->falseCount++; + break; + + case VALUE_NULL: + s->nullCount++; + break; + } +} + +class CentijsonParseResult : public ParseResultBase { +public: + CentijsonParseResult() { value_init_null(&root); } + ~CentijsonParseResult() { value_fini(&root); } + + VALUE root; +}; + +class CentijsonStringResult : public StringResultBase { +public: + CentijsonStringResult() {} + ~CentijsonStringResult() {} + virtual const char* c_str() const { return buffer.data(); } + + std::vector buffer; + + void append(const char* str, size_t size) + { buffer.insert(buffer.end(), str, str + size); } + void finish() + { buffer.insert(buffer.end(), '\0'); } +}; + +static int +write_callback(const char* str, size_t size, void* userdata) +{ + CentijsonStringResult* sr = (CentijsonStringResult*) userdata; + sr->append(str, size); + return 0; +} + + +/* Config to disable all limitations. */ +static const JSON_CONFIG cfg = { + 0, /* max_total_len */ + 0, /* max_total_values */ + 0, /* max_number_len */ + 0, /* max_string_len */ + 0, /* max_key_len */ + 0, /* max_nesting_level */ + 0 /* flags */ +}; + + +class CentijsonTest : public TestBase { +public: + CentijsonTest() { + } +#if TEST_INFO + virtual const char* GetName() const { return "Centijson (C)"; } + virtual const char* GetFilename() const { return __FILE__; } +#endif + +#if TEST_PARSE + virtual ParseResultBase* Parse(const char* json, size_t length) const { + CentijsonParseResult* pr = new CentijsonParseResult; + int err = json_dom_parse(json, length, &cfg, 0, &pr->root, NULL); + if (err != 0) { + delete pr; + return 0; + } + return pr; + } +#endif + +#if TEST_STRINGIFY + virtual StringResultBase* Stringify(const ParseResultBase* parseResult) const { + const CentijsonParseResult* pr = static_cast(parseResult); + CentijsonStringResult* sr = new CentijsonStringResult; + json_dom_dump(&pr->root, write_callback, sr, 0, JSON_DOM_DUMP_MINIMIZE); + sr->finish(); + return sr; + } +#endif + +#if TEST_PRETTIFY + virtual StringResultBase* Prettify(const ParseResultBase* parseResult) const { + const CentijsonParseResult* pr = static_cast(parseResult); + CentijsonStringResult* sr = new CentijsonStringResult; + json_dom_dump(&pr->root, write_callback, sr, 0, 0); + sr->finish(); + return sr; + } +#endif + +#if TEST_STATISTICS + virtual bool Statistics(const ParseResultBase* parseResult, Stat* stat) const { + const CentijsonParseResult* pr = static_cast(parseResult); + memset(stat, 0, sizeof(Stat)); + GenStat(stat, (VALUE*) &pr->root); + return true; + } +#endif + +#if TEST_SAXROUNDTRIP + struct SaxContext { + CentijsonStringResult* sr; + std::vector stack; + bool beginFlag; + }; + + static int + sax_process_callback(JSON_TYPE type, const char* data, size_t size, void* userdata) + { + SaxContext* ctx = (SaxContext*) userdata; + CentijsonStringResult* sr = ctx->sr; + + if(!ctx->beginFlag && + ((ctx->stack.back() == JSON_ARRAY_BEG && type != JSON_ARRAY_END && type != JSON_OBJECT_END) || + (ctx->stack.back() == JSON_OBJECT_BEG && type == JSON_KEY))) + { + write_callback(",", 1, sr); + } + + switch(type) { + case JSON_NULL: + write_callback("null", 4, sr); + break; + + case JSON_FALSE: + write_callback("false", 5, sr); + break; + + case JSON_TRUE: + write_callback("true", 4, sr); + break; + + case JSON_NUMBER: + write_callback(data, size, sr); + break; + + case JSON_STRING: + case JSON_KEY: + json_dump_string(data, size, write_callback, sr); + if(type == JSON_KEY) + write_callback(":", 1, sr); + break; + + case JSON_ARRAY_BEG: + case JSON_OBJECT_BEG: + write_callback(type == JSON_ARRAY_BEG ? "[" : "{", 1, sr); + ctx->stack.push_back(type); + break; + + case JSON_ARRAY_END: + case JSON_OBJECT_END: + write_callback(type == JSON_ARRAY_END ? "]" : "}", 1, sr); + ctx->stack.pop_back(); + break; + } + + ctx->beginFlag = (type == JSON_ARRAY_BEG || type == JSON_OBJECT_BEG); + + return 0; + } + + virtual StringResultBase* SaxRoundtrip(const char* json, size_t length) const { + static const JSON_CALLBACKS sax_callbacks = { + sax_process_callback + }; + + SaxContext ctx; + ctx.sr = new CentijsonStringResult; + ctx.beginFlag = true; + int err = json_parse(json, length, &sax_callbacks, &cfg, &ctx, NULL); + if (err != 0) { + delete ctx.sr; + return 0; + } + ctx.sr->finish(); + return ctx.sr; + } +#endif + +#if TEST_SAXSTATISTICS + struct SaxStatContext { + Stat* stat; + std::vector stack; + }; + + static int + saxstat_process_callback(JSON_TYPE type, const char* data, size_t size, void* userdata) + { + (void) data; // unused variable + + SaxStatContext* ctx = (SaxStatContext*) userdata; + Stat* s = ctx->stat; + + if(!ctx->stack.empty()) { + if(ctx->stack.back() == JSON_ARRAY_BEG && type != JSON_ARRAY_END) + s->elementCount++; + if(ctx->stack.back() == JSON_OBJECT_BEG && type != JSON_OBJECT_END && type != JSON_KEY) + s->memberCount++; + } + + switch(type) { + case JSON_NULL: + s->nullCount++; + break; + + case JSON_FALSE: + s->falseCount++; + break; + + case JSON_TRUE: + s->trueCount++; + break; + + case JSON_NUMBER: + s->numberCount++; + break; + + case JSON_STRING: + case JSON_KEY: + s->stringCount++; + s->stringLength += size; + break; + + case JSON_ARRAY_BEG: + case JSON_OBJECT_BEG: + ctx->stack.push_back(type); + if(type == JSON_ARRAY_BEG) + s->arrayCount++; + else + s->objectCount++; + break; + + case JSON_ARRAY_END: + case JSON_OBJECT_END: + ctx->stack.pop_back(); + break; + } + + return 0; + } + + virtual bool SaxStatistics(const char* json, size_t length, Stat* stat) const { + static const JSON_CALLBACKS sax_callbacks = { + saxstat_process_callback + }; + + SaxStatContext ctx; + ctx.stat = stat; + memset(ctx.stat, 0, sizeof(Stat)); + + int err = json_parse(json, length, &sax_callbacks, &cfg, &ctx, NULL); + return (err == 0); + } +#endif + +#if TEST_CONFORMANCE + virtual bool ParseDouble(const char* json, double* d) const { + CentijsonParseResult pr; + int err = json_dom_parse(json, strlen(json), &cfg, 0, &pr.root, NULL); + if (err == 0 && + value_type(&pr.root) == VALUE_ARRAY && + value_array_size(&pr.root) == 1 && + value_is_compatible(value_array_get(&pr.root, 0), VALUE_DOUBLE)) + { + *d = value_double(value_array_get(&pr.root, 0)); + return true; + } + else + return false; + } + + virtual bool ParseString(const char* json, std::string& s) const { + CentijsonParseResult pr; + int err = json_dom_parse(json, strlen(json), &cfg, 0, &pr.root, NULL); + if (err == 0 && + value_type(&pr.root) == VALUE_ARRAY && + value_array_size(&pr.root) == 1 && + value_is_compatible(value_array_get(&pr.root, 0), VALUE_STRING)) + { + const VALUE* str = value_array_get(&pr.root, 0); + s = std::string(value_string(str), value_string_length(str)); + return true; + } + else + return false; + } +#endif +}; + +REGISTER_TEST(CentijsonTest); diff --git a/thirdparty/centijson b/thirdparty/centijson new file mode 160000 index 00000000..ef0f806f --- /dev/null +++ b/thirdparty/centijson @@ -0,0 +1 @@ +Subproject commit ef0f806fc0d58fb91bbd4fa024fd0f146cd6eb05