diff --git a/CMakeLists.txt b/CMakeLists.txt index 3223e1c..ee771eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,17 @@ target_link_libraries(controller -luuid) option(WIRING_PI_DEBUG "Use WiringPi Debugging Tool (OFF)" OFF) -set(CMAKE_C_FLAGS "$ENV{CFLAGS}") -SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra -ffile-prefix-map=${CMAKE_SOURCE_DIR}/src/=") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} $ENV{CFLAGS}") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D'__FILENAME__=\"$(subst $(realpath ${CMAKE_SOURCE_DIR}/src/)/,,$(abspath $<))\"'") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -std=gnu99 -Wpedantic -Werror -Wall -Wextra") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -fprofile-arcs -ftest-coverage") +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + + +file(GLOB_RECURSE ALL_SOURCE_FILES src/*.c) + if(WIRING_PI_DEBUG) message("Showing wiringPi calls as debug") add_definitions("-DWIRING_PI_DEBUG") @@ -32,7 +38,6 @@ aux_source_directory(vendor VENDOR_SRC) add_dependencies(controller sql) -configure_file("controller.ini" "controller.ini" COPYONLY) configure_file("version.h.in" "version.h" @ONLY) @@ -47,21 +52,43 @@ add_custom_target(sql ) add_custom_target(run - COMMAND ./controller start + COMMAND ${CMAKE_BINARY_DIR}/controller DEPENDS controller - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} -) -add_custom_target(debug - COMMAND valgrind ./controller start - DEPENDS controller - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} -) -add_custom_target(debug-full - COMMAND valgrind --leak-check=full --show-leak-kinds=all ./controller start - DEPENDS controller - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) + add_custom_target(docs COMMAND doxygen WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) + +IF(CMAKE_BUILD_TYPE MATCHES Debug) + message(STATUS "loading debug targets") + add_custom_target(debug + COMMAND gdb ${CMAKE_BINARY_DIR}/controller + DEPENDS controller + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + add_custom_target(valgrind + COMMAND valgrind -s ${CMAKE_BINARY_DIR}/controller + DEPENDS controller + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + add_custom_target(valgrind-leak + COMMAND valgrind --leak-check=full --show-leak-kinds=all ${CMAKE_BINARY_DIR}/controller + DEPENDS controller + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + + add_custom_target( + clang-tidy + COMMAND /usr/bin/clang-tidy + ${ALL_SOURCE_FILES} + --header-filter=${CMAKE_SOURCE_DIR}/include/* + -- + -std=gnu99 + -I${CMAKE_SOURCE_DIR}/include + -I${CMAKE_SOURCE_DIR}/vendor + -I${CMAKE_BINARY_DIR} + ) +ENDIF(CMAKE_BUILD_TYPE MATCHES Debug) diff --git a/controller.ini b/controller.ini deleted file mode 100644 index dfec8e3..0000000 --- a/controller.ini +++ /dev/null @@ -1,69 +0,0 @@ -[controller] -name = new emgauwa device - -: 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env -discovery-port = 4421 -: 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env -mqtt-port = 1885 -mqtt-host = localhost - -relay-count = 10 -relays-init = 1 - -database = controller.sqlite -log-level = debug -log-file = stdout - -[relay-0] -driver = piface -pin = 0 -inverted = 0 -init = 0 - -[relay-1] -driver = piface -pin = 1 -inverted = 0 -init = 1 - -[relay-2] -driver = gpio -pin = 5 -inverted = 1 - -[relay-3] -driver = gpio -pin = 4 -inverted = 1 - -[relay-4] -driver = gpio -pin = 3 -inverted = 1 - -[relay-5] -driver = gpio -pin = 2 -inverted = 1 - -[relay-6] -driver = gpio -pin = 1 -inverted = 1 -pulse-duration = 3 - -[relay-7] -driver = gpio -pin = 0 -inverted = 1 -pulse-duration = 3 - -[relay-8] -driver = gpio -pin = 16 -inverted = 1 - -[relay-9] -driver = gpio -pin = 15 -inverted = 1 diff --git a/emgauwa-controller.conf b/emgauwa-controller.conf new file mode 100644 index 0000000..dcf6f9b --- /dev/null +++ b/emgauwa-controller.conf @@ -0,0 +1,66 @@ +[controller] +database = "emgauwa-controller.sqlite" + +mqtt-host = "127.0.0.1" + +[ports] +# 4422 for testing; 4421 for dev-env; 4420 for testing-env; 4419 for prod-env +discovery = 4421 +# 1886 for testing; 1885 for dev-env; 1884 for testing-env; 1883 for prod-env +mqtt = 1885 + +[logging] +level = "debug" +file = "stdout" + +[[relays]] +driver = "piface" +pin = 0 +inverted = 0 + +[[relays]] +driver = "piface" +pin = 1 +inverted = 0 + +[[relays]] +driver = "gpio" +pin = 5 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 4 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 3 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 2 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 1 +inverted = 1 +pulse-duration = 3 + +[[relays]] +driver = "gpio" +pin = 0 +inverted = 1 +pulse-duration = 3 + +[[relays]] +driver = "gpio" +pin = 16 +inverted = 1 + +[[relays]] +driver = "gpio" +pin = 15 +inverted = 1 diff --git a/emgauwa-controller.sqlite b/emgauwa-controller.sqlite new file mode 100644 index 0000000..2adcfc3 Binary files /dev/null and b/emgauwa-controller.sqlite differ diff --git a/include/cli.h b/include/cli.h new file mode 100644 index 0000000..41e7d4a --- /dev/null +++ b/include/cli.h @@ -0,0 +1,13 @@ +#ifndef CONTROLLER_CLI_H +#define CONTROLLER_CLI_H + +typedef struct cli +{ + const char *config_file; + int demo_mode; +} cli_t; + +void +cli_parse(int argc, const char **argv, cli_t *cli); + +#endif /* CONTROLLER_CLI_H */ diff --git a/include/config.h b/include/config.h index fe5b66a..9f623af 100644 --- a/include/config.h +++ b/include/config.h @@ -2,7 +2,8 @@ #define CONTROLLER_CONFIG_H #include -#include + +#include #include #include @@ -16,27 +17,50 @@ typedef struct uint8_t pulse_duration; } config_relay_t; -int -config_load(IniDispatch *disp, void *config_void); - typedef struct { - char *file; - char database[256]; - char user[256]; - char group[256]; - int log_level; - FILE *log_file; - run_type_t run_type; - char name[MAX_NAME_LENGTH + 1]; - uint16_t discovery_port; - uint16_t mqtt_port; - char mqtt_host[256]; - uint8_t relay_count; + char *name; + char *database; + char *user; + char *group; + char *mqtt_host; + char *include; int relays_init; + uint8_t relay_count; + + struct + { + int level; + FILE *file; + } logging; + + struct + { + uint16_t discovery; + uint16_t mqtt; + } ports; + config_relay_t *relay_configs; } config_t; -extern config_t global_config; +extern config_t *global_config; + +void +config_init(); + +void +config_free(); + +void +config_load_string(char **holder, const char *value); + +void +config_load(config_t *config, const char *cli_config_file); + +void +config_load_file(config_t *config, const char *file_name); + +void +config_load_directory(config_t *config, const char *directory_name); #endif //CONTROLLER_CONFIG_H diff --git a/include/constants.h b/include/constants.h index 0d27ab7..c195022 100644 --- a/include/constants.h +++ b/include/constants.h @@ -14,13 +14,6 @@ */ #define MAX_NAME_LENGTH 128 -/** - * @brief Maximum number of dbs for the databases for the MDB_env - * - * Used when calling mdb_env_set_maxdbs() in database_setup() - */ -#define MDB_MAXDBS 8 - /** * @brief How many milli seconds to wait until poll timeout in main loop */ @@ -28,4 +21,12 @@ #define PIFACE_GPIO_BASE 200 +#define DEFAULT_CONTROLLER_NAME "emgauwa-controller" +#define DEFAULT_CONFIG_PATH "emgauwa-controller.conf" +#define DEFAULT_GLOBAL_CONFIG_PATH "/etc/emgauwa/controller.conf" +#define DEFAULT_DATABASE_PATH "emgauwa-controller.sqlite" +#define DEFAULT_DISCOVERY_PORT 4421 +#define DEFAULT_MQTT_PORT 1885 +#define DEFAULT_MQTT_HOST "127.0.0.1" + #endif /* CONTROLLER_CONTANTS_H */ diff --git a/include/enums.h b/include/enums.h index 032622a..8606552 100644 --- a/include/enums.h +++ b/include/enums.h @@ -22,10 +22,4 @@ typedef enum RELAY_DRIVER_PIFACE, } relay_driver_t; -typedef enum -{ - RUN_TYPE_START, - RUN_TYPE_TEST, -} run_type_t; - #endif /* CONTROLLER_ENUMS_H */ diff --git a/include/helpers.h b/include/helpers.h index d1d3a38..314ca05 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -25,9 +25,6 @@ helper_get_port(int sock); int helper_open_discovery_socket(uint16_t discovery_port); -void -helper_parse_cli(int argc, const char **argv, config_t *config); - int helper_get_weekday(const struct tm *time_struct); diff --git a/src/helpers/parse_cli.c b/src/cli.c similarity index 52% rename from src/helpers/parse_cli.c rename to src/cli.c index af93eaf..9886dbd 100644 --- a/src/helpers/parse_cli.c +++ b/src/cli.c @@ -3,30 +3,31 @@ #include #include + +#include #include #include #include #include static const char *const usage[] = { - "controller [options] [[--] args]", "controller [options]", NULL, }; -#define PERM_READ (1<<0) -#define PERM_WRITE (1<<1) -#define PERM_EXEC (1<<2) - void -helper_parse_cli(int argc, const char **argv, config_t *config) +cli_parse(int argc, const char **argv, cli_t *cli) { + cli->config_file = NULL; + cli->demo_mode = 0; + int version = 0; struct argparse_option options[] = { OPT_HELP(), OPT_GROUP("Basic options"), - OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG), + OPT_STRING('c', "config", &cli->config_file, "path to config file", NULL, 0, OPT_NONEG), + OPT_BOOLEAN('d', "demo", &cli->demo_mode, "demo mode", NULL, 0, OPT_NONEG), OPT_BOOLEAN('v', "version", &version, "print version", NULL, 0, OPT_NONEG), OPT_END(), }; @@ -38,33 +39,11 @@ helper_parse_cli(int argc, const char **argv, config_t *config) "\nA brief description of what the program does and how it works.", "\nAdditional description of the program after the description of the arguments." ); - argc = argparse_parse(&argparse, argc, argv); + argparse_parse(&argparse, argc, argv); if(version) { printf("%s\n", EMGAUWA_CONTROLLER_VERSION); exit(0); } - - if(argc == 1) - { - if(strcmp(argv[0], "start") == 0) - { - config->run_type = RUN_TYPE_START; - return; - } - if(strcmp(argv[0], "test") == 0) - { - config->run_type = RUN_TYPE_TEST; - return; - } - LOGGER_CRIT("bad action '%s' given ('start', 'test')\n", argv[0]); - exit(1); - } - else - { - LOGGER_CRIT("no action given ('start', 'test')\n"); - exit(1); - } - return; } diff --git a/src/config.c b/src/config.c index 2e2fe47..df24d21 100644 --- a/src/config.c +++ b/src/config.c @@ -1,191 +1,442 @@ +#include +#include +#include #include #include +#include +#include -#include #include +#include #include -#include -#define CONFINI_IS_KEY(SECTION, KEY) \ - (ini_array_match(SECTION, disp->append_to, '.', disp->format) && \ - ini_string_match_ii(KEY, disp->data, disp->format)) +config_t *global_config; static int -config_load_log_level(IniDispatch *disp, config_t *config) +config_load_log_level(config_t *config, char *value) { - if(strcasecmp(disp->value, "debug") == 0) + if(strcmp(value, "debug") == 0) { setlogmask(LOG_UPTO(LOG_DEBUG)); - config->log_level = LOG_DEBUG; + config->logging.level = LOG_DEBUG; return 0; } - if(strcasecmp(disp->value, "info") == 0) + if(strcmp(value, "info") == 0) { setlogmask(LOG_UPTO(LOG_INFO)); - config->log_level = LOG_INFO; + config->logging.level = LOG_INFO; return 0; } - if(strcasecmp(disp->value, "notice") == 0) + if(strcmp(value, "notice") == 0) { setlogmask(LOG_UPTO(LOG_NOTICE)); - config->log_level = LOG_NOTICE; + config->logging.level = LOG_NOTICE; return 0; } - if(strcasecmp(disp->value, "warning") == 0) + if(strcmp(value, "warning") == 0) { setlogmask(LOG_UPTO(LOG_WARNING)); - config->log_level = LOG_WARNING; + config->logging.level = LOG_WARNING; return 0; } - if(strcasecmp(disp->value, "err") == 0) + if(strcmp(value, "err") == 0) { setlogmask(LOG_UPTO(LOG_ERR)); - config->log_level = LOG_ERR; + config->logging.level = LOG_ERR; return 0; } - if(strcasecmp(disp->value, "crit") == 0) + if(strcmp(value, "crit") == 0) { setlogmask(LOG_UPTO(LOG_CRIT)); - config->log_level = LOG_CRIT; + config->logging.level = LOG_CRIT; return 0; } - if(strcasecmp(disp->value, "emerg") == 0) + if(strcmp(value, "emerg") == 0) { setlogmask(LOG_UPTO(LOG_EMERG)); - config->log_level = LOG_EMERG; + config->logging.level = LOG_EMERG; return 0; } - LOGGER_WARNING("invalid log-level '%s'\n", disp->value); + LOGGER_WARNING("invalid log-level '%s'\n", value); + return 0; } static int -config_load_log_file(IniDispatch *disp, config_t *config) +config_load_log_file(config_t *config, char *value) { - if(strcasecmp(disp->value, "stdout") == 0) + if(strcmp(value, "stdout") == 0) { - config->log_file = stdout; + config->logging.file = stdout; return 0; } - if(strcasecmp(disp->value, "stderr") == 0) + if(strcmp(value, "stderr") == 0) { - config->log_file = stderr; + config->logging.file = stderr; return 0; } - config->log_file = fopen(disp->value, "a+"); + config->logging.file = fopen(value, "a+"); return 0; } -int -config_load(IniDispatch *disp, void *config_void) +static void +config_init_relay_configs(config_t *config) { - config_t *config = (config_t*)config_void; - char relay_section_name[10]; // "relay-255\0" is longest name - - if(disp->type == INI_KEY) + config->relay_configs = malloc(sizeof(config_relay_t) * config->relay_count); + for(uint8_t i = 0; i < config->relay_count; ++i) { - if(CONFINI_IS_KEY("controller", "name")) + config->relay_configs[i].driver = RELAY_DRIVER_NONE; + config->relay_configs[i].inverted = 0; + config->relay_configs[i].init = -1; + config->relay_configs[i].pin = 0; + config->relay_configs[i].pulse_duration = 0; + } +} + +static void +config_load_section_controller(config_t *config, toml_table_t* controller) +{ + toml_datum_t config_entry; + + config_entry = toml_string_in(controller, "name"); + if(config_entry.ok) + { + config_load_string(&config->name, config_entry.u.s); + free(config_entry.u.s); + } + + config_entry = toml_string_in(controller, "database"); + if(config_entry.ok) + { + config_load_string(&config->database, config_entry.u.s); + free(config_entry.u.s); + } + + config_entry = toml_string_in(controller, "user"); + if(config_entry.ok) + { + config_load_string(&config->user, config_entry.u.s); + free(config_entry.u.s); + } + + config_entry = toml_string_in(controller, "group"); + if(config_entry.ok) + { + config_load_string(&config->group, config_entry.u.s); + free(config_entry.u.s); + } + + config_entry = toml_string_in(controller, "mqtt-host"); + if(config_entry.ok) + { + config_load_string(&config->mqtt_host, config_entry.u.s); + free(config_entry.u.s); + } + + config_entry = toml_int_in(controller, "relays-init"); + if(config_entry.ok) + { + config->relays_init = config_entry.u.i; + } + + + config_entry = toml_string_in(controller, "include"); + if(config_entry.ok) + { + config_load_string(&config->include, config_entry.u.s); + free(config_entry.u.s); + } +} + +static void +config_load_section_logging(config_t *config, toml_table_t* logging) +{ + toml_datum_t config_entry; + + config_entry = toml_string_in(logging, "level"); + if(config_entry.ok) + { + config_load_log_level(config, config_entry.u.s); + free(config_entry.u.s); + } + + config_entry = toml_string_in(logging, "file"); + if(config_entry.ok) + { + config_load_log_file(config, config_entry.u.s); + free(config_entry.u.s); + } +} + +static void +config_load_section_ports(config_t *config, toml_table_t* ports) +{ + toml_datum_t config_entry; + + config_entry = toml_int_in(ports, "discovery"); + if(config_entry.ok) + { + config->ports.discovery = config_entry.u.i; + } + + config_entry = toml_int_in(ports, "mqtt"); + if(config_entry.ok) + { + config->ports.mqtt = config_entry.u.i; + } +} + +static void +config_load_section_relay(config_t *config, toml_table_t* relay, int relay_num) +{ + toml_datum_t config_entry; + + config_entry = toml_int_in(relay, "pin"); + if(config_entry.ok) + { + config->relay_configs[relay_num].pin = config_entry.u.i; + } + + config_entry = toml_int_in(relay, "inverted"); + if(config_entry.ok) + { + config->relay_configs[relay_num].inverted = config_entry.u.i; + } + + config_entry = toml_int_in(relay, "init"); + if(config_entry.ok) + { + config->relay_configs[relay_num].init = config_entry.u.i; + } + + config_entry = toml_int_in(relay, "pulse-duration"); + if(config_entry.ok) + { + config->relay_configs[relay_num].pulse_duration = config_entry.u.i; + } + + config_entry = toml_string_in(relay, "driver"); + if(config_entry.ok) + { + for(int i = 0; config_entry.u.s[i] != '\0'; ++i) { - strncpy(config->name, disp->value, MAX_NAME_LENGTH); - config->name[MAX_NAME_LENGTH] = '\0'; - return 0; + config_entry.u.s[i] = tolower(config_entry.u.s[i]); } - if(CONFINI_IS_KEY("controller", "database")) + + if(strcmp(config_entry.u.s, "gpio") == 0) { - strcpy(config->database, disp->value); - return 0; + config->relay_configs[relay_num].driver = RELAY_DRIVER_GPIO; } - if(CONFINI_IS_KEY("controller", "user")) + else if(strcmp(config_entry.u.s, "piface") == 0) { - strcpy(config->user, disp->value); - return 0; + config->relay_configs[relay_num].driver = RELAY_DRIVER_PIFACE; } - if(CONFINI_IS_KEY("controller", "group")) + else { - strcpy(config->group, disp->value); - return 0; - } - if(CONFINI_IS_KEY("controller", "log-level")) - { - return config_load_log_level(disp, config); - } - if(CONFINI_IS_KEY("controller", "log-file")) - { - return config_load_log_file(disp, config); - } - if(CONFINI_IS_KEY("controller", "discovery-port")) - { - config->discovery_port = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY("controller", "mqtt-port")) - { - config->mqtt_port = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY("controller", "mqtt-host")) - { - strcpy(config->mqtt_host, disp->value); - return 0; - } - if(CONFINI_IS_KEY("controller", "relays-init")) - { - config->relays_init = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY("controller", "relay-count")) - { - config->relay_count = atoi(disp->value); - config->relay_configs = malloc(sizeof(config_relay_t) * config->relay_count); - for(uint8_t i = 0; i < config->relay_count; ++i) - { - config->relay_configs[i].driver = RELAY_DRIVER_NONE; - config->relay_configs[i].inverted = 0; - config->relay_configs[i].init = -1; - config->relay_configs[i].pin = 0; - config->relay_configs[i].pulse_duration = 0; - } - return 0; - } - for(uint8_t i = 0; i < config->relay_count; ++i) - { - sprintf(relay_section_name, "relay-%u", i); - if(CONFINI_IS_KEY(relay_section_name, "pin")) - { - config->relay_configs[i].pin = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY(relay_section_name, "inverted")) - { - config->relay_configs[i].inverted = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY(relay_section_name, "init")) - { - config->relay_configs[i].init = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY(relay_section_name, "pulse-duration")) - { - config->relay_configs[i].pulse_duration = atoi(disp->value); - return 0; - } - if(CONFINI_IS_KEY(relay_section_name, "driver")) - { - if(strcasecmp(disp->value, "gpio") == 0) - { - config->relay_configs[i].driver = RELAY_DRIVER_GPIO; - return 0; - } - if(strcasecmp(disp->value, "piface") == 0) - { - config->relay_configs[i].driver = RELAY_DRIVER_PIFACE; - return 0; - } - LOGGER_WARNING("invalid driver '%s' in section '%s'\n", disp->value, relay_section_name); - return 0; - } + LOGGER_WARNING("invalid driver '%s' in section for relay %d\n", config_entry.u.s, relay_num); } + free(config_entry.u.s); + } +} + +void +config_init() +{ + global_config = calloc(1, sizeof(config_t)); + + config_load_string(&global_config->name, DEFAULT_CONTROLLER_NAME); + + config_load_string(&global_config->database, DEFAULT_DATABASE_PATH); + + config_load_string(&global_config->mqtt_host, DEFAULT_MQTT_HOST); + + + global_config->user = NULL; + global_config->group = NULL; + + global_config->include = NULL; + + global_config->relays_init = 0; + global_config->relay_count = 0; + + config_load_string(&global_config->mqtt_host, "127.0.0.1"); + + global_config->ports.mqtt = DEFAULT_MQTT_PORT; + global_config->ports.discovery = DEFAULT_DISCOVERY_PORT; + + global_config->logging.level = LOG_DEBUG; + global_config->logging.file = stdout; +} + +void +config_free() +{ + free(global_config->name); + free(global_config->database); + free(global_config->user); + free(global_config->group); + free(global_config->mqtt_host); + free(global_config->include); + + free(global_config); +} + +void +config_load_string(char **holder, const char *value) +{ + if(*holder) + { + free(*holder); + } + size_t value_len = strlen(value); + + char *new_holder = malloc(sizeof(char) * (value_len + 1)); + strcpy(new_holder, value); + new_holder[value_len] = '\0'; + + *holder = new_holder; +} + +static int +config_try_file(const char *path) +{ + if(access(path, F_OK) != 0) + { + return 1; + } + if(access(path, R_OK) != 0) + { + return 1; } return 0; } + +void +config_load(config_t *config, const char *cli_config_file) +{ + if(cli_config_file) + { + if(config_try_file(cli_config_file) == 0) + { + config_load_file(config, cli_config_file); + return; + } + LOGGER_CRIT("unable to open the passed config file '%s'\n", cli_config_file); + exit(1); + } + if(config_try_file(DEFAULT_CONFIG_PATH) == 0) + { + config_load_file(config, DEFAULT_CONFIG_PATH); + return; + } + if(config_try_file(DEFAULT_GLOBAL_CONFIG_PATH) == 0) + { + config_load_file(config, DEFAULT_GLOBAL_CONFIG_PATH); + return; + } +} + +void +config_load_file(config_t *config, const char *file_name) +{ + FILE *fp; + toml_table_t* config_toml; + char errbuf[256]; + + /* Open the file and parse content */ + fp = fopen(file_name, "r"); + if(fp == NULL) { + LOGGER_CRIT("unable to open config file '%s'\n", file_name); + exit(1); + } + config_toml = toml_parse_file(fp, errbuf, sizeof(errbuf)); + fclose(fp); + if(config_toml == NULL) { + LOGGER_CRIT("unable to parse config file '%s': %s\n", file_name, errbuf); + exit(1); + } + + toml_table_t* controller = toml_table_in(config_toml, "controller"); + if(controller) + { + config_load_section_controller(config, controller); + } + + toml_table_t* logging = toml_table_in(config_toml, "logging"); + if(logging) + { + config_load_section_logging(config, logging); + } + + toml_table_t* ports = toml_table_in(config_toml, "ports"); + if(ports) + { + config_load_section_ports(config, ports); + } + + toml_array_t* relays = toml_array_in(config_toml, "relays"); + if(relays) + { + config->relay_count = toml_array_nelem(relays); + config_init_relay_configs(config); + + for(int i = 0; i < config->relay_count; ++i) + { + config_load_section_relay(config, toml_table_at(relays, i), i); + } + } + + toml_free(config_toml); + + LOGGER_DEBUG("Loaded config from %s\n", file_name); +} + +void +config_load_directory(config_t *config, const char *directory_name) +{ + struct dirent *directory_entry; + DIR *directory; + + (void)config; + + directory = opendir(directory_name); + if(directory == NULL) + { + LOGGER_CRIT("cannot open directory '%s': %s\n", directory_name, strerror(errno)); + exit(1); + } + + while((directory_entry = readdir(directory)) != NULL) + { + struct stat sb; + const char *entry_name = directory_entry->d_name; + + size_t copied = 0; + + // Add 2 for '/' and '\0'. + size_t path_len = strlen(directory_name) + strlen(entry_name) + 1; + char *path = malloc(sizeof(char) * (path_len + 1)); + path[0] = '\0'; + + strncat(path + copied, directory_name, path_len - copied); + copied = strlen(path); + if(path[copied - 1] != '/') + { + strncat(path + copied, "/", path_len - copied); + copied = strlen(path); + } + strncat(path + copied, entry_name, path_len - copied); + + if(stat(path, &sb)) + { + LOGGER_WARNING("failed to get info for '%s': %s\n", path, strerror(errno)); + } + if(S_ISREG(sb.st_mode)) + { + config_load_file(config, path); + } + free(path); + } + + closedir(directory); +} diff --git a/src/connections.c b/src/connections.c index f32c062..45cb4af 100644 --- a/src/connections.c +++ b/src/connections.c @@ -11,7 +11,7 @@ struct mg_connection* connection_discovery_bind(struct mg_mgr *mgr) { char address[32]; - sprintf(address, "udp://0.0.0.0:%u", global_config.discovery_port); + sprintf(address, "udp://0.0.0.0:%u", global_config->ports.discovery); struct mg_connection *c = mg_bind(mgr, address, handler_discovery); return c; } @@ -29,7 +29,7 @@ struct mg_connection* connection_mqtt_connect(struct mg_mgr *mgr) { char address[512]; - sprintf(address, "tcp://%s:%u", global_config.mqtt_host, global_config.mqtt_port); + sprintf(address, "tcp://%s:%u", global_config->mqtt_host, global_config->ports.mqtt); struct mg_connection *c = mg_connect(mgr, address, handler_mqtt); return c; } diff --git a/src/database.c b/src/database.c index 1b6b36e..28392e5 100644 --- a/src/database.c +++ b/src/database.c @@ -14,7 +14,7 @@ static int in_transaction; void database_init() { - int rc = sqlite3_open(global_config.database, &global_database); + int rc = sqlite3_open(global_config->database, &global_database); if(rc) { diff --git a/src/handlers/command.c b/src/handlers/command.c index d2c04c9..9aa3984 100644 --- a/src/handlers/command.c +++ b/src/handlers/command.c @@ -53,9 +53,9 @@ handler_command_relay_pulse(mpack_node_t map) { uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM)); - if(relay_num > global_config.relay_count) + if(relay_num > global_config->relay_count) { - LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config.relay_count); + LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count); return; } @@ -65,7 +65,7 @@ handler_command_relay_pulse(mpack_node_t map) int duration = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_PULSE_DURATION)); if(duration == 0) { - duration = global_config.relay_configs[relay_num].pulse_duration; + duration = global_config->relay_configs[relay_num].pulse_duration; } target_relay->pulse_timer = duration; LOGGER_DEBUG("pulsing relay %d for %ds\n", relay_num, duration); @@ -87,9 +87,9 @@ handler_command_relay_name_set(mpack_node_t map) uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM)); const char *relay_name = mpack_node_str(mpack_node_map_uint(map, COMMAND_MAPPING_NAME)); - if(relay_num > global_config.relay_count) + if(relay_num > global_config->relay_count) { - LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config.relay_count); + LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count); return; } relay_set_name(global_controller->relays[relay_num], relay_name); @@ -120,7 +120,7 @@ handler_command_schedule_update(mpack_node_t map) int *relay_ids = junction_relay_schedule_get_relay_ids_with_schedule(new_schedule->id); - for(int i = 0; i < global_config.relay_count; ++i) + for(int i = 0; i < global_config->relay_count; ++i) { for(int j = 0; relay_ids[j] != 0; ++j) { @@ -140,9 +140,9 @@ handler_command_relay_schedules_set(mpack_node_t map) { uint8_t relay_num = mpack_node_u8(mpack_node_map_uint(map, COMMAND_MAPPING_RELAY_NUM)); - if(relay_num > global_config.relay_count) + if(relay_num > global_config->relay_count) { - LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config.relay_count); + LOGGER_WARNING("relay %d is not available (relay count: %d\n", relay_num, global_config->relay_count); return; } diff --git a/src/handlers/discovery.c b/src/handlers/discovery.c index db95e08..2cbbbbb 100644 --- a/src/handlers/discovery.c +++ b/src/handlers/discovery.c @@ -53,7 +53,7 @@ handler_discovery(struct mg_connection *c, int ev, void *ev_data) mpack_write_uint(&writer, DISCOVERY_MAPPING_COMMAND_PORT); mpack_write_u16(&writer, global_controller->command_port); mpack_write_uint(&writer, DISCOVERY_MAPPING_RELAY_COUNT); - mpack_write_u8(&writer, global_config.relay_count); + mpack_write_u8(&writer, global_config->relay_count); mpack_write_uint(&writer, DISCOVERY_MAPPING_NAME); mpack_write_cstr(&writer, global_controller->name); mpack_finish_map(&writer); diff --git a/src/handlers/loop.c b/src/handlers/loop.c index 2076952..51a0776 100644 --- a/src/handlers/loop.c +++ b/src/handlers/loop.c @@ -28,13 +28,13 @@ handler_loop(struct mg_connection *c_mqtt) localtime_r(×tamp, &time_last); timestamp = time(NULL); localtime_r(×tamp, &time_now); - for(uint_fast8_t i = 0; i < global_config.relay_count; ++i) + for(uint_fast8_t i = 0; i < global_config->relay_count; ++i) { relay_t *relay = global_controller->relays[i]; int is_on = 0; int is_on_schedule = relay_is_on_schedule(relay, &time_now); - int pulse_relay = global_config.relay_configs[i].pulse_duration; + int pulse_relay = global_config->relay_configs[i].pulse_duration; if(is_on_schedule) { @@ -73,17 +73,17 @@ handler_loop(struct mg_connection *c_mqtt) } relay->is_on = is_on; - if(global_config.relay_configs[i].inverted) + if(global_config->relay_configs[i].inverted) { is_on = !is_on; } - switch(global_config.relay_configs[i].driver) + switch(global_config->relay_configs[i].driver) { case RELAY_DRIVER_GPIO: - driver_gpio_set(global_config.relay_configs[i].pin, is_on); + driver_gpio_set(global_config->relay_configs[i].pin, is_on); break; case RELAY_DRIVER_PIFACE: - driver_piface_set(global_config.relay_configs[i].pin, is_on); + driver_piface_set(global_config->relay_configs[i].pin, is_on); break; default: LOGGER_WARNING("relay %d is not using a driver\n", i); diff --git a/src/helpers/drop_privileges.c b/src/helpers/drop_privileges.c index e2dde8a..df0ba7f 100644 --- a/src/helpers/drop_privileges.c +++ b/src/helpers/drop_privileges.c @@ -59,8 +59,8 @@ get_gid_for_group(char *group) int helper_drop_privileges() { - uid_t uid = get_uid_for_user(global_config.user); - gid_t gid = get_gid_for_group(global_config.group); + uid_t uid = get_uid_for_user(global_config->user); + gid_t gid = get_gid_for_group(global_config->group); LOGGER_DEBUG("drop privileges to %lu:%lu\n", uid, gid); diff --git a/src/logger.c b/src/logger.c index 2869668..2561462 100644 --- a/src/logger.c +++ b/src/logger.c @@ -17,10 +17,11 @@ const char *COLOR_EMERG = COLOR_MAGENTA; void logger_log(int level, const char *filename, int line, const char *func, const char *msg, ...) { - if(global_config.log_level < level) + if(global_config->logging.level < level) { return; } + va_list args; const char *level_str; const char *color; @@ -62,24 +63,28 @@ logger_log(int level, const char *filename, int line, const char *func, const ch time_t rawtime; time(&rawtime); strftime(timestamp_str, 32, "%Y-%m-%d %H:%M:%S", localtime(&rawtime)); + size_t timestamp_len = strlen(timestamp_str); - char *buffer = malloc(sizeof(char) * (128 + strlen(msg))); - sprintf(buffer, "%s[%5s] %s:%d:%s " COLOR_NONE "%s", color, level_str, filename, line, func, msg); + char *buffer = malloc(sizeof(char) * (128 + strlen(msg) + timestamp_len)); + sprintf(buffer, "%s %s[%5s] %s:%d:%s " COLOR_NONE "%s", timestamp_str, color, level_str, filename, line, func, msg); - //fprintf(stream, "%s %s:%d:%s " COLOR_NONE, timestamp_str, filename, line, func); - - va_list args; + // start arg va_list and find log_len va_start(args, msg); - vsyslog(level, buffer, args); + size_t log_len = vsnprintf(NULL, 0, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug va_end(args); - char *buffer_timed = malloc(sizeof(char) * (strlen(timestamp_str) + strlen(buffer) + 2)); - sprintf(buffer_timed, "%s %s", timestamp_str, buffer); + char *log_line = malloc(sizeof(char) * (log_len + 1)); + + // start arg va_list again and write log_line va_start(args, msg); - vfprintf(global_config.log_file, buffer_timed, args); - fflush(global_config.log_file); + vsprintf(log_line, buffer, args); // NOLINT(clang-analyzer-valist.Uninitialized): clang-tidy bug va_end(args); + syslog(level, "%s", log_line + timestamp_len + 1); + + fprintf(global_config->logging.file, "%s", log_line); + fflush(global_config->logging.file); + free(buffer); - free(buffer_timed); + free(log_line); } diff --git a/src/main.c b/src/main.c index 17149e2..d4906b5 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -21,9 +22,6 @@ #include #include #include -#include - -config_t global_config; static struct mg_mgr mgr; @@ -43,7 +41,7 @@ terminate(int signum) controller_free(global_controller); LOGGER_DEBUG("freeing relay configs config\n"); - free(global_config.relay_configs); + free(global_config->relay_configs); exit(signum); } @@ -66,45 +64,28 @@ main(int argc, const char** argv) signal(SIGABRT, terminate); signal(SIGTERM, terminate); + openlog("emgauwa-controller", 0, LOG_USER); setlogmask(LOG_UPTO(LOG_INFO)); /******************** LOAD CONFIG ********************/ - global_config.file = "controller.ini"; - global_config.discovery_port = 4421; - global_config.mqtt_port = 1885; + config_init(); - global_config.relay_count = 0; - global_config.relays_init = -1; + cli_t cli; + cli_parse(argc, argv, &cli); - global_config.log_level = LOG_INFO; - global_config.log_file = stdout; + config_load(global_config, cli.config_file); - strcpy(global_config.user, ""); - strcpy(global_config.group, ""); - - helper_parse_cli(argc, argv, &global_config); - - FILE * const ini_file = fopen(global_config.file, "rb"); - if(ini_file == NULL) + if(global_config->logging.file == NULL) { - LOGGER_CRIT("config file '%s' was not found\n", global_config.file); - exit(1); + global_config->logging.file = stdout; } - if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, config_load, &global_config)) - { - LOGGER_CRIT("unable to parse ini file\n"); - exit(1); - } - - fclose(ini_file); - if(global_config.log_file == NULL) + if(global_config->include) { - global_config.log_file = stdout; + config_load_directory(global_config, global_config->include); } - openlog("emgauwa-controller", 0, LOG_USER); if(sizeof(time_t) < 8) { @@ -146,38 +127,38 @@ main(int argc, const char** argv) int piface_setup = 0; - for(uint_fast8_t i = 0; i < global_config.relay_count; ++i) + for(uint_fast8_t i = 0; i < global_config->relay_count; ++i) { - int relay_default = global_config.relay_configs[i].init; + int relay_default = global_config->relay_configs[i].init; if(relay_default == -1) { - relay_default = global_config.relays_init; + relay_default = global_config->relays_init; } if(relay_default == -1) { - relay_default = global_config.relay_configs[i].inverted; + relay_default = global_config->relay_configs[i].inverted; } - if(global_config.relay_configs[i].driver == RELAY_DRIVER_GPIO) + if(global_config->relay_configs[i].driver == RELAY_DRIVER_GPIO) { - pinMode(global_config.relay_configs[i].pin, OUTPUT); - driver_gpio_set(global_config.relay_configs[i].pin, relay_default); + pinMode(global_config->relay_configs[i].pin, OUTPUT); + driver_gpio_set(global_config->relay_configs[i].pin, relay_default); } - if(global_config.relay_configs[i].driver == RELAY_DRIVER_PIFACE) + if(global_config->relay_configs[i].driver == RELAY_DRIVER_PIFACE) { if(!piface_setup) { piFaceSetup(PIFACE_GPIO_BASE); piface_setup = 1; } - driver_piface_set(global_config.relay_configs[i].pin, relay_default); + driver_piface_set(global_config->relay_configs[i].pin, relay_default); } } /******************** CHECK FOR TESTING RUN ********************/ - if(global_config.run_type == RUN_TYPE_TEST) + if(cli.demo_mode) { runner_test(global_controller); terminate(0); diff --git a/src/models/controller.c b/src/models/controller.c index 729404b..fd28506 100644 --- a/src/models/controller.c +++ b/src/models/controller.c @@ -56,9 +56,9 @@ controller_db_select_mapper(sqlite3_stmt *stmt) break; } } - new_controller->relays = malloc(sizeof(relay_t) * global_config.relay_count); + new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count); uint8_t i; - for(i = 0; i < global_config.relay_count; ++i) + for(i = 0; i < global_config->relay_count; ++i) { new_controller->relays[i] = relay_load(i); if(!new_controller->relays[i]) @@ -198,14 +198,14 @@ controller_create(void) new_controller->id = 0; uuid_generate(new_controller->uid); - strncpy(new_controller->name, global_config.name, MAX_NAME_LENGTH); + strncpy(new_controller->name, global_config->name, MAX_NAME_LENGTH); new_controller->name[MAX_NAME_LENGTH] = '\0'; new_controller->command_port = 0; - new_controller->relays = malloc(sizeof(relay_t) * global_config.relay_count); + new_controller->relays = malloc(sizeof(relay_t) * global_config->relay_count); uint8_t i; - for(i = 0; i < global_config.relay_count; ++i) + for(i = 0; i < global_config->relay_count; ++i) { new_controller->relays[i] = relay_load(i); if(!new_controller->relays[i]) @@ -228,7 +228,7 @@ controller_set_name(controller_t *controller, const char *name) void controller_free(controller_t *controller) { - for(int i = 0; i < global_config.relay_count; ++i) + for(int i = 0; i < global_config->relay_count; ++i) { relay_free(controller->relays[i]); } @@ -249,7 +249,7 @@ controller_debug(controller_t *controller) LOGGER_DEBUG("(1/3) %s @ %p\n", uuid_str, (void*)controller); LOGGER_DEBUG("(2/3) name: %s\n", controller->name); LOGGER_DEBUG("(3/3) relays @ %p:\n", (void*)controller->relays); - for(int i = 0; i < global_config.relay_count; ++i) + for(int i = 0; i < global_config->relay_count; ++i) { relay_debug(controller->relays[i]); } diff --git a/src/models/junction_relay_schedule.c b/src/models/junction_relay_schedule.c index 8d623c0..e1aa9ba 100644 --- a/src/models/junction_relay_schedule.c +++ b/src/models/junction_relay_schedule.c @@ -24,12 +24,12 @@ junction_relay_schedule_insert(uint8_t weekday, int relay_id, int schedule_id) if (rc != SQLITE_DONE) { LOGGER_ERR("error inserting data: %s\n", sqlite3_errmsg(global_database)); - return false; + return 0; } sqlite3_finalize(stmt); - return true; + return 1; } int @@ -67,12 +67,12 @@ junction_relay_schedule_insert_weekdays(int relay_id, int *schedule_ids) if (rc != SQLITE_DONE) { LOGGER_ERR("error inserting data: %s", sqlite3_errmsg(global_database)); - return false; + return 0; } sqlite3_finalize(stmt); - return true; + return 1; } int diff --git a/src/models/relay.c b/src/models/relay.c index 38c3aa0..8e0e37a 100644 --- a/src/models/relay.c +++ b/src/models/relay.c @@ -73,7 +73,7 @@ relay_db_select(sqlite3_stmt *stmt) int row = 0; - while(true) + for(;;) { int s; diff --git a/src/runners/test.c b/src/runners/test.c index 2eec714..b6e948b 100644 --- a/src/runners/test.c +++ b/src/runners/test.c @@ -9,22 +9,22 @@ void runner_test() { // from x down to 0 to turn all relays off in the end - for(uint_fast8_t i = 0; i < global_config.relay_count; ++i) + for(uint_fast8_t i = 0; i < global_config->relay_count; ++i) { for(int test_run = 2; test_run >= 0; --test_run) { int is_active = test_run % 2; - if(global_config.relay_configs[i].inverted) + if(global_config->relay_configs[i].inverted) { is_active = !is_active; } - switch(global_config.relay_configs[i].driver) + switch(global_config->relay_configs[i].driver) { case RELAY_DRIVER_GPIO: - driver_gpio_set(global_config.relay_configs[i].pin, is_active); + driver_gpio_set(global_config->relay_configs[i].pin, is_active); break; case RELAY_DRIVER_PIFACE: - driver_piface_set(global_config.relay_configs[i].pin, is_active); + driver_piface_set(global_config->relay_configs[i].pin, is_active); break; default: LOGGER_WARNING("relay %d is not using a driver\n", i); diff --git a/vendor/confini.c b/vendor/confini.c deleted file mode 100644 index 25353a7..0000000 --- a/vendor/confini.c +++ /dev/null @@ -1,5016 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ -/* Please make sure that the TAB width in your editor is set to 4 spaces */ - -/** - - @file confini.c - @brief libconfini functions - @author Stefano Gioffré - @copyright GNU General Public License, version 3 or any later version - @version 1.14.0 - @date 2016-2020 - @see https://madmurphy.github.io/libconfini - -**/ - - - /*/| - (_|_) _ _ _ __ _ _ - | (_) |__ ___ ___ _ __ / _(_)_ __ (_) - | | | '_ \ / __/ _ \| '_ \| |_| | '_ \| | - | | | |_) | (_| (_) | | | | _| | | | | | - |_|_|_.__/ \___\___/|_| |_|_| |_|_| |_|_| _ _ - ( | ) - |/*/ - - - -/** - - - @def INIFORMAT_TABLE_AS(_____) - - Content of the table: - - - Bits 1-19: INI syntax - - Bits 20-22: INI semantics - - Bits 23-24: Human syntax (disabled entries) - - - - @typedef int (* IniStatsHandler) ( - IniStatistics * statistics, - void * user_data - ) - - @param statistics - A pointer to the #IniStatistics to handle - @param user_data - The custom argument previously passed to the caller function - - - - @typedef int (* IniDispHandler) ( - IniDispatch *dispatch, - void * user_data - ) - - @param dispatch - A pointer to the #IniDispatch to handle - @param user_data - The custom argument previously passed to the caller function - - - - @typedef int (* IniStrHandler) ( - char * ini_string, - size_t string_length, - size_t string_num, - IniFormat format, - void * user_data - ) - - @param ini_string - The INI string to handle - @param string_length - The length of the INI string in bytes - @param string_num - The unique number that identifies @p ini_string within a - sequence of INI strings; it equals zero if @p ini_string is the - first or the only member of the sequence - @param format - The format of the INI file from which @p ini_string has been extracted - @param user_data - The custom argument previously passed to the caller function - - - - @typedef int (* IniSubstrHandler) ( - const char * ini_string, - size_t fragm_offset, - size_t fragm_length, - size_t fragm_num, - IniFormat format, - void * user_data - ) - - @param ini_string - The INI string containing the fragment to handle - @param fragm_offset - The offset of the selected fragment in bytes - @param fragm_length - The length of the selected fragment in bytes - @param fragm_num - The unique number that identifies the selected fragment within a - sequence of fragments of @p ini_string; it equals zero if the - fragment is the first or the only member of the sequence - @param format - The format of the INI file from which @p ini_string has been - extracted - @param user_data - The custom argument previously passed to the caller function - - - - @struct IniFormat - - @property IniFormat::delimiter_symbol - The key-value delimiter character (ASCII only allowed); if set - to `\0`, any space is delimiter - (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`); if, within the format - given, `IniFormat::delimiter_symbol` matches a metacharacter - (`'\\'`, `'\''`, `'\"'`), its role as metacharacter will have - higher priority than its role as delimiter symbol (i.e., the - format will have no key-value delimiter); you may use the - #IniDelimiters `enum` for this. - @property IniFormat::case_sensitive - If set to `true`, string comparisons will be always - case-sensitive. - @property IniFormat::semicolon_marker - The rule of the semicolon character (use `enum` - #IniCommentMarker for this). - @property IniFormat::hash_marker - The rule of the hash character (use `enum` #IniCommentMarker for - this). - @property IniFormat::section_paths - Defines whether and how the format supports sections (use `enum` - #IniSectionPaths for this). - @property IniFormat::multiline_nodes - Defines which class of entries are allowed to be multi-line (use - `enum` #IniMultiline for this). - @property IniFormat::no_spaces_in_names - If set to `true`, key and section names containing spaces (even - within quotes) will be rendered as #INI_UNKNOWN. Note that - setting #IniFormat::delimiter_symbol to #INI_ANY_SPACE will not - automatically set this option to `true` (spaces will still be - allowed in section names). - @property IniFormat::no_single_quotes - If set to `true`, the single-quote character (`'`) will be - considered as a normal character. - @property IniFormat::no_double_quotes - If set to `true`, the double-quote character (`"`) will be - considered as a normal character. - @property IniFormat::implicit_is_not_empty - If set to `true`, implicit keys (see @ref libconfini) will - be always dispatched using the values given by the global - variables #INI_GLOBAL_IMPLICIT_VALUE and - #INI_GLOBAL_IMPLICIT_V_LEN for the fields #IniDispatch::value - and to #IniDispatch::v_len respectively; if set to `false`, - implicit keys will be considered to be empty keys. - @property IniFormat::do_not_collapse_values - If set to `true`, sequences of one or more spaces in values - (`/\s+/`) will be dispatched verbatim. - @property IniFormat::preserve_empty_quotes - If set to `true`, and if single/double quotes are - metacharacters, ensures that, within values, empty strings - enclosed between quotes (`""` or `''`) will not be collapsed - together with the spaces that surround them. This option is - useful for values containing space-delimited arrays, in order to - preserve their empty members -- as in, for instance: - `coordinates = "" ""`. Note that, in section and key names, - empty strings enclosed between quotes are _always_ collapsed - together with their surrounding spaces. - @property IniFormat::disabled_after_space - If set to `true`, what follows `/[#;]\s/` is allowed to be - parsed as a disabled entry. - @property IniFormat::disabled_can_be_implicit - If set to `false`, comments that do not contain a key-value - delimiter will never be parsed as disabled keys, but always as - simple comments (even if the format supports implicit keys). - - - - @struct IniStatistics - - @property IniStatistics::format - The format of the INI file (see #IniFormat) - @property IniStatistics::bytes - The size in bytes of the parsed file - @property IniStatistics::members - The size in number of members (nodes) of the parsed file -- this - number always equals the number of dispatches that will be sent - by #load_ini_file(), #load_ini_path() or #strip_ini_cache() - - - - @struct IniDispatch - - @property IniDispatch::format - The format of the INI file (see #IniFormat) - @property IniDispatch::type - The dispatch type (see `enum` #IniNodeType) - @property IniDispatch::data - #IniDispatch::data can contain a comment, a section path or a - key name depending, on #IniDispatch::type; cannot be `NULL` - @property IniDispatch::value - It can be the value of a key element, an empty string or it can - point to the address pointed by the global variable - #INI_GLOBAL_IMPLICIT_VALUE (_the latter is the only case in - which `IniDispatch::value` can be `NULL`_) - @property IniDispatch::append_to - The current section path; cannot be `NULL` - @property IniDispatch::d_len - The length of the string #IniDispatch::data - @property IniDispatch::v_len - The length of the string #IniDispatch::value - @property IniDispatch::at_len - The length of the string #IniDispatch::append_to - @property IniDispatch::dispatch_id - The dispatch number (the first dispatch is number zero) - - -**/ - - - - /*\ - |*| - |*| LOCAL ENVIRONMENT - |*| ________________________________ - \*/ - - - - /* PREPROCESSOR PREAMBLE */ - - -/* String concatenation facilities */ -#define __PP_CAT__(STR1, STR2) STR1##STR2 -#define __PP_UCAT__(STR1, STR2) STR1##_##STR2 -#define __PP_EVALUCAT__(STR1, STR2) __PP_UCAT__(STR1, STR2) - - - - /* PREPROCESSOR ENVIRONMENT */ - - -#ifndef CONFINI_IO_FLAVOR -/** - - @brief The I/O API to use (possibly overridden via - `-DCONFINI_IO_FLAVOR=[FLAVOR]`) - - Possible values are `CONFINI_STANDARD` and `CONFINI_POSIX` - -**/ -#define CONFINI_IO_FLAVOR CONFINI_STANDARD -#endif - - - - /* PREPROCESSOR GLUE */ - - -#define _LIBCONFINI_PRIVATE_ITEM_(GROUP, ITEM) \ - __PP_EVALUCAT__(__PP_CAT__(_LIB, GROUP), __PP_CAT__(ITEM, _)) -#define _LIBCONFINI_CURRENT_FLAVOR_GET_(NAME) \ - _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, NAME) -#define _LIBCONFINI_IS_FLAVOR_(NAME) \ - _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == \ - _LIBCONFINI_PRIVATE_ITEM_(NAME, FLAVOR) - - - - /* AVAILABLE I/O FLAVORS */ - - -/* `-DCONFINI_IO_FLAVOR=CONFINI_STANDARD` (C99 Standard, default, REQUIRED) */ -#define _LIBCONFINI_STANDARD_SEOF_FN_(FILEPTR) fseek(FILEPTR, 0, SEEK_END) -#define _LIBCONFINI_STANDARD_FT_FN_(FILEPTR) ftell(FILEPTR) -#define _LIBCONFINI_STANDARD_FT_T_ long signed int -/* Any unique non-zero integer to identify this I/O API */ -#define _LIBCONFINI_STANDARD_FLAVOR_ 1 - -/* `-DCONFINI_IO_FLAVOR=CONFINI_POSIX` */ -#define _LIBCONFINI_POSIX_SEOF_FN_(FILEPTR) fseeko(FILEPTR, 0, SEEK_END) -#define _LIBCONFINI_POSIX_FT_FN_(FILEPTR) ftello(FILEPTR) -#define _LIBCONFINI_POSIX_FT_T_ off_t -/* Any unique non-zero integer to identify this I/O API */ -#define _LIBCONFINI_POSIX_FLAVOR_ 2 - -/* Define `_POSIX_C_SOURCE` macro when `CONFINI_IO_FLAVOR == CONFINI_POSIX` */ -#if _LIBCONFINI_IS_FLAVOR_(CONFINI_POSIX) -#ifndef _POSIX_C_SOURCE -#define _POSIX_C_SOURCE 200809L -#endif -#endif - -/* It is possible to add other I/O APIs here. Feel free to contribute! */ - - - - /* CHECKS */ - - -#if _LIBCONFINI_PRIVATE_ITEM_(CONFINI_IO_FLAVOR, FLAVOR) == 0 -#error Unsupported I/O API defined in macro CONFINI_IO_FLAVOR -#endif - - - - /* HEADERS */ - -#include -#include "confini.h" - - - - /* ALIASES */ - - -#define _LIBCONFINI_FALSE_ 0 -#define _LIBCONFINI_TRUE_ 1 -#define _LIBCONFINI_CHARBOOL_ unsigned char -#define _LIBCONFINI_SIMPLE_SPACE_ ' ' -#define _LIBCONFINI_HT_ '\t' -#define _LIBCONFINI_FF_ '\f' -#define _LIBCONFINI_VT_ '\v' -#define _LIBCONFINI_CR_ '\r' -#define _LIBCONFINI_LF_ '\n' -#define _LIBCONFINI_BACKSLASH_ '\\' -#define _LIBCONFINI_OPEN_SECTION_ '[' -#define _LIBCONFINI_CLOSE_SECTION_ ']' -#define _LIBCONFINI_SUBSECTION_ '.' -#define _LIBCONFINI_SEMICOLON_ ';' -#define _LIBCONFINI_HASH_ '#' -#define _LIBCONFINI_DOUBLE_QUOTES_ '"' -#define _LIBCONFINI_SINGLE_QUOTES_ '\'' -#define _LIBCONFINI_SEEK_EOF_(FILEPTR) \ - _LIBCONFINI_CURRENT_FLAVOR_GET_(SEOF_FN)(FILEPTR) -#define _LIBCONFINI_FTELL_(FILEPTR) \ - _LIBCONFINI_CURRENT_FLAVOR_GET_(FT_FN)(FILEPTR) -#define _LIBCONFINI_OFF_T_ \ - _LIBCONFINI_CURRENT_FLAVOR_GET_(FT_T) - - - - /* FUNCTIONAL MACROS AND CONSTANTS */ - - -/* The character that will replace sequences of one or more spaces (`/\s+/`) */ -#define _LIBCONFINI_COLLAPSED_ _LIBCONFINI_SIMPLE_SPACE_ - - -/* - - These may be any character in theory... But after the left-trim of each node - leading spaces work pretty well as metacharacters... - -*/ -/* Internal marker of standard comments */ -#define _LIBCONFINI_SC_INT_MARKER_ _LIBCONFINI_SIMPLE_SPACE_ -/* Internal marker of inline comments */ -#define _LIBCONFINI_IC_INT_MARKER_ _LIBCONFINI_HT_ - - -/* - - Checks whether a character can be escaped within a given format - -*/ -#define _LIBCONFINI_IS_ESC_CHAR_(CHR, FMT) ( \ - CHR == _LIBCONFINI_BACKSLASH_ ? \ - !INIFORMAT_HAS_NO_ESC(FMT) \ - : CHR == _LIBCONFINI_DOUBLE_QUOTES_ ? \ - !FMT.no_double_quotes \ - : \ - CHR == _LIBCONFINI_SINGLE_QUOTES_ && !FMT.no_single_quotes \ - ) - - -/* - - Checks whether a character represents any marker within a given format - -*/ -#define _LIBCONFINI_IS_ANY_MARKER_(CHR, FMT) ( \ - CHR == _LIBCONFINI_HASH_ ? \ - FMT.hash_marker != INI_IS_NOT_A_MARKER \ - : \ - CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER \ - ) - - -/* - - Checks whether a character represents any marker except `INI_IGNORE` within a - given format - -*/ -#define _LIBCONFINI_IS_COM_MARKER_(CHR, FMT) ( \ - CHR == _LIBCONFINI_HASH_ ? \ - FMT.hash_marker != INI_IS_NOT_A_MARKER && FMT.hash_marker != INI_IGNORE \ - : \ - CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker != INI_IS_NOT_A_MARKER && FMT.semicolon_marker != INI_IGNORE \ - ) - - -/* - - Checks whether a character represents a marker of type `INI_DISABLED_OR_COMMENT` - within a given format - -*/ -#define _LIBCONFINI_IS_DIS_MARKER_(CHR, FMT) ( \ - CHR == _LIBCONFINI_HASH_ ? \ - FMT.hash_marker == INI_DISABLED_OR_COMMENT \ - : \ - CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_DISABLED_OR_COMMENT \ - ) - - -/* - - Checks whether a character represents a marker of type `INI_IGNORE` within a - given format - -*/ -#define _LIBCONFINI_IS_IGN_MARKER_(CHR, FMT) ( \ - CHR == _LIBCONFINI_HASH_ ? \ - FMT.hash_marker == INI_IGNORE \ - : \ - CHR == _LIBCONFINI_SEMICOLON_ && FMT.semicolon_marker == INI_IGNORE \ - ) - - -/* - - Checks whether a pointer is within the range - `INI_GLOBAL_IMPLICIT_VALUE >= PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN` - -*/ -#define _LIBCONFINI_IMPLICIT_RANGE_(PTR) \ - (PTR >= INI_GLOBAL_IMPLICIT_VALUE && PTR <= INI_GLOBAL_IMPLICIT_VALUE + INI_GLOBAL_IMPLICIT_V_LEN) - - -/* - - Maybe in the future there will be support for UTF-8 casefold (although probably - not -- see "Code considerations" in the Manual), but for now only ASCII... - -*/ -#define _LIBCONFINI_CHR_CASEFOLD_(CHR) (CHR > 0x40 && CHR < 0x5b ? CHR | 0x60 : CHR) - - -/* - - Possible depths of `_LIBCONFINI_SPACES_` (see function #is_some_space()). - - Please, consider the following three constants as belonging together to a - virtual opaque `enum`. - -*/ -#define _LIBCONFINI_WITH_EOL_ -1 -#define _LIBCONFINI_NO_EOL_ 1 -#define _LIBCONFINI_JUST_S_T_ 3 - - -/* - - Other constants related to `_LIBCONFINI_SPACES_` - -*/ -#define _LIBCONFINI_EOL_IDX_ 0 -#define _LIBCONFINI_SPALEN_ 6 - - -/* - - The list of space characters -- do not change its order or its content! - -*/ -static const char _LIBCONFINI_SPACES_[_LIBCONFINI_SPALEN_] = { - _LIBCONFINI_LF_, - _LIBCONFINI_CR_, - _LIBCONFINI_VT_, - _LIBCONFINI_FF_, - _LIBCONFINI_HT_, - _LIBCONFINI_SIMPLE_SPACE_ -}; - - -/** - - @brief A list of possible string representations of boolean pairs - - There may be infinite pairs here. Each pair must be organized according to the - following order: - - 1. Signifier of `false` - 2. Signifier of `true` - - @note Everything **must** be lowercase in this list. - -**/ -static const char * const INI_BOOLEANS[][2] = { - { "no", "yes" }, - { "false", "true" }, - { "off", "on" } -}; - - - - /* ABSTRACT UTILITIES */ - - -/** - - @brief Checks whether a character is a space - @param chr The target character - @param depth What is actually considered a space (possible - values: `_LIBCONFINI_WITH_EOL_`, - `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) - @return A boolean: `true` if the character matches, `false` otherwise - -**/ -static inline _LIBCONFINI_CHARBOOL_ is_some_space (const char chr, const int8_t depth) { - register int8_t idx = depth; - while (++idx < _LIBCONFINI_SPALEN_ && chr != _LIBCONFINI_SPACES_[idx]); - return idx < _LIBCONFINI_SPALEN_; -} - - -/** - - @brief Soft left trim -- does not change the buffer - @param str The target string - @param offs The offset where to start the left trim - @param depth What is actually considered a space (possible - values: `_LIBCONFINI_WITH_EOL_`, - `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) - @return The offset of the first non-space character - -**/ -static inline size_t ltrim_s (const char * const str, const size_t offs, const int8_t depth) { - register size_t idx = offs; - while (is_some_space(str[idx++], depth)); - return idx - 1; -} - - -/** - - @brief Hard left trim -- **does** change the buffer - @param str The target string - @param offs The offset where to start the left trim - @param depth What is actually considered a space (possible - values: `_LIBCONFINI_WITH_EOL_`, - `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) - @return The offset of the first non-space character - -**/ -static inline size_t ltrim_h (char * const str, const size_t offs, const int8_t depth) { - register size_t idx = offs; - while (is_some_space(str[idx], depth)) { str[idx++] = '\0'; } - return idx; -} - - -/** - - @brief Shifting left trim -- **does** change the buffer - @param str The target string - @param offs The offset where to start the left trim - @param depth What is actually considered a space (possible - values: `_LIBCONFINI_WITH_EOL_`, - `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) - @return The new length of the string - -**/ -static inline size_t ltrim_hh (char * const str, const size_t offs, const int8_t depth) { - register size_t idx_d = offs, idx_s = offs; - while (is_some_space(str[idx_s++], depth)); - if (--idx_s - idx_d) { - while ((str[idx_d++] = str[idx_s++])); - for (idx_s = idx_d; str[idx_s]; str[idx_s++] = '\0'); - return idx_d - 1; - } - while (str[idx_s++]); - return idx_s - 1; -} - - -/** - - @brief Soft right trim -- does not change the buffer - @param str The target string - @param len The length of the string - @param depth What is actually considered a space (possible - values: `_LIBCONFINI_WITH_EOL_`, - `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) - @return The length of the string until the last non-space character - -**/ -static inline size_t rtrim_s (const char * const str, const size_t len, const int8_t depth) { - register size_t idx = len + 1; - while (--idx > 0 && is_some_space(str[idx - 1], depth)); - return idx; -} - - -/** - - @brief Hard right trim -- **does** change the buffer - @param str The target string - @param len The length of the string - @param depth What is actually considered a space (possible - values: `_LIBCONFINI_WITH_EOL_`, - `_LIBCONFINI_NO_EOL_`, `_LIBCONFINI_JUST_S_T_`) - @return The new length of the string - -**/ -static inline size_t rtrim_h (char * const str, const size_t len, const int8_t depth) { - register size_t idx = len; - while (idx > 0 && is_some_space(str[idx - 1], depth)) { str[--idx] = '\0'; } - return idx; -} - - -/** - - @brief Unescaped soft right trim (right trim of `/(?:\s|\\[\n\r])+$/`) - -- does not change the buffer - @param str The target string - @param len The length of the string - @return The length of the string until the last non-space character - -**/ -static inline size_t urtrim_s (const char * const str, const size_t len) { - - register uint8_t abcd = 1; - register size_t idx = len; - - - /* \ /\ - \ */ continue_urtrim: /* \ - \/ ______________________ \ */ - - - if (idx < 1) { - - return idx; - - } - - switch (str[--idx]) { - - case _LIBCONFINI_VT_: - case _LIBCONFINI_FF_: - case _LIBCONFINI_HT_: - case _LIBCONFINI_SIMPLE_SPACE_: - - abcd = 1; - goto continue_urtrim; - - case _LIBCONFINI_LF_: - case _LIBCONFINI_CR_: - - abcd = 3; - goto continue_urtrim; - - case _LIBCONFINI_BACKSLASH_: - - if (abcd >>= 1) { - - goto continue_urtrim; - - } - - } - - return idx + 1; - -} - - -/** - - @brief Converts an ASCII string to lower case - @param str The target string - @return Nothing - -**/ -static inline void string_tolower (char * const str) { - for (register char * chrptr = str; *chrptr; chrptr++) { - *chrptr = _LIBCONFINI_CHR_CASEFOLD_(*chrptr); - } -} - - - - /* CONCRETE UTILITIES */ - - -/** - - @brief Unparsed hard left trim (left trim of - `/^(?:\s+|\\[\n\r]|''|"")+/`) -- **does** change the buffer - @param srcstr The target string (it may contain multi-line - escape sequences) - @param offs The offset where to start the left trim - @param format The format of the INI file - @return The offset of the first non-trivial character - -**/ -static inline size_t qultrim_h (char * const srcstr, const size_t offs, const IniFormat format) { - - /* - - Mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 Erase the previous character - FLAG_64 Erase this character - FLAG_128 Continue the loop - - */ - - register uint8_t abcd = (format.no_double_quotes ? 130 : 128) | format.no_single_quotes; - size_t idx = offs; - - do { - - abcd = !(abcd & 28) && is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? - (abcd & 207) | 64 - : !(abcd & 12) && (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) ? - ( - abcd & 16 ? - (abcd & 239) | 96 - : - abcd | 64 - ) - : !(abcd & 25) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - abcd & 4 ? - (abcd & 235) | 96 - : - (abcd & 143) | 4 - ) - : !(abcd & 22) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - abcd & 8 ? - (abcd & 231) | 96 - : - (abcd & 159) | 8 - ) - : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? - (abcd & 159) ^ 16 - : - abcd & 31; - - - if (abcd & 32) { - - srcstr[idx - 1] = '\0'; - - } - - if (abcd & 64) { - - srcstr[idx] = '\0'; - - } - - idx++; - - } while (abcd & 128); - - return abcd & 28 ? idx - 2 : idx - 1; - -} - - -/** - - @brief Soft left trim within an unparsed disabled entry (left trim of - `/(?:(?:^|\\?[\n\r])[ \t\v\f]*(?:#(?:[ \t\v\f]|''|"")*)?)+/`) - -- does not change the buffer - @param srcstr The target string (it may contain multi-line - escape sequences) - @param offs The offset where to start the left trim - @param format The format of the INI file - @return The offset of the first non-trivial character - -**/ -static inline size_t dqultrim_s (const char * const srcstr, const size_t offs, const IniFormat format) { - - /* - - Mask `abcd` (7 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 A new line has just begun - FLAG_64 Continue the left trim - - */ - - - register uint16_t abcd = format.no_single_quotes | - (format.no_double_quotes << 1) | - 96; - - register size_t idx = offs; - - do { - - abcd = is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? - ( - abcd & 28 ? - abcd & 63 - : - abcd - ) - : srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ? - ( - abcd & 12 ? - (abcd & 47) | 32 - : - (abcd & 111) | 32 - ) - : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? - ( - abcd & 28 ? - (abcd & 31) | 16 - : - (abcd & 95) | 16 - ) - : (abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? - abcd & 79 - : !(abcd & 54) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - (abcd & 95) ^ 8 - : !(abcd & 57) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - (abcd & 95) ^ 4 - : srcstr[idx] ? - abcd & 31 - : - abcd & 19; - - - idx++; - - } while (abcd & 64); - - return abcd & 28 ? idx - 2 : idx - 1; - -} - - -/** - - @brief Gets the position of the first occurence out of quotes of a - given character, stopping after a given number of charcters - @param str The string where to search - @param chr The character to to search - @param len The maximum number of characters to read - @param format The format of the INI file - @return The offset of the first occurence of @p chr, or @p len if - @p chr has not been not found - -**/ -static inline size_t getn_metachar_pos (const char * const str, const char chr, const size_t len, const IniFormat format) { - - size_t idx = 0; - - /* - - Mask `abcd` (5 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - - */ - - for ( - - register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; - - idx < len && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_WITH_EOL_))); - - abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16 - : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8 - : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4 - : abcd & 15, - idx++ - - ); - - return idx; - -} - - -/** - - @brief Gets the position of the first occurence out of quotes of a - given character - @param str The string where to search - @param chr The character to to search - @param format The format of the INI file - @return The offset of the first occurence of @p chr or the length of - @p str if @p chr has not been not found - -**/ -static inline size_t get_metachar_pos (const char * const str, const char chr, const IniFormat format) { - - size_t idx = 0; - - /* - - Mask `abcd` (5 bits used): - - --> As in #getn_metachar_pos() - - */ - - for ( - - register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; - - str[idx] && ((abcd & 12) || (chr ? str[idx] != chr : !is_some_space(str[idx], _LIBCONFINI_NO_EOL_))); - - abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? abcd ^ 16 - : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? abcd ^ 8 - : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? abcd ^ 4 - : abcd & 15, - idx++ - - ); - - return idx; - -} - - -/** - - @brief Replaces `/\\(\n\r?|\r\n?)[\t \v\f]*[#;]/` or `/\\(\n\r?|\r\n?)/` - with `"$1"` - @param srcstr The target string (it may contain multi-line - escape sequences) - @param len Length of the string - @param is_disabled The string represents a disabled entry - @param format The format of the INI file - @return The new length of the string - -**/ -static size_t unescape_cr_lf (char * const srcstr, const size_t len, const _LIBCONFINI_CHARBOOL_ is_disabled, const IniFormat format) { - - register size_t idx_s = 0, idx_d = 0; - register uint8_t eol_i = _LIBCONFINI_EOL_IDX_; - register _LIBCONFINI_CHARBOOL_ is_escaped = _LIBCONFINI_FALSE_; - size_t probe; - - while (idx_s < len) { - - if ( - is_escaped && ( - srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i] || srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^= 1] - ) - ) { - - srcstr[idx_d - 1] = srcstr[idx_s++]; - - if (srcstr[idx_s] == _LIBCONFINI_SPACES_[eol_i ^ 1]) { - - srcstr[idx_d++] = srcstr[idx_s++]; - - } - - if (is_disabled) { - - probe = ltrim_s(srcstr, idx_s, _LIBCONFINI_NO_EOL_); - - if (_LIBCONFINI_IS_DIS_MARKER_(srcstr[probe], format)) { - - idx_s = probe + 1; - - } - - } - - is_escaped = _LIBCONFINI_FALSE_; - - } else { - - is_escaped = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ? - !is_escaped - : - _LIBCONFINI_FALSE_; - - - srcstr[idx_d++] = srcstr[idx_s++]; - - } - - } - - for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0'); - - return idx_d; - -} - - -/** - - @brief Sanitizes a section path - @param secpath The section path - @param format The format of the INI file - @return The new length of the string - - Out of quotes, similar to ECMAScript - `secpath.replace(/\.*\s*$|(?:\s*(\.))+\s*|^\s+/g, "$1").replace(/\s+/g, " ")` - - A section path can start with a dot (append), but cannot end with a dot. Spaces - surrounding dots will be removed. Fragments surrounded by single or double - quotes (if these are enabled) are prevented from changes. - -**/ -static size_t sanitize_section_path (char * const secpath, const IniFormat format) { - - /* - - Mask `abcd` (12 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 These are initial spaces - FLAG_64 This is a space out of quotes - FLAG_128 This is a dot out of quotes - FLAG_256 This is anything *but* an opening single/double quote - FLAG_512 Don't ignore the last two characters - FLAG_1024 Don't overwrite the previous character - FLAG_2048 Path contains at least one name - - */ - - register uint16_t abcd = (format.no_double_quotes ? 1826 : 1824) | format.no_single_quotes; - register size_t idx_s = 0, idx_d = 0; - - for (; secpath[idx_s]; idx_s++) { - - /* Revision #2 */ - - abcd = !(abcd & 12) && is_some_space(secpath[idx_s], _LIBCONFINI_WITH_EOL_) ? - ( - abcd & 224 ? - (abcd & 3055) | 832 - : - (abcd & 4079) | 1856 - ) - : !(abcd & 12) && secpath[idx_s] == _LIBCONFINI_SUBSECTION_ ? - ( - abcd & (abcd & 32 ? 128 : 192) ? - (abcd & 2959) | 896 - : - (abcd & 3983) | 1920 - ) - : !(abcd & 25) && secpath[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - ~abcd & 4 ? - (abcd & 3839) | 1540 - : abcd & 256 ? - (abcd & 3867) | 3840 - : - (abcd & 3579) | 1280 - ) - : !(abcd & 22) && secpath[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - ~abcd & 8 ? - (abcd & 3839) | 1544 - : abcd & 256 ? - (abcd & 3863) | 3840 - : - (abcd & 3575) | 1280 - ) - : secpath[idx_s] == _LIBCONFINI_BACKSLASH_ ? - ((abcd & 3871) | 3840) ^ 16 - : - (abcd & 3855) | 3840; - - - if (abcd & 512) { - - secpath[ - abcd & 1024 ? - idx_d++ - : idx_d ? - idx_d - 1 - : - idx_d - ] = !(~abcd & 384) ? - _LIBCONFINI_SUBSECTION_ - : !(~abcd & 320) ? - _LIBCONFINI_COLLAPSED_ - : - secpath[idx_s]; - - } else if (idx_d) { - - idx_d--; - - } - - } - - for ( - - idx_s = idx_d && (abcd & 2048) && (abcd & 192) ? - --idx_d - : - idx_d; - - secpath[idx_s]; - - secpath[idx_s++] = '\0' - - ); - - return idx_d; - -} - - -/** - - @brief Out of quotes similar to ECMAScript - `ini_string.replace(/''|""/g, "").replace(/^[\n\r]\s*|\s+/g, " ")` - @param ini_string The string to collapse -- multi-line escape - sequences must be already unescaped at - this stage - @param format The format of the INI file - @return The new length of the string - -**/ -static size_t collapse_everything (char * const ini_string, const IniFormat format) { - - /* - - Mask `abcd` (9 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 This is *not* a space out of quotes - FLAG_64 This is an opening single/double quote - FLAG_128 Don't ignore this character - FLAG_256 Jump this character and the one before this - - */ - - register size_t idx_s = 0, idx_d = 0; - - register uint16_t abcd = (is_some_space(*ini_string, _LIBCONFINI_WITH_EOL_) ? 128 : 160) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - - for (; ini_string[idx_s]; idx_s++) { - - /* Revision #2 */ - - abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? - ( - abcd & 32 ? - (abcd & 143) | 128 - : - abcd & 47 - ) - : !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - ~abcd & 4 ? - (abcd & 239) | 196 - : abcd & 64 ? - (abcd & 299) | 256 - : - (abcd & 171) | 160 - ) - : !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - ~abcd & 8 ? - (abcd & 239) | 200 - : abcd & 64 ? - (abcd & 295) | 256 - : - (abcd & 167) | 160 - ) - : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? - ((abcd & 191) | 160) ^ 16 - : - (abcd & 175) | 160; - - - if (abcd & 256) { - - idx_d--; - - } else if (abcd & 128) { - - ini_string[idx_d++] = abcd & 44 ? ini_string[idx_s] : _LIBCONFINI_COLLAPSED_; - - } - - } - - for ( - - idx_s = !(abcd & 32) && idx_d ? - --idx_d - : - idx_d; - - ini_string[idx_s]; - - ini_string[idx_s++] = '\0' - - ); - - return idx_d; - -} - - -/** - - @brief Out of quotes similar to ECMAScript - `ini_string.replace(/\s+/g, " ")` - @param ini_string The string to collapse -- multi-line escape - sequences must be already unescaped at this - stage - @param format The format of the INI file - @return The new length of the string - -**/ -static size_t collapse_spaces (char * const ini_string, const IniFormat format) { - - /* - - Mask `abcd` (7 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 This is a space out of quotes - FLAG_64 Jump this character - - */ - - register uint8_t abcd = (format.no_double_quotes ? 34 : 32) | format.no_single_quotes; - register size_t idx_s = 0; - size_t idx_d = 0; - - for (; ini_string[idx_s]; idx_s++) { - - /* Revision #1 */ - - abcd = !(abcd & 12) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? - ( - abcd & 32 ? - (abcd & 111) | 64 - : - (abcd & 47) | 32 - ) - : !(abcd & 25) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? - (abcd & 15) ^ 4 - : !(abcd & 22) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? - (abcd & 15) ^ 8 - : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? - (abcd & 31) ^ 16 - : - abcd & 15; - - - if (~abcd & 64) { - - ini_string[idx_d++] = abcd & 32 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx_s]; - - } - - } - - for ( - - idx_s = (abcd & 32) && idx_d ? - --idx_d - : - idx_d; - - ini_string[idx_s]; - - ini_string[idx_s++] = '\0' - - ); - - return idx_d; - -} - - -/** - - @brief Similar to ECMAScript `str.replace(/''|""/g, "")` - @param str The string to collapse - @param format The format of the INI file - @return The new length of the string - -**/ -static size_t collapse_empty_quotes (char * const str, const IniFormat format) { - - /* - - Mask `abcd` (7 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 This is an opening single/double quote - FLAG_64 These are empty quotes - - */ - - register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; - register size_t lshift = ltrim_s(str, 0, _LIBCONFINI_WITH_EOL_), idx = lshift; - - for (; str[idx]; idx++) { - - /* Revision #1 */ - - abcd = str[idx] == _LIBCONFINI_BACKSLASH_ ? - (abcd & 31) ^ 16 - : !(abcd & 22) && str[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - ~abcd & 40 ? - ((abcd & 47) | 32) ^ 8 - : - (abcd & 71) | 64 - ) - : !(abcd & 25) && str[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - ~abcd & 36 ? - ((abcd & 47) | 32) ^ 4 - : - (abcd & 75) | 64 - ) - : - abcd & 15; - - - str[idx - lshift] = str[idx]; - - if (abcd & 64) { - - lshift += 2; - - } - - } - - for (idx -= lshift; str[idx]; str[idx++] = '\0'); - - return rtrim_h(str, idx - lshift, _LIBCONFINI_WITH_EOL_); - -} - - -/** - - @brief Removes all comment initializers (`#` and/or `;`) from the - beginning of each line of a comment - @param srcstr The comment to parse (it may contain multi-line - escape sequences) - @param len The length of @p srcstr - @param format The format of the INI file - @return The new length of the string - - - In multi-line comments: `srcstr.replace(/^[#;]+|(\n\r?|\r\n?)[\t \v\f]*[#;]+/g, "$1")` - - In single-line comments: `srcstr.replace(/^[#;]+/, "")` - - The argument @p srcstr may begin with a comment initializer (`#` or `;` - depending on the format), or with the character that immediately follows it. - -**/ -static size_t uncomment (char * const srcstr, size_t len, const IniFormat format) { - - register size_t idx_s = 0, idx_d = 0; - - if (format.multiline_nodes == INI_MULTILINE_EVERYWHERE) { - - /* - - The comment can be multi-line - - */ - - /* - - Mask `abcd` (6 bits used): - - FLAG_1 Don't erase any character - FLAG_2 We are in an odd sequence of backslashes - FLAG_4 This new line character is escaped - FLAG_8 This character is a comment character and follows - `/(\n\s*|\r\s*)/` - FLAG_16 This character is a part of a group of spaces that follow - a new line (`/(\n|\r)[\t \v\f]+/`) - FLAG_32 This character is *not* a new line character (`/[\r\n]/`) - - */ - - for (register uint8_t abcd = 8; idx_s < len; idx_s++) { - - abcd = srcstr[idx_s] == _LIBCONFINI_BACKSLASH_ ? - ((abcd & 35) | 32) ^ 2 - : srcstr[idx_s] == _LIBCONFINI_LF_ || srcstr[idx_s] == _LIBCONFINI_CR_ ? - (abcd << 1) & 4 - : !(abcd & 32) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format) ? - (abcd & 40) | 8 - : !(abcd & 40) && is_some_space(srcstr[idx_s], _LIBCONFINI_NO_EOL_) ? - (abcd & 57) | 16 - : - (abcd & 33) | 32; - - - if (!(abcd & 25)) { - - srcstr[abcd & 4 ? idx_d - 1 : idx_d++] = srcstr[idx_s]; - - } else if (!(abcd & 28)) { - - idx_d++; - - } - - } - - } else { - - /* - - The comment cannot be multi-line - - */ - - for (; idx_s < len && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx_s], format); idx_s++); - - if (!idx_s) { - - return len; - - } - - for (; idx_s < len; srcstr[idx_d++] = srcstr[idx_s++]); - - } - - for (idx_s = idx_d; idx_s < len; srcstr[idx_s++] = '\0'); - - return idx_d; - -} - - -/** - - @brief Tries to determine the type of a member "as if it was active" - @param srcstr String containing an individual node (it may - contain multi-line escape sequences) - @param len Length of the node - @param allow_implicit A boolean: `true` if keys without a key-value - delimiter are allowed, `false` otherwise - @param format The format of the INI file - @return The node type (see header) - -**/ -static uint8_t get_type_as_active ( - const char * const srcstr, - const size_t len, - const _LIBCONFINI_CHARBOOL_ allow_implicit, - const IniFormat format -) { - - const _LIBCONFINI_CHARBOOL_ invalid_delimiter = _LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); - - if ( - !len || _LIBCONFINI_IS_ANY_MARKER_(*srcstr, format) || ( - *((unsigned char *) srcstr) == format.delimiter_symbol && !invalid_delimiter - ) - ) { - - return INI_UNKNOWN; - - } - - register uint16_t abcd; - register size_t idx; - - if (format.section_paths != INI_NO_SECTIONS && *srcstr == _LIBCONFINI_OPEN_SECTION_) { - - if (format.no_spaces_in_names) { - - /* - - Search for the CLOSE SECTION character and possible spaces in names - -- i.e., ECMAScript `/[^\.\s]\s+[^\.\s]/g.test(srcstr)`. The - algorithm is made more complex by the fact that LF and CR characters - are still escaped at this stage. - - */ - - /* - - Mask `abcd` (10 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Only one level of nesting is allowed (const) - FLAG_8 Unescaped single quotes are odd right now - FLAG_16 Unescaped double quotes are odd right now - FLAG_32 We are in an odd sequence of backslashes - FLAG_64 This is a space - FLAG_128 What follows cannot contain spaces - FLAG_256 Continue the loop - FLAG_512 Section path is *not* valid - - */ - - - idx = 1; - - abcd = (format.section_paths == INI_ONE_LEVEL_ONLY ? 772: 768) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - - do { - - /* Revision #2 */ - - abcd = idx >= len ? - abcd & 767 - : !(abcd & 42) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - (abcd & 991) ^ 16 - : !(abcd & 49) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - (abcd & 991) ^ 8 - : srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_ ? - (abcd & 991) | 64 - : is_some_space(srcstr[idx], _LIBCONFINI_NO_EOL_) ? - ( - ~abcd & 32 ? - abcd | 64 - : ~abcd & 192 ? - (abcd & 991) | 192 - : - (abcd & 767) | 128 - ) - : !(abcd & 28) && srcstr[idx] == _LIBCONFINI_SUBSECTION_ ? - ( - ~abcd & 224 ? - abcd & 799 - : - abcd & 767 - ) - : !(abcd & 24) && srcstr[idx] == _LIBCONFINI_CLOSE_SECTION_ ? - ( - ~abcd & 224 ? - abcd & 159 - : - abcd & 767 - ) - : srcstr[idx] == _LIBCONFINI_BACKSLASH_ ? - ( - ~abcd & 32 ? - abcd | 32 - : ~abcd & 192 ? - (abcd & 991) | 128 - : - (abcd & 735) - ) - : ~abcd & 192 ? - (abcd & 927) | 128 - : - (abcd & 671) | 128; - - - idx++; - - } while (abcd & 256); - - if (abcd & 512) { - - return INI_UNKNOWN; - - } - - } else if ((idx = getn_metachar_pos(srcstr, _LIBCONFINI_CLOSE_SECTION_, len, format) + 1) > len) { - - return INI_UNKNOWN; - - } - - /* - - Scan for possible non-space characters following the CLOSE SECTION - character: if found the node cannot represent a section path (but it can - possibly represent a key). Empty quotes surrounded by spaces will be - tolerated. - - */ - - /* - - Recycling variable `abcd` (6 bits used)...: - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes - FLAG_32 Continue the loop - - */ - - abcd = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; - - - /* \ /\ - \ */ nonspace_check: /* \ - \/ ______________________ \ */ - - - if (abcd) { - - if (idx >= len) { - - return INI_SECTION; - - } - - switch (srcstr[idx++]) { - - case _LIBCONFINI_VT_: - case _LIBCONFINI_FF_: - case _LIBCONFINI_HT_: - case _LIBCONFINI_SIMPLE_SPACE_: - - abcd = abcd & 28 ? 0 : abcd & 47; - goto nonspace_check; - - case _LIBCONFINI_LF_: - case _LIBCONFINI_CR_: - - abcd = abcd & 12 ? 0 : abcd & 47; - goto nonspace_check; - - case _LIBCONFINI_BACKSLASH_: - - abcd = abcd & 28 ? 0 : abcd | 16; - goto nonspace_check; - - case _LIBCONFINI_DOUBLE_QUOTES_: - - abcd = abcd & 22 ? 0 : (abcd & 47) ^ 8; - goto nonspace_check; - - case _LIBCONFINI_SINGLE_QUOTES_: - - abcd = abcd & 25 ? 0 : (abcd & 47) ^ 4; - goto nonspace_check; - - } - - } - - } - - /* - - It can be just a key... - - */ - - if (invalid_delimiter && !allow_implicit) { - - return INI_UNKNOWN; - - } - - /* - - Recycling variable `abcd` (2 bits used)...: - - FLAG_1 The delimiter **must** be present - FLAG_2 Search for spaces in names - - */ - - abcd = (format.no_spaces_in_names << 1) | (allow_implicit ? 0 : 1); - - if (abcd) { - - idx = getn_metachar_pos(srcstr, (char) format.delimiter_symbol, len, format); - - if ((abcd & 1) && idx == len) { - - return INI_UNKNOWN; - - } - - if (abcd & 2) { - - idx = urtrim_s(srcstr, idx); - - do { - - if (is_some_space(srcstr[--idx], _LIBCONFINI_WITH_EOL_)) { - - return INI_UNKNOWN; - - } - - } while (idx); - - } - - } - - return INI_KEY; - -} - - -/** - - @brief Examines a (single-/multi-line) segment and checks whether - it contains more than just one node - @param srcstr Segment to examine (it may contain multi-line - escape sequences) - @param format The format of the INI file - @return Number of entries found - -**/ -static size_t further_cuts (char * const srcstr, const IniFormat format) { - - /* - - Shared flags of mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Do not allow disabled entries after space (const) - FLAG_8 Formats supports multi-line entries everywhere (const) - FLAG_16 Formats supports multi-line entries everywhere except in - comments (const) - FLAG_32 Unescaped single quotes are odd right now - FLAG_64 Unescaped double quotes are odd right now - FLAG_128 We are in an odd sequence of backslashes - - */ - - register uint16_t abcd = ((format.disabled_after_space << 2) ^ 4) | - (format.no_double_quotes << 1) | - format.no_single_quotes | ( - format.multiline_nodes == INI_MULTILINE_EVERYWHERE ? - 8 - : format.multiline_nodes == INI_BUT_COMMENTS ? - 16 - : - 0 - ); - - - register size_t idx; - size_t focus_at, unparsable_at, search_at = 0, num_entries = 0; - - - /* \ /\ - \ */ search_for_cuts: /* \ - \/ ______________________ \ */ - - - if (!srcstr[search_at]) { - - return num_entries; - - } - - unparsable_at = 0; - - abcd = _LIBCONFINI_IS_DIS_MARKER_(srcstr[search_at], format) && ( - !(abcd & 4) || !is_some_space(srcstr[search_at + 1], _LIBCONFINI_NO_EOL_) - ) ? - (abcd & 31) | 2560 - : _LIBCONFINI_IS_IGN_MARKER_(srcstr[search_at], format) ? - (abcd & 8 ? (abcd & 31) | 1024 : abcd & 31) - : (abcd & 8) && ( - srcstr[search_at] == _LIBCONFINI_IC_INT_MARKER_ || _LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format) - ) ? - (abcd & 31) | 3072 - : - (abcd & 31) | 2048; - - - if (abcd & 2048) { - - num_entries++; - - } - - if (abcd & 1536) { - - /* - - Node starts with `/[;#]/` and can be a disabled entry in any format, or - a simple comment or a block that must be ignored in multi-line formats - - */ - - /* - - Mask `abcd` (14 bits used): - - FLAG_256 This or the previous character was not a space - FLAG_512 We are in a disabled entry or a comment (semi-const) - FLAG_1024 We are in a simple comment or in a block that must be ignored - and format supports multi-line entries (semi-const) - FLAG_2048 We are *not* in a block that must be ignored (semi-const) - FLAG_4096 We have *not* just found an inline comment nested within a - disabled entry - FLAG_8192 We had previously found an inline comment nested in this - segment, but the entry that preceded it had been checked and - did not seem to represent a valid disabled entry - - NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at - the beginning of the function; all other flags are already set - to zero. For the meaning of flags FLAG_1-FLAG_128 see the - beginning of the function. - - */ - - idx = ltrim_s(srcstr, search_at + 1, _LIBCONFINI_NO_EOL_) - 1; - - - /* \ /\ - \ */ inactive_cut: /* \ - \/ ______________________ \ */ - - - switch (srcstr[++idx]) { - - case '\0': - - /* End of string */ - - if (~abcd & 8) { - - /* - - Check if this is a valid disabled entry. If it is not, - search for line breaks. - - If the code has reached this point it means that according - to the format disabled entries can be multi-line but - comments cannot, and #get_type_as_active() has never been - invoked on this entry. - - */ - - focus_at = dqultrim_s(srcstr, search_at, format); - - if ( - srcstr[focus_at] && !get_type_as_active( - srcstr + focus_at, - idx - focus_at, - format.disabled_can_be_implicit, - format - ) - ) { - - srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_; - unparsable_at = search_at + 1; - - } - - } - - break; - - case _LIBCONFINI_LF_: - case _LIBCONFINI_CR_: - - /* - - Line break has been found in a multi-line disabled entry or - a comment. Search for `/\\(?:\n\r?|\r\n?)\s*[^;#]/`. - - */ - - focus_at = dqultrim_s(srcstr, search_at, format); - idx = ltrim_s(srcstr, idx + 1, _LIBCONFINI_WITH_EOL_); - - if ( - abcd & 2048 ? - !( - _LIBCONFINI_IS_DIS_MARKER_(srcstr[idx], format) && (abcd & 24) && ( - (~abcd & 516) || !is_some_space(srcstr[idx + 1], _LIBCONFINI_NO_EOL_) - ) - ) && !( - _LIBCONFINI_IS_COM_MARKER_(srcstr[idx], format) && (abcd & 8) && ( - ((abcd ^ 512) & 8704) || !get_type_as_active( - srcstr + focus_at, - idx - focus_at, - format.disabled_can_be_implicit, - format - ) - ) - ) - : - !_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format) - ) { - - rtrim_h(srcstr, idx, _LIBCONFINI_WITH_EOL_); - search_at = qultrim_h(srcstr, idx, format); - goto search_for_cuts; - - } - - /* - - No case break here, keep it like this! `case /[ \t\v\f]/` must - follow (switch case fallthrough). - - */ - __attribute__((fallthrough)); - - case _LIBCONFINI_VT_: - case _LIBCONFINI_FF_: - case _LIBCONFINI_HT_: - case _LIBCONFINI_SIMPLE_SPACE_: - - abcd = (abcd & 15999) | 4096; - goto inactive_cut; - - case _LIBCONFINI_BACKSLASH_: - - abcd = (abcd | 4352) ^ 128; - goto inactive_cut; - - default: - - abcd = !(abcd & 1376) && (~abcd & 8200) && _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? - (abcd & 12159) | 256 - : !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ((abcd & 16255) | 4352) ^ 64 - : !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ((abcd & 16255) | 4352) ^ 32 - : - (abcd & 16255) | 4352; - - - if (abcd & 4096) { - - goto inactive_cut; - - } - - if (~abcd & 8192) { - - /* - - Inline comment has been found in a (supposedly) disabled entry. - - */ - - focus_at = dqultrim_s(srcstr, search_at, format); - - if (get_type_as_active( - srcstr + focus_at, - idx - focus_at, - format.disabled_can_be_implicit, - format - )) { - - if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { - - srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_; - num_entries++; - - } - - srcstr[idx - 1] = '\0'; - - if (abcd & 8) { - - goto inactive_cut; - - } - - unparsable_at = idx + 1; - - } else { - - abcd |= 8192; - srcstr[search_at] = _LIBCONFINI_SC_INT_MARKER_; - - if (abcd & 8) { - - goto inactive_cut; - - } - - unparsable_at = search_at + 1; - - } - - } - - /* No case break here (last case) */ - - } - - } else if (_LIBCONFINI_IS_ANY_MARKER_(srcstr[search_at], format)) { - - /* - - Node starts with `/[;#]/` but cannot be multi-line or represent a - disabled entry - - */ - - unparsable_at = search_at + 1; - - } else { - - /* - - Node is active: search for inline comments - - */ - - /* - - Recycling variable `abcd` (11 bits used)...: - - FLAG_256 Comment marker follows an escaped new line made of only one - character (i.e., `"\\\n"` or `"\\\r"` but neither `"\\\r\n"` - or `"\\\n\r"`) - FLAG_512 This was neither a hash nor a semicolon character - FLAG_1024 This was not a space - - NOTE: For FLAG_1-FLAG_16 I will keep the values already assigned at the - beginning of the function; all other flags are already set to zero - (see previous usage of `abcd` within this function), with the only - exception of FLAG_2048, which I am going to overwrite immediately. - For the meaning of flags FLAG_1-FLAG_128 see the beginning of the - function. - - */ - - abcd = (abcd & 2047) | 1536; - idx = search_at; - - - /* \ /\ - \ */ active_cut: /* \ - \/ ______________________ \ */ - - - switch (srcstr[++idx]) { - - case '\0': - - /* End of string */ - break; - - case _LIBCONFINI_VT_: - case _LIBCONFINI_FF_: - case _LIBCONFINI_HT_: - case _LIBCONFINI_SIMPLE_SPACE_: - - abcd = (abcd & 639) | 512; - goto active_cut; - - case _LIBCONFINI_LF_: - case _LIBCONFINI_CR_: - - abcd = (abcd & 639) | ((abcd << 1) & 256) | 512; - goto active_cut; - - case _LIBCONFINI_BACKSLASH_: - - abcd = ((abcd & 1791) | 1536) ^ 128; - goto active_cut; - - default: - - abcd = _LIBCONFINI_IS_ANY_MARKER_(srcstr[idx], format) ? - abcd & 1407 - : !(abcd & 162) && srcstr[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ((abcd & 1791) | 1536) ^ 64 - : !(abcd & 193) && srcstr[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ((abcd & 1791) | 1536) ^ 32 - : - (abcd & 1791) | 1536; - - if (abcd & 1760) { - - goto active_cut; - - } - - /* - - Inline comment has been found in an active entry. - - */ - - if (abcd & 256) { - - /* - - Remove the backslash if the comment immediately follows an - escaped new line expressed by one chararacter - (`/\\[\r\n]/`). In case of CR + LF or LF + CR - (`/\\\n\r|\\\r\n/`) the backslash will be removed later by - #strip_ini_cache(). - - */ - - srcstr[idx - 2] = '\0'; - - } - - srcstr[idx - 1] = '\0'; - - if (!_LIBCONFINI_IS_IGN_MARKER_(srcstr[idx], format)) { - - srcstr[idx] = _LIBCONFINI_IC_INT_MARKER_; - - if (abcd & 8) { - - search_at = idx; - goto search_for_cuts; - - } - - num_entries++; - - } else if (abcd & 8) { - - search_at = idx; - goto search_for_cuts; - - } - - unparsable_at = idx + 1; - /* No case break here (last case) */ - - } - - } - - if (unparsable_at) { - - /* - - Cut unparsable multi-line comments - - */ - - for (idx = unparsable_at; srcstr[idx]; idx++) { - - if (srcstr[idx] == _LIBCONFINI_LF_ || srcstr[idx] == _LIBCONFINI_CR_) { - - search_at = qultrim_h(srcstr, idx, format); - goto search_for_cuts; - - } - - } - - } - - return num_entries; - -} - -/** @startfnlist **/ - - - - /*\ - |*| - |*| GLOBAL ENVIRONMENT - |*| ________________________________ - \*/ - - - - /* LIBRARY'S MAIN FUNCTIONS */ - - - /** @utility{strip_ini_cache} **/ -/** - - @brief Parses and tokenizes a buffer containing an INI file, then - dispatches its content to a custom callback - @param ini_source The buffer containing the INI file to tokenize - @param ini_length The length of @p ini_source without counting the - NUL terminator (if any -- se below) - @param format The format of the INI file - @param f_init The function that will be invoked before the - first dispatch, or `NULL` - @param f_foreach The function that will be invoked for each - dispatch, or `NULL` - @param user_data A custom argument, or `NULL` - @return Zero for success, otherwise an error code (see `enum` - #ConfiniInterruptNo) - - The @p ini_source parameter must be a valid pointer to a buffer of size - @p ini_length + 1 and cannot be `NULL`. The @p ini_source string does not need - to be NUL-terminated, but _it does need one extra byte where to append a NUL - terminator_ -- in fact, as soon as this function is invoked, - `ini_source[ini_length]` will be immediately set to `\0`. - - In most cases, as when using `strlen()` for computing @p ini_length, this is not - a concern, since `ini_source[ini_length]` will always be `\0` by the very - definition of `strlen()`, and will only get overwritten with the same value. - However, if you are passing a substring of a string, for example the fragment - `foo=bar` of the string `foo=barracuda`, you must expect the string to be - immediately truncated into `foo=bar\0acuda`. - - In other words, @p ini_source must point to a memory location where at least - `ini_length + 1` bytes are freely usable. - - The user given function @p f_init (see #IniStatsHandler data type) will be - invoked with two arguments: `statistics` (a pointer to an #IniStatistics - structure containing some properties about the file read) and `user_data` (the - custom argument @p user_data previously passed). If @p f_init returns a non-zero - value the caller function will be interrupted. - - The user given function @p f_foreach (see #IniDispHandler data type) will be - invoked with two arguments: `dispatch` (a pointer to an #IniDispatch structure - containing the parsed member of the INI file) and `user_data` (the custom - argument @p user_data previously passed). If @p f_foreach returns a non-zero - value the caller function will be interrupted. - - After invoking `strip_ini_cache()`, the buffer pointed by the @p ini_source - parameter must be considered as a _corrupted buffer_ and should be freed or - overwritten. For more information about this function, please refer to the - @ref libconfini. - - The parsing algorithms used by **libconfini** are able to parse any type of file - encoded in 8-bit code units, as long as the characters that match the regular - expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in - ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of - platform-specific conventions. - - @note In order to be null-byte-injection-safe, before dispatching the parsed - content this function will strip all `NUL` characters possibly present - in the buffer (with the exception of the last one). - - Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, - #CONFINI_EOOR. - - @include topics/strip_ini_cache.c - -**/ -int strip_ini_cache ( - register char * const ini_source, - const size_t ini_length, - const IniFormat format, - const IniStatsHandler f_init, - const IniDispHandler f_foreach, - void * const user_data -) { - - const _LIBCONFINI_CHARBOOL_ valid_delimiter = !_LIBCONFINI_IS_ESC_CHAR_(format.delimiter_symbol, format); - _LIBCONFINI_CHARBOOL_ tmp_bool; - register size_t idx, tmp_fast_size_t_1, tmp_fast_size_t_2; - size_t tmp_size_t_1, tmp_size_t_2; - - ini_source[ini_length] = '\0'; - - /* - - PART ONE: Examine and isolate each segment - - */ - - #define __ISNT_ESCAPED__ tmp_bool - #define __LSHIFT__ tmp_fast_size_t_1 - #define __EOL_N__ tmp_fast_size_t_2 - #define __N_MEMBERS__ tmp_size_t_1 - #define __NODE_AT__ tmp_size_t_2 - - /* UTF-8 BOM */ - __LSHIFT__ = *((unsigned char *) ini_source) == 0xEF && - *((unsigned char *) ini_source + 1) == 0xBB && - *((unsigned char *) ini_source + 2) == 0xBF - ? 3 : 0; - - - for ( - - __N_MEMBERS__ = 0, - __EOL_N__ = _LIBCONFINI_EOL_IDX_, - __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_, - __NODE_AT__ = 0, - idx = __LSHIFT__; - - idx < ini_length; - - idx++ - - ) { - - ini_source[idx - __LSHIFT__] = ini_source[idx]; - - if (ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__] || ini_source[idx] == _LIBCONFINI_SPACES_[__EOL_N__ ^= 1]) { - - if (format.multiline_nodes == INI_NO_MULTILINE || __ISNT_ESCAPED__) { - - ini_source[idx - __LSHIFT__] = '\0'; - __N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format); - __NODE_AT__ = idx - __LSHIFT__ + 1; - - } else if (ini_source[idx + 1] == _LIBCONFINI_SPACES_[__EOL_N__ ^ 1]) { - - idx++; - ini_source[idx - __LSHIFT__] = ini_source[idx]; - - } - - __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_; - - } else if (ini_source[idx] == _LIBCONFINI_BACKSLASH_) { - - __ISNT_ESCAPED__ = !__ISNT_ESCAPED__; - - } else if (ini_source[idx]) { - - __ISNT_ESCAPED__ = _LIBCONFINI_TRUE_; - - } else { - - /* Remove `NUL` characters from the buffer (if any) */ - __LSHIFT__++; - - } - - } - - const size_t real_length = idx - __LSHIFT__; - - while (idx > real_length) { - - ini_source[--idx] = '\0'; - - } - - __N_MEMBERS__ += further_cuts(ini_source + qultrim_h(ini_source, __NODE_AT__, format), format); - - /* Debug */ - - /* - - for (size_t tmp = 0; tmp < ini_length + 1; tmp++) { - putchar(ini_source[tmp] == 0 ? '$' : ini_source[tmp]); - } - putchar('\n'); - - */ - - IniStatistics this_doc = { - .format = format, - .bytes = ini_length, - .members = __N_MEMBERS__ - }; - - if (f_init && f_init(&this_doc, user_data)) { - - return CONFINI_IINTR; - - } - - #undef __NODE_AT__ - #undef __N_MEMBERS__ - #undef __EOL_N__ - #undef __LSHIFT__ - #undef __ISNT_ESCAPED__ - - /* - - PART TWO: Dispatch the parsed input - - */ - - if (!f_foreach) { - - return CONFINI_SUCCESS; - - } - - #define __ITER__ tmp_fast_size_t_1 - #define __NODE_AT__ tmp_fast_size_t_2 - #define __PARENT_IS_DISABLED__ tmp_bool - #define __REAL_PARENT_LEN__ tmp_size_t_1 - #define __CURR_PARENT_LEN__ tmp_size_t_2 - - __REAL_PARENT_LEN__ = 0, __CURR_PARENT_LEN__ = 0; - - char - * curr_parent_str = ini_source + real_length, - * subparent_str = curr_parent_str, - * real_parent_str = curr_parent_str; - - IniDispatch dsp = { - .format = format, - .dispatch_id = 0 - }; - - __PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_; - - for (__NODE_AT__ = 0, idx = 0; idx <= real_length; idx++) { - - if (ini_source[idx]) { - - continue; - - } - - if (!ini_source[__NODE_AT__] || _LIBCONFINI_IS_IGN_MARKER_(ini_source[__NODE_AT__], format)) { - - __NODE_AT__ = idx + 1; - continue; - - } - - if (dsp.dispatch_id >= this_doc.members) { - - return CONFINI_EOOR; - - } - - dsp.data = ini_source + __NODE_AT__; - dsp.d_len = idx - __NODE_AT__; - - /* Set `dsp.value` to an empty string */ - dsp.value = ini_source + idx; - - if ( - _LIBCONFINI_IS_DIS_MARKER_(*dsp.data, format) && ( - format.disabled_after_space || !is_some_space(dsp.data[1], _LIBCONFINI_NO_EOL_) - ) - ) { - - __ITER__ = dqultrim_s(dsp.data, 0, format); - - dsp.type = get_type_as_active( - dsp.data + __ITER__, - dsp.d_len - __ITER__, - format.disabled_can_be_implicit, - format - ); - - if (dsp.type) { - - dsp.data += __ITER__; - dsp.d_len -= __ITER__; - - /* - - // Not strictly needed... - for (; __ITER__ > 0; dsp.data[--__ITER__] = '\0'); - - */ - - } - - dsp.type |= 4; - - } else { - - switch (*dsp.data) { - - default: - - if (!_LIBCONFINI_IS_ANY_MARKER_(*dsp.data, format)) { - - dsp.type = get_type_as_active(dsp.data, dsp.d_len, _LIBCONFINI_TRUE_, format); - break; - - } - - /* - - No case break here, keep it like this! - `case _LIBCONFINI_SC_INT_MARKER_` must follow - (switch case fallthrough). - - */ - __attribute__((fallthrough)); - - case _LIBCONFINI_SC_INT_MARKER_: - - /* - - // Not strictly needed... - *dsp.data = '\0'; - - */ - - dsp.type = INI_COMMENT; - - break; - - case _LIBCONFINI_IC_INT_MARKER_: - - /* - - // Not strictly needed... - *dsp.data = '\0'; - - */ - - dsp.type = INI_INLINE_COMMENT; - /* No case break here (last case) */ - - } - - } - - if (__CURR_PARENT_LEN__ && *subparent_str) { - - __ITER__ = 0; - - do { - - curr_parent_str[__ITER__ + __CURR_PARENT_LEN__] = subparent_str[__ITER__]; - - } while (subparent_str[__ITER__++]); - - __CURR_PARENT_LEN__ += __ITER__ - 1; - subparent_str = curr_parent_str + __CURR_PARENT_LEN__; - - } - - if (__PARENT_IS_DISABLED__ && !(dsp.type & 4)) { - - real_parent_str[__REAL_PARENT_LEN__] = '\0'; - __CURR_PARENT_LEN__ = __REAL_PARENT_LEN__; - curr_parent_str = real_parent_str; - __PARENT_IS_DISABLED__ = _LIBCONFINI_FALSE_; - - } else if (!__PARENT_IS_DISABLED__ && dsp.type == INI_DISABLED_SECTION) { - - __REAL_PARENT_LEN__ = __CURR_PARENT_LEN__; - real_parent_str = curr_parent_str; - __PARENT_IS_DISABLED__ = _LIBCONFINI_TRUE_; - - } - - dsp.append_to = curr_parent_str; - dsp.at_len = __CURR_PARENT_LEN__; - - if (dsp.type == INI_COMMENT || dsp.type == INI_INLINE_COMMENT) { - - dsp.d_len = uncomment(++dsp.data, dsp.d_len - 1, format); - - } else if (format.multiline_nodes != INI_NO_MULTILINE) { - - dsp.d_len = unescape_cr_lf(dsp.data, dsp.d_len, dsp.type & 4, format); - - } - - switch (dsp.type) { - - /* - - case INI_UNKNOWN: - - // Do nothing - - break; - - */ - - case INI_SECTION: - case INI_DISABLED_SECTION: - - *dsp.data++ = '\0'; - __ITER__ = getn_metachar_pos(dsp.data, _LIBCONFINI_CLOSE_SECTION_, dsp.d_len, format); - - while (dsp.data[__ITER__]) { - - dsp.data[__ITER__++] = '\0'; - - } - - dsp.d_len = format.section_paths == INI_ONE_LEVEL_ONLY ? - collapse_everything(dsp.data, format) - : - sanitize_section_path(dsp.data, format); - - - if (format.section_paths == INI_ONE_LEVEL_ONLY || *dsp.data != _LIBCONFINI_SUBSECTION_) { - - /* - - Append to root (this is an absolute path) - - */ - - curr_parent_str = dsp.data; - __CURR_PARENT_LEN__ = dsp.d_len; - subparent_str = ini_source + idx; - dsp.append_to = subparent_str; - dsp.at_len = 0; - - } else if (format.section_paths == INI_ABSOLUTE_ONLY || !__CURR_PARENT_LEN__) { - - /* - - Append to root and remove the leading dot (parent is root or - relative paths are not allowed) - - */ - - curr_parent_str = ++dsp.data; - __CURR_PARENT_LEN__ = --dsp.d_len; - subparent_str = ini_source + idx; - dsp.append_to = subparent_str; - dsp.at_len = 0; - - } else if (dsp.d_len != 1) { - - /* - - Append to the current parent (this is a relative path - and parent is not root) - - */ - - subparent_str = dsp.data; - - } - - if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { - - string_tolower(dsp.data); - - } - - break; - - case INI_KEY: - case INI_DISABLED_KEY: - - if ( - valid_delimiter && ( - __ITER__ = getn_metachar_pos(dsp.data, (char) dsp.format.delimiter_symbol, dsp.d_len, format) - ) < dsp.d_len - ) { - - dsp.data[__ITER__] = '\0'; - dsp.value = dsp.data + __ITER__ + 1; - - - switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) { - - case 0: dsp.v_len = collapse_everything(dsp.value, format); break; - - case 1: dsp.v_len = collapse_empty_quotes(dsp.value, format); break; - - case 2: dsp.v_len = collapse_spaces(dsp.value, format); break; - - case 4: - - dsp.value += ltrim_h(dsp.value, 0, _LIBCONFINI_WITH_EOL_); - dsp.v_len = rtrim_h(dsp.value, dsp.d_len + dsp.data - dsp.value, _LIBCONFINI_WITH_EOL_); - /* No case break here (last case) */ - - } - - } else if (format.implicit_is_not_empty) { - - dsp.value = INI_GLOBAL_IMPLICIT_VALUE; - dsp.v_len = INI_GLOBAL_IMPLICIT_V_LEN; - - } - - dsp.d_len = collapse_everything(dsp.data, format); - - if (INI_GLOBAL_LOWERCASE_MODE && !format.case_sensitive) { - - string_tolower(dsp.data); - - } - - break; - - case INI_COMMENT: - case INI_INLINE_COMMENT: - - dsp.append_to = ini_source + idx; - dsp.at_len = 0; - /* No case break here (last case) */ - - } - - if (f_foreach(&dsp, user_data)) { - - return CONFINI_FEINTR; - - } - - dsp.dispatch_id++; - __NODE_AT__ = idx + 1; - - } - - #undef __CURR_PARENT_LEN__ - #undef __REAL_PARENT_LEN__ - #undef __PARENT_IS_DISABLED__ - #undef __NODE_AT__ - #undef __ITER__ - - return CONFINI_SUCCESS; - -} - - - /** @utility{load_ini_file} **/ -/** - - @brief Parses an INI file and dispatches its content to a custom - callback using a `FILE` structure as argument - @param ini_file The `FILE` handle pointing to the INI file to - parse - @param format The format of the INI file - @param f_init The function that will be invoked before the - first dispatch, or `NULL` - @param f_foreach The function that will be invoked for each - dispatch, or `NULL` - @param user_data A custom argument, or `NULL` - @return Zero for success, otherwise an error code (see `enum` - #ConfiniInterruptNo) - - @note This function is absent if the `--without-io-api` option was passed to - the `configure` script when the library was compiled - - The @p ini_file parameter must be a `FILE` handle with read privileges. On some - platforms, such as Microsoft Windows, it might be needed to add the binary - specifier to the mode string (`"b"`) in order to prevent discrepancies between - the physical size of the file and its computed size: - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} - FILE * my_file = fopen("example.conf", "rb"); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - For the two parameters @p f_init and @p f_foreach see function - #strip_ini_cache(). - - The parsing algorithms used by **libconfini** are able to parse any type of file - encoded in 8-bit code units, as long as the characters that match the regular - expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in - ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of - platform-specific conventions. - - @note In order to be null-byte-injection safe, `NUL` characters, if present in - the file, will be removed from the dispatched strings. - - Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, - #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, #CONFINI_EFBIG. - - @include topics/load_ini_file.c - -**/ -int load_ini_file ( - FILE * const ini_file, - const IniFormat format, - const IniStatsHandler f_init, - const IniDispHandler f_foreach, - void * const user_data -) { - - _LIBCONFINI_OFF_T_ file_size; - - if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) { - - return CONFINI_EBADF; - - } - - if ((size_t) file_size > SIZE_MAX) { - - return CONFINI_EFBIG; - - } - - char * const cache = (char *) malloc((size_t) file_size + 1); - - if (!cache) { - - return CONFINI_ENOMEM; - - } - - rewind(ini_file); - - if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) { - - free(cache); - return CONFINI_EIO; - - } - - const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data); - - free(cache); - return return_value; - -} - - - /** @utility{load_ini_path} **/ -/** - - @brief Parses an INI file and dispatches its content to a custom - callback using a path as argument - @param path The path of the INI file - @param format The format of the INI file - @param f_init The function that will be invoked before the - first dispatch, or `NULL` - @param f_foreach The function that will be invoked for each - dispatch, or `NULL` - @param user_data A custom argument, or `NULL` - @return Zero for success, otherwise an error code (see `enum` - #ConfiniInterruptNo) - - @note This function is absent if the `--without-io-api` option was passed to - the `configure` script when the library was compiled - - For the two parameters @p f_init and @p f_foreach see function - #strip_ini_cache(). - - The parsing algorithms used by **libconfini** are able to parse any type of file - encoded in 8-bit code units, as long as the characters that match the regular - expression `/[\s\[\]\.\\;#"']/` refer to the same code points they refer to in - ASCII (as they do, for example, in UTF-8 and ISO-8859-1), independently of - platform-specific conventions. - - @note In order to be null-byte-injection safe, `NUL` characters, if present in - the file, will be removed from the dispatched strings. - - Possible return values are: #CONFINI_SUCCESS, #CONFINI_IINTR, #CONFINI_FEINTR, - #CONFINI_ENOENT, #CONFINI_ENOMEM, #CONFINI_EIO, #CONFINI_EOOR, #CONFINI_EBADF, - #CONFINI_EFBIG. - - @include topics/load_ini_path.c - -**/ -int load_ini_path ( - const char * const path, - const IniFormat format, - const IniStatsHandler f_init, - const IniDispHandler f_foreach, - void * const user_data -) { - - FILE * const ini_file = fopen(path, "rb"); - - if (!ini_file) { - - return CONFINI_ENOENT; - - } - - _LIBCONFINI_OFF_T_ file_size; - - if (_LIBCONFINI_SEEK_EOF_(ini_file) || (file_size = _LIBCONFINI_FTELL_(ini_file)) < 0) { - - return CONFINI_EBADF; - - } - - if ((size_t) file_size > SIZE_MAX) { - - return CONFINI_EFBIG; - - } - - char * const cache = (char *) malloc((size_t) file_size + 1); - - if (!cache) { - - return CONFINI_ENOMEM; - - } - - rewind(ini_file); - - if (fread(cache, 1, (size_t) file_size, ini_file) < (size_t) file_size) { - - free(cache); - return CONFINI_EIO; - - } - - /* No checks here, as there is nothing we can do about it... */ - fclose(ini_file); - - const int return_value = strip_ini_cache(cache, (size_t) file_size, format, f_init, f_foreach, user_data); - - free(cache); - return return_value; - -} - - - - /* OTHER UTILITIES (NOT REQUIRED BY LIBCONFINI'S MAIN FUNCTIONS) */ - - - /** @utility{ini_string_match_ss} **/ -/** - - @brief Compares two simple strings and checks whether they match - @param simple_string_a The first simple string - @param simple_string_b The second simple string - @param format The format of the INI file - @return A boolean: `true` if the two strings match, `false` otherwise - - Simple strings are user-given strings or the result of #ini_string_parse(). The - @p format argument is used for the following fields: - - - `format.case_sensitive` - -**/ -bool ini_string_match_ss ( - const char * const simple_string_a, - const char * const simple_string_b, - const IniFormat format -) { - - register size_t idx = 0; - - if (format.case_sensitive) { - - do { - - if (simple_string_a[idx] != simple_string_b[idx]) { - - return _LIBCONFINI_FALSE_; - - } - - } while (simple_string_a[idx++]); - - return _LIBCONFINI_TRUE_; - - } - - do { - - if (_LIBCONFINI_CHR_CASEFOLD_(simple_string_a[idx]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string_b[idx])) { - - return _LIBCONFINI_FALSE_; - - } - - } while (simple_string_a[idx++]); - - return _LIBCONFINI_TRUE_; - -} - - - /** @utility{ini_string_match_si} **/ -/** - - @brief Compares a simple string and an INI string and and checks - whether they match - @param ini_string The INI string escaped according to - @p format - @param simple_string The simple string - @param format The format of the INI file - @return A boolean: `true` if the two strings match, `false` otherwise - - This function grants that the result of the comparison between a simple string - and an INI string - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} - printf( - "%s\n", - ini_string_match_si(my_simple_string, my_ini_string, format) ? - "They match" - : - "They don't match" - ); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - will always match the result of the _literal_ comparison between the simple - string and the INI string after the latter has been parsed by - #ini_string_parse() when `format.do_not_collapse_values` is set to `false`. - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} - ini_string_parse(my_ini_string, format); - - printf( - "%s\n", - ini_string_match_ss(my_simple_string, my_ini_string, format) ? - "They match" - : - "They don't match" - ); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - INI strings are the strings typically dispatched by #load_ini_file(), - #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three - escape sequences `\\`, `\'` and `\"`. Simple strings are user-given strings or - the result of #ini_string_parse(). - - In order to be suitable for both names and values, **this function always - considers sequences of one or more spaces out of quotes in the INI string as - collapsed**, even when `format.do_not_collapse_values` is set to `true`. - - The @p format argument is used for the following fields: - - - `format.case_sensitive` - - `format.no_double_quotes` - - `format.no_single_quotes` - - `format.multiline_nodes` - - @include topics/ini_string_match_si.c - -**/ -bool ini_string_match_si ( - const char * const simple_string, - const char * const ini_string, - const IniFormat format -) { - - /* - - Mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Format supports escape sequences (const) - FLAG_8 Unescaped single quotes are odd right now - FLAG_16 Unescaped double quotes are odd right now - FLAG_32 This is an escaped single/double quote in a format that supports - single/double quotes - FLAG_64 This is a space - FLAG_128 Skip this character - - */ - - register uint8_t abcd = INIFORMAT_HAS_NO_ESC(format) ? - 67 - : - 68 | (format.no_double_quotes << 1) | format.no_single_quotes; - - register size_t idx_i = 0; - size_t idx_s = 0, nbacksl = 0; - - - /* \ /\ - \ */ si_match: /* \ - \/ ______________________ \ */ - - - if ((abcd & 4) && ini_string[idx_i] == _LIBCONFINI_BACKSLASH_) { - - for (abcd &= 63, nbacksl++; ini_string[++idx_i] == _LIBCONFINI_BACKSLASH_; nbacksl++); - - } - - abcd = !(abcd & 10) && ini_string[idx_i] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - nbacksl & 1 ? - (abcd & 63) | 32 - : - ((abcd & 223) | 128) ^ 16 - ) - : !(abcd & 17) && ini_string[idx_i] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - nbacksl & 1 ? - (abcd & 63) | 32 - : - ((abcd & 223) | 128) ^ 8 - ) - : (abcd & 24) || !is_some_space(ini_string[idx_i], _LIBCONFINI_WITH_EOL_) ? - abcd & 31 - : abcd & 64 ? - (abcd & 223) | 128 - : - (abcd & 95) | 64; - - - if (nbacksl) { - - nbacksl = ((abcd & 32 ? nbacksl : nbacksl + 1) >> 1) + 1; - - while (--nbacksl) { - - if (simple_string[idx_s++] != _LIBCONFINI_BACKSLASH_) { - - return _LIBCONFINI_FALSE_; - - } - - } - - } - - if ((abcd & 128) || ((abcd & 64) && !simple_string[idx_s])) { - - idx_i++; - goto si_match; - - } - - if ( - abcd & 64 ? - simple_string[idx_s] != _LIBCONFINI_COLLAPSED_ || !simple_string[idx_s + 1] - : format.case_sensitive ? - ini_string[idx_i] != simple_string[idx_s] - : - _LIBCONFINI_CHR_CASEFOLD_(ini_string[idx_i]) != _LIBCONFINI_CHR_CASEFOLD_(simple_string[idx_s]) - ) { - - return _LIBCONFINI_FALSE_; - - } - - idx_s++; - - if (ini_string[idx_i++]) { - - goto si_match; - - } - - return _LIBCONFINI_TRUE_; - -} - - - /** @utility{ini_string_match_ii} **/ -/** - - @brief Compares two INI strings and checks whether they match - @param ini_string_a The first INI string unescaped according to - @p format - @param ini_string_b The second INI string unescaped according to - @p format - @param format The format of the INI file - @return A boolean: `true` if the two strings match, `false` otherwise - - This function grants that the result of the comparison between two INI strings - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} - printf( - "%s\n", - ini_string_match_ii(my_ini_string_1, my_ini_string_2, format) ? - "They match" - : - "They don't match" - ); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - will always match the result of the _literal_ comparison between the same two - INI strings after these have been parsed by #ini_string_parse() when - `format.do_not_collapse_values` is set to `false`. - - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.c} - ini_string_parse(my_ini_string_1, format); - ini_string_parse(my_ini_string_2, format); - - printf("%s\n", - ini_string_match_ss(my_ini_string_1, my_ini_string_2, format) ? - "They match" - : - "They don't match" - ); - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - INI strings are the strings typically dispatched by #load_ini_file(), - #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three - escape sequences `\\`, `\'` and `\"`. - - In order to be suitable for both names and values, **this function always - considers sequences of one or more spaces out of quotes in both strings as - collapsed**, even when `format.do_not_collapse_values` is set to `true`. - - The @p format argument is used for the following fields: - - - `format.case_sensitive` - - `format.no_double_quotes` - - `format.no_single_quotes` - - `format.multiline_nodes` - -**/ -bool ini_string_match_ii ( - const char * const ini_string_a, - const char * const ini_string_b, - const IniFormat format -) { - - const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); - register uint8_t side = 1; - register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_; - uint8_t abcd_pair[2]; - const char * chrptr_pair[2] = { ini_string_a, ini_string_b }; - size_t nbacksl_pair[2]; - - /* - - Masks `abcd_pair[0]` and `abcd_pair[1]` (7 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes and format supports - escape sequences - FLAG_32 This is a space - FLAG_64 Skip this character - - */ - - abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; - - - /* \ /\ - \ */ ii_match: /* \ - \/ ______________________ \ */ - - - nbacksl_pair[side] = 0; - - if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) { - - for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++); - - abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15; - - if ( - ( - (abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_ - ) && ( - (abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_ - ) - ) { - - nbacksl_pair[side]++; - - } - - } else { - - abcd_pair[side] = !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ? - ((abcd_pair[side] & 111) | 64) ^ 4 - : !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ((abcd_pair[side] & 111) | 64) ^ 8 - : !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ? - (abcd_pair[side] & 111) | 96 - : *chrptr_pair[side] ? - abcd_pair[side] & 47 - : - abcd_pair[side] & 15; - - - if (abcd_pair[side] & 64) { - - chrptr_pair[side]++; - goto ii_match; - - } - - } - - if (side && turn_allowed) { - - side ^= 1; - goto ii_match; - - } - - turn_allowed = _LIBCONFINI_TRUE_; - - if (*nbacksl_pair || nbacksl_pair[1]) { - - if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) { - - return _LIBCONFINI_FALSE_; - - } - - side = 1; - goto ii_match; - - } - - if ( - ( - abcd_pair[side ^ 1] & 32 ? - !(abcd_pair[side] & 32) - : - abcd_pair[(side ^= 1) ^ 1] & 32 - ) && *chrptr_pair[side] - ) { - - if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) { - - return _LIBCONFINI_FALSE_; - - } - - abcd_pair[side ^ 1] &= 95; - turn_allowed = _LIBCONFINI_FALSE_; - goto ii_match; - - } - - if ( - format.case_sensitive ? - **chrptr_pair != *chrptr_pair[1] - : - _LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1]) - ) { - - return _LIBCONFINI_FALSE_; - - } - - if (**chrptr_pair) { - - (*chrptr_pair)++; - - } - - if (*chrptr_pair[1]) { - - chrptr_pair[1]++; - - } - - if (**chrptr_pair || *chrptr_pair[1]) { - - *abcd_pair &= 95; - abcd_pair[1] &= 95; - side = 1; - goto ii_match; - - } - - return _LIBCONFINI_TRUE_; - -} - - - /** @utility{ini_array_match} **/ -/** - - @brief Compares two INI arrays and checks whether they match - @param ini_string_a The first INI array - @param ini_string_b The second INI array - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @return A boolean: `true` if the two arrays match, `false` otherwise - - This function grants that the result of the comparison between two INI arrays - will always match the the _literal_ comparison between the individual members - of both arrays after these have been parsed, one by one, by #ini_string_parse() - (with `format.do_not_collapse_values` set to `false`). - - This function can be used, with `'.'` as delimiter, to compare section paths. - - INI strings are the strings typically dispatched by #load_ini_file(), - #load_ini_path() or #strip_ini_cache(), which may contain quotes and the three - escape sequences `\\`, `\'` and `\"`. - - In order to be suitable for both names and values, **this function always - considers sequences of one or more spaces out of quotes in both strings as - collapsed**, even when `format.do_not_collapse_values` is set to `true`. - - The @p format argument is used for the following fields: - - - `format.case_sensitive` - - `format.no_double_quotes` - - `format.no_single_quotes` - - `format.multiline_nodes` - -**/ -bool ini_array_match ( - const char * const ini_string_a, - const char * const ini_string_b, - const char delimiter, - const IniFormat format -) { - - if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - /* - - We have no delimiters (array has only one member) - - */ - - return ini_string_match_ii(ini_string_a, ini_string_b, format); - - } - - const _LIBCONFINI_CHARBOOL_ has_escape = !INIFORMAT_HAS_NO_ESC(format); - register uint8_t side = 1; - register _LIBCONFINI_CHARBOOL_ turn_allowed = _LIBCONFINI_TRUE_; - uint8_t abcd_pair[2]; - size_t nbacksl_pair[2]; - const char * chrptr_pair[2] = { - ini_string_a + ltrim_s(ini_string_a, 0, _LIBCONFINI_WITH_EOL_), - ini_string_b + ltrim_s(ini_string_b, 0, _LIBCONFINI_WITH_EOL_) - }; - - /* - - Masks `abcd_pair[0]` and `abcd_pair[1]` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 We are in an odd sequence of backslashes and format supports - escape sequences - FLAG_32 This is a space - FLAG_64 This is a delimiter - FLAG_128 Skip this character - - */ - - abcd_pair[1] = *abcd_pair = 32 | (format.no_double_quotes << 1) | format.no_single_quotes; - - - /* \ /\ - \ */ delimited_match: /* \ - \/ ______________________ \ */ - - - nbacksl_pair[side] = 0; - - if (has_escape && *chrptr_pair[side] == _LIBCONFINI_BACKSLASH_) { - - for (nbacksl_pair[side]++; *(++chrptr_pair[side]) == _LIBCONFINI_BACKSLASH_; nbacksl_pair[side]++); - - abcd_pair[side] = nbacksl_pair[side] & 1 ? (abcd_pair[side] & 31) | 16 : abcd_pair[side] & 15; - - if ( - ( - (abcd_pair[side] & 9) || *chrptr_pair[side] != _LIBCONFINI_SINGLE_QUOTES_ - ) && ( - (abcd_pair[side] & 6) || *chrptr_pair[side] != _LIBCONFINI_DOUBLE_QUOTES_ - ) - ) { - - nbacksl_pair[side]++; - - } - - } else { - - abcd_pair[side] = !(abcd_pair[side] & 12) && is_some_space(*chrptr_pair[side], _LIBCONFINI_WITH_EOL_) ? - ( - delimiter || (abcd_pair[side] & 64) ? - (abcd_pair[side] & 239) | 160 - : - (abcd_pair[side] & 111) | 96 - ) - : delimiter && !(abcd_pair[side] & 12) && *chrptr_pair[side] == delimiter ? - (abcd_pair[side] & 111) | 96 - : !(abcd_pair[side] & 25) && *chrptr_pair[side] == _LIBCONFINI_SINGLE_QUOTES_ ? - ((abcd_pair[side] & 175) | 128) ^ 4 - : !(abcd_pair[side] & 22) && *chrptr_pair[side] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ((abcd_pair[side] & 175) | 128) ^ 8 - : *chrptr_pair[side] ? - abcd_pair[side] & 47 - : delimiter ? - abcd_pair[side] & 15 - : - (abcd_pair[side] & 79) ^ 64; - - - if (abcd_pair[side] & 128) { - - chrptr_pair[side]++; - goto delimited_match; - - } - - } - - if (side && turn_allowed) { - - side ^= 1; - goto delimited_match; - - } - - turn_allowed = _LIBCONFINI_TRUE_; - - if (*nbacksl_pair || nbacksl_pair[1]) { - - if (*nbacksl_pair >> 1 != nbacksl_pair[1] >> 1) { - - return _LIBCONFINI_FALSE_; - - } - - side = 1; - goto delimited_match; - - } - - if ((*abcd_pair ^ abcd_pair[1]) & 64) { - - return _LIBCONFINI_FALSE_; - - } - - if ( - !( - abcd_pair[side ^ 1] & 32 ? - abcd_pair[side] & 96 - : - (abcd_pair[(side ^= 1) ^ 1] & 96) ^ 32 - ) && *chrptr_pair[side] - ) { - - if (*chrptr_pair[side]++ != _LIBCONFINI_COLLAPSED_) { - - return _LIBCONFINI_FALSE_; - - } - - abcd_pair[side ^ 1] &= 223; - turn_allowed = _LIBCONFINI_FALSE_; - goto delimited_match; - - } - - if (~*abcd_pair & 64) { - - if ( - format.case_sensitive ? - **chrptr_pair != *chrptr_pair[1] - : - _LIBCONFINI_CHR_CASEFOLD_(**chrptr_pair) != _LIBCONFINI_CHR_CASEFOLD_(*chrptr_pair[1]) - ) { - - return _LIBCONFINI_FALSE_; - - } - - *abcd_pair &= 223; - abcd_pair[1] &= 223; - - } - - if (**chrptr_pair) { - - (*chrptr_pair)++; - - } - - if (*chrptr_pair[1]) { - - chrptr_pair[1]++; - - } - - if (**chrptr_pair || *chrptr_pair[1]) { - - side = 1; - goto delimited_match; - - } - - return _LIBCONFINI_TRUE_; - -} - - - /** @utility{ini_unquote} **/ -/** - - @brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes - (if single/double quotes are considered metacharacters in - respect to the format given) - @param ini_string The string to be unescaped - @param format The format of the INI file - @return The new length of the string - - This function is very similar to #ini_string_parse(), except that does not - bother collapsing the sequences of more than one space that might result from - removing empty quotes. Its purpose is to be used to parse key and section names, - since these are always dispatched as already collapsed. In order to parse - values, or array parts listed in values, please use #ini_string_parse(). - - If you only need to compare @p ini_string with another string, consider to use - #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former - and perform a simple comparison afterwards. These two functions are in fact able - to check directly for equality between unparsed INI strings without actually - modifiyng them. - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). If the string does not contain quotes, or if quotes are - considered to be normal characters, no changes will be made. - - @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is - no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus - the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. - - The @p format argument is used for the following fields: - - - `format.no_single_quotes` - - `format.no_double_quotes` - - `format.multiline_nodes` - - @include topics/ini_string_parse.c - -**/ -size_t ini_unquote (char * const ini_string, const IniFormat format) { - - if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { - - return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; - - } - - register size_t idx = 0; - - if (INIFORMAT_HAS_NO_ESC(format)) { - - /* - - There are no escape sequences... I will just return the length of the - string... - - */ - - while (ini_string[idx++]); - - return idx - 1; - - } - - size_t lshift = 0, nbacksl = 0; - - /* - - Mask `abcd` (6 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Unescaped single quotes are odd right now - FLAG_8 Unescaped double quotes are odd right now - FLAG_16 This is an unescaped single quote and format supports single - quotes - FLAG_32 This is an unescaped double quote and format supports double - quotes - - */ - - for (register uint8_t abcd = (format.no_double_quotes << 1) | format.no_single_quotes; ini_string[idx]; idx++) { - - abcd = !(abcd & 6) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - (abcd & 47) | 32 - : !(abcd & 9) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - (abcd & 31) | 16 - : - abcd & 15; - - - if (!(nbacksl & 1) && (abcd & 48)) { - - abcd ^= (abcd >> 2) & 12; - lshift++; - continue; - - } - - if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) { - - nbacksl++; - - } else { - - if ((nbacksl & 1) && (abcd & 48)) { - - lshift++; - - } - - lshift += nbacksl >> 1; - nbacksl = 0; - - } - - if (lshift) { - - ini_string[idx - lshift] = ini_string[idx]; - - } - - } - - lshift += nbacksl >> 1; - - for (idx -= lshift; ini_string[idx]; ini_string[idx++] = '\0'); - - return idx - lshift; - -} - - - /** @utility{ini_string_parse} **/ -/** - - @brief Unescapes `\'`, `\"`, and `\\` and removes all unescaped quotes - (if single/double quotes are considered metacharacters in - respect to the format given); if the format allows it, sequences - of one or more spaces out of quotes will be collapsed - @param ini_string The string to be unescaped - @param format The format of the INI file - @return The new length of the string - - This function is meant to be used to parse values. In order to parse key and - section names please use #ini_unquote(). - - If you only need to compare @p ini_string with another string, consider to use - #ini_string_match_si() and #ini_string_match_ii() instead of parsing the former - and perform a simple comparison afterwards. These two functions are in fact able - to check directly for equality between unparsed INI strings without actually - modifying them. - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). If `format.do_not_collapse_values` is set to non-zero, spaces - surrounding empty quotes will be collapsed together with the latter. - - @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is - no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus - the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. - - The @p format argument is used for the following fields: - - - `format.no_single_quotes` - - `format.no_double_quotes` - - `format.multiline_nodes` - - `format.do_not_collapse_values` - - @note `format.multiline_nodes` is used only to figure out whether there are - escape sequences or not. For all other purposes new line characters will - be considered to be equal to any other space character, even if the - format is not multi-line -- new line characters should never appear in - non-multi-line formats. - - @include topics/ini_string_parse.c - -**/ -size_t ini_string_parse (char * const ini_string, const IniFormat format) { - - if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { - - return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; - - } - - /* - - Mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Do not collapse spaces within members (const) - FLAG_8 Unescaped single quotes are odd right now - FLAG_16 Unescaped double quotes are odd right now - FLAG_32 This is an *escaped* single/double quote and format supports - single/double quotes - FLAG_64 This is a space - FLAG_128 Skip this character - - */ - - register uint8_t abcd = (format.do_not_collapse_values ? 68 : 64) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - size_t idx, lshift; - - if (format.multiline_nodes == INI_NO_MULTILINE) { - - switch (abcd) { - - case 64 | 2 | 1 : - - /* - - There are no escape sequences, but spaces might still need to - be collapsed. - - */ - - for (idx = 0, lshift = 0; ini_string[idx]; idx++) { - - abcd = !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? 3 : abcd & 64 ? 195 : 67; - - if (abcd & 128) { - - lshift++; - - } else { - - ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx]; - - } - - } - - for ( - - idx -= (abcd & 64) && lshift < idx ? - ++lshift - : - lshift; - - ini_string[idx]; - - ini_string[idx++] = '\0' - - ); - - return idx - lshift; - - case 64 | 4 | 2 | 1 : - - /* - - There are no escape sequences and spaces don't need to be - collapsed, but left and right trim might still be necessary. - - */ - - return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_); - - } - - } - - /* - - There might be escape sequences... - - */ - - size_t nbacksl = 0; - - for (idx = lshift = 0; ini_string[idx]; idx++) { - - abcd = !(abcd & 10) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - nbacksl & 1 ? - (abcd & 63) | 32 - : - ((abcd & 223) | 128) ^ 16 - ) - : !(abcd & 17) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - nbacksl & 1 ? - (abcd & 63) | 32 - : - ((abcd & 223) | 128) ^ 8 - ) - : (abcd & 28) || !is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? - abcd & 31 - : abcd & 64 ? - (abcd & 223) | 128 - : - (abcd & 95) | 64; - - - if (abcd & 128) { - - lshift++; - continue; - - } - - if (ini_string[idx] == _LIBCONFINI_BACKSLASH_) { - - nbacksl++; - - } else { - - if (abcd & 32) { - - lshift++; - - } - - lshift += nbacksl >> 1; - nbacksl = 0; - - } - - ini_string[idx - lshift] = abcd & 64 ? _LIBCONFINI_COLLAPSED_ : ini_string[idx]; - - } - - lshift += nbacksl >> 1; - - for ( - - idx -= (abcd & 64) && lshift < idx ? - ++lshift - : - lshift; - - ini_string[idx]; - - ini_string[idx++] = '\0' - - ); - - return (abcd & 28) ^ 4 ? - idx - lshift - : - rtrim_h(ini_string, idx - lshift, _LIBCONFINI_WITH_EOL_); - -} - - - /** @utility{ini_array_get_length} **/ -/** - - @brief Gets the length of a stringified INI array in number of members - @param ini_string The stringified array (it can be `NULL`) - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @return The length of the INI array - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - -**/ -size_t ini_array_get_length ( - const char * const ini_string, - const char delimiter, - const IniFormat format -) { - - if (!ini_string) { - - return 0; - - } - - if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - /* - - We have no delimiters (array has only one member) - - */ - - return 1; - - } - - /* - - Mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Delimiter is not any space (const) - FLAG_8 Unescaped single quotes are odd right now - FLAG_16 Unescaped double quotes are odd right now - FLAG_32 We are in an odd sequence of backslashes - FLAG_64 This is a space - FLAG_128 This is a delimiter - - */ - - register uint8_t abcd = (delimiter ? 64 : 68) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - size_t counter = 0; - - for (size_t idx = 0; ini_string[idx]; idx++) { - - /* Revision #1 */ - - abcd = !(abcd & 28) && ini_string[idx] == delimiter ? - (abcd & 159) | 128 - : !(abcd & 24) && is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_) ? - ( - (abcd & 68) ^ 4 ? - (abcd & 95) | 64 - : - (abcd & 223) | 192 - ) - : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? - (abcd & 63) ^ 32 - : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - (abcd & 31) ^ 16 - : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - (abcd & 31) ^ 8 - : - abcd & 31; - - - if (abcd & 128) { - - counter++; - - } - - } - - return !counter || (~abcd & 68) ? - counter + 1 - : - counter; - -} - - - /** @utility{ini_array_foreach} **/ -/** - - @brief Calls a custom function for each member of a stringified INI - array, without modifying the content of the buffer -- useful for - read-only (`const`) stringified arrays - @param ini_string The stringified array (it can be `NULL`) - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @param f_foreach The function that will be invoked for each array - member - @param user_data A custom argument, or `NULL` - @return Zero for success, otherwise an error code (see `enum` - #ConfiniInterruptNo) - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). - - The user given function @p f_foreach (see #IniSubstrHandler data type) will be - invoked with six arguments: `ini_string`, `memb_offset` (the offset of the - member in bytes), `memb_length` (the length of the member in bytes), `memb_num` - (the offset of the member in number of members), `format` (the format of the INI - file), `user_data` (the custom argument @p user_data previously passed). If - @p f_foreach returns a non-zero value the function #ini_array_foreach() will be - interrupted. - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - - Possible return values are: #CONFINI_SUCCESS, #CONFINI_FEINTR. - - @include topics/ini_array_foreach.c. - -**/ -int ini_array_foreach ( - const char * const ini_string, - const char delimiter, - const IniFormat format, - const IniSubstrHandler f_foreach, - void * const user_data -) { - - if (!ini_string) { - - return CONFINI_SUCCESS; - - } - - /* - - Mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Delimiter is not any space (const) - FLAG_8 Unescaped single quotes are odd until now - FLAG_16 Unescaped double quotes are odd until now - FLAG_32 We are in an odd sequence of backslashes - FLAG_64 This is not a delimiter - FLAG_128 Stop the loop - - */ - - register size_t idx; - size_t offs = ltrim_s(ini_string, 0, _LIBCONFINI_WITH_EOL_); - - if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - /* - - We have no delimiters (array has only one member) - - */ - - idx = 0; - - while (ini_string[idx++]); - - return f_foreach(ini_string, offs, rtrim_s(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ? - CONFINI_FEINTR - : - CONFINI_SUCCESS; - - } - - register uint8_t abcd = (delimiter ? 4 : 0) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - size_t memb_num = 0; - - idx = offs; - - do { - - abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ? - abcd & 159 - : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? - (abcd | 64) ^ 32 - : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ((abcd & 223) | 64) ^ 16 - : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ((abcd & 223) | 64) ^ 8 - : ini_string[idx] ? - (abcd & 223) | 64 - : - 128; - - - if (!(abcd & 88)) { - - if ( - f_foreach( - ini_string, - offs, - rtrim_s(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), - memb_num++, - format, - user_data - ) - ) { - - return CONFINI_FEINTR; - - } - - offs = abcd & 128 ? idx + 1 : ltrim_s(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_); - - } - - idx = abcd & 216 ? idx + 1 : offs; - - } while (!(abcd & 128) && ((abcd & 92) || ini_string[idx])); - - return CONFINI_SUCCESS; - -} - - - /** @utility{ini_array_shift} **/ -/** - - @brief Shifts the location pointed by @p ini_strptr to the next member - of the INI array (without modifying the content of the buffer), - or to `NULL` if the INI array has no more members -- useful for - read-only (`const`) stringified arrays - @param ini_strptr The memory location of the stringified array -- - it cannot be `NULL`, but it can point to `NULL` - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @return The length of the array member that has been left behind - - Usually @p ini_strptr comes from an #IniDispatch (but any other string may be - used as well). - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - - @include topics/ini_array_shift.c - -**/ -size_t ini_array_shift (const char ** const ini_strptr, const char delimiter, const IniFormat format) { - - size_t toklen = 0; - - if (*ini_strptr && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - if (!delimiter) { - - toklen = ltrim_s(*ini_strptr, 0, _LIBCONFINI_WITH_EOL_); - - } - - toklen += get_metachar_pos(*ini_strptr + toklen, delimiter, format); - *ini_strptr += toklen; - toklen = rtrim_s(*ini_strptr - toklen, toklen, _LIBCONFINI_WITH_EOL_); - - if (**ini_strptr) { - - *ini_strptr += ltrim_s(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_); - - if (delimiter || **ini_strptr) { - - return toklen; - - } - - } - - } - - *ini_strptr = (char *) 0; - return toklen; - -} - - - /** @utility{ini_array_collapse} **/ -/** - - @brief Compresses the distribution of the data in a stringified INI - array by removing all the white spaces that surround its - delimiters, empty quotes, collapsable spaces, etc - @param ini_string The stringified array - @param delimiter The delimiter between the array members -- - if zero (`INI_ANY_SPACE`) any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @return The new length of the stringified array - - Out of quotes similar to ECMAScript - `ini_string.replace(new RegExp("^\\s+|\\s*(?:(" + delimiter + ")\\s*|($))", "g"), "$1$2")`. - If #INI_ANY_SPACE (`0`) is used as delimiter, one or more different spaces - (`/[\t \v\f\n\r]+/`) will be always collapsed to one space, independently of - what the format says. - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). - - This function can be useful before invoking `memcpy()` using @p ini_string as - source, when saving memory is a priority. - - The @p format argument is used for the following fields: - - - `format.no_single_quotes` - - `format.no_double_quotes` - - `format.do_not_collapse_values` - - `format.preserve_empty_quotes` - - Examples: - - 1. Using comma as delimiter: - - Before: ` first   ,    second   - ,   third   ,  etc.  ` - - After: `first,second,third,etc.` - 2. Using `INI_ANY_SPACE` as delimiter: - - Before: `  first    second    - third     etc.   ` - - After: `first second third etc.` - - @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is - no-op and will only return the value of #INI_GLOBAL_IMPLICIT_V_LEN minus - the offset of @p ini_string within #INI_GLOBAL_IMPLICIT_VALUE. - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - - @include topics/ini_array_collapse.c - - @note The actual space occupied by the array might get reduced further after - each member is parsed by #ini_string_parse(). - -**/ -size_t ini_array_collapse (char * const ini_string, const char delimiter, const IniFormat format) { - - if (_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { - - return INI_GLOBAL_IMPLICIT_V_LEN + INI_GLOBAL_IMPLICIT_VALUE - ini_string; - - } - - if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - /* - - We have no delimiters (array has only one member) - - */ - - switch ((format.preserve_empty_quotes << 1) | format.do_not_collapse_values) { - - case 0: return collapse_everything(ini_string, format); - case 1: return collapse_empty_quotes(ini_string, format); - case 2: return collapse_spaces(ini_string, format); - case 3: return rtrim_h(ini_string, ltrim_hh(ini_string, 0, _LIBCONFINI_WITH_EOL_), _LIBCONFINI_WITH_EOL_); - - } - - } - - /* - - Mask `abcd` (16 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Do not collapse spaces within members (const) - FLAG_8 Preserve empty quotes (const) - FLAG_16 Any space is delimiter (const) - FLAG_32 Unescaped single quotes are odd right now - FLAG_64 Unescaped double quotes are odd right now - FLAG_128 We are in an odd sequence of backslashes - FLAG_256 This is *not* a delimiter out of quotes - FLAG_512 This is *not* a space out of quotes - FLAG_1024 These are some quotes - FLAG_2048 These are some quotes or among the last spaces are some empty - quotes - FLAG_4096 Save current `idx_d` in `fallback` - FLAG_8192 Restore `idx_d` from `fallback` before writing - FLAG_16384 Decrease `idx_d` before writing - FLAG_32768 Keep increasing `idx_d` after writing - - */ - - size_t idx_s = 0, idx_d = 0, fallback = 0; - - register uint16_t abcd = (delimiter ? 0 : 16) | - (format.preserve_empty_quotes << 3) | - (format.do_not_collapse_values << 2) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - - for (; ini_string[idx_s]; idx_s++) { - - /* Revision #1 */ - - abcd = !(abcd & 112) && ini_string[idx_s] == delimiter ? - ( - (abcd & 536) && ((abcd & 1560) ^ 8) && ((abcd & 1560) ^ 1544) && ((abcd & 1304) ^ 1032) ? - (abcd & 33407) | 33280 - : - (abcd & 41599) | 41472 - ) - : !(abcd & 96) && is_some_space(ini_string[idx_s], _LIBCONFINI_WITH_EOL_) ? - ( - !((abcd & 1816) ^ 1800) ? - (abcd & 43391) | 40960 - : !(~abcd & 1560) ? - (abcd & 41087) | 40960 - : !((abcd & 536) ^ 528) || !((abcd & 1560) ^ 536) || !((abcd & 1560) ^ 1048) ? - (abcd & 32895) | 32768 - : !(abcd & 540) || !((abcd & 1564) ^ 8) || !((abcd & 536) ^ 16) || !((abcd & 1560) ^ 24) ? - abcd & 2431 - : ((abcd & 540) ^ 4) && ((abcd & 796) ^ 12) && ((abcd & 1564) ^ 12) && ((abcd & 1308) ^ 1032) ? - (abcd & 39295) | 36864 - : - (abcd & 35199) | 32768 - ) - : !(abcd & 193) && ini_string[idx_s] == _LIBCONFINI_SINGLE_QUOTES_ ? - ( - !((abcd & 3896) ^ 8) ? - (abcd & 44927) | 44064 - : !((abcd & 3896) ^ 2056) ? - (abcd & 36735) | 36128 - : !((abcd & 1056) ^ 32) ? - (abcd & 33631) | 33536 - : !(abcd & 40) || !(~abcd & 1064) ? - ((abcd & 36735) | 35840) ^ 32 - : ((abcd & 1064) ^ 1032) && ((abcd & 1064) ^ 1056) ? - (abcd & 40831) | 39968 - : - ((abcd & 20351) | 19456) ^ 32 - ) - : !(abcd & 162) && ini_string[idx_s] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ( - !((abcd & 3928) ^ 8) ? - (abcd & 44927) | 44096 - : !((abcd & 3928) ^ 2056) ? - (abcd & 36735) | 36160 - : !((abcd & 1088) ^ 64) ? - (abcd & 33599) | 33536 - : !(abcd & 72) || !(~abcd & 1096) ? - ((abcd & 36735) | 35840) ^ 64 - : ((abcd & 1096) ^ 1088) && ((abcd & 1096) ^ 1032) ? - (abcd & 40831) | 40000 - : - ((abcd & 20351) | 19456) ^ 64 - ) - : ini_string[idx_s] == _LIBCONFINI_BACKSLASH_ ? - ( - (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? - ((abcd & 33791) | 33536) ^ 128 - : - ((abcd & 41983) | 41728) ^ 128 - ) - : (abcd & 888) && ((abcd & 1144) ^ 1032) && ((abcd & 1144) ^ 1048) && ((abcd & 2936) ^ 8) ? - (abcd & 33663) | 33536 - : - (abcd & 41855) | 41728; - - - ini_string[ - abcd & 16384 ? - --idx_d - : abcd & 8192 ? - (idx_d = fallback) - : abcd & 4096 ? - (fallback = idx_d) - : - idx_d - ] = (abcd & 1636) && ((abcd & 1392) ^ 16) ? - ini_string[idx_s] - : - _LIBCONFINI_COLLAPSED_; - - - if (abcd & 32768) { - - idx_d++; - - } - - } - - for ( - - idx_s = ((abcd & 16) && !idx_d) || (!(~abcd & 1040) && idx_d < 4) ? - (idx_d = 0) - : !(abcd & 536) || !(~abcd & 1544) || !((abcd & 1560) ^ 8) || !((abcd & 1304) ^ 1032) ? - (idx_d = fallback) - : !((abcd & 1624) ^ 1104) || !((abcd & 1592) ^ 1072) ? - (idx_d -= 2) - : ((abcd & 1552) ^ 16) && ((abcd & 632) ^ 16) && ((abcd & 1624) ^ 1616) && ((abcd & 1592) ^ 1584) ? - idx_d - : - --idx_d; - - ini_string[idx_s]; - - ini_string[idx_s++] = '\0' - - ); - - return idx_d; - -} - - - /** @utility{ini_array_break} **/ -/** - - @brief Replaces the first delimiter found (together with the spaces - that surround it) with `\0` - @param ini_string The stringified array (it can be `NULL`) - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @return A pointer to the remaining INI array or `NULL` if the remaining - array is empty - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). - - Similarly to `strtok_r()` this function can be used only once for a given - string. - - @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is - no-op and will return `NULL`. - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - - @include topics/ini_array_break.c - -**/ -char * ini_array_break (char * const ini_string, const char delimiter, const IniFormat format) { - - if (ini_string && !_LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { - - char * remnant; - - if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - /* - - We have no delimiters (array has only one member) - - */ - - remnant = ini_string; - - while (*remnant++); - - rtrim_h(ini_string, remnant - ini_string - 1, _LIBCONFINI_WITH_EOL_); - - } else { - - remnant = ini_string + get_metachar_pos(ini_string, delimiter, format); - - if (*remnant) { - - *remnant = '\0'; - rtrim_h(ini_string, remnant - ini_string, _LIBCONFINI_WITH_EOL_); - remnant += ltrim_h(remnant, 1, _LIBCONFINI_WITH_EOL_); - - if (delimiter || *remnant) { - - return remnant; - - } - - } - - } - - } - - return (char *) 0; - -} - - - /** @utility{ini_array_release} **/ -/** - - @brief Replaces the first delimiter found (together with the spaces - that surround it) with `\0`, then shifts the location pointed by - @p ini_strptr to the next member of the INI array, or to `NULL` - if the INI array has no more members - @param ini_strptr The memory location of the stringified array -- - it cannot be `NULL`, but it can point to `NULL` - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @return The array member that has been released - - Usually @p ini_strptr comes from an #IniDispatch (but any other string may be - used as well). - - Similarly to `strtok_r()` this function can be used only once for a given - string. - - @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE this function is - no-op and will set @p ini_strptr to `NULL`. - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - - @include topics/ini_array_release.c - -**/ -char * ini_array_release (char ** const ini_strptr, const char delimiter, const IniFormat format) { - - char * const token = *ini_strptr; - - if (token && !_LIBCONFINI_IMPLICIT_RANGE_(token) && !_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - *ini_strptr += get_metachar_pos(*ini_strptr, delimiter, format); - - if (**ini_strptr) { - - **ini_strptr = '\0'; - rtrim_h(token, *ini_strptr - token, _LIBCONFINI_WITH_EOL_); - *ini_strptr += ltrim_h(*ini_strptr, 1, _LIBCONFINI_WITH_EOL_); - - if (delimiter || **ini_strptr) { - - return token; - - } - - } - - } - - *ini_strptr = (char *) 0; - return token; - -} - - - /** @utility{ini_array_split} **/ -/** - - @brief Splits a stringified INI array into NUL-separated members and - calls a custom function for each member - @param ini_string The stringified array (it cannot be `NULL`) - @param delimiter The delimiter between the array members -- if - zero (see #INI_ANY_SPACE), any space is - delimiter (`/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`) - @param format The format of the INI file - @param f_foreach The function that will be invoked for each array - member - @param user_data A custom argument, or `NULL` - @return Zero for success, otherwise an error code (see `enum` - #ConfiniInterruptNo) - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). - - The user given function @p f_foreach (see #IniStrHandler data type) will be - invoked with five arguments: `member` (the member of the array), `memb_length` - (the length of the member in bytes), `memb_num` (the offset of the member in - number of members), `format` (the format of the INI file), `user_data` (the - custom argument @p user_data previously passed). If @p f_foreach returns a - non-zero value the function #ini_array_split() will be interrupted. - - Similarly to `strtok_r()` this function can be used only once for a given - string. - - @note If @p ini_string comes from #INI_GLOBAL_IMPLICIT_VALUE or is `NULL` this - function is no-op and will return an error code. - - @note If @p delimiter matches a metacharacter within the format given (`'\\'`, - `'\''` or `'\"'`), its role as metacharacter will have higher priority - than its role as delimiter (i.e., the array will have no delimiters and - will contain only one member). - - Possible return values are: #CONFINI_SUCCESS, #CONFINI_EROADDR, #CONFINI_FEINTR. - - @include topics/ini_array_split.c. - -**/ -int ini_array_split ( - char * const ini_string, - const char delimiter, - const IniFormat format, - const IniStrHandler f_foreach, - void * const user_data -) { - - if (!ini_string || _LIBCONFINI_IMPLICIT_RANGE_(ini_string)) { - - return CONFINI_EROADDR; - - } - - /* - - Mask `abcd` (8 bits used): - - FLAG_1 Single quotes are not metacharacters (const) - FLAG_2 Double quotes are not metacharacters (const) - FLAG_4 Delimiter is not any space (const) - FLAG_8 Unescaped single quotes are odd until now - FLAG_16 Unescaped double quotes are odd until now - FLAG_32 We are in an odd sequence of backslashes - FLAG_64 This is not a delimiter - FLAG_128 Stop the loop - - */ - - register size_t idx; - size_t offs = ltrim_h(ini_string, 0, _LIBCONFINI_WITH_EOL_); - - if (_LIBCONFINI_IS_ESC_CHAR_(delimiter, format)) { - - /* - - We have no delimiters (array has only one member) - - */ - - idx = 0; - - while (ini_string[idx++]); - - return f_foreach(ini_string + offs, rtrim_h(ini_string + offs, idx - offs - 1, _LIBCONFINI_WITH_EOL_), 0, format, user_data) ? - CONFINI_FEINTR - : - CONFINI_SUCCESS; - - } - - register uint8_t abcd = (delimiter ? 4 : 0) | - (format.no_double_quotes << 1) | - format.no_single_quotes; - - size_t memb_num = 0; - - idx = offs; - - - do { - - abcd = (delimiter ? ini_string[idx] == delimiter : is_some_space(ini_string[idx], _LIBCONFINI_WITH_EOL_)) ? - abcd & 159 - : ini_string[idx] == _LIBCONFINI_BACKSLASH_ ? - (abcd | 64) ^ 32 - : !(abcd & 42) && ini_string[idx] == _LIBCONFINI_DOUBLE_QUOTES_ ? - ((abcd & 223) | 64) ^ 16 - : !(abcd & 49) && ini_string[idx] == _LIBCONFINI_SINGLE_QUOTES_ ? - ((abcd & 223) | 64) ^ 8 - : ini_string[idx] ? - (abcd & 223) | 64 - : - 128; - - - if (!(abcd & 88)) { - - ini_string[idx] = '\0'; - - if ( - f_foreach( - ini_string + offs, - rtrim_h(ini_string + offs, idx - offs, _LIBCONFINI_WITH_EOL_), - memb_num++, - format, - user_data - ) - ) { - - return CONFINI_FEINTR; - - } - - offs = abcd & 128 ? idx + 1 : ltrim_h(ini_string, idx + 1, _LIBCONFINI_WITH_EOL_); - - } - - idx = abcd & 216 ? idx + 1 : offs; - - } while (!(abcd & 128) && ((abcd & 92) || ini_string[idx])); - - return CONFINI_SUCCESS; - -} - - - /** @utility{ini_global_set_lowercase_mode} **/ -/** - - @brief Sets the value of the global variable - #INI_GLOBAL_LOWERCASE_MODE - @param lowercase The new value - @return Nothing - - If @p lowercase is `true`, key and section names in case-insensitive INI formats - will be dispatched lowercase, verbatim otherwise (default value: `true`). - - @warning This function changes the value of one or more global variables. In - order to be thread-safe this function should be used only once at - beginning of execution, or otherwise a mutex logic must be - introduced. - -**/ -void ini_global_set_lowercase_mode (const bool lowercase) { - - INI_GLOBAL_LOWERCASE_MODE = lowercase; - -} - - - /** @utility{ini_global_set_implicit_value} **/ -/** - - @brief Sets the value to be to be assigned to implicit keys - @param implicit_value The string to be used as implicit value - (usually `"YES"`, or `"TRUE"`) - @param implicit_v_len The length of @p implicit_value (usually - `0`, independently of its real length) - @return Nothing - - @warning This function changes the value of one or more global variables. In - order to be thread-safe this function should be used only once at - beginning of execution, or otherwise a mutex logic must be - introduced. - - @include topics/ini_global_set_implicit_value.c - -**/ -void ini_global_set_implicit_value (char * const implicit_value, const size_t implicit_v_len) { - - INI_GLOBAL_IMPLICIT_VALUE = implicit_value; - INI_GLOBAL_IMPLICIT_V_LEN = implicit_v_len; - -} - - - /** @utility{ini_fton} **/ -/** - - @brief Calculates the #IniFormatNum of an #IniFormat - @param source The #IniFormat to compute - @return The unique unsigned integer that identifies the format given - -**/ -IniFormatNum ini_fton (const IniFormat source) { - - #define __INIFORMAT_ID__(NAME, OFFSET, SIZE, DEFVAL) (source.NAME << OFFSET) | - - return INIFORMAT_TABLE_AS(__INIFORMAT_ID__) 0; - - #undef __INIFORMAT_ID__ - -} - - - /** @utility{ini_ntof} **/ -/** - - @brief Constructs a new #IniFormat according to an #IniFormatNum - @param format_num The #IniFormatNum to parse - @return The new #IniFormat constructed - - @note If @p format_num `>` `16777215` it will be truncated to 24 bits. - -**/ -IniFormat ini_ntof (const IniFormatNum format_num) { - - #define __MAX_1_BITS__ 1 - #define __MAX_2_BITS__ 3 - #define __MAX_3_BITS__ 7 - #define __MAX_4_BITS__ 15 - #define __MAX_5_BITS__ 31 - #define __MAX_6_BITS__ 63 - #define __MAX_7_BITS__ 127 - #define __MAX_8_BITS__ 255 - #define __INIFORMAT_PROPERTIES__(NAME, OFFSET, SIZE, DEFVAL) \ - (unsigned char) ((format_num >> OFFSET) & __MAX_##SIZE##_BITS__), - - return (IniFormat) { INIFORMAT_TABLE_AS(__INIFORMAT_PROPERTIES__) }; - - #undef __INIFORMAT_PROPERTIES__ - #undef __MAX_8_BITS__ - #undef __MAX_7_BITS__ - #undef __MAX_6_BITS__ - #undef __MAX_5_BITS__ - #undef __MAX_4_BITS__ - #undef __MAX_3_BITS__ - #undef __MAX_2_BITS__ - #undef __MAX_1_BITS__ - -} - - - /** @utility{ini_get_bool} **/ -/** - - @brief Checks whether a string matches one of the booleans listed in - the private constant #INI_BOOLEANS (case-insensitive) - @param ini_string A string to check (it can be `NULL`) - @param when_fail A value that is returned if no matching - boolean is found - @return The matching boolean (`0` or `1`) or @p when_fail if - @p ini_string does not contain a valid INI boolean - - Usually @p ini_string comes from an #IniDispatch (but any other string may be - used as well). - - @include miscellanea/typed_ini.c - -**/ -int ini_get_bool (const char * const ini_string, const int when_fail) { - - if (!ini_string) { - - return when_fail; - - } - - register uint8_t bool_idx; - register size_t pair_idx, chr_idx; - - for (pair_idx = 0; pair_idx < sizeof(INI_BOOLEANS) / sizeof(const char * const [2]); pair_idx++) { - - for (bool_idx = 0; bool_idx < 2; bool_idx++) { - - chr_idx = 0; - - while (_LIBCONFINI_CHR_CASEFOLD_(ini_string[chr_idx]) == INI_BOOLEANS[pair_idx][bool_idx][chr_idx]) { - - if (!ini_string[chr_idx++]) { - - return (int) bool_idx; - - } - - } - - } - - } - - return when_fail; - -} - - - - /* LINKS - In case you don't have `#include ` in your source */ - - -/** - - @alias{ini_get_int} - Pointer to - [`atoi()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoi) - @alias{ini_get_lint} - Pointer to - [`atol()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atol) - @alias{ini_get_llint} - Pointer to - [`atoll()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atoll) - @alias{ini_get_double} - Pointer to - [`atof()`](http://www.gnu.org/software/libc/manual/html_node/Parsing-of-Integers.html#index-atof) - -**/ - -int (* const ini_get_int) (const char * ini_string) = atoi; - -long int (* const ini_get_lint) (const char * ini_string) = atol; - -long long int (* const ini_get_llint) (const char * ini_string) = atoll; - -double (* const ini_get_double) (const char * ini_string) = atof; - -/* Legacy support -- please **do not use this**! */ -#ifdef ini_get_float -#undef ini_get_float -#endif -double (* const ini_get_float) (const char * ini_string) = atof; - - - - /* GLOBAL VARIABLES */ - - -bool INI_GLOBAL_LOWERCASE_MODE = _LIBCONFINI_FALSE_; - -char * INI_GLOBAL_IMPLICIT_VALUE = (char *) 0; - -size_t INI_GLOBAL_IMPLICIT_V_LEN = 0; - - - -/** @endfnlist **/ - -/* EOF */ - diff --git a/vendor/confini.h b/vendor/confini.h deleted file mode 100644 index c4c0068..0000000 --- a/vendor/confini.h +++ /dev/null @@ -1,547 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ - -/** - - @file confini.h - @brief libconfini header - @author Stefano Gioffré - @copyright GNU General Public License, version 3 or any later version - @version 1.14.0 - @date 2016-2020 - @see https://madmurphy.github.io/libconfini - -**/ - - -#ifndef _LIBCONFINI_HEADER_ -#define _LIBCONFINI_HEADER_ - - - -#include -#include -#include - - - -#ifdef __cplusplus -extern "C" { -#endif - - - -/* PRIVATE (HEADER-SCOPED) MACROS */ - - -#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \ - unsigned char NAME:SIZE; -#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL, -#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0, -#define _LIBCONFINI_INIFORMAT_TYPE_ \ - struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) } -#define _LIBCONFINI_DEFAULT_FORMAT_ \ - { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) } -#define _LIBCONFINI_UNIXLIKE_FORMAT_ \ - { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) } - - - -/* PUBLIC MACROS */ - - -/** - @brief Calls a user-given macro (that accepts four arguments) for each row - of the table -**/ -/* - NOTE: The following table and the order of its rows **define** (and link - together) both the #IniFormat and #IniFormatNum data types declared in this - header -*/ -#define INIFORMAT_TABLE_AS(_____) /* IniFormat table *\ - - NAME BIT SIZE DEFAULT - */\ - _____( delimiter_symbol, 0, 7, INI_EQUALS ) \ - _____( case_sensitive, 7, 1, false )/* - */\ - _____( semicolon_marker, 8, 2, INI_DISABLED_OR_COMMENT ) \ - _____( hash_marker, 10, 2, INI_DISABLED_OR_COMMENT ) \ - _____( section_paths, 12, 2, INI_ABSOLUTE_AND_RELATIVE ) \ - _____( multiline_nodes, 14, 2, INI_MULTILINE_EVERYWHERE )/* - */\ - _____( no_single_quotes, 16, 1, false ) \ - _____( no_double_quotes, 17, 1, false ) \ - _____( no_spaces_in_names, 18, 1, false ) \ - _____( implicit_is_not_empty, 19, 1, false ) \ - _____( do_not_collapse_values, 20, 1, false ) \ - _____( preserve_empty_quotes, 21, 1, false ) \ - _____( disabled_after_space, 22, 1, false ) \ - _____( disabled_can_be_implicit, 23, 1, false ) - - - -/** - @brief Checks whether a format does **not** support escape sequences -**/ -#define INIFORMAT_HAS_NO_ESC(FORMAT) \ - (FORMAT.multiline_nodes == INI_NO_MULTILINE && \ - FORMAT.no_double_quotes && FORMAT.no_single_quotes) - - - -/* PUBLIC TYPEDEFS */ - - -/** - @brief 24-bit bitfield representing the format of an INI file (INI - dialect) -**/ -typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat; - - -/** - @brief Global statistics about an INI file -**/ -typedef struct IniStatistics { - const IniFormat format; - const size_t bytes; - const size_t members; -} IniStatistics; - - -/** - @brief Dispatch of a single INI node -**/ -typedef struct IniDispatch { - const IniFormat format; - uint8_t type; - char * data; - char * value; - const char * append_to; - size_t d_len; - size_t v_len; - size_t at_len; - size_t dispatch_id; -} IniDispatch; - - -/** - @brief The unique ID of an INI format (24-bit maximum) -**/ -typedef uint32_t IniFormatNum; - - -/** - @brief Callback function for handling an #IniStatistics structure -**/ -typedef int (* IniStatsHandler) ( - IniStatistics * statistics, - void * user_data -); - - -/** - @brief Callback function for handling an #IniDispatch structure -**/ -typedef int (* IniDispHandler) ( - IniDispatch * dispatch, - void * user_data -); - - -/** - @brief Callback function for handling an INI string belonging to a - sequence of INI strings -**/ -typedef int (* IniStrHandler) ( - char * ini_string, - size_t string_length, - size_t string_num, - IniFormat format, - void * user_data -); - - -/** - @brief Callback function for handling a selected fragment of an INI string -**/ -typedef int (* IniSubstrHandler) ( - const char * ini_string, - size_t fragm_offset, - size_t fragm_length, - size_t fragm_num, - IniFormat format, - void * user_data -); - - - -/* PUBLIC FUNCTIONS */ - - -extern int strip_ini_cache ( - register char * const ini_source, - const size_t ini_length, - const IniFormat format, - const IniStatsHandler f_init, - const IniDispHandler f_foreach, - void * const user_data -); - - -extern int load_ini_file ( - FILE * const ini_file, - const IniFormat format, - const IniStatsHandler f_init, - const IniDispHandler f_foreach, - void * const user_data -); - - -extern int load_ini_path ( - const char * const path, - const IniFormat format, - const IniStatsHandler f_init, - const IniDispHandler f_foreach, - void * const user_data -); - - -extern bool ini_string_match_ss ( - const char * const simple_string_a, - const char * const simple_string_b, - const IniFormat format -); - - -extern bool ini_string_match_si ( - const char * const simple_string, - const char * const ini_string, - const IniFormat format -); - - -extern bool ini_string_match_ii ( - const char * const ini_string_a, - const char * const ini_string_b, - const IniFormat format -); - - -extern bool ini_array_match ( - const char * const ini_string_a, - const char * const ini_string_b, - const char delimiter, - const IniFormat format -); - - -extern size_t ini_unquote ( - char * const ini_string, - const IniFormat format -); - - -extern size_t ini_string_parse ( - char * const ini_string, - const IniFormat format -); - - -extern size_t ini_array_get_length ( - const char * const ini_string, - const char delimiter, - const IniFormat format -); - - -extern int ini_array_foreach ( - const char * const ini_string, - const char delimiter, - const IniFormat format, - const IniSubstrHandler f_foreach, - void * const user_data -); - - -extern size_t ini_array_shift ( - const char ** const ini_strptr, - const char delimiter, - const IniFormat format -); - - -extern size_t ini_array_collapse ( - char * const ini_string, - const char delimiter, - const IniFormat format -); - - -extern char * ini_array_break ( - char * const ini_string, - const char delimiter, - const IniFormat format -); - - -extern char * ini_array_release ( - char ** const ini_strptr, - const char delimiter, - const IniFormat format -); - - -extern int ini_array_split ( - char * const ini_string, - const char delimiter, - const IniFormat format, - const IniStrHandler f_foreach, - void * const user_data -); - - -extern void ini_global_set_lowercase_mode ( - const bool lowercase -); - - -extern void ini_global_set_implicit_value ( - char * const implicit_value, - const size_t implicit_v_len -); - - -extern IniFormatNum ini_fton ( - const IniFormat format -); - - -extern IniFormat ini_ntof ( - const IniFormatNum format_id -); - - -extern int ini_get_bool ( - const char * const ini_string, - const int when_fail -); - - - -/* PUBLIC LINKS */ - - -extern int (* const ini_get_int) ( - const char * ini_string -); - - -extern long int (* const ini_get_lint) ( - const char * ini_string -); - - -extern long long int (* const ini_get_llint) ( - const char * ini_string -); - - -extern double (* const ini_get_double) ( - const char * ini_string -); - - -/** - @brief Legacy support, soon to be replaced with a `float` data type -- - please **do not use `ini_get_float()`!** -**/ -#define ini_get_float \ - _Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \ - ini_get_double - - - -/* PUBLIC CONSTANTS AND VARIABLES */ - - -/** - @brief Error mask (flags not present in user-generated interruptions) -**/ -#define CONFINI_ERROR 252 - - -/** - @brief Error codes -**/ -enum ConfiniInterruptNo { - CONFINI_SUCCESS = 0, /**< There have been no interruptions, everything - went well [value=0] **/ - CONFINI_IINTR = 1, /**< Interrupted by the user during `f_init()` - [value=1] **/ - CONFINI_FEINTR = 2, /**< Interrupted by the user during `f_foreach()` - [value=2] **/ - CONFINI_ENOENT = 4, /**< File inaccessible [value=4] **/ - CONFINI_ENOMEM = 5, /**< Error allocating virtual memory [value=5] **/ - CONFINI_EIO = 6, /**< Error reading the file [value=6] **/ - CONFINI_EOOR = 7, /**< Out-of-range error: callbacks are more than - expected [value=7] **/ - CONFINI_EBADF = 8, /**< The stream specified is not a seekable stream - [value=8] **/ - CONFINI_EFBIG = 9, /**< File too large [value=9] **/ - CONFINI_EROADDR = 10 /**< Address is read-only [value=10] **/ -}; - - -/** - @brief INI node types -**/ -enum IniNodeType { - INI_UNKNOWN = 0, /**< This is a node impossible to categorize - [value=0] **/ - INI_VALUE = 1, /**< Not used by **libconfini** (values are - dispatched together with keys) -- but - available for user's implementations - [value=1] **/ - INI_KEY = 2, /**< This is a key [value=2] **/ - INI_SECTION = 3, /**< This is a section or a section path - [value=3] **/ - INI_COMMENT = 4, /**< This is a comment [value=4] **/ - INI_INLINE_COMMENT = 5, /**< This is an inline comment [value=5] **/ - INI_DISABLED_KEY = 6, /**< This is a disabled key [value=6] **/ - INI_DISABLED_SECTION = 7 /**< This is a disabled section path - [value=7] **/ -}; - - -/** - @brief Common array and key-value delimiters (but a delimiter may also be - any other ASCII character not present in this list) -**/ -enum IniDelimiters { - INI_ANY_SPACE = 0, /**< In multi-line INIs: - `/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in - non-multi-line INIs: `/[\t \v\f])+/` **/ - INI_EQUALS = '=', /**< Equals character (`=`) **/ - INI_COLON = ':', /**< Colon character (`:`) **/ - INI_DOT = '.', /**< Dot character (`.`) **/ - INI_COMMA = ',' /**< Comma character (`,`) **/ -}; - - -/** - @brief Possible values of #IniFormat::semicolon_marker and - #IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in - respect to a format) -**/ -enum IniCommentMarker { - INI_DISABLED_OR_COMMENT = 0, /**< This marker opens a comment or a - disabled entry **/ - INI_ONLY_COMMENT = 1, /**< This marker opens a comment **/ - INI_IGNORE = 2, /**< This marker opens a comment that has - been marked for deletion and must not - be dispatched or counted **/ - INI_IS_NOT_A_MARKER = 3 /**< This is not a marker at all, but a - normal character instead **/ -}; - - -/** - @brief Possible values of #IniFormat::section_paths -**/ -enum IniSectionPaths { - INI_ABSOLUTE_AND_RELATIVE = 0, /**< Section paths starting with a dot - express nesting to the current parent, - to root otherwise **/ - INI_ABSOLUTE_ONLY = 1, /**< Section paths starting with a dot will - be cleaned of their leading dot and - appended to root **/ - INI_ONE_LEVEL_ONLY = 2, /**< Format supports sections, but the dot - does not express nesting and is not a - meta-character **/ - INI_NO_SECTIONS = 3 /**< Format does *not* support sections -- - `/\[[^\]]*\]/g`, if any, will be - treated as keys! **/ -}; - - -/** - @brief Possible values of #IniFormat::multiline_nodes -**/ -enum IniMultiline { - INI_MULTILINE_EVERYWHERE = 0, /**< Comments, section paths and keys - -- disabled or not -- are allowed - to be multi-line **/ - INI_BUT_COMMENTS = 1, /**< Only section paths and keys -- - disabled or not -- are allowed to - be multi-line **/ - INI_BUT_DISABLED_AND_COMMENTS = 2, /**< Only active section paths and - active keys are allowed to be - multi-line **/ - INI_NO_MULTILINE = 3 /**< Multi-line escape sequences are - disabled **/ -}; - - -/** - @brief A model format for standard INI files -**/ -static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_; - - -/** - @brief A model format for Unix-like .conf files (where space characters - are delimiters between keys and values) -**/ -/* All fields are set to `0` here. */ -static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_; - - -/** - @brief If set to `true`, key and section names in case-insensitive INI - formats will be dispatched lowercase, verbatim otherwise (default - value: `false`) -**/ -extern bool INI_GLOBAL_LOWERCASE_MODE; - - -/** - @brief Value to be assigned to implicit keys (default value: `NULL`) -**/ -extern char * INI_GLOBAL_IMPLICIT_VALUE; - - -/** - @brief Length of the value assigned to implicit keys (default value: `0`) -**/ -extern size_t INI_GLOBAL_IMPLICIT_V_LEN; - - - -/* CLEAN THE PRIVATE ENVIRONMENT */ - - -#undef _LIBCONFINI_UNIXLIKE_FORMAT_ -#undef _LIBCONFINI_DEFAULT_FORMAT_ -#undef _LIBCONFINI_INIFORMAT_TYPE_ -#undef __INIFORMAT_TABLE_CB_ZERO__ -#undef __INIFORMAT_TABLE_CB_DEFAULT__ -#undef __INIFORMAT_TABLE_CB_FIELDS__ - - - -/* END OF `_LIBCONFINI_HEADER_` */ - - -#ifdef __cplusplus -} -#endif - - -#endif - - - -/* EOF */ - diff --git a/vendor/toml.c b/vendor/toml.c new file mode 100644 index 0000000..98f765b --- /dev/null +++ b/vendor/toml.c @@ -0,0 +1,2274 @@ +/* + + MIT License + + Copyright (c) 2017 - 2019 CK Tan + https://github.com/cktan/tomlc99 + + 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. + +*/ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "toml.h" + + +static void* (*ppmalloc)(size_t) = malloc; +static void (*ppfree)(void*) = free; + +void toml_set_memutil(void* (*xxmalloc)(size_t), + void (*xxfree)(void*)) +{ + if (xxmalloc) ppmalloc = xxmalloc; + if (xxfree) ppfree = xxfree; +} + + +#define MALLOC(a) ppmalloc(a) +#define FREE(a) ppfree(a) + +static void* CALLOC(size_t nmemb, size_t sz) +{ + int nb = sz * nmemb; + void* p = MALLOC(nb); + if (p) { + memset(p, 0, nb); + } + return p; +} + + +static char* STRDUP(const char* s) +{ + int len = strlen(s); + char* p = MALLOC(len+1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +static char* STRNDUP(const char* s, size_t n) +{ + size_t len = strnlen(s, n); + char* p = MALLOC(len+1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + + + +/** + * Convert a char in utf8 into UCS, and store it in *ret. + * Return #bytes consumed or -1 on failure. + */ +int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret) +{ + const unsigned char* buf = (const unsigned char*) orig; + unsigned i = *buf++; + int64_t v; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (0 == (i >> 7)) { + if (len < 1) return -1; + v = i; + return *ret = v, 1; + } + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (0x6 == (i >> 5)) { + if (len < 2) return -1; + v = i & 0x1f; + for (int j = 0; j < 1; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (0xE == (i >> 4)) { + if (len < 3) return -1; + v = i & 0x0F; + for (int j = 0; j < 2; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x1E == (i >> 3)) { + if (len < 4) return -1; + v = i & 0x07; + for (int j = 0; j < 3; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x3E == (i >> 2)) { + if (len < 5) return -1; + v = i & 0x03; + for (int j = 0; j < 4; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x7e == (i >> 1)) { + if (len < 6) return -1; + v = i & 0x01; + for (int j = 0; j < 5; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*) buf - orig; + } + return -1; +} + + +/** + * Convert a UCS char to utf8 code, and return it in buf. + * Return #bytes used in buf to encode the char, or + * -1 on error. + */ +int toml_ucs_to_utf8(int64_t code, char buf[6]) +{ + /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 */ + /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well + * as 0xfffe and 0xffff (UCS noncharacters) should not appear in + * conforming UTF-8 streams. + */ + if (0xd800 <= code && code <= 0xdfff) return -1; + if (0xfffe <= code && code <= 0xffff) return -1; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (code < 0) return -1; + if (code <= 0x7F) { + buf[0] = (unsigned char) code; + return 1; + } + + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (code <= 0x000007FF) { + buf[0] = 0xc0 | (code >> 6); + buf[1] = 0x80 | (code & 0x3f); + return 2; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x0000FFFF) { + buf[0] = 0xe0 | (code >> 12); + buf[1] = 0x80 | ((code >> 6) & 0x3f); + buf[2] = 0x80 | (code & 0x3f); + return 3; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x001FFFFF) { + buf[0] = 0xf0 | (code >> 18); + buf[1] = 0x80 | ((code >> 12) & 0x3f); + buf[2] = 0x80 | ((code >> 6) & 0x3f); + buf[3] = 0x80 | (code & 0x3f); + return 4; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x03FFFFFF) { + buf[0] = 0xf8 | (code >> 24); + buf[1] = 0x80 | ((code >> 18) & 0x3f); + buf[2] = 0x80 | ((code >> 12) & 0x3f); + buf[3] = 0x80 | ((code >> 6) & 0x3f); + buf[4] = 0x80 | (code & 0x3f); + return 5; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x7FFFFFFF) { + buf[0] = 0xfc | (code >> 30); + buf[1] = 0x80 | ((code >> 24) & 0x3f); + buf[2] = 0x80 | ((code >> 18) & 0x3f); + buf[3] = 0x80 | ((code >> 12) & 0x3f); + buf[4] = 0x80 | ((code >> 6) & 0x3f); + buf[5] = 0x80 | (code & 0x3f); + return 6; + } + + return -1; +} + +/* + * TOML has 3 data structures: value, array, table. + * Each of them can have identification key. + */ +typedef struct toml_keyval_t toml_keyval_t; +struct toml_keyval_t { + const char* key; /* key to this value */ + const char* val; /* the raw value */ +}; + + +struct toml_array_t { + const char* key; /* key to this array */ + int kind; /* element kind: 'v'alue, 'a'rray, or 't'able */ + int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp */ + + int nelem; /* number of elements */ + union { + char** val; + toml_array_t** arr; + toml_table_t** tab; + } u; +}; + + +struct toml_table_t { + const char* key; /* key to this table */ + bool implicit; /* table was created implicitly */ + + /* key-values in the table */ + int nkval; + toml_keyval_t** kval; + + /* arrays in the table */ + int narr; + toml_array_t** arr; + + /* tables in the table */ + int ntab; + toml_table_t** tab; +}; + + +static inline void xfree(const void* x) { if (x) FREE((void*)(intptr_t)x); } + + +enum tokentype_t { + INVALID, + DOT, + COMMA, + EQUAL, + LBRACE, + RBRACE, + NEWLINE, + LBRACKET, + RBRACKET, + STRING, +}; +typedef enum tokentype_t tokentype_t; + +typedef struct token_t token_t; +struct token_t { + tokentype_t tok; + int lineno; + char* ptr; /* points into context->start */ + int len; + int eof; +}; + + +typedef struct context_t context_t; +struct context_t { + char* start; + char* stop; + char* errbuf; + int errbufsz; + + token_t tok; + toml_table_t* root; + toml_table_t* curtab; + + struct { + int top; + char* key[10]; + token_t tok[10]; + } tpath; + +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define FLINE __FILE__ ":" TOSTRING(__LINE__) + +static int next_token(context_t* ctx, int dotisspecial); + +/* + Error reporting. Call when an error is detected. Always return -1. +*/ +static int e_outofmemory(context_t* ctx, const char* fline) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); + return -1; +} + + +static int e_internal(context_t* ctx, const char* fline) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); + return -1; +} + +static int e_syntax(context_t* ctx, int lineno, const char* msg) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static int e_badkey(context_t* ctx, int lineno) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); + return -1; +} + +static int e_keyexists(context_t* ctx, int lineno) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); + return -1; +} + +static void* expand(void* p, int sz, int newsz) +{ + void* s = MALLOC(newsz); + if (!s) return 0; + + memcpy(s, p, sz); + FREE(p); + return s; +} + +static void** expand_ptrarr(void** p, int n) +{ + void** s = MALLOC((n+1) * sizeof(void*)); + if (!s) return 0; + + s[n] = 0; + memcpy(s, p, n * sizeof(void*)); + FREE(p); + return s; +} + + +static char* norm_lit_str(const char* src, int srclen, + int multiline, + char* errbuf, int errbufsz) +{ + char* dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char* sp = src; + const char* sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char* x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) break; + + ch = *sp++; + /* control characters other than tab is not allowed */ + if ((0 <= ch && ch <= 0x08) + || (0x0a <= ch && ch <= 0x1f) + || (ch == 0x7f)) { + if (! (multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + } + + dst[off++] = 0; + return dst; +} + + + + +/* + * Convert src to raw unescaped utf-8 string. + * Returns NULL if error with errmsg in errbuf. + */ +static char* norm_basic_str(const char* src, int srclen, + int multiline, + char* errbuf, int errbufsz) +{ + char* dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char* sp = src; + const char* sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char* x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) break; + + ch = *sp++; + if (ch != '\\') { + /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F */ + if ((0 <= ch && ch <= 0x08) + || (0x0a <= ch && ch <= 0x1f) + || (ch == 0x7f)) { + if (! (multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + continue; + } + + /* ch was backslash. we expect the escape char. */ + if (sp >= sq) { + snprintf(errbuf, errbufsz, "last backslash is invalid"); + xfree(dst); + return 0; + } + + /* for multi-line, we want to kill line-ending-backslash ... */ + if (multiline) { + + // if there is only whitespace after the backslash ... + if (sp[strspn(sp, " \t\r")] == '\n') { + /* skip all the following whitespaces */ + sp += strspn(sp, " \t\r\n"); + continue; + } + } + + /* get the escaped char */ + ch = *sp++; + switch (ch) { + case 'u': case 'U': + { + int64_t ucs = 0; + int nhex = (ch == 'u' ? 4 : 8); + for (int i = 0; i < nhex; i++) { + if (sp >= sq) { + snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); + xfree(dst); + return 0; + } + ch = *sp++; + int v = ('0' <= ch && ch <= '9') + ? ch - '0' + : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); + if (-1 == v) { + snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); + xfree(dst); + return 0; + } + ucs = ucs * 16 + v; + } + int n = toml_ucs_to_utf8(ucs, &dst[off]); + if (-1 == n) { + snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); + xfree(dst); + return 0; + } + off += n; + } + continue; + + case 'b': ch = '\b'; break; + case 't': ch = '\t'; break; + case 'n': ch = '\n'; break; + case 'f': ch = '\f'; break; + case 'r': ch = '\r'; break; + case '"': ch = '"'; break; + case '\\': ch = '\\'; break; + default: + snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); + xfree(dst); + return 0; + } + + dst[off++] = ch; + } + + // Cap with NUL and return it. + dst[off++] = 0; + return dst; +} + + +/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ +static char* normalize_key(context_t* ctx, token_t strtok) +{ + const char* sp = strtok.ptr; + const char* sq = strtok.ptr + strtok.len; + int lineno = strtok.lineno; + char* ret; + int ch = *sp; + char ebuf[80]; + + /* handle quoted string */ + if (ch == '\'' || ch == '\"') { + /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ + int multiline = 0; + if (sp[1] == ch && sp[2] == ch) { + sp += 3, sq -= 3; + multiline = 1; + } + else + sp++, sq--; + + if (ch == '\'') { + /* for single quote, take it verbatim. */ + if (! (ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + } else { + /* for double quote, we need to normalize */ + ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); + if (!ret) { + e_syntax(ctx, lineno, ebuf); + return 0; + } + } + + /* newlines are not allowed in keys */ + if (strchr(ret, '\n')) { + xfree(ret); + e_badkey(ctx, lineno); + return 0; + } + return ret; + } + + /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ + const char* xp; + for (xp = sp; xp != sq; xp++) { + int k = *xp; + if (isalnum(k)) continue; + if (k == '_' || k == '-') continue; + e_badkey(ctx, lineno); + return 0; + } + + /* dup and return it */ + if (! (ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + return ret; +} + + +/* + * Look up key in tab. Return 0 if not found, or + * 'v'alue, 'a'rray or 't'able depending on the element. + */ +static int check_key(toml_table_t* tab, const char* key, + toml_keyval_t** ret_val, + toml_array_t** ret_arr, + toml_table_t** ret_tab) +{ + int i; + void* dummy; + + if (!ret_tab) ret_tab = (toml_table_t**) &dummy; + if (!ret_arr) ret_arr = (toml_array_t**) &dummy; + if (!ret_val) ret_val = (toml_keyval_t**) &dummy; + + *ret_tab = 0; *ret_arr = 0; *ret_val = 0; + + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) { + *ret_val = tab->kval[i]; + return 'v'; + } + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) { + *ret_arr = tab->arr[i]; + return 'a'; + } + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) { + *ret_tab = tab->tab[i]; + return 't'; + } + } + return 0; +} + + +static int key_kind(toml_table_t* tab, const char* key) +{ + return check_key(tab, key, 0, 0, 0); +} + +/* Create a keyval in the table. + */ +static toml_keyval_t* create_keyval_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + if (!newkey) return 0; + + /* if key exists: error out. */ + toml_keyval_t* dest = 0; + if (key_kind(tab, newkey)) { + xfree(newkey); + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new entry */ + int n = tab->nkval; + toml_keyval_t** base; + if (0 == (base = (toml_keyval_t**) expand_ptrarr((void**)tab->kval, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->kval = base; + + if (0 == (base[n] = (toml_keyval_t*) CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->kval[tab->nkval++]; + + /* save the key in the new value struct */ + dest->key = newkey; + return dest; +} + + +/* Create a table in the table. + */ +static toml_table_t* create_keytable_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + if (!newkey) return 0; + + /* if key exists: error out */ + toml_table_t* dest = 0; + if (check_key(tab, newkey, 0, 0, &dest)) { + xfree(newkey); /* don't need this anymore */ + + /* special case: if table exists, but was created implicitly ... */ + if (dest && dest->implicit) { + /* we make it explicit now, and simply return it. */ + dest->implicit = false; + return dest; + } + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* create a new table entry */ + int n = tab->ntab; + toml_table_t** base; + if (0 == (base = (toml_table_t**) expand_ptrarr((void**)tab->tab, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->tab = base; + + if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->tab[tab->ntab++]; + + /* save the key in the new table struct */ + dest->key = newkey; + return dest; +} + + +/* Create an array in the table. + */ +static toml_array_t* create_keyarray_in_table(context_t* ctx, + toml_table_t* tab, + token_t keytok, + char kind) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + if (!newkey) return 0; + + /* if key exists: error out */ + if (key_kind(tab, newkey)) { + xfree(newkey); /* don't need this anymore */ + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new array entry */ + int n = tab->narr; + toml_array_t** base; + if (0 == (base = (toml_array_t**) expand_ptrarr((void**)tab->arr, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->arr = base; + + if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t* dest = tab->arr[tab->narr++]; + + /* save the key in the new array struct */ + dest->key = newkey; + dest->kind = kind; + return dest; +} + +/* Create an array in an array + */ +static toml_array_t* create_array_in_array(context_t* ctx, + toml_array_t* parent) +{ + const int n = parent->nelem; + toml_array_t** base; + if (0 == (base = (toml_array_t**) expand_ptrarr((void**)parent->u.arr, n))) { + e_outofmemory(ctx, FLINE); + return 0; + } + parent->u.arr = base; + parent->nelem++; + + if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { + e_outofmemory(ctx, FLINE); + return 0; + } + + return parent->u.arr[n]; +} + +/* Create a table in an array + */ +static toml_table_t* create_table_in_array(context_t* ctx, + toml_array_t* parent) +{ + int n = parent->nelem; + toml_table_t** base; + if (0 == (base = (toml_table_t**) expand_ptrarr((void**)parent->u.tab, n))) { + e_outofmemory(ctx, FLINE); + return 0; + } + parent->u.tab = base; + + if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { + e_outofmemory(ctx, FLINE); + return 0; + } + + return parent->u.tab[parent->nelem++]; +} + + +static int skip_newlines(context_t* ctx, int isdotspecial) +{ + while (ctx->tok.tok == NEWLINE) { + if (next_token(ctx, isdotspecial)) return -1; + if (ctx->tok.eof) break; + } + return 0; +} + + +static int parse_keyval(context_t* ctx, toml_table_t* tab); + +static inline int eat_token(context_t* ctx, tokentype_t typ, int isdotspecial, const char* fline) +{ + if (ctx->tok.tok != typ) + return e_internal(ctx, fline); + + if (next_token(ctx, isdotspecial)) + return -1; + + return 0; +} + + + +/* We are at '{ ... }'. + * Parse the table. + */ +static int parse_table(context_t* ctx, toml_table_t* tab) +{ + if (eat_token(ctx, LBRACE, 1, FLINE)) + return -1; + + for (;;) { + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); + + /* until } */ + if (ctx->tok.tok == RBRACE) + break; + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, ctx->tok.lineno, "expect a string"); + + if (parse_keyval(ctx, tab)) + return -1; + + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); + + /* on comma, continue to scan for next keyval */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 1, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACE, 1, FLINE)) + return -1; + return 0; +} + +static int valtype(const char* val) +{ + toml_timestamp_t ts; + if (*val == '\'' || *val == '"') return 's'; + if (0 == toml_rtob(val, 0)) return 'b'; + if (0 == toml_rtoi(val, 0)) return 'i'; + if (0 == toml_rtod(val, 0)) return 'd'; + if (0 == toml_rtots(val, &ts)) { + if (ts.year && ts.hour) return 'T'; /* timestamp */ + if (ts.year) return 'D'; /* date */ + return 't'; /* time */ + } + return 'u'; /* unknown */ +} + + +/* We are at '[...]' */ +static int parse_array(context_t* ctx, toml_array_t* arr) +{ + if (eat_token(ctx, LBRACKET, 0, FLINE)) return -1; + + for (;;) { + if (skip_newlines(ctx, 0)) return -1; + + /* until ] */ + if (ctx->tok.tok == RBRACKET) break; + + switch (ctx->tok.tok) { + case STRING: + { + char* val = ctx->tok.ptr; + int vlen = ctx->tok.len; + + /* set array kind if this will be the first entry */ + if (arr->kind == 0) arr->kind = 'v'; + /* check array kind */ + if (arr->kind != 'v') + return e_syntax(ctx, ctx->tok.lineno, "a string array can only contain strings"); + + /* make a new value in array */ + char** tmp = (char**) expand_ptrarr((void**)arr->u.val, arr->nelem); + if (!tmp) + return e_outofmemory(ctx, FLINE); + + arr->u.val = tmp; + if (! (val = STRNDUP(val, vlen))) + return e_outofmemory(ctx, FLINE); + + arr->u.val[arr->nelem++] = val; + + /* set array type if this is the first entry, or check that the types matched. */ + if (arr->nelem == 1) + arr->type = valtype(arr->u.val[0]); + else if (arr->type != valtype(val)) { + return e_syntax(ctx, ctx->tok.lineno, + "array type mismatch while processing array of values"); + } + + if (eat_token(ctx, STRING, 0, FLINE)) return -1; + break; + } + + case LBRACKET: + { /* [ [array], [array] ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) arr->kind = 'a'; + /* check array kind */ + if (arr->kind != 'a') { + return e_syntax(ctx, ctx->tok.lineno, + "array type mismatch while processing array of arrays"); + } + toml_array_t* subarr = create_array_in_array(ctx, arr); + if (!subarr) return -1; + if (parse_array(ctx, subarr)) return -1; + break; + } + + case LBRACE: + { /* [ {table}, {table} ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) arr->kind = 't'; + /* check array kind */ + if (arr->kind != 't') { + return e_syntax(ctx, ctx->tok.lineno, + "array type mismatch while processing array of tables"); + } + toml_table_t* subtab = create_table_in_array(ctx, arr); + if (!subtab) return -1; + if (parse_table(ctx, subtab)) return -1; + break; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + + if (skip_newlines(ctx, 0)) return -1; + + /* on comma, continue to scan for next element */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 0, FLINE)) return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; + return 0; +} + + +/* handle lines like these: + key = "value" + key = [ array ] + key = { table } +*/ +static int parse_keyval(context_t* ctx, toml_table_t* tab) +{ + token_t key = ctx->tok; + if (eat_token(ctx, STRING, 1, FLINE)) return -1; + + if (ctx->tok.tok == DOT) { + /* handle inline dotted key. + e.g. + physical.color = "orange" + physical.shape = "round" + */ + toml_table_t* subtab = 0; + { + char* subtabstr = normalize_key(ctx, key); + subtab = toml_table_in(tab, subtabstr); + xfree(subtabstr); + } + if (!subtab) { + subtab = create_keytable_in_table(ctx, tab, key); + if (!subtab) return -1; + } + if (next_token(ctx, 1)) return -1; + if (parse_keyval(ctx, subtab)) return -1; + return 0; + } + + if (ctx->tok.tok != EQUAL) { + return e_syntax(ctx, ctx->tok.lineno, "missing ="); + } + + if (next_token(ctx, 0)) return -1; + + switch (ctx->tok.tok) { + case STRING: + { /* key = "value" */ + toml_keyval_t* keyval = create_keyval_in_table(ctx, tab, key); + if (!keyval) return -1; + token_t val = ctx->tok; + + assert(keyval->val == 0); + if (! (keyval->val = STRNDUP(val.ptr, val.len))) + return e_outofmemory(ctx, FLINE); + + if (next_token(ctx, 1)) return -1; + + return 0; + } + + case LBRACKET: + { /* key = [ array ] */ + toml_array_t* arr = create_keyarray_in_table(ctx, tab, key, 0); + if (!arr) return -1; + if (parse_array(ctx, arr)) return -1; + return 0; + } + + case LBRACE: + { /* key = { table } */ + toml_table_t* nxttab = create_keytable_in_table(ctx, tab, key); + if (!nxttab) return -1; + if (parse_table(ctx, nxttab)) return -1; + return 0; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + return 0; +} + + +typedef struct tabpath_t tabpath_t; +struct tabpath_t { + int cnt; + token_t key[10]; +}; + +/* at [x.y.z] or [[x.y.z]] + * Scan forward and fill tabpath until it enters ] or ]] + * There will be at least one entry on return. + */ +static int fill_tabpath(context_t* ctx) +{ + int lineno = ctx->tok.lineno; + int i; + + /* clear tpath */ + for (i = 0; i < ctx->tpath.top; i++) { + char** p = &ctx->tpath.key[i]; + xfree(*p); + *p = 0; + } + ctx->tpath.top = 0; + + for (;;) { + if (ctx->tpath.top >= 10) + return e_syntax(ctx, lineno, "table path is too deep; max allowed is 10."); + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, lineno, "invalid or missing key"); + + char* key = normalize_key(ctx, ctx->tok); + if (!key) return -1; + ctx->tpath.tok[ctx->tpath.top] = ctx->tok; + ctx->tpath.key[ctx->tpath.top] = key; + ctx->tpath.top++; + + if (next_token(ctx, 1)) return -1; + + if (ctx->tok.tok == RBRACKET) break; + + if (ctx->tok.tok != DOT) + return e_syntax(ctx, lineno, "invalid key"); + + if (next_token(ctx, 1)) return -1; + } + + if (ctx->tpath.top <= 0) + return e_syntax(ctx, lineno, "empty table selector"); + + return 0; +} + + +/* Walk tabpath from the root, and create new tables on the way. + * Sets ctx->curtab to the final table. + */ +static int walk_tabpath(context_t* ctx) +{ + /* start from root */ + toml_table_t* curtab = ctx->root; + + for (int i = 0; i < ctx->tpath.top; i++) { + const char* key = ctx->tpath.key[i]; + + toml_keyval_t* nextval = 0; + toml_array_t* nextarr = 0; + toml_table_t* nexttab = 0; + switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { + case 't': + /* found a table. nexttab is where we will go next. */ + break; + + case 'a': + /* found an array. nexttab is the last table in the array. */ + if (nextarr->kind != 't') + return e_internal(ctx, FLINE); + + if (nextarr->nelem == 0) + return e_internal(ctx, FLINE); + + nexttab = nextarr->u.tab[nextarr->nelem-1]; + break; + + case 'v': + return e_keyexists(ctx, ctx->tpath.tok[i].lineno); + + default: + { /* Not found. Let's create an implicit table. */ + int n = curtab->ntab; + toml_table_t** base = (toml_table_t**) expand_ptrarr((void**)curtab->tab, n); + if (0 == base) + return e_outofmemory(ctx, FLINE); + + curtab->tab = base; + + if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) + return e_outofmemory(ctx, FLINE); + + if (0 == (base[n]->key = STRDUP(key))) + return e_outofmemory(ctx, FLINE); + + nexttab = curtab->tab[curtab->ntab++]; + + /* tabs created by walk_tabpath are considered implicit */ + nexttab->implicit = true; + } + break; + } + + /* switch to next tab */ + curtab = nexttab; + } + + /* save it */ + ctx->curtab = curtab; + + return 0; +} + + +/* handle lines like [x.y.z] or [[x.y.z]] */ +static int parse_select(context_t* ctx) +{ + assert(ctx->tok.tok == LBRACKET); + + /* true if [[ */ + int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); + /* need to detect '[[' on our own because next_token() will skip whitespace, + and '[ [' would be taken as '[[', which is wrong. */ + + /* eat [ or [[ */ + if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; + if (llb) { + assert(ctx->tok.tok == LBRACKET); + if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; + } + + if (fill_tabpath(ctx)) return -1; + + /* For [x.y.z] or [[x.y.z]], remove z from tpath. + */ + token_t z = ctx->tpath.tok[ctx->tpath.top-1]; + xfree(ctx->tpath.key[ctx->tpath.top-1]); + ctx->tpath.top--; + + /* set up ctx->curtab */ + if (walk_tabpath(ctx)) return -1; + + if (! llb) { + /* [x.y.z] -> create z = {} in x.y */ + toml_table_t* curtab = create_keytable_in_table(ctx, ctx->curtab, z); + if (!curtab) return -1; + ctx->curtab = curtab; + } else { + /* [[x.y.z]] -> create z = [] in x.y */ + toml_array_t* arr = 0; + { + char* zstr = normalize_key(ctx, z); + if (!zstr) return -1; + arr = toml_array_in(ctx->curtab, zstr); + xfree(zstr); + } + if (!arr) { + arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); + if (!arr) return -1; + } + if (arr->kind != 't') + return e_syntax(ctx, z.lineno, "array mismatch"); + + /* add to z[] */ + toml_table_t* dest; + { + int n = arr->nelem; + toml_table_t** base = (toml_table_t**) expand_ptrarr((void**)arr->u.tab, n); + if (0 == base) + return e_outofmemory(ctx, FLINE); + + arr->u.tab = base; + + if (0 == (base[n] = CALLOC(1, sizeof(*base[n])))) + return e_outofmemory(ctx, FLINE); + + if (0 == (base[n]->key = STRDUP("__anon__"))) + return e_outofmemory(ctx, FLINE); + + dest = arr->u.tab[arr->nelem++]; + } + + ctx->curtab = dest; + } + + if (ctx->tok.tok != RBRACKET) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]"); + } + if (llb) { + if (! (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); + } + if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + + if (ctx->tok.tok != NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); + + return 0; +} + + + + +toml_table_t* toml_parse(char* conf, + char* errbuf, + int errbufsz) +{ + context_t ctx; + + // clear errbuf + if (errbufsz <= 0) errbufsz = 0; + if (errbufsz > 0) errbuf[0] = 0; + + // init context + memset(&ctx, 0, sizeof(ctx)); + ctx.start = conf; + ctx.stop = ctx.start + strlen(conf); + ctx.errbuf = errbuf; + ctx.errbufsz = errbufsz; + + // start with an artificial newline of length 0 + ctx.tok.tok = NEWLINE; + ctx.tok.lineno = 1; + ctx.tok.ptr = conf; + ctx.tok.len = 0; + + // make a root table + if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { + e_outofmemory(&ctx, FLINE); + // Do not goto fail, root table not set up yet + return 0; + } + + // set root as default table + ctx.curtab = ctx.root; + + /* Scan forward until EOF */ + for (token_t tok = ctx.tok; ! tok.eof ; tok = ctx.tok) { + switch (tok.tok) { + + case NEWLINE: + if (next_token(&ctx, 1)) goto fail; + break; + + case STRING: + if (parse_keyval(&ctx, ctx.curtab)) goto fail; + + if (ctx.tok.tok != NEWLINE) { + e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); + goto fail; + } + + if (eat_token(&ctx, NEWLINE, 1, FLINE)) goto fail; + break; + + case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ + if (parse_select(&ctx)) goto fail; + break; + + default: + e_syntax(&ctx, tok.lineno, "syntax error"); + goto fail; + } + } + + /* success */ + for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); + return ctx.root; + +fail: + // Something bad has happened. Free resources and return error. + for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); + toml_free(ctx.root); + return 0; +} + + +toml_table_t* toml_parse_file(FILE* fp, + char* errbuf, + int errbufsz) +{ + int bufsz = 0; + char* buf = 0; + int off = 0; + + /* read from fp into buf */ + while (! feof(fp)) { + + if (off == bufsz) { + int xsz = bufsz + 1000; + char* x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + + errno = 0; + int n = fread(buf + off, 1, bufsz - off, fp); + if (ferror(fp)) { + snprintf(errbuf, errbufsz, "%s", + errno ? strerror(errno) : "Error reading file"); + xfree(buf); + return 0; + } + off += n; + } + + /* tag on a NUL to cap the string */ + if (off == bufsz) { + int xsz = bufsz + 1; + char* x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + buf[off] = 0; + + /* parse it, cleanup and finish */ + toml_table_t* ret = toml_parse(buf, errbuf, errbufsz); + xfree(buf); + return ret; +} + + +static void xfree_kval(toml_keyval_t* p) +{ + if (!p) return; + xfree(p->key); + xfree(p->val); + xfree(p); +} + +static void xfree_tab(toml_table_t* p); + +static void xfree_arr(toml_array_t* p) +{ + if (!p) return; + + xfree(p->key); + switch (p->kind) { + case 'v': + for (int i = 0; i < p->nelem; i++) xfree(p->u.val[i]); + xfree(p->u.val); + break; + + case 'a': + for (int i = 0; i < p->nelem; i++) xfree_arr(p->u.arr[i]); + xfree(p->u.arr); + break; + + case 't': + for (int i = 0; i < p->nelem; i++) xfree_tab(p->u.tab[i]); + xfree(p->u.tab); + break; + } + + xfree(p); +} + + +static void xfree_tab(toml_table_t* p) +{ + int i; + + if (!p) return; + + xfree(p->key); + + for (i = 0; i < p->nkval; i++) xfree_kval(p->kval[i]); + xfree(p->kval); + + for (i = 0; i < p->narr; i++) xfree_arr(p->arr[i]); + xfree(p->arr); + + for (i = 0; i < p->ntab; i++) xfree_tab(p->tab[i]); + xfree(p->tab); + + xfree(p); +} + + +void toml_free(toml_table_t* tab) +{ + xfree_tab(tab); +} + + +static void set_token(context_t* ctx, tokentype_t tok, int lineno, char* ptr, int len) +{ + token_t t; + t.tok = tok; + t.lineno = lineno; + t.ptr = ptr; + t.len = len; + t.eof = 0; + ctx->tok = t; +} + +static void set_eof(context_t* ctx, int lineno) +{ + set_token(ctx, NEWLINE, lineno, ctx->stop, 0); + ctx->tok.eof = 1; +} + + +/* Scan p for n digits compositing entirely of [0-9] */ +static int scan_digits(const char* p, int n) +{ + int ret = 0; + for ( ; n > 0 && isdigit(*p); n--, p++) { + ret = 10 * ret + (*p - '0'); + } + return n ? -1 : ret; +} + +static int scan_date(const char* p, int* YY, int* MM, int* DD) +{ + int year, month, day; + year = scan_digits(p, 4); + month = (year >= 0 && p[4] == '-') ? scan_digits(p+5, 2) : -1; + day = (month >= 0 && p[7] == '-') ? scan_digits(p+8, 2) : -1; + if (YY) *YY = year; + if (MM) *MM = month; + if (DD) *DD = day; + return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; +} + +static int scan_time(const char* p, int* hh, int* mm, int* ss) +{ + int hour, minute, second; + hour = scan_digits(p, 2); + minute = (hour >= 0 && p[2] == ':') ? scan_digits(p+3, 2) : -1; + second = (minute >= 0 && p[5] == ':') ? scan_digits(p+6, 2) : -1; + if (hh) *hh = hour; + if (mm) *mm = minute; + if (ss) *ss = second; + return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; +} + + +static int scan_string(context_t* ctx, char* p, int lineno, int dotisspecial) +{ + char* orig = p; + if (0 == strncmp(p, "'''", 3)) { + p = strstr(p + 3, "'''"); + if (0 == p) { + return e_syntax(ctx, lineno, "unterminated triple-s-quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 3 - orig); + return 0; + } + + if (0 == strncmp(p, "\"\"\"", 3)) { + int hexreq = 0; /* #hex required */ + int escape = 0; + int qcnt = 0; /* count quote */ + for (p += 3; *p && qcnt < 3; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) continue; + if (*p == 'u') { hexreq = 4; continue; } + if (*p == 'U') { hexreq = 8; continue; } + if (p[strspn(p, " \t\r")] == '\n') continue; /* allow for line ending backslash */ + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { escape = 1; continue; } + qcnt = (*p == '"') ? qcnt + 1 : 0; + } + if (qcnt != 3) { + return e_syntax(ctx, lineno, "unterminated triple-quote"); + } + + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; + } + + if ('\'' == *p) { + for (p++; *p && *p != '\n' && *p != '\''; p++); + if (*p != '\'') { + return e_syntax(ctx, lineno, "unterminated s-quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + if ('\"' == *p) { + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p++; *p; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) continue; + if (*p == 'u') { hexreq = 4; continue; } + if (*p == 'U') { hexreq = 8; continue; } + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { escape = 1; continue; } + if (*p == '\n') break; + if (*p == '"') break; + } + if (*p != '"') { + return e_syntax(ctx, lineno, "unterminated quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + /* check for timestamp without quotes */ + if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { + // forward thru the timestamp + for ( ; strchr("0123456789.:+-T Z", toupper(*p)); p++); + // squeeze out any spaces at end of string + for ( ; p[-1] == ' '; p--); + // tokenize + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; + } + + /* literals */ + for ( ; *p && *p != '\n'; p++) { + int ch = *p; + if (ch == '.' && dotisspecial) break; + if ('A' <= ch && ch <= 'Z') continue; + if ('a' <= ch && ch <= 'z') continue; + if (strchr("0123456789+-_.", ch)) continue; + break; + } + + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; +} + + +static int next_token(context_t* ctx, int dotisspecial) +{ + int lineno = ctx->tok.lineno; + char* p = ctx->tok.ptr; + int i; + + /* eat this tok */ + for (i = 0; i < ctx->tok.len; i++) { + if (*p++ == '\n') + lineno++; + } + + /* make next tok */ + while (p < ctx->stop) { + /* skip comment. stop just before the \n. */ + if (*p == '#') { + for (p++; p < ctx->stop && *p != '\n'; p++); + continue; + } + + if (dotisspecial && *p == '.') { + set_token(ctx, DOT, lineno, p, 1); + return 0; + } + + switch (*p) { + case ',': set_token(ctx, COMMA, lineno, p, 1); return 0; + case '=': set_token(ctx, EQUAL, lineno, p, 1); return 0; + case '{': set_token(ctx, LBRACE, lineno, p, 1); return 0; + case '}': set_token(ctx, RBRACE, lineno, p, 1); return 0; + case '[': set_token(ctx, LBRACKET, lineno, p, 1); return 0; + case ']': set_token(ctx, RBRACKET, lineno, p, 1); return 0; + case '\n': set_token(ctx, NEWLINE, lineno, p, 1); return 0; + case '\r': case ' ': case '\t': + /* ignore white spaces */ + p++; + continue; + } + + return scan_string(ctx, p, lineno, dotisspecial); + } + + set_eof(ctx, lineno); + return 0; +} + + +const char* toml_key_in(const toml_table_t* tab, int keyidx) +{ + if (keyidx < tab->nkval) return tab->kval[keyidx]->key; + + keyidx -= tab->nkval; + if (keyidx < tab->narr) return tab->arr[keyidx]->key; + + keyidx -= tab->narr; + if (keyidx < tab->ntab) return tab->tab[keyidx]->key; + + return 0; +} + +toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return tab->kval[i]->val; + } + return 0; +} + +toml_array_t* toml_array_in(const toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return tab->arr[i]; + } + return 0; +} + + +toml_table_t* toml_table_in(const toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return tab->tab[i]; + } + return 0; +} + +toml_raw_t toml_raw_at(const toml_array_t* arr, int idx) +{ + if (arr->kind != 'v') + return 0; + if (! (0 <= idx && idx < arr->nelem)) + return 0; + return arr->u.val[idx]; +} + +char toml_array_kind(const toml_array_t* arr) +{ + return arr->kind; +} + +char toml_array_type(const toml_array_t* arr) +{ + if (arr->kind != 'v') + return 0; + + if (arr->nelem == 0) + return 0; + + return arr->type; +} + + +int toml_array_nelem(const toml_array_t* arr) +{ + return arr->nelem; +} + +const char* toml_array_key(const toml_array_t* arr) +{ + return arr ? arr->key : (const char*) NULL; +} + +int toml_table_nkval(const toml_table_t* tab) +{ + return tab->nkval; +} + +int toml_table_narr(const toml_table_t* tab) +{ + return tab->narr; +} + +int toml_table_ntab(const toml_table_t* tab) +{ + return tab->ntab; +} + +const char* toml_table_key(const toml_table_t* tab) +{ + return tab ? tab->key : (const char*) NULL; +} + +toml_array_t* toml_array_at(const toml_array_t* arr, int idx) +{ + if (arr->kind != 'a') + return 0; + if (! (0 <= idx && idx < arr->nelem)) + return 0; + return arr->u.arr[idx]; +} + +toml_table_t* toml_table_at(const toml_array_t* arr, int idx) +{ + if (arr->kind != 't') + return 0; + if (! (0 <= idx && idx < arr->nelem)) + return 0; + return arr->u.tab[idx]; +} + + +int toml_rtots(toml_raw_t src_, toml_timestamp_t* ret) +{ + if (! src_) return -1; + + const char* p = src_; + int must_parse_time = 0; + + memset(ret, 0, sizeof(*ret)); + + int* year = &ret->__buffer.year; + int* month = &ret->__buffer.month; + int* day = &ret->__buffer.day; + int* hour = &ret->__buffer.hour; + int* minute = &ret->__buffer.minute; + int* second = &ret->__buffer.second; + int* millisec = &ret->__buffer.millisec; + + /* parse date YYYY-MM-DD */ + if (0 == scan_date(p, year, month, day)) { + ret->year = year; + ret->month = month; + ret->day = day; + + p += 10; + if (*p) { + // parse the T or space separator + if (*p != 'T' && *p != ' ') return -1; + must_parse_time = 1; + p++; + } + } + + /* parse time HH:MM:SS */ + if (0 == scan_time(p, hour, minute, second)) { + ret->hour = hour; + ret->minute = minute; + ret->second = second; + + /* optionally, parse millisec */ + p += 8; + if (*p == '.') { + char* qq; + p++; + errno = 0; + *millisec = strtol(p, &qq, 0); + if (errno) { + return -1; + } + while (*millisec > 999) { + *millisec /= 10; + } + + ret->millisec = millisec; + p = qq; + } + + if (*p) { + /* parse and copy Z */ + char* z = ret->__buffer.z; + ret->z = z; + if (*p == 'Z' || *p == 'z') { + *z++ = 'Z'; p++; + *z = 0; + + } else if (*p == '+' || *p == '-') { + *z++ = *p++; + + if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; + *z++ = *p++; + *z++ = *p++; + + if (*p == ':') { + *z++ = *p++; + + if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; + *z++ = *p++; + *z++ = *p++; + } + + *z = 0; + } + } + } + if (*p != 0) + return -1; + + if (must_parse_time && !ret->hour) + return -1; + + return 0; +} + + +/* Raw to boolean */ +int toml_rtob(toml_raw_t src, int* ret_) +{ + if (!src) return -1; + int dummy; + int* ret = ret_ ? ret_ : &dummy; + + if (0 == strcmp(src, "true")) { + *ret = 1; + return 0; + } + if (0 == strcmp(src, "false")) { + *ret = 0; + return 0; + } + return -1; +} + + +/* Raw to integer */ +int toml_rtoi(toml_raw_t src, int64_t* ret_) +{ + if (!src) return -1; + + char buf[100]; + char* p = buf; + char* q = p + sizeof(buf); + const char* s = src; + int base = 0; + int64_t dummy; + int64_t* ret = ret_ ? ret_ : &dummy; + + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_100 */ + if (s[0] == '_') + return -1; + + /* if 0 ... */ + if ('0' == s[0]) { + switch (s[1]) { + case 'x': base = 16; s += 2; break; + case 'o': base = 8; s += 2; break; + case 'b': base = 2; s += 2; break; + case '\0': return *ret = 0, 0; + default: + /* ensure no other digits after it */ + if (s[1]) return -1; + } + } + + /* just strip underscores and pass to strtoll */ + while (*s && p < q) { + int ch = *s++; + switch (ch) { + case '_': + // disallow '__' + if (s[0] == '_') return -1; + continue; /* skip _ */ + default: + break; + } + *p++ = ch; + } + if (*s || p == q) return -1; + + /* last char cannot be '_' */ + if (s[-1] == '_') return -1; + + /* cap with NUL */ + *p = 0; + + /* Run strtoll on buf to get the integer */ + char* endp; + errno = 0; + *ret = strtoll(buf, &endp, base); + return (errno || *endp) ? -1 : 0; +} + + +int toml_rtod_ex(toml_raw_t src, double* ret_, char* buf, int buflen) +{ + if (!src) return -1; + + char* p = buf; + char* q = p + buflen; + const char* s = src; + double dummy; + double* ret = ret_ ? ret_ : &dummy; + + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_1.00 */ + if (s[0] == '_') + return -1; + + /* disallow +.99 */ + if (s[0] == '.') + return -1; + + /* zero must be followed by . or 'e', or NUL */ + if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) + return -1; + + /* just strip underscores and pass to strtod */ + while (*s && p < q) { + int ch = *s++; + switch (ch) { + case '.': + if (s[-2] == '_') return -1; + if (s[0] == '_') return -1; + break; + case '_': + // disallow '__' + if (s[0] == '_') return -1; + continue; /* skip _ */ + default: + break; + } + *p++ = ch; + } + if (*s || p == q) return -1; /* reached end of string or buffer is full? */ + + /* last char cannot be '_' */ + if (s[-1] == '_') return -1; + + if (p != buf && p[-1] == '.') + return -1; /* no trailing zero */ + + /* cap with NUL */ + *p = 0; + + /* Run strtod on buf to get the value */ + char* endp; + errno = 0; + *ret = strtod(buf, &endp); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod(toml_raw_t src, double* ret_) +{ + char buf[100]; + return toml_rtod_ex(src, ret_, buf, sizeof(buf)); +} + + + + +int toml_rtos(toml_raw_t src, char** ret) +{ + int multiline = 0; + const char* sp; + const char* sq; + + *ret = 0; + if (!src) return -1; + + int qchar = src[0]; + int srclen = strlen(src); + if (! (qchar == '\'' || qchar == '"')) { + return -1; + } + + // triple quotes? + if (qchar == src[1] && qchar == src[2]) { + multiline = 1; + sp = src + 3; + sq = src + srclen - 3; + /* last 3 chars in src must be qchar */ + if (! (sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) + return -1; + + /* skip new line immediate after qchar */ + if (sp[0] == '\n') + sp++; + else if (sp[0] == '\r' && sp[1] == '\n') + sp += 2; + + } else { + sp = src + 1; + sq = src + srclen - 1; + /* last char in src must be qchar */ + if (! (sp <= sq && *sq == qchar)) + return -1; + } + + if (qchar == '\'') { + *ret = norm_lit_str(sp, sq - sp, + multiline, + 0, 0); + } else { + *ret = norm_basic_str(sp, sq - sp, + multiline, + 0, 0); + } + + return *ret ? 0 : -1; +} + + +toml_datum_t toml_string_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_at(arr, idx); + if (raw) { + ret.ok = (0 == toml_rtos(raw, &ret.u.s)); + } + return ret; +} + +toml_datum_t toml_bool_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_at(arr, idx); + if (raw) { + ret.ok = (0 == toml_rtob(raw, &ret.u.b)); + } + return ret; +} + +toml_datum_t toml_int_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_at(arr, idx); + if (raw) { + ret.ok = (0 == toml_rtoi(raw, &ret.u.i)); + } + return ret; +} + +toml_datum_t toml_double_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_at(arr, idx); + if (raw) { + ret.ok = (0 == toml_rtod(raw, &ret.u.d)); + } + return ret; +} + +toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx) +{ + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_at(arr, idx); + if (raw) { + ret.ok = (0 == toml_rtots(raw, &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + } + } + } + return ret; +} + +toml_datum_t toml_string_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtos(raw, &ret.u.s)); + } + return ret; +} + +toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtob(raw, &ret.u.b)); + } + return ret; +} + +toml_datum_t toml_int_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtoi(raw, &ret.u.i)); + } + return ret; +} + +toml_datum_t toml_double_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtod(raw, &ret.u.d)); + } + return ret; +} + +toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key) +{ + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtots(raw, &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + } + } + } + return ret; +} diff --git a/vendor/toml.h b/vendor/toml.h new file mode 100644 index 0000000..19f6f64 --- /dev/null +++ b/vendor/toml.h @@ -0,0 +1,175 @@ +/* + MIT License + + Copyright (c) 2017 - 2019 CK Tan + https://github.com/cktan/tomlc99 + + 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 TOML_H +#define TOML_H + + +#include +#include + + +#ifdef __cplusplus +#define TOML_EXTERN extern "C" +#else +#define TOML_EXTERN extern +#endif + +typedef struct toml_timestamp_t toml_timestamp_t; +typedef struct toml_table_t toml_table_t; +typedef struct toml_array_t toml_array_t; +typedef struct toml_datum_t toml_datum_t; + +/* Parse a file. Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, + char* errbuf, + int errbufsz); + +/* Parse a string containing the full config. + * Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */ + char* errbuf, + int errbufsz); + +/* Free the table returned by toml_parse() or toml_parse_file(). Once + * this function is called, any handles accessed through this tab + * directly or indirectly are no longer valid. + */ +TOML_EXTERN void toml_free(toml_table_t* tab); + + +/* Timestamp types. The year, month, day, hour, minute, second, z + * fields may be NULL if they are not relevant. e.g. In a DATE + * type, the hour, minute, second and z fields will be NULLs. + */ +struct toml_timestamp_t { + struct { /* internal. do not use. */ + int year, month, day; + int hour, minute, second, millisec; + char z[10]; + } __buffer; + int *year, *month, *day; + int *hour, *minute, *second, *millisec; + char* z; +}; + + +/*----------------------------------------------------------------- + * Enhanced access methods + */ +struct toml_datum_t { + int ok; + union { + toml_timestamp_t* ts; /* ts must be freed after use */ + char* s; /* string value. s must be freed after use */ + int b; /* bool value */ + int64_t i; /* int value */ + double d; /* double value */ + } u; +}; + +/* on arrays: */ +/* ... retrieve size of array. */ +TOML_EXTERN int toml_array_nelem(const toml_array_t* arr); +/* ... retrieve values using index. */ +TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx); +/* ... retrieve array or table using index. */ +TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx); + +/* on tables: */ +/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ +TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx); +/* ... retrieve values using key. */ +TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key); +/* .. retrieve array or table using key. */ +TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab, + const char* key); +TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab, + const char* key); + +/*----------------------------------------------------------------- + * lesser used + */ +/* Return the array kind: 't'able, 'a'rray, 'v'alue */ +TOML_EXTERN char toml_array_kind(const toml_array_t* arr); + +/* For array kind 'v'alue, return the type of values + i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp + 0 if unknown +*/ +TOML_EXTERN char toml_array_type(const toml_array_t* arr); + +/* Return the key of an array */ +TOML_EXTERN const char* toml_array_key(const toml_array_t* arr); + +/* Return the number of key-values in a table */ +TOML_EXTERN int toml_table_nkval(const toml_table_t* tab); + +/* Return the number of arrays in a table */ +TOML_EXTERN int toml_table_narr(const toml_table_t* tab); + +/* Return the number of sub-tables in a table */ +TOML_EXTERN int toml_table_ntab(const toml_table_t* tab); + +/* Return the key of a table*/ +TOML_EXTERN const char* toml_table_key(const toml_table_t* tab); + +/*-------------------------------------------------------------- + * misc + */ +TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret); +TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); +TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t), + void (*xxfree)(void*)); + + +/*-------------------------------------------------------------- + * deprecated + */ +/* A raw value, must be processed by toml_rto* before using. */ +typedef const char* toml_raw_t; +TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key); +TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx); +TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret); +TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret); +TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret); +TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret); +TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen); +TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret); + + +#endif /* TOML_H */