| @@ -42,6 +42,7 @@ static const char *error_descs[] = { | |||||
| [ED_SYM_DUPE_LABELS] = "duplicate definitions for label", | [ED_SYM_DUPE_LABELS] = "duplicate definitions for label", | ||||
| [ED_SYM_NO_LABEL] = "undefined reference to label", | [ED_SYM_NO_LABEL] = "undefined reference to label", | ||||
| [ED_SYM_IS_REGISTER] = "labels cannot share names with registers", | |||||
| [ED_PS_OP_TOO_LONG] = "opcode mnemonic is too long (2-4 characters)", | [ED_PS_OP_TOO_LONG] = "opcode mnemonic is too long (2-4 characters)", | ||||
| [ED_PS_OP_TOO_SHORT] = "opcode mnemonic is too short (2-4 characters)", | [ED_PS_OP_TOO_SHORT] = "opcode mnemonic is too short (2-4 characters)", | ||||
| @@ -49,7 +50,8 @@ static const char *error_descs[] = { | |||||
| [ED_PS_OP_UNKNOWN] = "unknown opcode mnemonic", | [ED_PS_OP_UNKNOWN] = "unknown opcode mnemonic", | ||||
| [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_ARG1_TYPE] = "invalid type for first argument", | |||||
| // [ED_PS_ARG1_NEED_REG] = "invalid type for first argument (needs register)", | |||||
| // [ED_PS_ARG1_BAD_REG] = "first argument should be a register", | |||||
| }; | }; | ||||
| /* Internal structs */ | /* Internal structs */ | ||||
| @@ -41,6 +41,7 @@ typedef enum { | |||||
| ED_SYM_DUPE_LABELS, | ED_SYM_DUPE_LABELS, | ||||
| ED_SYM_NO_LABEL, | ED_SYM_NO_LABEL, | ||||
| ED_SYM_IS_REGISTER, | |||||
| ED_PS_OP_TOO_LONG, | ED_PS_OP_TOO_LONG, | ||||
| ED_PS_OP_TOO_SHORT, | ED_PS_OP_TOO_SHORT, | ||||
| @@ -0,0 +1,59 @@ | |||||
| /* 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 <stdbool.h> | |||||
| #include <stdint.h> | |||||
| typedef enum { | |||||
| AT_REGISTER, | |||||
| AT_IMMEDIATE, | |||||
| AT_INDIRECT, | |||||
| AT_INDEXED, | |||||
| AT_LABEL, | |||||
| AT_CONDITION | |||||
| } ASMArgType; | |||||
| typedef enum { | |||||
| REG_A, REG_F, REG_B, REG_C, REG_D, REG_E, REG_H, REG_L, REG_I, REG_R, | |||||
| REG_AF, REG_BC, REG_DE, REG_HL, REG_IX, REG_IY, | |||||
| REG_PC, REG_SP, | |||||
| REG_AF_, REG_IXH, REG_IXL, REG_IYH, REG_IYL | |||||
| } ASMArgRegister; | |||||
| typedef struct { | |||||
| uint16_t value; | |||||
| bool is_negative; | |||||
| } ASMArgImmediate; | |||||
| typedef struct { | |||||
| bool is_reg; | |||||
| ASMArgRegister reg; | |||||
| ASMArgImmediate imm; | |||||
| } ASMArgIndirect; | |||||
| typedef struct { | |||||
| ASMArgRegister reg; | |||||
| ASMArgImmediate imm; | |||||
| } ASMArgIndexed; | |||||
| typedef char* ASMArgLabel; | |||||
| typedef enum { | |||||
| COND_NZ, COND_N, COND_NC, COND_C, COND_PO, COND_PE, COND_P, COND_M | |||||
| } ASMArgCondition; | |||||
| typedef union { | |||||
| ASMArgRegister reg; | |||||
| ASMArgImmediate imm; | |||||
| ASMArgIndirect indirect; | |||||
| ASMArgIndexed index; | |||||
| ASMArgLabel label; | |||||
| ASMArgCondition cond; | |||||
| } ASMArgData; | |||||
| typedef struct { | |||||
| ASMArgType type; | |||||
| ASMArgData data; | |||||
| } ASMInstArg; | |||||
| @@ -29,11 +29,11 @@ | |||||
| #define JOIN_(a, b, c, d) ((uint32_t) ((a << 24) + (b << 16) + (c << 8) + d)) | #define JOIN_(a, b, c, d) ((uint32_t) ((a << 24) + (b << 16) + (c << 8) + d)) | ||||
| #define DISPATCH_(s, z) ( \ | #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])) \ | |||||
| (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 MAKE_CMP_(s) DISPATCH_(s, sizeof(s) / sizeof(char) - 1) | |||||
| #define HANDLE(m) if (key == MAKE_CMP_(#m)) return parse_inst_##m; | #define HANDLE(m) if (key == MAKE_CMP_(#m)) return parse_inst_##m; | ||||
| @@ -27,9 +27,6 @@ | |||||
| */ | */ | ||||
| bool parse_bool(bool *result, const char *arg, ssize_t size) | bool parse_bool(bool *result, const char *arg, ssize_t size) | ||||
| { | { | ||||
| if (size <= 0 || size > 5) | |||||
| return false; | |||||
| switch (size) { | switch (size) { | ||||
| case 1: // 0, 1 | case 1: // 0, 1 | ||||
| if (*arg == '0' || *arg == '1') | if (*arg == '0' || *arg == '1') | ||||
| @@ -176,6 +173,63 @@ bool parse_bytes(uint8_t **result, size_t *length, const char *arg, ssize_t size | |||||
| } | } | ||||
| /* | /* | ||||
| Read in a register argument and store it in *result. | |||||
| */ | |||||
| bool parse_register(ASMArgRegister *result, const char *arg, ssize_t size) | |||||
| { | |||||
| if (size < 1 || size > 3) | |||||
| return false; | |||||
| #define LCASE(c) ((c >= 'A' && c <= 'Z') ? (c + 'a' - 'A') : c) | |||||
| char buf[3] = {'\0'}; | |||||
| switch (size) { | |||||
| case 3: buf[2] = LCASE(arg[2]); | |||||
| case 2: buf[1] = LCASE(arg[1]); | |||||
| case 1: buf[0] = LCASE(arg[0]); | |||||
| } | |||||
| #undef LCASE | |||||
| switch (size) { | |||||
| case 1: | |||||
| switch (buf[0]) { | |||||
| case 'a': return (*result = REG_A), true; | |||||
| case 'f': return (*result = REG_F), true; | |||||
| case 'b': return (*result = REG_B), true; | |||||
| case 'c': return (*result = REG_C), true; | |||||
| case 'd': return (*result = REG_D), true; | |||||
| case 'e': return (*result = REG_E), true; | |||||
| case 'h': return (*result = REG_H), true; | |||||
| case 'l': return (*result = REG_L), true; | |||||
| case 'i': return (*result = REG_I), true; | |||||
| case 'r': return (*result = REG_R), true; | |||||
| } | |||||
| return false; | |||||
| case 2: | |||||
| switch ((buf[0] << 8) + buf[1]) { | |||||
| case 0x6166: return (*result = REG_AF), true; | |||||
| case 0x6263: return (*result = REG_BC), true; | |||||
| case 0x6465: return (*result = REG_DE), true; | |||||
| case 0x686c: return (*result = REG_HL), true; | |||||
| case 0x6978: return (*result = REG_IX), true; | |||||
| case 0x6979: return (*result = REG_IY), true; | |||||
| case 0x7063: return (*result = REG_PC), true; | |||||
| case 0x7370: return (*result = REG_SP), true; | |||||
| } | |||||
| return false; | |||||
| case 3: | |||||
| switch ((buf[0] << 16) + (buf[1] << 8) + buf[2]) { | |||||
| case 0x616627: return (*result = REG_AF_), true; | |||||
| case 0x697868: return (*result = REG_IXH), true; | |||||
| case 0x69786c: return (*result = REG_IXL), true; | |||||
| case 0x697968: return (*result = REG_IYH), true; | |||||
| case 0x69796c: return (*result = REG_IYL), true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| /* | |||||
| Read in a boolean argument from the given line and store it in *result. | Read in a boolean argument from the given line and store it in *result. | ||||
| */ | */ | ||||
| DIRECTIVE_PARSE_FUNC(bool, bool) | DIRECTIVE_PARSE_FUNC(bool, bool) | ||||
| @@ -6,6 +6,7 @@ | |||||
| #include <stdbool.h> | #include <stdbool.h> | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include "inst_args.h" | |||||
| #include "state.h" | #include "state.h" | ||||
| #define dparse__Bool dparse_bool | #define dparse__Bool dparse_bool | ||||
| @@ -16,6 +17,7 @@ bool parse_bool(bool*, const char*, ssize_t); | |||||
| bool parse_uint32_t(uint32_t*, const char*, ssize_t); | bool parse_uint32_t(uint32_t*, const char*, ssize_t); | ||||
| bool parse_string(char**, size_t*, const char*, ssize_t); | bool parse_string(char**, size_t*, const char*, ssize_t); | ||||
| bool parse_bytes(uint8_t**, size_t*, const char*, ssize_t); | bool parse_bytes(uint8_t**, size_t*, const char*, ssize_t); | ||||
| bool parse_register(ASMArgRegister*, const char*, ssize_t); | |||||
| bool dparse_bool(bool*, const ASMLine*, const char*); | bool dparse_bool(bool*, const ASMLine*, const char*); | ||||
| bool dparse_uint32_t(uint32_t*, const ASMLine*, const char*); | bool dparse_uint32_t(uint32_t*, const ASMLine*, const char*); | ||||
| @@ -94,6 +94,19 @@ static inline bool is_valid_label_char(char c, bool first) | |||||
| } | } | ||||
| /* | /* | ||||
| Functions similar memcpy, but lowercases the characters along the way. | |||||
| */ | |||||
| static void memcpy_lc(char *restrict dst, const char *restrict src, size_t n) | |||||
| { | |||||
| while (n-- > 0) { | |||||
| char c = *(src++); | |||||
| if (c >= 'A' && c <= 'Z') | |||||
| c += 'a' - 'A'; | |||||
| *(dst++) = c; | |||||
| } | |||||
| } | |||||
| /* | |||||
| Preprocess a single source line for labels. | Preprocess a single source line for labels. | ||||
| Return the index of first non-whitespace non-label character. *head_ptr is | Return the index of first non-whitespace non-label character. *head_ptr is | ||||
| @@ -125,7 +138,7 @@ static size_t read_labels( | |||||
| if (!line->data) | if (!line->data) | ||||
| OUT_OF_MEMORY() | OUT_OF_MEMORY() | ||||
| strncpy(line->data, source + start, i - start + 1); | |||||
| memcpy_lc(line->data, source + start, i - start + 1); | |||||
| line->length = i - start + 1; | line->length = i - start + 1; | ||||
| line->is_label = true; | line->is_label = true; | ||||
| @@ -7,6 +7,7 @@ | |||||
| #include "tokenizer.h" | #include "tokenizer.h" | ||||
| #include "directives.h" | #include "directives.h" | ||||
| #include "instructions.h" | #include "instructions.h" | ||||
| #include "inst_args.h" | |||||
| #include "parse_util.h" | #include "parse_util.h" | ||||
| #include "../logging.h" | #include "../logging.h" | ||||
| #include "../mmu.h" | #include "../mmu.h" | ||||
| @@ -55,12 +56,16 @@ static inline int8_t default_bank_slot(uint8_t bank) | |||||
| /* | /* | ||||
| Add a given line, representing a label, to the symbol table. | Add a given line, representing a label, to the symbol table. | ||||
| Return NULL on success and an ErrorInfo object on failure (in the case of | |||||
| duplicate labels). | |||||
| Return NULL on success and an ErrorInfo object on failure (e.g. in the case | |||||
| of duplicate labels). | |||||
| */ | */ | ||||
| static ErrorInfo* add_label_to_table( | static ErrorInfo* add_label_to_table( | ||||
| ASMSymbolTable *symtable, const ASMLine *line, size_t offset, int8_t slot) | ASMSymbolTable *symtable, const ASMLine *line, size_t offset, int8_t slot) | ||||
| { | { | ||||
| ASMArgRegister reg; | |||||
| if (parse_register(®, line->data, line->length - 1)) | |||||
| return error_info_create(line, ET_SYMBOL, ED_SYM_IS_REGISTER); | |||||
| char *symbol = strndup(line->data, line->length - 1); | char *symbol = strndup(line->data, line->length - 1); | ||||
| if (!symbol) | if (!symbol) | ||||
| OUT_OF_MEMORY() | OUT_OF_MEMORY() | ||||
| @@ -69,6 +74,7 @@ static ErrorInfo* add_label_to_table( | |||||
| if (current) { | if (current) { | ||||
| ErrorInfo *ei = error_info_create(line, ET_SYMBOL, ED_SYM_DUPE_LABELS); | ErrorInfo *ei = error_info_create(line, ET_SYMBOL, ED_SYM_DUPE_LABELS); | ||||
| error_info_append(ei, current->line); | error_info_append(ei, current->line); | ||||
| free(symbol); | |||||
| return ei; | return ei; | ||||
| } | } | ||||
| @@ -249,7 +255,7 @@ static ErrorInfo* parse_instruction( | |||||
| uint8_t *bytes; | uint8_t *bytes; | ||||
| size_t arglen = line->length - i, length; | size_t arglen = line->length - i, length; | ||||
| char *argstart = arglen > 0 ? line->data + i : NULL, *symbol; | |||||
| char *argstart = arglen > 0 ? line->data + i : NULL, *symbol = NULL; | |||||
| ASMInstParser parser = get_inst_parser(mnemonic); | ASMInstParser parser = get_inst_parser(mnemonic); | ||||
| if (!parser) | if (!parser) | ||||