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