/*
    Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "hawk-prv.h"

#define PRINT_IOERR -99

#define CMP_ERROR -99
#define DEF_BUF_CAPA 256
#define HAWK_RTX_STACK_INCREMENT 512

enum exit_level_t
{
	EXIT_NONE,
	EXIT_BREAK,
	EXIT_CONTINUE,
	EXIT_FUNCTION,
	EXIT_NEXT,
	EXIT_GLOBAL,
	EXIT_ABORT
};

struct pafv_t
{
	hawk_val_t**       args;
	hawk_oow_t         nargs;
	const hawk_ooch_t* argspec;
	hawk_oow_t         argspeclen;
};

#define DEFAULT_CONVFMT  HAWK_T("%.6g")
#define DEFAULT_OFMT     HAWK_T("%.6g")
#define DEFAULT_FS       HAWK_T(" ")
#define DEFAULT_OFS      HAWK_T(" ")
#define DEFAULT_ORS      HAWK_T("\n")
#define DEFAULT_ORS_CRLF HAWK_T("\r\n")
#define DEFAULT_SUBSEP   HAWK_T("\034")

/* the index of a positional variable should be a positive interger
 * equal to or less than the maximum value of the type by which
 * the index is represented. but it has an extra check against the
 * maximum value of hawk_oow_t as the reference is represented
 * in a pointer variable of hawk_val_ref_t and sizeof(void*) is
 * equal to sizeof(hawk_oow_t). */
#define IS_VALID_POSIDX(idx) \
	((idx) >= 0 && \
	 (idx) <= HAWK_TYPE_MAX(hawk_int_t) && \
	 (idx) <= HAWK_TYPE_MAX(hawk_oow_t))

#define CLRERR(rtx) hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_ENOERR)
#define ADJERR_LOC(rtx,l) do { (rtx)->_gem.errloc = *(l); } while (0)

static hawk_oow_t push_arg_from_vals (hawk_rtx_t* rtx, hawk_nde_fncall_t* call, void* data);
static hawk_oow_t push_arg_from_nde (hawk_rtx_t* rtx, hawk_nde_fncall_t* call, void* data);

static int init_rtx (hawk_rtx_t* rtx, hawk_t* hawk, hawk_rio_cbs_t* rio);
static void fini_rtx (hawk_rtx_t* rtx, int fini_globals);

static int init_globals (hawk_rtx_t* rtx);
static void refdown_globals (hawk_rtx_t* rtx, int pop);

static int run_pblocks (hawk_rtx_t* rtx);
static int run_pblock_chain (hawk_rtx_t* rtx, hawk_chain_t* cha);
static int run_pblock (hawk_rtx_t* rtx, hawk_chain_t* cha, hawk_oow_t bno);
static int run_block (hawk_rtx_t* rtx, hawk_nde_blk_t* nde);
static int run_statement (hawk_rtx_t* rtx, hawk_nde_t* nde);
static int run_if (hawk_rtx_t* rtx, hawk_nde_if_t* nde);
static int run_switch (hawk_rtx_t* rtx, hawk_nde_switch_t* nde);
static int run_while (hawk_rtx_t* rtx, hawk_nde_while_t* nde);
static int run_for (hawk_rtx_t* rtx, hawk_nde_for_t* nde);
static int run_forin (hawk_rtx_t* rtx, hawk_nde_forin_t* nde);
static int run_break (hawk_rtx_t* rtx, hawk_nde_break_t* nde);
static int run_continue (hawk_rtx_t* rtx, hawk_nde_continue_t* nde);
static int run_return (hawk_rtx_t* rtx, hawk_nde_return_t* nde);
static int run_exit (hawk_rtx_t* rtx, hawk_nde_exit_t* nde);
static int run_next (hawk_rtx_t* rtx, hawk_nde_next_t* nde);
static int run_nextfile (hawk_rtx_t* rtx, hawk_nde_nextfile_t* nde);
static int run_delete (hawk_rtx_t* rtx, hawk_nde_delete_t* nde);
static int run_reset (hawk_rtx_t* rtx, hawk_nde_reset_t* nde);
static int run_print (hawk_rtx_t* rtx, hawk_nde_print_t* nde);
static int run_printf (hawk_rtx_t* rtx, hawk_nde_print_t* nde);

static int output_formatted (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* dst, const hawk_ooch_t* fmt, hawk_oow_t fmt_len, hawk_nde_t* args);
static int output_formatted_bytes (hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* dst, const hawk_bch_t* fmt, hawk_oow_t fmt_len, hawk_nde_t* args);

static hawk_val_t* eval_expression (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_expression0 (hawk_rtx_t* rtx, hawk_nde_t* nde);

static hawk_val_t* eval_group (hawk_rtx_t* rtx, hawk_nde_t* nde);

static hawk_val_t* eval_assignment (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* do_assignment (hawk_rtx_t* rtx, hawk_nde_t* var, hawk_val_t* val);
static hawk_val_t* do_assignment_nonindexed (hawk_rtx_t* rtx, hawk_nde_var_t* var, hawk_val_t* val);
static hawk_val_t* do_assignment_indexed (hawk_rtx_t* rtx, hawk_nde_var_t* var, hawk_val_t* val);
static hawk_val_t* do_assignment_positional (hawk_rtx_t* rtx, hawk_nde_pos_t* pos, hawk_val_t* val);

static hawk_val_t* eval_binary (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_binop_lor (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right);
static hawk_val_t* eval_binop_land (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right);
static hawk_val_t* eval_binop_in (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right);
static hawk_val_t* eval_binop_bor (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_bxor (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_band (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);

static hawk_val_t* eval_binop_teq (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_tne (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_eq (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_ne (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_gt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_ge (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_lt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_le (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_lshift (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_rshift (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_plus (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_minus (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_mul (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_div (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_idiv (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_mod (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_exp (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_concat (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
static hawk_val_t* eval_binop_ma (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right);
static hawk_val_t* eval_binop_nm (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right);

static hawk_val_t* eval_unary (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_incpre (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_incpst (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_cnd (hawk_rtx_t* rtx, hawk_nde_t* nde);

static hawk_val_t* eval_fncall_fnc (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_fncall_fun (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_fncall_var (hawk_rtx_t* rtx, hawk_nde_t* nde);

static int get_reference (hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_val_t*** ref);
static hawk_val_t** get_reference_indexed (hawk_rtx_t* rtx, hawk_nde_var_t* var);

static hawk_val_t* eval_char (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_bchr (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_int (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_flt (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_str (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_mbs (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_rex (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_xnil (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_xargc (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_xargv (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_xargvidx (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_fun (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_named (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_gbl (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_lcl (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_arg (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_namedidx (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_gblidx (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_lclidx (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_argidx (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_pos (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_getline (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_print (hawk_rtx_t* rtx, hawk_nde_t* nde);
static hawk_val_t* eval_printf (hawk_rtx_t* rtx, hawk_nde_t* nde);

static int read_record (hawk_rtx_t* rtx);

static hawk_ooch_t* idxnde_to_str (hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_ooch_t* buf, hawk_oow_t* len, hawk_nde_t** remidx, hawk_int_t* firstidxint);
static hawk_ooi_t idxnde_to_int (hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_nde_t** remidx);

typedef hawk_val_t* (*binop_func_t) (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right);
typedef hawk_val_t* (*eval_expr_t) (hawk_rtx_t* rtx, hawk_nde_t* nde);

#define POS_VAL(rtx, idx) \
	(((idx) == 0)? (rtx)->inrec.d0: \
	 ((idx) > 0 && (idx) <= (hawk_int_t)(rtx)->inrec.nflds)? (rtx)->inrec.flds[(idx) - 1].val: \
	 hawk_val_zls)

HAWK_INLINE hawk_oow_t hawk_rtx_getnargs (hawk_rtx_t* rtx)
{
	return (hawk_oow_t)HAWK_RTX_STACK_NARGS(rtx);
}

HAWK_INLINE hawk_val_t* hawk_rtx_getarg (hawk_rtx_t* rtx, hawk_oow_t idx)
{
	return HAWK_RTX_STACK_ARG(rtx, idx);
}

HAWK_INLINE hawk_val_t* hawk_rtx_getgbl (hawk_rtx_t* rtx, int id)
{
	HAWK_ASSERT(id >= 0 && id < (int)HAWK_ARR_SIZE(rtx->hawk->parse.gbls));
	return HAWK_RTX_STACK_GBL(rtx, id);
}

const hawk_oocs_t* hawk_rtx_getsubsep (hawk_rtx_t* rtx)
{
	return &rtx->gbl.subsep;
}

/* internal function to set a value to a global variable.
 * this function can handle a few special global variables that
 * require special treatment. */
static int set_global (hawk_rtx_t* rtx, int idx, hawk_nde_var_t* var, hawk_val_t* val, int assign)
{
	hawk_val_t* old;
	hawk_rtx_ecb_t* ecb, * ecb_next;
	hawk_val_type_t vtype, old_vtype;

	old = HAWK_RTX_STACK_GBL(rtx, idx);

	vtype = HAWK_RTX_GETVALTYPE(rtx, val);
	old_vtype = HAWK_RTX_GETVALTYPE(rtx, old);

	if (!(rtx->hawk->opt.trait & HAWK_FLEXMAP))
	{
		hawk_errnum_t errnum = HAWK_ENOERR;
		const hawk_ooch_t* errfmt;

		if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
		{
			if (old_vtype == HAWK_VAL_NIL)
			{
				/* a nil valul can be overridden with any values */
				/* ok. no error */
			}
			else if (!assign && old_vtype == vtype)
			{
				/* when both are maps, how should this operation be
				 * interpreted?
				 *
				 * is it an assignment?
				 *    old = new
				 *
				 * or is it to delete all elements in the array
				 * and add new items?
				 *    for (i in old) delete old[i];
				 *    for (i in new) old[i] = new[i];
				 *
				 * i interpret this operation as the latter.
				 */

				/* ok. no error */
			}
			else
			{
				errnum = HAWK_ESCALARTONONSCA;
				errfmt = HAWK_T("not allowed to change a scalar value in '%.*js' to a nonscalar value");
			}
		}
		else
		{
			if (old_vtype == HAWK_VAL_MAP || old_vtype == HAWK_VAL_ARR)
			{
				errnum = HAWK_ENONSCATOSCALAR;
				errfmt = HAWK_T("not allowed to change a nonscalar value in '%.*js' to a scalar value");
			}
		}

		if (errnum != HAWK_ENOERR)
		{
			/* once a variable becomes a map, it cannot be assigned
			 * others value than another map. you can only add a member
			 * using indexing. */
			if (var)
			{
				/* global variable */
				hawk_rtx_seterrfmt(rtx, &var->loc, errnum, errfmt, var->id.name.len, var->id.name.ptr);
			}
			else
			{
				/* hawk_rtx_setgbl() has been called */
				hawk_oocs_t ea;
				ea.ptr = (hawk_ooch_t*)hawk_getgblname(hawk_rtx_gethawk(rtx), idx, &ea.len);
				hawk_rtx_seterrfmt(rtx, HAWK_NULL, errnum, errfmt, ea.len, ea.ptr);
			}

			return -1;
		}
	}

	if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
	{
		if (idx >= HAWK_MIN_GBL_ID && idx <= HAWK_MAX_GBL_ID)
		{
			/* short-circuit check block to prevent the basic built-in
			 * variables from being assigned a map. if you happen to add
			 * one and if that's allowed to be a map, you may have to
			 * change the condition here. */

/* TODO: use global variable attribute. can it be a map? can it be a scalar? is it read-only???? */

			hawk_oocs_t ea;
			ea.ptr = (hawk_ooch_t*)hawk_getgblname(hawk_rtx_gethawk(rtx), idx, &ea.len);
			hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_ESCALARTONONSCA, HAWK_T("not allowed to change a scalar value in '%.*js' to a nonscalar value"), ea.len, ea.ptr);
			return -1;
		}
	}

	if (old == val && idx != HAWK_GBL_NF) /* read the comment in the case block for HAWK_GBL_NF below */
	{
		/* if the old value is the same as the new value, don't take any actions.
		 * note that several inspections have been performed before this check,
		 * mainly for consistency. anyway, this condition can be met if you execute
		 * a statement like 'ARGV=ARGV'. */
		return 0;
	}

	/* perform actual assignment or assignment-like operation */
	switch (idx)
	{
		case HAWK_GBL_CONVFMT:
		{
			hawk_oow_t i;
			hawk_oocs_t str;

			str.ptr = hawk_rtx_valtooocstrdup(rtx, val, &str.len);
			if (HAWK_UNLIKELY(!str.ptr)) return -1;

			for (i = 0; i < str.len; i++)
			{
				if (str.ptr[i] == '\0')
				{
					/* '\0' is included in the value */
					hawk_rtx_freemem(rtx, str.ptr);
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_ECONVFMTCHR);
					return -1;
				}
			}

			if (rtx->gbl.convfmt.ptr) hawk_rtx_freemem(rtx, rtx->gbl.convfmt.ptr);
			rtx->gbl.convfmt.ptr = str.ptr;
			rtx->gbl.convfmt.len = str.len;
			break;
		}

		case HAWK_GBL_FNR:
		{
			int n;
			hawk_int_t lv;

			n = hawk_rtx_valtoint(rtx, val, &lv);
			if (HAWK_UNLIKELY(n <= -1)) return -1;

			rtx->gbl.fnr = lv;
			break;
		}

		case HAWK_GBL_FS:
		{
			hawk_ooch_t* fs_ptr;
			hawk_oow_t fs_len;

			/* due to the expression evaluation rule, the
			 * regular expression can not be an assigned value */
			HAWK_ASSERT(vtype != HAWK_VAL_REX);

			fs_ptr = hawk_rtx_getvaloocstr(rtx, val, &fs_len);
			if (HAWK_UNLIKELY(!fs_ptr)) return -1;

			if (fs_len > 1 && !(fs_len == 5 && fs_ptr[0] == '?'))
			{
				/* it's a regular expression if FS contains multiple characters.
				 * however, it's not a regular expression if it's 5 character
				 * string beginning with a question mark. */
				hawk_tre_t* rex, * irex;

				if (hawk_rtx_buildrex(rtx, fs_ptr, fs_len, &rex, &irex) <= -1)
				{
					hawk_rtx_freevaloocstr(rtx, val, fs_ptr);
					return -1;
				}

				if (rtx->gbl.fs[0]) hawk_rtx_freerex(rtx, rtx->gbl.fs[0], rtx->gbl.fs[1]);

				rtx->gbl.fs[0] = rex;
				rtx->gbl.fs[1] = irex;
			}

			hawk_rtx_freevaloocstr(rtx, val, fs_ptr);
			break;
		}

		case HAWK_GBL_IGNORECASE:
		{
			hawk_int_t l;
			hawk_flt_t r;
			int vt;

			vt = hawk_rtx_valtonum(rtx, val, &l, &r);
			if (vt <= -1) return -1;

			if (vt == 0)
				rtx->gbl.ignorecase = ((l > 0)? 1: (l < 0)? -1: 0);
			else
				rtx->gbl.ignorecase = ((r > 0.0)? 1: (r < 0.0)? -1: 0);
			break;
		}

		case HAWK_GBL_NF:
		{
			int n;
			hawk_int_t lv;

			n = hawk_rtx_valtoint(rtx, val, &lv);
			if (n <= -1) return -1;

			if (lv < 0)
			{
				hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EINVAL, HAWK_T("negative value into NF"));
				return -1;
			}

			if (lv < (hawk_int_t)rtx->inrec.nflds || (assign && lv == (hawk_int_t)rtx->inrec.nflds))
			{
				/* when NF is assigned a value, it should rebuild $X values.
				 * even when there is no change in the value like NF = NF,
				 * it has to rebuil $X values with the current OFS value.
				 *  { OFS=":"; NF=NF; print $0; }
				 * the NF=value assignment is indicated by a non-zero value in the 'assign' variable.
				 * 'assign' is 0 if this function is called from a different context such as
				 * explicit call to hawk_rtx_setgbl().
				 */

				if (hawk_rtx_truncrec(rtx, (hawk_oow_t)lv) <= -1)
				{
					/* adjust the error line */
					/*if (var) ADJERR_LOC(rtx, &var->loc);*/
					return -1;
				}
			}
			else if (lv > (hawk_int_t)rtx->inrec.nflds)
			{
				hawk_oocs_t cs;
				cs.ptr = HAWK_T("");
				cs.len = 0;
				if (hawk_rtx_setrec(rtx, lv, &cs, 0) <= -1) return -1;
			}

			/* for all other globals, it returns before this switch/case block is reached
			 * if the same value is assigned. but NF change requires extra action to take
			 * as coded before this switch/case block. */
			if (old == val) return 0;

			break;
		}


		case HAWK_GBL_NR:
		{
			int n;
			hawk_int_t lv;

			n = hawk_rtx_valtoint(rtx, val, &lv);
			if (n <= -1) return -1;

			rtx->gbl.nr = lv;
			break;
		}

		case HAWK_GBL_NUMSTRDETECT:
		{
			hawk_int_t l;
			hawk_flt_t r;
			int vt;

			vt = hawk_rtx_valtonum(rtx, val, &l, &r);
			if (vt <= -1) return -1;

			if (vt == 0)
				rtx->gbl.numstrdetect = ((l > 0)? 1: (l < 0)? -1: 0);
			else
				rtx->gbl.numstrdetect = ((r > 0.0)? 1: (r < 0.0)? -1: 0);
			break;
		}

		case HAWK_GBL_OFMT:
		{
			hawk_oow_t i;
			hawk_oocs_t str;

			str.ptr = hawk_rtx_valtooocstrdup(rtx, val, &str.len);
			if (HAWK_UNLIKELY(!str.ptr)) return -1;

			for (i = 0; i < str.len; i++)
			{
				if (str.ptr[i] == '\0')
				{
					/* '\0' is included in the value */
					hawk_rtx_freemem(rtx, str.ptr);
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_ECONVFMTCHR);
					return -1;
				}
			}

			if (rtx->gbl.ofmt.ptr) hawk_rtx_freemem(rtx, rtx->gbl.ofmt.ptr);
			rtx->gbl.ofmt.ptr = str.ptr;
			rtx->gbl.ofmt.len = str.len;
		}

		case HAWK_GBL_OFS:
		{
			hawk_oocs_t str;

			str.ptr = hawk_rtx_valtooocstrdup(rtx, val, &str.len);
			if (HAWK_UNLIKELY(!str.ptr)) return -1;

			if (rtx->gbl.ofs.ptr) hawk_rtx_freemem(rtx, rtx->gbl.ofs.ptr);
			rtx->gbl.ofs.ptr = str.ptr;
			rtx->gbl.ofs.len = str.len;

			break;
		}

		case HAWK_GBL_ORS:
		{
			hawk_oocs_t str;

			str.ptr = hawk_rtx_valtooocstrdup(rtx, val, &str.len);
			if (HAWK_UNLIKELY(!str.ptr)) return -1;

			if (rtx->gbl.ors.ptr) hawk_rtx_freemem(rtx, rtx->gbl.ors.ptr);
			rtx->gbl.ors.ptr = str.ptr;
			rtx->gbl.ors.len = str.len;

			break;
		}

		case HAWK_GBL_RS:
		{
			hawk_oocs_t rss;

			/* due to the expression evaluation rule, the
			 * regular expression can not be an assigned
			 * value */
			HAWK_ASSERT(vtype != HAWK_VAL_REX);

			rss.ptr = hawk_rtx_getvaloocstr(rtx, val, &rss.len);
			if (!rss.ptr) return -1;

			if (rtx->gbl.rs[0])
			{
				hawk_rtx_freerex(rtx, rtx->gbl.rs[0], rtx->gbl.rs[1]);
				rtx->gbl.rs[0] = HAWK_NULL;
				rtx->gbl.rs[1] = HAWK_NULL;
			}

			if (rss.len > 1)
			{
				hawk_tre_t* rex, * irex;

				/* compile the regular expression */
				if (hawk_rtx_buildrex(rtx, rss.ptr, rss.len, &rex, &irex) <= -1)
				{
					hawk_rtx_freevaloocstr(rtx, val, rss.ptr);
					return -1;
				}

				rtx->gbl.rs[0] = rex;
				rtx->gbl.rs[1] = irex;
			}

			hawk_rtx_freevaloocstr(rtx, val, rss.ptr);
			break;
		}

		case HAWK_GBL_STRIPRECSPC:
		{
			hawk_int_t l;
			hawk_flt_t r;
			int vt;

			vt = hawk_rtx_valtonum(rtx, val, &l, &r);
			if (vt <= -1) return -1;

			if (vt == 0)
				rtx->gbl.striprecspc = ((l > 0)? 1: (l < 0)? -1: 0);
			else
				rtx->gbl.striprecspc = ((r > 0.0)? 1: (r < 0.0)? -1: 0);
			break;
		}

		case HAWK_GBL_STRIPSTRSPC:
		{
			hawk_int_t l;
			hawk_flt_t r;
			int vt;

			vt = hawk_rtx_valtonum(rtx, val, &l, &r);
			if (vt <= -1) return -1;

			if (vt == 0)
				rtx->gbl.stripstrspc = ((l > 0)? 1: (l < 0)? -1: 0);
			else
				rtx->gbl.stripstrspc = ((r > 0.0)? 1: (r < 0.0)? -1: 0);
			break;
		}

		case HAWK_GBL_SUBSEP:
		{
			hawk_oocs_t str;

			str.ptr = hawk_rtx_valtooocstrdup(rtx, val, &str.len);
			if (HAWK_UNLIKELY(!str.ptr)) return -1;

			if (rtx->gbl.subsep.ptr) hawk_rtx_freemem(rtx, rtx->gbl.subsep.ptr);
			rtx->gbl.subsep.ptr = str.ptr;
			rtx->gbl.subsep.len = str.len;

			break;
		}
	}

	hawk_rtx_refdownval(rtx, old);
	HAWK_RTX_STACK_GBL(rtx,idx) = val;
	hawk_rtx_refupval(rtx, val);

	for (ecb = rtx->ecb; ecb != (hawk_rtx_ecb_t*)rtx; ecb = ecb_next)
	{
		ecb_next = ecb->next;
		if (ecb->gblset) ecb->gblset(rtx, idx, val, ecb->ctx);
	}

	return 0;
}

HAWK_INLINE void hawk_rtx_setretval (hawk_rtx_t* rtx, hawk_val_t* val)
{
	hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_RETVAL(rtx));
	HAWK_RTX_STACK_RETVAL(rtx) = val;
	/* should use the same trick as run_return */
	hawk_rtx_refupval(rtx, val);
}

HAWK_INLINE int hawk_rtx_setgbl (hawk_rtx_t* rtx, int id, hawk_val_t* val)
{
	HAWK_ASSERT(id >= 0 && id < (int)HAWK_ARR_SIZE(rtx->hawk->parse.gbls));
	return set_global(rtx, id, HAWK_NULL, val, 0);
}

int hawk_rtx_setgbltostrbyname (hawk_rtx_t* rtx, const hawk_ooch_t* name, const hawk_ooch_t* val)
{
	int id, n;
	hawk_val_t* v;

	HAWK_ASSERT(hawk_isvalidident(hawk_rtx_gethawk(rtx), name));

	id = hawk_findgblwithoocstr(hawk_rtx_gethawk(rtx), name, 1);
	v = hawk_rtx_makestrvalwithoocstr(rtx, val);
	if (!v) return -1;

	hawk_rtx_refupval(rtx, v);
	if (id <= -1)
	{
		/* not found. it's not a builtin/declared global variable */
		hawk_nde_var_t var;

		/* make a fake variable node for assignment and use it */
		HAWK_MEMSET(&var, 0, HAWK_SIZEOF(var));
		var.type = HAWK_NDE_NAMED;
		var.id.name.ptr = (hawk_ooch_t*)name;
		var.id.name.len = hawk_count_oocstr(name);
		var.id.idxa = (hawk_oow_t)-1;
		var.idx = HAWK_NULL;

		n = do_assignment_nonindexed(rtx, &var, v)? 0: -1;
	}
	else
	{
		n = set_global(rtx, id, HAWK_NULL, v, 0);
	}
	hawk_rtx_refdownval(rtx, v);

	return n;
}

int hawk_rtx_setscriptnamewithuchars (hawk_rtx_t* rtx, const hawk_uch_t* name, hawk_oow_t len)
{
	hawk_val_t* tmp;
	int n;

	tmp = hawk_rtx_makestrvalwithuchars(rtx, name, len);
	if (tmp == HAWK_NULL) return -1;

	hawk_rtx_refupval(rtx, tmp);
	n = hawk_rtx_setgbl(rtx, HAWK_GBL_SCRIPTNAME, tmp);
	hawk_rtx_refdownval(rtx, tmp);

	return n;
}

int hawk_rtx_setscriptnamewithbchars (hawk_rtx_t* rtx, const hawk_bch_t* name, hawk_oow_t len)
{
	hawk_val_t* tmp;
	int n;

	tmp = hawk_rtx_makestrvalwithbchars(rtx, name, len);
	if (tmp == HAWK_NULL) return -1;

	hawk_rtx_refupval(rtx, tmp);
	n = hawk_rtx_setgbl(rtx, HAWK_GBL_SCRIPTNAME, tmp);
	hawk_rtx_refdownval(rtx, tmp);

	return n;
}

int hawk_rtx_setfilenamewithuchars (hawk_rtx_t* rtx, const hawk_uch_t* name, hawk_oow_t len)
{
	hawk_val_t* tmp;
	int n;

	tmp = hawk_rtx_makestrvalwithuchars(rtx, name, len);
	if (HAWK_UNLIKELY(!tmp)) return -1;

	hawk_rtx_refupval(rtx, tmp);
	n = hawk_rtx_setgbl(rtx, HAWK_GBL_FILENAME, tmp);
	hawk_rtx_refdownval(rtx, tmp);

	return n;
}

int hawk_rtx_setfilenamewithbchars (hawk_rtx_t* rtx, const hawk_bch_t* name, hawk_oow_t len)
{
	hawk_val_t* tmp;
	int n;

	tmp = hawk_rtx_makestrvalwithbchars(rtx, name, len);
	if (HAWK_UNLIKELY(!tmp)) return -1;

	hawk_rtx_refupval(rtx, tmp);
	n = hawk_rtx_setgbl(rtx, HAWK_GBL_FILENAME, tmp);
	hawk_rtx_refdownval(rtx, tmp);

	return n;
}


int hawk_rtx_setofilenamewithuchars (hawk_rtx_t* rtx, const hawk_uch_t* name, hawk_oow_t len)
{
	hawk_val_t* tmp;
	int n;

	if (rtx->hawk->opt.trait & HAWK_NEXTOFILE)
	{
		tmp = hawk_rtx_makestrvalwithuchars(rtx, name, len);
		if (HAWK_UNLIKELY(!tmp)) return -1;

		hawk_rtx_refupval(rtx, tmp);
		n = hawk_rtx_setgbl(rtx, HAWK_GBL_OFILENAME, tmp);
		hawk_rtx_refdownval(rtx, tmp);
	}
	else n = 0;

	return n;
}

int hawk_rtx_setofilenamewithbchars (hawk_rtx_t* rtx, const hawk_bch_t* name, hawk_oow_t len)
{
	hawk_val_t* tmp;
	int n;

	if (rtx->hawk->opt.trait & HAWK_NEXTOFILE)
	{
		tmp = hawk_rtx_makestrvalwithbchars(rtx, name, len);
		if (HAWK_UNLIKELY(!tmp)) return -1;

		hawk_rtx_refupval(rtx, tmp);
		n = hawk_rtx_setgbl(rtx, HAWK_GBL_OFILENAME, tmp);
		hawk_rtx_refdownval(rtx, tmp);
	}
	else n = 0;

	return n;
}

hawk_htb_t* hawk_rtx_getnvmap (hawk_rtx_t* rtx)
{
	return rtx->named;
}

struct module_init_ctx_t
{
	hawk_oow_t count;
	hawk_rtx_t* rtx;
};

struct module_fini_ctx_t
{
	hawk_oow_t limit;
	hawk_oow_t count;
	hawk_rtx_t* rtx;
};

static hawk_rbt_walk_t init_module (hawk_rbt_t* rbt, hawk_rbt_pair_t* pair, void* ctx)
{
	hawk_mod_data_t* md;
	struct module_init_ctx_t* mic;

	mic = (struct module_init_ctx_t*)ctx;

	md = (hawk_mod_data_t*)HAWK_RBT_VPTR(pair);
	if (md->mod.init && md->mod.init(&md->mod, mic->rtx) <= -1)
		return HAWK_RBT_WALK_STOP;

	mic->count++;
	return HAWK_RBT_WALK_FORWARD;
}

static hawk_rbt_walk_t fini_module (hawk_rbt_t* rbt, hawk_rbt_pair_t* pair, void* ctx)
{
	hawk_mod_data_t* md;
	struct module_fini_ctx_t* mfc;

	mfc = (struct module_fini_ctx_t*)ctx;

	if (mfc->limit > 0 && mfc->count >= mfc->limit)
		return HAWK_RBT_WALK_STOP;

	md = (hawk_mod_data_t*)HAWK_RBT_VPTR(pair);
	if (md->mod.fini) md->mod.fini(&md->mod, mfc->rtx);

	mfc->count++;
	return HAWK_RBT_WALK_FORWARD;
}

hawk_rtx_t* hawk_rtx_open (hawk_t* hawk, hawk_oow_t xtnsize, hawk_rio_cbs_t* rio)
{
	hawk_rtx_t* rtx;
	struct module_init_ctx_t mic;
	hawk_oow_t i;

	/* clear the hawk error code */
	hawk_seterrnum(hawk, HAWK_NULL, HAWK_ENOERR);

	/* check if the code has ever been parsed */
	if (hawk->tree.ngbls == 0 &&
	    hawk->tree.begin == HAWK_NULL &&
	    hawk->tree.end == HAWK_NULL &&
	    hawk->tree.chain_size == 0 &&
	    hawk_htb_getsize(hawk->tree.funs) == 0)
	{
		hawk_seterrnum(hawk, HAWK_NULL, HAWK_EPERM);
		return HAWK_NULL;
	}

	/* allocate the storage for the rtx object */
	rtx = (hawk_rtx_t*)hawk_allocmem(hawk, HAWK_SIZEOF(hawk_rtx_t) + xtnsize);
	if (HAWK_UNLIKELY(!rtx))
	{
		/* if it fails, the failure is reported thru the hawk object */
		return HAWK_NULL;
	}

	/* initialize the rtx object */
	HAWK_MEMSET(rtx, 0, HAWK_SIZEOF(hawk_rtx_t) + xtnsize);
	rtx->_instsize = HAWK_SIZEOF(hawk_rtx_t);
	if (HAWK_UNLIKELY(init_rtx(rtx, hawk, rio) <= -1))
	{
		/* because the error information is in the gem part,
		 * it should be ok to copy over rtx error to hawk even if
		 * rtx initialization fails. */
		hawk_rtx_errortohawk(rtx, hawk);
		hawk_freemem(hawk, rtx);
		return HAWK_NULL;
	}

	rtx->ecb = (hawk_rtx_ecb_t*)rtx; /* use this as a special sentinel node */

	if (HAWK_UNLIKELY(init_globals(rtx) <= -1))
	{
		hawk_rtx_errortohawk(rtx, hawk);
		fini_rtx(rtx, 0);
		hawk_freemem(hawk, rtx);
		return HAWK_NULL;
	}

	mic.count = 0;
	mic.rtx = rtx;
	hawk_rbt_walk(rtx->hawk->modtab, init_module, &mic);
	if (mic.count != HAWK_RBT_SIZE(rtx->hawk->modtab))
	{
		if (mic.count > 0)
		{
			struct module_fini_ctx_t mfc;
			mfc.limit = mic.count;
			mfc.count = 0;
			hawk_rbt_walk(rtx->hawk->modtab, fini_module, &mfc);
		}

		fini_rtx(rtx, 1);
		hawk_freemem(hawk, rtx);
		return HAWK_NULL;
	}

	/* chain the ctos slots */
	rtx->ctos.fi = HAWK_COUNTOF(rtx->ctos.b) - 1;
	for (i = HAWK_COUNTOF(rtx->ctos.b); i > 1;)
	{
		--i;
		rtx->ctos.b[i].c[0] = i - 1;
	}
	/* rtx->ctos.b[0] is not used as the free index of 0 indicates the end of the list.
	 * see hawk_rtx_getvaloocstr(). */

	/* chain the bctos slots */
	rtx->bctos.fi = HAWK_COUNTOF(rtx->bctos.b) - 1;
	for (i = HAWK_COUNTOF(rtx->bctos.b); i > 1;)
	{
		--i;
		rtx->bctos.b[i].c[0] = i - 1;
	}
	/* rtx->bctos.b[0] is not used as the free index of 0 indicates the end of the list.
	 * see hawk_rtx_getvaloocstr(). */

	return rtx;
}

void hawk_rtx_close (hawk_rtx_t* rtx)
{
	hawk_rtx_ecb_t* ecb, * ecb_next;
	struct module_fini_ctx_t mfc;

	mfc.limit = 0;
	mfc.count = 0;
	mfc.rtx = rtx;
	hawk_rbt_walk(rtx->hawk->modtab, fini_module, &mfc);

	for (ecb = rtx->ecb; ecb != (hawk_rtx_ecb_t*)rtx; ecb = ecb_next)
	{
		ecb_next = ecb->next;
		if (ecb->close) ecb->close(rtx, ecb->ctx);
	}

	do { ecb = hawk_rtx_popecb(rtx); } while (ecb);
	HAWK_ASSERT(rtx->ecb == (hawk_rtx_ecb_t*)rtx);

	/* NOTE:
	 *  the close callbacks are called before data in rtx
	 *  is destroyed. if the destruction count on any data
	 *  destroyed by the close callback, something bad
	 *  will happen.
	 */
	fini_rtx(rtx, 1);

	hawk_freemem(hawk_rtx_gethawk(rtx), rtx);
}

void hawk_rtx_halt (hawk_rtx_t* rtx)
{
	rtx->exit_level = EXIT_ABORT;
}

int hawk_rtx_ishalt (hawk_rtx_t* rtx)
{
	return (rtx->exit_level == EXIT_ABORT || rtx->hawk->haltall);
}

void hawk_rtx_getrio (hawk_rtx_t* rtx, hawk_rio_cbs_t* rio)
{
	rio->pipe = rtx->rio.handler[HAWK_RIO_PIPE];
	rio->file = rtx->rio.handler[HAWK_RIO_FILE];
	rio->console = rtx->rio.handler[HAWK_RIO_CONSOLE];
}

void hawk_rtx_setrio (hawk_rtx_t* rtx, const hawk_rio_cbs_t* rio)
{
	rtx->rio.handler[HAWK_RIO_PIPE] = rio->pipe;
	rtx->rio.handler[HAWK_RIO_FILE] = rio->file;
	rtx->rio.handler[HAWK_RIO_CONSOLE] = rio->console;
}

void hawk_rtx_killecb (hawk_rtx_t* rtx, hawk_rtx_ecb_t* ecb)
{
	hawk_rtx_ecb_t* prev, * cur;
	for (cur = rtx->ecb, prev = HAWK_NULL; cur != (hawk_rtx_ecb_t*)rtx; cur = cur->next)
	{
		if (cur == ecb)
		{
			if (prev) prev->next = cur->next;
			else rtx->ecb = cur->next;
			cur->next = HAWK_NULL;
			break;
		}
	}
}

hawk_rtx_ecb_t* hawk_rtx_popecb (hawk_rtx_t* rtx)
{
	hawk_rtx_ecb_t* top = rtx->ecb;
	if (top == (hawk_rtx_ecb_t*)rtx) return HAWK_NULL;
	rtx->ecb = top->next;
	top->next = HAWK_NULL;
	return top;
}

void hawk_rtx_pushecb (hawk_rtx_t* rtx, hawk_rtx_ecb_t* ecb)
{
	ecb->next = rtx->ecb;
	rtx->ecb = ecb;
}

static void free_namedval (hawk_htb_t* map, void* dptr, hawk_oow_t dlen)
{
	hawk_rtx_refdownval(*(hawk_rtx_t**)hawk_htb_getxtn(map), dptr);
}

static void same_namedval (hawk_htb_t* map, void* dptr, hawk_oow_t dlen)
{
	hawk_rtx_refdownval_nofree(*(hawk_rtx_t**)hawk_htb_getxtn(map), dptr);
}

static int init_rtx (hawk_rtx_t* rtx, hawk_t* hawk, hawk_rio_cbs_t* rio)
{
	static hawk_htb_style_t style_for_named =
	{
		{
			HAWK_HTB_COPIER_INLINE,
			HAWK_HTB_COPIER_DEFAULT
		},
		{
			HAWK_HTB_FREEER_DEFAULT,
			free_namedval
		},
		HAWK_HTB_COMPER_DEFAULT,
		same_namedval,
		HAWK_HTB_SIZER_DEFAULT,
		HAWK_HTB_HASHER_DEFAULT
	};
	hawk_oow_t stack_limit, i;

	rtx->_gem = hawk->_gem;
	rtx->hawk = hawk;

	CLRERR(rtx);

	stack_limit = hawk->parse.pragma.rtx_stack_limit > 0? hawk->parse.pragma.rtx_stack_limit: hawk->opt.rtx_stack_limit;
	if (stack_limit < HAWK_MIN_RTX_STACK_LIMIT) stack_limit = HAWK_MIN_RTX_STACK_LIMIT;
	rtx->stack = hawk_rtx_allocmem(rtx, stack_limit * HAWK_SIZEOF(void*));
	if (HAWK_UNLIKELY(!rtx->stack)) goto oops_0;
	rtx->stack_top = 0;
	rtx->stack_base = 0;
	rtx->stack_limit = stack_limit;

	rtx->exit_level = EXIT_NONE;

	rtx->vmgr.ichunk = HAWK_NULL;
	rtx->vmgr.ifree = HAWK_NULL;
	rtx->vmgr.rchunk = HAWK_NULL;
	rtx->vmgr.rfree = HAWK_NULL;

	for (i = 0; i < HAWK_COUNTOF(rtx->gc.g); i++)
	{
		/* initialize circular doubly-linked list */
		rtx->gc.g[i].gc_next = &rtx->gc.g[i];
		rtx->gc.g[i].gc_prev = &rtx->gc.g[i];

		/* initialize some counters */
		rtx->gc.pressure[i] = 0;
		rtx->gc.threshold[i] = (HAWK_COUNTOF(rtx->gc.g) - i) * 10;
		if (i == 0 && rtx->gc.threshold[i] < 100) rtx->gc.threshold[i] = 100; /* minimum threshold for gen 0 is 100 */
	}

	rtx->gc.pressure[i] = 0; /* pressure is larger than other elements by 1 in size */

	rtx->inrec.buf_pos = 0;
	rtx->inrec.buf_len = 0;
	rtx->inrec.flds = HAWK_NULL;
	rtx->inrec.nflds = 0;
	rtx->inrec.maxflds = 0;
	rtx->inrec.d0 = hawk_val_nil;

	if (HAWK_UNLIKELY(hawk_ooecs_init(&rtx->inrec.line, hawk_rtx_getgem(rtx), DEF_BUF_CAPA) <= -1)) goto oops_1;
	if (HAWK_UNLIKELY(hawk_ooecs_init(&rtx->inrec.linew, hawk_rtx_getgem(rtx), DEF_BUF_CAPA) <= -1)) goto oops_2;
	if (HAWK_UNLIKELY(hawk_ooecs_init(&rtx->inrec.lineg, hawk_rtx_getgem(rtx), DEF_BUF_CAPA) <= -1)) goto oops_3;
	if (HAWK_UNLIKELY(hawk_becs_init(&rtx->inrec.linegb, hawk_rtx_getgem(rtx), DEF_BUF_CAPA) <= -1)) goto oops_4;
	if (HAWK_UNLIKELY(hawk_ooecs_init(&rtx->format.out, hawk_rtx_getgem(rtx), 256) <= -1)) goto oops_5;
	if (HAWK_UNLIKELY(hawk_ooecs_init(&rtx->format.fmt, hawk_rtx_getgem(rtx), 256) <= -1)) goto oops_6;

	if (HAWK_UNLIKELY(hawk_becs_init(&rtx->formatmbs.out, hawk_rtx_getgem(rtx), 256) <= -1)) goto oops_7;
	if (HAWK_UNLIKELY(hawk_becs_init(&rtx->formatmbs.fmt, hawk_rtx_getgem(rtx), 256) <= -1)) goto oops_8;

	if (HAWK_UNLIKELY(hawk_becs_init(&rtx->fnc.bout, hawk_rtx_getgem(rtx), 256) <= -1)) goto oops_9;
	if (HAWK_UNLIKELY(hawk_ooecs_init(&rtx->fnc.oout, hawk_rtx_getgem(rtx), 256) <= -1)) goto oops_10;


	rtx->named = hawk_htb_open(hawk_rtx_getgem(rtx), HAWK_SIZEOF(rtx), 1024, 70, HAWK_SIZEOF(hawk_ooch_t), 1);
	if (HAWK_UNLIKELY(!rtx->named)) goto oops_11;
	*(hawk_rtx_t**)hawk_htb_getxtn(rtx->named) = rtx;
	hawk_htb_setstyle (rtx->named, &style_for_named);

	rtx->format.tmp.ptr = (hawk_ooch_t*)hawk_rtx_allocmem(rtx, 4096 * HAWK_SIZEOF(hawk_ooch_t));
	if (HAWK_UNLIKELY(!rtx->format.tmp.ptr)) goto oops_12; /* the error is set on the hawk object after this jump is made */
	rtx->format.tmp.len = 4096;
	rtx->format.tmp.inc = 4096 * 2;

	rtx->formatmbs.tmp.ptr = (hawk_bch_t*)hawk_rtx_allocmem(rtx, 4096 * HAWK_SIZEOF(hawk_bch_t));
	if (HAWK_UNLIKELY(!rtx->formatmbs.tmp.ptr)) goto oops_13;
	rtx->formatmbs.tmp.len = 4096;
	rtx->formatmbs.tmp.inc = 4096 * 2;

	if (rtx->hawk->tree.chain_size > 0)
	{
		rtx->pattern_range_state = (hawk_oob_t*)hawk_rtx_allocmem(rtx, rtx->hawk->tree.chain_size * HAWK_SIZEOF(hawk_oob_t));
		if (HAWK_UNLIKELY(!rtx->pattern_range_state)) goto oops_14;
		HAWK_MEMSET(rtx->pattern_range_state, 0, rtx->hawk->tree.chain_size * HAWK_SIZEOF(hawk_oob_t));
	}
	else rtx->pattern_range_state = HAWK_NULL;

	if (rio)
	{
		rtx->rio.handler[HAWK_RIO_PIPE] = rio->pipe;
		rtx->rio.handler[HAWK_RIO_FILE] = rio->file;
		rtx->rio.handler[HAWK_RIO_CONSOLE] = rio->console;
		rtx->rio.chain = HAWK_NULL;
	}

	rtx->gbl.rs[0] = HAWK_NULL;
	rtx->gbl.rs[1] = HAWK_NULL;
	rtx->gbl.fs[0] = HAWK_NULL;
	rtx->gbl.fs[1] = HAWK_NULL;
	rtx->gbl.ignorecase = 0;
	rtx->gbl.striprecspc = -1; /* means 'not set' */
	rtx->gbl.stripstrspc = -1; /* means 'not set' */
	rtx->gbl.numstrdetect = -1; /* means 'not set' */

	return 0;

oops_14:
	hawk_rtx_freemem(rtx, rtx->formatmbs.tmp.ptr);
oops_13:
	hawk_rtx_freemem(rtx, rtx->format.tmp.ptr);
oops_12:
	hawk_htb_close(rtx->named);
oops_11:
	hawk_ooecs_fini(&rtx->fnc.oout);
oops_10:
	hawk_becs_fini(&rtx->fnc.bout);
oops_9:
	hawk_becs_fini(&rtx->formatmbs.fmt);
oops_8:
	hawk_becs_fini(&rtx->formatmbs.out);
oops_7:
	hawk_ooecs_fini(&rtx->format.fmt);
oops_6:
	hawk_ooecs_fini(&rtx->format.out);
oops_5:
	hawk_becs_fini(&rtx->inrec.linegb);
oops_4:
	hawk_ooecs_fini(&rtx->inrec.lineg);
oops_3:
	hawk_ooecs_fini(&rtx->inrec.linew);
oops_2:
	hawk_ooecs_fini(&rtx->inrec.line);
oops_1:
	hawk_rtx_freemem(rtx, rtx->stack);
oops_0:
	return -1;
}

static void fini_rtx (hawk_rtx_t* rtx, int fini_globals)
{
	if (rtx->forin.ptr)
	{
		while (rtx->forin.size > 0)
		{
			hawk_rtx_refdownval(rtx, rtx->forin.ptr[--rtx->forin.size]);
		}
		hawk_rtx_freemem(rtx, rtx->forin.ptr);
		rtx->forin.ptr = HAWK_NULL;
		rtx->forin.capa = 0;
	}

	if (rtx->pattern_range_state)
		hawk_rtx_freemem(rtx, rtx->pattern_range_state);

	/* close all pending io's */
	/* TODO: what if this operation fails? */
	hawk_rtx_clearallios (rtx);
	HAWK_ASSERT(rtx->rio.chain == HAWK_NULL);

	if (rtx->gbl.rs[0])
	{
		hawk_rtx_freerex(rtx, rtx->gbl.rs[0], rtx->gbl.rs[1]);
		rtx->gbl.rs[0] = HAWK_NULL;
		rtx->gbl.rs[1] = HAWK_NULL;
	}
	if (rtx->gbl.fs[0])
	{
		hawk_rtx_freerex(rtx, rtx->gbl.fs[0], rtx->gbl.fs[1]);
		rtx->gbl.fs[0] = HAWK_NULL;
		rtx->gbl.fs[1] = HAWK_NULL;
	}

	if (rtx->gbl.convfmt.ptr != HAWK_NULL &&
	    rtx->gbl.convfmt.ptr != DEFAULT_CONVFMT)
	{
		hawk_rtx_freemem(rtx, rtx->gbl.convfmt.ptr);
		rtx->gbl.convfmt.ptr = HAWK_NULL;
		rtx->gbl.convfmt.len = 0;
	}

	if (rtx->gbl.ofmt.ptr != HAWK_NULL &&
	    rtx->gbl.ofmt.ptr != DEFAULT_OFMT)
	{
		hawk_rtx_freemem(rtx, rtx->gbl.ofmt.ptr);
		rtx->gbl.ofmt.ptr = HAWK_NULL;
		rtx->gbl.ofmt.len = 0;
	}

	if (rtx->gbl.ofs.ptr != HAWK_NULL &&
	    rtx->gbl.ofs.ptr != DEFAULT_OFS)
	{
		hawk_rtx_freemem(rtx, rtx->gbl.ofs.ptr);
		rtx->gbl.ofs.ptr = HAWK_NULL;
		rtx->gbl.ofs.len = 0;
	}

	if (rtx->gbl.ors.ptr != HAWK_NULL &&
	    rtx->gbl.ors.ptr != DEFAULT_ORS &&
	    rtx->gbl.ors.ptr != DEFAULT_ORS_CRLF)
	{
		hawk_rtx_freemem(rtx, rtx->gbl.ors.ptr);
		rtx->gbl.ors.ptr = HAWK_NULL;
		rtx->gbl.ors.len = 0;
	}

	if (rtx->gbl.subsep.ptr != HAWK_NULL &&
	    rtx->gbl.subsep.ptr != DEFAULT_SUBSEP)
	{
		hawk_rtx_freemem(rtx, rtx->gbl.subsep.ptr);
		rtx->gbl.subsep.ptr = HAWK_NULL;
		rtx->gbl.subsep.len = 0;
	}

	hawk_ooecs_fini(&rtx->fnc.oout);
	hawk_becs_fini(&rtx->fnc.bout);

	hawk_rtx_freemem(rtx, rtx->formatmbs.tmp.ptr);
	rtx->formatmbs.tmp.ptr = HAWK_NULL;
	rtx->formatmbs.tmp.len = 0;
	hawk_becs_fini(&rtx->formatmbs.fmt);
	hawk_becs_fini(&rtx->formatmbs.out);

	hawk_rtx_freemem(rtx, rtx->format.tmp.ptr);
	rtx->format.tmp.ptr = HAWK_NULL;
	rtx->format.tmp.len = 0;
	hawk_ooecs_fini(&rtx->format.fmt);
	hawk_ooecs_fini(&rtx->format.out);

	/* destroy input record. hawk_rtx_clrrec() should be called
	 * before the stack has been destroyed because it may try
	 * to change the value to HAWK_GBL_NF. */
	hawk_rtx_clrrec(rtx, 0);
	if (rtx->inrec.flds)
	{
		hawk_rtx_freemem(rtx, rtx->inrec.flds);
		rtx->inrec.flds = HAWK_NULL;
		rtx->inrec.maxflds = 0;
	}
	hawk_becs_fini(&rtx->inrec.linegb);
	hawk_ooecs_fini(&rtx->inrec.lineg);
	hawk_ooecs_fini(&rtx->inrec.linew);
	hawk_ooecs_fini(&rtx->inrec.line);

	if (fini_globals) refdown_globals(rtx, 1);

	/* destroy the stack if necessary */
	if (rtx->stack)
	{
		HAWK_ASSERT(rtx->stack_top == 0);

		hawk_rtx_freemem(rtx, rtx->stack);
		rtx->stack = HAWK_NULL;
		rtx->stack_top = 0;
		rtx->stack_base = 0;
		rtx->stack_limit = 0;
	}

	/* destroy named variables */
	hawk_htb_close (rtx->named);

#if defined(HAWK_ENABLE_GC)
	/* collect garbage after having released global variables and named global variables */
	hawk_rtx_gc(rtx, HAWK_RTX_GC_GEN_FULL);
#endif

	/* destroy values in free list */
	while (rtx->rcache_count > 0)
	{
		hawk_val_ref_t* tmp = rtx->rcache[--rtx->rcache_count];
		hawk_rtx_freeval(rtx, (hawk_val_t*)tmp, 0);
	}

#if defined(HAWK_ENABLE_STR_CACHE)
	{
		int i;
		for (i = 0; i < HAWK_COUNTOF(rtx->str_cache_count); i++)
		{
			while (rtx->str_cache_count[i] > 0)
			{
				hawk_val_str_t* t = rtx->str_cache[i][--rtx->str_cache_count[i]];
				hawk_rtx_freeval(rtx, (hawk_val_t*)t, 0);
			}
		}
	}
#endif

#if defined(HAWK_ENABLE_MBS_CACHE)
	{
		int i;
		for (i = 0; i < HAWK_COUNTOF(rtx->mbs_cache_count); i++)
		{
			while (rtx->mbs_cache_count[i] > 0)
			{
				hawk_val_mbs_t* t = rtx->mbs_cache[i][--rtx->mbs_cache_count[i]];
				hawk_rtx_freeval(rtx, (hawk_val_t*)t, 0);
			}
		}
	}
#endif

	hawk_rtx_freevalchunk(rtx, rtx->vmgr.ichunk);
	hawk_rtx_freevalchunk(rtx, rtx->vmgr.rchunk);
	rtx->vmgr.ichunk = HAWK_NULL;
	rtx->vmgr.rchunk = HAWK_NULL;
}

static int update_fnr (hawk_rtx_t* rtx, hawk_int_t fnr, hawk_int_t nr)
{
	hawk_val_t* tmp1, * tmp2;
	int n;

	tmp1 = hawk_rtx_makeintval(rtx, fnr);
	if (!tmp1) return -1;

	hawk_rtx_refupval(rtx, tmp1);

	if (nr == fnr) tmp2 = tmp1;
	else
	{
		tmp2 = hawk_rtx_makeintval(rtx, nr);
		if (HAWK_UNLIKELY(!tmp2)) { n = -1; goto oops; }
		hawk_rtx_refupval(rtx, tmp2);
	}

	n = (hawk_rtx_setgbl(rtx, HAWK_GBL_FNR, tmp1) <= -1 ||
	     hawk_rtx_setgbl(rtx, HAWK_GBL_NR, tmp2) <= -1)? -1: 0;

	if (tmp2 != tmp1) hawk_rtx_refdownval(rtx, tmp2);

oops:
	hawk_rtx_refdownval(rtx, tmp1);
	return n;
}

/*
 * create global variables into the runtime stack
 * each variable is initialized to nil or zero.
 */
static int prepare_globals (hawk_rtx_t* rtx)
{
	hawk_oow_t saved_stack_top;
	hawk_oow_t ngbls;

	ngbls = rtx->hawk->tree.ngbls;

	if (HAWK_UNLIKELY(HAWK_RTX_STACK_AVAIL(rtx) < ngbls))
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_ESTACK);
		return -1;
	}

	saved_stack_top = rtx->stack_top;

	/* initialize all global variables to nil by push nils to the stack */
	while (ngbls > 0)
	{
		--ngbls;
		HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil);
	}

	/* override NF to zero */
	if (hawk_rtx_setgbl(rtx, HAWK_GBL_NF, HAWK_VAL_ZERO) <= -1)
	{
		/* restore the stack_top this way instead of calling HAWK_RTX_STACK_POP()
		 * as many times as successful HAWK_RTX_STACK_PUSH(). it is ok because
		 * the values pushed so far are hawk_val_nils and HAWK_VAL_ZEROs.
		 */
		rtx->stack_top = saved_stack_top;
		return -1;
	}

	/* return success */
	return 0;
}

/*
 * assign initial values to the global variables whose desired initial
 * values are not nil or zero. some are handled in prepare_globals () and
 * update_fnr().
 */
static int defaultify_globals (hawk_rtx_t* rtx)
{
	struct gtab_t
	{
		int idx;
		const hawk_ooch_t* str[2];
	};
	static struct gtab_t gtab[9] =
	{
		{ HAWK_GBL_CONVFMT,    { DEFAULT_CONVFMT, DEFAULT_CONVFMT  } },
		{ HAWK_GBL_FILENAME,   { HAWK_NULL,       HAWK_NULL        } },
		{ HAWK_GBL_FS,         { DEFAULT_FS,      DEFAULT_FS       } },
		{ HAWK_GBL_OFILENAME,  { HAWK_NULL,       HAWK_NULL        } },
		{ HAWK_GBL_OFMT,       { DEFAULT_OFMT,    DEFAULT_OFMT     } },
		{ HAWK_GBL_OFS,        { DEFAULT_OFS,     DEFAULT_OFS      } },
		{ HAWK_GBL_ORS,        { DEFAULT_ORS,     DEFAULT_ORS_CRLF } },
		{ HAWK_GBL_SCRIPTNAME, { HAWK_NULL,       HAWK_NULL        } },
		{ HAWK_GBL_SUBSEP,     { DEFAULT_SUBSEP,  DEFAULT_SUBSEP   } }
	};

	hawk_val_t* tmp;
	hawk_oow_t i, j;
	int stridx;

	stridx = (rtx->hawk->opt.trait & HAWK_CRLF)? 1: 0;
	for (i = 0; i < HAWK_COUNTOF(gtab); i++)
	{
		if (gtab[i].str[stridx] == HAWK_NULL || gtab[i].str[stridx][0] == HAWK_T('\0'))
		{
			tmp = hawk_val_zls;
		}
		else
		{
			tmp = hawk_rtx_makestrvalwithoocstr(rtx, gtab[i].str[stridx]);
			if (tmp == HAWK_NULL) return -1;
		}

		hawk_rtx_refupval(rtx, tmp);

		HAWK_ASSERT(HAWK_RTX_STACK_GBL(rtx,gtab[i].idx) == hawk_val_nil);

		if (hawk_rtx_setgbl(rtx, gtab[i].idx, tmp) == -1)
		{
			for (j = 0; j < i; j++)
			{
				hawk_rtx_setgbl(rtx, gtab[i].idx, hawk_val_nil);
			}

			hawk_rtx_refdownval(rtx, tmp);
			return -1;
		}

		hawk_rtx_refdownval(rtx, tmp);
	}

	return 0;
}

static void refdown_globals (hawk_rtx_t* rtx, int pop)
{
	hawk_oow_t ngbls;

	ngbls = rtx->hawk->tree.ngbls;
	while (ngbls > 0)
	{
		--ngbls;
		hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_GBL(rtx,ngbls));
		if (pop) HAWK_RTX_STACK_POP (rtx);
		else HAWK_RTX_STACK_GBL(rtx,ngbls) = hawk_val_nil;
	}
}

static int init_globals (hawk_rtx_t* rtx)
{
	/* the stack must be clean when this function is invoked */
	HAWK_ASSERT(rtx->stack_base == 0);
	HAWK_ASSERT(rtx->stack_top == 0);

	if (prepare_globals(rtx) <= -1) return -1;
	if (update_fnr(rtx, 0, 0) <= -1 || defaultify_globals(rtx) <= -1) goto oops;
	return 0;

oops:
	refdown_globals(rtx, 1);
	return -1;
}

struct capture_retval_data_t
{
	hawk_rtx_t* rtx;
	hawk_val_t* val;
};

static void capture_retval_on_exit (void* arg)
{
	struct capture_retval_data_t* data;

	data = (struct capture_retval_data_t*)arg;
	data->val = HAWK_RTX_STACK_RETVAL(data->rtx);
	hawk_rtx_refupval(data->rtx, data->val);
}

static hawk_val_t* run_bpae_loop (hawk_rtx_t* rtx)
{
	hawk_nde_t* nde;
	hawk_oow_t nargs, i;
	hawk_val_t* retv;
	int ret = 0;

	/* set nargs to zero */
	nargs = 0;
	HAWK_RTX_STACK_NARGS(rtx) = (void*)nargs;

	/* execute the BEGIN block */
	for (nde = rtx->hawk->tree.begin;
	     ret == 0 && nde != HAWK_NULL && rtx->exit_level < EXIT_GLOBAL;
	     nde = nde->next)
	{
		hawk_nde_blk_t* blk;

		blk = (hawk_nde_blk_t*)nde;
		HAWK_ASSERT(blk->type == HAWK_NDE_BLK);

		rtx->active_block = blk;
		rtx->exit_level = EXIT_NONE;
		if (run_block(rtx, blk) <= -1) ret = -1;
	}

	if (ret <= -1 && hawk_rtx_geterrnum(rtx) == HAWK_ENOERR)
	{
		/* an error is returned with no error number set.
		 * this trait is used by eval_expression() to
		 * abort the evaluation when exit() is executed
		 * during function evaluation */
		ret = 0;
		CLRERR (rtx); /* clear it just in case */
	}

	/* run pattern block loops */
	if (ret == 0 &&
	    (rtx->hawk->tree.chain != HAWK_NULL ||
	     rtx->hawk->tree.end != HAWK_NULL) &&
	     rtx->exit_level < EXIT_GLOBAL)
	{
		if (run_pblocks(rtx) <= -1) ret = -1;
	}

	if (ret <= -1 && hawk_rtx_geterrnum(rtx) == HAWK_ENOERR)
	{
		/* an error is returned with no error number set.
		 * this trait is used by eval_expression() to
		 * abort the evaluation when exit() is executed
		 * during function evaluation */
		ret = 0;
		CLRERR (rtx); /* clear it just in case */
	}

	/* execute END blocks. the first END block is executed if the
	 * program is not explicitly aborted with hawk_rtx_halt().*/
	for (nde = rtx->hawk->tree.end;
	     ret == 0 && nde != HAWK_NULL && rtx->exit_level < EXIT_ABORT;
	     nde = nde->next)
	{
		hawk_nde_blk_t* blk;

		blk = (hawk_nde_blk_t*)nde;
		HAWK_ASSERT(blk->type == HAWK_NDE_BLK);

		rtx->active_block = blk;
		rtx->exit_level = EXIT_NONE;
		if (run_block(rtx, blk) <= -1) ret = -1;
		else if (rtx->exit_level >= EXIT_GLOBAL)
		{
			/* once exit is called inside one of END blocks,
			 * subsequent END blocks must not be executed */
			break;
		}
	}

	if (ret <= -1 && hawk_rtx_geterrnum(rtx) == HAWK_ENOERR)
	{
		/* an error is returned with no error number set.
		 * this trait is used by eval_expression() to
		 * abort the evaluation when exit() is executed
		 * during function evaluation */
		ret = 0;
		CLRERR (rtx); /* clear it just in case */
	}

	/* derefrence all arguments. however, there should be no arguments
	 * pushed to the stack as asserted below. we didn't push any arguments
	 * for BEGIN/pattern action/END block execution.*/
	nargs = (hawk_oow_t)HAWK_RTX_STACK_NARGS(rtx);
	HAWK_ASSERT(nargs == 0);
	for (i = 0; i < nargs; i++)
		hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_ARG(rtx,i));

	/* get the return value in the current stack frame */
	retv = HAWK_RTX_STACK_RETVAL(rtx);

	if (ret <= -1)
	{
		/* end the life of the global return value upon error */
		hawk_rtx_refdownval(rtx, retv);
		retv = HAWK_NULL;
	}

	return retv;
}

/* start the BEGIN-pattern block-END loop */
hawk_val_t* hawk_rtx_loop (hawk_rtx_t* rtx)
{
	hawk_val_t* retv = HAWK_NULL;
	hawk_oow_t saved_stack_top;

	rtx->exit_level = EXIT_NONE;

	/* make a new stack frame */

	if (HAWK_UNLIKELY(HAWK_RTX_STACK_AVAIL(rtx) < 4))
	{
		/* restore the stack top in a cheesy(?) way.
		 * it is ok to do so as the values pushed are
		 * nils and binary numbers. */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_ESTACK);
		return HAWK_NULL;
	}

	saved_stack_top = rtx->stack_top; 	/* remember the current stack top */
	HAWK_RTX_STACK_PUSH(rtx, (void*)rtx->stack_base); /* push the current stack base */
	HAWK_RTX_STACK_PUSH(rtx, (void*)saved_stack_top); /* push the current stack top before push the current stack base */
	HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil); /* secure space for a return value */
	HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil); /* secure space for HAWK_RTX_STACK_NARGS */

	/* enter the new stack frame */
	rtx->stack_base = saved_stack_top; /* let the stack top remembered be the base of a new stack frame */

	/* run the BEGIN/pattern-action/END loop */
	retv = run_bpae_loop(rtx);

	/* exit the stack frame */
	HAWK_ASSERT((rtx->stack_top - rtx->stack_base) == 4); /* at this point, the current stack frame should have the 4 entries pushed above */
	rtx->stack_top = (hawk_oow_t)rtx->stack[rtx->stack_base + 1];
	rtx->stack_base = (hawk_oow_t)rtx->stack[rtx->stack_base + 0];

	/* reset the exit level */
	rtx->exit_level = EXIT_NONE;

	/* flush all buffered io data */
	hawk_rtx_flushallios (rtx);

	return retv;
}

hawk_val_t* hawk_rtx_execwithucstrarr (hawk_rtx_t* rtx, const hawk_uch_t* args[], hawk_oow_t nargs)
{
	hawk_val_t* v;

	v = (rtx->hawk->parse.pragma.entry[0] != '\0')?
		hawk_rtx_callwithooucstrarr(rtx, rtx->hawk->parse.pragma.entry, args, nargs):
		hawk_rtx_loop(rtx);

#if defined(HAWK_ENABLE_GC)
	/* i assume this function is a usual hawk program starter.
	 * call garbage collection after a whole program finishes */
	hawk_rtx_gc(rtx, HAWK_RTX_GC_GEN_FULL);
#endif

	return v;
}

hawk_val_t* hawk_rtx_execwithbcstrarr (hawk_rtx_t* rtx, const hawk_bch_t* args[], hawk_oow_t nargs)
{
	hawk_val_t* v;

	v = (rtx->hawk->parse.pragma.entry[0] != '\0')?
		hawk_rtx_callwithoobcstrarr(rtx, rtx->hawk->parse.pragma.entry, args, nargs):
		hawk_rtx_loop(rtx);

#if defined(HAWK_ENABLE_GC)
	/* i assume this function is a usual hawk program starter.
	 * call garbage collection after a whole program finishes */
	hawk_rtx_gc(rtx, HAWK_RTX_GC_GEN_FULL);
#endif

	return v;
}

/* find an AWK function by name */
static hawk_fun_t* find_fun (hawk_rtx_t* rtx, const hawk_ooch_t* name)
{
	hawk_htb_pair_t* pair;

	pair = hawk_htb_search(rtx->hawk->tree.funs, name, hawk_count_oocstr(name));
	if (!pair)
	{
		hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EFUNNF, HAWK_T("unable to find function '%js'"), name);
		return HAWK_NULL;
	}

	return (hawk_fun_t*)HAWK_HTB_VPTR(pair);
}

hawk_fun_t* hawk_rtx_findfunwithbcstr (hawk_rtx_t* rtx, const hawk_bch_t* name)
{
#if defined(HAWK_OOCH_IS_BCH)
	return find_fun(rtx, name);
#else
	hawk_ucs_t wcs;
	hawk_fun_t* fun;
	wcs.ptr = hawk_rtx_dupbtoucstr(rtx, name, &wcs.len, 0);
	if (!wcs.ptr) return HAWK_NULL;
	fun = find_fun(rtx, wcs.ptr);
	hawk_rtx_freemem(rtx, wcs.ptr);
	return fun;
#endif
}

hawk_fun_t* hawk_rtx_findfunwithucstr (hawk_rtx_t* rtx, const hawk_uch_t* name)
{
#if defined(HAWK_OOCH_IS_BCH)
	hawk_bcs_t mbs;
	hawk_fun_t* fun;
	mbs.ptr = hawk_rtx_duputobcstr(rtx, name, &mbs.len);
	if (!mbs.ptr) return HAWK_NULL;
	fun = find_fun(rtx, mbs.ptr);
	hawk_rtx_freemem(rtx, mbs.ptr);
	return fun;
#else
	return find_fun(rtx, name);
#endif
}

/* call an AWK function by the function structure */
hawk_val_t* hawk_rtx_callfun (hawk_rtx_t* rtx, hawk_fun_t* fun, hawk_val_t* args[], hawk_oow_t nargs)
{
	struct capture_retval_data_t crdata;
	hawk_val_t* v;
	struct pafv_t pafv/*= { args, nargs }*/;
	hawk_nde_fncall_t call;

	HAWK_ASSERT(fun != HAWK_NULL);

	pafv.args = args;
	pafv.nargs = nargs;
	pafv.argspec = fun->argspec;
	pafv.argspeclen = fun->argspeclen;

	if (HAWK_UNLIKELY(rtx->exit_level >= EXIT_GLOBAL))
	{
		/* cannot call the function again when exit() is called
		 * in an AWK program or hawk_rtx_halt() is invoked */
		hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EPERM, HAWK_T("now allowed to call '%.*js' after exit"), fun->name.len, fun->name.ptr);
		return HAWK_NULL;
	}
	/*rtx->exit_level = EXIT_NONE;*/

#if 0
	if (fun->argspec)
	{
		/* this function contains pass-by-reference parameters.
		 * i don't support the call here as it requires variables */
		hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EPERM, HAWK_T("not allowed to call '%.*js' with pass-by-reference parameters"), fun->name.len, fun->name.ptr);
		return HAWK_NULL;
	}
#endif

	/* forge a fake node containing a function call */
	HAWK_MEMSET(&call, 0, HAWK_SIZEOF(call));
	call.type = HAWK_NDE_FNCALL_FUN;
	call.u.fun.name = fun->name;
	call.nargs = nargs;
	/* keep HAWK_NULL in call.args so that hawk_rtx_evalcall() knows it's a fake call structure */

	/* check if the number of arguments given is more than expected */
	if (nargs > fun->nargs && !fun->variadic)
	{
		/* TODO: is this correct? what if i want to
		 *       allow arbitrary numbers of arguments? */
		hawk_rtx_seterrfmt(rtx, HAWK_NULL, HAWK_EARGTM, HAWK_T("too many arguments to '%.*js'"), fun->name.len, fun->name.ptr);
		return HAWK_NULL;
	}

	/* now that the function is found and ok, let's execute it */

	crdata.rtx = rtx;
	crdata.val = HAWK_NULL;

	v = hawk_rtx_evalcall(rtx, &call, fun, push_arg_from_vals, (void*)&pafv, capture_retval_on_exit, &crdata);

	if (HAWK_UNLIKELY(!v))
	{
		/* an error occurred. let's check if it is caused by exit().
		 * if so, the return value should have been captured into
		 * crdata.val. */
		if (crdata.val) v = crdata.val; /* yet it is */
	}
	else
	{
		/* the return value captured in termination by exit()
		 * is reference-counted up in capture_retval_on_exit().
		 * let's do the same thing for the return value normally
		 * returned. */
		hawk_rtx_refupval(rtx, v);
	}

	/* flush all buffered io data */
	hawk_rtx_flushallios (rtx);

	/* return the return value with its reference count at least 1.
	 * the caller of this function should count down its reference. */
	return v;
}

/* call an AWK function by name */
hawk_val_t* hawk_rtx_callwithucstr (hawk_rtx_t* rtx, const hawk_uch_t* name, hawk_val_t* args[], hawk_oow_t nargs)
{
	hawk_fun_t* fun;

	fun = hawk_rtx_findfunwithucstr(rtx, name);
	if (HAWK_UNLIKELY(!fun)) return HAWK_NULL;

	return hawk_rtx_callfun(rtx, fun, args, nargs);
}

hawk_val_t* hawk_rtx_callwithbcstr (hawk_rtx_t* rtx, const hawk_bch_t* name, hawk_val_t* args[], hawk_oow_t nargs)
{
	hawk_fun_t* fun;

	fun = hawk_rtx_findfunwithbcstr(rtx, name);
	if (!fun) return HAWK_NULL;

	return hawk_rtx_callfun(rtx, fun, args, nargs);
}

hawk_val_t* hawk_rtx_callwithucstrarr (hawk_rtx_t* rtx, const hawk_uch_t* name, const hawk_uch_t* args[], hawk_oow_t nargs)
{
	hawk_oow_t i;
	hawk_val_t** v, * ret;

	v = hawk_rtx_allocmem(rtx, HAWK_SIZEOF(*v) * nargs);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	for (i = 0; i < nargs; i++)
	{
		v[i] = hawk_rtx_makestrvalwithucstr(rtx, args[i]);
		if (HAWK_UNLIKELY(!v[i]))
		{
			ret = HAWK_NULL;
			goto oops;
		}

		hawk_rtx_refupval(rtx, v[i]);
	}

	ret = hawk_rtx_callwithucstr(rtx, name, v, nargs);

oops:
	while (i > 0)
	{
		hawk_rtx_refdownval(rtx, v[--i]);
	}
	hawk_rtx_freemem(rtx, v);
	return ret;
}

hawk_val_t* hawk_rtx_callwithbcstrarr (hawk_rtx_t* rtx, const hawk_bch_t* name, const hawk_bch_t* args[], hawk_oow_t nargs)
{
	hawk_oow_t i;
	hawk_val_t** v, * ret;

	v = hawk_rtx_allocmem(rtx, HAWK_SIZEOF(*v) * nargs);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	for (i = 0; i < nargs; i++)
	{
		v[i] = hawk_rtx_makestrvalwithbcstr(rtx, args[i]);
		if (HAWK_UNLIKELY(!v[i]))
		{
			ret = HAWK_NULL;
			goto oops;
		}

		hawk_rtx_refupval(rtx, v[i]);
	}

	ret = hawk_rtx_callwithbcstr(rtx, name, v, nargs);

oops:
	while (i > 0)
	{
		hawk_rtx_refdownval(rtx, v[--i]);
	}
	hawk_rtx_freemem(rtx, v);
	return ret;
}

hawk_val_t* hawk_rtx_callwithooucstrarr (hawk_rtx_t* rtx, const hawk_ooch_t* name, const hawk_uch_t* args[], hawk_oow_t nargs)
{
	hawk_oow_t i;
	hawk_val_t** v, * ret;

	v = hawk_rtx_allocmem(rtx, HAWK_SIZEOF(*v) * nargs);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	for (i = 0; i < nargs; i++)
	{
		v[i] = hawk_rtx_makestrvalwithucstr(rtx, args[i]);
		if (HAWK_UNLIKELY(!v[i]))
		{
			ret = HAWK_NULL;
			goto oops;
		}

		hawk_rtx_refupval(rtx, v[i]);
	}

	ret = hawk_rtx_callwithoocstr(rtx, name, v, nargs);

oops:
	while (i > 0)
	{
		hawk_rtx_refdownval(rtx, v[--i]);
	}
	hawk_rtx_freemem(rtx, v);
	return ret;
}

hawk_val_t* hawk_rtx_callwithoobcstrarr (hawk_rtx_t* rtx, const hawk_ooch_t* name, const hawk_bch_t* args[], hawk_oow_t nargs)
{
	hawk_oow_t i;
	hawk_val_t** v, * ret;

	v = hawk_rtx_allocmem(rtx, HAWK_SIZEOF(*v) * nargs);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	for (i = 0; i < nargs; i++)
	{
		v[i] = hawk_rtx_makestrvalwithbcstr(rtx, args[i]);
		if (HAWK_UNLIKELY(!v[i]))
		{
			ret = HAWK_NULL;
			goto oops;
		}

		hawk_rtx_refupval(rtx, v[i]);
	}

	ret = hawk_rtx_callwithoocstr(rtx, name, v, nargs);

oops:
	while (i > 0)
	{
		hawk_rtx_refdownval(rtx, v[--i]);
	}
	hawk_rtx_freemem(rtx, v);
	return ret;
}

static int run_pblocks (hawk_rtx_t* rtx)
{
	int n;

#define ADJUST_ERROR(rtx) \
	if (rtx->hawk->tree.chain != HAWK_NULL) \
	{ \
		if (rtx->hawk->tree.chain->pattern != HAWK_NULL) \
			ADJERR_LOC(rtx, &rtx->hawk->tree.chain->pattern->loc); \
		else if (rtx->hawk->tree.chain->action != HAWK_NULL) \
			ADJERR_LOC(rtx, &rtx->hawk->tree.chain->action->loc); \
	} \
	else if (rtx->hawk->tree.end != HAWK_NULL) \
	{ \
		ADJERR_LOC(rtx, &rtx->hawk->tree.end->loc); \
	}

	rtx->inrec.buf_pos = 0;
	rtx->inrec.buf_len = 0;
	rtx->inrec.eof = 0;

	/* run each pattern block */
	while (rtx->exit_level < EXIT_GLOBAL)
	{
		rtx->exit_level = EXIT_NONE;

		n = read_record(rtx);
		if (n <= -1)
		{
			ADJUST_ERROR (rtx);
			return -1; /* error */
		}
		if (n == 0) break; /* end of input */

		if (rtx->hawk->tree.chain)
		{
			if (run_pblock_chain(rtx, rtx->hawk->tree.chain) <= -1) return -1;
		}
	}

#undef ADJUST_ERROR
	return 0;
}

static int run_pblock_chain (hawk_rtx_t* rtx, hawk_chain_t* chain)
{
	hawk_oow_t bno = 0;

	while (rtx->exit_level < EXIT_GLOBAL && chain)
	{
		if (rtx->exit_level == EXIT_NEXT)
		{
			rtx->exit_level = EXIT_NONE;
			break;
		}

		if (run_pblock(rtx, chain, bno) <= -1) return -1;

		chain = chain->next;
		bno++;
	}

	return 0;
}

static int run_pblock (hawk_rtx_t* rtx, hawk_chain_t* cha, hawk_oow_t bno)
{
	hawk_nde_t* ptn;
	hawk_nde_blk_t* blk;

	ptn = cha->pattern;
	blk = (hawk_nde_blk_t*)cha->action;

	if (!ptn)
	{
		/* just execute the block */
		rtx->active_block = blk;
		if (run_block(rtx, blk) <= -1) return -1;
	}
	else
	{
		if (!ptn->next)
		{
			/* pattern { ... } */
			hawk_val_t* v1;

			v1 = eval_expression(rtx, ptn);
			if (HAWK_UNLIKELY(!v1)) return -1;

			hawk_rtx_refupval(rtx, v1);

			if (hawk_rtx_valtobool(rtx, v1))
			{
				rtx->active_block = blk;
				if (run_block(rtx, blk) <= -1)
				{
					hawk_rtx_refdownval(rtx, v1);
					return -1;
				}
			}

			hawk_rtx_refdownval(rtx, v1);
		}
		else
		{
			/* pattern, pattern { ... } */
			HAWK_ASSERT(ptn->next->next == HAWK_NULL);
			HAWK_ASSERT(rtx->pattern_range_state != HAWK_NULL);

			if (rtx->pattern_range_state[bno] == 0)
			{
				hawk_val_t* v1;

				v1 = eval_expression(rtx, ptn);
				if (HAWK_UNLIKELY(!v1)) return -1;
				hawk_rtx_refupval(rtx, v1);
				rtx->pattern_range_state[bno] = hawk_rtx_valtobool(rtx, v1);
				hawk_rtx_refdownval(rtx, v1);
			}

			if (rtx->pattern_range_state[bno] == 1)
			{
				hawk_val_t* v2;

				v2 = eval_expression(rtx, ptn->next);
				if (HAWK_UNLIKELY(!v2)) return -1;
				hawk_rtx_refupval(rtx, v2);
				rtx->pattern_range_state[bno] = !hawk_rtx_valtobool(rtx, v2);
				hawk_rtx_refdownval(rtx, v2);

				rtx->active_block = blk;
				if (run_block(rtx, blk) <= -1) return -1;
			}
		}
	}

	return 0;
}

static HAWK_INLINE int run_block0 (hawk_rtx_t* rtx, hawk_nde_blk_t* nde)
{
	hawk_nde_t* p;
	/*hawk_oow_t saved_stack_top;*/
	int n = 0;

	if (nde == HAWK_NULL)
	{
		/* blockless pattern - execute print $0*/
		hawk_rtx_refupval(rtx, rtx->inrec.d0);

		n = hawk_rtx_writeiostr(rtx, HAWK_OUT_CONSOLE, HAWK_T(""), HAWK_OOECS_PTR(&rtx->inrec.line), HAWK_OOECS_LEN(&rtx->inrec.line));
		if (n <= -1)
		{
			hawk_rtx_refdownval(rtx, rtx->inrec.d0);
			/* ADJERR_LOC(rtx, &nde->loc); - can't adjust error location as nde is null */
			return -1;
		}

		n = hawk_rtx_writeiostr(rtx, HAWK_OUT_CONSOLE, HAWK_T(""), rtx->gbl.ors.ptr, rtx->gbl.ors.len);
		if (n <= -1)
		{
			hawk_rtx_refdownval(rtx, rtx->inrec.d0);
			/* ADJERR_LOC(rtx, &nde->loc); - can't adjust error location as nde is null */
			return -1;
		}

		hawk_rtx_refdownval(rtx, rtx->inrec.d0);
		return 0;
	}

	HAWK_ASSERT(nde->type == HAWK_NDE_BLK);
	/*saved_stack_top = rtx->stack_top;*/

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "securing space for local variables nlcls = %d\n", (int)nde->nlcls);
#endif

	if (nde->nlcls > 0)
	{
		hawk_oow_t tmp = nde->nlcls;

		/* ensure sufficient space for local variables */
		if (HAWK_UNLIKELY(HAWK_RTX_STACK_AVAIL(rtx) < tmp))
		{
			hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ESTACK);
			return -1;
		}

		do
		{
			--tmp;
			HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil);
			/* refupval is not required for hawk_val_nil */
		}
		while (tmp > 0);
	}
	else if (nde->nlcls != nde->org_nlcls)
	{
		/* this part can be reached if:
		 *   - the block is nested
		 *   - the block declares local variables
		 *   - the local variables have been migrated to the outer-most block by the parser.
		 *
		 * if the parser skips migrations, this part isn't reached but the normal push/pop
		 * will take place for a nested block with local variable declaration.
		 */
		hawk_oow_t tmp, end;

		/* when the outer-most block has been entered, the space large enough for all local
		 * variables defined has been secured. nullify part of the stack to initialze local
		 * variables defined for a nested block */
		end = nde->outer_nlcls + nde->org_nlcls;
		for (tmp = nde->outer_nlcls; tmp < end; tmp++)
		{
			hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_LCL(rtx, tmp));
			HAWK_RTX_STACK_LCL(rtx, tmp) = hawk_val_nil;
		}
	}

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "executing block statements\n");
#endif

	p = nde->body;
	while (p && rtx->exit_level == EXIT_NONE)
	{
		if (HAWK_UNLIKELY(run_statement(rtx, p) <= -1))
		{
			n = -1;
			break;
		}
		p = p->next;
	}

	/* pop off local variables */
#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "popping off local variables\n");
#endif

	if (nde->nlcls > 0)
	{
		hawk_oow_t tmp = nde->nlcls;

		do
		{
			--tmp;
			hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_LCL(rtx, tmp));
			HAWK_RTX_STACK_POP (rtx);
		}
		while (tmp > 0);
	}

	return n;
}

static int run_block (hawk_rtx_t* rtx, hawk_nde_blk_t* nde)
{
	int n;

	if (rtx->hawk->opt.depth.s.block_run > 0 &&
	    rtx->depth.block >= rtx->hawk->opt.depth.s.block_run)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EBLKNST);
		return -1;
	}

	rtx->depth.block++;
	n = run_block0(rtx, nde);
	rtx->depth.block--;

	return n;
}

#define ON_STATEMENT(rtx,nde) do { \
	hawk_rtx_ecb_t* ecb, * ecb_next; \
	if ((rtx)->hawk->haltall) (rtx)->exit_level = EXIT_ABORT; \
	for (ecb = (rtx)->ecb; ecb != (hawk_rtx_ecb_t*)(rtx); ecb = ecb_next) { \
		ecb_next = ecb->next; \
		if (ecb->stmt) ecb->stmt(rtx, nde, ecb->ctx);  \
	} \
} while(0)

static int run_statement (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	int xret;
	hawk_val_t* tmp;

	ON_STATEMENT(rtx, nde);

	switch (nde->type)
	{
		case HAWK_NDE_NULL:
			/* do nothing */
			xret = 0;
			break;

		case HAWK_NDE_BLK:
			xret = run_block(rtx, (hawk_nde_blk_t*)nde);
			break;

		case HAWK_NDE_IF:
			xret = run_if(rtx, (hawk_nde_if_t*)nde);
			break;

		case HAWK_NDE_SWITCH:
			xret = run_switch(rtx, (hawk_nde_switch_t*)nde);
			break;

		case HAWK_NDE_CASE:
			/* this must never happen - run_swtich must handle this part */
			HAWK_ASSERT(!"should never happen - HAWK_NDE_CASE must not be fed to run_statement");
			hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EINTERN);
			xret = -1;
			break;

		case HAWK_NDE_WHILE:
		case HAWK_NDE_DOWHILE:
			xret = run_while(rtx, (hawk_nde_while_t*)nde);
			break;

		case HAWK_NDE_FOR:
			xret = run_for(rtx, (hawk_nde_for_t*)nde);
			break;

		case HAWK_NDE_FORIN:
			xret = run_forin(rtx, (hawk_nde_forin_t*)nde);
			break;

		case HAWK_NDE_BREAK:
			xret = run_break(rtx, (hawk_nde_break_t*)nde);
			break;

		case HAWK_NDE_CONTINUE:
			xret = run_continue(rtx, (hawk_nde_continue_t*)nde);
			break;

		case HAWK_NDE_RETURN:
			xret = run_return(rtx, (hawk_nde_return_t*)nde);
			break;

		case HAWK_NDE_EXIT:
			xret = run_exit(rtx, (hawk_nde_exit_t*)nde);
			break;

		case HAWK_NDE_NEXT:
			xret = run_next(rtx, (hawk_nde_next_t*)nde);
			break;

		case HAWK_NDE_NEXTFILE:
			xret = run_nextfile(rtx, (hawk_nde_nextfile_t*)nde);
			break;

		case HAWK_NDE_DELETE:
			xret = run_delete(rtx, (hawk_nde_delete_t*)nde);
			break;

		case HAWK_NDE_RESET:
			xret = run_reset(rtx, (hawk_nde_reset_t*)nde);
			break;

		case HAWK_NDE_PRINT:
			if (rtx->hawk->opt.trait & HAWK_TOLERANT) goto __fallback__;
			xret = run_print(rtx, (hawk_nde_print_t*)nde);
			break;

		case HAWK_NDE_PRINTF:
			if (rtx->hawk->opt.trait & HAWK_TOLERANT) goto __fallback__;
			xret = run_printf(rtx, (hawk_nde_print_t*)nde);
			break;

		default:
		__fallback__:
			tmp = eval_expression(rtx, nde);
			if (HAWK_UNLIKELY(!tmp)) xret = -1;
			else
			{
				/* destroy the value if not referenced */
				hawk_rtx_refupval(rtx, tmp);
				hawk_rtx_refdownval(rtx, tmp);
				xret = 0;
			}
			break;
	}

	return xret;
}

static int run_if (hawk_rtx_t* rtx, hawk_nde_if_t* nde)
{
	hawk_val_t* test;
	int n = 0;

	/* the test expression for the if statement cannot have
	 * chained expressions. this should not be allowed by the
	 * parser first of all */
	HAWK_ASSERT(nde->test->next == HAWK_NULL);

	test = eval_expression(rtx, nde->test);
	if (HAWK_UNLIKELY(!test)) return -1;

	hawk_rtx_refupval(rtx, test);
	if (hawk_rtx_valtobool(rtx, test))
	{
		n = run_statement(rtx, nde->then_part);
	}
	else if (nde->else_part)
	{
		n = run_statement(rtx, nde->else_part);
	}

	hawk_rtx_refdownval(rtx, test); /* TODO: is this correct?*/
	return n;
}

static int run_switch (hawk_rtx_t* rtx, hawk_nde_switch_t* nde)
{
	hawk_val_t* test;
	hawk_val_t* v;
	hawk_nde_case_t* case_part;
	hawk_nde_case_t* default_part;
	int eval_true = 0;
	int ret = 0;

	/* the expression for the switch statement cannot have
	 * chained expressions. this should not be allowed by the
	 * parser first of all */
	HAWK_ASSERT(nde->type == HAWK_NDE_SWITCH);
	HAWK_ASSERT(nde->test->next == HAWK_NULL);

	test = eval_expression(rtx, nde->test);
	if (HAWK_UNLIKELY(!test)) return -1;

	hawk_rtx_refupval(rtx, test);

	case_part = (hawk_nde_case_t*)nde->case_part;
	default_part = (hawk_nde_case_t*)nde->default_part;
	HAWK_ASSERT(case_part->type == HAWK_NDE_CASE);
	HAWK_ASSERT(default_part->type == HAWK_NDE_CASE);

	while (case_part)
	{
		int x;

		if (eval_true) goto skip_eval;
		if (case_part->val) // skip the default part.
		{
			v = eval_expression(rtx, case_part->val);
			hawk_rtx_refupval(rtx, v);
			x = hawk_rtx_cmpval(rtx, test, v, &ret);
			hawk_rtx_refdownval(rtx, v);
			if (x <= -1)
			{
				hawk_rtx_refdownval(rtx, test);
				return -1;
			}
			if (ret == 0)
			{
			skip_eval:
				eval_true = 1;
				if (case_part->action)
				{
					hawk_nde_t* action;

					action = case_part->action;
					do
					{
						if (run_statement(rtx, action) <= -1)
						{
							ret = -1;
							goto done;
						}

						if (rtx->exit_level == EXIT_BREAK) /* if 'break' has been executed */
						{
							rtx->exit_level = EXIT_NONE;
							goto done;
						}
						else if (rtx->exit_level != EXIT_NONE) goto done;

						action = action->next;
					}
					while(action);

				}
			}
		}

		case_part = (hawk_nde_case_t*)case_part->next;
	}

	if (!eval_true && default_part)
	{
		case_part = default_part;
		do
		{
			if (case_part->action)
			{
				hawk_nde_t* action;

				action = case_part->action;
				do
				{
					if (run_statement(rtx, action) <= -1)
					{
						ret = -1;
						goto done;
					}

					if (rtx->exit_level == EXIT_BREAK) /* if 'break' has been executed */
					{
						rtx->exit_level = EXIT_NONE;
						goto done;
					}
					else if (rtx->exit_level != EXIT_NONE) goto done;

					action = action->next;
				}
				while (action);
			}
			case_part = (hawk_nde_case_t*)case_part->next;
		}
		while (case_part);
	}

done:
	hawk_rtx_refdownval(rtx, test);
	return ret;
}

static int run_while (hawk_rtx_t* rtx, hawk_nde_while_t* nde)
{
	hawk_val_t* test;

	if (nde->type == HAWK_NDE_WHILE)
	{
		/* no chained expressions are allowed for the test
		 * expression of the while statement */
		HAWK_ASSERT(nde->test->next == HAWK_NULL);

		while (1)
		{
			ON_STATEMENT(rtx, nde->test);

			test = eval_expression(rtx, nde->test);
			if (HAWK_UNLIKELY(!test)) return -1;

			hawk_rtx_refupval(rtx, test);

			if (hawk_rtx_valtobool(rtx, test))
			{
				if (run_statement(rtx,nde->body) <= -1)
				{
					hawk_rtx_refdownval(rtx, test);
					return -1;
				}
			}
			else
			{
				hawk_rtx_refdownval(rtx, test);
				break;
			}

			hawk_rtx_refdownval(rtx, test);

			if (rtx->exit_level == EXIT_BREAK)
			{
				rtx->exit_level = EXIT_NONE;
				break;
			}
			else if (rtx->exit_level == EXIT_CONTINUE)
			{
				rtx->exit_level = EXIT_NONE;
			}
			else if (rtx->exit_level != EXIT_NONE) break;
		}
	}
	else if (nde->type == HAWK_NDE_DOWHILE)
	{
		/* no chained expressions are allowed for the test
		 * expression of the while statement */
		HAWK_ASSERT(nde->test->next == HAWK_NULL);

		do
		{
			if (run_statement(rtx,nde->body) <= -1) return -1;

			if (rtx->exit_level == EXIT_BREAK)
			{
				rtx->exit_level = EXIT_NONE;
				break;
			}
			else if (rtx->exit_level == EXIT_CONTINUE)
			{
				rtx->exit_level = EXIT_NONE;
			}
			else if (rtx->exit_level != EXIT_NONE) break;

			ON_STATEMENT(rtx, nde->test);

			test = eval_expression(rtx, nde->test);
			if (HAWK_UNLIKELY(!test)) return -1;

			hawk_rtx_refupval(rtx, test);

			if (!hawk_rtx_valtobool(rtx, test))
			{
				hawk_rtx_refdownval(rtx, test);
				break;
			}

			hawk_rtx_refdownval(rtx, test);
		}
		while (1);
	}

	return 0;
}

static int run_for (hawk_rtx_t* rtx, hawk_nde_for_t* nde)
{
	hawk_val_t* val;

	if (nde->init)
	{
		HAWK_ASSERT(nde->init->next == HAWK_NULL);

		ON_STATEMENT(rtx, nde->init);
		val = eval_expression(rtx,nde->init);
		if (HAWK_UNLIKELY(!val)) return -1;

		hawk_rtx_refupval(rtx, val);
		hawk_rtx_refdownval(rtx, val);
	}

	while (1)
	{
		if (nde->test)
		{
			hawk_val_t* test;

			/* no chained expressions for the test expression of
			 * the for statement are allowed */
			HAWK_ASSERT(nde->test->next == HAWK_NULL);

			ON_STATEMENT(rtx, nde->test);
			test = eval_expression(rtx, nde->test);
			if (HAWK_UNLIKELY(!test)) return -1;

			hawk_rtx_refupval(rtx, test);
			if (hawk_rtx_valtobool(rtx, test))
			{
				if (run_statement(rtx, nde->body) <= -1)
				{
					hawk_rtx_refdownval(rtx, test);
					return -1;
				}
			}
			else
			{
				hawk_rtx_refdownval(rtx, test);
				break;
			}

			hawk_rtx_refdownval(rtx, test);
		}
		else
		{
			if (run_statement(rtx,nde->body) <= -1) return -1;
		}

		if (rtx->exit_level == EXIT_BREAK)
		{
			rtx->exit_level = EXIT_NONE;
			break;
		}
		else if (rtx->exit_level == EXIT_CONTINUE)
		{
			rtx->exit_level = EXIT_NONE;
		}
		else if (rtx->exit_level != EXIT_NONE) break;

		if (nde->incr != HAWK_NULL)
		{
			HAWK_ASSERT(nde->incr->next == HAWK_NULL);

			ON_STATEMENT(rtx, nde->incr);
			val = eval_expression(rtx, nde->incr);
			if (HAWK_UNLIKELY(!val)) return -1;

			hawk_rtx_refupval(rtx, val);
			hawk_rtx_refdownval(rtx, val);
		}
	}

	return 0;
}

static int run_forin (hawk_rtx_t* rtx, hawk_nde_forin_t* nde)
{
	hawk_nde_exp_t* test;
	hawk_val_t* rv;
	hawk_val_type_t rvtype;

	int ret;

	test = (hawk_nde_exp_t*)nde->test;
	HAWK_ASSERT(test->type == HAWK_NDE_EXP_BIN && test->opcode == HAWK_BINOP_IN);

	/* chained expressions should not be allowed by the parser first of all */
	HAWK_ASSERT(test->right->next == HAWK_NULL);

	rv = eval_expression(rtx, test->right);
	if (HAWK_UNLIKELY(!rv)) return -1;

	ret = 0;

	hawk_rtx_refupval(rtx, rv);
	rvtype = HAWK_RTX_GETVALTYPE(rtx, rv);
	switch (rvtype)
	{
		case HAWK_VAL_NIL:
			/* just return without excuting the loop body */
			goto done1;

		case HAWK_VAL_MAP:
		{
			hawk_map_t* map;
			hawk_map_pair_t* pair;
			hawk_map_itr_t itr;
			hawk_oow_t old_forin_size, i;

			map = ((hawk_val_map_t*)rv)->map;

			old_forin_size = rtx->forin.size;
			if (rtx->forin.capa - rtx->forin.size < hawk_map_getsize(map))
			{
				hawk_val_t** tmp;
				hawk_oow_t newcapa;

				newcapa = rtx->forin.size + hawk_map_getsize(map);
				newcapa = HAWK_ALIGN_POW2(newcapa, 128);
				tmp = hawk_rtx_reallocmem(rtx, rtx->forin.ptr, newcapa * HAWK_SIZEOF(*tmp));
				if (HAWK_UNLIKELY(!tmp))
				{
					ADJERR_LOC(rtx, &test->left->loc);
					ret = -1;
					goto done2;
				}

				rtx->forin.ptr = tmp;
				rtx->forin.capa = newcapa;
			}

			/* take a snapshot of the keys first so that the actual pairs become mutation safe
			 * this proctection is needed in case the body contains destructive statements like 'delete' or '@reset'*/
			hawk_init_map_itr (&itr, 0);
			pair = hawk_map_getfirstpair(map, &itr);
			while (pair)
			{
				hawk_val_t* str;

				str = (hawk_val_t*)hawk_rtx_makenstrvalwithoochars(rtx, HAWK_HTB_KPTR(pair), HAWK_HTB_KLEN(pair));
				if (HAWK_UNLIKELY(!str))
				{
					ADJERR_LOC(rtx, &test->left->loc);
					ret = -1;
					goto done2;
				}

				rtx->forin.ptr[rtx->forin.size++] = str;
				hawk_rtx_refupval(rtx, str);

				pair = hawk_map_getnextpair(map, &itr);
			}

			/* iterate over the keys in the snapshot */
			for (i = old_forin_size;  i < rtx->forin.size; i++)
			{
				if (HAWK_UNLIKELY(!do_assignment(rtx, test->left, rtx->forin.ptr[i])) || HAWK_UNLIKELY(run_statement(rtx, nde->body) <= -1))
				{
					ret = -1;
					goto done2;
				}

				if (rtx->exit_level == EXIT_BREAK)
				{
					rtx->exit_level = EXIT_NONE;
					goto done2;
				}
				else if (rtx->exit_level == EXIT_CONTINUE)
				{
					rtx->exit_level = EXIT_NONE;
				}
				else if (rtx->exit_level != EXIT_NONE)
				{
					goto done2;
				}
			}

		done2:
			while (rtx->forin.size > old_forin_size)
				hawk_rtx_refdownval(rtx, rtx->forin.ptr[--rtx->forin.size]);

			break;
		}

		case HAWK_VAL_ARR:
		{
			hawk_arr_t* arr;
			hawk_oow_t arr_size;
			hawk_oow_t old_forin_size, i;

			arr = ((hawk_val_arr_t*)rv)->arr;
			arr_size = HAWK_ARR_SIZE(arr);

			old_forin_size = rtx->forin.size;
			if (rtx->forin.capa - rtx->forin.size < hawk_arr_getsize(arr))
			{
				hawk_val_t** tmp;
				hawk_oow_t newcapa;

				newcapa = rtx->forin.size + hawk_arr_getsize(arr);
				newcapa = HAWK_ALIGN_POW2(newcapa, 128);
				tmp = hawk_rtx_reallocmem(rtx, rtx->forin.ptr, newcapa * HAWK_SIZEOF(*tmp));
				if (HAWK_UNLIKELY(!tmp))
				{
					ADJERR_LOC(rtx, &test->left->loc);
					ret = -1;
					goto done3;
				}

				rtx->forin.ptr = tmp;
				rtx->forin.capa = newcapa;
			}

			/* take a snapshot of the keys first so that the actual pairs become mutation safe
			 * this proctection is needed in case the body contains destructive statements like 'delete' or '@reset'.
			 * besides, there can be empty slots. after a[1] = 20; a[9] = 30;,  a[2], a[3], etc are empty. */
			for  (i = 0; i < arr_size; i++)
			{
				if (HAWK_ARR_SLOT(arr, i))
				{
					hawk_val_t* tmp;

					tmp = (hawk_val_t*)hawk_rtx_makeintval(rtx, i);
					if (HAWK_UNLIKELY(!tmp))
					{
						ADJERR_LOC(rtx, &test->left->loc);
						ret = -1;
						goto done3;
					}

					rtx->forin.ptr[rtx->forin.size++] = tmp;
					hawk_rtx_refupval(rtx, tmp);
				}
			}

			/* iterate over the keys in the snapshot */
			for (i = old_forin_size;  i < rtx->forin.size; i++)
			{
				if (HAWK_UNLIKELY(!do_assignment(rtx, test->left, rtx->forin.ptr[i])) || HAWK_UNLIKELY(run_statement(rtx, nde->body) <= -1))
				{
					ret = -1;
					goto done3;
				}

				if (rtx->exit_level == EXIT_BREAK)
				{
					rtx->exit_level = EXIT_NONE;
					goto done3;
				}
				else if (rtx->exit_level == EXIT_CONTINUE)
				{
					rtx->exit_level = EXIT_NONE;
				}
				else if (rtx->exit_level != EXIT_NONE)
				{
					goto done3;
				}
			}

		done3:
			while (rtx->forin.size > old_forin_size)
				hawk_rtx_refdownval(rtx, rtx->forin.ptr[--rtx->forin.size]);

			break;
		}

		default:
			hawk_rtx_seterrnum(rtx, &test->right->loc, HAWK_EINROP);
			ret = -1;
			goto done1;
	}


done1:
	hawk_rtx_refdownval(rtx, rv);
	return ret;
}

static int run_break (hawk_rtx_t* rtx, hawk_nde_break_t* nde)
{
	rtx->exit_level = EXIT_BREAK;
	return 0;
}

static int run_continue (hawk_rtx_t* rtx, hawk_nde_continue_t* nde)
{
	rtx->exit_level = EXIT_CONTINUE;
	return 0;
}

static int run_return (hawk_rtx_t* rtx, hawk_nde_return_t* nde)
{
	if (nde->val)
	{
		hawk_val_t* val;

		/* chained expressions should not be allowed
		 * by the parser first of all */
		HAWK_ASSERT(nde->val->next == HAWK_NULL);

		val = eval_expression(rtx, nde->val);
		if (HAWK_UNLIKELY(!val)) return -1;

		if (!(rtx->hawk->opt.trait & HAWK_FLEXMAP))
		{
			hawk_val_type_t vtype = HAWK_RTX_GETVALTYPE(rtx, val);

			if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
			{
				/* cannot return a map */
				hawk_rtx_refupval(rtx, val);
				hawk_rtx_refdownval(rtx, val);
				hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ENONSCARET);
				return -1;
			}
		}

		hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_RETVAL(rtx));
		HAWK_RTX_STACK_RETVAL(rtx) = val;

		/* NOTE: see eval_call() for the trick */
		hawk_rtx_refupval(rtx, val);
	}

	rtx->exit_level = EXIT_FUNCTION;
	return 0;
}

static int run_exit (hawk_rtx_t* rtx, hawk_nde_exit_t* nde)
{
	if (nde->val)
	{
		hawk_val_t* val;

		/* chained expressions should not be allowed
		 * by the parser first of all */
		HAWK_ASSERT(nde->val->next == HAWK_NULL);

		val = eval_expression(rtx, nde->val);
		if (HAWK_UNLIKELY(!val)) return -1;

		hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_RETVAL_GBL(rtx));
		HAWK_RTX_STACK_RETVAL_GBL(rtx) = val; /* global return value */

		hawk_rtx_refupval(rtx, val);
	}

	rtx->exit_level = (nde->abort)? EXIT_ABORT: EXIT_GLOBAL;
	return 0;
}

static int run_next (hawk_rtx_t* rtx, hawk_nde_next_t* nde)
{
	/* the parser checks if next has been called in the begin/end
	 * block or whereever inappropriate. so the rtxtime doesn't
	 * check that explicitly */
	if  (rtx->active_block == (hawk_nde_blk_t*)rtx->hawk->tree.begin)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ERNEXTBEG);
		return -1;
	}
	else if (rtx->active_block == (hawk_nde_blk_t*)rtx->hawk->tree.end)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ERNEXTEND);
		return -1;
	}

	rtx->exit_level = EXIT_NEXT;
	return 0;
}

static int run_nextinfile (hawk_rtx_t* rtx, hawk_nde_nextfile_t* nde)
{
	int n;

	/* normal nextfile statement */
	if  (rtx->active_block == (hawk_nde_blk_t*)rtx->hawk->tree.begin)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ERNEXTFBEG);
		return -1;
	}
	else if (rtx->active_block == (hawk_nde_blk_t*)rtx->hawk->tree.end)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ERNEXTFEND);
		return -1;
	}

	n = hawk_rtx_nextio_read(rtx, HAWK_IN_CONSOLE, HAWK_T(""));
	if (n <= -1)
	{
		ADJERR_LOC(rtx, &nde->loc);
		return -1;
	}

	if (n == 0)
	{
		/* no more input console */
		rtx->exit_level = EXIT_GLOBAL;
		return 0;
	}

	/* FNR resets to 0, NR remains the same */
	if (update_fnr(rtx, 0, rtx->gbl.nr) <= -1)
	{
		ADJERR_LOC(rtx, &nde->loc);
		return -1;
	}

	rtx->exit_level = EXIT_NEXT;
	return 0;

}

static int run_nextoutfile (hawk_rtx_t* rtx, hawk_nde_nextfile_t* nde)
{
	int n;

	/* nextofile can be called from BEGIN and END block unlike nextfile */

	n = hawk_rtx_nextio_write(rtx, HAWK_OUT_CONSOLE, HAWK_T(""));
	if (n <= -1)
	{
		/* adjust the error line */
		ADJERR_LOC(rtx, &nde->loc);
		return -1;
	}

	if (n == 0)
	{
		/* should it terminate the program there is no more
		 * output console? no. there will just be no more console
		 * output */
		/*rtx->exit_level = EXIT_GLOBAL;*/
		return 0;
	}

	return 0;
}

static int run_nextfile (hawk_rtx_t* rtx, hawk_nde_nextfile_t* nde)
{
	return (nde->out)? run_nextoutfile(rtx, nde): run_nextinfile(rtx, nde);
}


static hawk_val_t* assign_newmapval_in_map (hawk_rtx_t* rtx, hawk_map_t* container, hawk_ooch_t* idxptr, hawk_oow_t idxlen)
{
	hawk_val_t* tmp;
	hawk_map_pair_t* pair;

	tmp = hawk_rtx_makemapval(rtx);
	if (HAWK_UNLIKELY(!tmp)) return HAWK_NULL;

	/* as this is the assignment, it needs to update the reference count of the target value. */
	hawk_rtx_refupval(rtx, tmp);
	pair = hawk_map_upsert(container, idxptr, idxlen, tmp, 0);
	if (HAWK_UNLIKELY(!pair))
	{
		hawk_rtx_refdownval(rtx, tmp);  /* decrement upon upsert() failure */
		return HAWK_NULL;
	}

	HAWK_ASSERT(tmp == HAWK_MAP_VPTR(pair));
	return tmp;
}

static hawk_val_t* assign_newarrval_in_arr (hawk_rtx_t* rtx, hawk_arr_t* container, hawk_ooi_t idx)
{
	hawk_val_t* tmp;

	tmp = hawk_rtx_makearrval(rtx, -1);
	if (HAWK_UNLIKELY(!tmp)) return HAWK_NULL;

	/* as this is the assignment, it needs to update the reference count of the target value. */
	hawk_rtx_refupval(rtx, tmp);
	if (HAWK_UNLIKELY(hawk_arr_upsert(container, idx, tmp, 0) == HAWK_ARR_NIL))
	{
		hawk_rtx_refdownval(rtx, tmp);  /* decrement upon upsert() failure */
		return HAWK_NULL;
	}
	return tmp;
}

static hawk_val_t* fetch_topval_from_var (hawk_rtx_t* rtx, hawk_nde_var_t* var)
{
	hawk_val_t* val;

	switch (var->type)
	{

		case HAWK_NDE_NAMED:
		case HAWK_NDE_NAMEDIDX:
		{
			hawk_htb_pair_t* pair;
			pair = hawk_htb_search(rtx->named, var->id.name.ptr, var->id.name.len);
			val = pair? (hawk_val_t*)HAWK_HTB_VPTR(pair): hawk_val_nil;
			break;
		}

		case HAWK_NDE_GBL:
		case HAWK_NDE_GBLIDX:
			val = HAWK_RTX_STACK_GBL(rtx, var->id.idxa);
			break;

		case HAWK_NDE_LCL:
		case HAWK_NDE_LCLIDX:
			val = HAWK_RTX_STACK_LCL(rtx, var->id.idxa);
			break;

		default:
			HAWK_ASSERT(var->type == HAWK_NDE_ARG || var->type == HAWK_NDE_ARGIDX);
			val = HAWK_RTX_STACK_ARG(rtx, var->id.idxa);
			break;
	}

	return val;
}

static hawk_val_t* assign_topval_to_var (hawk_rtx_t* rtx, hawk_nde_var_t* var, hawk_val_t* val)
{
	switch (var->type)
	{
		case HAWK_NDE_NAMED:
		case HAWK_NDE_NAMEDIDX:
		{
			/* doesn't have to decrease the reference count
			 * of the previous value here as it is done by
			 * hawk_htb_upsert */
			hawk_rtx_refupval(rtx, val);
			if (HAWK_UNLIKELY(hawk_htb_upsert(rtx->named, var->id.name.ptr, var->id.name.len, val, 0) == HAWK_NULL))
			{
				hawk_rtx_refdownval(rtx, val);
				ADJERR_LOC(rtx, &var->loc);
				return HAWK_NULL;
			}
			break;
		}

		case HAWK_NDE_GBL:
		case HAWK_NDE_GBLIDX:
		{
			int x;

			hawk_rtx_refupval(rtx, val);
			x = hawk_rtx_setgbl(rtx, (int)var->id.idxa, val);
			hawk_rtx_refdownval(rtx, val);
			if (HAWK_UNLIKELY(x <= -1))
			{
				ADJERR_LOC(rtx, &var->loc);
				return HAWK_NULL;
			}
			break;
		}

		case HAWK_NDE_LCL:
		case HAWK_NDE_LCLIDX:
		{
			hawk_val_t* old;
			old = HAWK_RTX_STACK_LCL(rtx,var->id.idxa);
			hawk_rtx_refdownval(rtx, old);
			HAWK_RTX_STACK_LCL(rtx,var->id.idxa) = val;
			hawk_rtx_refupval(rtx, val);
			break;
		}

		default: /* HAWK_NDE_ARG, HAWK_NDE_ARGIDX */
		{
			hawk_val_t* old;
			old = HAWK_RTX_STACK_ARG(rtx,var->id.idxa);
			hawk_rtx_refdownval(rtx, old);
			HAWK_RTX_STACK_ARG(rtx,var->id.idxa) = val;
			hawk_rtx_refupval(rtx, val);
			break;
		}
	}

	return val;
}

static hawk_val_t* assign_newmapval_to_var (hawk_rtx_t* rtx, hawk_nde_var_t* var)
{
	hawk_val_t* tmp;

	HAWK_ASSERT(var->type >= HAWK_NDE_NAMED && var->type <= HAWK_NDE_ARGIDX);

	tmp = hawk_rtx_makemapval(rtx);
	if (HAWK_UNLIKELY(!tmp))
	{
		ADJERR_LOC(rtx, &var->loc);
		return HAWK_NULL;
	}

	return assign_topval_to_var(rtx, var, tmp);
}


static HAWK_INLINE int delete_indexed (hawk_rtx_t* rtx, hawk_val_t* vv, hawk_nde_var_t* var)
{
	hawk_map_t* map;
	hawk_ooch_t* str = HAWK_NULL;
	hawk_oow_t len;
	hawk_ooch_t idxbuf[HAWK_IDX_BUF_SIZE];

	hawk_arr_t* arr;
	hawk_ooi_t idx;

	hawk_nde_t* remidx;
	hawk_val_type_t vtype;

	HAWK_ASSERT(var->idx != HAWK_NULL);

	/* delete x["abc"];
	 * delete x[20,"abc"]; */

	vtype = HAWK_RTX_GETVALTYPE(rtx, vv);
	if (vtype == HAWK_VAL_MAP)
	{
		len = HAWK_COUNTOF(idxbuf);
		str = idxnde_to_str(rtx, var->idx, idxbuf, &len, &remidx, HAWK_NULL);
		if (HAWK_UNLIKELY(!str)) goto oops;
		map = ((hawk_val_map_t*)vv)->map;
	}
	else
	{
		HAWK_ASSERT(vtype == HAWK_VAL_ARR);
		idx = idxnde_to_int(rtx, var->idx, &remidx);
		if (HAWK_UNLIKELY(idx <= -1)) goto oops;
		arr = ((hawk_val_arr_t*)vv)->arr;
	}

#if defined(HAWK_ENABLE_GC)
	while (remidx)
	{
		hawk_val_type_t container_vtype;

		if (vtype == HAWK_VAL_MAP)
		{
			hawk_map_pair_t* pair;
			pair = hawk_map_search(map, str, len);
			vv = pair? (hawk_val_t*)HAWK_MAP_VPTR(pair): hawk_val_nil;
		}
		else
		{
			vv = (idx < HAWK_ARR_SIZE(arr) && HAWK_ARR_SLOT(arr, idx))? ((hawk_val_t*)HAWK_ARR_DPTR(arr, idx)): hawk_val_nil;
		}
		container_vtype = vtype;
		vtype = HAWK_RTX_GETVALTYPE(rtx, vv);

		switch (vtype)
		{
			case HAWK_VAL_MAP:
			val_map:
				if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
				len = HAWK_COUNTOF(idxbuf);
				str = idxnde_to_str(rtx, remidx, idxbuf, &len, &remidx, HAWK_NULL);
				if (HAWK_UNLIKELY(!str)) goto oops;
				map = ((hawk_val_map_t*)vv)->map;
				break;

			case HAWK_VAL_ARR:
			val_arr:
				idx = idxnde_to_int(rtx, remidx, &remidx);
				if (HAWK_UNLIKELY(idx <= -1)) goto oops;
				arr = ((hawk_val_arr_t*)vv)->arr;
				break;

			default:
				if (vtype == HAWK_VAL_NIL || (rtx->hawk->opt.trait & HAWK_FLEXMAP))
				{
					if (container_vtype == HAWK_VAL_MAP)
					{
						vv = assign_newmapval_in_map(rtx, map, str, len);
						if (HAWK_UNLIKELY(!vv)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
						vtype = HAWK_VAL_MAP;
						goto val_map;
					}
					else
					{
						vv = assign_newarrval_in_arr(rtx, arr, idx);
						if (HAWK_UNLIKELY(!vv)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
						vtype = HAWK_VAL_ARR;
						goto val_arr;
					}
				}
				else
				{
					hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENOTDEL, HAWK_T("nested scalar value under '%.*js' not deletable"), var->id.name.len, var->id.name.ptr);
					goto oops;
				}
		}
	}
#endif

	if (vtype == HAWK_VAL_MAP)
		hawk_map_delete(map, str, len);
	else
		hawk_arr_uplete(arr, idx,  1); /* no reindexing by compaction. keep the place unset */

	if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
	return 0;

oops:
	if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
	return -1;
}

static int run_delete (hawk_rtx_t* rtx, hawk_nde_delete_t* nde)
{
	hawk_nde_var_t* var;
	hawk_val_t* val;
	hawk_val_type_t vtype;

	var = (hawk_nde_var_t*)nde->var;

	val = fetch_topval_from_var(rtx, var);
	HAWK_ASSERT(val != HAWK_NULL);

	vtype = HAWK_RTX_GETVALTYPE(rtx, val);
	switch (vtype)
	{
		case HAWK_VAL_NIL:
			/* value not set. create a map and assign it to the variable */
			if (HAWK_UNLIKELY(assign_newmapval_to_var(rtx, var) == HAWK_NULL)) goto oops;
			break;

		case HAWK_VAL_MAP:
			if (var->type == HAWK_NDE_NAMEDIDX || var->type == HAWK_NDE_GBLIDX ||
			    var->type == HAWK_NDE_LCLIDX || var->type == HAWK_NDE_ARGIDX)
			{
				if (delete_indexed(rtx, val, var) <= -1) goto oops;
			}
			else
			{
				/*
				BEGIN {
				  @local a;
				  a[1] = 20; a[2] = "hello"; a[3][4][5]="ttt";
				  print typename(a), length(a);
				  delete a;
				  print typename(a), length(a);
				}
				*/
				hawk_map_clear(((hawk_val_map_t*)val)->map);
			}
			break;

		case HAWK_VAL_ARR:
			if (var->type == HAWK_NDE_NAMEDIDX || var->type == HAWK_NDE_GBLIDX ||
			    var->type == HAWK_NDE_LCLIDX || var->type == HAWK_NDE_ARGIDX)
			{
				if (delete_indexed(rtx, val, var) <= -1) goto oops;
			}
			else
			{
				hawk_arr_clear(((hawk_val_arr_t*)val)->arr);
			}
			break;

		default:
			hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENOTDEL, HAWK_T("'%.*js' not deletable"), var->id.name.len, var->id.name.ptr);
			goto oops;
	}

	return 0;

oops:
	ADJERR_LOC(rtx, &var->loc);
	return -1;

}

static int run_reset (hawk_rtx_t* rtx, hawk_nde_reset_t* nde)
{
	hawk_nde_var_t* var;

	var = (hawk_nde_var_t*)nde->var;

	HAWK_ASSERT(var->type >= HAWK_NDE_NAMED && var->type <= HAWK_NDE_ARG);

	switch (var->type)
	{
		case HAWK_NDE_NAMED:
			/* if a named variable has an index part, something is definitely wrong */
			HAWK_ASSERT(var->idx == HAWK_NULL);

			/* a named variable can be reset if removed from the internal map to manage it */
			hawk_htb_delete (rtx->named, var->id.name.ptr, var->id.name.len);
			return 0;

		case HAWK_NDE_GBL:
		case HAWK_NDE_LCL:
		case HAWK_NDE_ARG:
		{
			hawk_val_t* val;

			HAWK_ASSERT(var->idx == HAWK_NULL);

			val = fetch_topval_from_var(rtx, var);
			HAWK_ASSERT(val != HAWK_NULL);

			if (HAWK_RTX_GETVALTYPE(rtx, val) != HAWK_VAL_NIL)
			{
				assign_topval_to_var(rtx, var, hawk_val_nil); /* this must not fail */
			}
			return 0;
		}

		default:
			/* the reset statement can only be called with plain variables */
			HAWK_ASSERT(!"should never happen - wrong target for @reset");
			hawk_rtx_seterrnum(rtx, &var->loc, HAWK_EBADARG);
			return -1;
	}
}

static hawk_val_t* io_nde_to_str(hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_oocs_t* dst, int seterr)
{
	hawk_val_t* v;

	v = eval_expression(rtx, nde);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, v);
	dst->ptr = hawk_rtx_getvaloocstr(rtx, v, &dst->len);
	if (HAWK_UNLIKELY(!dst))
	{
		hawk_rtx_refdownval(rtx, v);
		return HAWK_NULL;
	}

	if (seterr && dst->len <= 0)
	{
		hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_EIONMEM, HAWK_T("empty I/O name"));
	}
	else
	{
		hawk_oow_t len;

		len = dst->len;
		while (len > 0)
		{
			if (dst->ptr[--len] == '\0')
			{
				hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_EIONMNL, HAWK_T("invalid I/O name of length %zu containing '\\0'"), dst->len);
				dst->len = 0; /* indicate that the name is not valid */
				break;
			}
		}
	}

	return v;
}

static int run_print (hawk_rtx_t* rtx, hawk_nde_print_t* nde)
{
	hawk_oocs_t out;
	hawk_val_t* out_v = HAWK_NULL;
	int n, xret = 0;

	HAWK_ASSERT(
		(nde->out_type == HAWK_OUT_PIPE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_RWPIPE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_FILE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_APFILE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_CONSOLE && nde->out == HAWK_NULL));

	/* check if destination has been specified. */
	if (nde->out)
	{
		out_v = io_nde_to_str(rtx, nde->out, &out, 1);
		if (!out_v || out.len <= 0) goto oops_1;
	}
	else
	{
		out.ptr = (hawk_ooch_t*)HAWK_T("");
		out.len = 0;
	}

	/* check if print is followed by any arguments */
	if (!nde->args)
	{
		/* if it doesn't have any arguments, print the entire input record */
		n = hawk_rtx_writeiostr(rtx, nde->out_type, out.ptr, HAWK_OOECS_PTR(&rtx->inrec.line), HAWK_OOECS_LEN(&rtx->inrec.line));
		if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
		{
			if (rtx->hawk->opt.trait & HAWK_TOLERANT)
			{
				xret = PRINT_IOERR;
			}
			else
			{
				goto oops;
			}
		}
	}
	else
	{
		/* if it has any arguments, print the arguments separated by
		 * the value OFS */
		hawk_nde_t* head, * np;
		hawk_val_t* v;

		if (nde->args->type == HAWK_NDE_GRP)
		{
			/* parenthesized print */
			HAWK_ASSERT(nde->args->next == HAWK_NULL);
			head = ((hawk_nde_grp_t*)nde->args)->body;
		}
		else head = nde->args;

		for (np = head; np != HAWK_NULL; np = np->next)
		{
			if (np != head)
			{
				n = hawk_rtx_writeiostr(rtx, nde->out_type, out.ptr, rtx->gbl.ofs.ptr, rtx->gbl.ofs.len);
				if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
				{
					if (rtx->hawk->opt.trait & HAWK_TOLERANT)
					{
						xret = PRINT_IOERR;
					}
					else
					{
						goto oops;
					}
				}
			}

			v = eval_expression(rtx, np);
			if (HAWK_UNLIKELY(!v)) goto oops_1;

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_writeioval(rtx, nde->out_type, out.ptr, v);
			hawk_rtx_refdownval(rtx, v);

			if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
			{
				if (rtx->hawk->opt.trait & HAWK_TOLERANT)
				{
					xret = PRINT_IOERR;
				}
				else
				{
					goto oops;
				}
			}
		}
	}

	/* print the value ORS to terminate the operation */
	n = hawk_rtx_writeiostr(rtx, nde->out_type, out.ptr, rtx->gbl.ors.ptr, rtx->gbl.ors.len);
	if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
	{
		if (rtx->hawk->opt.trait & HAWK_TOLERANT)
		{
			xret = PRINT_IOERR;
		}
		else
		{
			goto oops;
		}
	}

	/* unlike printf, flushio() is not needed here as print
	 * inserts <NL> that triggers auto-flush */
	if (out_v)
	{
		hawk_rtx_freevaloocstr(rtx, out_v, out.ptr);
		hawk_rtx_refdownval(rtx, out_v);
	}

	return xret;

oops:
	ADJERR_LOC(rtx, &nde->loc);

oops_1:
	if (out_v)
	{
		hawk_rtx_freevaloocstr(rtx, out_v, out.ptr);
		hawk_rtx_refdownval(rtx, out_v);
	}

	return -1;
}

static int run_printf (hawk_rtx_t* rtx, hawk_nde_print_t* nde)
{
	hawk_oocs_t out;
	hawk_val_t* out_v = HAWK_NULL;
	hawk_val_t* v;
	hawk_val_type_t vtype;
	hawk_nde_t* head;
	int n, xret = 0;

	HAWK_ASSERT(
		(nde->out_type == HAWK_OUT_PIPE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_RWPIPE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_FILE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_APFILE && nde->out != HAWK_NULL) ||
		(nde->out_type == HAWK_OUT_CONSOLE && nde->out == HAWK_NULL));

	if (nde->out)
	{
		out_v = io_nde_to_str(rtx, nde->out, &out, 1);
		if (!out_v || out.len <= 0) goto oops_1;
	}
	else
	{
		out.ptr = (hawk_ooch_t*)HAWK_T("");
		out.len = 0;
	}

	/*( valid printf statement should have at least one argument. the parser must ensure this. */
	HAWK_ASSERT(nde->args != HAWK_NULL);

	if (nde->args->type == HAWK_NDE_GRP)
	{
		/* parenthesized print */
		HAWK_ASSERT(nde->args->next == HAWK_NULL);
		head = ((hawk_nde_grp_t*)nde->args)->body;
	}
	else head = nde->args;

	/* valid printf statement should have at least one argument. the parser must ensure this */
	HAWK_ASSERT(head != HAWK_NULL);

	v = eval_expression(rtx, head);
	if (HAWK_UNLIKELY(!v)) goto oops_1;

	hawk_rtx_refupval(rtx, v);
	vtype = HAWK_RTX_GETVALTYPE(rtx, v);
	switch (vtype)
	{
		case HAWK_VAL_STR:
			/* perform the formatted output */
			n = output_formatted(rtx, nde->out_type, out.ptr, ((hawk_val_str_t*)v)->val.ptr, ((hawk_val_str_t*)v)->val.len, head->next);
			hawk_rtx_refdownval(rtx, v);
			if (n <= -1)
			{
				if (n == PRINT_IOERR) xret = n;
				else goto oops;
			}
			break;

		case HAWK_VAL_MBS:
			/* perform the formatted output */
			n = output_formatted_bytes(rtx, nde->out_type, out.ptr, ((hawk_val_mbs_t*)v)->val.ptr, ((hawk_val_mbs_t*)v)->val.len, head->next);
			hawk_rtx_refdownval(rtx, v);
			if (n <= -1)
			{
				if (n == PRINT_IOERR) xret = n;
				else goto oops;
			}
			break;

		case HAWK_VAL_BOB:
			n = output_formatted_bytes(rtx, nde->out_type, out.ptr, ((hawk_val_bob_t*)v)->val.ptr, ((hawk_val_bob_t*)v)->val.len, head->next);
			hawk_rtx_refdownval(rtx, v);
			if (n <= -1)
			{
				if (n == PRINT_IOERR) xret = n;
				else goto oops;
			}
			break;

		default:
			/* the remaining arguments are ignored as the format cannot
			 * contain any % characters. e.g. printf (1, "xxxx") */
			n = hawk_rtx_writeioval(rtx, nde->out_type, out.ptr, v);
			hawk_rtx_refdownval(rtx, v);

			if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
			{
				if (rtx->hawk->opt.trait & HAWK_TOLERANT) xret = PRINT_IOERR;
				else goto oops;
			}
			break;
	}

	if (hawk_rtx_flushio(rtx, nde->out_type, out.ptr) <= -1)
	{
		if (rtx->hawk->opt.trait & HAWK_TOLERANT) xret = PRINT_IOERR;
		else goto oops_1;
	}

	if (out_v)
	{
		hawk_rtx_freevaloocstr(rtx, out_v, out.ptr);
		hawk_rtx_refdownval(rtx, out_v);
	}

	return xret;

oops:
	ADJERR_LOC(rtx, &nde->loc);

oops_1:
	if (out_v)
	{
		hawk_rtx_freevaloocstr(rtx, out_v, out.ptr);
		hawk_rtx_refdownval(rtx, out_v);
	}

	return -1;
}

static int output_formatted (
	hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* dst,
	const hawk_ooch_t* fmt, hawk_oow_t fmt_len, hawk_nde_t* args)
{
	hawk_ooch_t* ptr;
	hawk_oow_t len;
	int n;

	ptr = hawk_rtx_format(rtx, HAWK_NULL, HAWK_NULL, fmt, fmt_len, 0, args, &len);
	if (!ptr) return -1;

	n = hawk_rtx_writeiostr(rtx, out_type, dst, ptr, len);
	if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
	{
		return (rtx->hawk->opt.trait & HAWK_TOLERANT)? PRINT_IOERR: -1;
	}

	return 0;
}

static int output_formatted_bytes (
	hawk_rtx_t* rtx, hawk_out_type_t out_type, const hawk_ooch_t* dst,
	const hawk_bch_t* fmt, hawk_oow_t fmt_len, hawk_nde_t* args)
{
	hawk_bch_t* ptr;
	hawk_oow_t len;
	int n;

	ptr = hawk_rtx_formatmbs(rtx, HAWK_NULL, HAWK_NULL, fmt, fmt_len, 0, args, &len);
	if (!ptr) return -1;

	n = hawk_rtx_writeiobytes(rtx, out_type, dst, ptr, len);
	if (n <= -1 /*&& rtx->errinf.num != HAWK_EIOIMPL*/)
	{
		return (rtx->hawk->opt.trait & HAWK_TOLERANT)? PRINT_IOERR: -1;
	}

	return 0;
}

static hawk_val_t* eval_expression (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* v;
	int n;

#if 0
	if (rtx->exit_level >= EXIT_GLOBAL)
	{
		/* returns HAWK_NULL as if an error occurred but
		 * clears the error number. run_main will
		 * detect this condition and treat it as a
		 * non-error condition.*/
		rtx->errinf.num = HAWK_ENOERR;
		return HAWK_NULL;
	}
#endif

	v = eval_expression0(rtx, nde);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	if (HAWK_RTX_GETVALTYPE(rtx, v) == HAWK_VAL_REX)
	{
		hawk_oocs_t vs;
		int free_vs = 0;

		/* special case where a regular expression is used in
		 * without any match operators:
		 *    print /abc/;
		 * perform match against $0.
		 */
		hawk_rtx_refupval(rtx, v);

		if (HAWK_RTX_GETVALTYPE(rtx, rtx->inrec.d0) == HAWK_VAL_NIL)
		{
			/* the record has never been read.
			 * probably, this function has been triggered
			 * by the statements in the BEGIN block */
			vs.ptr = HAWK_T("");
			vs.len = 0;
		}
		else
		{
		#if 0
			/* the internal value representing $0 should always be of the string type
			 * once it has been set/updated. it is nil initially. */
			HAWK_ASSERT(HAWK_RTX_GETVALTYPE(rtx, rtx->inrec.d0) == HAWK_VAL_STR);
			vs.ptr = ((hawk_val_str_t*)rtx->inrec.d0)->val.ptr;
			vs.len = ((hawk_val_str_t*)rtx->inrec.d0)->val.len;
		#else
			vs.ptr = hawk_rtx_getvaloocstr(rtx, rtx->inrec.d0, &vs.len);
			if (!vs.ptr)
			{
				ADJERR_LOC(rtx, &nde->loc);
				hawk_rtx_refdownval(rtx, v);
				return HAWK_NULL;
			}

			free_vs  = 1;
		#endif
		}

		n = hawk_rtx_matchvalwithoocs(rtx, v, &vs, &vs, HAWK_NULL, HAWK_NULL);
		hawk_rtx_refdownval(rtx, v);
		if (free_vs) hawk_rtx_freevaloocstr(rtx, rtx->inrec.d0, vs.ptr);
		if (n <= -1)
		{
			ADJERR_LOC(rtx, &nde->loc);
			return HAWK_NULL;
		}

		v = hawk_rtx_makeintval(rtx, (n != 0));
		HAWK_ASSERT(v != HAWK_NULL); /* this will never fail as the value is 0 or 1 */
	}

	return v;
}

static hawk_val_t* eval_expression0 (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	static eval_expr_t __evaluator[] =
	{
		/* the order of functions here should match the order
		 * of node types(hawk_nde_type_t) declared in hawk.h */
		eval_group,
		eval_assignment,
		eval_binary,
		eval_unary,
		eval_incpre,
		eval_incpst,
		eval_cnd,
		eval_fncall_fnc,
		eval_fncall_fun,
		eval_fncall_var,
		eval_char,
		eval_bchr,
		eval_int,
		eval_flt,
		eval_str,
		eval_mbs,
		eval_rex,
		eval_xnil,
		eval_xargc,
		eval_xargv,
		eval_xargvidx,
		eval_fun,
		eval_named,
		eval_gbl,
		eval_lcl,
		eval_arg,
		eval_namedidx,
		eval_gblidx,
		eval_lclidx,
		eval_argidx,
		eval_pos,
		eval_getline,
		eval_print,
		eval_printf
	};

	hawk_val_t* v;

	HAWK_ASSERT(nde->type >= HAWK_NDE_GRP && (nde->type - HAWK_NDE_GRP) < HAWK_COUNTOF(__evaluator));

	v = __evaluator[nde->type - HAWK_NDE_GRP](rtx, nde);

	if (HAWK_UNLIKELY(v && rtx->exit_level >= EXIT_GLOBAL))
	{
		hawk_rtx_refupval(rtx, v);
		hawk_rtx_refdownval(rtx, v);

		/* returns HAWK_NULL as if an error occurred but
		 * clears the error number. run_main will
		 * detect this condition and treat it as a
		 * non-error condition.*/
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_ENOERR);
		return HAWK_NULL;
	}

	/* this function returns a regular expression without further mathiching.
	 * it is done in eval_expression(). */
	return v;
}

static hawk_val_t* eval_group (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
#if 0
	/* eval_binop_in evaluates the HAWK_NDE_GRP specially.
	 * so this function should never be reached. */
	HAWK_ASSERT(!"should never happen - NDE_GRP only for in");
	hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EINTERN);
	return HAWK_NULL;
#endif

	/* a group can be evauluated in a normal context
	 * if a program is parsed with HAWK_TOLERANT on.
	 * before the introduction of this option, the grouped
	 * expression was valid only coerced with the 'in'
	 * operator.
	 * */

	/* when a group is evaluated in a normal context,
	 * we return the last expression as a value. */

	hawk_val_t* val;
	hawk_nde_t* np;

	np = ((hawk_nde_grp_t*)nde)->body;

	HAWK_ASSERT(np != HAWK_NULL);

loop:
	val = eval_expression(rtx, np);
	if (HAWK_UNLIKELY(!val)) return HAWK_NULL;

	np = np->next;
	if (np)
	{
		hawk_rtx_refupval(rtx, val);
		hawk_rtx_refdownval(rtx, val);
		goto loop;
	}

	return val;
}

static hawk_val_t* eval_assignment (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val, * ret;
	hawk_nde_ass_t* ass = (hawk_nde_ass_t*)nde;

	HAWK_ASSERT(ass->left != HAWK_NULL);
	HAWK_ASSERT(ass->right != HAWK_NULL);

	HAWK_ASSERT(ass->right->next == HAWK_NULL);
	val = eval_expression(rtx, ass->right);
	if (HAWK_UNLIKELY(!val)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, val);

	if (ass->opcode != HAWK_ASSOP_NONE)
	{
		hawk_val_t* val2, * tmp;
		static binop_func_t binop_func[] =
		{
			/* this table must match hawk_assop_type_t in rtx.h */
			HAWK_NULL, /* HAWK_ASSOP_NONE */
			eval_binop_plus,
			eval_binop_minus,
			eval_binop_mul,
			eval_binop_div,
			eval_binop_idiv,
			eval_binop_mod,
			eval_binop_exp,
			eval_binop_concat,
			eval_binop_rshift,
			eval_binop_lshift,
			eval_binop_band,
			eval_binop_bxor,
			eval_binop_bor
		};

		HAWK_ASSERT(ass->left->next == HAWK_NULL);
		val2 = eval_expression(rtx, ass->left);
		if (HAWK_UNLIKELY(!val2))
		{
			hawk_rtx_refdownval(rtx, val);
			return HAWK_NULL;
		}

		hawk_rtx_refupval(rtx, val2);

		HAWK_ASSERT(ass->opcode >= 0);
		HAWK_ASSERT(ass->opcode < HAWK_COUNTOF(binop_func));
		HAWK_ASSERT(binop_func[ass->opcode] != HAWK_NULL);

		tmp = binop_func[ass->opcode](rtx, val2, val);
		if (HAWK_UNLIKELY(!tmp))
		{
			hawk_rtx_refdownval(rtx, val2);
			hawk_rtx_refdownval(rtx, val);
			return HAWK_NULL;
		}

		hawk_rtx_refdownval(rtx, val2);
		hawk_rtx_refdownval(rtx, val);

		val = tmp;
		hawk_rtx_refupval(rtx, val);
	}

	ret = do_assignment(rtx, ass->left, val);
	hawk_rtx_refdownval(rtx, val);

	return ret;
}

static hawk_val_t* do_assignment (hawk_rtx_t* rtx, hawk_nde_t* var, hawk_val_t* val)
{
	hawk_val_t* ret;
	hawk_errnum_t errnum;

	switch (var->type)
	{
		case HAWK_NDE_NAMED:
		case HAWK_NDE_GBL:
		case HAWK_NDE_LCL:
		case HAWK_NDE_ARG:
			ret = do_assignment_nonindexed(rtx, (hawk_nde_var_t*)var, val);
			break;

		case HAWK_NDE_NAMEDIDX:
		case HAWK_NDE_GBLIDX:
		case HAWK_NDE_LCLIDX:
		case HAWK_NDE_ARGIDX:
		{
		#if !defined(HAWK_ENABLE_GC)
			hawk_val_type_t vtype = HAWK_RTX_GETVALTYPE(rtx, val);
			if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
			{
				/* a map cannot become a member of a map */
				errnum = HAWK_ENONSCATOIDX;
				goto exit_on_error;
			}
		#endif

			ret = do_assignment_indexed(rtx, (hawk_nde_var_t*)var, val);
			break;
		}

		case HAWK_NDE_POS:
		{
			hawk_val_type_t vtype = HAWK_RTX_GETVALTYPE(rtx, val);
			if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
			{
				/* a map cannot be assigned to a positional */
				errnum = HAWK_ENONSCATOPOS;
				goto exit_on_error;
			}

			ret = do_assignment_positional(rtx, (hawk_nde_pos_t*)var, val);
			break;
		}

		default:
			HAWK_ASSERT(!"should never happen - invalid variable type");
			errnum = HAWK_EINTERN;
			goto exit_on_error;
	}

	return ret;

exit_on_error:
	hawk_rtx_seterrnum(rtx, &var->loc, errnum);
	return HAWK_NULL;
}

static hawk_val_t* do_assignment_nonindexed (hawk_rtx_t* rtx, hawk_nde_var_t* var, hawk_val_t* val)
{
	hawk_val_type_t vtype;

	HAWK_ASSERT(
		var->type == HAWK_NDE_NAMED ||
		var->type == HAWK_NDE_GBL ||
		var->type == HAWK_NDE_LCL ||
		var->type == HAWK_NDE_ARG
	);

	HAWK_ASSERT(var->idx == HAWK_NULL);
	vtype = HAWK_RTX_GETVALTYPE(rtx, val);

	switch (var->type)
	{
		case HAWK_NDE_NAMED:
		{
			hawk_htb_pair_t* pair;

			pair = hawk_htb_search(rtx->named, var->id.name.ptr, var->id.name.len);

			if (!(rtx->hawk->opt.trait & HAWK_FLEXMAP))
			{
				hawk_val_type_t old_vtype;
				if (pair && ((old_vtype = HAWK_RTX_GETVALTYPE(rtx, (hawk_val_t*)HAWK_HTB_VPTR(pair))) == HAWK_VAL_MAP || old_vtype == HAWK_VAL_ARR))
				{
					/* old value is a map - it can only be accessed through indexing. */
					if (vtype == old_vtype)
						hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATONONSCA, HAWK_T("not allowed to change a nonscalar value in '%.*js' to another nonscalar value"), var->id.name.len, var->id.name.ptr);
					else
						hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATOSCALAR, HAWK_T("not allowed to change a nonscalar value in '%.*js' to a scalar value"), var->id.name.len, var->id.name.ptr);
					return HAWK_NULL;
				}
				else if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
				{
					/* old value is not a map but a new value is a map.
					 * a map cannot be assigned to a variable if FLEXMAP is off. */
					hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATOVAR, HAWK_T("not allowed to assign a nonscalar value to a variable '%.*js'"), var->id.name.len, var->id.name.ptr);
					return HAWK_NULL;
				}
			}

			if (hawk_htb_upsert(rtx->named, var->id.name.ptr, var->id.name.len, val, 0) == HAWK_NULL)
			{
				ADJERR_LOC(rtx, &var->loc);
				return HAWK_NULL;
			}

			hawk_rtx_refupval(rtx, val);
			break;
		}

		case HAWK_NDE_GBL:
		{
			if (set_global(rtx, var->id.idxa, var, val, 1) == -1)
			{
				ADJERR_LOC(rtx, &var->loc);
				return HAWK_NULL;
			}
			break;
		}

		case HAWK_NDE_LCL:
		{
			hawk_val_t* old = HAWK_RTX_STACK_LCL(rtx,var->id.idxa);

			if (!(rtx->hawk->opt.trait & HAWK_FLEXMAP))
			{
				hawk_val_type_t old_type;

				if ((old_type = HAWK_RTX_GETVALTYPE(rtx, old)) == HAWK_VAL_MAP || old_type == HAWK_VAL_ARR)
				{
					/* old value is a map - it can only be accessed through indexing. */
					if (vtype == old_type)
						hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATONONSCA, HAWK_T("not allowed to change a nonscalar value in '%.*js' to another nonscalar value"), var->id.name.len, var->id.name.ptr);
					else
						hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATOSCALAR, HAWK_T("not allowed to change a nonscalar value in '%.*js' to a scalar value"), var->id.name.len, var->id.name.ptr);
					return HAWK_NULL;
				}
				else if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
				{
					/* old value is not a map but a new value is a map.
					 * a map cannot be assigned to a variable if FLEXMAP is off. */
					hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATOVAR, HAWK_T("not allowed to assign a nonscalar value to a variable '%.*js'"), var->id.name.len, var->id.name.ptr);
					return HAWK_NULL;
				}
			}

			hawk_rtx_refdownval(rtx, old);
			HAWK_RTX_STACK_LCL(rtx,var->id.idxa) = val;
			hawk_rtx_refupval(rtx, val);
			break;
		}

		case HAWK_NDE_ARG:
		{
			hawk_val_t* old = HAWK_RTX_STACK_ARG(rtx,var->id.idxa);

			if (!(rtx->hawk->opt.trait & HAWK_FLEXMAP))
			{
				hawk_val_type_t old_type;

				if ((old_type = HAWK_RTX_GETVALTYPE(rtx, old) == HAWK_VAL_MAP) || old_type == HAWK_VAL_ARR)
				{
					/* old value is a map - it can only be accessed through indexing. */
					if (vtype == old_type)
						hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATONONSCA, HAWK_T("not allowed to change a nonscalar value in '%.*js' to another nonscalar value"), var->id.name.len, var->id.name.ptr);
					else
						hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATOSCALAR, HAWK_T("not allowed to change a nonscalar value in '%.*js' to a scalar value"), var->id.name.len, var->id.name.ptr);
					return HAWK_NULL;
				}
				else if (vtype == HAWK_VAL_MAP || vtype == HAWK_VAL_ARR)
				{
					/* old value is not a map but a new value is a map.
					 * a map cannot be assigned to a variable if FLEXMAP is off. */
					hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ENONSCATOVAR, HAWK_T("not allowed to assign a nonscalar value to a variable '%.*js'"), var->id.name.len, var->id.name.ptr);
					return HAWK_NULL;
				}
			}

			hawk_rtx_refdownval(rtx, old);
			HAWK_RTX_STACK_ARG(rtx,var->id.idxa) = val;
			hawk_rtx_refupval(rtx, val);
			break;
		}

		default:
			hawk_rtx_seterrnum(rtx, &var->loc, HAWK_EINTERN);
			return HAWK_NULL;
	}

	return val;
}

static hawk_val_t* do_assignment_indexed (hawk_rtx_t* rtx, hawk_nde_var_t* var, hawk_val_t* val)
{
	hawk_map_t* map;
	hawk_ooch_t* str = HAWK_NULL;
	hawk_oow_t len;
	hawk_ooch_t idxbuf[HAWK_IDX_BUF_SIZE];

	hawk_ooi_t idx;
	hawk_arr_t* arr;

	hawk_nde_t* remidx;
	hawk_val_t* vv; /* existing value pointed to by var */
	hawk_val_type_t vtype;

	HAWK_ASSERT(
		(var->type == HAWK_NDE_NAMEDIDX ||
		 var->type == HAWK_NDE_GBLIDX ||
		 var->type == HAWK_NDE_LCLIDX ||
		 var->type == HAWK_NDE_ARGIDX) && var->idx != HAWK_NULL);
#if !defined(HAWK_ENABLE_GC)
	HAWK_ASSERT(HAWK_RTX_GETVALTYPE(rtx, val) != HAWK_VAL_MAP &&
	             HAWK_RTX_GETVALTYPE(rtx, val) != HAWK_VAL_ARR);
#endif

	/* check the value that the assigned variable points to */
	vv = fetch_topval_from_var(rtx, var);
	vtype = HAWK_RTX_GETVALTYPE(rtx, vv);
	switch (vtype)
	{
		case HAWK_VAL_MAP:
		case HAWK_VAL_ARR:
		{
		val_map_or_arr:
			if (vtype == HAWK_VAL_MAP)
			{
				len = HAWK_COUNTOF(idxbuf);
				str = idxnde_to_str(rtx, var->idx, idxbuf, &len, &remidx, HAWK_NULL);
				if (HAWK_UNLIKELY(!str)) goto oops;
				map = ((hawk_val_map_t*)vv)->map;
			}
			else
			{
				idx = idxnde_to_int(rtx, var->idx, &remidx);
				if (idx <= -1) goto oops;
				arr = ((hawk_val_arr_t*)vv)->arr;
			}

		#if defined(HAWK_ENABLE_GC)
			while (remidx)
			{
				hawk_val_type_t container_vtype;

				if (vtype == HAWK_VAL_MAP)
				{
					hawk_map_pair_t* pair;
					pair = hawk_map_search(map, str, len);
					vv = pair? (hawk_val_t*)HAWK_MAP_VPTR(pair): hawk_val_nil;
				}
				else
				{
					vv = (idx < HAWK_ARR_SIZE(arr) && HAWK_ARR_SLOT(arr, idx))? ((hawk_val_t*)HAWK_ARR_DPTR(arr, idx)): hawk_val_nil;
				}
				container_vtype = vtype;
				vtype = HAWK_RTX_GETVALTYPE(rtx, vv);

				switch (vtype)
				{
					case HAWK_VAL_MAP:
					val_map:
						if (str != idxbuf) hawk_rtx_freemem(rtx, str);
						len = HAWK_COUNTOF(idxbuf);
						str = idxnde_to_str(rtx, remidx, idxbuf, &len, &remidx, HAWK_NULL);
						if (HAWK_UNLIKELY(!str)) goto oops;
						map = ((hawk_val_map_t*)vv)->map;
						break;

					case HAWK_VAL_ARR:
					val_arr:
						idx = idxnde_to_int(rtx, remidx, &remidx);
						if (idx <= -1) goto oops;
						arr = ((hawk_val_arr_t*)vv)->arr;
						break;

					default:
						if (vtype == HAWK_VAL_NIL || (rtx->hawk->opt.trait & HAWK_FLEXMAP))
						{
							/* BEGIN { @local a, b; b="hello"; a[1]=b; a[1][2]=20; print a[1][2];} */
							/* BEGIN { a[1]="hello"; a[1][2]=20; print a[1][2];  } */
							/* BEGIN { b="hello"; a[1]=b; a[1][2]=20; print a[1][2]; } */

							/* well, this is not the first level index.
							 * the logic is different from the first level where reset_variable is called.
							 * here it simply creates a new map. */
							if (container_vtype == HAWK_VAL_MAP)
							{
								vv = assign_newmapval_in_map(rtx, map, str, len);
								if (HAWK_UNLIKELY(!vv)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
								vtype = HAWK_VAL_MAP;
								goto val_map;
							}
							else
							{
								vv = assign_newarrval_in_arr(rtx, arr, idx);
								if (HAWK_UNLIKELY(!vv)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
								vtype = HAWK_VAL_ARR;
								goto val_arr;
							}
						}
						else
						{
							hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ESCALARTONONSCA, HAWK_T("not allowed to change a nested scalar value under '%.*js' to a nonscalar value"), var->id.name.len, var->id.name.ptr);
							goto oops;
						}
				}
			}
		#endif

		#if defined(DEBUG_RUN)
			hawk_logbfmt (hawk_rtx_gethawk(rtx), "**** index str=>%js, map->ref=%d, map->type=%d\n", str, (int)v->ref, (int)v->type);
		#endif

			if (vtype == HAWK_VAL_MAP)
			{
				if (HAWK_UNLIKELY(hawk_map_upsert(map, str, len, val, 0) == HAWK_NULL))
				{
					ADJERR_LOC(rtx, &var->loc);
					goto oops;
				}
			}
			else
			{
				if (HAWK_UNLIKELY(hawk_arr_upsert(arr, idx, val, 0) == HAWK_ARR_NIL))
				{
					ADJERR_LOC(rtx, &var->loc);
					goto oops;
				}
			}

			hawk_rtx_refupval(rtx, val);
			if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
			return val;
		}

		default:
			if (vtype == HAWK_VAL_NIL || (rtx->hawk->opt.trait & HAWK_FLEXMAP))
			{
				/* 1. switch a nil value to a map value at the first level */
				/* 2. if FLEXMAP is on, you can switch a scalar value to a map value */
				vv = assign_newmapval_to_var(rtx, var);
				if (HAWK_UNLIKELY(!vv)) return HAWK_NULL;
				vtype = HAWK_VAL_MAP;
				goto val_map_or_arr;
			}
			else
			{
				/* you can't manipulate a variable pointing to
				 * a scalar value with an index if FLEXMAP is off. */
				hawk_rtx_seterrfmt(rtx, &var->loc, HAWK_ESCALARTONONSCA, HAWK_T("not allowed to change a scalar value in '%.*js' to a nonscalar value"), var->id.name.len, var->id.name.ptr);
				goto oops;
			}
	}

oops:
	if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
	return HAWK_NULL;
}

static hawk_val_t* do_assignment_positional (hawk_rtx_t* rtx, hawk_nde_pos_t* pos, hawk_val_t* val)
{
	hawk_val_t* v;
	hawk_val_type_t vtype;
	hawk_int_t lv;
	hawk_oocs_t str;
	int n;

	v = eval_expression(rtx, pos->val);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, v);
	n = hawk_rtx_valtoint(rtx, v, &lv);
	hawk_rtx_refdownval(rtx, v);

	if (n <= -1)
	{
		hawk_rtx_seterrnum(rtx, &pos->loc, HAWK_EPOSIDX);
		return HAWK_NULL;
	}

	if (!IS_VALID_POSIDX(lv))
	{
		hawk_rtx_seterrnum(rtx, &pos->loc, HAWK_EPOSIDX);
		return HAWK_NULL;
	}

	vtype = HAWK_RTX_GETVALTYPE(rtx, val);
	if (vtype == HAWK_VAL_STR)
	{
		str = ((hawk_val_str_t*)val)->val;
	}
	else
	{
		hawk_rtx_valtostr_out_t out;

		out.type = HAWK_RTX_VALTOSTR_CPLDUP;
		if (hawk_rtx_valtostr(rtx, val, &out) <= -1)
		{
			ADJERR_LOC(rtx, &pos->loc);
			return HAWK_NULL;
		}

		str = out.u.cpldup;
	}

	n = hawk_rtx_setrec(rtx, (hawk_oow_t)lv, &str, 0);

	if (vtype == HAWK_VAL_STR)
	{
		/* do nothing */
	}
	else
	{
		hawk_rtx_freemem(rtx, str.ptr);
	}

	if (n <= -1) return HAWK_NULL;
	return (lv == 0)? rtx->inrec.d0: rtx->inrec.flds[lv-1].val;
}

static hawk_val_t* eval_binary (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	static binop_func_t binop_func[] =
	{
		/* the order of the functions should be inline with
		 * the operator declaration in rtx.h */

		HAWK_NULL, /* eval_binop_lor */
		HAWK_NULL, /* eval_binop_land */
		HAWK_NULL, /* eval_binop_in */

		eval_binop_bor,
		eval_binop_bxor,
		eval_binop_band,

		eval_binop_teq,
		eval_binop_tne,
		eval_binop_eq,
		eval_binop_ne,
		eval_binop_gt,
		eval_binop_ge,
		eval_binop_lt,
		eval_binop_le,

		eval_binop_lshift,
		eval_binop_rshift,

		eval_binop_plus,
		eval_binop_minus,
		eval_binop_mul,
		eval_binop_div,
		eval_binop_idiv,
		eval_binop_mod,
		eval_binop_exp,

		eval_binop_concat,
		HAWK_NULL, /* eval_binop_ma */
		HAWK_NULL  /* eval_binop_nm */
	};

	hawk_nde_exp_t* exp = (hawk_nde_exp_t*)nde;
	hawk_val_t* left, * right, * res;

	HAWK_ASSERT(exp->type == HAWK_NDE_EXP_BIN);

	switch (exp->opcode)
	{
		case HAWK_BINOP_LAND:
			res = eval_binop_land(rtx, exp->left, exp->right);
			break;

		case HAWK_BINOP_LOR:
			res = eval_binop_lor(rtx, exp->left, exp->right);
			break;

		case HAWK_BINOP_IN:
			/* treat the in operator specially */
			res = eval_binop_in(rtx, exp->left, exp->right);
			break;

		case HAWK_BINOP_NM:
			res = eval_binop_nm(rtx, exp->left, exp->right);
			break;

		case HAWK_BINOP_MA:
			res = eval_binop_ma(rtx, exp->left, exp->right);
			break;

		default:
			HAWK_ASSERT(exp->left->next == HAWK_NULL);
			left = eval_expression(rtx, exp->left);
			if (HAWK_UNLIKELY(!left)) return HAWK_NULL;

			hawk_rtx_refupval(rtx, left);

			HAWK_ASSERT(exp->right->next == HAWK_NULL);
			right = eval_expression(rtx, exp->right);
			if (HAWK_UNLIKELY(!right))
			{
				hawk_rtx_refdownval(rtx, left);
				return HAWK_NULL;
			}

			hawk_rtx_refupval(rtx, right);

			HAWK_ASSERT(exp->opcode >= 0 && exp->opcode < HAWK_COUNTOF(binop_func));
			HAWK_ASSERT(binop_func[exp->opcode] != HAWK_NULL);

			res = binop_func[exp->opcode](rtx, left, right);
			if (HAWK_UNLIKELY(!res)) ADJERR_LOC(rtx, &nde->loc);

			hawk_rtx_refdownval(rtx, left);
			hawk_rtx_refdownval(rtx, right);
	}

	return res;
}

static hawk_val_t* eval_binop_lor (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right)
{
	/*
	hawk_val_t* res = HAWK_NULL;

	res = hawk_rtx_makeintval(
		rtx,
		hawk_rtx_valtobool(rtx,left) ||
		hawk_rtx_valtobool(rtx,right)
	);
	if (res == HAWK_NULL)
	{
		ADJERR_LOC(rtx, &left->loc);
		return HAWK_NULL;
	}

	return res;
	*/

	/* short-circuit evaluation required special treatment */
	hawk_val_t* lv, * rv, * res;

	HAWK_ASSERT(left->next == HAWK_NULL);
	lv = eval_expression(rtx, left);
	if (HAWK_UNLIKELY(!lv)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, lv);
	if (hawk_rtx_valtobool(rtx, lv))
	{
		res = HAWK_VAL_ONE;
	}
	else
	{
		HAWK_ASSERT(right->next == HAWK_NULL);
		rv = eval_expression(rtx, right);
		if (HAWK_UNLIKELY(!rv))
		{
			hawk_rtx_refdownval(rtx, lv);
			return HAWK_NULL;
		}
		hawk_rtx_refupval(rtx, rv);

		res = hawk_rtx_valtobool(rtx,rv)?
			HAWK_VAL_ONE: HAWK_VAL_ZERO;
		hawk_rtx_refdownval(rtx, rv);
	}

	hawk_rtx_refdownval(rtx, lv);

	return res;
}

static hawk_val_t* eval_binop_land (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right)
{
	/*
	hawk_val_t* res = HAWK_NULL;

	res = hawk_rtx_makeintval(
		rtx,
		hawk_rtx_valtobool(rtx,left) &&
		hawk_rtx_valtobool(rtx,right)
	);
	if (res == HAWK_NULL)
	{
		ADJERR_LOC(rtx, &left->loc);
		return HAWK_NULL;
	}

	return res;
	*/

	/* short-circuit evaluation required special treatment */
	hawk_val_t* lv, * rv, * res;

	HAWK_ASSERT(left->next == HAWK_NULL);
	lv = eval_expression(rtx, left);
	if (HAWK_UNLIKELY(!lv)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, lv);
	if (!hawk_rtx_valtobool(rtx, lv))
	{
		res = HAWK_VAL_ZERO;
	}
	else
	{
		HAWK_ASSERT(right->next == HAWK_NULL);
		rv = eval_expression(rtx, right);
		if (HAWK_UNLIKELY(!rv))
		{
			hawk_rtx_refdownval(rtx, lv);
			return HAWK_NULL;
		}
		hawk_rtx_refupval(rtx, rv);

		res = hawk_rtx_valtobool(rtx,rv)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
		hawk_rtx_refdownval(rtx, rv);
	}

	hawk_rtx_refdownval(rtx, lv);

	return res;
}

static hawk_val_t* eval_binop_in (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right)
{
	hawk_val_t* res;
	hawk_val_t* ropv;
	hawk_val_type_t ropvtype;
	hawk_ooch_t* str;
	hawk_oow_t len;
	hawk_ooch_t idxbuf[HAWK_IDX_BUF_SIZE];
	hawk_nde_t* remidx;
	hawk_int_t idxint;

	if (right->type == HAWK_NDE_XARGV) goto rvalue_ok;

#if defined(HAWK_ENABLE_GC)
	if (right->type < HAWK_NDE_NAMED || right->type > HAWK_NDE_ARGIDX)
#else
	if (right->type < HAWK_NDE_NAMED || right->type > HAWK_NDE_ARG)
#endif
	{
		/* the compiler should have handled this case */
		HAWK_ASSERT(!"should never happen - it needs a plain variable");
		hawk_rtx_seterrnum(rtx, &right->loc, HAWK_EINTERN);
		return HAWK_NULL;
	}

rvalue_ok:
	/* evaluate the left-hand side of the operator */
	len = HAWK_COUNTOF(idxbuf);
	str = (left->type == HAWK_NDE_GRP)? /* it is inefficinet to call idxnde_to_str() for an array. but i don't know if the right hand side is a map or an array yet */
		idxnde_to_str(rtx, ((hawk_nde_grp_t*)left)->body, idxbuf, &len, &remidx, HAWK_NULL):
		idxnde_to_str(rtx, left, idxbuf, &len, &remidx, &idxint);
	if (HAWK_UNLIKELY(!str)) return HAWK_NULL;

	/* There is no way to express a true multi-dimensional indices for the 'in' operator.
	 * So remidx must be NULL here.
	 *   a[10][20] <--- no way to express the test of 20 under 10 in a.
	 * You may use multi-level test conjoined with a logical and operator in such a case.
	 *   ((10 in a) && (20 in a[10]))
	 *
	 *  '(10, 20) in a' is to test for a[10,20] if 'a' is a map.
	 */
	HAWK_ASSERT(remidx == HAWK_NULL);

	/* evaluate the right-hand side of the operator */
	HAWK_ASSERT(right->next == HAWK_NULL);
	ropv = eval_expression(rtx, right);
	if (HAWK_UNLIKELY(!ropv))
	{
		if (str != idxbuf) hawk_rtx_freemem(rtx, str);
		return HAWK_NULL;
	}

	hawk_rtx_refupval(rtx, ropv);

	ropvtype = HAWK_RTX_GETVALTYPE(rtx, ropv);
	switch (ropvtype)
	{
		case HAWK_VAL_NIL:
			res = HAWK_VAL_ZERO;
			break;

		case HAWK_VAL_MAP:
		{

			hawk_map_t* map;

			map = ((hawk_val_map_t*)ropv)->map;
			res = (hawk_map_search(map, str, len) == HAWK_NULL)? HAWK_VAL_ZERO: HAWK_VAL_ONE;
			break;
		}

		case HAWK_VAL_ARR:
		{
			hawk_arr_t* arr;

			if (left->type == HAWK_NDE_GRP)
			{
				hawk_rtx_seterrnum(rtx, &left->loc, HAWK_EARRIDXMULTI);
				goto oops;
			}

			arr = ((hawk_val_arr_t*)ropv)->arr;
			res = (idxint < 0 || idxint >= HAWK_ARR_SIZE(arr) || !HAWK_ARR_SLOT(arr, idxint))? HAWK_VAL_ZERO: HAWK_VAL_ONE;
			break;
		}

		default:
			hawk_rtx_seterrnum(rtx, &right->loc, HAWK_EINROP);
			goto oops;
	}

	if (str != idxbuf) hawk_rtx_freemem(rtx, str);
	hawk_rtx_refdownval(rtx, ropv);
	return res;

oops:
	if (str != idxbuf) hawk_rtx_freemem(rtx, str);
	hawk_rtx_refdownval(rtx, ropv);

	return HAWK_NULL;
}

static hawk_val_t* eval_binop_bor (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	hawk_int_t l1, l2;

	if (hawk_rtx_valtoint(rtx, left, &l1) <= -1 ||
	    hawk_rtx_valtoint(rtx, right, &l2) <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	return hawk_rtx_makeintval(rtx, l1 | l2);
}

static hawk_val_t* eval_binop_bxor (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	hawk_int_t l1, l2;

	if (hawk_rtx_valtoint(rtx, left, &l1) <= -1 ||
	    hawk_rtx_valtoint(rtx, right, &l2) <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	return hawk_rtx_makeintval(rtx, l1 ^ l2);
}

static hawk_val_t* eval_binop_band (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	hawk_int_t l1, l2;

	if (hawk_rtx_valtoint(rtx, left, &l1) <= -1 ||
	    hawk_rtx_valtoint(rtx, right, &l2) <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	return hawk_rtx_makeintval(rtx, l1 & l2);
}

/* -------------------------------------------------------------------- */

enum cmp_op_t
{
	CMP_OP_NONE = 0,
	CMP_OP_EQ = 1,
	CMP_OP_NE = 2,
	CMP_OP_GT = 3,
	CMP_OP_GE = 4,
	CMP_OP_LT = 5,
	CMP_OP_LE = 6
};
typedef enum cmp_op_t cmp_op_t;

static HAWK_INLINE cmp_op_t inverse_cmp_op (cmp_op_t op)
{
	static cmp_op_t inverse_cmp_op_tab[] =
	{
		CMP_OP_NONE,
		CMP_OP_NE,
		CMP_OP_EQ,
		CMP_OP_LT,
		CMP_OP_LE,
		CMP_OP_GT,
		CMP_OP_GE
	};
	return inverse_cmp_op_tab[op];
}

static HAWK_INLINE int __cmp_ensure_not_equal (hawk_rtx_t* rtx, cmp_op_t op_hint)
{
	/* checking equality is mostly obvious. however, it is not possible
	 * to test if one is less/greater than the other for some operands.
	 * this function return a number that ensures to make NE to true and
	 * all other operations false */

	switch (op_hint)
	{
		case CMP_OP_EQ:
		case CMP_OP_NE:
			return 1;  /* not equal */

		case CMP_OP_GT:
		case CMP_OP_LT:
			return 0; /* make GT or LT to be false by claiming equal */

		case CMP_OP_GE:
			return -1; /* make GE false by claiming less */
		case CMP_OP_LE:
			return 1;  /* make LE false by claiming greater */

		default:
			hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
			return -1;
	}
}

/* -------------------------------------------------------------------- */
static HAWK_INLINE int __cmp_nil_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return 0;
}

static HAWK_INLINE int __cmp_nil_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_oochu_t v = HAWK_RTX_GETCHARFROMVAL(rtx, right);
	return (v < 0)? 1: ((v > 0)? -1: 0);
}

static HAWK_INLINE int __cmp_nil_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_bchu_t v = HAWK_RTX_GETBCHRFROMVAL(rtx, right);
	return (v < 0)? 1: ((v > 0)? -1: 0);
}

static HAWK_INLINE int __cmp_nil_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_int_t v = HAWK_RTX_GETINTFROMVAL(rtx, right);
	return (v < 0)? 1: ((v > 0)? -1: 0);
}

static HAWK_INLINE int __cmp_nil_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	if (((hawk_val_flt_t*)right)->val < 0) return 1;
	if (((hawk_val_flt_t*)right)->val > 0) return -1;
	return 0;
}

static HAWK_INLINE int __cmp_nil_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return (((hawk_val_str_t*)right)->val.len == 0)? 0: -1;
}

static HAWK_INLINE int __cmp_nil_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return (((hawk_val_mbs_t*)right)->val.len == 0)? 0: -1;
}

static HAWK_INLINE int __cmp_nil_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/* != -> true, all others -> false */
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_nil_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return (HAWK_MAP_SIZE(((hawk_val_map_t*)right)->map) == 0)? 0: -1;
}

static HAWK_INLINE int __cmp_nil_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return (HAWK_ARR_SIZE(((hawk_val_arr_t*)right)->arr) == 0)? 0: -1;
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_char_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_char(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_char_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_oochu_t v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	hawk_oochu_t v2 = HAWK_RTX_GETCHARFROMVAL(rtx, right);
	return (v1 > v2)? 1: ((v1 < v2)? -1: 0);
}

static HAWK_INLINE int __cmp_char_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_oochu_t v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	hawk_bchu_t v2 = HAWK_RTX_GETBCHRFROMVAL(rtx, right);
	return (v1 > v2)? 1: ((v1 < v2)? -1: 0);
}

static HAWK_INLINE int __cmp_char_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/*
	hawk_oochu_t v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	hawk_int_t v2 = HAWK_RTX_GETINTFROMVAL(rtx, right);
	return (v1 > v2)? 1: ((v1 < v2)? -1: 0);
	*/
	hawk_ooch_t v1;
	hawk_ooch_t* str0;
	hawk_oow_t len0;
	int n;

	v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	str0 = hawk_rtx_getvaloocstr(rtx, right, &len0);
	if (!str0) return CMP_ERROR;
	n = hawk_comp_oochars(&v1, 1, str0, len0, rtx->gbl.ignorecase);
	hawk_rtx_freevaloocstr(rtx, right, str0);
	return n;
}

static HAWK_INLINE int __cmp_char_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/*
	hawk_ooch_t v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	if (v1 > ((hawk_val_flt_t*)right)->val) return 1;
	if (v1 < ((hawk_val_flt_t*)right)->val) return -1;
	return 0;
	*/
	return __cmp_char_int(rtx, left, right, op_hint);
}

static HAWK_INLINE int __cmp_char_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_ooch_t v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	return hawk_comp_oochars(&v1, 1, ((hawk_val_str_t*)right)->val.ptr, ((hawk_val_str_t*)right)->val.len, rtx->gbl.ignorecase);
}

static HAWK_INLINE int __cmp_char_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_ooch_t v1 = HAWK_RTX_GETCHARFROMVAL(rtx, left);
	hawk_bch_t bc;
	if (v1 > 0xFF) return 1;
	bc = v1;
	return hawk_comp_bchars(&bc, 1, ((hawk_val_mbs_t*)right)->val.ptr, ((hawk_val_mbs_t*)right)->val.len, rtx->gbl.ignorecase);
}

static HAWK_INLINE int __cmp_char_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_char_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_char_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_bchr_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_bchr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_bchr_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_bchr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_bchr_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_bchu_t v1 = HAWK_RTX_GETBCHRFROMVAL(rtx, left);
	hawk_bchu_t v2 = HAWK_RTX_GETBCHRFROMVAL(rtx, right);
	return (v1 > v2)? 1: ((v1 < v2)? -1: 0);
}

static HAWK_INLINE int __cmp_bchr_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/*
	hawk_bchu_t v1 = HAWK_RTX_GETBCHRFROMVAL(rtx, left);
	hawk_int_t v2 = HAWK_RTX_GETINTFROMVAL(rtx, right);
	return (v1 > v2)? 1: ((v1 < v2)? -1: 0);
	*/
	hawk_bch_t v1;
	hawk_bch_t* str0;
	hawk_oow_t len0;
	int n;

	v1 = HAWK_RTX_GETBCHRFROMVAL(rtx, left);
	str0 = hawk_rtx_getvalbcstr(rtx, right, &len0);
	if (!str0) return CMP_ERROR;
	n = hawk_comp_bchars(&v1, 1, str0, len0, rtx->gbl.ignorecase);
	hawk_rtx_freevalbcstr(rtx, right, str0);
	return n;
}

static HAWK_INLINE int __cmp_bchr_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/*
	hawk_bchu_t v1 = HAWK_RTX_GETBCHRFROMVAL(rtx, left);
	if (v1 > ((hawk_val_flt_t*)right)->val) return 1;
	if (v1 < ((hawk_val_flt_t*)right)->val) return -1;
	return 0;
	*/
	return __cmp_bchr_int(rtx, left, right, op_hint);
}

static HAWK_INLINE int __cmp_bchr_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_bchu_t v1 = HAWK_RTX_GETBCHRFROMVAL(rtx, left);
	hawk_oochu_t oc = v1;
	return hawk_comp_oochars((const hawk_ooch_t*)&oc, 1, ((hawk_val_str_t*)right)->val.ptr, ((hawk_val_str_t*)right)->val.len, rtx->gbl.ignorecase);
}

static HAWK_INLINE int __cmp_bchr_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_bch_t v1 = HAWK_RTX_GETBCHRFROMVAL(rtx, left);
	return hawk_comp_bchars(&v1, 1, ((hawk_val_mbs_t*)right)->val.ptr, ((hawk_val_mbs_t*)right)->val.len, rtx->gbl.ignorecase);
}

static HAWK_INLINE int __cmp_bchr_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_bchr_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_bchr_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_int_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_int(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_int_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_int(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_int_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_bchr_int(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_int_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_int_t v1 = HAWK_RTX_GETINTFROMVAL(rtx, left);
	hawk_int_t v2 = HAWK_RTX_GETINTFROMVAL(rtx, right);
	return (v1 > v2)? 1: ((v1 < v2)? -1: 0);
}

static HAWK_INLINE int __cmp_int_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_int_t v1 = HAWK_RTX_GETINTFROMVAL(rtx, left);
	if (v1 > ((hawk_val_flt_t*)right)->val) return 1;
	if (v1 < ((hawk_val_flt_t*)right)->val) return -1;
	return 0;
}

static HAWK_INLINE int __cmp_int_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_t* hawk = hawk_rtx_gethawk(rtx);
	hawk_ooch_t* str0;
	hawk_oow_t len0;
	int n;

	/* SCO CC doesn't seem to handle right->v_nstr > 0 properly */
	if ((hawk->opt.trait & HAWK_NCMPONSTR) || right->v_nstr /*> 0*/)
	{
		hawk_int_t ll, v1;
		hawk_flt_t rr;

		n = hawk_oochars_to_num(
			HAWK_OOCHARS_TO_NUM_MAKE_OPTION(1, 0, HAWK_RTX_IS_STRIPSTRSPC_ON(rtx), 0),
			((hawk_val_str_t*)right)->val.ptr,
			((hawk_val_str_t*)right)->val.len,
			&ll, &rr
		);

		v1 = HAWK_RTX_GETINTFROMVAL(rtx, left);
		if (n == 0)
		{
			/* a numeric integral string */
			return (v1 > ll)? 1: ((v1 < ll)? -1: 0);
		}
		else if (n > 0)
		{
			/* a numeric floating-point string */
			return (v1 > rr)? 1: ((v1 < rr)? -1: 0);
		}
	}

	str0 = hawk_rtx_getvaloocstr(rtx, left, &len0);
	if (!str0) return CMP_ERROR;
	n = hawk_comp_oochars(str0, len0, ((hawk_val_str_t*)right)->val.ptr, ((hawk_val_str_t*)right)->val.len, rtx->gbl.ignorecase);
	hawk_rtx_freevaloocstr(rtx, left, str0);
	return n;
}

static HAWK_INLINE int __cmp_int_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_t* hawk = hawk_rtx_gethawk(rtx);
	hawk_bch_t* str0;
	hawk_oow_t len0;
	int n;

	if ((hawk->opt.trait & HAWK_NCMPONSTR) || right->v_nstr /*> 0*/)
	{
		hawk_int_t ll, v1;
		hawk_flt_t rr;

		n = hawk_bchars_to_num (
			HAWK_OOCHARS_TO_NUM_MAKE_OPTION(1, 0, HAWK_RTX_IS_STRIPSTRSPC_ON(rtx), 0),
			((hawk_val_mbs_t*)right)->val.ptr,
			((hawk_val_mbs_t*)right)->val.len,
			&ll, &rr
		);

		v1 = HAWK_RTX_GETINTFROMVAL(rtx, left);
		if (n == 0)
		{
			/* a numeric integral string */
			return (v1 > ll)? 1: ((v1 < ll)? -1: 0);
		}
		else if (n > 0)
		{
			/* a numeric floating-point string */
			return (v1 > rr)? 1: ((v1 < rr)? -1: 0);
		}
	}

	str0 = hawk_rtx_getvalbcstr(rtx, left, &len0);
	if (!str0) return -1;
	n = hawk_comp_bchars(str0, len0, ((hawk_val_mbs_t*)right)->val.ptr, ((hawk_val_mbs_t*)right)->val.len, rtx->gbl.ignorecase);
	hawk_rtx_freevalbcstr(rtx, left, str0);
	return n;
}

static HAWK_INLINE int __cmp_int_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_int_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_int_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}
/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_flt_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_flt(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_flt_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_flt(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_flt_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_bchr_flt(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_flt_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_int_flt(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_flt_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	if (((hawk_val_flt_t*)left)->val > ((hawk_val_flt_t*)right)->val) return 1;
	if (((hawk_val_flt_t*)left)->val < ((hawk_val_flt_t*)right)->val) return -1;
	return 0;
}

static HAWK_INLINE int __cmp_flt_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_t* hawk = hawk_rtx_gethawk(rtx);
	hawk_ooch_t* str0;
	hawk_oow_t len0;
	int n;

	/* SCO CC doesn't seem to handle right->v_nstr > 0 properly */
	if ((hawk->opt.trait & HAWK_NCMPONSTR) || right->v_nstr /*> 0*/)
	{
		const hawk_ooch_t* end;
		hawk_flt_t rr;

		rr = hawk_oochars_to_flt(((hawk_val_str_t*)right)->val.ptr, ((hawk_val_str_t*)right)->val.len, &end, HAWK_RTX_IS_STRIPSTRSPC_ON(rtx));
		if (end == ((hawk_val_str_t*)right)->val.ptr + ((hawk_val_str_t*)right)->val.len)
		{
			return (((hawk_val_flt_t*)left)->val > rr)? 1:
			       (((hawk_val_flt_t*)left)->val < rr)? -1: 0;
		}
	}

	str0 = hawk_rtx_getvaloocstr(rtx, left, &len0);
	if (!str0) return CMP_ERROR;
	n = hawk_comp_oochars(str0, len0, ((hawk_val_str_t*)right)->val.ptr, ((hawk_val_str_t*)right)->val.len, rtx->gbl.ignorecase);
	hawk_rtx_freevaloocstr(rtx, left, str0);
	return n;
}

static HAWK_INLINE int __cmp_flt_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_t* hawk = hawk_rtx_gethawk(rtx);
	hawk_bch_t* str0;
	hawk_oow_t len0;
	int n;

	if ((hawk->opt.trait & HAWK_NCMPONSTR) || right->v_nstr /*> 0*/)
	{
		const hawk_bch_t* end;
		hawk_flt_t rr;

		rr = hawk_bchars_to_flt(((hawk_val_mbs_t*)right)->val.ptr, ((hawk_val_mbs_t*)right)->val.len, &end, HAWK_RTX_IS_STRIPSTRSPC_ON(rtx));
		if (end == ((hawk_val_mbs_t*)right)->val.ptr + ((hawk_val_mbs_t*)right)->val.len)
		{
			return (((hawk_val_flt_t*)left)->val > rr)? 1:
			       (((hawk_val_flt_t*)left)->val < rr)? -1: 0;
		}
	}

	str0 = hawk_rtx_getvalbcstr(rtx, left, &len0);
	if (HAWK_UNLIKELY(!str0)) return CMP_ERROR;
	n = hawk_comp_bchars(str0, len0, ((hawk_val_mbs_t*)right)->val.ptr, ((hawk_val_mbs_t*)right)->val.len, rtx->gbl.ignorecase);
	hawk_rtx_freevalbcstr(rtx, left, str0);
	return n;
}

static HAWK_INLINE int __cmp_flt_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_flt_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_flt_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_str_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_str(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_str_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_str(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_str_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_bchr_str(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_str_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_int_str(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_str_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_flt_str(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_str_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_val_str_t* ls, * rs;
	int stripspc;

	ls = (hawk_val_str_t*)left;
	rs = (hawk_val_str_t*)right;

	if (HAWK_LIKELY(ls->v_nstr == 0 || rs->v_nstr == 0))
	{
		/* both are definitely strings */
		return hawk_comp_oochars(ls->val.ptr, ls->val.len, rs->val.ptr, rs->val.len, rtx->gbl.ignorecase);
	}

	stripspc = HAWK_RTX_IS_STRIPSTRSPC_ON(rtx);

	if (ls->v_nstr == 1)
	{
		hawk_int_t ll;

		ll = hawk_oochars_to_int(ls->val.ptr, ls->val.len, HAWK_OOCHARS_TO_INT_MAKE_OPTION(stripspc, stripspc, 0), HAWK_NULL, HAWK_NULL);

		if (rs->v_nstr == 1)
		{
			hawk_int_t rr;

			rr = hawk_oochars_to_int(rs->val.ptr, rs->val.len, HAWK_OOCHARS_TO_INT_MAKE_OPTION(stripspc, stripspc, 0), HAWK_NULL, HAWK_NULL);

			return (ll > rr)? 1:
			       (ll < rr)? -1: 0;
		}
		else
		{
			hawk_flt_t rr;

			HAWK_ASSERT(rs->v_nstr == 2);

			rr = hawk_oochars_to_flt(rs->val.ptr, rs->val.len, HAWK_NULL, stripspc);

			return (ll > rr)? 1:
			       (ll < rr)? -1: 0;
		}
	}
	else
	{
		hawk_flt_t ll;

		HAWK_ASSERT(ls->v_nstr == 2);

		ll = hawk_oochars_to_flt(ls->val.ptr, ls->val.len, HAWK_NULL, stripspc);

		if (rs->v_nstr == 1)
		{
			hawk_int_t rr;

			rr = hawk_oochars_to_int(rs->val.ptr, rs->val.len, HAWK_OOCHARS_TO_INT_MAKE_OPTION(stripspc, stripspc, 0), HAWK_NULL, HAWK_NULL);

			return (ll > rr)? 1:
			       (ll < rr)? -1: 0;
		}
		else
		{
			hawk_flt_t rr;

			HAWK_ASSERT(rs->v_nstr == 2);

			rr = hawk_oochars_to_flt(rs->val.ptr, rs->val.len, HAWK_NULL, stripspc);

			return (ll > rr)? 1:
			       (ll < rr)? -1: 0;
		}
	}
}

static HAWK_INLINE int __cmp_str_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_val_str_t* ls = (hawk_val_str_t*)left;
	hawk_val_mbs_t* rs = (hawk_val_mbs_t*)right;

#if (HAWK_SIZEOF_BCH_T != HAWK_SIZEOF_UINT8_T)
#	error Unsupported size of hawk_bch_t
#endif

#if defined(HAWK_OOCH_IS_BCH)
	return hawk_comp_bchars(ls->val.ptr, ls->val.len, rs->val.ptr, rs->val.len, rtx->gbl.ignorecase);
#else
	hawk_bch_t* mbsptr;
	hawk_oow_t mbslen;
	int n;

	mbsptr = hawk_rtx_duputobchars(rtx, ls->val.ptr, ls->val.len, &mbslen);
	if (!mbsptr) return CMP_ERROR;
	n = hawk_comp_bchars(mbsptr, mbslen, (const hawk_bch_t*)rs->val.ptr, rs->val.len, rtx->gbl.ignorecase);
	hawk_rtx_freemem(rtx, mbsptr);
	return n;
#endif
}

static HAWK_INLINE int __cmp_str_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_str_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_str_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}


/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_mbs_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_mbs(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_mbs_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_mbs(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_mbs_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_bchr_mbs(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_mbs_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_int_mbs(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_mbs_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_flt_mbs(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_mbs_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_str_mbs(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_mbs_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	hawk_val_mbs_t* ls = (hawk_val_mbs_t*)left;
	hawk_val_mbs_t* rs = (hawk_val_mbs_t*)right;
#if (HAWK_SIZEOF_BCH_T != HAWK_SIZEOF_UINT8_T)
#	error Unsupported size of hawk_bch_t
#endif
	return hawk_comp_bchars(ls->val.ptr, ls->val.len, rs->val.ptr, rs->val.len, rtx->gbl.ignorecase);
}

static HAWK_INLINE int __cmp_mbs_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_mbs_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_mbs_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_fun_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return (((hawk_val_fun_t*)left)->fun == ((hawk_val_fun_t*)right)->fun)? 0: __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_fun_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_map_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_map(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_map_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_map(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_map_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_bchr_map(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_map_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_int_map(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_map_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_flt_map(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_map_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_map_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_map_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_map_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/* can't compare a map with a map */
	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
	return CMP_ERROR;
}

static HAWK_INLINE int __cmp_map_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/* can't compare a map with an array */
	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
	return CMP_ERROR;
}

/* -------------------------------------------------------------------- */

static HAWK_INLINE int __cmp_arr_nil (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_nil_arr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_arr_char (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_char_arr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_arr_bchr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_bchr_arr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_arr_int (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_int_arr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_arr_flt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	int n;
	n = __cmp_flt_arr(rtx, right, left, inverse_cmp_op(op_hint));
	if (n == CMP_ERROR) return CMP_ERROR;
	return -n;
}

static HAWK_INLINE int __cmp_arr_str (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_arr_mbs (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_arr_fun (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	return __cmp_ensure_not_equal(rtx, op_hint);
}

static HAWK_INLINE int __cmp_arr_map (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/* can't compare a map with a map */
	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
	return CMP_ERROR;
}

static HAWK_INLINE int __cmp_arr_arr (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint)
{
	/* can't compare a map with an array */
	hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
	return CMP_ERROR;
}

/* -------------------------------------------------------------------- */


static HAWK_INLINE int __cmp_val (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, cmp_op_t op_hint, int* ret)
{
	int n;
	hawk_val_type_t lvtype, rvtype;
	typedef int (*cmp_val_t) (hawk_rtx_t*, hawk_val_t*, hawk_val_t*, cmp_op_t op_hint);

	static cmp_val_t func[] =
	{
		/* this table must be synchronized with the HAWK_VAL_XXX values in hawk.h */
		__cmp_nil_nil,   __cmp_nil_char,   __cmp_nil_bchr,   __cmp_nil_int,   __cmp_nil_flt,   __cmp_nil_str,   __cmp_nil_mbs,   __cmp_nil_fun,   __cmp_nil_map,   __cmp_nil_arr,
		__cmp_char_nil,  __cmp_char_char,  __cmp_char_bchr,  __cmp_char_int,  __cmp_char_flt,  __cmp_char_str,  __cmp_char_mbs,  __cmp_char_fun,  __cmp_char_map,  __cmp_char_arr,
		__cmp_bchr_nil,  __cmp_bchr_char,  __cmp_bchr_bchr,  __cmp_bchr_int,  __cmp_bchr_flt,  __cmp_bchr_str,  __cmp_bchr_mbs,  __cmp_bchr_fun,  __cmp_bchr_map,  __cmp_bchr_arr,
		__cmp_int_nil,   __cmp_int_char,   __cmp_int_bchr,   __cmp_int_int,   __cmp_int_flt,   __cmp_int_str,   __cmp_int_mbs,   __cmp_int_fun,   __cmp_int_map,   __cmp_int_arr,
		__cmp_flt_nil,   __cmp_flt_char,   __cmp_flt_bchr,   __cmp_flt_int,   __cmp_flt_flt,   __cmp_flt_str,   __cmp_flt_mbs,   __cmp_flt_fun,   __cmp_flt_map,   __cmp_flt_arr,
		__cmp_str_nil,   __cmp_str_char,   __cmp_str_bchr,   __cmp_str_int,   __cmp_str_flt,   __cmp_str_str,   __cmp_str_mbs,   __cmp_str_fun,   __cmp_str_map,   __cmp_str_arr,
		__cmp_mbs_nil,   __cmp_mbs_char,   __cmp_mbs_bchr,   __cmp_mbs_int,   __cmp_mbs_flt,   __cmp_mbs_str,   __cmp_mbs_mbs,   __cmp_mbs_fun,   __cmp_mbs_map,   __cmp_mbs_arr,
		__cmp_fun_nil,   __cmp_fun_char,   __cmp_fun_bchr,   __cmp_fun_int,   __cmp_fun_flt,   __cmp_fun_str,   __cmp_fun_mbs,   __cmp_fun_fun,   __cmp_fun_map,   __cmp_fun_arr,
		__cmp_map_nil,   __cmp_map_char,   __cmp_map_bchr,   __cmp_map_int,   __cmp_map_flt,   __cmp_map_str,   __cmp_map_mbs,   __cmp_map_fun,   __cmp_map_map,   __cmp_map_arr,
		__cmp_arr_nil,   __cmp_arr_char,   __cmp_arr_bchr,   __cmp_arr_int,   __cmp_arr_flt,   __cmp_arr_str,   __cmp_arr_mbs,   __cmp_arr_fun,   __cmp_arr_map,   __cmp_arr_arr
	};

	lvtype = HAWK_RTX_GETVALTYPE(rtx, left);
	rvtype = HAWK_RTX_GETVALTYPE(rtx, right);
	if (!(rtx->hawk->opt.trait & HAWK_FLEXMAP) && (lvtype == HAWK_VAL_MAP || rvtype == HAWK_VAL_MAP || lvtype == HAWK_VAL_ARR || rvtype == HAWK_VAL_ARR))
	{
		/* a map can't be compared againt other values in the non-flexible mode. */
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return -1;
	}

	HAWK_ASSERT(lvtype >= HAWK_VAL_NIL && lvtype <= HAWK_VAL_ARR);
	HAWK_ASSERT(rvtype >= HAWK_VAL_NIL && rvtype <= HAWK_VAL_ARR);

	/* mapping fomula and table layout assume:
	 *  HAWK_VAL_NIL  = 0
	 *  HAWK_VAL_CHAR = 1
	 *  HAWK_VAL_BCHR = 2
	 *  HAWK_VAL_INT  = 3
	 *  HAWK_VAL_FLT  = 4
	 *  HAWK_VAL_STR  = 5
	 *  HAWK_VAL_MBS  = 6
	 *  HAWK_VAL_FUN  = 7
	 *  HAWK_VAL_MAP  = 8
	 *  HAWK_VAL_ARR  = 9
	 *
	 * op_hint indicate the operation in progress when this function is called.
	 * this hint doesn't require the comparison function to compare using this
	 * operation. the comparision function should return 0 if equal, -1 if less,
	 * 1 if greater, CMP_ERROR upon error regardless of this hint.
	 */
	n = func[lvtype * 10 + rvtype](rtx, left, right, op_hint);
	if (n == CMP_ERROR) return -1;

	*ret = n;
	return 0;
}

int hawk_rtx_cmpval (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right, int* ret)
{
	return __cmp_val(rtx, left, right, CMP_OP_NONE, ret);
}

static int teq_val (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	hawk_val_type_t lt, rt;

	if (left == right) n = 1;
	else
	{
		lt = HAWK_RTX_GETVALTYPE(rtx, left);
		rt = HAWK_RTX_GETVALTYPE(rtx, right);

		if (lt != rt) n = 0;
		else
		{
			switch (lt)
			{
				case HAWK_VAL_NIL:
					n = 1;
					break;

				case HAWK_VAL_CHAR:
					/* since a CHAR value is only reprensented in the value pointer,
					 * n is guaranteed to be 0 here. so the following check isn't needed */
					n = (HAWK_RTX_GETCHARFROMVAL(rtx, left) == HAWK_RTX_GETCHARFROMVAL(rtx, right));
					break;

				case HAWK_VAL_BCHR:
					 /* since a BCHR value is only reprensented in the value pointer,
					 * n is guaranteed to be 0 here. so the following check isn't needed */
					n = (HAWK_RTX_GETBCHRFROMVAL(rtx, left) == HAWK_RTX_GETBCHRFROMVAL(rtx, right));
					break;

				case HAWK_VAL_INT:
					n = (HAWK_RTX_GETINTFROMVAL(rtx, left) == HAWK_RTX_GETINTFROMVAL(rtx, right));
					break;

				case HAWK_VAL_FLT:
					n = ((hawk_val_flt_t*)left)->val == ((hawk_val_flt_t*)right)->val;
					break;

				case HAWK_VAL_STR:
					n = hawk_comp_oochars(
						((hawk_val_str_t*)left)->val.ptr,
						((hawk_val_str_t*)left)->val.len,
						((hawk_val_str_t*)right)->val.ptr,
						((hawk_val_str_t*)right)->val.len,
						rtx->gbl.ignorecase) == 0;
					break;

				case HAWK_VAL_MBS:
					n = hawk_comp_bchars(
						((hawk_val_mbs_t*)left)->val.ptr,
						((hawk_val_mbs_t*)left)->val.len,
						((hawk_val_mbs_t*)right)->val.ptr,
						((hawk_val_mbs_t*)right)->val.len,
						rtx->gbl.ignorecase) == 0;
					break;

				case HAWK_VAL_FUN:
					n = ((hawk_val_fun_t*)left)->fun == ((hawk_val_fun_t*)right)->fun;
					break;

				default:
					/* map-x and map-y are always different regardless of
					 * their contents. however, if they are pointing to the
					 * same map value, it won't reach here but will be
					 * handled by the first check in this function */
					n = 0;
					break;
			}
		}
	}

	return n;
}

static hawk_val_t* eval_binop_teq (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	return teq_val(rtx, left, right)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_tne (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	return teq_val(rtx, left, right)? HAWK_VAL_ZERO: HAWK_VAL_ONE;
}

static hawk_val_t* eval_binop_eq (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	if (__cmp_val(rtx, left, right, CMP_OP_EQ, &n) <= -1) return HAWK_NULL;
	return (n == 0)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_ne (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	if (__cmp_val(rtx, left, right, CMP_OP_NE, &n) <= -1) return HAWK_NULL;
	return (n != 0)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_gt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	if (__cmp_val(rtx, left, right, CMP_OP_GT, &n) <= -1) return HAWK_NULL;
	return (n > 0)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_ge (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	if (__cmp_val(rtx, left, right, CMP_OP_GE, &n) <= -1) return HAWK_NULL;
	return (n >= 0)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_lt (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	if (__cmp_val(rtx, left, right, CMP_OP_LT, &n) <= -1) return HAWK_NULL;
	return (n < 0)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_le (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n;
	if (__cmp_val(rtx, left, right, CMP_OP_LE, &n) <= -1) return HAWK_NULL;
	return (n <= 0)? HAWK_VAL_ONE: HAWK_VAL_ZERO;
}

static hawk_val_t* eval_binop_lshift (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	hawk_int_t l1, l2;

	if (hawk_rtx_valtoint(rtx, left, &l1) <= -1 ||
	    hawk_rtx_valtoint(rtx, right, &l2) <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	return hawk_rtx_makeintval(rtx, l1 << l2);
}

static hawk_val_t* eval_binop_rshift (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	hawk_int_t l1, l2;

	if (hawk_rtx_valtoint(rtx, left, &l1) <= -1 ||
	    hawk_rtx_valtoint(rtx, right, &l2) <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	return hawk_rtx_makeintval(rtx, l1 >> l2);
}

static hawk_val_t* eval_binop_plus (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2;

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}
	/*
	n1  n2    n3
	0   0   = 0
	1   0   = 1
	0   1   = 2
	1   1   = 3
	*/
	n3 = n1 + (n2 << 1);
	HAWK_ASSERT(n3 >= 0 && n3 <= 3);

	return (n3 == 0)? hawk_rtx_makeintval(rtx,(hawk_int_t)l1+(hawk_int_t)l2):
	       (n3 == 1)? hawk_rtx_makefltval(rtx,(hawk_flt_t)r1+(hawk_flt_t)l2):
	       (n3 == 2)? hawk_rtx_makefltval(rtx,(hawk_flt_t)l1+(hawk_flt_t)r2):
	                  hawk_rtx_makefltval(rtx,(hawk_flt_t)r1+(hawk_flt_t)r2);
}

static hawk_val_t* eval_binop_minus (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2;

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	n3 = n1 + (n2 << 1);
	HAWK_ASSERT(n3 >= 0 && n3 <= 3);
	return (n3 == 0)? hawk_rtx_makeintval(rtx,(hawk_int_t)l1-(hawk_int_t)l2):
	       (n3 == 1)? hawk_rtx_makefltval(rtx,(hawk_flt_t)r1-(hawk_flt_t)l2):
	       (n3 == 2)? hawk_rtx_makefltval(rtx,(hawk_flt_t)l1-(hawk_flt_t)r2):
	                  hawk_rtx_makefltval(rtx,(hawk_flt_t)r1-(hawk_flt_t)r2);
}

static hawk_val_t* eval_binop_mul (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2;

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	n3 = n1 + (n2 << 1);
	HAWK_ASSERT(n3 >= 0 && n3 <= 3);
	return (n3 == 0)? hawk_rtx_makeintval(rtx,(hawk_int_t)l1*(hawk_int_t)l2):
	       (n3 == 1)? hawk_rtx_makefltval(rtx,(hawk_flt_t)r1*(hawk_flt_t)l2):
	       (n3 == 2)? hawk_rtx_makefltval(rtx,(hawk_flt_t)l1*(hawk_flt_t)r2):
	                  hawk_rtx_makefltval(rtx,(hawk_flt_t)r1*(hawk_flt_t)r2);
}

static hawk_val_t* eval_binop_div (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2;
	hawk_val_t* res;

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	n3 = n1 + (n2 << 1);
	switch (n3)
	{
		case 0:
			if  (l2 == 0)
			{
				hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EDIVBY0);
				return HAWK_NULL;
			}

			if ((l1 % l2) == 0)
			{
				res = hawk_rtx_makeintval(rtx, l1 / l2);
			}
			else
			{
				res = hawk_rtx_makefltval(rtx, (hawk_flt_t)l1 / (hawk_flt_t)l2);
			}
			break;

		case 1:
			res = hawk_rtx_makefltval(rtx, r1 / (hawk_flt_t)l2);
			break;

		case 2:
			res = hawk_rtx_makefltval(rtx, (hawk_flt_t)l1 / r2);
			break;

		case 3:
			res = hawk_rtx_makefltval(rtx, r1 / r2);
			break;
	}

	return res;
}

static hawk_val_t* eval_binop_idiv (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2, quo;
	hawk_val_t* res;

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	n3 = n1 + (n2 << 1);
	switch (n3)
	{
		case 0:
			if (l2 == 0)
			{
				hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EDIVBY0);
				return HAWK_NULL;
			}
			res = hawk_rtx_makeintval(rtx, l1 / l2);
			break;

		case 1:
			quo = r1 / (hawk_flt_t)l2;
			res = hawk_rtx_makeintval(rtx, (hawk_int_t)quo);
			break;

		case 2:
			quo = (hawk_flt_t)l1 / r2;
			res = hawk_rtx_makeintval(rtx, (hawk_int_t)quo);
			break;

		case 3:
			quo = r1 / r2;
			res = hawk_rtx_makeintval(rtx, (hawk_int_t)quo);
			break;
	}

	return res;
}

static hawk_val_t* eval_binop_mod (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2;
	hawk_val_t* res;

	/* the mod function must be provided when the hawk object is created to be able to calculate floating-pointer remainder */
	HAWK_ASSERT(rtx->hawk->prm.math.mod != HAWK_NULL);

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	n3 = n1 + (n2 << 1);
	switch (n3)
	{
		case 0:
			if (l2 == 0)
			{
				hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EDIVBY0);
				return HAWK_NULL;
			}
			res = hawk_rtx_makeintval(rtx, l1 % l2);
			break;

		case 1:
			res = hawk_rtx_makefltval(rtx, rtx->hawk->prm.math.mod(hawk_rtx_gethawk(rtx), r1, (hawk_flt_t)l2));
			break;

		case 2:
			res = hawk_rtx_makefltval(rtx, rtx->hawk->prm.math.mod(hawk_rtx_gethawk(rtx), (hawk_flt_t)l1, r2));
			break;

		case 3:
			res = hawk_rtx_makefltval(rtx, rtx->hawk->prm.math.mod(hawk_rtx_gethawk(rtx), (hawk_flt_t)r1, (hawk_flt_t)r2));
			break;
	}

	return res;
}

static hawk_val_t* eval_binop_exp (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	int n1, n2, n3;
	hawk_int_t l1, l2;
	hawk_flt_t r1, r2;
	hawk_val_t* res;

	n1 = hawk_rtx_valtonum(rtx, left, &l1, &r1);
	n2 = hawk_rtx_valtonum(rtx, right, &l2, &r2);

	if (n1 <= -1 || n2 <= -1)
	{
		hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	n3 = n1 + (n2 << 1);
	switch (n3)
	{
		case 0:
			/* left - int, right - int */
			if (l2 >= 0)
			{
				hawk_int_t v = 1;
				while (l2-- > 0) v *= l1;
				res = hawk_rtx_makeintval(rtx, v);
			}
			else if (l1 == 0)
			{
				hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EDIVBY0);
				return HAWK_NULL;
			}
			else
			{
				hawk_flt_t v = 1.0;
				l2 *= -1;
				while (l2-- > 0) v /= l1;
				res = hawk_rtx_makefltval(rtx, v);
			}
			break;

		case 1:
			/* left - real, right - int */
			if (l2 >= 0)
			{
				hawk_flt_t v = 1.0;
				while (l2-- > 0) v *= r1;
				res = hawk_rtx_makefltval(rtx, v);
			}
			else if (r1 == 0.0)
			{
				hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EDIVBY0);
				return HAWK_NULL;
			}
			else
			{
				hawk_flt_t v = 1.0;
				l2 *= -1;
				while (l2-- > 0) v /= r1;
				res = hawk_rtx_makefltval(rtx, v);
			}
			break;

		case 2:
			/* left - int, right - real */
			res = hawk_rtx_makefltval(
				rtx,
				rtx->hawk->prm.math.pow(hawk_rtx_gethawk(rtx), (hawk_flt_t)l1, r2)
			);
			break;

		case 3:
			/* left - real, right - real */
			res = hawk_rtx_makefltval(
				rtx,
				rtx->hawk->prm.math.pow(hawk_rtx_gethawk(rtx), r1, r2)
			);
			break;
	}

	return res;
}

static hawk_val_t* eval_binop_concat (hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right)
{
	hawk_val_t* res;

	switch (HAWK_RTX_GETVALTYPE(rtx, left))
	{
		case HAWK_VAL_BCHR:
		case HAWK_VAL_MBS:
		case HAWK_VAL_BOB:
		{
			hawk_bcs_t l, r;

			l.ptr = hawk_rtx_getvalbcstr(rtx, left, &l.len);
			if (HAWK_UNLIKELY(!l.ptr)) return HAWK_NULL;

			r.ptr = hawk_rtx_getvalbcstr(rtx, right, &r.len);
			if (HAWK_UNLIKELY(!r.ptr))
			{
				hawk_rtx_freevalbcstr(rtx, left, l.ptr);
				return HAWK_NULL;
			}

			res = (hawk_val_t*)hawk_rtx_makembsvalwithbchars2(rtx, l.ptr, l.len, r.ptr, r.len);

			hawk_rtx_freevalbcstr(rtx, right, r.ptr);
			hawk_rtx_freevalbcstr(rtx, left, l.ptr);
			break;
		}

		default:
		{
			hawk_oocs_t l, r;

			l.ptr = hawk_rtx_getvaloocstr(rtx, left, &l.len);
			if (HAWK_UNLIKELY(!l.ptr)) return HAWK_NULL;

			r.ptr = hawk_rtx_getvaloocstr(rtx, right, &r.len);
			if (HAWK_UNLIKELY(!r.ptr))
			{
				hawk_rtx_freevaloocstr(rtx, left, l.ptr);
				return HAWK_NULL;
			}

			res = (hawk_val_t*)hawk_rtx_makestrvalwithoochars2(rtx, l.ptr, l.len, r.ptr, r.len);

			hawk_rtx_freevaloocstr(rtx, right, r.ptr);
			hawk_rtx_freevaloocstr(rtx, left, l.ptr);
			break;
		}
	}

	return res;
}

static hawk_val_t* eval_binop_match0 (
	hawk_rtx_t* rtx, hawk_val_t* left, hawk_val_t* right,
	const hawk_loc_t* lloc, const hawk_loc_t* rloc, int ret)
{
	hawk_val_t* res;
	hawk_oocs_t out;
	int n;

	out.ptr = hawk_rtx_getvaloocstr(rtx, left, &out.len);
	if (HAWK_UNLIKELY(!out.ptr)) return HAWK_NULL;

	n = hawk_rtx_matchvalwithoocs(rtx, right, &out, &out, HAWK_NULL, HAWK_NULL);
	hawk_rtx_freevaloocstr(rtx, left, out.ptr);

	if (HAWK_UNLIKELY(n <= -1))
	{
		ADJERR_LOC(rtx, lloc);
		return HAWK_NULL;
	}

	res = hawk_rtx_makeintval(rtx, (n == ret));
	if (HAWK_UNLIKELY(!res))
	{
		ADJERR_LOC(rtx, lloc);
		return HAWK_NULL;
	}

	return res;
}

static hawk_val_t* eval_binop_ma (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right)
{
	hawk_val_t* lv, * rv, * res;

	HAWK_ASSERT(left->next == HAWK_NULL);
	HAWK_ASSERT(right->next == HAWK_NULL);

	lv = eval_expression(rtx, left);
	if (HAWK_UNLIKELY(!lv)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, lv);

	rv = eval_expression0(rtx, right);
	if (HAWK_UNLIKELY(!rv))
	{
		hawk_rtx_refdownval(rtx, lv);
		return HAWK_NULL;
	}

	hawk_rtx_refupval(rtx, rv);

	res = eval_binop_match0(rtx, lv, rv, &left->loc, &right->loc, 1);

	hawk_rtx_refdownval(rtx, rv);
	hawk_rtx_refdownval(rtx, lv);

	return res;
}

static hawk_val_t* eval_binop_nm (hawk_rtx_t* rtx, hawk_nde_t* left, hawk_nde_t* right)
{
	hawk_val_t* lv, * rv, * res;

	HAWK_ASSERT(left->next == HAWK_NULL);
	HAWK_ASSERT(right->next == HAWK_NULL);

	lv = eval_expression(rtx, left);
	if (HAWK_UNLIKELY(!lv)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, lv);

	rv = eval_expression0(rtx, right);
	if (HAWK_UNLIKELY(!rv))
	{
		hawk_rtx_refdownval(rtx, lv);
		return HAWK_NULL;
	}

	hawk_rtx_refupval(rtx, rv);

	res = eval_binop_match0(rtx, lv, rv, &left->loc, &right->loc, 0);

	hawk_rtx_refdownval(rtx, rv);
	hawk_rtx_refdownval(rtx, lv);

	return res;
}

static hawk_val_t* eval_unary (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* left, * res = HAWK_NULL;
	hawk_nde_exp_t* exp = (hawk_nde_exp_t*)nde;
	int n;
	hawk_int_t l;
	hawk_flt_t r;

	HAWK_ASSERT(
		exp->type == HAWK_NDE_EXP_UNR);
	HAWK_ASSERT(
		exp->left != HAWK_NULL && exp->right == HAWK_NULL);
	HAWK_ASSERT(
		exp->opcode == HAWK_UNROP_PLUS ||
		exp->opcode == HAWK_UNROP_MINUS ||
		exp->opcode == HAWK_UNROP_LNOT ||
		exp->opcode == HAWK_UNROP_BNOT);

	HAWK_ASSERT(exp->left->next == HAWK_NULL);
	left = eval_expression(rtx, exp->left);
	if (HAWK_UNLIKELY(!left)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, left);

	switch (exp->opcode)
	{
		case HAWK_UNROP_MINUS:
			n = hawk_rtx_valtonum(rtx, left, &l, &r);
			if (HAWK_UNLIKELY(n <= -1)) goto exit_func;

			res = (n == 0)? hawk_rtx_makeintval(rtx, -l):
			                hawk_rtx_makefltval(rtx, -r);
			break;

		case HAWK_UNROP_LNOT:
			if (HAWK_RTX_GETVALTYPE(rtx, left) == HAWK_VAL_STR)
			{
				/* 0 if the string length is greater than 0.
				 * 1 if it's empty */
				res = hawk_rtx_makeintval(rtx, !(((hawk_val_str_t*)left)->val.len > 0));
			}
			else
			{
				n = hawk_rtx_valtonum(rtx, left, &l, &r);
				if (HAWK_UNLIKELY(n <= -1)) goto exit_func;

				res = (n == 0)? hawk_rtx_makeintval(rtx, !l):
				                hawk_rtx_makefltval(rtx, !r);
			}
			break;

		case HAWK_UNROP_BNOT:
			n = hawk_rtx_valtoint(rtx, left, &l);
			if (HAWK_UNLIKELY(n <= -1)) goto exit_func;

			res = hawk_rtx_makeintval(rtx, ~l);
			break;

		case HAWK_UNROP_PLUS:
			n = hawk_rtx_valtonum(rtx, left, &l, &r);
			if (HAWK_UNLIKELY(n <= -1)) goto exit_func;

			res = (n == 0)? hawk_rtx_makeintval(rtx, l):
			                hawk_rtx_makefltval(rtx, r);
			break;
	}

exit_func:
	hawk_rtx_refdownval(rtx, left);
	if (HAWK_UNLIKELY(!res)) ADJERR_LOC(rtx, &nde->loc);
	return res;
}

static hawk_val_t* eval_incpre (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* left, * res;
	hawk_val_type_t left_vtype;
	hawk_int_t inc_val_int;
	hawk_flt_t inc_val_flt;
	hawk_nde_exp_t* exp = (hawk_nde_exp_t*)nde;

	HAWK_ASSERT(exp->type == HAWK_NDE_EXP_INCPRE);
	HAWK_ASSERT(exp->left != HAWK_NULL && exp->right == HAWK_NULL);

	/* this way of checking if the l-value is assignable is
	 * ugly as it is dependent on the node types defined in hawk.h
	 * but let's keep going this way for the time being. */
	if (exp->left->type < HAWK_NDE_NAMED ||
	    /*exp->left->type > HAWK_NDE_ARGIDX) XXX */
	    exp->left->type > HAWK_NDE_POS)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	if (exp->opcode == HAWK_INCOP_PLUS)
	{
		inc_val_int = 1;
		inc_val_flt = 1.0;
	}
	else if (exp->opcode == HAWK_INCOP_MINUS)
	{
		inc_val_int = -1;
		inc_val_flt = -1.0;
	}
	else
	{
		HAWK_ASSERT(!"should never happen - invalid opcode");
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EINTERN);
		return HAWK_NULL;
	}

	HAWK_ASSERT(exp->left->next == HAWK_NULL);
	left = eval_expression(rtx, exp->left);
	if (HAWK_UNLIKELY(!left)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, left);
	left_vtype = HAWK_RTX_GETVALTYPE(rtx, left);

	switch (left_vtype)
	{
		case HAWK_VAL_INT:
		{
			hawk_int_t r = HAWK_RTX_GETINTFROMVAL(rtx, left);
			res = hawk_rtx_makeintval(rtx, r + inc_val_int);
			if (HAWK_UNLIKELY(!res))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}
			break;
		}

		case HAWK_VAL_FLT:
		{
			hawk_flt_t r = ((hawk_val_flt_t*)left)->val;
			res = hawk_rtx_makefltval(rtx, r + inc_val_flt);
			if (HAWK_UNLIKELY(!res))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}
			break;
		}

		default:
		{
			hawk_int_t v1;
			hawk_flt_t v2;
			int n;

			n = hawk_rtx_valtonum(rtx, left, &v1, &v2);
			if (HAWK_UNLIKELY(n <= -1))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			if (n == 0)
			{
				res = hawk_rtx_makeintval(rtx, v1 + inc_val_int);
			}
			else /* if (n == 1) */
			{
				HAWK_ASSERT(n == 1);
				res = hawk_rtx_makefltval(rtx, v2 + inc_val_flt);
			}

			if (HAWK_UNLIKELY(!res))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			break;
		}
	}

	if (HAWK_UNLIKELY(do_assignment(rtx, exp->left, res) == HAWK_NULL))
	{
		hawk_rtx_refdownval(rtx, left);
		return HAWK_NULL;
	}

	hawk_rtx_refdownval(rtx, left);
	return res;
}

static hawk_val_t* eval_incpst (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* left, * res, * res2;
	hawk_val_type_t left_vtype;
	hawk_int_t inc_val_int;
	hawk_flt_t inc_val_flt;
	hawk_nde_exp_t* exp = (hawk_nde_exp_t*)nde;

	HAWK_ASSERT(exp->type == HAWK_NDE_EXP_INCPST);
	HAWK_ASSERT(exp->left != HAWK_NULL && exp->right == HAWK_NULL);

	/* this way of checking if the l-value is assignable is
	 * ugly as it is dependent on the node types defined in hawk.h.
	 * but let's keep going this way for the time being. */
	if (exp->left->type < HAWK_NDE_NAMED ||
	    /*exp->left->type > HAWK_NDE_ARGIDX) XXX */
	    exp->left->type > HAWK_NDE_POS)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EOPERAND);
		return HAWK_NULL;
	}

	if (exp->opcode == HAWK_INCOP_PLUS)
	{
		inc_val_int = 1;
		inc_val_flt = 1.0;
	}
	else if (exp->opcode == HAWK_INCOP_MINUS)
	{
		inc_val_int = -1;
		inc_val_flt = -1.0;
	}
	else
	{
		HAWK_ASSERT(!"should never happen - invalid opcode");
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EINTERN);
		return HAWK_NULL;
	}

	HAWK_ASSERT(exp->left->next == HAWK_NULL);
	left = eval_expression(rtx, exp->left);
	if (HAWK_UNLIKELY(!left)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, left);

	left_vtype = HAWK_RTX_GETVALTYPE(rtx, left);

	switch (left_vtype)
	{
		case HAWK_VAL_INT:
		{
			hawk_int_t r = HAWK_RTX_GETINTFROMVAL(rtx, left);
			res = hawk_rtx_makeintval(rtx, r);
			if (HAWK_UNLIKELY(!res))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			res2 = hawk_rtx_makeintval(rtx, r + inc_val_int);
			if (HAWK_UNLIKELY(!res2))
			{
				hawk_rtx_refdownval(rtx, left);
				hawk_rtx_freeval(rtx, res, HAWK_RTX_FREEVAL_CACHE);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			break;
		}

		case HAWK_VAL_FLT:
		{
			hawk_flt_t r = ((hawk_val_flt_t*)left)->val;
			res = hawk_rtx_makefltval(rtx, r);
			if (HAWK_UNLIKELY(!res))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			res2 = hawk_rtx_makefltval(rtx, r + inc_val_flt);
			if (HAWK_UNLIKELY(!res2))
			{
				hawk_rtx_refdownval(rtx, left);
				hawk_rtx_freeval(rtx, res, HAWK_RTX_FREEVAL_CACHE);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			break;
		}

		default:
		{
			hawk_int_t v1;
			hawk_flt_t v2;
			int n;

			n = hawk_rtx_valtonum(rtx, left, &v1, &v2);
			if (HAWK_UNLIKELY(n <= -1))
			{
				hawk_rtx_refdownval(rtx, left);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			if (n == 0)
			{
				res = hawk_rtx_makeintval(rtx, v1);
				if (HAWK_UNLIKELY(!res))
				{
					hawk_rtx_refdownval(rtx, left);
					ADJERR_LOC(rtx, &nde->loc);
					return HAWK_NULL;
				}

				res2 = hawk_rtx_makeintval(rtx, v1 + inc_val_int);
				if (HAWK_UNLIKELY(!res2))
				{
					hawk_rtx_refdownval(rtx, left);
					hawk_rtx_freeval(rtx, res, HAWK_RTX_FREEVAL_CACHE);
					ADJERR_LOC(rtx, &nde->loc);
					return HAWK_NULL;
				}
			}
			else /* if (n == 1) */
			{
				HAWK_ASSERT(n == 1);
				res = hawk_rtx_makefltval(rtx, v2);
				if (HAWK_UNLIKELY(!res))
				{
					hawk_rtx_refdownval(rtx, left);
					ADJERR_LOC(rtx, &nde->loc);
					return HAWK_NULL;
				}

				res2 = hawk_rtx_makefltval(rtx, v2 + inc_val_flt);
				if (HAWK_UNLIKELY(!res2))
				{
					hawk_rtx_refdownval(rtx, left);
					hawk_rtx_freeval(rtx, res, HAWK_RTX_FREEVAL_CACHE);
					ADJERR_LOC(rtx, &nde->loc);
					return HAWK_NULL;
				}
			}

			break;
		}
	}

	if (HAWK_UNLIKELY(do_assignment(rtx, exp->left, res2) == HAWK_NULL))
	{
		hawk_rtx_refdownval(rtx, left);
		return HAWK_NULL;
	}

	hawk_rtx_refdownval(rtx, left);
	return res;
}

static hawk_val_t* eval_cnd (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* tv, * v;
	hawk_nde_cnd_t* cnd = (hawk_nde_cnd_t*)nde;

	HAWK_ASSERT(cnd->test->next == HAWK_NULL);

	tv = eval_expression(rtx, cnd->test);
	if (HAWK_UNLIKELY(!tv)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, tv);

	HAWK_ASSERT(cnd->left->next == HAWK_NULL && cnd->right->next == HAWK_NULL);
	v = (hawk_rtx_valtobool(rtx, tv))? eval_expression(rtx, cnd->left): eval_expression(rtx, cnd->right);

	hawk_rtx_refdownval(rtx, tv);
	return v;
}

static hawk_val_t* eval_fncall_fnc (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	/* intrinsic function */
	hawk_nde_fncall_t* call = (hawk_nde_fncall_t*)nde;
	/* the parser must make sure that the number of arguments is proper */
	HAWK_ASSERT(call->nargs >= call->u.fnc.spec.arg.min && call->nargs <= call->u.fnc.spec.arg.max);
	return hawk_rtx_evalcall(rtx, call, HAWK_NULL, push_arg_from_nde, (void*)call->u.fnc.spec.arg.spec, HAWK_NULL, HAWK_NULL);
}

static HAWK_INLINE hawk_val_t* eval_fncall_fun (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	/* user-defined function */
	hawk_nde_fncall_t* call = (hawk_nde_fncall_t*)nde;
	hawk_fun_t* fun;
	hawk_htb_pair_t* pair;

	if (!call->u.fun.fun)
	{
		/* there can be multiple runtime instances for a single hawk object.
		 * changing the parse tree from one runtime instance can affect
		 * other instances. however i do change the parse tree without protection
		 * hoping that the pointer assignment is atomic. (call->u.fun.fun = fun).
		 * i don't mind each instance performs a search duplicately for a short while */

		pair = hawk_htb_search(rtx->hawk->tree.funs, call->u.fun.name.ptr, call->u.fun.name.len);
		if (!pair)
		{
			hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_EFUNNF, HAWK_T("function '%.*js' not found"), call->u.fun.name.len, call->u.fun.name.ptr);
			return HAWK_NULL;
		}

		fun = (hawk_fun_t*)HAWK_HTB_VPTR(pair);
		HAWK_ASSERT(fun != HAWK_NULL);

		/* cache the search result */
		call->u.fun.fun = fun;
	}
	else
	{
		/* use the cached function */
		fun = call->u.fun.fun;
	}

	if (call->nargs > fun->nargs && !fun->variadic)
	{
		/* TODO: is this correct? what if i want to
		 *       allow arbitarary numbers of arguments? */
		hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_EARGTM, HAWK_T("too many arguments to '%.*js'"), fun->name.len, fun->name.ptr);
		return HAWK_NULL;
	}

	/* push_arg_from_nde() has special handling for references when the function
	 * argument spec contains 'r' or 'R'.
	 * a reference is passed to a built-in function as a reference value
	 * but its evaluation result is passed to user-defined function.
	 * I pass HAWK_NULL to prevent special handling.
	 * the value change for a reference variable inside a user-defined function is
	 * reflected by hawk_rtx_evalcall() specially whereas a built-in function must
	 * call hawk_rtx_setrefval() to update the reference.
	 */
	return hawk_rtx_evalcall(rtx, call, fun, push_arg_from_nde, HAWK_NULL/*fun->argspec*/, HAWK_NULL, HAWK_NULL);
}

static hawk_val_t* eval_fncall_var (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_nde_fncall_t* call = (hawk_nde_fncall_t*)nde;
	hawk_val_t* fv, * rv;
	hawk_fun_t* fun;

	fv = eval_expression(rtx, (hawk_nde_t*)call->u.var.var);
	if (HAWK_UNLIKELY(!fv)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, fv);
	fun = hawk_rtx_valtofun(rtx, fv);
	if (HAWK_UNLIKELY(!fun))
	{
		if (hawk_rtx_geterrnum(rtx) == HAWK_EINVAL)
			hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_ENOTFUN, HAWK_T("non-function value in %.*js"), call->u.var.var->id.name.len, call->u.var.var->id.name.ptr);
		ADJERR_LOC(rtx, &nde->loc);
		rv = HAWK_NULL;
	}
	else if (call->nargs > fun->nargs && !fun->variadic)
	{
		/* TODO: is this correct? what if i want to
		*       allow arbitarary numbers of arguments? */
		hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_EARGTM, HAWK_T("too many arguments to '%.*js'"), fun->name.len, fun->name.ptr);
		rv = HAWK_NULL;
	}
	else
	{
		/* pass HAWK_NULL for the argument spec regardless of the actual spec.
		 * see comments in eval_fncall_fun() for more */
		rv = hawk_rtx_evalcall(rtx, call, fun, push_arg_from_nde, HAWK_NULL/*fun->argspec*/, HAWK_NULL, HAWK_NULL);
	}
	hawk_rtx_refdownval(rtx, fv);

	return rv;
}

hawk_val_t* hawk_rtx_evalcall (
	hawk_rtx_t* rtx, hawk_nde_fncall_t* call, hawk_fun_t* fun,
	hawk_oow_t(*argpusher)(hawk_rtx_t*,hawk_nde_fncall_t*,void*), void* apdata,
	void(*errhandler)(void*), void* eharg)
{
	hawk_oow_t saved_stack_top, saved_arg_stack_top;
	hawk_oow_t nargs, i, stack_req;
	hawk_val_t* v;
	int n;

	/*
	 * ---------------------
	 *  lcln               <- stack top
	 * ---------------------
	 *  ....
	 * ---------------------
	 *  lcl0               local variables are pushed by run_block
	 * =====================
	 *  argn
	 * ---------------------
	 *  ....
	 * ---------------------
	 *  arg1
	 * ---------------------
	 *  arg0
	 * ---------------------
	 *  nargs
	 * ---------------------
	 *  return value
	 * ---------------------
	 *  previous stack top
	 * ---------------------
	 *  previous stack base  <- stack base
	 * =====================
	 *  0 (nargs)            <- stack top
	 * ---------------------
	 *  return value
	 * ---------------------
	 *  previous stack top
	 * ---------------------
	 *  previous stack base  <- stack base
	 * =====================
	 *  gbln
	 * ---------------------
	 *  ....
	 * ---------------------
	 *  gbl0
	 * ---------------------
	 */

	HAWK_ASSERT(HAWK_SIZEOF(void*) >= HAWK_SIZEOF(rtx->stack_top));
	HAWK_ASSERT(HAWK_SIZEOF(void*) >= HAWK_SIZEOF(rtx->stack_base));

	saved_stack_top = rtx->stack_top;

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "setting up function stack frame top=%zd base=%zd\n", (hawk_oow_t)rtx->stack_top, (hawk_oow_t)rtx->stack_base);
#endif

	/* make a new stack frame */
	stack_req = 4 + call->nargs;
	if (fun && fun->nargs > call->nargs)
	{
		/* for variadic functions, fun->nargs can be less than call->nargs.
		 * for non-variadic functions, the compiler must guarantee that fun->nargs >= call->nargs
		 * this case can happen for  for non-variadic functions only. */
		stack_req += fun->nargs - call->nargs;
	}
	/* if fun is HAWK_NULL, there is no way for this function to know expected argument numbers.
	 * the argument pusher must ensure the stack availality before pushing arguments */

	if (HAWK_UNLIKELY(HAWK_RTX_STACK_AVAIL(rtx) < stack_req))
	{
		hawk_rtx_seterrnum(rtx, &call->loc, HAWK_ESTACK);
		return HAWK_NULL;
	}

	HAWK_RTX_STACK_PUSH(rtx, (void*)rtx->stack_base);
	HAWK_RTX_STACK_PUSH(rtx, (void*)saved_stack_top);
	HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil); /* space for return value */
	HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil); /* space for number of arguments */

	saved_arg_stack_top = rtx->stack_top;

	/* push all arguments onto the stack */
	nargs = argpusher(rtx, call, apdata);
	if (nargs == (hawk_oow_t)-1) goto oops_making_stack_frame;

	HAWK_ASSERT(nargs == call->nargs);

	if (fun)
	{
		/* extra step for normal hawk functions */
		while (nargs < fun->nargs)
		{
			/* push as many nils as the number of missing actual arguments */
			HAWK_RTX_STACK_PUSH(rtx, hawk_val_nil);
			nargs++;
		}
	}

	/* entering a new stack frame */
	rtx->stack_base = saved_stack_top;
	HAWK_RTX_STACK_NARGS(rtx) = (void*)nargs;

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "running function body\n");
#endif

	if (fun)
	{
		/* normal hawk function */
		HAWK_ASSERT(fun->body->type == HAWK_NDE_BLK);
		n = run_block(rtx, (hawk_nde_blk_t*)fun->body);
	}
	else
	{
		n = 0;

		/* intrinsic function */
		HAWK_ASSERT(call->nargs >= call->u.fnc.spec.arg.min && call->nargs <= call->u.fnc.spec.arg.max);

		if (call->u.fnc.spec.impl)
		{
			n = call->u.fnc.spec.impl(rtx, &call->u.fnc.info);
			if (HAWK_UNLIKELY(n <= -1)) ADJERR_LOC(rtx, &call->loc);
		}
	}

	/* refdown args in the rtx.stack */
	nargs = (hawk_oow_t)HAWK_RTX_STACK_NARGS(rtx);
#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "block rtx complete nargs = %d\n", (int)nargs);
#endif

	i = 0;
	if (fun && fun->argspec && call->nargs > 0)  /* hawk_rtx_callfun() sets up a fake call structure with call->nargs > 0 but call->args == HAWK_NULL */
	{
		/* set back the values for pass-by-reference parameters of normal functions.
		 * the intrinsic functions are not handled here but their implementation would
		 * call hawk_rtx_setrefval() */

		/* even if fun->argspec exists, call->nargs may still be 0. so i test if call->nargs > 0.
		 *   function x(a1, &a2) {}
		 *   BEGIN { x(); }
		 * all parameters are @nils in this case. nargs and fun->nargs are 2 but call->nargs is 0.
		 */

		if (call->args)
		{
			hawk_oow_t cur_stack_base = rtx->stack_base;
			hawk_oow_t prev_stack_base = (hawk_oow_t)rtx->stack[rtx->stack_base + 0];
			hawk_nde_t* p = call->args;

			for (; i < call->nargs; i++)
			{
				if (n >= 0 && i < fun->argspeclen && (fun->argspec[i] == 'r' || fun->argspec[i] == 'R'))
				{
					/* if the function call is successful, update the call-by-reference arguments
					 * with hawk_rtx_setrefval() */

					hawk_val_t** ref;
					hawk_val_ref_t refv;
					hawk_val_t* av;
					int r;

					av = HAWK_RTX_STACK_ARG(rtx, i);
					if (HAWK_RTX_GETVALTYPE(rtx, av) == HAWK_VAL_REF)
					{
						/* the argument still has the reference type.
						 * this means, the argument has not been set.
						 *
						 *   function f1(&a, &b) { b = 20 }
						 *
						 * since a is not set in f1, the value for a is still the pushed value which is a reference
						 */

						/* ---- DO NOTHING ---- */
					}
					else
					{
						/* if an argument passed is a local variable or a parameter to the previous caller,
						 * the argument node information stored is relative to the previous stack frame.
						 * i revert rtx->stack_base to the previous stack frame base before calling
						 * get_reference() and restors it back to the current base. this tactic
						 * is very ugly because the assumption for this is dependent on get_reference()
						 * implementation */
						rtx->stack_base = prev_stack_base; /* UGLY */
						r = get_reference(rtx, p, &ref);
						rtx->stack_base = cur_stack_base; /* UGLY */

						/* if argspec is 'r', get_reference() must succeed all the time.
						 * if argspec is 'R', it may fail. if it happens, don't copy the value */
						if (HAWK_LIKELY(r >= 0))
						{
							HAWK_RTX_INIT_REF_VAL (&refv, p->type - HAWK_NDE_NAMED, ref, 9); /* initialize a fake reference variable. 9 chosen randomly */
							if (HAWK_UNLIKELY(hawk_rtx_setrefval(rtx, &refv, av) <= -1))
							{
								n = -1;
								ADJERR_LOC(rtx, &call->loc);
							}
						}
					}
				}

				hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_ARG(rtx,i));
				p = p->next;
			}
		}
		else if (call->arg_base > 0) /* special case. set by hawk::call() */
		{
			/*
			 *  function f1(a,&b) { b *= 20; }
			 *  BEGIN { q = 4; hawk::call(r, "f1", 20, q); print q; }
			 *
			 * the fourth argument to hawk::call() must map to the second argument to f1().
			 * hawk::call() accepts the third to the last arguments as reference if possible.
			 * this function attempts to copy back the pass-by-reference values to
			 * one stack frame up.
			 *
			 *  f1(1, 2) is an error as 2 is not referenceable.
			 *  hakw::call(r, "f1", 1, 2) is not an error but can't capture changes made inside f1.
			 */
			for (; i < call->nargs; i++)
			{
				if (n >= 0 && (fun->argspec[i] == 'r' || fun->argspec[i] == 'R'))
				{
					hawk_val_t* v, * av;

					av = HAWK_RTX_STACK_ARG(rtx, i);
					if (HAWK_RTX_GETVALTYPE(rtx, av) == HAWK_VAL_REF)
					{
						/* ---- DO NOTHING ---- */
					}
					else
					{
						v = rtx->stack[call->arg_base + i]; /* UGLY */
						if (HAWK_RTX_GETVALTYPE(rtx, v) == HAWK_VAL_REF)
						{
							if (HAWK_UNLIKELY(hawk_rtx_setrefval(rtx, (hawk_val_ref_t*)v, av) <= -1))
							{
								n = -1;
								ADJERR_LOC(rtx, &call->loc);
							}
						}
					}
				}

				hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_ARG(rtx,i));
			}
		}
	}

	for (; i < nargs; i++)
	{
		hawk_rtx_refdownval(rtx, HAWK_RTX_STACK_ARG(rtx,i));
	}

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "got return value\n");
#endif

	v = HAWK_RTX_STACK_RETVAL(rtx);
	if (HAWK_UNLIKELY(n <= -1))
	{
		if (hawk_rtx_geterrnum(rtx) == HAWK_ENOERR && errhandler != HAWK_NULL)
		{
			/* errhandler is passed only when hawk_rtx_evalcall() is
			 * invoked from hawk_rtx_call(). Under this
			 * circumstance, this stack frame is the first
			 * activated and the stack base is the first element
			 * after the global variables. so HAWK_RTX_STACK_RETVAL(rtx)
			 * effectively becomes HAWK_RTX_STACK_RETVAL_GBL(rtx).
			 * As hawk_rtx_evalcall() returns HAWK_NULL on error and
			 * the reference count of HAWK_RTX_STACK_RETVAL(rtx) should be
			 * decremented, it can't get the return value
			 * if it turns out to be terminated by exit().
			 * The return value could be destroyed by then.
			 * Unlikely, rtx_bpae_loop() just checks if rtx->errinf.num
			 * is HAWK_ENOERR and gets HAWK_RTX_STACK_RETVAL_GBL(rtx)
			 * to determine if it is terminated by exit().
			 *
			 * The handler capture_retval_on_exit()
			 * increments the reference of HAWK_RTX_STACK_RETVAL(rtx)
			 * and stores the pointer into accompanying space.
			 * This way, the return value is preserved upon
			 * termination by exit() out to the caller.
			 */
			errhandler (eharg);
		}

		/* if the earlier operations failed and this function
		 * has to return a error, the return value is just
		 * destroyed and replaced by nil */
		hawk_rtx_refdownval(rtx, v);
		HAWK_RTX_STACK_RETVAL(rtx) = hawk_val_nil;
	}
	else
	{
		/* this trick has been mentioned in rtx_return.
		 * adjust the reference count of the return value.
		 * the value must not be freed even if the reference count
		 * reached zero because its reference has been incremented
		 * in rtx_return or directly by hawk_rtx_setretval()
		 * regardless of its reference count. */
		hawk_rtx_refdownval_nofree(rtx, v);
	}

	rtx->stack_top =  (hawk_oow_t)rtx->stack[rtx->stack_base + 1];
	rtx->stack_base = (hawk_oow_t)rtx->stack[rtx->stack_base + 0];

	if (rtx->exit_level == EXIT_FUNCTION) rtx->exit_level = EXIT_NONE;

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "returning from function top=%zd, base=%zd\n", (hawk_oow_t)rtx->stack_top, (hawk_oow_t)rtx->stack_base);
#endif
	return (n <= -1)? HAWK_NULL: v;

oops_making_stack_frame:
	while (rtx->stack_top > saved_arg_stack_top)
	{
		/* call hawk_rtx_refdownval() for all arguments.
		 * it is safe because nil or quickint is immune to excessive hawk_rtx_refdownval() calls */
		hawk_rtx_refdownval(rtx, rtx->stack[rtx->stack_top - 1]);
		HAWK_RTX_STACK_POP (rtx);
	}
	HAWK_ASSERT(rtx->stack_top - saved_stack_top == 4);
	while (rtx->stack_top > saved_stack_top)
	{
		/* the stack frame prologue does not have a reference-counted value
		 * before being entered. so no hawk_rtx_refdownval().
		 * three slots contains raw integers for internal use.. only one
		 * slot that contains the return value would be reference counted
		 * after it is set to a reference counted value. here, it never happens */
		HAWK_RTX_STACK_POP (rtx);
	}
	ADJERR_LOC(rtx, &call->loc);
	return HAWK_NULL;
}

static hawk_oow_t push_arg_from_vals (hawk_rtx_t* rtx, hawk_nde_fncall_t* call, void* data)
{
	struct pafv_t* pafv = (struct pafv_t*)data;
	hawk_oow_t nargs = 0;

	if (HAWK_UNLIKELY(HAWK_RTX_STACK_AVAIL(rtx) < pafv->nargs))
	{
		hawk_rtx_seterrnum(rtx, &call->loc, HAWK_ESTACK);
		return (hawk_oow_t)-1;
	}

	for (nargs = 0; nargs < pafv->nargs; nargs++)
	{
		if (pafv->argspec && (pafv->argspec[nargs] == 'r' || pafv->argspec[nargs] == 'R'))
		{
			hawk_val_t** ref;
			hawk_val_t* v;

			ref = (hawk_val_t**)&pafv->args[nargs];
			v = hawk_rtx_makerefval(rtx, HAWK_VAL_REF_LCL, ref); /* this type(HAWK_VAL_REF_LCL) is fake */
			if (HAWK_UNLIKELY(!v))
			{
				ADJERR_LOC(rtx, &call->loc);
				return (hawk_oow_t)-1;
			}

			HAWK_RTX_STACK_PUSH(rtx, v);
			hawk_rtx_refupval(rtx, v);
		}
		else
		{
			HAWK_RTX_STACK_PUSH(rtx, pafv->args[nargs]);
			hawk_rtx_refupval(rtx, pafv->args[nargs]);
		}
	}

	return nargs;
}

static hawk_oow_t push_arg_from_nde (hawk_rtx_t* rtx, hawk_nde_fncall_t* call, void* data)
{
	hawk_nde_t* p;
	hawk_val_t* v;
	hawk_oow_t nargs;
	const hawk_ooch_t* arg_spec = (const hawk_ooch_t*)data;
	hawk_oow_t spec_len;

	if (HAWK_UNLIKELY(HAWK_RTX_STACK_AVAIL(rtx) < call->nargs))
	{
		hawk_rtx_seterrnum(rtx, &call->loc, HAWK_ESTACK);
		return (hawk_oow_t)-1;
	}

	/* in practice, this function gets NULL for a user-defined function regardless of its actual spec.
	 * it may get a non-NULL arg_spec for builtin/module functions */
	spec_len = arg_spec? hawk_count_oocstr(arg_spec): 0;
	for (p = call->args, nargs = 0; p; p = p->next, nargs++)
	{
		hawk_ooch_t spec;

		/* if not sufficient number of spec characters given, take the last value and use it.
		 * as said above, arg_spec is provided for the implicit functions only.
		 * so the logic taking the last value at run-time is elsewhere (e.g hawk_rtx_evalcall()). */
		spec = (spec_len <= 0)? '\0': arg_spec[((nargs < spec_len)? nargs: spec_len - 1)];

		switch (spec)
		{
			case 'R': /* make reference if a referenceable is given. otherwise accept a normal value - useful for hawk::call() */
			case 'r': /* make reference. a non-referenceable value is rejected */
			{
				hawk_val_t** ref;

				if (get_reference(rtx, p, &ref) <= -1)
				{
					if (spec == 'R') goto normal_arg;
					return (hawk_oow_t)-1; /* return -1 without unwinding stack as hawk_rtx_evalcall() does it */
				}

				/* 'p->type - HAWK_NDE_NAMED' must produce a relevant HAWK_VAL_REF_XXX value. */
				v = hawk_rtx_makerefval(rtx, p->type - HAWK_NDE_NAMED, ref);
				break;
			}

			case 'x':
				/* a regular expression is passed to the function as it is */
				v = eval_expression0(rtx, p);
				break;

			default:
			normal_arg:
				v = eval_expression(rtx, p);
				break;
		}

		if (HAWK_UNLIKELY(!v)) return (hawk_oow_t)-1; /* return -1 without unwinding stack as hawk_rtx_evalcall() does it */

		HAWK_RTX_STACK_PUSH(rtx, v);
		hawk_rtx_refupval(rtx, v);
	}

	HAWK_ASSERT(call->nargs == nargs);
	return nargs;
}

static int get_reference (hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_val_t*** ref)
{
	hawk_nde_var_t* tgt = (hawk_nde_var_t*)nde;
	hawk_val_t** tmp;

	/* refer to eval_indexed for application of a similar concept */

	switch (nde->type)
	{
		case HAWK_NDE_NAMED:
		{
			hawk_htb_pair_t* pair;

			pair = hawk_htb_search(rtx->named, tgt->id.name.ptr, tgt->id.name.len);
			if (pair == HAWK_NULL)
			{
				/* it is bad that the named variable has to be created here.
				 * would there be any better ways to avoid this? */
				pair = hawk_htb_upsert(rtx->named, tgt->id.name.ptr, tgt->id.name.len, hawk_val_nil, 0);
				if (!pair)
				{
					ADJERR_LOC(rtx, &nde->loc);
					return -1;
				}
			}

			*ref = (hawk_val_t**)&HAWK_HTB_VPTR(pair);
			return 0;
		}

		case HAWK_NDE_GBL:
			/* *ref = (hawk_val_t**)&HAWK_RTX_STACK_GBL(rtx,tgt->id.idxa); */
			*ref = (hawk_val_t**)((hawk_oow_t)tgt->id.idxa);
			return 0;

		case HAWK_NDE_LCL:
			*ref = (hawk_val_t**)&HAWK_RTX_STACK_LCL(rtx,tgt->id.idxa);
			return 0;

		case HAWK_NDE_ARG:
			*ref = (hawk_val_t**)&HAWK_RTX_STACK_ARG(rtx,tgt->id.idxa);
			return 0;

		case HAWK_NDE_NAMEDIDX:
		case HAWK_NDE_GBLIDX:
		case HAWK_NDE_LCLIDX:
		case HAWK_NDE_ARGIDX:
			tmp = get_reference_indexed(rtx, tgt);
			if (HAWK_UNLIKELY(!tmp)) return -1;
			*ref = tmp;
			return 0;

		case HAWK_NDE_POS:
		{
			int n;
			hawk_int_t lv;
			hawk_val_t* v;

			/* the position number is returned for the positional
			 * variable unlike other reference types. */
			v = eval_expression(rtx, ((hawk_nde_pos_t*)nde)->val);
			if (HAWK_UNLIKELY(!v)) return -1;

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoint(rtx, v, &lv);
			hawk_rtx_refdownval(rtx, v);

			if (HAWK_UNLIKELY(n <= -1))
			{
				hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EPOSIDX);
				return -1;
			}

			if (!IS_VALID_POSIDX(lv))
			{
				hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EPOSIDX);
				return -1;
			}

			*ref = (hawk_val_t**)((hawk_oow_t)lv);
			return 0;
		}

		default:
			hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_ENOTREF);
			return -1;
	}
}

static hawk_val_t** get_reference_indexed (hawk_rtx_t* rtx, hawk_nde_var_t* var)
{
	hawk_map_t* map;
	hawk_ooch_t* str = HAWK_NULL;
	hawk_oow_t len;
	hawk_ooch_t idxbuf[HAWK_IDX_BUF_SIZE];

	hawk_arr_t* arr;
	hawk_ooi_t idx;

	hawk_val_t* v;
	hawk_val_type_t vtype;
	hawk_nde_t* remidx;

	HAWK_ASSERT(var->idx != HAWK_NULL);

	v = fetch_topval_from_var(rtx, var);
	vtype = HAWK_RTX_GETVALTYPE(rtx, v);

	switch (vtype)
	{
		case HAWK_VAL_NIL:
			v = assign_newmapval_to_var(rtx, var);
			if (HAWK_UNLIKELY(!v)) goto oops;
			vtype = HAWK_VAL_MAP;
			goto val_map_init;

		case HAWK_VAL_MAP:
		val_map_init:
			len = HAWK_COUNTOF(idxbuf);
			str = idxnde_to_str(rtx, var->idx, idxbuf, &len, &remidx, HAWK_NULL);
			if (HAWK_UNLIKELY(!str)) goto oops;
			map = ((hawk_val_map_t*)v)->map;
			break;

		case HAWK_VAL_ARR:
			idx = idxnde_to_int(rtx, var->idx, &remidx);
			if (HAWK_UNLIKELY(idx <= -1)) goto oops;
			arr = ((hawk_val_arr_t*)v)->arr;
			break;

		default:
			hawk_rtx_seterrnum(rtx, &var->loc, HAWK_ENOTIDXACC);
			goto oops;
	}

#if defined(HAWK_ENABLE_GC)
	while (remidx)
	{
		hawk_val_type_t container_vtype;

		if (vtype == HAWK_VAL_MAP)
		{
			hawk_map_pair_t* pair;
			pair = hawk_map_search(map, str, len);
			v = pair? (hawk_val_t*)HAWK_MAP_VPTR(pair): hawk_val_nil;
		}
		else
		{
			v = (idx < HAWK_ARR_SIZE(arr) && HAWK_ARR_SLOT(arr, idx))? ((hawk_val_t*)HAWK_ARR_DPTR(arr, idx)): hawk_val_nil;
		}
		container_vtype = vtype;
		vtype = HAWK_RTX_GETVALTYPE(rtx, v);

		switch (vtype)
		{
			case HAWK_VAL_MAP:
			val_map:
				if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
				len = HAWK_COUNTOF(idxbuf);
				str = idxnde_to_str(rtx, remidx, idxbuf, &len, &remidx, HAWK_NULL);
				if (HAWK_UNLIKELY(!str)) goto oops;
				map = ((hawk_val_map_t*)v)->map;
				break;

			case HAWK_VAL_ARR:
			val_arr:
				idx = idxnde_to_int(rtx, remidx, &remidx);
				if (HAWK_UNLIKELY(idx <= -1)) goto oops;
				arr = ((hawk_val_arr_t*)v)->arr;
				break;

			default:
				if (vtype == HAWK_VAL_NIL /* || (rtx->hawk->opt.trait & HAWK_FLEXMAP) no flexmap because this is in a 'get' context */)
				{
					if (container_vtype == HAWK_VAL_MAP)
					{
						v = assign_newmapval_in_map(rtx, map, str, len);
						if (HAWK_UNLIKELY(!v)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
						vtype = HAWK_VAL_MAP;
						goto val_map;
					}
					else
					{
						v = assign_newarrval_in_arr(rtx, arr, idx);
						if (HAWK_UNLIKELY(!v)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
						vtype = HAWK_VAL_ARR;
						goto val_arr;
					}
				}

				hawk_rtx_seterrnum(rtx, &var->loc, HAWK_ENOTIDXACC);
				goto oops;
		}
	}
#endif

	if (vtype == HAWK_VAL_MAP)
	{
		hawk_map_pair_t* pair;
		pair = hawk_map_search(map, str, len);
		if (!pair)
		{
			/* if the value doesn't exist for the given key, insert a nil for it to create a placeholder for the reference */
			pair = hawk_map_upsert(map, str, len, hawk_val_nil, 0);
			if (HAWK_UNLIKELY(!pair)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
			HAWK_ASSERT(HAWK_MAP_VPTR(pair) == hawk_val_nil);
			/* no reference count increment as hawk_val_nil is upserted
			hawk_rtx_refupval(rtx, HAWK_MAP_VPTR(pair)); */
		}

		if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
		return (hawk_val_t**)&HAWK_MAP_VPTR(pair);
	}
	else
	{
		if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
		if (idx >= HAWK_ARR_SIZE(arr) || !HAWK_ARR_SLOT(arr, idx))
		{
			/* if the value doesn't exist for the given index, insert a nil at that position to create a placeholder for the reference */
			if (HAWK_UNLIKELY(hawk_arr_upsert (arr, idx, hawk_val_nil, 0) == HAWK_ARR_NIL)) { ADJERR_LOC(rtx, &var->loc);  goto oops; }
		}
		return (hawk_val_t**)&HAWK_ARR_DPTR(arr, idx);
	}

oops:
	if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
	return HAWK_NULL;
}

static hawk_val_t* eval_char (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makecharval(rtx, ((hawk_nde_char_t*)nde)->val);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	return val;
}

static hawk_val_t* eval_bchr (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makebchrval(rtx, ((hawk_nde_bchr_t*)nde)->val);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	return val;
}

static hawk_val_t* eval_int (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makeintval(rtx, ((hawk_nde_int_t*)nde)->val);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	else if (HAWK_VTR_IS_POINTER(val)) ((hawk_val_int_t*)val)->nde = nde;
	return val;
}

static hawk_val_t* eval_flt (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makefltval(rtx, ((hawk_nde_flt_t*)nde)->val);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	else ((hawk_val_flt_t*)val)->nde = nde;
	return val;
}

static hawk_val_t* eval_str (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makestrvalwithoochars(rtx, ((hawk_nde_str_t*)nde)->ptr, ((hawk_nde_str_t*)nde)->len);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	return val;
}

static hawk_val_t* eval_mbs (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makembsvalwithbchars(rtx, ((hawk_nde_mbs_t*)nde)->ptr, ((hawk_nde_mbs_t*)nde)->len);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	return val;
}

static hawk_val_t* eval_rex (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	val = hawk_rtx_makerexval(rtx, &((hawk_nde_rex_t*)nde)->str, ((hawk_nde_rex_t*)nde)->code);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	return val;
}

static hawk_val_t* eval_xnil (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return hawk_rtx_makenilval(rtx); /* this never fails */
}

static hawk_val_t* eval_xargc (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_int_t nargs = (hawk_int_t)hawk_rtx_getnargs(rtx);
	return hawk_rtx_makeintval(rtx, nargs);
}

static hawk_val_t* eval_xargv (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	/* this create a new array. referencing @argv without a subscript
	 * slows hawk. TODO: fix this issue */
	hawk_oow_t nargs, i;
	hawk_val_t* v;

	nargs = hawk_rtx_getnargs(rtx);
	v = hawk_rtx_makearrval(rtx, nargs);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	hawk_rtx_refupval(rtx,v);
	for (i = 0; i < nargs; i++)
	{
		if (HAWK_UNLIKELY(!hawk_rtx_setarrvalfld(rtx, v, i, hawk_rtx_getarg(rtx, i))))
		{
			hawk_rtx_refdownval(rtx, v);
			return HAWK_NULL;
		}
	}

	hawk_rtx_refdownval_nofree(rtx,v);
	return v;
}

static hawk_val_t* eval_xargvidx (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_nde_xargvidx_t* xarg;
	hawk_val_t* v;
	hawk_int_t pos;
	int n;

	xarg = (hawk_nde_xargvidx_t*)nde;

	v = eval_expression(rtx, xarg->pos);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, v);
	n = hawk_rtx_valtoint(rtx, v, &pos);
	hawk_rtx_refdownval(rtx, v);
	if (n <= -1)
	{
		hawk_rtx_seterrnum(rtx, &xarg->pos->loc, HAWK_EPOSIDX);
		return HAWK_NULL;
	}

	return (pos < 0 || pos >= hawk_rtx_getnargs(rtx))? hawk_rtx_makenilval(rtx): hawk_rtx_getarg(rtx, pos);
}

static hawk_val_t* eval_fun (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_val_t* val;
	hawk_fun_t* fun;

	fun = ((hawk_nde_fun_t*)nde)->funptr;
	if (!fun)
	{
		hawk_htb_pair_t* pair;
		/* for a program like this,
			BEGIN { @local x; abc(); x = abc; x(); } function abc() { print "abc"; }
		 * abc is defined after BEGIN but is know to be a function inside BEGIN.
		 * the funptr field in the node can be HAWK_NULL */

		/* TODO: support builtin functions?, support module functions? */
		pair = hawk_htb_search(rtx->hawk->tree.funs, ((hawk_nde_fun_t*)nde)->name.ptr, ((hawk_nde_fun_t*)nde)->name.len);
		if (!pair)
		{
			/* it's unlikely to be not found in the function table for the parser structure. but keep this code in case */
			hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_EFUNNF, HAWK_T("function '%.*js' not found"), ((hawk_nde_fun_t*)nde)->name.len, ((hawk_nde_fun_t*)nde)->name.ptr);
			return HAWK_NULL;
		}

		fun = (hawk_fun_t*)HAWK_HTB_VPTR(pair);
		HAWK_ASSERT(fun != HAWK_NULL);
	}

	val = hawk_rtx_makefunval(rtx, fun);
	if (HAWK_UNLIKELY(!val)) ADJERR_LOC(rtx, &nde->loc);
	return val;
}

static hawk_val_t* eval_named (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_htb_pair_t* pair;
	pair = hawk_htb_search(rtx->named, ((hawk_nde_var_t*)nde)->id.name.ptr, ((hawk_nde_var_t*)nde)->id.name.len);
	return (pair == HAWK_NULL)? hawk_val_nil: HAWK_HTB_VPTR(pair);
}

static hawk_val_t* eval_gbl (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return HAWK_RTX_STACK_GBL(rtx, ((hawk_nde_var_t*)nde)->id.idxa);
}

static hawk_val_t* eval_lcl (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return HAWK_RTX_STACK_LCL(rtx, ((hawk_nde_var_t*)nde)->id.idxa);
}

static hawk_val_t* eval_arg (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return HAWK_RTX_STACK_ARG(rtx, ((hawk_nde_var_t*)nde)->id.idxa);
}

static hawk_val_t* eval_indexed (hawk_rtx_t* rtx, hawk_nde_var_t* var)
{
	hawk_map_t* map; /* containing map */
	hawk_ooch_t* str = HAWK_NULL;
	hawk_oow_t len;
	hawk_ooch_t idxbuf[HAWK_IDX_BUF_SIZE];

	hawk_arr_t* arr; /* containing array */
	hawk_ooi_t idx;

	hawk_nde_t* remidx;

	hawk_val_t* v;
	hawk_val_type_t vtype;

	v = fetch_topval_from_var(rtx, var);
	vtype = HAWK_RTX_GETVALTYPE(rtx, v);

	HAWK_ASSERT(var->idx != HAWK_NULL);

	switch (vtype)
	{
		case HAWK_VAL_NIL:
			v = assign_newmapval_to_var(rtx, var);
			if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
			vtype = HAWK_VAL_MAP;
			goto init_val_map;

		case HAWK_VAL_MAP:
		init_val_map:
			len = HAWK_COUNTOF(idxbuf);
			str = idxnde_to_str(rtx, var->idx, idxbuf, &len, &remidx, HAWK_NULL);
			if (HAWK_UNLIKELY(!str)) goto oops;
			map = ((hawk_val_map_t*)v)->map;
			break;

		case HAWK_VAL_ARR:
			idx = idxnde_to_int(rtx, var->idx, &remidx);
			if (idx <= -1) goto oops;
			arr = ((hawk_val_arr_t*)v)->arr;
			break;

		default:
			hawk_rtx_seterrnum(rtx, &var->loc, HAWK_ENOTIDXACC);
			return HAWK_NULL;
	}

#if defined(HAWK_ENABLE_GC)
	while (remidx)
	{
		hawk_val_type_t container_vtype;

		if (vtype == HAWK_VAL_MAP)
		{
			hawk_map_pair_t* pair;
			pair = hawk_map_search(map, str, len);
			v = pair? (hawk_val_t*)HAWK_MAP_VPTR(pair): hawk_val_nil;
		}
		else
		{
			v = (idx < HAWK_ARR_SIZE(arr) && HAWK_ARR_SLOT(arr, idx))? ((hawk_val_t*)HAWK_ARR_DPTR(arr, idx)): hawk_val_nil;
		}
		container_vtype = vtype;
		vtype = HAWK_RTX_GETVALTYPE(rtx, v);

		switch (vtype)
		{
			case HAWK_VAL_MAP:
			val_map:
				if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
				len = HAWK_COUNTOF(idxbuf);
				str = idxnde_to_str(rtx, remidx, idxbuf, &len, &remidx, HAWK_NULL);
				if (HAWK_UNLIKELY(!str)) goto oops;
				map = ((hawk_val_map_t*)v)->map;
				break;

			case HAWK_VAL_ARR:
			val_arr:
				idx = idxnde_to_int(rtx, remidx, &remidx);
				if (HAWK_UNLIKELY(idx <= -1)) goto oops;
				arr = ((hawk_val_arr_t*)v)->arr;
				break;

			default:
				if (vtype == HAWK_VAL_NIL /* || (rtx->hawk->opt.trait & HAWK_FLEXMAP) no flexmap because this is in a 'get' context */)
				{
					if (container_vtype == HAWK_VAL_MAP)
					{
						v = assign_newmapval_in_map(rtx, map, str, len);
						if (HAWK_UNLIKELY(!v)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
						vtype = HAWK_VAL_MAP;
						goto val_map;
					}
					else
					{
						v = assign_newarrval_in_arr(rtx, arr, idx);
						if (HAWK_UNLIKELY(!v)) { ADJERR_LOC(rtx, &var->loc); goto oops; }
						vtype = HAWK_VAL_ARR;
						goto val_arr;
					}
				}

				hawk_rtx_seterrnum(rtx, &var->loc, HAWK_ENOTIDXACC);
				goto oops;
		}
	}
#endif

	if (vtype == HAWK_VAL_MAP)
	{
		hawk_map_pair_t* pair;
		pair = hawk_map_search(map, str, len);
		if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
		return pair? (hawk_val_t*)HAWK_MAP_VPTR(pair): hawk_val_nil;
	}
	else
	{
		/* return nil if the index is out of range or the element at the index is not set.
		 * no check for a negative index as it's guaranteed to be positive by idxnde_to_int() */
		if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
		return (idx < HAWK_ARR_SIZE(arr) && HAWK_ARR_SLOT(arr, idx))? ((hawk_val_t*)HAWK_ARR_DPTR(arr, idx)): hawk_val_nil;
	}

oops:
	if (str && str != idxbuf) hawk_rtx_freemem(rtx, str);
	return HAWK_NULL;


}

static hawk_val_t* eval_namedidx (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return eval_indexed(rtx, (hawk_nde_var_t*)nde);
}

static hawk_val_t* eval_gblidx (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return eval_indexed(rtx, (hawk_nde_var_t*)nde);
}

static hawk_val_t* eval_lclidx (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return eval_indexed(rtx, (hawk_nde_var_t*)nde);
}

static hawk_val_t* eval_argidx (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return eval_indexed(rtx, (hawk_nde_var_t*)nde);
}

static hawk_val_t* eval_pos (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_nde_pos_t* pos = (hawk_nde_pos_t*)nde;
	hawk_val_t* v;
	hawk_int_t lv;
	int n;

	v = eval_expression(rtx, pos->val);
	if (HAWK_UNLIKELY(!v)) return HAWK_NULL;

	hawk_rtx_refupval(rtx, v);
	n = hawk_rtx_valtoint(rtx, v, &lv);
	hawk_rtx_refdownval(rtx, v);
	if (n <= -1)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EPOSIDX);
		return HAWK_NULL;
	}

	if (lv < 0)
	{
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EPOSIDX);
		return HAWK_NULL;
	}

	v = POS_VAL(rtx, lv);
#if 0
	if (lv == 0) v = rtx->inrec.d0;
	else if (lv > 0 && lv <= (hawk_int_t)rtx->inrec.nflds)
		v = rtx->inrec.flds[lv-1].val;
	else v = hawk_val_zls; /*hawk_val_nil;*/
#endif

	return v;
}

static hawk_val_t* __eval_getline (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_nde_getline_t* p;
	hawk_val_t* v = HAWK_NULL, * tmp;
	hawk_oocs_t dst = { HAWK_NULL, 0 };
	hawk_ooecs_t* buf;
	int n, x;

	p = (hawk_nde_getline_t*)nde;

	HAWK_ASSERT(
		(p->in_type == HAWK_IN_PIPE && p->in != HAWK_NULL) ||
		(p->in_type == HAWK_IN_RWPIPE && p->in != HAWK_NULL) ||
		(p->in_type == HAWK_IN_FILE && p->in != HAWK_NULL) ||
		(p->in_type == HAWK_IN_CONSOLE && p->in == HAWK_NULL));

	if (p->in)
	{
		v = io_nde_to_str(rtx, p->in, &dst, 0);
		if (!v || dst.len <= 0)
		{
			n = -1;
			goto skip_read;
		}
	}
	else
	{
		dst.ptr = (hawk_ooch_t*)HAWK_T("");
		dst.len = 0;
	}

	buf = &rtx->inrec.lineg;
read_console_again:
	hawk_ooecs_clear(&rtx->inrec.lineg);

	n = hawk_rtx_readio(rtx, p->in_type, dst.ptr, buf);

	if (n <= 0)
	{
		/* make getline return -1 */
		if (p->var)
		{
			/* it's important to call do_assignment(). consider an expression like this:
			 *   getline x[++j]
			 * withtout do_assignment(), ++j is skipped when getline returns 0. */
			tmp = do_assignment(rtx, p->var, hawk_val_zls);
			if (HAWK_UNLIKELY(!tmp)) goto oops;
		}
	}
	else /*if (n > 0)*/
	{
		if (p->in_type == HAWK_IN_CONSOLE)
		{
			HAWK_ASSERT(p->in == HAWK_NULL);
			if (rtx->nrflt.limit > 0)
			{
				/* record filter based on record number(NR) */
				if (((rtx->gbl.nr / rtx->nrflt.limit) % rtx->nrflt.size) != rtx->nrflt.rank)
				{
					if (update_fnr(rtx, rtx->gbl.fnr + 1, rtx->gbl.nr + 1) <= -1) goto oops;
					/* this jump is a bit dirty. the 'if' block below hawk_rtx_readio()
					 * will never be true. but this makes code confusing */
					goto read_console_again;
				}
			}
		}

		if (!p->var)
		{
			/* set $0 with the input value */
			x = hawk_rtx_setrec(rtx, 0, HAWK_OOECS_OOCS(buf), 1);
			if (x <= -1) goto oops;
		}
		else
		{
			hawk_val_t* vv;

			/* treat external input numerically if it can compose a number. */
			/*vv = hawk_rtx_makestrvalwithoocs(rtx, HAWK_OOECS_OOCS(buf));*/
			vv = hawk_rtx_makenumorstrvalwithoochars(rtx, HAWK_OOECS_PTR(buf), HAWK_OOECS_LEN(buf));
			if (HAWK_UNLIKELY(!vv))
			{
				ADJERR_LOC(rtx, &nde->loc);
				goto oops;
			}

			hawk_rtx_refupval(rtx, vv);
			tmp = do_assignment(rtx, p->var, vv);
			hawk_rtx_refdownval(rtx, vv);
			if (HAWK_UNLIKELY(!tmp)) goto oops;
		}

		/* update FNR & NR if reading from console */
		if (p->in_type == HAWK_IN_CONSOLE &&
		    update_fnr(rtx, rtx->gbl.fnr + 1, rtx->gbl.nr + 1) <= -1)
		{
			goto oops;
		}
	}

skip_read:
	if (v)
	{
		if (dst.ptr) hawk_rtx_freevaloocstr(rtx, v, dst.ptr);
		hawk_rtx_refdownval(rtx, v);
	}

	tmp = hawk_rtx_makeintval(rtx, n);
	if (HAWK_UNLIKELY(!tmp)) ADJERR_LOC(rtx, &nde->loc);
	return tmp;

oops:
	if (v)
	{
		if (dst.ptr) hawk_rtx_freevaloocstr(rtx, v, dst.ptr);
		hawk_rtx_refdownval(rtx, v);
	}
	return HAWK_NULL;
}

static hawk_val_t* __eval_getbline (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	hawk_nde_getline_t* p;
	hawk_val_t* v = HAWK_NULL, * tmp;
	hawk_oocs_t dst = {HAWK_NULL, 0};
	hawk_becs_t* buf;
	int n;

	p = (hawk_nde_getline_t*)nde;

	HAWK_ASSERT(
		(p->in_type == HAWK_IN_PIPE && p->in != HAWK_NULL) ||
		(p->in_type == HAWK_IN_RWPIPE && p->in != HAWK_NULL) ||
		(p->in_type == HAWK_IN_FILE && p->in != HAWK_NULL) ||
		(p->in_type == HAWK_IN_CONSOLE && p->in == HAWK_NULL));

	if (p->in)
	{
		v = io_nde_to_str(rtx, p->in, &dst, 0);
		if (!v || dst.len <= 0)
		{
			n = -1;
			goto skip_read;
		}
	}
	else
	{
		dst.ptr = (hawk_ooch_t*)HAWK_T("");
		dst.len = 0;
	}

	buf = &rtx->inrec.linegb;
read_console_again:
	hawk_becs_clear(&rtx->inrec.linegb);

	n = hawk_rtx_readiobytes(rtx, p->in_type, dst.ptr, buf);

	if (n <= 0)
	{
		/* make getline return -1 or 0*/
		if (p->var)
		{
			tmp = do_assignment(rtx, p->var, hawk_val_zls);
			if (HAWK_UNLIKELY(!tmp)) goto oops;
		}
	}
	else /*if (n > 0)*/
	{
		if (p->in_type == HAWK_IN_CONSOLE)
		{
			HAWK_ASSERT(p->in == HAWK_NULL);
			if (rtx->nrflt.limit > 0)
			{
				/* record filter based on record number(NR) */
				if (((rtx->gbl.nr / rtx->nrflt.limit) % rtx->nrflt.size) != rtx->nrflt.rank)
				{
					if (update_fnr(rtx, rtx->gbl.fnr + 1, rtx->gbl.nr + 1) <= -1) goto oops;
					/* this jump is a bit dirty. the 'if' block below hawk_rtx_readio()
					 * will never be true. but this makes code confusing */
					goto read_console_again;
				}
			}
		}

		if (p->var == HAWK_NULL)
		{
			/* set $0 with the input value */
			/*x = hawk_rtx_setbrec(rtx, 0, HAWK_BECS_BCS(buf));
			if (x <= -1) goto oops;*/
/* TODO: can i support this? */
			hawk_rtx_seterrfmt(rtx, &nde->loc, HAWK_ENOIMPL, HAWK_T("getbline without a variable not supported"));
			goto oops;
		}
		else
		{
			hawk_val_t* vv;

			/* treat external input numerically if it can compose a number. */
			/*vv = hawk_rtx_makembsvalwithbcs(rtx, HAWK_BECS_BCS(buf));*/
			vv = hawk_rtx_makenumormbsvalwithbchars(rtx, HAWK_BECS_PTR(buf), HAWK_BECS_LEN(buf));
			if (vv == HAWK_NULL)
			{
				ADJERR_LOC(rtx, &nde->loc);
				goto oops;
			}

			hawk_rtx_refupval(rtx, vv);
			tmp = do_assignment(rtx, p->var, vv);
			hawk_rtx_refdownval(rtx, vv);
			if (tmp == HAWK_NULL) goto oops;
		}

		/* update FNR & NR if reading from console */
		if (p->in_type == HAWK_IN_CONSOLE &&
		    update_fnr(rtx, rtx->gbl.fnr + 1, rtx->gbl.nr + 1) <= -1)
		{
			goto oops;
		}
	}

skip_read:
	if (v)
	{
		if (dst.ptr) hawk_rtx_freevaloocstr(rtx, v, dst.ptr);
		hawk_rtx_refdownval(rtx, v);
	}
	tmp = hawk_rtx_makeintval(rtx, n);
	if (!tmp) ADJERR_LOC(rtx, &nde->loc);
	return tmp;

oops:
	if (v)
	{
		if (dst.ptr) hawk_rtx_freevaloocstr(rtx, v, dst.ptr);
		hawk_rtx_refdownval(rtx, v);
	}
	return HAWK_NULL;
}

static hawk_val_t* eval_getline (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	return ((hawk_nde_getline_t*)nde)->mbs? __eval_getbline(rtx, nde): __eval_getline(rtx, nde);
}

static hawk_val_t* eval_print (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	int n = run_print(rtx, (hawk_nde_print_t*)nde);
	if (n == PRINT_IOERR) n = -1; /* let print return -1 */
	else if (n <= -1) return HAWK_NULL;
	return hawk_rtx_makeintval(rtx, n);
}

static hawk_val_t* eval_printf (hawk_rtx_t* rtx, hawk_nde_t* nde)
{
	int n = run_printf(rtx, (hawk_nde_print_t*)nde);
	if (n == PRINT_IOERR) n = -1; /* let print return -1 */
	else if (n <= -1) return HAWK_NULL;
	return hawk_rtx_makeintval(rtx, n);
}

static int read_record (hawk_rtx_t* rtx)
{
	hawk_ooi_t n;
	hawk_ooecs_t* buf;

read_again:
	if (hawk_rtx_clrrec(rtx, 0) <= -1) return -1;

	buf = &rtx->inrec.line;
	n = hawk_rtx_readio(rtx, HAWK_IN_CONSOLE, HAWK_T(""), buf);
	if (n <= -1)
	{
		hawk_rtx_clrrec(rtx, 0);
		return -1;
	}

#if defined(DEBUG_RUN)
	hawk_logbfmt (hawk_rtx_gethawk(rtx), "record len = %d str=[%.*js]\n", (int)HAWK_OOECS_LEN(buf), HAWK_OOECS_LEN(buf), HAWK_OOECS_PTR(buf));
#endif
	if (n == 0)
	{
		HAWK_ASSERT(HAWK_OOECS_LEN(buf) == 0);
		return 0;
	}

	if (rtx->nrflt.limit > 0)
	{
		if (((rtx->gbl.nr / rtx->nrflt.limit) % rtx->nrflt.size) != rtx->nrflt.rank)
		{
			if (update_fnr(rtx, rtx->gbl.fnr + 1, rtx->gbl.nr + 1) <= -1) return -1;
			goto read_again;
		}
	}

	if (hawk_rtx_setrec(rtx, 0, HAWK_OOECS_OOCS(buf), 1) <= -1 ||
	    update_fnr(rtx, rtx->gbl.fnr + 1, rtx->gbl.nr + 1) <= -1) return -1;

	return 1;
}

static hawk_ooch_t* idxnde_to_str (hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_ooch_t* buf, hawk_oow_t* len, hawk_nde_t** remidx, hawk_int_t* firstidxint)
{
	hawk_ooch_t* str;
	hawk_val_t* idx;
	hawk_int_t idxint;

	HAWK_ASSERT(nde != HAWK_NULL);

	if (!nde->next)
	{
		hawk_rtx_valtostr_out_t out;

		/* single node index */
		idx = eval_expression(rtx, nde);
		if (HAWK_UNLIKELY(!idx)) return HAWK_NULL;

		hawk_rtx_refupval(rtx, idx);

		if (firstidxint)
		{
			if (hawk_rtx_valtoint(rtx, idx, &idxint) <= -1)
			{
				hawk_rtx_refdownval(rtx, idx);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}
		}

		str = HAWK_NULL;

		if (buf)
		{
			/* try with a fixed-size buffer if given */
			out.type = HAWK_RTX_VALTOSTR_CPLCPY;
			out.u.cplcpy.ptr = buf;
			out.u.cplcpy.len = *len;

			if (hawk_rtx_valtostr(rtx, idx, &out) >= 0)
			{
				str = out.u.cplcpy.ptr;
				*len = out.u.cplcpy.len;
				HAWK_ASSERT(str == buf);
			}
		}

		if (!str)
		{
			/* if no fixed-size buffer was given or the fixed-size
			 * conversion failed, switch to the dynamic mode */
			out.type = HAWK_RTX_VALTOSTR_CPLDUP;
			if (hawk_rtx_valtostr(rtx, idx, &out) <= -1)
			{
				hawk_rtx_refdownval(rtx, idx);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			str = out.u.cpldup.ptr;
			*len = out.u.cpldup.len;
		}

		hawk_rtx_refdownval(rtx, idx);
		*remidx = HAWK_NULL;
	}
	else
	{
		/* multidimensional index - e.g. [1,2,3] */
		hawk_ooecs_t idxstr;
		hawk_oocs_t tmp;
		hawk_rtx_valtostr_out_t out;
		hawk_nde_t* xnde;
		int first = 1;

		out.type = HAWK_RTX_VALTOSTR_STRPCAT;
		out.u.strpcat = &idxstr;

		if (hawk_ooecs_init(&idxstr, hawk_rtx_getgem(rtx), DEF_BUF_CAPA) <= -1)
		{
			ADJERR_LOC(rtx, &nde->loc);
			return HAWK_NULL;
		}

		xnde = nde;
#if defined(HAWK_ENABLE_GC)
		while (nde && nde->type != HAWK_NDE_NULL)
#else
		while (nde)
#endif
		{
			idx = eval_expression(rtx, nde);
			if (HAWK_UNLIKELY(!idx))
			{
				hawk_ooecs_fini(&idxstr);
				return HAWK_NULL;
			}

			hawk_rtx_refupval(rtx, idx);

			if (firstidxint && first)
			{
				if (hawk_rtx_valtoint(rtx, idx, &idxint) <= -1)
				{
					hawk_rtx_refdownval(rtx, idx);
					hawk_ooecs_fini(&idxstr);
					ADJERR_LOC(rtx, &nde->loc);
					return HAWK_NULL;
				}

				first = 0;
			}

			if (xnde != nde && hawk_ooecs_ncat(&idxstr, rtx->gbl.subsep.ptr, rtx->gbl.subsep.len) == (hawk_oow_t)-1)
			{
				hawk_rtx_refdownval(rtx, idx);
				hawk_ooecs_fini(&idxstr);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			if (hawk_rtx_valtostr(rtx, idx, &out) <= -1)
			{
				hawk_rtx_refdownval(rtx, idx);
				hawk_ooecs_fini(&idxstr);
				ADJERR_LOC(rtx, &nde->loc);
				return HAWK_NULL;
			}

			hawk_rtx_refdownval(rtx, idx);
			nde = nde->next;
		}

		hawk_ooecs_yield(&idxstr, &tmp, 0);
		str = tmp.ptr;
		*len = tmp.len;

		hawk_ooecs_fini(&idxstr);

		/* if nde is not HAWK_NULL, it should be of the HAWK_NDE_NULL type */
		*remidx = nde? nde->next: nde;
	}

	if (firstidxint) *firstidxint = idxint;
	return str;
}

static hawk_ooi_t idxnde_to_int (hawk_rtx_t* rtx, hawk_nde_t* nde, hawk_nde_t** remidx)
{
	hawk_int_t v;
	hawk_val_t* tmp;
	int n;

	if (nde->next && nde->next->type != HAWK_NDE_NULL)
	{
		/* multidimensional indices inside a single brakcet is not allowed for an array */
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EARRIDXMULTI);
		return -1;
	}

	tmp = eval_expression(rtx, nde);
	if (HAWK_UNLIKELY(!tmp)) return -1;

	hawk_rtx_refupval(rtx, tmp);
	n = hawk_rtx_valtoint(rtx, tmp, &v);
	hawk_rtx_refdownval(rtx, tmp);
	if (HAWK_UNLIKELY(n <= -1))
	{
		ADJERR_LOC(rtx, &nde->loc);
		return -1;
	}

	if (v < 0 || v > HAWK_INT_MAX)
	{
		/* array index out of permitted range  */
		hawk_rtx_seterrnum(rtx, &nde->loc, HAWK_EARRIDXRANGE);
		return -1;
	}

	*remidx = nde->next? nde->next->next: HAWK_NULL;
	return (hawk_ooi_t)v;
}

/* ========================================================================= */

hawk_ooch_t* hawk_rtx_format (
	hawk_rtx_t* rtx, hawk_ooecs_t* out, hawk_ooecs_t* fbu,
	const hawk_ooch_t* fmt, hawk_oow_t fmt_len,
	hawk_oow_t nargs_on_stack, hawk_nde_t* args, hawk_oow_t* len)
{
	hawk_oow_t i;
	hawk_oow_t stack_arg_idx = 1;
	hawk_val_t* val;

#define OUT_CHAR(c) do { if (hawk_ooecs_ccat(out, (c)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define OUT_STR(ptr,len) do { if (hawk_ooecs_ncat(out, (ptr), (len)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define FMT_CHAR(c) do { if (hawk_ooecs_ccat(fbu, (c)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define FMT_STR(ptr,len) do { if (hawk_ooecs_ncat(fbu, (ptr), (len)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define GROW(buf) do { \
	if ((buf)->ptr) \
	{ \
		hawk_rtx_freemem(rtx, (buf)->ptr); \
		(buf)->ptr = HAWK_NULL; \
	} \
	(buf)->len += (buf)->inc; \
	(buf)->ptr = (hawk_ooch_t*)hawk_rtx_allocmem(rtx, (buf)->len * HAWK_SIZEOF(hawk_ooch_t)); \
	if ((buf)->ptr == HAWK_NULL) \
	{ \
		(buf)->len = 0; \
		return HAWK_NULL; \
	} \
}  while(0)

#define GROW_WITH_INC(buf,incv) do { \
	if ((buf)->ptr) \
	{ \
		hawk_rtx_freemem(rtx, (buf)->ptr); \
		(buf)->ptr = HAWK_NULL; \
	} \
	(buf)->len += ((incv) > (buf)->inc)? (incv): (buf)->inc; \
	(buf)->ptr = (hawk_ooch_t*)hawk_rtx_allocmem(rtx, (buf)->len * HAWK_SIZEOF(hawk_ooch_t)); \
	if ((buf)->ptr == HAWK_NULL) \
	{ \
		(buf)->len = 0; \
		return HAWK_NULL; \
	} \
} while(0)

	/* run->format.tmp.ptr should have been assigned a pointer to a block of memory before this function has been called */
	HAWK_ASSERT(rtx->format.tmp.ptr != HAWK_NULL);

	if (nargs_on_stack == (hawk_oow_t)-1)
	{
		/* dirty hack to support a single value argument instead of a tree node */
		val = (hawk_val_t*)args;
		nargs_on_stack = 2; /* indicate 2 arguments of a formatting specifier and the given value */
	}
	else
	{
		val = HAWK_NULL;
	}

	if (out == HAWK_NULL) out = &rtx->format.out;
	if (fbu == HAWK_NULL) fbu = &rtx->format.fmt;

	hawk_ooecs_clear(out);
	hawk_ooecs_clear(fbu);

	for (i = 0; i < fmt_len; i++)
	{
		hawk_int_t wp[2];
		int wp_idx;
#define WP_WIDTH     0
#define WP_PRECISION 1

		int flags;
#define FLAG_SPACE (1 << 0)
#define FLAG_HASH  (1 << 1)
#define FLAG_ZERO  (1 << 2)
#define FLAG_PLUS  (1 << 3)
#define FLAG_MINUS (1 << 4)

		if (HAWK_OOECS_LEN(fbu) == 0)
		{
			/* format specifier is empty */
			if (fmt[i] == HAWK_T('%'))
			{
				/* add % to format specifier (fbu) */
				FMT_CHAR(fmt[i]);
			}
			else
			{
				/* normal output */
				OUT_CHAR(fmt[i]);
			}
			continue;
		}

		/* handle flags */
		flags = 0;
		while (i < fmt_len)
		{
			switch (fmt[i])
			{
				case HAWK_T(' '):
					flags |= FLAG_SPACE;
					break;
				case HAWK_T('#'):
					flags |= FLAG_HASH;
					break;
				case HAWK_T('0'):
					flags |= FLAG_ZERO;
					break;
				case HAWK_T('+'):
					flags |= FLAG_PLUS;
					break;
				case HAWK_T('-'):
					flags |= FLAG_MINUS;
					break;
				default:
					goto wp_mod_init;
			}

			FMT_CHAR(fmt[i]); i++;
		}

wp_mod_init:
		wp[WP_WIDTH] = 0; /* width */
		wp[WP_PRECISION] = -1; /* precision */
		wp_idx = WP_WIDTH; /* width first */

wp_mod_main:
		if (i < fmt_len && fmt[i] == HAWK_T('*'))
		{
			/* variable width/precision modifier.
			 * take the width/precision from a parameter and
			 * transform it to a fixed length format */
			hawk_val_t* v;
			int n;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoint(rtx, v, &wp[wp_idx]);
			hawk_rtx_refdownval(rtx, v);
			if (HAWK_UNLIKELY(n <= -1)) return HAWK_NULL;

			do
			{
				n = hawk_fmt_intmax_to_oocstr(
					rtx->format.tmp.ptr,
					rtx->format.tmp.len,
					wp[wp_idx],
					10 | HAWK_FMT_INTMAX_NOTRUNC | HAWK_FMT_INTMAX_NONULL,
					-1,
					HAWK_T('\0'),
					HAWK_NULL
				);
				if (n <= -1)
				{
					/* -n is the number of characters required
					 * including terminating null  */
					GROW_WITH_INC (&rtx->format.tmp, -n);
					continue;
				}

				break;
			}
			while (1);

			FMT_STR(rtx->format.tmp.ptr, n);

			if (!args || val) stack_arg_idx++;
			else args = args->next;
			i++;
		}
		else
		{
			/* fixed width/precision modifier */
			if (i < fmt_len && hawk_is_ooch_digit(fmt[i]))
			{
				wp[wp_idx] = 0;
				do
				{
					wp[wp_idx] = wp[wp_idx] * 10 + fmt[i] - HAWK_T('0');
					FMT_CHAR(fmt[i]); i++;
				}
				while (i < fmt_len && hawk_is_ooch_digit(fmt[i]));
			}
		}

		if (wp_idx == WP_WIDTH && i < fmt_len && fmt[i] == HAWK_T('.'))
		{
			FMT_CHAR(fmt[i]); i++;
			wp[WP_PRECISION] = 0;
			wp_idx = WP_PRECISION; /* change index to precision */
			goto wp_mod_main;
		}

		if (i >= fmt_len) break;

		if (wp[WP_WIDTH] < 0)
		{
			wp[WP_WIDTH] = -wp[WP_WIDTH];
			flags |= FLAG_MINUS;
		}

		if (fmt[i] == 'd' || fmt[i] == 'i' ||
		    fmt[i] == 'x' || fmt[i] == 'X' ||
		    fmt[i] == 'b' || fmt[i] == 'B' ||
		    fmt[i] == 'o' || fmt[i] == 'u')
		{
			hawk_val_t* v;
			hawk_int_t l;
			int n;

			int fmt_flags;
			int fmt_uint = 0;
			int fmt_width;
			hawk_ooch_t fmt_fill = HAWK_T('\0');
			const hawk_ooch_t* fmt_prefix = HAWK_NULL;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoint(rtx, v, &l);
			hawk_rtx_refdownval(rtx, v);
			if (HAWK_UNLIKELY(n <= -1)) return HAWK_NULL;

			fmt_flags = HAWK_FMT_INTMAX_NOTRUNC | HAWK_FMT_INTMAX_NONULL;

			if (l == 0 && wp_idx == WP_PRECISION && wp[WP_PRECISION] == 0)
			{
				/* printf ("%.d", 0); printf ("%.0d", 0); printf ("%.*d", 0, 0); */
				/* A zero value with a precision of zero produces no character. */
				fmt_flags |= HAWK_FMT_INTMAX_NOZERO;
			}

			if (wp[WP_WIDTH] > 0)
			{
				/* justification for width greater than 0 */
				if (flags & FLAG_ZERO)
				{
					if (flags & FLAG_MINUS)
					{
						 /* FLAG_MINUS wins if both FLAG_ZERO
						  * and FLAG_MINUS are specified. */
						fmt_fill = HAWK_T(' ');
						if (flags & FLAG_MINUS)
						{
							/* left justification. need to fill the right side */
							fmt_flags |= HAWK_FMT_INTMAX_FILLRIGHT;
						}
					}
					else
					{
						if (wp_idx != WP_PRECISION) /* if precision is not specified, wp_idx is at WP_WIDTH */
						{
							/* precision not specified.
							 * FLAG_ZERO can take effect */
							fmt_fill = HAWK_T('0');
							fmt_flags |= HAWK_FMT_INTMAX_FILLCENTER;
						}
						else
						{
							fmt_fill = HAWK_T(' ');
						}
					}
				}
				else
				{
					fmt_fill = HAWK_T(' ');
					if (flags & FLAG_MINUS)
					{
						/* left justification. need to fill the right side */
						fmt_flags |= HAWK_FMT_INTMAX_FILLRIGHT;
					}
				}
			}

			switch (fmt[i])
			{
				case 'B':
				case 'b':
					fmt_flags |= 2;
					fmt_uint = 1;
					if (l && (flags & FLAG_HASH))
					{
						/* A nonzero value is prefixed with 0b */
						fmt_prefix = HAWK_T("0b");
					}
					break;

				case 'X':
					fmt_flags |= HAWK_FMT_INTMAX_UPPERCASE;
				case 'x':
					fmt_flags |= 16;
					fmt_uint = 1;
					if (l && (flags & FLAG_HASH))
					{
						/* A nonzero value is prefixed with 0x */
						fmt_prefix = HAWK_T("0x");
					}
					break;

				case 'o':
					fmt_flags |= 8;
					fmt_uint = 1;
					if (flags & FLAG_HASH)
					{
						/* Force a leading zero digit including zero.
						 * 0 with FLAG_HASH and precision 0 still emits '0'.
						 * On the contrary, 'X' and 'x' emit no digits
						 * for 0 with FLAG_HASH and precision 0. */
						fmt_flags |= HAWK_FMT_INTMAX_ZEROLEAD;
					}
					break;

				case 'u':
					fmt_uint = 1;
				default:
					fmt_flags |= 10;
					if (flags & FLAG_PLUS)
						fmt_flags |= HAWK_FMT_INTMAX_PLUSSIGN;
					if (flags & FLAG_SPACE)
						fmt_flags |= HAWK_FMT_INTMAX_EMPTYSIGN;
					break;
			}


			if (wp[WP_WIDTH] > 0)
			{
				if (wp[WP_WIDTH] > rtx->format.tmp.len)
					GROW_WITH_INC (&rtx->format.tmp, wp[WP_WIDTH] - rtx->format.tmp.len);
				fmt_width = wp[WP_WIDTH];
			}
			else fmt_width = rtx->format.tmp.len;

			do
			{
				if (fmt_uint)
				{
					/* Explicit type-casting for 'l' from hawk_int_t
					 * to hawk_uint_t is needed before passing it to
					 * hawk_fmt_uintmax_to_oocstr().
 					 *
					 * Consider a value of -1 for example.
					 * -1 is a value with all bits set.
					 * If hawk_int_t is 4 bytes and hawk_uintmax_t
					 * is 8 bytes, the value is shown below for
					 * each type respectively .
					 *     -1 - 0xFFFFFFFF (hawk_int_t)
					 *     -1 - 0xFFFFFFFFFFFFFFFF (hawk_uintmax_t)
					 * Implicit typecasting of -1 from hawk_int_t to
					 * to hawk_uintmax_t results in 0xFFFFFFFFFFFFFFFF,
					 * though 0xFFFFFFF is expected in hexadecimal.
					 */
					n = hawk_fmt_uintmax_to_oocstr (
						rtx->format.tmp.ptr,
						fmt_width,
						(hawk_uint_t)l,
						fmt_flags,
						wp[WP_PRECISION],
						fmt_fill,
						fmt_prefix
					);
				}
				else
				{
					n = hawk_fmt_intmax_to_oocstr (
						rtx->format.tmp.ptr,
						fmt_width,
						l,
						fmt_flags,
						wp[WP_PRECISION],
						fmt_fill,
						fmt_prefix
					);
				}
				if (n <= -1)
				{
					/* -n is the number of characters required */
					GROW_WITH_INC (&rtx->format.tmp, -n);
					fmt_width = -n;
					continue;
				}

				break;
			}
			while (1);

			OUT_STR (rtx->format.tmp.ptr, n);
		}
		else if (fmt[i] == HAWK_T('e') || fmt[i] == HAWK_T('E') ||
		         fmt[i] == HAWK_T('g') || fmt[i] == HAWK_T('G') ||
		         fmt[i] == HAWK_T('f'))
		{

			hawk_val_t* v;
			hawk_flt_t r;
			int n;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)  /* nargs_on_stack == (hawk_oow_t)-1 */
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoflt(rtx, v, &r);
			hawk_rtx_refdownval(rtx, v);
			if (n <= -1) return HAWK_NULL;

		#if defined(HAWK_USE_FLTMAX)
			/*FMT_CHAR(HAWK_T('j'));*/
			FMT_STR (HAWK_T("jj"), 2); /* see fmt.c for info on jj */
			FMT_CHAR(fmt[i]);
			/*if (hawk_ooecs_fcat(out, HAWK_OOECS_PTR(fbu), r) == (hawk_oow_t)-1) return HAWK_NULL;*/
			if (hawk_ooecs_fcat(out, HAWK_OOECS_PTR(fbu), &r) == (hawk_oow_t)-1) return HAWK_NULL;
		#else
			FMT_CHAR(HAWK_T('z'));
			FMT_CHAR(fmt[i]);
			if (hawk_ooecs_fcat(out, HAWK_OOECS_PTR(fbu), r) == (hawk_oow_t)-1) return HAWK_NULL;
		#endif
		}
		else if (fmt[i] == HAWK_T('c'))
		{
			hawk_ooch_t ch;
			hawk_oow_t ch_len;
			hawk_val_t* v;
			hawk_val_type_t vtype;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			vtype = HAWK_RTX_GETVALTYPE(rtx, v);
			switch (vtype)
			{
				case HAWK_VAL_NIL:
					ch = HAWK_T('\0');
					ch_len = 0;
					break;

				case HAWK_VAL_CHAR:
					ch = (hawk_ooch_t)HAWK_RTX_GETCHARFROMVAL(rtx, v);
					ch_len = 1;
					break;

				case HAWK_VAL_BCHR:
					ch = (hawk_ooch_t)HAWK_RTX_GETBCHRFROMVAL(rtx, v);
					ch_len = 1;
					break;

				case HAWK_VAL_INT:
					ch = (hawk_ooch_t)HAWK_RTX_GETINTFROMVAL(rtx, v);
					ch_len = 1;
					break;

				case HAWK_VAL_FLT:
					ch = (hawk_ooch_t)((hawk_val_flt_t*)v)->val;
					ch_len = 1;
					break;

				case HAWK_VAL_STR:
					/* printf("%c", "") => produces '\0' character */
					ch = (((hawk_val_str_t*)v)->val.len  > 0)? ((hawk_val_str_t*)v)->val.ptr[0]: '\0';
					ch_len = 1;
					break;

				case HAWK_VAL_MBS:
					ch = (((hawk_val_mbs_t*)v)->val.len > 0)? ((hawk_val_mbs_t*)v)->val.ptr[0]: '\0';
					ch_len = 1;
					break;

				case HAWK_VAL_BOB:
					ch = (((hawk_val_bob_t*)v)->val.len > 0)? ((hawk_bch_t*)((hawk_val_bob_t*)v)->val.ptr)[0]: '\0';
					ch_len = 1;
					break;

				default:
					hawk_rtx_refdownval(rtx, v);
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EVALTOCHR);
					return HAWK_NULL;
			}

			if (wp[WP_PRECISION] <= 0 || wp[WP_PRECISION] > (hawk_int_t)ch_len)
			{
				wp[WP_PRECISION] = (hawk_int_t)ch_len;
			}

			if (wp[WP_PRECISION] > wp[WP_WIDTH]) wp[WP_WIDTH] = wp[WP_PRECISION];

			if (!(flags & FLAG_MINUS))
			{
				/* right align */
				while (wp[WP_WIDTH] > wp[WP_PRECISION])
				{
					if (hawk_ooecs_ccat(out, HAWK_T(' ')) == (hawk_oow_t)-1)
					{
						hawk_rtx_refdownval(rtx, v);
						return HAWK_NULL;
					}
					wp[WP_WIDTH]--;
				}
			}

			if (wp[WP_PRECISION] > 0)
			{
				if (hawk_ooecs_ccat(out, ch) == (hawk_oow_t)-1)
				{
					hawk_rtx_refdownval(rtx, v);
					return HAWK_NULL;
				}
			}

			if (flags & FLAG_MINUS)
			{
				/* left align */
				while (wp[WP_WIDTH] > wp[WP_PRECISION])
				{
					if (hawk_ooecs_ccat (out, HAWK_T(' ')) == (hawk_oow_t)-1)
					{
						hawk_rtx_refdownval(rtx, v);
						return HAWK_NULL;
					}
					wp[WP_WIDTH]--;
				}
			}

			hawk_rtx_refdownval(rtx, v);
		}
		else if (fmt[i] == 's' || fmt[i] == 'k' || fmt[i] == 'K' || fmt[i] == 'w' || fmt[i] == 'W')
		{
			hawk_val_t* v;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			if (val)
			{
				/* val_flt_to_str() in val.c calls hawk_rtx_format() with nargs_on_stack of (hawk_oow_t)-1 and the actual value.
				 * the actual value is assigned to 'val' at the beginning of this function.
				 *
				 * the following code can drive here.
				 *     BEGIN { CONVFMT="%s"; a=98.76 ""; }
				 *
				 * when the first attempt to convert 98.76 to a textual form invokes this function with %s and 98.76.
				 * it comes to this part because the format specifier is 's'. since the floating-point type is not
				 * specially handled, hawk_rtx_valtooocstrdup() is called below. it calls val_flt_to_str() again,
				 * which eventually creates recursion and stack depletion.
				 *
				 * assuming only val_flt_to_str() calls it this way, i must convert the floating point number
				 * to text in a rather crude way without calling hawk_rtx_valtooocstrdup().
				 */
				hawk_flt_t r;
				int n;

				HAWK_ASSERT(HAWK_RTX_GETVALTYPE(rtx, val) == HAWK_VAL_FLT);

				hawk_rtx_refupval(rtx, v);
				n = hawk_rtx_valtoflt(rtx, v, &r);
				hawk_rtx_refdownval(rtx, v);
				if (n <= -1) return HAWK_NULL;

				/* format the value as if '%g' is given */
			#if defined(HAWK_USE_FLTMAX)
				FMT_STR (HAWK_T("jjg"), 3); /* see fmt.c for info on jj */
				if (hawk_ooecs_fcat(out, HAWK_OOECS_PTR(fbu), &r) == (hawk_oow_t)-1) return HAWK_NULL;
			#else
				FMT_STR (HAWK_T("zg"), 2);
				if (hawk_ooecs_fcat(out, HAWK_OOECS_PTR(fbu), r) == (hawk_oow_t)-1) return HAWK_NULL;
			#endif
			}
			else
			{
				hawk_ooch_t* str_ptr, * str_free = HAWK_NULL, ooch_tmp;
				hawk_bch_t bch_tmp;
				hawk_oow_t str_len;
				hawk_int_t k;
				hawk_val_type_t vtype;
				int bytetostr_flagged_radix = 16;

				hawk_rtx_refupval(rtx, v);

				vtype = HAWK_RTX_GETVALTYPE(rtx, v);
				switch (vtype)
				{
					case HAWK_VAL_NIL:
						str_ptr = HAWK_T("");
						str_len = 0;
						break;

					case HAWK_VAL_CHAR:
						ooch_tmp = HAWK_RTX_GETCHARFROMVAL(rtx, v);
						str_ptr = &ooch_tmp;
						str_len = 1;
						break;

					case HAWK_VAL_STR:
						str_ptr = ((hawk_val_str_t*)v)->val.ptr;
						str_len = ((hawk_val_str_t*)v)->val.len;
						break;

					case HAWK_VAL_BCHR:
					#if defined(HAWK_OOCH_IS_BCH)
						ooch_tmp = HAWK_RTX_GETBCHRFROMVAL(rtx, v);
						str_ptr = &ooch_tmp;
						str_len = 1;
					#else
						if (fmt[i] == HAWK_T('s')) goto duplicate;
						bch_tmp = HAWK_RTX_GETBCHRFROMVAL(rtx, v);
						str_ptr = (hawk_ooch_t*)&bch_tmp;
						str_len = 1;
					#endif
						break;

					case HAWK_VAL_MBS:
					#if defined(HAWK_OOCH_IS_BCH)
						str_ptr = ((hawk_val_mbs_t*)v)->val.ptr;
						str_len = ((hawk_val_mbs_t*)v)->val.len;
					#else
						if (fmt[i] == HAWK_T('s')) goto duplicate;
						str_ptr = (hawk_ooch_t*)((hawk_val_mbs_t*)v)->val.ptr;
						str_len = ((hawk_val_mbs_t*)v)->val.len;
					#endif
						break;

					case HAWK_VAL_BOB:
					#if defined(HAWK_OOCH_IS_BCH)
						str_ptr = ((hawk_val_bob_t*)v)->val.ptr;
						str_len = ((hawk_val_bob_t*)v)->val.len;
					#else
						if (fmt[i] == HAWK_T('s')) goto duplicate;
						str_ptr = (hawk_ooch_t*)((hawk_val_bob_t*)v)->val.ptr;
						str_len = ((hawk_val_bob_t*)v)->val.len;
					#endif
						break;

					default:
					duplicate:
						str_ptr = hawk_rtx_valtooocstrdup(rtx, v, &str_len);
						if (!str_ptr)
						{
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}

						str_free = str_ptr;
						break;

				}

				if (wp_idx != WP_PRECISION || wp[WP_PRECISION] <= -1 || wp[WP_PRECISION] > (hawk_int_t)str_len)
				{
					/* precision not specified, or specified to a negative value or greater than the actual length */
					wp[WP_PRECISION] = (hawk_int_t)str_len;
				}
				if (wp[WP_PRECISION] > wp[WP_WIDTH]) wp[WP_WIDTH] = wp[WP_PRECISION];

				if (!(flags & FLAG_MINUS))
				{
					/* right align */
					while (wp[WP_WIDTH] > wp[WP_PRECISION])
					{
						if (hawk_ooecs_ccat(out, HAWK_T(' ')) == (hawk_oow_t)-1)
						{
							if (str_free) hawk_rtx_freemem(rtx, str_free);
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}
						wp[WP_WIDTH]--;
					}
				}

				if (fmt[i] == 'k' || fmt[i] == 'w') bytetostr_flagged_radix |= HAWK_BYTE_TO_BCSTR_LOWERCASE;

				for (k = 0; k < wp[WP_PRECISION]; k++)
				{
					hawk_ooch_t curc;

				#if defined(HAWK_OOCH_IS_BCH)
					curc = str_ptr[k];
				#else
					if ((vtype == HAWK_VAL_MBS || vtype == HAWK_VAL_BCHR) && fmt[i] != HAWK_T('s'))
						curc = (hawk_uint8_t)((hawk_bch_t*)str_ptr)[k];
					else curc = str_ptr[k];
				#endif

					if ((fmt[i] != 's' && !HAWK_BYTE_PRINTABLE(curc)) || fmt[i] == 'w' || fmt[i] == 'W')
					{
						hawk_ooch_t xbuf[3];
						if (curc <= 0xFF)
						{
							if (hawk_ooecs_ncat(out, HAWK_T("\\x"), 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr(curc, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
						}
						else if (curc <= 0xFFFF)
						{
							hawk_uint16_t u16 = curc;
							if (hawk_ooecs_ncat(out, HAWK_T("\\u"), 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr((u16 >> 8) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr(u16 & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
						}
						else
						{
							hawk_uint32_t u32 = curc;
							if (hawk_ooecs_ncat(out, HAWK_T("\\U"), 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr((u32 >> 24) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr((u32 >> 16) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr((u32 >> 8) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_oocstr(u32 & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetostr_flagged_radix, HAWK_T('0'));
							if (hawk_ooecs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
						}
					}
					else
					{
						if (hawk_ooecs_ccat(out, curc) == (hawk_oow_t)-1)
						{
						s_fail:
							if (str_free) hawk_rtx_freemem(rtx, str_free);
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}
					}
				}

				if (str_free) hawk_rtx_freemem(rtx, str_free);

				if (flags & FLAG_MINUS)
				{
					/* left align */
					while (wp[WP_WIDTH] > wp[WP_PRECISION])
					{
						if (hawk_ooecs_ccat(out, HAWK_T(' ')) == (hawk_oow_t)-1)
						{
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}
						wp[WP_WIDTH]--;
					}
				}

				hawk_rtx_refdownval(rtx, v);
			}
		}
		else
		{
			if (fmt[i] != HAWK_T('%')) OUT_STR (HAWK_OOECS_PTR(fbu), HAWK_OOECS_LEN(fbu));
			OUT_CHAR(fmt[i]);
			goto skip_taking_arg;
		}

		if (!args || val) stack_arg_idx++;
		else args = args->next;
	skip_taking_arg:
		hawk_ooecs_clear(fbu);
	}

	/* flush uncompleted formatting sequence */
	OUT_STR (HAWK_OOECS_PTR(fbu), HAWK_OOECS_LEN(fbu));

	*len = HAWK_OOECS_LEN(out);
	return HAWK_OOECS_PTR(out);
}

/* ========================================================================= */

hawk_bch_t* hawk_rtx_formatmbs (
	hawk_rtx_t* rtx, hawk_becs_t* out, hawk_becs_t* fbu,
	const hawk_bch_t* fmt, hawk_oow_t fmt_len,
	hawk_oow_t nargs_on_stack, hawk_nde_t* args, hawk_oow_t* len)
{
	hawk_oow_t i;
	hawk_oow_t stack_arg_idx = 1;
	hawk_val_t* val;

#define OUT_MCHAR(c) do { if (hawk_becs_ccat(out, (c)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define OUT_MBS(ptr,len) do { if (hawk_becs_ncat(out, (ptr), (len)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define FMT_MCHAR(c) do {  if (hawk_becs_ccat(fbu, (c)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define FMT_MBS(ptr,len) do { if (hawk_becs_ncat(fbu, (ptr), (len)) == (hawk_oow_t)-1) return HAWK_NULL; } while(0)

#define GROW_MBSBUF(buf) do { \
	if ((buf)->ptr) \
	{ \
		hawk_rtx_freemem(rtx, (buf)->ptr); \
		(buf)->ptr = HAWK_NULL; \
	} \
	(buf)->len += (buf)->inc; \
	(buf)->ptr = (hawk_bch_t*)hawk_rtx_allocmem(rtx, (buf)->len * HAWK_SIZEOF(hawk_bch_t)); \
	if (HAWK_UNLIKELY(!(buf)->ptr)) \
	{ \
		(buf)->len = 0; \
		return HAWK_NULL; \
	} \
} while(0)

#define GROW_MBSBUF_WITH_INC(buf,incv) do { \
	if ((buf)->ptr) \
	{ \
		hawk_rtx_freemem(rtx, (buf)->ptr); \
		(buf)->ptr = HAWK_NULL; \
	} \
	(buf)->len += ((incv) > (buf)->inc)? (incv): (buf)->inc; \
	(buf)->ptr = (hawk_bch_t*)hawk_rtx_allocmem(rtx, (buf)->len * HAWK_SIZEOF(hawk_bch_t)); \
	if (HAWK_UNLIKELY(!(buf)->ptr)) \
	{ \
		(buf)->len = 0; \
		return HAWK_NULL; \
	} \
} while(0)

	/* run->format.tmp.ptr should have been assigned a pointer to a block of memory before this function has been called */
	HAWK_ASSERT(rtx->formatmbs.tmp.ptr != HAWK_NULL);

	if (nargs_on_stack == (hawk_oow_t)-1)
	{
		/* dirty hack to support a single value argument instead of a tree node */
		val = (hawk_val_t*)args;
		nargs_on_stack = 2;
	}
	else
	{
		val = HAWK_NULL;
	}

	if (out == HAWK_NULL) out = &rtx->formatmbs.out;
	if (fbu == HAWK_NULL) fbu = &rtx->formatmbs.fmt;

	hawk_becs_clear(out);
	hawk_becs_clear(fbu);

	for (i = 0; i < fmt_len; i++)
	{
		hawk_int_t wp[2];
		int wp_idx;
#define WP_WIDTH     0
#define WP_PRECISION 1

		int flags;
#define FLAG_SPACE (1 << 0)
#define FLAG_HASH  (1 << 1)
#define FLAG_ZERO  (1 << 2)
#define FLAG_PLUS  (1 << 3)
#define FLAG_MINUS (1 << 4)

		if (HAWK_BECS_LEN(fbu) == 0)
		{
			/* format specifier is empty */
			if (fmt[i] == HAWK_BT('%'))
			{
				/* add % to format specifier (fbu) */
				FMT_MCHAR (fmt[i]);
			}
			else
			{
				/* normal output */
				OUT_MCHAR (fmt[i]);
			}
			continue;
		}

		/* handle flags */
		flags = 0;
		while (i < fmt_len)
		{
			switch (fmt[i])
			{
				case HAWK_BT(' '):
					flags |= FLAG_SPACE;
					break;
				case HAWK_BT('#'):
					flags |= FLAG_HASH;
					break;
				case HAWK_BT('0'):
					flags |= FLAG_ZERO;
					break;
				case HAWK_BT('+'):
					flags |= FLAG_PLUS;
					break;
				case HAWK_BT('-'):
					flags |= FLAG_MINUS;
					break;
				default:
					goto wp_mod_init;
			}

			FMT_MCHAR (fmt[i]); i++;
		}

wp_mod_init:
		wp[WP_WIDTH] = 0; /* width */
		wp[WP_PRECISION] = -1; /* precision */
		wp_idx = WP_WIDTH; /* width first */

wp_mod_main:
		if (i < fmt_len && fmt[i] == HAWK_BT('*'))
		{
			/* variable width/precision modifier.
			 * take the width/precision from a parameter and
			 * transform it to a fixed length format */
			hawk_val_t* v;
			int n;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoint(rtx, v, &wp[wp_idx]);
			hawk_rtx_refdownval(rtx, v);
			if (HAWK_UNLIKELY(n <= -1)) return HAWK_NULL;

			do
			{
				n = hawk_fmt_intmax_to_bcstr (
					rtx->formatmbs.tmp.ptr,
					rtx->formatmbs.tmp.len,
					wp[wp_idx],
					10 | HAWK_FMT_INTMAX_NOTRUNC | HAWK_FMT_INTMAX_NONULL,
					-1,
					HAWK_BT('\0'),
					HAWK_NULL
				);
				if (n <= -1)
				{
					/* -n is the number of characters required
					 * including terminating null  */
					GROW_MBSBUF_WITH_INC (&rtx->formatmbs.tmp, -n);
					continue;
				}

				break;
			}
			while (1);

			FMT_MBS(rtx->formatmbs.tmp.ptr, n);

			if (!args || val) stack_arg_idx++;
			else args = args->next;
			i++;
		}
		else
		{
			/* fixed width/precision modifier */
			if (i < fmt_len && hawk_is_bch_digit(fmt[i]))
			{
				wp[wp_idx] = 0;
				do
				{
					wp[wp_idx] = wp[wp_idx] * 10 + fmt[i] - HAWK_BT('0');
					FMT_MCHAR (fmt[i]); i++;
				}
				while (i < fmt_len && hawk_is_bch_digit(fmt[i]));
			}
		}

		if (wp_idx == WP_WIDTH && i < fmt_len && fmt[i] == HAWK_BT('.'))
		{
			FMT_MCHAR (fmt[i]); i++;

			wp[WP_PRECISION] = 0;
			wp_idx = WP_PRECISION; /* change index to precision */
			goto wp_mod_main;
		}

		if (i >= fmt_len) break;

		if (wp[WP_WIDTH] < 0)
		{
			wp[WP_WIDTH] = -wp[WP_WIDTH];
			flags |= FLAG_MINUS;
		}

		if (fmt[i] == HAWK_BT('d') || fmt[i] == HAWK_BT('i') ||
		    fmt[i] == HAWK_BT('x') || fmt[i] == HAWK_BT('X') ||
		    fmt[i] == HAWK_BT('b') || fmt[i] == HAWK_BT('B') ||
		    fmt[i] == HAWK_BT('o'))
		{
			hawk_val_t* v;
			hawk_int_t l;
			int n;

			int fmt_flags;
			int fmt_uint = 0;
			int fmt_width;
			hawk_bch_t fmt_fill = HAWK_BT('\0');
			const hawk_bch_t* fmt_prefix = HAWK_NULL;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoint(rtx, v, &l);
			hawk_rtx_refdownval(rtx, v);
			if (HAWK_UNLIKELY(n <= -1)) return HAWK_NULL;

			fmt_flags = HAWK_FMT_INTMAX_NOTRUNC | HAWK_FMT_INTMAX_NONULL;

			if (l == 0 && wp_idx == WP_PRECISION && wp[WP_PRECISION] == 0)
			{
				/* printf ("%.d", 0); printf ("%.0d", 0); printf ("%.*d", 0, 0); */
				/* A zero value with a precision of zero produces no character. */
				fmt_flags |= HAWK_FMT_INTMAX_NOZERO;
			}

			if (wp[WP_WIDTH] > 0)
			{
				/* justification for width greater than 0 */
				if (flags & FLAG_ZERO)
				{
					if (flags & FLAG_MINUS)
					{
						 /* FLAG_MINUS wins if both FLAG_ZERO
						  * and FLAG_MINUS are specified. */
						fmt_fill = HAWK_BT(' ');
						if (flags & FLAG_MINUS)
						{
							/* left justification. need to fill the right side */
							fmt_flags |= HAWK_FMT_INTMAX_FILLRIGHT;
						}
					}
					else
					{
						if (wp_idx != WP_PRECISION) /* if precision is not set, wp_idx is at WP_WIDTH */
						{
							/* precision not specified.
							 * FLAG_ZERO can take effect */
							fmt_fill = HAWK_BT('0');
							fmt_flags |= HAWK_FMT_INTMAX_FILLCENTER;
						}
						else
						{
							fmt_fill = HAWK_BT(' ');
						}
					}
				}
				else
				{
					fmt_fill = HAWK_BT(' ');
					if (flags & FLAG_MINUS)
					{
						/* left justification. need to fill the right side */
						fmt_flags |= HAWK_FMT_INTMAX_FILLRIGHT;
					}
				}
			}

			switch (fmt[i])
			{
				case HAWK_BT('B'):
				case HAWK_BT('b'):
					fmt_flags |= 2;
					fmt_uint = 1;
					if (l && (flags & FLAG_HASH))
					{
						/* A nonzero value is prefixed with 0b */
						fmt_prefix = HAWK_BT("0b");
					}
					break;

				case HAWK_BT('X'):
					fmt_flags |= HAWK_FMT_INTMAX_UPPERCASE;
				case HAWK_BT('x'):
					fmt_flags |= 16;
					fmt_uint = 1;
					if (l && (flags & FLAG_HASH))
					{
						/* A nonzero value is prefixed with 0x */
						fmt_prefix = HAWK_BT("0x");
					}
					break;

				case HAWK_BT('o'):
					fmt_flags |= 8;
					fmt_uint = 1;
					if (flags & FLAG_HASH)
					{
						/* Force a leading zero digit including zero.
						 * 0 with FLAG_HASH and precision 0 still emits '0'.
						 * On the contrary, 'X' and 'x' emit no digits
						 * for 0 with FLAG_HASH and precision 0. */
						fmt_flags |= HAWK_FMT_INTMAX_ZEROLEAD;
					}
					break;

				default:
					fmt_flags |= 10;
					if (flags & FLAG_PLUS)
						fmt_flags |= HAWK_FMT_INTMAX_PLUSSIGN;
					if (flags & FLAG_SPACE)
						fmt_flags |= HAWK_FMT_INTMAX_EMPTYSIGN;
					break;
			}


			if (wp[WP_WIDTH] > 0)
			{
				if (wp[WP_WIDTH] > rtx->formatmbs.tmp.len)
					GROW_MBSBUF_WITH_INC (&rtx->formatmbs.tmp, wp[WP_WIDTH] - rtx->formatmbs.tmp.len);
				fmt_width = wp[WP_WIDTH];
			}
			else fmt_width = rtx->formatmbs.tmp.len;

			do
			{
				if (fmt_uint)
				{
					/* Explicit type-casting for 'l' from hawk_int_t
					 * to hawk_uint_t is needed before passing it to
					 * hawk_fmt_uintmax_to_bcstr().
 					 *
					 * Consider a value of -1 for example.
					 * -1 is a value with all bits set.
					 * If hawk_int_t is 4 bytes and hawk_uintmax_t
					 * is 8 bytes, the value is shown below for
					 * each type respectively .
					 *     -1 - 0xFFFFFFFF (hawk_int_t)
					 *     -1 - 0xFFFFFFFFFFFFFFFF (hawk_uintmax_t)
					 * Implicit typecasting of -1 from hawk_int_t to
					 * to hawk_uintmax_t results in 0xFFFFFFFFFFFFFFFF,
					 * though 0xFFFFFFF is expected in hexadecimal.
					 */
					n = hawk_fmt_uintmax_to_bcstr (
						rtx->formatmbs.tmp.ptr,
						fmt_width,
						(hawk_uint_t)l,
						fmt_flags,
						wp[WP_PRECISION],
						fmt_fill,
						fmt_prefix
					);
				}
				else
				{
					n = hawk_fmt_intmax_to_bcstr (
						rtx->formatmbs.tmp.ptr,
						fmt_width,
						l,
						fmt_flags,
						wp[WP_PRECISION],
						fmt_fill,
						fmt_prefix
					);
				}
				if (n <= -1)
				{
					/* -n is the number of characters required */
					GROW_MBSBUF_WITH_INC (&rtx->formatmbs.tmp, -n);
					fmt_width = -n;
					continue;
				}

				break;
			}
			while (1);

			OUT_MBS (rtx->formatmbs.tmp.ptr, n);
		}
		else if (fmt[i] == HAWK_BT('e') || fmt[i] == HAWK_BT('E') ||
		         fmt[i] == HAWK_BT('g') || fmt[i] == HAWK_BT('G') ||
		         fmt[i] == HAWK_BT('f'))
		{
			hawk_val_t* v;
			hawk_flt_t r;
			int n;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			n = hawk_rtx_valtoflt(rtx, v, &r);
			hawk_rtx_refdownval(rtx, v);
			if (n <= -1) return HAWK_NULL;

		#if defined(HAWK_USE_FLTMAX)
			/*FMT_MCHAR (HAWK_BT('j'));*/
			FMT_MBS (HAWK_BT("jj"), 2); /* see fmt.c for info on jj */
			FMT_MCHAR (fmt[i]);
			/*if (hawk_becs_fcat(out, HAWK_BECS_PTR(fbu), r) == (hawk_oow_t)-1) return HAWK_NULL;*/
			if (hawk_becs_fcat(out, HAWK_BECS_PTR(fbu), &r) == (hawk_oow_t)-1) return HAWK_NULL;
		#else
			FMT_MCHAR (HAWK_BT('z'));
			FMT_MCHAR (fmt[i]);
			if (hawk_becs_fcat(out, HAWK_BECS_PTR(fbu), r) == (hawk_oow_t)-1) return HAWK_NULL;
		#endif
		}
		else if (fmt[i] == HAWK_BT('c'))
		{
			hawk_bch_t ch;
			hawk_oow_t ch_len;
			hawk_val_t* v;
			hawk_val_type_t vtype;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			hawk_rtx_refupval(rtx, v);
			vtype = HAWK_RTX_GETVALTYPE(rtx, v);
			switch (vtype)
			{
				case HAWK_VAL_NIL:
					ch = HAWK_BT('\0');
					ch_len = 0;
					break;

				case HAWK_VAL_CHAR:
					ch = (hawk_bch_t)HAWK_RTX_GETCHARFROMVAL(rtx, v); /* the value may get truncated */
					ch_len = 1;
					break;

				case HAWK_VAL_BCHR:
					ch = HAWK_RTX_GETBCHRFROMVAL(rtx, v);
					ch_len = 1;
					break;

				case HAWK_VAL_INT:
					ch = (hawk_bch_t)HAWK_RTX_GETINTFROMVAL(rtx, v);
					ch_len = 1;
					break;

				case HAWK_VAL_FLT:
					ch = (hawk_bch_t)((hawk_val_flt_t*)v)->val;
					ch_len = 1;
					break;

				case HAWK_VAL_STR:
					ch_len = ((hawk_val_str_t*)v)->val.len;
					if (ch_len > 0)
					{
						/* value truncation is expected */
						ch = ((hawk_val_str_t*)v)->val.ptr[0];
						ch_len = 1;
					}
					else ch = HAWK_BT('\0');
					break;

				case HAWK_VAL_MBS:
					ch_len = ((hawk_val_mbs_t*)v)->val.len;
					if (ch_len > 0)
					{
						ch = ((hawk_val_mbs_t*)v)->val.ptr[0];
						ch_len = 1;
					}
					else ch = HAWK_BT('\0');
					break;

				case HAWK_VAL_BOB:
					ch_len = ((hawk_val_bob_t*)v)->val.len;
					if (ch_len > 0)
					{
						ch = ((hawk_bch_t*)((hawk_val_bob_t*)v)->val.ptr)[0];
						ch_len = 1;
					}
					else ch = 0;
					break;

				default:
					hawk_rtx_refdownval(rtx, v);
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EVALTOCHR);
					return HAWK_NULL;
			}

			if (wp[WP_PRECISION] <= 0 || wp[WP_PRECISION] > (hawk_int_t)ch_len)
			{
				wp[WP_PRECISION] = (hawk_int_t)ch_len;
			}

			if (wp[WP_PRECISION] > wp[WP_WIDTH]) wp[WP_WIDTH] = wp[WP_PRECISION];

			if (!(flags & FLAG_MINUS))
			{
				/* right align */
				while (wp[WP_WIDTH] > wp[WP_PRECISION])
				{
					if (hawk_becs_ccat(out, HAWK_BT(' ')) == (hawk_oow_t)-1)
					{
						hawk_rtx_refdownval(rtx, v);
						return HAWK_NULL;
					}
					wp[WP_WIDTH]--;
				}
			}

			if (wp[WP_PRECISION] > 0)
			{
				if (hawk_becs_ccat(out, ch) == (hawk_oow_t)-1)
				{
					hawk_rtx_refdownval(rtx, v);
					return HAWK_NULL;
				}
			}

			if (flags & FLAG_MINUS)
			{
				/* left align */
				while (wp[WP_WIDTH] > wp[WP_PRECISION])
				{
					if (hawk_becs_ccat(out, HAWK_BT(' ')) == (hawk_oow_t)-1)
					{
						hawk_rtx_refdownval(rtx, v);
						return HAWK_NULL;
					}
					wp[WP_WIDTH]--;
				}
			}

			hawk_rtx_refdownval(rtx, v);
		}
		else if (fmt[i] == 's' || fmt[i] == 'k' || fmt[i] == 'K' || fmt[i] == 'w' || fmt[i] == 'W')
		{
			hawk_val_t* v;

			if (!args)
			{
				if (stack_arg_idx >= nargs_on_stack)
				{
					hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
					return HAWK_NULL;
				}
				v = hawk_rtx_getarg(rtx, stack_arg_idx);
			}
			else
			{
				if (val)
				{
					if (stack_arg_idx >= nargs_on_stack)
					{
						hawk_rtx_seterrnum(rtx, HAWK_NULL, HAWK_EFMTARG);
						return HAWK_NULL;
					}
					v = val;
				}
				else
				{
					v = eval_expression(rtx, args);
					if (HAWK_UNLIKELY(!v)) return HAWK_NULL;
				}
			}

			if (val)
			{
				/* val_flt_to_str() in val.c calls hawk_rtx_format() with nargs_on_stack of (hawk_oow_t)-1 and the actual value.
				 * the actual value is assigned to 'val' at the beginning of this function.
				 *
				 * the following code can drive here.
				 *     BEGIN { CONVFMT="%s"; a=98.76 ""; }
				 *
				 * when the first attempt to convert 98.76 to a textual form invokes this function with %s and 98.76.
				 * it comes to this part because the format specifier is 's'. since the floating-point type is not
				 * specially handled, hawk_rtx_valtooocstrdup() is called below. it calls val_flt_to_str() again,
				 * which eventually creates recursion and stack depletion.
				 *
				 * assuming only val_flt_to_str() calls it this way, i must convert the floating point number
				 * to text in a rather crude way without calling hawk_rtx_valtooocstrdup().
				 */
				hawk_flt_t r;
				int n;

				HAWK_ASSERT(HAWK_RTX_GETVALTYPE(rtx, val) == HAWK_VAL_FLT);

				hawk_rtx_refupval(rtx, v);
				n = hawk_rtx_valtoflt(rtx, v, &r);
				hawk_rtx_refdownval(rtx, v);
				if (n <= -1) return HAWK_NULL;

				/* format the value as if '%g' is given */
			#if defined(HAWK_USE_FLTMAX)
				FMT_MBS (HAWK_BT("jjg"), 3); /* see fmt.c for info on jj */
				if (hawk_becs_fcat(out, HAWK_BECS_PTR(fbu), &r) == (hawk_oow_t)-1) return HAWK_NULL;
			#else
				FMT_MBS (HAWK_BT("zg"), 2);
				if (hawk_becs_fcat(out, HAWK_BECS_PTR(fbu), r) == (hawk_oow_t)-1) return HAWK_NULL;
			#endif
			}
			else
			{
				hawk_bch_t* str_ptr, * str_free = HAWK_NULL, bchr_tmp;
				hawk_ooch_t ooch_tmp;
				hawk_oow_t str_len;
				hawk_int_t k;
				hawk_val_type_t vtype;
				int bytetombs_flagged_radix = 16;

				hawk_rtx_refupval(rtx, v);

				vtype = HAWK_RTX_GETVALTYPE(rtx, v);
				switch (vtype)
				{
					case HAWK_VAL_NIL:
						str_ptr = HAWK_BT("");
						str_len = 0;
						break;

					case HAWK_VAL_BCHR:
						bchr_tmp = HAWK_RTX_GETBCHRFROMVAL(rtx, v);
						str_ptr = &bchr_tmp;
						str_len = 1;
						break;

					case HAWK_VAL_MBS:
						str_ptr = ((hawk_val_mbs_t*)v)->val.ptr;
						str_len = ((hawk_val_mbs_t*)v)->val.len;
						break;

					case HAWK_VAL_BOB:
						str_ptr = (hawk_bch_t*)((hawk_val_bob_t*)v)->val.ptr;
						str_len = ((hawk_val_bob_t*)v)->val.len;
						break;

					case HAWK_VAL_CHAR:
					#if defined(HAWK_OOCH_IS_BCH)
						bchr_tmp = HAWK_RTX_GETBCHRFROMVAL(rtx, v);
						str_ptr = &bchr_tmp;
						str_len = 1;
					#else
						if (fmt[i] == HAWK_BT('s')) goto duplicate;
						ooch_tmp = HAWK_RTX_GETCHARFROMVAL(rtx, v);
						str_ptr = (hawk_bch_t*)&ooch_tmp;
						str_len = 1 * (HAWK_SIZEOF_OOCH_T / HAWK_SIZEOF_BCH_T);
					#endif
						break;

					case HAWK_VAL_STR:
					#if defined(HAWK_OOCH_IS_BCH)
						str_ptr = ((hawk_val_str_t*)v)->val.ptr;
						str_len = ((hawk_val_str_t*)v)->val.len;
					#else
						if (fmt[i] == HAWK_BT('s')) goto duplicate;
						/* arrange to print the wide character string byte by byte regardless of byte order */
						str_ptr = (hawk_bch_t*)((hawk_val_str_t*)v)->val.ptr;
						str_len = ((hawk_val_str_t*)v)->val.len * (HAWK_SIZEOF_OOCH_T / HAWK_SIZEOF_BCH_T);
					#endif
						break;

					default:
					duplicate:
						str_ptr = hawk_rtx_valtobcstrdup(rtx, v, &str_len);
						if (!str_ptr)
						{
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}

						str_free = str_ptr;
						break;
				}

				if (wp_idx != WP_PRECISION || wp[WP_PRECISION] <= -1 || wp[WP_PRECISION] > (hawk_int_t)str_len)
				{
					/* precision not specified, or specified to a negative value or greater than the actual length */
					wp[WP_PRECISION] = (hawk_int_t)str_len;
				}
				if (wp[WP_PRECISION] > wp[WP_WIDTH]) wp[WP_WIDTH] = wp[WP_PRECISION];

				if (!(flags & FLAG_MINUS))
				{
					/* right align */
					while (wp[WP_WIDTH] > wp[WP_PRECISION])
					{
						if (hawk_becs_ccat(out, HAWK_BT(' ')) == (hawk_oow_t)-1)
						{
							if (str_free) hawk_rtx_freemem(rtx, str_free);
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}
						wp[WP_WIDTH]--;
					}
				}

				if (fmt[i] == 'k' || fmt[i] == 'w') bytetombs_flagged_radix |= HAWK_BYTE_TO_BCSTR_LOWERCASE;

				for (k = 0; k < wp[WP_PRECISION]; k++)
				{
					hawk_bch_t curc;

					curc = str_ptr[k];

					if ((fmt[i] != 's' && !HAWK_BYTE_PRINTABLE(curc)) || fmt[i] == 'w' || fmt[i] == 'W')
					{
						hawk_bch_t xbuf[3];
					#if 0 /* the range check isn't needed for hawk_bch_t. it's always <= 0xFF */

						if (curc <= 0xFF)
						{
					#endif
							if (hawk_becs_ncat(out, HAWK_BT("\\x"), 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr(curc, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
					#if 0
						}
						else if (curc <= 0xFFFF)
						{
							hawk_uint16_t u16 = curc;
							if (hawk_becs_ncat(out, HAWK_BT("\\u"), 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr((u16 >> 8) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr(u16 & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
						}
						else
						{
							hawk_uint32_t u32 = curc;
							if (hawk_becs_ncat(out, HAWK_BT("\\U"), 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr((u32 >> 24) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr((u32 >> 16) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr((u32 >> 8) & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
							hawk_byte_to_bcstr(u32 & 0xFF, xbuf, HAWK_COUNTOF(xbuf), bytetombs_flagged_radix, HAWK_BT('0'));
							if (hawk_becs_ncat(out, xbuf, 2) == (hawk_oow_t)-1) goto s_fail;
						}
					#endif
					}
					else
					{
						if (hawk_becs_ccat(out, curc) == (hawk_oow_t)-1)
						{
						s_fail:
							if (str_free) hawk_rtx_freemem(rtx, str_free);
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}
					}
				}

				if (str_free) hawk_rtx_freemem(rtx, str_free);

				if (flags & FLAG_MINUS)
				{
					/* left align */
					while (wp[WP_WIDTH] > wp[WP_PRECISION])
					{
						if (hawk_becs_ccat(out, HAWK_BT(' ')) == (hawk_oow_t)-1)
						{
							hawk_rtx_refdownval(rtx, v);
							return HAWK_NULL;
						}
						wp[WP_WIDTH]--;
					}
				}

				hawk_rtx_refdownval(rtx, v);
			}
		}
		else
		{
			if (fmt[i] != HAWK_BT('%')) OUT_MBS (HAWK_BECS_PTR(fbu), HAWK_BECS_LEN(fbu));
			OUT_MCHAR (fmt[i]);
			goto skip_taking_arg;
		}

		if (!args || val) stack_arg_idx++;
		else args = args->next;
	skip_taking_arg:
		hawk_becs_clear(fbu);
	}

	/* flush uncompleted formatting sequence */
	OUT_MBS (HAWK_BECS_PTR(fbu), HAWK_BECS_LEN(fbu));

	*len = HAWK_BECS_LEN(out);
	return HAWK_BECS_PTR(out);
}

/* ------------------------------------------------------------------------- */

void hawk_rtx_setnrflt (hawk_rtx_t* rtx, const hawk_nrflt_t* nrflt)
{
	rtx->nrflt = *nrflt;
}

void hawk_rtx_getnrflt (hawk_rtx_t* rtx, hawk_nrflt_t* nrflt)
{
	*nrflt = rtx->nrflt;
}

/* ------------------------------------------------------------------------ */

hawk_oow_t hawk_rtx_fmttoucstr_ (hawk_rtx_t* rtx, hawk_uch_t* buf, hawk_oow_t bufsz, const hawk_uch_t* fmt, ...)
{
	va_list ap;
	hawk_oow_t n;
	va_start(ap, fmt);
	n = hawk_gem_vfmttoucstr(hawk_rtx_getgem(rtx), buf, bufsz, fmt, ap);
	va_end(ap);
	return n;
}

hawk_oow_t hawk_rtx_fmttobcstr_ (hawk_rtx_t* rtx, hawk_bch_t* buf, hawk_oow_t bufsz, const hawk_bch_t* fmt, ...)
{
	va_list ap;
	hawk_oow_t n;
	va_start(ap, fmt);
	n = hawk_gem_vfmttobcstr(hawk_rtx_getgem(rtx), buf, bufsz, fmt, ap);
	va_end(ap);
	return n;
}

/* ------------------------------------------------------------------------ */

int hawk_rtx_buildrex (hawk_rtx_t* rtx, const hawk_ooch_t* ptn, hawk_oow_t len, hawk_tre_t** code, hawk_tre_t** icode)
{
	return hawk_gem_buildrex(hawk_rtx_getgem(rtx), ptn, len, !(rtx->hawk->opt.trait & HAWK_REXBOUND), code, icode);
}
