text: #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";
  }
  