From 544100da719843438372c6e30739bf9b5d3ebb9c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yaroslav=20de=20la=20Pe=C3=B1a=20Smirnov?=
 <yps@yaroslavps.com>
Date: Wed, 18 Aug 2021 04:54:45 +0300
Subject: Init

Basically functional.
---
 src/parcini.c       | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/tests/parcini.c | 138 ++++++++++++++++++++++++++++++
 2 files changed, 377 insertions(+)
 create mode 100644 src/parcini.c
 create mode 100644 src/tests/parcini.c

(limited to 'src')

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);
+}
-- 
cgit v1.2.3