diff --git a/scripts/amalgamate.py b/scripts/amalgamate.py index fb60d25..23503ee 100644 --- a/scripts/amalgamate.py +++ b/scripts/amalgamate.py @@ -66,7 +66,9 @@ def parse(path): text = "" with open(path, 'r') as fp: for line in fp.readlines(): - if "//<< AMALG_INLINE >>" in line: + if "//<< AMALG_IGNORE >>" in line: + continue + elif "//<< AMALG_INLINE >>" in line: path = join(dir, _get_include_path(line)) path = path.replace('\\', '/') text += parse(path) + '\n' diff --git a/scripts/run_tests.py b/scripts/run_tests.py index 6c8b354..731bd69 100644 --- a/scripts/run_tests.py +++ b/scripts/run_tests.py @@ -34,6 +34,7 @@ TEST_SUITE = { "modules/dummy.pk", "modules/math.pk", "modules/io.pk", + "modules/json.pk", ), "Random Scripts" : ( diff --git a/src/core/core.c b/src/core/core.c index 8b92a8d..193fb0c 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -518,6 +518,30 @@ DEF(coreOrd, } } +DEF(coreMin, + "min(a:var, b:var) -> Bool\n" + "Returns minimum of [a] and [b].") { + + Var a = ARG(1), b = ARG(2); + Var islesser = varLesser(vm, a, b); + if (VM_HAS_ERROR(vm)) RET(VAR_NULL); + + if (toBool(islesser)) RET(a); + RET(b); +} + +DEF(coreMax, + "max(a:var, b:var) -> Bool\n" + "Returns maximum of [a] and [b].") { + + Var a = ARG(1), b = ARG(2); + Var islesser = varLesser(vm, a, b); + if (VM_HAS_ERROR(vm)) RET(VAR_NULL); + + if (toBool(islesser)) RET(b); + RET(a); +} + DEF(corePrint, "print(...) -> void\n" "Write each argument as space seperated, to the stdout and ends with a " @@ -650,6 +674,8 @@ static void initializeBuiltinFunctions(PKVM* vm) { INITIALIZE_BUILTIN_FN("str", coreToString, 1); INITIALIZE_BUILTIN_FN("chr", coreChr, 1); INITIALIZE_BUILTIN_FN("ord", coreOrd, 1); + INITIALIZE_BUILTIN_FN("min", coreMin, 2); + INITIALIZE_BUILTIN_FN("max", coreMax, 2); INITIALIZE_BUILTIN_FN("print", corePrint, -1); INITIALIZE_BUILTIN_FN("input", coreInput, -1); INITIALIZE_BUILTIN_FN("exit", coreExit, -1); @@ -1475,17 +1501,19 @@ Closure* getSuperMethod(PKVM* vm, Var self, String* name) { #define RIGHT_OPERAND "Right operand" -#define CHECK_NUMERIC_OP(op) \ +#define CHECK_NUMERIC_OP_AS(op, as) \ do { \ double n1, n2; \ if (isNumeric(v1, &n1)) { \ if (validateNumeric(vm, v2, &n2, RIGHT_OPERAND)) { \ - return VAR_NUM(n1 op n2); \ + return as(n1 op n2); \ } \ return VAR_NULL; \ } \ } while (false) +#define CHECK_NUMERIC_OP(op) CHECK_NUMERIC_OP_AS(op, VAR_NUM) + #define CHECK_BITWISE_OP(op) \ do { \ int64_t i1, i2; \ @@ -1708,7 +1736,7 @@ Var varEqals(PKVM* vm, Var v1, Var v2) { } Var varGreater(PKVM* vm, Var v1, Var v2) { - CHECK_NUMERIC_OP(>); + CHECK_NUMERIC_OP_AS(>, VAR_BOOL); const bool inplace = false; CHECK_INST_BINARY_OP(">"); UNSUPPORTED_BINARY_OP(">"); @@ -1716,7 +1744,7 @@ Var varGreater(PKVM* vm, Var v1, Var v2) { } Var varLesser(PKVM* vm, Var v1, Var v2) { - CHECK_NUMERIC_OP(<); + CHECK_NUMERIC_OP_AS(< , VAR_BOOL); const bool inplace = false; CHECK_INST_BINARY_OP("<"); UNSUPPORTED_BINARY_OP("<"); diff --git a/src/core/value.c b/src/core/value.c index 8de673a..90c1bc6 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -1451,6 +1451,19 @@ bool isValuesEqual(Var v1, Var v2) { return true; } + case OBJ_MAP: { + Map *m1 = (Map*) o1, *m2 = (Map*) o2; + + MapEntry* e = m1->entries; + for (; e < m1->entries + m1->capacity; e++) { + if (IS_UNDEF(e->key)) continue; + Var v = mapGet(m2, e->key); + if (IS_UNDEF(v)) return false; + if (!isValuesEqual(e->value, v)) return false; + } + return true; + } + default: return false; } diff --git a/src/core/vm.h b/src/core/vm.h index 261f95e..7f110ea 100644 --- a/src/core/vm.h +++ b/src/core/vm.h @@ -14,7 +14,7 @@ // The maximum number of temporary object reference to protect them from being // garbage collected. -#define MAX_TEMP_REFERENCE 16 +#define MAX_TEMP_REFERENCE 64 // The capacity of the builtin function array in the VM. #define BUILTIN_FN_CAPACITY 50 diff --git a/src/libs/libs.c b/src/libs/libs.c index 7908640..9060538 100644 --- a/src/libs/libs.c +++ b/src/libs/libs.c @@ -16,6 +16,7 @@ void registerModuleTime(PKVM* vm); void registerModuleIO(PKVM* vm); void registerModulePath(PKVM* vm); void registerModuleOS(PKVM* vm); +void registerModuleJson(PKVM* vm); void registerModuleDummy(PKVM* vm); // Extra libraries. @@ -31,6 +32,7 @@ void registerLibs(PKVM* vm) { registerModuleIO(vm); registerModulePath(vm); registerModuleOS(vm); + registerModuleJson(vm); registerModuleDummy(vm); registerModuleTerm(vm); diff --git a/src/libs/std_io.c b/src/libs/std_io.c index 2176599..703cd60 100644 --- a/src/libs/std_io.c +++ b/src/libs/std_io.c @@ -424,10 +424,10 @@ DEF(_open, NULL /* == _fileOpen */) { void registerModuleIO(PKVM* vm) { - pkRegisterBuiltinFn(vm, "open", _open, -1, DOCSTRING(_fileOpen)); - PkHandle* io = pkNewModule(vm, "io"); + pkRegisterBuiltinFn(vm, "open", _open, -1, DOCSTRING(_fileOpen)); + pkReserveSlots(vm, 2); pkSetSlotHandle(vm, 0, io); // slot[0] = io pkSetSlotNumber(vm, 1, 0); // slot[1] = 0 @@ -450,6 +450,17 @@ void registerModuleIO(PKVM* vm) { pkClassAddMethod(vm, cls_file, "tell", _fileTell, 0); pkReleaseHandle(vm, cls_file); + // A convinent function to read file by io.readfile(path). + pkModuleAddSource(vm, io, + "## Reads a file and return it's content as string.\n" + "def readfile(filepath)\n" + " fp = File()\n" + " fp.open(filepath, 'r')\n" + " text = fp.read()\n" + " fp.close()\n" + " return text\n" + "end\n"); + pkRegisterModule(vm, io); pkReleaseHandle(vm, io); } diff --git a/src/libs/std_json.c b/src/libs/std_json.c new file mode 100644 index 0000000..0e6d998 --- /dev/null +++ b/src/libs/std_json.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include + +#ifndef PK_AMALGAMATED +#include "libs.h" +#include "../core/vm.h" +#endif + +#include "thirdparty/cJSON/cJSON.h" //<< AMALG_INLINE >> +#include "thirdparty/cJSON/cJSON.c" //<< AMALG_INLINE >> + +// TODO: +// Convert a cJSON object into pocket variable. Note that the depth shouldn't +// exceed MAX_TEMP_REFERENCE of the PKVM and we should throw error if the dept +// is too much (cJSON complain at depth 1000), add depth as a parameter. +static Var _cJsonToPocket(PKVM* vm, cJSON* item) { + + switch ((item->type) & 0xFF) { + case cJSON_NULL: + return VAR_NULL; + + case cJSON_False: + return VAR_FALSE; + + case cJSON_True: + return VAR_TRUE; + + case cJSON_Number: + return VAR_NUM(item->valuedouble); + + case cJSON_Raw: + case cJSON_String: { + const char* s = item->valuestring ? item->valuestring : ""; + return VAR_OBJ(newString(vm, s)); + } + + case cJSON_Array: { + List* list = newList(vm, 8); + vmPushTempRef(vm, &list->_super); // list. + + cJSON* elem = item->child; + while (elem != NULL) { + Var v = _cJsonToPocket(vm, elem); + if (IS_OBJ(v)) vmPushTempRef(vm, AS_OBJ(v)); // v. + listAppend(vm, list, v); + if (IS_OBJ(v)) vmPopTempRef(vm); // v. + elem = elem->next; + } + vmPopTempRef(vm); // list. + return VAR_OBJ(list); + } + + case cJSON_Object: { + Map* map = newMap(vm); + vmPushTempRef(vm, &map->_super); // map. + cJSON* elem = item->child; + while (elem != NULL) { + String* key = newString(vm, elem->string); + vmPushTempRef(vm, &key->_super); // key. + { + Var value = _cJsonToPocket(vm, elem); + if (IS_OBJ(value)) vmPushTempRef(vm, AS_OBJ(value)); // value. + mapSet(vm, map, VAR_OBJ(key), value); + if (IS_OBJ(value)) vmPopTempRef(vm); // value. + } + vmPopTempRef(vm); // key. + elem = elem->next; + } + vmPopTempRef(vm); // map. + return VAR_OBJ(map); + } + + default: + UNREACHABLE(); + } + + UNREACHABLE(); +} + +DEF(_jsonParse, + "json.parse(json_str:String) -> var\n" + "Parse a json string into pocket lang object.") { + + const char* string; + if (!pkValidateSlotString(vm, 1, &string, NULL)) return; + + cJSON* tree = cJSON_Parse(string); + + if (tree == NULL) { + + // TODO: Print the position. + // const char* position = cJSON_GetErrorPtr(); + // if (position != NULL) ... + pkSetRuntimeError(vm, "Invalid json string"); + return; + } + + Var obj = _cJsonToPocket(vm, tree); + cJSON_Delete(tree); + + // Json is a standard libray "std_json" and has the direct access + // to the vm's internal stack. + vm->fiber->ret[0] = obj; +} + +/*****************************************************************************/ +/* MODULE REGISTER */ +/*****************************************************************************/ + +void registerModuleJson(PKVM* vm) { + PkHandle* json = pkNewModule(vm, "json"); + + pkModuleAddFunction(vm, json, "parse", _jsonParse, 1); + + pkRegisterModule(vm, json); + pkReleaseHandle(vm, json); +} diff --git a/src/libs/std_path.c b/src/libs/std_path.c index 2344d71..db1373f 100644 --- a/src/libs/std_path.c +++ b/src/libs/std_path.c @@ -13,9 +13,8 @@ // check if it's MSVC (and tcc in windows) or not for posix headers and // Refactor the bellow macro includes. -#define _CWALK_IMPL #include "thirdparty/cwalk/cwalk.h" //<< AMALG_INLINE >> -#undef _CWALK_IMPL +#include "thirdparty/cwalk/cwalk.c" //<< AMALG_INLINE >> #include @@ -94,14 +93,25 @@ static char* tryImportPaths(PKVM* vm, char* path, char* buff) { size_t path_size = 0; static const char* EXT[] = { + // Path can already end with '.pk' or anything when running from + // pkRunFile() so it's mandatory for the bellow empty string. "", + ".pk", "/_init.pk", + #ifndef PK_NO_DL - #ifdef _WIN32 + #if defined(_WIN32) ".dll", - #else + "/_init.dll", + + #elif defined(__APPLE__) + ".dylib", + "/_init.dylib", + + #elif defined(__linux__) ".so", + "/_init.so", #endif #endif NULL, // Sentinal to mark the array end. diff --git a/src/libs/thirdparty/cJSON/cJSON.c b/src/libs/thirdparty/cJSON/cJSON.c new file mode 100644 index 0000000..524ba46 --- /dev/null +++ b/src/libs/thirdparty/cJSON/cJSON.c @@ -0,0 +1,3119 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} diff --git a/src/libs/thirdparty/cJSON/cJSON.h b/src/libs/thirdparty/cJSON/cJSON.h new file mode 100644 index 0000000..95a9cf6 --- /dev/null +++ b/src/libs/thirdparty/cJSON/cJSON.h @@ -0,0 +1,300 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: + +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol + +For *nix builds that support visibility attribute, you can define similar behavior by + +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS + +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does + +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 15 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable address area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ +#define cJSON_SetBoolValue(object, boolValue) ( \ + (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ + (object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \ + cJSON_Invalid\ +) + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/thirdparty/cwalk/cwalk.c b/src/libs/thirdparty/cwalk/cwalk.c new file mode 100644 index 0000000..0dcddb0 --- /dev/null +++ b/src/libs/thirdparty/cwalk/cwalk.c @@ -0,0 +1,1483 @@ +#include +#include +// -- pocketlang start -- +#ifndef PK_AMALGAMATED +#include "cwalk.h" //<< AMALG_IGNORE >> +#endif +// -- pocketlang end -- +#include +#include +#include + +/** + * We try to default to a different path style depending on the operating + * system. So this should detect whether we should use windows or unix paths. + */ +#if defined(WIN32) || defined(_WIN32) || \ + defined(__WIN32) && !defined(__CYGWIN__) +static enum cwk_path_style path_style = CWK_STYLE_WINDOWS; +#else +static enum cwk_path_style path_style = CWK_STYLE_UNIX; +#endif + +/** + * This is a list of separators used in different styles. Windows can read + * multiple separators, but it generally outputs just a backslash. The output + * will always use the first character for the output. + */ +static const char *separators[] = { + "\\/", // CWK_STYLE_WINDOWS + "/" // CWK_STYLE_UNIX +}; + +/** + * A joined path represents multiple path strings which are concatenated, but + * not (necessarily) stored in contiguous memory. The joined path allows to + * iterate over the segments as if it was one piece of path. + */ +struct cwk_segment_joined +{ + struct cwk_segment segment; + const char **paths; + size_t path_index; +}; + +static size_t cwk_path_output_sized(char *buffer, size_t buffer_size, + size_t position, const char *str, size_t length) +{ + size_t amount_written; + + // First we determine the amount which we can write to the buffer. There are + // three cases. In the first case we have enough to store the whole string in + // it. In the second one we can only store a part of it, and in the third we + // have no space left. + if (buffer_size > position + length) { + amount_written = length; + } else if (buffer_size > position) { + amount_written = buffer_size - position; + } else { + amount_written = 0; + } + + // If we actually want to write out something we will do that here. We will + // always append a '\0', this way we are guaranteed to have a valid string at + // all times. + if (amount_written > 0) { + memmove(&buffer[position], str, amount_written); + } + + // Return the theoretical length which would have been written when everything + // would have fit in the buffer. + return length; +} + +static size_t cwk_path_output_current(char *buffer, size_t buffer_size, + size_t position) +{ + // We output a "current" directory, which is a single character. This + // character is currently not style dependant. + return cwk_path_output_sized(buffer, buffer_size, position, ".", 1); +} + +static size_t cwk_path_output_back(char *buffer, size_t buffer_size, + size_t position) +{ + // We output a "back" directory, which ahs two characters. This + // character is currently not style dependant. + return cwk_path_output_sized(buffer, buffer_size, position, "..", 2); +} + +static size_t cwk_path_output_separator(char *buffer, size_t buffer_size, + size_t position) +{ + // We output a separator, which is a single character. + return cwk_path_output_sized(buffer, buffer_size, position, + separators[path_style], 1); +} + +static size_t cwk_path_output_dot(char *buffer, size_t buffer_size, + size_t position) +{ + // We output a dot, which is a single character. This is used for extensions. + return cwk_path_output_sized(buffer, buffer_size, position, ".", 1); +} + +static size_t cwk_path_output(char *buffer, size_t buffer_size, size_t position, + const char *str) +{ + size_t length; + + // This just does a sized output internally, but first measuring the + // null-terminated string. + length = strlen(str); + return cwk_path_output_sized(buffer, buffer_size, position, str, length); +} + +static void cwk_path_terminate_output(char *buffer, size_t buffer_size, + size_t pos) +{ + if (buffer_size > 0) { + if (pos >= buffer_size) { + buffer[buffer_size - 1] = '\0'; + } else { + buffer[pos] = '\0'; + } + } +} + +static bool cwk_path_is_string_equal(const char *first, const char *second, + size_t first_size, size_t second_size) +{ + bool are_both_separators; + + // The two strings are not equal if the sizes are not equal. + if (first_size != second_size) { + return false; + } + + // If the path style is UNIX, we will compare case sensitively. This can be + // done easily using strncmp. + if (path_style == CWK_STYLE_UNIX) { + return strncmp(first, second, first_size) == 0; + } + + // However, if this is windows we will have to compare case insensitively. + // Since there is no standard method to do that we will have to do it on our + // own. + while (*first && *second && first_size > 0) { + // We can consider the string to be not equal if the two lowercase + // characters are not equal. The two chars may also be separators, which + // means they would be equal. + are_both_separators = strchr(separators[path_style], *first) != NULL && + strchr(separators[path_style], *second) != NULL; + + if (tolower(*first) != tolower(*second) && !are_both_separators) { + return false; + } + + first++; + second++; + + --first_size; + } + + // The string must be equal since they both have the same length and all the + // characters are the same. + return true; +} + +static const char *cwk_path_find_next_stop(const char *c) +{ + // We just move forward until we find a '\0' or a separator, which will be our + // next "stop". + while (*c != '\0' && !cwk_path_is_separator(c)) { + ++c; + } + + // Return the pointer of the next stop. + return c; +} + +static const char *cwk_path_find_previous_stop(const char *begin, const char *c) +{ + // We just move back until we find a separator or reach the beginning of the + // path, which will be our previous "stop". + while (c > begin && !cwk_path_is_separator(c)) { + --c; + } + + // Return the pointer to the previous stop. We have to return the first + // character after the separator, not on the separator itself. + if (cwk_path_is_separator(c)) { + return c + 1; + } else { + return c; + } +} + +static bool cwk_path_get_first_segment_without_root(const char *path, + const char *segments, struct cwk_segment *segment) +{ + // Let's remember the path. We will move the path pointer afterwards, that's + // why this has to be done first. + segment->path = path; + segment->segments = segments; + segment->begin = segments; + segment->end = segments; + segment->size = 0; + + // Now let's check whether this is an empty string. An empty string has no + // segment it could use. + if (*segments == '\0') { + return false; + } + + // If the string starts with separators, we will jump over those. If there is + // only a slash and a '\0' after it, we can't determine the first segment + // since there is none. + while (cwk_path_is_separator(segments)) { + ++segments; + if (*segments == '\0') { + return false; + } + } + + // So this is the beginning of our segment. + segment->begin = segments; + + // Now let's determine the end of the segment, which we do by moving the path + // pointer further until we find a separator. + segments = cwk_path_find_next_stop(segments); + + // And finally, calculate the size of the segment by subtracting the position + // from the end. + segment->size = (size_t)(segments - segment->begin); + segment->end = segments; + + // Tell the caller that we found a segment. + return true; +} + +static bool cwk_path_get_last_segment_without_root(const char *path, + struct cwk_segment *segment) +{ + // Now this is fairly similar to the normal algorithm, however, it will assume + // that there is no root in the path. So we grab the first segment at this + // position, assuming there is no root. + if (!cwk_path_get_first_segment_without_root(path, path, segment)) { + return false; + } + + // Now we find our last segment. The segment struct of the caller + // will contain the last segment, since the function we call here will not + // change the segment struct when it reaches the end. + while (cwk_path_get_next_segment(segment)) { + // We just loop until there is no other segment left. + } + + return true; +} + +static bool cwk_path_get_first_segment_joined(const char **paths, + struct cwk_segment_joined *sj) +{ + bool result; + + // Prepare the first segment. We position the joined segment on the first path + // and assign the path array to the struct. + sj->path_index = 0; + sj->paths = paths; + + // We loop through all paths until we find one which has a segment. The result + // is stored in a variable, so we can let the caller know whether we found one + // or not. + result = false; + while (paths[sj->path_index] != NULL && + (result = cwk_path_get_first_segment(paths[sj->path_index], + &sj->segment)) == false) { + ++sj->path_index; + } + + return result; +} + +static bool cwk_path_get_next_segment_joined(struct cwk_segment_joined *sj) +{ + bool result; + + if (sj->paths[sj->path_index] == NULL) { + // We reached already the end of all paths, so there is no other segment + // left. + return false; + } else if (cwk_path_get_next_segment(&sj->segment)) { + // There was another segment on the current path, so we are good to + // continue. + return true; + } + + // We try to move to the next path which has a segment available. We must at + // least move one further since the current path reached the end. + result = false; + + do { + ++sj->path_index; + + // And we obviously have to stop this loop if there are no more paths left. + if (sj->paths[sj->path_index] == NULL) { + break; + } + + // Grab the first segment of the next path and determine whether this path + // has anything useful in it. There is one more thing we have to consider + // here - for the first time we do this we want to skip the root, but + // afterwards we will consider that to be part of the segments. + result = cwk_path_get_first_segment_without_root(sj->paths[sj->path_index], + sj->paths[sj->path_index], &sj->segment); + + } while (!result); + + // Finally, report the result back to the caller. + return result; +} + +static bool cwk_path_get_previous_segment_joined(struct cwk_segment_joined *sj) +{ + bool result; + + if (*sj->paths == NULL) { + // It's possible that there is no initialized segment available in the + // struct since there are no paths. In that case we can return false, since + // there is no previous segment. + return false; + } else if (cwk_path_get_previous_segment(&sj->segment)) { + // Now we try to get the previous segment from the current path. If we can + // do that successfully, we can let the caller know that we found one. + return true; + } + + result = false; + + do { + // We are done once we reached index 0. In that case there are no more + // segments left. + if (sj->path_index == 0) { + break; + } + + // There is another path which we have to inspect. So we decrease the path + // index. + --sj->path_index; + + // If this is the first path we will have to consider that this path might + // include a root, otherwise we just treat is as a segment. + if (sj->path_index == 0) { + result = cwk_path_get_last_segment(sj->paths[sj->path_index], + &sj->segment); + } else { + result = cwk_path_get_last_segment_without_root(sj->paths[sj->path_index], + &sj->segment); + } + + } while (!result); + + return result; +} + +static bool cwk_path_segment_back_will_be_removed(struct cwk_segment_joined *sj) +{ + enum cwk_segment_type type; + int counter; + + // We are handling back segments here. We must verify how many back segments + // and how many normal segments come before this one to decide whether we keep + // or remove it. + + // The counter determines how many normal segments are our current segment, + // which will popped off before us. If the counter goes above zero it means + // that our segment will be popped as well. + counter = 0; + + // We loop over all previous segments until we either reach the beginning, + // which means our segment will not be dropped or the counter goes above zero. + while (cwk_path_get_previous_segment_joined(sj)) { + + // Now grab the type. The type determines whether we will increase or + // decrease the counter. We don't handle a CWK_CURRENT frame here since it + // has no influence. + type = cwk_path_get_segment_type(&sj->segment); + if (type == CWK_NORMAL) { + // This is a normal segment. The normal segment will increase the counter + // since it neutralizes one back segment. If we go above zero we can + // return immediately. + ++counter; + if (counter > 0) { + return true; + } + } else if (type == CWK_BACK) { + // A CWK_BACK segment will reduce the counter by one. We can not remove a + // back segment as long we are not above zero since we don't have the + // opposite normal segment which we would remove. + --counter; + } + } + + // We never got a count larger than zero, so we will keep this segment alive. + return false; +} + +static bool cwk_path_segment_normal_will_be_removed( + struct cwk_segment_joined *sj) +{ + enum cwk_segment_type type; + int counter; + + // The counter determines how many segments are above our current segment, + // which will popped off before us. If the counter goes below zero it means + // that our segment will be popped as well. + counter = 0; + + // We loop over all following segments until we either reach the end, which + // means our segment will not be dropped or the counter goes below zero. + while (cwk_path_get_next_segment_joined(sj)) { + + // First, grab the type. The type determines whether we will increase or + // decrease the counter. We don't handle a CWK_CURRENT frame here since it + // has no influence. + type = cwk_path_get_segment_type(&sj->segment); + if (type == CWK_NORMAL) { + // This is a normal segment. The normal segment will increase the counter + // since it will be removed by a "../" before us. + ++counter; + } else if (type == CWK_BACK) { + // A CWK_BACK segment will reduce the counter by one. If we are below zero + // we can return immediately. + --counter; + if (counter < 0) { + return true; + } + } + } + + // We never got a negative count, so we will keep this segment alive. + return false; +} + +static bool +cwk_path_segment_will_be_removed(const struct cwk_segment_joined *sj, + bool absolute) +{ + enum cwk_segment_type type; + struct cwk_segment_joined sjc; + + // We copy the joined path so we don't need to modify it. + sjc = *sj; + + // First we check whether this is a CWK_CURRENT or CWK_BACK segment, since + // those will always be dropped. + type = cwk_path_get_segment_type(&sj->segment); + if (type == CWK_CURRENT || (type == CWK_BACK && absolute)) { + return true; + } else if (type == CWK_BACK) { + return cwk_path_segment_back_will_be_removed(&sjc); + } else { + return cwk_path_segment_normal_will_be_removed(&sjc); + } +} + +static bool +cwk_path_segment_joined_skip_invisible(struct cwk_segment_joined *sj, + bool absolute) +{ + while (cwk_path_segment_will_be_removed(sj, absolute)) { + if (!cwk_path_get_next_segment_joined(sj)) { + return false; + } + } + + return true; +} + +static void cwk_path_get_root_windows(const char *path, size_t *length) +{ + const char *c; + bool is_device_path; + + // We can not determine the root if this is an empty string. So we set the + // root to NULL and the length to zero and cancel the whole thing. + c = path; + *length = 0; + if (!*c) { + return; + } + + // Now we have to verify whether this is a windows network path (UNC), which + // we will consider our root. + if (cwk_path_is_separator(c)) { + ++c; + + // Check whether the path starts with a single backslash, which means this + // is not a network path - just a normal path starting with a backslash. + if (!cwk_path_is_separator(c)) { + // Okay, this is not a network path but we still use the backslash as a + // root. + ++(*length); + return; + } + + // A device path is a path which starts with "\\." or "\\?". A device path + // can be a UNC path as well, in which case it will take up one more + // segment. So, this is a network or device path. Skip the previous + // separator. Now we need to determine whether this is a device path. We + // might advance one character here if the server name starts with a '?' or + // a '.', but that's fine since we will search for a separator afterwards + // anyway. + ++c; + is_device_path = (*c == '?' || *c == '.') && cwk_path_is_separator(++c); + if (is_device_path) { + // That's a device path, and the root must be either "\\.\" or "\\?\" + // which is 4 characters long. (at least that's how Windows + // GetFullPathName behaves.) + *length = 4; + return; + } + + // We will grab anything up to the next stop. The next stop might be a '\0' + // or another separator. That will be the server name. + c = cwk_path_find_next_stop(c); + + // If this is a separator and not the end of a string we wil have to include + // it. However, if this is a '\0' we must not skip it. + while (cwk_path_is_separator(c)) { + ++c; + } + + // We are now skipping the shared folder name, which will end after the + // next stop. + c = cwk_path_find_next_stop(c); + + // Then there might be a separator at the end. We will include that as well, + // it will mark the path as absolute. + if (cwk_path_is_separator(c)) { + ++c; + } + + // Finally, calculate the size of the root. + *length = (size_t)(c - path); + return; + } + + // Move to the next and check whether this is a colon. + if (*++c == ':') { + *length = 2; + + // Now check whether this is a backslash (or slash). If it is not, we could + // assume that the next character is a '\0' if it is a valid path. However, + // we will not assume that - since ':' is not valid in a path it must be a + // mistake by the caller than. We will try to understand it anyway. + if (cwk_path_is_separator(++c)) { + *length = 3; + } + } +} + +static void cwk_path_get_root_unix(const char *path, size_t *length) +{ + // The slash of the unix path represents the root. There is no root if there + // is no slash. + if (cwk_path_is_separator(path)) { + *length = 1; + } else { + *length = 0; + } +} + +static bool cwk_path_is_root_absolute(const char *path, size_t length) +{ + // This is definitely not absolute if there is no root. + if (length == 0) { + return false; + } + + // If there is a separator at the end of the root, we can safely consider this + // to be an absolute path. + return cwk_path_is_separator(&path[length - 1]); +} + +static void cwk_path_fix_root(char *buffer, size_t buffer_size, size_t length) +{ + size_t i; + + // This only affects windows. + if (path_style != CWK_STYLE_WINDOWS) { + return; + } + + // Make sure we are not writing further than we are actually allowed to. + if (length > buffer_size) { + length = buffer_size; + } + + // Replace all forward slashes with backwards slashes. Since this is windows + // we can't have any forward slashes in the root. + for (i = 0; i < length; ++i) { + if (cwk_path_is_separator(&buffer[i])) { + buffer[i] = *separators[CWK_STYLE_WINDOWS]; + } + } +} + +static size_t cwk_path_join_and_normalize_multiple(const char **paths, + char *buffer, size_t buffer_size) +{ + size_t pos; + bool absolute, has_segment_output; + struct cwk_segment_joined sj; + + // We initialize the position after the root, which should get us started. + cwk_path_get_root(paths[0], &pos); + + // Determine whether the path is absolute or not. We need that to determine + // later on whether we can remove superfluous "../" or not. + absolute = cwk_path_is_root_absolute(paths[0], pos); + + // First copy the root to the output. After copying, we will normalize the + // root. + cwk_path_output_sized(buffer, buffer_size, 0, paths[0], pos); + cwk_path_fix_root(buffer, buffer_size, pos); + + // So we just grab the first segment. If there is no segment we will always + // output a "/", since we currently only support absolute paths here. + if (!cwk_path_get_first_segment_joined(paths, &sj)) { + goto done; + } + + // Let's assume that we don't have any segment output for now. We will toggle + // this flag once there is some output. + has_segment_output = false; + + do { + // Check whether we have to drop this segment because of resolving a + // relative path or because it is a CWK_CURRENT segment. + if (cwk_path_segment_will_be_removed(&sj, absolute)) { + continue; + } + + // We add a separator if we previously wrote a segment. The last segment + // must not have a trailing separator. This must happen before the segment + // output, since we would override the null terminating character with + // reused buffers if this was done afterwards. + if (has_segment_output) { + pos += cwk_path_output_separator(buffer, buffer_size, pos); + } + + // Remember that we have segment output, so we can handle the trailing slash + // later on. This is necessary since we might have segments but they are all + // removed. + has_segment_output = true; + + // Write out the segment but keep in mind that we need to follow the + // buffer size limitations. That's why we use the path output functions + // here. + pos += cwk_path_output_sized(buffer, buffer_size, pos, sj.segment.begin, + sj.segment.size); + } while (cwk_path_get_next_segment_joined(&sj)); + + // Remove the trailing slash, but only if we have segment output. We don't + // want to remove anything from the root. + if (!has_segment_output && pos == 0) { + // This may happen if the path is absolute and all segments have been + // removed. We can not have an empty output - and empty output means we stay + // in the current directory. So we will output a ".". + assert(absolute == false); + pos += cwk_path_output_current(buffer, buffer_size, pos); + } + + // We must append a '\0' in any case, unless the buffer size is zero. If the + // buffer size is zero, which means we can not. +done: + cwk_path_terminate_output(buffer, buffer_size, pos); + + // And finally let our caller know about the total size of the normalized + // path. + return pos; +} + +size_t cwk_path_get_absolute(const char *base, const char *path, char *buffer, + size_t buffer_size) +{ + size_t i; + const char *paths[4]; + + // The basename should be an absolute path if the caller is using the API + // correctly. However, he might not and in that case we will append a fake + // root at the beginning. + if (cwk_path_is_absolute(base)) { + i = 0; + } else if (path_style == CWK_STYLE_WINDOWS) { + paths[0] = "\\"; + i = 1; + } else { + paths[0] = "/"; + i = 1; + } + + if (cwk_path_is_absolute(path)) { + // If the submitted path is not relative the base path becomes irrelevant. + // We will only normalize the submitted path instead. + paths[i++] = path; + paths[i] = NULL; + } else { + // Otherwise we append the relative path to the base path and normalize it. + // The result will be a new absolute path. + paths[i++] = base; + paths[i++] = path; + paths[i] = NULL; + } + + // Finally join everything together and normalize it. + return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); +} + +static void cwk_path_skip_segments_until_diverge(struct cwk_segment_joined *bsj, + struct cwk_segment_joined *osj, bool absolute, bool *base_available, + bool *other_available) +{ + // Now looping over all segments until they start to diverge. A path may + // diverge if two segments are not equal or if one path reaches the end. + do { + + // Check whether there is anything available after we skip everything which + // is invisible. We do that for both paths, since we want to let the caller + // know which path has some trailing segments after they diverge. + *base_available = cwk_path_segment_joined_skip_invisible(bsj, absolute); + *other_available = cwk_path_segment_joined_skip_invisible(osj, absolute); + + // We are done if one or both of those paths reached the end. They either + // diverge or both reached the end - but in both cases we can not continue + // here. + if (!*base_available || !*other_available) { + break; + } + + // Compare the content of both segments. We are done if they are not equal, + // since they diverge. + if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin, + bsj->segment.size, osj->segment.size)) { + break; + } + + // We keep going until one of those segments reached the end. The next + // segment might be invisible, but we will check for that in the beginning + // of the loop once again. + *base_available = cwk_path_get_next_segment_joined(bsj); + *other_available = cwk_path_get_next_segment_joined(osj); + } while (*base_available && *other_available); +} + +size_t cwk_path_get_relative(const char *base_directory, const char *path, + char *buffer, size_t buffer_size) +{ + size_t pos, base_root_length, path_root_length; + bool absolute, base_available, other_available, has_output; + const char *base_paths[2], *other_paths[2]; + struct cwk_segment_joined bsj, osj; + + pos = 0; + + // First we compare the roots of those two paths. If the roots are not equal + // we can't continue, since there is no way to get a relative path from + // different roots. + cwk_path_get_root(base_directory, &base_root_length); + cwk_path_get_root(path, &path_root_length); + if (base_root_length != path_root_length || + !cwk_path_is_string_equal(base_directory, path, base_root_length, + path_root_length)) { + cwk_path_terminate_output(buffer, buffer_size, pos); + return pos; + } + + // Verify whether this is an absolute path. We need to know that since we can + // remove all back-segments if it is. + absolute = cwk_path_is_root_absolute(base_directory, base_root_length); + + // Initialize our joined segments. This will allow us to use the internal + // functions to skip until diverge and invisible. We only have one path in + // them though. + base_paths[0] = base_directory; + base_paths[1] = NULL; + other_paths[0] = path; + other_paths[1] = NULL; + cwk_path_get_first_segment_joined(base_paths, &bsj); + cwk_path_get_first_segment_joined(other_paths, &osj); + + // Okay, now we skip until the segments diverge. We don't have anything to do + // with the segments which are equal. + cwk_path_skip_segments_until_diverge(&bsj, &osj, absolute, &base_available, + &other_available); + + // Assume there is no output until we have got some. We will need this + // information later on to remove trailing slashes or alternatively output a + // current-segment. + has_output = false; + + // So if we still have some segments left in the base path we will now output + // a back segment for all of them. + if (base_available) { + do { + // Skip any invisible segment. We don't care about those and we don't need + // to navigate back because of them. + if (!cwk_path_segment_joined_skip_invisible(&bsj, absolute)) { + break; + } + + // Toggle the flag if we have output. We need to remember that, since we + // want to remove the trailing slash. + has_output = true; + + // Output the back segment and a separator. No need to worry about the + // superfluous segment since it will be removed later on. + pos += cwk_path_output_back(buffer, buffer_size, pos); + pos += cwk_path_output_separator(buffer, buffer_size, pos); + } while (cwk_path_get_next_segment_joined(&bsj)); + } + + // And if we have some segments available of the target path we will output + // all of those. + if (other_available) { + do { + // Again, skip any invisible segments since we don't need to navigate into + // them. + if (!cwk_path_segment_joined_skip_invisible(&osj, absolute)) { + break; + } + + // Toggle the flag if we have output. We need to remember that, since we + // want to remove the trailing slash. + has_output = true; + + // Output the current segment and a separator. No need to worry about the + // superfluous segment since it will be removed later on. + pos += cwk_path_output_sized(buffer, buffer_size, pos, osj.segment.begin, + osj.segment.size); + pos += cwk_path_output_separator(buffer, buffer_size, pos); + } while (cwk_path_get_next_segment_joined(&osj)); + } + + // If we have some output by now we will have to remove the trailing slash. We + // simply do that by moving back one character. The terminate output function + // will then place the '\0' on this position. Otherwise, if there is no + // output, we will have to output a "current directory", since the target path + // points to the base path. + if (has_output) { + --pos; + } else { + pos += cwk_path_output_current(buffer, buffer_size, pos); + } + + // Finally, we can terminate the output - which means we place a '\0' at the + // current position or at the end of the buffer. + cwk_path_terminate_output(buffer, buffer_size, pos); + + return pos; +} + +size_t cwk_path_join(const char *path_a, const char *path_b, char *buffer, + size_t buffer_size) +{ + const char *paths[3]; + + // This is simple. We will just create an array with the two paths which we + // wish to join. + paths[0] = path_a; + paths[1] = path_b; + paths[2] = NULL; + + // And then call the join and normalize function which will do the hard work + // for us. + return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); +} + +size_t cwk_path_join_multiple(const char **paths, char *buffer, + size_t buffer_size) +{ + // We can just call the internal join and normalize function for this one, + // since it will handle everything. + return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); +} + +void cwk_path_get_root(const char *path, size_t *length) +{ + // We use a different implementation here based on the configuration of the + // library. + if (path_style == CWK_STYLE_WINDOWS) { + cwk_path_get_root_windows(path, length); + } else { + cwk_path_get_root_unix(path, length); + } +} + +size_t cwk_path_change_root(const char *path, const char *new_root, + char *buffer, size_t buffer_size) +{ + const char *tail; + size_t root_length, path_length, tail_length, new_root_length, new_path_size; + + // First we need to determine the actual size of the root which we will + // change. + cwk_path_get_root(path, &root_length); + + // Now we determine the sizes of the new root and the path. We need that to + // determine the size of the part after the root (the tail). + new_root_length = strlen(new_root); + path_length = strlen(path); + + // Okay, now we calculate the position of the tail and the length of it. + tail = path + root_length; + tail_length = path_length - root_length; + + // We first output the tail and then the new root, that's because the source + // path and the buffer may be overlapping. This way the root will not + // overwrite the tail. + cwk_path_output_sized(buffer, buffer_size, new_root_length, tail, + tail_length); + cwk_path_output_sized(buffer, buffer_size, 0, new_root, new_root_length); + + // Finally we calculate the size o the new path and terminate the output with + // a '\0'. + new_path_size = tail_length + new_root_length; + cwk_path_terminate_output(buffer, buffer_size, new_path_size); + + return new_path_size; +} + +bool cwk_path_is_absolute(const char *path) +{ + size_t length; + + // We grab the root of the path. This root does not include the first + // separator of a path. + cwk_path_get_root(path, &length); + + // Now we can determine whether the root is absolute or not. + return cwk_path_is_root_absolute(path, length); +} + +bool cwk_path_is_relative(const char *path) +{ + // The path is relative if it is not absolute. + return !cwk_path_is_absolute(path); +} + +void cwk_path_get_basename(const char *path, const char **basename, + size_t *length) +{ + struct cwk_segment segment; + + // We get the last segment of the path. The last segment will contain the + // basename if there is any. If there are no segments we will set the basename + // to NULL and the length to 0. + if (!cwk_path_get_last_segment(path, &segment)) { + *basename = NULL; + if (length) { + *length = 0; + } + return; + } + + // Now we can just output the segment contents, since that's our basename. + // There might be trailing separators after the basename, but the size does + // not include those. + *basename = segment.begin; + if (length) { + *length = segment.size; + } +} + +size_t cwk_path_change_basename(const char *path, const char *new_basename, + char *buffer, size_t buffer_size) +{ + struct cwk_segment segment; + size_t pos, root_size, new_basename_size; + + // First we try to get the last segment. We may only have a root without any + // segments, in which case we will create one. + if (!cwk_path_get_last_segment(path, &segment)) { + + // So there is no segment in this path. First we grab the root and output + // that. We are not going to modify the root in any way. + cwk_path_get_root(path, &root_size); + pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size); + + // We have to trim the separators from the beginning of the new basename. + // This is quite easy to do. + while (cwk_path_is_separator(new_basename)) { + ++new_basename; + } + + // Now we measure the length of the new basename, this is a two step + // process. First we find the '\0' character at the end of the string. + new_basename_size = 0; + while (new_basename[new_basename_size]) { + ++new_basename_size; + } + + // And then we trim the separators at the end of the basename until we reach + // the first valid character. + while (new_basename_size > 0 && + cwk_path_is_separator(&new_basename[new_basename_size - 1])) { + --new_basename_size; + } + + // Now we will output the new basename after the root. + pos += cwk_path_output_sized(buffer, buffer_size, pos, new_basename, + new_basename_size); + + // And finally terminate the output and return the total size of the path. + cwk_path_terminate_output(buffer, buffer_size, pos); + return pos; + } + + // If there is a last segment we can just forward this call, which is fairly + // easy. + return cwk_path_change_segment(&segment, new_basename, buffer, buffer_size); +} + +void cwk_path_get_dirname(const char *path, size_t *length) +{ + struct cwk_segment segment; + + // We get the last segment of the path. The last segment will contain the + // basename if there is any. If there are no segments we will set the length + // to 0. + if (!cwk_path_get_last_segment(path, &segment)) { + *length = 0; + return; + } + + // We can now return the length from the beginning of the string up to the + // beginning of the last segment. + *length = (size_t)(segment.begin - path); +} + +bool cwk_path_get_extension(const char *path, const char **extension, + size_t *length) +{ + struct cwk_segment segment; + const char *c; + + // We get the last segment of the path. The last segment will contain the + // extension if there is any. + if (!cwk_path_get_last_segment(path, &segment)) { + return false; + } + + // Now we search for a dot within the segment. If there is a dot, we consider + // the rest of the segment the extension. We do this from the end towards the + // beginning, since we want to find the last dot. + for (c = segment.end; c >= segment.begin; --c) { + if (*c == '.') { + // Okay, we found an extension. We can stop looking now. + *extension = c; + *length = (size_t)(segment.end - c); + return true; + } + } + + // We couldn't find any extension. + return false; +} + +bool cwk_path_has_extension(const char *path) +{ + const char *extension; + size_t length; + + // We just wrap the get_extension call which will then do the work for us. + return cwk_path_get_extension(path, &extension, &length); +} + +size_t cwk_path_change_extension(const char *path, const char *new_extension, + char *buffer, size_t buffer_size) +{ + struct cwk_segment segment; + const char *c, *old_extension; + size_t pos, root_size, trail_size, new_extension_size; + + // First we try to get the last segment. We may only have a root without any + // segments, in which case we will create one. + if (!cwk_path_get_last_segment(path, &segment)) { + + // So there is no segment in this path. First we grab the root and output + // that. We are not going to modify the root in any way. If there is no + // root, this will end up with a root size 0, and nothing will be written. + cwk_path_get_root(path, &root_size); + pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size); + + // Add a dot if the submitted value doesn't have any. + if (*new_extension != '.') { + pos += cwk_path_output_dot(buffer, buffer_size, pos); + } + + // And finally terminate the output and return the total size of the path. + pos += cwk_path_output(buffer, buffer_size, pos, new_extension); + cwk_path_terminate_output(buffer, buffer_size, pos); + return pos; + } + + // Now we seek the old extension in the last segment, which we will replace + // with the new one. If there is no old extension, it will point to the end of + // the segment. + old_extension = segment.end; + for (c = segment.begin; c < segment.end; ++c) { + if (*c == '.') { + old_extension = c; + } + } + + pos = cwk_path_output_sized(buffer, buffer_size, 0, segment.path, + (size_t)(old_extension - segment.path)); + + // If the new extension starts with a dot, we will skip that dot. We always + // output exactly one dot before the extension. If the extension contains + // multiple dots, we will output those as part of the extension. + if (*new_extension == '.') { + ++new_extension; + } + + // We calculate the size of the new extension, including the dot, in order to + // output the trail - which is any part of the path coming after the + // extension. We must output this first, since the buffer may overlap with the + // submitted path - and it would be overridden by longer extensions. + new_extension_size = strlen(new_extension) + 1; + trail_size = cwk_path_output(buffer, buffer_size, pos + new_extension_size, + segment.end); + + // Finally we output the dot and the new extension. The new extension itself + // doesn't contain the dot anymore, so we must output that first. + pos += cwk_path_output_dot(buffer, buffer_size, pos); + pos += cwk_path_output(buffer, buffer_size, pos, new_extension); + + // Now we terminate the output with a null-terminating character, but before + // we do that we must add the size of the trail to the position which we + // output before. + pos += trail_size; + cwk_path_terminate_output(buffer, buffer_size, pos); + + // And the position is our output size now. + return pos; +} + +size_t cwk_path_normalize(const char *path, char *buffer, size_t buffer_size) +{ + const char *paths[2]; + + // Now we initialize the paths which we will normalize. Since this function + // only supports submitting a single path, we will only add that one. + paths[0] = path; + paths[1] = NULL; + + return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); +} + +size_t cwk_path_get_intersection(const char *path_base, const char *path_other) +{ + bool absolute; + size_t base_root_length, other_root_length; + const char *end; + const char *paths_base[2], *paths_other[2]; + struct cwk_segment_joined base, other; + + // We first compare the two roots. We just return zero if they are not equal. + // This will also happen to return zero if the paths are mixed relative and + // absolute. + cwk_path_get_root(path_base, &base_root_length); + cwk_path_get_root(path_other, &other_root_length); + if (!cwk_path_is_string_equal(path_base, path_other, base_root_length, + other_root_length)) { + return 0; + } + + // Configure our paths. We just have a single path in here for now. + paths_base[0] = path_base; + paths_base[1] = NULL; + paths_other[0] = path_other; + paths_other[1] = NULL; + + // So we get the first segment of both paths. If one of those paths don't have + // any segment, we will return 0. + if (!cwk_path_get_first_segment_joined(paths_base, &base) || + !cwk_path_get_first_segment_joined(paths_other, &other)) { + return base_root_length; + } + + // We now determine whether the path is absolute or not. This is required + // because if will ignore removed segments, and this behaves differently if + // the path is absolute. However, we only need to check the base path because + // we are guaranteed that both paths are either relative or absolute. + absolute = cwk_path_is_root_absolute(path_base, base_root_length); + + // We must keep track of the end of the previous segment. Initially, this is + // set to the beginning of the path. This means that 0 is returned if the + // first segment is not equal. + end = path_base + base_root_length; + + // Now we loop over both segments until one of them reaches the end or their + // contents are not equal. + do { + // We skip all segments which will be removed in each path, since we want to + // know about the true path. + if (!cwk_path_segment_joined_skip_invisible(&base, absolute) || + !cwk_path_segment_joined_skip_invisible(&other, absolute)) { + break; + } + + if (!cwk_path_is_string_equal(base.segment.begin, other.segment.begin, + base.segment.size, other.segment.size)) { + // So the content of those two segments are not equal. We will return the + // size up to the beginning. + return (size_t)(end - path_base); + } + + // Remember the end of the previous segment before we go to the next one. + end = base.segment.end; + } while (cwk_path_get_next_segment_joined(&base) && + cwk_path_get_next_segment_joined(&other)); + + // Now we calculate the length up to the last point where our paths pointed to + // the same place. + return (size_t)(end - path_base); +} + +bool cwk_path_get_first_segment(const char *path, struct cwk_segment *segment) +{ + size_t length; + const char *segments; + + // We skip the root since that's not part of the first segment. The root is + // treated as a separate entity. + cwk_path_get_root(path, &length); + segments = path + length; + + // Now, after we skipped the root we can continue and find the actual segment + // content. + return cwk_path_get_first_segment_without_root(path, segments, segment); +} + +bool cwk_path_get_last_segment(const char *path, struct cwk_segment *segment) +{ + // We first grab the first segment. This might be our last segment as well, + // but we don't know yet. There is no last segment if there is no first + // segment, so we return false in that case. + if (!cwk_path_get_first_segment(path, segment)) { + return false; + } + + // Now we find our last segment. The segment struct of the caller + // will contain the last segment, since the function we call here will not + // change the segment struct when it reaches the end. + while (cwk_path_get_next_segment(segment)) { + // We just loop until there is no other segment left. + } + + return true; +} + +bool cwk_path_get_next_segment(struct cwk_segment *segment) +{ + const char *c; + + // First we jump to the end of the previous segment. The first character must + // be either a '\0' or a separator. + c = segment->begin + segment->size; + if (*c == '\0') { + return false; + } + + // Now we skip all separator until we reach something else. We are not yet + // guaranteed to have a segment, since the string could just end afterwards. + assert(cwk_path_is_separator(c)); + do { + ++c; + } while (cwk_path_is_separator(c)); + + // If the string ends here, we can safely assume that there is no other + // segment after this one. + if (*c == '\0') { + return false; + } + + // Now we are safe to assume there is a segment. We store the beginning of + // this segment in the segment struct of the caller. + segment->begin = c; + + // And now determine the size of this segment, and store it in the struct of + // the caller as well. + c = cwk_path_find_next_stop(c); + segment->end = c; + segment->size = (size_t)(c - segment->begin); + + // Tell the caller that we found a segment. + return true; +} + +bool cwk_path_get_previous_segment(struct cwk_segment *segment) +{ + const char *c; + + // The current position might point to the first character of the path, which + // means there are no previous segments available. + c = segment->begin; + if (c <= segment->segments) { + return false; + } + + // We move towards the beginning of the path until we either reached the + // beginning or the character is no separator anymore. + do { + --c; + if (c < segment->segments) { + // So we reached the beginning here and there is no segment. So we return + // false and don't change the segment structure submitted by the caller. + return false; + } + } while (cwk_path_is_separator(c)); + + // We are guaranteed now that there is another segment, since we moved before + // the previous separator and did not reach the segment path beginning. + segment->end = c + 1; + segment->begin = cwk_path_find_previous_stop(segment->segments, c); + segment->size = (size_t)(segment->end - segment->begin); + + return true; +} + +enum cwk_segment_type cwk_path_get_segment_type( + const struct cwk_segment *segment) +{ + // We just make a string comparison with the segment contents and return the + // appropriate type. + if (strncmp(segment->begin, ".", segment->size) == 0) { + return CWK_CURRENT; + } else if (strncmp(segment->begin, "..", segment->size) == 0) { + return CWK_BACK; + } + + return CWK_NORMAL; +} + +bool cwk_path_is_separator(const char *str) +{ + const char *c; + + // We loop over all characters in the read symbols. + c = separators[path_style]; + while (*c) { + if (*c == *str) { + return true; + } + + ++c; + } + + return false; +} + +size_t cwk_path_change_segment(struct cwk_segment *segment, const char *value, + char *buffer, size_t buffer_size) +{ + size_t pos, value_size, tail_size; + + // First we have to output the head, which is the whole string up to the + // beginning of the segment. This part of the path will just stay the same. + pos = cwk_path_output_sized(buffer, buffer_size, 0, segment->path, + (size_t)(segment->begin - segment->path)); + + // In order to trip the submitted value, we will skip any separator at the + // beginning of it and behave as if it was never there. + while (cwk_path_is_separator(value)) { + ++value; + } + + // Now we determine the length of the value. In order to do that we first + // locate the '\0'. + value_size = 0; + while (value[value_size]) { + ++value_size; + } + + // Since we trim separators at the beginning and in the end of the value we + // have to subtract from the size until there are either no more characters + // left or the last character is no separator. + while (value_size > 0 && cwk_path_is_separator(&value[value_size - 1])) { + --value_size; + } + + // We also have to determine the tail size, which is the part of the string + // following the current segment. This part will not change. + tail_size = strlen(segment->end); + + // Now we output the tail. We have to do that, because if the buffer and the + // source are overlapping we would override the tail if the value is + // increasing in length. + cwk_path_output_sized(buffer, buffer_size, pos + value_size, segment->end, + tail_size); + + // Finally we can output the value in the middle of the head and the tail, + // where we have enough space to fit the whole trimmed value. + pos += cwk_path_output_sized(buffer, buffer_size, pos, value, value_size); + + // Now we add the tail size to the current position and terminate the output - + // basically, ensure that there is a '\0' at the end of the buffer. + pos += tail_size; + cwk_path_terminate_output(buffer, buffer_size, pos); + + // And now tell the caller how long the whole path would be. + return pos; +} + +enum cwk_path_style cwk_path_guess_style(const char *path) +{ + const char *c; + size_t root_length; + struct cwk_segment segment; + + // First we determine the root. Only windows roots can be longer than a single + // slash, so if we can determine that it starts with something like "C:", we + // know that this is a windows path. + cwk_path_get_root_windows(path, &root_length); + if (root_length > 1) { + return CWK_STYLE_WINDOWS; + } + + // Next we check for slashes. Windows uses backslashes, while unix uses + // forward slashes. Windows actually supports both, but our best guess is to + // assume windows with backslashes and unix with forward slashes. + for (c = path; *c; ++c) { + if (*c == *separators[CWK_STYLE_UNIX]) { + return CWK_STYLE_UNIX; + } else if (*c == *separators[CWK_STYLE_WINDOWS]) { + return CWK_STYLE_WINDOWS; + } + } + + // This path does not have any slashes. We grab the last segment (which + // actually must be the first one), and determine whether the segment starts + // with a dot. A dot is a hidden folder or file in the UNIX world, in that + // case we assume the path to have UNIX style. + if (!cwk_path_get_last_segment(path, &segment)) { + // We couldn't find any segments, so we default to a UNIX path style since + // there is no way to make any assumptions. + return CWK_STYLE_UNIX; + } + + if (*segment.begin == '.') { + return CWK_STYLE_UNIX; + } + + // And finally we check whether the last segment contains a dot. If it + // contains a dot, that might be an extension. Windows is more likely to have + // file names with extensions, so our guess would be windows. + for (c = segment.begin; *c; ++c) { + if (*c == '.') { + return CWK_STYLE_WINDOWS; + } + } + + // All our checks failed, so we will return a default value which is currently + // UNIX. + return CWK_STYLE_UNIX; +} + +void cwk_path_set_style(enum cwk_path_style style) +{ + // We can just set the global path style variable and then the behaviour for + // all functions will change accordingly. + assert(style == CWK_STYLE_UNIX || style == CWK_STYLE_WINDOWS); + path_style = style; +} + +enum cwk_path_style cwk_path_get_style(void) +{ + // Simply return the path style which we store in a global variable. + return path_style; +} diff --git a/src/libs/thirdparty/cwalk/cwalk.h b/src/libs/thirdparty/cwalk/cwalk.h index 4d33a1c..bfb5ff9 100644 --- a/src/libs/thirdparty/cwalk/cwalk.h +++ b/src/libs/thirdparty/cwalk/cwalk.h @@ -1,3 +1,5 @@ +#pragma once + #ifndef CWK_LIBRARY_H #define CWK_LIBRARY_H @@ -30,1949 +32,467 @@ extern "C" { #endif - /** - * A segment represents a single component of a path. For instance, on linux a - * path might look like this "/var/log/", which consists of two segments "var" - * and "log". - */ - struct cwk_segment - { - const char* path; - const char* segments; - const char* begin; - const char* end; - size_t size; - }; +/** + * A segment represents a single component of a path. For instance, on linux a + * path might look like this "/var/log/", which consists of two segments "var" + * and "log". + */ +struct cwk_segment +{ + const char *path; + const char *segments; + const char *begin; + const char *end; + size_t size; +}; - /** - * The segment type can be used to identify whether a segment is a special - * segment or not. - * - * CWK_NORMAL - normal folder or file segment - * CWK_CURRENT - "./" current folder segment - * CWK_BACK - "../" relative back navigation segment - */ - enum cwk_segment_type - { - CWK_NORMAL, - CWK_CURRENT, - CWK_BACK - }; +/** + * The segment type can be used to identify whether a segment is a special + * segment or not. + * + * CWK_NORMAL - normal folder or file segment + * CWK_CURRENT - "./" current folder segment + * CWK_BACK - "../" relative back navigation segment + */ +enum cwk_segment_type +{ + CWK_NORMAL, + CWK_CURRENT, + CWK_BACK +}; - /** - * @brief Determines the style which is used for the path parsing and - * generation. - */ - enum cwk_path_style - { - CWK_STYLE_WINDOWS, - CWK_STYLE_UNIX - }; +/** + * @brief Determines the style which is used for the path parsing and + * generation. + */ +enum cwk_path_style +{ + CWK_STYLE_WINDOWS, + CWK_STYLE_UNIX +}; - /** - * @brief Generates an absolute path based on a base. - * - * This function generates an absolute path based on a base path and another - * path. It is guaranteed to return an absolute path. If the second submitted - * path is absolute, it will override the base path. The result will be - * written to a buffer, which might be truncated if the buffer is not large - * enough to hold the full path. However, the truncated result will always be - * null-terminated. The returned value is the amount of characters which the - * resulting path would take if it was not truncated (excluding the - * null-terminating character). - * - * @param base The absolute base path on which the relative path will be - * applied. - * @param path The relative path which will be applied on the base path. - * @param buffer The buffer where the result will be written to. - * @param buffer_size The size of the result buffer. - * @return Returns the total amount of characters of the new absolute path. - */ - CWK_PUBLIC size_t cwk_path_get_absolute(const char* base, const char* path, - char* buffer, size_t buffer_size); +/** + * @brief Generates an absolute path based on a base. + * + * This function generates an absolute path based on a base path and another + * path. It is guaranteed to return an absolute path. If the second submitted + * path is absolute, it will override the base path. The result will be + * written to a buffer, which might be truncated if the buffer is not large + * enough to hold the full path. However, the truncated result will always be + * null-terminated. The returned value is the amount of characters which the + * resulting path would take if it was not truncated (excluding the + * null-terminating character). + * + * @param base The absolute base path on which the relative path will be + * applied. + * @param path The relative path which will be applied on the base path. + * @param buffer The buffer where the result will be written to. + * @param buffer_size The size of the result buffer. + * @return Returns the total amount of characters of the new absolute path. + */ +CWK_PUBLIC size_t cwk_path_get_absolute(const char *base, const char *path, + char *buffer, size_t buffer_size); - /** - * @brief Generates a relative path based on a base. - * - * This function generates a relative path based on a base path and another - * path. It determines how to get to the submitted path, starting from the - * base directory. The result will be written to a buffer, which might be - * truncated if the buffer is not large enough to hold the full path. However, - * the truncated result will always be null-terminated. The returned value is - * the amount of characters which the resulting path would take if it was not - * truncated (excluding the null-terminating character). - * - * @param base_directory The base path from which the relative path will - * start. - * @param path The target path where the relative path will point to. - * @param buffer The buffer where the result will be written to. - * @param buffer_size The size of the result buffer. - * @return Returns the total amount of characters of the full path. - */ - CWK_PUBLIC size_t cwk_path_get_relative(const char* base_directory, - const char* path, char* buffer, size_t buffer_size); +/** + * @brief Generates a relative path based on a base. + * + * This function generates a relative path based on a base path and another + * path. It determines how to get to the submitted path, starting from the + * base directory. The result will be written to a buffer, which might be + * truncated if the buffer is not large enough to hold the full path. However, + * the truncated result will always be null-terminated. The returned value is + * the amount of characters which the resulting path would take if it was not + * truncated (excluding the null-terminating character). + * + * @param base_directory The base path from which the relative path will + * start. + * @param path The target path where the relative path will point to. + * @param buffer The buffer where the result will be written to. + * @param buffer_size The size of the result buffer. + * @return Returns the total amount of characters of the full path. + */ +CWK_PUBLIC size_t cwk_path_get_relative(const char *base_directory, + const char *path, char *buffer, size_t buffer_size); - /** - * @brief Joins two paths together. - * - * This function generates a new path by combining the two submitted paths. It - * will remove double separators, and unlike cwk_path_get_absolute it permits - * the use of two relative paths to combine. The result will be written to a - * buffer, which might be truncated if the buffer is not large enough to hold - * the full path. However, the truncated result will always be - * null-terminated. The returned value is the amount of characters which the - * resulting path would take if it was not truncated (excluding the - * null-terminating character). - * - * @param path_a The first path which comes first. - * @param path_b The second path which comes after the first. - * @param buffer The buffer where the result will be written to. - * @param buffer_size The size of the result buffer. - * @return Returns the total amount of characters of the full, combined path. - */ - CWK_PUBLIC size_t cwk_path_join(const char* path_a, const char* path_b, - char* buffer, size_t buffer_size); +/** + * @brief Joins two paths together. + * + * This function generates a new path by combining the two submitted paths. It + * will remove double separators, and unlike cwk_path_get_absolute it permits + * the use of two relative paths to combine. The result will be written to a + * buffer, which might be truncated if the buffer is not large enough to hold + * the full path. However, the truncated result will always be + * null-terminated. The returned value is the amount of characters which the + * resulting path would take if it was not truncated (excluding the + * null-terminating character). + * + * @param path_a The first path which comes first. + * @param path_b The second path which comes after the first. + * @param buffer The buffer where the result will be written to. + * @param buffer_size The size of the result buffer. + * @return Returns the total amount of characters of the full, combined path. + */ +CWK_PUBLIC size_t cwk_path_join(const char *path_a, const char *path_b, + char *buffer, size_t buffer_size); - /** - * @brief Joins multiple paths together. - * - * This function generates a new path by joining multiple paths together. It - * will remove double separators, and unlike cwk_path_get_absolute it permits - * the use of multiple relative paths to combine. The last path of the - * submitted string array must be set to NULL. The result will be written to a - * buffer, which might be truncated if the buffer is not large enough to hold - * the full path. However, the truncated result will always be - * null-terminated. The returned value is the amount of characters which the - * resulting path would take if it was not truncated (excluding the - * null-terminating character). - * - * @param paths An array of paths which will be joined. - * @param buffer The buffer where the result will be written to. - * @param buffer_size The size of the result buffer. - * @return Returns the total amount of characters of the full, combined path. - */ - CWK_PUBLIC size_t cwk_path_join_multiple(const char** paths, char* buffer, - size_t buffer_size); +/** + * @brief Joins multiple paths together. + * + * This function generates a new path by joining multiple paths together. It + * will remove double separators, and unlike cwk_path_get_absolute it permits + * the use of multiple relative paths to combine. The last path of the + * submitted string array must be set to NULL. The result will be written to a + * buffer, which might be truncated if the buffer is not large enough to hold + * the full path. However, the truncated result will always be + * null-terminated. The returned value is the amount of characters which the + * resulting path would take if it was not truncated (excluding the + * null-terminating character). + * + * @param paths An array of paths which will be joined. + * @param buffer The buffer where the result will be written to. + * @param buffer_size The size of the result buffer. + * @return Returns the total amount of characters of the full, combined path. + */ +CWK_PUBLIC size_t cwk_path_join_multiple(const char **paths, char *buffer, + size_t buffer_size); - /** - * @brief Determines the root of a path. - * - * This function determines the root of a path by finding its length. The - * root always starts at the submitted path. If the path has no root, the - * length will be set to zero. - * - * @param path The path which will be inspected. - * @param length The output of the root length. - */ - CWK_PUBLIC void cwk_path_get_root(const char* path, size_t* length); +/** + * @brief Determines the root of a path. + * + * This function determines the root of a path by finding its length. The + * root always starts at the submitted path. If the path has no root, the + * length will be set to zero. + * + * @param path The path which will be inspected. + * @param length The output of the root length. + */ +CWK_PUBLIC void cwk_path_get_root(const char *path, size_t *length); - /** - * @brief Changes the root of a path. - * - * This function changes the root of a path. It does not normalize the result. - * The result will be written to a buffer, which might be truncated if the - * buffer is not large enough to hold the full path. However, the truncated - * result will always be null-terminated. The returned value is the amount of - * characters which the resulting path would take if it was not truncated - * (excluding the null-terminating character). - * - * @param path The original path which will get a new root. - * @param new_root The new root which will be placed in the path. - * @param buffer The output buffer where the result is written to. - * @param buffer_size The size of the output buffer where the result is - * written to. - * @return Returns the total amount of characters of the new path. - */ - CWK_PUBLIC size_t cwk_path_change_root(const char* path, const char* new_root, - char* buffer, size_t buffer_size); +/** + * @brief Changes the root of a path. + * + * This function changes the root of a path. It does not normalize the result. + * The result will be written to a buffer, which might be truncated if the + * buffer is not large enough to hold the full path. However, the truncated + * result will always be null-terminated. The returned value is the amount of + * characters which the resulting path would take if it was not truncated + * (excluding the null-terminating character). + * + * @param path The original path which will get a new root. + * @param new_root The new root which will be placed in the path. + * @param buffer The output buffer where the result is written to. + * @param buffer_size The size of the output buffer where the result is + * written to. + * @return Returns the total amount of characters of the new path. + */ +CWK_PUBLIC size_t cwk_path_change_root(const char *path, const char *new_root, + char *buffer, size_t buffer_size); - /** - * @brief Determine whether the path is absolute or not. - * - * This function checks whether the path is an absolute path or not. A path is - * considered to be absolute if the root ends with a separator. - * - * @param path The path which will be checked. - * @return Returns true if the path is absolute or false otherwise. - */ - CWK_PUBLIC bool cwk_path_is_absolute(const char* path); +/** + * @brief Determine whether the path is absolute or not. + * + * This function checks whether the path is an absolute path or not. A path is + * considered to be absolute if the root ends with a separator. + * + * @param path The path which will be checked. + * @return Returns true if the path is absolute or false otherwise. + */ +CWK_PUBLIC bool cwk_path_is_absolute(const char *path); - /** - * @brief Determine whether the path is relative or not. - * - * This function checks whether the path is a relative path or not. A path is - * considered to be relative if the root does not end with a separator. - * - * @param path The path which will be checked. - * @return Returns true if the path is relative or false otherwise. - */ - CWK_PUBLIC bool cwk_path_is_relative(const char* path); +/** + * @brief Determine whether the path is relative or not. + * + * This function checks whether the path is a relative path or not. A path is + * considered to be relative if the root does not end with a separator. + * + * @param path The path which will be checked. + * @return Returns true if the path is relative or false otherwise. + */ +CWK_PUBLIC bool cwk_path_is_relative(const char *path); - /** - * @brief Gets the basename of a file path. - * - * This function gets the basename of a file path. A pointer to the beginning - * of the basename will be returned through the basename parameter. This - * pointer will be positioned on the first letter after the separator. The - * length of the file path will be returned through the length parameter. The - * length will be set to zero and the basename to NULL if there is no basename - * available. - * - * @param path The path which will be inspected. - * @param basename The output of the basename pointer. - * @param length The output of the length of the basename. This may be - * null if not required. - */ - CWK_PUBLIC void cwk_path_get_basename(const char* path, const char** basename, - size_t* length); +/** + * @brief Gets the basename of a file path. + * + * This function gets the basename of a file path. A pointer to the beginning + * of the basename will be returned through the basename parameter. This + * pointer will be positioned on the first letter after the separator. The + * length of the file path will be returned through the length parameter. The + * length will be set to zero and the basename to NULL if there is no basename + * available. + * + * @param path The path which will be inspected. + * @param basename The output of the basename pointer. + * @param length The output of the length of the basename. This may be + * null if not required. + */ +CWK_PUBLIC void cwk_path_get_basename(const char *path, const char **basename, + size_t *length); - /** - * @brief Changes the basename of a file path. - * - * This function changes the basename of a file path. This function will not - * write out more than the specified buffer can contain. However, the - * generated string is always null-terminated - even if not the whole path is - * written out. The function returns the total number of characters the - * complete buffer would have, even if it was not written out completely. The - * path may be the same memory address as the buffer. - * - * @param path The original path which will be used for the modified path. - * @param new_basename The new basename which will replace the old one. - * @param buffer The buffer where the changed path will be written to. - * @param buffer_size The size of the result buffer where the changed path is - * written to. - * @return Returns the size which the complete new path would have if it was - * not truncated. - */ - CWK_PUBLIC size_t cwk_path_change_basename(const char* path, - const char* new_basename, char* buffer, size_t buffer_size); +/** + * @brief Changes the basename of a file path. + * + * This function changes the basename of a file path. This function will not + * write out more than the specified buffer can contain. However, the + * generated string is always null-terminated - even if not the whole path is + * written out. The function returns the total number of characters the + * complete buffer would have, even if it was not written out completely. The + * path may be the same memory address as the buffer. + * + * @param path The original path which will be used for the modified path. + * @param new_basename The new basename which will replace the old one. + * @param buffer The buffer where the changed path will be written to. + * @param buffer_size The size of the result buffer where the changed path is + * written to. + * @return Returns the size which the complete new path would have if it was + * not truncated. + */ +CWK_PUBLIC size_t cwk_path_change_basename(const char *path, + const char *new_basename, char *buffer, size_t buffer_size); - /** - * @brief Gets the dirname of a file path. - * - * This function determines the dirname of a file path and returns the length - * up to which character is considered to be part of it. If no dirname is - * found, the length will be set to zero. The beginning of the dirname is - * always equal to the submitted path pointer. - * - * @param path The path which will be inspected. - * @param length The length of the dirname. - */ - CWK_PUBLIC void cwk_path_get_dirname(const char* path, size_t* length); +/** + * @brief Gets the dirname of a file path. + * + * This function determines the dirname of a file path and returns the length + * up to which character is considered to be part of it. If no dirname is + * found, the length will be set to zero. The beginning of the dirname is + * always equal to the submitted path pointer. + * + * @param path The path which will be inspected. + * @param length The length of the dirname. + */ +CWK_PUBLIC void cwk_path_get_dirname(const char *path, size_t *length); - /** - * @brief Gets the extension of a file path. - * - * This function extracts the extension portion of a file path. A pointer to - * the beginning of the extension will be returned through the extension - * parameter if an extension is found and true is returned. This pointer will - * be positioned on the dot. The length of the extension name will be returned - * through the length parameter. If no extension is found both parameters - * won't be touched and false will be returned. - * - * @param path The path which will be inspected. - * @param extension The output of the extension pointer. - * @param length The output of the length of the extension. - * @return Returns true if an extension is found or false otherwise. - */ - CWK_PUBLIC bool cwk_path_get_extension(const char* path, const char** extension, - size_t* length); +/** + * @brief Gets the extension of a file path. + * + * This function extracts the extension portion of a file path. A pointer to + * the beginning of the extension will be returned through the extension + * parameter if an extension is found and true is returned. This pointer will + * be positioned on the dot. The length of the extension name will be returned + * through the length parameter. If no extension is found both parameters + * won't be touched and false will be returned. + * + * @param path The path which will be inspected. + * @param extension The output of the extension pointer. + * @param length The output of the length of the extension. + * @return Returns true if an extension is found or false otherwise. + */ +CWK_PUBLIC bool cwk_path_get_extension(const char *path, const char **extension, + size_t *length); - /** - * @brief Determines whether the file path has an extension. - * - * This function determines whether the submitted file path has an extension. - * This will evaluate to true if the last segment of the path contains a dot. - * - * @param path The path which will be inspected. - * @return Returns true if the path has an extension or false otherwise. - */ - CWK_PUBLIC bool cwk_path_has_extension(const char* path); +/** + * @brief Determines whether the file path has an extension. + * + * This function determines whether the submitted file path has an extension. + * This will evaluate to true if the last segment of the path contains a dot. + * + * @param path The path which will be inspected. + * @return Returns true if the path has an extension or false otherwise. + */ +CWK_PUBLIC bool cwk_path_has_extension(const char *path); - /** - * @brief Changes the extension of a file path. - * - * This function changes the extension of a file name. The function will - * append an extension if the basename does not have an extension, or use the - * extension as a basename if the path does not have a basename. This function - * will not write out more than the specified buffer can contain. However, the - * generated string is always null-terminated - even if not the whole path is - * written out. The function returns the total number of characters the - * complete buffer would have, even if it was not written out completely. The - * path may be the same memory address as the buffer. - * - * @param path The path which will be used to make the change. - * @param new_extension The extension which will be placed within the new - * path. - * @param buffer The output buffer where the result will be written to. - * @param buffer_size The size of the output buffer where the result will be - * written to. - * @return Returns the total size which the output would have if it was not - * truncated. - */ - CWK_PUBLIC size_t cwk_path_change_extension(const char* path, - const char* new_extension, char* buffer, size_t buffer_size); +/** + * @brief Changes the extension of a file path. + * + * This function changes the extension of a file name. The function will + * append an extension if the basename does not have an extension, or use the + * extension as a basename if the path does not have a basename. This function + * will not write out more than the specified buffer can contain. However, the + * generated string is always null-terminated - even if not the whole path is + * written out. The function returns the total number of characters the + * complete buffer would have, even if it was not written out completely. The + * path may be the same memory address as the buffer. + * + * @param path The path which will be used to make the change. + * @param new_extension The extension which will be placed within the new + * path. + * @param buffer The output buffer where the result will be written to. + * @param buffer_size The size of the output buffer where the result will be + * written to. + * @return Returns the total size which the output would have if it was not + * truncated. + */ +CWK_PUBLIC size_t cwk_path_change_extension(const char *path, + const char *new_extension, char *buffer, size_t buffer_size); - /** - * @brief Creates a normalized version of the path. - * - * This function creates a normalized version of the path within the specified - * buffer. This function will not write out more than the specified buffer can - * contain. However, the generated string is always null-terminated - even if - * not the whole path is written out. The function returns the total number of - * characters the complete buffer would have, even if it was not written out - * completely. The path may be the same memory address as the buffer. - * - * The following will be true for the normalized path: - * 1) "../" will be resolved. - * 2) "./" will be removed. - * 3) double separators will be fixed with a single separator. - * 4) separator suffixes will be removed. - * - * @param path The path which will be normalized. - * @param buffer The buffer where the new path is written to. - * @param buffer_size The size of the buffer. - * @return The size which the complete normalized path has if it was not - * truncated. - */ - CWK_PUBLIC size_t cwk_path_normalize(const char* path, char* buffer, - size_t buffer_size); +/** + * @brief Creates a normalized version of the path. + * + * This function creates a normalized version of the path within the specified + * buffer. This function will not write out more than the specified buffer can + * contain. However, the generated string is always null-terminated - even if + * not the whole path is written out. The function returns the total number of + * characters the complete buffer would have, even if it was not written out + * completely. The path may be the same memory address as the buffer. + * + * The following will be true for the normalized path: + * 1) "../" will be resolved. + * 2) "./" will be removed. + * 3) double separators will be fixed with a single separator. + * 4) separator suffixes will be removed. + * + * @param path The path which will be normalized. + * @param buffer The buffer where the new path is written to. + * @param buffer_size The size of the buffer. + * @return The size which the complete normalized path has if it was not + * truncated. + */ +CWK_PUBLIC size_t cwk_path_normalize(const char *path, char *buffer, + size_t buffer_size); - /** - * @brief Finds common portions in two paths. - * - * This function finds common portions in two paths and returns the number - * characters from the beginning of the base path which are equal to the other - * path. - * - * @param path_base The base path which will be compared with the other path. - * @param path_other The other path which will compared with the base path. - * @return Returns the number of characters which are common in the base path. - */ - CWK_PUBLIC size_t cwk_path_get_intersection(const char* path_base, - const char* path_other); +/** + * @brief Finds common portions in two paths. + * + * This function finds common portions in two paths and returns the number + * characters from the beginning of the base path which are equal to the other + * path. + * + * @param path_base The base path which will be compared with the other path. + * @param path_other The other path which will compared with the base path. + * @return Returns the number of characters which are common in the base path. + */ +CWK_PUBLIC size_t cwk_path_get_intersection(const char *path_base, + const char *path_other); - /** - * @brief Gets the first segment of a path. - * - * This function finds the first segment of a path. The position of the - * segment is set to the first character after the separator, and the length - * counts all characters until the next separator (excluding the separator). - * - * @param path The path which will be inspected. - * @param segment The segment which will be extracted. - * @return Returns true if there is a segment or false if there is none. - */ - CWK_PUBLIC bool cwk_path_get_first_segment(const char* path, - struct cwk_segment* segment); +/** + * @brief Gets the first segment of a path. + * + * This function finds the first segment of a path. The position of the + * segment is set to the first character after the separator, and the length + * counts all characters until the next separator (excluding the separator). + * + * @param path The path which will be inspected. + * @param segment The segment which will be extracted. + * @return Returns true if there is a segment or false if there is none. + */ +CWK_PUBLIC bool cwk_path_get_first_segment(const char *path, + struct cwk_segment *segment); - /** - * @brief Gets the last segment of the path. - * - * This function gets the last segment of a path. This function may return - * false if the path doesn't contain any segments, in which case the submitted - * segment parameter is not modified. The position of the segment is set to - * the first character after the separator, and the length counts all - * characters until the end of the path (excluding the separator). - * - * @param path The path which will be inspected. - * @param segment The segment which will be extracted. - * @return Returns true if there is a segment or false if there is none. - */ - CWK_PUBLIC bool cwk_path_get_last_segment(const char* path, - struct cwk_segment* segment); +/** + * @brief Gets the last segment of the path. + * + * This function gets the last segment of a path. This function may return + * false if the path doesn't contain any segments, in which case the submitted + * segment parameter is not modified. The position of the segment is set to + * the first character after the separator, and the length counts all + * characters until the end of the path (excluding the separator). + * + * @param path The path which will be inspected. + * @param segment The segment which will be extracted. + * @return Returns true if there is a segment or false if there is none. + */ +CWK_PUBLIC bool cwk_path_get_last_segment(const char *path, + struct cwk_segment *segment); - /** - * @brief Advances to the next segment. - * - * This function advances the current segment to the next segment. If there - * are no more segments left, the submitted segment structure will stay - * unchanged and false is returned. - * - * @param segment The current segment which will be advanced to the next one. - * @return Returns true if another segment was found or false otherwise. - */ - CWK_PUBLIC bool cwk_path_get_next_segment(struct cwk_segment* segment); +/** + * @brief Advances to the next segment. + * + * This function advances the current segment to the next segment. If there + * are no more segments left, the submitted segment structure will stay + * unchanged and false is returned. + * + * @param segment The current segment which will be advanced to the next one. + * @return Returns true if another segment was found or false otherwise. + */ +CWK_PUBLIC bool cwk_path_get_next_segment(struct cwk_segment *segment); - /** - * @brief Moves to the previous segment. - * - * This function moves the current segment to the previous segment. If the - * current segment is the first one, the submitted segment structure will stay - * unchanged and false is returned. - * - * @param segment The current segment which will be moved to the previous one. - * @return Returns true if there is a segment before this one or false - * otherwise. - */ - CWK_PUBLIC bool cwk_path_get_previous_segment(struct cwk_segment* segment); +/** + * @brief Moves to the previous segment. + * + * This function moves the current segment to the previous segment. If the + * current segment is the first one, the submitted segment structure will stay + * unchanged and false is returned. + * + * @param segment The current segment which will be moved to the previous one. + * @return Returns true if there is a segment before this one or false + * otherwise. + */ +CWK_PUBLIC bool cwk_path_get_previous_segment(struct cwk_segment *segment); - /** - * @brief Gets the type of the submitted path segment. - * - * This function inspects the contents of the segment and determines the type - * of it. Currently, there are three types CWK_NORMAL, CWK_CURRENT and - * CWK_BACK. A CWK_NORMAL segment is a normal folder or file entry. A - * CWK_CURRENT is a "./" and a CWK_BACK a "../" segment. - * - * @param segment The segment which will be inspected. - * @return Returns the type of the segment. - */ - CWK_PUBLIC enum cwk_segment_type cwk_path_get_segment_type( - const struct cwk_segment* segment); +/** + * @brief Gets the type of the submitted path segment. + * + * This function inspects the contents of the segment and determines the type + * of it. Currently, there are three types CWK_NORMAL, CWK_CURRENT and + * CWK_BACK. A CWK_NORMAL segment is a normal folder or file entry. A + * CWK_CURRENT is a "./" and a CWK_BACK a "../" segment. + * + * @param segment The segment which will be inspected. + * @return Returns the type of the segment. + */ +CWK_PUBLIC enum cwk_segment_type cwk_path_get_segment_type( + const struct cwk_segment *segment); - /** - * @brief Changes the content of a segment. - * - * This function overrides the content of a segment to the submitted value and - * outputs the whole new path to the submitted buffer. The result might - * require less or more space than before if the new value length differs from - * the original length. The output is truncated if the new path is larger than - * the submitted buffer size, but it is always null-terminated. The source of - * the segment and the submitted buffer may be the same. - * - * @param segment The segment which will be modifier. - * @param value The new content of the segment. - * @param buffer The buffer where the modified path will be written to. - * @param buffer_size The size of the output buffer. - * @return Returns the total size which would have been written if the output - * was not truncated. - */ - CWK_PUBLIC size_t cwk_path_change_segment(struct cwk_segment* segment, - const char* value, char* buffer, size_t buffer_size); +/** + * @brief Changes the content of a segment. + * + * This function overrides the content of a segment to the submitted value and + * outputs the whole new path to the submitted buffer. The result might + * require less or more space than before if the new value length differs from + * the original length. The output is truncated if the new path is larger than + * the submitted buffer size, but it is always null-terminated. The source of + * the segment and the submitted buffer may be the same. + * + * @param segment The segment which will be modifier. + * @param value The new content of the segment. + * @param buffer The buffer where the modified path will be written to. + * @param buffer_size The size of the output buffer. + * @return Returns the total size which would have been written if the output + * was not truncated. + */ +CWK_PUBLIC size_t cwk_path_change_segment(struct cwk_segment *segment, + const char *value, char *buffer, size_t buffer_size); - /** - * @brief Checks whether the submitted pointer points to a separator. - * - * This function simply checks whether the submitted pointer points to a - * separator, which has to be null-terminated (but not necessarily after the - * separator). The function will return true if it is a separator, or false - * otherwise. - * - * @param symbol A pointer to a string. - * @return Returns true if it is a separator, or false otherwise. - */ - CWK_PUBLIC bool cwk_path_is_separator(const char* str); +/** + * @brief Checks whether the submitted pointer points to a separator. + * + * This function simply checks whether the submitted pointer points to a + * separator, which has to be null-terminated (but not necessarily after the + * separator). The function will return true if it is a separator, or false + * otherwise. + * + * @param symbol A pointer to a string. + * @return Returns true if it is a separator, or false otherwise. + */ +CWK_PUBLIC bool cwk_path_is_separator(const char *str); - /** - * @brief Guesses the path style. - * - * This function guesses the path style based on a submitted path-string. The - * guessing will look at the root and the type of slashes contained in the - * path and return the style which is more likely used in the path. - * - * @param path The path which will be inspected. - * @return Returns the style which is most likely used for the path. - */ - CWK_PUBLIC enum cwk_path_style cwk_path_guess_style(const char* path); +/** + * @brief Guesses the path style. + * + * This function guesses the path style based on a submitted path-string. The + * guessing will look at the root and the type of slashes contained in the + * path and return the style which is more likely used in the path. + * + * @param path The path which will be inspected. + * @return Returns the style which is most likely used for the path. + */ +CWK_PUBLIC enum cwk_path_style cwk_path_guess_style(const char *path); - /** - * @brief Configures which path style is used. - * - * This function configures which path style is used. The following styles are - * currently supported. - * - * CWK_STYLE_WINDOWS: Use backslashes as a separator and volume for the root. - * CWK_STYLE_UNIX: Use slashes as a separator and a slash for the root. - * - * @param style The style which will be used from now on. - */ - CWK_PUBLIC void cwk_path_set_style(enum cwk_path_style style); +/** + * @brief Configures which path style is used. + * + * This function configures which path style is used. The following styles are + * currently supported. + * + * CWK_STYLE_WINDOWS: Use backslashes as a separator and volume for the root. + * CWK_STYLE_UNIX: Use slashes as a separator and a slash for the root. + * + * @param style The style which will be used from now on. + */ +CWK_PUBLIC void cwk_path_set_style(enum cwk_path_style style); - /** - * @brief Gets the path style configuration. - * - * This function gets the style configuration which is currently used for the - * paths. This configuration determines how paths are parsed and generated. - * - * @return Returns the current path style configuration. - */ - CWK_PUBLIC enum cwk_path_style cwk_path_get_style(void); +/** + * @brief Gets the path style configuration. + * + * This function gets the style configuration which is currently used for the + * paths. This configuration determines how paths are parsed and generated. + * + * @return Returns the current path style configuration. + */ +CWK_PUBLIC enum cwk_path_style cwk_path_get_style(void); #ifdef __cplusplus } // extern "C" #endif -#if defined(_CWALK_IMPL) -#include -#include - -#include -#include -#include - -/** - * We try to default to a different path style depending on the operating - * system. So this should detect whether we should use windows or unix paths. - */ -#if defined(WIN32) || defined(_WIN32) || \ - defined(__WIN32) && !defined(__CYGWIN__) -static enum cwk_path_style path_style = CWK_STYLE_WINDOWS; -#else -static enum cwk_path_style path_style = CWK_STYLE_UNIX; #endif - -/** - * This is a list of separators used in different styles. Windows can read - * multiple separators, but it generally outputs just a backslash. The output - * will always use the first character for the output. - */ -static const char* separators[] = { - "\\/", // CWK_STYLE_WINDOWS - "/" // CWK_STYLE_UNIX -}; - -/** - * A joined path represents multiple path strings which are concatenated, but - * not (necessarily) stored in contiguous memory. The joined path allows to - * iterate over the segments as if it was one piece of path. - */ -struct cwk_segment_joined -{ - struct cwk_segment segment; - const char** paths; - size_t path_index; -}; - -static size_t cwk_path_output_sized(char* buffer, size_t buffer_size, - size_t position, const char* str, size_t length) -{ - size_t amount_written; - - // First we determine the amount which we can write to the buffer. There are - // three cases. In the first case we have enough to store the whole string in - // it. In the second one we can only store a part of it, and in the third we - // have no space left. - if (buffer_size > position + length) { - amount_written = length; - } else if (buffer_size > position) { - amount_written = buffer_size - position; - } else { - amount_written = 0; - } - - // If we actually want to write out something we will do that here. We will - // always append a '\0', this way we are guaranteed to have a valid string at - // all times. - if (amount_written > 0) { - memmove(&buffer[position], str, amount_written); - } - - // Return the theoretical length which would have been written when everything - // would have fit in the buffer. - return length; -} - -static size_t cwk_path_output_current(char* buffer, size_t buffer_size, - size_t position) -{ - // We output a "current" directory, which is a single character. This - // character is currently not style dependant. - return cwk_path_output_sized(buffer, buffer_size, position, ".", 1); -} - -static size_t cwk_path_output_back(char* buffer, size_t buffer_size, - size_t position) -{ - // We output a "back" directory, which ahs two characters. This - // character is currently not style dependant. - return cwk_path_output_sized(buffer, buffer_size, position, "..", 2); -} - -static size_t cwk_path_output_separator(char* buffer, size_t buffer_size, - size_t position) -{ - // We output a separator, which is a single character. - return cwk_path_output_sized(buffer, buffer_size, position, - separators[path_style], 1); -} - -static size_t cwk_path_output_dot(char* buffer, size_t buffer_size, - size_t position) -{ - // We output a dot, which is a single character. This is used for extensions. - return cwk_path_output_sized(buffer, buffer_size, position, ".", 1); -} - -static size_t cwk_path_output(char* buffer, size_t buffer_size, size_t position, - const char* str) -{ - size_t length; - - // This just does a sized output internally, but first measuring the - // null-terminated string. - length = strlen(str); - return cwk_path_output_sized(buffer, buffer_size, position, str, length); -} - -static void cwk_path_terminate_output(char* buffer, size_t buffer_size, - size_t pos) -{ - if (buffer_size > 0) { - if (pos >= buffer_size) { - buffer[buffer_size - 1] = '\0'; - } else { - buffer[pos] = '\0'; - } - } -} - -static bool cwk_path_is_string_equal(const char* first, const char* second, - size_t first_size, size_t second_size) -{ - bool are_both_separators; - - // The two strings are not equal if the sizes are not equal. - if (first_size != second_size) { - return false; - } - - // If the path style is UNIX, we will compare case sensitively. This can be - // done easily using strncmp. - if (path_style == CWK_STYLE_UNIX) { - return strncmp(first, second, first_size) == 0; - } - - // However, if this is windows we will have to compare case insensitively. - // Since there is no standard method to do that we will have to do it on our - // own. - while (*first && *second && first_size > 0) { - // We can consider the string to be not equal if the two lowercase - // characters are not equal. The two chars may also be separators, which - // means they would be equal. - are_both_separators = strchr(separators[path_style], *first) != NULL && - strchr(separators[path_style], *second) != NULL; - - if (tolower(*first) != tolower(*second) && !are_both_separators) { - return false; - } - - first++; - second++; - - --first_size; - } - - // The string must be equal since they both have the same length and all the - // characters are the same. - return true; -} - -static const char* cwk_path_find_next_stop(const char* c) -{ - // We just move forward until we find a '\0' or a separator, which will be our - // next "stop". - while (*c != '\0' && !cwk_path_is_separator(c)) { - ++c; - } - - // Return the pointer of the next stop. - return c; -} - -static const char* cwk_path_find_previous_stop(const char* begin, const char* c) -{ - // We just move back until we find a separator or reach the beginning of the - // path, which will be our previous "stop". - while (c > begin && !cwk_path_is_separator(c)) { - --c; - } - - // Return the pointer to the previous stop. We have to return the first - // character after the separator, not on the separator itself. - if (cwk_path_is_separator(c)) { - return c + 1; - } else { - return c; - } -} - -static bool cwk_path_get_first_segment_without_root(const char* path, - const char* segments, struct cwk_segment* segment) -{ - // Let's remember the path. We will move the path pointer afterwards, that's - // why this has to be done first. - segment->path = path; - segment->segments = segments; - segment->begin = segments; - segment->end = segments; - segment->size = 0; - - // Now let's check whether this is an empty string. An empty string has no - // segment it could use. - if (*segments == '\0') { - return false; - } - - // If the string starts with separators, we will jump over those. If there is - // only a slash and a '\0' after it, we can't determine the first segment - // since there is none. - while (cwk_path_is_separator(segments)) { - ++segments; - if (*segments == '\0') { - return false; - } - } - - // So this is the beginning of our segment. - segment->begin = segments; - - // Now let's determine the end of the segment, which we do by moving the path - // pointer further until we find a separator. - segments = cwk_path_find_next_stop(segments); - - // And finally, calculate the size of the segment by subtracting the position - // from the end. - segment->size = (size_t)(segments - segment->begin); - segment->end = segments; - - // Tell the caller that we found a segment. - return true; -} - -static bool cwk_path_get_last_segment_without_root(const char* path, - struct cwk_segment* segment) -{ - // Now this is fairly similar to the normal algorithm, however, it will assume - // that there is no root in the path. So we grab the first segment at this - // position, assuming there is no root. - if (!cwk_path_get_first_segment_without_root(path, path, segment)) { - return false; - } - - // Now we find our last segment. The segment struct of the caller - // will contain the last segment, since the function we call here will not - // change the segment struct when it reaches the end. - while (cwk_path_get_next_segment(segment)) { - // We just loop until there is no other segment left. - } - - return true; -} - -static bool cwk_path_get_first_segment_joined(const char** paths, - struct cwk_segment_joined* sj) -{ - bool result; - - // Prepare the first segment. We position the joined segment on the first path - // and assign the path array to the struct. - sj->path_index = 0; - sj->paths = paths; - - // We loop through all paths until we find one which has a segment. The result - // is stored in a variable, so we can let the caller know whether we found one - // or not. - result = false; - while (paths[sj->path_index] != NULL && - (result = cwk_path_get_first_segment(paths[sj->path_index], - &sj->segment)) == false) { - ++sj->path_index; - } - - return result; -} - -static bool cwk_path_get_next_segment_joined(struct cwk_segment_joined* sj) -{ - bool result; - - if (sj->paths[sj->path_index] == NULL) { - // We reached already the end of all paths, so there is no other segment - // left. - return false; - } else if (cwk_path_get_next_segment(&sj->segment)) { - // There was another segment on the current path, so we are good to - // continue. - return true; - } - - // We try to move to the next path which has a segment available. We must at - // least move one further since the current path reached the end. - result = false; - - do { - ++sj->path_index; - - // And we obviously have to stop this loop if there are no more paths left. - if (sj->paths[sj->path_index] == NULL) { - break; - } - - // Grab the first segment of the next path and determine whether this path - // has anything useful in it. There is one more thing we have to consider - // here - for the first time we do this we want to skip the root, but - // afterwards we will consider that to be part of the segments. - result = cwk_path_get_first_segment_without_root(sj->paths[sj->path_index], - sj->paths[sj->path_index], &sj->segment); - - } while (!result); - - // Finally, report the result back to the caller. - return result; -} - -static bool cwk_path_get_previous_segment_joined(struct cwk_segment_joined* sj) -{ - bool result; - - if (*sj->paths == NULL) { - // It's possible that there is no initialized segment available in the - // struct since there are no paths. In that case we can return false, since - // there is no previous segment. - return false; - } else if (cwk_path_get_previous_segment(&sj->segment)) { - // Now we try to get the previous segment from the current path. If we can - // do that successfully, we can let the caller know that we found one. - return true; - } - - result = false; - - do { - // We are done once we reached index 0. In that case there are no more - // segments left. - if (sj->path_index == 0) { - break; - } - - // There is another path which we have to inspect. So we decrease the path - // index. - --sj->path_index; - - // If this is the first path we will have to consider that this path might - // include a root, otherwise we just treat is as a segment. - if (sj->path_index == 0) { - result = cwk_path_get_last_segment(sj->paths[sj->path_index], - &sj->segment); - } else { - result = cwk_path_get_last_segment_without_root(sj->paths[sj->path_index], - &sj->segment); - } - - } while (!result); - - return result; -} - -static bool cwk_path_segment_back_will_be_removed(struct cwk_segment_joined* sj) -{ - enum cwk_segment_type type; - int counter; - - // We are handling back segments here. We must verify how many back segments - // and how many normal segments come before this one to decide whether we keep - // or remove it. - - // The counter determines how many normal segments are our current segment, - // which will popped off before us. If the counter goes above zero it means - // that our segment will be popped as well. - counter = 0; - - // We loop over all previous segments until we either reach the beginning, - // which means our segment will not be dropped or the counter goes above zero. - while (cwk_path_get_previous_segment_joined(sj)) { - - // Now grab the type. The type determines whether we will increase or - // decrease the counter. We don't handle a CWK_CURRENT frame here since it - // has no influence. - type = cwk_path_get_segment_type(&sj->segment); - if (type == CWK_NORMAL) { - // This is a normal segment. The normal segment will increase the counter - // since it neutralizes one back segment. If we go above zero we can - // return immediately. - ++counter; - if (counter > 0) { - return true; - } - } else if (type == CWK_BACK) { - // A CWK_BACK segment will reduce the counter by one. We can not remove a - // back segment as long we are not above zero since we don't have the - // opposite normal segment which we would remove. - --counter; - } - } - - // We never got a count larger than zero, so we will keep this segment alive. - return false; -} - -static bool cwk_path_segment_normal_will_be_removed( - struct cwk_segment_joined* sj) -{ - enum cwk_segment_type type; - int counter; - - // The counter determines how many segments are above our current segment, - // which will popped off before us. If the counter goes below zero it means - // that our segment will be popped as well. - counter = 0; - - // We loop over all following segments until we either reach the end, which - // means our segment will not be dropped or the counter goes below zero. - while (cwk_path_get_next_segment_joined(sj)) { - - // First, grab the type. The type determines whether we will increase or - // decrease the counter. We don't handle a CWK_CURRENT frame here since it - // has no influence. - type = cwk_path_get_segment_type(&sj->segment); - if (type == CWK_NORMAL) { - // This is a normal segment. The normal segment will increase the counter - // since it will be removed by a "../" before us. - ++counter; - } else if (type == CWK_BACK) { - // A CWK_BACK segment will reduce the counter by one. If we are below zero - // we can return immediately. - --counter; - if (counter < 0) { - return true; - } - } - } - - // We never got a negative count, so we will keep this segment alive. - return false; -} - -static bool -cwk_path_segment_will_be_removed(const struct cwk_segment_joined* sj, - bool absolute) -{ - enum cwk_segment_type type; - struct cwk_segment_joined sjc; - - // We copy the joined path so we don't need to modify it. - sjc = *sj; - - // First we check whether this is a CWK_CURRENT or CWK_BACK segment, since - // those will always be dropped. - type = cwk_path_get_segment_type(&sj->segment); - if (type == CWK_CURRENT || (type == CWK_BACK && absolute)) { - return true; - } else if (type == CWK_BACK) { - return cwk_path_segment_back_will_be_removed(&sjc); - } else { - return cwk_path_segment_normal_will_be_removed(&sjc); - } -} - -static bool -cwk_path_segment_joined_skip_invisible(struct cwk_segment_joined* sj, - bool absolute) -{ - while (cwk_path_segment_will_be_removed(sj, absolute)) { - if (!cwk_path_get_next_segment_joined(sj)) { - return false; - } - } - - return true; -} - -static void cwk_path_get_root_windows(const char* path, size_t* length) -{ - const char* c; - bool is_device_path; - - // We can not determine the root if this is an empty string. So we set the - // root to NULL and the length to zero and cancel the whole thing. - c = path; - *length = 0; - if (!*c) { - return; - } - - // Now we have to verify whether this is a windows network path (UNC), which - // we will consider our root. - if (cwk_path_is_separator(c)) { - ++c; - - // Check whether the path starts with a single backslash, which means this - // is not a network path - just a normal path starting with a backslash. - if (!cwk_path_is_separator(c)) { - // Okay, this is not a network path but we still use the backslash as a - // root. - ++(*length); - return; - } - - // A device path is a path which starts with "\\." or "\\?". A device path - // can be a UNC path as well, in which case it will take up one more - // segment. So, this is a network or device path. Skip the previous - // separator. Now we need to determine whether this is a device path. We - // might advance one character here if the server name starts with a '?' or - // a '.', but that's fine since we will search for a separator afterwards - // anyway. - ++c; - is_device_path = (*c == '?' || *c == '.') && cwk_path_is_separator(++c); - if (is_device_path) { - // That's a device path, and the root must be either "\\.\" or "\\?\" - // which is 4 characters long. (at least that's how Windows - // GetFullPathName behaves.) - *length = 4; - return; - } - - // We will grab anything up to the next stop. The next stop might be a '\0' - // or another separator. That will be the server name. - c = cwk_path_find_next_stop(c); - - // If this is a separator and not the end of a string we wil have to include - // it. However, if this is a '\0' we must not skip it. - while (cwk_path_is_separator(c)) { - ++c; - } - - // We are now skipping the shared folder name, which will end after the - // next stop. - c = cwk_path_find_next_stop(c); - - // Then there might be a separator at the end. We will include that as well, - // it will mark the path as absolute. - if (cwk_path_is_separator(c)) { - ++c; - } - - // Finally, calculate the size of the root. - *length = (size_t)(c - path); - return; - } - - // Move to the next and check whether this is a colon. - if (*++c == ':') { - *length = 2; - - // Now check whether this is a backslash (or slash). If it is not, we could - // assume that the next character is a '\0' if it is a valid path. However, - // we will not assume that - since ':' is not valid in a path it must be a - // mistake by the caller than. We will try to understand it anyway. - if (cwk_path_is_separator(++c)) { - *length = 3; - } - } -} - -static void cwk_path_get_root_unix(const char* path, size_t* length) -{ - // The slash of the unix path represents the root. There is no root if there - // is no slash. - if (cwk_path_is_separator(path)) { - *length = 1; - } else { - *length = 0; - } -} - -static bool cwk_path_is_root_absolute(const char* path, size_t length) -{ - // This is definitely not absolute if there is no root. - if (length == 0) { - return false; - } - - // If there is a separator at the end of the root, we can safely consider this - // to be an absolute path. - return cwk_path_is_separator(&path[length - 1]); -} - -static void cwk_path_fix_root(char* buffer, size_t buffer_size, size_t length) -{ - size_t i; - - // This only affects windows. - if (path_style != CWK_STYLE_WINDOWS) { - return; - } - - // Make sure we are not writing further than we are actually allowed to. - if (length > buffer_size) { - length = buffer_size; - } - - // Replace all forward slashes with backwards slashes. Since this is windows - // we can't have any forward slashes in the root. - for (i = 0; i < length; ++i) { - if (cwk_path_is_separator(&buffer[i])) { - buffer[i] = *separators[CWK_STYLE_WINDOWS]; - } - } -} - -static size_t cwk_path_join_and_normalize_multiple(const char** paths, - char* buffer, size_t buffer_size) -{ - size_t pos; - bool absolute, has_segment_output; - struct cwk_segment_joined sj; - - // We initialize the position after the root, which should get us started. - cwk_path_get_root(paths[0], &pos); - - // Determine whether the path is absolute or not. We need that to determine - // later on whether we can remove superfluous "../" or not. - absolute = cwk_path_is_root_absolute(paths[0], pos); - - // First copy the root to the output. After copying, we will normalize the - // root. - cwk_path_output_sized(buffer, buffer_size, 0, paths[0], pos); - cwk_path_fix_root(buffer, buffer_size, pos); - - // So we just grab the first segment. If there is no segment we will always - // output a "/", since we currently only support absolute paths here. - if (!cwk_path_get_first_segment_joined(paths, &sj)) { - goto done; - } - - // Let's assume that we don't have any segment output for now. We will toggle - // this flag once there is some output. - has_segment_output = false; - - do { - // Check whether we have to drop this segment because of resolving a - // relative path or because it is a CWK_CURRENT segment. - if (cwk_path_segment_will_be_removed(&sj, absolute)) { - continue; - } - - // We add a separator if we previously wrote a segment. The last segment - // must not have a trailing separator. This must happen before the segment - // output, since we would override the null terminating character with - // reused buffers if this was done afterwards. - if (has_segment_output) { - pos += cwk_path_output_separator(buffer, buffer_size, pos); - } - - // Remember that we have segment output, so we can handle the trailing slash - // later on. This is necessary since we might have segments but they are all - // removed. - has_segment_output = true; - - // Write out the segment but keep in mind that we need to follow the - // buffer size limitations. That's why we use the path output functions - // here. - pos += cwk_path_output_sized(buffer, buffer_size, pos, sj.segment.begin, - sj.segment.size); - } while (cwk_path_get_next_segment_joined(&sj)); - - // Remove the trailing slash, but only if we have segment output. We don't - // want to remove anything from the root. - if (!has_segment_output && pos == 0) { - // This may happen if the path is absolute and all segments have been - // removed. We can not have an empty output - and empty output means we stay - // in the current directory. So we will output a ".". - assert(absolute == false); - pos += cwk_path_output_current(buffer, buffer_size, pos); - } - - // We must append a '\0' in any case, unless the buffer size is zero. If the - // buffer size is zero, which means we can not. -done: - cwk_path_terminate_output(buffer, buffer_size, pos); - - // And finally let our caller know about the total size of the normalized - // path. - return pos; -} - -size_t cwk_path_get_absolute(const char* base, const char* path, char* buffer, - size_t buffer_size) -{ - size_t i; - const char* paths[4]; - - // The basename should be an absolute path if the caller is using the API - // correctly. However, he might not and in that case we will append a fake - // root at the beginning. - if (cwk_path_is_absolute(base)) { - i = 0; - } else if (path_style == CWK_STYLE_WINDOWS) { - paths[0] = "\\"; - i = 1; - } else { - paths[0] = "/"; - i = 1; - } - - if (cwk_path_is_absolute(path)) { - // If the submitted path is not relative the base path becomes irrelevant. - // We will only normalize the submitted path instead. - paths[i++] = path; - paths[i] = NULL; - } else { - // Otherwise we append the relative path to the base path and normalize it. - // The result will be a new absolute path. - paths[i++] = base; - paths[i++] = path; - paths[i] = NULL; - } - - // Finally join everything together and normalize it. - return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); -} - -static void cwk_path_skip_segments_until_diverge(struct cwk_segment_joined* bsj, - struct cwk_segment_joined* osj, bool absolute, bool* base_available, - bool* other_available) -{ - // Now looping over all segments until they start to diverge. A path may - // diverge if two segments are not equal or if one path reaches the end. - do { - - // Check whether there is anything available after we skip everything which - // is invisible. We do that for both paths, since we want to let the caller - // know which path has some trailing segments after they diverge. - *base_available = cwk_path_segment_joined_skip_invisible(bsj, absolute); - *other_available = cwk_path_segment_joined_skip_invisible(osj, absolute); - - // We are done if one or both of those paths reached the end. They either - // diverge or both reached the end - but in both cases we can not continue - // here. - if (!*base_available || !*other_available) { - break; - } - - // Compare the content of both segments. We are done if they are not equal, - // since they diverge. - if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin, - bsj->segment.size, osj->segment.size)) { - break; - } - - // We keep going until one of those segments reached the end. The next - // segment might be invisible, but we will check for that in the beginning - // of the loop once again. - *base_available = cwk_path_get_next_segment_joined(bsj); - *other_available = cwk_path_get_next_segment_joined(osj); - } while (*base_available && *other_available); -} - -size_t cwk_path_get_relative(const char* base_directory, const char* path, - char* buffer, size_t buffer_size) -{ - size_t pos, base_root_length, path_root_length; - bool absolute, base_available, other_available, has_output; - const char* base_paths[2], * other_paths[2]; - struct cwk_segment_joined bsj, osj; - - pos = 0; - - // First we compare the roots of those two paths. If the roots are not equal - // we can't continue, since there is no way to get a relative path from - // different roots. - cwk_path_get_root(base_directory, &base_root_length); - cwk_path_get_root(path, &path_root_length); - if (base_root_length != path_root_length || - !cwk_path_is_string_equal(base_directory, path, base_root_length, - path_root_length)) { - cwk_path_terminate_output(buffer, buffer_size, pos); - return pos; - } - - // Verify whether this is an absolute path. We need to know that since we can - // remove all back-segments if it is. - absolute = cwk_path_is_root_absolute(base_directory, base_root_length); - - // Initialize our joined segments. This will allow us to use the internal - // functions to skip until diverge and invisible. We only have one path in - // them though. - base_paths[0] = base_directory; - base_paths[1] = NULL; - other_paths[0] = path; - other_paths[1] = NULL; - cwk_path_get_first_segment_joined(base_paths, &bsj); - cwk_path_get_first_segment_joined(other_paths, &osj); - - // Okay, now we skip until the segments diverge. We don't have anything to do - // with the segments which are equal. - cwk_path_skip_segments_until_diverge(&bsj, &osj, absolute, &base_available, - &other_available); - - // Assume there is no output until we have got some. We will need this - // information later on to remove trailing slashes or alternatively output a - // current-segment. - has_output = false; - - // So if we still have some segments left in the base path we will now output - // a back segment for all of them. - if (base_available) { - do { - // Skip any invisible segment. We don't care about those and we don't need - // to navigate back because of them. - if (!cwk_path_segment_joined_skip_invisible(&bsj, absolute)) { - break; - } - - // Toggle the flag if we have output. We need to remember that, since we - // want to remove the trailing slash. - has_output = true; - - // Output the back segment and a separator. No need to worry about the - // superfluous segment since it will be removed later on. - pos += cwk_path_output_back(buffer, buffer_size, pos); - pos += cwk_path_output_separator(buffer, buffer_size, pos); - } while (cwk_path_get_next_segment_joined(&bsj)); - } - - // And if we have some segments available of the target path we will output - // all of those. - if (other_available) { - do { - // Again, skip any invisible segments since we don't need to navigate into - // them. - if (!cwk_path_segment_joined_skip_invisible(&osj, absolute)) { - break; - } - - // Toggle the flag if we have output. We need to remember that, since we - // want to remove the trailing slash. - has_output = true; - - // Output the current segment and a separator. No need to worry about the - // superfluous segment since it will be removed later on. - pos += cwk_path_output_sized(buffer, buffer_size, pos, osj.segment.begin, - osj.segment.size); - pos += cwk_path_output_separator(buffer, buffer_size, pos); - } while (cwk_path_get_next_segment_joined(&osj)); - } - - // If we have some output by now we will have to remove the trailing slash. We - // simply do that by moving back one character. The terminate output function - // will then place the '\0' on this position. Otherwise, if there is no - // output, we will have to output a "current directory", since the target path - // points to the base path. - if (has_output) { - --pos; - } else { - pos += cwk_path_output_current(buffer, buffer_size, pos); - } - - // Finally, we can terminate the output - which means we place a '\0' at the - // current position or at the end of the buffer. - cwk_path_terminate_output(buffer, buffer_size, pos); - - return pos; -} - -size_t cwk_path_join(const char* path_a, const char* path_b, char* buffer, - size_t buffer_size) -{ - const char* paths[3]; - - // This is simple. We will just create an array with the two paths which we - // wish to join. - paths[0] = path_a; - paths[1] = path_b; - paths[2] = NULL; - - // And then call the join and normalize function which will do the hard work - // for us. - return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); -} - -size_t cwk_path_join_multiple(const char** paths, char* buffer, - size_t buffer_size) -{ - // We can just call the internal join and normalize function for this one, - // since it will handle everything. - return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); -} - -void cwk_path_get_root(const char* path, size_t* length) -{ - // We use a different implementation here based on the configuration of the - // library. - if (path_style == CWK_STYLE_WINDOWS) { - cwk_path_get_root_windows(path, length); - } else { - cwk_path_get_root_unix(path, length); - } -} - -size_t cwk_path_change_root(const char* path, const char* new_root, - char* buffer, size_t buffer_size) -{ - const char* tail; - size_t root_length, path_length, tail_length, new_root_length, new_path_size; - - // First we need to determine the actual size of the root which we will - // change. - cwk_path_get_root(path, &root_length); - - // Now we determine the sizes of the new root and the path. We need that to - // determine the size of the part after the root (the tail). - new_root_length = strlen(new_root); - path_length = strlen(path); - - // Okay, now we calculate the position of the tail and the length of it. - tail = path + root_length; - tail_length = path_length - root_length; - - // We first output the tail and then the new root, that's because the source - // path and the buffer may be overlapping. This way the root will not - // overwrite the tail. - cwk_path_output_sized(buffer, buffer_size, new_root_length, tail, - tail_length); - cwk_path_output_sized(buffer, buffer_size, 0, new_root, new_root_length); - - // Finally we calculate the size o the new path and terminate the output with - // a '\0'. - new_path_size = tail_length + new_root_length; - cwk_path_terminate_output(buffer, buffer_size, new_path_size); - - return new_path_size; -} - -bool cwk_path_is_absolute(const char* path) -{ - size_t length; - - // We grab the root of the path. This root does not include the first - // separator of a path. - cwk_path_get_root(path, &length); - - // Now we can determine whether the root is absolute or not. - return cwk_path_is_root_absolute(path, length); -} - -bool cwk_path_is_relative(const char* path) -{ - // The path is relative if it is not absolute. - return !cwk_path_is_absolute(path); -} - -void cwk_path_get_basename(const char* path, const char** basename, - size_t* length) -{ - struct cwk_segment segment; - - // We get the last segment of the path. The last segment will contain the - // basename if there is any. If there are no segments we will set the basename - // to NULL and the length to 0. - if (!cwk_path_get_last_segment(path, &segment)) { - *basename = NULL; - if (length) { - *length = 0; - } - return; - } - - // Now we can just output the segment contents, since that's our basename. - // There might be trailing separators after the basename, but the size does - // not include those. - *basename = segment.begin; - if (length) { - *length = segment.size; - } -} - -size_t cwk_path_change_basename(const char* path, const char* new_basename, - char* buffer, size_t buffer_size) -{ - struct cwk_segment segment; - size_t pos, root_size, new_basename_size; - - // First we try to get the last segment. We may only have a root without any - // segments, in which case we will create one. - if (!cwk_path_get_last_segment(path, &segment)) { - - // So there is no segment in this path. First we grab the root and output - // that. We are not going to modify the root in any way. - cwk_path_get_root(path, &root_size); - pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size); - - // We have to trim the separators from the beginning of the new basename. - // This is quite easy to do. - while (cwk_path_is_separator(new_basename)) { - ++new_basename; - } - - // Now we measure the length of the new basename, this is a two step - // process. First we find the '\0' character at the end of the string. - new_basename_size = 0; - while (new_basename[new_basename_size]) { - ++new_basename_size; - } - - // And then we trim the separators at the end of the basename until we reach - // the first valid character. - while (new_basename_size > 0 && - cwk_path_is_separator(&new_basename[new_basename_size - 1])) { - --new_basename_size; - } - - // Now we will output the new basename after the root. - pos += cwk_path_output_sized(buffer, buffer_size, pos, new_basename, - new_basename_size); - - // And finally terminate the output and return the total size of the path. - cwk_path_terminate_output(buffer, buffer_size, pos); - return pos; - } - - // If there is a last segment we can just forward this call, which is fairly - // easy. - return cwk_path_change_segment(&segment, new_basename, buffer, buffer_size); -} - -void cwk_path_get_dirname(const char* path, size_t* length) -{ - struct cwk_segment segment; - - // We get the last segment of the path. The last segment will contain the - // basename if there is any. If there are no segments we will set the length - // to 0. - if (!cwk_path_get_last_segment(path, &segment)) { - *length = 0; - return; - } - - // We can now return the length from the beginning of the string up to the - // beginning of the last segment. - *length = (size_t)(segment.begin - path); -} - -bool cwk_path_get_extension(const char* path, const char** extension, - size_t* length) -{ - struct cwk_segment segment; - const char* c; - - // We get the last segment of the path. The last segment will contain the - // extension if there is any. - if (!cwk_path_get_last_segment(path, &segment)) { - return false; - } - - // Now we search for a dot within the segment. If there is a dot, we consider - // the rest of the segment the extension. We do this from the end towards the - // beginning, since we want to find the last dot. - for (c = segment.end; c >= segment.begin; --c) { - if (*c == '.') { - // Okay, we found an extension. We can stop looking now. - *extension = c; - *length = (size_t)(segment.end - c); - return true; - } - } - - // We couldn't find any extension. - return false; -} - -bool cwk_path_has_extension(const char* path) -{ - const char* extension; - size_t length; - - // We just wrap the get_extension call which will then do the work for us. - return cwk_path_get_extension(path, &extension, &length); -} - -size_t cwk_path_change_extension(const char* path, const char* new_extension, - char* buffer, size_t buffer_size) -{ - struct cwk_segment segment; - const char* c, * old_extension; - size_t pos, root_size, trail_size, new_extension_size; - - // First we try to get the last segment. We may only have a root without any - // segments, in which case we will create one. - if (!cwk_path_get_last_segment(path, &segment)) { - - // So there is no segment in this path. First we grab the root and output - // that. We are not going to modify the root in any way. If there is no - // root, this will end up with a root size 0, and nothing will be written. - cwk_path_get_root(path, &root_size); - pos = cwk_path_output_sized(buffer, buffer_size, 0, path, root_size); - - // Add a dot if the submitted value doesn't have any. - if (*new_extension != '.') { - pos += cwk_path_output_dot(buffer, buffer_size, pos); - } - - // And finally terminate the output and return the total size of the path. - pos += cwk_path_output(buffer, buffer_size, pos, new_extension); - cwk_path_terminate_output(buffer, buffer_size, pos); - return pos; - } - - // Now we seek the old extension in the last segment, which we will replace - // with the new one. If there is no old extension, it will point to the end of - // the segment. - old_extension = segment.end; - for (c = segment.begin; c < segment.end; ++c) { - if (*c == '.') { - old_extension = c; - } - } - - pos = cwk_path_output_sized(buffer, buffer_size, 0, segment.path, - (size_t)(old_extension - segment.path)); - - // If the new extension starts with a dot, we will skip that dot. We always - // output exactly one dot before the extension. If the extension contains - // multiple dots, we will output those as part of the extension. - if (*new_extension == '.') { - ++new_extension; - } - - // We calculate the size of the new extension, including the dot, in order to - // output the trail - which is any part of the path coming after the - // extension. We must output this first, since the buffer may overlap with the - // submitted path - and it would be overridden by longer extensions. - new_extension_size = strlen(new_extension) + 1; - trail_size = cwk_path_output(buffer, buffer_size, pos + new_extension_size, - segment.end); - - // Finally we output the dot and the new extension. The new extension itself - // doesn't contain the dot anymore, so we must output that first. - pos += cwk_path_output_dot(buffer, buffer_size, pos); - pos += cwk_path_output(buffer, buffer_size, pos, new_extension); - - // Now we terminate the output with a null-terminating character, but before - // we do that we must add the size of the trail to the position which we - // output before. - pos += trail_size; - cwk_path_terminate_output(buffer, buffer_size, pos); - - // And the position is our output size now. - return pos; -} - -size_t cwk_path_normalize(const char* path, char* buffer, size_t buffer_size) -{ - const char* paths[2]; - - // Now we initialize the paths which we will normalize. Since this function - // only supports submitting a single path, we will only add that one. - paths[0] = path; - paths[1] = NULL; - - return cwk_path_join_and_normalize_multiple(paths, buffer, buffer_size); -} - -size_t cwk_path_get_intersection(const char* path_base, const char* path_other) -{ - bool absolute; - size_t base_root_length, other_root_length; - const char* end; - const char* paths_base[2], * paths_other[2]; - struct cwk_segment_joined base, other; - - // We first compare the two roots. We just return zero if they are not equal. - // This will also happen to return zero if the paths are mixed relative and - // absolute. - cwk_path_get_root(path_base, &base_root_length); - cwk_path_get_root(path_other, &other_root_length); - if (!cwk_path_is_string_equal(path_base, path_other, base_root_length, - other_root_length)) { - return 0; - } - - // Configure our paths. We just have a single path in here for now. - paths_base[0] = path_base; - paths_base[1] = NULL; - paths_other[0] = path_other; - paths_other[1] = NULL; - - // So we get the first segment of both paths. If one of those paths don't have - // any segment, we will return 0. - if (!cwk_path_get_first_segment_joined(paths_base, &base) || - !cwk_path_get_first_segment_joined(paths_other, &other)) { - return base_root_length; - } - - // We now determine whether the path is absolute or not. This is required - // because if will ignore removed segments, and this behaves differently if - // the path is absolute. However, we only need to check the base path because - // we are guaranteed that both paths are either relative or absolute. - absolute = cwk_path_is_root_absolute(path_base, base_root_length); - - // We must keep track of the end of the previous segment. Initially, this is - // set to the beginning of the path. This means that 0 is returned if the - // first segment is not equal. - end = path_base + base_root_length; - - // Now we loop over both segments until one of them reaches the end or their - // contents are not equal. - do { - // We skip all segments which will be removed in each path, since we want to - // know about the true path. - if (!cwk_path_segment_joined_skip_invisible(&base, absolute) || - !cwk_path_segment_joined_skip_invisible(&other, absolute)) { - break; - } - - if (!cwk_path_is_string_equal(base.segment.begin, other.segment.begin, - base.segment.size, other.segment.size)) { - // So the content of those two segments are not equal. We will return the - // size up to the beginning. - return (size_t)(end - path_base); - } - - // Remember the end of the previous segment before we go to the next one. - end = base.segment.end; - } while (cwk_path_get_next_segment_joined(&base) && - cwk_path_get_next_segment_joined(&other)); - - // Now we calculate the length up to the last point where our paths pointed to - // the same place. - return (size_t)(end - path_base); -} - -bool cwk_path_get_first_segment(const char* path, struct cwk_segment* segment) -{ - size_t length; - const char* segments; - - // We skip the root since that's not part of the first segment. The root is - // treated as a separate entity. - cwk_path_get_root(path, &length); - segments = path + length; - - // Now, after we skipped the root we can continue and find the actual segment - // content. - return cwk_path_get_first_segment_without_root(path, segments, segment); -} - -bool cwk_path_get_last_segment(const char* path, struct cwk_segment* segment) -{ - // We first grab the first segment. This might be our last segment as well, - // but we don't know yet. There is no last segment if there is no first - // segment, so we return false in that case. - if (!cwk_path_get_first_segment(path, segment)) { - return false; - } - - // Now we find our last segment. The segment struct of the caller - // will contain the last segment, since the function we call here will not - // change the segment struct when it reaches the end. - while (cwk_path_get_next_segment(segment)) { - // We just loop until there is no other segment left. - } - - return true; -} - -bool cwk_path_get_next_segment(struct cwk_segment* segment) -{ - const char* c; - - // First we jump to the end of the previous segment. The first character must - // be either a '\0' or a separator. - c = segment->begin + segment->size; - if (*c == '\0') { - return false; - } - - // Now we skip all separator until we reach something else. We are not yet - // guaranteed to have a segment, since the string could just end afterwards. - assert(cwk_path_is_separator(c)); - do { - ++c; - } while (cwk_path_is_separator(c)); - - // If the string ends here, we can safely assume that there is no other - // segment after this one. - if (*c == '\0') { - return false; - } - - // Now we are safe to assume there is a segment. We store the beginning of - // this segment in the segment struct of the caller. - segment->begin = c; - - // And now determine the size of this segment, and store it in the struct of - // the caller as well. - c = cwk_path_find_next_stop(c); - segment->end = c; - segment->size = (size_t)(c - segment->begin); - - // Tell the caller that we found a segment. - return true; -} - -bool cwk_path_get_previous_segment(struct cwk_segment* segment) -{ - const char* c; - - // The current position might point to the first character of the path, which - // means there are no previous segments available. - c = segment->begin; - if (c <= segment->segments) { - return false; - } - - // We move towards the beginning of the path until we either reached the - // beginning or the character is no separator anymore. - do { - --c; - if (c < segment->segments) { - // So we reached the beginning here and there is no segment. So we return - // false and don't change the segment structure submitted by the caller. - return false; - } - } while (cwk_path_is_separator(c)); - - // We are guaranteed now that there is another segment, since we moved before - // the previous separator and did not reach the segment path beginning. - segment->end = c + 1; - segment->begin = cwk_path_find_previous_stop(segment->segments, c); - segment->size = (size_t)(segment->end - segment->begin); - - return true; -} - -enum cwk_segment_type cwk_path_get_segment_type( - const struct cwk_segment* segment) -{ - // We just make a string comparison with the segment contents and return the - // appropriate type. - if (strncmp(segment->begin, ".", segment->size) == 0) { - return CWK_CURRENT; - } else if (strncmp(segment->begin, "..", segment->size) == 0) { - return CWK_BACK; - } - - return CWK_NORMAL; -} - -bool cwk_path_is_separator(const char* str) -{ - const char* c; - - // We loop over all characters in the read symbols. - c = separators[path_style]; - while (*c) { - if (*c == *str) { - return true; - } - - ++c; - } - - return false; -} - -size_t cwk_path_change_segment(struct cwk_segment* segment, const char* value, - char* buffer, size_t buffer_size) -{ - size_t pos, value_size, tail_size; - - // First we have to output the head, which is the whole string up to the - // beginning of the segment. This part of the path will just stay the same. - pos = cwk_path_output_sized(buffer, buffer_size, 0, segment->path, - (size_t)(segment->begin - segment->path)); - - // In order to trip the submitted value, we will skip any separator at the - // beginning of it and behave as if it was never there. - while (cwk_path_is_separator(value)) { - ++value; - } - - // Now we determine the length of the value. In order to do that we first - // locate the '\0'. - value_size = 0; - while (value[value_size]) { - ++value_size; - } - - // Since we trim separators at the beginning and in the end of the value we - // have to subtract from the size until there are either no more characters - // left or the last character is no separator. - while (value_size > 0 && cwk_path_is_separator(&value[value_size - 1])) { - --value_size; - } - - // We also have to determine the tail size, which is the part of the string - // following the current segment. This part will not change. - tail_size = strlen(segment->end); - - // Now we output the tail. We have to do that, because if the buffer and the - // source are overlapping we would override the tail if the value is - // increasing in length. - cwk_path_output_sized(buffer, buffer_size, pos + value_size, segment->end, - tail_size); - - // Finally we can output the value in the middle of the head and the tail, - // where we have enough space to fit the whole trimmed value. - pos += cwk_path_output_sized(buffer, buffer_size, pos, value, value_size); - - // Now we add the tail size to the current position and terminate the output - - // basically, ensure that there is a '\0' at the end of the buffer. - pos += tail_size; - cwk_path_terminate_output(buffer, buffer_size, pos); - - // And now tell the caller how long the whole path would be. - return pos; -} - -enum cwk_path_style cwk_path_guess_style(const char* path) -{ - const char* c; - size_t root_length; - struct cwk_segment segment; - - // First we determine the root. Only windows roots can be longer than a single - // slash, so if we can determine that it starts with something like "C:", we - // know that this is a windows path. - cwk_path_get_root_windows(path, &root_length); - if (root_length > 1) { - return CWK_STYLE_WINDOWS; - } - - // Next we check for slashes. Windows uses backslashes, while unix uses - // forward slashes. Windows actually supports both, but our best guess is to - // assume windows with backslashes and unix with forward slashes. - for (c = path; *c; ++c) { - if (*c == *separators[CWK_STYLE_UNIX]) { - return CWK_STYLE_UNIX; - } else if (*c == *separators[CWK_STYLE_WINDOWS]) { - return CWK_STYLE_WINDOWS; - } - } - - // This path does not have any slashes. We grab the last segment (which - // actually must be the first one), and determine whether the segment starts - // with a dot. A dot is a hidden folder or file in the UNIX world, in that - // case we assume the path to have UNIX style. - if (!cwk_path_get_last_segment(path, &segment)) { - // We couldn't find any segments, so we default to a UNIX path style since - // there is no way to make any assumptions. - return CWK_STYLE_UNIX; - } - - if (*segment.begin == '.') { - return CWK_STYLE_UNIX; - } - - // And finally we check whether the last segment contains a dot. If it - // contains a dot, that might be an extension. Windows is more likely to have - // file names with extensions, so our guess would be windows. - for (c = segment.begin; *c; ++c) { - if (*c == '.') { - return CWK_STYLE_WINDOWS; - } - } - - // All our checks failed, so we will return a default value which is currently - // UNIX. - return CWK_STYLE_UNIX; -} - -void cwk_path_set_style(enum cwk_path_style style) -{ - // We can just set the global path style variable and then the behaviour for - // all functions will change accordingly. - assert(style == CWK_STYLE_UNIX || style == CWK_STYLE_WINDOWS); - path_style = style; -} - -enum cwk_path_style cwk_path_get_style(void) -{ - // Simply return the path style which we store in a global variable. - return path_style; -} -#endif // _CWALK_IMPL - -#endif \ No newline at end of file diff --git a/tests/lang/builtin_fn.pk b/tests/lang/builtin_fn.pk index eb2cac6..1b38b86 100644 --- a/tests/lang/builtin_fn.pk +++ b/tests/lang/builtin_fn.pk @@ -8,5 +8,22 @@ assert(list_join([1, 2, 3]) == "123") assert(list_join(["hello", " world"]) == "hello world") assert(list_join([[], []]) == "[][]") +assert(min(-1, 2) == -1) +assert(max(100, -200) == 100) + +class N + def _init(n) + self.n = n + end + def < (other) + return self.n < other.n + end +end + +n1 = N(1) +n2 = N(2) +assert(min(n1, n2) == n1) +assert(max(n1, n2) == n2) + ## If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/lang/builtin_ty.pk b/tests/lang/builtin_ty.pk index 45e668a..d80f159 100644 --- a/tests/lang/builtin_ty.pk +++ b/tests/lang/builtin_ty.pk @@ -132,6 +132,20 @@ assert(l == ['h', 'e', 'l', 'l', 'o']) ## MAP ############################################################################### +m = { 'fruit': 'Apple' } +m['size'] = 'Large' +m['colors'] = [] +colors = m['colors'] +colors.append('Red') +colors.append('Green') +colors.append('Blue') + +assert(m == { + "fruit": "Apple", + "size": "Large", + "colors": ["Red", "Green", "Blue"] +}) + swtich = { 0 : fn return 1 end, 1 : fn return 2 end, diff --git a/tests/modules/json.pk b/tests/modules/json.pk new file mode 100644 index 0000000..e2fae82 --- /dev/null +++ b/tests/modules/json.pk @@ -0,0 +1,46 @@ + +import io, json, path + +## https://support.oneskyapp.com/hc/en-us/articles/208047697-JSON-sample-files +jsonfile = path.join(path.dirname(__file__), 'test.json') + +obj = json.parse(io.readfile(jsonfile)) +assert(obj is Map) + +sports_q1 = obj['quiz']['sport']['q1'] +assert(sports_q1['question'] == 'Which one is correct team name in NBA?') + +assert(sports_q1['options'] == + [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriros", + "Huston Rocket", + ]) + +assert(obj['quiz']['maths'] == + { + "q1": { + "question": "5 + 7 = ?", + "options": [ + "10", + "11", + "12", + "13", + ], + "answer": "12" + }, + "q2": { + "question": "12 - 8 = ?", + "options": [ + "1", + "2", + "3", + "4", + ], + "answer": "4" + } + }) + +# If we got here, that means all test were passed. +print('All TESTS PASSED') diff --git a/tests/modules/path.pk b/tests/modules/path.pk index c909441..6908621 100644 --- a/tests/modules/path.pk +++ b/tests/modules/path.pk @@ -3,3 +3,6 @@ import path THIS_PATH = path.dirname(__file__) assert('path.pk' in path.listdir(THIS_PATH)) + +# If we got here, that means all test were passed. +print('All TESTS PASSED') diff --git a/tests/modules/test.json b/tests/modules/test.json new file mode 100644 index 0000000..48109ed --- /dev/null +++ b/tests/modules/test.json @@ -0,0 +1,38 @@ +{ + "quiz": { + "sport": { + "q1": { + "question": "Which one is correct team name in NBA?", + "options": [ + "New York Bulls", + "Los Angeles Kings", + "Golden State Warriros", + "Huston Rocket" + ], + "answer": "Huston Rocket" + } + }, + "maths": { + "q1": { + "question": "5 + 7 = ?", + "options": [ + "10", + "11", + "12", + "13" + ], + "answer": "12" + }, + "q2": { + "question": "12 - 8 = ?", + "options": [ + "1", + "2", + "3", + "4" + ], + "answer": "4" + } + } + } +}