@@ -12,6 +12,7 @@ CFLAGS = $(shell sdl2-config --cflags) | |||||
LIBS = $(shell sdl2-config --libs) | LIBS = $(shell sdl2-config --libs) | ||||
MKDIR = mkdir -p | MKDIR = mkdir -p | ||||
RM = rm -rf | RM = rm -rf | ||||
ASM_UP = scripts/update_asm_instructions.py | |||||
MODE = release | MODE = release | ||||
BNRY = $(PROGRAM) | BNRY = $(PROGRAM) | ||||
@@ -23,7 +24,7 @@ DIRS = $(sort $(dir $(OBJS))) | |||||
ifdef DEBUG | ifdef DEBUG | ||||
BNRY := $(BNRY)$(DEVEXT) | BNRY := $(BNRY)$(DEVEXT) | ||||
FLAGS += -g -fsanitize=address -DDEBUG_MODE | |||||
FLAGS += -g -DDEBUG_MODE | |||||
MODE = debug | MODE = debug | ||||
endif | endif | ||||
@@ -47,6 +48,10 @@ $(BUILD)/$(MODE)/%.o: %.c | |||||
-include $(DEPS) | -include $(DEPS) | ||||
ASM_INST = $(SOURCES)/assembler/instructions | |||||
$(ASM_INST).inc.c: $(ASM_INST).yml $(ASM_UP) | |||||
python $(ASM_UP) | |||||
test: test-all test-z80 test-asm test-dasm | test: test-all test-z80 test-asm test-dasm | ||||
test-all: | test-all: | ||||
@@ -0,0 +1,406 @@ | |||||
#!/usr/bin/env python | |||||
# -*- coding: utf-8 -*- | |||||
# Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | |||||
""" | |||||
This script generates 'src/assembler/instructions.inc.c' from | |||||
'src/assembler/instructions.yml'. It should be run automatically by make | |||||
when the latter is modified, but can also be run manually. | |||||
""" | |||||
from __future__ import print_function | |||||
from itertools import product | |||||
import re | |||||
import time | |||||
SOURCE = "src/assembler/instructions.yml" | |||||
DEST = "src/assembler/instructions.inc.c" | |||||
ENCODING = "utf8" | |||||
TAB = " " * 4 | |||||
try: | |||||
import yaml | |||||
except ImportError: | |||||
print("Error: PyYAML is required (https://pypi.python.org/pypi/PyYAML)\n" | |||||
"If you don't want to rebuild {0}, do:\n`make -t {0}`".format(DEST)) | |||||
exit(1) | |||||
re_date = re.compile(r"^(\s*@AUTOGEN_DATE\s*)(.*?)$", re.M) | |||||
re_inst = re.compile( | |||||
r"(/\* @AUTOGEN_INST_BLOCK_START \*/\n*)(.*?)" | |||||
r"(\n*/\* @AUTOGEN_INST_BLOCK_END \*/)", re.S) | |||||
re_lookup = re.compile( | |||||
r"(/\* @AUTOGEN_LOOKUP_BLOCK_START \*/\n*)(.*?)" | |||||
r"(\n*/\* @AUTOGEN_LOOKUP_BLOCK_END \*/)", re.S) | |||||
class Instruction(object): | |||||
""" | |||||
Represent a single ASM instruction mnemonic. | |||||
""" | |||||
ARG_TYPES = { | |||||
"register": "AT_REGISTER", | |||||
"immediate": "AT_IMMEDIATE", | |||||
"indirect": "AT_INDIRECT", | |||||
"indexed": "AT_INDEXED", | |||||
"condition": "AT_CONDITION", | |||||
"port": "AT_PORT" | |||||
} | |||||
PSEUDO_TYPES = { | |||||
"indirect_hl_or_indexed": ["AT_INDIRECT", "AT_INDEXED"] | |||||
} | |||||
def __init__(self, name, data): | |||||
self._name = name | |||||
self._data = data | |||||
self._has_optional_args = False | |||||
def _get_arg_parse_mask(self, num): | |||||
""" | |||||
Return the appropriate mask to parse_args() for the num-th argument. | |||||
""" | |||||
types = set() | |||||
optional = False | |||||
for case in self._data["cases"]: | |||||
if num < len(case["type"]): | |||||
atype = case["type"][num] | |||||
if atype in self.ARG_TYPES: | |||||
types.add(self.ARG_TYPES[atype]) | |||||
else: | |||||
types.update(self.PSEUDO_TYPES[atype]) | |||||
else: | |||||
optional = True | |||||
if not types: | |||||
return "AT_NONE" | |||||
if optional: | |||||
types.add("AT_OPTIONAL") | |||||
self._has_optional_args = True | |||||
return "|".join(sorted(types)) | |||||
def _handle_return(self, ret, indent=1): | |||||
""" | |||||
Return code to handle an instruction return statement. | |||||
""" | |||||
data = ", ".join("0x%02X" % byte if isinstance(byte, int) else byte | |||||
for byte in ret) | |||||
return TAB * indent + "INST_RETURN({0}, {1})".format(len(ret), data) | |||||
def _build_case_type_check(self, args): | |||||
""" | |||||
Return the test part of an if statement for an instruction case. | |||||
""" | |||||
conds = ["INST_TYPE({0}) == {1}".format(i, self.ARG_TYPES[cond]) | |||||
for i, cond in enumerate(args)] | |||||
check = " && ".join(conds) | |||||
if self._has_optional_args: | |||||
return "INST_NARGS == {0} && ".format(len(args)) + check | |||||
return check | |||||
def _build_register_check(self, num, cond): | |||||
""" | |||||
Return an expression to check for a particular register value. | |||||
""" | |||||
return "INST_REG({0}) == REG_{1}".format(num, cond.upper()) | |||||
def _build_immediate_check(self, num, cond): | |||||
""" | |||||
Return an expression to check for a particular immediate value. | |||||
""" | |||||
if "." in cond: | |||||
itype, value = cond.split(".", 1) | |||||
try: | |||||
value = int(value) | |||||
except ValueError: | |||||
value = int(value, 16) | |||||
vtype = "sval" if itype.upper() in ["S8", "REL"] else "uval" | |||||
test1 = "INST_IMM({0}).mask & IMM_{1}".format(num, itype.upper()) | |||||
if (itype.upper() == "U16"): | |||||
test1 += " && !INST_IMM({0}).is_label".format(num) | |||||
test2 = "INST_IMM({0}).{1} == {2}".format(num, vtype, value) | |||||
return "({0} && {1})".format(test1, test2) | |||||
return "INST_IMM({0}).mask & IMM_{1}".format(num, cond.upper()) | |||||
def _build_indirect_check(self, num, cond): | |||||
""" | |||||
Return an expression to check for a particular indirect value. | |||||
""" | |||||
if cond.startswith("reg."): | |||||
test1 = "INST_INDIRECT({0}).type == AT_REGISTER".format(num) | |||||
test2 = "INST_INDIRECT({0}).addr.reg == REG_{1}".format( | |||||
num, cond[len("reg."):].upper()) | |||||
return "({0} && {1})".format(test1, test2) | |||||
if cond == "imm" or cond == "immediate": | |||||
return "INST_INDIRECT({0}).type == AT_IMMEDIATE".format(num) | |||||
err = "Unknown condition for indirect argument: {0}" | |||||
return RuntimeError(err.format(cond)) | |||||
def _build_indexed_check(self, num, cond): | |||||
""" | |||||
Return an expression to check for a particular indexed value. | |||||
""" | |||||
raise RuntimeError("The indexed arg type does not support conditions") | |||||
def _build_condition_check(self, num, cond): | |||||
""" | |||||
Return an expression to check for a particular condition value. | |||||
""" | |||||
return "INST_COND({0}) == COND_{1}".format(num, cond.upper()) | |||||
def _build_port_check(self, num, cond): | |||||
""" | |||||
Return an expression to check for a particular port value. | |||||
""" | |||||
if cond == "reg" or cond == "reg.c": | |||||
return "INST_PORT({0}).type == AT_REGISTER".format(num) | |||||
if cond == "imm" or cond == "immediate": | |||||
return "INST_PORT({0}).type == AT_IMMEDIATE".format(num) | |||||
err = "Unknown condition for port argument: {0}" | |||||
return RuntimeError(err.format(cond)) | |||||
_SUBCASE_LOOKUP_TABLE = { | |||||
"register": _build_register_check, | |||||
"immediate": _build_immediate_check, | |||||
"indirect": _build_indirect_check, | |||||
"indexed": _build_indexed_check, | |||||
"condition": _build_condition_check, | |||||
"port": _build_port_check | |||||
} | |||||
def _build_subcase_check(self, types, conds): | |||||
""" | |||||
Return the test part of an if statement for an instruction subcase. | |||||
""" | |||||
conds = [self._SUBCASE_LOOKUP_TABLE[types[i]](self, i, cond) | |||||
for i, cond in enumerate(conds) if cond != "_"] | |||||
return " && ".join(conds) | |||||
def _iter_permutations(self, types, conds): | |||||
""" | |||||
Iterate over all permutations of the given subcase conditions. | |||||
""" | |||||
def split(typ, cond): | |||||
if "|" in cond: | |||||
splits = [split(typ, c) for c in cond.split("|")] | |||||
merged = [choice for s in splits for choice in s] | |||||
if len(merged) != len(set(merged)): | |||||
msg = "Repeated conditions for {0}: {1}" | |||||
raise RuntimeError(msg.format(typ, cond)) | |||||
return merged | |||||
if typ == "register": | |||||
if cond == "i": | |||||
return ["ix", "iy"] | |||||
if cond == "ih": | |||||
return ["ixh", "iyh"] | |||||
if cond == "il": | |||||
return ["ixl", "iyl"] | |||||
return [cond] | |||||
splits = [split(typ, cond) for typ, cond in zip(types, conds)] | |||||
num = max(len(cond) for cond in splits) | |||||
if any(1 < len(cond) < num for cond in splits): | |||||
msg = "Invalid condition permutations: {0}" | |||||
raise RuntimeError(msg.format(conds)) | |||||
choices = [cond * num if len(cond) == 1 else cond for cond in splits] | |||||
return zip(*choices) | |||||
def _adapt_return(self, types, conds, ret): | |||||
""" | |||||
Return a modified byte list to accomodate for prefixes and immediates. | |||||
""" | |||||
ret = ret[:] | |||||
for i, byte in enumerate(ret): | |||||
if not isinstance(byte, int): | |||||
if byte == "u8": | |||||
try: | |||||
index = types.index("immediate") | |||||
imm = "INST_IMM({0})".format(index) | |||||
except ValueError: | |||||
index = types.index("port") | |||||
imm = "INST_PORT({0}).port.imm".format(index) | |||||
ret[i] = imm + ".uval" | |||||
elif byte == "u16": | |||||
if i < len(ret) - 1: | |||||
raise RuntimeError("U16 return byte must be last") | |||||
try: | |||||
index = types.index("immediate") | |||||
imm = "INST_IMM({0})".format(index) | |||||
except ValueError: | |||||
indir = types.index("indirect") | |||||
if not conds[indir].startswith("imm"): | |||||
msg = "Passing non-immediate indirect as immediate" | |||||
raise RuntimeError(msg) | |||||
imm = "INST_INDIRECT({0}).addr.imm".format(indir) | |||||
ret[i] = "INST_IMM_U16_B1({0})".format(imm) | |||||
ret.append("INST_IMM_U16_B2({0})".format(imm)) | |||||
break | |||||
elif byte == "rel": | |||||
index = types.index("immediate") | |||||
ret[i] = "INST_IMM({0}).sval - 2".format(index) | |||||
else: | |||||
msg = "Unsupported return byte: {0}" | |||||
raise RuntimeError(msg.format(byte)) | |||||
for i, cond in enumerate(conds): | |||||
if types[i] == "register" and cond[0] == "i": | |||||
prefix = "INST_I{0}_PREFIX".format(cond[1].upper()) | |||||
if ret[0] != prefix: | |||||
ret.insert(0, prefix) | |||||
elif types[i] == "indexed": | |||||
ret.insert(0, "INST_INDEX_PREFIX({0})".format(i)) | |||||
ret.insert(2, "INST_INDEX({0}).offset".format(i)) | |||||
return ret | |||||
def _handle_null_case(self, case): | |||||
""" | |||||
Return code to handle an instruction case that takes no arguments. | |||||
""" | |||||
return [ | |||||
TAB + "if (INST_NARGS == 0) {", | |||||
self._handle_return(case["return"], 2), | |||||
TAB + "}" | |||||
] | |||||
def _handle_pseudo_case(self, pseudo, case): | |||||
""" | |||||
Return code to handle an instruction pseudo-case. | |||||
Pseudo-cases are cases that have pseudo-types as arguments. This means | |||||
they are expanded to cover multiple "real" argument types. | |||||
""" | |||||
index = case["type"].index(pseudo) | |||||
if pseudo == "indirect_hl_or_indexed": | |||||
case["type"][index] = "indexed" | |||||
indexed = self._handle_case(case) | |||||
case["type"][index] = "indirect" | |||||
indirect = self._handle_case(case) | |||||
base_cond = self._build_case_type_check(case["type"]) | |||||
hl_reg = TAB * 3 + self._build_indirect_check(index, "reg.hl") | |||||
indirect[0] = TAB + "if ({0} &&\n{1}) {{".format(base_cond, hl_reg) | |||||
return indirect + indexed | |||||
raise RuntimeError("Unknown pseudo-type: {0}".format(pseudo)) | |||||
def _handle_case(self, case): | |||||
""" | |||||
Return code to handle an instruction case. | |||||
""" | |||||
ctype = case["type"] | |||||
if not ctype: | |||||
return self._handle_null_case(case) | |||||
for pseudo in self.PSEUDO_TYPES: | |||||
if pseudo in ctype: | |||||
return self._handle_pseudo_case(pseudo, case) | |||||
lines = [] | |||||
cond = self._build_case_type_check(ctype) | |||||
lines.append(TAB + "if ({0}) {{".format(cond)) | |||||
subcases = [(perm, sub["return"]) for sub in case["cases"] | |||||
for perm in self._iter_permutations(ctype, sub["cond"])] | |||||
for cond, ret in subcases: | |||||
check = self._build_subcase_check(ctype, cond) | |||||
ret = self._adapt_return(ctype, cond, ret) | |||||
if check: | |||||
lines.append(TAB * 2 + "if ({0})".format(check)) | |||||
lines.append(self._handle_return(ret, 3)) | |||||
else: | |||||
lines.append(self._handle_return(ret, 2)) | |||||
break # Unconditional subcase | |||||
else: | |||||
lines.append(TAB * 2 + "INST_ERROR(ARG_VALUE)") | |||||
lines.append(TAB + "}") | |||||
return lines | |||||
def render(self): | |||||
""" | |||||
Convert data for an individual instruction into a C parse function. | |||||
""" | |||||
lines = [] | |||||
if self._data["args"]: | |||||
lines.append("{tab}INST_TAKES_ARGS(\n{tab2}{0},\n{tab2}{1}," | |||||
"\n{tab2}{2}\n{tab})".format( | |||||
self._get_arg_parse_mask(0), self._get_arg_parse_mask(1), | |||||
self._get_arg_parse_mask(2), tab=TAB, tab2=TAB * 2)) | |||||
else: | |||||
lines.append(TAB + "INST_TAKES_NO_ARGS") | |||||
if "return" in self._data: | |||||
lines.append(self._handle_return(self._data["return"])) | |||||
elif "cases" in self._data: | |||||
for case in self._data["cases"]: | |||||
lines.extend(self._handle_case(case)) | |||||
lines.append(TAB + "INST_ERROR(ARG_TYPE)") | |||||
else: | |||||
msg = "Missing return or case block for {0} instruction" | |||||
raise RuntimeError(msg.format(self._name)) | |||||
contents = "\n".join(lines) | |||||
return "INST_FUNC({0})\n{{\n{1}\n}}".format(self._name, contents) | |||||
def build_inst_block(data): | |||||
""" | |||||
Return the instruction parser block, given instruction data. | |||||
""" | |||||
return "\n\n".join( | |||||
Instruction(k, v).render() for k, v in sorted(data.items())) | |||||
def build_lookup_block(data): | |||||
""" | |||||
Return the instruction lookup block, given instruction data. | |||||
""" | |||||
macro = TAB + "HANDLE({0})" | |||||
return "\n".join(macro.format(inst) for inst in sorted(data.keys())) | |||||
def process(template, data): | |||||
""" | |||||
Return C code generated from a source template and instruction data. | |||||
""" | |||||
inst_block = build_inst_block(data) | |||||
lookup_block = build_lookup_block(data) | |||||
date = time.asctime(time.gmtime()) | |||||
result = re_date.sub(r"\1{0} UTC".format(date), template) | |||||
result = re_inst.sub(r"\1{0}\3".format(inst_block), result) | |||||
result = re_lookup.sub(r"\1{0}\3".format(lookup_block), result) | |||||
return result | |||||
def main(): | |||||
""" | |||||
Main script entry point. | |||||
""" | |||||
with open(SOURCE, "r") as fp: | |||||
text = fp.read().decode(ENCODING) | |||||
with open(DEST, "r") as fp: | |||||
template = fp.read().decode(ENCODING) | |||||
data = yaml.load(text) | |||||
result = process(template, data) | |||||
with open(DEST, "w") as fp: | |||||
fp.write(result.encode(ENCODING)) | |||||
if __name__ == "__main__": | |||||
main() |
@@ -53,11 +53,8 @@ static const char *error_descs[] = { | |||||
[ED_PS_TOO_FEW_ARGS] = "too few arguments for opcode", | [ED_PS_TOO_FEW_ARGS] = "too few arguments for opcode", | ||||
[ED_PS_TOO_MANY_ARGS] = "too many arguments for opcode", | [ED_PS_TOO_MANY_ARGS] = "too many arguments for opcode", | ||||
[ED_PS_ARG_SYNTAX] = "invalid syntax in argument(s)", | [ED_PS_ARG_SYNTAX] = "invalid syntax in argument(s)", | ||||
[ED_PS_ARG0_TYPE] = "invalid type for first argument", | |||||
[ED_PS_ARG0_BAD_REG] = "unsupported register as first argument", | |||||
[ED_PS_ARG1_TYPE] = "invalid type for second argument", | |||||
[ED_PS_ARG1_BAD_REG] = "unsupported register as second argument", | |||||
[ED_PS_ARG1_RANGE] = "second argument out of range" | |||||
[ED_PS_ARG_TYPE] = "invalid argument type", | |||||
[ED_PS_ARG_VALUE] = "invalid value for argument" | |||||
}; | }; | ||||
/* Internal structs */ | /* Internal structs */ | ||||
@@ -52,11 +52,8 @@ typedef enum { | |||||
ED_PS_TOO_FEW_ARGS, | ED_PS_TOO_FEW_ARGS, | ||||
ED_PS_TOO_MANY_ARGS, | ED_PS_TOO_MANY_ARGS, | ||||
ED_PS_ARG_SYNTAX, | ED_PS_ARG_SYNTAX, | ||||
ED_PS_ARG0_TYPE, | |||||
ED_PS_ARG0_BAD_REG, | |||||
ED_PS_ARG1_TYPE, | |||||
ED_PS_ARG1_BAD_REG, | |||||
ED_PS_ARG1_RANGE | |||||
ED_PS_ARG_TYPE, | |||||
ED_PS_ARG_VALUE | |||||
} ASMErrorDesc; | } ASMErrorDesc; | ||||
/* Structs */ | /* Structs */ | ||||
@@ -8,12 +8,14 @@ | |||||
#define MAX_SYMBOL_SIZE 256 | #define MAX_SYMBOL_SIZE 256 | ||||
typedef enum { | typedef enum { | ||||
AT_REGISTER, | |||||
AT_IMMEDIATE, | |||||
AT_INDIRECT, | |||||
AT_INDEXED, | |||||
AT_LABEL, | |||||
AT_CONDITION | |||||
AT_NONE = 0x00, | |||||
AT_OPTIONAL = 0x01, | |||||
AT_REGISTER = 0x02, | |||||
AT_IMMEDIATE = 0x04, | |||||
AT_INDIRECT = 0x08, | |||||
AT_INDEXED = 0x10, | |||||
AT_CONDITION = 0x20, | |||||
AT_PORT = 0x40 | |||||
} ASMArgType; | } ASMArgType; | ||||
typedef enum { | typedef enum { | ||||
@@ -35,20 +37,17 @@ typedef enum { | |||||
typedef struct { | typedef struct { | ||||
ASMArgImmType mask; | ASMArgImmType mask; | ||||
bool is_label; | |||||
uint16_t uval; | uint16_t uval; | ||||
int16_t sval; | int16_t sval; | ||||
char label[MAX_SYMBOL_SIZE]; | |||||
} ASMArgImmediate; | } ASMArgImmediate; | ||||
typedef struct { | typedef struct { | ||||
char text[MAX_SYMBOL_SIZE]; | |||||
} ASMArgLabel; | |||||
typedef struct { | |||||
ASMArgType type; | ASMArgType type; | ||||
union { | union { | ||||
ASMArgRegister reg; | ASMArgRegister reg; | ||||
ASMArgImmediate imm; | ASMArgImmediate imm; | ||||
ASMArgLabel label; | |||||
} addr; | } addr; | ||||
} ASMArgIndirect; | } ASMArgIndirect; | ||||
@@ -58,7 +57,7 @@ typedef struct { | |||||
} ASMArgIndexed; | } ASMArgIndexed; | ||||
typedef enum { | typedef enum { | ||||
COND_NZ, COND_N, COND_NC, COND_C, COND_PO, COND_PE, COND_P, COND_M | |||||
COND_NZ, COND_Z, COND_NC, COND_C, COND_PO, COND_PE, COND_P, COND_M | |||||
} ASMArgCondition; | } ASMArgCondition; | ||||
typedef struct { | typedef struct { | ||||
@@ -66,9 +65,17 @@ typedef struct { | |||||
union { | union { | ||||
ASMArgRegister reg; | ASMArgRegister reg; | ||||
ASMArgImmediate imm; | ASMArgImmediate imm; | ||||
} port; | |||||
} ASMArgPort; | |||||
typedef struct { | |||||
ASMArgType type; | |||||
union { | |||||
ASMArgRegister reg; | |||||
ASMArgImmediate imm; | |||||
ASMArgIndirect indirect; | ASMArgIndirect indirect; | ||||
ASMArgIndexed index; | ASMArgIndexed index; | ||||
ASMArgLabel label; | |||||
ASMArgCondition cond; | ASMArgCondition cond; | ||||
ASMArgPort port; | |||||
} data; | } data; | ||||
} ASMInstArg; | } ASMInstArg; |
@@ -1,98 +0,0 @@ | |||||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Released under the terms of the MIT License. See LICENSE for details. */ | |||||
#include "inst_support.h" | |||||
/* Macro used by parse_arg() */ | |||||
#define TRY_PARSER(func, argtype, field) \ | |||||
if (argparse_##func(&arg->data.field, info)) { \ | |||||
arg->type = argtype; \ | |||||
return ED_NONE; \ | |||||
} | |||||
/* | |||||
Fill an instruction's byte array with the given data. | |||||
This internal function is only called for instructions longer than four | |||||
bytes (of which there is only one: the fake emulator debugging/testing | |||||
opcode with mnemonic "emu"), so it does not get used in normal situations. | |||||
Return the value of the last byte inserted, for compatibility with the | |||||
INST_SETn_ family of macros. | |||||
*/ | |||||
uint8_t fill_bytes_variadic(uint8_t *bytes, size_t len, ...) | |||||
{ | |||||
va_list vargs; | |||||
va_start(vargs, len); | |||||
for (size_t i = 0; i < len; i++) | |||||
bytes[i] = va_arg(vargs, unsigned); | |||||
va_end(vargs); | |||||
return bytes[len - 1]; | |||||
} | |||||
/* | |||||
Parse a single instruction argument into an ASMInstArg object. | |||||
Return ED_NONE (0) on success or an error code on failure. | |||||
*/ | |||||
static ASMErrorDesc parse_arg( | |||||
ASMInstArg *arg, const char *str, size_t size, ASMDefineTable *deftable) | |||||
{ | |||||
ASMArgParseInfo info = {.arg = str, .size = size, .deftable = deftable}; | |||||
TRY_PARSER(register, AT_REGISTER, reg) | |||||
TRY_PARSER(immediate, AT_IMMEDIATE, imm) | |||||
TRY_PARSER(indirect, AT_INDIRECT, indirect) | |||||
TRY_PARSER(indexed, AT_INDEXED, index) | |||||
TRY_PARSER(condition, AT_CONDITION, cond) | |||||
TRY_PARSER(label, AT_LABEL, label) | |||||
return ED_PS_ARG_SYNTAX; | |||||
} | |||||
/* | |||||
Parse an argument string into ASMInstArg objects. | |||||
Return ED_NONE (0) on success or an error code on failure. | |||||
*/ | |||||
ASMErrorDesc parse_args( | |||||
ASMInstArg args[3], size_t *nargs, ASMArgParseInfo ap_info) | |||||
{ | |||||
ASMErrorDesc err; | |||||
ASMDefineTable *dt = ap_info.deftable; | |||||
const char *str = ap_info.arg; | |||||
size_t size = ap_info.size, start = 0, i = 0; | |||||
while (i < size) { | |||||
char c = str[i]; | |||||
if (c == ',') { | |||||
if (i == start) | |||||
return ED_PS_ARG_SYNTAX; | |||||
if ((err = parse_arg(&args[*nargs], str + start, i - start, dt))) | |||||
return err; | |||||
(*nargs)++; | |||||
i++; | |||||
if (i < size && str[i] == ' ') | |||||
i++; | |||||
start = i; | |||||
if (i == size) | |||||
return ED_PS_ARG_SYNTAX; | |||||
if (*nargs >= 3) | |||||
return ED_PS_TOO_MANY_ARGS; | |||||
} else { | |||||
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || | |||||
c == ' ' || c == '+' || c == '-' || c == '(' || c == ')' || | |||||
c == '$' || c == '_' || c == '.') | |||||
i++; | |||||
else | |||||
return ED_PS_ARG_SYNTAX; | |||||
} | |||||
} | |||||
if (i > start) { | |||||
if ((err = parse_arg(&args[*nargs], str + start, i - start, dt))) | |||||
return err; | |||||
(*nargs)++; | |||||
} | |||||
return ED_NONE; | |||||
} |
@@ -1,155 +0,0 @@ | |||||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Released under the terms of the MIT License. See LICENSE for details. */ | |||||
#pragma once | |||||
#include <stdarg.h> | |||||
#include <stdlib.h> | |||||
#include "errors.h" | |||||
#include "inst_args.h" | |||||
#include "parse_util.h" | |||||
#include "../util.h" | |||||
/* Helper macros for get_inst_parser() */ | |||||
#define JOIN_(a, b, c, d) ((uint32_t) ((a << 24) + (b << 16) + (c << 8) + d)) | |||||
#define DISPATCH_(s, z) ( \ | |||||
(z) == 2 ? JOIN_(s[0], s[1], 0x00, 0x00) : \ | |||||
(z) == 3 ? JOIN_(s[0], s[1], s[2], 0x00) : \ | |||||
JOIN_(s[0], s[1], s[2], s[3])) \ | |||||
#define MAKE_CMP_(s) DISPATCH_(s, sizeof(s) / sizeof(char) - 1) | |||||
#define HANDLE(m) if (key == MAKE_CMP_(#m)) return parse_inst_##m; | |||||
/* Internal helper macros */ | |||||
#define INST_ALLOC_(len) \ | |||||
*length = len; \ | |||||
*bytes = cr_malloc(sizeof(uint8_t) * (len)); | |||||
#define INST_SET_(b, val) ((*bytes)[b] = val) | |||||
#define INST_SET1_(b1) INST_SET_(0, b1) | |||||
#define INST_SET2_(b1, b2) INST_SET1_(b1), INST_SET_(1, b2) | |||||
#define INST_SET3_(b1, b2, b3) INST_SET2_(b1, b2), INST_SET_(2, b3) | |||||
#define INST_SET4_(b1, b2, b3, b4) INST_SET3_(b1, b2, b3), INST_SET_(3, b4) | |||||
#define INST_DISPATCH_(a, b, c, d, target, ...) target | |||||
#define INST_FILL_BYTES_(len, ...) \ | |||||
((len > 4) ? fill_bytes_variadic(*bytes, len, __VA_ARGS__) : \ | |||||
INST_DISPATCH_(__VA_ARGS__, INST_SET4_, INST_SET3_, INST_SET2_, \ | |||||
INST_SET1_, __VA_ARGS__)(__VA_ARGS__)); | |||||
#define INST_IX_PREFIX_ 0xDD | |||||
#define INST_IY_PREFIX_ 0xFD | |||||
#define INST_PREFIX_(reg) \ | |||||
(((reg) == REG_IX || (reg) == REG_IXH || (reg) == REG_IXL) ? \ | |||||
INST_IX_PREFIX_ : INST_IY_PREFIX_) | |||||
#define INST_RETURN_WITH_SYMBOL_(len, label, ...) { \ | |||||
*symbol = cr_strdup(label.text); \ | |||||
INST_ALLOC_(len) \ | |||||
INST_FILL_BYTES_(len - 2, __VA_ARGS__) \ | |||||
return ED_NONE; \ | |||||
} | |||||
/* Essential/basic helper macros */ | |||||
#define INST_FUNC(mnemonic) \ | |||||
static ASMErrorDesc parse_inst_##mnemonic( \ | |||||
uint8_t **bytes, size_t *length, char **symbol, ASMArgParseInfo ap_info) \ | |||||
#define INST_ERROR(desc) return ED_PS_##desc; | |||||
#define INST_TAKES_NO_ARGS \ | |||||
if (ap_info.arg) \ | |||||
INST_ERROR(TOO_MANY_ARGS) | |||||
#define INST_TAKES_ARGS(lo, hi) \ | |||||
if (!ap_info.arg) \ | |||||
INST_ERROR(TOO_FEW_ARGS) \ | |||||
ASMInstArg args[3]; \ | |||||
size_t nargs = 0; \ | |||||
ASMErrorDesc err = parse_args(args, &nargs, ap_info); \ | |||||
if (err) \ | |||||
return err; \ | |||||
if (nargs < lo) \ | |||||
INST_ERROR(TOO_FEW_ARGS) \ | |||||
if (nargs > hi) \ | |||||
INST_ERROR(TOO_MANY_ARGS) | |||||
#define INST_NARGS nargs | |||||
#define INST_TYPE(n) args[n].type | |||||
#define INST_REG(n) args[n].data.reg | |||||
#define INST_IMM(n) args[n].data.imm | |||||
#define INST_INDIRECT(n) args[n].data.indirect | |||||
#define INST_INDEX(n) args[n].data.index | |||||
#define INST_LABEL(n) args[n].data.label | |||||
#define INST_COND(n) args[n].data.cond | |||||
#define INST_RETURN(len, ...) { \ | |||||
(void) symbol; \ | |||||
INST_ALLOC_(len) \ | |||||
INST_FILL_BYTES_(len, __VA_ARGS__) \ | |||||
return ED_NONE; \ | |||||
} | |||||
/* Convenience macros */ | |||||
#define INST_FORCE_TYPE(n, t) { \ | |||||
if (INST_TYPE(n) != t) \ | |||||
INST_ERROR(ARG##n##_TYPE) \ | |||||
} | |||||
#define INST_CHECK_IMM(n, m) { \ | |||||
if (!(INST_IMM(n).mask & (m))) \ | |||||
INST_ERROR(ARG##n##_RANGE) \ | |||||
} | |||||
#define INST_REG_ONLY(n, reg) { \ | |||||
if (INST_TYPE(n) != AT_REGISTER) \ | |||||
INST_ERROR(ARG##n##_TYPE) \ | |||||
if (INST_REG(n) != reg) \ | |||||
INST_ERROR(ARG##n##_BAD_REG) \ | |||||
} | |||||
#define INST_INDIRECT_HL_ONLY(n) { \ | |||||
if (INST_INDIRECT(n).type != AT_REGISTER) \ | |||||
INST_ERROR(ARG##n##_TYPE) \ | |||||
if (INST_INDIRECT(n).addr.reg != REG_HL) \ | |||||
INST_ERROR(ARG##n##_BAD_REG) \ | |||||
} | |||||
#define INST_INDEX_PREFIX(n) INST_PREFIX_(INST_INDEX(n).reg) | |||||
#define INST_INDEX_BYTES(n, b) \ | |||||
INST_INDEX_PREFIX(n), b, INST_INDEX(n).offset | |||||
#define INST_INDIRECT_IMM(n) \ | |||||
INST_INDIRECT(n).addr.imm.uval >> 8, \ | |||||
INST_INDIRECT(n).addr.imm.uval & 0xFF | |||||
#define INST_RETURN_INDIRECT_LABEL(n, len, ...) \ | |||||
INST_RETURN_WITH_SYMBOL_(len, INST_INDIRECT(n).addr.label, __VA_ARGS__) | |||||
#define INST_HANDLE_MAIN_8_BIT_REGS(base) \ | |||||
case REG_A: INST_RETURN(1, base + 7) \ | |||||
case REG_B: INST_RETURN(1, base) \ | |||||
case REG_C: INST_RETURN(1, base + 1) \ | |||||
case REG_D: INST_RETURN(1, base + 2) \ | |||||
case REG_E: INST_RETURN(1, base + 3) \ | |||||
case REG_H: INST_RETURN(1, base + 4) \ | |||||
case REG_L: INST_RETURN(1, base + 5) \ | |||||
case REG_IXH: INST_RETURN(2, INST_IX_PREFIX_, base + 4) \ | |||||
case REG_IXL: INST_RETURN(2, INST_IX_PREFIX_, base + 5) \ | |||||
case REG_IYH: INST_RETURN(2, INST_IY_PREFIX_, base + 4) \ | |||||
case REG_IYL: INST_RETURN(2, INST_IY_PREFIX_, base + 5) \ | |||||
/* Internal functions */ | |||||
uint8_t fill_bytes_variadic(uint8_t*, size_t, ...); | |||||
ASMErrorDesc parse_args(ASMInstArg args[3], size_t*, ASMArgParseInfo); |
@@ -1,705 +1,203 @@ | |||||
/* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | /* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | ||||
Released under the terms of the MIT License. See LICENSE for details. */ | Released under the terms of the MIT License. See LICENSE for details. */ | ||||
#include "instructions.h" | |||||
#include "inst_support.h" | |||||
/* Instruction parser functions */ | |||||
INST_FUNC(adc) | |||||
{ | |||||
INST_TAKES_ARGS(2, 2) | |||||
INST_FORCE_TYPE(0, AT_REGISTER) | |||||
switch (INST_REG(0)) { | |||||
case REG_A: | |||||
switch (INST_TYPE(1)) { | |||||
case AT_REGISTER: | |||||
switch (INST_REG(1)) { | |||||
INST_HANDLE_MAIN_8_BIT_REGS(0x88) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
case AT_IMMEDIATE: | |||||
INST_CHECK_IMM(1, IMM_U8) | |||||
INST_RETURN(2, 0xCE, INST_IMM(1).uval) | |||||
case AT_INDIRECT: | |||||
INST_INDIRECT_HL_ONLY(1) | |||||
INST_RETURN(1, 0x8E) | |||||
case AT_INDEXED: | |||||
INST_RETURN(3, INST_INDEX_BYTES(1, 0x8E)) | |||||
default: | |||||
INST_ERROR(ARG1_TYPE) | |||||
} | |||||
case REG_HL: | |||||
INST_FORCE_TYPE(1, AT_REGISTER) | |||||
switch (INST_REG(1)) { | |||||
case REG_BC: INST_RETURN(2, 0xED, 0x4A) | |||||
case REG_DE: INST_RETURN(2, 0xED, 0x5A) | |||||
case REG_HL: INST_RETURN(2, 0xED, 0x6A) | |||||
case REG_SP: INST_RETURN(2, 0xED, 0x7A) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
default: | |||||
INST_ERROR(ARG0_TYPE) | |||||
} | |||||
} | |||||
INST_FUNC(add) | |||||
{ | |||||
INST_TAKES_ARGS(2, 2) | |||||
INST_FORCE_TYPE(0, AT_REGISTER) | |||||
switch (INST_REG(0)) { | |||||
case REG_A: | |||||
switch (INST_TYPE(1)) { | |||||
case AT_REGISTER: | |||||
switch (INST_REG(1)) { | |||||
INST_HANDLE_MAIN_8_BIT_REGS(0x80) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
case AT_IMMEDIATE: | |||||
INST_CHECK_IMM(1, IMM_U8) | |||||
INST_RETURN(2, 0xC6, INST_IMM(1).uval) | |||||
case AT_INDIRECT: | |||||
INST_INDIRECT_HL_ONLY(1) | |||||
INST_RETURN(1, 0x86) | |||||
case AT_INDEXED: | |||||
INST_RETURN(3, INST_INDEX_BYTES(1, 0x86)) | |||||
default: | |||||
INST_ERROR(ARG1_TYPE) | |||||
} | |||||
case REG_HL: | |||||
INST_FORCE_TYPE(1, AT_REGISTER) | |||||
switch (INST_REG(1)) { | |||||
case REG_BC: INST_RETURN(1, 0x09) | |||||
case REG_DE: INST_RETURN(1, 0x19) | |||||
case REG_HL: INST_RETURN(1, 0x29) | |||||
case REG_SP: INST_RETURN(1, 0x39) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
case REG_IX: | |||||
case REG_IY: | |||||
INST_FORCE_TYPE(1, AT_REGISTER) | |||||
switch (INST_REG(1)) { | |||||
case REG_BC: INST_RETURN(2, INST_INDEX_PREFIX(1), 0x09) | |||||
case REG_DE: INST_RETURN(2, INST_INDEX_PREFIX(1), 0x19) | |||||
case REG_IX: | |||||
case REG_IY: | |||||
if (INST_REG(0) != INST_REG(1)) | |||||
INST_ERROR(ARG1_BAD_REG) | |||||
INST_RETURN(2, INST_INDEX_PREFIX(1), 0x29) | |||||
case REG_SP: INST_RETURN(2, INST_INDEX_PREFIX(1), 0x39) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
default: | |||||
INST_ERROR(ARG0_TYPE) | |||||
} | |||||
} | |||||
INST_FUNC(and) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(bit) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(call) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(ccf) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x3F) | |||||
} | |||||
INST_FUNC(cp) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(cpd) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xA9) | |||||
} | |||||
INST_FUNC(cpdr) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xB9) | |||||
} | |||||
INST_FUNC(cpi) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xA1) | |||||
} | |||||
INST_FUNC(cpir) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xB1) | |||||
} | |||||
INST_FUNC(cpl) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x2F) | |||||
} | |||||
INST_FUNC(daa) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x27) | |||||
} | |||||
INST_FUNC(dec) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(di) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0xF3) | |||||
} | |||||
#include <stdarg.h> | |||||
#include <stdlib.h> | |||||
INST_FUNC(djnz) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
#include "instructions.h" | |||||
#include "inst_args.h" | |||||
#include "../util.h" | |||||
INST_FUNC(ei) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0xFB) | |||||
} | |||||
/* Helper macros for get_inst_parser() and lookup_parser() */ | |||||
INST_FUNC(ex) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
#define JOIN(a, b, c, d) ((uint32_t) ((a << 24) + (b << 16) + (c << 8) + d)) | |||||
INST_FUNC(exx) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0xD9) | |||||
} | |||||
#define DISPATCH_(s, z) ( \ | |||||
(z) == 2 ? JOIN(s[0], s[1], 0x00, 0x00) : \ | |||||
(z) == 3 ? JOIN(s[0], s[1], s[2], 0x00) : \ | |||||
JOIN(s[0], s[1], s[2], s[3])) \ | |||||
INST_FUNC(halt) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x76) | |||||
} | |||||
#define MAKE_CMP_(s) DISPATCH_(s, sizeof(s) / sizeof(char) - 1) | |||||
INST_FUNC(im) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
#define HANDLE(m) if (key == MAKE_CMP_(#m)) return parse_inst_##m; | |||||
INST_FUNC(in) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
/* Helper macro for parse_arg() */ | |||||
INST_FUNC(inc) | |||||
{ | |||||
INST_TAKES_ARGS(1, 1) | |||||
switch (INST_TYPE(0)) { | |||||
case AT_REGISTER: | |||||
switch (INST_REG(0)) { | |||||
case REG_A: INST_RETURN(1, 0x3C) | |||||
case REG_B: INST_RETURN(1, 0x04) | |||||
case REG_C: INST_RETURN(1, 0x0C) | |||||
case REG_D: INST_RETURN(1, 0x14) | |||||
case REG_E: INST_RETURN(1, 0x1C) | |||||
case REG_H: INST_RETURN(1, 0x24) | |||||
case REG_L: INST_RETURN(1, 0x2C) | |||||
case REG_BC: INST_RETURN(1, 0x03) | |||||
case REG_DE: INST_RETURN(1, 0x13) | |||||
case REG_HL: INST_RETURN(1, 0x23) | |||||
case REG_SP: INST_RETURN(1, 0x33) | |||||
case REG_IX: INST_RETURN(2, 0xDD, 0x23) | |||||
case REG_IY: INST_RETURN(2, 0xFD, 0x23) | |||||
case REG_IXH: INST_RETURN(2, 0xDD, 0x2C) | |||||
case REG_IXL: INST_RETURN(2, 0xFD, 0x2C) | |||||
case REG_IYH: INST_RETURN(2, 0xDD, 0x2C) | |||||
case REG_IYL: INST_RETURN(2, 0xFD, 0x2C) | |||||
default: INST_ERROR(ARG0_BAD_REG) | |||||
} | |||||
case AT_INDIRECT: | |||||
INST_INDIRECT_HL_ONLY(0) | |||||
INST_RETURN(1, 0x34) | |||||
case AT_INDEXED: | |||||
INST_RETURN(3, INST_INDEX_BYTES(0, 0x34)) | |||||
default: | |||||
INST_ERROR(ARG0_TYPE) | |||||
#define TRY_PARSER(func, argtype, field) \ | |||||
if (mask & argtype && argparse_##func(&arg->data.field, info)) { \ | |||||
arg->type = argtype; \ | |||||
return ED_NONE; \ | |||||
} | } | ||||
} | |||||
INST_FUNC(ind) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xAA) | |||||
} | |||||
INST_FUNC(indr) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xBA) | |||||
} | |||||
INST_FUNC(ini) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xA2) | |||||
} | |||||
INST_FUNC(inir) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xB2) | |||||
} | |||||
INST_FUNC(jp) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(jr) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(ld) | |||||
{ | |||||
uint8_t base; | |||||
INST_TAKES_ARGS(2, 2) | |||||
switch (INST_TYPE(0)) { | |||||
case AT_REGISTER: | |||||
switch (INST_REG(0)) { | |||||
case REG_A: | |||||
switch (INST_TYPE(1)) { | |||||
case AT_REGISTER: | |||||
switch (INST_REG(1)) { | |||||
INST_HANDLE_MAIN_8_BIT_REGS(0x78) | |||||
case REG_I: INST_RETURN(2, 0xED, 0x57) | |||||
case REG_R: INST_RETURN(2, 0xED, 0x5F) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
case AT_IMMEDIATE: | |||||
INST_CHECK_IMM(1, IMM_U8) | |||||
INST_RETURN(2, 0x3E, INST_IMM(1).uval) | |||||
case AT_INDIRECT: | |||||
switch (INST_INDIRECT(1).type) { | |||||
case AT_REGISTER: | |||||
switch (INST_INDIRECT(1).addr.reg) { | |||||
case REG_BC: INST_RETURN(1, 0x0A) | |||||
case REG_DE: INST_RETURN(1, 0x1A) | |||||
case REG_HL: INST_RETURN(1, 0x7E) | |||||
default: INST_ERROR(ARG0_BAD_REG) | |||||
} | |||||
case AT_IMMEDIATE: | |||||
INST_RETURN(3, 0x3A, INST_INDIRECT_IMM(1)) | |||||
case AT_LABEL: | |||||
INST_RETURN_INDIRECT_LABEL(1, 3, 0x3A) | |||||
default: | |||||
INST_ERROR(ARG1_TYPE) | |||||
} | |||||
case AT_INDEXED: | |||||
INST_RETURN(3, INST_INDEX_BYTES(1, 0x7E)) | |||||
default: | |||||
INST_ERROR(ARG1_TYPE) | |||||
} | |||||
case REG_B: | |||||
base = 0x00; | |||||
case REG_C: | |||||
base = 0x08; | |||||
case REG_D: | |||||
base = 0x10; | |||||
case REG_E: | |||||
base = 0x18; | |||||
switch (INST_TYPE(1)) { | |||||
case AT_REGISTER: | |||||
switch (INST_REG(1)) { | |||||
INST_HANDLE_MAIN_8_BIT_REGS(base + 0x40) | |||||
default: INST_ERROR(ARG1_BAD_REG) | |||||
} | |||||
case AT_IMMEDIATE: | |||||
INST_CHECK_IMM(1, IMM_U8) | |||||
INST_RETURN(2, base + 0x06, INST_IMM(1).uval) | |||||
case AT_INDIRECT: | |||||
INST_INDIRECT_HL_ONLY(1) | |||||
INST_RETURN(1, base + 0x46) | |||||
case AT_INDEXED: | |||||
INST_RETURN(3, INST_INDEX_BYTES(1, base + 0x46)) | |||||
default: | |||||
INST_ERROR(ARG1_TYPE) | |||||
} | |||||
case REG_H: // TODO (11 cases) | |||||
case REG_L: // TODO (11 cases) | |||||
case REG_I: | |||||
INST_REG_ONLY(1, REG_A) | |||||
INST_RETURN(2, 0xED, 0x47) | |||||
case REG_R: | |||||
INST_REG_ONLY(1, REG_A) | |||||
INST_RETURN(2, 0xED, 0x4F) | |||||
case REG_BC: // TODO ( 2 cases) | |||||
case REG_DE: // TODO ( 2 cases) | |||||
case REG_HL: // TODO ( 3 cases) | |||||
case REG_IX: // TODO ( 2 cases) | |||||
case REG_IY: // TODO ( 2 cases) | |||||
case REG_SP: // TODO ( 5 cases) | |||||
case REG_IXH: // TODO ( 8 cases) | |||||
case REG_IXL: // TODO ( 8 cases) | |||||
case REG_IYH: // TODO ( 8 cases) | |||||
case REG_IYL: // TODO ( 8 cases) | |||||
default: INST_ERROR(ARG0_BAD_REG) | |||||
} | |||||
case AT_INDIRECT: | |||||
switch (INST_INDIRECT(0).type) { | |||||
case AT_REGISTER: | |||||
switch (INST_INDIRECT(0).addr.reg) { | |||||
case REG_BC: // TODO (1 case ) | |||||
case REG_DE: // TODO (1 case ) | |||||
case REG_HL: // TODO (8 cases) | |||||
default: INST_ERROR(ARG0_BAD_REG) | |||||
} | |||||
case AT_IMMEDIATE: | |||||
// TODO (8 cases) | |||||
case AT_LABEL: | |||||
// TODO (same 8 cases) | |||||
default: | |||||
INST_ERROR(ARG0_TYPE) | |||||
} | |||||
case AT_INDEXED: | |||||
// TODO (16 cases) | |||||
default: | |||||
INST_ERROR(ARG0_TYPE) | |||||
/* Internal helper macros */ | |||||
#define INST_ALLOC_(len) \ | |||||
*length = len; \ | |||||
*bytes = cr_malloc(sizeof(uint8_t) * (len)); | |||||
#define INST_SET_(b, val) ((*bytes)[b] = val) | |||||
#define INST_SET1_(b1) INST_SET_(0, b1) | |||||
#define INST_SET2_(b1, b2) INST_SET1_(b1), INST_SET_(1, b2) | |||||
#define INST_SET3_(b1, b2, b3) INST_SET2_(b1, b2), INST_SET_(2, b3) | |||||
#define INST_SET4_(b1, b2, b3, b4) INST_SET3_(b1, b2, b3), INST_SET_(3, b4) | |||||
#define INST_DISPATCH_(a, b, c, d, target, ...) target | |||||
#define INST_FILL_BYTES_(len, ...) \ | |||||
((len > 4) ? fill_bytes_variadic(*bytes, len, __VA_ARGS__) : \ | |||||
INST_DISPATCH_(__VA_ARGS__, INST_SET4_, INST_SET3_, INST_SET2_, \ | |||||
INST_SET1_, __VA_ARGS__)(__VA_ARGS__)); | |||||
#define INST_PREFIX_(reg) \ | |||||
(((reg) == REG_IX || (reg) == REG_IXH || (reg) == REG_IXL) ? \ | |||||
INST_IX_PREFIX : INST_IY_PREFIX) | |||||
/* Helper macros for instruction parsers */ | |||||
#define INST_FUNC(mnemonic) \ | |||||
static ASMErrorDesc parse_inst_##mnemonic( \ | |||||
uint8_t **bytes, size_t *length, char **symbol, ASMArgParseInfo ap_info) \ | |||||
#define INST_ERROR(desc) return ED_PS_##desc; | |||||
#define INST_TAKES_NO_ARGS \ | |||||
if (ap_info.arg) \ | |||||
INST_ERROR(TOO_MANY_ARGS) | |||||
#define INST_TAKES_ARGS(a0, a1, a2) \ | |||||
ASMInstArg args[3]; \ | |||||
size_t nargs; \ | |||||
ASMArgType masks[] = {a0, a1, a2}; \ | |||||
ASMErrorDesc err = parse_args(args, &nargs, ap_info, masks); \ | |||||
if (err) \ | |||||
return err; \ | |||||
#define INST_NARGS nargs | |||||
#define INST_TYPE(n) args[n].type | |||||
#define INST_REG(n) args[n].data.reg | |||||
#define INST_IMM(n) args[n].data.imm | |||||
#define INST_INDIRECT(n) args[n].data.indirect | |||||
#define INST_INDEX(n) args[n].data.index | |||||
#define INST_COND(n) args[n].data.cond | |||||
#define INST_PORT(n) args[n].data.port | |||||
#define INST_IX_PREFIX 0xDD | |||||
#define INST_IY_PREFIX 0xFD | |||||
#define INST_RETURN(len, ...) { \ | |||||
(void) symbol; \ | |||||
INST_ALLOC_(len) \ | |||||
INST_FILL_BYTES_(len, __VA_ARGS__) \ | |||||
return ED_NONE; \ | |||||
} | } | ||||
} | |||||
INST_FUNC(ldd) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xA8) | |||||
} | |||||
INST_FUNC(lddr) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xB8) | |||||
} | |||||
INST_FUNC(ldi) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xA0) | |||||
} | |||||
INST_FUNC(ldir) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xB0) | |||||
} | |||||
INST_FUNC(neg) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(nop) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x00) | |||||
} | |||||
INST_FUNC(or) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(otdr) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xBB) | |||||
} | |||||
INST_FUNC(otir) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xB3) | |||||
} | |||||
INST_FUNC(out) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(outd) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xAB) | |||||
} | |||||
INST_FUNC(outi) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0xA3) | |||||
} | |||||
INST_FUNC(pop) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(push) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(res) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(ret) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(reti) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0x4D) | |||||
} | |||||
INST_FUNC(retn) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0x45) | |||||
} | |||||
INST_FUNC(rl) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(rla) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x17) | |||||
} | |||||
INST_FUNC(rlc) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
#define INST_IMM_U16_B1(imm) \ | |||||
((imm).is_label ? (*symbol = cr_strdup((imm).label), 0) : (imm).uval >> 8) | |||||
#define INST_IMM_U16_B2(imm) \ | |||||
((imm).is_label ? 0 : (imm).uval & 0xFF) | |||||
INST_FUNC(rlca) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x07) | |||||
} | |||||
#define INST_INDEX_PREFIX(n) INST_PREFIX_(INST_INDEX(n).reg) | |||||
INST_FUNC(rld) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(2, 0xED, 0x6F) | |||||
} | |||||
/* ----------------------------- END WORK BLOCK ---------------------------- */ | |||||
INST_FUNC(rr) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(rra) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x1F) | |||||
} | |||||
INST_FUNC(rrc) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(rrca) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x0F) | |||||
} | |||||
INST_FUNC(rrd) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(rst) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(sbc) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
INST_FUNC(scf) | |||||
{ | |||||
INST_TAKES_NO_ARGS | |||||
INST_RETURN(1, 0x37) | |||||
} | |||||
/* | |||||
Fill an instruction's byte array with the given data. | |||||
INST_FUNC(set) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
This internal function is only called for instructions longer than four | |||||
bytes (of which there is only one: the fake emulator debugging/testing | |||||
opcode with mnemonic "emu"), so it does not get used in normal situations. | |||||
INST_FUNC(sl1) | |||||
Return the value of the last byte inserted, for compatibility with the | |||||
INST_SETn_ family of macros. | |||||
*/ | |||||
static uint8_t fill_bytes_variadic(uint8_t *bytes, size_t len, ...) | |||||
{ | { | ||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
va_list vargs; | |||||
va_start(vargs, len); | |||||
for (size_t i = 0; i < len; i++) | |||||
bytes[i] = va_arg(vargs, unsigned); | |||||
va_end(vargs); | |||||
return bytes[len - 1]; | |||||
} | } | ||||
INST_FUNC(sla) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
/* | |||||
Parse a single instruction argument into an ASMInstArg object. | |||||
INST_FUNC(sll) | |||||
Return ED_NONE (0) on success or an error code on failure. | |||||
*/ | |||||
static ASMErrorDesc parse_arg( | |||||
ASMInstArg *arg, const char *str, size_t size, ASMDefineTable *deftable, | |||||
ASMArgType mask) | |||||
{ | { | ||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
ASMArgParseInfo info = {.arg = str, .size = size, .deftable = deftable}; | |||||
TRY_PARSER(register, AT_REGISTER, reg) | |||||
TRY_PARSER(condition, AT_CONDITION, cond) | |||||
TRY_PARSER(indexed, AT_INDEXED, index) | |||||
TRY_PARSER(indirect, AT_INDIRECT, indirect) | |||||
TRY_PARSER(port, AT_PORT, port) | |||||
TRY_PARSER(immediate, AT_IMMEDIATE, imm) | |||||
return ED_PS_ARG_SYNTAX; | |||||
} | } | ||||
INST_FUNC(sls) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
/* | |||||
Parse an argument string into ASMInstArg objects. | |||||
INST_FUNC(sra) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
Return ED_NONE (0) on success or an error code on failure. | |||||
*/ | |||||
static ASMErrorDesc parse_args( | |||||
ASMInstArg args[3], size_t *nargs, ASMArgParseInfo ap_info, | |||||
ASMArgType masks[3]) | |||||
{ | |||||
ASMErrorDesc err; | |||||
ASMDefineTable *dt = ap_info.deftable; | |||||
const char *str = ap_info.arg; | |||||
size_t size = ap_info.size, start = 0, i = 0, n = 0; | |||||
while (i < size) { | |||||
char c = str[i]; | |||||
if (c == ',') { | |||||
if (i == start) | |||||
return ED_PS_ARG_SYNTAX; | |||||
if (masks[n] == AT_NONE) | |||||
return ED_PS_TOO_MANY_ARGS; | |||||
err = parse_arg(&args[n], str + start, i - start, dt, masks[n]); | |||||
if (err) | |||||
return err; | |||||
n++; | |||||
i++; | |||||
if (i < size && str[i] == ' ') | |||||
i++; | |||||
start = i; | |||||
if (i == size) | |||||
return ED_PS_ARG_SYNTAX; | |||||
if (n >= 3) | |||||
return ED_PS_TOO_MANY_ARGS; | |||||
} else { | |||||
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || | |||||
c == ' ' || c == '+' || c == '-' || c == '(' || c == ')' || | |||||
c == '$' || c == '_' || c == '.') | |||||
i++; | |||||
else | |||||
return ED_PS_ARG_SYNTAX; | |||||
} | |||||
} | |||||
INST_FUNC(srl) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
if (i > start) { | |||||
if (masks[n] == AT_NONE) | |||||
return ED_PS_TOO_MANY_ARGS; | |||||
if ((err = parse_arg(&args[n], str + start, i - start, dt, masks[n]))) | |||||
return err; | |||||
n++; | |||||
} | |||||
INST_FUNC(sub) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
if (n < 3 && masks[n] != AT_NONE && !(masks[n] & AT_OPTIONAL)) | |||||
return ED_PS_TOO_FEW_ARGS; | |||||
*nargs = n; | |||||
return ED_NONE; | |||||
} | } | ||||
INST_FUNC(xor) | |||||
{ | |||||
// TODO | |||||
INST_TAKES_NO_ARGS | |||||
INST_ERROR(ARG_SYNTAX) | |||||
INST_RETURN(1, 0xFF) | |||||
} | |||||
#include "instructions.inc.c" | |||||
/* | /* | ||||
Return the relevant ASMInstParser function for a given mnemonic. | Return the relevant ASMInstParser function for a given mnemonic. | ||||
@@ -710,78 +208,6 @@ ASMInstParser get_inst_parser(char mstr[MAX_MNEMONIC_SIZE]) | |||||
{ | { | ||||
// Exploit the fact that we can store the entire mnemonic string as a | // Exploit the fact that we can store the entire mnemonic string as a | ||||
// single 32-bit value to do fast lookups: | // single 32-bit value to do fast lookups: | ||||
uint32_t key = (mstr[0] << 24) + (mstr[1] << 16) + (mstr[2] << 8) + mstr[3]; | |||||
HANDLE(adc) | |||||
HANDLE(add) | |||||
HANDLE(and) | |||||
HANDLE(bit) | |||||
HANDLE(call) | |||||
HANDLE(ccf) | |||||
HANDLE(cp) | |||||
HANDLE(cpd) | |||||
HANDLE(cpdr) | |||||
HANDLE(cpi) | |||||
HANDLE(cpir) | |||||
HANDLE(cpl) | |||||
HANDLE(daa) | |||||
HANDLE(dec) | |||||
HANDLE(di) | |||||
HANDLE(djnz) | |||||
HANDLE(ei) | |||||
HANDLE(ex) | |||||
HANDLE(exx) | |||||
HANDLE(halt) | |||||
HANDLE(im) | |||||
HANDLE(in) | |||||
HANDLE(inc) | |||||
HANDLE(ind) | |||||
HANDLE(indr) | |||||
HANDLE(ini) | |||||
HANDLE(inir) | |||||
HANDLE(jp) | |||||
HANDLE(jr) | |||||
HANDLE(ld) | |||||
HANDLE(ldd) | |||||
HANDLE(lddr) | |||||
HANDLE(ldi) | |||||
HANDLE(ldir) | |||||
HANDLE(neg) | |||||
HANDLE(nop) | |||||
HANDLE(or) | |||||
HANDLE(otdr) | |||||
HANDLE(otir) | |||||
HANDLE(out) | |||||
HANDLE(outd) | |||||
HANDLE(outi) | |||||
HANDLE(pop) | |||||
HANDLE(push) | |||||
HANDLE(res) | |||||
HANDLE(ret) | |||||
HANDLE(reti) | |||||
HANDLE(retn) | |||||
HANDLE(rl) | |||||
HANDLE(rla) | |||||
HANDLE(rlc) | |||||
HANDLE(rlca) | |||||
HANDLE(rld) | |||||
HANDLE(rr) | |||||
HANDLE(rra) | |||||
HANDLE(rrc) | |||||
HANDLE(rrca) | |||||
HANDLE(rrd) | |||||
HANDLE(rst) | |||||
HANDLE(sbc) | |||||
HANDLE(scf) | |||||
HANDLE(set) | |||||
HANDLE(sl1) | |||||
HANDLE(sla) | |||||
HANDLE(sll) | |||||
HANDLE(sls) | |||||
HANDLE(sra) | |||||
HANDLE(srl) | |||||
HANDLE(sub) | |||||
HANDLE(xor) | |||||
return NULL; | |||||
uint32_t key = JOIN(mstr[0], mstr[1], mstr[2], mstr[3]); | |||||
return lookup_parser(key); | |||||
} | } |
@@ -57,29 +57,31 @@ static bool adjust_for_indirection(ASMArgParseInfo *ap_info) | |||||
/* | /* | ||||
Calculate the mask field for an ASMArgImmediate based on its uval/sval. | Calculate the mask field for an ASMArgImmediate based on its uval/sval. | ||||
*/ | */ | ||||
static void calculate_immediate_mask(ASMArgImmediate *imm) | |||||
static ASMArgImmType calculate_immediate_mask(uint32_t uval, bool negative) | |||||
{ | { | ||||
imm->mask = 0; | |||||
if (imm->sval < 0) { | |||||
if (imm->sval >= INT8_MIN) | |||||
imm->mask |= IMM_S8; | |||||
if (imm->sval >= INT8_MIN + 2) | |||||
imm->mask |= IMM_REL; | |||||
ASMArgImmType mask = 0; | |||||
if (negative && uval != 0) { | |||||
int32_t sval = -uval; | |||||
if (sval >= INT8_MIN) | |||||
mask |= IMM_S8; | |||||
if (sval >= INT8_MIN + 2) | |||||
mask |= IMM_REL; | |||||
} else { | } else { | ||||
imm->mask = IMM_U16; | |||||
if (imm->uval <= UINT8_MAX) | |||||
imm->mask |= IMM_U8; | |||||
if (imm->uval <= INT8_MAX) | |||||
imm->mask |= IMM_S8; | |||||
if (imm->uval <= INT8_MAX + 2) | |||||
imm->mask |= IMM_REL; | |||||
if (imm->uval <= 7) | |||||
imm->mask |= IMM_BIT; | |||||
if (!(imm->uval & ~0x38)) | |||||
imm->mask |= IMM_RST; | |||||
if (imm->uval <= 2) | |||||
imm->mask |= IMM_IM; | |||||
mask = IMM_U16; | |||||
if (uval <= UINT8_MAX) | |||||
mask |= IMM_U8; | |||||
if (uval <= INT8_MAX) | |||||
mask |= IMM_S8; | |||||
if (uval <= INT8_MAX + 2) | |||||
mask |= IMM_REL; | |||||
if (uval <= 7) | |||||
mask |= IMM_BIT; | |||||
if (!(uval & ~0x38)) | |||||
mask |= IMM_RST; | |||||
if (uval <= 2) | |||||
mask |= IMM_IM; | |||||
} | } | ||||
return mask; | |||||
} | } | ||||
/* | /* | ||||
@@ -300,7 +302,7 @@ bool argparse_condition(ASMArgCondition *result, ASMArgParseInfo ai) | |||||
switch (ai.size) { | switch (ai.size) { | ||||
case 1: | case 1: | ||||
switch (buf[0]) { | switch (buf[0]) { | ||||
case 'n': return (*result = COND_N), true; | |||||
case 'z': return (*result = COND_Z), true; | |||||
case 'c': return (*result = COND_C), true; | case 'c': return (*result = COND_C), true; | ||||
case 'p': return (*result = COND_P), true; | case 'p': return (*result = COND_P), true; | ||||
case 'm': return (*result = COND_M), true; | case 'm': return (*result = COND_M), true; | ||||
@@ -319,6 +321,26 @@ bool argparse_condition(ASMArgCondition *result, ASMArgParseInfo ai) | |||||
} | } | ||||
/* | /* | ||||
Read in a label immediate argument and store it in *result. | |||||
*/ | |||||
static bool argparse_imm_label(ASMArgImmediate *result, ASMArgParseInfo ai) | |||||
{ | |||||
if (ai.size >= MAX_SYMBOL_SIZE) | |||||
return false; | |||||
for (const char *i = ai.arg; i < ai.arg + ai.size; i++) { | |||||
if (!is_valid_symbol_char(*i, i == ai.arg)) | |||||
return false; | |||||
} | |||||
result->mask = IMM_U16; | |||||
result->is_label = true; | |||||
strncpy(result->label, ai.arg, ai.size); | |||||
result->label[ai.size] = '\0'; | |||||
return true; | |||||
} | |||||
/* | |||||
Read in an immediate argument and store it in *result. | Read in an immediate argument and store it in *result. | ||||
*/ | */ | ||||
bool argparse_immediate(ASMArgImmediate *result, ASMArgParseInfo ai) | bool argparse_immediate(ASMArgImmediate *result, ASMArgParseInfo ai) | ||||
@@ -326,10 +348,11 @@ bool argparse_immediate(ASMArgImmediate *result, ASMArgParseInfo ai) | |||||
if (ai.size <= 0) | if (ai.size <= 0) | ||||
return false; | return false; | ||||
bool negative = false; | |||||
bool negative = false, modifiers = false; | |||||
ssize_t i = 0; | ssize_t i = 0; | ||||
while (ai.arg[i] == '-' || ai.arg[i] == '+' || ai.arg[i] == ' ') { | while (ai.arg[i] == '-' || ai.arg[i] == '+' || ai.arg[i] == ' ') { | ||||
modifiers = true; | |||||
if (ai.arg[i] == '-') | if (ai.arg[i] == '-') | ||||
negative = !negative; | negative = !negative; | ||||
if (++i >= ai.size) | if (++i >= ai.size) | ||||
@@ -341,9 +364,10 @@ bool argparse_immediate(ASMArgImmediate *result, ASMArgParseInfo ai) | |||||
const ASMDefine *define = asm_deftable_find(ai.deftable, ai.arg, ai.size); | const ASMDefine *define = asm_deftable_find(ai.deftable, ai.arg, ai.size); | ||||
if (define) { | if (define) { | ||||
if (negative) { | if (negative) { | ||||
result->is_label = false; | |||||
result->uval = define->value.uval; | result->uval = define->value.uval; | ||||
result->sval = -define->value.sval; | result->sval = -define->value.sval; | ||||
calculate_immediate_mask(result); | |||||
result->mask = calculate_immediate_mask(result->uval, true); | |||||
} else { | } else { | ||||
*result = define->value; | *result = define->value; | ||||
} | } | ||||
@@ -351,16 +375,23 @@ bool argparse_immediate(ASMArgImmediate *result, ASMArgParseInfo ai) | |||||
} | } | ||||
uint32_t uval; | uint32_t uval; | ||||
if (!parse_uint32_t(&uval, ai.arg, ai.size) || uval > UINT16_MAX) | |||||
if (!parse_uint32_t(&uval, ai.arg, ai.size)) { | |||||
if (!modifiers && argparse_imm_label(result, ai)) | |||||
return true; | |||||
return false; | |||||
} | |||||
if (uval > UINT16_MAX) | |||||
return false; | return false; | ||||
int32_t sval = negative ? -uval : uval; | int32_t sval = negative ? -uval : uval; | ||||
if (sval < INT16_MIN) | if (sval < INT16_MIN) | ||||
return false; | return false; | ||||
result->is_label = false; | |||||
result->uval = uval; | result->uval = uval; | ||||
result->sval = sval; | result->sval = sval; | ||||
calculate_immediate_mask(result); | |||||
result->mask = calculate_immediate_mask(uval, negative); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -374,9 +405,9 @@ bool argparse_indirect(ASMArgIndirect *result, ASMArgParseInfo ai) | |||||
ASMArgRegister reg; | ASMArgRegister reg; | ||||
ASMArgImmediate imm; | ASMArgImmediate imm; | ||||
ASMArgLabel label; | |||||
if (argparse_register(®, ai)) { | if (argparse_register(®, ai)) { | ||||
if (reg == REG_BC || reg == REG_DE || reg == REG_HL) { | |||||
if (reg == REG_BC || reg == REG_DE || reg == REG_HL || | |||||
reg == REG_IX || reg == REG_IY) { | |||||
result->type = AT_REGISTER; | result->type = AT_REGISTER; | ||||
result->addr.reg = reg; | result->addr.reg = reg; | ||||
return true; | return true; | ||||
@@ -387,10 +418,6 @@ bool argparse_indirect(ASMArgIndirect *result, ASMArgParseInfo ai) | |||||
result->addr.imm = imm; | result->addr.imm = imm; | ||||
return true; | return true; | ||||
} | } | ||||
} else if (argparse_label(&label, ai)) { | |||||
result->type = AT_LABEL; | |||||
result->addr.label = label; | |||||
return true; | |||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
@@ -433,24 +460,29 @@ bool argparse_indexed(ASMArgIndexed *result, ASMArgParseInfo ai) | |||||
} | } | ||||
/* | /* | ||||
Read in a label argument and store it in *result. | |||||
Read in a port argument and store it in *result. | |||||
*/ | */ | ||||
bool argparse_label(ASMArgLabel *result, ASMArgParseInfo ai) | |||||
bool argparse_port(ASMArgPort *result, ASMArgParseInfo ai) | |||||
{ | { | ||||
if (ai.size <= 0 || ai.size >= MAX_SYMBOL_SIZE) | |||||
if (ai.size < 3 || !adjust_for_indirection(&ai)) | |||||
return false; | return false; | ||||
for (const char *i = ai.arg; i < ai.arg + ai.size; i++) { | |||||
if (!is_valid_symbol_char(*i, i == ai.arg)) | |||||
return false; | |||||
ASMArgRegister reg; | |||||
ASMArgImmediate imm; | |||||
if (argparse_register(®, ai)) { | |||||
if (reg == REG_C) { | |||||
result->type = AT_REGISTER; | |||||
result->port.reg = reg; | |||||
return true; | |||||
} | |||||
} else if (argparse_immediate(&imm, ai)) { | |||||
if (imm.mask & IMM_U8) { | |||||
result->type = AT_IMMEDIATE; | |||||
result->port.imm = imm; | |||||
return true; | |||||
} | |||||
} | } | ||||
if (asm_deftable_find(ai.deftable, ai.arg, ai.size)) | |||||
return false; | |||||
strncpy(result->text, ai.arg, ai.size); | |||||
result->text[ai.size] = '\0'; | |||||
return true; | |||||
return false; | |||||
} | } | ||||
/* | /* | ||||
@@ -33,7 +33,7 @@ bool argparse_condition(ASMArgCondition*, ASMArgParseInfo); | |||||
bool argparse_immediate(ASMArgImmediate*, ASMArgParseInfo); | bool argparse_immediate(ASMArgImmediate*, ASMArgParseInfo); | ||||
bool argparse_indirect(ASMArgIndirect*, ASMArgParseInfo); | bool argparse_indirect(ASMArgIndirect*, ASMArgParseInfo); | ||||
bool argparse_indexed(ASMArgIndexed*, ASMArgParseInfo); | bool argparse_indexed(ASMArgIndexed*, ASMArgParseInfo); | ||||
bool argparse_label(ASMArgLabel*, ASMArgParseInfo); | |||||
bool argparse_port(ASMArgPort*, ASMArgParseInfo); | |||||
/* Preprocessor directive parsers */ | /* Preprocessor directive parsers */ | ||||
bool dparse_bool(bool*, const ASMLine*, const char*); | bool dparse_bool(bool*, const ASMLine*, const char*); | ||||
@@ -155,7 +155,7 @@ static ErrorInfo* handle_define_directive( | |||||
ASMArgImmediate imm; | ASMArgImmediate imm; | ||||
ASMArgParseInfo info = { | ASMArgParseInfo info = { | ||||
.arg = line->data + i, .size = line->length - i, .deftable = deftab}; | .arg = line->data + i, .size = line->length - i, .deftable = deftab}; | ||||
if (!argparse_immediate(&imm, info)) | |||||
if (!argparse_immediate(&imm, info) || imm.is_label) | |||||
return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG); | return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG); | ||||
ASMDefine *define = cr_malloc(sizeof(ASMDefine)); | ASMDefine *define = cr_malloc(sizeof(ASMDefine)); | ||||