diff --git a/Makefile b/Makefile
index ef62a7b..412f538 100644
--- a/Makefile
+++ b/Makefile
@@ -3,8 +3,8 @@ all: build
build: libkv kv
-LIBKV_SRC = buffer.c kv.c kv_path.c
-LIBKV_O = buffer.o kv.o kv_path.o
+LIBKV_SRC = buffer.c kv_chars.c kv_parse.c kv_path.c kv_quote.c
+LIBKV_O = buffer.o kv_chars.o kv_parse.o kv_path.o kv_quote.o
KV_SRC = kv_cli.c
KV_O = kv_cli.o libkv.a
diff --git a/README.md b/README.md
index a40ef28..6ff260f 100644
--- a/README.md
+++ b/README.md
@@ -18,13 +18,13 @@
M : M2 | M1 | M3
-K1 = V1 : [^{}:"\s]+
+K1 = V1 : [^<{}:,"\s][^{}:,"\s]*
K2 = V2 : '"' ([^"] | Q)+ '"'
Q : '\"' | '\\' | '\n' | '\r' | '\t' | '\v'
-K3 = V3 : '"""' .*? '"""'
+K3 = V3 : '"""\n' .*? '\n"""'
K4 = V4 : '<<' D \s .*? D
diff --git a/kv.c b/kv.c
deleted file mode 100644
index 36dd26f..0000000
--- a/kv.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * kv - key value text file format
- *
- * Copyright 2022 Thomas de Grivel
-*/
-
-#include <assert.h>
-#include <stdlib.h>
-#include "kv.h"
-
-int kv_is_space (int c)
-{
- switch (c) {
- case ' ':
- case '\n':
- case '\r':
- case '\t':
- case '\v':
- return 1;
- default:
- return 0;
- }
- return 0;
-}
-
-int kv_is_quotable_char (int c)
-{
- switch (c) {
- case '"':
- case '\\':
- case '\n':
- case '\r':
- case '\t':
- case '\v':
- return 1;
- default:
- return 0;
- }
- return 0;
-}
-
-int kv_is_reserved_char (int c)
-{
- if (kv_is_quotable_char(c))
- return 1;
- switch (c) {
- case '{':
- case '}':
- case ':':
- case ',':
- return 1;
- default:
- return 0;
- }
- return 0;
-}
-
-void kv_sp (s_buffer *b)
-{
- int c = buffer_peek(b);
- while (kv_is_space(c)) {
- buffer_read(b);
- c = buffer_peek(b);
- }
-}
-
-int kv_k (s_buffer *b, char **k, size_t *ksz)
-{
- (void) b;
- (void) k;
- (void) ksz;
- return 0;
-}
-
-int kv_v (s_buffer *b, char **v, size_t *vsz, f_kv *f)
-{
- (void) b;
- (void) v;
- (void) vsz;
- (void) f;
- return 0;
-}
-
-int kv_m1 (s_buffer *b, f_kv *f)
-{
- char *k = NULL;
- size_t ksz = 0;
- size_t pos;
- int r;
- char *v = NULL;
- size_t vsz = 0;
- assert(b);
- pos = b->pos;
- kv_sp(b);
- r = kv_k(b, &k, &ksz);
- if (r < 0)
- return r;
- if (r) {
- kv_sp(b);
- r = buffer_eat(b, ":");
- if (r < 0)
- goto error;
- kv_sp(b);
- r = kv_v(b, &v, &vsz, f);
- if (r < 0)
- goto error;
- if (r) {
- if (f) {
- b->k = kv_path_new(k, ksz, b->k);
- if (!b->k)
- exit(1);
- r = f(b, v, vsz);
- if (r < 0) {
- buffer_err(b, "event callback returned %d", r);
- r = -5;
- goto error_kv;
- }
- }
- kv_sp(b);
- if (buffer_peek(b) == ',') {
- buffer_read(b);
- kv_sp(b);
- }
- free(k);
- k = NULL;
- free(v);
- v = NULL;
- return 1;
- }
- free(k);
- k = NULL;
- }
- b->pos = pos;
- return 0;
-error_kv:
- free(v);
-error:
- free(k);
- return r;
-}
-
-int kv_m2 (s_buffer *b, f_kv *f)
-{
- size_t pos;
- int r;
- assert(b);
- pos = b->pos;
- kv_sp(b);
- if (buffer_peek(b) == '{') {
- buffer_read(b);
- kv_m1(b, f);
- r = buffer_eat(b, "}");
- if (r < 0)
- return r;
- return 1;
- }
- b->pos = pos;
- return 0;
-}
-
-int kv_parse (s_buffer *b, f_kv *f)
-{
- int c;
- int r;
- assert(b);
- kv_sp(b);
- c = buffer_peek(b);
- if (c < 0) {
- if (c == -1)
- return 0;
- return c;
- }
- r = kv_m2(b, f);
- if (r < 0)
- return r;
- if (r) {
- c = buffer_read(b);
- if (c == -1)
- return 0;
- buffer_err(b, "expected EOF");
- return -2;
- }
- r = kv_m1(b, f);
- if (r < 0)
- return r;
- if (r) {
- c = buffer_read(b);
- if (c == -1)
- return 0;
- buffer_err(b, "expected EOF");
- return -2;
- }
- return -2;
-}
diff --git a/kv.h b/kv.h
index db1b829..8adac98 100644
--- a/kv.h
+++ b/kv.h
@@ -9,10 +9,23 @@
#include "buffer.h"
-/* access key path through b->k */
+/* parse
+ * access key path through b->k */
typedef int f_kv (s_buffer *b, const char *v, size_t vsz);
int kv_parse (s_buffer *b, f_kv *f);
+/* chars */
+int kv_is_quotable_char (int c);
+int kv_is_reserved_char (int c);
+int kv_is_space (int c);
+
+/* quote */
+int kv_contains_triple_double_quotes (const char *str, size_t str_size);
+int kv_quote_triple_double_quotes (char **str, size_t *str_size);
+int kv_contains_reserved_char (const char *str, size_t str_size);
+int kv_needs_quoting (const char *str, size_t str_size);
+int kv_quote (char **str, size_t *str_size);
+
#endif
diff --git a/kv_chars.c b/kv_chars.c
new file mode 100644
index 0000000..d86efb0
--- /dev/null
+++ b/kv_chars.c
@@ -0,0 +1,50 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#include "kv.h"
+
+int kv_is_quotable_char (int c)
+{
+ switch (c) {
+ case '"':
+ case '\\':
+ return 1;
+ default:
+ return kv_is_space(c);
+ }
+ return 0;
+}
+
+int kv_is_reserved_char (int c)
+{
+ switch (c) {
+ case 0:
+ case '"':
+ case ',':
+ case ':':
+ case '{':
+ case '}':
+ return 1;
+ default:
+ return kv_is_space(c);
+ }
+ return 0;
+}
+
+int kv_is_space (int c)
+{
+ switch (c) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case '\t':
+ case '\v':
+ return 1;
+ default:
+ return 0;
+ }
+ return 0;
+}
diff --git a/kv_parse.c b/kv_parse.c
new file mode 100644
index 0000000..3ce2671
--- /dev/null
+++ b/kv_parse.c
@@ -0,0 +1,147 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include "kv.h"
+
+void kv_parse_sp (s_buffer *b)
+{
+ int c = buffer_peek(b);
+ while (kv_is_space(c)) {
+ buffer_read(b);
+ c = buffer_peek(b);
+ }
+}
+
+int kv_parse_k (s_buffer *b, char **k, size_t *ksz)
+{
+ (void) b;
+ (void) k;
+ (void) ksz;
+ return 0;
+}
+
+int kv_parse_v (s_buffer *b, char **v, size_t *vsz, f_kv *f)
+{
+ (void) b;
+ (void) v;
+ (void) vsz;
+ (void) f;
+ return 0;
+}
+
+int kv_parse_m1 (s_buffer *b, f_kv *f)
+{
+ char *k = NULL;
+ size_t ksz = 0;
+ size_t pos;
+ int r;
+ char *v = NULL;
+ size_t vsz = 0;
+ assert(b);
+ pos = b->pos;
+ kv_parse_sp(b);
+ r = kv_parse_k(b, &k, &ksz);
+ if (r < 0)
+ return r;
+ if (r) {
+ kv_parse_sp(b);
+ r = buffer_eat(b, ":");
+ if (r < 0)
+ goto error;
+ kv_parse_sp(b);
+ r = kv_parse_v(b, &v, &vsz, f);
+ if (r < 0)
+ goto error;
+ if (r) {
+ if (f) {
+ b->k = kv_path_new(k, ksz, b->k);
+ if (!b->k)
+ exit(1);
+ r = f(b, v, vsz);
+ if (r < 0) {
+ buffer_err(b, "event callback returned %d", r);
+ r = -5;
+ goto error_kv;
+ }
+ }
+ kv_parse_sp(b);
+ if (buffer_peek(b) == ',') {
+ buffer_read(b);
+ kv_parse_sp(b);
+ }
+ free(k);
+ k = NULL;
+ free(v);
+ v = NULL;
+ return 1;
+ }
+ free(k);
+ k = NULL;
+ }
+ b->pos = pos;
+ return 0;
+error_kv:
+ free(v);
+error:
+ free(k);
+ return r;
+}
+
+int kv_parse_m2 (s_buffer *b, f_kv *f)
+{
+ size_t pos;
+ int r;
+ assert(b);
+ pos = b->pos;
+ kv_parse_sp(b);
+ if (buffer_peek(b) == '{') {
+ buffer_read(b);
+ kv_parse_m1(b, f);
+ r = buffer_eat(b, "}");
+ if (r < 0)
+ return r;
+ return 1;
+ }
+ b->pos = pos;
+ return 0;
+}
+
+int kv_parse (s_buffer *b, f_kv *f)
+{
+ int c;
+ int r;
+ assert(b);
+ kv_parse_sp(b);
+ c = buffer_peek(b);
+ if (c < 0) {
+ if (c == -1)
+ return 0;
+ return c;
+ }
+ r = kv_parse_m2(b, f);
+ if (r < 0)
+ return r;
+ if (r) {
+ c = buffer_read(b);
+ if (c == -1)
+ return 0;
+ buffer_err(b, "expected EOF");
+ return -2;
+ }
+ r = kv_parse_m1(b, f);
+ if (r < 0)
+ return r;
+ if (r) {
+ c = buffer_read(b);
+ if (c == -1)
+ return 0;
+ buffer_err(b, "expected EOF");
+ return -2;
+ }
+ return -2;
+}
diff --git a/kv_quote.c b/kv_quote.c
new file mode 100644
index 0000000..02fda65
--- /dev/null
+++ b/kv_quote.c
@@ -0,0 +1,72 @@
+/*
+ * kv - key value text file format
+ *
+ * Copyright 2022 Thomas de Grivel
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "kv.h"
+
+int kv_contains_triple_double_quotes (const char *str, size_t str_size)
+{
+ while (str_size >= 4) {
+ if (str[0] == '\n' && str[1] == '"' && str[2] == '"' && str[3] == '"')
+ return 1;
+ str++;
+ str_size--;
+ }
+ return 0;
+}
+
+int kv_quote_triple_double_quotes (char **str, size_t *str_size)
+{
+ char *e;
+ char *s;
+ if (kv_contains_triple_double_quotes(*str, *str_size))
+ return 1;
+ s = calloc(*str_size + 9, 1);
+ if (!s)
+ return -1;
+ s[0] = '"'; s[1] = '"'; s[2] = '"'; s[3] = '\n';
+ memcpy(s + 4, *str, *str_size);
+ e = s + 4 + *str_size;
+ e[0] = '\n'; e[1] = '"'; e[2] = '"'; e[3] = '"'; e[4] = 0;
+ *str = e;
+ *str_size = *str_size + 8;
+ return 0;
+}
+
+int kv_contains_reserved_char (const char *str, size_t str_size)
+{
+ while (str_size) {
+ if (kv_is_reserved_char(str[0]))
+ return 1;
+ str++;
+ str_size--;
+ }
+ return 0;
+}
+
+int kv_needs_quoting (const char *str, size_t str_size)
+{
+ if (str_size == 0 ||
+ (str[0] != '<' && !kv_contains_reserved_char(str, str_size)))
+ return 0;
+ return 1;
+}
+
+int kv_quote (char **str, size_t *str_size)
+{
+ int r;
+ assert(str);
+ assert(*str);
+ assert(str_size);
+ if (!kv_needs_quoting(*str, *str_size))
+ return 0;
+ r = kv_quote_triple_double_quotes(str, str_size);
+ if (r <= 0)
+ return r;
+ return -1;
+}