aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/parcini.c239
-rw-r--r--src/tests/parcini.c138
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);
+}