commit 5b6723f86f944bb6abeb5677be52036932297da7 Author: Andreev Gregory Date: Tue Jul 23 16:31:51 2024 +0300 Initial tested version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc8ccb4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Never use CMAKE in production +CMakeLists.txt +cmake-build-debug/ +# Output of build system +built/ +# This is a compilated build system script +building/main +building/*.png +building/*.svg + +.idea/ + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..f5fdeeb --- /dev/null +++ b/README.txt @@ -0,0 +1,60 @@ +### libjsonincpp + +Suckless C++ library for storing JSON and generating and parsing JSON strings + +### Examples + + +#include +#include + +void func(){ + json::JSON obj = json::JSON("Hello, World\n"); + assert(obj.isString()); + std::cout << obj.asString() << std::endl; +} + +void foo(){ + json::JSON obj = json::JSON(std::vector(JSON(12l), JSON(true))); + for (json::JSON& el: obj.asArray()){ + std::cout << json::generate_str(el, json::print_pretty) << std::endl; + } +} + + +### Pros + +libjsonincpp is non-recurrsive, out-of-memory-safe C++ library. + +Accessing a member of json object that does not exist (for example taking second element of true value, or taking element, corresponding to key "dict-key" of an array value) +does not result in error. libjsonincpp defines a special structure json::JSON_reference. It can be undefined. It is returned by operator[]. + +Also, this is a correct code in libjsonincpp: + + +json::JSON obj; // Initial value is null symbol +obj["aaaa"]["foo"][12][0] = JSON("value"); + +This code will automatically make obj a dictionary, make obj["aaaa"] a dictionary, make obj["aaaa"]["foo"] an array, e.t.c. And at the end, it will perform copy of "value" + +### Cons + +It has no cons, but more like "limitations": + +JSON specification allows some crazy precise float values as JOSN number (imagine unironically handling float with decimal exponent equal to 99....999 with as +many nines as your internet connection can handle). Handling floats and big integers was not my goal. Thus, it can correctly interpret obly numbers with less then 19 digits. +Other number values are stored as unparsed strings. + +Also, libregexis024 imposes a restriction on JSON string encoding: All JSON string values MUST be correct UTF-8 strings, otherwise generating string representation of +your JSON object will result in json::misuse exception. + +### Dependencies + +C++, regexis024_build_system + +What is regexis024_build_system? It's nothing. Don't think about it. Just download it and forget about it. + +Also, don't forget to disable overcommitment on your system in order for _your system_ to be usable. +Bruh, why don't we already rename OOM-killer to just regular killer and charge it for all the crimes it did? +Why we keep executing OOM-killer on our servers when we can take the guy who thought that overcommitment was a good idea and execute him on an electric chair? + diff --git a/building/build_build_system.sh b/building/build_build_system.sh new file mode 100755 index 0000000..6cc3b46 --- /dev/null +++ b/building/build_build_system.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env sh + +BUILDING_DIR="./building" +[ -d "$BUILDING_DIR" ] || exit 1 +MAIN_FILE="$BUILDING_DIR/main.cpp" +[ -f "$MAIN_FILE" ] || exit 1 + +COOL_FLAGS="$(pkg-config --cflags regexis024-build-system)" + +g++ $COOL_FLAGS -o "$BUILDING_DIR/main" "$MAIN_FILE" || exit 1 \ No newline at end of file diff --git a/building/main.cpp b/building/main.cpp new file mode 100644 index 0000000..1878731 --- /dev/null +++ b/building/main.cpp @@ -0,0 +1,102 @@ +#include "regexis024_build_system.h" + +struct TestWebsiteBuildScript { + /* Building runlevel */ + BuildUnitsArray runlevel_1; + /* Installation runlevel */ + BuildUnitsArray runlevel_2; + + /* "debug" or "release" */ + std::string build_type; + bool make_tests = false; + + std::vector warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic", + "-Wno-unused-but-set-variable", "-Wno-reorder"}; + std::vector version_flags = {"--std", "c++14", "-D", "_POSIX_C_SOURCE=200809L"}; + + std::vector debug_defines_release = {"_GLIBCXX_DEBUG"}; + std::vector debug_defines_debug = {"_GLIBCXX_DEBUG", "DEBUG_ALLOW_LOUD"}; + std::vector opt_flags_release = {"-g", "-O2"}; + std::vector opt_flags_debug = {"-g", "-ggdb", "-O0"}; + + + std::vector getSomeRadFlags() { + std::vector my_flag_collection; + gxx_add_cli_options(my_flag_collection, warning_flags); + gxx_add_cli_options(my_flag_collection, version_flags); + if (build_type == "release") { + gxx_add_cli_defines(my_flag_collection, debug_defines_release); + gxx_add_cli_options(my_flag_collection, opt_flags_release); + } else if (build_type == "debug") { + gxx_add_cli_defines(my_flag_collection, debug_defines_debug); + gxx_add_cli_options(my_flag_collection, opt_flags_debug); + } + return my_flag_collection; + } + + TestWebsiteBuildScript(const std::string& _build_type, bool make_tests, + const NormalCBuildSystemCommandMeaning& cmd) + : build_type(_build_type), make_tests(make_tests) + { + ASSERT(build_type == "release" || build_type == "debug", "Unknown build type"); + + std::vector ext_targets; + + std::vector my_targets; + { CTarget T("libjsonincpp", "shared_library"); + T.additional_compilation_flags = getSomeRadFlags(); + T.units_dir = "library"; + T.units = { + "libjsonincpp/utf8.cpp", + "libjsonincpp/jsonobj.cpp", + "libjsonincpp/quality_of_life.cpp", + "libjsonincpp/quality_of_life_2.cpp", + "libjsonincpp/integer.cpp", + "libjsonincpp/inner_storage.cpp", + "libjsonincpp/generator.cpp", + "libjsonincpp/parser.cpp", + "libjsonincpp/parser_context.cpp", + "libjsonincpp/container_parsing.cpp", + }; + T.include_pr = "library"; + T.include_ir = ""; + T.exported_headers = { + "libjsonincpp/jsonobj.h", + "libjsonincpp/string_representation.h", + "libjsonincpp/utf8.h" + }; + T.installation_dir = ""; + T.description = "C++ JSON object structure + parser and generator"; + my_targets.push_back(T); + } + if (make_tests) { CTarget T("test0", "executable"); + T.additional_compilation_flags = getSomeRadFlags(); + T.proj_deps = {CTargetDependenceOnProjectsLibrary("libjsonincpp")}; + T.units_dir = "tests"; + T.units = {"test0.cpp"}; + T.include_pr = "tests"; + my_targets.push_back(T); + } + regular_ctargets_to_2bus_conversion(ext_targets, my_targets, runlevel_1, runlevel_2, + cmd.project_root, cmd.installation_root); + } +}; + +int main(int argc, char** argv) { + try { + assert(argc > 0); + std::vector args(argc - 1); + for (int i = 0; i + 1 < argc; i++) { + args[i] = argv[i + 1]; + } + NormalCBuildSystemCommandMeaning cmd; + regular_bs_cli_cmd_interpret(args, cmd); + TestWebsiteBuildScript bs("debug", true, cmd); + if (cmd.need_to_build) + complete_tasks_of_build_units(bs.runlevel_1); + if (cmd.need_to_install) + complete_tasks_of_build_units(bs.runlevel_2); + } catch (const buildSystemFailure& e) { + printf("Build system failure\n""%s\n", e.toString().c_str()); + } +} \ No newline at end of file diff --git a/src/library/libjsonincpp/container_parsing.cpp b/src/library/libjsonincpp/container_parsing.cpp new file mode 100644 index 0000000..dc3f2db --- /dev/null +++ b/src/library/libjsonincpp/container_parsing.cpp @@ -0,0 +1,39 @@ +#include "parser.h" + +namespace json { + ArrayParseCall::ArrayParseCall(std::vector &result): result(result) {} + + std::unique_ptr ArrayParseCall::here(ParserContext& pctx) { + skipWhitespaces(pctx); + if (peep(pctx) == ']') { + skip(pctx); + return NULL; + } + if (got_one) { + demandSkip(pctx, ','); + } + result.emplace_back(); + got_one = true; + return std::make_unique(result.back()); + } + + DictionaryParseCall::DictionaryParseCall(std::map& result): result(result) {} + + std::unique_ptr DictionaryParseCall::here(ParserContext& pctx) { + skipWhitespaces(pctx); + if (peep(pctx) == '}') { + skip(pctx); + return NULL; + } + if (got_one) { + demandSkip(pctx, ','); + } + std::string key = demandStringJson(pctx); + skipWhitespaces(pctx); + demandSkip(pctx, ':'); + if (result.count(key) > 0) + throw bad_syntax(); + got_one = true; + return std::make_unique(result[key]); + } +} diff --git a/src/library/libjsonincpp/generator.cpp b/src/library/libjsonincpp/generator.cpp new file mode 100644 index 0000000..cde5bf3 --- /dev/null +++ b/src/library/libjsonincpp/generator.cpp @@ -0,0 +1,134 @@ +#include "string_representation.h" +#include "inner_storage.h" +#include +#include "utf8.h" + +namespace json { + void escape_string(std::string& dest, const std::string& src) { + if (!isUtf8String(src)) { + throw misuse("Can't stringify json string value that is not a utf-8 string"); + } + dest += '"'; + for (char ch: src) { + if (ch == '"' || ch == '\\' || (0 <= ch && ch <= 0x1f)) { + char buf[10]; + snprintf(buf, 10, "\\u%04x", ch); + dest += buf; + } else { + dest += ch; + } + } + dest += '"'; + } + + int try_append_rep_childless(const JSON& obj, uint32_t mode, std::string& ret_ans) { +#define azaza(sym) case sym ## _symbol: ret_ans += #sym; return 0; + switch (obj.type) { + azaza(null) + azaza(false) + azaza(true) + case integer: + ret_ans += obj.asInteger().to_string(); + return 0; + case string: + escape_string(ret_ans, obj.asString()); + return 0; + case array: + if (obj.asArray().empty()) { + ret_ans += "[]"; + return 0; + } + return -1; + case dictionary: + if (obj.asDictionary().empty()) { + ret_ans += "{}"; + return 0; + } + return -1; + default: + assert(false); + } + } + + void indent(int depth, std::string& res) { + for (int i = 0; i < depth; i++) + res += " "; + } + + /* Returns node.it == node.data.end(). If true is returned, cur is switched to parent */ + template + bool print_iter_pref(ObjData& node, std::string& result, int& cur_depth, const JSON*& cur, + char container_begin, char container_end, uint32_t mode) { + if (node.it == node.data.begin()) { + cur_depth++; + result += container_begin; + if (mode == print_pretty) { + result += "\n"; + indent(cur_depth, result); + } + } else if (node.it == node.data.end()) { + cur_depth--; + if (mode == print_pretty) { + result += '\n'; + indent(cur_depth, result); + } + result += container_end; + cur = (const JSON*)node.wayback; + return true; + } else { + result += ','; + if (mode == print_pretty) { + result += '\n'; + indent(cur_depth, result); + } + } + return false; + } + + std::string generate_str(const JSON &obj, uint32_t mode) { + int ret; + std::string result; + ret = try_append_rep_childless(obj, mode, result); + if (ret < 0) { + int cur_depth = 0; + const JSON* cur = &obj; + setup_nr_iterators(obj, NULL); + while (cur) { + if (cur->isArray()) { + ArrayData& node = *static_cast(cur->value); + proc_child_1: + if (print_iter_pref(node, result, cur_depth, cur, '[', ']', mode)) + continue; + JSON& child = *node.it; + ++node.it; + ret = try_append_rep_childless(child, mode, result); + if (ret == 0) { + goto proc_child_1; + } + setup_nr_iterators(child, (void*)cur); + cur = &child; + } else if (cur->isDictionary()) { + DictionaryData& node = *static_cast(cur->value); + proc_child_2: + if (print_iter_pref(node, result, cur_depth, cur, '{', '}', mode)) + continue; + escape_string(result, node.it->first); + result += ':'; + if (mode == print_pretty) + result += ' '; + JSON& child = node.it->second; + ++node.it; + ret = try_append_rep_childless(child, mode, result); + if (ret == 0) + goto proc_child_2; + setup_nr_iterators(child, (void*)cur); + cur = &child; + } + } + } + if (mode == print_pretty) { + result += "\n"; + } + return result; + } +} diff --git a/src/library/libjsonincpp/inner_storage.cpp b/src/library/libjsonincpp/inner_storage.cpp new file mode 100644 index 0000000..0a9ec27 --- /dev/null +++ b/src/library/libjsonincpp/inner_storage.cpp @@ -0,0 +1,183 @@ +#include "jsonobj.h" +#include "inner_storage.h" +#include + +namespace json { + ArrayData::~ArrayData() { + for (auto& p: data) + assert(p.isNull()); + } + + DictionaryData::~DictionaryData() { + for (auto& p: data) { + assert(p.second.isNull()); + } + } + + void stomp(JSON& finished) noexcept { + finished.value = NULL; + finished.type = null_symbol; + } + + void nullify_childless(JSON& obj) noexcept { + if (obj.type == integer) { + delete static_cast(obj.value); + } else if (obj.type == string) { + delete static_cast(obj.value); + } + stomp(obj); + } + + void setup_nr_iterators(const JSON& obj_child, void* wayback) noexcept { + if (obj_child.type == array) { + ArrayData& ad = *static_cast(obj_child.value); + ad.wayback = wayback; + ad.it = ad.data.begin(); + } else if (obj_child.type == dictionary) { + DictionaryData& dd = *static_cast(obj_child.value); + dd.wayback = wayback; + dd.it = dd.data.begin(); + } else + assert(false); + } + + void nullify(JSON &obj) noexcept { + if (!obj.isNatalistic()) { + nullify_childless(obj); + return; + } + JSON* current = &obj; + setup_nr_iterators(obj, NULL); + while (current) { + if (current->type == array) { + ArrayData& ad = *static_cast(current->value); + it_check: + if (ad.it == ad.data.end()) { + stomp(*current); + current = static_cast(ad.wayback); + delete &ad; + } else { + JSON& child = *ad.it; + ++ad.it; + if (!child.isNatalistic()) { + nullify_childless(child); + goto it_check; + } + setup_nr_iterators(child, current); + current = &child; + } + } else if (current->type == dictionary) { + DictionaryData& dd = *static_cast(current->value); + it_check2: + if (dd.it == dd.data.end()) { + stomp(*current); + current = static_cast(dd.wayback); + delete ⅆ + } else { + JSON& child = dd.it->second; + ++dd.it; + if (!child.isNatalistic()) { + nullify_childless(child); + goto it_check2; + } + setup_nr_iterators(child, current); + current = &child; + } + } + } + } + + /* Strong exception guarantee */ + void copy_childless(JSON& destination, const JSON& source) { + assert(destination.type == null_symbol); + if (source.type == integer) { + destination.value = new Integer(source.asInteger()); + } else if (source.type == string) { + destination.value = new std::string(source.asString()); + } + destination.type = source.type; + } + + /* Basic exception guarantee */ + void setup_it_plus_double_wayback(JSON& dest_child, const JSON& src_child, JSON* dest_wayback, const JSON* src_wayback) + { + assert(dest_child.isNull()); + if (src_child.type == array) { + ArrayData& SRC_DATA = *static_cast(src_child.value); + dest_child.value = new ArrayData(); + dest_child.type = array; + /* Destination is in correct state */ + ArrayData& DEST_DATA = *static_cast(dest_child.value); + DEST_DATA.data.resize(SRC_DATA.data.size()); + DEST_DATA.it = DEST_DATA.data.begin(); + DEST_DATA.wayback = dest_wayback; + SRC_DATA.it = SRC_DATA.data.begin(); + SRC_DATA.wayback = const_cast(static_cast(src_wayback)); + } else if (src_child.type == dictionary) { + DictionaryData& SRC_DATA = *static_cast(src_child.value); + dest_child.value = new DictionaryData(); + dest_child.type = dictionary; + DictionaryData& DEST_DATA = *static_cast(dest_child.value); + // Here keeping destination data iterator is unnecessary + DEST_DATA.wayback = dest_wayback; + SRC_DATA.it = SRC_DATA.data.begin(); + SRC_DATA.wayback = const_cast(static_cast(src_wayback)); + } else + assert(false); + } + + void copy_json(JSON& destination, const JSON& source) { + assert(destination.type == null_symbol); + if (!source.isNatalistic()) { + copy_childless(destination, source); + return; + } + setup_it_plus_double_wayback(destination, source, NULL, NULL); + JSON* dest_cur = &destination; + const JSON* src_cur = &source; + while (dest_cur) { + assert(src_cur); + if (src_cur->type == array) { + ArrayData& ad_dest = *static_cast(dest_cur->value); + ArrayData& ad_src = *static_cast(src_cur->value); + it_check: + if (ad_src.it == ad_src.data.end()) { + dest_cur = static_cast(ad_dest.wayback); + src_cur = static_cast(ad_src.wayback); + } else { + JSON& src_child = *ad_src.it; + JSON& dest_child = *ad_dest.it; + ++ad_src.it; + ++ad_dest.it; + if (!src_child.isNatalistic()) { + copy_childless(dest_child, src_child); + goto it_check; + } + setup_it_plus_double_wayback(dest_child, src_child, dest_cur, src_cur); + dest_cur = &dest_child; + src_cur = &src_child; + } + } else if (src_cur->type == dictionary) { + DictionaryData& dd_dest = *static_cast(dest_cur->value); + DictionaryData& dd_src = *static_cast(src_cur->value); + it_check2: + if (dd_src.it == dd_src.data.end()) { + dest_cur = static_cast(dd_dest.wayback); + src_cur = static_cast(dd_src.wayback); + } else { + JSON& src_child = dd_src.it->second; + JSON& dest_child = dd_dest.data[dd_src.it->first]; // This function blows. ... I mean throws + ++dd_src.it; + if (!src_child.isNatalistic()) { + copy_childless(dest_child, src_child); + goto it_check2; + } + setup_it_plus_double_wayback(dest_child, src_child, dest_cur, src_cur); + dest_cur = &dest_child; + src_cur = &src_child; + } + } + } + assert(!src_cur); + } +} \ No newline at end of file diff --git a/src/library/libjsonincpp/inner_storage.h b/src/library/libjsonincpp/inner_storage.h new file mode 100644 index 0000000..978498e --- /dev/null +++ b/src/library/libjsonincpp/inner_storage.h @@ -0,0 +1,40 @@ +#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_INNER_STORAGE_H +#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_INNER_STORAGE_H + +/* DO NOT EXPORT THIS FILE */ + +#include "jsonobj.h" + +namespace json { + struct ArrayData { + std::vector data; + /* This field will be used in destructor to iterat over children. Destructor must not be + * recursive and must not allocate memory */ + std::vector::iterator it; + /* Parent (used only in destructor) */ + void* wayback = NULL; // Null means no parent (end of recursion) + + ~ArrayData(); + }; + + struct DictionaryData { + std::map data; + /* Again, destructors can't allocate a heap stack by themselves (it would be too late), JSON has to take + * care of it NOW */ + std::map::iterator it; + void* wayback = NULL; // Same as ArrayDaya::wayback + + ~DictionaryData(); + }; + + void setup_nr_iterators(const JSON& obj_child, void* wayback) noexcept; + + void nullify(JSON& obj) noexcept; + + void copy_json(JSON& destination, const JSON& source); +} + +// #define get_wayback(data) static_cast + + +#endif diff --git a/src/library/libjsonincpp/integer.cpp b/src/library/libjsonincpp/integer.cpp new file mode 100644 index 0000000..f2e11ba --- /dev/null +++ b/src/library/libjsonincpp/integer.cpp @@ -0,0 +1,49 @@ +#include "integer.h" +#include + +namespace json { + Integer::Integer(int64_t v): value(v) {} + + /* Throw bad_alloc. Very safe */ + void copy_horror(Integer& i, const char* other_horror) { + if (!other_horror) + return; + size_t n = strlen(other_horror); + i.uncomprehendable_horror = (char*)calloc(n + 1, 1); + if (!i.uncomprehendable_horror) + throw std::bad_alloc(); + memcpy(i.uncomprehendable_horror, other_horror, n); + } + + Integer::Integer(const char *terrifyingly_big_string) { + copy_horror(*this, terrifyingly_big_string); + } + + Integer::Integer(const Integer &other): value(other.value) { + copy_horror(*this, other.uncomprehendable_horror); + } + + Integer & Integer::operator=(const Integer &other) { + value = other.value; + free(uncomprehendable_horror); uncomprehendable_horror = NULL; + copy_horror(*this, other.uncomprehendable_horror); + return *this; + } + + std::string Integer::to_string() const { + if (uncomprehendable_horror) { + return std::string(uncomprehendable_horror); + } + return std::to_string(value); + } + + int64_t Integer::get_int() const { + if (uncomprehendable_horror) + return 9999999; + return value; + } + + Integer::~Integer() { + free(uncomprehendable_horror); + } +} diff --git a/src/library/libjsonincpp/integer.h b/src/library/libjsonincpp/integer.h new file mode 100644 index 0000000..fa1922a --- /dev/null +++ b/src/library/libjsonincpp/integer.h @@ -0,0 +1,30 @@ +#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_INTEGER_H +#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_INTEGER_H + +#include +#include +#include + +namespace json { + struct Integer { + int64_t value = 0; + /* JSON specification allows enormously big enormously precise float values. I don't want to handle this + * nonsense */ + char* uncomprehendable_horror = NULL; + + /* Only these members should be accessed */ + Integer() = default; + explicit Integer(int64_t v); + explicit Integer(const char* terrifyingly_big_string); + Integer(const Integer& other); + Integer& operator=(const Integer& other); + + std::string to_string() const; + + int64_t get_int() const; + + ~Integer(); + }; +} + +#endif diff --git a/src/library/libjsonincpp/jsonobj.cpp b/src/library/libjsonincpp/jsonobj.cpp new file mode 100644 index 0000000..edd1a90 --- /dev/null +++ b/src/library/libjsonincpp/jsonobj.cpp @@ -0,0 +1,86 @@ +#include "jsonobj.h" +#include "inner_storage.h" +#include +#include +#include "utf8.h" + +#define constr_begin try { +#define constr_end } catch (const std::bad_alloc& ba) { nullify(*this); throw; } + +namespace json { + misuse::misuse(const char *string): runtime_error(string) {} + + JSON::JSON(json_t type): type(type) { + if (type == integer) { + value = new Integer(); + } else if (type == string) { + value = new std::string(); + } else if (type == array) { + value = new ArrayData(); + } else if (type == dictionary) { + value = new DictionaryData(); + } + } + + JSON::JSON(bool V) { + type = V ? true_symbol : false_symbol; + } + + JSON::JSON(int64_t val): type(integer) { + value = new Integer(val); + } + + JSON::JSON(const Integer &V): type(integer) { + value = new Integer(V); + } + + JSON::JSON(const char *str): type(string) { + value = new std::string(str); + } + + JSON::JSON(const std::string &V): type(string) { + value = new std::string(V); + } + + JSON::JSON(const std::vector &V): type(array) { + ArrayData* dataPtr = new ArrayData(); + value = dataPtr; + // Now the object is in correct state, with allocated memory + constr_begin + /* std::vector invokes one of those really serious JSON non-recursive copy operators */ + dataPtr->data = V; + constr_end + } + + JSON::JSON(const std::map &V): type(dictionary) { + DictionaryData* dataPtr = new DictionaryData(); + value = dataPtr; + constr_begin + /* std::map will invoke non-recursive JSON copy */ + dataPtr->data = V; + constr_end + } + + JSON::JSON(const JSON &other) { + constr_begin + /* This is a very serious function. It must not be implemented recursively */ + copy_json(*this, other); + constr_end + } + + JSON & JSON::operator=(const JSON &other) { + /* This is another one of those super serious functions that must not be recursive no matter what */ + nullify(*this); + copy_json(*this, other); + return *this; + } + + JSON::~JSON() { + /* This is by far the most serious function I have ever written */ + nullify(*this); + } + + JSON_reference JSON::r() noexcept { + return {this, {}}; + } +} diff --git a/src/library/libjsonincpp/jsonobj.h b/src/library/libjsonincpp/jsonobj.h new file mode 100644 index 0000000..1832ce4 --- /dev/null +++ b/src/library/libjsonincpp/jsonobj.h @@ -0,0 +1,116 @@ +#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_JSONOBJ_H +#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_JSONOBJ_H + +#include "integer.h" +#include +#include +#include + +namespace json { + struct misuse : std::runtime_error { + explicit misuse(const char *string); + }; + + enum json_t { + null_symbol, + false_symbol, + true_symbol, + integer, + string, + array, + dictionary, + }; + + enum imaginary_key_t { + undefined_array_element, + undefined_dictionary_element, + }; + + struct JSON_reference; + + struct JSON { + void* value = NULL; + json_t type = null_symbol; + + JSON() = default; + explicit JSON(json_t type); + explicit JSON(bool V); + explicit JSON(int64_t val); + explicit JSON(const Integer& V); + explicit JSON(const char* str); + explicit JSON(const std::string& V); + explicit JSON(const std::vector& V); + explicit JSON(const std::map& V); + + JSON(const JSON& other); + JSON& operator=(const JSON& other); + ~JSON(); + + JSON_reference r() noexcept; + + json_t getType() const; + + bool isNull() const; + + bool isBool() const; + + bool isInteger() const; + + bool isFalse() const; + + bool isTrue() const; + + bool isString() const; + + bool isArray() const; + + bool isDictionary() const; + + bool isNatalistic() const; + + bool isSymbol() const; + + bool toBool() const; + + Integer& asInteger() const; + + std::string& asString() const; + + std::vector& asArray() const; + + std::map& asDictionary() const; + + JSON_reference operator[](size_t index); + JSON_reference operator[](const std::string& key); + + JSON& operator=(int64_t V); + JSON& operator=(const Integer& V); + JSON& operator=(const char* V); + JSON& operator=(const std::string& V); + }; + + struct ImaginaryKeyChainEValue { + imaginary_key_t type; + /* Why messing with RAII-ing (int|string) value behind some void pointer when I can just include both. + * C'mon, bros, memory consumption issue does not exist */ + size_t when_array_index; + std::string when_dictionary_key; + }; + + /* These references get invalidated as soon as referenced object or any of its parents get changed */ + struct JSON_reference { + JSON* last_real = NULL; + std::vector imaginary_chain; + + bool isDefined() const; + + JSON& operator*() const; + + void operator=(const JSON& obj); + + JSON_reference operator[](size_t index); + JSON_reference operator[](const std::string& key); + }; +} + +#endif diff --git a/src/library/libjsonincpp/parser.cpp b/src/library/libjsonincpp/parser.cpp new file mode 100644 index 0000000..8af7f80 --- /dev/null +++ b/src/library/libjsonincpp/parser.cpp @@ -0,0 +1,244 @@ +#include "string_representation.h" +#include "parser.h" +#include +#include + +#include "utf8.h" + +namespace json { + std::unique_ptr ParsingCall::here(ParserContext &pctx) { + return NULL; + } + + ValueParseCall::ValueParseCall(JSON &result) : result(result) { + assert(result.isNull()); + } + + bool isDigit(int ch) { + return ('0' <= ch && ch <= '9'); + } + + bool isIntegerStart(int ch) { + return isDigit(ch) || ch == '-'; + } + + bool isSymbolConstituent(int ch) { + return 'a' <= ch && ch <= 'z'; + } + + void read_int_minus_part(ParserContext& pctx, bool& mantis_minus) { + mantis_minus = false; + if (peep(pctx) == '-') { + skip(pctx); + mantis_minus = true; + } + } + + void read_int_int_part(ParserContext& pctx, int64_t& mantis_max18, bool& is_terrifying) { + mantis_max18 = 0; + int d = 0; + while (true) { + int ch = peep(pctx); + if (!isDigit(ch)) + break; + skip(pctx); + if (ch == '0' && d == 0) + break; + if (d < 18) { + mantis_max18 = mantis_max18 * 10 + (ch - '0'); + d++; + } else { + is_terrifying = true; + } + } + if (d == 0) + throw bad_syntax(); + } + + void read_that_int_part_with_at_least_one_digit(ParserContext& pctx) { + if (!isDigit(peep(pctx))) + throw bad_syntax(); + skip(pctx); + while (isDigit(peep(pctx))) + skip(pctx); + } + + void read_int_frac_exp_part(ParserContext& pctx, bool& is_terrifying) { + if (peep(pctx) == '.') { + is_terrifying = true; + skip(pctx); + read_that_int_part_with_at_least_one_digit(pctx); + } + if (peep(pctx) == 'e' || peep(pctx) == 'E') { + is_terrifying = true; + skip(pctx); + if (peep(pctx) == '+' || peep(pctx) == '-') + skip(pctx); + read_that_int_part_with_at_least_one_digit(pctx); + } + } + + /* Starts with reading u. Throws json::bad_syntax on bad syntax */ + uint32_t read_4nibbles(ParserContext& pctx) { + uint32_t result = 0; + demandSkip(pctx, 'u'); + for (int i = 0; i < 4; i++) { + int ch = peep(pctx); + result <<= 4; + if (isDigit(ch)) { + result += (ch - '0'); + } else if ('a' <= ch && ch <= 'f') { + result += (ch - 'a') + 10; + } else if ('A' <= ch && ch <= 'F') { + result += (ch - 'A') + 10; + } else + throw bad_syntax(); + skip(pctx); + } + return result; + } + + bool is_utf16_2bp_high_surrogate(uint32_t v) { + return 0xD800 <= v && v <= 0xDBFF; + } + + bool is_utf16_2bp_low_surrogate(uint32_t v) { + return 0xDC00 <= v && v <= 0xE000; + } + + constexpr char escaping_rules[][2] = {{'"', '"'}, {'\\', '\\'}, {'/', '/'}, {'b', '\b'}, {'f', '\f',}, + {'n', '\n'}, {'r', '\r'}, {'t', '\t'}, {0, 0}}; + + void resert_to_one_char_escape(int leader, std::string& str) { + for (int i = 0; escaping_rules[i][0] != 0; i++) { + if (escaping_rules[i][0] == leader) { + str += escaping_rules[i][1]; + return; + } + } + throw bad_syntax(); + } + + std::string demandStringJson(ParserContext &pctx) { + skipWhitespaces(pctx); + std::string str; + demandSkip(pctx, '"'); + int ch; + while ((ch = peep(pctx)) != '"') { + if ((0 <= ch && ch <= 0x1f) || ch == endOfFile) + throw bad_syntax(); + skip(pctx); + if (ch == '\\') { + int leader = peep(pctx); + if (leader == 'u') { + uint32_t first_utf16 = read_4nibbles(pctx); + if (is_utf16_2bp_low_surrogate(first_utf16)) + throw bad_syntax(); + if (!is_utf16_2bp_high_surrogate(first_utf16)) { + codepoint_to_utf8(first_utf16, str); + } else { + demandSkip(pctx, '\\'); + uint32_t second_utf16 = read_4nibbles(pctx); + if (!is_utf16_2bp_low_surrogate(second_utf16)) + throw bad_syntax(); + uint32_t cp = 0x10000 + ((first_utf16 - 0xD800) << 10) + (second_utf16 - 0xDC00); + codepoint_to_utf8(cp, str); + } + } else { + resert_to_one_char_escape(leader, str); + skip(pctx); // Skipping leader + } + } else { + str += ch; + } + } + skip(pctx); + if (!isUtf8String(str)) + throw bad_syntax(); + return str; + } + + std::unique_ptr ValueParseCall::here(ParserContext &pctx) { + if (got_him) + return NULL; + got_him = true; + skipWhitespaces(pctx); + int herald = peep(pctx); + if (herald == '"') { + result = demandStringJson(pctx); + } else if (isIntegerStart(herald)) { + size_t pos_beg = pctx.pos; + bool terrifying = false; + bool mantis_minus; + read_int_minus_part(pctx, mantis_minus); + int64_t mantis_abs_max18; + read_int_int_part(pctx, mantis_abs_max18, terrifying); + read_int_frac_exp_part(pctx, terrifying); + if (terrifying) { + result = Integer(pctx.text.substr(pos_beg, pctx.pos).c_str()); + } else if (mantis_minus) { + result = -mantis_abs_max18; + } else { + result = mantis_abs_max18; + } + } else if (isSymbolConstituent(herald)) { + std::string sym; + while (isSymbolConstituent(peep(pctx))) { + sym += peep(pctx); + skip(pctx); + } + if (sym == "null") { + result = JSON(null_symbol); + } else if (sym == "false") { + result = JSON(false_symbol); + } else if (sym == "true") { + result = JSON(true_symbol); + } else + throw bad_syntax(); + } else if (herald == '[') { + skip(pctx); + result = JSON(array); + return std::make_unique(result.asArray()); + } else if (herald == '{') { + skip(pctx); + result = JSON(dictionary); + return std::make_unique(result.asDictionary()); + } else + throw bad_syntax(); + return NULL; + } + + int parse_str(const std::string& text, JSON& ret_ans, WrongSyntax& ret_error) { + assert(ret_ans.isNull()); + ParserContext pctx(text); + try { + std::vector> callStack; + callStack.push_back(std::make_unique(ret_ans)); + while (!callStack.empty()) { + std::unique_ptr rt = callStack.back()->here(pctx); + if (rt) { + callStack.push_back(std::move(rt)); + } else { + callStack.pop_back(); + } + } + skipWhitespaces(pctx); + if (!isEof(pctx)) + throw bad_syntax(); + return 0; + } catch (bad_syntax&) { + ret_error.line = pctx.line; + ret_error.column = pctx.column; + return -1; + } + } + + JSON parse_str_flawless(const std::string &text) { + WrongSyntax wsErr; + JSON result; + int ret = parse_str(text, result, wsErr); + if (ret < 0) + throw misuse("JSON parsing error"); + return result; + } +} diff --git a/src/library/libjsonincpp/parser.h b/src/library/libjsonincpp/parser.h new file mode 100644 index 0000000..f45cd9c --- /dev/null +++ b/src/library/libjsonincpp/parser.h @@ -0,0 +1,72 @@ +#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_PARSER_H +#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_PARSER_H + +/* DO NOT EXPORT THIS FILE! User parser API is in string_representation.h */ + +#include "jsonobj.h" +#include +#include + +namespace json { + constexpr int endOfFile = 999999; + + struct bad_syntax: public std::exception { + inline bad_syntax() { + assert(false); + } + }; + + struct ParserContext { + const std::string& text; + size_t pos = 0; + size_t line = 0; + size_t column = 0; + + explicit ParserContext(const std::string& text); + }; + + bool isEof(ParserContext& pctx); + int peep(ParserContext& pctx); + int skip(ParserContext& pctx); + void demandSkip(ParserContext& pctx, char ch); + bool isWhitespace(int vch); + void skipWhitespaces(ParserContext& pctx); + + /* Function that parses string value */ + std::string demandStringJson(ParserContext& pctx); + + struct ParsingCall { + virtual std::unique_ptr here(ParserContext& pctx); + + virtual ~ParsingCall() = default; + }; + + struct ValueParseCall: public ParsingCall { + JSON& result; + bool got_him = false; + + std::unique_ptr here(ParserContext& pctx) override; + + ValueParseCall(JSON& result); + }; + + struct ArrayParseCall : public ParsingCall { + std::vector& result; + bool got_one = false; + + explicit ArrayParseCall(std::vector& result); + + std::unique_ptr here(ParserContext& pctx) override; + }; + + struct DictionaryParseCall: public ParsingCall { + std::map& result; + bool got_one = false; + + explicit DictionaryParseCall(std::map& result); + + std::unique_ptr here(ParserContext& pctx) override; + }; +} + +#endif diff --git a/src/library/libjsonincpp/parser_context.cpp b/src/library/libjsonincpp/parser_context.cpp new file mode 100644 index 0000000..8e77344 --- /dev/null +++ b/src/library/libjsonincpp/parser_context.cpp @@ -0,0 +1,45 @@ +#include "parser.h" +#include + +namespace json { + ParserContext::ParserContext(const std::string& text): text(text) {} + + bool isEof(ParserContext &pctx) { + return pctx.pos >= pctx.text.size(); + } + + int peep(ParserContext &pctx) { + if (pctx.pos >= pctx.text.size()) + return endOfFile; + return pctx.text[pctx.pos]; + } + + int skip(ParserContext &pctx) { + if (isEof(pctx)) + throw bad_syntax(); + int v = pctx.text[pctx.pos++]; + if (v == '\n') { + pctx.line++; + pctx.column = 0; + } else + pctx.column++; + return v; + + } + + void demandSkip(ParserContext &pctx, char ch) { + if (peep(pctx) != ch) + throw bad_syntax(); + skip(pctx); + } + + bool isWhitespace(int vch) { + return vch == ' ' || vch == '\t' || vch == '\n'; + } + + void skipWhitespaces(ParserContext &pctx) { + while (isWhitespace(peep(pctx))) { + skip(pctx); + } + } +} diff --git a/src/library/libjsonincpp/quality_of_life.cpp b/src/library/libjsonincpp/quality_of_life.cpp new file mode 100644 index 0000000..71565dd --- /dev/null +++ b/src/library/libjsonincpp/quality_of_life.cpp @@ -0,0 +1,120 @@ +#include "inner_storage.h" + +#include "jsonobj.h" + +namespace json { + json_t JSON::getType() const { + return type; + } + + bool JSON::isNull() const { + return type == null_symbol; + } + + bool JSON::isBool() const { + return type == false_symbol || type == true_symbol; + } + + bool JSON::isInteger() const { + return type == integer; + } + + bool JSON::isFalse() const { + return type == false_symbol; + } + + bool JSON::isTrue() const { + return type == true_symbol; + } + + bool JSON::isString() const { + return type == string; + } + + bool JSON::isArray() const { + return type == array; + } + + bool JSON::isDictionary() const { + return type == dictionary; + } + + bool JSON::isNatalistic() const { + return type == array || type == dictionary; + } + + bool JSON::isSymbol() const { + return type == null_symbol || type == false_symbol || type == true_symbol; + } + + bool JSON::toBool() const { + switch(type) { + case false_symbol: + return false; + case true_symbol: + return true; + default: + throw misuse("json obj is not boolean"); + } + } + + Integer& JSON::asInteger() const { + if (isInteger()) + return *static_cast(value); + throw misuse("json obj is not integer"); + } + + std::string& JSON::asString() const { + if (isString()) + return *static_cast(value); + throw misuse("json obj is not string"); + } + + std::vector& JSON::asArray() const { + if (isArray()) + return static_cast(value)->data; + throw misuse("json obj is not array"); + } + + std::map& JSON::asDictionary() const { + if (isDictionary()) + return static_cast(value)->data; + throw misuse("json obj is not dictionary"); + } + + JSON_reference JSON::operator[](size_t index) { + return r()[index]; + } + + JSON_reference JSON::operator[](const std::string &key) { + return r()[key]; + } + + JSON& JSON::operator=(int64_t V) { + nullify(*this); + value = new Integer(V); + type = integer; + return *this; + } + + JSON & JSON::operator=(const Integer &V) { + nullify(*this); + value = new Integer(V); + type = integer; + return *this; + } + + JSON & JSON::operator=(const char *V) { + nullify(*this); + value = new std::string(V); + type = string; + return *this; + } + + JSON & JSON::operator=(const std::string &V) { + nullify(*this); + value = new std::string(V); + type = string; + return *this; + } +} diff --git a/src/library/libjsonincpp/quality_of_life_2.cpp b/src/library/libjsonincpp/quality_of_life_2.cpp new file mode 100644 index 0000000..a1fae8a --- /dev/null +++ b/src/library/libjsonincpp/quality_of_life_2.cpp @@ -0,0 +1,58 @@ +#include "jsonobj.h" + +namespace json { + bool JSON_reference::isDefined() const { + return imaginary_chain.empty(); + } + + JSON& JSON_reference::operator*() const { + if (!isDefined()) + throw misuse("dereferencing json reference with non-empty imaginary part"); + return *last_real; + } + + void JSON_reference::operator=(const JSON &obj) { + JSON* cur_last_real = last_real; + for (const auto& ck: imaginary_chain) { + if (ck.type == undefined_array_element) { + if (cur_last_real->type == null_symbol) + *cur_last_real = JSON(array); + if (cur_last_real->type != array) + throw misuse("Implicit array creation on top of neither non-null nor short-array json obj is not allowed"); + cur_last_real->asArray().resize(ck.when_array_index + 1); + cur_last_real = &cur_last_real->asArray()[ck.when_array_index]; + } else { + if (cur_last_real->type == null_symbol) + *cur_last_real = JSON(dictionary); + if (cur_last_real->type != dictionary) + throw misuse("Implicit dictionary creation on top of neither non-null nor illiterate-dict json obj is not allowed"); + cur_last_real = &(cur_last_real->asDictionary()[ck.when_dictionary_key]); + } + } + *cur_last_real = obj; + } + + JSON_reference JSON_reference::operator[](size_t index) { + if (!imaginary_chain.empty()) { + std::vector elongated = imaginary_chain; + elongated.push_back({undefined_array_element, index, ""}); + return {last_real, elongated}; + } + if (last_real->isArray() && last_real->asArray().size() > index) { + return {&last_real->asArray()[index], {}}; + } + return {last_real, {ImaginaryKeyChainEValue{undefined_array_element, index, ""}}}; + } + + JSON_reference JSON_reference::operator[](const std::string &key) { + if (!imaginary_chain.empty()) { + std::vector elongated = imaginary_chain; + elongated.push_back({undefined_dictionary_element, 0, key}); + return {last_real, elongated}; + } + if (last_real->isDictionary() && last_real->asDictionary().count(key) > 0) { + return {&last_real->asDictionary()[key], {}}; + } + return {last_real, {ImaginaryKeyChainEValue{undefined_dictionary_element, 0, key}}}; + } +} diff --git a/src/library/libjsonincpp/string_representation.h b/src/library/libjsonincpp/string_representation.h new file mode 100644 index 0000000..80e5d89 --- /dev/null +++ b/src/library/libjsonincpp/string_representation.h @@ -0,0 +1,27 @@ +#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_STRING_REPRESENTATION_H +#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_STRING_REPRESENTATION_H + +#include "jsonobj.h" + +namespace json { + constexpr uint32_t print_compact = 1; + constexpr uint32_t print_pretty = 0; + + /* Throws json::misuse if obj contains non-utf8 strings. Throws std::bad_alloc */ + std::string generate_str(const JSON &obj, uint32_t mode); + + struct WrongSyntax { + size_t line; + size_t column; + }; + + /* Returns negative on error. Fills ret_error on syntax error. Still can throw std::bad_alloc + * ret_ans should be passed as null symbol + */ + int parse_str(const std::string& text, JSON& ret_ans, WrongSyntax& ret_error); + + /* Throws json::misuse when parsing fails */ + JSON parse_str_flawless(const std::string& text); +} + +#endif diff --git a/src/library/libjsonincpp/utf8.cpp b/src/library/libjsonincpp/utf8.cpp new file mode 100644 index 0000000..a6cb591 --- /dev/null +++ b/src/library/libjsonincpp/utf8.cpp @@ -0,0 +1,82 @@ +#include "utf8.h" +#include + +int _utf8_retrieve_size(uint8_t firstByte) { + if (!(firstByte & 0b10000000)) + return 1; + uint8_t a = 0b11000000; + uint8_t b = 0b00100000; + for (int i = 2; i <= 4; i++){ + if ((firstByte & (a | b)) == a) + return i; + a |= b; + b >>= 1; + } + return -1; +} + +int32_t _utf8_retrieve_character(int sz, size_t pos, const char *string) { + if (sz == 1) + return (int32_t)string[pos]; + uint32_t v = ((uint8_t)string[pos]) & (0b01111111 >> sz); + pos++; + for (int i = 1; i < sz; i++){ + uint32_t th = (uint8_t)string[pos]; + if ((th & 0b11000000) != 0b10000000) + return -1; + v <<= 6; + v |= (th & 0b00111111); + pos++; + } + assert(v <= INT32_MAX); + return static_cast(v); +} + +void utf8_string_iterat(int32_t &cp, size_t &adj, size_t pos, const char *string, size_t string_size) { + if (pos >= string_size) {cp = -1; return;} + adj = _utf8_retrieve_size((uint8_t)string[pos]); + if (adj < 0 || pos + adj > string_size) {cp = -1; return;} + if ((cp = _utf8_retrieve_character(adj, pos, string)) < 0) {cp = -1; return;} +} + +bool isUtf8String(const std::string &str) { + size_t N = str.size(); + size_t cpos = 0; + while (cpos < N) { + int32_t codepoint; + size_t adj; + utf8_string_iterat(codepoint, adj, cpos, str.data(), N); + if (codepoint < 0) + return false; + cpos += adj; + } + return true; +} + +int codepoint_to_utf8(uint32_t cp, std::string &out) { + size_t N = out.size(); + auto make_compl = [cp](int imp) -> char { + return (char)(((cp >> imp) & 0x3f) | 0x80); + }; + if (cp > 0x10FFFF) + return -1; + if (cp <= 0x7F) { + out += (char)cp; + } else if (cp <= 0x7ff) { + out.resize(N + 2); + out[N] = (char)((cp >> 6) | 0xc0); + out[N + 1] = make_compl(0); + } else if (cp <= 0xffff) { + out.resize(N + 3); + out[N] = (char)((cp >> 12) | 0xe0); + out[N + 1] = make_compl(6); + out[N + 2] = make_compl(0); + } else { + out.resize(N + 4); + out[N] = (char)((cp >> 18) | 0xf0); + out[N + 1] = make_compl(12); + out[N + 2] = make_compl(6); + out[N + 3] = make_compl(0); + } + return 0; +} diff --git a/src/library/libjsonincpp/utf8.h b/src/library/libjsonincpp/utf8.h new file mode 100644 index 0000000..5a84ebb --- /dev/null +++ b/src/library/libjsonincpp/utf8.h @@ -0,0 +1,18 @@ +#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_UTF8_H +#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_UTF8_H + +#include +#include + +int _utf8_retrieve_size(uint8_t firstByte); + +int32_t _utf8_retrieve_character(int sz, size_t pos, const char *string); + +void utf8_string_iterat(int32_t &cp, size_t &adj, size_t pos, const char *string, size_t string_size); + +bool isUtf8String(const std::string& str); + +/* Returns -1 if cp is not in 0-0x10FFFF range */ +int codepoint_to_utf8(uint32_t cp, std::string& out); + +#endif diff --git a/src/tests/test0.cpp b/src/tests/test0.cpp new file mode 100644 index 0000000..b4f0f2c --- /dev/null +++ b/src/tests/test0.cpp @@ -0,0 +1,17 @@ +#include +#include +#include + +using namespace json; + +void prettyprint_json(const JSON& obj) { + printf("%s", generate_str(obj, print_pretty).c_str()); +} + +int main() { + std::string text = "{\"\":\"\", \"true\":true, \"asd\" : { \"aaa\" : [[[[[[[123, 123, 13123123]]]]]]]}} "; + JSON j; + j = parse_str_flawless(text); + prettyprint_json(j); + return 0; +} \ No newline at end of file