changeset 392:7c167cbffcb7 v3.0

rework variable code The previous code was pretty awful. It confused the idea of variables and values. This version rectifies that. Currently, the environment names for fmt3 commands are the only thing leaked.
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Wed, 05 Jun 2013 03:02:07 +0000
parents 1e8dbcacf1ea
children 5d2dc675b40d
files comment.c config.cmake config.h.in feed.c index.c main.c pipeline.c pipeline.h post.c post.h post_fmt3.l post_fmt3.y post_fmt3_cmds.c sidebar.c story.c tag.c template.y vars.c vars.h
diffstat 19 files changed, 679 insertions(+), 531 deletions(-) [+]
line wrap: on
line diff
--- a/comment.c	Thu May 30 23:52:40 2013 +0000
+++ b/comment.c	Wed Jun 05 03:02:07 2013 +0000
@@ -58,12 +58,16 @@
 	time_t now_sec;
 	struct tm *now_tm;
 
-	ret = load_post(req, id, NULL, false);
-	if (ret) {
-		LOG("Gah! %d (postid=%d)", ret, id);
+	struct val *val;
+
+	val = load_post(req, id, NULL, false);
+	if (!val) {
+		LOG("Gah! %d (postid=%d)", -1, id);
 		return 1;
 	}
 
+	val_putref(val);
+
 	if ((strlen(author) == 0) || (strlen(comment) == 0)) {
 		LOG("You must fill in name, and comment (postid=%d)", id);
 		return 1;
--- a/config.cmake	Thu May 30 23:52:40 2013 +0000
+++ b/config.cmake	Wed Jun 05 03:02:07 2013 +0000
@@ -28,9 +28,9 @@
 set_default(PHOTO_BASE_URL		"http://www.josefsipek.net/photos")
 
 set_default(VAR_MAX_SCOPES		10)
-set_default(VAR_MAX_VAR_NAME		32)
-set_default(VAR_MAX_ARRAY_SIZE		200)
 set_default(VAR_MAX_VARS_SIZE		10)
+set_default(VAR_VAL_KEY_MAXLEN		32)
+set_default(VAR_NAME_MAXLEN		32)
 
 set_default(TAGCLOUD_MIN_SIZE		6)
 set_default(TAGCLOUD_MAX_SIZE		18)
--- a/config.h.in	Thu May 30 23:52:40 2013 +0000
+++ b/config.h.in	Wed Jun 05 03:02:07 2013 +0000
@@ -24,9 +24,9 @@
 #cmakedefine PHOTO_BASE_URL		"${PHOTO_BASE_URL}"
 
 #cmakedefine VAR_MAX_SCOPES		${VAR_MAX_SCOPES}
-#cmakedefine VAR_MAX_VAR_NAME		${VAR_MAX_VAR_NAME}
-#cmakedefine VAR_MAX_ARRAY_SIZE		${VAR_MAX_ARRAY_SIZE}
 #cmakedefine VAR_MAX_VARS_SIZE		${VAR_MAX_VARS_SIZE}
+#cmakedefine VAR_VAL_KEY_MAXLEN		${VAR_VAL_KEY_MAXLEN}
+#cmakedefine VAR_NAME_MAXLEN		${VAR_NAME_MAXLEN}
 
 #cmakedefine TAGCLOUD_MIN_SIZE		${TAGCLOUD_MIN_SIZE}
 #cmakedefine TAGCLOUD_MAX_SIZE		${TAGCLOUD_MAX_SIZE}
--- a/feed.c	Thu May 30 23:52:40 2013 +0000
+++ b/feed.c	Wed Jun 05 03:02:07 2013 +0000
@@ -11,12 +11,17 @@
 
 static void __load_posts(struct req *req, int page)
 {
-	struct var_val vv;
 	sqlite3_stmt *stmt;
+	struct val *posts;
+	struct val *val;
 	time_t maxtime;
 	int ret;
+	int i;
 
 	maxtime = 0;
+	i = 0;
+
+	posts = VAR_LOOKUP_VAL(&req->vars, "posts");
 
 	open_db();
 	SQL(stmt, "SELECT id, strftime(\"%s\", time) FROM posts ORDER BY time DESC LIMIT ? OFFSET ?");
@@ -29,19 +34,17 @@
 		postid   = SQL_COL_INT(stmt, 0);
 		posttime = SQL_COL_INT(stmt, 1);
 
-		if (load_post(req, postid, NULL, false))
+		val = load_post(req, postid, NULL, false);
+		if (!val)
 			continue;
 
+		VAL_SET_LIST(posts, i++, val);
+
 		if (posttime > maxtime)
 			maxtime = posttime;
 	}
 
-	memset(&vv, 0, sizeof(vv));
-
-	vv.type = VT_INT;
-	vv.i    = maxtime;
-
-	ASSERT(!var_append(&req->vars, "lastupdate", &vv));
+	VAR_SET_INT(&req->vars, "lastupdate", maxtime);
 }
 
 static int __feed(struct req *req)
@@ -51,8 +54,6 @@
 	else if (!strcmp(req->fmt, "rss2"))
 		req_head(req, "Content-Type", "application/rss+xml");
 
-	vars_scope_push(&req->vars);
-
 	__load_posts(req, 0);
 
 	req->body = render_page(req, "{feed}");
--- a/index.c	Thu May 30 23:52:40 2013 +0000
+++ b/index.c	Wed Jun 05 03:02:07 2013 +0000
@@ -21,7 +21,14 @@
 static void __load_posts(struct req *req, int page)
 {
 	sqlite3_stmt *stmt;
+	struct val *posts;
+	struct val *val;
 	int ret;
+	int i;
+
+	i = 0;
+
+	posts = VAR_LOOKUP_VAL(&req->vars, "posts");
 
 	open_db();
 	SQL(stmt, "SELECT id FROM posts ORDER BY time DESC LIMIT ? OFFSET ?");
@@ -32,9 +39,14 @@
 
 		postid = SQL_COL_INT(stmt, 0);
 
-		if (load_post(req, postid, NULL, false))
+		val = load_post(req, postid, NULL, false);
+		if (!val)
 			continue;
+
+		VAL_SET_LIST(posts, i++, val);
 	}
+
+	val_putref(posts);
 }
 
 static void __load_posts_archive(struct req *req, int page, int archid)
@@ -42,8 +54,11 @@
 	char fromtime[32];
 	char totime[32];
 	sqlite3_stmt *stmt;
+	struct val *posts;
+	struct val *val;
 	int toyear, tomonth;
 	int ret;
+	int i;
 
 	toyear = archid / 100;
 	tomonth = (archid % 100) + 1;
@@ -52,6 +67,10 @@
 		toyear++;
 	}
 
+	i = 0;
+
+	posts = VAR_LOOKUP_VAL(&req->vars, "posts");
+
 	snprintf(fromtime, sizeof(fromtime), "%04d-%02d-01 00:00",
 		 archid / 100, archid % 100);
 	snprintf(totime, sizeof(totime), "%04d-%02d-01 00:00",
@@ -68,51 +87,30 @@
 
 		postid = SQL_COL_INT(stmt, 0);
 
-		if (load_post(req, postid, NULL, false))
+		val = load_post(req, postid, NULL, false);
+		if (!val)
 			continue;
+
+		VAL_SET_LIST(posts, i++, val);
 	}
+
+	val_putref(posts);
 }
 
-static void __store_title(struct vars *vars, const char *title)
+static void __store_title(struct vars *vars, char *title)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-        vv.type = VT_STR;
-        vv.str  = xstrdup(title);
-        ASSERT(vv.str);
-
-        ASSERT(!var_append(vars, "title", &vv));
+	VAR_SET_STR(vars, "title", xstrdup(title));
 }
 
 static void __store_pages(struct vars *vars, int page)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-	vv.type = VT_INT;
-	vv.i    = page + 1;
-
-	ASSERT(!var_append(vars, "prevpage", &vv));
-
-	vv.type = VT_INT;
-	vv.i    = page - 1;
-
-	ASSERT(!var_append(vars, "nextpage", &vv));
+	VAR_SET_INT(vars, "prevpage", page + 1);
+	VAR_SET_INT(vars, "nextpage", page - 1);
 }
 
 static void __store_archid(struct vars *vars, int archid)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-	vv.type = VT_INT;
-	vv.i    = archid;
-
-	ASSERT(!var_append(vars, "archid", &vv));
+	VAR_SET_INT(vars, "archid", archid);
 }
 
 int blahg_index(struct req *req, int page)
@@ -126,8 +124,6 @@
 
 	sidebar(req);
 
-	vars_scope_push(&req->vars);
-
 	__load_posts(req, page);
 
 	req->body = render_page(req, "{index}");
@@ -168,8 +164,6 @@
 
 	sidebar(req);
 
-	vars_scope_push(&req->vars);
-
 	__load_posts_archive(req, page, m);
 
 	req->body = render_page(req, "{archive}");
--- a/main.c	Thu May 30 23:52:40 2013 +0000
+++ b/main.c	Wed Jun 05 03:02:07 2013 +0000
@@ -138,31 +138,6 @@
 	return 0;
 }
 
-static void __store_str(struct vars *vars, const char *key, char *val)
-{
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-        vv.type = VT_STR;
-        vv.str  = xstrdup(val);
-        ASSERT(vv.str);
-
-        ASSERT(!var_append(vars, key, &vv));
-}
-
-static void __store_int(struct vars *vars, const char *key, uint64_t val)
-{
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-        vv.type = VT_INT;
-        vv.i    = val;
-
-        ASSERT(!var_append(vars, key, &vv));
-}
-
 static void req_init(struct req *req)
 {
 	req->dump_latency = true;
@@ -175,8 +150,10 @@
 
 	vars_init(&req->vars);
 
-	__store_str(&req->vars, "baseurl", BASE_URL);
-	__store_int(&req->vars, "now", gettime());
+	VAR_SET_STR(&req->vars, "baseurl", xstrdup(BASE_URL));
+	VAR_SET_INT(&req->vars, "now", gettime());
+
+	VAR_SET(&req->vars, "posts", VAL_ALLOC(VT_LIST));
 }
 
 static void req_destroy(struct req *req)
@@ -206,6 +183,8 @@
 		       delta / 1000000000UL,
 		       delta % 1000000000UL);
 	}
+
+	free(req->body);
 }
 
 void req_head(struct req *req, char *name, char *val)
--- a/pipeline.c	Thu May 30 23:52:40 2013 +0000
+++ b/pipeline.c	Wed Jun 05 03:02:07 2013 +0000
@@ -18,9 +18,9 @@
 	ASSERT(pipeline_cache);
 }
 
-static struct var_val *nop_fxn(struct var_val *v)
+static struct val *nop_fxn(struct val *val)
 {
-	return v;
+	return val;
 }
 
 static char *str_of_int(uint64_t v)
@@ -93,76 +93,79 @@
 	return out;
 }
 
-static struct var_val *__escape(struct var_val *v, char *(*cvt)(char*))
+static struct val *__escape(struct val *val, char *(*cvt)(char*))
 {
 	char *out;
 
 	out = NULL;
 
-	switch (v->type) {
-		case VT_VARS:
-			var_val_dump(v, 0, 10);
+	switch (val->type) {
+		case VT_NV:
+		case VT_LIST:
+			out = xstrdup("FAKED");
+			val_dump(val, 10);
 			ASSERT(0);
 			break;
-		case VT_NIL:
-			out = xstrdup("");
-			break;
 		case VT_INT:
-			out = str_of_int(v->i);
+			out = str_of_int(val->i);
 			break;
 		case VT_STR:
-			out = cvt(v->str);
+			out = cvt(val->str);
 			break;
 	}
 
+	val_putref(val);
+
 	ASSERT(out);
 
-	return VAR_VAL_ALLOC_STR(out);
+	return VAL_ALLOC_STR(out);
 }
 
-static struct var_val *urlescape_fxn(struct var_val *v)
+static struct val *urlescape_fxn(struct val *val)
 {
-	return __escape(v, __urlescape_str);
+	return __escape(val, __urlescape_str);
 }
 
-static struct var_val *escape_fxn(struct var_val *v)
+static struct val *escape_fxn(struct val *val)
 {
-	return __escape(v, mangle_htmlescape);
+	return __escape(val, mangle_htmlescape);
 }
 
-static struct var_val *__datetime(struct var_val *v, const char *fmt)
+static struct val *__datetime(struct val *val, const char *fmt)
 {
 	char buf[64];
 	struct tm tm;
 	time_t ts;
 
-	ASSERT(v->type == VT_INT);
+	ASSERT3U(val->type, ==, VT_INT);
 
-	ts = v->i;
+	ts = val->i;
 	gmtime_r(&ts, &tm);
 	strftime(buf, sizeof(buf), fmt, &tm);
 
-	return VAR_VAL_ALLOC_STR(xstrdup(buf));
+	val_putref(val);
+
+	return VAL_ALLOC_STR(xstrdup(buf));
 }
 
-static struct var_val *time_fxn(struct var_val *v)
+static struct val *time_fxn(struct val *val)
 {
-	return __datetime(v, "%H:%M");
+	return __datetime(val, "%H:%M");
 }
 
-static struct var_val *date_fxn(struct var_val *v)
+static struct val *date_fxn(struct val *val)
 {
-	return __datetime(v, "%B %e, %Y");
+	return __datetime(val, "%B %e, %Y");
 }
 
-static struct var_val *zulu_fxn(struct var_val *v)
+static struct val *zulu_fxn(struct val *val)
 {
-	return __datetime(v, "%Y-%m-%dT%H:%M:%SZ");
+	return __datetime(val, "%Y-%m-%dT%H:%M:%SZ");
 }
 
-static struct var_val *rfc822_fxn(struct var_val *v)
+static struct val *rfc822_fxn(struct val *val)
 {
-	return __datetime(v, "%a, %d %b %Y %H:%M:%S +0000");
+	return __datetime(val, "%a, %d %b %Y %H:%M:%S +0000");
 }
 
 static const struct pipestages stages[] = {
@@ -203,6 +206,7 @@
 	struct pipeline *cur;
 	struct pipeline *tmp;
 
-	list_for_each_entry_safe(cur, tmp, pipelist, pipe)
+	list_for_each_entry_safe(cur, tmp, pipelist, pipe) {
 		umem_cache_free(pipeline_cache, cur);
+	}
 }
--- a/pipeline.h	Thu May 30 23:52:40 2013 +0000
+++ b/pipeline.h	Wed Jun 05 03:02:07 2013 +0000
@@ -6,7 +6,7 @@
 
 struct pipestages {
 	const char *name;
-	struct var_val *(*f)(struct var_val *);
+	struct val *(*f)(struct val *);
 };
 
 struct pipeline {
--- a/post.c	Thu May 30 23:52:40 2013 +0000
+++ b/post.c	Wed Jun 05 03:02:07 2013 +0000
@@ -236,91 +236,84 @@
 	return 0;
 }
 
-static struct var *__tag_var(const char *name, struct list_head *val)
+static struct val *__tag_val(struct list_head *list)
 {
 	struct post_tag *cur, *tmp;
-	struct var *v;
+	struct val *val;
+	struct val *sub;
 	int i;
 
-	v = var_alloc(name);
-	ASSERT(v);
+	val = VAL_ALLOC(VT_LIST);
 
 	i = 0;
-	list_for_each_entry_safe(cur, tmp, val, list) {
-		ASSERT(i < VAR_MAX_ARRAY_SIZE);
+	list_for_each_entry_safe(cur, tmp, list, list) {
+		sub = VAL_ALLOC_STR(xstrdup(cur->tag));
 
-		v->val[i].type = VT_STR;
-		v->val[i].str  = xstrdup(cur->tag);
-		ASSERT(v->val[i].str);
-
+		VAL_SET_LIST(val, i, sub);
 		i++;
 	}
 
-	return v;
+	return val;
 }
 
-static struct var *__com_var(const char *name, struct list_head *val)
+static struct val *__com_val(struct list_head *list)
 {
 	struct comment *cur, *tmp;
-	struct var *v;
+	struct val *val;
+	struct val *sub;
 	int i;
 
-	v = var_alloc(name);
-	ASSERT(v);
+	val = VAL_ALLOC(VT_LIST);
 
 	i = 0;
-	list_for_each_entry_safe(cur, tmp, val, list) {
-		ASSERT(i < VAR_MAX_ARRAY_SIZE);
+	list_for_each_entry_safe(cur, tmp, list, list) {
+		sub = VAL_ALLOC(VT_NV);
 
-		v->val[i].type = VT_VARS;
-		v->val[i].vars[0] = VAR_ALLOC_INT("commid", cur->id);
-		v->val[i].vars[1] = VAR_ALLOC_INT("commtime", cur->time);
-		v->val[i].vars[2] = VAR_ALLOC_STR("commauthor", cur->author);
-		v->val[i].vars[3] = VAR_ALLOC_STR("commemail", cur->email);
-		v->val[i].vars[4] = VAR_ALLOC_STR("commip", cur->ip);
-		v->val[i].vars[5] = VAR_ALLOC_STR("commurl", cur->url);
-		v->val[i].vars[6] = VAR_ALLOC_STR("commbody", cur->body);
+		VAL_SET_NVINT(sub, "commid", cur->id);
+		VAL_SET_NVINT(sub, "commtime", cur->time);
+		VAL_SET_NVSTR(sub, "commauthor", xstrdup(cur->author));
+		VAL_SET_NVSTR(sub, "commemail", xstrdup(cur->email));
+		VAL_SET_NVSTR(sub, "commip", xstrdup(cur->ip));
+		VAL_SET_NVSTR(sub, "commurl", xstrdup(cur->url));
+		VAL_SET_NVSTR(sub, "commbody", xstrdup(cur->body));
 
+		VAL_SET_LIST(val, i, sub);
 		i++;
 	}
 
-	return v;
+	return val;
 }
 
-static void __store_vars(struct req *req, const char *var, struct post *post,
-			 const char *titlevar)
+static struct val *__store_vars(struct req *req, struct post *post, const char *titlevar)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-	vv.type    = VT_VARS;
-	vv.vars[0] = VAR_ALLOC_INT("id", post->id);
-	vv.vars[1] = VAR_ALLOC_INT("time", post->time);
-	vv.vars[2] = VAR_ALLOC_STR("title", post->title);
-	vv.vars[3] = __tag_var("tags", &post->tags);
-	vv.vars[4] = VAR_ALLOC_STR("body", post->body);
-	vv.vars[5] = VAR_ALLOC_INT("numcom", post->numcom);
-	vv.vars[6] = __com_var("comments", &post->comments);
-
-	ASSERT(!var_append(&req->vars, "posts", &vv));
+	struct val *val;
 
 	if (titlevar) {
-		vv.type = VT_STR;
-		vv.str  = xstrdup(post->title);
-		ASSERT(vv.str);
+		val = VAL_ALLOC_STR(xstrdup(post->title));
+		VAR_SET(&req->vars, titlevar, val);
+	}
+
+	val = VAL_ALLOC(VT_NV);
 
-		ASSERT(!var_append(&req->vars, titlevar, &vv));
-	}
+	VAL_SET_NVINT(val, "id", post->id);
+	VAL_SET_NVINT(val, "time", post->time);
+	VAL_SET_NVSTR(val, "title", xstrdup(post->title));
+	VAL_SET_NV   (val, "tags", __tag_val(&post->tags));
+	VAL_SET_NVSTR(val, "body", xstrdup(post->body));
+	VAL_SET_NVINT(val, "numcom", post->numcom);
+	VAL_SET_NV   (val, "comments", __com_val(&post->comments));
+
+	return val;
 }
 
-int load_post(struct req *req, int postid, const char *titlevar, bool preview)
+struct val *load_post(struct req *req, int postid, const char *titlevar, bool preview)
 {
 	char path[FILENAME_MAX];
 	struct post post;
 	int ret;
 	int err;
 	sqlite3_stmt *stmt;
+	struct val *val;
 
 	snprintf(path, FILENAME_MAX, "data/posts/%d", postid);
 
@@ -374,11 +367,11 @@
 
 err:
 	if (!err)
-		__store_vars(req, "posts", &post, titlevar);
+		val = __store_vars(req, &post, titlevar);
 
 	destroy_post(&post);
 
-	return err;
+	return err ? NULL : val;
 }
 
 void destroy_post(struct post *post)
@@ -386,27 +379,20 @@
 	struct post_tag *pt, *pttmp;
 	struct comment *com, *comtmp;
 
-	list_for_each_entry_safe(pt, pttmp, &post->tags, list)
+	list_for_each_entry_safe(pt, pttmp, &post->tags, list) {
+		free(pt->tag);
 		free(pt);
+	}
 
-	list_for_each_entry_safe(com, comtmp, &post->comments, list)
+	list_for_each_entry_safe(com, comtmp, &post->comments, list) {
+		free(com->author);
+		free(com->email);
+		free(com->ip);
+		free(com->url);
+		free(com->body);
 		free(com);
+	}
 
 	free(post->title);
 	free(post->body);
 }
-
-#if 0
-void dump_post(struct post_old *post)
-{
-	if (!post)
-		fprintf(stdout, "p=NULL\n");
-	else
-		fprintf(post->out, "p=%p { %d, '%s', '%s', '%s', '%04d-%02d-%02d %02d:%02d' }\n\n",
-			post, post->id, post->title, post->cats,
-			post->tags,
-			post->time.tm_year, post->time.tm_mon,
-			post->time.tm_mday, post->time.tm_hour,
-			post->time.tm_min);
-}
-#endif
--- a/post.h	Thu May 30 23:52:40 2013 +0000
+++ b/post.h	Wed Jun 05 03:02:07 2013 +0000
@@ -63,7 +63,7 @@
 
 struct req;
 
-extern int load_post(struct req *req, int postid, const char *titlevar, bool preview);
+extern struct val *load_post(struct req *req, int postid, const char *titlevar, bool preview);
 extern void dump_post(struct post_old *post);
 extern void destroy_post(struct post *post);
 
--- a/post_fmt3.l	Thu May 30 23:52:40 2013 +0000
+++ b/post_fmt3.l	Wed Jun 05 03:02:07 2013 +0000
@@ -45,17 +45,17 @@
 
 "$"			{ BEGIN(MATH); return MATHSTART; }
 <MATH>"$"		{ BEGIN(INITIAL); return MATHEND; }
-<MATH>"\\"		{ yylval->ptr = xstrdup(yytext); return BSLASH; }
-<MATH>"_"		{ yylval->ptr = xstrdup(yytext); return USCORE; }
+<MATH>"\\"		{ return BSLASH; }
+<MATH>"_"		{ return USCORE; }
 <MATH>"^"		{ yylval->ptr = xstrdup(yytext); return CARRET; }
 <MATH>"+"		{ yylval->ptr = xstrdup(yytext); return PLUS; }
 <MATH>"-"		{ yylval->ptr = xstrdup(yytext); return MINUS; }
-<MATH>"*"		{ yylval->ptr = xstrdup(yytext); return ASTERISK; }
-<MATH>"/"		{ yylval->ptr = xstrdup(yytext); return SLASH; }
-<MATH>"{"		{ yylval->ptr = xstrdup(yytext); return OCURLY; }
-<MATH>"}"		{ yylval->ptr = xstrdup(yytext); return CCURLY; }
-<MATH>"("		{ yylval->ptr = xstrdup(yytext); return OPAREN; }
-<MATH>")"		{ yylval->ptr = xstrdup(yytext); return CPAREN; }
+<MATH>"*"		{ return ASTERISK; }
+<MATH>"/"		{ return SLASH; }
+<MATH>"{"		{ return OCURLY; }
+<MATH>"}"		{ return CCURLY; }
+<MATH>"("		{ return OPAREN; }
+<MATH>")"		{ return CPAREN; }
 <MATH>[=<>]		{ yylval->ptr = xstrdup(yytext); return EQLTGT; }
 <MATH>[A-Za-z0-9]+	{ yylval->ptr = xstrdup(yytext); return WORD; }
 <MATH>[".,=<>!:;?@*#]	{ yylval->ptr = xstrdup(yytext); return SCHAR; }
@@ -76,17 +76,17 @@
 			}
 %[^\n]*			{ /* tex comment */ }
 [ \t]+			{ yylval->ptr = xstrdup(yytext); return WSPACE; }
-"\\"			{ yylval->ptr = xstrdup(yytext); return BSLASH; }
-"{"			{ yylval->ptr = xstrdup(yytext); return OCURLY; }
-"}"			{ yylval->ptr = xstrdup(yytext); return CCURLY; }
-"["			{ yylval->ptr = xstrdup(yytext); return OBRACE; }
-"]"			{ yylval->ptr = xstrdup(yytext); return CBRACE; }
-"&"			{ yylval->ptr = xstrdup(yytext); return AMP; }
-"_"			{ yylval->ptr = xstrdup(yytext); return USCORE; }
-"\\%"			{ yylval->ptr = xstrdup(yytext+1); return PERCENT; }
-"~"			{ yylval->ptr = xstrdup(yytext); return TILDE; }
-"|"			{ yylval->ptr = xstrdup(yytext); return PIPE; }
-\.{3}			{ yylval->ptr = xstrdup(yytext); return ELLIPSIS; }
+"\\"			{ return BSLASH; }
+"{"			{ return OCURLY; }
+"}"			{ return CCURLY; }
+"["			{ return OBRACE; }
+"]"			{ return CBRACE; }
+"&"			{ return AMP; }
+"_"			{ return USCORE; }
+"\\%"			{ return PERCENT; }
+"~"			{ return TILDE; }
+"|"			{ return PIPE; }
+\.{3}			{ return ELLIPSIS; }
 -{1,3}			{ yylval->ptr = xstrdup(yytext); return DASH; }
 `{1,2}			{ yylval->ptr = xstrdup(yytext); return OQUOT; }
 '{1,2}			{ yylval->ptr = xstrdup(yytext); return CQUOT; }
--- a/post_fmt3.y	Thu May 30 23:52:40 2013 +0000
+++ b/post_fmt3.y	Wed Jun 05 03:02:07 2013 +0000
@@ -225,14 +225,19 @@
 };
 
 /* generic tokens */
-%token <ptr> WSPACE BSLASH OCURLY CCURLY OBRACE CBRACE AMP
-%token <ptr> USCORE PERCENT TILDE DASH OQUOT CQUOT SCHAR ELLIPSIS
-%token <ptr> UTF8FIRST3 UTF8FIRST2 UTF8REST WORD ASTERISK SLASH
-%token <ptr> PIPE
+%token <ptr> WSPACE
+%token <ptr> DASH OQUOT CQUOT SCHAR
+%token <ptr> UTF8FIRST3 UTF8FIRST2 UTF8REST WORD
+%token SLASH
+%token PIPE
+%token OCURLY CCURLY OBRACE CBRACE
+%token USCORE ASTERISK
+%token BSLASH PERCENT AMP TILDE ELLIPSIS
 %token PAREND NLINE
 
 /* math specific tokens */
-%token <ptr> PLUS MINUS OPAREN CPAREN EQLTGT CARRET
+%token <ptr> PLUS MINUS EQLTGT CARRET
+%token OPAREN CPAREN
 %token MATHSTART MATHEND
 
 /* verbose & listing environment */
@@ -268,20 +273,18 @@
       | UTF8FIRST3 UTF8REST UTF8REST	{ $$ = concat3($1, $2, $3); }
       | NLINE				{ $$ = xstrdup(data->post->texttt_nesting ? "\n" : " "); }
       | WSPACE				{ $$ = $1; }
-      | PIPE				{ $$ = $1; }
-      | PLUS				{ $$ = $1; }
-      | MINUS				{ $$ = dash(strlen($1)); }
-      | ASTERISK			{ $$ = $1; }
-      | SLASH				{ $$ = $1; }
-      | DASH				{ $$ = dash(strlen($1)); }
-      | OQUOT				{ $$ = oquote(strlen($1)); }
-      | CQUOT				{ $$ = cquote(strlen($1)); }
-      | SCHAR				{ $$ = special_char($1); }
+      | PIPE				{ $$ = xstrdup("|"); }
+      | ASTERISK			{ $$ = xstrdup("*"); }
+      | SLASH				{ $$ = xstrdup("/"); }
+      | DASH				{ $$ = dash(strlen($1)); free($1); }
+      | OQUOT				{ $$ = oquote(strlen($1)); free($1); }
+      | CQUOT				{ $$ = cquote(strlen($1)); free($1); }
+      | SCHAR				{ $$ = special_char($1); free($1); }
       | ELLIPSIS			{ $$ = xstrdup("&hellip;"); }
       | TILDE				{ $$ = xstrdup("&nbsp;"); }
       | AMP				{ $$ = xstrdup("</td><td>"); }
       | DOLLAR				{ $$ = xstrdup("$"); }
-      | PERCENT				{ $$ = $1; }
+      | PERCENT				{ $$ = xstrdup("%"); }
       | BSLASH cmd			{ $$ = $2; }
       | MATHSTART math MATHEND		{ $$ = render_math($2); }
       | VERBSTART verb VERBEND		{ $$ = concat3(S("</p><p>"), $2, S("</p><p>")); }
@@ -290,17 +293,17 @@
 							 S("</pre><p>")); }
       ;
 
-cmd : WORD optcmdarg cmdarg	{ $$ = process_cmd(data->post, $1, $3, $2); }
-    | WORD cmdarg		{ $$ = process_cmd(data->post, $1, $2, NULL); }
-    | WORD			{ $$ = process_cmd(data->post, $1, NULL, NULL); }
+cmd : WORD optcmdarg cmdarg	{ $$ = process_cmd(data->post, $1, $3, $2); free($1); }
+    | WORD cmdarg		{ $$ = process_cmd(data->post, $1, $2, NULL); free($1); }
+    | WORD			{ $$ = process_cmd(data->post, $1, NULL, NULL); free($1); }
     | BSLASH			{ $$ = xstrdup("<br/>"); }
-    | OCURLY			{ $$ = $1; }
-    | CCURLY			{ $$ = $1; }
-    | OBRACE			{ $$ = $1; }
-    | CBRACE			{ $$ = $1; }
+    | OCURLY			{ $$ = xstrdup("{"); }
+    | CCURLY			{ $$ = xstrdup("}"); }
+    | OBRACE			{ $$ = xstrdup("["); }
+    | CBRACE			{ $$ = xstrdup("]"); }
     | AMP			{ $$ = xstrdup("&amp;"); }
-    | USCORE			{ $$ = $1; }
-    | TILDE			{ $$ = $1; }
+    | USCORE			{ $$ = xstrdup("_"); }
+    | TILDE			{ $$ = xstrdup("~"); }
     ;
 
 optcmdarg : OBRACE paragraph CBRACE	{ $$ = $2; }
@@ -320,15 +323,15 @@
       | WSPACE				{ $$ = $1; }
       | SCHAR				{ $$ = $1; }
       | mexpr EQLTGT mexpr 		{ $$ = concat3($1, $2, $3); }
-      | mexpr USCORE mexpr 		{ $$ = concat3($1, $2, $3); }
+      | mexpr USCORE mexpr 		{ $$ = concat3($1, S("_"), $3); }
       | mexpr CARRET mexpr 		{ $$ = concat3($1, $2, $3); }
       | mexpr PLUS mexpr 		{ $$ = concat3($1, $2, $3); }
       | mexpr MINUS mexpr 		{ $$ = concat3($1, $2, $3); }
-      | mexpr ASTERISK mexpr	 	{ $$ = concat3($1, $2, $3); }
-      | mexpr SLASH mexpr	 	{ $$ = concat3($1, $2, $3); }
-      | BSLASH WORD			{ $$ = concat($1, $2); }
-      | OPAREN math CPAREN		{ $$ = concat3($1, $2, $3); }
-      | OCURLY math CCURLY		{ $$ = concat3($1, $2, $3); }
+      | mexpr ASTERISK mexpr	 	{ $$ = concat3($1, S("*"), $3); }
+      | mexpr SLASH mexpr	 	{ $$ = concat3($1, S("/"), $3); }
+      | BSLASH WORD			{ $$ = concat(S("\\"), $2); }
+      | OPAREN math CPAREN		{ $$ = concat3(S("("), $2, S(")")); }
+      | OCURLY math CCURLY		{ $$ = concat3(S("{"), $2, S("}")); }
       ;
 
 %%
--- a/post_fmt3_cmds.c	Thu May 30 23:52:40 2013 +0000
+++ b/post_fmt3_cmds.c	Wed Jun 05 03:02:07 2013 +0000
@@ -16,7 +16,13 @@
 
 static char *__process_listing(struct post *post, char *txt, char *opt)
 {
-	return concat3(S("</p><pre>"), listing(post, txt), S("</pre><p>"));
+	char *out;
+
+	out = listing(post, txt);
+
+	free(txt);
+
+	return concat3(S("</p><pre>"), out, S("</pre><p>"));
 }
 
 static char *__process_link(struct post *post, char *txt, char *opt)
@@ -61,6 +67,7 @@
 
 static char *__process_begin(struct post *post, char *txt, char *opt)
 {
+#warning memory leaks galore
 	if (!strcmp(txt, "texttt")) {
 		post->texttt_nesting++;
 		return xstrdup("</p><pre>");
@@ -89,6 +96,7 @@
 
 static char *__process_end(struct post *post, char *txt, char *opt)
 {
+#warning memory leaks galore
 	if (!strcmp(txt, "texttt")) {
 		post->texttt_nesting--;
 		return xstrdup("</pre><p>");
@@ -166,6 +174,9 @@
 	snprintf(buf, sizeof(buf), "<a href=\"/?p=%s\">%s</a>",
 		 txt, opt ? opt : txt);
 
+	free(txt);
+	free(opt);
+
 	return S(buf);
 }
 
@@ -211,6 +222,9 @@
 
 static char *__process_nop(struct post *post, char *txt, char *opt)
 {
+	free(txt);
+	free(opt);
+
 	return xstrdup("");
 }
 
--- a/sidebar.c	Thu May 30 23:52:40 2013 +0000
+++ b/sidebar.c	Wed Jun 05 03:02:07 2013 +0000
@@ -24,19 +24,21 @@
 
 static void tagcloud(struct req *req)
 {
-	struct var_val vv;
+	struct val *cloud;
+	struct val *val;
 	sqlite3_stmt *stmt;
 	int cmin, cmax;
 	int ret;
+	int i;
+
+	i = 0;
+
+	cloud = VAL_ALLOC(VT_LIST);
 
 	/* pacify gcc */
 	cmin = 0;
 	cmax = 0;
 
-	memset(&vv, 0, sizeof(vv));
-
-	vv.type = VT_VARS;
-
 	open_db();
 	SQL(stmt, "SELECT min(cnt), max(cnt) FROM tagcloud");
 	SQL_FOR_EACH(stmt) {
@@ -54,12 +56,16 @@
 		count = SQL_COL_INT(stmt, 1);
 		size  = __tag_size(count, cmin, cmax);
 
-		vv.vars[0] = VAR_ALLOC_STR("name", tag);
-		vv.vars[1] = VAR_ALLOC_INT("size", size);
-		vv.vars[2] = VAR_ALLOC_INT("count", count);
+		val = VAL_ALLOC(VT_NV);
 
-		ASSERT(!var_append(&req->vars, "tagcloud", &vv));
+		VAL_SET_NVSTR(val, "name", xstrdup(tag));
+		VAL_SET_NVINT(val, "size", size);
+		VAL_SET_NVINT(val, "count", count);
+
+		VAL_SET_LIST(cloud, i++, val);
 	}
+
+	VAR_SET(&req->vars, "tagcloud", cloud);
 }
 
 static void archive(struct req *req)
@@ -70,13 +76,15 @@
 		"December",
 	};
 
-	struct var_val vv;
+	struct val *archives;
+	struct val *val;
 	sqlite3_stmt *stmt;
 	int ret;
+	int i;
 
-	memset(&vv, 0, sizeof(vv));
+	i = 0;
 
-	vv.type = VT_VARS;
+	archives = VAL_ALLOC(VT_LIST);
 
 	open_db();
 	SQL(stmt, "SELECT DISTINCT STRFTIME(\"%Y%m\", time) AS t FROM posts ORDER BY t DESC");
@@ -89,11 +97,14 @@
 		snprintf(buf, sizeof(buf), "%s %d", months[(archid % 100) - 1],
 			 archid / 100);
 
-		vv.vars[0] = VAR_ALLOC_INT("name", archid);
-		vv.vars[1] = VAR_ALLOC_STR("desc", buf);
+		val = VAL_ALLOC(VT_NV);
+		VAL_SET_NVINT(val, "name", archid);
+		VAL_SET_NVSTR(val, "desc", xstrdup(buf));
 
-		ASSERT(!var_append(&req->vars, "archives", &vv));
+		VAL_SET_LIST(archives, i++, val);
 	}
+
+	VAR_SET(&req->vars, "archives", archives);
 }
 
 void sidebar(struct req *req)
--- a/story.c	Thu May 30 23:52:40 2013 +0000
+++ b/story.c	Wed Jun 05 03:02:07 2013 +0000
@@ -11,32 +11,24 @@
 #include "error.h"
 #include "utils.h"
 
-static void __store_title(struct vars *vars, const char *title)
-{
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-        vv.type = VT_STR;
-        vv.str  = xstrdup(title);
-        ASSERT(vv.str);
-
-        ASSERT(!var_append(vars, "title", &vv));
-}
-
 static int __load_post(struct req *req, int p, bool preview)
 {
-	int ret;
-
-	ret = load_post(req, p, "title", preview);
+	struct val *posts;
+	struct val *post;
 
-	if (ret) {
-		LOG("failed to load post #%d: %s (%d)%s", p, strerror(ret),
-		    ret, preview ? " preview" : "");
-		__store_title(&req->vars, "not found");
+	post = load_post(req, p, "title", preview);
+	if (!post) {
+		LOG("failed to load post #%d: %s (%d)%s", p, "XXX",
+		    -1, preview ? " preview" : "");
+
+		VAR_SET_STR(&req->vars, "title", xstrdup("not found"));
+	} else {
+		posts = VAR_LOOKUP_VAL(&req->vars, "posts");
+
+		VAL_SET_LIST(posts, 0, post);
 	}
 
-	return ret;
+	return post ? 0 : ENOENT;
 }
 
 int blahg_story(struct req *req, int p, bool preview)
--- a/tag.c	Thu May 30 23:52:40 2013 +0000
+++ b/tag.c	Wed Jun 05 03:02:07 2013 +0000
@@ -57,52 +57,32 @@
 
 static void __store_title(struct vars *vars, const char *title)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-        vv.type = VT_STR;
-        vv.str  = xstrdup(title);
-        ASSERT(vv.str);
-
-        ASSERT(!var_append(vars, "title", &vv));
+	VAR_SET_STR(vars, "title", xstrdup(title));
 }
 
 static void __store_tag(struct vars *vars, const char *tag)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-        vv.type = VT_STR;
-        vv.str  = xstrdup(tag);
-        ASSERT(vv.str);
-
-        ASSERT(!var_append(vars, "tagid", &vv));
+	VAR_SET_STR(vars, "tagid", xstrdup(tag));
 }
 
 static void __store_pages(struct vars *vars, int page)
 {
-	struct var_val vv;
-
-	memset(&vv, 0, sizeof(vv));
-
-	vv.type = VT_INT;
-	vv.i    = page + 1;
-
-	ASSERT(!var_append(vars, "prevpage", &vv));
-
-	vv.type = VT_INT;
-	vv.i    = page - 1;
-
-	ASSERT(!var_append(vars, "nextpage", &vv));
+	VAR_SET_INT(vars, "prevpage", page + 1);
+	VAR_SET_INT(vars, "nextpage", page - 1);
 }
 
 static void __load_posts_tag(struct req *req, int page, const char *tag,
 			     bool istag, int nstories)
 {
 	sqlite3_stmt *stmt;
+	struct val *posts;
+	struct val *val;
 	int ret;
+	int i;
+
+	i = 0;
+
+	posts = VAR_LOOKUP_VAL(&req->vars, "posts");
 
 	open_db();
 	if (istag)
@@ -121,8 +101,11 @@
 
 		postid = SQL_COL_INT(stmt, 0);
 
-		if (load_post(req, postid, NULL, false))
+		val = load_post(req, postid, NULL, false);
+		if (!val)
 			continue;
+
+		VAL_SET_LIST(posts, i++, val);
 	}
 }
 
--- a/template.y	Thu May 30 23:52:40 2013 +0000
+++ b/template.y	Wed Jun 05 03:02:07 2013 +0000
@@ -38,107 +38,135 @@
 	return ret;
 }
 
-static void __foreach_vars(struct vars *vars, struct var_val *vv)
+static int __expand_val(struct avl_node *node, void *data)
 {
-	int j, k;
-
-	for (j = 0; j < VAR_MAX_VARS_SIZE; j++) {
-		if (!vv->vars[j])
-			continue;
+	struct vars *vars = data;
+	struct val_item *val_item;
 
-		for (k = 0; k < VAR_MAX_ARRAY_SIZE; k++) {
-			if (vv->vars[j]->val[k].type == VT_NIL)
-				continue;
+	val_item = container_of(node, struct val_item, tree);
 
-			var_append(vars, vv->vars[j]->name,
-				   &vv->vars[j]->val[k]);
-		}
-	}
+	VAR_SET(vars, val_item->key.name, val_getref(val_item->val));
+
+	return 0;
 }
 
-char *foreach(struct req *req, char *var, char *tmpl)
+struct wrap_state {
+	struct req *req;
+	char *varname;
+	char *tmpl;
+
+	char *out;
+};
+
+static int __foreach_val(struct avl_node *node, void *arg)
+{
+	struct wrap_state *state = arg;
+	struct req *req = state->req;
+	struct val *val;
+	char *ret;
+
+	val = container_of(node, struct val_item, tree)->val;
+
+	vars_scope_push(&req->vars);
+
+	switch (val->type) {
+		case VT_STR:
+		case VT_INT:
+			VAR_SET(&req->vars, state->varname, val_getref(val));
+			break;
+		case VT_NV:
+			avl_for_each(&val->tree, __expand_val, &req->vars,
+				     NULL, NULL);
+			break;
+		default:
+			fprintf(stderr, "XXX %p\n", val);
+			val_dump(val, 0);
+			ASSERT(0);
+			break;
+	}
+
+	ret = render_template(req, state->tmpl);
+
+	state->out = concat(state->out, ret);
+
+	vars_scope_pop(&req->vars);
+
+	return !ret;
+}
+
+char *foreach(struct req *req, char *varname, char *tmpl)
 {
 	struct vars *vars = &req->vars;
-	struct var *v;
+	struct wrap_state state = {
+		.req = req,
+		.varname = varname,
+		.tmpl = tmpl,
+	};
+	struct var *var;
+	struct val *val;
 	char *ret;
-	char *tmp;
-	int i;
-
-	ret = xstrdup("");
 
-	v = var_lookup(vars, var);
-	if (!v)
-		return ret;
-
-	for (i = 0; i < VAR_MAX_ARRAY_SIZE; i++) {
-		struct var_val *vv = &v->val[i];
-
-		if (vv->type == VT_NIL)
-			continue;
-
-		vars_scope_push(vars);
+	var = var_lookup(vars, varname);
+	if (!var)
+		return xstrdup("");
 
-		switch (vv->type) {
-			case VT_NIL:
-				ASSERT(0);
-				break;
-			case VT_VARS:
-				__foreach_vars(vars, vv);
-				break;
-			case VT_STR:
-			case VT_INT:
-				var_append(vars, var, vv);
-				break;
-		}
+	val = var->val;
+	if (!val)
+		return xstrdup("");
 
-		tmp = render_template(req, tmpl);
-
-		ret = concat(ret, tmp);
-
-		vars_scope_pop(vars);
+	if (val->type == VT_LIST) {
+		state.out = NULL;
+		avl_for_each(&val->tree, __foreach_val, &state, NULL, NULL);
+		ret = state.out;
+	} else {
+		ret = NULL;
+		LOG("%s called with '%s' which has type %d", __func__,
+		    varname, val->type);
+		ASSERT(0);
 	}
 
 	return ret;
 }
 
-static char *print_var_val(struct var_val *vv)
+static char *print_val(struct val *val)
 {
 	char buf[32];
-	char *tmp;
+	const char *tmp;
 
 	tmp = NULL;
 
-	switch (vv->type) {
-		case VT_NIL:
-			tmp = "NIL";
-			break;
+	switch (val->type) {
 		case VT_STR:
-			tmp = vv->str;
+			tmp = val->str;
 			break;
 		case VT_INT:
-			snprintf(buf, sizeof(buf), "%lu", vv->i);
+			snprintf(buf, sizeof(buf), "%lu", val->i);
 			tmp = buf;
 			break;
-		case VT_VARS:
-			tmp = "VARS";
+		case VT_LIST:
+			tmp = "LIST";
+			break;
+		case VT_NV:
+			tmp = "NV";
 			break;
 	}
 
 	return xstrdup(tmp);
 }
 
-static char *print_var(struct var *v)
+static char *print_var(struct var *var)
 {
-	return print_var_val(&v->val[0]);
+	return print_val(var->val);
 }
 
 static char *pipeline(struct req *req, char *var, struct pipeline *pipe)
 {
 	struct list_head line;
-	struct var_val *vv;
+	struct val *val;
 	struct var *v;
 	struct pipeline *cur;
 	struct pipeline *tmp;
+	char *out;
 
 	v = var_lookup(&req->vars, var);
 	if (!v)
@@ -150,14 +178,18 @@
 	pipe->pipe.prev->next = &line;
 	pipe->pipe.prev = &line;
 
-	vv = &v->val[0];
+	val = val_getref(v->val);
 
 	list_for_each_entry_safe(cur, tmp, &line, pipe)
-		vv = cur->stage->f(vv);
+		val = cur->stage->f(val);
 
 	pipeline_destroy(&line);
 
-	return print_var_val(vv);
+	out = print_val(val);
+
+	val_putref(val);
+
+	return out;
 }
 %}
 
@@ -216,7 +248,7 @@
 	 | pipe				{ $$ = $1; }
          ;
 
-pipe : '|' WORD				{ $$ = pipestage($2); }
+pipe : '|' WORD				{ $$ = pipestage($2); free($2); }
      ;
 
 %%
--- a/vars.c	Thu May 30 23:52:40 2013 +0000
+++ b/vars.c	Wed Jun 05 03:02:07 2013 +0000
@@ -9,7 +9,8 @@
 #include "utils.h"
 
 static umem_cache_t *var_cache;
-static umem_cache_t *var_val_cache;
+static umem_cache_t *val_cache;
+static umem_cache_t *val_item_cache;
 
 void init_var_subsys()
 {
@@ -17,22 +18,45 @@
 				      NULL, NULL, NULL, NULL, NULL, 0);
 	ASSERT(var_cache);
 
-	var_val_cache = umem_cache_create("var-val-cache", sizeof(struct var_val),
-					  0, NULL, NULL, NULL, NULL, NULL, 0);
-	ASSERT(var_val_cache);
+	val_cache = umem_cache_create("val-cache", sizeof(struct val),
+				      0, NULL, NULL, NULL, NULL, NULL, 0);
+	ASSERT(val_cache);
+
+	val_item_cache = umem_cache_create("val-item-cache",
+					   sizeof(struct val_item), 0, NULL,
+					   NULL, NULL, NULL, NULL, 0);
+	ASSERT(val_item_cache);
 }
 
-static int cmp(struct avl_node *aa, struct avl_node *ab)
+static int cmp_var(struct avl_node *aa, struct avl_node *ab)
 {
 	struct var *a = container_of(aa, struct var, tree);
 	struct var *b = container_of(ab, struct var, tree);
 
-	return strncmp(a->name, b->name, VAR_MAX_VAR_NAME);
+	return strncmp(a->name, b->name, VAR_NAME_MAXLEN);
+}
+
+static int cmp_val_list(struct avl_node *aa, struct avl_node *ab)
+{
+	struct val_item *a = container_of(aa, struct val_item, tree);
+	struct val_item *b = container_of(ab, struct val_item, tree);
+
+	if (a->key.i < b->key.i)
+		return -1;
+	return (a->key.i != b->key.i);
+}
+
+static int cmp_val_nv(struct avl_node *aa, struct avl_node *ab)
+{
+	struct val_item *a = container_of(aa, struct val_item, tree);
+	struct val_item *b = container_of(ab, struct val_item, tree);
+
+	return strncmp(a->key.name, b->key.name, VAR_VAL_KEY_MAXLEN);
 }
 
 static void __init_scope(struct vars *vars)
 {
-	AVL_ROOT_INIT(&vars->scopes[vars->cur], cmp, 0);
+	AVL_ROOT_INIT(&vars->scopes[vars->cur], cmp_var, 0);
 }
 
 static void __free_scope(struct avl_root *root)
@@ -44,7 +68,7 @@
 
 		avl_remove_node(root, node);
 
-		var_putref(v);
+		var_free(v);
 	}
 }
 
@@ -90,7 +114,7 @@
 	struct avl_node *node;
 	int scope;
 
-	strncpy(key.name, name, VAR_MAX_VAR_NAME);
+	strncpy(key.name, name, VAR_NAME_MAXLEN);
 
 	for (scope = vars->cur; scope >= 0; scope--) {
 		node = avl_find_node(&vars->scopes[scope], &key.tree);
@@ -105,75 +129,193 @@
 {
 	struct var *v;
 
-	ASSERT(strlen(name) < VAR_MAX_VAR_NAME);
+	ASSERT(strlen(name) < VAR_NAME_MAXLEN);
 
 	v = umem_cache_alloc(var_cache, 0);
 	if (!v)
 		return NULL;
 
 	memset(v, 0, sizeof(struct var));
-	strncpy(v->name, name, VAR_MAX_VAR_NAME);
-
-	v->refcnt = 1;
+	strncpy(v->name, name, VAR_NAME_MAXLEN);
 
 	return v;
 }
 
-void var_free(struct var *v)
+void var_free(struct var *var)
+{
+	val_putref(var->val);
+
+	umem_cache_free(var_cache, var);
+}
+
+static void __val_init(struct val *val, enum val_type type)
 {
-	int i, j;
+	val->type = type;
+
+	switch (type) {
+		case VT_LIST:
+			AVL_ROOT_INIT(&val->tree, cmp_val_list, 0);
+			break;
+		case VT_NV:
+			AVL_ROOT_INIT(&val->tree, cmp_val_nv, 0);
+			break;
+		case VT_INT:
+			val->i = 0;
+			break;
+		case VT_STR:
+			val->str = NULL;
+			break;
+		default:
+			ASSERT(0);
+	}
+}
 
-	ASSERT(v);
-	ASSERT3U(v->refcnt, ==, 0);
+static void __val_cleanup(struct val *val)
+{
+	switch (val->type) {
+		case VT_INT:
+			break;
+		case VT_STR:
+			free(val->str);
+			break;
+		case VT_NV:
+		case VT_LIST:
+			while (val->tree.root) {
+				struct val_item *vi;
+				struct avl_node *n;
+
+				n = avl_find_minimum(&val->tree);
+
+				avl_remove_node(&val->tree, n);
+
+				vi = container_of(n, struct val_item, tree);
+
+				val_putref(vi->val);
+
+				umem_cache_free(val_item_cache, vi);
+			}
 
-	for (i = 0; i < VAR_MAX_ARRAY_SIZE; i++) {
-		switch (v->val[i].type) {
-			case VT_NIL:
-			case VT_INT:
-				break;
-			case VT_STR:
-				break;
-			case VT_VARS:
-				for (j = 0; j < VAR_MAX_VARS_SIZE; j++)
-					var_putref(v->val[i].vars[j]);
-				break;
-		}
+			break;
+		default:
+			ASSERT(0);
+	}
+}
+
+struct val *val_alloc(enum val_type type)
+{
+	struct val *val;
+
+	val = umem_cache_alloc(val_cache, 0);
+	if (!val)
+		return val;
+
+	val->refcnt = 1;
+
+	__val_init(val, type);
+
+	return val;
+}
+
+void val_free(struct val *val)
+{
+	ASSERT(val);
+	ASSERT3U(val->refcnt, ==, 0);
+
+	__val_cleanup(val);
+
+	umem_cache_free(val_cache, val);
+}
+
+static int __val_set_val(struct val *val, enum val_type type, char *name,
+			 uint64_t idx, struct val *sub)
+{
+	struct val_item *vi;
+
+	if ((type != VT_NV) && (type != VT_LIST))
+		return EINVAL;
+
+	vi = umem_cache_alloc(val_item_cache, 0);
+	if (!vi)
+		return ENOMEM;
+
+	vi->val = sub;
+
+	if (type == VT_NV)
+		strncpy(vi->key.name, name, VAR_VAL_KEY_MAXLEN);
+	else if (type == VT_LIST)
+		vi->key.i = idx;
+
+	if (val->type != type) {
+		__val_cleanup(val);
+		__val_init(val, type);
 	}
 
-	umem_cache_free(var_cache, v);
+	avl_insert_node(&val->tree, &vi->tree);
+
+	return 0;
+}
+
+int val_set_list(struct val *val, uint64_t idx, struct val *sub)
+{
+	return __val_set_val(val, VT_LIST, NULL, idx, sub);
+}
+
+int val_set_nv(struct val *val, char *name, struct val *sub)
+{
+	if (!name)
+		return EINVAL;
+
+	return __val_set_val(val, VT_NV, name, 0, sub);
 }
 
-struct var_val *var_val_alloc()
-{
-	return umem_cache_alloc(var_val_cache, 0);
+#define DEF_VAL_SET(fxn, vttype, valelem, ctype)		\
+int val_set_nv##fxn(struct val *val, char *name, ctype v)	\
+{								\
+	struct val *sub;					\
+	int ret;						\
+								\
+	sub = val_alloc(vttype);				\
+	if (!sub)						\
+		return ENOMEM;					\
+								\
+	sub->valelem = v;					\
+								\
+	ret = val_set_nv(val, name, sub);			\
+	if (ret)						\
+		val_putref(sub);				\
+								\
+	return ret;						\
+}								\
+								\
+int val_set_##fxn(struct val *val, ctype v)			\
+{								\
+	__val_cleanup(val);					\
+								\
+	val->type = vttype;					\
+	val->valelem = v;					\
+								\
+	return 0;						\
 }
 
-void var_val_free(struct var_val *vv)
-{
-	umem_cache_free(var_val_cache, vv);
-}
+DEF_VAL_SET(int, VT_INT, i, uint64_t)
+DEF_VAL_SET(str, VT_STR, str, char *)
 
-int var_append(struct vars *vars, const char *name, struct var_val *vv)
+int var_set(struct vars *vars, const char *name, struct val *val)
 {
 	struct var key;
 	struct avl_node *node;
 	struct var *v;
-	bool shouldfree;
-	int i, j;
 
-	shouldfree = false;
-	strncpy(key.name, name, VAR_MAX_VAR_NAME);
+	strncpy(key.name, name, VAR_NAME_MAXLEN);
 
 	node = avl_find_node(&vars->scopes[vars->cur], &key.tree);
 	if (!node) {
-		shouldfree = true;
-
 		v = var_alloc(name);
 		if (!v)
 			return ENOMEM;
 
 		if (avl_insert_node(&vars->scopes[vars->cur], &v->tree)) {
-			var_putref(v);
+			var_free(v);
 			return EEXIST;
 		}
 
@@ -182,69 +324,71 @@
 
 	v = container_of(node, struct var, tree);
 
-	for (i = 0; i < VAR_MAX_ARRAY_SIZE; i++) {
-		if (v->val[i].type != VT_NIL)
-			continue;
+	if (v->val)
+		val_putref(v->val);
+
+	v->val = val;
 
-		if (vv->type == VT_VARS)
-			for (j = 0; j < VAR_MAX_VARS_SIZE; j++)
-				var_getref(vv->vars[j]);
+	return 0;
+}
+
+static void __dump_val(struct avl_node *node, int indent, enum val_type type)
+{
+	struct val_item *vi;
 
-		v->val[i] = *vv;
+	vi = container_of(node, struct val_item, tree);
 
-		return 0;
-	}
+	if (type == VT_LIST)
+		fprintf(stderr, "%*s [%lu] = ", indent, "", vi->key.i);
+	else
+		fprintf(stderr, "%*s [%s] = ", indent, "", vi->key.name);
 
-	if (shouldfree)
-		var_putref(v);
+	val_dump(vi->val, indent + 4);
+}
 
-	return E2BIG;
+static int __dump_val_list(struct avl_node *node, void *data)
+{
+	__dump_val(node, (uintptr_t) data, VT_LIST);
+	return 0;
 }
 
-void var_val_dump(struct var_val *vv, int idx, int indent)
+static int __dump_val_nv(struct avl_node *node, void *data)
 {
-	int i;
-
-	fprintf(stderr, "%*s   [%02d] ", indent, "", idx);
+	__dump_val(node, (uintptr_t) data, VT_NV);
+	return 0;
+}
 
-	switch (vv->type) {
-		case VT_NIL:
-			fprintf(stderr, "NIL\n");
-			break;
+void val_dump(struct val *val, int indent)
+{
+	switch (val->type) {
 		case VT_STR:
-			fprintf(stderr, "'%s'\n", vv->str);
+			fprintf(stderr, "%*s'%s'\n", indent, "", val->str);
 			break;
 		case VT_INT:
-			fprintf(stderr, "%lu\n", vv->i);
+			fprintf(stderr, "%*s%lu\n", indent, "", val->i);
 			break;
-		case VT_VARS:
-			fprintf(stderr, "VARS\n");
-			for (i = 0; i < VAR_MAX_VARS_SIZE; i++)
-				if (vv->vars[i])
-					var_dump(vv->vars[i], indent + 6);
+		case VT_LIST:
+			fprintf(stderr, "\n");
+			avl_for_each(&val->tree, __dump_val_list,
+				     (void*) (uintptr_t) (indent + 4), NULL, NULL);
+			break;
+		case VT_NV:
+			fprintf(stderr, "\n");
+			avl_for_each(&val->tree, __dump_val_nv,
+				     (void*) (uintptr_t) (indent + 4), NULL, NULL);
 			break;
 		default:
-			fprintf(stderr, "Unknown type %d\n", vv->type);
+			fprintf(stderr, "Unknown type %d\n", val->type);
 			break;
 	}
 }
 
-void var_dump(struct var *v, int indent)
-{
-	int i;
-
-	fprintf(stderr, "%*s-> '%s'\n", indent, "", v->name);
-
-	for (i = 0; i < VAR_MAX_ARRAY_SIZE; i++)
-		if (v->val[i].type != VT_NIL)
-			var_val_dump(&v->val[i], i, indent);
-}
-
 static int __vars_dump(struct avl_node *node, void *data)
 {
 	struct var *v = container_of(node, struct var, tree);
 
-	var_dump(v, 0);
+	fprintf(stderr, "-> '%s'\n", v->name);
+	val_dump(v->val, 4);
 
 	return 0;
 }
--- a/vars.h	Thu May 30 23:52:40 2013 +0000
+++ b/vars.h	Wed Jun 05 03:02:07 2013 +0000
@@ -5,29 +5,38 @@
 #include "avl.h"
 #include "utils.h"
 
-enum var_type {
-	VT_NIL = 0,	/* invalid */
+enum val_type {
+	VT_INT = 0,	/* 64-bit uint */
 	VT_STR,		/* null-terminated string */
-	VT_INT,		/* 64-bit uint */
-	VT_VARS,	/* name-value set of variables */
+	VT_NV,		/* name-value set of values */
+	VT_LIST,	/* array of values */
 };
 
-struct var;
+struct val;
 
-struct var_val {
-	enum var_type type;
+struct val_item {
+	struct avl_node tree;
 	union {
-		struct var *vars[VAR_MAX_VARS_SIZE];
+		uint64_t i;
+		char name[VAR_VAL_KEY_MAXLEN];
+	} key;
+	struct val *val;
+};
+
+struct val {
+	enum val_type type;
+	int refcnt;
+	union {
+		uint64_t i;
 		char *str;
-		uint64_t i;
+		struct avl_root tree;
 	};
 };
 
 struct var {
 	struct avl_node tree;
-	int refcnt;
-	char name[VAR_MAX_VAR_NAME];
-	struct var_val val[VAR_MAX_ARRAY_SIZE];
+	char name[VAR_NAME_MAXLEN];
+	struct val *val;
 };
 
 struct vars {
@@ -47,102 +56,94 @@
 extern void var_free(struct var *v);
 extern void var_dump(struct var *v, int indent);
 extern struct var *var_lookup(struct vars *vars, const char *name);
-extern int var_append(struct vars *vars, const char *name, struct var_val *vv);
+extern int var_set(struct vars *vars, const char *name, struct val *val);
 
-extern struct var_val *var_val_alloc();
-extern void var_val_free(struct var_val *vv);
-extern void var_val_dump(struct var_val *vv, int idx, int indent);
+extern struct val *val_alloc(enum val_type type);
+extern void val_free(struct val *v);
+extern int val_set_int(struct val *val, uint64_t v);
+extern int val_set_str(struct val *val, char *v);
+extern int val_set_list(struct val *val, uint64_t idx, struct val *sub);
+extern int val_set_nv(struct val *val, char *name, struct val *sub);
+extern int val_set_nvint(struct val *val, char *name, uint64_t v);
+extern int val_set_nvstr(struct val *val, char *name, char *v);
+extern void val_dump(struct val *v, int indent);
 
-static inline struct var *var_getref(struct var *v)
+static inline struct val *val_getref(struct val *vv)
 {
-	if (!v)
+	if (!vv)
 		return NULL;
 
-	ASSERT3U(v->refcnt, >=, 1);
-
-	v->refcnt++;
-	return v;
-}
+	ASSERT3U(vv->refcnt, >=, 1);
 
-static inline void var_putref(struct var *v)
-{
-	if (!v)
-		return;
-
-	ASSERT3S(v->refcnt, >=, 1);
-
-	v->refcnt--;
-
-	if (!v->refcnt)
-		var_free(v);
+	vv->refcnt++;
+	return vv;
 }
 
-static inline struct var *var_alloc_int(const char *name, uint64_t val)
+static inline void val_putref(struct val *vv)
 {
-	struct var *v;
+	if (!vv)
+		return;
 
-	v = var_alloc(name);
-	if (!v)
-		return NULL;
+	ASSERT3S(vv->refcnt, >=, 1);
 
-	v->val[0].type = VT_INT;
-	v->val[0].i    = val;
+	vv->refcnt--;
 
-	return v;
+	if (!vv->refcnt)
+		val_free(vv);
 }
 
-static inline struct var *var_alloc_str(const char *name, const char *val)
-{
-	struct var *v;
-
-	v = var_alloc(name);
-	if (!v)
-		return NULL;
-
-	v->val[0].type = VT_STR;
-	v->val[0].str  = xstrdup(val);
-
-	if (!v->val[0].str) {
-		var_putref(v);
-		return NULL;
-	}
-
-	return v;
-}
-
-#define __VAR_ALLOC(n, v, t)			\
+#define VAR_LOOKUP_VAL(vars, n)			\
 	({					\
 		struct var *_x;			\
-		_x = var_alloc_##t((n), (v));	\
+		_x = var_lookup((vars), (n));	\
+		ASSERT(_x);			\
+		val_getref(_x->val);		\
+	})
+#define VAR_SET(vars, n, val)		ASSERT0(var_set((vars), (n), (val)))
+
+#define VAR_SET_STR(vars, n, v)			\
+	do {					\
+		struct val *_x;			\
+		_x = VAL_ALLOC_STR(v);		\
+		VAR_SET((vars), (n), _x);	\
+	} while (0)
+
+#define VAR_SET_INT(vars, n, v)			\
+	do {					\
+		struct val *_x;			\
+		_x = VAL_ALLOC_INT(v);		\
+		VAR_SET((vars), (n), _x);	\
+	} while (0)
+
+#define VAL_ALLOC(t)				\
+	({					\
+		struct val *_x;			\
+		_x = val_alloc(t);		\
 		ASSERT(_x);			\
 		_x;				\
 	})
 
-#define VAR_ALLOC_INT(n, v)	__VAR_ALLOC((n), (v), int)
-#define VAR_ALLOC_STR(n, v)	__VAR_ALLOC((n), (v), str)
-
-static inline struct var_val *var_val_alloc_str(char *str)
-{
-	struct var_val *vv;
-
-	vv = var_val_alloc();
-	if (!vv)
-		return NULL;
-
-	vv->type = VT_STR;
-	vv->str = str;
-
-	return vv;
-}
-
-#define __VAR_VAL_ALLOC(v, t)			\
+#define VAL_ALLOC_STR(v)			\
 	({					\
-		struct var_val *_x;		\
-		_x = var_val_alloc_##t(v);	\
-		ASSERT(_x);			\
+		struct val *_x;			\
+		_x = VAL_ALLOC(VT_STR);		\
+		VAL_SET_STR(_x, v);		\
 		_x;				\
 	})
 
-#define VAR_VAL_ALLOC_STR(v)	__VAR_VAL_ALLOC((v), str)
+#define VAL_ALLOC_INT(v)			\
+	({					\
+		struct val *_x;			\
+		_x = VAL_ALLOC(VT_INT);		\
+		VAL_SET_INT(_x, (v));		\
+		_x;				\
+	})
+
+#define VAL_SET_INT(val, v)		ASSERT0(val_set_int((val), (v)))
+#define VAL_SET_STR(val, v)		ASSERT0(val_set_str((val), (v)))
+#define VAL_SET_NVINT(val, n, v)	ASSERT0(val_set_nvint((val), (n), (v)))
+#define VAL_SET_NVSTR(val, n, v)	ASSERT0(val_set_nvstr((val), (n), (v)))
+#define VAL_SET_NV(val, n, v)		ASSERT0(val_set_nv((val), (n), (v)))
+#define VAL_SET_LIST(val, idx, v)	ASSERT0(val_set_list((val), (idx), (v)))
 
 #endif