/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

/*
 * Glenn Fowler
 * AT&T Research
 *
 * expression library evaluator
 */

#include "config.h"

#include <expr/exlib.h>
#include <expr/exop.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <stdalign.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <util/agxbuf.h>
#include <util/alloc.h>
#include <util/arena.h>
#include <util/exit.h>
#include <util/gv_ctype.h>
#include <util/gv_ftell.h>
#include <util/strview.h>
#include <util/unreachable.h>
#ifdef _WIN32
#define srand48 srand
#define drand48 rand
#endif

#define TIME_LEN 80             /* max. characters to store time */

static Extype_t	eval(Expr_t*, Exnode_t*, void*);

#define FRAME		64

static char *lexname(long op, int subop) {
	agxbuf b = {0};

	if (op > MINTOKEN && op < MAXTOKEN)
		return gv_strdup(exop((size_t)op - MINTOKEN));
	if (op == '=')
	{
		if (subop > MINTOKEN && subop < MAXTOKEN)
			agxbprint(&b, "%s=", exop((size_t)subop - MINTOKEN));
		else if (subop > ' ' && subop <= '~')
			agxbprint(&b, "%c=", subop);
		else
			agxbprint(&b, "(%d)=", subop);
	}
	else if (subop < 0)
		agxbprint(&b, "(EXTERNAL:%ld)", op);
	else if (op > ' ' && op <= '~')
		agxbprint(&b, "%c", (char)op);
	else
		agxbprint(&b, "(%ld)", op);
	return agxbdisown(&b);
}

/* evaldyn:
 * Evaluate item from array given key.
 * Returns 1 if item existed, zero otherwise
 * 
 */
static int evaldyn(Expr_t *ex, Exnode_t *exnode, void *env, int delete) {
	Exassoc_t *b;
	Extype_t v;
	char buf[32];
	Extype_t key;
	char *keyname;

	v = eval(ex, exnode->data.variable.index, env);
	if (exnode->data.variable.symbol->index > 0) {
		if (ex->disc->inf != NULL) {
			return ex->disc->inf(v, exnode->data.variable.symbol, ex->disc);
		}
		return 0; // treat the RHS as an empty array
	}
	if (exnode->data.variable.symbol->index_type == INTEGER) {
		if (!(b = dtmatch(exnode->data.variable.symbol->local, &v))) {
			return 0;
		}
	} 
	else {
		const long type = exnode->data.variable.index->type;
		if (type != STRING) {
			if (!BUILTIN(type)) {
				key = ex->disc->keyf(v, type);
			} else
				key.integer = v.integer;
			snprintf(buf, sizeof(buf), "%llx", (unsigned long long)key.integer);
			keyname = buf;
		} else
			keyname = v.string;
		if (!(b = dtmatch(exnode->data.variable.symbol->local, keyname))) {
			return 0;
		}
	}
	if (delete) {
		dtdelete(exnode->data.variable.symbol->local, b);
	}
	return 1;
}

/*
 * return dynamic (associative array) variable value
 * assoc will point to the associative array bucket
 */
static Extype_t getdyn(Expr_t *ex, Exnode_t *exnode, void *env,
                       Exassoc_t **assoc) {
	Exassoc_t*	b;
	Extype_t	v;

	if (exnode->data.variable.index)
	{
		Extype_t key;
		char	buf[2*sizeof(key.integer)+1];  /* no. of hex chars needed plus null byte */
		char *keyname;

		v = eval(ex, exnode->data.variable.index, env);
		if (exnode->data.variable.symbol->index_type == INTEGER) {
			if (!(b = dtmatch(exnode->data.variable.symbol->local, &v)))
			{
				b = ARENA_NEW(&ex->vm, Exassoc_t);
				b->key = v;
				dtinsert(exnode->data.variable.symbol->local, b);
			}
		} else {
			const long type = exnode->data.variable.index->type;
			if (type != STRING) {
				if (!BUILTIN(type)) {
					key = ex->disc->keyf(v, type);
				} else
					key.integer = v.integer;
				snprintf(buf, sizeof(buf), "%llx", (unsigned long long)key.integer);
				keyname = buf;
			} else
				keyname = v.string;
			if (!(b = dtmatch(exnode->data.variable.symbol->local, keyname)))
			{
				const size_t keyname_len = strlen(keyname);
				b = gv_arena_alloc(&ex->vm, alignof(Exassoc_t), sizeof(Exassoc_t) + keyname_len);
				memcpy(b->name, keyname, keyname_len + 1);
				b->key = v;
				dtinsert(exnode->data.variable.symbol->local, b);
			}
		}
		*assoc = b;
		if (b)
		{
			if (exnode->data.variable.symbol->type == STRING && !b->value.string)
				b->value = exzero(exnode->data.variable.symbol->type);
			return b->value;
		}
		v = exzero(exnode->data.variable.symbol->type);
		return v;
	}
	*assoc = 0;
	return exnode->data.variable.symbol->value->data.constant.value;
}

typedef struct
{
	Sffmt_t		fmt;
	Expr_t*		expr;
	void*		env;
	Print_t*	args;
	Extype_t	value;
	Exnode_t*	actuals;
} Fmt_t;

/*
 * printf %! extension function
 */

static int
prformat(void* vp, Sffmt_t* dp)
{
	Fmt_t *const fmt = (Fmt_t *)((char *)dp - offsetof(Fmt_t, fmt));
	Exnode_t*	node;
	char*		s;
	long to = 0;
	time_t			tm;
    struct tm *stm;

	dp->flags |= SFFMT_VALUE;
	if (fmt->args)
	{
		if ((node = dp->fmt == '*' ? fmt->args->param[dp->size] : fmt->args->arg))
			fmt->value = exeval(fmt->expr, node, fmt->env);
		else
			fmt->value.integer = 0;
		to = fmt->args->arg->type;
	}
	else if (!(fmt->actuals = fmt->actuals->data.operand.right))
		exerror("printf: not enough arguments");
	else
	{
		node = fmt->actuals->data.operand.left;
		const long from = node->type;
		switch (dp->fmt)
		{
		case 'f':
		case 'g':
			to = FLOATING;
			break;
		case 's':
			to = STRING;
			break;
		default:
			switch (from)
			{
			case INTEGER:
			case UNSIGNED:
				to = from;
				break;
			default:
				to = INTEGER;
				break;
			}
			break;
		}
		if (to == from)
			fmt->value = exeval(fmt->expr, node, fmt->env);
		else
		{
			node = excast(fmt->expr, node, to, NULL, 0);
			fmt->value = exeval(fmt->expr, node, fmt->env);
			node->data.operand.left = 0;
			gv_arena_free(&fmt->expr->vm, node, sizeof(*node));
			if (to == STRING)
			{
				if (fmt->value.string)
				{
					char *const copy = gv_arena_strdup(&fmt->expr->vm, fmt->value.string);
					gv_arena_free(&fmt->expr->vm, fmt->value.string,
					              strlen(fmt->value.string) + 1);
					fmt->value.string = copy;
				}
				if (!fmt->value.string)
					fmt->value.string = "";
			}
		}
	}
	switch (to)
	{
	case STRING:
		*(char**)vp = fmt->value.string;
		fmt->fmt.size = -1;
		break;
	case FLOATING:
		*(double*)vp = fmt->value.floating;
		fmt->fmt.size = sizeof(double);
		break;
	default:
		*(long long *)vp = fmt->value.integer;
		dp->size = sizeof(long long);
		break;
	}
	strview_t txt = {0};
	if (dp->n_str > 0)
	{
		txt = (strview_t){.data = dp->t_str, .size = (size_t)dp->n_str};
	}
	switch (dp->fmt)
	{
	case 'q':
	case 'Q': {
		s = *(char**)vp;
		char *quoted = fmtquote(s, "$'", "'");
		*(char**)vp = gv_arena_strdup(&fmt->expr->vm, quoted);
		free(quoted);
		dp->fmt = 's';
		dp->size = -1;
		break;
	}
	case 'S':
		dp->flags &= ~SFFMT_LONG;
		s = *(char**)vp;
		if (txt.data != NULL)
		{
			if (strview_str_eq(txt, "identifier"))
			{
				if (*s && !gv_isalpha(*s))
					*s++ = '_';
				for (; *s; s++)
					if (!gv_isalnum(*s))
						*s = '_';
			}
			else if (strview_str_eq(txt, "invert"))
			{
				for (; *s; s++)
					if (gv_isupper(*s))
						*s = (char)tolower(*s);
					else if (gv_islower(*s))
						*s = (char)toupper(*s);
			}
			else if (strview_str_eq(txt, "lower"))
			{
				gv_tolower_str(s);
			}
			else if (strview_str_eq(txt, "upper"))
			{
				gv_toupper_str(s);
			}
			else if (strview_str_eq(txt, "variable"))
			{
				for (; *s; s++)
					if (!gv_isalnum(*s) && *s != '_')
						*s = '.';
			}
		}
		dp->fmt = 's';
		dp->size = -1;
		break;
	case 't':
	case 'T':
		if ((tm = *(long long *)vp) == -1)
			tm = time(NULL);
        if (txt.data == NULL) {
            exerror("printf: no time format provided");
        } else {
            s = gv_arena_alloc(&fmt->expr->vm, 1, TIME_LEN);
            stm = localtime(&tm);
            char *format = malloc(sizeof(char) * (txt.size + 1));
            if (format == NULL) {
                gv_arena_free(&fmt->expr->vm, s, TIME_LEN);
                exerror("printf: out of memory");
            } else {
                memcpy(format, txt.data, txt.size);
                format[txt.size] = '\0';
                strftime(s, TIME_LEN, format, stm);
                *(char **)vp = s;
            }
            free(format);
        }
		dp->fmt = 's';
		dp->size = -1;
		break;
	}
	return 0;
}

/*
 * print a list of strings
 */
static int prints(Expr_t *ex, Exnode_t *exnode, void *env, FILE *sp) {
    Extype_t v;
    Exnode_t *args;

    args = exnode->data.operand.left;
    while (args) {
	v = eval(ex, args->data.operand.left, env);
	fputs(v.string, sp);
	args = args->data.operand.right;
    }
    putc('\n', sp);
    return 0;
}

/*
 * do printf
 */
static int print(Expr_t *ex, Exnode_t *exnode, void *env, FILE *sp) {
	Print_t*	x;
	Extype_t		v;
	Fmt_t			fmt;

	if (!sp)
	{
		v = eval(ex, exnode->data.print.descriptor, env);
		if (v.integer < 0 || v.integer >= (long long)elementsof(ex->file) ||
		    (!(sp = ex->file[v.integer]) &&
		    !(sp = ex->file[v.integer] = tmpfile())))
		{
			exerror("printf: %lld: invalid descriptor", v.integer);
			return -1;
		}
	}
	memset(&fmt, 0, sizeof(fmt));
	fmt.fmt.extf = prformat;
	fmt.expr = ex;
	fmt.env = env;
	x = exnode->data.print.args;
	if (x->format)
		do
		{
			if (x->arg)
			{
				fmt.fmt.form = x->format;
				fmt.args = x;
				sfprint(sp, &fmt.fmt);
			}
			else
				fputs(x->format, sp);
		} while ((x = x->next));
	else
	{
		v = eval(ex, x->arg->data.operand.left, env);
		fmt.fmt.form = v.string;
		fmt.actuals = x->arg;
		sfprint(sp, &fmt.fmt);
		if (fmt.actuals->data.operand.right)
			exerror("(s)printf: \"%s\": too many arguments", fmt.fmt.form);
	}
	return 0;
}

/*
 * scanf %! extension function
 */

static int
scformat(void* vp, Sffmt_t* dp)
{
	Fmt_t *const fmt = (Fmt_t *)((char *)dp - offsetof(Fmt_t, fmt));
	Exnode_t*	node;

	if (!fmt->actuals)
	{
		exerror("scanf: not enough arguments");
		return -1;
	}
	node = fmt->actuals->data.operand.left;
	switch (dp->fmt)
	{
	case 'f':
	case 'g': {
		if (node->type != FLOATING)
		{
			exerror("scanf: %s: floating variable address argument expected", node->data.variable.symbol->name);
			return -1;
		}
		fmt->fmt.size = sizeof(double);
		const void *const value = &node->data.variable.symbol->value->data.constant.value;
		memcpy(vp, &value, sizeof(value));
		break;
	}
	case 's':
	case '[': {
		if (node->type != STRING)
		{
			exerror("scanf: %s: string variable address argument expected", node->data.variable.symbol->name);
			return -1;
		}
		if (node->data.variable.symbol->value->data.constant.value.string == expr.nullstring)
			node->data.variable.symbol->value->data.constant.value.string = 0;
		fmt->fmt.size = 1024;
		char *s = node->data.variable.symbol->value->data.constant.value.string;
		gv_arena_free(&fmt->expr->vm, s, strlen(s) + 1);
		s = gv_arena_alloc(&fmt->expr->vm, 1, (size_t)fmt->fmt.size);
		memcpy(vp, &s, sizeof(s));
		node->data.variable.symbol->value->data.constant.value.string = s;
		break;
	}
	case 'c': {
		if (node->type != CHARACTER) {
			exerror("scanf: %s: char variable address argument expected", node->data.variable.symbol->name);
			return -1;
		}
		fmt->fmt.size = sizeof(long long);
		const void *const value = &node->data.variable.symbol->value->data.constant.value;
		memcpy(vp, &value, sizeof(value));
		break;
	}
	default: {
		if (node->type != INTEGER && node->type != UNSIGNED)
		{
			exerror("scanf: %s: integer variable address argument expected", node->data.variable.symbol->name);
			return -1;
		}
		dp->size = sizeof(long long);
		const void *const value = &node->data.variable.symbol->value->data.constant.value;
		memcpy(vp, &value, sizeof(value));
		break;
	}
	}
	fmt->actuals = fmt->actuals->data.operand.right;
	dp->flags |= SFFMT_VALUE;
	return 0;
}

/*
 * do scanf
 */

static int scan(Expr_t *ex, Exnode_t *exnode, void *env, FILE *sp) {
	Extype_t		v;
	Extype_t		u;
	Fmt_t			fmt;
	int			n;

	if (!sp)
	{
		if (exnode->data.scan.descriptor)
		{
			v = eval(ex, exnode->data.scan.descriptor, env);
			if (exnode->data.scan.descriptor->type == STRING)
				goto get;
		}
		else
			v.integer = 0;
		if (v.integer < 0 || v.integer >= (long long)elementsof(ex->file) || (!(sp = ex->file[v.integer]) && !(sp = ex->file[v.integer] = tmpfile())))
		{
			exerror("scanf: %lld: invalid descriptor", v.integer);
			return 0;
		}
	}
 get:
	memset(&fmt, 0, sizeof(fmt));
	fmt.fmt.extf = scformat;
	fmt.expr = ex;
	fmt.env = env;
	u = eval(ex, exnode->data.scan.format, env);
	fmt.fmt.form = u.string;
	fmt.actuals = exnode->data.scan.args;
	if (sp == NULL) {
		sp = tmpfile();
		if (sp == NULL) {
			exerror("scanf: failed to open temporary file");
			return 0;
		}
		fputs(v.string, sp);
		rewind(sp);
		n = sfvscanf(sp, &fmt.fmt);
		fclose(sp);
	} else {
		n = sfvscanf(sp, &fmt.fmt);
	}
	if (fmt.actuals && !*fmt.fmt.form)
		exerror("scanf: %s: too many arguments", fmt.actuals->data.operand.left->data.variable.symbol->name);
	return n;
}

/*
 * string add
 */

static char*
str_add(Expr_t* ex, char* l, char* r)
{
	size_t sz = strlen(l) + strlen(r) + 1;
	char *const s = gv_arena_alloc(&ex->ve, 1, sz);
	snprintf(s, sz, "%s%s", l, r);
	return s;
}

/*
 * string ior
 */

static char *str_ior(Expr_t *ex, const char *l, const char *r) {

  // compute how much space the result will occupy
  size_t len = 1; // 1 for NUL terminator
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(p + 1, *p) == NULL) {
      ++len;
    }
  }
  for (const char *p = r; *p != '\0'; ++p) {
    if (strchr(l, *p) == NULL && strchr(p + 1, *p) == NULL) {
      ++len;
    }
  }

  // allocate a buffer to store this
  char *const result = gv_arena_alloc(&ex->ve, 1, len);

  // write the result
  size_t i = 0;
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(p + 1, *p) == NULL) {
      assert(i < len && "incorrect preceding length computation");
      result[i] = *p;
      ++i;
    }
  }
  for (const char *p = r; *p != '\0'; ++p) {
    if (strchr(l, *p) == NULL && strchr(p + 1, *p) == NULL) {
      assert(i < len && "incorrect preceding length computation");
      result[i] = *p;
      ++i;
    }
  }
  assert(i + 1 == len && "incorrect preceding length computation");

  return result;
}

/*
 * string and
 */

static char *str_and(Expr_t *ex, const char *l, const char *r) {

  // compute how much space the result will occupy
  size_t len = 1; // 1 for NUL terminator
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(r, *p) != NULL && strchr(p + 1, *p) == NULL) {
      ++len;
    }
  }

  // allocate a buffer to store this
  char *const result = gv_arena_alloc(&ex->ve, 1, len);

  // write the result
  size_t i = 0;
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(r, *p) != NULL && strchr(p + 1, *p) == NULL) {
      assert(i < len && "incorrect preceding length computation");
      result[i] = *p;
      ++i;
    }
  }
  assert(i + 1 == len && "incorrect preceding length computation");

  return result;
}

/*
 * string xor
 */

static char *str_xor(Expr_t *ex, const char *l, const char *r) {

  // compute how much space the result will occupy
  size_t len = 1; // 1 for NUL terminator
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(r, *p) == NULL && strchr(p + 1, *p) == NULL) {
      ++len;
    }
  }
  for (const char *p = r; *p != '\0'; ++p) {
    if (strchr(l, *p) == NULL && strchr(p + 1, *p) == NULL) {
      ++len;
    }
  }

  // allocate a buffer to store this
  char *const result = gv_arena_alloc(&ex->ve, 1, len);

  // write the result
  size_t i = 0;
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(r, *p) == NULL && strchr(p + 1, *p) == NULL) {
      assert(i < len && "incorrect preceding length computation");
      result[i] = *p;
      ++i;
    }
  }
  for (const char *p = r; *p != '\0'; ++p) {
    if (strchr(l, *p) == NULL && strchr(p + 1, *p) == NULL) {
      assert(i < len && "incorrect preceding length computation");
      result[i] = *p;
      ++i;
    }
  }
  assert(i + 1 == len && "incorrect preceding length computation");

  return result;
}

/*
 * string mod
 */

static char *str_mod(Expr_t *ex, const char *l, const char *r) {

  // compute how much space the result will occupy
  size_t len = 1; // 1 for NUL terminator
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(r, *p) == NULL && strchr(p + 1, *p) == NULL) {
      ++len;
    }
  }

  // allocate a buffer to store this
  char *const result = gv_arena_alloc(&ex->ve, 1, len);

  // write the result
  size_t i = 0;
  for (const char *p = l; *p != '\0'; ++p) {
    if (strchr(r, *p) == NULL && strchr(p + 1, *p) == NULL) {
      assert(i < len && "incorrect preceding length computation");
      result[i] = *p;
      ++i;
    }
  }
  assert(i + 1 == len && "incorrect preceding length computation");

  return result;
}

/*
 * string mpy
 */

static char *str_mpy(Expr_t *ex, const char *l, const char *r) {

  // compute how much space the result will occupy
  size_t len = strlen(l);
  {
    size_t len2 = strlen(r);
    if (len2 < len) {
      len = len2;
    }
  }
  ++len; // 1 for NUL terminator

  // allocate a buffer to store this
  char *const result = gv_arena_alloc(&ex->ve, 1, len);

  // write the result
  size_t i = 0;
  for (; l[i] != '\0' && r[i] != '\0'; ++i) {
    assert(i < len && "incorrect preceding length computation");
    result[i] = l[i] == r[i] ? l[i] : ' ';
  }
  assert(i + 1 == len && "incorrect preceding length computation");

  return result;
}

/* replace:
 * Add replacement string.
 * \digit is replaced with a subgroup match, if any.
 */
static void replace(agxbuf *s, char *base, char *repl, int ng, size_t *sub) {
  char c;
  int idx;

  while ((c = *repl++)) {
    if (c == '\\') {
      if ((c = *repl) && gv_isdigit(c)) {
        idx = c - '0';
        if (idx < ng) {
          const size_t offset = sub[2 * idx];
          agxbput_n(s, base + offset, sub[2 * idx + 1] - offset);
        }
        repl++;
      } else {
        agxbputc(s, '\\');
      }
    } else {
      agxbputc(s, c);
    }
  }
}

static void addItem(Expr_t *ex, Dt_t *arr, Extype_t v, char *tok) {
	Exassoc_t* b;

	if (!(b = dtmatch(arr, &v))) {
		b = ARENA_NEW(&ex->vm, Exassoc_t);
		b->key = v;
		dtinsert(arr, b);
	}
	b->value.string = tok;
}

/* exsplit:
 * break string into possibly empty fields and store in array
 * return number of fields
 */
static Extype_t exsplit(Expr_t *ex, Exnode_t *exnode, void *env) {
	Extype_t v;
	char *str;
	char *seps;
	char *tok;
	size_t sz;
	Dt_t* arr = exnode->data.split.array->local;

	str = eval(ex, exnode->data.split.string, env).string;
	if (exnode->data.split.seps)
		seps = eval(ex, exnode->data.split.seps, env).string;
	else
		seps = " \t\n";

	v.integer = 0;
	while (*str) {
		sz = strspn (str, seps);
	    if (sz) {
			if (v.integer == 0) {  /* initial separator => empty field */
	    		addItem(ex, arr, v, "");
	    		v.integer++;
			}
			for (size_t i = 1; i < sz; i++) {
	    		addItem(ex, arr, v, "");
	    		v.integer++;
			}
		}
		str += sz;
		if (*str == '\0') { /* terminal separator => empty field */
			addItem(ex, arr, v, "");
			v.integer++;
	    	break;
		}
		sz = strcspn (str, seps);
		tok = gv_arena_alloc(&ex->vm, 1, sz + 1);
		memcpy(tok, str, sz);
		addItem(ex, arr, v, tok);
		v.integer++;
		str += sz;
	}

	return v;
}

/* extoken:
 * tokenize string and store in array
 * return number of tokens
 */
static Extype_t extokens(Expr_t *ex, Exnode_t *exnode, void *env) {
	Extype_t v;
	char *str;
	char *seps;
	char *tok;
	size_t sz;
	Dt_t* arr = exnode->data.split.array->local;

	str = eval(ex, exnode->data.split.string, env).string;
	if (exnode->data.split.seps)
		seps = eval(ex, exnode->data.split.seps, env).string;
	else
		seps = " \t\n";

	v.integer = 0;
	while (*str) {
		sz = strspn (str, seps);
		str += sz;
		if (*str == '\0')
	    	break;

		sz = strcspn (str, seps);
		assert (sz);
		tok = gv_arena_alloc(&ex->vm, 1, sz + 1);
		memcpy(tok, str, sz);
		addItem(ex, arr, v, tok);
		v.integer++;
		str += sz;
	}

	return v;
}

/* exsub:
 * return string after pattern substitution
 */
static Extype_t exsub(Expr_t *ex, Exnode_t *exnode, void *env, bool global) {
	char *str;
	char *pat;
	char *repl;
	char *p;
	char *s;
	Extype_t v;
	size_t sub[20];
	int flags = 0;
	int ng;

	str = eval(ex, exnode->data.string.base, env).string;
	pat = eval(ex, exnode->data.string.pat, env).string;
	if (exnode->data.string.repl)
		repl = eval(ex, exnode->data.string.repl, env).string;
	else
		repl = 0;

	if (!global) {
		if (*pat == '^') {
		    pat++;
		    flags |= STR_LEFT;
		}
		p = pat;
		while (*p)
		    p++;
		if (p > pat)
		    p--;
		if (*p == '$') {
		    if (p > pat && p[-1] == '\\') {
				*p-- = '\0';
				*p = '$';
	    	} else {
				flags |= STR_RIGHT;
				*p = '\0';
	    	}
		}
	}
	if (*pat == '\0') {
		v.string = gv_arena_strdup(&ex->ve, str);
		return v;
	}

	ng = strgrpmatch(str, pat, sub, sizeof(sub) / (sizeof(sub[0]) * 2), flags);
	if (ng == 0) {
		v.string = gv_arena_strdup(&ex->ve, str);
		return v;
	}
    if (sub[0] == sub[1]) {
		exwarn("pattern match of empty string - ill-specified pattern \"%s\"?", pat);
		v.string = gv_arena_strdup(&ex->ve, str);
		return v;
    } 

	agxbuf buffer = {0};

	agxbput_n(&buffer, str, sub[0]);

	if (repl) {
		replace(&buffer, str, repl, ng, sub);
	}

	s = str + sub[1];
	if (global) {
		while ((ng = strgrpmatch(s, pat, sub, sizeof(sub) / (sizeof(sub[0]) * 2), flags))) {
			agxbput_n(&buffer, s, sub[0]);
			if (repl) {
				replace(&buffer, s, repl, ng, sub);
			}
			s = s + sub[1];
		}
	}

	agxbput(&buffer, s);

	v.string = gv_arena_strdup(&ex->ve, agxbuse(&buffer));
	agxbfree(&buffer);
	return v;
}

/* exsubstr:
 * return substring.
 */
static Extype_t exsubstr(Expr_t *ex, Exnode_t *exnode, void *env) {
	Extype_t s;
	Extype_t i;
	Extype_t l;
	Extype_t v;

	s = eval(ex, exnode->data.string.base, env);
	const size_t len = strlen(s.string);
	i = eval(ex, exnode->data.string.pat, env);
	if (i.integer < 0 || len < (unsigned long long)i.integer)
		exerror("illegal start index in substr(%s,%lld)", s.string, i.integer);
	if (exnode->data.string.repl) {
		l = eval(ex, exnode->data.string.repl, env);
		if (l.integer < 0 ||
		    len - (unsigned long long)i.integer < (unsigned long long)l.integer)
	    exerror("illegal length in substr(%s,%lld,%lld)",
	            s.string, i.integer, l.integer);
	} else
		l.integer = (long long)(len - (unsigned long long)i.integer);

	v.string = gv_arena_alloc(&ex->ve, 1, (size_t)l.integer + 1);
	if (exnode->data.string.repl) {
		memcpy(v.string, s.string + i.integer, (size_t)l.integer);
	} else
		memcpy(v.string, s.string + i.integer, len - (size_t)i.integer);
	return v;
}

/* xConvert:
 * Convert from external type.
 */
static void xConvert(Expr_t *ex, Exnode_t *exnode, long type, Extype_t v,
	 Exnode_t * tmp)
{
	*tmp = *exnode->data.operand.left;
	tmp->data.constant.value = v;
	if (ex->disc->convertf(tmp, type, 0)) {
		exerror("%s: cannot convert %s value to %s",
			exnode->data.operand.left->data.variable.symbol->name,
			extypename(ex, exnode->data.operand.left->type), extypename(ex, type));
	}
	tmp->type = type;
}

/* xPrint:
 * Generate string representation from value of external type.
 */
static void xPrint(Expr_t *ex, Exnode_t *exnode, Extype_t v, Exnode_t *tmp) {
	*tmp = *exnode->data.operand.left;
	tmp->data.constant.value = v;
	if (ex->disc->stringof(ex, tmp, 0))
	exerror("%s: no string representation of %s value",
		exnode->data.operand.left->data.variable.symbol->name,
		extypename(ex, exnode->data.operand.left->type));
	tmp->type = STRING;
}

/*
 * internal exeval
 */
static long seed;

static Extype_t eval(Expr_t *ex, Exnode_t *exnode, void *env) {
	Exnode_t*	x;
	Exnode_t*	a;
	Extype_t**	t;
	int		n;
	Extype_t		v;
	Extype_t		r = {0};
	Extype_t		i;
	char*			e;
	Exnode_t		tmp;
	Exnode_t		rtmp;
	Exnode_t*		rp;
	Exassoc_t*		assoc;
	Extype_t		args[FRAME+1];
	Extype_t		save[FRAME];

	if (!exnode || ex->loopcount)
	{
		v.integer = 1;
		return v;
	}
	x = exnode->data.operand.left;
	switch (exnode->op)
	{
	case BREAK:
	case CONTINUE:
		v = eval(ex, x, env);
		ex->loopcount = v.integer;
		ex->loopop = exnode->op;
		return v;
	case CONSTANT:
		return exnode->data.constant.value;
	case DEC:
		n = -1;
	add:
		if (x->op == DYNAMIC)
			r = getdyn(ex, x, env, &assoc);
		else
		{
			if (x->data.variable.index)
				i = eval(ex, x->data.variable.index, env);
			else
				i.integer = EX_SCALAR;
			if (x->data.variable.dyna) {
				Extype_t locv;
				locv = getdyn(ex, x->data.variable.dyna, env, &assoc);
				x->data.variable.dyna->data.variable.dyna->data.constant.value = locv;
			}
			r = ex->disc->getf(ex, x, x->data.variable.symbol,
			                   x->data.variable.reference, env, (int)i.integer,
			                   ex->disc);
		}
		v = r;
		switch (x->type)
		{
		case FLOATING:
			v.floating += n;
			break;
		case INTEGER:
		case UNSIGNED:
			v.integer += n;
			break;
		default:
			goto huh;
		}
	set:
		if (x->op == DYNAMIC)
		{
			if (x->type == STRING)
			{
				v.string = gv_arena_strdup(&ex->vm, v.string);
			}
			if (assoc)
				assoc->value = v;
			else
				x->data.variable.symbol->value->data.constant.value = v;
		}
		else
		{
			if (x->data.variable.index)
				eval(ex, x->data.variable.index, env);
			if (x->data.variable.dyna) {
				Extype_t locv = getdyn(ex, x->data.variable.dyna, env, &assoc);
				x->data.variable.dyna->data.variable.dyna->data.constant.value = locv;
			}
			if (ex->disc->setf(ex, x, x->data.variable.symbol,
			                   x->data.variable.reference, env, v) < 0)
				exerror("%s: cannot set value", x->data.variable.symbol->name);
		}
		if (exnode->subop == PRE)
			r = v;
		return r;
	case DYNAMIC:
		return getdyn(ex, exnode, env, &assoc);
	case SPLIT:
		return exsplit(ex, exnode, env);
	case TOKENS:
		return extokens(ex, exnode, env);
	case GSUB:
		return exsub(ex, exnode, env, /* global = */ true);
	case SUB:
		return exsub(ex, exnode, env, /* global = */ false);
	case SUBSTR:
		return exsubstr(ex, exnode, env);
	case SRAND:
		v.integer = seed;
		if (exnode->binary) {
			seed = (long)eval(ex, x, env).integer;
		} else
			seed = (long)time(0);
		srand48(seed);
		return v;
	case RAND:
		v.floating = drand48();
		return v;
	case EXIT:
		v = eval(ex, x, env);
		if (ex->disc->exitf)
			ex->disc->exitf(env, (int)v.integer);
		else
			graphviz_exit((int)v.integer);
		UNREACHABLE();
	case IF:
		v = eval(ex, x, env);
		if (v.integer)
			eval(ex, exnode->data.operand.right->data.operand.left, env);
		else
			eval(ex, exnode->data.operand.right->data.operand.right, env);
		v.integer = 1;
		return v;
	case FOR:
	case WHILE:
		exnode = exnode->data.operand.right;
		for (;;)
		{
			r = eval(ex, x, env);
			if (!r.integer)
			{
				v.integer = 1;
				return v;
			}
			if (exnode->data.operand.right)
			{
				eval(ex, exnode->data.operand.right, env);
				if (ex->loopcount > 0 && (--ex->loopcount > 0 || ex->loopop != CONTINUE))
				{
					v.integer = 0;
					return v;
				}
			}
			if (exnode->data.operand.left)
				eval(ex, exnode->data.operand.left, env);
		}
		UNREACHABLE();
	case SWITCH:
		v = eval(ex, x, env);
		i.integer = x->type;
		r.integer = 0;
		x = exnode->data.operand.right;
		a = x->data.select.statement;
		n = 0;
		while ((x = x->data.select.next))
		{
			if (!(t = x->data.select.constant))
				n = 1;
			else
				for (; *t; t++)
				{
					switch ((int)i.integer)
					{
					case INTEGER:
					case UNSIGNED:
						if ((*t)->integer == v.integer)
							break;
						continue;
					case STRING:
						if (strmatch((*t)->string, v.string))
							break;
						continue;
					case FLOATING:
						if ((*t)->floating == v.floating)
							break;
						continue;
					}
					n = 1;
					break;
				}
			if (n)
			{
				if (!x->data.select.statement)
				{
					r.integer = 1;
					break;
				}
				r = eval(ex, x->data.select.statement, env);
				if (ex->loopcount > 0)
				{
					ex->loopcount--;
					break;
				}
			}
		}
		if (!n && a)
		{
			r = eval(ex, a, env);
			if (ex->loopcount > 0)
				ex->loopcount--;
		}
		return r;
	case ITERATE:
		v.integer = 0;
		if (exnode->data.generate.array->op == DYNAMIC)
		{
			n = exnode->data.generate.index->type == STRING;
			for (assoc = dtfirst(exnode->data.generate.array->data.variable.symbol->local); assoc; assoc = dtnext(exnode->data.generate.array->data.variable.symbol->local, assoc))
			{
				v.integer++;
				if (n)
					exnode->data.generate.index->value->data.constant.value.string = assoc->name;
				else
					exnode->data.generate.index->value->data.constant.value = assoc->key;
				eval(ex, exnode->data.generate.statement, env);
				if (ex->loopcount > 0 && (--ex->loopcount > 0 || ex->loopop != CONTINUE))
				{
					v.integer = 0;
					break;
				}
			}
		}
		else
		{
			r = ex->disc->getf(ex, exnode, exnode->data.generate.array->data.variable.symbol,
			                   exnode->data.generate.array->data.variable.reference, env,
			                   0, ex->disc);
			for (v.integer = 0; v.integer < r.integer; v.integer++)
			{
				exnode->data.generate.index->value->data.constant.value.integer = v.integer;
				eval(ex, exnode->data.generate.statement, env);
				if (ex->loopcount > 0 && (--ex->loopcount > 0 || ex->loopop != CONTINUE))
				{
					v.integer = 0;
					break;
				}
			}
		}
		return v;
    case ITERATOR:
		v.integer = 0;
		if (exnode->data.generate.array->op == DYNAMIC) {
			n = exnode->data.generate.index->type == STRING;
			for (assoc = dtlast(exnode->data.generate.array->
						   data.variable.symbol->local); assoc;
		 		assoc = dtprev(exnode->data.generate.array->data.variable.symbol->local,
						  assoc)) {
				v.integer++;
				if (n)
					exnode->data.generate.index->value->data.constant.value.string = assoc->name;
				else
					exnode->data.generate.index->value->data.constant.value = assoc->key;
				eval(ex, exnode->data.generate.statement, env);
				if (ex->loopcount > 0 && (--ex->loopcount > 0 || ex->loopop != CONTINUE)) {
					v.integer = 0;
					break;
				}
			}
		} else {
			r = ex->disc->getf(ex, exnode, exnode->data.generate.array->data.variable.symbol,
			                   exnode->data.generate.array->data.variable.reference, env,
			                   0, ex->disc);
			for (v.integer = r.integer-1; 0 <= v.integer; v.integer--) {
				exnode->data.generate.index->value->data.constant.value.integer = v.integer;
				eval(ex, exnode->data.generate.statement, env);
				if (ex->loopcount > 0 && (--ex->loopcount > 0 || ex->loopop != CONTINUE)) {
					v.integer = 0;
					break;
				}
			}
		}
		return v;
	case '#':
		if (exnode->data.variable.symbol->index > 0) {
			if (ex->disc->lengthf != NULL) {
				v = ex->disc->lengthf(exnode->data.variable.symbol, ex->disc);
			} else {
				exerror("%s: cannot get length", x->data.variable.symbol->name);
				v.integer = 0;
			}
		} else {
			v.integer = dtsize(exnode->data.variable.symbol->local);
		}
		return v;
	case IN_OP:
		v.integer = evaldyn (ex, exnode, env, 0);
		return v;
	case UNSET:
		if (exnode->data.variable.index) {
			v.integer = evaldyn (ex, exnode, env, 1);
		}
		else {
			dtclear(exnode->data.variable.symbol->local);
			v.integer = 0;
		}
		return v;
	case CALL:
		x = exnode->data.call.args;
		for (n = 0, a = exnode->data.call.procedure->value->data.procedure.args; a && x; a = a->data.operand.right)
		{
			if (n < (int)elementsof(args))
			{
				save[n] = a->data.operand.left->data.variable.symbol->value->data.constant.value;
				args[n++] = eval(ex, x->data.operand.left, env);
			}
			else
				a->data.operand.left->data.variable.symbol->value->data.constant.value = eval(ex, x->data.operand.left, env);
			x = x->data.operand.right;
		}
		for (n = 0, a = exnode->data.call.procedure->value->data.procedure.args;
		     a && n < (int)elementsof(save); a = a->data.operand.right)
			a->data.operand.left->data.variable.symbol->value->data.constant.value = args[n++];
		if (x)
			exerror("too many actual args");
		else if (a)
			exerror("not enough actual args");
		v = exeval(ex, exnode->data.call.procedure->value->data.procedure.body, env);
		for (n = 0, a = exnode->data.call.procedure->value->data.procedure.args;
		     a && n < (int)elementsof(save); a = a->data.operand.right)
			a->data.operand.left->data.variable.symbol->value->data.constant.value = save[n++];
		return v;
	case ARRAY:
		n = 0;
		for (x = exnode->data.operand.right; x && n < (int)elementsof(args);
		     x = x->data.operand.right)
			args[n++] = eval(ex, x->data.operand.left, env);
		return ex->disc->getf(ex, exnode->data.operand.left,
		                      exnode->data.operand.left->data.variable.symbol,
		                      exnode->data.operand.left->data.variable.reference, args,
		                      EX_ARRAY, ex->disc);
	case FUNCTION:
		n = 0;
		args[n++].string = env;
		for (x = exnode->data.operand.right; x && n < (int)elementsof(args);
		     x = x->data.operand.right)
			args[n++] = eval(ex, x->data.operand.left, env);
		return ex->disc->getf(ex, exnode->data.operand.left,
		                      exnode->data.operand.left->data.variable.symbol,
		                      exnode->data.operand.left->data.variable.reference,
		                      args+1, EX_CALL, ex->disc);
	case ID:
		if (exnode->data.variable.index)
			i = eval(ex, exnode->data.variable.index, env);
		else
			i.integer = EX_SCALAR;
		if (exnode->data.variable.dyna) {
			Extype_t locv = getdyn(ex, exnode->data.variable.dyna, env, &assoc);
			exnode->data.variable.dyna->data.variable.dyna->data.constant.value = locv;
		}
		return ex->disc->getf(ex, exnode, exnode->data.variable.symbol,
		                      exnode->data.variable.reference, env, (int)i.integer,
		                      ex->disc);
	case INC:
		n = 1;
		goto add;
	case PRINT:
		v.integer = prints(ex, exnode, env, stdout);
		return v;
	case PRINTF:
		v.integer = print(ex, exnode, env, NULL);
		return v;
	case RETURN:
		ex->loopret = eval(ex, x, env);
		ex->loopcount = 32767;
		ex->loopop = exnode->op;
		return ex->loopret;
	case SCANF:
	case SSCANF:
		v.integer = scan(ex, exnode, env, NULL);
		return v;
	case SPRINTF: {
		FILE *buffer = tmpfile();
		if (buffer == NULL) {
			fprintf(stderr, "out of memory\n");
			graphviz_exit(EXIT_FAILURE);
		}
		print(ex, exnode, env, buffer);
		const int64_t size = gv_ftell(buffer);
		if (size < 0) {
			fclose(buffer);
			fprintf(stderr, "failed to read back temporary file\n");
			graphviz_exit(EXIT_FAILURE);
		}
		rewind(buffer);
		v.string = gv_arena_alloc(&ex->ve, 1, (size_t)size + 1);
		if (fread(v.string, (size_t)size, 1, buffer) < 1) {
			fprintf(stderr, "failed to read back temporary file\n");
			graphviz_exit(EXIT_FAILURE);
		}
		fclose(buffer);
		return v;
	}
	case '=':
		v = eval(ex, exnode->data.operand.right, env);
		if (exnode->subop != '=')
		{
			r = v;
			if (x->op == DYNAMIC)
				v = getdyn(ex, x, env, &assoc);
			else
			{
				if (x->data.variable.index)
					v = eval(ex, x->data.variable.index, env);
				else
					v.integer = EX_SCALAR;
				if (x->data.variable.dyna) {
					Extype_t locv;
					locv = getdyn(ex, x->data.variable.dyna, env, &assoc);
					x->data.variable.dyna->data.variable.dyna->data.constant.value = locv;
				}
				v = ex->disc->getf(ex, x, x->data.variable.symbol,
				                   x->data.variable.reference, env, (int)v.integer,
				                   ex->disc);
			}
			switch (x->type)
			{
			case FLOATING:
				switch (exnode->subop)
				{
				case '+':
					v.floating += r.floating;
					break;
				case '-':
					v.floating -= r.floating;
					break;
				case '*':
					v.floating *= r.floating;
					break;
				case '/':
					if (r.floating == 0.0)
						exerror("floating divide by 0");
					else
						v.floating /= r.floating;
					break;
				case '%': {
					const double rounded = round(r.floating);
					if (LLONG_MAX < (INT64_C(1) << 53) &&
					    (rounded > (double)LLONG_MAX || rounded < (double)LLONG_MIN)) {
						exerror("floating modulus out of range");
					}
					if ((long long)rounded == 0)
						exerror("floating 0 modulus");
					else
						v.floating = (long long)v.floating % (long long)rounded;
					break;
				}
				case '&':
					v.floating = (long long)v.floating & (long long)r.floating;
					break;
				case '|':
					v.floating = (long long)v.floating | (long long)r.floating;
					break;
				case '^':
					v.floating = (long long)v.floating ^ (long long)r.floating;
					break;
				case LSH:
					v.floating = (long long)v.floating << (long long)r.floating;
					break;
				case RSH:
#ifdef _WIN32
					v.floating = (long long)((unsigned long long)v.floating >> (long long)r.floating);
#else
					v.floating = (unsigned long long)v.floating >> (long long)r.floating;
#endif
					break;
				default:
					goto huh;
				}
				break;
			case INTEGER:
			case UNSIGNED:
				switch (exnode->subop)
				{
				case '+':
					v.integer += r.integer;
					break;
				case '-':
					v.integer -= r.integer;
					break;
				case '*':
					v.integer *= r.integer;
					break;
				case '/':
					if (r.integer == 0)
						exerror("integer divide by 0");
					else
						v.integer /= r.integer;
					break;
				case '%':
					if (r.integer == 0)
						exerror("integer 0 modulus");
					else
						v.integer %= r.integer;
					break;
				case '&':
					v.integer &= r.integer;
					break;
				case '|':
					v.integer |= r.integer;
					break;
				case '^':
					v.integer ^= r.integer;
					break;
				case LSH:
					v.integer <<= r.integer;
					break;
				case RSH:
					v.integer = (unsigned long long)v.integer >> r.integer;
					break;
				default:
					goto huh;
				}
				break;
			case STRING:
				switch (exnode->subop)
				{
				case '+':
					v.string = str_add(ex, v.string, r.string);
					break;
				case '|':
					v.string = str_ior(ex, v.string, r.string);
					break;
				case '&':
					v.string = str_and(ex, v.string, r.string);
					break;
				case '^':
					v.string = str_xor(ex, v.string, r.string);
					break;
				case '%':
					v.string = str_mod(ex, v.string, r.string);
					break;
				case '*':
					v.string = str_mpy(ex, v.string, r.string);
					break;
				default:
					goto huh;
				}
				break;
			default:
				goto huh;
			}
		}
		else if (x->op == DYNAMIC)
			getdyn(ex, x, env, &assoc);
		else
			assoc = 0;
		r = v;
		goto set;
	case ';':
	case ',':
		v = eval(ex, x, env);
		while ((exnode = exnode->data.operand.right) && (exnode->op == ';' || exnode->op == ','))
		{
			v = eval(ex, exnode->data.operand.left, env);
			if (ex->loopcount)
				return v;
		}
		return exnode ? eval(ex, exnode, env) : v;
	case '?':
		v = eval(ex, x, env);
		return v.integer ? eval(ex, exnode->data.operand.right->data.operand.left, env) : eval(ex, exnode->data.operand.right->data.operand.right, env);
	case AND:
		v = eval(ex, x, env);
		return v.integer ? eval(ex, exnode->data.operand.right, env) : v;
	case OR:
		v = eval(ex, x, env);
		return v.integer ? v : eval(ex, exnode->data.operand.right, env);
	}
	v = eval(ex, x, env);
	if ((x = exnode->data.operand.right)) {
		r = eval(ex, x, env);
		if (!BUILTIN(x->type) && exnode->binary) {
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			rtmp = *x;
			rtmp.data.constant.value = r;
			if (!ex->disc->binaryf(&tmp, exnode, &rtmp, 0))
			  return tmp.data.constant.value;
		}
	}
	switch (exnode->data.operand.left->type)
	{
	case FLOATING:
		switch (exnode->op)
		{
		case F2I:
			v.integer = v.floating;
			return v;
		case F2S:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (exnode->data.operand.left->op != DYNAMIC && exnode->data.operand.left->op != ID)
			{
				tmp.data.constant.value.string = exprintf(&ex->ve, "%g", v.floating);
			}
			else if (ex->disc->convertf(&tmp, STRING, 0)) {
				tmp.data.constant.value.string = exprintf(&ex->ve, "%g", v.floating);
			}
			tmp.type = STRING;
			return tmp.data.constant.value;
		case F2X:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (ex->disc->convertf(&tmp, exnode->type, 0))
				exerror("%s: cannot convert floating value to external", tmp.data.variable.symbol->name);
			tmp.type = exnode->type;
			return tmp.data.constant.value;
		case '!':
			v.floating = !(long long)v.floating;
			return v;
		case '~':
			v.floating = ~(long long)v.floating;
			return v;
		case '-':
			if (x)
				v.floating -= r.floating;
			else
				v.floating = -v.floating;
			return v;
		case '+':
			v.floating += r.floating;
			return v;
		case '&':
			v.floating = (long long)v.floating & (long long)r.floating;
			return v;
		case '|':
			v.floating = (long long)v.floating | (long long)r.floating;
			return v;
		case '^':
			v.floating = (long long)v.floating ^ (long long)r.floating;
			return v;
		case '*':
			v.floating *= r.floating;
			return v;
		case '/':
			if (r.floating == 0.0)
				exerror("floating divide by 0");
			else
				v.floating /= r.floating;
			return v;
		case '%':
			if ((r.integer = r.floating) == 0)
				exerror("floating 0 modulus");
			else
				v.floating = (long long)v.floating % r.integer;
			return v;
		case '<':
			v.integer = v.floating < r.floating;
			return v;
		case LE:
			v.integer = v.floating <= r.floating;
			return v;
		case EQ:
			v.integer = v.floating == r.floating;
			return v;
		case NE:
			v.integer = v.floating != r.floating;
			return v;
		case GE:
			v.integer = v.floating >= r.floating;
			return v;
		case '>':
			v.integer = v.floating > r.floating;
			return v;
		case LSH:
			v.integer = (long long)((unsigned long long)v.floating << (long long)r.floating);
			return v;
		case RSH:
			v.integer = (long long)((unsigned long long)v.floating >> (long long)r.floating);
			return v;
		}
		break;
	default:
		switch (exnode->op)
		{
		case X2F:
			xConvert(ex, exnode, FLOATING, v, &tmp);
			return tmp.data.constant.value;
		case X2I:
			xConvert(ex, exnode, INTEGER, v, &tmp);
			return tmp.data.constant.value;
		case X2S:
			xConvert(ex, exnode, STRING, v, &tmp);
			return tmp.data.constant.value;
		case X2X:
			xConvert(ex, exnode, exnode->type, v, &tmp);
			return tmp.data.constant.value;
		case XPRINT:
			xPrint(ex, exnode, v, &tmp);
			return tmp.data.constant.value;
		default:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (x) {
				rtmp = *x;
				rtmp.data.constant.value = r;
				rp = &rtmp;
			} else
				rp = 0;
			if (!ex->disc->binaryf(&tmp, exnode, rp, 0))
				return tmp.data.constant.value;
		}
		goto integer;
	case UNSIGNED:
		switch (exnode->op)
		{
		case '<':
			v.integer = (unsigned long long)v.integer < (unsigned long long)r.integer;
			return v;
		case LE:
			v.integer = (unsigned long long)v.integer <= (unsigned long long)r.integer;
			return v;
		case GE:
			v.integer = (unsigned long long)v.integer >= (unsigned long long)r.integer;
			return v;
		case '>':
			v.integer = (unsigned long long)v.integer > (unsigned long long)r.integer;
			return v;
		}
		/*FALLTHROUGH*/
	case INTEGER:
	integer:
		switch (exnode->op)
		{
		case I2F:
#ifdef _WIN32
			v.floating = v.integer;
#else
			if (exnode->type == UNSIGNED)
				v.floating = (unsigned long long)v.integer;
			else
				v.floating = v.integer;
#endif
			return v;
		case I2S:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (exnode->data.operand.left->op != DYNAMIC && exnode->data.operand.left->op != ID)
			{
				char *str;
				if (exnode->data.operand.left->type == UNSIGNED)
					str = exprintf(&ex->ve, "%llu", (unsigned long long)v.integer);
				else
					str = exprintf(&ex->ve, "%lld", v.integer);
				tmp.data.constant.value.string = str;
			}
			else if (ex->disc->convertf(&tmp, STRING, 0)) {
				char *str = NULL;
				if (exnode->data.operand.left->type == UNSIGNED)
					str = exprintf(&ex->ve, "%llu", (unsigned long long)v.integer);
				else
					str = exprintf(&ex->ve, "%lld", v.integer);
				tmp.data.constant.value.string = str;
			}
			tmp.type = STRING;
			return tmp.data.constant.value;
		case I2X:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (ex->disc->convertf(&tmp, exnode->type, 0))
				exerror("%s: cannot convert integer value to external", tmp.data.variable.symbol->name);
			tmp.type = exnode->type;
			return tmp.data.constant.value;
		case '!':
			v.integer = !v.integer;
			return v;
		case '~':
			v.integer = ~v.integer;
			return v;
		case '-':
			if (x)
				v.integer -= r.integer;
			else
				v.integer = -v.integer;
			return v;
		case '+':
			v.integer += r.integer;
			return v;
		case '&':
			v.integer &= r.integer;
			return v;
		case '|':
			v.integer |= r.integer;
			return v;
		case '^':
			v.integer ^= r.integer;
			return v;
		case '*':
			v.integer *= r.integer;
			return v;
		case '/':
			if (r.integer == 0)
				exerror("integer divide by 0");
			else
				v.integer /= r.integer;
			return v;
		case '%':
			if (r.integer == 0)
				exerror("integer 0 modulus");
			else
				v.integer %= r.integer;
			return v;
		case EQ:
			v.integer = v.integer == r.integer;
			return v;
		case NE:
			v.integer = v.integer != r.integer;
			return v;
		case LSH:
			v.integer = v.integer << r.integer;
			return v;
		case RSH:
			v.integer = (unsigned long long)v.integer >> r.integer;
			return v;
		case '<':
			v.integer = v.integer < r.integer;
			return v;
		case LE:
			v.integer = v.integer <= r.integer;
			return v;
		case GE:
			v.integer = v.integer >= r.integer;
			return v;
		case '>':
			v.integer = v.integer > r.integer;
			return v;
		}
		break;
	case STRING:
		switch (exnode->op)
		{
		case S2B:
			v.integer = *v.string != 0;
			return v;
		case S2F:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (ex->disc->convertf(&tmp, FLOATING, 0))
			{
				tmp.data.constant.value.floating = strtod(v.string, &e);
				if (*e)
					tmp.data.constant.value.floating = *v.string != 0;
			}
			tmp.type = FLOATING;
			return tmp.data.constant.value;
		case S2I:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (ex->disc->convertf(&tmp, INTEGER, 0))
			{
				if (v.string) {
					tmp.data.constant.value.integer = strtoll(v.string, &e, 0);
                    if (*e)
                        tmp.data.constant.value.integer = *v.string != 0;
				}
				else
					tmp.data.constant.value.integer = 0;
			}
			tmp.type = INTEGER;
			return tmp.data.constant.value;
		case S2X:
			tmp = *exnode->data.operand.left;
			tmp.data.constant.value = v;
			if (ex->disc->convertf(&tmp, exnode->type, 0))
				exerror("%s: cannot convert string value to external", tmp.data.variable.symbol->name);
			tmp.type = exnode->type;
			return tmp.data.constant.value;
		case EQ:
		case NE:
			v.integer = ((v.string && r.string)
			              ? strmatch(v.string, r.string)
			              : (v.string == r.string)) == (exnode->op == EQ);
			return v;
		case '+':
			v.string = str_add(ex, v.string, r.string);
			return v;
		case '|':
			v.string = str_ior(ex, v.string, r.string);
			return v;
		case '&':
			v.string = str_and(ex, v.string, r.string);
			return v;
		case '^':
			v.string = str_xor(ex, v.string, r.string);
			return v;
		case '%':
			v.string = str_mod(ex, v.string, r.string);
			return v;
		case '*':
			v.string = str_mpy(ex, v.string, r.string);
			return v;
		}
		v.integer = strcoll(v.string, r.string);
		switch (exnode->op)
		{
		case '<':
			v.integer = v.integer < 0;
			return v;
		case LE:
			v.integer = v.integer <= 0;
			return v;
		case GE:
			v.integer = v.integer >= 0;
			return v;
		case '>':
			v.integer = v.integer > 0;
			return v;
		}
		goto huh;
	}
 huh:;
	char *left = lexname(exnode->data.operand.left->type, -1);
	char *op = lexname(exnode->op, exnode->subop);
	if (exnode->binary) {
		char *right = exnode->data.operand.right
		            ? lexname(exnode->data.operand.right->type, -1) : NULL;
		exerror("operator %s %s %s not implemented", left, op,
		        right ? right : "UNARY");
		free(right);
	} else
		exerror("operator %s %s not implemented", op, left);
	free(op);
	free(left);
	return exzero(exnode->type);
}

/*
 * evaluate expression expr
 */

Extype_t exeval(Expr_t *ex, Exnode_t *exnode, void *env) {
	Extype_t v = eval(ex, exnode, env);
	if (ex->loopcount > 0)
	{
		ex->loopcount = 0;
		if (ex->loopop == RETURN)
			return ex->loopret;
	}
	return v;
}

/* exstring:
 * Generate copy of input string using
 * string memory.
 */
char *exstring(Expr_t * ex, char *s)
{
    return gv_arena_strdup(&ex->ve, s);
}

/* exstralloc:
 * allocate sz bytes in expression memory.
 */
void *exstralloc(Expr_t * ex, size_t sz)
{
    return gv_arena_alloc(&ex->ve, 1, sz);
}
