Initial tested version

This commit is contained in:
Андреев Григорий 2024-07-23 16:31:51 +03:00
commit 5b6723f86f
21 changed files with 1544 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -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/

60
README.txt Normal file
View File

@ -0,0 +1,60 @@
### libjsonincpp
Suckless C++ library for storing JSON and generating and parsing JSON strings
### Examples
#include <libjsonincpp/jsonobj.h>
#include <libjsonincpp/string_representation.h>
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>(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?

10
building/build_build_system.sh Executable file
View File

@ -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

102
building/main.cpp Normal file
View File

@ -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<std::string> warning_flags = {"-Wall", "-Wno-unused-variable", "-Werror=return-type","-pedantic",
"-Wno-unused-but-set-variable", "-Wno-reorder"};
std::vector<std::string> version_flags = {"--std", "c++14", "-D", "_POSIX_C_SOURCE=200809L"};
std::vector<std::string> debug_defines_release = {"_GLIBCXX_DEBUG"};
std::vector<std::string> debug_defines_debug = {"_GLIBCXX_DEBUG", "DEBUG_ALLOW_LOUD"};
std::vector<std::string> opt_flags_release = {"-g", "-O2"};
std::vector<std::string> opt_flags_debug = {"-g", "-ggdb", "-O0"};
std::vector<std::string> getSomeRadFlags() {
std::vector<std::string> 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<ExternalLibraryTarget> ext_targets;
std::vector<CTarget> 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<std::string> 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());
}
}

View File

@ -0,0 +1,39 @@
#include "parser.h"
namespace json {
ArrayParseCall::ArrayParseCall(std::vector<JSON> &result): result(result) {}
std::unique_ptr<ParsingCall> 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<ValueParseCall>(result.back());
}
DictionaryParseCall::DictionaryParseCall(std::map<std::string, JSON>& result): result(result) {}
std::unique_ptr<ParsingCall> 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<ValueParseCall>(result[key]);
}
}

View File

@ -0,0 +1,134 @@
#include "string_representation.h"
#include "inner_storage.h"
#include <assert.h>
#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<typename ObjData>
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<ArrayData*>(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<DictionaryData*>(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;
}
}

View File

@ -0,0 +1,183 @@
#include "jsonobj.h"
#include "inner_storage.h"
#include <assert.h>
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<Integer*>(obj.value);
} else if (obj.type == string) {
delete static_cast<std::string*>(obj.value);
}
stomp(obj);
}
void setup_nr_iterators(const JSON& obj_child, void* wayback) noexcept {
if (obj_child.type == array) {
ArrayData& ad = *static_cast<ArrayData*>(obj_child.value);
ad.wayback = wayback;
ad.it = ad.data.begin();
} else if (obj_child.type == dictionary) {
DictionaryData& dd = *static_cast<DictionaryData*>(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<ArrayData*>(current->value);
it_check:
if (ad.it == ad.data.end()) {
stomp(*current);
current = static_cast<JSON*>(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<DictionaryData*>(current->value);
it_check2:
if (dd.it == dd.data.end()) {
stomp(*current);
current = static_cast<JSON*>(dd.wayback);
delete &dd;
} 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<ArrayData*>(src_child.value);
dest_child.value = new ArrayData();
dest_child.type = array;
/* Destination is in correct state */
ArrayData& DEST_DATA = *static_cast<ArrayData*>(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<void*>(static_cast<const void*>(src_wayback));
} else if (src_child.type == dictionary) {
DictionaryData& SRC_DATA = *static_cast<DictionaryData*>(src_child.value);
dest_child.value = new DictionaryData();
dest_child.type = dictionary;
DictionaryData& DEST_DATA = *static_cast<DictionaryData*>(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<void*>(static_cast<const void*>(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<ArrayData*>(dest_cur->value);
ArrayData& ad_src = *static_cast<ArrayData*>(src_cur->value);
it_check:
if (ad_src.it == ad_src.data.end()) {
dest_cur = static_cast<JSON*>(ad_dest.wayback);
src_cur = static_cast<const JSON*>(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<DictionaryData*>(dest_cur->value);
DictionaryData& dd_src = *static_cast<DictionaryData*>(src_cur->value);
it_check2:
if (dd_src.it == dd_src.data.end()) {
dest_cur = static_cast<JSON*>(dd_dest.wayback);
src_cur = static_cast<const JSON*>(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);
}
}

View File

@ -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<JSON> data;
/* This field will be used in destructor to iterat over children. Destructor must not be
* recursive and must not allocate memory */
std::vector<JSON>::iterator it;
/* Parent (used only in destructor) */
void* wayback = NULL; // Null means no parent (end of recursion)
~ArrayData();
};
struct DictionaryData {
std::map<std::string, JSON> 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<std::string, JSON>::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<JSON*&>
#endif

View File

@ -0,0 +1,49 @@
#include "integer.h"
#include <string.h>
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);
}
}

View File

@ -0,0 +1,30 @@
#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_INTEGER_H
#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_INTEGER_H
#include <stdint.h>
#include <string>
#include <stdexcept>
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

View File

@ -0,0 +1,86 @@
#include "jsonobj.h"
#include "inner_storage.h"
#include <memory>
#include <assert.h>
#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<JSON> &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<std::string, JSON> &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, {}};
}
}

View File

@ -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 <vector>
#include <map>
#include <stdexcept>
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<JSON>& V);
explicit JSON(const std::map<std::string, JSON>& 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<JSON>& asArray() const;
std::map<std::string, JSON>& 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<ImaginaryKeyChainEValue> 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

View File

@ -0,0 +1,244 @@
#include "string_representation.h"
#include "parser.h"
#include <memory>
#include <assert.h>
#include "utf8.h"
namespace json {
std::unique_ptr<ParsingCall> 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<ParsingCall> 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<ArrayParseCall>(result.asArray());
} else if (herald == '{') {
skip(pctx);
result = JSON(dictionary);
return std::make_unique<DictionaryParseCall>(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<std::unique_ptr<ParsingCall>> callStack;
callStack.push_back(std::make_unique<ValueParseCall>(ret_ans));
while (!callStack.empty()) {
std::unique_ptr<ParsingCall> 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;
}
}

View File

@ -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 <memory>
#include <assert.h>
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<ParsingCall> here(ParserContext& pctx);
virtual ~ParsingCall() = default;
};
struct ValueParseCall: public ParsingCall {
JSON& result;
bool got_him = false;
std::unique_ptr<ParsingCall> here(ParserContext& pctx) override;
ValueParseCall(JSON& result);
};
struct ArrayParseCall : public ParsingCall {
std::vector<JSON>& result;
bool got_one = false;
explicit ArrayParseCall(std::vector<JSON>& result);
std::unique_ptr<ParsingCall> here(ParserContext& pctx) override;
};
struct DictionaryParseCall: public ParsingCall {
std::map<std::string, JSON>& result;
bool got_one = false;
explicit DictionaryParseCall(std::map<std::string, JSON>& result);
std::unique_ptr<ParsingCall> here(ParserContext& pctx) override;
};
}
#endif

View File

@ -0,0 +1,45 @@
#include "parser.h"
#include <assert.h>
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);
}
}
}

View File

@ -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<Integer*>(value);
throw misuse("json obj is not integer");
}
std::string& JSON::asString() const {
if (isString())
return *static_cast<std::string*>(value);
throw misuse("json obj is not string");
}
std::vector<JSON>& JSON::asArray() const {
if (isArray())
return static_cast<ArrayData*>(value)->data;
throw misuse("json obj is not array");
}
std::map<std::string, JSON>& JSON::asDictionary() const {
if (isDictionary())
return static_cast<DictionaryData*>(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;
}
}

View File

@ -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<ImaginaryKeyChainEValue> 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<ImaginaryKeyChainEValue> 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}}};
}
}

View File

@ -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

View File

@ -0,0 +1,82 @@
#include "utf8.h"
#include <assert.h>
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<int32_t>(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;
}

View File

@ -0,0 +1,18 @@
#ifndef TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_UTF8_H
#define TEST_WEBSITE_SRC_MODULE_JSON_LIBJSONINCPP_UTF8_H
#include <stdint.h>
#include <string>
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

17
src/tests/test0.cpp Normal file
View File

@ -0,0 +1,17 @@
#include <libjsonincpp/jsonobj.h>
#include <libjsonincpp/string_representation.h>
#include <assert.h>
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;
}