view src/ploki/expr.c @ 12292:d51f2100210c draft

<kspalaiologos> `` cat <<<"asmbf && bfi output.b" > /hackenv/ibin/asmbf
author HackEso <hackeso@esolangs.org>
date Thu, 02 Jan 2020 15:38:21 +0000
parents ac0403686959
children
line wrap: on
line source

#include "config.h"
#include "Str.h"
#include "expr.h"
#include "hang.h"
#include "list.h"
#include "main_io.h"
#include "main_label.h"
#include "main_var.h"
#include "mars.h"
#include "match.h"
#include "op.h"
#include "pp.h"
#include "random.h"
#include "re.h"
#include "run.h"
#include "stack.h"
#include "val.h"
#include "venus.h"
#include "xmalloc.h"
#include "zz.h"

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <setjmp.h>

#ifdef M_PI
	#define MY_PI M_PI
#else
	#define MY_PI 3.1415926535897932384626433832795028841971693993751058209749445923
#endif

#ifdef M_E
	#define MY_E M_E
#else
	#define MY_E 2.71828182845904523536028747135266249775724709369995957496696762772
#endif

typedef struct val *svalp;
stack_declare(svalp, static)

static void sv_init(svalp *x) {
	*x = v_undef();
}

static void sv_end(svalp *x) {
	v_delete(*x);
}

static void sv_copy(svalp *dst, const svalp *src) {
	v_set(*dst, *src);
}

stack_define(svalp, static, sv_init, sv_end, sv_copy)
static stack(svalp) Stack;

static void con_nop(t_context *c) {
	(void)c;
}

ATTR_NORETURN
static void con_error(const t_context *x, const t_context *y) {
	(void)x;
	(void)y;
	NOTREACHED;
}

stack_define(t_context, extern, con_nop, con_nop, con_error)
stack(t_context) Context;

void expr_init(void) {
	stack_func(svalp, init)(&Stack);
	stack_func(t_context, init)(&Context);
}

void expr_end(void) {
	stack_func(t_context, end)(&Context);
	stack_func(svalp, end)(&Stack);
}

void expr_pp(enum t_binop op, struct val *x, struct val *y) {
	switch (op) {
		case B_AMPERSAND: pp_and(x, y); return;
		case B_ANGLE: pp_lt_n(x, y); return;
		case B_BACKSPARK: pp_tobase(x, y); return;
		case B_BRACELET: pp_gt(x, y); return;
		case B_DOUBLE_OH_SEVEN: pp_mod(x, y); return;
		case B_EMBRACE: pp_lt(x, y); return;
		case B_FLATWORM: pp_concat(x, y); return;
		case B_HALF_MESH: pp_eq_n(x, y); return;
		case B_HYBRID: pp_ne(x, y); return;
		case B_INTERSECTION: pp_add(x, y); return;
		case B_RIGHT_ANGLE: pp_gt_n(x, y); return;
		case B_SHARK_FIN: pp_pow(x, y); return;
		case B_SLAT: pp_div(x, y); return;
		case B_SPARK: pp_frombase(x, y); return;
		case B_SPARK_SPOT: pp_ne_n(x, y); return;
		case B_SPIKE: pp_or(x, y); return;
		case B_SPLAT: pp_mult(x, y); return;
		case B_SPOT: pp_read(x, y); return;
		case B_SQIGGLE: pp_match(x, y); return;
		case B_TAIL: pp_comma(x, y); return;
		case B_TWO_SPOT: pp_eq(x, y); return;
		case B_U_TURN: pp_shift(x, y); return;
		case B_U_TURN_BACK: pp_pop(x, y); return;
		case B_WORM: pp_sub(x, y); return;
		case B_XMATCH: NOTREACHED;
	}
}

enum t_binop expr_binop(int c) {
	switch (c) {
		case '!': return B_SPARK_SPOT;
		case '%': return B_DOUBLE_OH_SEVEN;
		case '&': return B_AMPERSAND;
		case '\'': return B_SPARK;
		case '*': return B_SPLAT;
		case '+': return B_INTERSECTION;
		case ',': return B_TAIL;
		case '-': return B_WORM;
		case '.': return B_SPOT;
		case '/': return B_SLAT;
		case ':': return B_TWO_SPOT;
		case ';': return B_HYBRID;
		case '<': return B_ANGLE;
		case '=': return B_HALF_MESH;
		case '>': return B_RIGHT_ANGLE;
		case '[': return B_U_TURN;
		case ']': return B_U_TURN_BACK;
		case '^': return B_SHARK_FIN;
		case '_': return B_FLATWORM;
		case '`': return B_BACKSPARK;
		case '{': return B_EMBRACE;
		case '|': return B_SPIKE;
		case '}': return B_BRACELET;
		case '~': return B_SQIGGLE;
	}
	NOTREACHED;
}

void free_expr(struct expr *x) {
	if (!x) {
		return;
	}

	switch (x->type) {
		case literE:
			v_end(x->v.val);
			xfree(x->v.val);
			break;

		case varE:
			break;

		case varhashE:
			free_expr(x->right);
			break;

		case symbolE:
			switch (x->op) {
				case S_ARGV:
					free_expr(x->right);
					break;
			}
			break;

		case unopE:
			free_expr(x->right);
			if (x->op == F_MATCH) {
				re_free(x->left.rx);
			}
			break;

		case binopE:
			free_expr(x->left.expr);
			free_expr(x->right);
			break;

		case listE:
			free_expr(x->right);
			free_expr(x->left.expr);
			break;
	}
	xfree(x);
}

static struct expr *undef(void) {
	struct expr *e;
	e = xmalloc(1, sizeof *e);
	e->type = literE;
	e->left.expr = e->right = NULL;
	e->v.val = v_undef();
	return e;
}

static void xv_delete(void *v) {
	v_delete(v);
}

struct expr *get_lval(struct op *op) {
	struct expr *x;
	int c;
	size_t n;

	St_shiftws(&op->txt);
	c = ST_FIRSTCHAR(&op->txt);
	if (!(c == '$' || isalpha(c))) {
		return NULL;
	}
	for (n = 1; (c = ST_INDEX(&op->txt, n)) == '$' || isalpha(c); ++n)
		;

	x = xmalloc(1, sizeof *x);
	x->left.expr = x->right = NULL;

	if (ST_INDEX(&op->txt, n) != '(') {
		x->type = varE;
		if ((x->v.tent = vr_exists(Var_plain, St_ptr(&op->txt), n)) == VR_NO_COOKIE) {
			x->v.tent = vr_register(Var_plain, St_ptr(&op->txt), n, v_undef());
		}
		St_del(&op->txt, 0, n);
	} else {
		x->type = varhashE;
		if ((x->v.tent = vr_exists(Var_hash, St_ptr(&op->txt), n)) == VR_NO_COOKIE) {
			x->v.tent = vr_register(Var_hash, St_ptr(&op->txt), n, sh_new(xv_delete));
		}
		St_del(&op->txt, 0, n + 1);
		x->right = get_expr(op);
		if (ST_FIRSTCHAR(&op->txt) == ')') {
			St_shift(&op->txt);
		}
	}
	return x;
}

ATTR_CONST
static int xval(int c) {
	switch (c) {
		case '0': return 0;
		case '1': return 1;
		case '2': return 2;
		case '3': return 3;
		case '4': return 4;
		case '5': return 5;
		case '6': return 6;
		case '7': return 7;
		case '8': return 8;
		case '9': return 9;
		case 'A':
		case 'a': return 10;
		case 'B':
		case 'b': return 11;
		case 'C':
		case 'c': return 12;
		case 'D':
		case 'd': return 13;
		case 'E':
		case 'e': return 14;
		case 'F':
		case 'f': return 15;
	}
	NOTREACHED;
}

static struct expr *get_value(struct op *, int);

static struct expr *get_list(struct op *op, int null) {
	struct expr *e;

	e = xmalloc(1, sizeof *e);
	e->type = listE;
	if ((e->right = get_value(op, 1))) {
		e->left.expr = get_list(op, 1);
	} else if (null) {
		xfree(e);
		return NULL;
	} else {
		e->right = e->left.expr = NULL;
	}
	return e;
}

static struct expr *get_value(struct op *op, int null) {
	struct expr *e;
	int c;

	St_shiftws(&op->txt);
	if (!St_len(&op->txt)) {
		return null ? NULL : undef();
	}
	c = ST_FIRSTCHAR(&op->txt);

	if (c == '$' || isalpha(c)) {
		return get_lval(op);
	}

	if (isdigit(c) || (c == '.' && isdigit(ST_INDEX(&op->txt, 1)))) {
		char *tmp;

		e = undef();
		tmp = St_ptr(&op->txt);
		v_set_n(e->v.val, strtod(tmp, &tmp));
		tmp += tmp == St_ptr(&op->txt);
		St_del(&op->txt, 0, tmp - St_ptr(&op->txt));

		return e;
	}

	if (OPERATOR_P(c)) {
		e = xmalloc(1, sizeof *e);
		e->type = binopE;
		e->op = expr_binop(c);
		St_shift(&op->txt);
		e->left.expr = undef();
		e->right = get_value(op, 0);
		return e;
	}
	if (c == '?' && ST_INDEX(&op->txt, 1) == 'o' && ST_INDEX(&op->txt, 2) == '~') {
		e = xmalloc(1, sizeof *e);
		e->type = binopE;
		e->op = B_XMATCH;
		St_del(&op->txt, 0, 3);
		e->left.expr = undef();
		e->right = get_value(op, 0);
		return e;
	}

	switch (c) {
		case '#':
			if (ST_INDEX(&op->txt, 1) != '<') {
				goto unrecog;
			}
			St_del(&op->txt, 0, 2);
			e = get_list(op, 0);
			St_shiftws(&op->txt);
			if (ST_FIRSTCHAR(&op->txt) == '#' && ST_INDEX(&op->txt, 1) == '>') {
				St_del(&op->txt, 0, 2);
			}
			break;

		case '(':
			St_shift(&op->txt);
			e = get_expr(op);
			St_shiftws(&op->txt);
			if (ST_FIRSTCHAR(&op->txt) == ')') {
				St_shift(&op->txt);
			}
			break;

		case '"': {
			struct val *v;
			St_shift(&op->txt);
			e = undef();
			v = e->v.val;
			V_STR(v);
			for (; St_len(&op->txt) && (c = St_shift(&op->txt)) != '"'; ) {
				switch (c) {
					case '\\':
						if (St_len(&op->txt) == 0) {
							v_cat_c(v, c);
							break;
						}
						c = St_shift(&op->txt);
						if (c >= '0' && c <= '7') {
							int oct;
							oct = c - '0';
							c = ST_FIRSTCHAR(&op->txt);
							if (c >= '0' && c <= '7') {
								oct *= 010;
								oct += c - '0';
								St_shift(&op->txt);
								c = ST_FIRSTCHAR(&op->txt);
								if (c >= '0' && c <= '7') {
									oct *= 010;
									oct += c - '0';
									St_shift(&op->txt);
								}
							}
							v_cat_c(v, oct);
							break;
						}
						switch (c) {
							case 'x':
								c = ST_FIRSTCHAR(&op->txt);
								if (isxdigit(c)) {
									int hex;
									hex = xval(c);
									St_shift(&op->txt);
									c = ST_FIRSTCHAR(&op->txt);
									if (isxdigit(c)) {
										hex *= 0x10;
										hex += xval(c);
										St_shift(&op->txt);
									}
									v_cat_c(v, hex);
								} else {
									v_cat_m(v, "\\x", 2);
								}
								break;

							case 'a': v_cat_c(v, '\a'); break;
							case 'b': v_cat_c(v, '\b'); break;
							case 'f': v_cat_c(v, '\f'); break;
							case 'n': v_cat_c(v, '\n'); break;
							case 'r': v_cat_c(v, '\r'); break;
							case 't': v_cat_c(v, '\t'); break;
							case 'v': v_cat_c(v, '\v'); break;

							case 'c':
								if (St_len(&op->txt)) {
									c = St_shift(&op->txt);
									v_cat_c(v, (toupper(c) + 64) % 128);
								} else {
									v_cat_m(v, "\\c", 2);
								}
								break;

							case 'V': {
								struct expr *tmp;
								tmp = e;
								e = xmalloc(1, sizeof *e);
								e->type = binopE;
								e->op = B_FLATWORM;
								e->left.expr = tmp;
								e->right = get_value(op, 0);
								tmp = e;
								e = xmalloc(1, sizeof *e);
								e->type = binopE;
								e->op = B_FLATWORM;
								e->left.expr = tmp;
								e->right = undef();
								v = e->right->v.val;
								break;
							}

							default:
								v_cat_c(v, c);
								break;
						}
						break;

					default:
						v_cat_c(v, c);
						break;
				}
			}
		}
		break;

		case '\\':
			St_shift(&op->txt);
			e = xmalloc(1, sizeof *e);
			e->type = symbolE;
			e->right = NULL;
			e->left.expr = NULL;

			c = ST_FIRSTCHAR(&op->txt);
			if (isdigit(c)) {
				char *tmp = St_ptr(&op->txt);
				e->left.bonus = strtoul(tmp, &tmp, 10);
				St_del(&op->txt, 0, tmp - St_ptr(&op->txt));
				e->op = S_MATCH;
			} else switch (c) {
				case '!': St_shift(&op->txt); e->op = S_ERR; break;
				case '?': St_shift(&op->txt); e->op = S_RAND; break;
				case '_': St_shift(&op->txt); e->op = S_RESULT; break;

				case '@':
					St_shift(&op->txt);
					e->op = S_ARG;
					break;

				case 'A':
					switch (ST_INDEX(&op->txt, 1)) {
						case 'R':
							if (ST_INDEX(&op->txt, 2) == 'G') {
								St_del(&op->txt, 0, 3);
								e->op = S_ARGC;
								if (ST_FIRSTCHAR(&op->txt) == ':') {
									St_shift(&op->txt);
									e->op = S_ARGV;
									e->right = get_value(op, 0);
								}
								break;
							}
							goto nullsym;

						case 'U':
							if (ST_INDEX(&op->txt, 2) == 'S' &&
									ST_INDEX(&op->txt, 3) == 'G') {
								St_del(&op->txt, 0, 4);
								e->op = S_STDOUT;
								break;
							}
							goto nullsym;

						default:
							goto nullsym;
					}
					break;

				case 'E':
					switch (ST_INDEX(&op->txt, 1)) {
						case 'I':
							if (
									ST_INDEX(&op->txt, 2) == 'N' &&
									ST_INDEX(&op->txt, 3) == 'G'
							   ) {
								St_del(&op->txt, 0, 4);
								e->op = S_STDIN;
								break;
							}
							if (0)
						case 'N': {
								if (ST_INDEX(&op->txt, 2) == 'V') {
									St_del(&op->txt, 0, 3);
									e->type = unopE;
									e->op = F_GETENV;
									e->right = get_value(op, 0);
									break;
								}
							}
							/*DURCHFALL*/
						default:
							St_shift(&op->txt);
							e->op = S_EULER;
							break;
					}
					break;

				case 'F':
					if (
							ST_INDEX(&op->txt, 1) == 'E' &&
							ST_INDEX(&op->txt, 2) == 'H' &&
							ST_INDEX(&op->txt, 3) == 'L'
					   ) {
						St_del(&op->txt, 0, 4);
						e->op = S_STDERR;
						break;
					}
					goto nullsym;

				case 'L': e->op = F_LOWER;  goto simplefunc;
				case 'Q': e->op = F_QUOTE;  goto simplefunc;
				case 'R': e->op = F_RE_ESC; goto simplefunc;
				case 'U': e->op = F_UPPER;  goto simplefunc;
simplefunc:
					St_shift(&op->txt);
					e->type = unopE;
					e->right = get_value(op, 0);
					break;

				case 'P':
					if (ST_INDEX(&op->txt, 1) == 'I') {
						St_del(&op->txt, 0, 2);
						e->op = S_LUDOLF;
						break;
					}
					goto nullsym;

nullsym:
				default:
					e->op = S_NUL;
					break;
			}
			break;

		case '@':
			#define CASE_DO_STUFF(n, o)            \
			if (1) {                               \
				St_del(&op->txt, 0, (n));          \
				e->op = (o);                       \
				e->right = get_value(op, 0);       \
				break;                             \
			} else ((void)0)

			#define CASE(i, w, n, o)                                       \
			case i: if (1) {                                               \
				if (St_len(&op->txt) < (n) || St_ncmp_m(&op->txt, w, n)) { \
					goto usrfunc;                                          \
				}                                                          \
				CASE_DO_STUFF(n, o);                                       \
			} else ((void)0)

			St_shift(&op->txt);
			e = xmalloc(1, sizeof *e);
			e->type = unopE;
			e->left.expr = NULL;

			switch (ST_FIRSTCHAR(&op->txt)) {
				case '+': CASE_DO_STUFF(1, F_MOEND);
				case '-': CASE_DO_STUFF(1, F_MOSTART);
				case 'A':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('B', "ABS", 3, F_ABS);
						CASE('C', "ACOS", 4, F_ACOS);
						CASE('P', "APERS", 5, F_OPEN);
						CASE('S', "ASIN", 4, F_ASIN);
						case 'T':
							if (St_len(&op->txt) >= 4) {
								if (St_ncmp_m(&op->txt, "ATAN2", 5) == 0) {
									CASE_DO_STUFF(5, F_ATAN2);
								}
								if (St_ncmp_m(&op->txt, "ATAN", 4) == 0) {
									CASE_DO_STUFF(4, F_ATAN);
								}
							}
							goto usrfunc;

						default:
							goto usrfunc;
					}
					break;

				case 'C':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('H', "CHR", 3, F_CHR);
						CASE('O', "COS", 3, F_COS);
						default:
							goto usrfunc;
					}
					break;

				CASE('D', "DEF-P", 5, F_DEFINED);

				case 'E':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('D', "EDD-P", 5, F_EOF);
						CASE('N', "ENV", 3, F_GETENV);
						CASE('R', "ERR-P", 5, F_ERROR);
						CASE('V', "EVAL", 4, F_CATCH);
						default: goto usrfunc;
					}
					break;

				CASE('G', "GET", 3, F_GETC);

				case 'I':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('N', "INT", 3, F_INT);
						CASE('O', "IO-P", 4, F_IO);
						default: goto usrfunc;
					}
					break;

				case 'L':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('A', "LAPERS", 6, F_OPENR);
						case 'E':
							switch (ST_INDEX(&op->txt, 2)) {
								CASE('G', "LEGS", 4, F_GETS);
								CASE('N', "LENGTH", 6, F_LENGTH);
								default: goto usrfunc;
							}
							break;
						CASE('G', "LG", 2, F_LOG10);
						CASE('N', "LN", 2, F_LOG);
						default: goto usrfunc;
					}
					break;

				case 'N':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('E', "NEG", 3, F_NEG);
						CASE('O', "NOT", 3, F_NOT);
						CASE('U', "NUM", 3, F_NUM);
						default: goto usrfunc;
					}
					break;

				case 'O':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('R', "ORD", 3, F_ORD);
						CASE('M', "OMFG", 4, F_FREEZE);
						default: goto usrfunc;
					}
					break;

				case 'R':
					if (ST_INDEX(&op->txt, 1) != 'E') {
						goto usrfunc;
					}
					switch (ST_INDEX(&op->txt, 2)) {
						CASE('M', "REMOVE", 6, F_REMOVE);
						CASE('N', "RENAEM", 6, F_RENAME);
						CASE('V', "REVERSE", 7, F_REVERSE);
						default: goto usrfunc;
					}
					break;

				case 'S':
					switch (ST_INDEX(&op->txt, 1)) {
						case 'A':
							switch (ST_INDEX(&op->txt, 2)) {
								CASE('G', "SAG", 3, F_TELL);
								CASE('P', "SAPERS", 6, F_OPENW);
								default: goto usrfunc;
							}
							break;
						CASE('I', "SIN", 3, F_SIN);
						CASE('Q', "SQRT", 4, F_SQRT);
						CASE('T', "STR", 3, F_STR);
						CASE('U', "SUCH", 4, F_SEEK);
						default: goto usrfunc;
					}
					break;

				case 'T':
					switch (ST_INDEX(&op->txt, 1)) {
						CASE('A', "TAN", 3, F_TAN);
						CASE('Y', "TYPE OF", 7, F_TYPEOF);
						default: goto usrfunc;
					}
					break;

usrfunc:
				default:
				if ((e->left.op = ma_find(&Mars, &op->txt))) {
					e->op = F_CALL;
				} else {
					e->op = F_NUL;
					e->left.bonus = op->line;
				}
				e->right = get_value(op, 0);
				break;
			}
			#undef CASE_DO_STUFF
			#undef CASE
			break;

unrecog:
		default:
			if (null) {
				e = NULL;
			} else {
				e = undef();
			}
			break;
	}

	return e;
}

struct expr *get_iobj(struct op *op) {
	return get_value(op, 0);
}

struct expr *get_expr(struct op *op) {
	struct expr *a, *b;

	a = get_value(op, 0);

	for (St_shiftws(&op->txt); St_len(&op->txt); St_shiftws(&op->txt)) {
		struct expr *tmp;
		enum t_binop k;

		if (OPERATOR_P(ST_FIRSTCHAR(&op->txt))) {
			k = expr_binop(St_shift(&op->txt));
			b = get_value(op, 0);
		} else if (St_ncmp_m(&op->txt, "?o~", 3) == 0) {
			k = B_XMATCH;
			St_del(&op->txt, 0, 3);
			b = get_value(op, 0);
		} else {
			if (!(b = get_value(op, 1))) {
				break;
			}
			k = B_SPOT;
		}

		tmp = a;
		a = xmalloc(1, sizeof *a);
		a->type = binopE;
		a->op = k;
		a->left.expr = tmp;
		a->right = b;
	}

	return a;
}

#define TOS (*stack_func(svalp, at)(&Stack, 0))

void eval_push(struct expr *ex) {
	const struct expr *const e = ex;

	switch (e->type) {
		case literE:
		case varE:
			stack_func(svalp, push)(&Stack, &e->v.val);
			return;

		case varhashE: {
			struct val *tmp, *tos;
			const char *ptr;
			size_t len;

			eval_push(e->right);
			tos = TOS;
			ptr = v_sptr(tos, &len);
			if ((tmp = sh_get(e->v.hash, ptr, len))) {
				v_set(tos, tmp);
			} else {
				v_set_undef(tos);
			}
			return;
		}

		case symbolE:
			switch (e->op) {
				case S_NUL:
					stack_func(svalp, pushnull)(&Stack);
					break;

				case S_ARG: {
					struct val *const tmp = &Interp.arg;
					stack_func(svalp, push)(&Stack, &tmp);
					break;
				}

				case S_ARGC:
					stack_func(svalp, pushnull)(&Stack);
					v_set_n(TOS, Interp.a.argc);
					break;

				case S_ARGV: {
					struct val *tos;
					size_t n;

					eval_push(e->right);
					tos = TOS;
					V_NUM(tos);
					n = RINT(tos->num);

					if (n < Interp.a.argc) {
						v_set(tos, &Interp.a.argv[n]);
					} else {
						v_set_undef(tos);
					}
					break;
				}

				case S_ERR: {
					struct val *tos;
					const char *const tmp = strerror(errno);
					stack_func(svalp, pushnull)(&Stack);
					tos = TOS;
					v_set_m(tos, tmp, strlen(tmp));
					tos->num = errno;
					tos->type |= V_NUM_K;
					break;
				}

				case S_EULER:
					stack_func(svalp, pushnull)(&Stack);
					v_set_n(TOS, MY_E);
					break;

				case S_LUDOLF:
					stack_func(svalp, pushnull)(&Stack);
					v_set_n(TOS, MY_PI);
					break;

				case S_MATCH:
					stack_func(svalp, pushnull)(&Stack);
					if (e->left.bonus < Interp.match.length) {
						v_set(TOS, &Interp.match.matches[e->left.bonus]);
					}
					break;

				case S_RAND:
					stack_func(svalp, pushnull)(&Stack);
					v_set_n(TOS, randval());
					break;

				case S_RESULT: {
					struct val *const tmp = &Interp.result;
					stack_func(svalp, push)(&Stack, &tmp);
					break;
				}

				case S_STDIN:
					stack_func(svalp, pushnull)(&Stack);
					v_set_io(TOS, In);
					break;

				case S_STDOUT:
					stack_func(svalp, pushnull)(&Stack);
					v_set_io(TOS, Out);
					break;

				case S_STDERR:
					stack_func(svalp, pushnull)(&Stack);
					v_set_io(TOS, Err);
					break;
			}
			return;

		case unopE:
			if (e->op == F_FREEZE) {
				struct sub *const tmp = sub_new(e->right);
				stack_func(svalp, pushnull)(&Stack);
				v_set_sub(TOS, tmp);
				sub_decr(tmp);
				return;
			}

			if (e->op == F_CATCH) {
				const size_t stackmark = stack_func(svalp, size)(&Stack);
				t_context *tos;

				stack_func(t_context, pushnull)(&Context);
				tos = stack_func(t_context, at)(&Context, 0);
				if (!setjmp(tos->buf)) {
					tos->depth = stack_func(save_pair, size)(&Saved);
					eval_push(e->right);
					assert(stack_func(svalp, size)(&Stack) - stackmark == 1u);
					stack_func(t_context, clear)(&Context, 1);
				} else {
					tos = stack_func(t_context, at)(&Context, 0);
					depth_restore(tos->depth);
					stack_func(t_context, clear)(&Context, 1);
					assert(stackmark <= stack_func(svalp, size)(&Stack));
					stack_func(svalp, clear)(&Stack, stack_func(svalp, size)(&Stack) - stackmark);
					stack_func(svalp, pushnull)(&Stack);
				}
				return;
			}

			eval_push(e->right);
			switch (e->op) {
				case F_NUL: {
					struct op *op;
					struct val *const tos = TOS;

					TOLABEL(tos);
					if ((op = ve_findnext(&Venus, ko_str(tos->ko), e->left.bonus))) {
						struct val *tmp = v_undef();
						v_set(tmp, tos);
						tmp = execute(op, tmp);
						v_set(TOS, tmp);
						v_delete(tmp);
					} else {
						hang();
					}
					break;
				}

				case F_CALL: {
					struct val *tmp = v_undef();
					v_set(tmp, TOS);
					tmp = execute(e->left.op, tmp);
					v_set(TOS, tmp);
					v_delete(tmp);
					break;
				}

				case F_MATCH:
					do_match(TOS, e->left.rx);
					break;

				case F_ABS:     pp_abs(TOS);     break;
				case F_ACOS:    pp_acos(TOS);    break;
				case F_ASIN:    pp_asin(TOS);    break;
				case F_ATAN:    pp_atan(TOS);    break;
				case F_ATAN2:   pp_atan2(TOS);   break;
				case F_CHR:     pp_chr(TOS);     break;
				case F_COS:     pp_cos(TOS);     break;
				case F_DEFINED: pp_defined(TOS); break;
				case F_EOF:     pp_eof(TOS);     break;
				case F_ERROR:   pp_error(TOS);   break;
				case F_GETC:    pp_getc(TOS);    break;
				case F_GETENV:  pp_getenv(TOS);  break;
				case F_GETS:    pp_gets(TOS);    break;
				case F_HANG:    hang();          break;
				case F_INT:     pp_int(TOS);     break;
				case F_IO:      pp_io(TOS);      break;
				case F_LENGTH:  pp_length(TOS);  break;
				case F_LOG:     pp_log(TOS);     break;
				case F_LOG10:   pp_log10(TOS);   break;
				case F_LOWER:   pp_lower(TOS);   break;
				case F_MOEND:   pp_moend(TOS);   break;
				case F_MOSTART: pp_mostart(TOS); break;
				case F_NEG:     pp_neg(TOS);     break;
				case F_NOT:     pp_not(TOS);     break;
				case F_NUM:     pp_num(TOS);     break;
				case F_OPEN:    pp_open(TOS);    break;
				case F_OPENR:   pp_openr(TOS);   break;
				case F_OPENW:   pp_openw(TOS);   break;
				case F_ORD:     pp_ord(TOS);     break;
				case F_QUOTE:   pp_quote(TOS);   break;
				case F_RE_ESC:  pp_escape(TOS);  break;
				case F_REMOVE:  pp_remove(TOS);  break;
				case F_RENAME:  pp_rename(TOS);  break;
				case F_REVERSE: pp_reverse(TOS); break;
				case F_SEEK:    pp_seek(TOS);    break;
				case F_SIN:     pp_sin(TOS);     break;
				case F_SQRT:    pp_sqrt(TOS);    break;
				case F_STR:     pp_str(TOS);     break;
				case F_TAN:     pp_tan(TOS);     break;
				case F_TELL:    pp_tell(TOS);    break;
				case F_TYPEOF:  pp_typeof(TOS);  break;
				case F_UPPER:   pp_upper(TOS);   break;
			}
			return;

		case binopE:
			eval_push(e->left.expr);
			eval_push(e->right);

			if (e->op != B_XMATCH) {
				expr_pp(e->op, *stack_func(svalp, at)(&Stack, 1), TOS);
				stack_func(svalp, clear)(&Stack, 1);
			} else {
				t_regex *rx;
				struct val *const tos = TOS;
				V_STR(tos);
				rx = re_compile(ko_str(tos->ko));
				stack_func(svalp, clear)(&Stack, 1);
				free_expr(ex->right);
				ex->right = ex->left.expr;
				ex->left.rx = rx;
				ex->op = F_MATCH;
				ex->type = unopE;

				do_match(TOS, rx);
			}
			return;

		case listE: {
			struct val *tos;

			stack_func(svalp, pushnull)(&Stack);
			tos = TOS;
			tos->magic.list = li_new();
			tos->type = V_LIST_K;

			if (e->right) {
				struct val *tmp;

				eval_push(e->right);
				tmp = v_undef();
				stack_func(svalp, pop)(&Stack, &tmp);
				li_push(tos->magic.list, tmp);

				if (e->left.expr) {
					eval_push(e->left.expr);
					assert(!!V_LIST_P(TOS));
					li_append((*stack_func(svalp, at)(&Stack, 1))->magic.list, TOS->magic.list);
					stack_func(svalp, clear)(&Stack, 1);
				}
			}
			return;
		}
	}

	NOTREACHED;
}

void eval_into(struct expr *e, struct val *v) {
	DEBUG(const size_t oldsize = stack_func(svalp, size)(&Stack);)
	eval_push(e);
	assert(stack_func(svalp, size)(&Stack) - oldsize == 1u);
	stack_func(svalp, pop)(&Stack, &v);
}

struct val *eval_pop(void) {
	struct val *new = v_undef();
	assert(stack_func(svalp, size)(&Stack) != 0);
	stack_func(svalp, pop)(&Stack, &new);
	return new;
}

struct val *eval_expr(struct expr *e) {
	eval_push(e);
	return eval_pop();
}