From a5aff56fb1ab08ceecac9756b71136a70ca35857 Mon Sep 17 00:00:00 2001 From: Timerix Date: Sun, 9 Nov 2025 23:47:51 +0500 Subject: [PATCH] project created --- .gitignore | 21 + .vscode/.gitignore | 1 + .vscode/c_cpp_properties.json | 15 + .vscode/launch.json | 29 + .vscode/tasks.json | 31 + README.md | 21 + dependencies/tlibc.config | 19 + include/tlibtoml/toml.h | 190 ++++ project.config | 208 ++++ project.config.user.default | 11 + src/toml.c | 1786 +++++++++++++++++++++++++++++++ tests/complex-structure.toml | 35 + tests/example.toml | 47 + tests/fruit.toml | 13 + tests/hard_example.toml | 33 + tests/hard_example_unicode.toml | 36 + tests/key-values.toml | 115 ++ tests/long_config.toml | 132 +++ tests/main.c | 139 +++ tlibtoml.config | 27 + 20 files changed, 2909 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/.gitignore create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 README.md create mode 100644 dependencies/tlibc.config create mode 100644 include/tlibtoml/toml.h create mode 100644 project.config create mode 100644 project.config.user.default create mode 100644 src/toml.c create mode 100644 tests/complex-structure.toml create mode 100644 tests/example.toml create mode 100644 tests/fruit.toml create mode 100644 tests/hard_example.toml create mode 100644 tests/hard_example_unicode.toml create mode 100644 tests/key-values.toml create mode 100644 tests/long_config.toml create mode 100644 tests/main.c create mode 100644 tlibtoml.config diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..498924c --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# build results +bin/ +obj/ + +# IDE files +.vs/ +.vshistory/ +.editorconfig +*.user +*.vcxproj.filters + +# other files +.old*/ +old/ +tmp/ +temp/ +*.tmp +*.temp +logs/ +log/ +*.log diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 0000000..c3d3a20 --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1 @@ +settings.json \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..8ab0327 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,15 @@ +{ + "configurations": [ + { + "name": "all", + "defines": [], + "includePath": [ + "include", + "../tlibc/include", + "${default}" + ], + "cStandard": "c99" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..34083dd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "gdb_debug", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/bin/tlibtoml", + "windows": { "program": "${workspaceFolder}/bin/tlibtoml.exe" }, + "preLaunchTask": "build_exec_dbg", + "stopAtEntry": false, + "cwd": "${workspaceFolder}/bin", + "externalConsole": false, + "internalConsoleOptions": "neverOpen", + "MIMode": "gdb", + "miDebuggerPath": "gdb", + "setupCommands": [ + { + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..17a453c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,31 @@ + +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build_exec_dbg", + "detail": "build project with debug symbols", + "type": "cppbuild", + "command": "bash", + "args": [ + "-c", + "cbuild build_exec_dbg" + ], + "options": { + "cwd": "${workspaceFolder}" + }, + "problemMatcher": ["$gcc"], + "group": { + "kind": "build" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": true, + "panel": "shared", + "showReuseMessage": false, + "clear": true + } + } + ] + } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..bad787f --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# tlibtoml +A fork of [libtoml](https://github.com/brglng/libtoml) rewritten to use [tlibc](https://timerix.ddns.net/git/Timerix/tlibtoml) instead of its own (worse) implementation of collections and strings. For example, Table from libtoml is just an array of key-value pairs, where search happens by calling `strcmp()` on each element. Such inefficient code hurts my mind, so i have no other choise than to rewrite this library. + +## Build +1. Clone the repository. + ``` + git clone https://timerix.ddns.net/git/Timerix/tlibtoml.git + ``` +2. Install [cbuild](https://timerix.ddns.net/git/Timerix/cbuild.git). +3. Clone [tlibc](https://timerix.ddns.net/git/Timerix/tlibtoml). By default + `dependencies/tlibc.config` expects that `tlibc/` is present in the same directory as `tlibtoml/`. + If you cloned it to another directory, change `DEPENDENCIES_DIR` in `tlibtoml/project.user.config`. + ``` + git clone https://timerix.ddns.net/git/Timerix/tlibc.git + ``` +4. Build and run tests + ``` + cd tlibtoml + cbuild build_exec exec + ``` +5. To build library use tasks `build_static_lib[_dbg]` or `build_shared_lib[_dbg]` diff --git a/dependencies/tlibc.config b/dependencies/tlibc.config new file mode 100644 index 0000000..f5dfff7 --- /dev/null +++ b/dependencies/tlibc.config @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# This is a dependency config. +# You can copy it to another project to add tlibc dependency. + +DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibc" +if [[ "$TASK" = *_dbg ]]; then + dep_build_target="build_static_lib_dbg" +else + dep_build_target="build_static_lib" +fi +DEP_PRE_BUILD_COMMAND="setup_user_config" +DEP_BUILD_COMMAND="cbuild $dep_build_target" +DEP_POST_BUILD_COMMAND="" +DEP_CLEAN_COMMAND="cbuild clean" +DEP_DYNAMIC_OUT_FILES="" +DEP_STATIC_OUT_FILES="bin/tlibc.a" +DEP_OTHER_OUT_FILES="" +PRESERVE_OUT_DIRECTORY_STRUCTURE=false diff --git a/include/tlibtoml/toml.h b/include/tlibtoml/toml.h new file mode 100644 index 0000000..0b913ed --- /dev/null +++ b/include/tlibtoml/toml.h @@ -0,0 +1,190 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "tlibc/std.h" +#include + +#if defined(__cplusplus) && __cplusplus >= 201103L +#define ATTRIBUTE_THREAD_LOCAL thread_local +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define ATTRIBUTE_THREAD_LOCAL _Thread_local +#elif defined(_MSC_VER) +#define ATTRIBUTE_THREAD_LOCAL __declspec(thread) +#else +#define ATTRIBUTE_THREAD_LOCAL __thread +#endif + +#define TOML_FALSE 0 +#define TOML_TRUE 1 + +typedef enum { + TOML_OK, + TOML_ERR, + TOML_ERR_OS, + TOML_ERR_NOMEM, + TOML_ERR_SYNTAX, + TOML_ERR_UNICODE +} TomlErrCode; + +typedef struct { + TomlErrCode code; + char* message; + int _is_literal; +} TomlErr; + +typedef struct { + char* str; + size_t len; + size_t _capacity; +} TomlString; + +typedef struct _TomlValue TomlValue; + +typedef struct { + TomlValue** elements; + size_t len; + size_t _capacity; +} TomlArray; + +typedef struct _TomlKeyValue TomlKeyValue; + +typedef struct { + size_t _capacity; + size_t len; + TomlKeyValue* _keyvals; +} TomlTable; + +typedef struct { + TomlTable* _table; + TomlKeyValue* _keyval; +} TomlTableIter; + +typedef enum { + TOML_TABLE, + TOML_ARRAY, + TOML_STRING, + TOML_INTEGER, + TOML_FLOAT, + TOML_DATETIME, + TOML_BOOLEAN, +} TomlType; + +struct _TomlValue { + TomlType type; + union { + TomlTable* table; + TomlArray* array; + TomlString* string; +#if defined(_MSC_VER) || defined(__APPLE__) + long long integer; +#else + long integer; +#endif + double float_; + struct tm datetime; + int boolean; + } value; +}; + +struct _TomlKeyValue { + TomlString* key; + TomlValue* value; +}; + +typedef struct { + void* (*malloc)(void *context, size_t size); + void* (*realloc)(void *context, void *p, size_t size); + void (*free)(void *context, void *p); +} TomlAllocFuncs; + +void toml_set_allocator(void *context, const TomlAllocFuncs *funcs); + +void* toml_malloc(size_t size); +void* toml_realloc(void *p, size_t size); +void toml_free(void *p); + +char* toml_strdup(const char *str); +char* toml_strndup(const char *str, size_t n); +int toml_vasprintf(char **str, const char *format, va_list args); +int toml_asprintf(char **str, const char *format, ...); + +const TomlErr* toml_err(void); +void toml_err_clear(void); + +TomlString* toml_string_new(void); +TomlString* toml_string_from_str(const char *str); +TomlString* toml_string_from_nstr(const char *str, size_t len); +void toml_string_append_char(TomlString *self, char ch); +void toml_string_append_str(TomlString *self, const char *str); +void toml_string_append_nstr(TomlString *self, const char *str, size_t len); +TomlString* toml_string_clone(const TomlString *self); +void toml_string_free(TomlString *self); +int toml_string_equals(const TomlString *self, const TomlString *other); + +TomlTable* toml_table_new(void); +void toml_table_free(TomlTable *self); + +void toml_table_set_by_string(TomlTable *self, TomlString *key, TomlValue *value); +TomlValue *toml_table_get_by_string(const TomlTable *self, const TomlString *key); +void toml_table_set(TomlTable *self, const char *key, TomlValue *value); +void toml_table_setn(TomlTable *self, const char *key, size_t key_len, TomlValue *value); +TomlValue* toml_table_get(const TomlTable *self, const char *key); +TomlTable* toml_table_get_as_table(const TomlTable *self, const char *key); +TomlArray* toml_table_get_as_array(const TomlTable *self, const char *key); +TomlString* toml_table_get_as_string(const TomlTable *self, const char *key); +#if defined(_MSC_VER) || defined(__APPLE__) +long long toml_table_get_as_integer(const TomlTable *self, const char *key); +#else +long toml_table_get_as_integer(const TomlTable *self, const char *key); +#endif +double toml_table_get_as_float(const TomlTable *self, const char *key); +const struct tm* toml_table_get_as_datetime(const TomlTable *self, const char *key); +int toml_table_get_as_boolean(const TomlTable *self, const char *key); +TomlValue* toml_table_getn(const TomlTable *self, const char *key, size_t key_len); + +TomlTableIter toml_table_iter_new(TomlTable *table); +TomlKeyValue* toml_table_iter_get(TomlTableIter *self); +int toml_table_iter_has_next(TomlTableIter *self); +void toml_table_iter_next(TomlTableIter *self); + +TomlArray* toml_array_new(void); +void toml_array_free(TomlArray *self); +void toml_array_append(TomlArray *self, TomlValue *value); + +TomlValue* toml_value_new(TomlType type); +TomlValue* toml_value_new_string(TomlType type); +TomlValue* toml_value_new_table(void); +TomlValue* toml_value_new_array(void); +#if defined(_MSC_VER) || defined(__APPLE__) +TomlValue *toml_value_new_integer(long long integer); +#else +TomlValue *toml_value_new_integer(long integer); +#endif +TomlValue* toml_value_new_float(double flt); +TomlValue* toml_value_new_datetime(void); +TomlValue* toml_value_new_boolean(int boolean); +TomlValue* toml_value_from_str(const char *str); +TomlValue* toml_value_from_nstr(const char *str, size_t len); +void toml_value_free(TomlValue *self); + +TomlTable* toml_load_str(const char *str); +TomlTable* toml_load_nstr(const char *str, size_t len); +TomlTable* toml_load_file(FILE *file); +TomlTable* toml_load_filename(const char *filename); + +/* TODO: implement dump functions +char *toml_dump_str(const TomlTable *self, TomlErr *err); +TomlString *toml_dump_nstr(const TomlTable *self, TomlErr *err); +void toml_dump_file(const TomlTable *self, FILE *file, TomlErr *err); +*/ + +#ifdef __cplusplus +} +#endif diff --git a/project.config b/project.config new file mode 100644 index 0000000..2da86e8 --- /dev/null +++ b/project.config @@ -0,0 +1,208 @@ +#!/usr/bin/env bash +CBUILD_VERSION=2.3.0 + +PROJECT="tlibtoml" +CMP_C="gcc" +CMP_CPP="g++" +STD_C="c99" +STD_CPP="c++11" +WARN_C="-Wall -Wextra + -Wduplicated-branches + -Wduplicated-cond + -Wformat=2 + -Wmissing-include-dirs + -Wshadow + -Werror=return-type + -Werror=pointer-arith + -Werror=init-self + -Werror=incompatible-pointer-types" +WARN_CPP="$WARN_C" +SRC_C="$(find src -name '*.c')" +SRC_CPP="$(find src -name '*.cpp')" +TESTS_C="$(find tests -name '*.c')" +TESTS_CPP="$(find tests -name '*.cpp')" + +# Directory with dependency configs. +# See cbuild/example_dependency_configs +DEPENDENCY_CONFIGS_DIR='dependencies' +# List of dependency config files in DEPENDENCY_CONFIGS_DIR separated by space. +ENABLED_DEPENDENCIES='tlibc' + +# OBJDIR structure: +# ├── objects/ - Compiled object files. Cleans on each call of build task +# ├── static_libs/ - Symbolic links to static libraries used by linker. Cleans on each call of build task. +# ├── static_libs/ - Symbolic links to dynamic libraries used by linker. Cleans on each call of build task. +# └── profile/ - gcc *.gcda profiling info files +OBJDIR="obj" +OUTDIR="bin" +STATIC_LIB_FILE="$PROJECT.a" + +INCLUDE="-I./include -I../tlibc/include" + +# OS-specific options +case "$OS" in + WINDOWS) + EXEC_FILE="test.exe" + SHARED_LIB_FILE="$PROJECT.dll" + INCLUDE="$INCLUDE " + # example: "-lSDL2 -lSDL2_image" + LINKER_LIBS="" + ;; + LINUX) + EXEC_FILE="test" + SHARED_LIB_FILE="$PROJECT.so" + INCLUDE="$INCLUDE " + LINKER_LIBS="" + ;; + *) + error "operating system $OS has no configuration variants" + ;; +esac + +# TASKS +case "$TASK" in + # creates executable using profiling info if it exists + build_exec) + SRC_C="$SRC_C $TESTS_C" + SRC_CPP="$SRC_CPP $TESTS_CPP" + # -flto applies more optimizations across object files + # -flto=auto is needed to multithreaded copilation + # -fuse-linker-plugin is required to use static libs with lto + # -fprofile-use enables compiler to use profiling info files to optimize executable + # -fprofile-prefix-path sets path where profiling info about objects are be saved + # -fdata-sections -ffunction-sections -Wl,--gc-sections removes unused code + C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-use -fprofile-prefix-path=$(realpath $OBJDIR)/objects -fdata-sections -ffunction-sections -Wl,--gc-sections" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + POST_TASK_SCRIPT="" + ;; + # creates executable with debug info and no optimizations + build_exec_dbg) + SRC_C="$SRC_C $TESTS_C" + SRC_CPP="$SRC_CPP $TESTS_CPP" + C_ARGS="-O0 -g3" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + POST_TASK_SCRIPT="" + ;; + # creates shared library + build_shared_lib) + C_ARGS="-O2 -fpic -flto -shared" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh" + POST_TASK_SCRIPT="" + ;; + # creates shared library with debug symbols and no optimizations + build_shared_lib_dbg) + C_ARGS="-O0 -g3 -fpic -shared" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS -Wl,-soname,$SHARED_LIB_FILE" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_shared_lib.sh" + POST_TASK_SCRIPT="" + ;; + # creates static library + build_static_lib) + C_ARGS="-O2 -fpic -fdata-sections -ffunction-sections" + CPP_ARGS="$C_ARGS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh" + POST_TASK_SCRIPT="" + ;; + # creates static library with debug symbols and no optimizations + build_static_lib_dbg) + C_ARGS="-O0 -g3" + CPP_ARGS="$C_ARGS" + PRE_TASK_SCRIPT="" + TASK_SCRIPT="@cbuild/default_tasks/build_static_lib.sh" + POST_TASK_SCRIPT="" + ;; + # executes $EXEC_FILE + exec) + TASK_SCRIPT="@cbuild/default_tasks/exec.sh" + ;; + # executes $EXEC_FILE with valgrind memory checker + valgrind) + VALGRIND_ARGS="-s --read-var-info=yes --track-origins=yes --fullpath-after=$(pwd)/ --leak-check=full --show-leak-kinds=all" + TASK_SCRIPT="@cbuild/default_tasks/valgrind.sh" + ;; + # generates profiling info + profile) + OUTDIR="$OUTDIR/profile" + # -flto applies more optimizations across object files + # -flto=auto is needed to multithreaded copilation + # -fuse-linker-plugin is required to use static libs with lto + # -pg adds code to executable, that generates file containing function call info (gmon.out) + # -fprofile-generate generates executable with profiling code + # -fprofile-prefix-path sets path where profiling info about objects will be saved + C_ARGS="-O2 -flto=auto -fuse-linker-plugin -fprofile-generate -fprofile-prefix-path=$(realpath $OBJDIR)/objects" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/profile.sh" + POST_TASK_SCRIPT="" + ;; + # compiles program with -pg and runs it with gprof + # uses gprof2dot python script to generate function call tree (pip install gprof2dot) + # requires graphviz (https://www.graphviz.org/download/source/) + gprof) + OUTDIR="$OUTDIR/gprof" + # arguments that emit some call counter code and disable optimizations to see function names + # https://github.com/msys2/MINGW-packages/issues/8503#issuecomment-1365475205 + C_ARGS="-O0 -g -pg -no-pie -fno-omit-frame-pointer + -fno-inline-functions -fno-inline-functions-called-once + -fno-optimize-sibling-calls -fopenmp" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/gprof.sh" + POST_TASK_SCRIPT="" + ;; + # compiles program and runs it with callgrind (part of valgrind) + # uses gprof2dot python script to generate function call tree (pip install gprof2dot) + # requires graphviz (https://www.graphviz.org/download/source/) + # P.S. detailed results can be viewed in KCacheGrind + callgrind) + OUTDIR="$OUTDIR/callgrind" + # -pg adds code to executable, that generates file containing function call info (gmon.out) + C_ARGS="-O2 -flto=auto -fuse-linker-plugin" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/callgrind.sh" + POST_TASK_SCRIPT="" + ;; + # compiles executable with sanitizers and executes it to find errors and warnings + sanitize) + OUTDIR="$OUTDIR/sanitize" + C_ARGS="-O0 -g3 -fsanitize=undefined,address" + CPP_ARGS="$C_ARGS" + LINKER_ARGS="$CPP_ARGS $LINKER_LIBS" + PRE_TASK_SCRIPT="@cbuild/default_tasks/build_exec.sh" + TASK_SCRIPT="@cbuild/default_tasks/exec.sh" + POST_TASK_SCRIPT="" + ;; + # rebuilds specified dependencies + # EXAMPLE: `cbuild rebuild_dependencies=libexample1,fonts` + # 'all' can be specified to rebuild all dependencies + rebuild_dependencies) + TASK_SCRIPT="@cbuild/default_tasks/rebuild_dependencies.sh" + ;; + # deletes generated files + clean) + TASK_SCRIPT="@cbuild/default_tasks/clean.sh" + ;; + # nothing to do + "" | no_task) + ;; + # unknown task + *) + error "task <$PROJECT/$TASK> not found" + ;; +esac diff --git a/project.config.user.default b/project.config.user.default new file mode 100644 index 0000000..901aa1d --- /dev/null +++ b/project.config.user.default @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +# Project user config is ignored by git. +# Here you can add variables that users might want to change +# on their local machine, without commiting to the repository. + +# Directory where you install dependencies. +# Do not confuse with DEPENDENCY_CONFIGS_DIR +# Example: +# libexample source code is at `../libexample`, and dependency config +# that specifies how to build this lib is at `dependencies/libexample.config` +DEPENDENCIES_DIR=".." diff --git a/src/toml.c b/src/toml.c new file mode 100644 index 0000000..f460949 --- /dev/null +++ b/src/toml.c @@ -0,0 +1,1786 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include +#include +#include "tlibtoml/toml.h" + +static ATTRIBUTE_THREAD_LOCAL TomlErr g_err = {TOML_OK, (char*)"", TOML_TRUE}; + +static void* toml_default_malloc(void *context, size_t size) +{ + (void)context; + void *p = malloc(size); + assert(p != NULL); + return p; +} + +static void* toml_default_realloc(void *context, void *p, size_t size) +{ + (void)context; + void *ptr = realloc(p, size); + assert(ptr != NULL); + return ptr; +} + +static void toml_default_free(void *context, void *p) +{ + (void)context; + free(p); +} + +static TomlAllocFuncs g_default_alloc_funcs = { + &toml_default_malloc, + &toml_default_realloc, + &toml_default_free +}; + +static void *g_alloc_context = NULL; +static const TomlAllocFuncs* g_alloc_funcs = &g_default_alloc_funcs; + +void toml_set_allocator(void *context, const TomlAllocFuncs *funcs) +{ + g_alloc_context = context; + g_alloc_funcs = funcs; +} + +void* toml_malloc(size_t size) +{ + return g_alloc_funcs->malloc(g_alloc_context, size); +} + +void* toml_realloc(void *p, size_t size) +{ + return g_alloc_funcs->realloc(g_alloc_context, p, size); +} + +void toml_free(void *p) +{ + if (p != NULL) { + g_alloc_funcs->free(g_alloc_context, p); + } +} + +char* toml_strdup(const char *str) +{ + size_t len = strlen(str) + 1; + void *new = toml_malloc(len); + if (new == NULL) + return NULL; + + return memcpy(new, str, len); +} + +char *toml_strndup(const char *str, size_t n) +{ + char *result = toml_malloc(n + 1); + if (result == NULL) + return NULL; + + result[n] = 0; + return memcpy(result, str, n); +} + +int toml_vasprintf(char **str, const char *format, va_list args) +{ + int size = 0; + + va_list args_copy; + va_copy(args_copy, args); + size = vsnprintf(NULL, (size_t)size, format, args_copy); + va_end(args_copy); + + if (size < 0) { + return size; + } + + *str = toml_malloc((size_t)size + 1); + if (*str == NULL) + return -1; + + return vsprintf(*str, format, args); +} + +int toml_asprintf(char **str, const char *format, ...) +{ + va_list args; + va_start(args, format); + int size = toml_vasprintf(str, format, args); + va_end(args); + return size; +} + +const TomlErr* toml_err(void) +{ + return &g_err; +} + +void toml_err_clear(void) +{ + if (g_err.code != TOML_OK) { + if (!g_err._is_literal) { + toml_free(g_err.message); + } + g_err.code = TOML_OK; + g_err.message = (char*)""; + g_err._is_literal = TOML_TRUE; + } +} + +static inline void toml_err_set(TomlErrCode code, const char *format, ...) +{ + assert(g_err.code == TOML_OK); + va_list args; + va_start(args, format); + g_err.code = code; + toml_vasprintf(&g_err.message, format, args); + g_err._is_literal = TOML_FALSE; + va_end(args); +} + +static inline void toml_err_set_literal(TomlErrCode code, const char *message) +{ + assert(g_err.code == TOML_OK); + g_err.code = code; + g_err.message = (char *)message; + g_err._is_literal = TOML_TRUE; +} + +static inline size_t toml_roundup_pow_of_two_size_t(size_t x) +{ + size_t v = x; + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; +#if SIZE_MAX == 0xffff + v |= v >> 8; +#elif SIZE_MAX == 0xffffffff + v |= v >> 8; + v |= v >> 16; +#elif SIZE_MAX == 0xffffffffffffffffll + v |= v >> 8; + v |= v >> 16; + v |= v >> 32; +#endif + v++; + return v; +} + +TomlString *toml_string_new(void) +{ + TomlString *self = toml_malloc(sizeof(TomlString)); + self->str = NULL; + self->len = 0; + self->_capacity = 0; + return self; +} + +TomlString *toml_string_from_str(const char *str) +{ + TomlString *self = toml_string_new(); + toml_string_append_str(self, str); + return self; +} + +TomlString *toml_string_from_nstr(const char *str, size_t len) +{ + TomlString *self = toml_string_new(); + toml_string_append_nstr(self, str, len); + return self; +} + +static inline void toml_string_expand_if_necessary(TomlString *self, size_t len_to_add) +{ + if (self->len + len_to_add + 1 > self->_capacity) { + size_t new_capacity = toml_roundup_pow_of_two_size_t(self->len + len_to_add + 1); + new_capacity = new_capacity >= 8 ? new_capacity : 8; + self->str = toml_realloc(self->str, new_capacity); + self->_capacity = new_capacity; + } +} + +void toml_string_append_char(TomlString *self, char ch) +{ + toml_string_expand_if_necessary(self, 1); + self->str[self->len] = ch; + self->str[self->len + 1] = 0; + self->len++; +} + +void toml_string_append_str(TomlString *self, const char *str) +{ + size_t len = strlen(str); + toml_string_expand_if_necessary(self, len); + memcpy(&self->str[self->len], str, len + 1); + self->len += len; +} + +void toml_string_append_nstr(TomlString *self, const char *str, size_t len) +{ + toml_string_expand_if_necessary(self, len); + memcpy(&self->str[self->len], str, len); + self->len += len; + self->str[self->len] = 0; +} + +void toml_string_free(TomlString *self) +{ + if (self != NULL) { + free(self->str); + free(self); + } +} + +TomlString *toml_string_clone(const TomlString *self) +{ + return toml_string_from_nstr(self->str, self->len); +} + +int toml_string_equals(const TomlString *self, const TomlString *other) +{ + if (self == other) { + return TOML_TRUE; + } + + if (self->len != other->len) { + return TOML_FALSE; + } + + if (self->str == other->str) { + return TOML_TRUE; + } + + for (size_t i = 0; i < self->len; i++) { + if (self->str[i] != other->str[i]) { + return TOML_FALSE; + } + } + + return TOML_TRUE; +} + +TomlTable *toml_table_new(void) +{ + TomlTable *self = toml_malloc(sizeof(TomlTable)); + self->_capacity = 0; + self->_keyvals = NULL; + self->len = 0; + return self; +} + +void toml_table_free(TomlTable *self) +{ + if (self != NULL) { + for (size_t i = 0; i < self->len; i++) { + toml_string_free(self->_keyvals[i].key); + toml_value_free(self->_keyvals[i].value); + } + free(self->_keyvals); + free(self); + } +} + +void toml_table_expand_if_necessary(TomlTable *self) +{ + if (self->len + 1 > self->_capacity) { + size_t new_capacity = self->_capacity > 0 ? self->_capacity * 2 : 8; + void *p = toml_realloc(self->_keyvals, sizeof(TomlKeyValue) * new_capacity); + self->_keyvals = p; + self->_capacity = new_capacity; + } +} + +void toml_table_set_by_string(TomlTable *self, TomlString *key, TomlValue *value) +{ + TomlValue **slot = NULL; + for (size_t i = 0; i < self->len; i++) { + if (toml_string_equals(self->_keyvals[i].key, key)) { + slot = &self->_keyvals[i].value; + } + } + + if (slot == NULL) { + toml_table_expand_if_necessary(self); + self->_keyvals[self->len].key = key; + self->_keyvals[self->len].value = value; + self->len++; + } else { + *slot = value; + } +} + +TomlValue *toml_table_get_by_string(const TomlTable *self, const TomlString *key) +{ + TomlValue *value = NULL; + for (size_t i = 0; i < self->len; i++) { + if (toml_string_equals(self->_keyvals[i].key, key)) { + value = self->_keyvals[i].value; + } + } + return value; +} + +TomlValue *toml_table_getn(const TomlTable *self, const char *key, size_t key_len) +{ + TomlString str = {(char *)key, key_len, 0}; + return toml_table_get_by_string(self, &str); +} + +TomlValue *toml_table_get(const TomlTable *self, const char *key) +{ + return toml_table_getn(self, key, strlen(key)); +} + +TomlTable* toml_table_get_as_table(const TomlTable *self, const char *key) +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_TABLE); + return v->value.table; +} + +TomlArray* toml_table_get_as_array(const TomlTable *self, const char *key) +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_ARRAY); + return v->value.array; +} + +TomlString* toml_table_get_as_string(const TomlTable *self, const char *key) +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_STRING); + return v->value.string; +} + +#if defined(_MSC_VER) || defined(__APPLE__) +long long toml_table_get_as_integer(const TomlTable *self, const char *key) +#else +long toml_table_get_as_integer(const TomlTable *self, const char *key) +#endif +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_INTEGER); + return v->value.integer; +} + +double toml_table_get_as_float(const TomlTable *self, const char *key) +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_FLOAT); + return v->value.float_; +} + +const struct tm* toml_table_get_as_datetime(const TomlTable *self, const char *key) +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_DATETIME); + return &v->value.datetime; +} + +int toml_table_get_as_boolean(const TomlTable *self, const char *key) +{ + TomlValue *v = toml_table_get(self, key); + assert(v != NULL); + assert(v->type == TOML_BOOLEAN); + return v->value.boolean; +} + +void toml_table_setn(TomlTable *self, const char *key, size_t key_len, TomlValue *value) +{ + TomlString *str = toml_string_from_nstr(key, key_len); + toml_table_set_by_string(self, str, value); +} + +void toml_table_set(TomlTable *self, const char *key, TomlValue *value) +{ + toml_table_setn(self, key, strlen(key), value); +} + +TomlTableIter toml_table_iter_new(TomlTable *table) +{ + TomlTableIter self = { table, table->_keyvals }; + return self; +} + +TomlKeyValue* toml_table_iter_get(TomlTableIter *self) +{ + return self->_keyval; +} + +int toml_table_iter_has_next(TomlTableIter *self) +{ + return self->_keyval != NULL; +} + +void toml_table_iter_next(TomlTableIter *self) +{ + if (self->_keyval < self->_table->_keyvals + self->_table->len) { + self->_keyval++; + } + + if (self->_keyval >= self->_table->_keyvals + self->_table->len) { + self->_keyval = NULL; + } +} + +TomlArray *toml_array_new(void) +{ + TomlArray *self = toml_malloc(sizeof(TomlArray)); + self->elements = NULL; + self->len = 0; + self->_capacity = 0; + return self; +} + +void toml_array_free(TomlArray *self) +{ + if (self != NULL) { + for (size_t i = 0; i < self->len; i++) { + toml_value_free(self->elements[i]); + } + free(self->elements); + free(self); + } +} + +void toml_array_expand_if_necessary(TomlArray *self) +{ + if (self->len + 1 > self->_capacity) { + size_t new_capacity = self->_capacity > 0 ? self->_capacity * 2 : 8; + void *p = toml_realloc(self->elements, sizeof(TomlValue *) * new_capacity); + self->elements = p; + self->_capacity = new_capacity; + } +} + +void toml_array_append(TomlArray *self, TomlValue *value) +{ + toml_array_expand_if_necessary(self); + self->elements[self->len++] = value; +} + +TomlValue *toml_value_new(TomlType type) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->type = type; + switch (type) { + case TOML_TABLE: + self->value.table = NULL; + break; + case TOML_ARRAY: + self->value.array = NULL; + break; + case TOML_STRING: + self->value.string = NULL; + break; + case TOML_INTEGER: + self->value.integer = 0; + break; + case TOML_FLOAT: + self->value.float_ = 0.0; + break; + case TOML_BOOLEAN: + self->value.boolean = TOML_FALSE; + break; + case TOML_DATETIME: + memset(&self->value.datetime, 0, sizeof(struct tm)); + break; + } + return self; +} + +TomlValue *toml_value_from_str(const char *str) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.string = toml_string_from_str(str); + self->type = TOML_STRING; + return self; +} + +TomlValue *toml_value_from_nstr(const char *str, size_t len) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.string = toml_string_from_nstr(str, len); + self->type = TOML_STRING; + return self; +} + +TomlValue *toml_value_new_table(void) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.table = toml_table_new(); + self->type = TOML_TABLE; + return self; +} + +TomlValue *toml_value_new_array(void) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.array = toml_array_new(); + self->type = TOML_ARRAY; + return self; +} + +#if defined(_MSC_VER) || defined(__APPLE__) +TomlValue *toml_value_new_integer(long long integer) +#else +TomlValue *toml_value_new_integer(long integer) +#endif +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.integer = integer; + self->type = TOML_INTEGER; + return self; +} + +TomlValue *toml_value_new_float(double float_) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.float_ = float_; + self->type = TOML_FLOAT; + return self; +} + +TomlValue *toml_value_new_datetime(void) +{ + return toml_value_new(TOML_DATETIME); +} + +TomlValue *toml_value_new_boolean(int boolean) +{ + TomlValue *self = toml_malloc(sizeof(TomlValue)); + self->value.boolean = boolean; + self->type = TOML_BOOLEAN; + return self; +} + +void toml_value_free(TomlValue *self) +{ + if (self != NULL) { + switch (self->type) { + case TOML_STRING: + toml_string_free(self->value.string); + break; + case TOML_TABLE: + toml_table_free(self->value.table); + break; + case TOML_ARRAY: + toml_array_free(self->value.array); + break; + case TOML_DATETIME: + memset(&self->value.datetime, 0, sizeof(struct tm)); + break; + default: + break; + } + free(self); + } +} + +typedef struct _TomlParser { + const char* begin; + const char* end; + const char* ptr; + int lineno; + int colno; + char* filename; +} TomlParser; + +TomlParser *toml_parser_new(const char *str, size_t len) +{ + TomlParser *self = toml_malloc(sizeof(TomlParser)); + self->begin = str; + self->end = str + len; + self->ptr = str; + self->lineno = 1; + self->colno = 1; + self->filename = NULL; + return self; +} + +void toml_parser_free(TomlParser *self) +{ + if (self != NULL) { + free(self->filename); + free(self); + } +} + +void toml_move_next(TomlParser *self) +{ + if (self->ptr < self->end) { + if (*self->ptr == '\n') { + self->lineno++; + self->colno = 1; + } else { + self->colno++; + } + self->ptr++; + } +} + +void toml_next_n(TomlParser *self, int n) +{ + for (int i = 0; i < n; i++) { + toml_move_next(self); + } +} + +TomlString* toml_parse_bare_key(TomlParser *self) +{ + const char *str = self->ptr; + size_t len = 0; + + while (self->ptr < self->end) { + char ch = *self->ptr; + + if (!(isalnum(ch) || ch == '_' || ch == '-')) + break; + + len++; + toml_move_next(self); + } + + return toml_string_from_nstr(str, len); +} + +char toml_hex_char_to_int(char ch) +{ + assert(isxdigit(ch)); + if (isdigit(ch)) { + return ch - '0'; + } else if (islower(ch)) { + return ch - 'a' + 10; + } else if (isupper(ch)) { + return ch - 'A' + 10; + } + return 0; +} + +int toml_encode_unicode_scalar(TomlString *result, TomlParser *parser, int n) +{ + unsigned int scalar = 0; + + if (parser->ptr + n > parser->end) { + toml_err_set(TOML_ERR_UNICODE, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + return TOML_ERR_UNICODE; + } + + for (int i = 0; i < n; i++) { + char ch = *parser->ptr; + if (isxdigit(ch)) { + scalar = scalar * 16 + (unsigned int)toml_hex_char_to_int(ch); + toml_move_next(parser); + } else { + toml_err_set(TOML_ERR_UNICODE, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + return TOML_ERR_UNICODE; + } + } + + if ((scalar >= 0xd800 && scalar <= 0xdfff) || + (scalar >= 0xfffe && scalar <= 0xffff)) { + toml_err_set(TOML_ERR_UNICODE, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + return TOML_ERR_UNICODE; + } + + if (scalar <= 0x7f) { + toml_string_append_char(result, (char)scalar); + return 0; + } + + if (scalar <= 0x7ff) { + toml_string_append_char(result, (char)(0xc0 | (scalar >> 6))); + toml_string_append_char(result, (char)(0x80 | (scalar & 0x3f))); + return 0; + } + + if (scalar <= 0xffff) { + toml_string_append_char(result, (char)(0xe0 | (scalar >> 12))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 6) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | (scalar & 0x3f))); + return 0; + } + + if (scalar <= 0x1fffff) { + toml_string_append_char(result, (char)(0xf0 | (scalar >> 18))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 12) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 6) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | (scalar & 0x3f))); + return 0; + } + + if (scalar <= 0x3ffffff) { + toml_string_append_char(result, (char)(0xf8 | (scalar >> 24))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 18) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 12) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 6) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | (scalar & 0x3f))); + return 0; + } + + if (scalar <= 0x7fffffff) { + toml_string_append_char(result, (char)(0xfc | (scalar >> 30))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 24) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 18) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 12) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | ((scalar >> 6) & 0x3f))); + toml_string_append_char(result, (char)(0x80 | (scalar & 0x3f))); + return 0; + } + + toml_err_set(TOML_ERR_UNICODE, "%s:%d:%d: invalid unicode scalar", + parser->filename, parser->lineno, parser->colno); + + return TOML_ERR_UNICODE; +} + +TomlString* toml_parse_basic_string(TomlParser *self) +{ + TomlString *result = toml_string_new(); + + while (self->ptr < self->end && *self->ptr != '\"' && *self->ptr != '\n') { + char ch1 = *self->ptr; + if (ch1 == '\\') { + if (self->ptr >= self->end) break; + + toml_move_next(self); + char ch2 = *self->ptr; + + if (ch2 == '\"') { + toml_string_append_char(result, '\"'); + toml_move_next(self); + } else if (ch2 == 'b') { + toml_string_append_char(result, '\b'); + toml_move_next(self); + } else if (ch2 == 't') { + toml_string_append_char(result, '\t'); + toml_move_next(self); + } else if (ch2 == 'n') { + toml_string_append_char(result, '\n'); + toml_move_next(self); + } else if (ch2 == 'f') { + toml_string_append_char(result, '\f'); + toml_move_next(self); + } else if (ch2 == 'r') { + toml_string_append_char(result, '\r'); + toml_move_next(self); + } else if (ch2 == '"') { + toml_string_append_char(result, '\"'); + toml_move_next(self); + } else if (ch2 == '\\') { + toml_string_append_char(result, '\\'); + toml_move_next(self); + } else if (ch2 == 'u') { + toml_move_next(self); + if (toml_encode_unicode_scalar(result, self, 4) != 0) { + toml_string_free(result); + return NULL; + } + } else if (ch2 == 'U') { + toml_move_next(self); + if (toml_encode_unicode_scalar(result, self, 8) != 0) { + toml_string_free(result); + return NULL; + } + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid escape charactor"); + toml_string_free(result); + } + } else { + toml_string_append_char(result, ch1); + toml_move_next(self); + } + } + + if (self->ptr >= self->end || *self->ptr != '\"' || *self->ptr == '\n') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated basic string", + self->filename, self->lineno, self->colno); + toml_string_free(result); + } + + toml_move_next(self); + + return result; +} + +TomlString* toml_parse_literal_string(TomlParser *self) +{ + TomlString *result = toml_string_new(); + + while (self->ptr < self->end && *self->ptr != '\'' && *self->ptr != '\n') { + toml_string_append_char(result, *self->ptr); + toml_move_next(self); + } + + if (self->ptr >= self->end || *self->ptr != '\'' || *self->ptr == '\n') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated literal string", + self->filename, self->lineno, self->colno); + toml_string_free(result); + return NULL; + } + + toml_move_next(self); + + return result; +} + +TomlValue* toml_parse_basic_string_value(TomlParser *self) +{ + TomlValue *value = NULL; + + TomlString *string = toml_parse_basic_string(self); + if (string == NULL) + return NULL; + + value = toml_value_new(TOML_STRING); + value->value.string = string; + + return value; +} + +TomlValue *toml_parse_literal_string_value(TomlParser *self) +{ + TomlValue *value = NULL; + + TomlString *string = toml_parse_literal_string(self); + if (string == NULL) + return NULL; + + value = toml_value_new(TOML_STRING); + value->value.string = string; + + return value; +} + +TomlValue* toml_parse_multi_line_basic_string(TomlParser *self) +{ + TomlValue *value = NULL; + + TomlString *result = toml_string_new(); + + if (*self->ptr == '\n') { + toml_move_next(self); + } + + while (self->ptr + 3 <= self->end && strncmp(self->ptr, "\"\"\"", 3) != 0) { + char ch1 = *self->ptr; + + if (ch1 == '\\') { + if (self->ptr + 3 > self->end || strncmp(self->ptr, "\"\"\"", 3) == 0) + break; + + toml_move_next(self); + char ch2 = *self->ptr; + + if (ch2 == '\"') { + toml_string_append_char(result, '\"'); + toml_move_next(self); + } else if (ch2 == 'b') { + toml_string_append_char(result, '\b'); + toml_move_next(self); + } else if (ch2 == 't') { + toml_string_append_char(result, '\t'); + toml_move_next(self); + } else if (ch2 == 'n') { + toml_string_append_char(result, '\n'); + toml_move_next(self); + } else if (ch2 == 'f') { + toml_string_append_char(result, '\f'); + toml_move_next(self); + } else if (ch2 == 'r') { + toml_string_append_char(result, '\r'); + toml_move_next(self); + } else if (ch2 == '"') { + toml_string_append_char(result, '\"'); + toml_move_next(self); + } else if (ch2 == '\\') { + toml_string_append_char(result, '\\'); + toml_move_next(self); + } else if (ch2 == 'u') { + toml_move_next(self); + if (toml_encode_unicode_scalar(result, self, 4) != 0) { + toml_string_free(result); + return NULL; + } + } else if (ch2 == 'U') { + toml_move_next(self); + if (toml_encode_unicode_scalar(result, self, 8) != 0) { + toml_string_free(result); + return NULL; + } + } else if (ch2 == '\n') { + do { + toml_move_next(self); + } while (self->ptr + 3 <= self->end && isspace(*self->ptr)); + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid escape charactor", + self->filename, self->lineno, self->colno); + toml_string_free(result); + return NULL; + } + } else { + toml_string_append_char(result, ch1); + toml_move_next(self); + } + } + + if (self->ptr + 3 > self->end || strncmp(self->ptr, "\"\"\"", 3) != 0) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated multi-line basic string", + self->filename, self->lineno, self->colno); + toml_string_free(result); + return NULL; + } + + toml_next_n(self, 3); + + value = toml_value_new(TOML_STRING); + value->value.string = result; + + return value; +} + +TomlValue* toml_parse_multi_line_literal_string(TomlParser *self) +{ + TomlValue *value = NULL; + + TomlString *result = toml_string_new(); + + if (*self->ptr == '\n') { + toml_move_next(self); + } + + while (self->ptr + 3 <= self->end && strncmp(self->ptr, "\'\'\'", 3) != 0) { + toml_string_append_char(result, *self->ptr); + toml_move_next(self); + } + + if (self->ptr + 3 > self->end || strncmp(self->ptr, "\'\'\'", 3) != 0) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated multi-line literal string", + self->filename, self->lineno, self->colno); + toml_string_free(result); + return NULL; + } + + toml_next_n(self, 3); + + value = toml_value_new(TOML_STRING); + value->value.string = result; + + return value; +} + +TomlValue* toml_parse_datetime(const char *str, size_t len) +{ + (void)str; + (void)len; + return toml_value_new(TOML_DATETIME); +} + +TomlValue* toml_parse_int_or_float_or_time(TomlParser *self) +{ + TomlString *str = NULL; + TomlValue *result = NULL; + + char type = 'i'; + int base = 10; + + str = toml_string_new(); + + // Determine nan and inf type as float, as we cannot determine by dot. + // But do not strip it because we will append it to the string later + if (self->ptr + 3 <= self->end && + (strncmp(self->ptr, "nan", 3) == 0 || strncmp(self->ptr, "inf", 3) == 0)) { + type = 'f'; + } + + if (self->ptr + 4 <= self->end && + (strncmp(self->ptr, "+nan", 4) == 0 || + strncmp(self->ptr, "-nan", 4) == 0 || + strncmp(self->ptr, "+inf", 4) == 0 || + strncmp(self->ptr, "-inf", 4) == 0)) { + type = 'f'; + } + + // If there is a base prefix, set the base and strip the prefix, + // because strtoll() do not recognize 0o and 0b + if (self->ptr + 2 <= self->end) { + if (strncmp(self->ptr, "0x", 2) == 0) { + base = 16; + toml_next_n(self, 2); + } else if (strncmp(self->ptr, "0o", 2) == 0) { + base = 8; + toml_next_n(self, 2); + } else if (strncmp(self->ptr, "0b", 2) == 0) { + base = 2; + toml_next_n(self, 2); + } + } + + char last_char = 0; + int has_exp = TOML_FALSE; + while (self->ptr < self->end) { + if (*self->ptr == '+' || *self->ptr == '-') { + if (last_char == 0 || ((last_char == 'e' || last_char == 'E') && !has_exp)) { + if (last_char != 0) { + type = 'f'; + has_exp = TOML_TRUE; + } + toml_string_append_char(str, *self->ptr); + } else { + break; + } + } else if (isalnum(*self->ptr)) { + if ((*self->ptr == 'e' || *self->ptr == 'E') && base == 10) { + type = 'f'; + } + + toml_string_append_char(str, *self->ptr); + } else if (*self->ptr == '.') { + if (type == 'i') { + type = 'f'; + toml_string_append_char(str, *self->ptr); + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid float", + self->filename, self->lineno, self->colno); + toml_string_free(str); + return NULL; + } + } else if (*self->ptr == '_') { + if (type == 't') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid datetime", + self->filename, self->lineno, self->colno); + toml_string_free(str); + return NULL; + } + + if (!isalnum(last_char)) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid integer or float", + self->filename, self->lineno, self->colno); + toml_string_free(str); + return NULL; + } + } else if (*self->ptr == '-') { + type = 't'; + toml_string_append_char(str, *self->ptr); + } else { + break; + } + + last_char = *self->ptr; + toml_move_next(self); + } + + if (last_char == '_') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid integer or float or datetime", + self->filename, self->lineno, self->colno); + toml_string_free(str); + return NULL; + } + + if (type == 'i') { + char *end = NULL; + long long n = strtoll(str->str, &end, base); + if (end < str->str + str->len) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid integer", + self->filename, self->lineno, self->colno); + toml_string_free(str); + return NULL; + } + result = toml_value_new_integer(n); + } else if (type == 'f') { + char *end = NULL; + double n = strtod(str->str, &end); + if (end < str->str + str->len) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: invalid float"); + goto cleanup; + } + result = toml_value_new_float(n); + } else if (type == 't') { + result = toml_parse_datetime(str->str, str->len); + } + +cleanup: + toml_string_free(str); + return result; +} + +TomlValue* toml_parse_bool(TomlParser *self) +{ + if (self->ptr + 4 <= self->end && strncmp(self->ptr, "true", 4) == 0 && + (self->ptr + 4 == self->end || isspace(*(self->ptr + 4)) || *(self->ptr + 4) == ',' || *(self->ptr + 4) == ']' || *(self->ptr + 4) == '}')) { + toml_next_n(self, 4); + return toml_value_new_boolean(TOML_TRUE); + } + + if (self->ptr + 5 <= self->end && strncmp(self->ptr, "false", 5) == 0 && + (self->ptr + 5 == self->end || isspace(*(self->ptr + 5)) || *(self->ptr + 5) == ',' || *(self->ptr + 5) == ']' || *(self->ptr + 5) == '}')) { + toml_next_n(self, 5); + return toml_value_new_boolean(TOML_FALSE); + } + + return NULL; +} + +TomlValue* toml_parse_array(TomlParser *self); +TomlValue* toml_parse_inline_table(TomlParser *self); + +TomlValue* toml_parse_value(TomlParser *self) +{ + TomlValue *value = NULL; + + char ch = *self->ptr; + + if (strncmp(self->ptr, "\"\"\"", 3) == 0) { + toml_next_n(self, 3); + value = toml_parse_multi_line_basic_string(self); + } else if (strncmp(self->ptr, "\'\'\'", 3) == 0) { + toml_next_n(self, 3); + value = toml_parse_multi_line_literal_string(self); + } else if (ch == '\"') { + toml_move_next(self); + value = toml_parse_basic_string_value(self); + } else if (ch == '\'') { + toml_move_next(self); + value = toml_parse_literal_string_value(self); + } else if (isdigit(ch) || ch == '+' || ch == '-' || ch == '.' || ch == 'n' || ch == 'i') { + value = toml_parse_int_or_float_or_time(self); + } else if (ch == 't' || ch == 'f') { + value = toml_parse_bool(self); + } else if (ch == '[') { + toml_move_next(self); + value = toml_parse_array(self); + } else if (ch == '{') { + toml_move_next(self); + value = toml_parse_inline_table(self); + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + } + + return value; +} + +TomlErrCode toml_parse_key_value(TomlParser *self, TomlTable *table) +{ + TomlString *key = NULL; + TomlValue *value = NULL; + + while (self->ptr < self->end) { + char ch; + + ch = *self->ptr; + while (self->ptr < self->end && isspace(ch)) { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) break; + + if (isalnum(ch) || ch == '_') { + key = toml_parse_bare_key(self); + if (key == NULL) + return toml_err()->code; + } else if (ch == '\"') { + toml_move_next(self); + key = toml_parse_basic_string(self); + if (key == NULL) + return toml_err()->code; + } else if (ch == '\'') { + toml_move_next(self); + key = toml_parse_literal_string(self); + if (key == NULL) + return toml_err()->code; + } else if (ch == '[') { + break; + } else if (ch == '#') { + do { + toml_move_next(self); + ch = *self->ptr; + } while (self->ptr < self->end && ch != '\n'); + toml_move_next(self); + continue; + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + return TOML_ERR_SYNTAX; + } + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + return TOML_ERR_SYNTAX; + } + + if (ch != '=') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + return TOML_ERR_SYNTAX; + } + + toml_move_next(self); + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + return TOML_ERR_SYNTAX; + } + + value = toml_parse_value(self); + if (value == NULL) + return toml_err()->code; + + toml_table_set_by_string(table, key, value); + + key = NULL; + value = NULL; + + while (self->ptr < self->end && (*self->ptr == ' ' || *self->ptr == '\t')) { + toml_move_next(self); + } + + if (self->ptr == self->end) + break; + + if (*self->ptr == '#') { + do { + toml_move_next(self); + } while (self->ptr < self->end && *self->ptr != '\n'); + } + + if (*self->ptr == '\r') { + toml_move_next(self); + } + + if (*self->ptr == '\n') { + toml_move_next(self); + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: new line expected", + self->filename, self->lineno, self->colno); + toml_value_free(value); + toml_string_free(key); + return TOML_ERR_SYNTAX; + } + } + + return TOML_OK; +} + +TomlValue* toml_parse_array(TomlParser *self) +{ + TomlValue *array = NULL; + TomlValue *value = NULL; + + array = toml_value_new_array(); + + while (self->ptr < self->end) { + if (isspace(*self->ptr)) { + while (self->ptr < self->end && isspace(*self->ptr)) { + toml_move_next(self); + } + } else if (*self->ptr == '#') { + do { + toml_move_next(self); + } while (self->ptr < self->end && *self->ptr != '\n'); + toml_move_next(self); + } else if (*self->ptr == '\n') { + toml_move_next(self); + } else if (*self->ptr == ']') { + toml_move_next(self); + break; + } else { + value = toml_parse_value(self); + if (value == NULL) { + goto error; + } + + toml_array_append(array->value.array, value); + + value = NULL; + + while (self->ptr < self->end) { + if (isspace(*self->ptr)) { + do { + toml_move_next(self); + } while (self->ptr < self->end && isspace(*self->ptr)); + } else if (*self->ptr == '#') { + do { + toml_move_next(self); + } while (self->ptr < self->end && *self->ptr != '\n'); + } else { + break; + } + } + + if (self->ptr < self->end && *self->ptr == ',') { + toml_move_next(self); + } + } + } + + goto end; + +error: + toml_value_free(value); + toml_value_free(array); + array = NULL; + +end: + return array; +} + +TomlValue* toml_parse_inline_table(TomlParser *self) +{ + TomlValue *table = NULL; + + table = toml_value_new_table(); + + while (self->ptr < self->end) { + TomlString *key = NULL; + TomlValue *value = NULL; + char ch; + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) { + toml_move_next(self); + ch = *self->ptr; + } + + if (isalnum(ch) || ch == '_') { + key = toml_parse_bare_key(self); + if (key == NULL) + goto error; + } else if (ch == '\"') { + toml_move_next(self); + key = toml_parse_basic_string(self); + if (key == NULL) + goto error; + } else if (ch == '\'') { + toml_move_next(self); + key = toml_parse_literal_string(self); + if (key == NULL) + goto error; + } else if (ch == '}') { + break; + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + goto error; + } + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + goto error; + } + + if (ch != '=') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + goto error; + } + + toml_move_next(self); + + ch = *self->ptr; + while (self->ptr < self->end && (ch == ' ' || ch == '\t')) { + toml_move_next(self); + ch = *self->ptr; + } + + if (self->ptr == self->end) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unterminated key value pair"); + goto error; + } + + value = toml_parse_value(self); + if (value == NULL) + goto error; + + toml_table_set_by_string(table->value.table, key, value); + + while (self->ptr < self->end && (*self->ptr == ' ' || *self->ptr == '\t')) { + toml_move_next(self); + } + + if (*self->ptr == ',') { + toml_move_next(self); + } else if (*self->ptr == '}') { + toml_move_next(self); + break; + } + } + + goto end; + +error: + toml_value_free(table); + table = NULL; + +end: + return table; +} + +TomlTable* toml_walk_table_path(TomlParser *parser, TomlTable *table, + TomlArray *key_path, int is_array, + int create_if_not_exist) +{ + TomlTable *real_table = table; + TomlValue *new_table = NULL; + TomlValue *array = NULL; + TomlString *part_copy = NULL; + + if (is_array) { + size_t i = 0; + for (; i < key_path->len - 1; i++) { + TomlString *part = key_path->elements[i]->value.string; + TomlValue *t = toml_table_get_by_string(real_table, part); + if (t == NULL) { + if (create_if_not_exist) { + new_table = toml_value_new_table(); + part_copy = toml_string_clone(part); + toml_table_set_by_string(real_table, part_copy, new_table); + real_table = new_table->value.table; + part_copy = NULL; + new_table = NULL; + } else { + real_table = NULL; + break; + } + } else { + real_table = t->value.table; + } + } + + TomlString *part = key_path->elements[i]->value.string; + TomlValue *t = toml_table_get_by_string(real_table, part); + if (t == NULL) { + if (create_if_not_exist) { + array = toml_value_new_array(); + new_table = toml_value_new_table(); + toml_array_append(array->value.array, new_table); + part_copy = toml_string_clone(part); + toml_table_set_by_string(real_table, part_copy, array); + real_table = new_table->value.table; + part_copy = NULL; + array = NULL; + new_table = NULL; + } else { + real_table = NULL; + } + } else { + if (t->type != TOML_ARRAY) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: this key was not an array", + parser->filename, parser->lineno, parser->colno); + goto error; + } + + TomlValue *new_table = toml_value_new_table(); + toml_array_append(t->value.array, new_table); + real_table = new_table->value.table; + } + } else { + for (size_t i = 0; i < key_path->len; i++) { + TomlString *part = key_path->elements[i]->value.string; + TomlValue *t = toml_table_get_by_string(real_table, part); + if (t == NULL) { + if (create_if_not_exist) { + new_table = toml_value_new_table(); + part_copy = toml_string_clone(part); + toml_table_set_by_string(real_table, part_copy, new_table); + real_table = new_table->value.table; + part_copy = NULL; + new_table = NULL; + } else { + real_table = NULL; + break; + } + } else { + if (t->type == TOML_ARRAY) { + real_table = t->value.array->elements[t->value.array->len - 1]->value.table; + } else if (t->type == TOML_TABLE) { + real_table = t->value.table; + } + } + } + } + + goto end; + +error: + toml_string_free(part_copy); + toml_value_free(new_table); + toml_value_free(array); + real_table = NULL; + +end: + return real_table; +} + +TomlErrCode toml_parse_table(TomlParser *self, TomlTable *table) +{ + TomlArray *key_path = NULL; + int is_array = TOML_FALSE; + TomlTable *real_table = table; + TomlErrCode rc = 0; + + key_path = toml_array_new(); + + if (self->ptr < self->end && *self->ptr == '[') { + is_array = TOML_TRUE; + toml_move_next(self); + } + + while (1) { + if (*self->ptr == ' ' || *self->ptr == '\t') { + do { + toml_move_next(self); + } while (*self->ptr < *self->end && (*self->ptr == ' ' || *self->ptr == '\t')); + } else if (*self->ptr == ']') { + if (is_array) { + if (self->ptr + 2 <= self->end && strncmp(self->ptr, "]]", 2) == 0) { + toml_next_n(self, 2); + break; + } + } else { + toml_move_next(self); + break; + } + } else { + TomlString *key_part = NULL; + TomlValue *key_part_value = NULL; + + if (isalnum(*self->ptr) || *self->ptr == '_') { + key_part = toml_parse_bare_key(self); + if (key_part == NULL) + goto cleanup; + } else if (*self->ptr == '\"') { + toml_move_next(self); + key_part = toml_parse_basic_string(self); + if (key_part == NULL) + goto cleanup; + } else if (*self->ptr == '\'') { + toml_move_next(self); + key_part = toml_parse_literal_string(self); + if (key_part == NULL) + goto cleanup; + } else { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: unexpected token", + self->filename, self->lineno, self->colno); + } + + key_part_value = toml_value_new(TOML_STRING); + key_part_value->value.string = key_part; + + toml_array_append(key_path, key_part_value); + + while (self->ptr < self->end && + (*self->ptr == ' ' || *self->ptr == '\t')) { + toml_move_next(self); + } + + if (self->ptr < self->end && *self->ptr == '.') { + toml_move_next(self); + } + } + } + + if (key_path->len == 0) { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: empty table name", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + while (self->ptr < self->end && + (*self->ptr == ' ' || *self->ptr == '\t' || *self->ptr == '\r')) { + toml_move_next(self); + } + + if (self->ptr < self->end && *self->ptr != '\n') { + toml_err_set(TOML_ERR_SYNTAX, "%s:%d:%d: new line expected", + self->filename, self->lineno, self->colno); + goto cleanup; + } + + real_table = toml_walk_table_path(self, table, key_path, is_array, TOML_TRUE); + if (real_table == NULL) + goto error; + + toml_parse_key_value(self, real_table); + + goto cleanup; + +error: + rc = toml_err()->code; +cleanup: + toml_array_free(key_path); + return rc; +} + +TomlTable* toml_parse(TomlParser *self) +{ + TomlTable *table = NULL; + + table = toml_table_new(); + + while (self->ptr < self->end) { + char ch = *self->ptr; + + while (self->ptr < self->end && isspace(ch)) { + toml_move_next(self); + ch = *self->ptr; + } + + if (ch == '#') { + do { + toml_move_next(self); + ch = *self->ptr; + } while (self->ptr < self->end && ch != '\n'); + toml_move_next(self); + } else if (ch == '[') { + toml_move_next(self); + if (toml_parse_table(self, table) != 0) + return NULL; + } else if (isalnum(ch) || ch == '_' || ch == '-') { + if (toml_parse_key_value(self, table) != 0) + return NULL; + } else if (ch == ' ' || ch == '\t' || ch == '\r') { + do { + toml_move_next(self); + ch = *self->ptr; + } while (ch == ' ' || ch == '\t' || ch == '\r'); + } else if (ch == '\n') { + toml_move_next(self); + } + } + + return table; +} + +TomlTable* toml_load_nstr_filename(const char *str, size_t len, + const char *filename) +{ + TomlParser *parser = NULL; + TomlTable *table = NULL; + + parser = toml_parser_new(str, len); + parser->filename = toml_strdup(filename); + + table = toml_parse(parser); + + toml_parser_free(parser); + return table; +} + +TomlTable* toml_load_nstr(const char *str, size_t len) +{ + return toml_load_nstr_filename(str, len, ""); +} + +TomlTable* toml_load_str(const char *str) +{ + return toml_load_nstr(str, sizeof(str)); +} + +TomlTable* toml_load_file_filename(FILE *file, const char *filename) +{ + TomlTable *table = NULL; + TomlString *str = NULL; + + str = toml_string_new(); + + toml_string_expand_if_necessary(str, 4095); + + size_t count; + size_t bytes_to_read; + do { + bytes_to_read = str->_capacity - str->len - 1; + + count = fread(&str->str[str->len], 1, bytes_to_read, file); + if (ferror(file)) { + toml_err_set(TOML_ERR_OS, "Error when reading %s [errno %d: %s]", filename, errno, strerror(errno)); + goto error; + } + + str->len += count; + + if (str->len + 1 >= str->_capacity) { + toml_string_expand_if_necessary(str, str->_capacity * 2); + } + } while (count == bytes_to_read); + + str->str[str->len] = 0; + + table = toml_load_nstr_filename(str->str, str->len, filename); + + goto cleanup; + +error: + toml_table_free(table); + table = NULL; + +cleanup: + toml_string_free(str); + return table; +} + +TomlTable* toml_load_file(FILE *file) +{ + return toml_load_file_filename(file, ""); +} + +TomlTable* toml_load_filename(const char *filename) +{ + TomlTable *table = NULL; + FILE *f = NULL; + + f = fopen(filename, "r"); + if (f == NULL) { + toml_err_set(TOML_ERR_OS, "Cannot open file %s [errno %d: %s]", filename, errno, strerror(errno)); + goto error; + } + + table = toml_load_file_filename(f, filename); + + goto cleanup; + +error: + toml_table_free(table); + table = NULL; + +cleanup: + if (f != NULL) + fclose(f); + return table; +} + +// vim: sw=4 ts=8 sts=4 et diff --git a/tests/complex-structure.toml b/tests/complex-structure.toml new file mode 100644 index 0000000..0ead9a9 --- /dev/null +++ b/tests/complex-structure.toml @@ -0,0 +1,35 @@ +[a] +aa = 1 + +[[a.b]] +bb = 2 + +[[a.b]] +cc = 3 + +[a.c] +dd = 4 + +[a.c.d] +ee = 5 + +[[a.c.d.e]] +ff = 6 + +[[a.c.d.e]] +gg = 7 + +[a.c.d.e.f] +hh = 8 + +[[b]] +ii = 9 + +[[b]] +jj = 10 + +[b.a] +kk = 11 + +[this.is-a."complex" . 'table' . name] +ok = true diff --git a/tests/example.toml b/tests/example.toml new file mode 100644 index 0000000..75df63b --- /dev/null +++ b/tests/example.toml @@ -0,0 +1,47 @@ +# This is a TOML document. Boom. + +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +# dob = 1979-05-27T07:32:00Z # First class dates? Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + + # You can indent as you please. Tabs or spaces. TOML don't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + country = "中国" # This should be parsed as UTF-8 + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it + +# Line breaks are OK when inside arrays +hosts = [ + "alpha", + "omega" +] + +# Products + + [[products]] + name = "Hammer" + sku = 738594937 + + [[products]] + name = "Nail" + sku = 284758393 + color = "gray" diff --git a/tests/fruit.toml b/tests/fruit.toml new file mode 100644 index 0000000..ef7e9ac --- /dev/null +++ b/tests/fruit.toml @@ -0,0 +1,13 @@ +[[fruit.blah]] + name = "apple" + + [fruit.blah.physical] + color = "red" + shape = "round" + +[[fruit.blah]] + name = "banana" + + [fruit.blah.physical] + color = "yellow" + shape = "bent" diff --git a/tests/hard_example.toml b/tests/hard_example.toml new file mode 100644 index 0000000..6abe76d --- /dev/null +++ b/tests/hard_example.toml @@ -0,0 +1,33 @@ +# Test file for TOML +# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate +# This part you'll really hate + +[the] +test_string = "You'll hate me after this - #" # " Annoying, isn't it? + + [the.hard] + test_array = [ "] ", " # "] # ] There you go, parse this! + test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ] + # You didn't think it'd as easy as chucking out the last #, did you? + another_test_string = " Same thing, but with a string #" + harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too" + # Things will get harder + + [the.hard."bit#"] + "what?" = "You don't think some user won't do that?" + multi_line_array = [ + "]", + # ] Oh yes I did + ] + +# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test + +#[error] if you didn't catch this, your parser is broken +#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this +#array = [ +# "This might most likely happen in multiline arrays", +# Like here, +# "or here, +# and here" +# ] End of array comment, forgot the # +#number = 3.14 pi <--again forgot the # diff --git a/tests/hard_example_unicode.toml b/tests/hard_example_unicode.toml new file mode 100644 index 0000000..6173ce3 --- /dev/null +++ b/tests/hard_example_unicode.toml @@ -0,0 +1,36 @@ +# Tèƨƭ ƒïℓè ƒôř TÓM£ + +# Óñℓ¥ ƭλïƨ ôñè ƭřïèƨ ƭô è₥úℓáƭè á TÓM£ ƒïℓè ωřïƭƭèñ β¥ á úƨèř ôƒ ƭλè ƙïñδ ôƒ ƥářƨèř ωřïƭèřƨ ƥřôβáβℓ¥ λáƭè +# Tλïƨ ƥářƭ ¥ôú'ℓℓ řèáℓℓ¥ λáƭè + +[the] +test_string = "Ýôú'ℓℓ λáƭè ₥è áƒƭèř ƭλïƨ - #" # " Âññô¥ïñϱ, ïƨñ'ƭ ïƭ? + + + [the.hard] + test_array = [ "] ", " # "] # ] Tλèřè ¥ôú ϱô, ƥářƨè ƭλïƨ! + test_array2 = [ "Tèƨƭ #11 ]ƥřôƲèδ ƭλáƭ", "Éжƥèřï₥èñƭ #9 ωáƨ á ƨúççèƨƨ" ] + # Ýôú δïδñ'ƭ ƭλïñƙ ïƭ'δ áƨ èáƨ¥ áƨ çλúçƙïñϱ ôúƭ ƭλè ℓáƨƭ #, δïδ ¥ôú? + another_test_string = "§á₥è ƭλïñϱ, βúƭ ωïƭλ á ƨƭřïñϱ #" + harder_test_string = " Âñδ ωλèñ \"'ƨ ářè ïñ ƭλè ƨƭřïñϱ, áℓôñϱ ωïƭλ # \"" # "áñδ çô₥₥èñƭƨ ářè ƭλèřè ƭôô" + # Tλïñϱƨ ωïℓℓ ϱèƭ λářδèř + + [the.hard."βïƭ#"] + "ωλáƭ?" = "Ýôú δôñ'ƭ ƭλïñƙ ƨô₥è úƨèř ωôñ'ƭ δô ƭλáƭ?" + multi_line_array = [ + "]", + # ] Óλ ¥èƨ Ì δïδ + ] + +# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test + +#[error] ïƒ ¥ôú δïδñ'ƭ çáƭçλ ƭλïƨ, ¥ôúř ƥářƨèř ïƨ βřôƙèñ +#string = "Âñ¥ƭλïñϱ ôƭλèř ƭλáñ ƭáβƨ, ƨƥáçèƨ áñδ ñèωℓïñè áƒƭèř á ƙè¥ϱřôúƥ ôř ƙè¥ Ʋáℓúè ƥáïř λáƨ èñδèδ ƨλôúℓδ ƥřôδúçè áñ èřřôř úñℓèƨƨ ïƭ ïƨ á çô₥₥èñƭ" ℓïƙè ƭλïƨ + +#array = [ +# "Tλïƨ ₥ïϱλƭ ₥ôƨƭ ℓïƙèℓ¥ λáƥƥèñ ïñ ₥úℓƭïℓïñè ářřá¥ƨ", +# £ïƙè λèřè, +# "ôř λèřè, +# áñδ λèřè" +# ] Éñδ ôƒ ářřᥠçô₥₥èñƭ, ƒôřϱôƭ ƭλè # +#number = 3.14 ƥï <--áϱáïñ ƒôřϱôƭ ƭλè # \ No newline at end of file diff --git a/tests/key-values.toml b/tests/key-values.toml new file mode 100644 index 0000000..344c370 --- /dev/null +++ b/tests/key-values.toml @@ -0,0 +1,115 @@ +# All kinds of keys and string values + +key1-bare-with-dash = "basic string 1" + +key2-bare_with_underscore = 'literal string 2' + +3-key-start-with-digit = "basic string \b 3 with \t space \f and\"escapes \\\u05d0\n" + +"key 4 double quoted\n" = "basic string \rbasic string with carriage return" + +'key 5 single quoted' = """multi line basic string""" + +'key 6 multi-line basic string strip beginning new line' = """ + There should be no new line, but two leading spaces.""" + +'key 7 line ending backslash' = """ +lines with \ +ending backslash \ +should be concatenated into a single line and \ + + + new lines and spaces after the line ending backslash should be stripped.""" + +'key 8 multi-line basic string with escapes' = """ +Escapes \b + should\t +also \f + work \" +in \\ +multi-line \nbasic string.""" + +'key 9 multi-line literal string' = ''' +The first newline is +trimmed in raw strings. + All other whitespace or [(*&%$@!/\~`^#)] + is preserved. +''' + +# Numbers, copied from README.md on https://github.com/toml-lang/toml + +int1 = +99 +int2 = 42 +int3 = 0 +int4 = -17 +int5 = 1_000 +int6 = 5_349_221 +int7 = 1_2_3_4_5 # VALID but discouraged + +# hexadecimal with prefix `0x` +hex1 = 0xDEADBEEF +hex2 = 0xdeadbeef +hex3 = 0xdead_beef + +# octal with prefix `0o` +oct1 = 0o01234567 +oct2 = 0o755 # useful for Unix file permissions +oct3 = 0o0123_4567 +oct4 = 0o7_5_5 + +# binary with prefix `0b` +bin1 = 0b11010110 +bin2 = 0b1101_0110 + +# fractional +flt1 = +1.0 +flt2 = 3.1415 +flt3 = -0.01 + +# exponent +flt4 = 5e+22 +flt5 = 1e6 +flt6 = -2E-2 + +# both +flt7 = 6.626e-34 +flt8 = 9_224_617.445_991_228_313 + +# infinity +sf1 = inf # positive infinity +sf2 = +inf # positive infinity +sf3 = -inf # negative infinity + +# not a number +sf4 = nan # actual sNaN/qNaN encoding is implementation specific +sf5 = +nan # same as `nan` +sf6 = -nan # valid, actual encoding is implementation specific + +# Boolean +bool1 = true +bool2 = false + +# Array +arr1 = [ 1, 2, 3 ] +arr2 = [ "red", "yellow", "green" ] +arr3 = [ [ 1, 2 ], [3, 4, 5] ] +arr4 = [ "all", 'strings', """are the same""", '''type'''] +arr5 = [ [ 1, 2 ], ["a", "b", "c"] ] + +#arr6 = [ 1, 2.0 ] # INVALID + +arr7 = [ + 1, 2, 3 +] + +arr8 = [ + 1, + 2, # this is ok +] + +# Inline tables +name = { first = "Tom", last = "Preston-Werner" } +point = { x = 1, y = 2 } +points = [ { x = 1, y = 2, z = 3 }, + { x = 7, y = 8, z = 9 }, + { x = 2, y = 4, z = 8 } ] diff --git a/tests/long_config.toml b/tests/long_config.toml new file mode 100644 index 0000000..bbd2ba6 --- /dev/null +++ b/tests/long_config.toml @@ -0,0 +1,132 @@ +[voice-5band-compressor] +input = "voice_44k_16bit_1ch.wav" +output = "voice_44k_16bit_2ch_5band_compressor.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, true, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] + +[MultibandCompressor-SweepLevelChange-5Bands-LR-32k] +input = "SweepLevelChange_32k_LR_2ch.wav" +output = "MultibandCompressor_SweepLevelChange_5Bands_LR_32k.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, true, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] + +[MultibandCompressor-SweepLevelChange-5Bands-LR-44k] +input = "SweepLevelChange_44k_LR_2ch.wav" +output = "MultibandCompressor_SweepLevelChange_5Bands_LR_44k.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, true, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] + +[MultibandCompressor-SweepLevelChange-5Bands-LR-48k] +input = "SweepLevelChange_48k_LR_2ch.wav" +output = "MultibandCompressor_SweepLevelChange_5Bands_LR_48k.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, true, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] + +[MultibandCompressor-SweepLevelChange-5Bands-1BandBypass-LR-32k] +input = "SweepLevelChange_32k_LR_2ch.wav" +output = "MultibandCompressor_SweepLevelChange_5Bands_1BandBypass_LR_32k.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, false, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] + +[MultibandCompressor-SweepLevelChange-5Bands-1BandBypass-LR-44k] +input = "SweepLevelChange_44k_LR_2ch.wav" +output = "MultibandCompressor_SweepLevelChange_5Bands_1BandBypass_LR_44k.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, false, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] + +[MultibandCompressor-SweepLevelChange-5Bands-1BandBypass-LR-48k] +input = "SweepLevelChange_48k_LR_2ch.wav" +output = "MultibandCompressor_SweepLevelChange_5Bands_1BandBypass_LR_48k.wav" +sidechain = "WhiteNoiseChange_44.1k_16bit_1ch.wav" +block-size = 256 +out-channels = 2 +bandnum = 5 +enabled = [true, true, true, false, true] +threshold = [-60.0, -10.0, -10.0, -10.0, -10.0] +ratio = [1.5, 1.5, 1.5, 1.5, 1.5] +attack-time = [30, 40, 50, 60, 70] +release-time = [100, 110, 120, 130, 140] +averaging-time = [30, 40, 50, 60, 70] +crossover_fc = [500.0, 1000.0, 4000.0, 10000.0] +saturation-threshold = [-1.0, -1.0, -1.0, -1.0, -1.0] +input-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +output-gain = [1.0, 1.0, 1.0, 1.0, 1.0] +side_chain_enabled = [true, true, true, true, true] diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..4e089c0 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,139 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include "tlibtoml/toml.h" + +#ifndef PROJECT_SOURCE_DIR +#define PROJECT_SOURCE_DIR ".." +#endif + +void print_table(const TomlTable *table); +void print_value(const TomlValue *value); + +void print_array(const TomlArray *array) +{ + printf("["); + for (size_t i = 0; i < array->len; i++) { + if (i > 0) { + printf(", "); + } + print_value(array->elements[i]); + } + printf("]"); +} + +void print_value(const TomlValue *value) +{ + switch (value->type) { + case TOML_TABLE: + print_table(value->value.table); + break; + case TOML_ARRAY: + print_array(value->value.array); + break; + case TOML_STRING: + printf("\"%s\"", value->value.string->str); + break; + case TOML_INTEGER: + printf("%" PRId64, value->value.integer); + break; + case TOML_FLOAT: + printf("%f", value->value.float_); + break; + case TOML_DATETIME: + printf("(datetime)"); + break; + case TOML_BOOLEAN: + printf("%s", value->value.boolean ? "true" : "false"); + break; + } +} + +void print_keyval(const TomlKeyValue *keyval) +{ + printf("\"%s\": ", keyval->key->str); + print_value(keyval->value); +} + +void print_table(const TomlTable *table) +{ + TomlTableIter it = toml_table_iter_new((TomlTable *)table); + + printf("{"); + size_t i = 0; + while (toml_table_iter_has_next(&it)) { + TomlKeyValue *keyval = toml_table_iter_get(&it); + + if (i > 0) { + printf(", "); + } + print_keyval(keyval); + + toml_table_iter_next(&it); + i++; + } + printf("}"); +} + +int test_run(const char *filename) +{ + TomlTable *table = NULL; + int rc = 0; + + table = toml_load_filename(filename); + if (table == NULL) + goto cleanup; + + print_table(table); + printf("\n"); + +cleanup: + toml_table_free(table); + + if (toml_err()->code != TOML_OK) { + fprintf(stderr, "%s\n", toml_err()->message); + rc = (int)toml_err()->code; + } + toml_err_clear(); + return rc; +} + +int main(void) +{ + static const char *const filenames[] = { + /* should parse */ + PROJECT_SOURCE_DIR "/tests/key-values.toml", + PROJECT_SOURCE_DIR "/tests/complex-structure.toml", + PROJECT_SOURCE_DIR "/tests/long_config.toml", + + /* should not parse */ + + /* tests from https://github.com/toml-lang/toml */ + PROJECT_SOURCE_DIR "/tests/example.toml", + PROJECT_SOURCE_DIR "/tests/fruit.toml", + PROJECT_SOURCE_DIR "/tests/hard_example.toml", + PROJECT_SOURCE_DIR "/tests/hard_example_unicode.toml" + }; + + int total_tests = sizeof(filenames) / sizeof(char *); + int num_passed = 0; + int num_failed = 0; + + for (int i = 0; i < total_tests; i++) { + int rc = test_run(filenames[i]); + if (rc == 0) { + printf("test %d success\n", i); + num_passed++; + } else { + printf("test %d returned %d\n", i, rc); + num_failed++; + } + } + + printf("total %d tests, %d passed, %d failed\n", + total_tests, num_passed, num_failed); + + return num_failed; +} diff --git a/tlibtoml.config b/tlibtoml.config new file mode 100644 index 0000000..db0830b --- /dev/null +++ b/tlibtoml.config @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# This is a dependency config. +# You can copy it to another project to add tlibtoml dependency. + +DEP_WORKING_DIR="$DEPENDENCIES_DIR/tlibtoml" + +function setup_user_config(){ + local user_config_path="project.config.user" + local absolute_dep_dir=$(realpath "$DEPENDENCIES_DIR") + file_copy_default_if_not_present "$user_config_path" "$user_config_path.default" + replace_var_value_in_script "$user_config_path" "DEPENDENCIES_DIR" "$absolute_dep_dir" +} + +if [[ "$TASK" = *_dbg ]]; then + dep_build_target="build_static_lib_dbg" +else + dep_build_target="build_static_lib" +fi +DEP_PRE_BUILD_COMMAND="setup_user_config" +DEP_BUILD_COMMAND="cbuild $dep_build_target" +DEP_POST_BUILD_COMMAND="" +DEP_CLEAN_COMMAND="cbuild clean" +DEP_DYNAMIC_OUT_FILES="" +DEP_STATIC_OUT_FILES="bin/tlibtoml.a" +DEP_OTHER_OUT_FILES="" +PRESERVE_OUT_DIRECTORY_STRUCTURE=false