/* * 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 #include #include #include #include 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; if (*dstn < srcn + 1) { *dstn = srcn + 1; char *newptr = realloc(*dst, *dstn); if (newptr == NULL) { return NULL; } *dst = newptr; } for (size_t i = 0; i < srcn; i++) { (*dst)[i] = start[i]; } (*dst)[srcn] = '\0'; return *dst; } bool parcini_value_handle(const struct parcini_value *value, const enum parcini_value_type expected, void *dst) { if (value->type != expected) { return false; } if(expected == PARCINI_VALUE_STRING) { char **string = (char **)dst; *string = strdup(value->value.string); return true; } if(expected == PARCINI_VALUE_INTEGER) { long int *integer = (long int *)dst; *integer = value->value.integer; return true; } if(expected == PARCINI_VALUE_BOOLEAN) { bool *boolean = (bool *)dst; *boolean = value->value.boolean; return true; } return false; } 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, &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 (start == delim) { return PARCINI_KEY_PARSE_ERROR; } 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); 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 = 1; } 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); }