nisse

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 00e37840bc8dbfd912e89d252696bd65296621ae
Author: Samdal <samdal@protonmail.com>
Date:   Sun, 13 Nov 2022 22:57:55 +0100

initial commit

Diffstat:
A.gitignore | 2++
AREADME.md | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anisse.h | 408+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atest.nisse | 29+++++++++++++++++++++++++++++
Atest2.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