/* radare - LGPL - Copyright 2009-2019 - hmht */

/*****************************************************************************\
 *              Architecture
 *
 * File Contents:
 * ## Section 1. Token parsers
 * ## Section 2: some weird datastructure
 * ## Section 3: token classifiers
 * ## Section 4: Generic instruction emitters
 * ## Section 5: Specific instruction parsing
 * ## Section 6: mnemonic token dispatcher
 * ## Section 7: radare2 glue and mnemonic tokenization
 *
 * documentation date: 2019-10-04
 * documentation date: 2019-10-14
 *
 * 1. Token parsers
 *
 * I'm sure most of this is re-inventing the wheel, (poorly, too), this is
 * because I didn't take enough time to find a proper implementation.
 * If you know a r2lib function that does the job it should be used instead.
 *
 *
 * 2. Some weird datastructure
 *
 * Started out for matching strings whitespace-independently, and uses c99s
 * (struct literal){} notation and is zero-terminated.
 * I wrote this thing in the late hours of r2con2019 while jetlagged.
 *
 * Currently the last place it's used in is mnemonic matching, since I hacked
 * in a nr-of-arguments field into the table. Whitespace-independence is
 * currently a bug since it'll accept "n o p" as "nop"... also functions need
 * to be renamed.
 *
 * One pitfall is that the match is lazy (non-greedy?) in other words, "reti"
 * is matched by "ret", but not the other way around, so the most-specific match
 * must come first in the list.
 *
 *
 * 3. token classifiers
 *
 * right now mostly has functions to distinguish between argument types, and
 * parses their data. (Some argument types, such as relative addresses and bits,
 * require parsing to asses their validity.)
 *
 *
 * 4. Generic instruction parsing
 *
 * I started out just writing specific parser for each
 * mnemonic(-variant), and halfway through I started noticing a lot of
 * code duplication, so extracted some of it.
 *	Their basic operation is simple: dump whatever you're given into the out
 * parameter, and move the write pointer forward.
 *
 *
 * 5. Specific instruction parsing
 *
 * Of course, in the very beginning I started out with the idea to
 * completely generalize everything, but there were more edge cases than
 * my small brain could handle, so I scrapped that and started punching
 * out special parsers for each instruction variant mindlessly.
 *	The result of this approach is really glaring. Lots of duplication.
 * There's lots of easy deduplication opportunity, and now that it's finished I
 * have some ideas on how to do it better, but eh.
 *
 *
 * 6. mnemonic token dispatcher
 *
 * The weird datastructure returns! with macros! it's basically just a jump
 * table with one bit of validation.
 *
 *
 * 7. Radare2 glue and mnemonic tokenization
 *
 * Had one look at the gb glue code and copied the lot of it without really
 * understanding what I'm doing.
 *
 * also splits out the first word (asserted mnemonic) for the token dispatcher,
 * and splits up the arguments
 *
\*****************************************************************************/
#include<r_util.h>
#include<string.h>

/******************************************************************************
 * ## Section 1. Generic Token parsers
 *               ------- -------------*/

static bool parse_hexadecimal(char const* hexstr, ut16* out) {
	if (!hexstr || hexstr[0] != '0'
		|| !(hexstr[1] == 'x' || hexstr[1] == 'X')) {
		return false;
	}
	*out = 0;
	char const*p;
	for (p = hexstr + 2; p < hexstr + 6 && *p ; p += 1) {
		*out <<= 4;
		if ('0' <= *p && *p <= '9' ) {
			*out |= *p - '0';
		} else if ('a' <= *p && *p <= 'f' ) {
			*out |= *p - 'a' + 10;
		} else if ('A' <= *p && *p <= 'F' ) {
			*out |= *p - 'A' + 10;
		} else {
			return false;
		}
	}
	return ! *p;
}

// FIXME: may write outside buffer
/**
 * splits up the given string into multiple chucks, separated by unquoted
 * commas. It will then copy chunk n-1 into dest, with the leading and trailing
 * whitespace stripped.
 *
 * if chunk n-1 does not exist or is empty, it will return false;
 *
 * only text before newlines, NUL, and unquoted semicolons is chunked.
 *
 * any text after a single-quote and before the next single-quote is considered
 * quoted. There is no escaping.
 */
static bool get_arg(char const*multi, int n, char *dest)
{
	char* lastnonws = dest;
	bool anynonws = false;
	bool in_string = false;
	n -= 1;
	if (!multi) return false;
	while (n && *multi && *multi != '\n' && *multi != '\r') {
		if (*multi == '\'') in_string = !in_string;
		if (!in_string) {
			if (*multi == ';') {
				return false;
			}
			if (*multi == ',') {
				multi += 1;
				n -= 1;
				continue;
			}
		}
		multi += 1;
	}
	if (!*multi || *multi == '\n' || *multi == '\r' || in_string) {
		return false;
	}

	while (*multi && (*multi == ' ' || *multi == '\t')) {
		multi += 1;
	}

	while (*multi && *multi != '\n' && *multi != '\r') {
		if (*multi == '\'') in_string = !in_string;
		if (!in_string) {
			if (*multi == ';' ||  *multi == ',') {
				break;
			}
			if (*multi != ' ' && *multi != '\t') {
				lastnonws = dest;
				anynonws = true;
			}
			*dest = *multi;
			dest += 1;
			multi += 1;
		}
	}

	if (in_string) return false;

	if (!anynonws) {
		*dest = '\0';
		return false;
	}
	*(lastnonws + 1) = '\0';
	return true;
}

/**
 * tokenizes the argument list
 * arg parameter must be 3 char pointers wide.
 * TODO: merge with get_arg, as this is now the only user
 */
static int get_arguments(char**arg, char const*arguments) {
	size_t arglen = strlen (arguments) + 1;
	char*tmp = malloc (arglen);
	if (!get_arg (arguments, 1, tmp)) {
		free (tmp); tmp = 0;
		return 0;
	} else {
		arg[0] = realloc (tmp, strlen (tmp) + 1); tmp = 0;
		tmp = malloc (arglen);
		if (!get_arg (arguments, 2, tmp)) {
			free (tmp); tmp = 0;
			return 1;
		} else {
			arg[1] = realloc (tmp, strlen (tmp) + 1); tmp = 0;
			tmp = malloc (arglen + 1);
			if (!get_arg (arguments, 3, tmp)) {
				free (tmp); tmp = 0;
				return 2;
			} else {
				arg[2] = realloc (tmp, strlen (tmp) + 1); tmp = 0;
				tmp = malloc (arglen + 1);
				if (get_arg (arguments, 4, tmp)) {
					free (tmp); tmp = 0;
					free (arg[0]); arg[0] = 0;
					free (arg[1]); arg[1] = 0;
					free (arg[2]); arg[2] = 0;
					return 4;
				}
				free (tmp); tmp = 0;
				return 3;
			}
		}
	}
}

/**
 * returns true if there is no more valid assembly code after this character
 */
static bool terminates_asm_line(char c) {
	return c == '\0' || c == '\n' || c == '\r' || c == ';' ;
}

/**
 * Like r_str_casecmp, but ignores all isspace characters
 */
static int str_iwhitecasecmp(char const*a, char const*b) {
	if (!a && !b) {
		return *a - *b;
	}
	while (a && b) {
		if (!*a && !*b) {
			break;
		}
		if (!*a || !*b) {
			break;
		}
		if (isspace ((unsigned char)*a)) {
			a += 1;
			continue;
		}
		if (isspace ((unsigned char)*b)) {
			b += 1;
			continue;
		}
		if (tolower ((unsigned char)*a) == tolower ((unsigned char)*b)) {
			a += 1;
			b += 1;
			continue;
		}
		break;
	}
	return *a - *b;
}

/******************************************************************************
 * ## Section 2: some weird datastructure
                 ------------------------*/

typedef bool (*parse_mnem_args)(char const*const*, ut16, ut8**);

typedef struct {
	char const*const pattern;
	parse_mnem_args res;
	int args;
} ftable[];

static bool pattern_match(char const*str, char const*pattern) {
	int si = 0;
	int ti = 0;
	if (!pattern) {
		return true;
	}

	while (pattern[ti] != '\0') {
		while (isspace ((unsigned char)str[si]) && !isspace ((unsigned char)pattern[ti])) {
			si += 1;
		}
		if (isspace ((unsigned char)pattern[ti])) {
			ti += 1;
			continue;
		}
		if (tolower ((unsigned char)pattern[ti]) == tolower ((unsigned char)str[si])) {
			si += 1;
			ti += 1;
		} else {
			return false;
		}
	}
	return true;
}

static parse_mnem_args match_prefix_f(int*args, char const*str, ftable const tbl) {
	int row = 0;
	while (tbl[row].pattern) {
		if (pattern_match (str, tbl[row].pattern)) {
			*args = tbl[row].args;
			return tbl[row].res;
		} else {
			row += 1;
		}
	}
	*args = tbl[row].args;
	return tbl[row].res;
}

/******************************************************************************
 * ## Section 3: token classifiers
                 -----------------*/

/**
 * matches registers r0 and r1 when they are indirectly-addressed.
 * 8051-style syntax @r0, but also r2 defacto [r0]
 */
static bool is_indirect_reg(char const*str)
{
	if (!str) {
		return false;
	}

	if (str[0] == '@' ) {
		return r_str_ansi_nlen (str, 4) == 3
			&& tolower ((unsigned char)str[1]) == 'r'
			&& (str[2] == '0' || str[2] == '1');
	}

	if (str[0] == '[' ) {
		return r_str_ansi_nlen (str, 5) == 4
			&& tolower ((unsigned char)str[1]) == 'r'
			&& (str[2] == '0' || str[2] == '1')
			&& str[3] == ']';
	}

	return false;
}

static bool is_direct(char const *str) {
	ut16 useless;
	return parse_hexadecimal (str, &useless);
}

/**
 * returns true if the given string denotes an 'r'-register
 */
static bool is_reg(char const *str) {
	R_RETURN_VAL_IF_FAIL (str, false);
	return tolower (str[0]) == 'r' && R_BETWEEN ('0', str[1], '7') && !str[2];
}

/**
 * returns true if the given number is a valid relative address from the given
 *	pc, the relative address is stored in the *out parameter.
 */
static bool relative_address(ut16 pc, ut16 address, ut8 *out)
{
	st16 diff = address - (pc + 2);
	if (diff < INT8_MIN || INT8_MAX < diff) {
		return false;
	} else {
		*out = diff;
		return true;
	}
}

static bool resolve_immediate(char const* imm_str, ut16* imm_out) {
	// rasm2 resolves symbols, so does this really only need to parse hex?
	// maybe TODO: skip leading '#' if exists?
	return parse_hexadecimal (imm_str, imm_out);
}

static bool to_address(char const* addr_str, ut16* addr_out) {
	// rasm2 resolves symbols, so does this really only need to parse hex?
	// maybe TODO: check address bounds?
	return parse_hexadecimal (addr_str, addr_out);
}

static bool parse_register(char const* register_input, ut8* hex_out) {
	if (!strcmp (register_input, "r0")) {
		*hex_out = 0x00;
	} else if (!strcmp (register_input, "r1")) {
		*hex_out = 0x01;
	} else if (!strcmp (register_input, "r2")) {
		*hex_out = 0x02;
	} else if (!strcmp (register_input, "r3")) {
		*hex_out = 0x03;
	} else if (!strcmp (register_input, "r4")) {
		*hex_out = 0x04;
	} else if (!strcmp (register_input, "r5")) {
		*hex_out = 0x05;
	} else if (!strcmp (register_input, "r6")) {
		*hex_out = 0x06;
	} else if (!strcmp (register_input, "r7")) {
		*hex_out = 0x07;
	} else if (!strcmp (register_input, "p0")) {
		*hex_out = 0x80;
	} else if (!strcmp (register_input, "sp")) {
		*hex_out = 0x81;
	} else if (!strcmp (register_input, "dpl")) {
		*hex_out = 0x82;
	} else if (!strcmp (register_input, "dph")) {
		*hex_out = 0x83;
	} else if (!strcmp (register_input, "pcon")) {
		*hex_out = 0x87;
	} else if (!strcmp (register_input, "tcon")) {
		*hex_out = 0x88;
	} else if (!strcmp (register_input, "tmod")) {
		*hex_out = 0x89;
	} else if (!strcmp (register_input, "tl0")) {
		*hex_out = 0x8a;
	} else if (!strcmp (register_input, "tl1")) {
		*hex_out = 0x8b;
	} else if (!strcmp (register_input, "th0")) {
		*hex_out = 0x8c;
	} else if (!strcmp (register_input, "th1")) {
		*hex_out = 0x8d;
	} else if (!strcmp (register_input, "p1")) {
		*hex_out = 0x90;
	} else if (!strcmp (register_input, "scon")) {
		*hex_out = 0x98;
	} else if (!strcmp (register_input, "sbuf")) {
		*hex_out = 0x99;
	} else if (!strcmp (register_input, "p2")) {
		*hex_out = 0xa0;
	} else if (!strcmp (register_input, "ie")) {
		*hex_out = 0xa8;
	} else if (!strcmp (register_input, "p3")) {
		*hex_out = 0xb0;
	} else if (!strcmp (register_input, "ip")) {
		*hex_out = 0xb8;
	} else if (!strcmp (register_input, "psw")) {
		*hex_out = 0xd0;
	} else if (!strcmp (register_input, "a") || !strcmp (register_input, "acc")) {
		*hex_out = 0xe8;
	} else if (!strcmp (register_input, "b")) {
		*hex_out = 0xf0;
	} else {
		return false;
	}
	return true;
}

  /*
  * attempts to parse the given string as an 8bit-wide address
  */
static bool parse_address_direct(char const* addr_str, ut8* addr_out) {
	ut16 addr_big;
	ut8 addr_short;
	// rasm2 resolves symbols, so does this really only need to parse hex?
	// maybe TODO: check address bounds?
	bool found = parse_hexadecimal (addr_str, &addr_big);// && (addr_big < 0xFF);

	if (!found) {
		if (!parse_register (addr_str, &addr_short)) {
			return false;
		}
		*addr_out = addr_short;
		return true;
	}

	*addr_out = addr_big;
	return found;
}

/**
 * attempts to parse the given string as a bit-address
 */
static bool address_bit(char const* addr_str, ut8* addr_out) {
	char *bitpart = malloc (strlen (addr_str) + 1);
	char *bytepart = malloc (strlen (addr_str) + 1);
	char const *separator = r_str_lchr (addr_str, '.');
	ut8 byte;
	int bit;
	bool ret = true;
	// TODO: check if symbols are resolved properly in all cases:
	// - symbol.2
	// - 0x25.symbol
	// - symbol.symbol
	// - symbol
	if (!separator) {
		ret = false;
		goto end;
	}
	r_str_ncpy (bytepart, addr_str, separator - addr_str + 1);
	bytepart[separator - addr_str + 1] = '\0';
	r_str_ncpy (bitpart, separator + 1, strlen (separator));

	bit = bitpart[0] - '0';

	if (bit > 9) {
		ret = false;
		goto end;
	}

	ut8 addr_bytepart = 0x00;
	if (!parse_register(bytepart, &addr_bytepart)) {
		if (!parse_address_direct (bytepart, &byte)) {
			ret = false;
			goto end;
		}
		if (1 < strlen (bitpart)
			|| bitpart[0] < '0' || '7' < bitpart[0]) {
			ret = false;
			goto end;
		}

		if (0x20 <= byte && byte < 0x30) {
			*addr_out = (byte - 0x20) * 8 + bit;
		} else if (0x80 <= byte && !(byte%8)) {
			*addr_out = byte + bit;
		}
		goto end;
	}

	*addr_out = addr_bytepart + bit;

end:
	free (bitpart); bitpart = 0;
	free (bytepart); bytepart = 0;
	return ret;
}

/**
 * figures out which register is denoted by the given string
 * returns 8 if invalid
 */
static int register_number(char const*reg) {
	if (is_reg (reg)) {
		return reg[1] - '0';
	}
	if (is_indirect_reg (reg)) {
		return reg[2] - '0';
	}
	return 8; // not register 0-7, so...
}

/**
 * translate a register name to an offset for an instruction
 * failure case is 10
 */
static ut8 register_offset(const char *reg) {
	if (!r_str_casecmp (reg, "@r0") || !r_str_casecmp (reg, "[r0]")) {
		return 0;
	}
	if (!r_str_casecmp (reg, "@r1") || !r_str_casecmp (reg, "[r1]")) {
		return 1;
	}
	return register_number (reg) + 2;
}

/******************************************************************************
 * ## Section 4: Generic instruction emmiters
                 ----------------------------*/

static bool single_byte_instr(ut8 const instr, ut8 **out) {
	(*out)[0] = instr;
	*out += 1;
	return true;
}

static bool singlearg_bit(ut8 const firstbyte, char const* arg, ut8 **out) {
	ut8 address;
	if (!address_bit (arg, &address)) {
		return false;
	}
	(*out)[0] = firstbyte;
	(*out)[1] = address;
	*out += 2;
	return true;
}

static bool singlearg_reladdr(ut8 const firstbyte, char const* arg
	, ut16 const pc, ut8 **out)
{
	ut16 address;
	if (!to_address (arg, &address)
		|| !relative_address (pc, address, (*out)+1)) {
		return false;
	}
	(*out)[0] = firstbyte;
	*out += 2;
	return true;
}

static bool singlearg_direct(ut8 const firstbyte, char const* arg, ut8 **out) {
	bool ret = true;
	ut8 address = 0x00;
	if (!parse_address_direct (arg, &address)) {
		return false;
	}

	(*out)[0] = firstbyte;
	(*out)[1] = address;
	*out += 2;
	return ret;
}

static bool singlearg_immediate(ut8 firstbyte, char const* imm_str, ut8**out) {
	ut16 imm;
	if (imm_str[0] != '#'
		|| !resolve_immediate (imm_str + 1, &imm)) {
		return false;
	}
	(*out)[0] = firstbyte;
	(*out)[1] = imm & 0x00FF;
	*out += 2;
	return true;
}

static bool singlearg_register(ut8 firstbyte, char const*reg, ut8**out) {
	return single_byte_instr (firstbyte | register_number (reg), out);
}

static bool single_a_arg_instr(ut8 const firstbyte, char const*arg
	, ut8 **out)
{
	if (r_str_casecmp ("a", arg)) {
		return false;
	}
	return single_byte_instr (firstbyte, out);
}

/******************************************************************************
 * ## Section 5: Specific instruction parsing
                 ----------------------------*/

static bool mnem_acall(char const*const*arg, ut16 pc, ut8**out) {
	ut16 address;
	if (!to_address (arg[0], &address)) {
		return false;
	}
	(*out)[0] = ((address & 0x0700) >> 3) | 0x11;
	(*out)[1] = address & 0x00FF;
	*out += 2;
	return true;
}

static bool mnem_add(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp (arg[0], "a")) {
		return false;
	}
	switch (arg[1][0]) {
	case '@':
	case '[':
		return singlearg_register (0x26, arg[1], out);
	case '#':
		return singlearg_immediate (0x24, arg[1], out);
	}
	if (is_reg (arg[1])) {
		return singlearg_register (0x28, arg[1], out);
	} else {
		return singlearg_direct (0x25, arg[1], out);
	}
}

static bool mnem_addc(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp (arg[0], "a")) {
		return false;
	}
	if (is_indirect_reg (arg[1])) {
		return singlearg_register (0x36, arg[1], out);
	}
	if (arg[1][0] == '#') {
		return singlearg_immediate (0x34, arg[1], out);
	}
	if (is_reg (arg[1])) {
		return singlearg_register (0x38, arg[1], out);
	}
	return singlearg_direct (0x35, arg[1], out);
}

static bool mnem_ajmp(char const*const*arg, ut16 pc, ut8**out) {
	ut16 address;
	if (!to_address (arg[0], &address)) {
		return false;
	}
	(*out)[0] = ((address & 0x0700) >> 3 ) | 0x01;
	(*out)[1] = address & 0x00FF;
	*out += 2;
	return true;
}

static bool mnem_anl(char const*const*arg, ut16 pc, ut8**out) {
	if (!strcmp (arg[0], "c")) {
		if (arg[1][0] == '/') {
			return singlearg_bit (0xb0, arg[1] + 1, out);
		}
		return singlearg_bit (0x82, arg[1], out);
	}
	if (!strcmp (arg[0], "a")) {
		if (is_indirect_reg (arg[1])) {
			return singlearg_register (0x56, arg[1], out);
		}
		if (arg[1][0] == '#') {
			return singlearg_immediate (0x54, arg[1], out);
		}
		if (is_reg (arg[1])) {
			return singlearg_register (0x58, arg[1], out);
		}
		return singlearg_direct (0x55, arg[1], out);
	}

	ut8 address;
	if (!parse_address_direct (arg[0], &address)) {
		return false;
	}
	if (!r_str_casecmp (arg[1], "a")) {
		return singlearg_direct (0x52, arg[0], out);
	}
	ut16 imm;
	if (arg[1][0] != '#' || !resolve_immediate (arg[1] + 1, &imm)) {
		return false;
	}
	(*out)[0] = 0x53;
	(*out)[1] = address;
	(*out)[2] = imm & 0x00FF;
	*out += 3;
	return true;
}

static bool mnem_cjne(char const*const*arg, ut16 pc, ut8**out) {
	ut16 address;
	if (!to_address (arg[2], &address)
		|| !relative_address (pc+1, address, (*out)+2)) {
		return false;
	}
	if (!r_str_casecmp (arg[0], "a")) {
		if (arg[1][0] == '#') {
			ut16 imm;
			if (!resolve_immediate (arg[1] + 1, &imm)) {
				return false;
			}
			(*out)[0] = 0xb4;
			(*out)[1] = imm & 0x00FF;
			// out[2] set earlier
			*out += 3;
			return true;
		}
		ut8 address;
		if (!parse_address_direct (arg[1], &address)) {
			return false;
		}
		(*out)[0] = 0xb5;
		(*out)[1] = address;
		// out[2] set earlier
		*out += 3;
		return true;
	}
	if (is_reg (arg[0])) {
		ut16 imm;
		if (!resolve_immediate (arg[1] + 1, &imm)) {
			return false;
		}
		(*out)[0] = 0xb8 | register_number (arg[0]) ;
		(*out)[1] = imm & 0x00FF;
		// out[2] set earlier
		*out += 3;
		return true;
	}
	if (is_indirect_reg (arg[0])) {
		ut16 imm;
		if (!resolve_immediate (arg[1] + 1, &imm)) {
			return false;
		}
		(*out)[0] = 0xb6 | register_number (arg[0]) ;
		(*out)[1] = imm & 0x00FF;
		// out[2] set earlier
		*out += 3;
		return true;
	}
	return false;
}

static bool mnem_clr(char const*const*arg, ut16 pc, ut8**out) {
	if (!r_str_casecmp  ("a", arg[0])) {
		return single_byte_instr (0xe4, out);
	}
	if (!r_str_casecmp  ("c", arg[0])) {
		return single_byte_instr (0xc3, out);
	}
	return singlearg_bit (0xc2, arg[0], out);
}

static bool mnem_cpl(char const*const*arg, ut16 pc, ut8**out) {
	if (!r_str_casecmp  ("a", arg[0])) {
		return single_byte_instr (0xf4, out);
	}
	if (!r_str_casecmp  ("c", arg[0])) {
		return single_byte_instr (0xb3, out);
	}
	return singlearg_bit (0xb2, arg[0], out);
}

static bool mnem_da(char const*const*arg, ut16 pc, ut8**out) {
	return single_a_arg_instr (0xd4, arg[0], out);
}

static bool mnem_dec(char const*const*arg, ut16 pc, ut8**out) {
	if (is_indirect_reg (arg[0])) {
		return singlearg_register (0x16, arg[0], out);
	}
	if (is_reg (arg[0])) {
		return singlearg_register (0x18, arg[0], out);
	}
	if (!r_str_casecmp ("a", arg[0])) {
		return single_byte_instr (0x14, out);
	}
	return singlearg_direct (0x15, arg[0], out);
}

static bool mnem_div(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp  ("ab", arg[0])) {
		return false;
	}
	return single_byte_instr (0x84, out);
}

static bool mnem_djnz(char const*const*arg, ut16 pc, ut8**out) {
	ut16 jmp_address;
	if (!to_address (arg[1], &jmp_address)) {
		return false;
	}
	if (!relative_address (pc, jmp_address, (*out) + 2)) {
		return false;
	}

	if (is_reg (arg[0])) {
		(*out)[0] = 0xd8 | register_number (arg[0]);
		(*out)[1] = (*out)[2];
		*out += 2;
		return true;
	}
	ut8 dec_address;
	if (!parse_address_direct (arg[0], &dec_address))  {
		return false;
	}
	(*out)[0] = 0xd5;
	(*out)[1] = dec_address;
	(*out)[2] -= 1;
	*out += 3;
	return true;
}

static bool mnem_inc(char const*const*arg, ut16 pc, ut8**out) {
	if (is_reg (arg[0])) {
		return singlearg_register (0x08, arg[0], out);
	}
	if (is_indirect_reg (arg[0])) {
		return singlearg_register (0x06, arg[0], out);
	}
	if (!r_str_casecmp  ("a", arg[0])) {
		return single_byte_instr (0x04, out);
	}
	if (!r_str_casecmp ("dptr", arg[0])) {
		return single_byte_instr (0xa3, out);
	}
	return singlearg_direct (0x05, arg[0], out);
}

static bool mnem_jb(char const*const*arg, ut16 pc, ut8**out) {
	ut8 cmp_addr;
	if (!address_bit (arg[0], &cmp_addr)) {
		return false;
	}
	ut16 jmp_addr;
	if (!to_address (arg[1], &jmp_addr)
		|| !relative_address (pc + 1, jmp_addr, (*out) + 2)) {
		return false;
	}
	(*out)[0] = 0x20;
	(*out)[1] = cmp_addr;
	// out[2] set earlier
	*out += 3;
	return true;
}

static bool mnem_jbc(char const*const*arg, ut16 pc, ut8**out) {
	ut8 cmp_addr;
	if (!address_bit (arg[0], &cmp_addr)) {
		R_LOG_DEBUG ("address bit not found");
		return false;
	}

	ut16 jmp_addr = 0;
	if (!to_address (arg[1], &jmp_addr)) {
		R_LOG_DEBUG ("address %x not found", jmp_addr);
		return false;
	}

	if (!relative_address (pc + 1, jmp_addr, (*out) + 2)) {
		R_LOG_DEBUG ("address %x not found", jmp_addr);
		return false;
	}

	(*out)[0] = 0x10;
	(*out)[1] = cmp_addr;
	// out[2] set earlier
	*out += 3;
	return true;
}

static bool mnem_jc(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_reladdr (0x40, arg[0], pc, out);
}

static bool mnem_jnb(char const*const*arg, ut16 pc, ut8**out) {
	ut8 cmp_addr;
	if (!address_bit (arg[0], &cmp_addr)) {
		return false;
	}
	ut16 jmp_addr;
	if (!to_address (arg[1], &jmp_addr)
		|| !relative_address (pc + 1, jmp_addr, (*out) + 2)) {
		return false;
	}
	(*out)[0] = 0x30;
	(*out)[1] = cmp_addr;
	// out[2] set earlier
	*out += 3;
	return true;
}

static bool mnem_jnc(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_reladdr (0x50, arg[0], pc, out);
}

static bool mnem_jnz(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_reladdr (0x70, arg[0], pc, out);
}

static bool mnem_jz(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_reladdr (0x60, arg[0], pc, out);
}

static bool mnem_lcall(char const*const*arg, ut16 pc, ut8**out) {
	ut16 address;
	if (!to_address (arg[0], &address)) {
		return false;
	}
	(*out)[0] = 0x12;
	(*out)[1] = ((address & 0xFF00) >> 8) & 0x00FF;
	(*out)[2] = address & 0x00FF;
	*out += 3;
	return true;
}

static bool mnem_ljmp(char const*const*arg, ut16 pc, ut8**out) {
	ut16 address;
	if (!to_address (arg[0], &address)) {
		return false;
	}
	(*out)[0] = 0x02;
	(*out)[1] = ((address & 0xFF00) >> 8) & 0x00FF;
	(*out)[2] = address & 0x00FF;
	*out += 3;
	return true;
}

static bool mnem_mov_c(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_bit (0xa2, arg[1], out);
}

static bool mnem_mov(char const*const*arg, ut16 pc, ut8**out) {
	ut16 src_imm, dst_imm;
	ut8 src_addr, dst_addr;
	ut8 offset;

	if (!r_str_casecmp (arg[0], "a")) {
		if (resolve_immediate (arg[1] + 1, &src_imm)) {
			return singlearg_immediate (0x74, arg[1], out);
		}

		if (is_direct (arg[1])) {
			return singlearg_direct (0xe5, arg[1], out);
		}

		offset = register_offset (arg[1]);
		if (offset < 10) {
			return single_byte_instr (0xe6 + offset, out);
		}
	}

	offset = register_offset (arg[0]);
	if (offset < 10) {
		if (resolve_immediate (arg[1] + 1, &src_imm)) {
			return singlearg_immediate (0x76 + offset, arg[1], out);
		} else if (is_direct (arg[1])) {
			return singlearg_direct (0xa6 + offset, arg[1], out);
		} else if (!r_str_casecmp (arg[1], "a")) {
			return single_byte_instr (0xf6 + offset, out);
		}
	}

	if (parse_hexadecimal (arg[0], &dst_imm)) {
		if (resolve_immediate (arg[1] + 1, &src_imm)) {//TODO: write doublearg_direct_register for 3 bytes instructions
			//use : "arg[1][0] == '#'" ?
			(*out)[0] = 0x75;
			(*out)[1] = dst_imm;
			(*out)[2] = src_imm & 0x00ff;
			*out += 3;
			return true;
		}

		if (parse_hexadecimal (arg[1], &src_imm)) {
			(*out)[0] = 0x85;
			(*out)[1] = src_imm;
			(*out)[2] = dst_imm;
			*out += 3;
			return true;
		}

		offset = register_offset (arg[1]);
		if (offset < 10) {
			return singlearg_direct (0x86 + offset, arg[0], out);
		}
	}

	if (!r_str_casecmp (arg[0], "dptr")) {
		if (resolve_immediate (arg[1] + 1, &src_imm)) {
			parse_register ("dptr", &dst_addr);
			(*out)[0] = 0x90;
			(*out)[1] = src_imm >> 8;
			(*out)[2] = src_imm & 0xff;
			*out += 3;
			return true;
		}
	}
	if (address_bit (arg[0], &src_addr)) {
		if (!r_str_casecmp (arg[1], "c")) {
			return singlearg_bit (0x92, arg[0], out);
		}

		if (address_bit (arg[1], &src_addr)) {
			return singlearg_bit (0xa2, arg[1], out);
		}
	}

	if (is_direct (arg[0])) {
		if (!r_str_casecmp (arg[1], "a")) {
			return singlearg_direct (0xf5, arg[0], out);
		}
	}

	return false;
}

static bool mnem_movc(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp (arg[0], "a")) {
		return false;
	}
	if (!str_iwhitecasecmp (arg[1], "@a+dptr")
		|| !str_iwhitecasecmp (arg[1], "[a+dptr]")) {
		return single_byte_instr (0x93, out);
	}
	if (!str_iwhitecasecmp (arg[1], "@a+pc")
		|| !str_iwhitecasecmp (arg[1], "[a+pc]")) {
		return single_byte_instr (0x83, out);
	}
	return false;
}

static bool mnem_movx(char const*const*arg, ut16 pc, ut8**out) {
	if (!r_str_casecmp (arg[0], "a")) {
		if (is_indirect_reg (arg[1])) {
			return singlearg_register (0xe2, arg[1], out);
		}
		if (!str_iwhitecasecmp (arg[1], "@dptr")
			|| !str_iwhitecasecmp (arg[1], "[dptr]")) {
			return single_byte_instr (0xe0, out);
		}
	}
	if (r_str_casecmp (arg[1], "a")) {
		return false;
	}
	if (is_indirect_reg (arg[0])) {
		return singlearg_register (0xf2, arg[0], out);
	}
	if (!str_iwhitecasecmp (arg[0], "@dptr")
		|| !str_iwhitecasecmp (arg[0], "[dptr]")) {
		return single_byte_instr (0xf0, out);
	}
	return false;
}

static bool mnem_mul(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_ncasecmp ("ab", arg[0], 3)) {
		return false;
	}
	return single_byte_instr (0xa4, out);
}

static bool mnem_nop(char const*const*arg, ut16 pc, ut8**out) {
	return single_byte_instr (0x00, out);
}

static bool mnem_orl(char const*const*arg, ut16 pc, ut8**out) {
	if (!r_str_casecmp (arg[0], "c")) {
		if (arg[1][0] == '/') {
			return singlearg_bit (0xa0, arg[1] + 1, out);
		}
		return singlearg_bit (0x72, arg[1], out);
	}
	if (!r_str_casecmp (arg[0], "a")) {
		if (is_indirect_reg (arg[1])) {
			return singlearg_register (0x46, arg[1], out);
		}
		if (arg[1][0] == '#') {
			return singlearg_immediate (0x44, arg[1], out);
		}
		if (is_reg (arg[1])) {
			return singlearg_register (0x48, arg[1], out);
		}
		return singlearg_direct (0x45, arg[1], out);
	}

	if (arg[1][0] != '#') {
		return singlearg_direct (0x42, arg[0], out);
	}

	ut8 dest_addr;
	if (!parse_address_direct (arg[0], &dest_addr)) {
		return false;
	}
	ut16 imm;
	if (!resolve_immediate (arg[1] + 1, &imm)) {
		return false;
	}
	(*out)[0] = 0x43;
	(*out)[1] = dest_addr;
	(*out)[2] = imm & 0x00FF;
	*out += 3;
	return true;
}

static bool mnem_pop(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_direct (0xd0, arg[0], out);
}

static bool mnem_push(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_direct (0xc0, arg[0], out);
}

static bool mnem_ret(char const*const*arg, ut16 pc, ut8**out) {
	return single_byte_instr (0x22, out);
}

static bool mnem_reti(char const*const*arg, ut16 pc, ut8**out) {
	return single_byte_instr (0x32, out);
}

static bool mnem_rl(char const*const*arg, ut16 pc, ut8**out) {
	return single_a_arg_instr (0x23, arg[0], out);
}

static bool mnem_rlc(char const*const*arg, ut16 pc, ut8**out) {
	return single_a_arg_instr (0x33, arg[0], out);
}

static bool mnem_rr(char const*const*arg, ut16 pc, ut8**out) {
	return single_a_arg_instr (0x03, arg[0], out);
}

static bool mnem_rrc(char const*const*arg, ut16 pc, ut8**out) {
	return single_a_arg_instr (0x13, arg[0], out);
}

static bool mnem_setb(char const*const*arg, ut16 pc, ut8**out) {
	if (!r_str_casecmp  ("c", arg[0])) {
		return single_byte_instr (0xd3, out);
	}
	return singlearg_bit (0xd2, arg[0], out);
}

static bool mnem_sjmp(char const*const*arg, ut16 pc, ut8**out) {
	return singlearg_reladdr (0x80, arg[0], pc, out);
}

static bool mnem_jmp(char const*const*arg, ut16 pc, ut8**out) {
	if (!str_iwhitecasecmp (arg[0], "@a+dptr")
		|| !str_iwhitecasecmp (arg[0], "[a+dptr]")) {
		return single_byte_instr (0x73, out);
	}

	ut16 address;
	if (!to_address (arg[0], &address)) {
		return false;
	}
	ut16 reladdr;
	if (pc < address ) {
		reladdr = address - pc;
	} else {
		reladdr = pc - address;
	}

	if (reladdr < 0x100 ) {
		return mnem_sjmp (arg, pc, out);
	} else if (reladdr < 0x08FF ) {
		return mnem_ajmp (arg, pc, out);
	} else {
		return mnem_ljmp (arg, pc, out);
	}
}

static bool mnem_subb(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp (arg[0], "a")) {
		return false;
	}
	if (is_indirect_reg (arg[1])) {
		return singlearg_register (0x96, arg[1], out);
	}
	if (arg[1][0] == '#') {
		return singlearg_immediate (0x94, arg[1], out);
	}
	if (is_reg (arg[1])) {
		return singlearg_register (0x98, arg[1], out);
	}
	return singlearg_direct (0x95, arg[1], out);
}

static bool mnem_swap(char const*const*arg, ut16 pc, ut8**out) {
	return single_a_arg_instr (0xc4, arg[0], out);
}

static bool mnem_xrl(char const*const*arg, ut16 pc, ut8**out) {
	ut16 dest_hexadecimal;
	ut8 offset;
	if (parse_hexadecimal (arg[0], &dest_hexadecimal)) {
		if (!r_str_casecmp (arg[1], "a")) {
			return singlearg_direct (0x62, arg[0], out);
		}
		if (arg[1][0] == '#') {
			(*out)[0] = 0x63;
			parse_hexadecimal (arg[0], &dest_hexadecimal);
			(*out)[1] = dest_hexadecimal;
			parse_hexadecimal (arg[1] + 1, &dest_hexadecimal);
			(*out)[2] = dest_hexadecimal;
			*out += 3;
			return true;
		}
	}
	if (!r_str_casecmp (arg[0], "a")) {
		if (arg[1][0] == '#') {
			return singlearg_immediate (0x64, arg[1], out);
		}
		if (parse_hexadecimal (arg[1], &dest_hexadecimal)) {
			return singlearg_direct (0x65, arg[1], out);
		}

		offset = register_offset (arg[1]);
		if (offset < 2) {
			return singlearg_register (0x66 + offset, arg[1], out);
		}
		if (offset < 10) {
			return single_byte_instr (0x66 + offset, out);
		}
	}
	return true;
}

static bool mnem_xch(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp (arg[0], "a")) {
		return false;
	}
	if (is_indirect_reg (arg[1])) {
		return singlearg_register (0xc6, arg[1], out);
	}
	if (is_reg (arg[1])) {
		return singlearg_register (0xc8, arg[1], out);
	}
	return singlearg_direct (0xc5, arg[1], out);
}

static bool mnem_xchd(char const*const*arg, ut16 pc, ut8**out) {
	if (r_str_casecmp (arg[0], "a")) {
		return false;
	}
	if (!is_indirect_reg (arg[1])) {
		return false;
	}
	return singlearg_register (0xd6, arg[1], out);
}

static bool mnem_reserved(char const*const*arg, ut16 pc, ut8**out) {
	return single_byte_instr (0xa5, out);
}

/******************************************************************************
 * ## Section 6: mnemonic token dispatcher
                 -------------------------*/

static parse_mnem_args mnemonic(char const *user_asm, int*nargs) {
	return match_prefix_f (nargs, user_asm, (ftable) {
	#define mnem(n, mn) { #mn " ", &mnem_ ## mn, n },
		mnem (1, acall)
		mnem (2, addc)
		mnem (2, add)
		mnem (1, ajmp)
		mnem (2, anl)
		mnem (3, cjne)
		mnem (1, clr)
		mnem (1, cpl)
		mnem (1, da)
		mnem (1, dec)
		mnem (1, div)
		mnem (2, djnz)
		mnem (1, inc)
		mnem (2, jbc)
		mnem (2, jb)
		mnem (1, jc)
		mnem (1, jmp)
		mnem (2, jnb)
		mnem (1, jnc)
		mnem (1, jz)
		mnem (1, jnz)
		mnem (1, lcall)
		mnem (1, ljmp)
		mnem (0, reserved)
/* so uh, the whitespace-independent matching sees movc and mov c as the same
 * thing...
 * My first thought was to add an exception for mov c, but later I saw that it'd
 * be better to match the space after each instruction, but the exception is
 * still here
 */
		{ "mov c,", &mnem_mov_c, 2 },
		mnem (2, movc)
		mnem (2, movx)
		mnem (2, mov)
		mnem (1, mul)
		mnem (2, orl)
		mnem (1, pop)
		mnem (1, push)
		mnem (2, xchd)
		mnem (2, xch)
		mnem (2, xrl)
		mnem (1, rlc)
		mnem (1, rl)
		mnem (1, rrc)
		mnem (1, rr)
		mnem (1, setb)
		mnem (1, sjmp)
		mnem (2, subb)
		mnem (1, swap)
		mnem (0, nop)
		mnem (0, reti)
		mnem (0, ret)
	#undef mnem
		{0}});
}

/******************************************************************************
 * ## Section 7: radare2 glue and mnemonic tokenization
                 --------------------------------------*/

int assemble_8051(ut64 pc, char const *user_asm, ut8 *outbuf) {
	if (!outbuf || !user_asm) {
		return 0;
	}
	while (!terminates_asm_line (*user_asm)
		&& (*user_asm == ' ' || *user_asm == '\t')) {
		user_asm += 1;
	}
	char const *arguments = user_asm;
	while (!terminates_asm_line (*arguments)
		&& (('a' <= *arguments && *arguments <= 'z')
		|| ('A' <= *arguments && *arguments <= 'Z'))) {
		arguments += 1;
	}
	while (!terminates_asm_line (*arguments)
		&& (*arguments == ' ' || *arguments == '\t')) {
		arguments += 1;
	}
	char*arg[3] = {0};
	int nr_of_arguments = get_arguments (arg, arguments);
	char const*carg[3] = { arg[0], arg[1], arg[2] }; /* aliasing pointers...
		I need to pass char const *s, but I can't free char const *s
		not without compiler warnings, at least */
	int wants_arguments;
	parse_mnem_args mnem = mnemonic (user_asm, &wants_arguments);

	if (!mnem || nr_of_arguments != wants_arguments) {
		free (arg[2]); arg[2] = 0; carg[2] = 0;
		free (arg[1]); arg[1] = 0; carg[1] = 0;
		free (arg[0]); arg[0] = 0; carg[0] = 0;
		return 0;
	}

	ut8 instr[4] = {0};
	ut8 *binp = instr;
	if (!mnem (carg, pc, &binp)) {
		R_FREE (arg[0]);
		R_FREE (arg[1]);
		R_FREE (arg[2]);
		return 0;
	}

	R_FREE (arg[0]);
	R_FREE (arg[1]);
	R_FREE (arg[2]);
	size_t len = binp - instr;
	memcpy (outbuf, instr, len);

	return binp - instr;
}
