/* radare - LGPL - Copyright 2010-2026 - pancake */

#include <r_egg.h>

#define CTX egg->context

static inline bool is_var(char *x) {
	return x[0] == '.' || ((x[0] == '*' || x[0] == '&') && x[1] == '.');
}

static inline bool is_space(char c) {
	return c == ' ' || c == '\t' || c == '\n' || c == '\r';
}

static inline int is_op(char x) {
	switch (x) {
	case '-':
	case '+':
		return 1;
	case '*':
	case '/':
		return 2;
	case '^':
	case '|':
	case '&':
		return 3;
	default:
		return 0;
	}
}

static inline int get_op(char **pos) {
	while (**pos && ! (is_op (**pos) && !is_var (*pos))) {
		(*pos)++;
	}
	return (**pos)? ((is_op (**pos)) + 1): 0;
}

static void rcc_pushstr(REgg *egg, char *str, int filter);
static void rcc_context(REgg *egg, int delta);

enum {
	LANG_MODE_NORMAL = 0,
	LANG_MODE_ALIAS,
	LANG_MODE_INCLUDE,
	LANG_MODE_DATA,
	LANG_MODE_INLINE,
	LANG_MODE_NAKED,
	LANG_MODE_FASTCALL,
	LANG_MODE_SYSCALL,
	LANG_MODE_SYSCALLBODY,
	LANG_MODE_GOTO,
	LANG_MODE_LAST
};

static char *find_include(const char *prefix, const char *file) {
	char *pfx = NULL, *ret = NULL, *env = r_sys_getenv (R_EGG_INCDIR_ENV);
	if (!prefix) {
		prefix = "";
	}
	if (*prefix == '$') {
		char *out = r_sys_getenv (prefix + 1);
		pfx = out? out: strdup ("");
	} else {
		pfx = strdup (prefix);
		if (!pfx) {
			free (env);
			return NULL;
		}
	}

	if (env) {
		char *str, *ptr = strchr (env, ':');
		// eprintf ("MUST FIND IN PATH (%s)\n", env);
		str = env;
		while (str) {
			if (ptr) {
				*ptr = 0;
			}
			free (ret);
			ret = r_str_appendf (NULL, "%s/%s", pfx, file);
			{
				char *filepath = r_str_appendf (NULL, "%s/%s/%s", str, pfx, file);
				// eprintf ("try (%s)\n", filepath);
				if (r_file_exists (filepath)) {
					free (env);
					free (pfx);
					free (ret);
					return filepath;
				}
				free (filepath);
			}
			if (!ptr) {
				break;
			}
			str = ptr + 1;
			ptr = strchr (str, ':');
		}
		free (env);
	} else {
		ret = r_str_appendf (NULL, "%s/%s", pfx, file);
	}
	free (pfx);
	return ret;
}

R_API void r_egg_lang_init(REgg *egg) {
	egg->lang.varsize = 'l';
	/* do call or inline it? */ // BOOL
	egg->lang.docall = 1;
	egg->lang.line = 1;
	egg->lang.file = strdup ("stdin");
	egg->lang.oc = '\n';
	egg->lang.mode = LANG_MODE_NORMAL;
}

R_API void r_egg_lang_fini(REgg *egg) {
	int i, len;

	for (i = 0; i < egg->lang.nsyscalls; i++) {
		R_FREE (egg->lang.syscalls[i].name);
		R_FREE (egg->lang.syscalls[i].arg);
	}
	for (i = 0; i < egg->lang.nalias; i++) {
		R_FREE (egg->lang.aliases[i].name);
		R_FREE (egg->lang.aliases[i].content);
	}
	for (i = 0; i < egg->lang.ninlines; i++) {
		R_FREE (egg->lang.inlines[i].name);
		R_FREE (egg->lang.inlines[i].body);
	}
	len = sizeof (egg->lang.ctxpush) / sizeof (char *);
	for (i = 0; i < len; i++) {
		R_FREE (egg->lang.ctxpush[i]);
	}
	len = sizeof (egg->lang.inlines) / sizeof (egg->lang.inlines[0]);
	for (i = 0; i < len; i++) {
		R_FREE (egg->lang.inlines[i].name);
		R_FREE (egg->lang.inlines[i].body);
	}
	len = sizeof (egg->lang.aliases) / sizeof (egg->lang.aliases[0]);
	for (i = 0; i < len; i++) {
		R_FREE (egg->lang.aliases[i].name);
		R_FREE (egg->lang.aliases[i].content);
	}
	len = sizeof (egg->lang.nested) / sizeof (char *);
	for (i = 0; i < len; i++) {
		R_FREE (egg->lang.nested[i]);
	}
	len = sizeof (egg->lang.nested_callname) / sizeof (char *);
	for (i = 0; i < len; i++) {
		R_FREE (egg->lang.nested_callname[i]);
	}
	R_FREE (egg->lang.conditionstr);
	R_FREE (egg->lang.syscallbody);
	R_FREE (egg->lang.includefile);
	R_FREE (egg->lang.setenviron);
	R_FREE (egg->lang.mathline);
	R_FREE (egg->lang.callname);
	R_FREE (egg->lang.endframe);
	R_FREE (egg->lang.file);
	R_FREE (egg->lang.dstvar);
	R_FREE (egg->lang.dstval);
	R_FREE (egg->lang.includedir);
}

R_API void r_egg_lang_include_path(REgg *egg, const char *path) {
	char *tmp_ptr = NULL;
	char *env = r_sys_getenv (R_EGG_INCDIR_ENV);
	if (!env || !*env) {
		r_egg_lang_include_init (egg);
		free (env);
		env = r_sys_getenv (R_EGG_INCDIR_ENV);
		tmp_ptr = env;
	}
	env = r_str_appendf (NULL, "%s:%s", path, env);
	free (tmp_ptr);
	r_sys_setenv (R_EGG_INCDIR_ENV, env);
	free (env);
}

R_API void r_egg_lang_include_init(REgg *egg) {
	char *pfx = r_sys_prefix (NULL);
	char *s = r_str_newf (".:%s/%s", pfx, R_EGG_INCDIR_PATH);
	free (pfx);
	r_sys_setenv (R_EGG_INCDIR_ENV, s);
	free (s);
}

static void rcc_set_callname(REgg *egg, const char *s) {
	R_FREE (egg->lang.callname);
	egg->lang.nargs = 0;
	egg->lang.callname = r_str_trim_dup (s);
	egg->lang.pushargs = ! ((!strcmp (s, "goto")) || (!strcmp (s, "break")));
}

static void rcc_reset_callname(REgg *egg) {
	R_FREE (egg->lang.callname);
	egg->lang.nargs = 0;
}

static void rcc_fastcall(REgg *egg, bool with_body) {
	REggEmit *e = egg->remit;
	char *value = egg->lang.dstval? r_str_trim_dup (egg->lang.dstval): NULL;
	if (!with_body && egg->lang.dstvar && R_STR_ISNOTEMPTY (value)) {
		e->equ (egg, egg->lang.dstvar, value);
	}
	if (with_body && egg->lang.dstvar) {
		r_egg_printf (egg, "%s:\n", egg->lang.dstvar);
		rcc_context (egg, 1);
	}
	R_FREE (value);
	R_FREE (egg->lang.dstvar);
	R_FREE (egg->lang.dstval);
	egg->lang.mode = LANG_MODE_NORMAL;
	egg->lang.elem[0] = '\0';
	egg->lang.elem_n = 0;
	egg->lang.ndstval = 0;
	egg->lang.slurp = 0;
}

#define SYNTAX_ATT 0
#if SYNTAX_ATT
#define FRAME_FMT ".LC%d_%d_frame%d"
#define FRAME_END_FMT ".LC%d_%d_end_frame%d"
#else
#define FRAME_FMT "__%d_%d_frame%d"
#define FRAME_END_FMT "__%d_%d_end_frame%d"
#endif

static const char *find_alias(REgg *egg, const char *str) {
	// do not forget to free return strings to avoid memory leak
	char *p = (char *)str;
	int i;
	if (*str == '"') {
		return strdup (str);
	}
	// strings could not means aliases
	while (*p && !is_space (*p)) {
		p++;
	}
	*p = '\x00';
	for (i = 0; i < egg->lang.nalias; i++) {
		const char *name = egg->lang.aliases[i].name;
		if (name && !strcmp (str, name)) {
			return strdup (egg->lang.aliases[i].content);
		}
	}
	return NULL;
	// only strings or alias could return valuable data
}

static void rcc_internal_mathop(REgg *egg, const char *ptr, char *ep, char op) {
	char *q, *oldp;
	const char *p;
	char *allocated = NULL; // extra heap pointer that needs freeing
	char type = ' ';
	oldp = q = strdup (ptr);
	if (get_op (&q)) {
		*q = '\x00';
	}
	REggEmit *e = egg->remit;
	p = r_str_trim_head_ro (oldp);
	/* also strip trailing whitespace in-place (we own oldp) */
	{
		char *end = (char *)p + strlen (p);
		while (end > p && (end[-1] == ' ' || end[-1] == '\t')) {
			end--;
			*end = '\0';
		}
	}
	if (is_var ((char *)p)) {
		allocated = r_egg_mkvar (egg, NULL, p, 0);
		p = allocated;
		if (egg->lang.varxs == '*') {
			e->load (egg, p, egg->lang.varsize);
			R_FREE (allocated);
			allocated = strdup (e->regs (egg, 0));
			p = allocated;
			// XXX: which will go wrong in arm
			// for reg used in emit.load in arm is r7 not r0
		} else if (egg->lang.varxs == '&') {
			e->load_ptr (egg, p);
			R_FREE (allocated);
			allocated = strdup (e->regs (egg, 0));
			p = allocated;
		}
		type = ' ';
	} else {
		type = '$';
	}
	if (p && *p) {
		e->mathop (egg, op, egg->lang.varsize, type, p, ep);
	}
	R_FREE (allocated);
	R_FREE (oldp);
	R_FREE (ep);
}

/*
 * level 2: + -
 * level 3: * /
 * level 4: ^ $ |
 */
static void rcc_mathop(REgg *egg, char **pos, int level) {
	REggEmit *e = egg->remit;
	int op_ret = level;
	char op, *next_pos;

	*pos = (char *)r_str_trim_head_ro (*pos);
	next_pos = *pos + 1;

	do {
		op = (is_op (**pos) && ! (is_var (*pos)))? **pos: '=';
		*pos = (is_op (**pos) && ! (is_var (*pos)))? *pos + 1: *pos;
		op_ret = get_op (&next_pos);
		if (op_ret > level) {
			rcc_mathop (egg, pos, op_ret);
			rcc_internal_mathop (egg, e->regs (egg, op_ret - 1),
				strdup (e->regs (egg, level - 1)), op);
			next_pos = *pos + 1;
		} else {
			rcc_internal_mathop (egg, *pos, strdup (e->regs (egg, level - 1)), op);
			*pos = next_pos;
			next_pos++;
		}
	} while (**pos && op_ret >= level);

	/* following code block sould not handle '-' and '/' well
		if (op_ret < level) {
			rcc_internal_mathop (egg, p, strdup (e->regs (egg, level-1)), '=');
			return;
		}
		op = *pos, *pos = '\x00', (*pos)++;
		rcc_mathop (egg, pos, op_ret);
		if (op_ret > level) {
			rcc_internal_mathop (egg, p, strdup (e->regs (egg, op_ret-1)), op);
			rcc_internal_mathop (egg, (char *)e->regs (egg, op_ret-1),
					strdup (e->regs (egg, level-1)), '=');
		} else { rcc_internal_mathop (egg, p, strdup (e->regs (egg, level-1)), op); }
	 */
}

static void rcc_pusharg(REgg *egg, char *str) {
	REggEmit *e = egg->remit;
	char *p = r_egg_mkvar (egg, NULL, str, 0);
	if (!p) {
		return;
	}
	R_FREE (egg->lang.ctxpush[CTX]);
	egg->lang.ctxpush[CTX] = strdup (p); // INDEX IT WITH NARGS OR CONTEXT? !?
	egg->lang.nargs++;
	if (egg->lang.pushargs) {
		e->push_arg (egg, egg->lang.varxs, egg->lang.nargs, p);
	}
	// egg->lang.ctxpush[context+egg->lang.nbrackets] = strdup (str); // use egg->lang.nargs??? (in callname)
	free (p);
}

static void rcc_element(REgg *egg, char *str) {
	if (!egg || !str) {
		return;
	}
	REggEmit *e = egg->remit;
	char *ptr, *p = str + strlen (str);
	int inside = 0;
	int num, num2;
	int i;

	if (CTX) {
		if (egg->lang.slurp == '"') {
			if (egg->lang.mode == LANG_MODE_NORMAL) {
				if (!egg->lang.dstvar) {
					egg->lang.dstvar = strdup (".fix0");
				}
				rcc_pushstr (egg, str, 1);
			}
		} else {
			if (egg->lang.callname) {
				if (strstr (egg->lang.callname, "while") || strstr (egg->lang.callname, "if")) {
					egg->lang.conditionstr = strdup (str);
				}
			}
			egg->lang.nargs = 0;
			if (egg->lang.mode == LANG_MODE_GOTO) {
				egg->lang.mode = LANG_MODE_NORMAL; // XXX
			}
			while (p-- != str) {
				if (*p == '"') {
					inside ^= 1;
				} else if (*p == ',' && !inside) {
					*p = '\0';
					p = (char *)r_str_trim_head_ro (p + 1);
					rcc_pusharg (egg, p);
				}
			}
			rcc_pusharg (egg, str);
		}
	} else {
		switch (egg->lang.mode) {
		case LANG_MODE_ALIAS:
			if (!egg->lang.dstvar) {
				R_LOG_ERROR ("does not set name or content for alias");
				break;
			}
			e->equ (egg, egg->lang.dstvar, str);
			if (egg->lang.nalias > 255) {
				R_LOG_ERROR ("global-buffer-overflow in aliases");
				break;
			}
			for (i = 0; i < egg->lang.nalias; i++) {
				const char *name = egg->lang.aliases[i].name;
				if (name && !strcmp (egg->lang.dstvar, name)) {
					R_FREE (egg->lang.aliases[i].name);
					R_FREE (egg->lang.aliases[i].content);
					break;
				}
			}
			egg->lang.aliases[i].name = strdup (egg->lang.dstvar);
			egg->lang.aliases[i].content = strdup (str);
			egg->lang.nalias = (i == egg->lang.nalias)? (egg->lang.nalias + 1): egg->lang.nalias;
			// allow alias overwrite
			R_FREE (egg->lang.dstvar);
			egg->lang.mode = LANG_MODE_NORMAL;
			break;
		case LANG_MODE_SYSCALL:
			if (!egg->lang.dstvar) {
				R_LOG_ERROR ("does not set name or arg for syscall");
				break;
			}
			if (egg->lang.nsyscalls > 255) {
				R_LOG_ERROR ("global-buffer-overflow in syscalls");
				break;
			}
			{
				bool found = false;
				int idx = egg->lang.nsyscalls;
				for (i = 0; i < egg->lang.nsyscalls; i++) {
				if (egg->lang.syscalls[i].name && !strcmp (egg->lang.dstvar, egg->lang.syscalls[i].name)) {
						idx = i;
						found = true;
						break;
					}
				}
				// XXX the mem for name and arg are not freed - MEMLEAK
				R_FREE (egg->lang.syscalls[idx].name);
				R_FREE (egg->lang.syscalls[idx].arg);
				egg->lang.syscalls[idx].name = strdup (egg->lang.dstvar);
				egg->lang.syscalls[idx].arg = strdup (str);
				if (!found) {
					egg->lang.nsyscalls++;
				}
				R_FREE (egg->lang.dstvar);
			}
			break;
		case LANG_MODE_GOTO:
			egg->lang.elem[egg->lang.elem_n] = 0;
			e->jmp (egg, egg->lang.elem, 0);
			break;
		case LANG_MODE_INCLUDE:
			str = ptr = (char *)find_alias (egg, r_str_trim_head_ro (str));
			if (ptr) {
				if (strchr (ptr, '"')) {
					ptr = strchr (ptr, '"') + 1;
					if ((p = strchr (ptr, '"'))) {
						*p = '\x00';
					} else {
						R_LOG_ERROR ("loss back quote in include directory");
					}
					egg->lang.includedir = strdup (ptr);
				} else {
					R_LOG_ERROR ("wrong include syntax");
					// for must use string to symbolize directory
					egg->lang.includedir = NULL;
				}
			} else {
				egg->lang.includedir = NULL;
			}
			R_FREE (str);
			break;
		case LANG_MODE_FASTCALL:
			R_FREE (egg->lang.dstval);
			egg->lang.dstval = r_str_trim_dup (str);
			break;
		default:
			p = strchr (str, ',');
			if (p) {
				*p = '\0';
				num2 = atoi (p + 1);
			} else {
				num2 = 0;
			}
			num = atoi (str) + num2;
			egg->lang.stackframe = num;
			egg->lang.stackfixed = num2;
			if (egg->lang.mode != LANG_MODE_NAKED) {
				e->frame (egg, egg->lang.stackframe + egg->lang.stackfixed);
			}
		}
		egg->lang.elem[0] = 0;
		egg->lang.elem_n = 0;
	}
}

static void rcc_pushstr(REgg *egg, char *str, int filter) {
	int dotrim;
	int i, j, len, ch;
	REggEmit *e = egg->remit;

	e->comment (egg, "encode %s string (%s) (%s)",
		filter? "filtered": "unfiltered", str, egg->lang.callname);

	// fixed by izhuer
	if (filter) {
		for (i = 0; str[i]; i++) {
			dotrim = 0;
			if (str[i] == '\\') {
				switch (str[i + 1]) {
				case 't':
					str[i] = '\t';
					dotrim = 1;
					break;
				case 'n':
					str[i] = '\n';
					dotrim = 1;
					break;
				case 'e':
					str[i] = '\x1b';
					dotrim = 1;
					break;
				case 'x':
					ch = r_hex_pair2bin (str + i + 2);
					if (ch == -1) {
						R_LOG_ERROR ("%s:%d Error string format",
							egg->lang.file, egg->lang.line);
					}
					str[i] = (char)ch;
					dotrim = 3;
					break;
				default:
					break;
				}
				if (dotrim) {
					memmove (str + i + 1, str + i + dotrim + 1,
						strlen (str + i + dotrim + 1) + 1);
				}
				// DO NOT forget the '\x00' terminate char
			}
		}
	}

	len = strlen (str);
	j = (len - len % e->size) + e->size;
	e->set_string (egg, egg->lang.dstvar, str, j);
	R_FREE (egg->lang.dstvar);
}

/* Emit a branch-target label in a backend-appropriate form. Backends that
 * assemble to native code keep the classic ".S" style "name:\n"; backends
 * like ESIL override the callback to drop labels entirely. */
static void emit_label(REgg *egg, const char *name) {
	if (egg->remit && egg->remit->label) {
		egg->remit->label (egg, name);
	} else {
		r_egg_printf (egg, "%s:\n", name);
	}
}

/* Return the integer value of the leading (decimal or hex) number in s.
 * Unlike r_num_math this stops at the first non-digit character so that
 * trailing tokens like "==" or whitespace do not affect the result. This
 * is used when resolving `.varN` / `.argN` / `.regN` names out of strings
 * that may still contain comparison operators. */
static int parse_leading_idx(const char *s) {
	int n = 0;
	int i = 0;
	int base = 10;
	if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
		base = 16;
		i = 2;
	}
	for (; s[i]; i++) {
		int digit;
		if (isdigit ((unsigned char)s[i])) {
			digit = s[i] - '0';
		} else if (base == 16 && s[i] >= 'a' && s[i] <= 'f') {
			digit = s[i] - 'a' + 10;
		} else if (base == 16 && s[i] >= 'A' && s[i] <= 'F') {
			digit = s[i] - 'A' + 10;
		} else {
			break;
		}
		if (digit >= base) {
			break;
		}
		n = (n * base) + digit;
	}
	return n;
}

static void r_egg_mkvar_name(REgg *egg, RStrBuf *out, const char *name, int delta) {
	REggEmit *e = egg->remit;
	int i;
	if (*name == '%') {
		/* Explicit raw native register: `.%name` skips the alias
		 * table and copies the register name verbatim. This keeps
		 * the unambiguous "target register" syntax separate from
		 * `.varN`, `.regN` and user-defined aliases. */
		r_strbuf_set (out, name + 1);
	} else if (r_str_startswith (name, "ret")) {
		r_strbuf_set (out, e->retvar);
	} else if (r_str_startswith (name, "fix")) {
		int idx = parse_leading_idx (name + 3) + delta + e->size;
		e->get_var (egg, 0, out, idx - egg->lang.stackfixed);
	} else if (r_str_startswith (name, "var")) {
		int idx = parse_leading_idx (name + 3) + delta + e->size;
		e->get_var (egg, 0, out, idx);
	} else if (r_str_startswith (name, "rarg")) {
		if (e->get_arg) {
			int idx = parse_leading_idx (name + 4);
			e->get_arg (egg, out, idx);
		}
	} else if (r_str_startswith (name, "arg")) {
		if (name[3]) {
			if (egg->lang.stackframe == 0) {
				e->get_var (egg, 1, out, 4); // idx-4);
			} else {
				int idx = parse_leading_idx (name + 3) + delta + e->size;
				e->get_var (egg, 2, out, idx + 4);
			}
		} else if (egg->lang.callname) {
			for (i = 0; i < egg->lang.nsyscalls; i++) {
				const char *scname = egg->lang.syscalls[i].name;
				if (scname && !strcmp (scname, egg->lang.callname)) {
					r_strbuf_set (out, r_str_get (egg->lang.syscalls[i].arg));
					return;
				}
			}
			R_LOG_ERROR ("Unknown arg for syscall '%s'", r_str_get (egg->lang.callname));
		} else {
			R_LOG_WARN ("No CallName '%s'", r_str_get (egg->lang.callname));
		}
	} else if (r_str_startswith (name, "reg")) {
		int idx = parse_leading_idx (name + 3);
		if (egg->lang.attsyntax) {
			r_strbuf_setf (out, "%%%s", e->regs (egg, idx));
		} else {
			r_strbuf_set (out, e->regs (egg, idx));
		}
	} else {
		for (i = 0; i < egg->lang.nalias; i++) {
			const char *aname = egg->lang.aliases[i].name;
			if (aname && !strcmp (name, aname)) {
				const char *content = egg->lang.aliases[i].content;
				r_strbuf_set (out, content? content: "");
				return;
			}
		}
		R_LOG_ERROR ("Unknown name '.%s' (use .%%%s for a raw register or `%s@alias(...)`)",
			name, name, name);
	}
}

static bool r_egg_mkvar_buf(REgg *egg, RStrBuf *out, const char *_str, int delta) {
	int len, qi;
	char *oldstr = NULL, *str = NULL, *q;

	delta += egg->lang.stackfixed; // XXX can be problematic
	if (!_str) {
		return false; /* fix segfault, but not badparsing */
	}
	str = oldstr = r_str_trim_dup (_str);
	if (!oldstr) {
		return false;
	}
	r_strbuf_set (out, "");
	if ((q = strchr (str, ':'))) {
		*q = '\0';
		qi = atoi (q + 1);
		egg->lang.varsize = (qi == 1)? 'b': 'l';
	} else {
		egg->lang.varsize = 'l';
	}
	if (*str == '*' || *str == '&') {
		egg->lang.varxs = *str;
		str++;
	} else {
		egg->lang.varxs = 0;
	}
	if (str[0] == '.') {
		r_egg_mkvar_name (egg, out, str + 1, delta);
		free (oldstr);
		return true;
	}
	if (*str == '"' || *str == '\'') {
		int mustfilter = *str == '"';
		/* TODO: check for room in egg->lang.stackfixed area */
		str++;
		len = strlen (str) - 1;
		if (!egg->lang.stackfixed || egg->lang.stackfixed < len) {
			R_LOG_WARN ("No room in the static stackframe! (%d must be %d)",
				egg->lang.stackfixed, len);
		}
		str[len] = '\0';
		char *foo = r_str_newf (".fix%d", egg->lang.nargs * 16); /* XXX FIX DELTA !!!1 */
		if (!foo) {
			free (oldstr);
			return false;
		}
		char *tmp = egg->lang.dstvar;
		egg->lang.dstvar = r_str_trim_dup (foo);
		rcc_pushstr (egg, str, mustfilter);
		free (egg->lang.dstvar);
		egg->lang.dstvar = tmp;
		bool ret = r_egg_mkvar_buf (egg, out, foo, 0);
		free (foo);
		free (oldstr);
		return ret;
	}
	r_strbuf_set (out, oldstr);
	free (oldstr);
	return true;
}

R_API char *r_egg_mkvar(REgg *egg, char *out, const char *_str, int delta) {
	RStrBuf sb;
	r_strbuf_init (&sb);
	if (!r_egg_mkvar_buf (egg, &sb, _str, delta)) {
		r_strbuf_fini (&sb);
		return NULL;
	}
	if (out) {
		r_str_ncpy (out, R_STRBUF_SAFEGET (&sb), 32);
	}
	char *ret = r_strbuf_drain_nofree (&sb);
	r_strbuf_fini (&sb);
	return ret;
}

static void rcc_fun(REgg *egg, char *str) {
	char *ptr, *ptr2;
	REggEmit *e = egg->remit;
	if (CTX) {
		ptr = strchr (str, '=');
		if (ptr) {
			*ptr++ = '\0';
			free (egg->lang.dstvar);
			egg->lang.dstvar = r_str_trim_dup (str);
			const char *callname = r_str_trim_head_ro (ptr);
			if (*callname) {
				rcc_set_callname (egg, callname);
			}
		} else {
			rcc_set_callname (egg, str);
			egg->remit->comment (egg, "rcc_fun %d (%s)", CTX, egg->lang.callname);
		}
	} else {
		ptr = strchr (str, '@');
		if (ptr) {
			*ptr++ = '\0';
			egg->lang.mode = LANG_MODE_NORMAL;
			if (strstr (ptr, "env")) {
				// eprintf ("SETENV (%s)\n", str);
				free (egg->lang.setenviron);
				egg->lang.setenviron = r_str_trim_dup (str);
				egg->lang.slurp = 0;
			} else if (strstr (ptr, "fastcall")) {
				free (egg->lang.dstvar);
				egg->lang.dstvar = r_str_trim_dup (str);
				R_FREE (egg->lang.dstval);
				egg->lang.mode = LANG_MODE_FASTCALL;
			} else if (strstr (ptr, "syscall")) {
				if (*str) {
					egg->lang.mode = LANG_MODE_SYSCALL;
					egg->lang.dstvar = r_str_trim_dup (str);
				} else {
					egg->lang.mode = LANG_MODE_INLINE;
					char *old_syscallbody = egg->lang.syscallbody;
					egg->lang.syscallbody = malloc (4096); // XXX hardcoded size
					if (egg->lang.syscallbody) {
						egg->lang.dstval = egg->lang.syscallbody;
						*egg->lang.syscallbody = '\0';
					}
					free (old_syscallbody);
					R_FREE (egg->lang.dstvar);
					egg->lang.ndstval = 0;
				}
			} else if (strstr (ptr, "include")) {
				egg->lang.mode = LANG_MODE_INCLUDE;
				free (egg->lang.includefile);
				egg->lang.includefile = r_str_trim_dup (str);
				// egg->lang.slurp = 0;
				// try to deal with alias
			} else if (strstr (ptr, "alias")) {
				egg->lang.mode = LANG_MODE_ALIAS;
				ptr2 = egg->lang.dstvar = r_str_trim_dup (str);
				while (*ptr2 && !is_space (*ptr2)) {
					ptr2++;
				}
				*ptr2 = '\x00';
				// for aliases must be valid and accurate strings
			} else if (strstr (ptr, "data")) {
				egg->lang.mode = LANG_MODE_DATA;
				egg->lang.ndstval = 0;
				egg->lang.dstvar = r_str_trim_dup (str);
				egg->lang.dstval = malloc (4096);
			} else if (strstr (ptr, "naked")) {
				egg->lang.mode = LANG_MODE_NAKED;
				/*
				free (egg->lang.dstvar);
				egg->lang.dstvar = r_str_trim_dup (str);
				egg->lang.dstval = malloc (4096);
				egg->lang.ndstval = 0;
				 */
				r_egg_printf (egg, "%s:\n", str);
			} else if (strstr (ptr, "inline")) {
				egg->lang.mode = LANG_MODE_INLINE;
				free (egg->lang.dstvar);
				egg->lang.dstvar = r_str_trim_dup (str);
				egg->lang.dstval = malloc (4096);
				egg->lang.ndstval = 0;
			} else {
				// naked label
				if (*ptr) {
					r_egg_printf (egg, "\n.%s %s\n", ptr, str);
				}
				r_egg_printf (egg, "%s:\n", str);
			}
		} else {
			// e->jmp (egg, egg->lang.ctxpush[context], 0);
			if (CTX > 0) {
				// WTF?
				eprintf ("LABEL %d\n", CTX);
				r_egg_printf (egg, "\n%s:\n", str);
			} else {
				if (!strcmp (str, "goto")) {
					egg->lang.mode = LANG_MODE_GOTO;
				} else {
					// call () // or maybe jmp?
					e->call (egg, str, 0);
				}
			}
		}
	}
}

#if 0
static void shownested(void) {
	int i;
	eprintf ("[[[NESTED %d]]] ", context);
	for (i = 0; egg->lang.nested[i]; i++) {
		eprintf ("%s ", egg->lang.nested[i]);
	}
	eprintf ("\n");
}
#endif

static void set_nested(REgg *egg, const char *s) {
	int i = 0;
	if (CTX < 1) {
		return;
	}
	free (egg->lang.nested[CTX]);
	egg->lang.nested[CTX] = strdup (s);
	// egg->lang.nestedi[c]++;
	// seems not need to increase egg->lang.nestedi[c]
	/** clear inner levels **/
	for (i = 1; i < 10; i++) {
		// egg->lang.nestedi[context+i] = 0;
		R_FREE (egg->lang.nested[CTX + i]);
	}
}

// nestedi[ctx] counts every block opened at that level, ifelse_table[ctx] only has room for the first 32
static char **ifelse_slot(REgg *egg, int ctx) {
	int col = egg->lang.nestedi[ctx] - 1;
	if (col < 0 || col >= R_ARRAY_SIZE (egg->lang.ifelse_table[ctx])) {
		R_LOG_ERROR ("%s:%d too many blocks in this scope for an if", egg->lang.file, egg->lang.line);
		return NULL;
	}
	return &egg->lang.ifelse_table[ctx][col];
}

static void rcc_context(REgg *egg, int delta) {
	REggEmit *emit = egg->remit;

	if (CTX > 31 || CTX < 0) {
		return;
	}

	if (delta > 0) {
		egg->lang.nestedi[CTX]++;
		R_FREE (egg->lang.nested_callname[CTX]);
		if (egg->lang.callname) {
			egg->lang.nested_callname[CTX] = strdup (egg->lang.callname);
		}
	}
	if (egg->lang.callname && CTX > 0) { // && delta>0) {
		// set_nested (callname);
		// eprintf (" - - - - - - -  set nested d=%d c=%d (%s)\n", delta, context-1, callname);
		// shownested ();
	}
	CTX += delta;
	egg->lang.lastctxdelta = delta;

	if (CTX == 0 && delta < 0) {
		if (egg->lang.mode != LANG_MODE_NAKED) {
			emit->frame_end (egg, egg->lang.stackframe + egg->lang.stackfixed, egg->lang.nbrackets);
		}
		if (egg->lang.mode == LANG_MODE_NORMAL) { /* XXX : commenting this makes hello.r unhappy! TODO: find a cleaner alternative */
			egg->lang.stackframe = 0;
		}
		egg->lang.mode = LANG_MODE_NORMAL;
	} else {
		/* conditional block */
		// eprintf ("Callname is (%s)\n", callname);
		const char *elm = r_str_trim_head_ro (egg->lang.elem);
		// const char *cn = callname;
		// seems cn is useless in nowadays content
// if (egg->lang.nested[context-1])
#if 0
		if (delta < 0 && context > 0) {
			eprintf ("close bracket foo!!!\n");
			shownested ();
			cn = strdup (egg->lang.nested[context - 1]);
			eprintf ("STATEMENT cn=(%s) idx=%d (%s)\n", cn, context - 1, egg->lang.nested[context - 1]);
			eprintf ("CNTXXXPUSH (%s)\n", egg->lang.ctxpush[context - 1]);
#if 0
			if (!strcmp (cn, "while")) {
				emit->while_end (egg, get_frame_label (context - 1));
				// char *var = get_frame_label (0);
				// emit->jmp (egg, var, 0);
				return;
			}
#endif
		}
#endif
		// eprintf ("ELEM (%s)\n", elm);
		// eprintf ("END BLOCK %d, (%s)\n", context, egg->lang.nested[context-1]);
		// eprintf ("CN = (%s) %d (%s) delta=%d\n", cn, context, egg->lang.nested[context-1], delta);
		if (egg->lang.callname) {
			/* TODO: this must be an array */
			char *b, *g, *e, *n;
			emit->comment (egg, "cond frame %s (%s)", egg->lang.callname, elm);
			/* TODO: simplify with a single for */
			if (egg->lang.conditionstr) {
				b = strchr (egg->lang.conditionstr, '<'); /* below */
				g = strchr (egg->lang.conditionstr, '>'); /* greater */
				e = strchr (egg->lang.conditionstr, '='); /* equal */
				n = strchr (egg->lang.conditionstr, '!'); /* negate */
			} else {
				b = g = e = n = NULL;
			}
			if (!strcmp (egg->lang.callname, "while")) {
				char *lab = r_str_newf ("__begin_%d_%d_%d", egg->lang.nfunctions,
					CTX - 1, egg->lang.nestedi[CTX - 1] - 1);
				// the egg->lang.nestedi[CTX-1] has increased
				// so we should decrease it in label
				RStrBuf frame;
				r_strbuf_init (&frame);
				emit->get_while_end (egg, &frame, egg->lang.ctxpush[CTX - 1], lab); // get_frame_label (2));
				// get_frame_label (2));
				// eprintf ("------ (%s)\n", egg->lang.ctxpush[context-1]);
				// free (egg->lang.endframe);
				// XXX: egg->lang.endframe is deprecated, must use set_nested only
				if (delta > 0) {
					set_nested (egg, R_STRBUF_SAFEGET (&frame));
				}
				r_strbuf_fini (&frame);
				free (lab);
				rcc_set_callname (egg, "if"); // append 'if' body
			}
			if (!strcmp (egg->lang.callname, "if")) {
				// emit->branch (egg, b, g, e, n, egg->lang.varsize, get_end_frame_label (egg));
				// HACK HACK :D
				// nestede[CTX-1] = strdup (str);
				// where give nestede value
				char *label = r_str_newf ("__end_%d_%d_%d", egg->lang.nfunctions, CTX - 1, egg->lang.nestedi[CTX - 1] - 1);
				emit->branch (egg, b, g, e, n, egg->lang.varsize, label);
				free (label);
				if (CTX > 0) {
					/* XXX .. */
				}
				rcc_reset_callname (egg);
				R_FREE (egg->lang.conditionstr);
			} // else eprintf ("Unknown statement (%s)(%s)\n", cn, elem);
		} // handle '{ ..'
	}
}

static int parsedatachar(REgg *egg, char c) {
	char *str;
	int i, j;

	if (!egg->lang.dstval) {
		return 0;
	}
	/* skip until '{' */
	if (c == '{') { /* XXX: repeated code!! */
		rcc_context (egg, 1);
		if (++ (egg->lang.inlinectr) == 1) {
			return egg->lang.ndstval = 0;
		}
	} else if (egg->lang.inlinectr == 0) {
		/* capture value between parenthesis foo@data (NNN) { ... } */
		if (c == ')') {
			egg->lang.dstval[egg->lang.ndstval] = '\0';
			egg->lang.stackframe = atoi (egg->lang.dstval);
			egg->lang.ndstval = 0;
		} else {
			if (egg->lang.ndstval + 1 >= 4096) {
				R_LOG_ERROR ("data size too large");
				return -1;
			}
			egg->lang.dstval[egg->lang.ndstval++] = c;
		}
		return 0;
	}
	/* capture body */
	if (c == '}') { /* XXX: repeated code!! */
		if (CTX < 2) {
			egg->lang.inlinectr = 0;
			rcc_context (egg, -1);
			egg->lang.slurp = 0;
			egg->lang.mode = LANG_MODE_NORMAL;
			/* register */
			if (egg->lang.dstval && egg->lang.dstvar) {
				egg->lang.dstval[egg->lang.ndstval] = '\0';
				egg->remit->comment (egg, "data (%s)(%s)size=(%d)\n",
					egg->lang.dstvar, egg->lang.dstval, egg->lang.stackframe);
				r_egg_printf (egg, ".data\n");
				for (str = egg->lang.dstval; is_space (*str); str++) {
					;
				}
				j = (egg->lang.stackframe)? egg->lang.stackframe: 1;
				/* emit label */
				r_egg_printf (egg, "%s:\n", egg->lang.dstvar);
				for (i = 1; i <= j; i++) {
					if (*str == '"') {
						r_egg_printf (egg, ".ascii %s%s\n", egg->lang.dstval, (i == j)? "\"\\x00\"": "");
					} else {
						r_egg_printf (egg, ".long %s\n", egg->lang.dstval);
					}
				}
				r_egg_printf (egg, ".text\n");
				R_FREE (egg->lang.dstvar);
				R_FREE (egg->lang.dstval);
				egg->lang.ndstval = 0;
				CTX = 0;
				return 1;
			}
		}
	}
	if (egg->lang.dstval) {
		if (egg->lang.ndstval + 1 >= 4096) {
			R_LOG_ERROR ("data block too large");
			return -1;
		}
		egg->lang.dstval[egg->lang.ndstval++] = c;
	}
	return 0;
}

static int parseinlinechar(REgg *egg, char c) {
	/* skip until '{' */
	if (c == '{') { /* XXX: repeated code!! */
		rcc_context (egg, 1);
		egg->lang.inlinectr++;
		if (egg->lang.inlinectr == 1) {
			return 0;
		}
	} else if (egg->lang.inlinectr == 0) {
		return 0;
	}

	/* capture body */
	if (c == '}') { /* XXX: repeated code!! */
		if (CTX < 2) {
			rcc_context (egg, -1);
			egg->lang.slurp = 0;
			egg->lang.mode = LANG_MODE_NORMAL;
			egg->lang.inlinectr = 0;
			if (!egg->lang.dstvar && egg->lang.dstval == egg->lang.syscallbody) {
				egg->lang.dstval = NULL;
				return 1;
			} else /* register */
				if (egg->lang.dstval && egg->lang.dstvar) {
					if (egg->lang.ninlines >= R_ARRAY_SIZE (egg->lang.inlines)) {
						R_LOG_ERROR ("Too many inline functions");
						R_FREE (egg->lang.dstvar);
						R_FREE (egg->lang.dstval);
						return -1;
					}
					egg->lang.dstval[egg->lang.ndstval] = '\0';
					// printf (" /* END OF LANG_MODE_INLINE (%s)(%s) */\n", egg->lang.dstvar, egg->lang.dstval);
					egg->lang.inlines[egg->lang.ninlines].name = r_str_trim_dup (egg->lang.dstvar);
					egg->lang.inlines[egg->lang.ninlines].body = r_str_trim_dup (egg->lang.dstval);
					egg->lang.ninlines++;
					R_FREE (egg->lang.dstvar);
					R_FREE (egg->lang.dstval);
					return 1;
				}
			R_LOG_ERROR ("Cannot parse expression");
		}
	}
	if (egg->lang.dstval) {
		if (egg->lang.ndstval + 2 >= 4096) {
			R_LOG_ERROR ("inline block too large");
			return -1;
		}
		egg->lang.dstval[egg->lang.ndstval++] = c;
		egg->lang.dstval[egg->lang.ndstval] = 0;
	}
	return 0;
}

static int rcc_parse(REgg *egg, const char *s) {
	for (; *s; s++) {
		if (r_egg_lang_parsechar (egg, *s) < 0) {
			return -1;
		}
	}
	return 0;
}

/* TODO: split this function into several ones..quite long fun */
static int rcc_next(REgg *egg) {
	const char *ocn;
	REggEmit *e = egg->remit;
	char *str = NULL, *p, *ptr;
	int i, ret = 0;

	if (egg->lang.setenviron) {
		egg->lang.elem[egg->lang.elem_n - 1] = 0;
		r_sys_setenv (egg->lang.setenviron, egg->lang.elem);
		R_FREE (egg->lang.setenviron);
		return 0;
	}
	if (egg->lang.includefile) {
		char *p, *q, *path;
		path = find_include (egg->lang.includedir, egg->lang.includefile);
		if (!path) {
			R_LOG_ERROR ("Cannot find include file '%s'", egg->lang.elem);
			return 0;
		}
		R_FREE (egg->lang.includefile);
		R_FREE (egg->lang.includedir);
		rcc_reset_callname (egg);
		p = q = r_file_slurp (path, NULL);
		if (p) {
			int oline = ++ (egg->lang.line);
			egg->lang.elem[0] = 0; // TODO: this must be a separate function
			egg->lang.elem_n = 0;
			egg->lang.line = 0;
			ret = rcc_parse (egg, p);
			free (q);
			egg->lang.line = oline;
		} else {
			R_LOG_ERROR ("Cannot find '%s'", path);
		}
		free (path);
		return ret;
	}
	egg->lang.docall = 1;
	if (egg->lang.callname) {
		if (!strcmp (egg->lang.callname, "goto")) {
			if (egg->lang.nargs != 1) {
				R_LOG_ERROR ("Invalid number of arguments for goto()");
				return 0;
			}
			e->jmp (egg, egg->lang.ctxpush[CTX], 0);
			rcc_reset_callname (egg);
			return 0;
		}
		if (!strcmp (egg->lang.callname, "break")) {
			e->trap (egg);
			rcc_reset_callname (egg);
			return 0;
		}
		ptr = strchr (egg->lang.callname, '=');
		if (ptr) {
			*ptr = '\0';
			// ocn = ptr+1; // what is the point of this?
		}
		ocn = r_str_trim_head_ro (egg->lang.callname);
		if (!ocn) {
			return 0;
		}
		str = r_egg_mkvar (egg, NULL, ocn, 0);
		if (!str) {
			R_LOG_ERROR ("Cannot mkvar");
			return 0;
		}
		if (*ocn == '.') {
			e->call (egg, str, 1);
		}
		if (!strcmp (str, "while")) {
			if (egg->lang.lastctxdelta >= 0) {
				R_LOG_ERROR ("Unsupported while syntax");
				free (str);
				return 0;
			}
			char *var = r_str_newf ("__begin_%d_%d_%d\n", egg->lang.nfunctions, CTX, egg->lang.nestedi[CTX - 1]);
			e->while_end (egg, var); // get_frame_label (1));
			free (var);
#if 0
			eprintf ("------------------------------------------ lastctx: %d\n", egg->lang.lastctxdelta);
			// TODO: the pushvar is required for the if () {} while (); constructions
			// char *pushvar = egg->lang.ctxpush[context+egg->lang.nbrackets-1];
			/* TODO: support to compare more than one expression (LOGICAL OR) */
			rcc_printf ("  pop %%eax\n");
			rcc_printf ("  cmp $0, %%eax\n");	// XXX MUST SUPPORT != 0 COMPARE HERE
			/* TODO : Simplify!! */
			// if (pushvar)
			// printf ("  push %s /* while push */\n", pushvar);
			if (egg->lang.lastctxdelta < 0) {
				rcc_printf ("  jnz %s\n", get_frame_label (1));
			} else {
				rcc_printf ("  jnz %s\n", get_frame_label (0));
			}
			// if (pushvar)
			// printf ("  pop %%"R_AX" /* while pop */\n");
#endif
			egg->lang.nargs = 0;
		} else {
			for (i = 0; i < egg->lang.nsyscalls; i++) {
				const char *name = egg->lang.syscalls[i].name;
				if (name && !strcmp (str, name)) {
					p = egg->lang.syscallbody;
					e->comment (egg, "set syscall args");
					e->syscall_args (egg, egg->lang.nargs);
					egg->lang.docall = 0;
					e->comment (egg, "syscall");
					r_egg_lang_parsechar (egg, '\n'); /* FIX parsing issue */
					if (p) {
						ret = rcc_parse (egg, p);
					} else {
						char *s = e->syscall (egg, egg->lang.nargs);
						if (s) {
							ret = rcc_parse (egg, s);
							free (s);
						} else {
							R_LOG_ERROR ("Cannot get @syscall payload");
						}
					}
					if (ret < 0) {
						goto beach;
					}
					egg->lang.docall = 0;
					break;
				}
			}
			if (egg->lang.docall) {
				for (i = 0; i < egg->lang.ninlines; i++) {
					if (!strcmp (str, egg->lang.inlines[i].name)) {
						char *body = egg->lang.inlines[i].body;
						egg->lang.docall = 0;
						if (!body) {
							R_LOG_ERROR ("Recursive expansion of inline '%s'", str);
							break;
						}
						// take the body out while expanding it, so an inline cannot call itself forever
						egg->lang.inlines[i].body = NULL;
						rcc_reset_callname (egg);
						e->comment (egg, "inline");
						r_egg_lang_parsechar (egg, '\n'); /* FIX parsing issue */
						ret = rcc_parse (egg, body);
						egg->lang.inlines[i].body = body;
						if (ret < 0) {
							goto beach;
						}
						egg->lang.docall = 0;
						break;
					}
				}
			}
			if (egg->lang.docall) {
				e->comment (egg, "call in egg->lang.mode %d", egg->lang.mode);
				e->call (egg, str, 0);
			}
		}
		if (egg->lang.nargs > 0) {
			e->restore_stack (egg, egg->lang.nargs * e->size);
		}

		/* store result of call */
		if (egg->lang.dstvar) {
			// if (egg->lang.mode != LANG_MODE_NAKED) {
			free (str);
			str = r_egg_mkvar (egg, NULL, egg->lang.dstvar, 0);
			if (R_STR_ISEMPTY (str)) {
				R_LOG_ERROR ("Cannot resolve variable '%s'", egg->lang.dstvar);
			} else {
				e->get_result (egg, str);
			}
			//}
			R_FREE (egg->lang.dstvar);
		}
		rcc_reset_callname (egg);
	} else { // handle mathop
		int vs = 'l';
		char type, *eq, *ptr = egg->lang.elem, *tmp;
		char compound_op = 0; // 0 = plain '=', otherwise +,-,*,/,|,&,^
		egg->lang.elem[egg->lang.elem_n] = '\0';
		ptr = (char *)r_str_trim_head_ro (ptr);
		if (*ptr) {
			eq = strchr (ptr, '=');
			if (eq && eq > ptr) {
				/* detect compound operators: +=, -=, *=, /=, |=, &=, ^= */
				char prev = eq[-1];
				if (prev == '+' || prev == '-' || prev == '*' || prev == '/'
				    || prev == '|' || prev == '&' || prev == '^') {
					compound_op = prev;
					eq[-1] = '\x00'; /* chop the operator off the LHS */
				}
			}
			if (eq) {
				vs = egg->lang.varsize;
				*eq = '\x00';
				e->mathop (egg, '=', vs, '$', "0", e->regs (egg, 1));
				// avoid situation that egg->lang.mathline starts with a single '-'
				egg->lang.mathline = r_str_trim_dup (eq + 1);
				tmp = egg->lang.mathline;
				rcc_mathop (egg, &tmp, 2);
				R_FREE (egg->lang.mathline);
				tmp = NULL;
				// following code block is too ugly, oh noes
				char *p = r_egg_mkvar (egg, NULL, ptr, 0);
				if (is_var (p)) {
					char *q = r_egg_mkvar (egg, NULL, p, 0);
					if (q) {
						free (p);
						p = q;
					}
					if (egg->lang.varxs == '*' || egg->lang.varxs == '&') {
						R_LOG_ERROR ("not support for *ptr in egg->lang.dstvar");
					}
					// XXX: Not support for pointer
					type = ' ';
				} else {
					type = '$';
				}
				if (compound_op) {
					/* emit: p <compound_op>= tmpreg */
					e->mathop (egg, compound_op, vs, type, e->regs (egg, 1), p);
				} else {
					e->mathop (egg, '=', vs, type, e->regs (egg, 1), p);
				}
				free (p);
			} else {
				if (!strcmp (ptr, "break")) { // handle 'break;'
					e->trap (egg);
					rcc_reset_callname (egg);
				} else {
					e->mathop (egg, '=', vs, '$', ptr, NULL);
				}
			}
		}
	}
beach:
	free (str);
	/* reset the element buffer so the next statement starts clean */
	egg->lang.elem[0] = '\0';
	egg->lang.elem_n = 0;
	return ret;
}

static bool rcc_element_append(REgg *egg, char c) {
	if ((unsigned int)egg->lang.elem_n >= sizeof (egg->lang.elem) - 1) {
		R_LOG_ERROR ("too large element");
		return false;
	}
	egg->lang.elem[egg->lang.elem_n++] = c;
	egg->lang.elem[egg->lang.elem_n] = 0;
	return true;
}

R_API int r_egg_lang_parsechar(REgg *egg, char c) {
	REggEmit *e = egg->remit;
	char *tmp_ptr = NULL;
	int i, j;
	if (c == '\n') {
		egg->lang.line++;
		egg->lang.elem_n = 0;
	}
	/* comments */
	if (egg->lang.skipline) {
		if (c != '\n') {
			egg->lang.oc = c;
			return 0;
		}
		egg->lang.skipline = 0;
	}
	if (egg->lang.mode == LANG_MODE_DATA) {
		return parsedatachar (egg, c);
	}
	if (egg->lang.mode == LANG_MODE_INLINE) {
		return parseinlinechar (egg, c);
	}
	if (egg->lang.mode == LANG_MODE_FASTCALL && CTX == 0) {
		switch (c) {
		case ';':
		case '\n':
			rcc_fastcall (egg, false);
			if (c != '\t' && c != ' ') {
				egg->lang.oc = c;
			}
			return 0;
		case '{':
			rcc_fastcall (egg, true);
			if (c != '\t' && c != ' ') {
				egg->lang.oc = c;
			}
			return 0;
		}
	}
	/* quotes */
	if (egg->lang.quoteline) {
		if (c != egg->lang.quoteline) {
			if (egg->lang.quotelinevar == 1) {
				if (c == '`') {
					egg->lang.elem[egg->lang.elem_n] = 0;
					egg->lang.elem_n = 0;
					tmp_ptr = r_egg_mkvar (egg, NULL, egg->lang.elem, 0);
					r_egg_printf (egg, "%s", tmp_ptr);
					free (tmp_ptr);
					egg->lang.quotelinevar = 0;
				} else {
					if (!rcc_element_append (egg, c)) {
						return -1;
					}
				}
			} else {
				if (c == '`') {
					egg->lang.elem_n = 0;
					egg->lang.quotelinevar = 1;
				} else {
					r_egg_printf (egg, "%c", c);
				}
			}
			egg->lang.oc = c;
			return 0;
		} else {
			r_egg_printf (egg, "\n");
			egg->lang.quoteline = 0;
		}
	}

	if (egg->lang.commentmode) {
		if (c == '/' && egg->lang.oc == '*') {
			egg->lang.commentmode = 0;
		}
		egg->lang.oc = c;
		return 0;
	} else if (c == '*' && egg->lang.oc == '/') {
		egg->lang.commentmode = 1;
	}
	if (egg->lang.slurp) {
		if (egg->lang.slurp != '"' && c == egg->lang.slurpin) { // only happend when (... (...)...)
			R_LOG_ERROR ("%s:%d Nesting of expressions not yet supported",
				egg->lang.file, egg->lang.line);
			return -1;
		}
		if (c == egg->lang.slurp && egg->lang.oc != '\\') { // close egg->lang.slurp
			egg->lang.elem[egg->lang.elem_n] = '\0';
			if (egg->lang.elem_n > 0) {
				rcc_element (egg, egg->lang.elem);
			} else if (!(egg->lang.mode == LANG_MODE_FASTCALL && CTX == 0)) {
				e->frame (egg, 0);
			}
			egg->lang.elem_n = 0;
			egg->lang.slurp = 0;
		} else {
			if (!rcc_element_append (egg, c)) {
				return -1;
			}
		}
		egg->lang.elem[egg->lang.elem_n] = '\0';
	} else {
		switch (c) {
		case ';':
			if (rcc_next (egg) < 0) {
				return -1;
			}
			break;
		case '"':
			egg->lang.slurp = '"';
			break;
		case '(':
			egg->lang.slurpin = '(';
			egg->lang.slurp = ')';
			break;
		case '{':
			if (CTX > 0) {
				if (CTX > 31 || CTX < 0) {
					R_LOG_ERROR ("Sinking before overflow");
					CTX = 0;
					break;
				}
				// r_egg_printf (egg, " %s:\n", get_frame_label (0));
				if (egg->lang.nested_callname[CTX] && strstr (egg->lang.nested_callname[CTX], "if") &&
					strstr (egg->lang.elem, "else")) {
					*egg->lang.elem = '\x00';
					egg->lang.elem_n = 0;
					char **slot = ifelse_slot (egg, CTX);
					if (slot) {
						free (*slot);
						*slot = r_str_newf ("  __end_%d_%d_%d",
							egg->lang.nfunctions, CTX, egg->lang.nestedi[CTX]);
					}
				}
					tmp_ptr = r_str_newf ("__begin_%d_%d_%d",
						egg->lang.nfunctions, CTX, egg->lang.nestedi[CTX]);
					emit_label (egg, tmp_ptr);
					R_FREE (tmp_ptr);
			}
			rcc_context (egg, 1);
			break;
		case '}':
			egg->lang.endframe = egg->lang.nested[CTX];
			if (egg->lang.endframe) {
				// XXX: use egg->lang.endframe[context]
				r_egg_printf (egg, "%s", egg->lang.endframe);
				R_FREE (egg->lang.nested[CTX]);
				// R_FREE (egg->lang.endframe);
			}
			if (CTX > 1) {
				if (egg->lang.nested_callname[CTX - 1] && strstr (egg->lang.nested_callname[CTX - 1], "if")) {
					tmp_ptr = r_str_newf ("__ifelse_%d_%d", CTX - 1, egg->lang.nestedi[CTX - 1] - 1);
					e->jmp (egg, tmp_ptr, 0);
					R_FREE (tmp_ptr);
					char **slot = ifelse_slot (egg, CTX - 1);
					if (slot) {
						*slot = r_str_newf ("__end_%d_%d_%d",
							egg->lang.nfunctions, CTX - 1, egg->lang.nestedi[CTX - 1] - 1);
					}
				}
				tmp_ptr = r_str_newf ("__end_%d_%d_%d",
					egg->lang.nfunctions, CTX - 1, egg->lang.nestedi[CTX - 1] - 1);
				emit_label (egg, tmp_ptr);
				R_FREE (tmp_ptr);
			}
			if (CTX > 0) {
				egg->lang.nbrackets++;
			}
			rcc_context (egg, -1);
			if (CTX == 0) {
				r_egg_printf (egg, "\n");
				// edit this unnessary jmp to bypass tests
				for (i = 0; i < 32; i++) {
					for (j = 0; j < egg->lang.nestedi[i] && j < 32; j++) {
						if (egg->lang.ifelse_table[i][j]) {
							tmp_ptr = r_str_newf ("__ifelse_%d_%d", i, j);
							emit_label (egg, tmp_ptr);
							R_FREE (tmp_ptr);
							e->jmp (egg, egg->lang.ifelse_table[i][j], 0);
							R_FREE (egg->lang.ifelse_table[i][j]);
						}
					}
				}
				// r_egg_printf (egg, "  __end_%d:\n\n", egg->lang.nfunctions);
				// edit this unnessary jmp to bypass tests
				egg->lang.nbrackets = 0;
				egg->lang.nfunctions++;
			}
			break;
		case ':':
			if (egg->lang.oc == '\n' || egg->lang.oc == '}') {
				egg->lang.quoteline = '\n';
			} else {
				if (!rcc_element_append (egg, c)) {
					return -1;
				}
			}
			break;
		case '#':
			if (egg->lang.oc == '\n') {
				egg->lang.skipline = 1;
			}
			break;
		case '/':
			if (egg->lang.oc == '/') {
				egg->lang.skipline = 1;
			} else {
				if (!rcc_element_append (egg, c)) {
					return -1;
				}
			}
			break;
		default:
			if (!rcc_element_append (egg, c)) {
				return -1;
			}
		}
		if (egg->lang.slurp) {
			if (egg->lang.elem_n) {
				char *ptr = egg->lang.elem;
				egg->lang.elem[egg->lang.elem_n] = '\0';
				while (is_space (*ptr)) {
					ptr++;
				}
				rcc_fun (egg, ptr);
			}
			egg->lang.elem_n = 0;
		}
	}
	if (c != '\t' && c != ' ') {
		egg->lang.oc = c;
	}
	return 0;
}
