options/config
[demo_prior] / libs / treestore / src / text.c
diff --git a/libs/treestore/src/text.c b/libs/treestore/src/text.c
new file mode 100644 (file)
index 0000000..b743312
--- /dev/null
@@ -0,0 +1,465 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include "treestore.h"
+#include "dynarr.h"
+
+struct parser {
+       struct ts_io *io;
+       int nline;
+       char *token;
+       int nextc;
+};
+
+enum { TOK_SYM, TOK_ID, TOK_NUM, TOK_STR };
+
+static struct ts_node *read_node(struct parser *pstate);
+static int read_array(struct parser *pstate, struct ts_value *tsv, char endsym);
+static int next_token(struct parser *pstate);
+
+static int print_attr(struct ts_attr *attr, struct ts_io *io, int level);
+static char *value_to_str(struct ts_value *value);
+static int tree_level(struct ts_node *n);
+static const char *indent(int x);
+static const char *toktypestr(int type);
+
+#define EXPECT(type) \
+       do { \
+               if(next_token(pst) != (type)) { \
+                       fprintf(stderr, "expected %s token\n", toktypestr(type)); \
+                       goto err; \
+               } \
+       } while(0)
+
+#define EXPECT_SYM(c) \
+       do { \
+               if(next_token(pst) != TOK_SYM || pst->token[0] != (c)) { \
+                       fprintf(stderr, "expected symbol: %c\n", c); \
+                       goto err; \
+               } \
+       } while(0)
+
+
+struct ts_node *ts_text_load(struct ts_io *io)
+{
+       char *root_name;
+       struct parser pstate, *pst = &pstate;
+       struct ts_node *node = 0;
+
+       pstate.io = io;
+       pstate.nline = 0;
+       pstate.nextc = -1;
+       if(!(pstate.token = ts_dynarr_alloc(0, 1))) {
+               perror("failed to allocate token string");
+               return 0;
+       }
+
+       EXPECT(TOK_ID);
+       if(!(root_name = strdup(pst->token))) {
+               perror("failed to allocate root node name");
+               ts_dynarr_free(pst->token);
+               return 0;
+       }
+       EXPECT_SYM('{');
+       if(!(node = read_node(pst))) {
+               ts_dynarr_free(pst->token);
+               return 0;
+       }
+       node->name = root_name;
+
+err:
+       ts_dynarr_free(pst->token);
+       return node;
+}
+
+static int read_value(struct parser *pst, int toktype, struct ts_value *val)
+{
+       switch(toktype) {
+       case TOK_NUM:
+               ts_set_valuef(val, atof(pst->token));
+               break;
+
+       case TOK_SYM:
+               if(pst->token[0] == '[' || pst->token[0] == '{') {
+                       char endsym = pst->token[0] + 2; /* end symbol is dist 2 from either '[' or '{' */
+                       if(read_array(pst, val, endsym) == -1) {
+                               return -1;
+                       }
+               } else {
+                       fprintf(stderr, "read_node: unexpected rhs symbol: %c\n", pst->token[0]);
+               }
+               break;
+
+       case TOK_ID:
+       case TOK_STR:
+       default:
+               ts_set_value_str(val, pst->token);
+       }
+
+       return 0;
+}
+
+static struct ts_node *read_node(struct parser *pst)
+{
+       int type;
+       struct ts_node *node;
+
+       if(!(node = ts_alloc_node())) {
+               perror("failed to allocate treestore node");
+               return 0;
+       }
+
+       while((type = next_token(pst)) == TOK_ID) {
+               char *id;
+
+               if(!(id = strdup(pst->token))) {
+                       goto err;
+               }
+
+               EXPECT(TOK_SYM);
+
+               if(pst->token[0] == '=') {
+                       /* attribute */
+                       struct ts_attr *attr;
+                       int type;
+
+                       if(!(attr = ts_alloc_attr())) {
+                               goto err;
+                       }
+
+                       if((type = next_token(pst)) == -1) {
+                               ts_free_attr(attr);
+                               fprintf(stderr, "read_node: unexpected EOF\n");
+                               goto err;
+                       }
+
+                       if(read_value(pst, type, &attr->val) == -1) {
+                               ts_free_attr(attr);
+                               fprintf(stderr, "failed to read value\n");
+                               goto err;
+                       }
+                       attr->name = id;
+                       ts_add_attr(node, attr);
+
+               } else if(pst->token[0] == '{') {
+                       /* child */
+                       struct ts_node *child;
+
+                       if(!(child = read_node(pst))) {
+                               ts_free_node(node);
+                               return 0;
+                       }
+
+                       child->name = id;
+                       ts_add_child(node, child);
+
+               } else {
+                       fprintf(stderr, "unexpected token: %s\n", pst->token);
+                       goto err;
+               }
+       }
+
+       if(type != TOK_SYM || pst->token[0] != '}') {
+               fprintf(stderr, "expected closing brace\n");
+               goto err;
+       }
+       return node;
+
+err:
+       fprintf(stderr, "treestore read_node failed\n");
+       ts_free_node(node);
+       return 0;
+}
+
+static int read_array(struct parser *pst, struct ts_value *tsv, char endsym)
+{
+       int type;
+       struct ts_value values[32];
+       int i, nval = 0;
+       int res;
+
+       while((type = next_token(pst)) != -1) {
+               ts_init_value(values + nval);
+               if(read_value(pst, type, values + nval) == -1) {
+                       return -1;
+               }
+               if(nval < 31) {
+                       ++nval;
+               } else {
+                       ts_destroy_value(values + nval);
+               }
+
+               type = next_token(pst);
+               if(!(type == TOK_SYM && (pst->token[0] == ',' || pst->token[0] == endsym))) {
+                       fprintf(stderr, "read_array: expected comma or end symbol ('%c')\n", endsym);
+                       return -1;
+               }
+               if(pst->token[0] == endsym) {
+                       break;  /* we're done */
+               }
+       }
+
+       if(!nval) {
+               return -1;
+       }
+
+       res = ts_set_value_arr(tsv, nval, values);
+
+       for(i=0; i<nval; i++) {
+               ts_destroy_value(values + i);
+       }
+       return res;
+}
+
+static int nextchar(struct parser *pst)
+{
+       char c;
+
+       if(pst->nextc >= 0) {
+               c = pst->nextc;
+               pst->nextc = -1;
+       } else {
+               if(pst->io->read(&c, 1, pst->io->data) < 1) {
+                       return -1;
+               }
+       }
+       return c;
+}
+
+static void ungetchar(char c, struct parser *pst)
+{
+       assert(pst->nextc == -1);
+       pst->nextc = c;
+}
+
+static int next_token(struct parser *pst)
+{
+       int c;
+
+       DYNARR_CLEAR(pst->token);
+
+       /* skip whitespace */
+       while((c = nextchar(pst)) != -1) {
+               if(c == '#') { /* skip to end of line */
+                       while((c = nextchar(pst)) != -1 && c != '\n');
+                       if(c == -1) return -1;
+               }
+               if(!isspace(c)) break;
+               if(c == '\n') ++pst->nline;
+       }
+       if(c == -1) return -1;
+
+       DYNARR_STRPUSH(pst->token, c);
+
+       if(isdigit(c) || c == '-' || c == '+') {
+               /* token is a number */
+               int found_dot = 0;
+               while((c = nextchar(pst)) != -1 &&
+                               (isdigit(c) || (c == '.' && !found_dot))) {
+                       DYNARR_STRPUSH(pst->token, c);
+                       if(c == '.') found_dot = 1;
+               }
+               if(c != -1) ungetchar(c, pst);
+               return TOK_NUM;
+       }
+       if(isalpha(c)) {
+               /* token is an identifier */
+               while((c = nextchar(pst)) != -1 && (isalnum(c) || c == '_')) {
+                       DYNARR_STRPUSH(pst->token, c);
+               }
+               if(c != -1) ungetchar(c, pst);
+               return TOK_ID;
+       }
+       if(c == '"') {
+               /* token is a string constant, remove the opening quote */
+               DYNARR_STRPOP(pst->token);
+               while((c = nextchar(pst)) != -1 && c != '"') {
+                       DYNARR_STRPUSH(pst->token, c);
+                       if(c == '\n') ++pst->nline;
+               }
+               if(c != '"') {
+                       return -1;
+               }
+               return TOK_STR;
+       }
+       return TOK_SYM;
+}
+
+int ts_text_save(struct ts_node *tree, struct ts_io *io)
+{
+       char *buf;
+       struct ts_node *c;
+       struct ts_attr *attr;
+       int lvl = tree_level(tree);
+       int sz, inline_attr, res = -1;
+
+       if(!(buf = malloc(lvl + strlen(tree->name) + 4))) {
+               perror("ts_text_save failed to allocate buffer");
+               goto end;
+       }
+
+       if(tree->child_list || (tree->attr_list && tree->attr_list->next)) {
+               inline_attr = 0;
+       } else {
+               inline_attr = 1;
+       }
+
+       sz = sprintf(buf, "%s%s {", indent(lvl), tree->name);
+       if(!inline_attr) {
+               strcat(buf, "\n");
+               sz++;
+       }
+       if(io->write(buf, sz, io->data) < sz) {
+               goto end;
+       }
+
+       attr = tree->attr_list;
+       while(attr) {
+               if(print_attr(attr, io, inline_attr ? -1 : lvl) == -1) {
+                       goto end;
+               }
+               attr = attr->next;
+       }
+
+       c = tree->child_list;
+       while(c) {
+               if(ts_text_save(c, io) == -1) {
+                       goto end;
+               }
+               c = c->next;
+       }
+
+       if(inline_attr) {
+               sz = sprintf(buf, "}\n");
+       } else {
+               sz = sprintf(buf, "%s}\n", indent(lvl));
+       }
+       if(io->write(buf, sz, io->data) < sz) {
+               goto end;
+       }
+       res = 0;
+end:
+       free(buf);
+       return res;
+}
+
+static int print_attr(struct ts_attr *attr, struct ts_io *io, int level)
+{
+       char *buf, *val;
+       int sz;
+
+       if(!(val = value_to_str(&attr->val))) {
+               return -1;
+       }
+
+       sz = (level >= 0 ? level : 0) + strlen(attr->name) + ts_dynarr_size(val) + 5;
+       if(!(buf = malloc(sz))) {
+               perror("print_attr: failed to allocate name buffer");
+               ts_dynarr_free(val);
+       }
+
+       if(level >= 0) {
+               sz = sprintf(buf, "%s%s = %s\n", indent(level + 1), attr->name, val);
+       } else {
+               sz = sprintf(buf, " %s = %s ", attr->name, val);
+       }
+       if(io->write(buf, sz, io->data) < sz) {
+               ts_dynarr_free(val);
+               free(buf);
+               return -1;
+       }
+       ts_dynarr_free(val);
+       free(buf);
+       return 0;
+}
+
+static char *append_dynstr(char *dest, char *s)
+{
+       while(*s) {
+               DYNARR_STRPUSH(dest, *s++);
+       }
+       return dest;
+}
+
+static char *value_to_str(struct ts_value *value)
+{
+       int i;
+       char buf[128];
+       char *str, *valstr;
+
+       if(!(str = ts_dynarr_alloc(0, 1))) {
+               return 0;
+       }
+
+       switch(value->type) {
+       case TS_NUMBER:
+               sprintf(buf, "%g", value->fnum);
+               str = append_dynstr(str, buf);
+               break;
+
+       case TS_VECTOR:
+               DYNARR_STRPUSH(str, '[');
+               for(i=0; i<value->vec_size; i++) {
+                       if(i == 0) {
+                               sprintf(buf, "%g", value->vec[i]);
+                       } else {
+                               sprintf(buf, ", %g", value->vec[i]);
+                       }
+                       str = append_dynstr(str, buf);
+               }
+               DYNARR_STRPUSH(str, ']');
+               break;
+
+       case TS_ARRAY:
+               DYNARR_STRPUSH(str, '[');
+               for(i=0; i<value->array_size; i++) {
+                       if(i > 0) {
+                               str = append_dynstr(str, ", ");
+                       }
+                       if(!(valstr = value_to_str(value->array + i))) {
+                               ts_dynarr_free(str);
+                               return 0;
+                       }
+                       str = append_dynstr(str, valstr);
+                       ts_dynarr_free(valstr);
+               }
+               DYNARR_STRPUSH(str, ']');
+               break;
+
+       default:
+               sprintf(buf, "\"%s\"", value->str);
+               str = append_dynstr(str, buf);
+       }
+
+       return str;
+}
+
+static int tree_level(struct ts_node *n)
+{
+       if(!n->parent) return 0;
+       return tree_level(n->parent) + 1;
+}
+
+static const char *indent(int x)
+{
+       static const char buf[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
+       const char *end = buf + sizeof buf - 1;
+       return x > sizeof buf - 1 ? buf : end - x;
+}
+
+static const char *toktypestr(int type)
+{
+       switch(type) {
+       case TOK_ID:
+               return "identifier";
+       case TOK_NUM:
+               return "number";
+       case TOK_STR:
+               return "string";
+       case TOK_SYM:
+               return "symbol";
+       }
+       return "unknown";
+}