diff options
Diffstat (limited to 'src/parcini.c')
-rw-r--r-- | src/parcini.c | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/src/parcini.c b/src/parcini.c new file mode 100644 index 0000000..6c19d7a --- /dev/null +++ b/src/parcini.c @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2021 Yaroslav de la Peña Smirnov + * + * Released under the GNU Lesser General Public License, version 2.1 only. + * For more information see `LICENSE' or visit + * https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html + */ +#define _POSIX_C_SOURCE 200809L +#include "parcini.h" + +#include <ctype.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +struct parcini { + FILE *stream; + char *last_section; + size_t last_section_n; + char *last_line; + size_t last_line_n; + size_t last_line_no; +}; + +/* + * Strip string "slice" of any whitespace characters to the right until the next + * non-whitespace character. + */ +static void +rstrip(const char *start, char **end) { + while (start < *end && isspace(**end)) { + **end = '\0'; + *end -= 1; + } +} + +/* + * Ignore whitespace characters on the left, moving the pointer until we find + * a non-whitespace character. + */ +static void +lskip(char **start) { + while (**start && isspace(**start)) { + *start += 1; + } +} + +/* + * Make a copy of the string "slice" located from pointer start to pointer end, + * copying it over to dst, reallocating memory if needed, and terminating it + * with a null character. + */ +static char * +slicecpy(char *start, char *end, char **dst, size_t *dstn) +{ + size_t srcn = end - start + 1; + if (*dst == NULL) { + *dst = malloc(srcn + 1); + } + if (*dstn < srcn) { + char *newptr = realloc(*dst, srcn + 1); + if (newptr == NULL) { + return NULL; + } + *dstn = srcn + 1; + *dst = newptr; + } + for (size_t i = 0; i < srcn; i++) { + (*dst)[i] = start[i]; + } + (*dst)[srcn] = '\0'; + + return *dst; +} + +enum parcini_result +parcini_parse_next_line(parcini_t *parser, struct parcini_line *parsed) +{ + ssize_t nread = getline(&parser->last_line, &parser->last_line_n, + parser->stream); + + if (nread < 0) { + if (feof(parser->stream)) { + return PARCINI_EOF; + } + return PARCINI_STREAM_ERROR; + } + + parsed->lineno = ++parser->last_line_no; + if (nread > 0) { + char *start = parser->last_line; + if (*start == PARCINI_COMMENT_CHAR) { + goto empty; + } + + if (*start == '[') { + char *end = strchr(start, ']'); + if (!end) { + return PARCINI_SECTION_PARSE_ERROR; + } + char *cmnt = strchr(start, PARCINI_COMMENT_CHAR); + if (cmnt && cmnt < end) { + return PARCINI_SECTION_PARSE_ERROR; + } + if (!slicecpy(start + 1, end - 1, &parser->last_section, + &parser->last_section_n)) { + return PARCINI_MEMORY_ERROR; + } + parsed->section = parser->last_section; + parsed->key = NULL; + parsed->value.type = PARCINI_VALUE_NONE; + return PARCINI_SECTION; + } + + parsed->section = parser->last_section; + char *delim = strchr(start, '='); + if (delim) { + char *cmnt = strchr(start, PARCINI_COMMENT_CHAR); + if (cmnt && cmnt < delim) { + return PARCINI_KEY_PARSE_ERROR; + } + char *end = delim; + *end = '\0'; + end -= 1; + rstrip(start, &end); + if (start == end) { + return PARCINI_KEY_PARSE_ERROR; + } + parsed->key = start; + + start = delim + 1; + lskip(&start); + end = parser->last_line + nread - 1; + if (*start == '"') { + delim = strrchr(start, '"'); + if (!delim) { + return PARCINI_VALUE_PARSE_ERROR; + } + if (delim != end) { + char *postq = delim + 1; + lskip(&postq); + if (*postq && *postq != PARCINI_COMMENT_CHAR) { + return PARCINI_VALUE_PARSE_ERROR; + } + } + *delim = '\0'; + parsed->value.type = PARCINI_VALUE_STRING; + parsed->value.value.string = start + 1; + return PARCINI_KEYVALUE; + } + + if (cmnt) { + if (start == cmnt) { + return PARCINI_VALUE_PARSE_ERROR; + } + *cmnt = '\0'; + end = cmnt - 1; + } + rstrip(start, &end); + + if (!strcmp(start, "yes") || !strcmp(start, "true")) { + parsed->value.type = PARCINI_VALUE_BOOLEAN; + parsed->value.value.boolean = true; + return PARCINI_KEYVALUE; + } + if (!strcmp(start, "no") || !strcmp(start, "false")) { + parsed->value.type = PARCINI_VALUE_BOOLEAN; + parsed->value.value.boolean = false; + return PARCINI_KEYVALUE; + } + + char *invalid = NULL; + errno = ETIME; + long int no = strtol(start, &invalid, 0); + if ((*invalid && *invalid != '\n' && *invalid != '\r') + || errno == EINVAL) { + return PARCINI_VALUE_PARSE_ERROR; + } + if (errno == ERANGE) { + return PARCINI_VALUE_RANGE_ERROR; + } + parsed->value.type = PARCINI_VALUE_INTEGER; + parsed->value.value.integer = no; + return PARCINI_KEYVALUE; + } + + lskip(&start); + if (*start) { + return PARCINI_KEY_PARSE_ERROR; + } + } + +empty: + return PARCINI_EMPTY_LINE; +} + +parcini_t * +parcini_init(FILE *stream) +{ + parcini_t *parser = calloc(1, sizeof *parser); + if (parser != NULL) { + parser->stream = stream; + parser->last_section = strdup(""); + parser->last_section_n = 2; + } + + return parser; +} + +parcini_t * +parcini_from_file(const char *fpath) +{ + FILE *f = NULL; + f = fopen(fpath, "r"); + if (f == NULL) { + return NULL; + } + return parcini_init(f); +} + +parcini_t * +parcini_from_string(const char *str, const size_t len) +{ + FILE *stream = fmemopen((void *)str, len, "r"); + if (stream == NULL) { + return NULL; + } + return parcini_init(stream); +} + +void +parcini_destroy(parcini_t *parser) +{ + fclose(parser->stream); + free(parser->last_line); + free(parser->last_section); + free(parser); +} |