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); +} | 
