summaryrefslogtreecommitdiffstats
path: root/src/csv.c
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-26 12:25:48 -0300
committerMurilo Ijanc <[email protected]>2026-07-26 12:25:48 -0300
commite2c6ae13af335b311da2429c903032124f926df2 (patch)
tree73776fab7f3e52124a217e2768968fc9a8a76c34 /src/csv.c
downloadc-e2c6ae13af335b311da2429c903032124f926df2.tar.gz
Initial importv0.1.0
Diffstat (limited to 'src/csv.c')
-rw-r--r--src/csv.c168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/csv.c b/src/csv.c
new file mode 100644
index 0000000..ea9859b
--- /dev/null
+++ b/src/csv.c
@@ -0,0 +1,168 @@
+#include "csv.h"
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct csv_reader {
+ const char *p;
+ const char *end;
+};
+
+typedef struct {
+ char *data;
+ size_t len;
+ size_t cap;
+} csv_buf_t;
+
+csv_reader_t *csv_reader_new(const char *data, size_t len)
+{
+ csv_reader_t *reader;
+
+ if (data == NULL)
+ return NULL;
+ reader = calloc(1, sizeof(*reader));
+ if (reader == NULL)
+ return NULL;
+ reader->p = data;
+ reader->end = data + len;
+ if (len >= 3 && memcmp(data, "\xEF\xBB\xBF", 3) == 0)
+ reader->p += 3;
+ return reader;
+}
+
+void csv_reader_free(csv_reader_t *reader)
+{
+ free(reader);
+}
+
+void csv_row_free(csv_row_t *row)
+{
+ size_t i;
+
+ if (row == NULL)
+ return;
+ for (i = 0; i < row->count; i++)
+ free(row->fields[i]);
+ free(row->fields);
+ row->fields = NULL;
+ row->count = 0;
+}
+
+static bool buf_push(csv_buf_t *buf, char c)
+{
+ if (buf->len + 2 > buf->cap) {
+ size_t cap = buf->cap ? buf->cap * 2 : 32;
+ char *grown = realloc(buf->data, cap);
+ if (grown == NULL)
+ return false;
+ buf->data = grown;
+ buf->cap = cap;
+ }
+ buf->data[buf->len++] = c;
+ buf->data[buf->len] = '\0';
+ return true;
+}
+
+static bool row_push(csv_row_t *row, size_t *cap, char *field)
+{
+ if (row->count + 1 > *cap) {
+ size_t next = *cap ? *cap * 2 : 16;
+ char **grown = realloc(row->fields, next * sizeof(*grown));
+ if (grown == NULL)
+ return false;
+ row->fields = grown;
+ *cap = next;
+ }
+ row->fields[row->count++] = field;
+ return true;
+}
+
+static char *parse_field(csv_reader_t *reader)
+{
+ csv_buf_t field = {NULL, 0, 0};
+ bool quoted = false;
+
+ if (!buf_push(&field, '\0'))
+ return NULL;
+ field.len = 0;
+ field.data[0] = '\0';
+
+ if (reader->p < reader->end && *reader->p == '"') {
+ quoted = true;
+ reader->p++;
+ }
+
+ while (reader->p < reader->end) {
+ char c = *reader->p;
+
+ if (quoted) {
+ reader->p++;
+ if (c != '"') {
+ if (!buf_push(&field, c))
+ goto fail;
+ continue;
+ }
+ if (reader->p < reader->end && *reader->p == '"') {
+ reader->p++;
+ if (!buf_push(&field, '"'))
+ goto fail;
+ continue;
+ }
+ quoted = false;
+ continue;
+ }
+ if (c == ',' || c == '\n' || c == '\r')
+ break;
+ reader->p++;
+ if (!buf_push(&field, c))
+ goto fail;
+ }
+
+ if (quoted)
+ goto fail;
+ return field.data;
+
+fail:
+ free(field.data);
+ return NULL;
+}
+
+int csv_reader_next(csv_reader_t *reader, csv_row_t *out)
+{
+ csv_row_t row = {NULL, 0};
+ size_t cap = 0;
+
+ if (reader == NULL || out == NULL)
+ return -1;
+ if (reader->p >= reader->end)
+ return 0;
+
+ for (;;) {
+ char *field = parse_field(reader);
+
+ if (field == NULL) {
+ csv_row_free(&row);
+ return -1;
+ }
+ if (!row_push(&row, &cap, field)) {
+ free(field);
+ csv_row_free(&row);
+ return -1;
+ }
+ if (reader->p >= reader->end)
+ break;
+ if (*reader->p == ',') {
+ reader->p++;
+ continue;
+ }
+ if (*reader->p == '\r')
+ reader->p++;
+ if (reader->p < reader->end && *reader->p == '\n')
+ reader->p++;
+ break;
+ }
+
+ *out = row;
+ return 1;
+}