An emulator, assembler, and disassembler for the Sega Game Gear
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

399 lines
12 KiB

  1. /* Copyright (C) 2014-2015 Ben Kurtovic <ben.kurtovic@gmail.com>
  2. Released under the terms of the MIT License. See LICENSE for details. */
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include "tokenizer.h"
  6. #include "directives.h"
  7. #include "parse_util.h"
  8. #include "../logging.h"
  9. #include "../mmu.h"
  10. #include "../rom.h"
  11. /* Internal structs */
  12. typedef struct {
  13. size_t size;
  14. const ASMLine **overlap_table;
  15. const ASMLine **overlap_origins;
  16. const ASMLine *origin;
  17. uint8_t bank;
  18. bool cross_blocks;
  19. } ASMLayoutInfo;
  20. typedef struct {
  21. int8_t slots[MMU_NUM_ROM_BANKS];
  22. const ASMLine *lines[MMU_NUM_ROM_BANKS];
  23. } ASMSlotInfo;
  24. /* Sentinel values for overlap table */
  25. const ASMLine header_sentinel, bounds_sentinel;
  26. /* Typedef for parse_util data parser functions */
  27. typedef bool (*parser_func)(uint8_t**, size_t*, const char*, ssize_t);
  28. /*
  29. Return the address of a given ROM offset when mapped into the given slot.
  30. */
  31. static inline uint16_t map_into_slot(size_t offset, int8_t slot)
  32. {
  33. return (slot * MMU_ROM_BANK_SIZE) + (offset & (MMU_ROM_BANK_SIZE - 1));
  34. }
  35. /*
  36. Return the default slot associated with a given memory bank.
  37. */
  38. static inline int8_t default_bank_slot(uint8_t bank)
  39. {
  40. return bank > 2 ? 2 : bank;
  41. }
  42. /*
  43. Add a given line, representing a label, to the symbol table.
  44. Return NULL on success and an ErrorInfo object on failure (in the case of
  45. duplicate labels).
  46. */
  47. static ErrorInfo* add_label_to_table(
  48. ASMSymbolTable *symtable, const ASMLine *line, size_t offset, int8_t slot)
  49. {
  50. char *symbol = strndup(line->data, line->length - 1);
  51. if (!symbol)
  52. OUT_OF_MEMORY()
  53. const ASMSymbol *current = asm_symtable_find(symtable, symbol);
  54. if (current) {
  55. ErrorInfo *ei = error_info_create(line, ET_SYMBOL, ED_SYM_DUPE_LABELS);
  56. error_info_append(ei, current->line);
  57. return ei;
  58. }
  59. ASMSymbol *label = malloc(sizeof(ASMSymbol));
  60. if (!label)
  61. OUT_OF_MEMORY()
  62. label->offset = map_into_slot(offset,
  63. (slot >= 0) ? slot : default_bank_slot(offset / MMU_ROM_BANK_SIZE));
  64. label->symbol = symbol;
  65. label->line = line;
  66. asm_symtable_insert(symtable, label);
  67. return NULL;
  68. }
  69. /*
  70. Handle an origin directive by updating the offset.
  71. Return NULL on success and an ErrorInfo object on failure.
  72. */
  73. static ErrorInfo* handle_origin_directive(const ASMLine *line, size_t *offset)
  74. {
  75. if (!DIRECTIVE_HAS_ARG(line, DIR_ORIGIN))
  76. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  77. uint32_t arg;
  78. if (!dparse_uint32_t(&arg, line, DIR_ORIGIN))
  79. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  80. if (arg >= ROM_SIZE_MAX)
  81. return error_info_create(line, ET_PREPROC, ED_PP_ARG_RANGE);
  82. *offset = arg;
  83. return NULL;
  84. }
  85. /*
  86. Handle a block directive by updating the offset and slot.
  87. Return NULL on success and an ErrorInfo object on failure.
  88. */
  89. static ErrorInfo* handle_block_directive(
  90. const ASMLine *line, size_t *offset, ASMSlotInfo *si)
  91. {
  92. if (!DIRECTIVE_HAS_ARG(line, DIR_BLOCK))
  93. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  94. uint8_t *args, bank, slot;
  95. size_t dir_offset = DIRECTIVE_OFFSET(line, DIR_BLOCK) + 1, nargs;
  96. if (!parse_bytes(&args, &nargs, line->data + dir_offset,
  97. line->length - dir_offset))
  98. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  99. if (nargs < 1 || nargs > 2)
  100. return free(args), error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  101. bank = args[0];
  102. slot = nargs == 2 ? args[1] : default_bank_slot(bank);
  103. free(args);
  104. if (bank >= MMU_NUM_ROM_BANKS || slot >= MMU_NUM_SLOTS)
  105. return error_info_create(line, ET_PREPROC, ED_PP_ARG_RANGE);
  106. if (bank == 0 && slot != 0)
  107. return error_info_create(line, ET_LAYOUT, ED_LYT_BLOCK0);
  108. if (si->slots[bank] >= 0 && si->slots[bank] != slot) {
  109. ErrorInfo *ei = error_info_create(line, ET_LAYOUT, ED_LYT_SLOTS);
  110. error_info_append(ei, si->lines[bank]);
  111. return ei;
  112. }
  113. *offset = bank * MMU_ROM_BANK_SIZE;
  114. si->slots[bank] = slot;
  115. if (!si->lines[bank])
  116. si->lines[bank] = line;
  117. return NULL;
  118. }
  119. /*
  120. Parse a .space directive, which fills a region with a single byte.
  121. */
  122. static bool parse_space(
  123. uint8_t **result, size_t *length, const char *arg, ssize_t size)
  124. {
  125. uint8_t *bytes;
  126. size_t nbytes;
  127. if (!parse_bytes(&bytes, &nbytes, arg, size))
  128. return false;
  129. if (nbytes < 1 || nbytes > 2) {
  130. free(bytes);
  131. return false;
  132. }
  133. *length = bytes[0];
  134. if (!(*result = malloc(sizeof(uint8_t) * (*length))))
  135. OUT_OF_MEMORY()
  136. memset(*result, nbytes == 2 ? bytes[1] : 0, *length);
  137. free(bytes);
  138. return true;
  139. }
  140. /*
  141. Parse data encoded in a line into an ASMData object.
  142. On success, return NULL and store the instruction in *data_ptr. On failure,
  143. return an ErrorInfo object; *data_ptr is not modified.
  144. */
  145. static ErrorInfo* parse_data(
  146. const ASMLine *line, ASMData **data_ptr, size_t offset)
  147. {
  148. const char *directive;
  149. parser_func parser = (parser_func) parse_string;
  150. if (IS_DIRECTIVE(line, DIR_BYTE)) {
  151. directive = DIR_BYTE;
  152. parser = parse_bytes;
  153. } else if (IS_DIRECTIVE(line, DIR_SPACE)) {
  154. directive = DIR_SPACE;
  155. parser = parse_space;
  156. } else if (IS_DIRECTIVE(line, DIR_ASCII)) {
  157. directive = DIR_ASCII;
  158. } else if (IS_DIRECTIVE(line, DIR_ASCIZ)) {
  159. directive = DIR_ASCIZ;
  160. } else if (IS_DIRECTIVE(line, DIR_ASCIIZ)) {
  161. directive = DIR_ASCIIZ;
  162. } else {
  163. return error_info_create(line, ET_PREPROC, ED_PP_UNKNOWN);
  164. }
  165. if (!DIRECTIVE_HAS_ARG(line, directive))
  166. return error_info_create(line, ET_PREPROC, ED_PP_NO_ARG);
  167. size_t dir_offset = DIRECTIVE_OFFSET(line, directive) + 1;
  168. const char *arg = line->data + dir_offset;
  169. size_t arglen = line->length - dir_offset;
  170. ASMData *data = malloc(sizeof(ASMData));
  171. if (!data)
  172. OUT_OF_MEMORY()
  173. data->loc.offset = offset;
  174. data->next = NULL;
  175. if (!parser(&data->bytes, &data->loc.length, arg, arglen)) {
  176. free(data);
  177. return error_info_create(line, ET_PREPROC, ED_PP_BAD_ARG);
  178. }
  179. *data_ptr = data;
  180. return NULL;
  181. }
  182. /*
  183. Parse an instruction encoded in a line into an ASMInstruction object.
  184. On success, return NULL and store the instruction in *inst_ptr. On failure,
  185. return an ErrorInfo object; *inst_ptr is not modified.
  186. */
  187. static ErrorInfo* parse_instruction(
  188. const ASMLine *line, ASMInstruction **inst_ptr, size_t offset)
  189. {
  190. // TODO
  191. DEBUG("parse_instruction(): %.*s", (int) line->length, line->data)
  192. // SYNTAX NOTES:
  193. // see http://clrhome.org/table/ and http://www.z80.info/z80undoc.htm
  194. // return error_info_create(line, ET_PARSER, ED_PARSE_SYNTAX);
  195. ASMInstruction *inst = malloc(sizeof(ASMInstruction));
  196. if (!inst)
  197. OUT_OF_MEMORY()
  198. inst->loc.offset = offset;
  199. inst->loc.length = 1;
  200. uint8_t tmp = 0x3C;
  201. inst->bytes = memcpy(malloc(1), &tmp, 1);
  202. inst->symbol = NULL;
  203. inst->line = line;
  204. inst->next = NULL;
  205. *inst_ptr = inst;
  206. return NULL;
  207. }
  208. /*
  209. Check if the given object location is legal.
  210. Checks include ROM size bounding, overlapping with existing objects, and
  211. block-crossing assuming the .cross_blocks directive has not been specified.
  212. On success, return NULL and add the location to the overlap table.
  213. On failure, return an ErrorInfo object.
  214. */
  215. static ErrorInfo* check_layout(
  216. ASMLayoutInfo *li, const ASMLocation *loc, const ASMLine *line)
  217. {
  218. const ASMLine *clash = NULL, *clash_origin;
  219. if (loc->offset + loc->length > li->size) {
  220. clash = &bounds_sentinel;
  221. } else {
  222. for (size_t i = 0; i < loc->length; i++) {
  223. if (li->overlap_table[loc->offset + i]) {
  224. clash = li->overlap_table[loc->offset + i];
  225. clash_origin = li->overlap_origins[loc->offset + i];
  226. break;
  227. }
  228. }
  229. }
  230. if (clash) {
  231. ErrorInfo *ei = error_info_create(line, ET_LAYOUT,
  232. (clash == &header_sentinel) ? ED_LYT_OVERLAP_HEAD :
  233. (clash == &bounds_sentinel) ? ED_LYT_BOUNDS : ED_LYT_OVERLAP);
  234. if (li->origin)
  235. error_info_append(ei, li->origin);
  236. if (clash != &header_sentinel && clash != &bounds_sentinel) {
  237. error_info_append(ei, clash);
  238. if (clash_origin)
  239. error_info_append(ei, clash_origin);
  240. }
  241. return ei;
  242. }
  243. uint8_t bank = (loc->offset + loc->length - 1) / MMU_ROM_BANK_SIZE;
  244. if (bank != li->bank && !li->cross_blocks) {
  245. ErrorInfo *ei = error_info_create(line, ET_LAYOUT, ED_LYT_BLOCK_CROSS);
  246. if (li->origin)
  247. error_info_append(ei, li->origin);
  248. return ei;
  249. }
  250. for (size_t i = 0; i < loc->length; i++) {
  251. li->overlap_table[loc->offset + i] = line;
  252. li->overlap_origins[loc->offset + i] = li->origin;
  253. }
  254. return NULL;
  255. }
  256. /*
  257. Tokenize ASMLines into ASMInstructions and ASMData.
  258. NULL is returned on success and an ErrorInfo object is returned on failure.
  259. state->instructions, state->data, and state->symtable may or may not be
  260. modified regardless of success.
  261. */
  262. ErrorInfo* tokenize(AssemblerState *state)
  263. {
  264. ASMLayoutInfo li = {
  265. .size = state->rom_size ? state->rom_size : ROM_SIZE_MAX,
  266. .origin = NULL, .bank = 0, .cross_blocks = state->cross_blocks
  267. };
  268. li.overlap_table = calloc(li.size, sizeof(const ASMLine*));
  269. li.overlap_origins = calloc(li.size, sizeof(const ASMLine*));
  270. if (!li.overlap_table || !li.overlap_origins)
  271. OUT_OF_MEMORY()
  272. ErrorInfo *ei = NULL;
  273. ASMInstruction dummy_inst = {.next = NULL}, *inst, *prev_inst = &dummy_inst;
  274. ASMData dummy_data = {.next = NULL}, *data, *prev_data = &dummy_data;
  275. const ASMLine *line = state->lines;
  276. size_t offset = 0;
  277. ASMSlotInfo si = {.lines = {0}};
  278. for (size_t i = 0; i < HEADER_SIZE; i++)
  279. li.overlap_table[state->header.offset + i] = &header_sentinel;
  280. memset(si.slots, -1, MMU_NUM_ROM_BANKS);
  281. while (line) {
  282. if (line->is_label) {
  283. if (offset >= li.size) {
  284. ei = error_info_create(line, ET_LAYOUT, ED_LYT_BOUNDS);
  285. goto cleanup;
  286. }
  287. int8_t slot = si.slots[offset / MMU_NUM_ROM_BANKS];
  288. if ((ei = add_label_to_table(state->symtable, line, offset, slot)))
  289. goto cleanup;
  290. }
  291. else if (IS_LOCAL_DIRECTIVE(line)) {
  292. if (IS_DIRECTIVE(line, DIR_ORIGIN)) {
  293. if ((ei = handle_origin_directive(line, &offset)))
  294. goto cleanup;
  295. li.origin = line;
  296. li.bank = offset / MMU_ROM_BANK_SIZE;
  297. }
  298. else if (IS_DIRECTIVE(line, DIR_BLOCK)) {
  299. if ((ei = handle_block_directive(line, &offset, &si)))
  300. goto cleanup;
  301. li.origin = line;
  302. li.bank = offset / MMU_ROM_BANK_SIZE;
  303. }
  304. else {
  305. if ((ei = parse_data(line, &data, offset)))
  306. goto cleanup;
  307. offset += data->loc.length;
  308. prev_data->next = data;
  309. prev_data = data;
  310. if ((ei = check_layout(&li, &data->loc, line)))
  311. goto cleanup;
  312. }
  313. }
  314. else {
  315. if ((ei = parse_instruction(line, &inst, offset)))
  316. goto cleanup;
  317. offset += inst->loc.length;
  318. prev_inst->next = inst;
  319. prev_inst = inst;
  320. if ((ei = check_layout(&li, &inst->loc, line)))
  321. goto cleanup;
  322. }
  323. line = line->next;
  324. }
  325. cleanup:
  326. state->instructions = dummy_inst.next;
  327. state->data = dummy_data.next;
  328. free(li.overlap_table);
  329. free(li.overlap_origins);
  330. return ei;
  331. }