/*
 * Copyright (C) 2020 Jo-Philipp Wich <jo@mein.io>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "lexer.h"
#include "parser.h"
#include "eval.h"
#include "lib.h"

#include <math.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <regex.h>

bool
uc_val_is_truish(struct json_object *val)
{
	struct uc_op *tag = json_object_get_userdata(val);
	double d;

	switch (tag ? tag->type : 0) {
	case T_EXCEPTION:
		return false;

	default:
		switch (json_object_get_type(val)) {
		case json_type_int:
			return (json_object_get_int64(val) != 0);

		case json_type_double:
			d = json_object_get_double(val);

			return (d != 0 && !isnan(d));

		case json_type_boolean:
			return (json_object_get_boolean(val) != false);

		case json_type_string:
			return (json_object_get_string_len(val) > 0);

		case json_type_array:
		case json_type_object:
			return true;

		default:
			return false;
		}
	}
}

enum json_type
uc_cast_number(struct json_object *v, int64_t *n, double *d)
{
	bool is_double = false;
	const char *s;
	char *e;

	*d = 0.0;
	*n = 0;

	switch (json_object_get_type(v)) {
	case json_type_int:
		*n = json_object_get_int64(v);

		return json_type_int;

	case json_type_double:
		*d = json_object_get_double(v);

		return json_type_double;

	case json_type_null:
		return json_type_int;

	case json_type_boolean:
		*n = json_object_get_boolean(v) ? 1 : 0;

		return json_type_int;

	case json_type_string:
		s = json_object_get_string(v);

		while (isspace(*s))
			s++;

		if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && isxdigit(s[2])) {
			*n = strtoll(s, &e, 16);
		}
		else if (s[0] == '0' && isdigit(s[2])) {
			*n = strtoll(s, &e, 8);
		}
		else {
			*n = strtoll(s, &e, 10);

			if (*e == '.') {
				*d = strtod(s, &e);
				is_double = (e > s);
			}
		}

		while (isspace(*e))
			e++;

		if (*e) {
			*d = NAN;

			return json_type_double;
		}

		if (is_double)
			return json_type_double;

		return json_type_int;

	default:
		*d = NAN;

		return json_type_double;
	}
}

static struct json_object *
uc_execute_op(struct uc_state *state, uint32_t off);

static struct json_object *
uc_execute_op_sequence(struct uc_state *state, uint32_t off);

static struct json_object *
uc_execute_list(struct uc_state *state, uint32_t off);

static char *
uc_ref_to_str(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct uc_op *op2 = uc_get_child(state, off, 1);
	const char *l;
	char *s, *p;

	switch (op ? op->type : 0) {
	case T_DOT:
		s = uc_ref_to_str(state, op->tree.operand[0]);
		l = ((op2 ? op2->type : 0) == T_LABEL) ? json_object_get_string(op2->val) : "???";

		if (asprintf(&p, "%s.%s", s ? s : "(...)", l) == -1)
			p = NULL;

		free(s);

		return p;

	case T_LBRACK:
		if (!op->is_postfix)
			return NULL;

		/* fall through */

	case T_LPAREN:
		s = uc_ref_to_str(state, op->tree.operand[0]);

		switch (op2 ? op2->type : 0) {
		case T_STRING:
			l = json_object_to_json_string_ext(op2->val, JSON_C_TO_STRING_NOSLASHESCAPE);
			break;

		case T_NUMBER:
		case T_LABEL:
		case T_BOOL:
			l = json_object_get_string(op2->val);
			break;

		default:
			l = "...";
		}

		if (asprintf(&p, "%s%c%s%c", s ? s : "(...)",
		             (op->type == T_LPAREN) ? '(' : '[', l,
		             (op->type == T_LPAREN) ? ')' : ']') == -1)
			p = NULL;

		free(s);

		return p;

	case T_LABEL:
		return strdup(json_object_get_string(op->val));

	default:
		return NULL;
	}
}

static struct json_object *
uc_getref(struct uc_state *state, uint32_t off, struct json_object **key)
{
	struct uc_op *op = uc_get_op(state, off);
	uint32_t off1 = op ? op->tree.operand[0] : 0;
	uint32_t off2 = op ? op->tree.operand[1] : 0;
	struct uc_scope *sc, *next;
	struct json_object *val;

	if (key)
		*key = NULL;

	if (op && op->type == T_DOT) {
		if (key)
			*key = off2 ? json_object_get(uc_get_op(state, off2)->val) : NULL;

		return uc_execute_op_sequence(state, off1);
	}
	else if (op && op->type == T_LBRACK && op->is_postfix) {
		if (key) {
			val = off2 ? uc_execute_op_sequence(state, off2) : NULL;

			if (uc_is_type(val, T_EXCEPTION))
				return val;

			*key = val;
		}

		return uc_execute_op_sequence(state, off1);
	}
	else if (op && op->type == T_LABEL) {
		sc = state->scope;

		while (true) {
			if (json_object_object_get_ex(sc->scope, json_object_get_string(op->val), NULL))
				break;

			next = uc_parent_scope(sc);

			if (!next) {
				if (state->strict_declarations) {
					return uc_new_exception(state, op->off,
					                        "Reference error: access to undeclared variable %s",
					                        json_object_get_string(op->val));
				}

				break;
			}

			sc = next;
		}

		if (key)
			*key = json_object_get(op->val);

		return json_object_get(sc->scope);
	}
	else {
		if (key)
			*key = NULL;

		return NULL;
	}
}

static struct json_object *
uc_getref_required(struct uc_state *state, uint32_t off, struct json_object **key)
{
	struct uc_op *op1 = uc_get_child(state, off, 0);
	struct json_object *scope, *skey, *rv;
	char *lhs;

	scope = uc_getref(state, off, &skey);

	if (!json_object_is_type(scope, json_type_array) &&
		!json_object_is_type(scope, json_type_object)) {
		if (!uc_is_type(scope, T_EXCEPTION)) {
			lhs = op1 ? uc_ref_to_str(state, uc_get_off(state, op1)) : NULL;

			if (lhs) {
				rv = uc_new_exception(state, op1->off, "Type error: `%s` is %s",
				                      lhs, scope ? "not an array or object" : "null");
				free(lhs);
			}
			else {
				rv = uc_new_exception(state, op1->off, "Type error: left-hand side is not an array or object");
			}

			json_object_put(scope);
		}
		else {
			rv = scope;
		}

		json_object_put(skey);

		*key = NULL;
		return rv;
	}

	*key = skey;
	return scope;
}

static struct json_object *
uc_getproto(struct json_object *obj)
{
	struct uc_op *op = json_object_get_userdata(obj);

	if (!op || (op->type != T_LBRACE && op->type <= __T_MAX) || !op->val)
		return NULL;

	return op->tag.proto;
}

static struct json_object *
uc_getval(struct json_object *scope, struct json_object *key)
{
	struct json_object *o, *v;
	int64_t idx;
	double d;

	if (!key)
		return NULL;

	if (json_object_is_type(scope, json_type_array)) {
		/* only consider doubles with integer values as array keys */
		if (json_object_is_type(key, json_type_double)) {
			d = json_object_get_double(key);

			if ((double)(int64_t)(d) != d)
				return NULL;

			idx = (int64_t)d;
		}
		else {
			errno = 0;
			idx = json_object_get_int64(key);

			if (errno != 0)
				return NULL;
		}

		return json_object_get(json_object_array_get_idx(scope, idx));
	}

	for (o = scope; o; o = uc_getproto(o)) {
		if (!json_object_is_type(o, json_type_object))
			continue;

		if (json_object_object_get_ex(o, key ? json_object_get_string(key) : "null", &v))
			return json_object_get(v);
	}

	return NULL;
}

static struct json_object *
uc_setval(struct json_object *scope, struct json_object *key, struct json_object *val)
{
	int64_t idx;

	if (!key)
		return NULL;

	if (json_object_is_type(scope, json_type_array)) {
		errno = 0;
		idx = json_object_get_int64(key);

		if (errno != 0)
			return NULL;

		if (json_object_array_put_idx(scope, idx, val))
			return NULL;

		return json_object_get(val);
	}

	if (json_object_object_add(scope, key ? json_object_get_string(key) : "null", val))
		return NULL;

	return json_object_get(val);
}

static struct json_object *
uc_execute_assign(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	uint32_t label = op ? op->tree.operand[0] : 0;
	uint32_t value = op ? op->tree.operand[1] : 0;
	struct json_object *scope, *key, *val;

	scope = uc_getref_required(state, label, &key);

	if (!key)
		return scope;

	val = uc_execute_op_sequence(state, value);

	if (!uc_is_type(val, T_EXCEPTION))
		uc_setval(scope, key, val);

	json_object_put(scope);
	json_object_put(key);

	return val;
}

static struct json_object *
uc_execute_local(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off), *asop, *alop;
	uint32_t as = op ? op->tree.operand[0] : 0;
	struct json_object *val, *rv = NULL;

	while (as) {
		asop = uc_get_op(state, as);
		as = asop ? asop->tree.next : 0;

		switch (asop ? asop->type : 0) {
		case T_ASSIGN:
			alop = uc_get_op(state, asop->tree.operand[0]);
			val = uc_execute_op_sequence(state, asop->tree.operand[1]);

			if (uc_is_type(val, T_EXCEPTION))
				return val;

			break;

		case T_LABEL:
			alop = asop;
			val = NULL;
			break;

		default:
			continue;
		}

		if (alop) {
			json_object_put(rv);
			rv = uc_setval(state->scope->scope, alop->val, val);
		}
	}

	return rv;
}

static struct json_object *
uc_execute_op_sequence(struct uc_state *state, uint32_t off);

static bool
uc_test_condition(struct uc_state *state, uint32_t off)
{
	struct json_object *val = uc_execute_op_sequence(state, off);
	bool istrue = uc_val_is_truish(val);

	json_object_put(val);

	return istrue;
}

static struct json_object *
uc_execute_if(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	uint32_t cond = op ? op->tree.operand[0] : 0;
	uint32_t Then = op ? op->tree.operand[1] : 0;
	uint32_t Else = op ? op->tree.operand[2] : 0;
	bool res = uc_test_condition(state, cond);

	if (state->exception)
		return json_object_get(state->exception);
	else if (res)
		return uc_execute_op_sequence(state, Then);
	else if (Else)
		return uc_execute_op_sequence(state, Else);

	return NULL;
}

static struct json_object *
uc_execute_for(struct uc_state *state, uint32_t off)
{
	struct json_object *kscope, *vscope, *val, *item, *ik, *iv = NULL, *rv = NULL;
	struct uc_op *loop = uc_get_op(state, off);
	struct uc_op *init = uc_get_child(state, off, 0);
	uint32_t test = loop ? loop->tree.operand[1] : 0;
	uint32_t incr = loop ? loop->tree.operand[2] : 0;
	uint32_t body = loop ? loop->tree.operand[3] : 0;
	struct uc_op *ikvar, *ivvar, *tag;
	size_t arridx, arrlen;
	bool local = false;

	/* for (x in ...) loop variant */
	if (loop->is_for_in) {
		if (init->type == T_LOCAL) {
			local = true;
			init = uc_get_op(state, init->tree.operand[0]);
		}

		ikvar = uc_get_op(state, init->tree.operand[0]);
		ik = ikvar->val;
		kscope = local ? state->scope->scope : uc_getref(state, uc_get_off(state, ikvar), NULL);

		if (uc_is_type(kscope, T_EXCEPTION))
			return kscope;

		if (ikvar->tree.next) {
			ivvar = uc_get_op(state, ikvar->tree.next);
			iv = ivvar->val;
			vscope = local ? kscope : uc_getref(state, uc_get_off(state, ivvar), NULL);

			if (uc_is_type(vscope, T_EXCEPTION))
				return vscope;
		}

		val = uc_execute_op_sequence(state, init->tree.operand[1]);

		if (uc_is_type(val, T_EXCEPTION))
			return val;

		if (json_object_is_type(val, json_type_array)) {
			for (arridx = 0, arrlen = json_object_array_length(val);
			     arridx < arrlen; arridx++) {
				item = json_object_array_get_idx(val, arridx);

				if (iv) {
					uc_setval(kscope, ik, xjs_new_int64(arridx));
					uc_setval(vscope, iv, item);
				}
				else {
					uc_setval(kscope, ik, item);
				}

				json_object_put(rv);

				rv = uc_execute_op_sequence(state, body);
				tag = json_object_get_userdata(rv);

				switch (tag ? tag->type : 0) {
				case T_RETURN:
				case T_EXCEPTION:
					json_object_put(val);

					return rv;

				case T_BREAK:
					json_object_put(val);
					json_object_put(rv);

					return NULL;
				}
			}
		}
		else if (json_object_is_type(val, json_type_object)) {
			json_object_object_foreach(val, key, item) {
				json_object_put(uc_setval(kscope, ik, xjs_new_string(key)));

				if (iv)
					uc_setval(vscope, iv, item);

				json_object_put(rv);

				rv = uc_execute_op_sequence(state, body);
				tag = json_object_get_userdata(rv);

				switch (tag ? tag->type : 0) {
				case T_RETURN:
				case T_EXCEPTION:
					json_object_put(val);

					return rv;

				case T_BREAK:
					json_object_put(val);
					json_object_put(rv);

					return NULL;
				}
			}
		}

		json_object_put(val);
		json_object_put(rv);

		return NULL;
	}

	if (init) {
		val = uc_execute_op_sequence(state, uc_get_off(state, init));

		if (uc_is_type(val, T_EXCEPTION))
			return val;

		json_object_put(val);
	}

	while (test ? uc_test_condition(state, test) : true) {
		json_object_put(rv);

		rv = uc_execute_op_sequence(state, body);
		tag = json_object_get_userdata(rv);

		switch (tag ? tag->type : 0) {
		case T_RETURN:
		case T_EXCEPTION:
			return rv;

		case T_BREAK:
			json_object_put(rv);

			return NULL;
		}

		if (incr) {
			val = uc_execute_op_sequence(state, incr);

			if (uc_is_type(val, T_EXCEPTION)) {
				json_object_put(rv);

				return val;
			}

			json_object_put(val);
		}
	}

	json_object_put(rv);

	return NULL;
}

static struct json_object *
uc_execute_while(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	uint32_t test = op ? op->tree.operand[0] : 0;
	uint32_t body = op ? op->tree.operand[1] : 0;
	struct json_object *v, *rv = NULL;
	struct uc_op *tag = NULL;
	bool cond;

	while (1) {
		json_object_put(rv);

		v = test ? uc_execute_op_sequence(state, test) : NULL;
		cond = test ? uc_val_is_truish(v) : true;

		if (uc_is_type(v, T_EXCEPTION))
			return v;

		json_object_put(v);

		if (!cond)
			return NULL;

		rv = uc_execute_op_sequence(state, body);
		tag = json_object_get_userdata(rv);

		switch (tag ? tag->type : 0) {
		case T_RETURN:
		case T_EXCEPTION:
			return rv;

		case T_BREAK:
			json_object_put(rv);

			return NULL;
		}
	}

	json_object_put(rv);

	return NULL;
}

static struct json_object *
uc_execute_and_or(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *val = NULL;
	int i;

	for (i = 0; i < ARRAY_SIZE(op->tree.operand) && op->tree.operand[i]; i++) {
		json_object_put(val);

		val = uc_execute_op_sequence(state, op->tree.operand[i]);

		if (uc_is_type(val, T_EXCEPTION))
			break;

		if (uc_val_is_truish(val) == (op->type == T_OR))
			break;
	}

	return val;
}

bool
uc_cmp(int how, struct json_object *v1, struct json_object *v2)
{
	enum json_type t1 = json_object_get_type(v1);
	enum json_type t2 = json_object_get_type(v2);
	int64_t n1, n2, delta;
	double d1, d2;

	if (t1 == json_type_string && t2 == json_type_string) {
		delta = strcmp(json_object_get_string(v1), json_object_get_string(v2));
	}
	else {
		if ((t1 == json_type_array && t2 == json_type_array) ||
		    (t1 == json_type_object && t2 == json_type_object))	{
			delta = (void *)v1 - (void *)v2;
		}
		else {
			t1 = uc_cast_number(v1, &n1, &d1);
			t2 = uc_cast_number(v2, &n2, &d2);

			if (t1 == json_type_double || t2 == json_type_double) {
				d1 = (t1 == json_type_double) ? d1 : (double)n1;
				d2 = (t2 == json_type_double) ? d2 : (double)n2;

				if (d1 == d2)
					delta = 0;
				else if (d1 < d2)
					delta = -1;
				else
					delta = 1;
			}
			else {
				delta = n1 - n2;
			}
		}
	}

	switch (how) {
	case T_LT:
		return (delta < 0);

	case T_LE:
		return (delta <= 0);

	case T_GT:
		return (delta > 0);

	case T_GE:
		return (delta >= 0);

	case T_EQ:
		return (delta == 0);

	case T_NE:
		return (delta != 0);

	default:
		return false;
	}
}

static struct json_object *
_uc_get_operands(struct uc_state *state, struct uc_op *op, size_t n, struct json_object **v)
{
	struct json_object *ctx = NULL;
	struct uc_op *child;
	size_t i, j;

	for (i = 0; i < n; i++) {
		child = op ? uc_get_op(state, op->tree.operand[i]) : NULL;

		if (child && child->is_list)
			v[i] = uc_execute_list(state, uc_get_off(state, child));
		else if (child)
			v[i] = uc_execute_op_sequence(state, uc_get_off(state, child));
		else
			v[i] = NULL;

		if (i == 0)
			ctx = json_object_get(state->ctx);

		if (uc_is_type(v[i], T_EXCEPTION)) {
			json_object_put(ctx);

			for (j = 0; j < i; j++)
				json_object_put(v[j]);

			return v[i];
		}
	}

	json_object_put(state->ctx);
	state->ctx = ctx;

	return NULL;
}

#define uc_get_operands(state, op, vals) \
	do { \
		struct json_object *ex = _uc_get_operands(state, op, ARRAY_SIZE(vals), vals); \
		if (ex) return ex; \
	} while(0)

static struct json_object *
uc_execute_rel(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[2], *rv;

	uc_get_operands(state, op, v);

	rv = xjs_new_boolean(uc_cmp(op->type, v[0], v[1]));

	json_object_put(v[0]);
	json_object_put(v[1]);

	return rv;
}

static bool
uc_eq(struct json_object *v1, struct json_object *v2)
{
	struct uc_op *tag1 = json_object_get_userdata(v1);
	struct uc_op *tag2 = json_object_get_userdata(v2);
	enum json_type t1 = json_object_get_type(v1);
	enum json_type t2 = json_object_get_type(v2);

	if ((tag1 ? tag1->type : 0) != (tag2 ? tag2->type : 0))
		return false;

	if (t1 != t2)
		return false;

	switch (t1) {
	case json_type_array:
	case json_type_object:
		return (v1 == v2);

	case json_type_boolean:
		return (json_object_get_boolean(v1) == json_object_get_boolean(v2));

	case json_type_double:
		if (isnan(json_object_get_double(v1)) || isnan(json_object_get_double(v2)))
			return false;

		return (json_object_get_double(v1) == json_object_get_double(v2));

	case json_type_int:
		return (json_object_get_int64(v1) == json_object_get_int64(v2));

	case json_type_string:
		return !strcmp(json_object_get_string(v1), json_object_get_string(v2));

	case json_type_null:
		return true;
	}

	return false;
}

static struct json_object *
uc_execute_equality(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[2], *rv;
	bool equal = false;

	uc_get_operands(state, op, v);

	equal = uc_eq(v[0], v[1]);
	rv = xjs_new_boolean((op->type == T_EQS) ? equal : !equal);

	json_object_put(v[0]);
	json_object_put(v[1]);

	return rv;
}

static struct json_object *
uc_execute_in(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[2], *item;
	size_t arrlen, arridx;
	bool found = false;
	const char *key;

	uc_get_operands(state, op, v);

	if (json_object_is_type(v[1], json_type_array)) {
		for (arridx = 0, arrlen = json_object_array_length(v[1]);
		     arridx < arrlen; arridx++) {
			item = json_object_array_get_idx(v[1], arridx);

			if (uc_cmp(T_EQ, v[0], item)) {
				found = true;
				break;
			}
		}
	}
	else if (json_object_is_type(v[1], json_type_object)) {
		key = v[0] ? json_object_get_string(v[0]) : "null";
		found = json_object_object_get_ex(v[1], key, NULL);
	}

	json_object_put(v[0]);
	json_object_put(v[1]);

	return xjs_new_boolean(found);
}

static struct json_object *
uc_execute_inc_dec(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *val, *nval, *scope, *key;
	uint32_t label = op ? op->tree.operand[0] : 0;
	int64_t n;
	double d;

	scope = uc_getref_required(state, label, &key);

	if (!key)
		return scope;

	val = uc_getval(scope, key);

	json_object_put(scope);
	json_object_put(key);

	if (uc_cast_number(val, &n, &d) == json_type_double)
		nval = uc_new_double(d + (op->type == T_INC ? 1.0 : -1.0));
	else
		nval = xjs_new_int64(n + (op->type == T_INC ? 1 : -1));

	json_object_put(uc_setval(scope, key, nval));

	/* postfix inc/dec, return old val */
	if (op->is_postfix)
		return val;

	json_object_put(val);

	return json_object_get(nval);
}

static struct json_object *
uc_execute_list(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *ex, *val, *arr = xjs_new_array();
	size_t i;

	while (op) {
		val = uc_execute_op(state, uc_get_off(state, op));

		if (uc_is_type(val, T_EXCEPTION)) {
			json_object_put(arr);

			return val;
		}

		if (op->is_ellip) {
			if (!json_object_is_type(val, json_type_array)) {
				ex = uc_new_exception(state, op->off, "Type error: (%s) is not iterable",
				                      json_object_get_string(val));

				json_object_put(arr);
				json_object_put(val);

				return ex;
			}

			for (i = 0; i < json_object_array_length(val); i++)
				json_object_array_add(arr, json_object_get(json_object_array_get_idx(val, i)));

			json_object_put(val);
		}
		else {
			json_object_array_add(arr, val);
		}

		op = uc_get_op(state, op->tree.next);
	}

	return arr;
}

static struct json_object *
uc_execute_object(struct uc_state *state, uint32_t off)
{
	struct json_object *ex, *v, *obj = uc_new_object(NULL);
	struct uc_op *key;
	char *istr;
	size_t i;

	for (key = uc_get_child(state, off, 0); key; key = uc_get_op(state, key->tree.next)) {
		v = uc_execute_op_sequence(state, key->tree.operand[0]);

		if (uc_is_type(v, T_EXCEPTION)) {
			json_object_put(obj);

			return v;
		}

		if (key->type == T_ELLIP) {
			switch (json_object_get_type(v)) {
			case json_type_object:
				; /* a label can only be part of a statement and a declaration is not a statement */
				json_object_object_foreach(v, vk, vv)
					json_object_object_add(obj, vk, json_object_get(vv));

				json_object_put(v);

				break;

			case json_type_array:
				for (i = 0; i < json_object_array_length(v); i++) {
					xasprintf(&istr, "%zu", i);
					json_object_object_add(obj, istr, json_object_get(json_object_array_get_idx(v, i)));
					free(istr);
				}

				json_object_put(v);

				break;

			default:
				ex = uc_new_exception(state, key->off, "Type error: (%s) is not iterable",
				                      json_object_get_string(v));

				json_object_put(obj);
				json_object_put(v);

				return ex;
			}
		}
		else {
			json_object_object_add(obj, json_object_get_string(key->val), v);
		}
	}

	return obj;
}

struct json_object *
uc_invoke(struct uc_state *state, uint32_t off, struct json_object *this,
          struct json_object *func, struct json_object *argvals)
{
	struct uc_op *op, *tag = json_object_get_userdata(func);
	struct json_object *arr, *rv = NULL;
	struct uc_callstack callstack = {};
	struct uc_function *fn, *prev_fn;
	size_t arridx, arglen;
	struct uc_scope *sc;
	uc_c_fn *fptr;
	bool rest;

	if (!tag)
		return NULL;

	op = uc_get_op(state, off);

	if (state->calldepth >= 1000)
		return uc_new_exception(state, op->off, "Runtime error: Too much recursion");

	callstack.next = state->callstack;
	callstack.function = state->function;
	callstack.off = op ? op->off : 0;

	if (tag->is_arrow)
		callstack.ctx = state->callstack ? json_object_get(state->callstack->ctx) : NULL;
	else
		callstack.ctx = json_object_get(this ? this : state->ctx);

	state->callstack = &callstack;
	state->calldepth++;

	fn = tag->tag.data;

	prev_fn = state->function;
	state->function = fn;

	/* is native function */
	if (tag->type == T_CFUNC) {
		fptr = (uc_c_fn *)fn->cfn;
		rv = fptr ? fptr(state, off, argvals) : NULL;
	}

	/* is ucode function */
	else {
		callstack.scope = uc_new_scope(state, fn->parent_scope);

		sc = state->scope;
		state->scope = uc_acquire_scope(callstack.scope);

		if (fn->args) {
			arglen = json_object_array_length(fn->args);
			rest = (arglen > 1) && json_object_is_type(json_object_array_get_idx(fn->args, arglen - 1), json_type_null);

			for (arridx = 0; arridx < arglen - rest; arridx++) {
				/* if the last argument is a rest one (...arg), put all remaining parameter values in an array */
				if (rest && arridx == arglen - 2) {
					arr = xjs_new_array();

					uc_setval(callstack.scope->scope,
					          json_object_array_get_idx(fn->args, arridx),
					          arr);

					for (; argvals && arridx < json_object_array_length(argvals); arridx++)
						json_object_array_add(arr, json_object_get(json_object_array_get_idx(argvals, arridx)));

					break;
				}

				uc_setval(callstack.scope->scope, json_object_array_get_idx(fn->args, arridx),
				          argvals ? json_object_array_get_idx(argvals, arridx) : NULL);
			}
		}

		rv = uc_execute_op_sequence(state, fn->entry);
		tag = json_object_get_userdata(rv);

		switch (tag ? tag->type : 0) {
		case T_BREAK:
		case T_CONTINUE:
			json_object_put(rv);
			rv = uc_new_exception(state, uc_get_off(state, tag),
			                      "Syntax error: %s statement must be inside loop",
			                      uc_get_tokenname(tag->type));
			break;

		case T_RETURN:
			json_object_put(rv);
			rv = json_object_get(state->rval);
			break;
		}

		/* we left the function, pop the function scope... */
		uc_release_scope(state->scope);
		state->scope = sc;

		/* ... and release it */
		uc_release_scope(callstack.scope);

	}

	state->function = prev_fn;

	json_object_put(callstack.ctx);
	state->callstack = callstack.next;
	state->calldepth--;

	return rv;
}

static struct json_object *
uc_execute_call(struct uc_state *state, uint32_t off)
{
	struct uc_op *decl, *op = uc_get_op(state, off);
	struct uc_op *op1 = uc_get_child(state, off, 0);
	struct json_object *v[2], *rv;
	char *lhs;

	uc_get_operands(state, op, v);

	decl = json_object_get_userdata(v[0]);

	if (!decl || (decl->type != T_FUNC && decl->type != T_CFUNC)) {
		lhs = uc_ref_to_str(state, uc_get_off(state, op1));

		rv = uc_new_exception(state, op1->off, "Type error: %s is not a function",
		                      lhs ? lhs : "left-hand side expression");

		free(lhs);
	}
	else {
		if (v[1] == NULL)
			v[1] = xjs_new_array();

		rv = uc_invoke(state, off, NULL, v[0], v[1]);
	}

	json_object_put(v[0]);
	json_object_put(v[1]);

	return rv;
}

static void
uc_write_str(struct json_object *v)
{
	const char *p;
	size_t len;

	switch (json_object_get_type(v)) {
	case json_type_object:
	case json_type_array:
		p = json_object_to_json_string_ext(v, JSON_C_TO_STRING_NOSLASHESCAPE|JSON_C_TO_STRING_SPACED);
		len = strlen(p);
		break;

	case json_type_string:
		p = json_object_get_string(v);
		len = json_object_get_string_len(v);
		break;

	case json_type_null:
		p = "";
		len = 0;
		break;

	default:
		p = json_object_get_string(v);
		len = strlen(p);
	}

	fwrite(p, 1, len, stdout);
}

static struct json_object *
uc_execute_exp(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *val = uc_execute_op_sequence(state, op ? op->tree.operand[0] : 0);
	struct uc_op *tag = val ? json_object_get_userdata(val) : NULL;

	switch (tag ? tag->type : 0) {
	case T_EXCEPTION:
		printf("<exception: %s>", json_object_get_string(val));
		break;

	default:
		uc_write_str(val);
		break;
	}

	json_object_put(val);

	return NULL;
}

static struct json_object *
uc_execute_unary_plus_minus(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct uc_op *op1 = uc_get_child(state, off, 0);
	struct json_object *v[1];
	enum json_type t;
	int64_t n;
	double d;

	uc_get_operands(state, op, v);

	t = uc_cast_number(v[0], &n, &d);

	json_object_put(v[0]);

	switch (t) {
	case json_type_int:
		if (op1->is_overflow)
			return xjs_new_int64(((n >= 0) == (op->type == T_SUB)) ? INT64_MIN : INT64_MAX);

		return xjs_new_int64((op->type == T_SUB) ? -n : n);

	default:
		return uc_new_double((op->type == T_SUB) ? -d : d);
	}
}

static struct json_object *
uc_execute_arith(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[2], *rv;
	enum json_type t1, t2;
	const char *s1, *s2;
	size_t len1, len2;
	int64_t n1, n2;
	double d1, d2;
	char *s;

	if (!op->tree.operand[1])
		return uc_execute_unary_plus_minus(state, off);

	uc_get_operands(state, op, v);

	if (op->type == T_ADD &&
	    (json_object_is_type(v[0], json_type_string) ||
	     json_object_is_type(v[1], json_type_string))) {
		s1 = v[0] ? json_object_get_string(v[0]) : "null";
		s2 = v[1] ? json_object_get_string(v[1]) : "null";
		len1 = strlen(s1);
		len2 = strlen(s2);
		s = xalloc(len1 + len2 + 1);

		snprintf(s, len1 + len2 + 1, "%s%s", s1, s2);

		rv = xjs_new_string(s);

		json_object_put(v[0]);
		json_object_put(v[1]);
		free(s);

		return rv;
	}

	t1 = uc_cast_number(v[0], &n1, &d1);
	t2 = uc_cast_number(v[1], &n2, &d2);

	json_object_put(v[0]);
	json_object_put(v[1]);

	if (t1 == json_type_double || t2 == json_type_double) {
		d1 = (t1 == json_type_double) ? d1 : (double)n1;
		d2 = (t2 == json_type_double) ? d2 : (double)n2;

		switch (op->type) {
		case T_ADD:
			return uc_new_double(d1 + d2);

		case T_SUB:
			return uc_new_double(d1 - d2);

		case T_MUL:
			return uc_new_double(d1 * d2);

		case T_DIV:
			if (d2 == 0.0)
				return uc_new_double(INFINITY);
			else if (isnan(d2))
				return uc_new_double(NAN);
			else if (!isfinite(d2))
				return uc_new_double(isfinite(d1) ? 0.0 : NAN);

			return uc_new_double(d1 / d2);

		case T_MOD:
			return uc_new_double(NAN);
		}
	}

	switch (op->type) {
	case T_ADD:
		return xjs_new_int64(n1 + n2);

	case T_SUB:
		return xjs_new_int64(n1 - n2);

	case T_MUL:
		return xjs_new_int64(n1 * n2);

	case T_DIV:
		if (n2 == 0)
			return uc_new_double(INFINITY);

		return xjs_new_int64(n1 / n2);

	case T_MOD:
		return xjs_new_int64(n1 % n2);
	}

	return uc_new_double(NAN);
}

static struct json_object *
uc_execute_bitop(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[2];
	int64_t n1, n2;
	double d;

	uc_get_operands(state, op, v);

	if (uc_cast_number(v[0], &n1, &d) == json_type_double)
		n1 = isnan(d) ? 0 : (int64_t)d;

	if (uc_cast_number(v[1], &n2, &d) == json_type_double)
		n2 = isnan(d) ? 0 : (int64_t)d;

	json_object_put(v[0]);
	json_object_put(v[1]);

	switch (op->type) {
	case T_LSHIFT:
		return xjs_new_int64(n1 << n2);

	case T_RSHIFT:
		return xjs_new_int64(n1 >> n2);

	case T_BAND:
		return xjs_new_int64(n1 & n2);

	case T_BXOR:
		return xjs_new_int64(n1 ^ n2);

	case T_BOR:
		return xjs_new_int64(n1 | n2);

	default:
		return NULL;
	}
}

static struct json_object *
uc_execute_not(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);

	return xjs_new_boolean(!uc_test_condition(state, op ? op->tree.operand[0] : 0));
}

static struct json_object *
uc_execute_compl(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[1];
	int64_t n;
	double d;

	uc_get_operands(state, op, v);

	if (uc_cast_number(v[0], &n, &d) == json_type_double)
		n = isnan(d) ? 0 : (int64_t)d;

	json_object_put(v[0]);

	return xjs_new_int64(~n);
}

static struct json_object *
uc_execute_return(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[1], *rv;

	uc_get_operands(state, op, v);

	json_object_put(state->rval);
	state->rval = v[0];

	rv = xjs_new_boolean(false);

	json_object_set_userdata(rv, op, NULL);

	return rv;
}

static struct json_object *
uc_execute_break_cont(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *rv = xjs_new_int64(0);

	json_object_set_userdata(rv, op, NULL);

	return rv;
}

static struct json_object *
uc_execute_function(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct uc_op *op1 = uc_get_child(state, off, 0);
	struct json_object *obj = uc_new_func(state, op, state->scope);

	if (op1)
		uc_setval(state->scope->scope, op1->val, obj);

	return obj;
}

static struct json_object *
uc_execute_this(struct uc_state *state, uint32_t off)
{
	return json_object_get(state->callstack->ctx);
}

static struct json_object *
uc_execute_try_catch(struct uc_state *state, uint32_t off)
{
	struct uc_op *tag, *op = uc_get_op(state, off);
	struct json_object *rv;

	rv = uc_execute_op_sequence(state, op->tree.operand[0]);

	if (uc_is_type(rv, T_EXCEPTION)) {
		if (op->tree.operand[1]) {
			/* remove the T_EXCEPTION type from the object to avoid handling
			 * it as a new exception in the catch block */
			tag = json_object_get_userdata(rv);
			tag->type = T_LBRACE;

			json_object_put(uc_setval(state->scope->scope, uc_get_child(state, off, 1)->val,
			                json_object_get(rv)));
		}

		json_object_put(state->exception);
		state->exception = NULL;

		json_object_put(rv);
		rv = uc_execute_op_sequence(state, op->tree.operand[2]);
	}

	return rv;
}

static bool
uc_match_case(struct uc_state *state, struct json_object *v, struct uc_op *Case)
{
	struct json_object *caseval = uc_execute_op_sequence(state, Case->tree.operand[0]);
	bool rv = uc_eq(v, caseval);

	json_object_put(caseval);
	return rv;
}

static struct json_object *
uc_execute_switch_case(struct uc_state *state, uint32_t off)
{
	struct uc_op *Default = NULL, *Case = NULL, *jmp = NULL;
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *v[1], *rv = NULL;

	uc_get_operands(state, op, v);

	/* First try to find matching case... */
	for (Case = uc_get_child(state, off, 1);
	     Case != NULL;
	     Case = uc_get_op(state, Case->tree.next))
	{
		/* remember default case and throw on dupes */
		if (Case->type == T_DEFAULT) {
			if (Default) {
				json_object_put(v[0]);

				return uc_new_exception(state, Case->off,
				                        "Syntax error: more than one switch default case");
			}

			Default = Case;
			continue;
		}

		/* Found a matching case, remember jump offset */
		if (uc_match_case(state, v[0], Case)) {
			jmp = Case;
			break;
		}
	}

	/* jump to matching case (or default) and continue until break */
	for (Case = jmp ? jmp : Default;
	     Case != NULL;
	     Case = uc_get_op(state, Case->tree.next))
	{
		json_object_put(rv);

		if (Case == Default)
			rv = uc_execute_op_sequence(state, Default->tree.operand[0]);
		else
			rv = uc_execute_op_sequence(state, Case->tree.operand[1]);

		if (uc_is_type(rv, T_BREAK)) {
			json_object_put(rv);
			rv = NULL;
			break;
		}
		else if (uc_is_type(rv, T_RETURN) || uc_is_type(rv, T_EXCEPTION) || uc_is_type(rv, T_CONTINUE)) {
			break;
		}
	}

	json_object_put(v[0]);

	return rv;
}

static struct json_object *
uc_execute_atom(struct uc_state *state, uint32_t off)
{
	return json_object_get(uc_get_op(state, off)->val);
}

static struct json_object *
uc_execute_text(struct uc_state *state, uint32_t off)
{
	printf("%s", json_object_get_string(uc_get_op(state, off)->val));

	return NULL;
}

static struct json_object *
uc_execute_label(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);
	struct json_object *scope, *key, *val;

	scope = uc_getref(state, off, &key);

	json_object_put(state->ctx);
	state->ctx = NULL;

	if (state->strict_declarations && scope == NULL) {
		return uc_new_exception(state, op->off,
		                        "Reference error: %s is not defined",
		                        json_object_get_string(op->val));
	}

	val = uc_getval(scope, key);
	json_object_put(scope);
	json_object_put(key);

	return val;
}

static struct json_object *
uc_execute_dot(struct uc_state *state, uint32_t off)
{
	struct json_object *scope, *key, *val;

	scope = uc_getref_required(state, off, &key);

	json_object_put(state->ctx);
	state->ctx = json_object_get(scope);

	if (!key)
		return scope;

	val = uc_getval(scope, key);
	json_object_put(scope);
	json_object_put(key);

	return val;
}

static struct json_object *
uc_execute_lbrack(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);

	/* postfix access */
	if (op->is_postfix)
		return uc_execute_dot(state, off);

	return uc_execute_list(state, op->tree.operand[0]);
}

static struct json_object *
uc_execute_exp_list(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);

	return uc_execute_op_sequence(state, op->tree.operand[0]);
}

static struct json_object *(*fns[__T_MAX])(struct uc_state *, uint32_t) = {
	[T_NUMBER]   = uc_execute_atom,
	[T_DOUBLE]   = uc_execute_atom,
	[T_STRING]   = uc_execute_atom,
	[T_REGEXP]   = uc_execute_atom,
	[T_BOOL]     = uc_execute_atom,
	[T_NULL]     = uc_execute_atom,
	[T_THIS]     = uc_execute_this,
	[T_FUNC]     = uc_execute_function,
	[T_ARROW]    = uc_execute_function,
	[T_TEXT]     = uc_execute_text,
	[T_ASSIGN]   = uc_execute_assign,
	[T_LOCAL]    = uc_execute_local,
	[T_LABEL]    = uc_execute_label,
	[T_DOT]      = uc_execute_dot,
	[T_LBRACK]   = uc_execute_lbrack,
	[T_LBRACE]   = uc_execute_object,
	[T_IF]       = uc_execute_if,
	[T_ELIF]     = uc_execute_if,
	[T_QMARK]    = uc_execute_if,
	[T_FOR]      = uc_execute_for,
	[T_WHILE]    = uc_execute_while,
	[T_AND]      = uc_execute_and_or,
	[T_OR]       = uc_execute_and_or,
	[T_LT]       = uc_execute_rel,
	[T_LE]       = uc_execute_rel,
	[T_GT]       = uc_execute_rel,
	[T_GE]       = uc_execute_rel,
	[T_EQ]       = uc_execute_rel,
	[T_NE]       = uc_execute_rel,
	[T_EQS]      = uc_execute_equality,
	[T_NES]      = uc_execute_equality,
	[T_IN]       = uc_execute_in,
	[T_INC]      = uc_execute_inc_dec,
	[T_DEC]      = uc_execute_inc_dec,
	[T_LPAREN]   = uc_execute_call,
	[T_LEXP]     = uc_execute_exp,
	[T_ADD]      = uc_execute_arith,
	[T_SUB]      = uc_execute_arith,
	[T_MUL]      = uc_execute_arith,
	[T_DIV]      = uc_execute_arith,
	[T_MOD]      = uc_execute_arith,
	[T_LSHIFT]   = uc_execute_bitop,
	[T_RSHIFT]   = uc_execute_bitop,
	[T_BAND]     = uc_execute_bitop,
	[T_BXOR]     = uc_execute_bitop,
	[T_BOR]      = uc_execute_bitop,
	[T_COMPL]    = uc_execute_compl,
	[T_NOT]      = uc_execute_not,
	[T_RETURN]   = uc_execute_return,
	[T_BREAK]    = uc_execute_break_cont,
	[T_CONTINUE] = uc_execute_break_cont,
	[T_TRY]      = uc_execute_try_catch,
	[T_SWITCH]   = uc_execute_switch_case,
	[T_COMMA]    = uc_execute_exp_list,
};

static struct json_object *
uc_execute_op(struct uc_state *state, uint32_t off)
{
	struct uc_op *op = uc_get_op(state, off);

	if (!fns[op->type])
		return uc_new_exception(state, op->off, "Runtime error: Unrecognized opcode %d", op->type);

	return fns[op->type](state, off);
}

static struct json_object *
uc_execute_op_sequence(struct uc_state *state, uint32_t off)
{
	struct json_object *v = NULL;
	struct uc_op *tag = NULL;
	struct uc_op *op = NULL;

	while (off) {
		json_object_put(v);

		v = uc_execute_op(state, off);
		tag = v ? json_object_get_userdata(v) : NULL;

		switch (tag ? tag->type : 0) {
		case T_BREAK:
		case T_CONTINUE:
		case T_RETURN:
		case T_EXCEPTION:
			return v;
		}

		op = uc_get_op(state, off);
		off = op ? op->tree.next : 0;
	}

	return v;
}

static void
uc_globals_init(struct uc_state *state, struct json_object *scope)
{
	struct json_object *arr = xjs_new_array();
	const char *p, *last;

	for (p = last = LIB_SEARCH_PATH;; p++) {
		if (*p == ':' || *p == '\0') {
			json_object_array_add(arr, xjs_new_string_len(last, p - last));

			if (!*p)
				break;

			last = p + 1;
		}
	}

	json_object_object_add(scope, "REQUIRE_SEARCH_PATH", arr);
}

static void
uc_register_variable(struct json_object *scope, const char *key, struct json_object *val)
{
	char *name = strdup(key);
	char *p;

	if (!name)
		return;

	for (p = name; *p; p++)
		if (!isalnum(*p) && *p != '_')
			*p = '_';

	json_object_object_add(scope, name, val);
	free(name);
}

struct json_object *
uc_run(struct uc_state *state, struct json_object *env, struct json_object *modules)
{
	struct json_object *args, *rv;
	struct uc_function fn = {};
	size_t i;

	state->scope = uc_new_scope(state, NULL);
	state->ctx = NULL;

	fn.source = state->source;
	state->function = &fn;

	if (env) {
		json_object_object_foreach(env, key, val)
			uc_register_variable(state->scope->scope, key, json_object_get(val));
	}

	uc_globals_init(state, state->scope->scope);
	uc_lib_init(state, state->scope->scope);

	if (modules) {
		args = xjs_new_array();

		for (i = 0; i < json_object_array_length(modules); i++) {
			json_object_array_put_idx(args, 0, json_object_get(json_object_array_get_idx(modules, i)));

			rv = uc_invoke(state, 0, NULL,
			               json_object_object_get(state->scope->scope, "require"),
			               args);

			if (uc_is_type(rv, T_EXCEPTION))
				goto out;

			uc_register_variable(state->scope->scope,
			                     json_object_get_string(json_object_array_get_idx(modules, i)),
			                     rv);
		}

		json_object_put(args);
	}

	rv = uc_execute_source(state, state->source, state->scope);

out:
	uc_release_scope(state->scope);

	return rv;
}