commit 00e37840bc8dbfd912e89d252696bd65296621ae
Author: Samdal <samdal@protonmail.com>
Date: Sun, 13 Nov 2022 22:57:55 +0100
initial commit
Diffstat:
A | .gitignore | | | 2 | ++ |
A | README.md | | | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | nisse.h | | | 408 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | test.c | | | 113 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | test.nisse | | | 29 | +++++++++++++++++++++++++++++ |
A | test2.nisse | | | 29 | +++++++++++++++++++++++++++++ |
6 files changed, 651 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,2 @@
+*.exe
+*.out
diff --git a/README.md b/README.md
@@ -0,0 +1,70 @@
+# nisse, the not intricate serialised s expressions
+
+```
+nisse __
+ .-' |
+ / <\|
+ / \'
+ |_.- o-o
+ / C -._)\
+ /', |
+| `-,_,__,'
+(,,)====[_]=|
+ '. ____/
+ | -|-|_
+ |____)_)
+```
+
+
+### Typesystem:
+- Entries starting with any of `1234567890-.` are interpreted as numbers
+ - And if the number contains a `.` it is considered a float
+- Entries starting with \` are a multiline raw string \`like this\`
+ - They can contain raw newlines, does not support escaped characters.
+- Entries starting with `(` are arrays, and they are ended with a `)`
+- Other entries are interpreted as whitespace delimited words
+
+
+A common behaviour is to have a string as the first element of an array, where that string is the tag or variable name.
+
+### API
+
+The C library exposes the AST directly, to write to a file you must generate
+one yourself. There are macros and functions to make this step easier
+
+A parsed file returns a generated AST. There are functions that help you
+verify formats and for getting tagged arrays/variables.
+
+**View nisse.h for information about the functions.**
+
+### example .nisse file
+```lisp
+(player
+ (hp 2.000000)
+ (items
+ (sword (type weapon) (damage 10))
+ (knife (damage 4))
+ (bow (type weapon) (damage 10) (range 15))
+ (crossbow
+ (type `ranged weapon`)
+ (damage 16)
+ (range 25)
+ (properties poison fire)
+ )
+ (sling (damage 4) (range 3))
+ )
+)
+(items
+ (test)
+ ()
+ (())
+ (sword (damage 10))
+)
+(`v3 array`
+ (1.000000 2.000000 3.000000)
+ (4.000000 5.000000 6.000000)
+ (7.000000 8.000000 9.000000)
+ (10.000000 0.000010 420.690002)
+)
+
+```
diff --git a/nisse.h b/nisse.h
@@ -0,0 +1,408 @@
+#ifndef NISSE_H_
+#define NISSE_H_
+
+/*
+** nisse, the not intricate serialised s expressions.
+**
+** do #define NISSE_NO_ERROR to disable errors on stdout
+**
+** NOTE: does not support escaped characters in strings!
+** However strings will include whatever binary junk you put into them except `
+** Basically you only have "raw" strings
+*/
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+
+enum nisse_types {
+ NISSE_TYPE_NONE,
+ NISSE_TYPE_INT, // 10
+ NISSE_TYPE_FLOAT, // 1.000000
+ NISSE_TYPE_STRING, // string or `string with whitespace`
+ NISSE_TYPE_ARRAY, // (....) or (name ......) for 'tagged' variables
+};
+
+typedef struct nisse_data_entry_s {
+ enum nisse_types type;
+ union {
+ int i;
+ float f;
+ char* str;
+ struct nisse_data_entry_s* nde;
+ };
+
+ int is_str_allocated;
+
+ //////////////////
+ // NISSE_TYPE_ARRAY stuff
+ int nde_len;
+ int is_nde_allocated;
+ int len_is_fixed;
+
+ // for pretty serialisation
+ int new_line_at_start;
+ int new_line_at_end;
+ int new_line_at_end_of_subsequent_elements;
+} nde_t;
+
+// anonymous nde macros
+#define nisse_andei(__i) {.type = NISSE_TYPE_INT, .i = __i}
+#define nisse_andef(__f) {.type = NISSE_TYPE_FLOAT, .f = __f}
+#define nisse_andes(__s) {.type = NISSE_TYPE_STRING, .str = __s}
+#define nisse_andea(__count, ...) {.type = NISSE_TYPE_ARRAY, .nde_len = __count, .nde = (nde_t[]){__VA_ARGS__}}
+#define nisse_andeanl(__count, ...) {.new_line_at_end_of_subsequent_elements = 1, .type = NISSE_TYPE_ARRAY, .nde_len = __count, .nde = (nde_t[]){__VA_ARGS__}}
+
+// tagged nde macros
+#define nisse_tndei(__name, __i) {.type = NISSE_TYPE_ARRAY, .nde_len = 2, .nde = (nde_t[]){nisse_andes(__name), nisse_andei(__i)}}
+#define nisse_tndef(__name, __f) {.type = NISSE_TYPE_ARRAY, .nde_len = 2, .nde = (nde_t[]){nisse_andes(__name), nisse_andef(__f)}}
+#define nisse_tndes(__name, __s) {.type = NISSE_TYPE_ARRAY, .nde_len = 2, .nde = (nde_t[]){nisse_andes(__name), nisse_andes(__s)}}
+#define nisse_tndea(__name, __count, ...) nisse_andea(__count + 1, nisse_andes(__name), __VA_ARGS__)
+#define nisse_tndeanl(__name, __count, ...) nisse_andeanl(__count + 1, nisse_andes(__name), __VA_ARGS__)
+
+extern int nisse_nde_fits_format(nde_t* nde, nde_t fmt);
+// shallow comapre, does not support anonymous string arrays
+// if nde is array:
+// > will check if named structs are present in fmt
+// > will check if anonymous variables have the right type for their given index in nde
+// > (direct index in nde, not like in c literals)
+// > if it is anonymous and fmt has tagged array at that index with a single variable,
+// > it will check those instead
+// else:
+// > check if type is the same
+
+extern nde_t* nisse_nde_get_tagged(const nde_t* nde, const char* tag);
+extern nde_t* nisse_nde_get_index(const nde_t* nde, int index); // has bounds checking
+
+extern int nisse_write_to_file(char* filename, const nde_t nde);
+
+extern nde_t nisse_parse_file(char* filename);
+extern nde_t nisse_parse_memory(char* mem, int sz);
+
+extern void nisse_free_nde(nde_t* nde);
+void nisse_dup_nde(nde_t* nde);
+
+
+
+
+
+
+
+#ifdef NISSE_IMPL
+
+
+#ifndef NISSE_NO_ERROR
+static void
+nisse_eprintf(const char* fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+}
+#else // NISSE_NO_ERROR
+#define nisse_eprintf(...)
+#endif // NISSE_NO_ERROR
+
+int
+nisse_nde_fits_format(nde_t* nde, nde_t fmt)
+{
+ if (!nde) return 0;
+
+ if (nde->type == NISSE_TYPE_ARRAY) {
+ for (int i = nde->nde_len && nde->nde->type == NISSE_TYPE_STRING; i < nde->nde_len; i++) {
+ if (nde->nde[i].type == NISSE_TYPE_ARRAY && nde->nde[i].nde_len && nde->nde[i].nde->type == NISSE_TYPE_STRING) {
+ // tagged array
+ nde_t* res = nisse_nde_get_tagged(&fmt, nde->nde[i].nde->str);
+ if (!res) return 0;
+ } else {
+ // untagged
+ nde_t* res = nisse_nde_get_index(&fmt, i);
+ if (!res) return 0;
+ if (nde->nde[i].type != NISSE_TYPE_ARRAY && res->type == NISSE_TYPE_ARRAY && res->nde_len == 2) {
+ if (res->nde[1].type != nde->nde[i].type)
+ return 0;
+ } else if (nde->nde[i].type != res->type) {
+ return 0;
+ }
+ }
+ }
+ return 1;
+ }
+ return nde->type == fmt.type;
+}
+
+nde_t*
+nisse_nde_get_tagged(const nde_t* nde, const char* tag)
+{
+ if (!nde || nde->type != NISSE_TYPE_ARRAY) return NULL;
+
+ for (int i = 0; i < nde->nde_len; i++)
+ if (nde->nde[i].type == NISSE_TYPE_ARRAY && nde->nde[i].nde_len && nde->nde[i].nde->type == NISSE_TYPE_STRING)
+ if (strcmp(tag, nde->nde[i].nde->str) == 0)
+ return nde->nde + i;
+ return NULL;
+}
+
+nde_t*
+nisse_nde_get_index(const nde_t* nde, int index)
+{
+ if (!nde || nde->type != NISSE_TYPE_ARRAY || index < 0 || index >= nde->nde_len)
+ return NULL;
+ return nde->nde + index;
+}
+
+static
+void nisse_write_nde(FILE* fd, const nde_t* nde, int root, int indents, int new_line_start, int first)
+{
+ if (!nde) {
+ nisse_eprintf("NISSE ERROR: nde was NULL\n");
+ return;
+ }
+
+ if ((new_line_start && (!first || root)) || nde->new_line_at_start) {
+ fprintf(fd, "\n");
+ for (int i = 0; i < indents; i++)
+ fprintf(fd, " ");
+ } else if (!root && !first) {
+ fprintf(fd, " ");
+ }
+
+ if (nde->type == NISSE_TYPE_ARRAY) {
+ fprintf(fd, "(");
+ } else if (root) {
+ nisse_eprintf("NISSE ERROR: a root nde was not an array\n");
+ return;
+ }
+
+ if (nde->type == NISSE_TYPE_NONE) {
+ nisse_eprintf("NISSE ERROR: skipping writing an uninitialized member\n");
+ } else if (nde->type == NISSE_TYPE_INT) {
+ fprintf(fd, "%d", nde->i);
+ } else if (nde->type == NISSE_TYPE_FLOAT) {
+ fprintf(fd, "%f", nde->f);
+ } else if (nde->type == NISSE_TYPE_STRING) {
+ int res = 0;
+ for (int i = 0; i < strlen(nde->str); i++) {
+ if (memchr("\n '\t\v()\r", nde->str[i], sizeof("\n '\t\v()\r"))) {
+ res = 1;
+ break;
+ }
+ }
+ if (strchr(nde->str, '`')) {
+ nisse_eprintf("NISSE ERROR: string '%s' contains a `, nisse does not support this\n", nde->str);
+ } else {
+ if (res) fprintf(fd, "`%s`", nde->str);
+ else fprintf(fd, "%s", nde->str);
+ }
+ } else if (nde->type == NISSE_TYPE_ARRAY) {
+ for (int i = 0; i < nde->nde_len; i++)
+ nisse_write_nde(fd, nde->nde + i, 0, indents + 1, nde->new_line_at_end_of_subsequent_elements, i == 0);
+ }
+
+ if (nde->new_line_at_end_of_subsequent_elements) {
+ fprintf(fd, "\n");
+ for (int i = 0; i < indents; i++)
+ fprintf(fd, " ");
+ }
+ if (nde->type == NISSE_TYPE_ARRAY)
+ fprintf(fd, ")");
+}
+
+int
+nisse_write_to_file(char* filename, const nde_t nde)
+{
+ if (nde.type != NISSE_TYPE_ARRAY) return 0;
+
+ FILE* fd = fopen(filename, "wb");
+ if (!fd) {
+ nisse_eprintf("NISSE ERROR: unable to open file\n");
+ return 0;
+ }
+
+ for (int i = 0; i < nde.nde_len; i++)
+ nisse_write_nde(fd, nde.nde + i, 1, 0, i > 0, 1);
+
+ fclose(fd);
+ return 1;
+}
+
+nde_t
+nisse_parse_file(char* filename)
+{
+ FILE* file = fopen(filename, "rb");
+ if (!file) {
+ nisse_eprintf("NISSE ERROR: unable to open file\n");
+ return (nde_t){0};
+ }
+
+ fseek(file, 0L, SEEK_END);
+ int readsize = ftell(file);
+ rewind(file);
+
+ char* buffer = malloc(readsize);
+ if (!buffer) return (nde_t){0};
+
+ fread(buffer, 1, readsize, file);
+ fclose(file);
+
+ return nisse_parse_memory(buffer, readsize);
+}
+
+static int
+nisse_seek_whitespace(char* mem, int index, int sz)
+{
+ while (index < sz && !memchr("\n '\t)\v\r", mem[index], sizeof("\n '\t)\v\r")))
+ index++;
+ return index;
+}
+
+static int
+nisse_parse_memory_array(char* mem, int sz, nde_t* nde) {
+ int index = 0;
+ if (index >= sz) return index;
+ if (mem[index] == '(') index++;
+ if (index >= sz) return index;
+
+ nde->type = NISSE_TYPE_ARRAY;
+ nde->is_nde_allocated = 1;
+ int new_line_on_all_elements = 1;
+
+ while (index < sz) {
+ int last_index = index;
+
+ while (index < sz && memchr("\n '\t\v\r", mem[index], sizeof("\n '\t\v\r")))
+ index++;
+
+ if (mem[index] == ')') {
+ if (nde->nde_len <= 1) new_line_on_all_elements = 0;
+ break;
+ }
+
+ int i = ++nde->nde_len - 1;
+ nde->nde = realloc(nde->nde, sizeof(*nde->nde) * nde->nde_len);
+ nde->nde[i] = (nde_t){0};
+
+ if (index != last_index && memchr(mem + last_index, '\n', index - last_index))
+ nde->nde[i].new_line_at_start = 1;
+
+ if (memchr("1234567890.-", mem[index], sizeof("1234567890.-"))) {
+ int number_end = nisse_seek_whitespace(mem, index, sz);
+
+ char tmp = mem[number_end];
+ mem[number_end] = 0;
+ if (memchr(mem + index, '.', number_end - index)) /* float */ {
+ nde->nde[i].f = atof(mem + index);
+ nde->nde[i].type = NISSE_TYPE_FLOAT;
+ } else /* int */ {
+ nde->nde[i].i = atoi(mem + index);
+ nde->nde[i].type = NISSE_TYPE_INT;
+ }
+ mem[number_end] = tmp;
+ index = number_end;
+ } else if (mem[index] == '(') /* array */ {
+ index += nisse_parse_memory_array(mem + index, sz - index, nde->nde + i);
+ } else if (mem[index] == '`') /* string */ {
+ char* end;
+ int times = 0;
+ do {
+ times++;
+ end = (sz - index - 1 < 0) ? NULL : memchr(mem + index + times, '`', sz - index - times);
+ } while (end && end[-1] == '\\');
+ if (!end) {
+ nisse_eprintf("NISSE ERROR: unable to find closing quote\n");
+ return sz;
+ }
+ size_t strsz = (end) - (mem + index + 1);
+
+ nde->nde[i].str = malloc(strsz + 1);
+ nde->nde[i].is_str_allocated = 1;
+
+ memcpy(nde->nde[i].str, mem + index + 1, strsz);
+ nde->nde[i].str[strsz] = 0;
+
+ index += strsz + 2;
+ nde->nde[i].type = NISSE_TYPE_STRING;
+ } else /* string without " " */ {
+ int end = nisse_seek_whitespace(mem, index, sz);
+
+ size_t strsz = (end - index);
+
+ nde->nde[i].str = malloc(strsz + 1);
+ nde->nde[i].is_str_allocated = 1;
+
+ memcpy(nde->nde[i].str, mem + index, strsz);
+ nde->nde[i].str[strsz] = 0;
+
+ index += strsz;
+ nde->nde[i].type = NISSE_TYPE_STRING;
+ }
+
+ if (mem[index] == '\n' && nde->nde[i].type == NISSE_TYPE_ARRAY && !nde->nde[i].new_line_at_start)
+ nde->nde[i].new_line_at_end = 1;
+
+ if ((!nde->nde[i].new_line_at_start || nde->nde[i].new_line_at_end) && i != 0)
+ new_line_on_all_elements = 0;
+ }
+ nde->new_line_at_end_of_subsequent_elements = new_line_on_all_elements;
+
+ if (index >= sz) {
+ nisse_eprintf("NISSE ERROR: unable to find closing parenthesis\n");
+ return sz;
+ }
+ return index + 1;
+}
+
+nde_t
+nisse_parse_memory(char* mem, int sz)
+{
+ int index = 0;
+ nde_t nde = {.type = NISSE_TYPE_ARRAY};
+ nde.nde_len = 0;
+ while (index < sz) {
+ char* new_pos = memchr(mem + index, '(', sz - index);
+ if (!new_pos) return nde;
+
+ index += (int)(new_pos - (mem + index));
+
+ nde.nde_len += 1;
+ nde.nde = realloc(nde.nde, sizeof(*nde.nde) * nde.nde_len);
+ nde.nde[nde.nde_len - 1] = (nde_t){0};
+
+ index += nisse_parse_memory_array(mem + index, sz - index, nde.nde + (nde.nde_len - 1));
+ }
+ if (nde.nde_len) nde.is_nde_allocated = 1;
+ return nde;
+}
+
+void
+nisse_free_nde(nde_t* nde)
+{
+
+ if (nde->type == NISSE_TYPE_STRING && nde->is_str_allocated) {
+ free(nde->str);
+ } else if (nde->type == NISSE_TYPE_ARRAY) {
+ for (int i = 0; i < nde->nde_len; i++) nisse_free_nde(nde->nde + i);
+ if (nde->is_nde_allocated) free(nde->nde);
+ }
+}
+
+void
+nisse_dup_nde(nde_t* nde)
+{
+ if (nde->type == NISSE_TYPE_STRING) {
+ nde->str = strdup(nde->str);
+ } else if (nde->type == NISSE_TYPE_ARRAY) {
+ for (int i = 0; i < nde->nde_len; i++) nisse_free_nde(nde->nde + i);
+ nde_t* n = malloc(nde->nde_len * sizeof(*n));
+ memcpy(n, nde->nde, nde->nde_len * sizeof(*n));
+ }
+}
+
+#endif // NISSE_IMPL
+
+#endif // NISSE_H_
diff --git a/test.c b/test.c
@@ -0,0 +1,113 @@
+#define NISSE_IMPL
+#include "nisse.h"
+
+/*
+** You generate a following structure as you need for you application.
+** This structure contains enough information for nisse to serialise
+*/
+
+nde_t fmt_v3 = nisse_andea(3, nisse_andef(0), nisse_andef(0), nisse_andef(0));
+
+nde_t ndes[] = {
+ {
+ .type = NISSE_TYPE_ARRAY,
+ .new_line_at_end_of_subsequent_elements = 1,
+ .nde = (nde_t[]){
+ nisse_andes("player"),
+ nisse_andei(-100),
+ nisse_tndef("hp", 2.0f),
+ {
+ .type = NISSE_TYPE_ARRAY,
+ .new_line_at_end_of_subsequent_elements = 1,
+ .new_line_at_start = 1,
+ .nde = (nde_t[]){
+ nisse_andes("items"),
+ nisse_tndea("sword", 2,
+ nisse_tndes("type", "weapon"),
+ nisse_tndei("damage", 10)
+ ),
+ nisse_tndea("knife", 1, nisse_tndei("damage", 4)),
+ nisse_tndea("bow", 3,
+ nisse_tndes("type", "weapon"),
+ nisse_tndei("damage", 10),
+ nisse_tndei("range", 15)
+ ),
+ nisse_tndeanl("crossbow", 4,
+ nisse_tndes("type", "ranged weapon"),
+ nisse_tndei("damage", 16),
+ nisse_tndei("range", 25),
+ nisse_tndea("properties", 2,
+ nisse_andes("poison"),
+ nisse_andes("fire")
+ ),
+ ),
+ nisse_tndea("sling", 2,
+ nisse_tndei("damage", 4),
+ nisse_tndei("range", 3)
+ ),
+ },
+ .nde_len = 6,
+ }
+ },
+ .nde_len = 4,
+ },
+ {
+ .type = NISSE_TYPE_ARRAY,
+ .new_line_at_end_of_subsequent_elements = 1,
+ .nde = (nde_t[]){
+ nisse_andes("items"),
+ nisse_andea(1, nisse_andes("test")),
+ nisse_andea(0, ),
+ nisse_andea(1, nisse_andea(0, )),
+ nisse_tndea("sword", 1, nisse_tndei("damage", 10)),
+ nisse_tndea("knife", 1, nisse_tndei("damage", 4)),
+ nisse_tndea("bow", 2,
+ nisse_tndei("damage", 10),
+ nisse_tndei("range", 15)
+ ),
+ },
+ .nde_len = 5,
+ },
+ nisse_tndeanl("v3 array", 4,
+ nisse_andea(3,
+ nisse_andef(1.0f),
+ nisse_andef(2.0f),
+ nisse_andef(3.0f),
+ ),
+ nisse_andea(3,
+ nisse_andef(4.0f),
+ nisse_andef(5.0f),
+ nisse_andef(6.0f),
+ ),
+ nisse_andea(3,
+ nisse_andef(7.0f),
+ nisse_andef(8.0f),
+ nisse_andef(9.0f),
+ ),
+ nisse_andea(3,
+ nisse_andef(10.0f),
+ nisse_andef(0.00001f),
+ nisse_andef(420.69f),
+ ),
+ ),
+};
+
+#define ARR_SZ(__ARR) sizeof(__ARR) / sizeof(__ARR[0])
+
+int main()
+{
+ nde_t test = {.type = NISSE_TYPE_ARRAY, .nde_len = ARR_SZ(ndes), .nde = ndes};
+ nisse_write_to_file("./test.nisse", test);
+ nde_t nde = nisse_parse_file("./test.nisse");
+
+ printf("does test fit? %s.\n", nisse_nde_fits_format(nisse_nde_get_index(&nde, 0), *nisse_nde_get_index(&test, 0)) ? "yes" : "no");
+
+ nde_t* v3a = nisse_nde_get_tagged(&nde, "v3 array");
+ assert(v3a);
+ for (int i = 1; i < v3a->nde_len; i++)
+ if (!nisse_nde_fits_format(v3a->nde+ i, fmt_v3))
+ printf("infalid v3 array %d!\n", i);
+
+ nisse_write_to_file("./test2.nisse", nde);
+ nisse_free_nde(&nde);
+}
diff --git a/test.nisse b/test.nisse
@@ -0,0 +1,28 @@
+(player
+ -100
+ (hp 2.000000)
+ (items
+ (sword (type weapon) (damage 10))
+ (knife (damage 4))
+ (bow (type weapon) (damage 10) (range 15))
+ (crossbow
+ (type ´ranged weapon´)
+ (damage 16)
+ (range 25)
+ (properties poison fire)
+ )
+ (sling (damage 4) (range 3))
+ )
+)
+(items
+ (test)
+ ()
+ (())
+ (sword (damage 10))
+)
+(´v3 array´
+ (1.000000 2.000000 3.000000)
+ (4.000000 5.000000 6.000000)
+ (7.000000 8.000000 9.000000)
+ (10.000000 0.000010 420.690002)
+)
+\ No newline at end of file
diff --git a/test2.nisse b/test2.nisse
@@ -0,0 +1,28 @@
+(player
+ -100
+ (hp 2.000000)
+ (items
+ (sword (type weapon) (damage 10))
+ (knife (damage 4))
+ (bow (type weapon) (damage 10) (range 15))
+ (crossbow
+ (type `ranged weapon`)
+ (damage 16)
+ (range 25)
+ (properties poison fire)
+ )
+ (sling (damage 4) (range 3))
+ )
+)
+(items
+ (test)
+ ()
+ (())
+ (sword (damage 10))
+)
+(`v3 array`
+ (1.000000 2.000000 3.000000)
+ (4.000000 5.000000 6.000000)
+ (7.000000 8.000000 9.000000)
+ (10.000000 0.000010 420.690002)
+)
+\ No newline at end of file