Răsfoiți Sursa

core: utils - added simple expression evaluations with strings and numbers

- MIT license, import from github.com/miconda/snexpr
Daniel-Constantin Mierla 3 ani în urmă
părinte
comite
c19583117d
1 a modificat fișierele cu 1393 adăugiri și 0 ștergeri
  1. 1393 0
      src/core/utils/snexpr.h

+ 1393 - 0
src/core/utils/snexpr.h

@@ -0,0 +1,1393 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2016 Serge Zaitsev
+ * Copyright (c) 2022 Daniel-Constantin Mierla
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _SNEXPR_H_
+#define _SNEXPR_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <ctype.h> /* for isspace */
+#include <limits.h>
+#include <math.h> /* for pow */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define SNEXPR_TOP (1 << 0)
+#define SNEXPR_TOPEN (1 << 1)
+#define SNEXPR_TCLOSE (1 << 2)
+#define SNEXPR_TNUMBER (1 << 3)
+#define SNEXPR_TSTRING (1 << 4)
+#define SNEXPR_TWORD (1 << 5)
+#define SNEXPR_TDEFAULT (SNEXPR_TOPEN | SNEXPR_TNUMBER | SNEXPR_TSTRING | SNEXPR_TWORD)
+
+#define SNEXPR_UNARY (1 << 16)
+#define SNEXPR_COMMA (1 << 17)
+#define SNEXPR_EXPALLOC (1 << 18)
+#define SNEXPR_VALALLOC (1 << 19)
+#define SNEXPR_VALASSIGN (1 << 20)
+
+
+/*
+ * Simple expandable vector implementation
+ */
+static int sne_vec_expand(char **buf, int *length, int *cap, int memsz)
+{
+	if(*length + 1 > *cap) {
+		void *ptr;
+		int n = (*cap == 0) ? 1 : *cap << 1;
+		ptr = realloc(*buf, n * memsz);
+		if(ptr == NULL) {
+			return -1; /* allocation failed */
+		}
+		*buf = (char *)ptr;
+		*cap = n;
+	}
+	return 0;
+}
+#define sne_vec(T)   \
+	struct       \
+	{            \
+		T *buf;  \
+		int len; \
+		int cap; \
+	}
+#define sne_vec_init() \
+	{              \
+		NULL, 0, 0 \
+	}
+#define sne_vec_len(v) ((v)->len)
+#define sne_vec_unpack(v) \
+	(char **)&(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf)
+#define sne_vec_push(v, val) \
+	sne_vec_expand(sne_vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0)
+#define sne_vec_nth(v, i) (v)->buf[i]
+#define sne_vec_peek(v) (v)->buf[(v)->len - 1]
+#define sne_vec_pop(v) (v)->buf[--(v)->len]
+#define sne_vec_free(v) (free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0)
+#define sne_vec_foreach(v, var, iter)                                             \
+	if((v)->len > 0)                                                          \
+		for((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \
+				++(iter))
+
+/*
+ * Expression data types
+ */
+struct snexpr;
+struct snexpr_func;
+struct snexpr_var;
+
+enum snexpr_type
+{
+	SNE_OP_UNKNOWN,
+	SNE_OP_UNARY_MINUS,
+	SNE_OP_UNARY_LOGICAL_NOT,
+	SNE_OP_UNARY_BITWISE_NOT,
+
+	SNE_OP_POWER,
+	SNE_OP_DIVIDE,
+	SNE_OP_MULTIPLY,
+	SNE_OP_REMAINDER,
+
+	SNE_OP_PLUS,
+	SNE_OP_MINUS,
+
+	SNE_OP_SHL,
+	SNE_OP_SHR,
+
+	SNE_OP_LT,
+	SNE_OP_LE,
+	SNE_OP_GT,
+	SNE_OP_GE,
+	SNE_OP_EQ,
+	SNE_OP_NE,
+
+	SNE_OP_BITWISE_AND,
+	SNE_OP_BITWISE_OR,
+	SNE_OP_BITWISE_XOR,
+
+	SNE_OP_LOGICAL_AND,
+	SNE_OP_LOGICAL_OR,
+
+	SNE_OP_ASSIGN,
+	SNE_OP_COMMA,
+
+	SNE_OP_CONSTNUM,
+	SNE_OP_CONSTSTZ,
+	SNE_OP_VAR,
+	SNE_OP_FUNC,
+};
+
+static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5, 5, 6, 7,
+		8, 9, 10, 11, 12, 0, 0, 0, 0};
+
+typedef sne_vec(struct snexpr) sne_vec_expr_t;
+typedef void (*snexprfn_cleanup_t)(struct snexpr_func *f, void *context);
+typedef struct snexpr* (*snexprfn_t)(struct snexpr_func *f, sne_vec_expr_t *args, void *context);
+
+typedef struct snexpr* (*snexternval_cbf_t)(char *vname);
+
+static snexternval_cbf_t _snexternval_cbf = NULL;
+
+struct snexpr
+{
+	enum snexpr_type type;
+	unsigned int eflags;
+	union
+	{
+		struct
+		{
+			float nval;
+		} num;
+		struct
+		{
+			char *sval;
+		} stz;
+		struct
+		{
+			struct snexpr_var *vref;
+		} var;
+		struct
+		{
+			sne_vec_expr_t args;
+		} op;
+		struct
+		{
+			struct snexpr_func *f;
+			sne_vec_expr_t args;
+			void *context;
+		} func;
+	} param;
+};
+
+#define snexpr_init()                \
+	{                                \
+		.type = (enum snexpr_type)0, \
+		.eflags = 0u                 \
+	}
+
+struct snexpr_string
+{
+	const char *s;
+	int n;
+};
+struct snexpr_arg
+{
+	int oslen;
+	int eslen;
+	sne_vec_expr_t args;
+};
+
+typedef sne_vec(struct snexpr_string) sne_vec_str_t;
+typedef sne_vec(struct snexpr_arg) sne_vec_arg_t;
+
+static int snexpr_is_unary(enum snexpr_type op)
+{
+	return op == SNE_OP_UNARY_MINUS || op == SNE_OP_UNARY_LOGICAL_NOT
+		   || op == SNE_OP_UNARY_BITWISE_NOT;
+}
+
+static int snexpr_is_binary(enum snexpr_type op)
+{
+	return !snexpr_is_unary(op) && op != SNE_OP_CONSTNUM && op != SNE_OP_CONSTSTZ
+		 && op != SNE_OP_VAR && op != SNE_OP_FUNC && op != SNE_OP_UNKNOWN;
+}
+
+static int snexpr_prec(enum snexpr_type a, enum snexpr_type b)
+{
+	int left = snexpr_is_binary(a) && a != SNE_OP_ASSIGN && a != SNE_OP_POWER
+			   && a != SNE_OP_COMMA;
+	return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]);
+}
+
+#define isfirstvarchr(c) \
+	(((unsigned char)c >= '@' && c != '^' && c != '|') || c == '$')
+#define isvarchr(c)                                                            \
+	(((unsigned char)c >= '@' && c != '^' && c != '|') || c == '$' || c == '#' \
+			|| (c >= '0' && c <= '9'))
+
+static struct
+{
+	const char *s;
+	const enum snexpr_type op;
+} OPS[] = {
+		{"-u", SNE_OP_UNARY_MINUS},
+		{"!u", SNE_OP_UNARY_LOGICAL_NOT},
+		{"^u", SNE_OP_UNARY_BITWISE_NOT},
+		{"**", SNE_OP_POWER},
+		{"*", SNE_OP_MULTIPLY},
+		{"/", SNE_OP_DIVIDE},
+		{"%", SNE_OP_REMAINDER},
+		{"+", SNE_OP_PLUS},
+		{"-", SNE_OP_MINUS},
+		{"<<", SNE_OP_SHL},
+		{">>", SNE_OP_SHR},
+		{"<", SNE_OP_LT},
+		{"<=", SNE_OP_LE},
+		{">", SNE_OP_GT},
+		{">=", SNE_OP_GE},
+		{"==", SNE_OP_EQ},
+		{"!=", SNE_OP_NE},
+		{"&", SNE_OP_BITWISE_AND},
+		{"|", SNE_OP_BITWISE_OR},
+		{"^", SNE_OP_BITWISE_XOR},
+		{"&&", SNE_OP_LOGICAL_AND},
+		{"||", SNE_OP_LOGICAL_OR},
+		{"=", SNE_OP_ASSIGN},
+		{",", SNE_OP_COMMA},
+
+		/* These are used by lexer and must be ignored by parser, so we put
+       them at the end */
+		{"-", SNE_OP_UNARY_MINUS},
+		{"!", SNE_OP_UNARY_LOGICAL_NOT},
+		{"^", SNE_OP_UNARY_BITWISE_NOT},
+};
+
+static enum snexpr_type snexpr_op(const char *s, size_t len, int unary)
+{
+	for(unsigned int i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) {
+		if(strlen(OPS[i].s) == len && strncmp(OPS[i].s, s, len) == 0
+				&& (unary == -1 || snexpr_is_unary(OPS[i].op) == unary)) {
+			return OPS[i].op;
+		}
+	}
+	return SNE_OP_UNKNOWN;
+}
+
+static float snexpr_parse_number(const char *s, size_t len)
+{
+	float num = 0;
+	unsigned int frac = 0;
+	unsigned int digits = 0;
+	for(unsigned int i = 0; i < len; i++) {
+		if(s[i] == '.' && frac == 0) {
+			frac++;
+			continue;
+		}
+		if(isdigit(s[i])) {
+			digits++;
+			if(frac > 0) {
+				frac++;
+			}
+			num = num * 10 + (s[i] - '0');
+		} else {
+			return NAN;
+		}
+	}
+	while(frac > 1) {
+		num = num / 10;
+		frac--;
+	}
+	return (digits > 0 ? num : NAN);
+}
+
+/*
+ * Functions
+ */
+struct snexpr_func
+{
+	const char *name;
+	snexprfn_t f;
+	snexprfn_cleanup_t cleanup;
+	size_t ctxsz;
+};
+
+static struct snexpr_func *snexpr_func_find(
+		struct snexpr_func *funcs, const char *s, size_t len)
+{
+	for(struct snexpr_func *f = funcs; f->name; f++) {
+		if(strlen(f->name) == len && strncmp(f->name, s, len) == 0) {
+			return f;
+		}
+	}
+	return NULL;
+}
+
+/*
+ * Variables
+ */
+struct snexpr_var
+{
+	unsigned int evflags;
+	char *name;
+	union
+	{
+		float nval;
+		char *sval;
+	} v;
+	struct snexpr_var *next;
+};
+
+struct snexpr_var_list
+{
+	struct snexpr_var *head;
+};
+
+static struct snexpr_var *snexpr_var_find(
+		struct snexpr_var_list *vars, const char *s, size_t len)
+{
+	struct snexpr_var *v = NULL;
+	if(len == 0 || !isfirstvarchr(*s)) {
+		return NULL;
+	}
+	for(v = vars->head; v; v = v->next) {
+		if(strlen(v->name) == len && strncmp(v->name, s, len) == 0) {
+			return v;
+		}
+	}
+	v = (struct snexpr_var *)calloc(1, sizeof(struct snexpr_var) + len + 1);
+	if(v == NULL) {
+		return NULL; /* allocation failed */
+	}
+	memset(v, 0, sizeof(struct snexpr_var) + len + 1);
+	v->next = vars->head;
+	v->name = (char *)v + sizeof(struct snexpr_var);
+	strncpy(v->name, s, len);
+	v->name[len] = '\0';
+	vars->head = v;
+	return v;
+}
+
+static int to_int(float x)
+{
+	if(isnan(x)) {
+		return 0;
+	} else if(isinf(x) != 0) {
+		return INT_MAX * isinf(x);
+	} else {
+		return (int)x;
+	}
+}
+
+
+static struct snexpr *snexpr_convert_num(float value, unsigned int ctype)
+{
+	struct snexpr *e = (struct snexpr *)malloc(sizeof(struct snexpr));
+	if(e == NULL) {
+		return NULL;
+	}
+	memset(e, 0, sizeof(struct snexpr));
+
+	if(ctype == SNE_OP_CONSTSTZ) {
+		e->eflags |= SNEXPR_EXPALLOC | SNEXPR_VALALLOC;
+		e->type = SNE_OP_CONSTSTZ;
+		asprintf(&e->param.stz.sval, "%g", value);
+		return e;
+	}
+
+	e->eflags |= SNEXPR_EXPALLOC;
+	e->type = SNE_OP_CONSTNUM;
+	e->param.num.nval = value;
+	return e;
+}
+
+static struct snexpr *snexpr_convert_stzl(char *value, size_t len, unsigned int ctype)
+{
+	struct snexpr *e = NULL;
+	if(value==NULL) {
+		return NULL;
+	}
+	e = (struct snexpr *)malloc(sizeof(struct snexpr));
+	if(e == NULL) {
+		return NULL;
+	}
+	memset(e, 0, sizeof(struct snexpr));
+
+	if(ctype == SNE_OP_CONSTNUM) {
+		e->eflags |= SNEXPR_EXPALLOC;
+		e->type = SNE_OP_CONSTNUM;
+		e->param.num.nval = snexpr_parse_number(value, len);
+		return e;
+	}
+
+	e->param.stz.sval = (char *)malloc(len + 1);
+	if(e->param.stz.sval == NULL) {
+		free(e);
+		return NULL;
+	}
+	e->eflags |= SNEXPR_EXPALLOC | SNEXPR_VALALLOC;
+	e->type = SNE_OP_CONSTSTZ;
+	memcpy(e->param.stz.sval, value, len);
+	e->param.stz.sval[len] = '\0';
+	return e;
+}
+
+static struct snexpr *snexpr_convert_stz(char *value, unsigned int ctype)
+{
+	if(value==NULL) {
+		return NULL;
+	}
+
+	return snexpr_convert_stzl(value, strlen(value), ctype);
+}
+
+static struct snexpr *snexpr_concat_strz(char *value0, char *value1)
+{
+	struct snexpr *e = (struct snexpr *)malloc(sizeof(struct snexpr));
+	if(e == NULL) {
+		return NULL;
+	}
+	memset(e, 0, sizeof(struct snexpr));
+
+	e->param.stz.sval = (char *)malloc(strlen(value0) + strlen(value1) + 1);
+	if(e->param.stz.sval == NULL) {
+		free(e);
+		return NULL;
+	}
+	e->eflags |= SNEXPR_EXPALLOC | SNEXPR_VALALLOC;
+	e->type = SNE_OP_CONSTSTZ;
+	strcpy(e->param.stz.sval, value0);
+	strcat(e->param.stz.sval, value1);
+	return e;
+}
+
+static void snexpr_result_free(struct snexpr *e)
+{
+	if(e == NULL) {
+		return;
+	}
+	if((e->eflags & SNEXPR_VALALLOC) && (e->type == SNE_OP_CONSTSTZ)
+			&& (e->param.stz.sval != NULL)) {
+		free(e->param.stz.sval);
+	}
+	if(!(e->eflags & SNEXPR_EXPALLOC)) {
+		return;
+	}
+	free(e);
+}
+
+#define snexpr_eval_check_val(val, vtype) do { \
+		if(val==NULL || val->type != vtype) { \
+			goto error; \
+		} \
+	} while(0)
+
+#define snexpr_eval_check_null(val, vtype) do { \
+		if(val==NULL) { \
+			goto error; \
+		} \
+	} while(0)
+
+#define snexpr_eval_cmp(_CMPOP_) do { \
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]); \
+			snexpr_eval_check_null(rv0, SNE_OP_CONSTNUM); \
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]); \
+			snexpr_eval_check_null(rv1, SNE_OP_CONSTNUM); \
+			if(rv0->type == SNE_OP_CONSTSTZ) { \
+				/* string comparison */ \
+				if(rv1->type == SNE_OP_CONSTNUM) { \
+					tv = snexpr_convert_num(rv1->param.num.nval, SNE_OP_CONSTSTZ); \
+					snexpr_result_free(rv1); \
+					rv1 = tv; \
+					snexpr_eval_check_val(rv1, SNE_OP_CONSTSTZ); \
+				} \
+				lv = snexpr_concat_strz(rv0->param.stz.sval, rv1->param.stz.sval); \
+				if(strcmp(rv0->param.stz.sval, rv1->param.stz.sval) _CMPOP_ 0) { \
+					lv = snexpr_convert_num(1, SNE_OP_CONSTNUM); \
+				} else { \
+					lv = snexpr_convert_num(0, SNE_OP_CONSTNUM); \
+				} \
+			} else { \
+				/* number comparison */ \
+				if(rv1->type == SNE_OP_CONSTSTZ) { \
+					tv = snexpr_convert_stz(rv1->param.stz.sval, SNE_OP_CONSTNUM); \
+					snexpr_result_free(rv1); \
+					rv1 = tv; \
+					snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM); \
+				} \
+				lv = snexpr_convert_num( \
+						rv0->param.num.nval _CMPOP_ rv1->param.num.nval, SNE_OP_CONSTNUM); \
+			} \
+	} while(0)
+
+static struct snexpr *snexpr_eval(struct snexpr *e)
+{
+	float n;
+	struct snexpr *lv = NULL;
+	struct snexpr *rv0 = NULL;
+	struct snexpr *rv1 = NULL;
+	struct snexpr *tv = NULL;
+
+	switch(e->type) {
+		case SNE_OP_UNARY_MINUS:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(-(rv0->param.num.nval), SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_UNARY_LOGICAL_NOT:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(!(rv0->param.num.nval), SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_UNARY_BITWISE_NOT:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(~(to_int(rv0->param.num.nval)), SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_POWER:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					powf(rv0->param.num.nval, rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_MULTIPLY:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					rv0->param.num.nval * rv1->param.num.nval, SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_DIVIDE:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			if(rv1->param.num.nval == 0) {
+				goto error;
+			}
+			lv = snexpr_convert_num(
+					rv0->param.num.nval / rv1->param.num.nval, SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_REMAINDER:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					fmodf(rv0->param.num.nval, rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_PLUS:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_null(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_null(rv1, SNE_OP_CONSTNUM);
+			if(rv0->type == SNE_OP_CONSTSTZ) {
+				/* string concatenation */
+				if(rv1->type == SNE_OP_CONSTNUM) {
+					tv = snexpr_convert_num(rv1->param.num.nval, SNE_OP_CONSTSTZ);
+					snexpr_result_free(rv1);
+					rv1 = tv;
+					snexpr_eval_check_val(rv1, SNE_OP_CONSTSTZ);
+				}
+				lv = snexpr_concat_strz(rv0->param.stz.sval, rv1->param.stz.sval);
+			} else {
+				/* add */
+				if(rv1->type == SNE_OP_CONSTSTZ) {
+					tv = snexpr_convert_stz(rv1->param.stz.sval, SNE_OP_CONSTNUM);
+					snexpr_result_free(rv1);
+					rv1 = tv;
+					snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+				}
+				lv = snexpr_convert_num(
+						rv0->param.num.nval + rv1->param.num.nval, SNE_OP_CONSTNUM);
+			}
+			goto done;
+		case SNE_OP_MINUS:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					rv0->param.num.nval - rv1->param.num.nval, SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_SHL:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					to_int(rv0->param.num.nval) << to_int(rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_SHR:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					to_int(rv0->param.num.nval) >> to_int(rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_LT:
+			snexpr_eval_cmp(<);
+			goto done;
+		case SNE_OP_LE:
+			snexpr_eval_cmp(<=);
+			goto done;
+		case SNE_OP_GT:
+			snexpr_eval_cmp(>);
+			goto done;
+		case SNE_OP_GE:
+			snexpr_eval_cmp(>=);
+			goto done;
+		case SNE_OP_EQ:
+			snexpr_eval_cmp(==);
+			goto done;
+		case SNE_OP_NE:
+			snexpr_eval_cmp(!=);
+			goto done;
+		case SNE_OP_BITWISE_AND:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					to_int(rv0->param.num.nval) & to_int(rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_BITWISE_OR:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					to_int(rv0->param.num.nval) | to_int(rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_BITWISE_XOR:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			snexpr_eval_check_val(rv0, SNE_OP_CONSTNUM);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_val(rv1, SNE_OP_CONSTNUM);
+			lv = snexpr_convert_num(
+					to_int(rv0->param.num.nval) ^ to_int(rv1->param.num.nval),
+					SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_LOGICAL_AND:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			n = rv0->param.num.nval;
+			if(n != 0) {
+				rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+				n = rv1->param.num.nval;
+				if(n != 0) {
+					lv = snexpr_convert_num(n, SNE_OP_CONSTNUM);
+					goto done;
+				}
+			}
+			lv = snexpr_convert_num(0, SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_LOGICAL_OR:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			n = rv0->param.num.nval;
+			if(n != 0 && !isnan(n)) {
+				lv = snexpr_convert_num(n, SNE_OP_CONSTNUM);
+				goto done;
+			} else {
+				rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+				n = rv1->param.num.nval;
+				if(n != 0) {
+					lv = snexpr_convert_num(n, SNE_OP_CONSTNUM);
+					goto done;
+				}
+			}
+			lv = snexpr_convert_num(0, SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_ASSIGN:
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			snexpr_eval_check_null(rv1, SNE_OP_CONSTNUM);
+			if(sne_vec_nth(&e->param.op.args, 0).type == SNE_OP_VAR) {
+				if(e->param.op.args.buf[0].param.var.vref->evflags & SNEXPR_VALALLOC) {
+					if(e->param.op.args.buf[0].param.var.vref->v.sval!=NULL) {
+						free(e->param.op.args.buf[0].param.var.vref->v.sval);
+						e->param.op.args.buf[0].param.var.vref->v.sval = NULL;
+					}
+					e->param.op.args.buf[0].param.var.vref->evflags &= ~(SNEXPR_TSTRING|SNEXPR_VALALLOC);
+				}
+				if(rv1->type == SNE_OP_CONSTSTZ) {
+					e->param.op.args.buf[0].param.var.vref->v.sval = strdup(rv1->param.stz.sval);
+					e->param.op.args.buf[0].param.var.vref->evflags |= SNEXPR_VALASSIGN|SNEXPR_TSTRING|SNEXPR_VALALLOC;
+					lv = snexpr_convert_stz(rv1->param.stz.sval, SNE_OP_CONSTSTZ);
+				} else {
+					n = rv1->param.num.nval;
+					e->param.op.args.buf[0].param.var.vref->v.nval = n;
+					e->param.op.args.buf[0].param.var.vref->evflags |= SNEXPR_VALASSIGN;
+					lv = snexpr_convert_num(n, SNE_OP_CONSTNUM);
+				}
+			}
+			goto done;
+		case SNE_OP_COMMA:
+			rv0 = snexpr_eval(&e->param.op.args.buf[0]);
+			rv1 = snexpr_eval(&e->param.op.args.buf[1]);
+			if(rv1->type == SNE_OP_CONSTSTZ) {
+				lv = snexpr_convert_stz(rv1->param.stz.sval, SNE_OP_CONSTSTZ);
+			} else {
+				lv = snexpr_convert_num(rv1->param.num.nval, SNE_OP_CONSTNUM);
+			}
+			goto done;
+		case SNE_OP_CONSTNUM:
+			lv = snexpr_convert_num(e->param.num.nval, SNE_OP_CONSTNUM);
+			goto done;
+		case SNE_OP_CONSTSTZ:
+			lv = snexpr_convert_stz(e->param.stz.sval, SNE_OP_CONSTSTZ);
+			goto done;
+		case SNE_OP_VAR:
+			if((_snexternval_cbf == NULL)
+					|| (e->param.var.vref->evflags & SNEXPR_VALASSIGN)) {
+				if(e->param.var.vref->evflags & SNEXPR_TSTRING) {
+					lv = snexpr_convert_stz(e->param.var.vref->v.sval, SNE_OP_CONSTSTZ);
+				} else {
+					snexpr_convert_num(e->param.var.vref->v.nval, SNE_OP_CONSTNUM);
+				}
+			} else {
+				lv = _snexternval_cbf(e->param.var.vref->name);
+			}
+			goto done;
+		case SNE_OP_FUNC:
+			rv0 = e->param.func.f->f(e->param.func.f, &e->param.func.args,
+							e->param.func.context);
+			if(rv0->type == SNE_OP_CONSTSTZ) {
+				lv = snexpr_convert_stz(rv0->param.stz.sval, SNE_OP_CONSTSTZ);
+			} else {
+				lv = snexpr_convert_num(rv0->param.num.nval, SNE_OP_CONSTNUM);
+			}
+			goto done;
+		default:
+			lv = snexpr_convert_num(NAN, SNE_OP_CONSTNUM);
+			goto done;
+	}
+
+done:
+	if(rv0 != NULL) {
+		snexpr_result_free(rv0);
+	}
+	if(rv1 != NULL) {
+		snexpr_result_free(rv1);
+	}
+	return lv;
+
+error:
+	if(rv0 != NULL) {
+		snexpr_result_free(rv0);
+	}
+	if(rv1 != NULL) {
+		snexpr_result_free(rv1);
+	}
+	return NULL;
+
+}
+
+static int snexpr_next_token(const char *s, size_t len, int *flags)
+{
+	unsigned int i = 0;
+	char b;
+	if(len == 0) {
+		return 0;
+	}
+	char c = s[0];
+	if(c == '#') {
+		for(; i < len && s[i] != '\n'; i++)
+			;
+		return i;
+	} else if(c == '\n') {
+		for(; i < len && isspace(s[i]); i++)
+			;
+		if(*flags & SNEXPR_TOP) {
+			if(i == len || s[i] == ')') {
+				*flags = *flags & (~SNEXPR_COMMA);
+			} else {
+				*flags = SNEXPR_TNUMBER | SNEXPR_TSTRING | SNEXPR_TWORD | SNEXPR_TOPEN
+						 | SNEXPR_COMMA;
+			}
+		}
+		return i;
+	} else if(isspace(c)) {
+		while(i < len && isspace(s[i]) && s[i] != '\n') {
+			i++;
+		}
+		return i;
+	} else if(isdigit(c)) {
+		if((*flags & SNEXPR_TNUMBER) == 0) {
+			return -1; // unexpected number
+		}
+		*flags = SNEXPR_TOP | SNEXPR_TCLOSE;
+		while((c == '.' || isdigit(c)) && i < len) {
+			i++;
+			c = s[i];
+		}
+		return i;
+	} else if(c == '"' || c == '\'') {
+		if((*flags & SNEXPR_TSTRING) == 0) {
+			return -1; // unexpected string
+		}
+		if(i == len - 1) {
+			return -1; // invalud start of string
+		}
+		*flags = SNEXPR_TOP | SNEXPR_TCLOSE;
+		b = c;
+		i++;
+		c = s[i];
+		while(c != b && i < len) {
+			i++;
+			c = s[i];
+		}
+		return i + 1;
+	} else if(isfirstvarchr(c)) {
+		if((*flags & SNEXPR_TWORD) == 0) {
+			return -2; // unexpected word
+		}
+		*flags = SNEXPR_TOP | SNEXPR_TOPEN | SNEXPR_TCLOSE;
+		while((isvarchr(c)) && i < len) {
+			i++;
+			c = s[i];
+		}
+		return i;
+	} else if(c == '(' || c == ')') {
+		if(c == '(' && (*flags & SNEXPR_TOPEN) != 0) {
+			*flags = SNEXPR_TNUMBER | SNEXPR_TSTRING | SNEXPR_TWORD | SNEXPR_TOPEN
+					 | SNEXPR_TCLOSE;
+		} else if(c == ')' && (*flags & SNEXPR_TCLOSE) != 0) {
+			*flags = SNEXPR_TOP | SNEXPR_TCLOSE;
+		} else {
+			return -3; // unexpected parenthesis
+		}
+		return 1;
+	} else {
+		if((*flags & SNEXPR_TOP) == 0) {
+			if(snexpr_op(&c, 1, 1) == SNE_OP_UNKNOWN) {
+				return -4; // missing expected operand
+			}
+			*flags = SNEXPR_TNUMBER | SNEXPR_TSTRING | SNEXPR_TWORD | SNEXPR_TOPEN
+					 | SNEXPR_UNARY;
+			return 1;
+		} else {
+			int found = 0;
+			while(!isvarchr(c) && !isspace(c) && c != '(' && c != ')'
+					&& i < len) {
+				if(snexpr_op(s, i + 1, 0) != SNE_OP_UNKNOWN) {
+					found = 1;
+				} else if(found) {
+					break;
+				}
+				i++;
+				c = s[i];
+			}
+			if(!found) {
+				return -5; // unknown operator
+			}
+			*flags = SNEXPR_TNUMBER | SNEXPR_TSTRING | SNEXPR_TWORD | SNEXPR_TOPEN;
+			return i;
+		}
+	}
+}
+
+#define SNEXPR_PAREN_ALLOWED 0
+#define SNEXPR_PAREN_EXPECTED 1
+#define SNEXPR_PAREN_FORBIDDEN 2
+
+static int snexpr_bind(const char *s, size_t len, sne_vec_expr_t *es)
+{
+	enum snexpr_type op = snexpr_op(s, len, -1);
+	if(op == SNE_OP_UNKNOWN) {
+		return -1;
+	}
+
+	if(snexpr_is_unary(op)) {
+		if(sne_vec_len(es) < 1) {
+			return -1;
+		}
+		struct snexpr arg = sne_vec_pop(es);
+		struct snexpr unary = snexpr_init();
+		unary.type = op;
+		sne_vec_push(&unary.param.op.args, arg);
+		sne_vec_push(es, unary);
+	} else {
+		if(sne_vec_len(es) < 2) {
+			return -1;
+		}
+		struct snexpr b = sne_vec_pop(es);
+		struct snexpr a = sne_vec_pop(es);
+		struct snexpr binary = snexpr_init();
+		binary.type = op;
+		if(op == SNE_OP_ASSIGN && a.type != SNE_OP_VAR) {
+			return -1; /* Bad assignment */
+		}
+		sne_vec_push(&binary.param.op.args, a);
+		sne_vec_push(&binary.param.op.args, b);
+		sne_vec_push(es, binary);
+	}
+	return 0;
+}
+
+static struct snexpr snexpr_constnum(float value)
+{
+	struct snexpr e = snexpr_init();
+	e.type = SNE_OP_CONSTNUM;
+	e.param.num.nval = value;
+	return e;
+}
+
+static struct snexpr snexpr_varref(struct snexpr_var *v)
+{
+	struct snexpr e = snexpr_init();
+	e.type = SNE_OP_VAR;
+	e.param.var.vref = v;
+	return e;
+}
+
+static struct snexpr snexpr_conststr(const char *value, int len)
+{
+	struct snexpr e = snexpr_init();
+	if(len < 2) {
+		len = 0;
+	} else {
+		/* skip the quotes */
+		len -= 2;
+	}
+	e.type = SNE_OP_CONSTSTZ;
+	e.param.stz.sval = malloc(len + 1);
+	if(e.param.stz.sval) {
+		if(len > 0) {
+			/* do not copy the quotes */
+			memcpy(e.param.stz.sval, value + 1, len);
+			e.param.stz.sval[len] = '\0';
+		} else {
+			e.param.stz.sval[0] = '\0';
+		}
+	}
+	return e;
+}
+
+static struct snexpr snexpr_binary(
+		enum snexpr_type type, struct snexpr a, struct snexpr b)
+{
+	struct snexpr e = snexpr_init();
+	e.type = type;
+	sne_vec_push(&e.param.op.args, a);
+	sne_vec_push(&e.param.op.args, b);
+	return e;
+}
+
+static inline void snexpr_copy(struct snexpr *dst, struct snexpr *src)
+{
+	int i;
+	struct snexpr arg;
+	dst->type = src->type;
+	if(src->type == SNE_OP_FUNC) {
+		dst->param.func.f = src->param.func.f;
+		sne_vec_foreach(&src->param.func.args, arg, i)
+		{
+			struct snexpr tmp = snexpr_init();
+			snexpr_copy(&tmp, &arg);
+			sne_vec_push(&dst->param.func.args, tmp);
+		}
+		if(src->param.func.f->ctxsz > 0) {
+			dst->param.func.context = calloc(1, src->param.func.f->ctxsz);
+		}
+	} else if(src->type == SNE_OP_CONSTNUM) {
+		dst->param.num.nval = src->param.num.nval;
+	} else if(src->type == SNE_OP_VAR) {
+		dst->param.var.vref = src->param.var.vref;
+	} else {
+		sne_vec_foreach(&src->param.op.args, arg, i)
+		{
+			struct snexpr tmp = snexpr_init();
+			snexpr_copy(&tmp, &arg);
+			sne_vec_push(&dst->param.op.args, tmp);
+		}
+	}
+}
+
+static void snexpr_destroy_args(struct snexpr *e);
+
+static struct snexpr *snexpr_create(const char *s, size_t len,
+		struct snexpr_var_list *vars, struct snexpr_func *funcs,
+		snexternval_cbf_t evcbf)
+{
+	float num;
+	struct snexpr_var *v;
+	const char *id = NULL;
+	size_t idn = 0;
+
+	struct snexpr *result = NULL;
+
+	_snexternval_cbf = evcbf;
+
+	sne_vec_expr_t es = sne_vec_init();
+	sne_vec_str_t os = sne_vec_init();
+	sne_vec_arg_t as = sne_vec_init();
+
+	struct macro
+	{
+		char *name;
+		sne_vec_expr_t body;
+	};
+	sne_vec(struct macro) macros = sne_vec_init();
+
+	int flags = SNEXPR_TDEFAULT;
+	int paren = SNEXPR_PAREN_ALLOWED;
+	for(;;) {
+		int n = snexpr_next_token(s, len, &flags);
+		if(n == 0) {
+			break;
+		} else if(n < 0) {
+			goto cleanup;
+		}
+		const char *tok = s;
+		s = s + n;
+		len = len - n;
+		if(*tok == '#') {
+			continue;
+		}
+		if(flags & SNEXPR_UNARY) {
+			if(n == 1) {
+				switch(*tok) {
+					case '-':
+						tok = "-u";
+						break;
+					case '^':
+						tok = "^u";
+						break;
+					case '!':
+						tok = "!u";
+						break;
+					default:
+						goto cleanup;
+				}
+				n = 2;
+			}
+		}
+		if(*tok == '\n' && (flags & SNEXPR_COMMA)) {
+			flags = flags & (~SNEXPR_COMMA);
+			n = 1;
+			tok = ",";
+		}
+		if(isspace(*tok)) {
+			continue;
+		}
+		int paren_next = SNEXPR_PAREN_ALLOWED;
+
+		if(idn > 0) {
+			if(n == 1 && *tok == '(') {
+				int i;
+				int has_macro = 0;
+				struct macro m;
+				sne_vec_foreach(&macros, m, i)
+				{
+					if(strlen(m.name) == idn && strncmp(m.name, id, idn) == 0) {
+						has_macro = 1;
+						break;
+					}
+				}
+				if((idn == 1 && id[0] == '$') || has_macro
+						|| snexpr_func_find(funcs, id, idn) != NULL) {
+					struct snexpr_string str = {id, (int)idn};
+					sne_vec_push(&os, str);
+					paren = SNEXPR_PAREN_EXPECTED;
+				} else {
+					goto cleanup; /* invalid function name */
+				}
+			} else if((v = snexpr_var_find(vars, id, idn)) != NULL) {
+				sne_vec_push(&es, snexpr_varref(v));
+				paren = SNEXPR_PAREN_FORBIDDEN;
+			}
+			id = NULL;
+			idn = 0;
+		}
+
+		if(n == 1 && *tok == '(') {
+			if(paren == SNEXPR_PAREN_EXPECTED) {
+				struct snexpr_string str = {"{", 1};
+				sne_vec_push(&os, str);
+				struct snexpr_arg arg = {sne_vec_len(&os), sne_vec_len(&es), sne_vec_init()};
+				sne_vec_push(&as, arg);
+			} else if(paren == SNEXPR_PAREN_ALLOWED) {
+				struct snexpr_string str = {"(", 1};
+				sne_vec_push(&os, str);
+			} else {
+				goto cleanup; // Bad call
+			}
+		} else if(paren == SNEXPR_PAREN_EXPECTED) {
+			goto cleanup; // Bad call
+		} else if(n == 1 && *tok == ')') {
+			int minlen = (sne_vec_len(&as) > 0 ? sne_vec_peek(&as).oslen : 0);
+			while(sne_vec_len(&os) > minlen && *sne_vec_peek(&os).s != '('
+					&& *sne_vec_peek(&os).s != '{') {
+				struct snexpr_string str = sne_vec_pop(&os);
+				if(snexpr_bind(str.s, str.n, &es) == -1) {
+					goto cleanup;
+				}
+			}
+			if(sne_vec_len(&os) == 0) {
+				goto cleanup; // Bad parens
+			}
+			struct snexpr_string str = sne_vec_pop(&os);
+			if(str.n == 1 && *str.s == '{') {
+				str = sne_vec_pop(&os);
+				struct snexpr_arg arg = sne_vec_pop(&as);
+				if(sne_vec_len(&es) > arg.eslen) {
+					sne_vec_push(&arg.args, sne_vec_pop(&es));
+				}
+				if(str.n == 1 && str.s[0] == '$') {
+					if(sne_vec_len(&arg.args) < 1) {
+						sne_vec_free(&arg.args);
+						goto cleanup; /* too few arguments for $() function */
+					}
+					struct snexpr *u = &sne_vec_nth(&arg.args, 0);
+					if(u->type != SNE_OP_VAR) {
+						sne_vec_free(&arg.args);
+						goto cleanup; /* first argument is not a variable */
+					}
+					for(struct snexpr_var *v = vars->head; v; v = v->next) {
+						if(v == u->param.var.vref) {
+							struct macro m = {v->name, arg.args};
+							sne_vec_push(&macros, m);
+							break;
+						}
+					}
+					sne_vec_push(&es, snexpr_constnum(0));
+				} else {
+					int i = 0;
+					int found = -1;
+					struct macro m;
+					sne_vec_foreach(&macros, m, i)
+					{
+						if(strlen(m.name) == (size_t)str.n
+								&& strncmp(m.name, str.s, str.n) == 0) {
+							found = i;
+						}
+					}
+					if(found != -1) {
+						m = sne_vec_nth(&macros, found);
+						struct snexpr root = snexpr_constnum(0);
+						struct snexpr *p = &root;
+						/* Assign macro parameters */
+						for(int j = 0; j < sne_vec_len(&arg.args); j++) {
+							char varname[4];
+							snprintf(varname, sizeof(varname) - 1, "$%d",
+									(j + 1));
+							struct snexpr_var *v =
+									snexpr_var_find(vars, varname, strlen(varname));
+							struct snexpr ev = snexpr_varref(v);
+							struct snexpr assign = snexpr_binary(
+									SNE_OP_ASSIGN, ev, sne_vec_nth(&arg.args, j));
+							*p = snexpr_binary(
+									SNE_OP_COMMA, assign, snexpr_constnum(0));
+							p = &sne_vec_nth(&p->param.op.args, 1);
+						}
+						/* Expand macro body */
+						for(int j = 1; j < sne_vec_len(&m.body); j++) {
+							if(j < sne_vec_len(&m.body) - 1) {
+								*p = snexpr_binary(SNE_OP_COMMA, snexpr_constnum(0),
+										snexpr_constnum(0));
+								snexpr_copy(&sne_vec_nth(&p->param.op.args, 0),
+										&sne_vec_nth(&m.body, j));
+							} else {
+								snexpr_copy(p, &sne_vec_nth(&m.body, j));
+							}
+							p = &sne_vec_nth(&p->param.op.args, 1);
+						}
+						sne_vec_push(&es, root);
+						sne_vec_free(&arg.args);
+					} else {
+						struct snexpr_func *f = snexpr_func_find(funcs, str.s, str.n);
+						struct snexpr bound_func = snexpr_init();
+						bound_func.type = SNE_OP_FUNC;
+						bound_func.param.func.f = f;
+						bound_func.param.func.args = arg.args;
+						if(f->ctxsz > 0) {
+							void *p = calloc(1, f->ctxsz);
+							if(p == NULL) {
+								goto cleanup; /* allocation failed */
+							}
+							bound_func.param.func.context = p;
+						}
+						sne_vec_push(&es, bound_func);
+					}
+				}
+			}
+			paren_next = SNEXPR_PAREN_FORBIDDEN;
+		} else if(!isnan(num = snexpr_parse_number(tok, n))) {
+			sne_vec_push(&es, snexpr_constnum(num));
+			paren_next = SNEXPR_PAREN_FORBIDDEN;
+		} else if(*tok == '"' || *tok == '\'') {
+			sne_vec_push(&es, snexpr_conststr(tok, n));
+			paren_next = SNEXPR_PAREN_FORBIDDEN;
+		} else if(snexpr_op(tok, n, -1) != SNE_OP_UNKNOWN) {
+			enum snexpr_type op = snexpr_op(tok, n, -1);
+			struct snexpr_string o2 = {NULL, 0};
+			if(sne_vec_len(&os) > 0) {
+				o2 = sne_vec_peek(&os);
+			}
+			for(;;) {
+				if(n == 1 && *tok == ',' && sne_vec_len(&os) > 0) {
+					struct snexpr_string str = sne_vec_peek(&os);
+					if(str.n == 1 && *str.s == '{') {
+						struct snexpr e = sne_vec_pop(&es);
+						sne_vec_push(&sne_vec_peek(&as).args, e);
+						break;
+					}
+				}
+				enum snexpr_type type2 = snexpr_op(o2.s, o2.n, -1);
+				if(!(type2 != SNE_OP_UNKNOWN && snexpr_prec(op, type2))) {
+					struct snexpr_string str = {tok, n};
+					sne_vec_push(&os, str);
+					break;
+				}
+
+				if(snexpr_bind(o2.s, o2.n, &es) == -1) {
+					goto cleanup;
+				}
+				(void)sne_vec_pop(&os);
+				if(sne_vec_len(&os) > 0) {
+					o2 = sne_vec_peek(&os);
+				} else {
+					o2.n = 0;
+				}
+			}
+		} else {
+			if(n > 0 && !isdigit(*tok)) {
+				/* Valid identifier, a variable or a function */
+				id = tok;
+				idn = n;
+			} else {
+				goto cleanup; // Bad variable name, e.g. '2.3.4' or '4ever'
+			}
+		}
+		paren = paren_next;
+	}
+
+	if(idn > 0) {
+		sne_vec_push(&es, snexpr_varref(snexpr_var_find(vars, id, idn)));
+	}
+
+	while(sne_vec_len(&os) > 0) {
+		struct snexpr_string rest = sne_vec_pop(&os);
+		if(rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) {
+			goto cleanup; // Bad paren
+		}
+		if(snexpr_bind(rest.s, rest.n, &es) == -1) {
+			goto cleanup;
+		}
+	}
+
+	result = (struct snexpr *)calloc(1, sizeof(struct snexpr));
+	if(result != NULL) {
+		if(sne_vec_len(&es) == 0) {
+			result->type = SNE_OP_CONSTNUM;
+		} else {
+			*result = sne_vec_pop(&es);
+		}
+	}
+
+	int i, j;
+	struct macro m;
+	struct snexpr e;
+	struct snexpr_arg a;
+cleanup:
+	sne_vec_foreach(&macros, m, i)
+	{
+		struct snexpr e;
+		sne_vec_foreach(&m.body, e, j)
+		{
+			snexpr_destroy_args(&e);
+		}
+		sne_vec_free(&m.body);
+	}
+	sne_vec_free(&macros);
+
+	sne_vec_foreach(&es, e, i)
+	{
+		snexpr_destroy_args(&e);
+	}
+	sne_vec_free(&es);
+
+	sne_vec_foreach(&as, a, i)
+	{
+		sne_vec_foreach(&a.args, e, j)
+		{
+			snexpr_destroy_args(&e);
+		}
+		sne_vec_free(&a.args);
+	}
+	sne_vec_free(&as);
+
+	/*sne_vec_foreach(&os, o, i) {sne_vec_free(&m.body);}*/
+	sne_vec_free(&os);
+	if(result==NULL) {
+		_snexternval_cbf = NULL;
+	}
+
+	return result;
+}
+
+static void snexpr_destroy_args(struct snexpr *e)
+{
+	int i;
+	struct snexpr arg;
+	if(e->type == SNE_OP_FUNC) {
+		sne_vec_foreach(&e->param.func.args, arg, i)
+		{
+			snexpr_destroy_args(&arg);
+		}
+		sne_vec_free(&e->param.func.args);
+		if(e->param.func.context != NULL) {
+			if(e->param.func.f->cleanup != NULL) {
+				e->param.func.f->cleanup(
+						e->param.func.f, e->param.func.context);
+			}
+			free(e->param.func.context);
+		}
+	} else if(e->type != SNE_OP_CONSTNUM && e->type != SNE_OP_CONSTSTZ
+			&& e->type != SNE_OP_VAR) {
+		sne_vec_foreach(&e->param.op.args, arg, i)
+		{
+			snexpr_destroy_args(&arg);
+		}
+		sne_vec_free(&e->param.op.args);
+	}
+}
+
+static void snexpr_destroy(struct snexpr *e, struct snexpr_var_list *vars)
+{
+	_snexternval_cbf = NULL;
+
+	if(e != NULL) {
+		snexpr_destroy_args(e);
+		free(e);
+	}
+	if(vars != NULL) {
+		for(struct snexpr_var *v = vars->head; v;) {
+			struct snexpr_var *next = v->next;
+			if(v->evflags & SNEXPR_VALALLOC) {
+				free(v->v.sval);
+			}
+			free(v);
+			v = next;
+		}
+	}
+}
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* _SNEXPR_H_ */