/*
    Copyright (c) 2016-2018 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 "hak-prv.h"

/* limit the `do` expression to have not more than 1 expression and
 * no variable declaration if not enclosed in parentheses */
#define LANG_LIMIT_DO

#define FOR_NONE  (0)
#define FOR_IF    (1)
#define FOR_TRY   (2)
#define FOR_CLASS (3)

#define MAX_NIVARS (HAK_SMOOI_MAX < MAX_CODE_PARAM2? HAK_SMOOI_MAX: MAX_CODE_PARAM2)
#define MAX_NCVARS (HAK_SMOOI_MAX < MAX_CODE_PARAM2? HAK_SMOOI_MAX: MAX_CODE_PARAM2)

enum
{
	VAR_NAMED,
	VAR_INDEXED,
	VAR_INST, /* instance variable */
	VAR_CLASS_I, /* class variable in class initialization scope */
	VAR_CLASS_CM, /* class variable in class method scope */
	VAR_CLASS_IM /* class variable in instance method scope */
};

enum
{
	VAR_ACCESS_PUSH,
	VAR_ACCESS_POP,
	VAR_ACCESS_STORE
};

enum
{
	/* these enumerators are stored in the lower 8 bits of
	 * the fun_type field of hak_funblk_info_t.
	 * the 9th bit of the field indicate a method is defined
	 * out of a class */

	FUN_PLAIN, /* plain function */
	FUN_IM,    /* instance method */
	FUN_CM,    /* class method */
	FUN_CIM    /* class instantiation method */
};

#define TV_BUFFER_ALIGN 256
#define BLK_INFO_BUFFER_ALIGN 128


static hak_ooch_t _empty_str_[1] = { '\0' };

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

fun plus(x y) {
	printf "plus %d %d\n" x y
	fun minus(x y) {
		printf "minus %d %d\n" x y
		- x y
	}
	+ x y
}

fun dummy(q) {
	printf "%s\n" q
}

plus 10 20 ## minus is now available after plus is executed
minus 10 1

literals -->
//
// character 'A'
// "string"
// B"byte string" <-- not yet implemented
// array ---> #[   ] or [ ] ? constant or not?  dynamic???
// hash table - dictionary  ---> #{   }
// the rest must be manipulated with code...
------------------------------ */

int hak_copy_string_to (hak_t* hak, const hak_oocs_t* src, hak_oocs_t* dst, hak_oow_t* dstcapa, int append, hak_ooch_t delim_char)
{
	hak_oow_t len, pos;
	int delim = 0;

	if (append)
	{
		pos = dst->len;
		len = dst->len + src->len;
		if (dst->len > 0 && delim_char != '\0')
		{
			delim = 1;
			len++;
		}
	}
	else
	{
		pos = 0;
		len = src->len;
	}

	if (len >= *dstcapa)
	{
		hak_ooch_t* tmp;
		hak_oow_t capa;

		capa = HAK_ALIGN(len + 1, TV_BUFFER_ALIGN);

		tmp = (hak_ooch_t*)hak_reallocmem(hak, dst->ptr, HAK_SIZEOF(*tmp) * capa);
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to grow string buffer - %js", orgmsg);
			return -1;
		}

		dst->ptr = tmp;
		*dstcapa = capa - 1;
	}

	if (delim) dst->ptr[pos++] = delim_char;
	hak_copy_oochars (&dst->ptr[pos], src->ptr, src->len);
	dst->ptr[len] = '\0';
	dst->len = len;
	return 0;
}

static int __find_word_in_string (const hak_oocs_t* haystack, const hak_oocs_t* name, int last, hak_oow_t* xindex)
{
	/* this function is inefficient. but considering the typical number
	 * of arguments and temporary variables, the inefficiency can be
	 * ignored in my opinion. the overhead to maintain the reverse lookup
	 * table from a name to an index should be greater than this simple
	 * inefficient lookup */

	hak_ooch_t* t, * e;
	hak_oow_t index, i, found;

	t = haystack->ptr;
	e = t + haystack->len;
	index = 0;
	found = HAK_TYPE_MAX(hak_oow_t);

	while (t < e)
	{
		while (t < e && *t == ' ') t++;

		for (i = 0; i < name->len; i++)
		{
			if (t >= e || name->ptr[i] != *t) goto unmatched;
			t++;
		}
		if (t >= e || *t == ' ')
		{
			if (last)
			{
				/* if last is true, find the last matching entry */
				found = index;
			}
			else
			{
				if (xindex) *xindex = index;
				return 0; /* found */
			}
		}

	unmatched:
		while (t < e)
		{
			if (*t == ' ')
			{
				t++;
				break;
			}
			t++;
		}

		index++;
	}

	if (found != HAK_TYPE_MAX(hak_oow_t))
	{
		if (xindex) *xindex = found;
		return 0; /* found */
	}

	return -1; /* not found */
}

static int add_temporary_variable (hak_t* hak, const hak_cnode_t* var, hak_oow_t dup_check_start, const hak_bch_t* desc, const hak_oocs_t* tgt)
{
	hak_oocs_t s;
	const hak_oocs_t* name;
	int x;

	name = HAK_CNODE_GET_TOK(var);

	s.ptr = hak->c->tv.s.ptr + dup_check_start;
	s.len = hak->c->tv.s.len - dup_check_start;
	if (__find_word_in_string(&s, name, 0, HAK_NULL) >= 0)
	{
		if (tgt)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEDUP, HAK_CNODE_GET_LOC(var),
				"duplicate %hs name '%.*js' for '%.*js'",
				desc, name->len, name->ptr, tgt->len, tgt->ptr);
		}
		else
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEDUP, HAK_CNODE_GET_LOC(var),
				"duplicate %hs name '%.*js'",
				desc, name->len, name->ptr);
		}
		return -1;
	}
	x = hak_copy_string_to(hak, name, &hak->c->tv.s, &hak->c->tv.capa, 1, ' ');
	if (HAK_LIKELY(x >= 0)) hak->c->tv.wcount++;
	return x;
}

static int kill_temporary_variables (hak_t* hak, hak_oow_t start_wpos, hak_oow_t end_wpos)
{
	/* this function doesn't remove the added temporary variable nor does it lower the word count.
	 * it simply changes a word at the given postion to some garbage characters so that
	 * the variable can't be found in the search */
	hak_oow_t i;

	for (i = start_wpos; i < end_wpos; i++)
	{
		if (hak->c->tv.s.ptr[i] != ' ')
		{
			hak->c->tv.s.ptr[i] = '('; /* HACK!! put a special character which can't form a variable name */
		}
	}
	return 0;
}

static void kill_temporary_variable_at_offset (hak_t* hak, hak_oow_t offset)
{
	/* this is a hacky function. it's better to implement kill_temporary_variables() which uses word positions */
	HAK_ASSERT(hak, offset < hak->c->tv.s.len);
	HAK_ASSERT(hak, hak->c->tv.s.ptr[offset] != ' ');
	hak->c->tv.s.ptr[offset] = '('; /* HACK!! put a special character which can't form a variable name */
}

static int init_class_level_variable_buffer (hak_t* hak, hak_oocsc_t* dst, const hak_ooch_t* ivptr, hak_oow_t ivlen)
{
	hak_ooch_t* tmp;
	hak_oow_t capa = 128;

	if (ivlen > capa) capa = ivlen;

	tmp = (hak_ooch_t*)hak_allocmem(hak, (capa + 1) * HAK_SIZEOF(*dst->ptr));
	if (HAK_UNLIKELY(!tmp)) return -1;

	dst->ptr = tmp;
	dst->len = ivlen;
	dst->capa = capa;

	if (ivlen > 0) hak_copy_oochars_to_oocstr(dst->ptr, dst->capa + 1, ivptr, ivlen);

	return 0;
}

static void fini_class_level_variable_buffer (hak_t* hak, hak_oocsc_t* dst)
{
	if (dst->ptr)
	{
		hak_freemem(hak, dst->ptr);
		dst->ptr = HAK_NULL;
		dst->len = 0;
		dst->capa = 0;
	}
}

static int add_class_level_variable (hak_t* hak, hak_oocsc_t* dst, hak_oocsc_t* altdst, const hak_cnode_t* var, const hak_bch_t* desc)
{
	/* it downcasts hak_oocsc_t* to hak_oocs_t*. take extra care to keep their type defintion
	 * compatible for downcasting in hak-cmn.h */
	const hak_oocs_t* name;

	name = HAK_CNODE_GET_TOK(var);
	if (__find_word_in_string((hak_oocs_t*)dst, name, 0, HAK_NULL) >= 0 ||
	   (__find_word_in_string((hak_oocs_t*)altdst, name, 0, HAK_NULL) >= 0))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEDUP, HAK_CNODE_GET_LOC(var),
			"duplicate %hs variable name '%.*js'", desc, name->len, name->ptr);
		return -1;
	}
	return hak_copy_string_to(hak, name, (hak_oocs_t*)dst, &dst->capa, 1, ' ');
}

static int is_in_top_scope (hak_t* hak)
{
	hak_funblk_info_t* fbi;
/*printf (">>> ---- funblk.depth ....%d\n", (int)hak->c->funblk.depth);*/
	if (hak->c->funblk.depth > 0) return 0;
	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
/*printf ("fbi->clsblk_top....%d\n", (int)fbi->clsblk_top);*/
	return fbi->clsblk_top < 0;
}

static int is_in_top_fun_scope (hak_t* hak)
{
	return hak->c->funblk.depth == 0;
}

static int is_in_class_init_scope (hak_t* hak)
{
	hak_funblk_info_t* fbi;
	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	return fbi->clsblk_top >= 0;
}

static int is_in_class_init_scope_but_not_at_llvl (hak_t* hak, hak_oow_t llvl)
{
	hak_funblk_info_t* fbi;
	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	return fbi->clsblk_top >= 0 && hak->c->clsblk.info[fbi->clsblk_top].def_llvl != llvl;
}

static int is_in_class_method_scope (hak_t* hak)
{
	hak_oow_t i;

	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);
	for (i = hak->c->funblk.depth + 1; i > 0; )
	{
		hak_funblk_info_t* fbi;
		fbi = &hak->c->funblk.info[--i];
		if (fbi->clsblk_top >= 0)
		{
			if (i >= hak->c->funblk.depth) return 0; /* in class initialization scope */
			return 1; /* in class method scope */
		}
	}

	return 0; /* in plain function scope */
}

static int find_variable_backward_with_word (hak_t* hak, const hak_oocs_t* name, const hak_loc_t* loc, int class_level_only, hak_var_info_t* vi)
{
	hak_oow_t i;

	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);
	HAK_ASSERT(hak, hak->c->funblk.info[hak->c->funblk.depth].tmprlen == hak->c->tv.s.len);

	/* depth begins at -1. so it is the actual index. let looping begin at depth + 1
	 * to avoid an extra exit check without it */
	for (i = hak->c->funblk.depth + 1; i > 0; )
	{
		hak_funblk_info_t* fbi;
		hak_oocs_t haystack;
		hak_oow_t parent_tmprcnt, parent_tmprlen, index;

		fbi = &hak->c->funblk.info[--i];

		if (fbi->clsblk_top >= 0)
		{
			/* this function block has a class defined.
			 * that is, it is in a class defintion.
			 * variable lookup must be limited to the class scope */
			hak_clsblk_info_t* cbi;

		#if 0
			for (j = fbi->clsblk_top + 1; j > fbi->clsblk_base; )
			{
				cbi = &hak->c->clsblk.info[--j];
		#endif
				cbi = &hak->c->clsblk.info[fbi->clsblk_top];

				/* find the name in the instance variables */
				haystack.ptr = cbi->ivars.ptr;
				haystack.len = cbi->ivars.len;

				if (__find_word_in_string(&haystack, name, 1, &index) >= 0)
				{
					hak_oow_t fi;

					if (i >= hak->c->funblk.depth)
					{
						/* instance variables are accessible only in an instance method defintion scope.
						 * it is in class initialization scope */
						hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, loc,
							"prohibited access to instance variable around '%.*js'", name->len, name->ptr);
						return -1;
					}

					for (fi = hak->c->funblk.depth + 1; fi > i; ) /* TOOD: review this loop for correctness */
					{
						/* 'i' is the function level that holds the class defintion block. the check must not go past it */
						if ((hak->c->funblk.info[--fi].fun_type & 0xFF) == FUN_CM)
						{
							/* the function where this variable is defined is a class method or an plain function block within a class method*/
							hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, loc,
								"prohibited access to instance variable in class method context around '%.*js'",
								name->len, name->ptr);
							return -1;
						}

						/* instance methods and instantiation methods can access instance variables */
						if ((hak->c->funblk.info[fi].fun_type & 0xFF) != FUN_PLAIN) break;
					}

					vi->type = VAR_INST;
					vi->ctx_offset = 0;
					vi->index_in_ctx = index;
/*
HAK_INFO6(hak, "FOUND INST VAR [%.*js]...[%.*js]................ ===> ctx_offset %d index %d\n",
	haystack.len, haystack.ptr, name->len, name->ptr, (int)(vi->ctx_offset), (int)vi->index_in_ctx);
*/
					return 1;
				}

				/* find the name in the class variables */
				haystack.ptr = cbi->cvars.ptr;
				haystack.len = cbi->cvars.len;
				if (__find_word_in_string(&haystack, name, 1, &index) >= 0)
				{
					/* TODO: VAR_CLASS_CM vs VAR_CLASS_IM, need to know if it's an instance method or a class method */
/* TODO: check if it's in the class variable .... */
					vi->type = (i >= hak->c->funblk.depth? VAR_CLASS_I: VAR_CLASS_IM);
					vi->ctx_offset = 0;
					vi->index_in_ctx = index;
/*
HAK_INFO6(hak, "FOUND CLASS VAR [%.*js]...[%.*js]................ ===> ctx_offset %d index %d\n",
	haystack.len, haystack.ptr, name->len, name->ptr, (int)(vi->ctx_offset), (int)vi->index_in_ctx);
*/
					return 1;
				}
	#if 0
			}

			if (i == hak->c->funblk.depth)
			{
				/* this condition indicates that the current function level contains a class defintion
				 * and this variable is looked up inside the class defintion */
HAK_INFO2(hak, "CLASS NAMED VAR [%.*js]\n", name->len, name->ptr);
				vi->type = VAR_CLASS;/*_NAMED;*/ // TODO: create VAR_CLASS_NAMED???
				vi->ctx_offset = 0;
				vi->index_in_ctx = 0;
			}
	#endif

			break; /* stop searching beyond class definition */
		}

		if (class_level_only) continue; /* skip local variable declarations */

		if (HAK_LIKELY(i > 0))
		{
			parent_tmprlen = hak->c->funblk.info[i - 1].tmprlen;
			parent_tmprcnt = hak->c->funblk.info[i - 1].tmprcnt;
		}
		else
		{
			parent_tmprlen = 0;
			parent_tmprcnt = 0;
		}

		/* narrow the search scope to the current block */
		haystack.ptr = &hak->c->tv.s.ptr[parent_tmprlen];
		haystack.len = fbi->tmprlen - parent_tmprlen;

		if (__find_word_in_string(&haystack, name, 1, &index) >= 0)
		{
			/* temporary variables or arguments */
			vi->type = VAR_INDEXED;
			vi->ctx_offset = hak->c->funblk.depth - i; /* context offset */
			vi->index_in_ctx = index;
/*HAK_INFO4(hak, "FOUND ...[%.*js]................ ===> ctx_offset %d index %d\n", name->len, name->ptr, (int)(vi->ctx_offset), (int)vi->index_in_ctx);*/

			if (vi->ctx_offset > 0)
			{
				/* the current function block accesses temporaries in an outer function block */
				hak->c->funblk.info[hak->c->funblk.depth].access_outer = 1;
				/* temporaries in an outer function block is accessed by the current function block */

				if (i > 0) hak->c->funblk.info[i - 1].accessed_by_inner = 1;
			}

			return 1;
		}
	}

/*HAK_INFO2(hak, "NOT FOUND => %.*js\n", name->len, name->ptr); */
	return 0; /* not found */
}

static int find_variable_backward_with_token (hak_t* hak, const hak_cnode_t* cnode, hak_var_info_t* vi)
{
	if (HAK_CNODE_IS_DSYMBOL_CLA(cnode))
	{
		/* prefixed with self or super. remove the first segment */
		hak_oocs_t newtok;
		newtok = *HAK_CNODE_GET_TOK(cnode);
		while (*newtok.ptr != '.')
		{
			newtok.ptr++;
			newtok.len--;
		}
		newtok.ptr++;
		newtok.len--;
		return find_variable_backward_with_word(hak, &newtok, HAK_CNODE_GET_LOC(cnode), 1, vi);
	}

	return find_variable_backward_with_word(hak, HAK_CNODE_GET_TOK(cnode), HAK_CNODE_GET_LOC(cnode), HAK_CNODE_IS_DSYMBOL_CLA(cnode), vi);
}

/* ========================================================================= */

static int check_block_expression_as_body (hak_t* hak, hak_cnode_t* c, const hak_cnode_t* ctx, int for_what)
{
	hak_cnode_t* car = HAK_NULL, * cdr;

	/* variable declaration is disallowed.
	 * a block expression is allowed if not followed by another expression.
	 * unlike the function name, other types of expressions are allowed if not followed by another expression.
	 */
	if (!c || !HAK_CNODE_IS_CONS(c)) goto no_block; /* not cons */

	car = HAK_CNODE_CONS_CAR(c);
	if (!car || (HAK_CNODE_IS_CONS_CONCODED(car, HAK_CONCODE_VLIST) ||
	             HAK_CNODE_IS_ELIST_CONCODED(car, HAK_CONCODE_VLIST)))
	{
	no_block:
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLOCK,
			(car? HAK_CNODE_GET_LOC(car): c? HAK_CNODE_GET_LOC(c): HAK_CNODE_GET_LOC(ctx)),
			"block expression expected as '%.*js' body", HAK_CNODE_GET_TOKLEN(ctx), HAK_CNODE_GET_TOKPTR(ctx)
		);
		return -1;
	}

	if (for_what == FOR_CLASS)
	{
		/* the class body must be enclosed in { .. }. e.g class X { ... } */
		if (!HAK_CNODE_IS_CONS_CONCODED(car, HAK_CONCODE_BLOCK) &&
		    !HAK_CNODE_IS_ELIST_CONCODED(car, HAK_CONCODE_BLOCK)) goto no_block;
	}

	/* there are special words that can't start a new expression */
	if (HAK_CNODE_IS_TYPED(car, HAK_CNODE_ELIF) ||
	    HAK_CNODE_IS_TYPED(car, HAK_CNODE_ELSE) ||
	    HAK_CNODE_IS_TYPED(car, HAK_CNODE_CATCH))
	{
		goto no_block;
	}

	cdr = HAK_CNODE_CONS_CDR(c);
	if (cdr)
	{
		/* there is redundant expression after the block expression */
		if (HAK_CNODE_IS_CONS(cdr))
		{
			hak_cnode_t* nxt;
			nxt = HAK_CNODE_CONS_CAR(cdr);

			if (for_what == FOR_IF)
			{
				/* after the body for `if` or `elif`, there can come `elif` or `else` */
				if (HAK_CNODE_IS_TYPED(nxt, HAK_CNODE_ELIF) ||
				    HAK_CNODE_IS_TYPED(nxt, HAK_CNODE_ELSE)) goto ok;
			}
			else if (for_what == FOR_TRY)
			{
				if (HAK_CNODE_IS_TYPED(nxt, HAK_CNODE_CATCH)) goto ok;
			}
		}

		hak_setsynerrbfmt(
			hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(cdr),
			"redundant expression prohibited after '%.*js' body",
			HAK_CNODE_GET_TOKLEN(ctx), HAK_CNODE_GET_TOKPTR(ctx)
		);
		return -1;
	}

ok:
	return 0;
}

/* ========================================================================= */

static HAK_INLINE int add_literal (hak_t* hak, hak_oop_t obj, hak_oow_t* index)
{
	hak_oow_t lfbase;
	int n;

	lfbase = (hak->option.trait & HAK_TRAIT_INTERACTIVE)? hak->c->funblk.info[hak->c->funblk.depth].lfbase: 0;
	n = hak_addliteraltocode(hak, &hak->code, obj, lfbase, index);
	if (n <= -1) return -1;
	if (*index >= MAX_CODE_PARAM2)
	{
		/* most literal-related instructions can't handle index higher than MAX_CODE_PARAM2.
		 * MAKE_FUNCTION uses MAX_CODE_PARAM2 as an indicator that no name literal is specified.
		 * To be safe, limit the maximum number of index to MAX_CODE_PARAM2 - 1 */
		hak_seterrbfmt(hak, HAK_EFLOOD, "literal frame full - %O\n", obj);
		return -1;
	}
	return 0;
}

/* ========================================================================= */

static HAK_INLINE void patch_instruction (hak_t* hak, hak_oow_t index, hak_oob_t bc)
{
	HAK_ASSERT(hak, index < hak->code.bc.len);
	hak->code.bc.ptr[index] = bc;
}

static int emit_byte_instruction (hak_t* hak, hak_oob_t bc, const hak_loc_t* srcloc)
{
	/* the context object has the ip field. it should be representable
	 * in a small integer. for simplicity, limit the total byte code length
	 * to fit in a small integer. because 'ip' points to the next instruction
	 * to execute, the upper bound should be (max - 1) so that 'ip' stays
	 * at the max when incremented */
	if (hak->code.bc.len == HAK_SMOOI_MAX - 1)
	{
		hak_seterrnum(hak, HAK_EBCFULL); /* byte code full/too big */
		return -1;
	}

	if (hak->code.bc.len >= hak->code.bc.capa)
	{
		hak_oow_t newcapa;
		hak_oob_t* tmp;
		hak_dbgi_t* tmp2;

		newcapa = HAK_ALIGN(hak->code.bc.capa + 1, HAK_BC_BUFFER_ALIGN);
		tmp = (hak_oob_t*)hak_reallocmem(hak, hak->code.bc.ptr, HAK_SIZEOF(*tmp) * newcapa);
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to grow byte code buffer - %js", orgmsg);
			return -1;
		}

		tmp2 = (hak_dbgi_t*)hak_reallocmem(hak, hak->code.dbgi, HAK_SIZEOF(*tmp2) * newcapa);
		if (HAK_UNLIKELY(!tmp2))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to grow debug info buffer - %js", orgmsg);
			hak_freemem(hak, tmp);
			return -1;
		}
		HAK_MEMSET(&tmp2[hak->code.bc.capa], 0, HAK_SIZEOF(*tmp2) * (newcapa - hak->code.bc.capa));

		hak->code.bc.ptr = tmp;
		hak->code.bc.capa = newcapa;
		hak->code.dbgi = tmp2;
	}

	hak->code.bc.ptr[hak->code.bc.len] = bc;

	if (srcloc)
	{
		hak->code.dbgi[hak->code.bc.len].fname = srcloc->file;
		hak->code.dbgi[hak->code.bc.len].sline = srcloc->line;
	}

	hak->code.bc.len++;
	return 0;
}

/*
COMMENTED OUT TEMPORARILY
int hak_emitbyteinstruction (hak_t* hak, hak_oob_t bc)
{
	return emit_byte_instruction(hak, bc, HAK_NULL);
}*/

static int emit_one_param_instruction (hak_t* hak, int cmd, hak_oow_t param_1, const hak_loc_t* srcloc)
{
	hak_oob_t bc;

	switch (cmd)
	{
		case HAK_CODE_PUSH_IVAR_0:
		case HAK_CODE_STORE_INTO_IVAR_0:
		case HAK_CODE_POP_INTO_IVAR_0:
		case HAK_CODE_PUSH_TEMPVAR_0:
		case HAK_CODE_STORE_INTO_TEMPVAR_0:
		case HAK_CODE_POP_INTO_TEMPVAR_0:
			if (param_1 < 8)
			{
				/* low 3 bits to hold the parameter */
				bc = (hak_oob_t)(cmd & 0xF8) | (hak_oob_t)param_1;
				goto write_short;
			}
			else
			{
				/* convert the code to a long version */
				bc = cmd | 0x80;
				goto write_long;
			}

		case HAK_CODE_PUSH_LITERAL_0:
			if (param_1 < 8)
			{
				/* low 3 bits to hold the parameter */
				bc = (hak_oob_t)(cmd & 0xF8) | (hak_oob_t)param_1;
				goto write_short;
			}
			else if (param_1 <= MAX_CODE_PARAM)
			{
				bc = HAK_CODE_PUSH_LITERAL_X; /* cmd | 0x80 */
				goto write_long;
			}
			else
			{
				bc = HAK_CODE_PUSH_LITERAL_X2; /* HAK_CODE_PUSH_LITERAL_4 | 0x80 */
				goto write_long2;
			}

		case HAK_CODE_PUSH_OBJECT_0:
		case HAK_CODE_STORE_INTO_OBJECT_0:
		case HAK_CODE_POP_INTO_OBJECT_0:
		case HAK_CODE_JUMP_FORWARD_0:
		case HAK_CODE_JUMP_BACKWARD_0:
		case HAK_CODE_CALL_0:
		case HAK_CODE_SEND_0:
		case HAK_CODE_SEND_TO_SUPER_0:
			if (param_1 < 4)
			{
				/* low 2 bits to hold the parameter */
				bc = (hak_oob_t)(cmd & 0xFC) | (hak_oob_t)param_1;
				goto write_short;
			}
			else
			{
				/* convert the code to a long version */
				bc = cmd | 0x80;
				goto write_long;
			}

		case HAK_CODE_JUMP_FORWARD_IF_TRUE:
		case HAK_CODE_JUMP_FORWARD_IF_FALSE:
		case HAK_CODE_JUMP2_FORWARD_IF_TRUE:
		case HAK_CODE_JUMP2_FORWARD_IF_FALSE:
		case HAK_CODE_JUMP2_FORWARD:
		case HAK_CODE_JUMP_BACKWARD_IF_TRUE:
		case HAK_CODE_JUMP_BACKWARD_IF_FALSE:
		case HAK_CODE_JUMP2_BACKWARD_IF_TRUE:
		case HAK_CODE_JUMP2_BACKWARD_IF_FALSE:
		case HAK_CODE_JUMP2_BACKWARD:

		case HAK_CODE_PUSH_CVAR_I_X:
		case HAK_CODE_STORE_INTO_CVAR_I_X:
		case HAK_CODE_POP_INTO_CVAR_I_X:
		case HAK_CODE_PUSH_CVAR_M_X:
		case HAK_CODE_STORE_INTO_CVAR_M_X:
		case HAK_CODE_POP_INTO_CVAR_M_X:

		case HAK_CODE_CLASS_CMSTORE:
		case HAK_CODE_CLASS_CIMSTORE:
		case HAK_CODE_CLASS_IMSTORE:
		case HAK_CODE_TRY_ENTER:
		case HAK_CODE_TRY_ENTER2:
		case HAK_CODE_PUSH_INTLIT:
		case HAK_CODE_PUSH_NEGINTLIT:
		case HAK_CODE_PUSH_CHARLIT:

		case HAK_CODE_MAKE_DIC: /* TODO: don't these need write_long2? */
		case HAK_CODE_MAKE_ARRAY:
		case HAK_CODE_MAKE_BYTEARRAY:
		case HAK_CODE_MAKE_CHARARRAY:
		case HAK_CODE_POP_INTO_ARRAY:
		case HAK_CODE_POP_INTO_BYTEARRAY:
		case HAK_CODE_POP_INTO_CHARARRAY:
			bc = cmd;
			goto write_long;
	}

	hak_seterrbfmt(hak, HAK_EINVAL, "unhandled single-parameter instruction %u", (unsigned int)cmd);
	return -1;

write_short: /* short parameter */
	if (emit_byte_instruction(hak, bc, srcloc) <= -1) return -1;
	return 0;

write_long: /* long parameter */
	if (param_1 > MAX_CODE_PARAM)
	{
		hak_seterrbfmt(hak, HAK_ERANGE, "parameter too large to single-parameter instruction %u", (unsigned int)cmd);
		return -1;
	}
#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 8) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 0) & 0xFF, HAK_NULL) <= -1) return -1;
#else
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, param_1, HAK_NULL) <= -1) return -1;
#endif
	return 0;

write_long2: /* double-long parameter */
	if (param_1 > MAX_CODE_PARAM2)
	{
		hak_seterrbfmt(hak, HAK_ERANGE, "parameter too large to single-parameter instruction %u", (unsigned int)cmd);
		return -1;
	}
#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 24) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 16) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >>  8) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >>  0) & 0xFF, HAK_NULL) <= -1) return -1;
#else
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 8) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 0) & 0xFF, HAK_NULL) <= -1) return -1;
#endif
	return 0;
}

static int emit_two_param_instruction (hak_t* hak, int cmd, hak_oow_t param_1, hak_oow_t param_2, const hak_loc_t* srcloc)
{
	hak_oob_t bc;

	switch (cmd)
	{
		case HAK_CODE_STORE_INTO_CTXTEMPVAR_0:
		case HAK_CODE_POP_INTO_CTXTEMPVAR_0:
		case HAK_CODE_PUSH_CTXTEMPVAR_0:
		case HAK_CODE_PUSH_OBJVAR_0:
		case HAK_CODE_STORE_INTO_OBJVAR_0:
		case HAK_CODE_POP_INTO_OBJVAR_0:
			if (param_1 < 4 && param_2 < 0xFF)
			{
				/* low 2 bits of the instruction code is the first parameter */
				bc = (hak_oob_t)(cmd & 0xFC) | (hak_oob_t)param_1;
				goto write_short;
			}
			else
			{
				/* convert the code to a long version */
				bc = cmd | 0x80;
				goto write_long;
			}

		/* MAKE_FUNCTION is a quad-parameter instruction.
		 * The caller must emit two more parameters after the call to this function.
		 * however the instruction format is the same up to the second
		 * parameters between MAKE_FUNCTION and MAKE_BLOCK.
		 */
		case HAK_CODE_MAKE_FUNCTION:
		case HAK_CODE_MAKE_BLOCK:
		case HAK_CODE_CALL_R:
		case HAK_CODE_SEND_R:
			bc = cmd;
			goto write_long;
	}

	hak_seterrbfmt(hak, HAK_EINVAL, "unhandled double-parameter instruction %u", (unsigned int)cmd);
	return -1;

write_short:
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, param_2, HAK_NULL) <= -1) return -1;
	return 0;

write_long:
	if (param_1 > MAX_CODE_PARAM || param_2 > MAX_CODE_PARAM)
	{
		hak_seterrbfmt(hak, HAK_ERANGE, "parameter too large to double-parameter instruction 0x%u - param_1 0x%zu param_2 0x%zu", (unsigned int)cmd, param_1, param_2);
		return -1;
	}
#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 8) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_1 >> 0) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_2 >> 8) & 0xFF, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, (param_2 >> 0) & 0xFF, HAK_NULL) <= -1) return -1;
#else
	if (emit_byte_instruction(hak, bc, srcloc) <= -1 ||
	    emit_byte_instruction(hak, param_1, HAK_NULL) <= -1 ||
	    emit_byte_instruction(hak, param_2, HAK_NULL) <= -1) return -1;
#endif
	return 0;
}

static HAK_INLINE int emit_long_param (hak_t* hak, hak_oow_t param)
{
	/* generate a long parameter */
	if (param > MAX_CODE_PARAM)
	{
		hak_seterrnum(hak, HAK_ERANGE);
		return -1;
	}

#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	/* take up 2 bytes */
	return (emit_byte_instruction(hak, (param >> 8) & 0xFF, HAK_NULL) <= -1 ||
	        emit_byte_instruction(hak, (param >> 0) & 0xFF, HAK_NULL) <= -1)? -1: 0;
#else
	/* take up 1 byte */
	return emit_byte_instruction(hak, param_1, HAK_NULL);
#endif
}

static HAK_INLINE int emit_extended_long_param (hak_t* hak, hak_oow_t param)
{
	/* generate an extended long parameter */
	if (param > MAX_CODE_PARAM2)
	{
		hak_seterrnum(hak, HAK_ERANGE);
		return -1;
	}

#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	/* take up 4 bytes */
	return (emit_byte_instruction(hak, (param >> 24) & 0xFF, HAK_NULL) <= -1 ||
	        emit_byte_instruction(hak, (param >> 16) & 0xFF, HAK_NULL) <= -1 ||
	        emit_byte_instruction(hak, (param >> 8) & 0xFF, HAK_NULL) <= -1 ||
	        emit_byte_instruction(hak, (param >> 0) & 0xFF, HAK_NULL) <= -1)? -1: 0;
#else
	/* take up 2 bytes */
	return (emit_byte_instruction(hak, (param >> 8) & 0xFF, HAK_NULL) <= -1 ||
	        emit_byte_instruction(hak, (param >> 0) & 0xFF, HAK_NULL) <= -1)? -1: 0;
#endif
}

static int emit_push_literal (hak_t* hak, hak_oop_t obj, const hak_loc_t* srcloc)
{
	hak_oow_t index;

	if (HAK_OOP_IS_SMOOI(obj))
	{
		hak_ooi_t i;

		i = HAK_OOP_TO_SMOOI(obj);
		switch (i)
		{
			case -1:
				return emit_byte_instruction(hak, HAK_CODE_PUSH_NEGONE, srcloc);

			case 0:
				return emit_byte_instruction(hak, HAK_CODE_PUSH_ZERO, srcloc);

			case 1:
				return emit_byte_instruction(hak, HAK_CODE_PUSH_ONE, srcloc);

			case 2:
				return emit_byte_instruction(hak, HAK_CODE_PUSH_TWO, srcloc);
		}

		if (i >= 0 && i <= MAX_CODE_PARAM)
		{
			return emit_one_param_instruction(hak, HAK_CODE_PUSH_INTLIT, i, srcloc);
		}
		else if (i < 0 && i >= -(hak_ooi_t)MAX_CODE_PARAM)
		{
			return emit_one_param_instruction(hak, HAK_CODE_PUSH_NEGINTLIT, -i, srcloc);
		}
	}
	else if (HAK_OOP_IS_CHAR(obj))
	{
		hak_ooch_t i;

		i = HAK_OOP_TO_CHAR(obj);

		if (i >= 0 && i <= MAX_CODE_PARAM)
			return emit_one_param_instruction(hak, HAK_CODE_PUSH_CHARLIT, i, srcloc);
	}

	if (add_literal(hak, obj, &index) <= -1 ||
	    emit_one_param_instruction(hak, HAK_CODE_PUSH_LITERAL_0, index, srcloc) <= -1) return -1;

	return 0;
}

static HAK_INLINE void patch_long_jump (hak_t* hak, hak_ooi_t jip, hak_ooi_t jump_offset)
{
	if (jump_offset > MAX_CODE_JUMP)
	{
		/* switch to JUMP2 instruction to allow a bigger jump offset.
		 * up to twice MAX_CODE_JUMP only */

		HAK_ASSERT(hak, jump_offset <= MAX_CODE_JUMP * 2);

		HAK_ASSERT(hak, hak->code.bc.ptr[jip] == HAK_CODE_JUMP_FORWARD_X ||
		                 hak->code.bc.ptr[jip] == HAK_CODE_JUMP_FORWARD_IF_TRUE ||
		                 hak->code.bc.ptr[jip] == HAK_CODE_JUMP_FORWARD_IF_FALSE ||
		                 hak->code.bc.ptr[jip] == HAK_CODE_JUMP_BACKWARD_X ||
		                 hak->code.bc.ptr[jip] == HAK_CODE_JUMP_BACKWARD_IF_TRUE ||
		                 hak->code.bc.ptr[jip] == HAK_CODE_JUMP_BACKWARD_IF_FALSE ||
		                 hak->code.bc.ptr[jip] == HAK_CODE_TRY_ENTER);

		/* JUMP2 instructions are chosen to be greater than its JUMP counterpart by 1 */
		patch_instruction(hak, jip, hak->code.bc.ptr[jip] + 1);
		jump_offset -= MAX_CODE_JUMP;
	}

#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	patch_instruction(hak, jip + 1, jump_offset >> 8);
	patch_instruction(hak, jip + 2, jump_offset & 0xFF);
#else
	patch_instruction(hak, jip + 1, jump_offset);
#endif
}

static HAK_INLINE void patch_long_param (hak_t* hak, hak_ooi_t ip, hak_oow_t param)
{
	HAK_ASSERT(hak, param <= MAX_CODE_PARAM);

#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	patch_instruction(hak, ip, param >> 8);
	patch_instruction(hak, ip + 1, param & 0xFF);
#else
	patch_instruction(hak, ip, param);
#endif
}

/*
static HAK_INLINE void patch_two_long_params (hak_t* hak, hak_ooi_t ip, hak_oow_t param_1, hak_oow_t param_2)
{
#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	patch_instruction(hak, ip, param_1 >> 8);
	patch_instruction(hak, ip + 1, param_1 & 0xFF);
	patch_instruction(hak, ip + 2, param_2 >> 8);
	patch_instruction(hak, ip + 3, param_2 & 0xFF);
#else
	patch_instruction(hak, ip, param_1);
	patch_instruction(hak, ip + 1, param_2);
#endif
}
*/

static HAK_INLINE void patch_extended_long_param (hak_t* hak, hak_ooi_t ip, hak_oow_t param)
{
	HAK_ASSERT(hak, param <= MAX_CODE_PARAM2);

#if (HAK_CODE_LONG_PARAM_SIZE == 2)
	patch_instruction(hak, ip,     (param >> 24) & 0xFF);
	patch_instruction(hak, ip + 1, (param >> 16) & 0xFF);
	patch_instruction(hak, ip + 2, (param >> 8) & 0xFF);
	patch_instruction(hak, ip + 3, (param >> 0) & 0xFF);
#else
	patch_instruction(hak, ip,     (param >> 8) & 9xFF);
	patch_instruction(hak, ip + 1, (param >> 0) & 0xFF);
#endif
}

static int emit_variable_access (hak_t* hak, int mode, const hak_var_info_t* vi, const hak_loc_t* srcloc)
{
	static hak_oob_t inst_map[][3] =
	{
		{ HAK_CODE_PUSH_CTXTEMPVAR_0, HAK_CODE_POP_INTO_CTXTEMPVAR_0, HAK_CODE_STORE_INTO_CTXTEMPVAR_0 },
		{ HAK_CODE_PUSH_IVAR_0,       HAK_CODE_POP_INTO_IVAR_0,       HAK_CODE_STORE_INTO_IVAR_0    },
		{ HAK_CODE_PUSH_CVAR_I_X,     HAK_CODE_POP_INTO_CVAR_I_X,     HAK_CODE_STORE_INTO_CVAR_I_X   },
		{ HAK_CODE_PUSH_CVAR_M_X,     HAK_CODE_POP_INTO_CVAR_M_X,     HAK_CODE_STORE_INTO_CVAR_M_X   }
	};

	switch (vi->type)
	{
		case VAR_INDEXED:
			return emit_two_param_instruction(hak, inst_map[0][mode], vi->ctx_offset, vi->index_in_ctx, srcloc);

		case VAR_INST:
			HAK_ASSERT(hak, vi->ctx_offset == 0);
			return emit_one_param_instruction(hak, inst_map[1][mode], vi->index_in_ctx, srcloc);

		case VAR_CLASS_I: /* class variable in initialization scope */
			HAK_ASSERT(hak, vi->ctx_offset == 0);
			return emit_one_param_instruction(hak, inst_map[2][mode], vi->index_in_ctx, srcloc);

		case VAR_CLASS_CM: /* class variable in class method scope */
		case VAR_CLASS_IM: /* class variable in instance method scope */
			HAK_ASSERT(hak, vi->ctx_offset == 0);
			return emit_one_param_instruction(hak, inst_map[3][mode], vi->index_in_ctx, srcloc);
	}

	return -1;
}

/* ========================================================================= */
static int push_ctlblk (hak_t* hak, const hak_loc_t* errloc, hak_ctlblk_type_t type)
{
	hak_oow_t new_depth;

	HAK_ASSERT(hak, hak->c->ctlblk.depth >= -1);

	if (hak->c->ctlblk.depth == HAK_TYPE_MAX(hak_ooi_t))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKDEPTH, errloc, "control block depth too deep");
		return -1;
	}

	new_depth = hak->c->ctlblk.depth + 1;
	if (hak->c->ctlblk.depth >= hak->c->ctlblk.info_capa)
	{
		hak_ctlblk_info_t* tmp;
		hak_oow_t newcapa;

		newcapa = HAK_ALIGN(new_depth + 1, BLK_INFO_BUFFER_ALIGN);
		tmp = (hak_ctlblk_info_t*)hak_reallocmem(hak, hak->c->ctlblk.info, newcapa * HAK_SIZEOF(*tmp));
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to resize control block info buffer - %js", orgmsg);
			return -1;
		}

		hak->c->ctlblk.info_capa = newcapa;
		hak->c->ctlblk.info = tmp;
	}

	HAK_MEMSET(&hak->c->ctlblk.info[new_depth], 0, HAK_SIZEOF(hak->c->ctlblk.info[new_depth]));
	hak->c->ctlblk.info[new_depth]._type = type;
	hak->c->ctlblk.depth = new_depth;
	return 0;
}

static void pop_ctlblk (hak_t* hak)
{
	HAK_ASSERT(hak, hak->c->ctlblk.depth >= 0); /* depth is of a signed type */

	/* a control block stays inside a function block.
	 * the control block stack must not be popped past the starting base
	 * of the owning function block */
	HAK_ASSERT(hak, hak->c->ctlblk.depth - 1 >= hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base);
	hak->c->ctlblk.depth--;
}

static int push_clsblk (
	hak_t* hak, const hak_loc_t* errloc, hak_oow_t llvl, hak_cnode_t* class_name, hak_oow_t nivars, hak_oow_t ncvars,
	const hak_ooch_t* ivars_str, hak_oow_t ivars_strlen, const hak_ooch_t* cvars_str, hak_oow_t cvars_strlen)
{
	hak_oow_t new_depth;
	hak_clsblk_info_t* ci;
	hak_funblk_info_t* fbi;

	HAK_ASSERT(hak, hak->c->clsblk.depth >= -1);

	if (hak->c->clsblk.depth == HAK_TYPE_MAX(hak_ooi_t))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKDEPTH, errloc, "class block depth too deep");
		return -1;
	}

	new_depth = hak->c->clsblk.depth + 1;
	if (hak->c->clsblk.depth >= hak->c->clsblk.info_capa)
	{
		hak_clsblk_info_t* tmp;
		hak_oow_t newcapa;

		newcapa = HAK_ALIGN(new_depth + 1, BLK_INFO_BUFFER_ALIGN);
		tmp = (hak_clsblk_info_t*)hak_reallocmem(hak, hak->c->clsblk.info, newcapa * HAK_SIZEOF(*tmp));
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to resize class block info buffer - %js", orgmsg);
			return -1;
		}

		hak->c->clsblk.info_capa = newcapa;
		hak->c->clsblk.info = tmp;
	}

	ci = &hak->c->clsblk.info[new_depth];
	HAK_MEMSET(ci, 0, HAK_SIZEOF(*ci));
	ci->def_llvl = llvl;
	ci->class_name = class_name;
	ci->nivars = nivars;
	ci->ncvars = ncvars;

	if (init_class_level_variable_buffer(hak, &ci->ivars, ivars_str, ivars_strlen) <= -1) return -1;
	if (init_class_level_variable_buffer(hak, &ci->cvars, cvars_str, cvars_strlen) <= -1) return -1;

	/* remember the function block depth before the class block is entered */
	ci->funblk_base = hak->c->funblk.depth;

	/* attach the class block to the current function block */
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	if (fbi->clsblk_base <= -1) fbi->clsblk_base = new_depth;
	fbi->clsblk_top = new_depth;

	hak->c->clsblk.depth = new_depth;
	return 0;
}

static void pop_clsblk (hak_t* hak)
{
	hak_funblk_info_t* fbi;
	hak_clsblk_info_t* cbi;

	HAK_ASSERT(hak, hak->c->clsblk.depth >= 0); /* depth is of a signed type */
	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);

	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	HAK_ASSERT(hak, fbi->clsblk_base >= 0 && fbi->clsblk_top >= 0 && fbi->clsblk_top >= fbi->clsblk_base);
	HAK_ASSERT(hak, fbi->clsblk_top == hak->c->clsblk.depth);
	if (fbi->clsblk_top == fbi->clsblk_base)
	{
		/* the first class block inside a function block */
		fbi->clsblk_base = -1;
		fbi->clsblk_top = -1;
	}
	else
	{
		fbi->clsblk_top--;
	}

	cbi = &hak->c->clsblk.info[hak->c->clsblk.depth];
	fini_class_level_variable_buffer(hak, &cbi->ivars);
	fini_class_level_variable_buffer(hak, &cbi->cvars);
	hak->c->clsblk.depth--;
}


static int push_funblk (hak_t* hak, const hak_loc_t* errloc,
	hak_oow_t tmpr_va, hak_oow_t tmpr_nargs, hak_oow_t tmpr_nrvars, hak_oow_t tmpr_nlvars,
	hak_oow_t tmpr_count, hak_oow_t tmpr_len, hak_oow_t make_inst_pos, hak_oow_t lfbase, unsigned int fun_type)
{
	hak_oow_t new_depth;
	hak_funblk_info_t* fbi;

	HAK_ASSERT(hak, hak->c->funblk.depth >= -1);
	if (hak->c->funblk.depth == HAK_TYPE_MAX(hak_ooi_t))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKDEPTH, errloc, "function block depth too deep");
		return -1;
	}

	new_depth = hak->c->funblk.depth + 1;
	if (hak->c->funblk.depth >= hak->c->funblk.info_capa)
	{
		hak_funblk_info_t* tmp;
		hak_oow_t newcapa;

		newcapa = HAK_ALIGN(new_depth + 1, BLK_INFO_BUFFER_ALIGN);
		tmp = (hak_funblk_info_t*)hak_reallocmem(hak, hak->c->funblk.info, newcapa * HAK_SIZEOF(*tmp));
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to resize function block info buffer - %js", orgmsg);
			return -1;
		}

		hak->c->funblk.info_capa = newcapa;
		hak->c->funblk.info = tmp;
	}

	fbi = &hak->c->funblk.info[new_depth];
	HAK_MEMSET(fbi, 0, HAK_SIZEOF(*fbi));

	fbi->fun_type = fun_type;

	fbi->tmprlen = tmpr_len;
	fbi->tmprcnt = tmpr_count;
	fbi->tmpr_va = tmpr_va;
	fbi->tmpr_nargs = tmpr_nargs;
	fbi->tmpr_nrvars = tmpr_nrvars;
	fbi->tmpr_nlvars = tmpr_nlvars;

	/* remember the control block depth before the function block is entered */
	fbi->ctlblk_base = hak->c->ctlblk.depth;

	/* no class block when the funtion block is entered */
	fbi->clsblk_base = -1;
	fbi->clsblk_top = -1;

	fbi->make_inst_pos = make_inst_pos;
	fbi->lfbase = lfbase;

	fbi->access_outer = 0;
	fbi->accessed_by_inner = 0;

	hak->c->funblk.depth = new_depth;
	return 0;
}

static void clear_funblk_inners (hak_t* hak)
{
	hak_funblk_info_t* fbi;
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	while (hak->c->ctlblk.depth > fbi->ctlblk_base) pop_ctlblk(hak);
	while (!(fbi->clsblk_base <= -1 && fbi->clsblk_top <= -1)) pop_clsblk(hak);
}

static void pop_funblk (hak_t* hak)
{
	hak_funblk_info_t* fbi;

	HAK_ASSERT(hak, hak->c->funblk.depth >= 0);

	clear_funblk_inners(hak);
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	/* if pop_ctlblk() has been called properly, the following assertion must be true
	 * and the assignment on the next line isn't necessary */
	HAK_ASSERT(hak, hak->c->ctlblk.depth == fbi->ctlblk_base);
	HAK_ASSERT(hak, fbi->clsblk_base <= -1 && fbi->clsblk_top <= -1);

	hak->c->ctlblk.depth = fbi->ctlblk_base;
	/* keep hak->code.lit.len without restoration */

	hak->c->funblk.depth--;

	if (hak->c->funblk.depth >= 0)
	{
		/* restore the string length and the word count to the values captured
		 * at the previous level */
		hak->c->tv.s.len = hak->c->funblk.info[hak->c->funblk.depth].tmprlen;
		hak->c->tv.wcount = hak->c->funblk.info[hak->c->funblk.depth].tmprcnt;
	}
	else
	{
		hak->c->tv.s.len = 0;
		hak->c->tv.wcount = 0;
	}

	if (fbi->make_inst_pos < hak->code.bc.len)
	{
		hak_oow_t attr_mask;

		/* patch the temporaries mask parameter for the MAKE_BLOCK or MAKE_FUNCTION instruction */
		HAK_ASSERT(hak, hak->code.bc.ptr[fbi->make_inst_pos] == HAK_CODE_MAKE_BLOCK ||
		                hak->code.bc.ptr[fbi->make_inst_pos] == HAK_CODE_MAKE_FUNCTION);

		/* the total number of temporaries in this function block must be the sum of
		 * the number of arguments, return variables and local variables */
		HAK_ASSERT(hak, fbi->tmprcnt - hak->c->tv.wcount == fbi->tmpr_nargs + fbi->tmpr_nrvars + fbi->tmpr_nlvars);

		/* the temporaries mask is a bit-mask that encodes the counts of different temporary variables.
		 * and it's split to two intruction parameters when used with MAKE_BLOCK and MAKE_FUNCTION.
		 * the INSTA bit is on if fbi->fun_type == FUN_CIM */
		attr_mask = ENCODE_BLK_MASK(((fbi->fun_type & 0xFF) == FUN_CIM), fbi->tmpr_va, fbi->tmpr_nargs, fbi->tmpr_nrvars, fbi->tmpr_nlvars);
		patch_extended_long_param(hak, fbi->make_inst_pos + 1, attr_mask);
	}
}

/* ========================================================================= */
static HAK_INLINE int _insert_cframe (hak_t* hak, hak_ooi_t index, int opcode, hak_cnode_t* operand)
{
	hak_cframe_t* tmp;

	HAK_ASSERT(hak, index >= 0);

	hak->c->cfs.top++;
	HAK_ASSERT(hak, hak->c->cfs.top >= 0);
	HAK_ASSERT(hak, index <= hak->c->cfs.top);

	if ((hak_oow_t)hak->c->cfs.top >= hak->c->cfs.capa)
	{
		hak_oow_t newcapa;

		newcapa = HAK_ALIGN (hak->c->cfs.top + 256, 256); /* TODO: adjust this capacity */
		tmp = (hak_cframe_t*)hak_reallocmem(hak, hak->c->cfs.ptr, newcapa * HAK_SIZEOF(*tmp));
		if (HAK_UNLIKELY(!tmp))
		{
			const hak_ooch_t* orgmsg = hak_backuperrmsg(hak);
			hak_seterrbfmt(hak, HAK_ERRNUM(hak), "failed to grow compiler frame stack- %js", orgmsg);
			hak->c->cfs.top--;
			return -1;
		}

		hak->c->cfs.capa = newcapa;
		hak->c->cfs.ptr = tmp;
	}

	if (index < hak->c->cfs.top)
	{
		HAK_MEMMOVE(&hak->c->cfs.ptr[index + 1], &hak->c->cfs.ptr[index], (hak->c->cfs.top - index) * HAK_SIZEOF(*tmp));
	}

	tmp = &hak->c->cfs.ptr[index];
	tmp->opcode = opcode;
	tmp->operand = operand;
	HAK_MEMSET(&tmp->u, 0, HAK_SIZEOF(tmp->u));
	return 0;
}

static int insert_cframe (hak_t* hak, hak_ooi_t index, int opcode, hak_cnode_t* operand)
{
	if (hak->c->cfs.top == HAK_TYPE_MAX(hak_ooi_t))
	{
		hak_seterrnum(hak, HAK_EFRMFLOOD);
		return -1;
	}

	return _insert_cframe(hak, index, opcode, operand);
}

static int push_cframe (hak_t* hak, int opcode, hak_cnode_t* operand)
{
	if (hak->c->cfs.top == HAK_TYPE_MAX(hak_ooi_t))
	{
		hak_seterrnum(hak, HAK_EFRMFLOOD);
		return -1;
	}

	return _insert_cframe(hak, hak->c->cfs.top + 1, opcode, operand);
}

static HAK_INLINE void pop_cframe (hak_t* hak)
{
	HAK_ASSERT(hak, hak->c->cfs.top >= 0);
	hak->c->cfs.top--;
}

#define PUSH_CFRAME(hak,opcode,operand) \
	do { if (push_cframe(hak,opcode,operand) <= -1) return -1; } while (0)

#define INSERT_CFRAME(hak,index,opcode,operand) \
	do { if (insert_cframe(hak,index,opcode,operand) <= -1) return -1; } while (0)

#define POP_CFRAME(hak) pop_cframe(hak)

#define POP_ALL_CFRAMES(hak) (hak->c->cfs.top = -1)

#define GET_TOP_CFRAME_INDEX(hak) (hak->c->cfs.top)

#define GET_TOP_CFRAME(hak) (&hak->c->cfs.ptr[hak->c->cfs.top])

#define GET_CFRAME(hak,index) (&hak->c->cfs.ptr[index])

#define SWITCH_TOP_CFRAME(hak,_opcode,_operand) \
	do { \
		hak_cframe_t* _cf = GET_TOP_CFRAME(hak); \
		_cf->opcode = _opcode; \
		_cf->operand = _operand; \
	} while (0)

#define SWITCH_CFRAME(hak,_index,_opcode,_operand) \
	do { \
		hak_cframe_t* _cf = GET_CFRAME(hak,_index); \
		_cf->opcode = _opcode; \
		_cf->operand = _operand; \
	} while (0)

static int push_subcframe (hak_t* hak, int opcode, hak_cnode_t* operand)
{
	hak_cframe_t* cf, tmp;

	cf = GET_TOP_CFRAME(hak);
	tmp = *cf;
	cf->opcode = opcode;
	cf->operand = operand;

	if (push_cframe(hak, tmp.opcode, tmp.operand) <= -1) return -1;
	cf = GET_TOP_CFRAME(hak);
	cf->u = tmp.u; /* copy the extra information */

	return 0;
}

static HAK_INLINE hak_cframe_t* find_cframe_from_top (hak_t* hak, int opcode)
{
	hak_cframe_t* cf;
	hak_ooi_t i;

	for (i = hak->c->cfs.top; i >= 0; i--)
	{
		cf = &hak->c->cfs.ptr[i];
		if (cf->opcode == opcode) return cf;
	}

	return HAK_NULL;
}

#define PUSH_SUBCFRAME(hak,opcode,operand) \
	do { if (push_subcframe(hak,opcode,operand) <= -1) return -1; } while (0)

#define GET_SUBCFRAME(hak) (&hak->c->cfs.ptr[hak->c->cfs.top - 1])

/* ========================================================================= */

struct class_vardcl_t
{
	hak_oow_t nivars;
	hak_oow_t ncvars;
	hak_oow_t ivar_start;
	hak_oow_t ivar_len;
	hak_oow_t cvar_start;
	hak_oow_t cvar_len;
};
typedef struct class_vardcl_t class_vardcl_t;

static int collect_vardcl_for_class (hak_t* hak, hak_cnode_t* obj, hak_cnode_t** nextobj, class_vardcl_t* vardcl)
{
	hak_oow_t tv_wcount_saved, tv_slen_saved;
	hak_cnode_t* dcl, * dcl_saved;
	int enclosed = 0;
	static const hak_bch_t* desc[] = { "instance variable", "class variable" };

	HAK_MEMSET(vardcl, 0, HAK_SIZEOF(*vardcl));
	tv_wcount_saved = hak->c->tv.wcount;
	tv_slen_saved = hak->c->tv.s.len;

	dcl = HAK_CNODE_CONS_CAR(obj);
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(dcl, HAK_CONCODE_XLIST));

	do
	{
		hak_cnode_t* var;
		int n;
		hak_oow_t checkpoint;

		var = HAK_CNODE_CONS_CAR(dcl);

		if (HAK_CNODE_IS_CONS_CONCODED(var, HAK_CONCODE_XLIST)) /* ( ... ) */
		{
			if (enclosed)
			{
			synerr_varname:
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(var),
					"not variable name '%.*js'", HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
				return -1;
			}
			enclosed = 1;
			dcl_saved = dcl;
			dcl = var;
			continue; /* start over */
		}
		else if (HAK_CNODE_IS_ELIST_CONCODED(var, HAK_CONCODE_XLIST))
		{
			/* no variables inside () */
			if (enclosed) goto synerr_varname; /* () inside () */
			goto next;
		}

		if (!HAK_CNODE_IS_SYMBOL(var) /*|| HAK_CNODE_IS_SYMBOL_BINOP(var)*/) goto synerr_varname;

		checkpoint = hak->c->tv.s.len;
		n = add_temporary_variable(hak, var, tv_slen_saved, desc[enclosed], HAK_NULL);
		if (n <= -1) return -1;

		if (enclosed)
		{
			/* class variable */
			if (vardcl->nivars >= MAX_NCVARS)
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_VARFLOOD, HAK_CNODE_GET_LOC(var),
					"too many(%zu) class variables before '%.*js'",
					vardcl->nivars, HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
				return -1;
			}
			/*if (cvar_len <= 0) cvar_start = prev_tv_len;
			cvar_len = hak->c->tv.s.len - cvar_start; */
			if (vardcl->cvar_len <= 0) vardcl->cvar_start = checkpoint;
			vardcl->cvar_len += hak->c->tv.s.len - checkpoint;
			vardcl->ncvars++;
		}
		else
		{
			/* instance variable */
			if (vardcl->nivars >= MAX_NIVARS)
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_VARFLOOD, HAK_CNODE_GET_LOC(var),
					"too many(%zu) instance variables before '%.*js'",
					vardcl->nivars, HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
				return -1;
			}
			if (vardcl->ivar_len <= 0) vardcl->ivar_start = (vardcl->cvar_len <= 0)? checkpoint: vardcl->cvar_start;
			vardcl->ivar_len += hak->c->tv.s.len - checkpoint;
			if (vardcl->cvar_len > 0)
			{
				/* place the instance variables before the class variables
				 * if class variables "a b" has been collected before instance variables "cc dd ee"
				 * the rotation below manipulates the buffer to contain "cc dd ee a b".
				 */
				hak_rotate_oochars (&hak->c->tv.s.ptr[vardcl->cvar_start], hak->c->tv.s.len - vardcl->cvar_start, -1, vardcl->cvar_len);
				vardcl->cvar_start += hak->c->tv.s.len - checkpoint;
			}
			vardcl->nivars++;
		}

	next:
		dcl = HAK_CNODE_CONS_CDR(dcl);
		if (!dcl)
		{
			if (enclosed)
			{
				enclosed = 0;
				dcl = dcl_saved;
				goto next;
			}
			break;
		}

		if (!HAK_CNODE_IS_CONS(dcl))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(dcl),
				"redundant cdr in %hs declaration around '%.*js'",
				desc[enclosed], HAK_CNODE_GET_TOKLEN(dcl), HAK_CNODE_GET_TOKPTR(dcl));
			return -1;
		}
	}
	while (1);

	/* remove leading spaces if any */
	while (vardcl->ivar_len > 0)
	{
		if (hak->c->tv.s.ptr[vardcl->ivar_start] != ' ') break;
		vardcl->ivar_start++;
		vardcl->ivar_len--;
	}
	while (vardcl->cvar_len > 0)
	{
		if (hak->c->tv.s.ptr[vardcl->cvar_start] != ' ') break;
		vardcl->cvar_start++;
		vardcl->cvar_len--;
	}

	HAK_ASSERT(hak, vardcl->nivars + vardcl->ncvars == hak->c->tv.wcount - tv_wcount_saved);
	*nextobj = HAK_CNODE_CONS_CDR(obj);

	return 0;
}

static int collect_vardcl (hak_t* hak, hak_cnode_t* obj, hak_cnode_t** nextobj, hak_oow_t tv_dup_check_start, hak_oow_t* nvardcls, const hak_bch_t* desc)
{
	/* process a single variable declaration list */
	hak_oow_t ndcls = 0;
	hak_oow_t old_wcount = hak->c->tv.wcount;
	hak_cnode_t* dcl;
	hak_cnode_t* var;

	dcl = HAK_CNODE_CONS_CAR(obj);
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(dcl, HAK_CONCODE_VLIST));

	do
	{
		var = HAK_CNODE_CONS_CAR(dcl);
	#if 0
		if (!HAK_CNODE_IS_SYMBOL(var))
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_ARGNAME, HAK_CNODE_GET_LOC(var),
				"invalid local variable name '%.*js'",
				HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
			return -1;
		}
	#else
		/* the above checks are not needed as the reader guarantees the followings */
		HAK_ASSERT(hak, HAK_CNODE_IS_SYMBOL(var));
	#endif

		if (add_temporary_variable(hak, var, tv_dup_check_start, desc, HAK_NULL) <= -1) return -1;
		ndcls++;

		dcl = HAK_CNODE_CONS_CDR(dcl);
		if (!dcl) break;

		if (!HAK_CNODE_IS_CONS(dcl))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(dcl),
				"redundant cdr in %hs variable list around '%.*js'",
				desc, HAK_CNODE_GET_TOKLEN(dcl), HAK_CNODE_GET_TOKPTR(dcl));
			return -1;
		}
	}
	while (1);

	HAK_ASSERT(hak, ndcls == hak->c->tv.wcount - old_wcount);
	*nextobj = HAK_CNODE_CONS_CDR(obj);
	*nvardcls = ndcls;

	return 0;
}

static int collect_vardcls (hak_t* hak, hak_cnode_t* obj, hak_cnode_t** nextobj, hak_oow_t tv_dup_check_start, hak_oow_t* nvardcls, const hak_bch_t* desc)
{
	/* process zero or more variable declaration lists in a row */
	hak_oow_t ndcls = 0;
	hak_oow_t old_wcount = hak->c->tv.wcount;

	while (obj && HAK_CNODE_IS_CONS(obj))
	{
		hak_cnode_t* dcl;
		hak_oow_t dclcount;

		dcl = HAK_CNODE_CONS_CAR(obj);
		if (!HAK_CNODE_IS_CONS_CONCODED(dcl, HAK_CONCODE_VLIST)) break;

		if (collect_vardcl(hak, obj, &obj, tv_dup_check_start, &dclcount, desc) <= -1) return -1;
		ndcls += dclcount;
	}

	HAK_ASSERT(hak, ndcls == hak->c->tv.wcount - old_wcount);
	*nvardcls = ndcls;
	*nextobj = obj;
	return 0;
}

static int is_followed_by_vlist (hak_t* hak, hak_cnode_t* obj)
{
	if (obj && HAK_CNODE_IS_CONS(obj))
	{
		hak_cnode_t* dcl;
		dcl = HAK_CNODE_CONS_CAR(obj);
		return HAK_CNODE_IS_CONS_CONCODED(dcl, HAK_CONCODE_VLIST);
	}
	return 0;
}

static int check_if_plain_cnode (hak_t* hak, hak_cnode_t* obj, hak_cnode_t* prev, hak_cnode_t* container, hak_synerrnum_t errnum, const hak_bch_t* bname)
{
	if (!obj)
	{
		hak_setsynerrbfmt(hak, errnum, HAK_CNODE_GET_LOC(prev),
			"no %hs in %.*js", bname, HAK_CNODE_GET_TOKLEN(container), HAK_CNODE_GET_TOKPTR(container));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(prev),
			"redundant cdr where %.*js is expected in %.*js around '%.*js'",
			bname, HAK_CNODE_GET_TOKLEN(container), HAK_CNODE_GET_TOKPTR(container),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	return 0;
}

/* ========================================================================= */

enum
{
	COP_COMPILE_OBJECT,
	COP_COMPILE_OBJECT_R,
	COP_COMPILE_SYMBOL_LITERAL,

	COP_COMPILE_ARGUMENT_LIST,
	COP_COMPILE_OBJECT_LIST,
	COP_COMPILE_OBJECT_LIST_TAIL,
	COP_COMPILE_IF_OBJECT_LIST,
	COP_COMPILE_IF_OBJECT_LIST_TAIL,
	COP_COMPILE_TRY_OBJECT_LIST,
	COP_COMPILE_TRY_OBJECT_LIST_TAIL,

	COP_COMPILE_ARRAY_LIST,
	COP_COMPILE_PURE_ARRAY_LIST,
	COP_COMPILE_DIC_LIST,
	COP_COMPILE_QLIST, /* compile data list */

	COP_COMPILE_ELIF,
	COP_COMPILE_ELSE,
	COP_COMPILE_CATCH,

	COP_COMPILE_AND_P1,
	COP_COMPILE_AND_P2,

	COP_COMPILE_BREAK_P1,

	COP_COMPILE_DO_P1,

	COP_COMPILE_OR_P1,
	COP_COMPILE_OR_P2,

	COP_COMPILE_CLASS_P1,
	COP_COMPILE_CLASS_P2,

	COP_EMIT_PUSH_NIL,
	COP_EMIT_PUSH_SYMBOL,
	COP_EMIT_CALL,
	COP_EMIT_SEND,

	COP_EMIT_MAKE_ARRAY,
	COP_EMIT_MAKE_PURE_ARRAY,
	COP_EMIT_MAKE_DIC,
	COP_EMIT_MAKE_CONS,
	COP_EMIT_POP_INTO_ARRAY,
	COP_EMIT_POP_INTO_PURE_ARRAY,
	COP_EMIT_POP_INTO_DIC,
	COP_EMIT_POP_INTO_CONS,
	COP_EMIT_POP_INTO_CONS_END,
	COP_EMIT_POP_INTO_CONS_CDR,

	COP_EMIT_FUN,
#if defined(USE_KW_PLUS)
	COP_EMIT_PLUS,
#endif
	COP_EMIT_POP_STACKTOP,
	COP_EMIT_RETURN,
	COP_EMIT_SET,
	COP_EMIT_CLASS_CMSTORE,
	COP_EMIT_CLASS_CIMSTORE,
	COP_EMIT_CLASS_IMSTORE,
	COP_EMIT_THROW,

	COP_POST_IF_COND,
	COP_POST_IF_BODY,

	COP_POST_UNTIL_BODY,
	COP_POST_UNTIL_COND,
	COP_POST_WHILE_BODY,
	COP_POST_WHILE_COND,

	COP_POST_TRY,
	COP_POST_CATCH,

	COP_POST_FUN
};

/* ========================================================================= */

static int compile_and (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* obj, * expr;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_AND));

	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src), "no expression specified in 'and'");
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in 'and' around '%.*js'", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

/* TODO: optimization - eat away all true expressions */
	expr = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, expr); /* 1 */
	if (obj) PUSH_SUBCFRAME(hak, COP_COMPILE_AND_P1, obj); /* 2 */

	return 0;
}


static HAK_INLINE int compile_and_p1 (hak_t* hak)
{
	hak_cnode_t* obj, * expr;
	hak_cframe_t* cf;
	hak_ooi_t jump_inst_pos;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_AND_P1);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

/* TODO: optimization - eat away all true expressions */
	obj = cf->operand;
	if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in 'and' around '%.*js'", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jump_inst_pos = hak->code.bc.len;

	/* this conditional jump make evaluation short-circuited. the actual jump point is to be patched in compile_and_p2() */
	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_IF_FALSE, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(obj)) <= -1) return -1;
	if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_CNODE_GET_LOC(obj)) <= -1) return -1;

	expr = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, expr); /* 1 - compile the current part */

	PUSH_SUBCFRAME(hak, COP_COMPILE_AND_P2, expr); /* 3 - patch the conditional jump instruction */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_and.jump_inst_pos = jump_inst_pos;

	if (obj) PUSH_SUBCFRAME(hak, COP_COMPILE_AND_P1, obj); /* 2 - recurse to compile remaining parts */
	return 0;
}

static HAK_INLINE int compile_and_p2 (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_ooi_t jip;
	hak_oow_t jump_offset;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_AND_P2);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jip = cf->u.post_and.jump_inst_pos;

	/* patch the jump insruction emitted after each expression inside the 'and' expression
	 * the jump make evaluation short-circuited. */
	jump_offset = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);
	patch_long_jump(hak, jip, jump_offset);

	POP_CFRAME(hak);
	return 0;
}

/* ========================================================================= */

static int compile_or (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* obj, * expr;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_OR));

	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src), "no expression specified in 'or'");
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in 'or' around '%.*js'", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

/* TODO: optimization - eat away all false expressions */
	expr = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, expr); /* 1 */
	PUSH_SUBCFRAME(hak, COP_COMPILE_OR_P1, obj); /* 2 */

	return 0;
}


static HAK_INLINE int compile_or_p1 (hak_t* hak)
{
	hak_cnode_t* obj, * expr;
	hak_cframe_t* cf;
	hak_ooi_t jump_inst_pos;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_OR_P1);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

/* TODO: optimization - eat away all false expressions */

	obj = cf->operand;
	if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in 'or' around '%.*js'", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jump_inst_pos = hak->code.bc.len;

	/* this conditional jump makes evaluation short-circuited. the actual jump point is to be patched in compile_or_p2() */
	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_IF_TRUE, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(obj)) <= -1) return -1;
	if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_CNODE_GET_LOC(obj)) <= -1) return -1;

	expr = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, expr); /* 1 */

	PUSH_SUBCFRAME(hak, COP_COMPILE_OR_P2, expr); /* 3 */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_or.jump_inst_pos = jump_inst_pos;

	if (obj) PUSH_SUBCFRAME(hak, COP_COMPILE_OR_P1, obj); /* 2 */

	return 0;
}

static HAK_INLINE int compile_or_p2 (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_ooi_t jip;
	hak_oow_t jump_offset;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_OR_P2);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jip = cf->u.post_or.jump_inst_pos;

	/* patch the jump insruction emitted after each expression inside the 'and' expression */
	jump_offset = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);
	patch_long_jump(hak, jip, jump_offset);

	POP_CFRAME(hak);
	return 0;
}

/* ========================================================================= */

#if defined(USE_KW_PLUS)
/* EXPERIMENT WITH BINOP */
static int compile_plus (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* obj, * expr;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_PLUS));

	obj = HAK_CNODE_CONS_CDR(src);
	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src), "no expression specified in 'plus'");
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in plus around '%.*js'", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	expr = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, expr); /* 1 */

	if (!obj)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src), "no second expression specified in 'plus'");
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in 'plus' around '%.*js", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	expr = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	if (obj)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in 'plus' around '%.*js", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

/* TODO: more check on obj */
	PUSH_SUBCFRAME(hak, COP_EMIT_PLUS, src); /* 3 */
	PUSH_SUBCFRAME(hak, COP_COMPILE_OBJECT, expr); /* 2 */
	return 0;
}

static HAK_INLINE int emit_plus (hak_t* hak)
{
	hak_cframe_t* cf;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_PLUS);

	if (emit_byte_instruction(hak, HAK_CODE_PLUS, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	POP_CFRAME(hak);
	return 0;
}
#endif

/* ========================================================================= */

static int compile_break (hak_t* hak, hak_cnode_t* src)
{
	/* (break) */
	hak_cnode_t* cmd, * obj;
	hak_ooi_t i;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_BREAK));

	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);
	if (obj)
	{
		if (HAK_CNODE_IS_CONS(obj))
		{
			hak_cnode_t* tmp;
			tmp = HAK_CNODE_CONS_CAR(obj);
			hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(obj),
				"excessive argument to '%.*js' around '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
				HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
		}
		else
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		}
		return -1;
	}

	for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i)
	{
		switch (hak->c->ctlblk.info[i]._type)
		{
			case HAK_CTLBLK_TYPE_LOOP:
				goto inside_loop;

			case HAK_CTLBLK_TYPE_TRY:
				/* emit an instruction to exit from the try loop. */
				if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
				break;

			case HAK_CTLBLK_TYPE_CLASS:
				/* emit an instruction to exit from the class definition scope being defined */
				if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
				break;
		}
	}

	hak_setsynerrbfmt(hak, HAK_SYNERR_BREAK, HAK_CNODE_GET_LOC(src),
		"%.*js outside loop", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
	return -1;

inside_loop:
	for (i = hak->c->cfs.top; i >= 0; --i)
	{
		const hak_cframe_t* tcf;
		tcf = &hak->c->cfs.ptr[i];

		if (tcf->opcode == COP_EMIT_FUN) break; /* seems to cross function boundary */

		if (tcf->opcode == COP_POST_UNTIL_BODY || tcf->opcode == COP_POST_WHILE_BODY)
		{
			hak_ooi_t jump_inst_pos;
			hak_cframe_t* cf;

			/* (break) is not really a function call. but to make it look like a
			 * function call, i generate PUSH_NIL so nil becomes a return value.
			 *     (set x (until #f (break)))
			 * x will get nill. */
			if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;

/* TODO: study if supporting expression after break is good like return. (break (+ 10 20)) */
			HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
			jump_inst_pos = hak->code.bc.len;

			if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_0, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;
			INSERT_CFRAME(hak, i, COP_COMPILE_BREAK_P1, cmd);
			cf = GET_CFRAME(hak, i);
			cf->u._break.jump_inst_pos = jump_inst_pos;

			POP_CFRAME(hak);
			return 0;
		}
	}

	/* this part must no be reached. if a loop control block is found,
	 * there must exist a COP_POST_UNTIL_BODY or COP_POST_WHILE_BODY frame */
	hak_setsynerrbfmt(hak, HAK_SYNERR_INTERN, HAK_CNODE_GET_LOC(src),
		"internal error in compiling %.*js", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
	return -1;
}

static int compile_break_p1 (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_ooi_t jip, jump_offset;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_BREAK_P1);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	jip = cf->u._break.jump_inst_pos;

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD instruction */
	jump_offset = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	/* no explicit about jump_offset. because break can only place inside
	 * a loop, the same check in post_while_body() must assert
	 * this break jump_offset to be small enough */
	HAK_ASSERT(hak, jump_offset <= MAX_CODE_JUMP * 2);
	patch_long_jump(hak, jip, jump_offset);

	POP_CFRAME(hak);
	return 0;
}

/* ========================================================================= */

static int compile_continue (hak_t* hak, hak_cnode_t* src)
{
	/* (continue) */
	hak_cnode_t* cmd, * obj;
	hak_ooi_t i;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_CONTINUE));

	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);
	if (obj)
	{
		if (HAK_CNODE_IS_CONS(obj))
		{
			hak_cnode_t* tmp;
			tmp = HAK_CNODE_CONS_CAR(obj);
			hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(obj),
				"excessive argument to '%.*js' around '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
				HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
		}
		else
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
				"redundant cdr in %.*js around %.*js",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
				HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		}
		return -1;
	}

	for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i)
	{
		switch (hak->c->ctlblk.info[i]._type)
		{
			case HAK_CTLBLK_TYPE_LOOP:
				goto inside_loop;

			case HAK_CTLBLK_TYPE_TRY:
				/*must emit an instruction to exit from the try loop.*/
				if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
				break;

			case HAK_CTLBLK_TYPE_CLASS:
				if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
				break;
		}
	}

	hak_setsynerrbfmt(hak, HAK_SYNERR_BREAK, HAK_CNODE_GET_LOC(src),
		"%.*js outside loop", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
	return -1;

inside_loop:
	for (i = hak->c->cfs.top; i >= 0; --i)
	{
		const hak_cframe_t* tcf;
		tcf = &hak->c->cfs.ptr[i];

		if (tcf->opcode == COP_EMIT_FUN) break; /* seems to cross function boundary */

		if (tcf->opcode == COP_POST_UNTIL_BODY || tcf->opcode == COP_POST_WHILE_BODY)
		{
			hak_ooi_t jump_offset;

			HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
			jump_offset = hak->code.bc.len - tcf->u.post_while.cond_pos + 1;
			if (jump_offset > 3) jump_offset += HAK_CODE_LONG_PARAM_SIZE;
			if (emit_one_param_instruction(hak, HAK_CODE_JUMP_BACKWARD_0, jump_offset, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;

			POP_CFRAME(hak);
			return 0;
		}
	}

	/* this part must no be reached. if a loop control block is found,
	 * there must exist a COP_POST_UNTIL_BODY or COP_POST_WHILE_BODY frame */
	hak_setsynerrbfmt(hak, HAK_SYNERR_INTERN, HAK_CNODE_GET_LOC(src),
		"internal error in compiling %.*js", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
	return -1;
}

/* ========================================================================= */

#define CEB_IS_BLOCK (1 << 0)
#define CEB_AUTO_FORGED (1 << 1)

static int compile_expression_block (hak_t* hak, hak_cnode_t* src, const hak_bch_t* ctxname, int flags)
{
	hak_cnode_t* cmd, * obj;
	hak_oow_t nlvars, tvslen;
	hak_funblk_info_t* fbi;
	hak_cframe_t* cf;

	/* called for {} after 'do' or a standalone {} */

	if (flags & CEB_IS_BLOCK)
	{
		HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(src, HAK_CONCODE_BLOCK) || HAK_CNODE_IS_ELIST_CONCODED(src, HAK_CONCODE_BLOCK));
		cmd = src; /* it's the cons cell itself  */
		/* `obj` must point to the cons cell pointing to the braced expression list */
		obj = HAK_CNODE_IS_ELIST(src)? HAK_NULL: src;
		/* no check for redundant cdr because {} cannot be dotted */
	}
	else
	{
		/* called for 'do ...' */
		cmd = HAK_CNODE_CONS_CAR(src); /* `do` itself */
		/* `obj` must point to the expression list after `do` */
		obj = HAK_CNODE_CONS_CDR(src); /* expression list after it */
		if (obj && !HAK_CNODE_IS_CONS(obj))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
				"redundant cdr in %.*js around %.*js",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
				HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
			return -1;
		}
	}

	if (is_followed_by_vlist(hak, obj))
	{
		if (is_in_class_init_scope(hak))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_VARDCLBANNED, HAK_CNODE_GET_LOC(obj),
				"variable declaration disallowed in class init scope");
			return -1;
		}

#if defined(LANG_LIMIT_DO) /* this limitation doesn't seem really useful? or make it #pragma based? */
		if (!(flags & CEB_IS_BLOCK) /*&& (flags & CEB_AUTO_FORGED)*/)
		{
			/*
			 * e.g. do | x | { set x 20; };
			 *         ^
			 *         this is not allowed
			 *
			 *      k := (do | x | { set x 20; })
			 *               ^
			 *               not allowed either
			 */
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_VARDCLBANNED, HAK_CNODE_GET_LOC(obj),
				"variable declaration disallowed in '%hs' context", ctxname);
			return -1;
		}
#endif
	}

	tvslen = hak->c->tv.s.len;
	nlvars = 0;
	if (obj)
	{
		hak_cnode_t* tmp = obj;
		if (collect_vardcls(hak, obj, &obj, tvslen, &nlvars, ctxname) <= -1) return -1;
		if (nlvars > MAX_CODE_NBLKLVARS)
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_VARFLOOD, HAK_CNODE_GET_LOC(tmp),
				"too many(%zu) variables in %.*js", nlvars, HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
			return -1;
		}
	}

	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	fbi->tmprlen = hak->c->tv.s.len;
	fbi->tmprcnt = hak->c->tv.wcount;
	fbi->tmpr_nlvars = fbi->tmpr_nlvars + nlvars;

	/* for an expression like `(do   )` or `(do | a b |  ) , `obj` will point to HAK_NULL.
	 * let `obj` point to the internal cnode to convert the expression like `(do #nil)` or `(do |a b| #nil)`. */
	if (!obj) obj = &hak->c->fake_cnode.cons_to_nil;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, obj);  /* 1 */

	PUSH_SUBCFRAME(hak, COP_COMPILE_DO_P1, src); /* 2 */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_do.lvar_start = tvslen;
	cf->u.post_do.lvar_end = fbi->tmprlen;

	return 0;
}

static int compile_do (hak_t* hak, hak_cnode_t* xlist)
{
#if 0
	hak_cnode_t* cmd, * obj;
#endif
	int flags = 0;

	/*
	 * 'do' evaluates series of expressions.
	 *
	 * do { ... }
	 * do { ... }  { ... }
	 * do 1 2 3
	 * do (printf "111\n") (printf "2222\n")
	 *
	 * while it looks like a cosmetic element, the followings show obvious difference:
	 *   1 2 3    ## the first element is treated as a callable element. in this case syntax error
	 *   do 1 2 3 ## 1, 2, 3 are just evaulated in that order without 1 being treated as a callable
	 *
	 * More serious example:
	 *   (fun(a b) { return (+ a b) }) 20 30  ## the returned value is 50
	 *   do (fun(a b) { return (+ a b) }) 20 30  ## the returned value is 30
	 *
	 * The following two are mostly equivalent:
	 *   do (a := 20) (b := 30)
	 *   { a := 20; b := 30 }
	 */

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(xlist));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(xlist), HAK_CNODE_DO));

#if 0
	cmd = HAK_CNODE_CONS_CAR(xlist); /* do itself */
	obj = HAK_CNODE_CONS_CDR(xlist); /* expression list after it */
#endif

	if (HAK_CNODE_GET_FLAGS(xlist) & HAK_CNODE_AUTO_FORGED) flags |= CEB_AUTO_FORGED;
	return compile_expression_block(hak, xlist, "do", flags);
}

static int compile_do_p1 (hak_t* hak)
{
	hak_cframe_t* cf;
	cf = GET_TOP_CFRAME(hak);

	/* invalidate variables without shrinking the hak->c->tv buffer.
	 *   { | a b | }
	 *   { | d | }
	 * this way, 'a' doesn't overlap with 'd' in terms of their position
	 * within the same function context and. however, it make 'a' invisible in
	 * the second block where 'd' is declared.
	 *
	 * If we shrinked the buffer instead, some extra code to initialize overlapping
	 *    hak->c->tv.wcount = saved_wcount (not available in the current code)
	 *    hak->c->tv.s.len = cf->u.post_do.lvar_start;
	 * variables upon entry to a block would need to be emitted. 'd' would need to
	 * get explicitly initialized to `nil` in the above case.
	 */
	kill_temporary_variables(hak, cf->u.post_do.lvar_start, cf->u.post_do.lvar_end);

	POP_CFRAME(hak);
	return 0;
}

/* ========================================================================= */

static int compile_if (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* cmd, * obj, * cond;
	hak_cframe_t* cf;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_IF));

	/* (if (< 20 30)
	 *   (perform this)
	 *   (perform that)
	 * elif (< 20 30)
	 *   (perform it)
	 * else
	 *   (perform this finally)
	 * )
	 */
	cmd = HAK_CNODE_CONS_CAR(src); /* if itself */
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src),
			"no conditional expression after '%.*js'", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in %.*js around %.*js",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	cond = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, cond); /* 1 */
	PUSH_SUBCFRAME(hak, COP_POST_IF_COND, obj); /* 2 */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_if.body_pos = -1; /* unknown yet */
	cf->u.post_if.jump_inst_pos = -1; /* not needed */
	cf->u.post_if.start_loc = *HAK_CNODE_GET_LOC(src);
	cf->u.post_if.cmd_cnode = cmd;
/* TODO: OPTIMIZATION:
 *       pass information on the conditional if it's an absoluate true or absolute false to
 *       eliminate some code .. i can't eliminate code because there can be else or elif...
 *       if absoluate true, don't need else or other elif part
 *       if absoluate false, else or other elif part is needed.
 */
	return 0;
}

static HAK_INLINE int patch_nearest_post_if_body (hak_t* hak, hak_cnode_t* cmd)
{
	hak_ooi_t jump_inst_pos, body_pos;
	hak_ooi_t jip, jump_offset;
	hak_cframe_t* cf;

	cf = find_cframe_from_top(hak, COP_POST_IF_BODY);
	HAK_ASSERT(hak, cf != HAK_NULL);
	HAK_ASSERT(hak, cf->opcode == COP_POST_IF_BODY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	/* jump instruction position of the JUMP_FORWARD_IF_FALSE after the conditional of the previous if or elif*/
	jip = cf->u.post_if.jump_inst_pos;

	if (hak->code.bc.len <= cf->u.post_if.body_pos)
	{
		/* the if body is empty. */
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
	}

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jump_inst_pos = hak->code.bc.len;

	/* emit jump_forward before the beginning of the else block.
	 * this is to make the earlier if or elif block to skip
	 * the else part. it is to be patched in post_else_body(). */
	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_0, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD instruction */
	jump_offset = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	if (jump_offset > MAX_CODE_JUMP * 2)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_IFFLOOD, HAK_CNODE_GET_LOC(cmd),
			"code in %.*js too big - size %zu",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd), jump_offset);
		return -1;
	}
	patch_long_jump(hak, jip, jump_offset);

	/* beginning of the elif/else block code */
	/* to drop the result of the conditional when the conditional is false */
	if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	/* this is the actual beginning */
	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	body_pos = hak->code.bc.len;

	/* modify the POST_IF_BODY frame */
	HAK_ASSERT(hak, cf->opcode == COP_POST_IF_BODY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);
	cf->u.post_if.body_pos = body_pos;
	cf->u.post_if.jump_inst_pos = jump_inst_pos;

	return 0;
}

static HAK_INLINE int compile_elif (hak_t* hak)
{
	hak_cnode_t* cmd, * obj, * cond, * src;
	hak_cframe_t* cf;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_ELIF);

	src = cf->operand;
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_ELIF));

	cmd = HAK_CNODE_CONS_CAR(src); /* elif itself */
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src),
			"no conditional expression after '%.*js'", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in %.*js around %.*js",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	cond = HAK_CNODE_CONS_CAR(obj);
	obj = HAK_CNODE_CONS_CDR(obj);

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, cond); /* 1 */
	PUSH_SUBCFRAME(hak, COP_POST_IF_COND, obj); /* 2 */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_if.body_pos = -1; /* unknown yet */
	cf->u.post_if.jump_inst_pos = -1; /* not needed */
	cf->u.post_if.start_loc = *HAK_CNODE_GET_LOC(src);
	cf->u.post_if.cmd_cnode = cmd;

	return patch_nearest_post_if_body(hak, cmd);
}

static HAK_INLINE int compile_else (hak_t* hak)
{
	hak_cnode_t* cmd, * obj, * src;
	hak_cframe_t* cf;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_ELSE);

	src = cf->operand;
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_ELSE));

	cmd = HAK_CNODE_CONS_CAR(src); /* else itself */
	obj = HAK_CNODE_CONS_CDR(src);

	if (obj && !HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in %.*js around %.*js",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	if (check_block_expression_as_body(hak, obj, cmd, FOR_NONE) <= -1) return -1;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, obj);

	return patch_nearest_post_if_body(hak, cmd);
}

/* ========================================================================= */

static int check_class_attr_list (hak_t* hak, hak_cnode_t* attr_list, unsigned int* indexed_type, hak_cnode_t* cmd, hak_cnode_t* class_name)
{
	static struct
	{
		const hak_bch_t* name;
		int shifts;
		unsigned int mask;
		unsigned int value;
	} attr_tab[] = {
		/* the value with 0xFF in the mask field takes up the whole byte
		 * the value with 0x00 in the mask field is a bit value.
		 *
		 * shifts: 0 for object type, 8 for selfspec bit, 12 for spec bit
		 * mask: 0xFF for object type, 0x00 for spec/selfspec bit.
		 *
		 * keep the table sorted in alphabestical order ascending for
		 * binary search */

		{ "b",           0,  0xFF, HAK_OBJ_TYPE_BYTE },
		{ "byte",        0,  0xFF, HAK_OBJ_TYPE_BYTE },
		{ "c",           0,  0xFF, HAK_OBJ_TYPE_CHAR },
		{ "char",        0,  0xFF, HAK_OBJ_TYPE_CHAR },
		{ "character",   0,  0xFF, HAK_OBJ_TYPE_CHAR },

		{ "final",       8,  0x00, HAK_CLASS_SELFSPEC_FLAG_FINAL },

		{ "halfword",    0,  0xFF, HAK_OBJ_TYPE_HALFWORD },
		{ "hw",          0,  0xFF, HAK_OBJ_TYPE_HALFWORD },

		{ "immutable",  12,  0x00, HAK_CLASS_SPEC_FLAG_IMMUTABLE },

		{ "limited",     8,  0x00, HAK_CLASS_SELFSPEC_FLAG_LIMITED },

		{ "uncopyable", 12,  0x00, HAK_CLASS_SPEC_FLAG_UNCOPYABLE },

		{ "v",          12,  0x00, HAK_CLASS_SPEC_FLAG_INDEXED },
		{ "var",        12,  0x00, HAK_CLASS_SPEC_FLAG_INDEXED },
		{ "varying",    12,  0x00, HAK_CLASS_SPEC_FLAG_INDEXED },

		{ "w",           0,  0xFF, HAK_OBJ_TYPE_WORD },
		{ "word",        0,  0xFF, HAK_OBJ_TYPE_WORD }

		/* TODO: uint32 uint16 .. etc */
	};
	hak_obj_type_t ct;

	ct = HAK_OBJ_TYPE_OOP;

	HAK_ASSERT(hak, attr_list != HAK_NULL);
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(attr_list, HAK_CONCODE_TUPLE) ||
	                HAK_CNODE_IS_ELIST_CONCODED(attr_list, HAK_CONCODE_TUPLE));

	if (HAK_CNODE_IS_ELIST_CONCODED(attr_list, HAK_CONCODE_TUPLE))
	{
		/* don't allow empty attribute list */
		if (class_name)
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on '%.*js' for '%.*js'",
				HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		else
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on unnamed class for '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		return -1;
	}

	if (HAK_CNODE_IS_CONS_CONCODED(attr_list, HAK_CONCODE_TUPLE))
	{
		hak_cnode_t* c;
		const hak_ooch_t* tokptr;
		hak_oow_t toklen;

		c = attr_list;
		while (c)
		{
			/* [NOTE] this algorithm is underflow safe with hak_oow_t types */
			hak_oow_t base, lim;
			hak_cnode_t* attr;

			attr = HAK_CNODE_CONS_CAR(c);

			tokptr = HAK_CNODE_GET_TOKPTR(attr);
			toklen = HAK_CNODE_GET_TOKLEN(attr);

			if (!HAK_CNODE_IS_TYPED(attr, HAK_CNODE_SYMLIT))
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr),
					"invalid class attribute name '%.*js'", toklen, tokptr);
				return -1;
			}

			/*
			 * 4 bits for spec flags (bit 12 .. 15)
			 * 4 bits for selfspec flags (bit 8 .. 11)
			 * 8 bits for object type for indexing/variablility (bit 0 .. 7)
			 */

			for (base = 0, lim = HAK_COUNTOF(attr_tab); lim > 0; lim >>= 1) /* binary search */
			{
				hak_oow_t i;
				int n;

				i = base + (lim >> 1); /* mid-point */
				n = hak_comp_oochars_bcstr(tokptr, toklen, attr_tab[i].name);
				if (n == 0)
				{
					/* this is to derive the real mask: (attr_tab[i].mask | attr_tab[i].value).
					 * roughly speaking, it's similary to
					 * 	real_mask = attr_tab[i].mask == 0? attr_tab[i].value: attr_tab[i].mask;
					 *
					 * To flag out duplicate or conflicting attribute, we check if
					 *  - the same bit is already set for a bit-based item (mask field 0x00).
					 *  - a value is non-zero for a byte-based item (mask field 0xFF)
					 */
					if (!!((ct >> attr_tab[i].shifts) & (attr_tab[i].mask | attr_tab[i].value)))
					{
						hak_setsynerrbfmt(
							hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr),
							"conflicting or duplicate class attribute name '#%.*js'", toklen, tokptr);
						return -1;
					}

					ct &= ~attr_tab[i].mask;
					ct |= (attr_tab[i].value << attr_tab[i].shifts);
					goto found;
				}

				if (n > 0) { base = i + 1; lim--; }
			}

			if (lim <= 0)
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr),
					"unrecognized class attribute name '#%.*js'", toklen, tokptr);
				return -1;
			}

		found:
			c = HAK_CNODE_CONS_CDR(c);
		}
	}

	*indexed_type = ct;
	return 0;
}

/*
	class[#attr1 #attr2] Name: SuperclassName (ivar1 ivar2 (cvar1) ivar3) {
		cvar1 := 20
		fun[#ci] new(a b c) {
			self.ivar1 := a
			ivar2 := b
			ivar3 := (self.cvar1 * 2)
		}
	}

	class Name(ivar1 ivar2 (cvar1) ivar3) {
		cvar1 := 20
		fun[#ci] new(a b c) {
			self.ivar1 := a
			ivar2 := b
			ivar3 := (self.cvar1 * 2)
		}
	}
*/

static int compile_class (hak_t* hak, hak_cnode_t* src)
{
	hak_cframe_t* cf;
	hak_cnode_t* cmd, * obj, * tmp;
	hak_cnode_t* attr_list;
	hak_cnode_t* class_name, * superclass;
	int nsuperclasses;
	unsigned int indexed_type;

	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);

	attr_list = HAK_NULL;
	class_name = HAK_NULL;
	indexed_type = HAK_OBJ_TYPE_OOP;

	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(cmd, HAK_CNODE_CLASS));

	if (obj)
	{
		HAK_ASSERT(hak, HAK_CNODE_IS_CONS(obj));

		/* class[#b] .... */
		tmp = HAK_CNODE_CONS_CAR(obj);
		if (HAK_CNODE_IS_ELIST_CONCODED(tmp, HAK_CONCODE_TUPLE) ||
		    HAK_CNODE_IS_CONS_CONCODED(tmp, HAK_CONCODE_TUPLE))
		{
			attr_list = tmp;
			obj = HAK_CNODE_CONS_CDR(obj);
		}
		else goto check_class_name; /* for optimzation. it still works without this jump */
	}

	if (obj)
	{
		HAK_ASSERT(hak, HAK_CNODE_IS_CONS(obj));

		tmp = HAK_CNODE_CONS_CAR(obj);
	check_class_name:
		if (HAK_CNODE_IS_FOR_DATA_SIMPLE(tmp) || HAK_CNODE_IS_FOR_LANG(tmp))
		{
			if (!HAK_CNODE_IS_SYMBOL(tmp))
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_CLASS, HAK_CNODE_GET_LOC(tmp),
					"invalid class name '%.*js' for '%.*js'",
					HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				return -1;
			}

			class_name = tmp;
			obj = HAK_CNODE_CONS_CDR(obj);
		}
	}

	if (!obj)
	{
		if (class_name)
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_CLASS, HAK_CNODE_GET_LOC(src),
				"incomplete definition of '%.*js' for '%.*js'",
				HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		else
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_CLASS, HAK_CNODE_GET_LOC(src),
				"incomplete defintion of unnamed class for '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		return -1;
	}

	if (attr_list && check_class_attr_list(hak, attr_list, &indexed_type, cmd, class_name) <= -1) return -1;

	tmp = HAK_CNODE_CONS_CAR(obj);
	if (HAK_CNODE_IS_COLON(tmp)) /* check for superclass marker */
	{
		hak_cnode_t* marker;

		marker = tmp;
		obj = HAK_CNODE_CONS_CDR(obj);
		if (!obj || !HAK_CNODE_IS_CONS(obj))
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_CLASS, HAK_CNODE_GET_LOC(marker),
				"no expression or declaration after %.*js",
				HAK_CNODE_GET_TOKLEN(marker), HAK_CNODE_GET_TOKPTR(marker));
			return -1;
		}

		/* superclass part */
		superclass = HAK_CNODE_CONS_CAR(obj);
		if (!HAK_CNODE_IS_SYMBOL(superclass))
		{
			if (HAK_CNODE_IS_FOR_DATA_SIMPLE(superclass) || HAK_CNODE_IS_FOR_LANG(superclass))
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_CLASS, HAK_CNODE_GET_LOC(marker),
					"invalid superclass name '%.*js' after '%.*js' for '%.*js'",
					HAK_CNODE_GET_TOKLEN(superclass), HAK_CNODE_GET_TOKPTR(superclass),
					HAK_CNODE_GET_TOKLEN(marker), HAK_CNODE_GET_TOKPTR(marker),
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
			}
			else
			{
				hak_setsynerrbfmt(
					hak, HAK_SYNERR_CLASS, HAK_CNODE_GET_LOC(marker),
					"no valid superclass name after '%.*js' for '%.*js'",
					HAK_CNODE_GET_TOKLEN(marker), HAK_CNODE_GET_TOKPTR(marker),
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
			}
			return -1;
		}

		nsuperclasses = 1;
		obj = HAK_CNODE_CONS_CDR(obj);
	}
	else
	{
		nsuperclasses = 0;
		superclass = HAK_NULL;
	}

	if (class_name)
	{
	#if 0
		SWITCH_TOP_CFRAME(hak, COP_COMPILE_SYMBOL_LITERAL, class_name); /* 1 - push the class name for a named class */
	#else
		hak_oow_t index;
		hak_oop_t cons, sym;

		sym = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(class_name), HAK_CNODE_GET_TOKLEN(class_name));
		if (HAK_UNLIKELY(!sym)) return -1;

		cons = (hak_oop_t)hak_getatsysdic(hak, sym);
		if (!cons)
		{
			cons = (hak_oop_t)hak_putatsysdic(hak, sym, hak->_nil);
			if (HAK_UNLIKELY(!cons)) return -1;
		}

		/* add an association in the system dictionary to the literal frame.
		 * this provides performance advantage at the execution time because
		 * the dictionary doesn't need to be searched for the object.  */
		if (add_literal(hak, cons, &index) <= -1 ||
		    emit_one_param_instruction(hak, HAK_CODE_PUSH_LITERAL_0, index, HAK_CNODE_GET_LOC(class_name)) <= -1) return -1;
	#endif
	}
	else
	{
		/* push nil for class name of an anonymous class */
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;
	}
	POP_CFRAME(hak);

	PUSH_CFRAME(hak, COP_COMPILE_CLASS_P2, class_name); /* 3 - use class name for assignment */
	cf = GET_TOP_CFRAME(hak);
	cf->u._class.nsuperclasses = 0; /* unsed for CLASS_P2 */
	cf->u._class.indexed_type = indexed_type;
	cf->u._class.llvl = HAK_CNODE_GET_LLVL(src);
	cf->u._class.start_loc = *HAK_CNODE_GET_LOC(src); /* TODO: use *HAK_CNODE_GET_LOC(cmd) instead? */
	cf->u._class.cmd_cnode = cmd;
	cf->u._class.class_name_cnode = class_name; /* duplicate with operand to COP_COMPILE_CLASS_P2 */

	PUSH_CFRAME(hak, COP_COMPILE_CLASS_P1, obj); /* 2 - variables declaraions and actual body */
	cf = GET_TOP_CFRAME(hak);
	cf->u._class.nsuperclasses = nsuperclasses; /* this needs to change if we support multiple superclasses... */
	cf->u._class.indexed_type = indexed_type;
	cf->u._class.llvl = HAK_CNODE_GET_LLVL(src);
	cf->u._class.start_loc = *HAK_CNODE_GET_LOC(src); /* TODO: use *HAK_CNODE_GET_LOC(cmd) instead? */
	cf->u._class.cmd_cnode = cmd;
	cf->u._class.class_name_cnode = class_name;

	if (superclass) PUSH_CFRAME(hak, COP_COMPILE_OBJECT, superclass); /* 1 - superclass expression */
	return 0;
}

static HAK_INLINE int compile_class_p1 (hak_t* hak)
{
	/* collect information about declared class-level variables */
	hak_cframe_t* cf;
	hak_cnode_t* obj;
	hak_oow_t saved_tv_wcount, saved_tv_slen;
	class_vardcl_t vardcl;

	cf = GET_TOP_CFRAME(hak);
	obj = cf->operand;

	saved_tv_wcount = hak->c->tv.wcount;
	saved_tv_slen = hak->c->tv.s.len;

	HAK_MEMSET(&vardcl, 0, HAK_SIZEOF(vardcl));

	if (obj && HAK_CNODE_IS_CONS(obj))
	{
		/* class-level variables - instance variables and class variable
		 *  - class X ( a b c (d e) x ) { ... }
		 *  - a b c are instance variables.
		 *  - d e, enclsoed in another [], are class variables.
		 * */
		hak_cnode_t* tmp;
		tmp = HAK_CNODE_CONS_CAR(obj);
		if (HAK_CNODE_IS_CONS_CONCODED(tmp, HAK_CONCODE_XLIST))
		{
			if (collect_vardcl_for_class(hak, obj, &obj, &vardcl) <= -1) return -1;
		}
	}

	/* emit placeholder instructions to be patched in compile_class_p2() */
	if (emit_one_param_instruction(hak, HAK_CODE_PUSH_LITERAL_0, MAX_CODE_PARAM2, &cf->u._class.start_loc) <= -1) return -1;
	if (emit_one_param_instruction(hak, HAK_CODE_PUSH_LITERAL_0, MAX_CODE_PARAM2, &cf->u._class.start_loc) <= -1) return -1;

	if (check_block_expression_as_body(hak, obj, cf->u._class.cmd_cnode, FOR_CLASS) <= -1) return -1;

	if (push_clsblk(hak, &cf->u._class.start_loc, cf->u._class.llvl,
		cf->u._class.class_name_cnode, vardcl.nivars, vardcl.ncvars,
		&hak->c->tv.s.ptr[vardcl.ivar_start], vardcl.ivar_len,
		&hak->c->tv.s.ptr[vardcl.cvar_start], vardcl.cvar_len) <= -1) goto oops;
	if (push_ctlblk(hak, &cf->u._class.start_loc, HAK_CTLBLK_TYPE_CLASS) <= -1) goto oops; /* the class block shall be treated as a control block, too */

	/* discard the instance variables and class variables in the temporary variable collection buffer
	 * because they have been pushed to the class block structure */
	hak->c->tv.s.len = saved_tv_slen;
	hak->c->tv.wcount = saved_tv_wcount;

	/* the position of the CLASS_ENTER instruction */
	hak->c->clsblk.info[hak->c->clsblk.depth].class_enter_inst_pos = hak->code.bc.len;

	/* class_enter nsuperclasses, nivars, ncvars, spec/selfspec, indexed_type  */
	if (emit_byte_instruction(hak, HAK_CODE_CLASS_ENTER, &cf->u._class.start_loc) <= -1) goto oops;
	if (emit_long_param(hak, cf->u._class.nsuperclasses) <= -1) goto oops;
	if (emit_long_param(hak, vardcl.nivars) <= -1) goto oops;
	if (emit_long_param(hak, vardcl.ncvars) <= -1) goto oops;
	if (emit_byte_instruction(hak, (hak_oob_t)((cf->u._class.indexed_type >> 8) & 0xFF), &cf->u._class.start_loc) <= -1) goto oops;
	if (emit_byte_instruction(hak, (hak_oob_t)(cf->u._class.indexed_type & 0xFF), &cf->u._class.start_loc) <= -1) goto oops;

	/* remember the first byte code position to be emitted for the body of
	 * this class. this posistion is used for empty class body check at the
	 * end of the class before 'class_exit' is generated */
	hak->c->clsblk.info[hak->c->clsblk.depth].class_start_inst_pos = hak->code.bc.len;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, obj);
	return 0;

oops:
	hak->c->tv.s.len = saved_tv_slen;
	hak->c->tv.wcount = saved_tv_wcount;
	return -1;
}

static HAK_INLINE int compile_class_p2 (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* class_name;
	hak_loc_t class_loc;
	hak_clsblk_info_t* cbi;
	hak_oow_t patch_pos, patch_end;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_CLASS_P2);
	/*HAK_ASSERT(hak, cf->operand != HAK_NULL);*/

	class_name = cf->operand;
	class_loc = class_name? *HAK_CNODE_GET_LOC(class_name): cf->u._class.start_loc;

	if (hak->code.bc.len > hak->c->clsblk.info[hak->c->clsblk.depth].class_start_inst_pos)
	{
		/* no instructions generated after the class_enter instruction */
		if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, &class_loc) <= -1) return -1;
	}

	cbi = &hak->c->clsblk.info[hak->c->clsblk.depth];
	/* patch the CLASS_ENTER instruction with the final nicvars and ncvars values.
	 * CLASS_ENTER nsuperclasses(lp)|nivars(lp)|ncvars(lp)|spec/selfspec(b)|index_type(b)
	 *  (lp) = long param, (b) = byte */
	patch_pos = cbi->class_enter_inst_pos + 1;
	patch_pos += HAK_CODE_LONG_PARAM_SIZE; /* skip nsuperclasses */
	patch_long_param(hak, patch_pos, cbi->nivars);
	patch_pos += HAK_CODE_LONG_PARAM_SIZE; /* skip nivars */
	patch_long_param(hak, patch_pos, cbi->ncvars);

	/* two placeholder instructions have been pushed before push_clsblk()
	 * in compile_class_p1().
	 *   push_literal long-param long-param <-- (1) position of first long-param (extended long)
	 *   push_literal long-param long-param <-- (2) position of first long-param (extended long)
	 *   class_enter ...                    <-- class_enter_inst_pos
	 */
	patch_pos = cbi->class_enter_inst_pos - (HAK_CODE_LONG_PARAM_SIZE * 4 + 1); /* (1) */
	if (cbi->nivars > 0)
	{
		/* patch the PUSH_LITERAL instruction for ivars */
		/* TODO: reduce space waste for fixed double-long param */
		hak_oop_t obj;
		hak_oow_t index;

		HAK_ASSERT(hak, cbi->nivars <= MAX_NIVARS);
		obj = hak_makestring(hak, cbi->ivars.ptr, cbi->ivars.len);
		if (HAK_UNLIKELY(!obj)) return -1;
		if (add_literal(hak, obj, &index) <= -1) return -1;
		patch_extended_long_param(hak, patch_pos, index);
	}
	else
	{
		/* TODO: reduce space waste for patched NOOP */
		patch_end = (--patch_pos) + (HAK_CODE_LONG_PARAM_SIZE * 2) + 1;
		for (; patch_pos < patch_end; patch_pos++)
			patch_instruction(hak, patch_pos, HAK_CODE_NOOP);
	}

	patch_pos = cbi->class_enter_inst_pos - (HAK_CODE_LONG_PARAM_SIZE * 2); /* (2) */
	if (cbi->ncvars > 0)
	{
		/* patch the PUSH_LITERAL instruction for cvars */
		/* TODO: reduce space waste for fixed extended long param */
		hak_oop_t obj;
		hak_oow_t index;

		HAK_ASSERT(hak, cbi->ncvars <= MAX_NCVARS);
		obj = hak_makestring(hak, cbi->cvars.ptr, cbi->cvars.len);
		if (HAK_UNLIKELY(!obj)) return -1;
		if (add_literal(hak, obj, &index) <= -1) return -1;
		patch_extended_long_param(hak, patch_pos, index);
	}
	else
	{
		/* TODO: reduce space waste for patched NOOP */
		patch_end = (--patch_pos) + (HAK_CODE_LONG_PARAM_SIZE * 2) + 1;
		HAK_ASSERT(hak, patch_end == cbi->class_enter_inst_pos);
		for (;patch_pos < patch_end; patch_pos++)
			patch_instruction(hak, patch_pos, HAK_CODE_NOOP);
	}

	pop_ctlblk(hak);
	pop_clsblk(hak);  /* end of the class block */

	if (emit_byte_instruction(hak, HAK_CODE_CLASS_PEXIT, &class_loc) <= -1) return -1; /* pop + exit */

	if (class_name)
	{
		/* a class name is treated like a global variable */
		SWITCH_TOP_CFRAME(hak, COP_EMIT_SET, class_name);
		cf = GET_TOP_CFRAME(hak);
		cf->u.set.vi.type = VAR_NAMED;
		cf->u.set.mode = VAR_ACCESS_STORE;
	}
	else
	{
		POP_CFRAME(hak);
	}

	return 0;
}

/* ========================================================================= */

static int check_fun_attr_list (hak_t* hak, hak_cnode_t* attr_list, unsigned int* fun_type, hak_cnode_t* cmd, hak_cnode_t* class_name, hak_cnode_t* fun_name)
{
	unsigned int ft;

	ft = FUN_IM;

	HAK_ASSERT(hak, attr_list != HAK_NULL);
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(attr_list, HAK_CONCODE_TUPLE) ||
	                HAK_CNODE_IS_ELIST_CONCODED(attr_list, HAK_CONCODE_TUPLE));

	if (HAK_CNODE_IS_ELIST_CONCODED(attr_list, HAK_CONCODE_TUPLE))
	{
		/* don't allow empty attribute list */
		if (class_name && fun_name)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on '%.*js:%.*js' for '%.*js'",
				HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
				HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		else if (fun_name)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on '%.*js' for '%.*js'",
				HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		else
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on unnamed function for '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		return -1;
	}

	if (HAK_CNODE_IS_CONS_CONCODED(attr_list, HAK_CONCODE_TUPLE))
	{
		hak_cnode_t* c, * a;
		const hak_ooch_t* tokptr;
		hak_oow_t toklen;

		c = attr_list;
		while (c)
		{
			a = HAK_CNODE_CONS_CAR(c);

			tokptr = HAK_CNODE_GET_TOKPTR(a);
			toklen = HAK_CNODE_GET_TOKLEN(a);

			if (!HAK_CNODE_IS_TYPED(a, HAK_CNODE_SYMLIT))
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(a),
					"invalid function attribute name '%.*js'", toklen, tokptr);
				return -1;
			}

			if (hak_comp_oochars_bcstr(tokptr, toklen, "class") == 0 ||
			    hak_comp_oochars_bcstr(tokptr, toklen, "c") == 0)
			{
				if (ft != FUN_IM)
				{
				conflicting:
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(a),
						"conflicting function attribute name '#%.*js'", toklen, tokptr);
					return -1;
				}
				ft = FUN_CM;
			}
			else if (hak_comp_oochars_bcstr(tokptr, toklen, "classinst") == 0 ||
			         hak_comp_oochars_bcstr(tokptr, toklen, "ci") == 0)
			{
				if (ft != FUN_IM) goto conflicting;
				ft = FUN_CIM;
			}
			else
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(a),
					"unrecognized function attribute name '#%.*js'", toklen, tokptr);
				return -1;
			}

			c = HAK_CNODE_CONS_CDR(c);
		}
	}

	*fun_type = ft;
	return 0;
}

static int is_cnode_eligible_for_fun_name (const hak_cnode_t* tmp)
{
	return HAK_CNODE_IS_SYMBOL(tmp) || HAK_CNODE_IS_BINOP(tmp);
}

static int is_cnode_eligible_for_fun_name_after_colon (const hak_cnode_t* tmp)
{
	return HAK_CNODE_IS_SYMBOL(tmp) || HAK_CNODE_IS_BINOP(tmp) || (HAK_CNODE_IS_STRLIT(tmp) && hak_is_binop_string(HAK_CNODE_GET_TOK(tmp)));
}

static int compile_fun (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* cmd, * next;
	hak_oow_t va, nargs, nrvars, nlvars;
	hak_ooi_t jump_inst_pos, lfsize_pos;
	hak_oow_t saved_tv_wcount, tv_dup_start;
	hak_cnode_t* fun_name;
	hak_oow_t fun_name_lfindex;
	hak_cnode_t* class_name;
	hak_cnode_t* attr_list;
	hak_cnode_t* arg_list;
	hak_cnode_t* fun_body;
	hak_cframe_t* cf;
	unsigned int fun_type;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));

	saved_tv_wcount = hak->c->tv.wcount;
	cmd = HAK_CNODE_CONS_CAR(src);
	next = HAK_CNODE_CONS_CDR(src);
	fun_name = HAK_NULL;
	class_name = HAK_NULL;
	attr_list = HAK_NULL;
	arg_list = HAK_NULL;
	fun_body = HAK_NULL;
	fun_type = FUN_PLAIN;

	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(cmd, HAK_CNODE_FUN));

	if (next)
	{
		hak_cnode_t* tmp;

		/* the reader ensures that the cdr field of a cons cell points to the next cell.
		 * and only the field of the last cons cell is NULL. */
		HAK_ASSERT(hak, HAK_CNODE_IS_CONS(next));

		/* fun (arg..)
		 * fun name(arg..)
		 * fun[#attr..] name(arg..)  ## valid as class method, not valid as plain function
		 * fun[#attr..] (arg..)      ## not valid. no attribute list for unnamed functions
		 * fun[#attr..] class:name(arg..)
		 */

		tmp = HAK_CNODE_CONS_CAR(next);
		if (HAK_CNODE_IS_CONS_CONCODED(tmp, HAK_CONCODE_TUPLE) ||
		    HAK_CNODE_IS_ELIST_CONCODED(tmp, HAK_CONCODE_TUPLE))
		{
			attr_list = tmp;
			next = HAK_CNODE_CONS_CDR(next);
			if (!next)
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(fun_name),
					"no function name or arguments after attribute list for '%.*js'",
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				return -1;
			}
			tmp = HAK_CNODE_CONS_CAR(next);
		}

		if (is_cnode_eligible_for_fun_name(tmp))
		{
			/* 'fun' followed by name */
		fun_got_name:
			/* name must be followed by argument list */
			fun_name = tmp;

			next = HAK_CNODE_CONS_CDR(next);
			if (!next)
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(fun_name),
					"function name '%.*js' not followed by ( or : for '%.*js'",
					HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				return -1;
			}

			tmp = HAK_CNODE_CONS_CAR(next);
			if (HAK_CNODE_IS_COLON(tmp))
			{
				/* fun class:name(arg..)
				 * fun(#attr..) class:name(arg..) */

				class_name = fun_name;
				next = HAK_CNODE_CONS_CDR(next);
				if (!next)
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(class_name),
						"no function name after class name '%.*js:' for '%.*js'",
						HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
					return -1;
				}

				tmp = HAK_CNODE_CONS_CAR(next);
				if (!is_cnode_eligible_for_fun_name_after_colon(tmp))
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(tmp),
						"invalid function name '%.*js' after '%.*js:' for '%.*js'",
						HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
						HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
					return -1;
				}
				fun_name = tmp;

				next = HAK_CNODE_CONS_CDR(next);
				if (!next)
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(fun_name),
						"function name '%.*js:%.*js' not followed by ( for '%.*js'",
						HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
						HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
					return -1;
				}

				tmp = HAK_CNODE_CONS_CAR(next); /* pointing to argument list */

				if (is_in_class_init_scope(hak))
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(fun_name),
						"class name '%.*js' before :'%.*js' prohibited in class initialization context",
						HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
						HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name));
					return -1;
				}
			}
		}

		if (HAK_CNODE_IS_CONS_CONCODED(tmp, HAK_CONCODE_XLIST) ||
		    HAK_CNODE_IS_ELIST_CONCODED(tmp, HAK_CONCODE_XLIST))
		{
			/* 'fun' followed by attribute or argument list */
			arg_list = tmp;
			/* argument list
			 *   fun class:name()
			 *   fun name      ()
			 *   fun           ()
			 *                 ^  */
			next = HAK_CNODE_CONS_CDR(next); /* point past argument list */
			if (!next)
			{
				if (fun_name)
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(cmd),
						"no function body after argument list of function '%.*js' for '%.*js'",
						HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				}
				else
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(cmd),
						"unnamed function not followed by function body for '%.*js'",
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				}
				return -1;
			}
			fun_body = next;

			if (src->cn_llvl >= 2 && is_in_class_init_scope_but_not_at_llvl(hak, src->cn_llvl - 2))
			{
				if (fun_name)
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(cmd),
						"function '%.*js' defined with '%.*js' prohibited in class initialziation context",
						HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				}
				else
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(cmd),
						"unnamed function defined with '%.*js' prohibited in class initialziation context",
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				}
				return -1;
			}

			if (!fun_name && is_in_class_init_scope(hak))
			{
				/* TODO: it must allow as rvalue..
				 * class X {
				 *     b := (fun() {}) ## this is prohibited in the previous if check
				 *     (fun() {}) ## this is prohibited in the previous if check as well
				 *     fun() {} ## this is prohibited in this if check
				 * }
				 */
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(cmd),
					"unnamed function defined with '%.*js' prohibited in class initialziation context",
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
				return -1;
			}
		}
		else
		{
			/* not valid argument list or attribute list
			 * fun if         ## cmd is 'fun', fun_name is nil
			 * fun a if       ## cmd is 'fun', fun_name is 'a'
			 * fun a:b if     ## cmd is 'fun', fun_name is 'b'
			 * fun self.a     ## cmd is 'fun', fun_name is nil
			 */
			if (fun_name)
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(tmp),
					"'%.*js' not followed by ( but followed by '%.*js'",
					HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name),
					HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
			}
			else
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(tmp),
					"invalid function name '%.*js' for '%.*js'",
					HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
			}
			return -1;
		}
	}
	else /* !next */
	{
		/* nothing after 'fun' (e.g. fun ) */
		hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(cmd),
			"'%.*js' not followed by name or (",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}

	if (attr_list)
	{
		if (is_in_class_init_scope(hak) || class_name)
		{
			/* NOTE
			 *  checking simply with is_in_class_init_scope() isn't sufficient for code like this:
			 *    class X {
			 *      a := (fun[#ci] x(){})
			 *    }
			 * but the prior checks prevents the function definition itself in that context.
			 * so the simple check is just ok.
			 */
			if (check_fun_attr_list(hak, attr_list, &fun_type, cmd, class_name, fun_name) <= -1) return -1;
			if (class_name) fun_type |= 0x100; /* defined in `fun class:xxx` style outside class */
		}
		else
		{
			if (fun_name)
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
					"attribute list prohibited on plain function '%.*js'",
					HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name));
			}
			else
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_FUN, HAK_CNODE_GET_LOC(attr_list),
					"attribute list prohibited on unnamed function for '%.*js'",
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
			}
			return -1;
		}
	}
	else
	{
		if (is_in_class_init_scope(hak) || class_name)
		{
			fun_type = FUN_IM;
			if (class_name) fun_type |= 0x100;
		}
	}

	/* process the argument list */
	va = 0;
	nargs = 0;
	nrvars = 0;

	HAK_ASSERT(hak, HAK_CNODE_IS_ELIST_CONCODED(arg_list, HAK_CONCODE_XLIST) ||
	                HAK_CNODE_IS_CONS_CONCODED(arg_list, HAK_CONCODE_XLIST));
	if (HAK_CNODE_IS_ELIST_CONCODED(arg_list, HAK_CONCODE_XLIST))
	{
		/* empty list - no argument - fun () {+ 10 20} */
		/* do nothing */
	}
	else
	{
		hak_cnode_t* arg, * dcl;
		int in_ret_args = 0;

		tv_dup_start = hak->c->tv.s.len;
		dcl = arg_list;
		do
		{
			arg = HAK_CNODE_CONS_CAR(dcl);

			if (in_ret_args)
			{
				if (!HAK_CNODE_IS_SYMBOL(arg))
				{
					/* in 'fun x (x :: 20) { }', '20' is not a valid return variable name.
					 * in 'fun x (x :: if) { }', 'if' is not a valid return variable name. */
					hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(arg),
						"invalid return variable '%.*js' for '%.*js'",
						HAK_CNODE_GET_TOKLEN(arg), HAK_CNODE_GET_TOKPTR(arg),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
					return -1;
				}

				if (add_temporary_variable(hak, arg, tv_dup_start, "return variable", HAK_CNODE_GET_TOK(cmd)) <= -1) return -1;
				nrvars++;
			}
			else if (va)
			{
				if (HAK_CNODE_IS_DBLCOLONS(arg))
				{
					in_ret_args = 1;
				}
				else
				{
					/* in 'fun x (... a) {}', 'a' is an unexpected token.
					 * only ')' or '::' can follow ... */
					hak_setsynerrbfmt(hak, HAK_SYNERR_CNODE, HAK_CNODE_GET_LOC(arg),
						"unexpected token '%.*js' after '...' for '%.*js'",
						HAK_CNODE_GET_TOKLEN(arg), HAK_CNODE_GET_TOKPTR(arg),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
					return -1;
				}
			}
			else
			{
				if (HAK_CNODE_IS_DBLCOLONS(arg))
				{
					in_ret_args = 1;
				}
				else if (HAK_CNODE_IS_ELLIPSIS(arg))
				{
					va = 1;
				}
				else if (!HAK_CNODE_IS_SYMBOL(arg))
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_ARGNAME, HAK_CNODE_GET_LOC(arg),
						"invalid argument name '%.*js' for '%.*js'",
						HAK_CNODE_GET_TOKLEN(arg), HAK_CNODE_GET_TOKPTR(arg),
						HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
					return -1;
				}
				else
				{
					if (add_temporary_variable(hak, arg, tv_dup_start, "argument", HAK_CNODE_GET_TOK(cmd)) <= -1) return -1;
					nargs++;
				}
			}

			dcl = HAK_CNODE_CONS_CDR(dcl);
			if (!dcl) break;

			if (!HAK_CNODE_IS_CONS(dcl))
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(dcl),
					"redundant cdr in argument list in %.*js around %.*js",
					HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
					HAK_CNODE_GET_TOKLEN(dcl), HAK_CNODE_GET_TOKPTR(dcl));
				return -1;
			}
		}
		while (1);
	}

	if ((fun_type & 0xFF) == FUN_IM && fun_name && HAK_CNODE_IS_BINOP(fun_name) && (is_in_class_init_scope(hak) || class_name) && nargs != 1)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(arg_list),
			"only one argument expected for '%.*js%hs%.*js'",
			(class_name? HAK_CNODE_GET_TOKLEN(class_name): 0),
			(class_name? HAK_CNODE_GET_TOKPTR(class_name): _empty_str_),
			(class_name? ":": ""),
			HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name));
		return -1;
	}

	if (nargs > MAX_CODE_NBLKARGS) /*TODO: change this limit to max call argument count */
	{
		/* while an integer object is pused to indicate the number of
		 * block arguments, evaluation which is done by message passing
		 * limits the number of arguments that can be passed. so the
		 * check is implemented */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGFLOOD, HAK_CNODE_GET_LOC(arg_list),
			"too many(%zu) arguments in %.*js", nargs,
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}

	if (nrvars > MAX_CODE_NBLKLVARS)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_VARFLOOD, HAK_CNODE_GET_LOC(arg_list),
			"too many(%zu) return variables in %.*js", nrvars,
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	HAK_ASSERT(hak, nargs + nrvars == hak->c->tv.wcount - saved_tv_wcount);

	/*
	 * fun aa(a b) { ...	};
	 *
	 * the block expression must be the first and the only expression at the body position.
	 * the variable declaration can't be placed before the block expression.
	 * it is supported inside the block expression itself.
	 */
	if (check_block_expression_as_body(hak, fun_body, cmd, FOR_NONE) <= -1) return -1;

	HAK_ASSERT(hak, fun_body != HAK_NULL);
	nlvars = 0; /* no known local variables until the actual block is processed */

	HAK_ASSERT(hak, nargs + nrvars + nlvars == hak->c->tv.wcount - saved_tv_wcount);

	if (push_funblk(
		hak, HAK_CNODE_GET_LOC(src), va, nargs, nrvars, nlvars, hak->c->tv.wcount,
		hak->c->tv.s.len, hak->code.bc.len, hak->code.lit.len, fun_type) <= -1) return -1;

	fun_name_lfindex = MAX_CODE_PARAM2; /* this maximum value indicates no function name */
	if (fun_name)
	{
		hak_oop_t sym;
		sym = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(fun_name), HAK_CNODE_GET_TOKLEN(fun_name));
		if (HAK_UNLIKELY(!sym)) return -1;
		if (add_literal(hak, sym, &fun_name_lfindex) <= -1) return -1;
	}

	if (hak->option.trait & HAK_TRAIT_INTERACTIVE)
	{
		/* MAKE_FUNCTION attr_mask_1 attr_mask_2 name_lfindex lfbase lfsize */
		hak_oow_t lfbase;

		lfbase = hak->code.lit.len - hak->c->funblk.info[hak->c->funblk.depth - 1].lfbase;
		if (lfbase > MAX_CODE_PARAM2)
		{
			hak_seterrbfmt(hak, HAK_ERANGE, "literal frame base too large");
			return -1;
		}

		if (emit_two_param_instruction(hak, HAK_CODE_MAKE_FUNCTION, 0, 0, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;
		if (emit_extended_long_param(hak, fun_name_lfindex) <= -1) return -1;
		if (emit_long_param(hak, lfbase) <= -1) return -1; /* lfbase(literal frame base) */
		lfsize_pos = hak->code.bc.len; /* literal frame size */
		if (emit_long_param(hak, 0) <= -1) return -1; /* place holder for lfsize */
	}
	else
	{
		/* MAKE_BLOCK attr_mask_1 attr_mask_2 - will patch attr_mask in pop_funblk() */
		if (emit_two_param_instruction(hak, HAK_CODE_MAKE_BLOCK, 0, 0, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;
		if (emit_extended_long_param(hak, fun_name_lfindex) <= -1) return -1;
	}

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);  /* guaranteed in emit_byte_instruction() */
	jump_inst_pos = hak->code.bc.len;
	/* specifying MAX_CODE_JUMP causes emit_one_param_instruction() to
	 * produce the long jump instruction (HAK_CODE_JUMP_FORWARD_X) */
	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_0, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, fun_body); /* 1 */
	PUSH_SUBCFRAME(hak, COP_POST_FUN, fun_name); /* 3*/
	cf = GET_SUBCFRAME(hak);
	cf->u.fun.fun_type = fun_type;
	cf->u.fun.class_name = class_name;
	cf->u.fun.jump_inst_pos = jump_inst_pos;
	if (hak->option.trait & HAK_TRAIT_INTERACTIVE) cf->u.fun.lfsize_pos = lfsize_pos;

	PUSH_SUBCFRAME(hak, COP_EMIT_FUN, src); /* 2 */
	cf = GET_SUBCFRAME(hak);
	cf->u.fun.fun_type = fun_type;
	cf->u.fun.class_name = class_name;
	cf->u.fun.jump_inst_pos = jump_inst_pos;
	if (hak->option.trait & HAK_TRAIT_INTERACTIVE) cf->u.fun.lfsize_pos = lfsize_pos;

	return 0;
}
/* ========================================================================= */

static int check_var_attr_list (hak_t* hak, hak_cnode_t* attr_list, unsigned int* var_type, hak_cnode_t* cmd, hak_cnode_t* class_name, hak_cnode_t* var_name)
{
	unsigned int ft;

	ft = VAR_INST;

	HAK_ASSERT(hak, attr_list != HAK_NULL);
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(attr_list, HAK_CONCODE_XLIST) ||
	                HAK_CNODE_IS_ELIST_CONCODED(attr_list, HAK_CONCODE_XLIST));

	if (HAK_CNODE_IS_ELIST_CONCODED(attr_list, HAK_CONCODE_XLIST))
	{
		/* don't allow empty attribute list */
		if (class_name)
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on '%.*js' in '%.*js' for '%.*js'",
				HAK_CNODE_GET_TOKLEN(var_name), HAK_CNODE_GET_TOKPTR(var_name),
				HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name),
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		else
		{
			hak_setsynerrbfmt(
				hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(attr_list),
				"empty attribute list on '%.*js' in unnamed class for '%.*js'",
				HAK_CNODE_GET_TOKLEN(var_name), HAK_CNODE_GET_TOKPTR(var_name),
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		}
		return -1;
	}

	if (HAK_CNODE_IS_CONS_CONCODED(attr_list, HAK_CONCODE_XLIST))
	{
		hak_cnode_t* c, * a;
		const hak_ooch_t* tokptr;
		hak_oow_t toklen;

		c = attr_list;
		while (c)
		{
			a = HAK_CNODE_CONS_CAR(c);

			tokptr = HAK_CNODE_GET_TOKPTR(a);
			toklen = HAK_CNODE_GET_TOKLEN(a);

			if (!HAK_CNODE_IS_TYPED(a, HAK_CNODE_SYMLIT))
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(a),
					"invalid variable attribute name '%.*js'", toklen, tokptr);
				return -1;
			}

			if (hak_comp_oochars_bcstr(tokptr, toklen, "class") == 0 ||
			    hak_comp_oochars_bcstr(tokptr, toklen, "c") == 0)
			{
				if (ft != VAR_INST)
				{
				conflicting:
					hak_setsynerrbfmt(hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(a),
						"conflicting variable attribute name '#%.*js'", toklen, tokptr);
					return -1;
				}
				ft = VAR_CLASS_I;
			}
			else
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(a),
					"unrecognized variable attribute name '#%.*js'", toklen, tokptr);
				return -1;
			}

			c = HAK_CNODE_CONS_CDR(c);
		}
	}

	*var_type = ft;
	return 0;
}

static int compile_var (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* cmd, * next, * tmp;
	hak_cnode_t* attr_list;
	hak_clsblk_info_t* cbi;

	/* this is for instance/class variable declaration
	 * inside the class body block */

	cmd = HAK_CNODE_CONS_CAR(src);
	next = HAK_CNODE_CONS_CDR(src);
	attr_list = HAK_NULL;

/*TODO: put some assertion regarding funblk to cslblk relaion */
	cbi = &hak->c->clsblk.info[hak->c->clsblk.depth];

	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(cmd, HAK_CNODE_VAR));

	if (!next)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(cmd),
			"'%.*js' not followed by name or (",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		goto oops;
	}

	/* the reader ensures that the cdr field of a cons cell points to the next cell.
	 * and only the field of the last cons cell is NULL. */
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(next));

	if (!is_in_class_init_scope(hak))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(cmd),
			"'%.*js' prohibited in this context",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		goto oops;
	}

	tmp = HAK_CNODE_CONS_CAR(next);
	if (HAK_CNODE_IS_CONS_CONCODED(tmp, HAK_CONCODE_XLIST) ||
	    HAK_CNODE_IS_ELIST_CONCODED(tmp, HAK_CONCODE_XLIST))
	{
		/* probably attribute list */
		attr_list = tmp;

		next = HAK_CNODE_CONS_CDR(next);
		if (!next)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(attr_list),
				"no name after attribute list for '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
			goto oops;
		}

		tmp = HAK_CNODE_CONS_CAR(next);
	}

	if (HAK_CNODE_IS_SYMBOL(tmp))
	{
		unsigned int var_type = VAR_INST;

		if (attr_list && check_var_attr_list(hak, attr_list, &var_type, cmd, cbi->class_name, tmp) <= -1) goto oops;

		HAK_ASSERT(hak, var_type == VAR_INST || var_type == VAR_CLASS_I);
		while (1)
		{
			if (var_type == VAR_INST)
			{
				if (cbi->nivars >= MAX_NIVARS)
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_VARFLOOD, HAK_CNODE_GET_LOC(tmp),
						"too many(%zu) instance variables before '%.*js'",
						cbi->nivars, HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
					goto oops;
				}

				if (add_class_level_variable(hak, &cbi->ivars, &cbi->cvars, tmp, "instance") <= -1) goto oops;
				cbi->nivars++;
			}
			else
			{
				if (cbi->ncvars >= MAX_NCVARS)
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_VARFLOOD, HAK_CNODE_GET_LOC(tmp),
						"too many(%zu) class variables before '%.*js'",
						cbi->ncvars, HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
					goto oops;
				}

				if (add_class_level_variable(hak, &cbi->cvars, &cbi->ivars, tmp, "class") <= -1) goto oops;
				cbi->ncvars++;
			}

			next = HAK_CNODE_CONS_CDR(next);
			if (!next) break;
			tmp = HAK_CNODE_CONS_CAR(next);
			if (!HAK_CNODE_IS_SYMBOL(tmp)) goto not_ident;
		}
	}
	else
	{
	not_ident:
		hak_setsynerrbfmt(hak, HAK_SYNERR_VAR, HAK_CNODE_GET_LOC(tmp),
			"invalid variable name '%.*js' for '%.*js'",
			HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		goto oops;
	}

/* if there is assignment with expression, we can arragne to compile RHS. in that case
 * the explicit PUSH_NIL here isn't needed */
	/* TODO: hack for POP_STACKPOP generated in compile_object_list().
	 *       remove generating this instruction after having fixed the problem in that function */
	if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;

	POP_CFRAME(hak);
	return 0;

oops:
	return -1;
}

static int compile_return (hak_t* hak, hak_cnode_t* src, int ret_from_home)
{
	hak_cnode_t* obj, * val;
	hak_cframe_t* cf;
	hak_funblk_info_t* fbi;
	hak_ooi_t i;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_RETURN) ||
	                HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_REVERT));

	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	obj = HAK_CNODE_CONS_CDR(src);

	for (i = hak->c->ctlblk.depth; i > hak->c->funblk.info[hak->c->funblk.depth].ctlblk_base; --i)
	{
		switch (hak->c->ctlblk.info[i]._type)
		{
			case HAK_CTLBLK_TYPE_LOOP:
				/* do nothing */
				break;

			case HAK_CTLBLK_TYPE_TRY:
				if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
				break;

			case HAK_CTLBLK_TYPE_CLASS:
				if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(src)) <= -1) return -1;
				break;
		}
	}

	if (fbi->tmpr_nrvars > 0)
	{
		hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);

		if (ret_from_home)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(src),
				"'%.*js' not compatible with return variables",
				HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
			return -1;
		}

		/* if a return variable are specified in the current function block, the return statement must not be followed by a return value */
		if (obj)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(src),
				"use of return value in '%.*js' not compatible with return variables",
				HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
			return -1;
		}

/* TODO: pop stack if this is not the first statement... */
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_RETURN_R, HAK_CNODE_GET_LOC(tmp)) <= -1) return -1;
		POP_CFRAME(hak);
	}
	else
	{
		if (!obj)
		{
	/* TODO: should i allow (return)? does it return the last value on the stack? */
			/* no value */
			hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
			hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src),
				"no value specified for '%.*js'", HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
			return -1;
		}
		else if (!HAK_CNODE_IS_CONS(obj))
		{
			hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
				"redundant cdr in '%.*js' around '%.*js'",
				HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
				HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
			return -1;
		}

		val = HAK_CNODE_CONS_CAR(obj);

		obj = HAK_CNODE_CONS_CDR(obj);
		if (obj)
		{
			hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
			hak_cnode_t* next = HAK_CNODE_CONS_CAR(obj);
			hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(obj),
				"excessive argument to '%.*js' around '%.*js'",
				HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
				HAK_CNODE_GET_TOKLEN(next), HAK_CNODE_GET_TOKPTR(next));
			return -1;
		}

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, val);

		PUSH_SUBCFRAME(hak, COP_EMIT_RETURN, src);
		cf = GET_SUBCFRAME(hak);
		cf->u._return.from_home = ret_from_home;
	}

	return 0;
}

static int compile_set (hak_t* hak, hak_cnode_t* src)
{
	hak_cframe_t* cf;
	hak_cnode_t* cmd, * obj, * var, * val;
	hak_var_info_t vi;
	int x;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_SET));

	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(src),
			"no variable name in %.*js", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in %.*js around %.*js",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	var = HAK_CNODE_CONS_CAR(obj);
	if (!HAK_CNODE_IS_SYMBOL(var) && !HAK_CNODE_IS_DSYMBOL_CLA(var))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(var),
			"variable name not symbol in %.*js around %.*js",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
		return -1;
	}

	obj = HAK_CNODE_CONS_CDR(obj);
	if (!obj)
	{
		hak_setsynerrbfmt(
			hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(var),
			"no value after '%.*js' for '%.*js'",
			HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var),
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	val = HAK_CNODE_CONS_CAR(obj);

	obj = HAK_CNODE_CONS_CDR(obj);
	if (obj)
	{
		hak_cnode_t* next = HAK_CNODE_CONS_CAR(obj);
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(obj),
			"too many arguments to '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(next), HAK_CNODE_GET_TOKPTR(next));
		return -1;
	}

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, val);

	x = find_variable_backward_with_token(hak, var, &vi);
	if (x <= -1) return -1;

	if (x == 0)
	{
		if (HAK_CNODE_IS_DSYMBOL_CLA(var))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEUNKNOWN, HAK_CNODE_GET_LOC(var),
				"unknown class-level variable name '%.*js'",
				HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
			return -1;

		}

		PUSH_SUBCFRAME(hak, COP_EMIT_SET, var); /* set doesn't evaluate the variable name */
		cf = GET_SUBCFRAME(hak);
		cf->u.set.vi.type = VAR_NAMED;
	}
	else
	{
		/* the check in compile_fun() must ensure this condition */
		PUSH_SUBCFRAME(hak, COP_EMIT_SET, cmd);
		cf = GET_SUBCFRAME(hak);
		cf->u.set.vi = vi;
	}
	cf->u.set.mode = VAR_ACCESS_STORE;

	return 0;
}

static int compile_set_r (hak_t* hak, hak_cnode_t* src)
{
	hak_cframe_t* cf;
	hak_cnode_t* cmd, * obj, * var, * val, * var_start;
	hak_oow_t nvars, i;
	hak_var_info_t vi;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_SET_R));

	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(src),
			"no variable name in '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	nvars = 0;
	var_start = obj;
	do
	{
		var = HAK_CNODE_CONS_CAR(obj);
		if (!HAK_CNODE_IS_SYMBOL(var))
		{
			if (nvars > 0) break;
			hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(var),
				"variable name not symbol in '%.*js' around '%.*js'",
				HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
				HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
			return -1;
		}

		nvars++;
		obj = HAK_CNODE_CONS_CDR(obj);
	}
	while (obj);


	if (!obj)
	{
		hak_setsynerrbfmt(
			hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(var),
			"no value after '%.*js' for '%.*js'",
			HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var),
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	val = HAK_CNODE_CONS_CAR(obj);

	obj = HAK_CNODE_CONS_CDR(obj);
	if (obj)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(obj),
			"too many arguments to '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_R, val); /* special for set_r */
	cf = GET_TOP_CFRAME(hak);
	cf->u.obj_r.nrets = nvars; /* number of return variables to get assigned */

	for (i = 0, obj = var_start; i < nvars; i++, obj = HAK_CNODE_CONS_CDR(obj))
	{
		int x;

		var = HAK_CNODE_CONS_CAR(obj);

		x = find_variable_backward_with_token(hak, var, &vi);
		if (x <= -1) return -1;

		if (x == 0)
		{
			if (HAK_CNODE_IS_DSYMBOL_CLA(var))
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEUNKNOWN, HAK_CNODE_GET_LOC(var),
					"unknown class-level variable name '%.*js'", HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
				return -1;
			}

			PUSH_SUBCFRAME(hak, COP_EMIT_SET, var); /* set_r doesn't evaluate the variable name */
			cf = GET_SUBCFRAME(hak);
			cf->u.set.vi.type = VAR_NAMED;
		}
		else
		{
			PUSH_SUBCFRAME(hak, COP_EMIT_SET, cmd);
			cf = GET_SUBCFRAME(hak);
			cf->u.set.vi = vi;
		}

		/*
		 * (fun f(x y ::: aa bb cc) ....)
		 * (set_r a b c (f 1 2))
		 *
		 *    the call to f
		 *       call 2 3  ; 2 arguments, 3 return variables (CALL_R)
		 *                 ; 3 to be emitted from cf->u.obj_r.nrets
		 *                 ; this gets remembered in req_nrvars of the created context.
		 *
		 *    the return from f must push 3 values.
		 *        push_return_r  ; as remembered in the ctx->req_nrvars
		 *
		 *    emit store_into_xxx instruction for the first return variable assignment.
		 *    emit pop_into_xxx instructions for the rest.
		 *        pop_into c
		 *        pop_into b
		 *        store_into a
		 */
		cf->u.set.mode = (i <= 0)? VAR_ACCESS_STORE: VAR_ACCESS_POP; /* STORE_INTO or POP_INTO  */
	}

	return 0;
}

/* ========================================================================= */

static int compile_try (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* cmd, * obj;
	hak_cframe_t* cf;
	hak_ooi_t jump_inst_pos;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));

	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_TRY));

	/* try {
	 *   perform this
	 *   perform that
	 *   throw 10
	 * } catch (x) {
	 *   perform xxx
	 *   perform yyy
	 * }
	 */
	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src),
			"no expression specified in '%.*js'", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	if (push_ctlblk(hak, HAK_CNODE_GET_LOC(src), HAK_CTLBLK_TYPE_TRY) <= -1) return -1;

/* TODO: HAK_TRAIT_INTERACTIVE??? */

	jump_inst_pos = hak->code.bc.len;
	if (emit_one_param_instruction(hak, HAK_CODE_TRY_ENTER, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(cmd)) <= -1) return -1;

	if (check_block_expression_as_body(hak, obj, cmd, FOR_TRY) <= -1) return -1;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_TRY_OBJECT_LIST, obj);  /* 1*/
	PUSH_SUBCFRAME(hak, COP_POST_TRY, cmd); /* 2 */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_try.jump_inst_pos = jump_inst_pos;

	return 0;
}

static HAK_INLINE int patch_nearest_post_try (hak_t* hak, hak_ooi_t* catch_skip_jip)
{
	hak_ooi_t jip, block_code_size;
	hak_cframe_t* cf;

	cf = find_cframe_from_top(hak, COP_POST_TRY);
	HAK_ASSERT(hak, cf != HAK_NULL);
	HAK_ASSERT(hak, cf->opcode == COP_POST_TRY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	jip = cf->u.post_try.jump_inst_pos;

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD instruction */
	block_code_size = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	if (block_code_size == 0)
	{
		/* no body in try */
/* TODO: is this correct??? */
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
	}

	if (emit_byte_instruction(hak, HAK_CODE_TRY_EXIT, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	*catch_skip_jip = hak->code.bc.len;
	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_0,  MAX_CODE_JUMP, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD instruction */
	block_code_size = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	if (block_code_size > MAX_CODE_JUMP * 2)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKFLOOD, HAK_CNODE_GET_LOC(cf->operand), "code too big - size %zu", block_code_size);
		return -1;
	}
	patch_long_jump(hak, jip, block_code_size); /* patch TRY_ENTER */

	return 0;
}

static HAK_INLINE int compile_catch (hak_t* hak)
{
	hak_cnode_t* cmd, * obj, * src, * exarg;
	hak_cframe_t* cf;
	hak_ooi_t jump_inst_pos;
	hak_oow_t exarg_offset;
	hak_var_info_t vi;
	hak_funblk_info_t* fbi;
	hak_oow_t par_tmprcnt;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_CATCH);

	src = cf->operand;
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_CATCH));

	cmd = HAK_CNODE_CONS_CAR(src);
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
/* TODO: change error code */
		hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(src),
			"no exception variable for '%.*js'", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	exarg = HAK_CNODE_CONS_CAR(obj);
	if (!HAK_CNODE_IS_CONS_CONCODED(exarg, HAK_CONCODE_XLIST) || hak_countcnodecons(hak, exarg) != 1)
	{
		hak_setsynerrbfmt(
			hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(exarg),
			"improper exception variable for '%.*js'",
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}

	exarg = HAK_CNODE_CONS_CAR(exarg);
	if (!HAK_CNODE_IS_SYMBOL(exarg))
	{
		hak_setsynerrbfmt(
			hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(exarg),
			"invalid exception variable name '%.*js' for '%.*js'",
			HAK_CNODE_GET_TOKLEN(exarg), HAK_CNODE_GET_TOKPTR(exarg),
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}

	/* add the exception variable to the local variable list. increase the number of local variables */
	exarg_offset = hak->c->tv.s.len;
	if (hak->c->tv.s.len > 0) exarg_offset++; /* if the variable is not the first, add 1 for a preceding space */

	if (hak->c->funblk.depth > 0)
	{
		fbi = &hak->c->funblk.info[hak->c->funblk.depth - 1]; /* parent block */
		par_tmprcnt = fbi->tmprcnt;
	}
	else
	{
		par_tmprcnt = 0;
	}

	/* fill the variable information structure as if it's found by find_variable_backward_with_token().
	 * we know it's the last variable as add_temporary_variable() is called below.
	 * there is no need to call find_variable_backward_with_token() */
	vi.type = VAR_INDEXED;
	vi.ctx_offset = 0;
	vi.index_in_ctx = hak->c->tv.wcount - par_tmprcnt;
	if (add_temporary_variable(hak, exarg, hak->c->tv.s.len, "exception variable", HAK_NULL) <= -1) return -1;

	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	/* a variable string inserts a space(' ') between variable name.
	 * if 'exarg' is the first variable in the variable string, there is no preceding space.
	 * if it is not, there is a preceding space. */
	HAK_ASSERT(hak, (hak->c->tv.s.len == HAK_CNODE_GET_TOKLEN(exarg) && fbi->tmprlen == hak->c->tv.s.len - HAK_CNODE_GET_TOKLEN(exarg)) || /* first */
	                (hak->c->tv.s.len > HAK_CNODE_GET_TOKLEN(exarg) && fbi->tmprlen == hak->c->tv.s.len - HAK_CNODE_GET_TOKLEN(exarg) - 1)); /* not first */
	HAK_ASSERT(hak, fbi->tmprcnt == vi.index_in_ctx + par_tmprcnt);
	fbi->tmprlen = hak->c->tv.s.len;
	fbi->tmprcnt = hak->c->tv.wcount;
	fbi->tmpr_nlvars = fbi->tmpr_nlvars + 1;

	HAK_ASSERT(hak, fbi->tmpr_nargs + fbi->tmpr_nrvars + fbi->tmpr_nlvars == fbi->tmprcnt - par_tmprcnt);

	obj = HAK_CNODE_CONS_CDR(obj);
	if (check_block_expression_as_body(hak, obj, cmd, FOR_NONE) <= -1) return -1;

	/* jump_inst_pos hold the instruction pointer that skips the catch block at the end of the try block */
	if (patch_nearest_post_try(hak, &jump_inst_pos) <= -1) return -1;

	/* produce an instruction to store the exception value to an exception variable pushed by the 'throw' instruction */
	if (emit_variable_access(hak, VAR_ACCESS_POP, &vi, HAK_CNODE_GET_LOC(src)) <= -1) return -1;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, obj);

	PUSH_SUBCFRAME(hak, COP_POST_CATCH, cmd);
	cf = GET_SUBCFRAME(hak);
	cf->u.post_catch.jump_inst_pos = jump_inst_pos;
	cf->u.post_catch.exarg_offset = exarg_offset; /* there is only 1 exception variable. using the offset is easier than to use the variable position */

	return 0;
}

static HAK_INLINE int post_try (hak_t* hak)
{
/* TODO: anything else? */
	pop_ctlblk(hak);
	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int post_catch (hak_t* hak)
{
	hak_ooi_t jip, block_code_size;
	hak_cframe_t* cf;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf != HAK_NULL);
	HAK_ASSERT(hak, cf->opcode == COP_POST_CATCH);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	jip = cf->u.post_catch.jump_inst_pos; /* jump instruction position between the try block and the catch block */

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD instruction */
	block_code_size = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	if (block_code_size == 0)
	{
		/* no body in try */
/* TODO: is this correct??? */
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
		block_code_size++;
	}

	if (block_code_size > MAX_CODE_JUMP * 2)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKFLOOD, HAK_CNODE_GET_LOC(cf->operand), "code too big - size %zu", block_code_size);
		return -1;
	}

	patch_long_jump(hak, jip, block_code_size); /* patch the jump between the try block and the catch block */

	/* make the exception variable unsearchable outside the catch block.
	 * the variable entity is still be accounted into the local variable list. */
	kill_temporary_variable_at_offset(hak, cf->u.post_catch.exarg_offset);

	POP_CFRAME(hak);
	return 0;
}

static int compile_throw (hak_t* hak, hak_cnode_t* src)
{
	hak_cnode_t* obj, * val;
	/*hak_cframe_t* cf;*/

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_THROW));

	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
/* TODO: should i allow (throw)? does it return the last value on the stack? */
		/* no value */
		hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
		hak_setsynerrbfmt(
			hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src),
			"no value or expression after '%.*js'",
			HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	val = HAK_CNODE_CONS_CAR(obj);

	obj = HAK_CNODE_CONS_CDR(obj);
	if (obj)
	{
		hak_cnode_t* tmp = HAK_CNODE_CONS_CAR(src);
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(obj),
			"excessive argument in '%.*js' around '%.*js'",
			HAK_CNODE_GET_TOKLEN(tmp), HAK_CNODE_GET_TOKPTR(tmp),
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
		return -1;
	}

	/* throw can be located anywhere, however,
	 * if there is no outer try-catch, it ends up with a fatal runtime error */
	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, val);

	PUSH_SUBCFRAME(hak, COP_EMIT_THROW, src);
	/*cf = GET_SUBCFRAME(hak);*/
	return 0;
}

/* ========================================================================= */

static int compile_while (hak_t* hak, hak_cnode_t* src, int next_cop)
{
	/* (while (xxxx) ... )
	 * (until (xxxx) ... ) */
	hak_cnode_t* cmd, * obj, * cond, * body;
	hak_oow_t cond_pos;
	hak_cframe_t* cf;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS(src));
	HAK_ASSERT(hak, HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_UNTIL) ||
	                HAK_CNODE_IS_TYPED(HAK_CNODE_CONS_CAR(src), HAK_CNODE_WHILE));
	HAK_ASSERT(hak, next_cop == COP_POST_UNTIL_COND || next_cop == COP_POST_WHILE_COND);

	cmd = HAK_CNODE_CONS_CAR(src); /* while or until itself */
	obj = HAK_CNODE_CONS_CDR(src);

	if (!obj)
	{
		/* no value */
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(src),
			"no loop condition specified in %.*js", HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}
	else if (!HAK_CNODE_IS_CONS(obj))
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(obj),
			"redundant cdr around '%.*js' in '%.%js'",
			HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj),
			HAK_CNODE_GET_TOKLEN(cmd), HAK_CNODE_GET_TOKPTR(cmd));
		return -1;
	}

	if (push_ctlblk(hak, HAK_CNODE_GET_LOC(src), HAK_CTLBLK_TYPE_LOOP) <= -1) return -1;

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	cond_pos = hak->code.bc.len; /* position where the bytecode for the conditional is emitted */

	cond = HAK_CNODE_CONS_CAR(obj);
	body = HAK_CNODE_CONS_CDR(obj);

	if (check_block_expression_as_body(hak, body, cmd, FOR_NONE) <= -1) return -1;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, cond); /* 1 */

	/* pass the cons cell branching to the conditional and the body. see post_while_cond() for the reason */
	PUSH_SUBCFRAME(hak, next_cop, obj); /* 2 */
	cf = GET_SUBCFRAME(hak);
	cf->u.post_while.cond_pos = cond_pos;
	cf->u.post_while.body_pos = -1; /* unknown yet*/
	cf->u.post_while.jump_inst_pos = -1; /* not needed */
	cf->u.post_while.start_loc = *HAK_CNODE_GET_LOC(src);

	return 0;
}

/* ========================================================================= */

static int compile_cons_array_expression (hak_t* hak, hak_cnode_t* obj)
{
	/* #[ ] */
	hak_ooi_t nargs;
	hak_cframe_t* cf;

	nargs = hak_countcnodecons(hak, obj);
	if (nargs > MAX_CODE_PARAM)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGFLOOD, HAK_CNODE_GET_LOC(obj), "too many(%zd) elements in array", nargs);
		return -1;
	}

	SWITCH_TOP_CFRAME(hak, COP_EMIT_MAKE_ARRAY, obj);
	cf = GET_TOP_CFRAME(hak);
	cf->u.array_list.index = nargs;

	/* redundant cdr check is performed inside compile_object_list() */
	PUSH_SUBCFRAME(hak, COP_COMPILE_ARRAY_LIST, obj);
	cf = GET_SUBCFRAME(hak);
	cf->u.array_list.index = 0;

	return 0;
}

static int compile_cons_bytearray_expression (hak_t* hak, hak_cnode_t* obj, int concode)
{
	/* compile the singular-type array such as byte array or char array */
	/* #b[ ] - e.g. #b[1, 2, 3] or #b[ 1 2 3 ] */
	hak_ooi_t nargs;
	hak_cframe_t* cf;

	nargs = hak_countcnodecons(hak, obj);
	if (nargs > MAX_CODE_PARAM)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGFLOOD, HAK_CNODE_GET_LOC(obj), "too many(%zd) elements in byte-array", nargs);
		return -1;
	}

	SWITCH_TOP_CFRAME(hak, COP_EMIT_MAKE_PURE_ARRAY, obj);
	cf = GET_TOP_CFRAME(hak);
	cf->u.pure_array_list.elem_type = concode;
	cf->u.pure_array_list.index = nargs;

	/* redundant cdr check is performed inside compile_object_list() */
	PUSH_SUBCFRAME(hak, COP_COMPILE_PURE_ARRAY_LIST, obj);
	cf = GET_SUBCFRAME(hak);
	cf->u.pure_array_list.elem_type = concode;
	cf->u.pure_array_list.index = 0;

	return 0;
}

static int compile_cons_dic_expression (hak_t* hak, hak_cnode_t* obj)
{
	/* { } - e.g. {1:2, 3:4,"abc":def, "hwaddr":"00:00:00:01"} or { 1 2 3 4 } */
	hak_ooi_t nargs;
	hak_cframe_t* cf;

	nargs = hak_countcnodecons(hak, obj);
	if (nargs > MAX_CODE_PARAM)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_ARGFLOOD, HAK_CNODE_GET_LOC(obj), "too many(%zd) elements in dictionary", nargs);
		return -1;
	}

	SWITCH_TOP_CFRAME(hak, COP_EMIT_MAKE_DIC, obj);
	cf = GET_TOP_CFRAME(hak);
	cf->u.dic_list.index = nargs >> 1; /* only the half */

	/* redundant cdr check is performed inside compile_object_list() */
	PUSH_SUBCFRAME(hak, COP_COMPILE_DIC_LIST, obj);

	return 0;
}

static int compile_cons_qlist_expression (hak_t* hak, hak_cnode_t* obj)
{
	/* #( 1 2  3 )
	 * #(1 (+ 2 3) 5) --> #(1 5 5)
	 * */
	SWITCH_TOP_CFRAME(hak, COP_EMIT_MAKE_CONS, obj);
	PUSH_SUBCFRAME(hak, COP_COMPILE_QLIST, obj);
	return 0;
}

static int compile_cons_alist_expression (hak_t* hak, hak_cnode_t* cmd)
{
	/* assignment expression */
	/* (a := 20)
	 * ([a,b] := (xxx 20))
	 */

	hak_cframe_t* cf;
	hak_cnode_t* obj, * var, * val;
	hak_var_info_t vi;
	int x;

	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(cmd, HAK_CONCODE_ALIST));

	var = HAK_CNODE_CONS_CAR(cmd);
	obj = HAK_CNODE_CONS_CDR(cmd);

	HAK_ASSERT(hak, HAK_CNODE_IS_SYMBOL(var) || HAK_CNODE_IS_DSYMBOL_CLA(var) || HAK_CNODE_IS_CONS_CONCODED(var, HAK_CONCODE_TUPLE));
	HAK_ASSERT(hak, obj && HAK_CNODE_IS_CONS(obj)); /* reader guaranteed */

	val = HAK_CNODE_CONS_CAR(obj);
	HAK_ASSERT(hak, HAK_CNODE_CONS_CDR(obj) ==  HAK_NULL); /* reader guaranteed */

	if (HAK_CNODE_IS_CONS_CONCODED(var, HAK_CONCODE_TUPLE))
	{
		/* multi-variable assignment
		 * fun xxx(x :: p q) { p := x + 1; q := x + 2 }
		 * ([a,b] := (xxx 20)) */
		hak_oow_t nvars, i;

		nvars = hak_countcnodecons(hak, var);

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_R, val); /* special for set_r */
		cf = GET_TOP_CFRAME(hak);
		cf->u.obj_r.nrets = nvars; /* number of return variables to get assigned */

		for (i = 0, obj = var; i < nvars; i++, obj = HAK_CNODE_CONS_CDR(obj))
		{
			int x;

			var = HAK_CNODE_CONS_CAR(obj);

			HAK_ASSERT(hak, HAK_CNODE_IS_SYMBOL(var) || HAK_CNODE_IS_DSYMBOL_CLA(var)); /* reader guaranteed */

			x = find_variable_backward_with_token(hak, var, &vi);
			if (x <= -1) return -1;

			if (x == 0)
			{
				if (HAK_CNODE_IS_DSYMBOL_CLA(var))
				{
					hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEUNKNOWN, HAK_CNODE_GET_LOC(var),
						"unknown class-level variable name '%.*js'", HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
					return -1;
				}

				PUSH_SUBCFRAME(hak, COP_EMIT_SET, var); /* set_r doesn't evaluate the variable name */
				cf = GET_SUBCFRAME(hak);
				cf->u.set.vi.type = VAR_NAMED;
			}
			else
			{
				PUSH_SUBCFRAME(hak, COP_EMIT_SET, cmd);
				cf = GET_SUBCFRAME(hak);
				cf->u.set.vi = vi;
			}

			/*
			 * (fun f(x y ::: aa bb cc) ....)
			 * ([a b c] := (f 1 2))
			 *
			 *    the call to f
			 *       call 2 3  ; 2 arguments, 3 return variables (CALL_R)
			 *                 ; 3 to be emitted from cf->u.obj_r.nrets
			 *                 ; this gets remembered in req_nrvars of the created context.
			 *
			 *    the return from f must push 3 values.
			 *        push_return_r  ; as remembered in the ctx->req_nrvars
			 *
			 *    emit store_into_xxx instruction for the first return variable assignment.
			 *    emit pop_into_xxx instructions for the rest.
			 *        pop_into c
			 *        pop_into b
			 *        store_into a
			 */
			cf->u.set.mode = (i <= 0)? VAR_ACCESS_STORE: VAR_ACCESS_POP; /* STORE_INTO or POP_INTO  */
		}
	}
	else
	{
		/* single-variable assignment
		 * (a := 20) */

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, val);

		x = find_variable_backward_with_token(hak, var, &vi);
		if (x <= -1) return -1;

		if (x == 0)
		{
			/* not found */
			if (HAK_CNODE_IS_DSYMBOL_CLA(var))
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEUNKNOWN, HAK_CNODE_GET_LOC(var),
					"unknown class-level variable name '%.*js'", HAK_CNODE_GET_TOKLEN(var), HAK_CNODE_GET_TOKPTR(var));
				return -1;
			}

			PUSH_SUBCFRAME(hak, COP_EMIT_SET, var); /* set doesn't evaluate the variable name */
			cf = GET_SUBCFRAME(hak);
			cf->u.set.vi.type = VAR_NAMED;
		}
		else
		{
			/* the check in compile_fun() must ensure this condition */
			PUSH_SUBCFRAME(hak, COP_EMIT_SET, cmd);
			cf = GET_SUBCFRAME(hak);
			cf->u.set.vi = vi;
		}
		cf->u.set.mode = VAR_ACCESS_STORE;
	}

	return 0;
}

static int compile_cons_xlist_expression (hak_t* hak, hak_cnode_t* obj, int nrets)
{
	hak_cnode_t* car;

	/* a valid function call
	 * (function-name argument-list)
	 *   function-name can be:
	 *     a symbol.
	 *     another function call.
	 * if the name is another function call, i can't know if the
	 * function name will be valid at the compile time.
	 */
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(obj, HAK_CONCODE_XLIST));

	car = HAK_CNODE_CONS_CAR(obj);
	switch (HAK_CNODE_GET_TYPE(car))
	{
		case HAK_CNODE_CLASS:
			if (compile_class(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_FUN:
			if (compile_fun(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_VAR:
			if (compile_var(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_DO:
			if (compile_do(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_IF:
			if (compile_if(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_ELIF: /* elif without if */
			hak_setsynerrbfmt(hak, HAK_SYNERR_ELIF, HAK_CNODE_GET_LOC(car),
				"%.*js without if", HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
			return -1;

		case HAK_CNODE_ELSE: /* else without if */
			hak_setsynerrbfmt(hak, HAK_SYNERR_ELSE, HAK_CNODE_GET_LOC(car),
				"%.*js without if", HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
			return -1;

		case HAK_CNODE_THROW:
			if (compile_throw(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_TRY:
			if (compile_try(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_CATCH: /* catch without try */
			hak_setsynerrbfmt(hak, HAK_SYNERR_CATCH, HAK_CNODE_GET_LOC(car),
				"%.*js without try", HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
			return -1;

		case HAK_CNODE_BREAK:
			if (compile_break(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_CONTINUE:
			if (compile_continue(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_UNTIL:
			if (compile_while(hak, obj, COP_POST_UNTIL_COND) <= -1) return -1;
			goto done;

		case HAK_CNODE_WHILE:
			if (compile_while(hak, obj, COP_POST_WHILE_COND) <= -1) return -1;
			goto done;

		case HAK_CNODE_RETURN:
			/* (return 10)
			 * (return (+ 10 20)) */
			if (compile_return(hak, obj, 0) <= -1) return -1;
			goto done;

		case HAK_CNODE_REVERT:
			if (compile_return(hak, obj, 1) <= -1) return -1;
			goto done;

		case HAK_CNODE_AND:
			if (compile_and(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_OR:
			if (compile_or(hak, obj) <= -1) return -1;
			goto done;

#if defined(USE_KW_PLUS)
		case HAK_CNODE_PLUS:
			if (compile_plus(hak, obj) <= -1) return -1;
			goto done;
#endif

		case HAK_CNODE_SET:
			if (compile_set(hak, obj) <= -1) return -1;
			goto done;

		case HAK_CNODE_SET_R:
			if (compile_set_r(hak, obj) <= -1) return -1;
			goto done;
	}

	if (HAK_CNODE_IS_SYMBOL(car)  || HAK_CNODE_IS_DSYMBOL(car) || HAK_CNODE_IS_BINOP(car) ||
	    HAK_CNODE_IS_STRLIT(car) || /* this condition to represent an external program TODO: change this to a dedicated cnode */
	    HAK_CNODE_IS_SYMLIT(car) || /* this condition to represent an external program TODO: change this to a dedicated cnode */
	    HAK_CNODE_IS_CONS_CONCODED(car, HAK_CONCODE_XLIST) ||
	    HAK_CNODE_IS_CONS_CONCODED(car, HAK_CONCODE_MLIST) ||
	    HAK_CNODE_IS_CONS_CONCODED(car, HAK_CONCODE_BLIST) ||
	    HAK_CNODE_IS_CONS_CONCODED(car, HAK_CONCODE_ALIST))
	{
		/* normal function call
		 *  (<operator> <operand1> ...) */
		hak_ooi_t nargs;
		hak_ooi_t oldtop;
		hak_cframe_t* cf;
		hak_cnode_t* cdr;

		/* NOTE: cframe management functions don't use the object memory.
		 *       many operations can be performed without taking GC into account */

		/* store the position of COP_EMIT_CALL to be produced with
		 * SWITCH_TOP_CFRAME() in oldtop for argument count patching
		 * further down */
		oldtop = GET_TOP_CFRAME_INDEX(hak);
		HAK_ASSERT(hak, oldtop >= 0);

		SWITCH_TOP_CFRAME(hak, COP_EMIT_CALL, car); /* <4> */

		/* compile <operator> */
		PUSH_CFRAME(hak, COP_COMPILE_OBJECT, car); /* <2> */

		/* compile <operand1> ... etc */
		cdr = HAK_CNODE_CONS_CDR(obj);

		if (!cdr)
		{
			nargs = 0;
		}
		else
		{
			if (!HAK_CNODE_IS_CONS(cdr))
			{
				/* (funname . 10) */
				hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(cdr),
					"redundant cdr in function call around '%.*js'",
					HAK_CNODE_GET_TOKLEN(cdr), HAK_CNODE_GET_TOKPTR(cdr));
				return -1;
			}

			nargs = hak_countcnodecons(hak, cdr);
			if (nargs > MAX_CODE_PARAM)
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_ARGFLOOD, HAK_CNODE_GET_LOC(cdr),
					"too many(%zd) parameters in function call", nargs);
				return -1;
			}
		}

		if (HAK_CNODE_IS_SYMBOL(car) || HAK_CNODE_IS_DSYMBOL(car) || HAK_CNODE_IS_BINOP(car))
		{
			hak_oop_cons_t sdc;

			/* only symbols are added to the system dictionary.
			 * perform this lookup only if car is a symbol */
			sdc = hak_lookupsysdicforsymbol_noseterr(hak, HAK_CNODE_GET_TOK(car));
			if (sdc)
			{
				hak_oop_word_t sdv;
				sdv = (hak_oop_word_t)HAK_CONS_CDR(sdc);
				if (HAK_IS_PRIM(hak, sdv))
				{
					if (nargs < sdv->slot[1] || nargs > sdv->slot[2])
					{
						hak_setsynerrbfmt(hak, HAK_SYNERR_ARGCOUNT, HAK_CNODE_GET_LOC(car),
							"parameters count(%zd) mismatch in function call - %.*js - expecting %zu-%zu parameters",
							nargs, HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car), sdv->slot[1], sdv->slot[2]);
						return -1;
					}
				}
			}
		}

		/* redundant cdr check is performed inside compile_object_list() */
		PUSH_SUBCFRAME(hak, COP_COMPILE_ARGUMENT_LIST, cdr); /* <3> */

		/* patch the argument count in the operand field of the COP_EMIT_CALL frame */
		cf = GET_CFRAME(hak, oldtop);
		HAK_ASSERT(hak, cf->opcode == COP_EMIT_CALL);
		cf->u.call.index = nargs;
		cf->u.call.nrets = nrets;

		/* arrange to push a dummy receiver to make the call look like a message send.
		 * if you change the dummy receiver instruction to something else, you must change
		 * the receiver value of the initial context in start_initial_process_and_context(), too */
		PUSH_CFRAME(hak, COP_EMIT_PUSH_NIL, car); /* <1> this will be executed the COP_COMPILE_OBJECT car frame */
	}
	else
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_CALLABLE, HAK_CNODE_GET_LOC(car),
			"invalid callable '%.*js' in function call",
			HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
		return -1;
	}

done:
	return 0;
}

static int compile_cons_mlist_expression (hak_t* hak, hak_cnode_t* obj, hak_ooi_t nrets)
{
	hak_cnode_t* car, * cdr, * rcv;
	hak_ooi_t nargs;
	hak_ooi_t oldtop;
	hak_cframe_t* cf;

	/* message sending
	 *  (:<receiver> <operator> <operand1> ...)
	 *  (<receiver>:<operator> <operand1> ...)
	 *  (<receiver> <binop> <operand>
	 */
	HAK_ASSERT(hak, HAK_CNODE_IS_CONS_CONCODED(obj, HAK_CONCODE_BLIST) ||
	                HAK_CNODE_IS_CONS_CONCODED(obj, HAK_CONCODE_MLIST));

	car = HAK_CNODE_CONS_CAR(obj);

	/* store the position of COP_EMIT_CALL to be produced with
	 * SWITCH_TOP_CFRAME() in oldtop for argument count patching
	 * further down */
	oldtop = GET_TOP_CFRAME_INDEX(hak);
	HAK_ASSERT(hak, oldtop >= 0);

	/* compile <receiver> */
	rcv = car; /* remember the receiver node to to push it later */
	SWITCH_TOP_CFRAME(hak, COP_EMIT_SEND, rcv);

	/* compile <operator> */
	cdr = HAK_CNODE_CONS_CDR(obj);
	if (!cdr)
	{
		/* thie part may never be reached as the reader ensures this doesn't happen */
		hak_setsynerrbfmt(hak, HAK_SYNERR_CALLABLE, HAK_CNODE_GET_LOC(car),
			"missing message for '%.*js'", HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
		return -1;
	}
	if (!HAK_CNODE_IS_CONS(cdr))
	{
		/* (<receiver> . 10) */
		hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(cdr),
			"redundant cdr in message send around '%.*js'", HAK_CNODE_GET_TOKLEN(cdr), HAK_CNODE_GET_TOKPTR(cdr));
		return -1;
	}
	car = HAK_CNODE_CONS_CAR(cdr);
	if (HAK_CNODE_IS_SYMBOL(car) || HAK_CNODE_IS_BINOP(car))
	{
		/* do not resolve the message itself to an associated value.
		 * treat it as if it's a literal.
		 * the actual message sending can be written with a symbol
		 * as well as a symbol literal in that sense.
		 *
		 * For this class definition
		 *   class X { fun[#ci] new() {} }
		 * X:new is equivalent to X:#new
		 *
		 * If the method is a binop selector,
		 *  class X { fun[#ci] +(a) {} } ## one argument is guaranteed by the reader
		 * the followings are equivalent.
		 *   X + 20
		 *   X:#+ 20
		 */
		PUSH_CFRAME(hak, COP_EMIT_PUSH_SYMBOL, car);
	}
	else if (HAK_CNODE_IS_SYMLIT(car) || HAK_CNODE_IS_STRLIT(car))
	{
		PUSH_CFRAME(hak, COP_COMPILE_OBJECT, car);
	}
	else
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_MESSAGE, HAK_CNODE_GET_LOC(car),
			"invalid message '%.*js'", HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
		return -1;
	}

	/* compile <operand1> ... etc */
	cdr = HAK_CNODE_CONS_CDR(cdr);
	if (!cdr)
	{
		nargs = 0;
	}
	else
	{
		if (!HAK_CNODE_IS_CONS(cdr))
		{
			/* (funname . 10) */
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(cdr),
				"redundant cdr in function call around '%.*js'",
				HAK_CNODE_GET_TOKLEN(cdr), HAK_CNODE_GET_TOKPTR(cdr));
			return -1;
		}

		nargs = hak_countcnodecons(hak, cdr);
		if (nargs > MAX_CODE_PARAM)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_ARGFLOOD, HAK_CNODE_GET_LOC(cdr),
				"too many(%zd) parameters in function call", nargs);
			return -1;
		}
	}

	/* redundant cdr check is performed inside compile_object_list() */
	PUSH_SUBCFRAME(hak, COP_COMPILE_ARGUMENT_LIST, cdr);

	/* patch the argument count in the operand field of the COP_EMIT_CALL frame */
	cf = GET_CFRAME(hak, oldtop);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_SEND);
	cf->u.sendmsg.nargs = nargs;
	cf->u.sendmsg.nrets = nrets;
	cf->u.sendmsg.to_super = (HAK_CNODE_GET_TYPE(rcv) == HAK_CNODE_SUPER);

	PUSH_CFRAME(hak, COP_COMPILE_OBJECT, rcv);
	return 0;
}

static int compile_cons_block_expression (hak_t* hak, hak_cnode_t* obj)
{
	return compile_expression_block(hak, obj, "block", CEB_IS_BLOCK);
}

static HAK_INLINE int compile_symbol (hak_t* hak, hak_cnode_t* obj)
{
	hak_var_info_t vi;
	int x;

	HAK_ASSERT(hak, HAK_CNODE_IS_SYMBOL(obj) || HAK_CNODE_IS_BINOP(obj));

	/* check if a symbol is a local variable */
	x = find_variable_backward_with_token(hak, obj, &vi);
	if (x <= -1) return -1;

	if (x == 0)
	{
		hak_oop_t sym, cons;
		hak_oow_t index;
/* TODO: if i require all variables to be declared, this part is not needed and should handle it as an error */
/* TODO: change the scheme... allow declaration??? */
		/* global variable */
		sym = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(obj), HAK_CNODE_GET_TOKLEN(obj));
		if (HAK_UNLIKELY(!sym)) return -1;

		cons = (hak_oop_t)hak_getatsysdic(hak, sym);
		if (!cons)
		{
			cons = (hak_oop_t)hak_putatsysdic(hak, sym, hak->_undef);
			if (HAK_UNLIKELY(!cons)) return -1;
		}

		/* add the entire cons pair to the literal frame */
		if (add_literal(hak, cons, &index) <= -1 ||
		    emit_one_param_instruction(hak, HAK_CODE_PUSH_OBJECT_0, index, HAK_CNODE_GET_LOC(obj)) <= -1) return -1;

		return 0;
	}
	else
	{
		HAK_ASSERT(hak, vi.type != VAR_NAMED);
		return emit_variable_access(hak, VAR_ACCESS_PUSH, &vi, HAK_CNODE_GET_LOC(obj));
	}
}

static HAK_INLINE int compile_dsymbol (hak_t* hak, hak_cnode_t* obj)
{
	hak_oop_t cons;
	hak_oow_t index;

/* TODO: need a total revamp on the dotted symbols.
 *       must differentiate module access and dictioary member access...
 *       must implementate dictionary member access syntax... */

	/* the dot notation collides with car/cdr separator? no. dotted symbols don't contains space.
	 * the car cdr separator must be a single character */
	{ /* HACK FOR NOW */
		const hak_ooch_t* sep;
		hak_oocs_t name;
		int x = 0;
		hak_var_info_t vi;
		hak_funblk_info_t* fbi;

		name = *HAK_CNODE_GET_TOK(obj);
		fbi = &hak->c->funblk.info[hak->c->funblk.depth];

		sep = hak_find_oochar(name.ptr, name.len, '.');
		HAK_ASSERT(hak, sep != HAK_NULL);
		if (hak_comp_oochars_bcstr(name.ptr, (sep - (const hak_ooch_t*)name.ptr), "self") == 0)
		{
			/* instance variable?  or instance method? */
			if (fbi->fun_type >> 8)
			{
				/* if defined using A::xxx syntax, it's not possible to know the instance position of an instance variable.
				 * class X [ a b ] {
				 *   fun a() {
				 *      fun J:t() {
				 *        ## J has nothing to to with X in priciple even if J may point to X when a() is executed.
				 *        ## it's not meaningful to look up the variable `a` in the context of class X.
				 *        ## it must be prohibited to access instance variables using the self or super prefix
				 *        ## in this context
				 *        return self.a
				 *      }
				 *   }
				 * }
				 */
				hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(obj),
					"not allowed to prefix with self in out-of-class method context around '%.*js'",
					HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
				return -1;
			}
			name.ptr = (hak_ooch_t*)(sep + 1);
			name.len -= 5;
			x = find_variable_backward_with_word(hak, &name, HAK_CNODE_GET_LOC(obj), 1, &vi);
		}
		else if (hak_comp_oochars_bcstr(name.ptr, sep - (const hak_ooch_t*)name.ptr, "super") == 0)
		{
			if (fbi->fun_type >> 8) /* if defined using A:xxx syntax */
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(obj),
					"not allowed to prefix with super in out-of-class method context around '%.*js'",
					HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
				return -1;
			}
			name.ptr = (hak_ooch_t*)(sep + 1);
			name.len -= 6;
			x = find_variable_backward_with_word(hak, &name, HAK_CNODE_GET_LOC(obj), 2, &vi); /* TODO: arrange to skip the current class */
		}

		if (x <= -1) return -1; /* error */
		if (x >= 1)
		{
			/* found */
			HAK_ASSERT(hak, vi.type != VAR_NAMED);
			return emit_variable_access(hak, VAR_ACCESS_PUSH, &vi, HAK_CNODE_GET_LOC(obj));
		}

/* TODO: check if it's the method name??? NOT POSSIBLE??? */

		/* if not found or not beginning with self/super, carry on with remaining resolution methods */
	}

	cons = (hak_oop_t)hak_lookupsysdicforsymbol_noseterr(hak, HAK_CNODE_GET_TOK(obj));
	if (!cons)
	{
		/* query the module for information if it is the first time
		 * when the dotted symbol is seen */

		hak_pfbase_t* pfbase;
		hak_mod_t* mod;
		hak_oop_t sym, val;
		unsigned int kernel_bits;

		pfbase = hak_querymod(hak, HAK_CNODE_GET_TOKPTR(obj), HAK_CNODE_GET_TOKLEN(obj), &mod);
		if (!pfbase)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAME, HAK_CNODE_GET_LOC(obj),
				"unknown dotted symbol '%.*js'", HAK_CNODE_GET_TOKLEN(obj), HAK_CNODE_GET_TOKPTR(obj));
			return -1;
		}

		sym = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(obj), HAK_CNODE_GET_TOKLEN(obj));
		if (HAK_UNLIKELY(!sym)) return -1;

		hak_pushvolat(hak, &sym);
		switch (pfbase->type)
		{
			case HAK_PFBASE_FUNC:
				kernel_bits = 2;
				val = hak_makeprim(hak, pfbase->handler, pfbase->minargs, pfbase->maxargs, mod);
				break;

			case HAK_PFBASE_VAR:
				kernel_bits = 1;
				val = hak->_nil;
				break;

			case HAK_PFBASE_CONST:
				/* TODO: create a value from the pfbase information. it needs to get extended first
				 * can i make use of pfbase->handler type-cast to a differnt type? */
				kernel_bits = 2;
				val = hak->_nil;
				break;

			default:
				hak_popvolat(hak);
				hak_seterrbfmt(hak, HAK_EINVAL, "invalid pfbase type - %d\n", pfbase->type);
				return -1;
		}

		if (!val || !(cons = (hak_oop_t)hak_putatsysdic(hak, sym, val)))
		{
			hak_popvolat(hak);
			return -1;
		}
		hak_popvolat(hak);

		/* make this dotted symbol special that it can't get changed
		 * to a different value */
		HAK_OBJ_SET_FLAGS_KERNEL (sym, kernel_bits);
	}

	if (add_literal(hak, cons, &index) <= -1 ||
	    emit_one_param_instruction(hak, HAK_CODE_PUSH_OBJECT_0, index, HAK_CNODE_GET_LOC(obj)) <= -1) return -1;

	return 0;
}

HAK_UNUSED static int string_to_ooi (hak_t* hak, hak_oocs_t* str, int radixed, hak_ooi_t* num)
{
	/* [NOTE]
	 * it is not a generic conversion functionu
	 * it assumes a certain pre-sanity check on the string
	 * done by the lexical analyzer */

	int v, negsign, base = 10;
	const hak_ooch_t* ptr, * end;
	hak_oow_t value, old_value;

	negsign = 0;
	ptr = str->ptr,
	end = str->ptr + str->len;

	HAK_ASSERT(hak, ptr < end);

	if (*ptr == '+' || *ptr == '-')
	{
		negsign = *ptr - '+';
		ptr++;
	}

	if (radixed)
	{
		/* 0xFF80, 0b1111 */
		HAK_ASSERT(hak, ptr < end);

		if (*ptr == '0')
		{
			ptr++;
			HAK_ASSERT(hak, ptr < end);

			if (*ptr == 'x') base = 16;
			else if (*ptr == 'o') base = 8;
			else if (*ptr == 'b') base = 2;
			else goto radix_r;

			ptr++;
		}

		if (base == 10)
		{
		radix_r:
			base = 0;
			do
			{
				base = base * 10 + HAK_CHAR_TO_NUM(*ptr, 10);
				ptr++;
			}
			while (*ptr != 'r');
			ptr++;
		}

		if (base < 2 || base > 36)
		{
			hak_seterrbfmt(hak, HAK_EINVAL,
				"unsupported radix '%d' in radixed number '%.*js'", base, str->len, str->ptr);
			return -1;
		}
	}

	HAK_ASSERT(hak, ptr < end);

	value = old_value = 0;
	while (ptr < end && (v = HAK_CHAR_TO_NUM(*ptr, base)) < base)
	{
		value = value * base + v;
		if (value < old_value)
		{
			/* overflow must have occurred */
			hak_seterrbfmt(hak, HAK_ERANGE, "number too big - %.*js", str->len, str->ptr);
			return -1;
		}
		old_value = value;
		ptr++;
	}

	if (ptr < end)
	{
		/* trailing garbage? */
		hak_seterrbfmt(hak, HAK_EINVAL, "trailing garbage after numeric literal - %.*js", str->len, str->ptr);
		return -1;
	}

	if (value > HAK_TYPE_MAX(hak_ooi_t) + (negsign? 1: 0)) /* assume 2's complement */
	{
		hak_seterrbfmt(hak, HAK_ERANGE, "number too big - %.*js", str->len, str->ptr);
		return -1;
	}

	*num = value;
	if (negsign) *num *= -1;

	return 0;
}

static hak_oop_t string_to_num (hak_t* hak, hak_oocs_t* str, const hak_loc_t* loc, int radixed)
{
	int negsign, base = 10;
	const hak_ooch_t* ptr, * end;

	negsign = 0;
	ptr = str->ptr,
	end = str->ptr + str->len;

	/* [NOTE]
	 * - this is not a generic conversion functionu
	 * - it assumes a certain pre-sanity check on the string
	 *   done by the lexical analyzer.
	 * - it also assumes that the reader ensures that
	 *   there is at least 1 valid digit after radix specifier. */

	HAK_ASSERT(hak, ptr < end);

	if (*ptr == '+' || *ptr == '-')
	{
		negsign = *ptr - '+';
		ptr++;
	}

	if (radixed)
	{
		/* 0xFF80, 0b1111 */
		HAK_ASSERT(hak, ptr < end);

		if (*ptr == '0')
		{
			ptr++;
			HAK_ASSERT(hak, ptr < end);

			if (*ptr == 'x') base = 16;
			else if (*ptr == 'o') base = 8;
			else if (*ptr == 'b') base = 2;
			else goto radix_r;

			ptr++;
		}


		if (base == 10)
		{
		radix_r:
			base = 0;
			do
			{
				base = base * 10 + HAK_CHAR_TO_NUM(*ptr, 10);
				ptr++;
			}
			while (*ptr != 'r');
			ptr++;
		}

		HAK_ASSERT(hak, base >= 2 && base <= 36); /* the lexer must guarantee this */
		/*
		if (base < 2 || base > 36)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_RADIX, loc,
				"unsupported radix '%d' in radixed number '%.*js'", base, str->len, str->ptr);
			return HAK_NULL;
		}*/
	}

/* TODO: handle floating point numbers ... etc */
	if (negsign) base = -base;
	return hak_strtoint(hak, ptr, end - ptr, base);
}

static hak_oop_t string_to_fpdec (hak_t* hak, hak_oocs_t* str, const hak_loc_t* loc)
{
	hak_oow_t pos;
	hak_oow_t scale = 0;
	hak_oop_t v;

	pos = str->len;
	while (pos > 0)
	{
		pos--;
		if (str->ptr[pos] == '.')
		{
			scale = str->len - pos - 1;
			if (scale > HAK_SMOOI_MAX)
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_NUMRANGE, loc, "too many digits after decimal point around '%.*js'", str->len, str->ptr);
				return HAK_NULL;
			}

			HAK_ASSERT(hak, scale > 0);
			/*if (scale > 0)*/ HAK_MEMMOVE(&str->ptr[pos], &str->ptr[pos + 1], scale * HAK_SIZEOF(str->ptr[0])); /* remove the decimal point */
			break;
		}
	}

	/* if no decimal point is included or no digit after the point , you must not call this function */
	HAK_ASSERT(hak, scale > 0);

	v = hak_strtoint(hak, str->ptr, str->len - 1, 10);
	if (HAK_UNLIKELY(!v)) return HAK_NULL;

	return hak_makefpdec(hak, v, scale);
}

static int compile_symbol_literal (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;
	hak_oop_t lit;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_SYMBOL_LITERAL);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	oprnd = cf->operand;
	HAK_ASSERT(hak, HAK_CNODE_GET_TYPE(oprnd) == HAK_CNODE_SYMBOL);

	lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(oprnd), HAK_CNODE_GET_TOKLEN(oprnd));
	if (HAK_UNLIKELY(!lit)) return -1;

	if (emit_push_literal(hak, lit, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
	POP_CFRAME(hak);
	return 0;
}

static int compile_object (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;
	hak_oop_t lit;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_OBJECT);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	oprnd = cf->operand;
redo:
	switch (HAK_CNODE_GET_TYPE(oprnd))
	{
		case HAK_CNODE_NIL:
			if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
			goto done;

		case HAK_CNODE_TRUE:
			if (emit_byte_instruction(hak, HAK_CODE_PUSH_TRUE, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
			goto done;

		case HAK_CNODE_FALSE:
			if (emit_byte_instruction(hak, HAK_CODE_PUSH_FALSE, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
			goto done;

		case HAK_CNODE_SELF:
		case HAK_CNODE_SUPER:
			/* if super is not sent a message, super represents the receiver just like self does */
/* TODO: SELF and SUPER must be limited to methods or is it ok if it just pushes the fake receiver in a normal function call?? */
			if (emit_byte_instruction(hak, HAK_CODE_PUSH_RECEIVER, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
			goto done;

		/* TODO: this-context */

		case HAK_CNODE_CHARLIT:
			lit = HAK_CHAR_TO_OOP(oprnd->u.charlit.v);
			goto literal;

		case HAK_CNODE_BCHRLIT: /* byte character still converts to charcter */
			lit = HAK_CHAR_TO_OOP((hak_ooch_t)oprnd->u.bchrlit.v);
			goto literal;

		case HAK_CNODE_STRLIT:
			lit = hak_makestring(hak, HAK_CNODE_GET_TOKPTR(oprnd), HAK_CNODE_GET_TOKLEN(oprnd));
			if (HAK_UNLIKELY(!lit)) return -1;
			goto literal;

		case HAK_CNODE_BSTRLIT:
			lit = hak_makebytestring(hak, HAK_CNODE_GET_TOKPTR(oprnd), HAK_CNODE_GET_TOKLEN(oprnd));
			if (HAK_UNLIKELY(!lit)) return -1;
			goto literal;

		case HAK_CNODE_SYMLIT:
			lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(oprnd), HAK_CNODE_GET_TOKLEN(oprnd));
			if (HAK_UNLIKELY(!lit)) return -1;
			goto literal;

		case HAK_CNODE_NUMLIT:
			lit = string_to_num(hak, HAK_CNODE_GET_TOK(oprnd), HAK_CNODE_GET_LOC(oprnd), 0);
			if (HAK_UNLIKELY(!lit)) return -1;
			goto literal;

		case HAK_CNODE_RADNUMLIT:
			lit = string_to_num(hak, HAK_CNODE_GET_TOK(oprnd), HAK_CNODE_GET_LOC(oprnd), 1);
			if (HAK_UNLIKELY(!lit)) return -1;
			goto literal;

		case HAK_CNODE_FPDECLIT:
			lit = string_to_fpdec(hak, HAK_CNODE_GET_TOK(oprnd), HAK_CNODE_GET_LOC(oprnd));
			if (HAK_UNLIKELY(!lit)) return -1;
			goto literal;

		case HAK_CNODE_SMPTRLIT:
			lit = HAK_SMPTR_TO_OOP(oprnd->u.smptrlit.v);
			goto literal;

		case HAK_CNODE_ERRLIT:
			lit = HAK_ERROR_TO_OOP(oprnd->u.errlit.v);
			goto literal;

		case HAK_CNODE_BINOP: /* binop symbol. treat it like a normal symbol in the object context */
		case HAK_CNODE_SYMBOL: /* symbol. but not a literal. usually a variable */
			if (compile_symbol(hak, oprnd) <= -1) return -1;
			goto done;

		case  HAK_CNODE_DSYMBOL:
			if (compile_dsymbol(hak, oprnd) <= -1) return -1;
			goto done;

		case HAK_CNODE_CONS:
		{
			switch (HAK_CNODE_CONS_CONCODE(oprnd))
			{
				case HAK_CONCODE_ALIST:
					if (compile_cons_alist_expression(hak, oprnd) <= -1) return -1;
					break;

				case HAK_CONCODE_XLIST:
					if (compile_cons_xlist_expression(hak, oprnd, 0) <= -1) return -1;
					break;

				case HAK_CONCODE_BLIST: /* message send with binop */
				case HAK_CONCODE_MLIST: /* message send expression */
					if (compile_cons_mlist_expression(hak, oprnd, 0) <= -1) return -1;
					break;

				case HAK_CONCODE_BLOCK:
					if (compile_cons_block_expression(hak, oprnd) <= -1) return -1;
					break;

				case HAK_CONCODE_ARRAY:
					if (compile_cons_array_expression(hak, oprnd) <= -1) return -1;
					break;

				case HAK_CONCODE_BYTEARRAY:
				case HAK_CONCODE_CHARARRAY:
					if (compile_cons_bytearray_expression(hak, oprnd, HAK_CNODE_CONS_CONCODE(oprnd)) <= -1) return -1;
					break;

				case HAK_CONCODE_DIC:
					if (compile_cons_dic_expression(hak, oprnd) <= -1) return -1;
					break;

				case HAK_CONCODE_QLIST:
					if (compile_cons_qlist_expression(hak, oprnd) <= -1) return -1;
					break;

				case HAK_CONCODE_VLIST:
					hak_setsynerrbfmt(hak, HAK_SYNERR_VARDCLBANNED, HAK_CNODE_GET_LOC(oprnd), "variable declaration disallowed");
					return -1;

				case HAK_CONCODE_TUPLE:
					/* [a, b] is only allowed as a lvalue or in class member varialble declaration for now */
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "tuple disallowed");
					return -1;

				default:
					hak_setsynerrbfmt(hak, HAK_SYNERR_INTERN, HAK_CNODE_GET_LOC(oprnd), "internal error - unknown cons type %d", HAK_CNODE_CONS_CONCODE(oprnd));
					return -1;
			}

			break;
		}

		case HAK_CNODE_ELIST:
		{
			/* empty list */
			switch (HAK_CNODE_ELIST_CONCODE(oprnd))
			{
				case HAK_CONCODE_ALIST:
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "empty assignment list");
					return -1;

				case HAK_CONCODE_XLIST:
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "empty executable list");
					return -1;

				case HAK_CONCODE_BLIST:
					/* this must not happend as the reader prevents it */
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "empty binop list");
					return -1;

				case HAK_CONCODE_MLIST:
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "empty message send list");
					return -1;

				case HAK_CONCODE_BLOCK:
					if (compile_cons_block_expression(hak, oprnd) <= -1) return -1;
					break;

				case HAK_CONCODE_ARRAY:
					if (emit_one_param_instruction(hak, HAK_CODE_MAKE_ARRAY, 0, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
					goto done;

				case HAK_CONCODE_BYTEARRAY:
					if (emit_one_param_instruction(hak, HAK_CODE_MAKE_BYTEARRAY, 0, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
					goto done;

				case HAK_CONCODE_CHARARRAY:
					if (emit_one_param_instruction(hak, HAK_CODE_MAKE_CHARARRAY, 0, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
					goto done;

				case HAK_CONCODE_DIC:
					if (emit_one_param_instruction(hak, HAK_CODE_MAKE_DIC, 16, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
					goto done;

				case HAK_CONCODE_QLIST:
					if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;
					goto done;

				case HAK_CONCODE_VLIST:
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "empty variable declaration");
					return -1;

				case HAK_CONCODE_TUPLE:
					hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "empty tuple");
					return -1;

				default:
					hak_setsynerrbfmt(hak, HAK_SYNERR_INTERN, HAK_CNODE_GET_LOC(oprnd),
						"internal error - unknown list type %d", HAK_CNODE_CONS_CONCODE(oprnd));
					return -1;
			}

			break;
		}

		case HAK_CNODE_SHELL:
			/* a shell node is just a wrapper of an actual node */
			oprnd = oprnd->u.shell.obj;
			goto redo;

		case HAK_CNODE_ELLIPSIS:
		case HAK_CNODE_TRPCOLONS:
		case HAK_CNODE_DBLCOLONS:
		case HAK_CNODE_COLON:
		default:
			hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd),
				"'%.*js' prohibited in this context",
				HAK_CNODE_GET_TOKLEN(oprnd), HAK_CNODE_GET_TOKPTR(oprnd));
			return -1;
	}

	/* the control reaches here in case a compile_xxxx() function(e.g. compile_cons_xlist_expression()) is called.
	 * such a function removes the top cframe. so POP_CFRAME() needs not be called here */
	return 0;

literal:
	if (emit_push_literal(hak, lit, HAK_CNODE_GET_LOC(oprnd)) <= -1) return -1;

done:
	POP_CFRAME(hak);
	return 0;
}

static int compile_object_r (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_OBJECT_R);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	oprnd = cf->operand;
	if (HAK_CNODE_IS_CONS(oprnd))
	{
		hak_concode_t cc;

		cc = HAK_CNODE_CONS_CONCODE(oprnd);

		switch (cc)
		{
			case HAK_CONCODE_XLIST:
				return compile_cons_xlist_expression(hak, oprnd, cf->u.obj_r.nrets);

			case HAK_CONCODE_BLIST:
			case HAK_CONCODE_MLIST:
				return compile_cons_mlist_expression(hak, oprnd, cf->u.obj_r.nrets);

#if 0
			case HAK_CONCODE_ALIST:
				/* TODO: can support it? */
				k := ([a, b, c] := (+ 10 20 30))
				break;
#endif
			default:
				break;
		}
	}

	hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(oprnd), "non-function call/non-message send disallowed");
	return -1;
}

static int compile_object_list (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;
	int cop;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_ARGUMENT_LIST ||
	                cf->opcode == COP_COMPILE_OBJECT_LIST ||
	                cf->opcode == COP_COMPILE_OBJECT_LIST_TAIL ||
	                cf->opcode == COP_COMPILE_IF_OBJECT_LIST ||
	                cf->opcode == COP_COMPILE_IF_OBJECT_LIST_TAIL ||
	                cf->opcode == COP_COMPILE_TRY_OBJECT_LIST ||
	                cf->opcode == COP_COMPILE_TRY_OBJECT_LIST_TAIL);

	cop = cf->opcode;
	oprnd = cf->operand;

	if (!oprnd)
	{
		POP_CFRAME(hak);
	}
	else
	{
		hak_cnode_t* car, * cdr;

#if 0
		if (cop != COP_COMPILE_ARGUMENT_LIST)
		{
			/* eliminate unnecessary non-function calls. keep the last one */
			while (HAK_CNODE_IS_CONS(oprnd))
			{
				cdr = HAK_CNODE_CONS_CDR(oprnd);
				if (!cdr) break; /* keep the last one */

				if (HAK_CNODE_IS_CONS(cdr))
				{
					/* look ahead */
					/* keep the last one before elif or else... */
					car = HAK_CNODE_CONS_CAR(cdr);
					if (HAK_CNODE_IS_SYMBOL(car) && HAK_CNODE_SYMBOL_SYNCODE(car)) break;
				}

				car = HAK_CNODE_CONS_CAR(oprnd);
/* this optimization is buggy for now... need to perfect the break condition here */
				if (HAK_CNODE_IS_CONS(car) || (HAK_CNODE_IS_SYMBOL(car) && HAK_CNODE_SYMBOL_SYNCODE(car)) || HAK_CNODE_IS_ELLIPSIS(car) || HAK_CNODE_IS_DBLCOLONS(car)) break;
				oprnd = cdr;
			}

			HAK_ASSERT(hak, oprnd != HAK_NULL);
		}
#endif
		if (!HAK_CNODE_IS_CONS(oprnd))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(oprnd),
				"redundant cdr in the object list around '%.*js'",
				HAK_CNODE_GET_TOKLEN(oprnd), HAK_CNODE_GET_TOKPTR(oprnd));
			return -1;
		}

		car = HAK_CNODE_CONS_CAR(oprnd);
		cdr = HAK_CNODE_CONS_CDR(oprnd);

		if (cop == COP_COMPILE_IF_OBJECT_LIST || cop == COP_COMPILE_IF_OBJECT_LIST_TAIL)
		{
			if (HAK_CNODE_IS_TYPED(car, HAK_CNODE_ELIF))
			{
				SWITCH_TOP_CFRAME(hak, COP_COMPILE_ELIF, oprnd);
				goto done;
			}
			else if (HAK_CNODE_IS_TYPED(car, HAK_CNODE_ELSE))
			{
				SWITCH_TOP_CFRAME(hak, COP_COMPILE_ELSE, oprnd);
				goto done;
			}
		}
		else if (cop == COP_COMPILE_TRY_OBJECT_LIST || cop == COP_COMPILE_TRY_OBJECT_LIST_TAIL)
		{
			if (HAK_CNODE_IS_TYPED(car, HAK_CNODE_CATCH))
			{
				SWITCH_TOP_CFRAME(hak, COP_COMPILE_CATCH, oprnd);
				goto done;
			}
		}

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, car);

		if (cdr)
		{
			/* there is a next statement to compile
			 *
			 * (+ 1 2 3) - argument list. 1, 2, 3 pushed must remain in
			 *             the stack until the function '+' is called.
			 *
			 * (fun (x y) (+ x 10) (+ y 20))
			 *    - the result of (+ x 10) should be popped before (+ y 20)
			 *      is executed
			 *
			 * for the latter, inject POP_STACKTOP after each object evaluation
			 * except the last.
			 */
			int nextcop;
			nextcop = (cop == COP_COMPILE_OBJECT_LIST)?     COP_COMPILE_OBJECT_LIST_TAIL:
			          (cop == COP_COMPILE_IF_OBJECT_LIST)?  COP_COMPILE_IF_OBJECT_LIST_TAIL:
			          (cop == COP_COMPILE_TRY_OBJECT_LIST)? COP_COMPILE_TRY_OBJECT_LIST_TAIL: cop;
			PUSH_SUBCFRAME(hak, nextcop, cdr);
		}

		if (cop == COP_COMPILE_OBJECT_LIST_TAIL ||
		    cop == COP_COMPILE_IF_OBJECT_LIST_TAIL ||
		    cop == COP_COMPILE_TRY_OBJECT_LIST_TAIL)
		{
			/* emit POP_STACKTOP before evaluating the second objects
			 * and onwards. this goes above COP_COMPILE_OBJECT.*/
			/* TODO: if the previous operators is known to divert execution flow, it may skip this.
			 *       for instance, some 'RETURN' or 'JUMP' operators or class-level variable declaration with 'var' */
			PUSH_CFRAME(hak, COP_EMIT_POP_STACKTOP, oprnd);
		}
	}

done:
	return 0;
}

static int compile_array_list (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_ARRAY_LIST);

	oprnd = cf->operand;

	if (!oprnd)
	{
		POP_CFRAME(hak);
	}
	else
	{
		hak_cnode_t* car, * cdr;
		hak_ooi_t oldidx;

		if (!HAK_CNODE_IS_CONS(oprnd))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(oprnd),
				"redundant cdr in the array list around '%.*js'",
				HAK_CNODE_GET_TOKLEN(oprnd), HAK_CNODE_GET_TOKPTR(oprnd));
			return -1;
		}

		car = HAK_CNODE_CONS_CAR(oprnd);
		cdr = HAK_CNODE_CONS_CDR(oprnd);

		oldidx = cf->u.array_list.index;

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, car);
		if (cdr)
		{
			PUSH_SUBCFRAME(hak, COP_COMPILE_ARRAY_LIST, cdr);
			cf = GET_SUBCFRAME(hak);
			cf->u.array_list.index = oldidx + 1;
		}

		PUSH_SUBCFRAME(hak, COP_EMIT_POP_INTO_ARRAY, car);
		cf = GET_SUBCFRAME(hak);
		cf->u.array_list.index = oldidx;
	}

	return 0;
}

static int compile_pure_array_list (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_PURE_ARRAY_LIST);

	oprnd = cf->operand;

	if (!oprnd)
	{
		POP_CFRAME(hak);
	}
	else
	{
		hak_cnode_t* car, * cdr;
		hak_ooi_t oldidx;
		int elem_type;

		if (!HAK_CNODE_IS_CONS(oprnd))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(oprnd),
				"redundant cdr in the byte-array list around '%.*js'",
				HAK_CNODE_GET_TOKLEN(oprnd), HAK_CNODE_GET_TOKPTR(oprnd));
			return -1;
		}

		car = HAK_CNODE_CONS_CAR(oprnd);
		cdr = HAK_CNODE_CONS_CDR(oprnd);

		oldidx = cf->u.pure_array_list.index;
		elem_type = cf->u.pure_array_list.elem_type;

/* TODO: compile type check if the data element is literal...
	 runtime check if the data is a variable or something... */

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, car);
		if (cdr)
		{
			PUSH_SUBCFRAME(hak, COP_COMPILE_PURE_ARRAY_LIST, cdr);
			cf = GET_SUBCFRAME(hak);
			cf->u.pure_array_list.elem_type = elem_type;
			cf->u.pure_array_list.index = oldidx + 1;
		}

		PUSH_SUBCFRAME(hak, COP_EMIT_POP_INTO_PURE_ARRAY, car);
		cf = GET_SUBCFRAME(hak);
		cf->u.pure_array_list.elem_type = elem_type;
		cf->u.pure_array_list.index = oldidx;
	}

	return 0;
}


static int compile_dic_list (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_DIC_LIST);

	oprnd = cf->operand;

	if (!oprnd)
	{
		POP_CFRAME(hak);
	}
	else
	{
		hak_cnode_t* car, * cdr, * cadr, * cddr;

		if (!HAK_CNODE_IS_CONS(oprnd))
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_DOTBANNED, HAK_CNODE_GET_LOC(oprnd),
				"redundant cdr in the dictionary list around '%.*js'", HAK_CNODE_GET_TOKLEN(oprnd), HAK_CNODE_GET_TOKPTR(oprnd));
			return -1;
		}

		car = HAK_CNODE_CONS_CAR(oprnd);
		cdr = HAK_CNODE_CONS_CDR(oprnd);

		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, car);
		if (!cdr)
		{
			hak_setsynerrbfmt(hak, HAK_SYNERR_UNBALKV, HAK_CNODE_GET_LOC(car),
				"no value for key %.*js", HAK_CNODE_GET_TOKLEN(car), HAK_CNODE_GET_TOKPTR(car));
			return -1;
		}

		cadr = HAK_CNODE_CONS_CAR(cdr);
		cddr = HAK_CNODE_CONS_CDR(cdr);

		if (cddr)
		{
			PUSH_SUBCFRAME(hak, COP_COMPILE_DIC_LIST, cddr);
		}

		PUSH_SUBCFRAME(hak, COP_EMIT_POP_INTO_DIC, cdr);
		PUSH_SUBCFRAME(hak, COP_COMPILE_OBJECT, cadr);
	}

	return 0;
}

static int compile_qlist (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_cnode_t* oprnd;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_COMPILE_QLIST);

	oprnd = cf->operand;

	if (!oprnd)
	{
		POP_CFRAME(hak);
	}
	else
	{
		if (!HAK_CNODE_IS_CONS(oprnd))
		{
			/* the last element after . */
			SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, oprnd);
			PUSH_SUBCFRAME(hak, COP_EMIT_POP_INTO_CONS_CDR, oprnd);
		}
		else
		{
			hak_cnode_t* car, * cdr;

			car = HAK_CNODE_CONS_CAR(oprnd);
			cdr = HAK_CNODE_CONS_CDR(oprnd);

			SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT, car); /* 1 */
			if (cdr)
			{
				PUSH_SUBCFRAME(hak, COP_COMPILE_QLIST, cdr); /* 3 */
				PUSH_SUBCFRAME(hak, COP_EMIT_POP_INTO_CONS, car); /* 2 */
			}
			else
			{
				/* the last element */
				PUSH_SUBCFRAME(hak, COP_EMIT_POP_INTO_CONS_END, car); /* 2 */
			}
		}
	}

	return 0;
}

/* ========================================================================= */

static HAK_INLINE int post_if_cond (hak_t* hak)
{
	hak_cframe_t* cf, * cf2;
	hak_ooi_t jump_inst_pos;
	hak_ooi_t body_pos;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_POST_IF_COND);
	/* cf->operand can be HAK_NULL in these expressions
	 *   (if true)
	 *   (if false)
	 *   (if true 20 elif false)
	 */
	/*HAK_ASSERT(hak, cf->operand != HAK_NULL);*/

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jump_inst_pos = hak->code.bc.len;

	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_FORWARD_IF_FALSE, MAX_CODE_JUMP, &cf->u.post_if.start_loc) <= -1) return -1;

	/* to drop the result of the conditional when it is true */
	if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, &cf->u.post_if.start_loc) <= -1) return -1;

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	body_pos = hak->code.bc.len;

	if (check_block_expression_as_body(hak, cf->operand, cf->u.post_if.cmd_cnode, FOR_IF) <= -1) return -1;

	SWITCH_TOP_CFRAME(hak, COP_COMPILE_IF_OBJECT_LIST, cf->operand); /* 1 */
	PUSH_SUBCFRAME(hak, COP_POST_IF_BODY, cf->operand); /* 2 */
	cf2 = GET_SUBCFRAME(hak);
	cf2->u.post_if.body_pos = body_pos;
	cf2->u.post_if.jump_inst_pos = jump_inst_pos;
	cf2->u.post_if.start_loc = cf->u.post_if.start_loc;
	cf2->u.post_if.cmd_cnode = cf->u.post_if.cmd_cnode;
	return 0;
}

static HAK_INLINE int post_if_body (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_ooi_t jip;
	hak_oow_t jump_offset;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_POST_IF_BODY);

	/* cf->operand can be HAK_NULL in these expressions
	 *   (if true)
	 *   (if false)
	 *   (if true 20 elif false)
	 */
	/*HAK_ASSERT(hak, cf->operand != HAK_NULL);*/

	jip = cf->u.post_if.jump_inst_pos;

	if (hak->code.bc.len <= cf->u.post_if.body_pos)
	{
		/* if body is empty */
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, &cf->u.post_if.start_loc) <= -1) return -1;
	}

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD_IF_FALSE instruction */
	jump_offset = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	if (jump_offset > MAX_CODE_JUMP * 2)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_IFFLOOD, &cf->u.post_if.start_loc, "code too big - size %zu", jump_offset);
		return -1;
	}
	patch_long_jump(hak, jip, jump_offset);

	POP_CFRAME(hak);
	return 0;
}

/* ========================================================================= */
static HAK_INLINE int post_while_cond (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_ooi_t jump_inst_pos;
	hak_ooi_t cond_pos, body_pos;
	hak_loc_t start_loc;
	int jump_inst, next_cop;
	hak_cnode_t* cond, * body;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_POST_UNTIL_COND || cf->opcode == COP_POST_WHILE_COND);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	/* the caller must pass the cons cell branching to the conditinal and the body
	 * if the body cell is given, things get complicated because the body part can be HAK_NULL.
	 * for instance, the body part is empty in (while (< i 1)  ) */
	cond = HAK_CNODE_CONS_CAR(cf->operand);
	body = HAK_CNODE_CONS_CDR(cf->operand);

	cond_pos = cf->u.post_while.cond_pos;
	start_loc = cf->u.post_while.start_loc;
	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jump_inst_pos = hak->code.bc.len;

	if (cf->opcode == COP_POST_UNTIL_COND)
	{
		jump_inst = HAK_CODE_JUMP_FORWARD_IF_TRUE;
		next_cop = COP_POST_UNTIL_BODY;
	}
	else
	{
		jump_inst = HAK_CODE_JUMP_FORWARD_IF_FALSE;
		next_cop = COP_POST_WHILE_BODY;
	}

	if (emit_one_param_instruction(hak, jump_inst, MAX_CODE_JUMP, HAK_CNODE_GET_LOC(cond)) <= -1) return -1;
	if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_CNODE_GET_LOC(cond)) <= -1) return -1;

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	body_pos = hak->code.bc.len;

	if (body)
	{
		SWITCH_TOP_CFRAME(hak, COP_COMPILE_OBJECT_LIST, body); /* 1 */
		PUSH_SUBCFRAME(hak, next_cop, cf->operand); /* 2 */
		cf = GET_SUBCFRAME(hak);
	}
	else
	{
		/* the body is empty */
		SWITCH_TOP_CFRAME(hak, next_cop, cond); /* 2 */
		cf = GET_TOP_CFRAME(hak);
	}
	cf->u.post_while.cond_pos = cond_pos;
	cf->u.post_while.body_pos = body_pos;
	cf->u.post_while.jump_inst_pos = jump_inst_pos;
	cf->u.post_while.start_loc = start_loc;

	return 0;
}

static HAK_INLINE int post_while_body (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_ooi_t jip;
	hak_ooi_t jump_offset;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_POST_UNTIL_BODY || cf->opcode == COP_POST_WHILE_BODY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	HAK_ASSERT(hak, hak->code.bc.len >= cf->u.post_while.cond_pos);
	if (hak->code.bc.len > cf->u.post_while.body_pos)
	{
		/* some code exist after POP_STACKTOP after JUMP_FORWARD_IF_TRUE/FALSE.
		 * (until #f) =>
		 *   push_false
		 *   jump_forward_if_true XXXX
		 *   pop_stacktop            <-- 1) emitted in post_while_cond();
		 *   jump_backward YYYY      <-- 2) emitted below
		 *   pop_stacktop
		 * this check prevents another pop_stacktop between 1) and 2)
		 */
		if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
	}

	HAK_ASSERT(hak, hak->code.bc.len < HAK_SMOOI_MAX);
	jump_offset = hak->code.bc.len - cf->u.post_while.cond_pos + 1;
	if (jump_offset > 3) jump_offset += HAK_CODE_LONG_PARAM_SIZE;
	if (emit_one_param_instruction(hak, HAK_CODE_JUMP_BACKWARD_0, jump_offset, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	jip = cf->u.post_while.jump_inst_pos;
	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD_IF_FALSE/JUMP_FORWARD_IF_TRUE instruction */
	jump_offset = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);
	if (jump_offset > MAX_CODE_JUMP * 2)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKFLOOD, &cf->u.post_while.start_loc, "code too big - size %zu", jump_offset);
		return -1;
	}
	patch_long_jump(hak, jip, jump_offset);

	POP_CFRAME(hak);

	HAK_ASSERT(hak, hak->c->ctlblk.info[hak->c->ctlblk.depth]._type == HAK_CTLBLK_TYPE_LOOP);
	pop_ctlblk(hak);

	return 0;
}


/* ========================================================================= */

static HAK_INLINE int emit_call (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_CALL);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	if (cf->u.call.nrets > 0)
	{
		n = emit_two_param_instruction(hak, HAK_CODE_CALL_R, cf->u.call.index, cf->u.call.nrets, HAK_CNODE_GET_LOC(cf->operand));
	}
	else
	{
		n = emit_one_param_instruction(hak, HAK_CODE_CALL_0, cf->u.call.index, HAK_CNODE_GET_LOC(cf->operand));
	}

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_push_nil (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_PUSH_NIL);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, HAK_CNODE_GET_LOC(cf->operand));
	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_push_symbol (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_oop_t lit;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_PUSH_SYMBOL);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(cf->operand), HAK_CNODE_GET_TOKLEN(cf->operand));
	if (HAK_UNLIKELY(!lit)) return -1;
	if (emit_push_literal(hak, lit, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int emit_send (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_SEND);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	if (cf->u.sendmsg.nrets > 0)
	{
		n = emit_two_param_instruction(hak, (cf->u.sendmsg.to_super? HAK_CODE_SEND_TO_SUPER_R: HAK_CODE_SEND_R), cf->u.sendmsg.nargs, cf->u.sendmsg.nrets, HAK_CNODE_GET_LOC(cf->operand));
	}
	else
	{
		n = emit_one_param_instruction(hak, (cf->u.sendmsg.to_super? HAK_CODE_SEND_TO_SUPER_0: HAK_CODE_SEND_0), cf->u.sendmsg.nargs, HAK_CNODE_GET_LOC(cf->operand));
	}

	POP_CFRAME(hak);
	return n;
}

/* ========================================================================= */

static HAK_INLINE int emit_make_array (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_MAKE_ARRAY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_one_param_instruction(hak, HAK_CODE_MAKE_ARRAY, cf->u.array_list.index, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_make_pure_array (hak_t* hak)
{
	hak_cframe_t* cf;
	int n, inst;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_MAKE_PURE_ARRAY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	inst = (cf->u.pure_array_list.elem_type == HAK_CONCODE_BYTEARRAY)? HAK_CODE_MAKE_BYTEARRAY: HAK_CODE_MAKE_CHARARRAY;
	n = emit_one_param_instruction(hak, inst, cf->u.pure_array_list.index, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_make_dic (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_MAKE_DIC);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_one_param_instruction(hak, HAK_CODE_MAKE_DIC, cf->u.dic_list.index, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_make_cons (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_MAKE_CONS);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, HAK_CODE_MAKE_CONS, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_pop_into_array (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_POP_INTO_ARRAY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_one_param_instruction(hak, HAK_CODE_POP_INTO_ARRAY, cf->u.array_list.index, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_pop_into_pure_array (hak_t* hak)
{
	hak_cframe_t* cf;
	int n, inst;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_POP_INTO_PURE_ARRAY);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	inst = (cf->u.pure_array_list.elem_type == HAK_CONCODE_BYTEARRAY)? HAK_CODE_POP_INTO_BYTEARRAY: HAK_CODE_POP_INTO_CHARARRAY;
	n = emit_one_param_instruction(hak, inst, cf->u.pure_array_list.index, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_pop_into_dic (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_POP_INTO_DIC);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, HAK_CODE_POP_INTO_DIC, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_pop_into_cons (hak_t* hak, int cmd)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_POP_INTO_CONS ||
	                 cf->opcode == COP_EMIT_POP_INTO_CONS_END ||
	                 cf->opcode == COP_EMIT_POP_INTO_CONS_CDR);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, cmd, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

/* ========================================================================= */

static HAK_INLINE int emit_fun (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_oow_t block_code_size, lfsize;
	hak_ooi_t jip;
	hak_funblk_info_t* fbi;
	hak_loc_t* oploc;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_FUN);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	oploc = HAK_CNODE_GET_LOC(cf->operand);
	fbi = &hak->c->funblk.info[hak->c->funblk.depth];
	jip = cf->u.fun.jump_inst_pos;

	if (hak->option.trait & HAK_TRAIT_INTERACTIVE)
		lfsize = hak->code.lit.len - hak->c->funblk.info[hak->c->funblk.depth].lfbase;

	/* HAK_CODE_LONG_PARAM_SIZE + 1 => size of the long JUMP_FORWARD instruction */
	block_code_size = hak->code.bc.len - jip - (HAK_CODE_LONG_PARAM_SIZE + 1);

	if (fbi->tmpr_nrvars > 0)
	{
		/* this function block defines one or more return variables */
		if (block_code_size > 0)
		{
			if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, oploc) <= -1) return -1;
			block_code_size++;
		}
		if (emit_byte_instruction(hak, HAK_CODE_PUSH_RETURN_R, oploc) <= -1) return -1;
		block_code_size++;
	}
	else
	{
		/* single return value */
		if ((cf->u.fun.fun_type & 0xFF) == FUN_PLAIN)
		{
			if (block_code_size == 0)
			{
				/* no body in fun - (fun (a b c)) */
	/* TODO: is this correct??? */
				if (emit_byte_instruction(hak, HAK_CODE_PUSH_NIL, oploc) <= -1) return -1;
				block_code_size++;
			}
		}
		else
		{
			/* in-class methods */
			if (block_code_size == 1)
			{
				/* simple optimization not to skip emitting POP_STACKTOP */
				HAK_ASSERT(hak, hak->code.bc.len > 0);
				if (hak->code.bc.ptr[hak->code.bc.len - 1] == HAK_CODE_PUSH_NIL)
				{
					hak->code.bc.len--;
					block_code_size--;
				}
				else if (hak->code.bc.ptr[hak->code.bc.len - 1] == HAK_CODE_PUSH_RECEIVER)
				{
					goto emit_return_from_block;
				}
			}

			if (block_code_size > 0)
			{
				if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, oploc) <= -1) return -1;
				block_code_size++;
			}

			if (emit_byte_instruction(hak, HAK_CODE_PUSH_RECEIVER, oploc) <= -1) return -1;
			block_code_size++;
		}

	emit_return_from_block:
		if (emit_byte_instruction(hak, HAK_CODE_RETURN_FROM_BLOCK, oploc) <= -1) return -1;
		block_code_size++;
	}

	if (block_code_size > MAX_CODE_JUMP * 2)
	{
		hak_setsynerrbfmt(hak, HAK_SYNERR_BLKFLOOD, oploc, "code too big - size %zu", block_code_size);
		return -1;
	}
	patch_long_jump(hak, jip, block_code_size);

	if (hak->option.trait & HAK_TRAIT_INTERACTIVE)
		patch_long_param(hak, cf->u.fun.lfsize_pos, lfsize);

	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int post_fun (hak_t* hak)
{
	hak_cframe_t* cf;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_POST_FUN);

	/*hak->c->funblk.depth--;
	hak->c->tv.s.len = hak->c->funblk.info[hak->c->funblk.depth].tmprlen;
	hak->c->tv.wcount = hak->c->funblk.info[hak->c->funblk.depth].tmprcnt;*/
	pop_funblk(hak);

	if (cf->operand)
	{
		/* (fun x()  ; this x refers to a variable in the outer scope.
		 *     | t1 t2 x |
		 *     (set x 10)  ; this x refers to the local variable.
		 * )
		 *
		 * the block has been exited(blk.depth--) before finding 'x' in the outer scope.
		 */
		hak_cnode_t* fun_name = cf->operand;
		hak_cnode_t* class_name = cf->u.fun.class_name;
		hak_var_info_t vi;
		int x;

		HAK_ASSERT(hak, is_cnode_eligible_for_fun_name_after_colon(fun_name));

		if (is_in_class_init_scope(hak))
		{
			/* method definition */

			if (class_name)
			{
				/* something wrong - this must not happen because the reader must prevent this
				 * but if it happens, it is a syntax error */
				hak_setsynerrbfmt(hak, HAK_SYNERR_BANNED, HAK_CNODE_GET_LOC(class_name),
					"class name '%.*js' prohibited class initialization context",
					HAK_CNODE_GET_TOKLEN(class_name), HAK_CNODE_GET_TOKPTR(class_name));
				return -1;
			}

			x = find_variable_backward_with_token(hak, fun_name, &vi);
			if (x <= -1) return -1;
			if (x == 0)
			{
				/* arrange to save to the method slot */
				switch (cf->u.fun.fun_type & 0xFF)
				{
					case FUN_CM: /* class method */
						SWITCH_TOP_CFRAME(hak, COP_EMIT_CLASS_CMSTORE, fun_name);
						break;

					case FUN_CIM: /* class instantiation method */
						SWITCH_TOP_CFRAME(hak, COP_EMIT_CLASS_CIMSTORE, fun_name);
						break;

					case FUN_IM: /* instance method */
						SWITCH_TOP_CFRAME(hak, COP_EMIT_CLASS_IMSTORE, fun_name);
						break;

					default:
						/* in the class initialization scope, the type must not be other than the listed above */
						HAK_DEBUG1(hak, "Internal error - invalid method type %d\n", cf->u.fun.fun_type & 0xFF);
						hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid method type %d", cf->u.fun.fun_type & 0xFF);
						return -1;
				}
				cf = GET_TOP_CFRAME(hak);
			}
			else
			{
				hak_setsynerrbfmt(hak, HAK_SYNERR_VARNAMEDUP, HAK_CNODE_GET_LOC(fun_name),
					"duplicate method name '%.*js'", HAK_CNODE_GET_TOKLEN(fun_name), HAK_CNODE_GET_TOKPTR(fun_name));
				return -1;
			}
		}
		else
		{
			/* the function name must be global or module-wide.(no module implemented yet. so only global) */
		#if 0
			x = find_variable_backward_with_token(hak, fun_name, &vi);
			if (x <= -1) return -1;
			if (x == 0)
			{
				SWITCH_TOP_CFRAME(hak, COP_EMIT_SET, fun_name);
				cf = GET_TOP_CFRAME(hak);
				cf->u.set.vi.type = VAR_NAMED;
			}
			else
			{
				SWITCH_TOP_CFRAME(hak, COP_EMIT_SET, fun_name);
				cf = GET_TOP_CFRAME(hak);
				cf->u.set.vi = vi;
			}
			cf->u.set.mode = VAR_ACCESS_STORE;
		#else
			if (class_name)
			{
				/* out-of-class definition */
/* TODO:  - other types of out-of-class definition - CIM_STORE, CM_STORE...  use different marker? */
				hak_oow_t index;
				hak_oop_t lit;
				int inst;

				/* treat the class name part as a normal variable.
				 * it can be a global variable like 'String' or a local variable declared */
				if (compile_symbol(hak, class_name) <= -1) return -1;

				if (emit_byte_instruction(hak, HAK_CODE_CLASS_LOAD, HAK_CNODE_GET_LOC(class_name)) <= -1) return -1;

				/* the function name is always named */
				lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(fun_name), HAK_CNODE_GET_TOKLEN(fun_name));
				if (HAK_UNLIKELY(!lit)) return -1;
				if (add_literal(hak, lit, &index) <= -1) return -1;

				switch (cf->u.fun.fun_type & 0xFF)
				{
					case FUN_CM: /* class method */
						inst =  HAK_CODE_CLASS_CMSTORE;
						break;

					case FUN_CIM: /* class instantiation method */
						inst =  HAK_CODE_CLASS_CIMSTORE;
						break;

					case FUN_IM: /* instance method */
						inst =  HAK_CODE_CLASS_IMSTORE;
						break;

					default:
						/* in the class initialization scope, the type must not be other than the listed above */
						HAK_DEBUG1(hak, "Internal error - invalid function type %d\n", cf->u.fun.fun_type & 0xFF);
						hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid function type %d", cf->u.fun.fun_type & 0xFF);
						return -1;
				}

				if (emit_one_param_instruction(hak, inst, index, HAK_CNODE_GET_LOC(fun_name)) <= -1) return -1;
				if (emit_byte_instruction(hak, HAK_CODE_CLASS_EXIT, HAK_CNODE_GET_LOC(class_name)) <= -1) return -1;
				POP_CFRAME(hak);
			}
			else
			{
				/* An explicitly named function is always global */
				SWITCH_TOP_CFRAME(hak, COP_EMIT_SET, fun_name);
				cf = GET_TOP_CFRAME(hak);
				cf->u.set.vi.type = VAR_NAMED;
				cf->u.set.mode = VAR_ACCESS_STORE;
			}
		#endif
		}
	}
	else
	{
		POP_CFRAME(hak);
	}

	return 0;
}

/* ========================================================================= */

static HAK_INLINE int emit_pop_stacktop (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_POP_STACKTOP);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_return (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_RETURN);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, (cf->u._return.from_home? HAK_CODE_RETURN_STACKTOP: HAK_CODE_RETURN_FROM_BLOCK), HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

static HAK_INLINE int emit_set (hak_t* hak)
{
	hak_cframe_t* cf;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_SET);
	HAK_ASSERT(hak, cf->u.set.mode == VAR_ACCESS_POP || cf->u.set.mode == VAR_ACCESS_STORE);

	if (cf->u.set.vi.type == VAR_NAMED)
	{
		hak_oow_t index;
		hak_oop_t cons, sym;

		HAK_ASSERT(hak, HAK_CNODE_IS_SYMBOL(cf->operand) || HAK_CNODE_IS_BINOP(cf->operand));

		sym = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(cf->operand), HAK_CNODE_GET_TOKLEN(cf->operand));
		if (HAK_UNLIKELY(!sym)) return -1;

		cons = (hak_oop_t)hak_getatsysdic(hak, sym);
		if (!cons)
		{
			cons = (hak_oop_t)hak_putatsysdic(hak, sym, hak->_nil);
			if (HAK_UNLIKELY(!cons)) return -1;
		}

		if (add_literal(hak, cons, &index) <= -1) return -1;
		if (emit_one_param_instruction(hak, (cf->u.set.mode == VAR_ACCESS_POP? HAK_CODE_POP_INTO_OBJECT_0: HAK_CODE_STORE_INTO_OBJECT_0), index, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
	}
	else
	{
		HAK_ASSERT(hak, cf->operand != HAK_NULL);
		if (emit_variable_access(hak, cf->u.set.mode, &cf->u.set.vi, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;
	}

	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int emit_class_cmstore (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_oop_t lit;
	hak_oow_t index;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_CLASS_CMSTORE);

	lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(cf->operand), HAK_CNODE_GET_TOKLEN(cf->operand));
	if (HAK_UNLIKELY(!lit)) return -1;

	if (add_literal(hak, lit, &index) <= -1) return -1;
	if (emit_one_param_instruction(hak, HAK_CODE_CLASS_CMSTORE, index, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int emit_class_cimstore (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_oop_t lit;
	hak_oow_t index;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_CLASS_CIMSTORE);

	lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(cf->operand), HAK_CNODE_GET_TOKLEN(cf->operand));
	if (HAK_UNLIKELY(!lit)) return -1;

	if (add_literal(hak, lit, &index) <= -1) return -1;
	if (emit_one_param_instruction(hak, HAK_CODE_CLASS_CIMSTORE, index, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int emit_class_imstore (hak_t* hak)
{
	hak_cframe_t* cf;
	hak_oop_t lit;
	hak_oow_t index;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_CLASS_IMSTORE);

	lit = hak_makesymbol(hak, HAK_CNODE_GET_TOKPTR(cf->operand), HAK_CNODE_GET_TOKLEN(cf->operand));
	if (HAK_UNLIKELY(!lit)) return -1;

	if (add_literal(hak, lit, &index) <= -1) return -1;
	if (emit_one_param_instruction(hak, HAK_CODE_CLASS_IMSTORE, index, HAK_CNODE_GET_LOC(cf->operand)) <= -1) return -1;

	POP_CFRAME(hak);
	return 0;
}

static HAK_INLINE int emit_throw (hak_t* hak)
{
	hak_cframe_t* cf;
	int n;

	cf = GET_TOP_CFRAME(hak);
	HAK_ASSERT(hak, cf->opcode == COP_EMIT_THROW);
	HAK_ASSERT(hak, cf->operand != HAK_NULL);

	n = emit_byte_instruction(hak, HAK_CODE_THROW, HAK_CNODE_GET_LOC(cf->operand));

	POP_CFRAME(hak);
	return n;
}

/* ========================================================================= */

int hak_compile (hak_t* hak, hak_cnode_t* obj, int flags)
{
	hak_oow_t saved_bc_len, saved_lit_len;
	hak_bitmask_t log_default_type_mask;
	hak_funblk_info_t top_funblk_saved;
	int top_funblk_pushed_here = 0;

	hak->c->flags = flags;

	HAK_ASSERT(hak, hak->c->funblk.depth <= 0); /* 0 or 1 funblk must exist at this phase */
	HAK_ASSERT(hak, GET_TOP_CFRAME_INDEX(hak) < 0);

	if (flags & HAK_COMPILE_CLEAR_FUNBLK)
	{
		/* if the program is executed in the interactive mode,
		 * each compiled expression is executed immediately.
		 * that is, hak_compile() is followed by hak_execute()
		 * immediately.
		 *
		 *   (1) a := 20
		 *   (2) { | b c | b := 20; c := 30 }
		 *   (3) printf "%d\n" a
		 *
		 * in the interactive mode,
		 *  (1) is compiled and executed
		 *  (2) is compiled and executed
		 *  (3) is compiled and executed
		 *
		 * in the non-interactive mode,
		 *  (1) is compiled, (2) is compiled, (3) is compiled
		 *  (1), (2), (3) are executed
		 * funblk holds information about temporaries seen so far.
		 * (2) has defined two temporary variables. this count
		 * must get carried until (3) has been compiled in the
		 * non-interactive mode. the accumulated count is used
		 * in creating an initial context for execution.
		 *
		 * in the interactive mode, the information doesn't have
		 * to get carried over.
		 */
		while (hak->c->funblk.depth >= 0) pop_funblk(hak);
		HAK_ASSERT(hak, hak->c->funblk.depth == -1);
		/* it will be recreated below */
	}
	if (flags & HAK_COMPILE_CLEAR_CODE) hak_clearcode(hak);

	saved_bc_len = hak->code.bc.len;
	saved_lit_len = hak->code.lit.len;

	log_default_type_mask = hak->log.default_type_mask;
	hak->log.default_type_mask |= HAK_LOG_COMPILER;

	/*
	 * In the non-INTERACTIVE mode, the literal frame base(lfbase) doesn't matter.
	 * Only the initial function object contains the literal frame.
	 * No other function objects are created. All 'fun' defintions are
	 * translated to block context objects instead.
	 *
	 * In the INTERACTIVE mode, the literal frame base(lfbase) plays a key role.
	 * hak_compile() is called for the top-level expression and the literal
	 * frame base can be 0. The means it is ok for a top-level code to
	 * reference part of the literal frame reserved for a function.
	 *
	 *  set b 1
	 *  fun set-a(x) { set a x }
	 *  set a 2
	 *  set-a 4
	 *  printf "%d\n" a
	 *
	 * the global literal frame looks like this:
	 *  @0         (b)
	 *  @1         (a)
	 *  @2         (set-a)
	 *  @3         (printf . #<PRIM>)
	 *  @4         "%d\n"
	 *
	 * @1 to @2 will be copied to a function object when fun is executed.
	 * The literal frame of the created function object for set-a looks
	 * like this
	 *  @0         (a)
	 *  @1         (set-a)
	 */

/* TODO: in case i implement all global variables as block arguments at the top level...what should i do? */
	HAK_ASSERT(hak, hak->c->ctlblk.depth == -1);

	if (hak->c->funblk.depth <= -1)
	{
		HAK_ASSERT(hak, hak->c->funblk.depth == -1);
		HAK_ASSERT(hak, hak->c->tv.s.len == 0);
		HAK_ASSERT(hak, hak->c->tv.wcount == 0);

		/* keep a virtual function block for the top-level compilation.
		 * pass HAK_TYPE_MAX(hak_oow_t) as make_inst_pos because there is
		 * no actual MAKE_BLOCK/MAKE_FUNCTION instruction which otherwise
		 * would be patched in pop_funblk(). */

		if (push_funblk(
			hak, HAK_NULL,
			0, /* tmpr_va */
			0, /* tmpr_nargs */
			0, /* tmpr_nrvars */
			hak->c->tv.wcount, /* tmpr_nlvars */
			hak->c->tv.wcount, /* tmpr_count */
			hak->c->tv.s.len, /* tmpr_len */
			HAK_TYPE_MAX(hak_oow_t), /* make_inst_pos */
			0, /* lfbase */
			FUN_PLAIN /* fun_type */
		) <= -1) return -1; /* must not goto oops */

		top_funblk_pushed_here = 1;
	}
	top_funblk_saved = hak->c->funblk.info[0];
	HAK_ASSERT(hak, hak->c->funblk.depth == 0); /* ensure the virtual function block is added */

	PUSH_CFRAME(hak, COP_COMPILE_OBJECT, obj);

	while (GET_TOP_CFRAME_INDEX(hak) >= 0)
	{
		hak_cframe_t* cf;

		cf = GET_TOP_CFRAME(hak);

/* TODO: tabulate this switch-based dispatch */
		switch (cf->opcode)
		{
			case COP_COMPILE_OBJECT:
				if (compile_object(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_OBJECT_R:
				if (compile_object_r(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_SYMBOL_LITERAL:
				if (compile_symbol_literal(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_ARGUMENT_LIST:
			case COP_COMPILE_OBJECT_LIST:
			case COP_COMPILE_OBJECT_LIST_TAIL:
			case COP_COMPILE_IF_OBJECT_LIST:
			case COP_COMPILE_IF_OBJECT_LIST_TAIL:
			case COP_COMPILE_TRY_OBJECT_LIST:
			case COP_COMPILE_TRY_OBJECT_LIST_TAIL:
				if (compile_object_list(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_ARRAY_LIST:
				if (compile_array_list(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_PURE_ARRAY_LIST:
				if (compile_pure_array_list(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_DIC_LIST:
				if (compile_dic_list(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_QLIST:
				if (compile_qlist(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_ELIF:
				if (compile_elif (hak) <= -1) goto oops;
				break;

			case COP_COMPILE_ELSE:
				if (compile_else(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_CATCH:
				if (compile_catch(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_AND_P1:
				if (compile_and_p1(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_AND_P2:
				if (compile_and_p2(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_BREAK_P1:
				if (compile_break_p1(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_CLASS_P1:
				if (compile_class_p1(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_CLASS_P2:
				if (compile_class_p2(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_DO_P1:
				if (compile_do_p1(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_OR_P1:
				if (compile_or_p1(hak) <= -1) goto oops;
				break;

			case COP_COMPILE_OR_P2:
				if (compile_or_p2(hak) <= -1) goto oops;
				break;

			case COP_EMIT_CALL:
				if (emit_call(hak) <= -1) goto oops;
				break;

			case COP_EMIT_PUSH_NIL:
				if (emit_push_nil(hak) <= -1) goto oops;
				break;

			case COP_EMIT_PUSH_SYMBOL:
				if (emit_push_symbol(hak) <= -1) goto oops;
				break;

			case COP_EMIT_SEND:
				if (emit_send(hak) <= -1) goto oops;
				break;

			case COP_EMIT_MAKE_ARRAY:
				if (emit_make_array(hak) <= -1) goto oops;
				break;

			case COP_EMIT_MAKE_PURE_ARRAY:
				if (emit_make_pure_array(hak) <= -1) goto oops;
				break;

			case COP_EMIT_MAKE_DIC:
				if (emit_make_dic(hak) <= -1) goto oops;
				break;

			case COP_EMIT_MAKE_CONS:
				if (emit_make_cons(hak) <= -1) goto oops;
				break;

			case COP_EMIT_POP_INTO_ARRAY:
				if (emit_pop_into_array(hak) <= -1) goto oops;
				break;

			case COP_EMIT_POP_INTO_PURE_ARRAY:
				if (emit_pop_into_pure_array(hak) <= -1) goto oops;
				break;

			case COP_EMIT_POP_INTO_DIC:
				if (emit_pop_into_dic(hak) <= -1) goto oops;
				break;

			case COP_EMIT_POP_INTO_CONS:
				if (emit_pop_into_cons(hak, HAK_CODE_POP_INTO_CONS) <= -1) goto oops;
				break;

			case COP_EMIT_POP_INTO_CONS_END:
				if (emit_pop_into_cons(hak, HAK_CODE_POP_INTO_CONS_END) <= -1) goto oops;
				break;

			case COP_EMIT_POP_INTO_CONS_CDR:
				if (emit_pop_into_cons(hak, HAK_CODE_POP_INTO_CONS_CDR) <= -1) goto oops;
				break;

			case COP_EMIT_FUN:
				if (emit_fun(hak) <= -1) goto oops;
				break;

#if defined(USE_KW_PLUS)
			case COP_EMIT_PLUS:
				if (emit_plus(hak) <= -1) goto oops;
				break;
#endif

			case COP_EMIT_POP_STACKTOP:
				if (emit_pop_stacktop(hak) <= -1) goto oops;
				break;

			case COP_EMIT_RETURN:
				if (emit_return(hak) <= -1) goto oops;
				break;

			case COP_EMIT_SET:
				if (emit_set(hak) <= -1) goto oops;
				break;

			case COP_EMIT_CLASS_CMSTORE:
				if (emit_class_cmstore(hak) <= -1) goto oops;
				break;

			case COP_EMIT_CLASS_CIMSTORE:
				if (emit_class_cimstore(hak) <= -1) goto oops;
				break;

			case COP_EMIT_CLASS_IMSTORE:
				if (emit_class_imstore(hak) <= -1) goto oops;
				break;

			case COP_EMIT_THROW:
				if (emit_throw(hak) <= -1) goto oops;
				break;

			case COP_POST_IF_COND:
				if (post_if_cond(hak) <= -1) goto oops;
				break;

			case COP_POST_IF_BODY:
				if (post_if_body(hak) <= -1) goto oops;
				break;

			case COP_POST_UNTIL_BODY:
			case COP_POST_WHILE_BODY:
				if (post_while_body(hak) <= -1) goto oops;
				break;

			case COP_POST_UNTIL_COND:
			case COP_POST_WHILE_COND:
				if (post_while_cond(hak) <= -1) goto oops;
				break;

			case COP_POST_TRY:
				if (post_try(hak) <= -1) goto oops;
				break;
			case COP_POST_CATCH:
				if (post_catch(hak) <= -1) goto oops;
				break;

			case COP_POST_FUN:
				if (post_fun(hak) <= -1) goto oops;
				break;

			default:
				HAK_DEBUG1(hak, "Internal error - invalid compiler opcode %d\n", cf->opcode);
				hak_seterrbfmt(hak, HAK_EINTERN, "internal error - invalid compiler opcode %d", cf->opcode);
				goto oops;
		}
	}

	/* emit the pop instruction to clear the final result */
	if (emit_byte_instruction(hak, HAK_CODE_POP_STACKTOP, HAK_NULL) <= -1) goto oops;

	HAK_ASSERT(hak, GET_TOP_CFRAME_INDEX(hak) < 0);
	HAK_ASSERT(hak, hak->c->tv.s.len >= hak->c->funblk.info[0].tmprlen);
	HAK_ASSERT(hak, hak->c->tv.wcount >= hak->c->funblk.info[0].tmprcnt);
	HAK_ASSERT(hak, hak->c->ctlblk.depth == -1); /* no control blocks expected at this point */

	HAK_ASSERT(hak, hak->c->funblk.depth == 0); /* ensure the virtual function block be the only one left */
	hak->code.ngtmprs = hak->c->funblk.info[0].tmprcnt; /* populate the number of global temporary variables */

#if defined(CLEAR_FUNBLK_ALWAYS)
	pop_funblk(hak);
	HAK_ASSERT(hak, hak->c->tv.s.len == 0);
	HAK_ASSERT(hak, hak->c->tv.wcount == 0);
#endif

	hak->log.default_type_mask = log_default_type_mask;
	return 0;

oops:
	POP_ALL_CFRAMES(hak);

	hak->log.default_type_mask = log_default_type_mask;

	/* rollback any bytecodes or literals emitted so far */
	hak->code.bc.len = saved_bc_len;
	hak->code.lit.len = saved_lit_len;

	while (hak->c->funblk.depth > 0) pop_funblk(hak);
	HAK_ASSERT(hak, hak->c->funblk.depth == 0);

	if (top_funblk_pushed_here)
	{
		pop_funblk(hak);
		HAK_ASSERT(hak, hak->c->funblk.depth == -1);
		HAK_ASSERT(hak, hak->c->tv.s.len == 0);
		HAK_ASSERT(hak, hak->c->tv.wcount == 0);
	}
	else
	{
		/*
		{ |a b c| } ## tv.s.len 6, tv.wcount 3
		{ |k a| (set x y z) }
		*
		* at this point when (set a b c) triggers a syntax error
		* tv.s.len is 10 and tv.wcount is 5.
		* it must be restored to 6 and 3 each.
		*/

		/* restore the top level function block as it's first captured in this function */
		clear_funblk_inners(hak);
		HAK_ASSERT(hak, hak->c->funblk.depth == 0);
		hak->c->funblk.info[0] = top_funblk_saved;
		hak->c->tv.s.len = top_funblk_saved.tmprlen;
		hak->c->tv.wcount = top_funblk_saved.tmprcnt;
	}

	return -1;
}
