diff options
author | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2021-08-18 04:54:45 +0300 |
---|---|---|
committer | Yaroslav de la Peña Smirnov <yps@yaroslavps.com> | 2021-08-18 04:54:45 +0300 |
commit | 544100da719843438372c6e30739bf9b5d3ebb9c (patch) | |
tree | 00894be08c33d97dd0873052d01609f5a78c2a54 /src | |
download | parcini-544100da719843438372c6e30739bf9b5d3ebb9c.tar.gz parcini-544100da719843438372c6e30739bf9b5d3ebb9c.zip |
Init
Basically functional.
Diffstat (limited to 'src')
-rw-r--r-- | src/parcini.c | 239 | ||||
-rw-r--r-- | src/tests/parcini.c | 138 |
2 files changed, 377 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); +} diff --git a/src/tests/parcini.c b/src/tests/parcini.c new file mode 100644 index 0000000..f53ab09 --- /dev/null +++ b/src/tests/parcini.c @@ -0,0 +1,138 @@ +#include "tests/tests.h" +#include "parcini.h" + +#include <string.h> + +#define PARCINI_TEST_FILE "test.ini" + +void +test_parcini_parse_file(void) +{ + parcini_t *parser = parcini_from_file(PARCINI_TEST_FILE); + struct parcini_line line; + enum parcini_result res; + + /* line 1 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 1); + asserteq(strcmp(line.section, ""), 0); + asserteq(strcmp(line.key, "key1"), 0); + asserteq(line.value.type, PARCINI_VALUE_STRING); + asserteq(strcmp(line.value.value.string, "string"), 0); + + /* line 2 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 2); + asserteq(strcmp(line.section, ""), 0); + asserteq(strcmp(line.key, "key2"), 0); + asserteq(line.value.type, PARCINI_VALUE_INTEGER); + asserteq(line.value.value.integer, -1520); + + /* line 3 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 3); + asserteq(strcmp(line.section, ""), 0); + asserteq(strcmp(line.key, "key3"), 0); + asserteq(line.value.type, PARCINI_VALUE_BOOLEAN); + asserteq(line.value.value.boolean, false); + + /* line 4 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_EMPTY_LINE); + asserteq(line.lineno, 4); + + /* line 5 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_EMPTY_LINE); + asserteq(line.lineno, 5); + + /* line 6 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_SECTION); + asserteq(line.lineno, 6); + asserteq(strcmp(line.section, "asection"), 0); + asserteq(line.key, NULL); + asserteq(line.value.type, PARCINI_VALUE_NONE); + + /* line 7 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 7); + asserteq(strcmp(line.section, "asection"), 0); + asserteq(strcmp(line.key, "skey"), 0); + asserteq(line.value.type, PARCINI_VALUE_STRING); + asserteq(strcmp(line.value.value.string, "ur 2 slow"), 0); + + /* line 8 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_VALUE_PARSE_ERROR); + asserteq(line.lineno, 8); + + /* line 9 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEY_PARSE_ERROR); + asserteq(line.lineno, 9); + + /* line 10 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_VALUE_PARSE_ERROR); + asserteq(line.lineno, 10); + + /* line 11 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_VALUE_RANGE_ERROR); + asserteq(line.lineno, 11); + + /* line 12 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 12); + asserteq(strcmp(line.section, "asection"), 0); + asserteq(strcmp(line.key, "bool2"), 0); + asserteq(line.value.type, PARCINI_VALUE_BOOLEAN); + asserteq(line.value.value.boolean, true); + + /* line 13 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 13); + asserteq(strcmp(line.section, "asection"), 0); + asserteq(strcmp(line.key, "bool3"), 0); + asserteq(line.value.type, PARCINI_VALUE_BOOLEAN); + asserteq(line.value.value.boolean, false); + + /* line 14 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_KEYVALUE); + asserteq(line.lineno, 14); + asserteq(strcmp(line.section, "asection"), 0); + asserteq(strcmp(line.key, "bool3"), 0); + asserteq(line.value.type, PARCINI_VALUE_BOOLEAN); + asserteq(line.value.value.boolean, true); + + /* line 15 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_EMPTY_LINE); + asserteq(line.lineno, 15); + + /* line 16 */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_SECTION_PARSE_ERROR); + asserteq(line.lineno, 16); + + /* EOF */ + res = parcini_parse_next_line(parser, &line); + asserteq(res, PARCINI_EOF); + + parcini_destroy(parser); +} + +int +main(void) +{ + INIT_TESTS(); + RUN_TEST(test_parcini_parse_file); +} |