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.
 
 
 
 
 

950 lines
26 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 <errno.h>
  4. #include <libgen.h>
  5. #include <limits.h>
  6. #include <stdio.h>
  7. #include <string.h>
  8. #include <sys/stat.h>
  9. #include "assembler.h"
  10. #include "asm_errors.h"
  11. #include "logging.h"
  12. #include "util.h"
  13. #define DEFAULT_HEADER_OFFSET 0x7FF0
  14. #define DEFAULT_REGION "GG Export"
  15. #define DIRECTIVE_MARKER '.'
  16. #define DIR_INCLUDE ".include"
  17. #define DIR_ORIGIN ".org"
  18. #define DIR_OPTIMIZER ".optimizer"
  19. #define DIR_ROM_SIZE ".rom_size"
  20. #define DIR_ROM_HEADER ".rom_header"
  21. #define DIR_ROM_CHECKSUM ".rom_checksum"
  22. #define DIR_ROM_PRODUCT ".rom_product"
  23. #define DIR_ROM_VERSION ".rom_version"
  24. #define DIR_ROM_REGION ".rom_region"
  25. #define DIR_ROM_DECLSIZE ".rom_declsize"
  26. #define DIRECTIVE_HAS_ARG(line, d) ((line)->length > strlen(d))
  27. #define IS_DIRECTIVE(line, d) \
  28. (((line)->length >= strlen(d)) && \
  29. !strncmp((line)->data, d, strlen(d)) && \
  30. (!DIRECTIVE_HAS_ARG(line, d) || (line)->data[strlen(d)] == ' '))
  31. #define DIRECTIVE_OFFSET(line, d) \
  32. (DIRECTIVE_HAS_ARG(line, d) ? strlen(d) : 0)
  33. #define ERROR_TYPE(err_info) (asm_error_types[err_info->type])
  34. #define ERROR_DESC(err_info) (asm_error_descs[err_info->desc])
  35. #define SYMBOL_TABLE_BUCKETS 128
  36. /* Internal structs */
  37. struct ASMLine {
  38. char *data;
  39. size_t length;
  40. const Line *original;
  41. const char *filename;
  42. struct ASMLine *next;
  43. };
  44. typedef struct ASMLine ASMLine;
  45. struct ASMInclude {
  46. LineBuffer *lines;
  47. struct ASMInclude *next;
  48. };
  49. typedef struct ASMInclude ASMInclude;
  50. struct ASMInstruction {
  51. size_t offset;
  52. uint8_t length;
  53. uint8_t b1, b2, b3, b4;
  54. uint8_t virtual_byte;
  55. char *symbol;
  56. struct ASMInstruction *next;
  57. };
  58. typedef struct ASMInstruction ASMInstruction;
  59. struct ASMSymbol {
  60. size_t offset;
  61. char *symbol;
  62. struct ASMSymbol *next;
  63. };
  64. typedef struct ASMSymbol ASMSymbol;
  65. typedef struct {
  66. ASMSymbol *buckets[SYMBOL_TABLE_BUCKETS];
  67. } ASMSymbolTable;
  68. typedef struct {
  69. size_t offset;
  70. bool checksum;
  71. uint32_t product_code;
  72. uint8_t version;
  73. uint8_t region;
  74. uint8_t rom_size;
  75. } ASMHeaderInfo;
  76. typedef struct {
  77. ASMHeaderInfo header;
  78. bool optimizer;
  79. size_t rom_size;
  80. ASMLine *lines;
  81. ASMInclude *includes;
  82. ASMInstruction *instructions;
  83. ASMSymbolTable *symtable;
  84. } AssemblerState;
  85. struct ASMErrorLine {
  86. char *data;
  87. size_t length;
  88. size_t lineno;
  89. char *filename;
  90. struct ASMErrorLine *next;
  91. };
  92. typedef struct ASMErrorLine ASMErrorLine;
  93. struct ErrorInfo {
  94. ASMErrorType type;
  95. ASMErrorDesc desc;
  96. ASMErrorLine *line;
  97. };
  98. /*
  99. Deallocate a LineBuffer previously created with read_source_file().
  100. */
  101. static void free_line_buffer(LineBuffer *buffer)
  102. {
  103. Line *line = buffer->lines, *temp;
  104. while (line) {
  105. temp = line->next;
  106. free(line->data);
  107. free(line);
  108. line = temp;
  109. }
  110. free(buffer->filename);
  111. free(buffer);
  112. }
  113. /*
  114. Read the contents of the source file at the given path into a line buffer.
  115. Return the buffer if reading was successful; it must be freed with
  116. free_line_buffer() when done. Return NULL if an error occurred while
  117. reading. If print_errors is true, a message will also be printed to stderr.
  118. */
  119. static LineBuffer* read_source_file(const char *path, bool print_errors)
  120. {
  121. FILE *fp;
  122. struct stat st;
  123. if (!(fp = fopen(path, "r"))) {
  124. if (print_errors)
  125. ERROR_ERRNO("couldn't open source file")
  126. return NULL;
  127. }
  128. if (fstat(fileno(fp), &st)) {
  129. fclose(fp);
  130. if (print_errors)
  131. ERROR_ERRNO("couldn't open source file")
  132. return NULL;
  133. }
  134. if (!(st.st_mode & S_IFREG)) {
  135. fclose(fp);
  136. if (print_errors)
  137. ERROR("couldn't open source file: %s", st.st_mode & S_IFDIR ?
  138. "Is a directory" : "Is not a regular file")
  139. return NULL;
  140. }
  141. LineBuffer *source = malloc(sizeof(LineBuffer));
  142. if (!source)
  143. OUT_OF_MEMORY()
  144. source->lines = NULL;
  145. source->filename = strdup(path);
  146. if (!source->filename)
  147. OUT_OF_MEMORY()
  148. Line dummy = {.next = NULL};
  149. Line *line, *prev = &dummy;
  150. size_t lineno = 1;
  151. while (1) {
  152. char *data = NULL;
  153. size_t cap = 0;
  154. ssize_t len;
  155. if ((len = getline(&data, &cap, fp)) < 0) {
  156. if (feof(fp))
  157. break;
  158. if (errno == ENOMEM)
  159. OUT_OF_MEMORY()
  160. if (print_errors)
  161. ERROR_ERRNO("couldn't read source file")
  162. fclose(fp);
  163. source->lines = dummy.next;
  164. free_line_buffer(source);
  165. return NULL;
  166. }
  167. line = malloc(sizeof(Line));
  168. if (!line)
  169. OUT_OF_MEMORY()
  170. line->data = data;
  171. line->length = feof(fp) ? len : (len - 1);
  172. line->lineno = lineno++;
  173. line->next = NULL;
  174. prev->next = line;
  175. prev = line;
  176. }
  177. fclose(fp);
  178. source->lines = dummy.next;
  179. return source;
  180. }
  181. /*
  182. Write an assembled binary file to the given path.
  183. Return whether the file was written successfully. On error, a message is
  184. printed to stderr.
  185. */
  186. static bool write_binary_file(const char *path, const uint8_t *data, size_t size)
  187. {
  188. FILE *fp;
  189. if (!(fp = fopen(path, "wb"))) {
  190. ERROR_ERRNO("couldn't open destination file")
  191. return false;
  192. }
  193. if (!fwrite(data, size, 1, fp)) {
  194. fclose(fp);
  195. ERROR_ERRNO("couldn't write to destination file")
  196. return false;
  197. }
  198. fclose(fp);
  199. return true;
  200. }
  201. /*
  202. Create an ASMErrorLine object from an ASMLine.
  203. */
  204. static ASMErrorLine* create_error_line(const ASMLine *line)
  205. {
  206. ASMErrorLine *el = malloc(sizeof(ASMErrorLine));
  207. if (!el)
  208. OUT_OF_MEMORY()
  209. const char *source = line->original->data;
  210. size_t length = line->original->length;
  211. if (!(el->data = malloc(sizeof(char) * length)))
  212. OUT_OF_MEMORY()
  213. // Ignore spaces at beginning:
  214. while (length > 0 && (*source == ' ' || *source == '\t'))
  215. source++, length--;
  216. memcpy(el->data, source, length);
  217. el->length = length;
  218. el->lineno = line->original->lineno;
  219. el->filename = strdup(line->filename);
  220. if (!el->filename)
  221. OUT_OF_MEMORY()
  222. el->next = NULL;
  223. return el;
  224. }
  225. /*
  226. Create an ErrorInfo object describing a particular error.
  227. The ErrorInfo object can be printed with error_info_print(), and must be
  228. freed when done with error_info_destroy().
  229. This function never fails (OOM triggers an exit()); the caller can be
  230. confident the returned object is valid.
  231. */
  232. static ErrorInfo* create_error(
  233. const ASMLine *line, ASMErrorType err_type, ASMErrorDesc err_desc)
  234. {
  235. ErrorInfo *einfo = malloc(sizeof(ErrorInfo));
  236. if (!einfo)
  237. OUT_OF_MEMORY()
  238. einfo->type = err_type;
  239. einfo->desc = err_desc;
  240. einfo->line = create_error_line(line);
  241. return einfo;
  242. }
  243. /*
  244. Add an ASMLine to an ErrorInfo object, as part of a file trace.
  245. */
  246. static void append_to_error(ErrorInfo *einfo, const ASMLine *line)
  247. {
  248. ASMErrorLine* el = create_error_line(line);
  249. el->next = einfo->line;
  250. einfo->line = el;
  251. }
  252. /*
  253. Print an ErrorInfo object returned by assemble() to the given stream.
  254. */
  255. void error_info_print(const ErrorInfo *einfo, FILE *file)
  256. {
  257. ASMErrorLine *line = einfo->line;
  258. fprintf(file, "error: %s: %s\n", ERROR_TYPE(einfo), ERROR_DESC(einfo));
  259. while (line) {
  260. fprintf(file, "%s:%zu:\n", line->filename, line->lineno);
  261. fprintf(file, " %.*s\n", (int) line->length, line->data);
  262. line = line->next;
  263. }
  264. }
  265. /*
  266. Destroy an ErrorInfo object created by assemble().
  267. */
  268. void error_info_destroy(ErrorInfo *error_info)
  269. {
  270. if (!error_info)
  271. return;
  272. ASMErrorLine *line = error_info->line, *temp;
  273. while (line) {
  274. temp = line->next;
  275. free(line->data);
  276. free(line->filename);
  277. free(line);
  278. line = temp;
  279. }
  280. free(error_info);
  281. }
  282. /*
  283. Initialize default values in an AssemblerState object.
  284. */
  285. static void init_state(AssemblerState *state)
  286. {
  287. state->header.offset = DEFAULT_HEADER_OFFSET;
  288. state->header.checksum = true;
  289. state->header.product_code = 0;
  290. state->header.version = 0;
  291. state->header.region = region_string_to_code(DEFAULT_REGION);
  292. state->header.rom_size = 0;
  293. state->optimizer = false;
  294. state->rom_size = 0;
  295. state->lines = NULL;
  296. state->includes = NULL;
  297. state->instructions = NULL;
  298. state->symtable = NULL;
  299. }
  300. /*
  301. Deallocate an ASMLine list.
  302. */
  303. static void free_asm_lines(ASMLine *line)
  304. {
  305. while (line) {
  306. ASMLine *temp = line->next;
  307. free(line->data);
  308. free(line);
  309. line = temp;
  310. }
  311. }
  312. /*
  313. Deallocate an ASMInclude list.
  314. */
  315. static void free_asm_includes(ASMInclude *include)
  316. {
  317. while (include) {
  318. ASMInclude *temp = include->next;
  319. free_line_buffer(include->lines);
  320. free(include);
  321. include = temp;
  322. }
  323. }
  324. /*
  325. Deallocate an ASMInstruction list.
  326. */
  327. static void free_asm_instructions(ASMInstruction *inst)
  328. {
  329. while (inst) {
  330. ASMInstruction *temp = inst->next;
  331. if (inst->symbol)
  332. free(inst->symbol);
  333. free(inst);
  334. inst = temp;
  335. }
  336. }
  337. /*
  338. Deallocate an ASMSymbolTable.
  339. */
  340. static void free_asm_symtable(ASMSymbolTable *symtable)
  341. {
  342. if (!symtable)
  343. return;
  344. for (size_t bucket = 0; bucket < SYMBOL_TABLE_BUCKETS; bucket++) {
  345. ASMSymbol *sym = symtable->buckets[bucket], *temp;
  346. while (sym) {
  347. temp = sym->next;
  348. free(sym->symbol);
  349. free(sym);
  350. sym = temp;
  351. }
  352. }
  353. free(symtable);
  354. }
  355. /*
  356. Preprocess a single source line (source, length) into a normalized ASMLine.
  357. *Only* the data and length fields in the ASMLine object are populated. The
  358. normalization process converts tabs to spaces, lowercases all alphabetical
  359. characters, and removes runs of multiple spaces (outside of string
  360. literals), strips comments, and other things.
  361. Return NULL if an ASM line was not generated from the source, i.e. if it is
  362. blank after being stripped.
  363. */
  364. static ASMLine* normalize_line(const char *source, size_t length)
  365. {
  366. char *data = malloc(sizeof(char) * length);
  367. if (!data)
  368. OUT_OF_MEMORY()
  369. size_t si, di, slashes = 0;
  370. bool has_content = false, space_pending = false, in_string = false;
  371. for (si = di = 0; si < length; si++) {
  372. char c = source[si];
  373. if (c == '\\')
  374. slashes++;
  375. else
  376. slashes = 0;
  377. if (in_string) {
  378. if (c == '"' && (slashes % 2) == 0)
  379. in_string = false;
  380. data[di++] = c;
  381. } else {
  382. if (c == ';')
  383. break;
  384. if (c == '"' && (slashes % 2) == 0)
  385. in_string = true;
  386. if (c >= 'A' && c <= 'Z')
  387. c += 'a' - 'A';
  388. if (c == ' ' || c == '\t')
  389. space_pending = true;
  390. else {
  391. if (space_pending) {
  392. if (has_content)
  393. data[di++] = ' ';
  394. space_pending = false;
  395. }
  396. has_content = true;
  397. data[di++] = c;
  398. }
  399. }
  400. }
  401. if (!has_content) {
  402. free(data);
  403. return NULL;
  404. }
  405. ASMLine *line = malloc(sizeof(ASMLine));
  406. if (!line)
  407. OUT_OF_MEMORY()
  408. data = realloc(data, sizeof(char) * di);
  409. if (!data)
  410. OUT_OF_MEMORY()
  411. line->data = data;
  412. line->length = di;
  413. return line;
  414. }
  415. /*
  416. Read and return the target path from an include directive.
  417. This function allocates a buffer to store the filename; it must be free()'d
  418. after calling read_source_file(). If a syntax error occurs while trying to
  419. read the path, it returns NULL.
  420. */
  421. char* read_include_path(const ASMLine *line)
  422. {
  423. size_t maxlen = strlen(line->filename) + line->length, i, start, slashes;
  424. if (maxlen >= INT_MAX) // Allows us to safely downcast to int later
  425. return NULL;
  426. char *path = malloc(sizeof(char) * maxlen);
  427. if (!path)
  428. OUT_OF_MEMORY()
  429. if (!(i = DIRECTIVE_OFFSET(line, DIR_INCLUDE)))
  430. goto error;
  431. if (line->length - i <= 4) // Not long enough to hold a non-zero argument
  432. goto error;
  433. if (line->data[i++] != ' ' || line->data[i++] != '"')
  434. goto error;
  435. // TODO: parse escaped characters properly
  436. for (start = i, slashes = 0; i < line->length; i++) {
  437. if (line->data[i] == '"' && (slashes % 2) == 0)
  438. break;
  439. if (line->data[i] == '\\')
  440. slashes++;
  441. else
  442. slashes = 0;
  443. }
  444. if (i != line->length - 1) // Junk present after closing quote
  445. goto error;
  446. char *dup = strdup(line->filename);
  447. if (!dup)
  448. OUT_OF_MEMORY()
  449. // TODO: should normalize filenames in some way to prevent accidental dupes
  450. snprintf(path, maxlen, "%s/%.*s", dirname(dup), (int) (i - start),
  451. line->data + start);
  452. free(dup);
  453. return path;
  454. error:
  455. free(path);
  456. return NULL;
  457. }
  458. /*
  459. Return whether the given path has already been loaded.
  460. */
  461. static bool path_has_been_loaded(
  462. const char *path, const LineBuffer *root, const ASMInclude *include)
  463. {
  464. if (!strcmp(path, root->filename))
  465. return true;
  466. while (include) {
  467. if (!strcmp(path, include->lines->filename))
  468. return true;
  469. include = include->next;
  470. }
  471. return false;
  472. }
  473. /*
  474. Build a LineBuffer into a ASMLines, normalizing them along the way.
  475. This function operates recursively to handle includes, but handles no other
  476. preprocessor directives.
  477. On success, NULL is returned; *head points to the head of the new ASMLine
  478. list, and *tail to its tail (assuming it is non-NULL). On error, an
  479. ErrorInfo object is returned, and *head and *tail are not modified.
  480. *includes may be updated in either case.
  481. */
  482. static ErrorInfo* build_asm_lines(
  483. const LineBuffer *root, const LineBuffer *source, ASMLine **head,
  484. ASMLine **tail, ASMInclude **includes)
  485. {
  486. ASMLine dummy = {.next = NULL};
  487. ASMLine *line, *prev = &dummy;
  488. const Line *orig, *next_orig = source->lines;
  489. while ((orig = next_orig)) {
  490. line = normalize_line(orig->data, orig->length);
  491. next_orig = orig->next;
  492. if (!line)
  493. continue;
  494. // Populate ASMLine fields not set by normalize_line():
  495. line->original = orig;
  496. line->filename = source->filename;
  497. line->next = NULL;
  498. if (IS_DIRECTIVE(line, DIR_INCLUDE)) {
  499. ErrorInfo *ei;
  500. char *path = read_include_path(line);
  501. if (!path) {
  502. ei = create_error(line, ET_INCLUDE, ED_INC_BAD_ARG);
  503. free_asm_lines(line);
  504. free_asm_lines(dummy.next);
  505. return ei;
  506. }
  507. if (path_has_been_loaded(path, root, *includes)) {
  508. ei = create_error(line, ET_INCLUDE, ED_INC_RECURSION);
  509. free_asm_lines(line);
  510. free_asm_lines(dummy.next);
  511. free(path);
  512. return ei;
  513. }
  514. DEBUG("- reading included file: %s", path)
  515. LineBuffer *incbuffer = read_source_file(path, false);
  516. free(path);
  517. if (!incbuffer) {
  518. ei = create_error(line, ET_INCLUDE, ED_INC_FILE_READ);
  519. free_asm_lines(line);
  520. free_asm_lines(dummy.next);
  521. return ei;
  522. }
  523. ASMInclude *include = malloc(sizeof(ASMInclude));
  524. if (!include)
  525. OUT_OF_MEMORY()
  526. include->lines = incbuffer;
  527. include->next = *includes;
  528. *includes = include;
  529. ASMLine *inchead, *inctail;
  530. if ((ei = build_asm_lines(root, incbuffer, &inchead, &inctail,
  531. includes))) {
  532. append_to_error(ei, line);
  533. free_asm_lines(line);
  534. free_asm_lines(dummy.next);
  535. return ei;
  536. }
  537. prev->next = inchead;
  538. prev = inctail;
  539. free_asm_lines(line); // Destroy only the .include line
  540. }
  541. else {
  542. prev->next = line;
  543. prev = line;
  544. }
  545. }
  546. *head = dummy.next;
  547. if (tail)
  548. *tail = prev;
  549. return NULL;
  550. }
  551. /*
  552. Read in a boolean argument from the given line and store it in *result.
  553. auto_val is used if the argument's value is "auto". Return true on success
  554. and false on failure; in the latter case, *result is not modified.
  555. */
  556. static inline bool read_bool_argument(
  557. bool *result, const ASMLine *line, const char *directive, bool auto_val)
  558. {
  559. const char *arg = line->data + (DIRECTIVE_OFFSET(line, directive) + 1);
  560. ssize_t len = line->length - (DIRECTIVE_OFFSET(line, directive) + 1);
  561. if (len <= 0 || len > 5)
  562. return false;
  563. switch (len) {
  564. case 1: // 0, 1
  565. if (*arg == '0' || *arg == '1')
  566. return (*result = *arg - '0'), true;
  567. return false;
  568. case 2: // on
  569. if (!strncmp(arg, "on", 2))
  570. return (*result = true), true;
  571. return false;
  572. case 3: // off
  573. if (!strncmp(arg, "off", 3))
  574. return (*result = false), true;
  575. return false;
  576. case 4: // true, auto
  577. if (!strncmp(arg, "true", 4))
  578. return (*result = true), true;
  579. if (!strncmp(arg, "auto", 4))
  580. return (*result = auto_val), true;
  581. return false;
  582. case 5: // false
  583. if (!strncmp(arg, "false", 5))
  584. return (*result = false), true;
  585. return false;
  586. }
  587. return false;
  588. }
  589. /*
  590. Preprocess the LineBuffer into ASMLines. Change some state along the way.
  591. This function processes include directives, so read_source_file() may be
  592. called multiple times (along with the implications that has), and
  593. state->includes may be modified.
  594. On success, NULL is returned. On error, an ErrorInfo object is returned.
  595. state->lines and state->includes may still be modified.
  596. */
  597. static ErrorInfo* preprocess(AssemblerState *state, const LineBuffer *source)
  598. {
  599. // state->header.offset <-- check in list of acceptable values
  600. // state->header.checksum <-- boolean check
  601. // state->header.product_code <-- range check
  602. // state->header.version <-- range check
  603. // state->header.region <-- string conversion, check
  604. // state->header.rom_size <-- value/range check
  605. // state->optimizer <-- boolean check
  606. // state->rom_size <-- value check
  607. // if giving rom size, check header offset is in rom size range
  608. // if giving reported and actual rom size, check reported is <= actual
  609. #define CATCH_DUPES(line, first, oldval, newval) \
  610. if (first && oldval != newval) { \
  611. ei = create_error(line, ET_PREPROC, ED_PP_DUPLICATE); \
  612. append_to_error(ei, first); \
  613. return ei; \
  614. }
  615. #define REQUIRE_ARG(line, d) \
  616. if (!DIRECTIVE_HAS_ARG(line, d)) \
  617. return create_error(line, ET_PREPROC, ED_PP_NO_ARG);
  618. #define VALIDATE(retval) \
  619. if (!(retval)) \
  620. return create_error(line, ET_PREPROC, ED_PP_BAD_ARG);
  621. DEBUG("Running preprocessor:")
  622. ErrorInfo* ei;
  623. if ((ei = build_asm_lines(source, source, &state->lines, NULL,
  624. &state->includes)))
  625. return ei;
  626. ASMLine dummy = {.next = state->lines};
  627. ASMLine *prev, *line = &dummy, *next = state->lines;
  628. const ASMLine *first_optimizer = NULL;
  629. while ((prev = line, line = next)) {
  630. next = line->next;
  631. if (line->data[0] == DIRECTIVE_MARKER) {
  632. if (IS_DIRECTIVE(line, DIR_ORIGIN))
  633. continue; // Origins are handled by tokenizer
  634. DEBUG("- handling directive: %.*s", (int) line->length, line->data)
  635. if (IS_DIRECTIVE(line, DIR_OPTIMIZER)) {
  636. REQUIRE_ARG(line, DIR_OPTIMIZER)
  637. bool arg;
  638. VALIDATE(read_bool_argument(&arg, line, DIR_OPTIMIZER, false))
  639. CATCH_DUPES(line, first_optimizer, state->optimizer, arg)
  640. state->optimizer = arg;
  641. first_optimizer = line;
  642. } else if (IS_DIRECTIVE(line, DIR_ROM_SIZE)) {
  643. // TODO
  644. } else if (IS_DIRECTIVE(line, DIR_ROM_HEADER)) {
  645. // TODO
  646. } else if (IS_DIRECTIVE(line, DIR_ROM_CHECKSUM)) {
  647. // TODO
  648. } else if (IS_DIRECTIVE(line, DIR_ROM_PRODUCT)) {
  649. // TODO
  650. } else if (IS_DIRECTIVE(line, DIR_ROM_VERSION)) {
  651. // TODO
  652. } else if (IS_DIRECTIVE(line, DIR_ROM_REGION)) {
  653. // TODO
  654. } else if (IS_DIRECTIVE(line, DIR_ROM_DECLSIZE)) {
  655. // TODO
  656. } else {
  657. return create_error(line, ET_PREPROC, ED_PP_UNKNOWN);
  658. }
  659. // Remove the directive from the line list:
  660. prev->next = next;
  661. line->next = NULL;
  662. free_asm_lines(line);
  663. line = prev;
  664. }
  665. }
  666. state->rom_size = 8; // TODO
  667. #ifdef DEBUG_MODE
  668. DEBUG("Dumping ASMLines:")
  669. const ASMLine *temp = state->lines;
  670. while (temp) {
  671. DEBUG("- %-40.*s [%s:%02zu]", (int) temp->length, temp->data,
  672. temp->filename, temp->original->lineno)
  673. temp = temp->next;
  674. }
  675. #endif
  676. return NULL;
  677. #undef VALIDATE
  678. #undef REQUIRE_ARG
  679. #undef CATCH_DUPES
  680. }
  681. /*
  682. Tokenize ASMLines into ASMInstructions.
  683. On success, state->instructions is modified and NULL is returned. On error,
  684. an ErrorInfo object is returned and state->instructions is not modified.
  685. state->symtable may or may not be modified regardless of success.
  686. */
  687. static ErrorInfo* tokenize(AssemblerState *state)
  688. {
  689. // TODO
  690. // verify no instructions clash with header offset
  691. // if rom size is set, verify nothing overflows
  692. return NULL;
  693. }
  694. /*
  695. Resolve default placeholder values in assembler state, such as ROM size.
  696. On success, no new heap objects are allocated. On error, an ErrorInfo
  697. object is returned.
  698. */
  699. static ErrorInfo* resolve_defaults(AssemblerState *state)
  700. {
  701. // TODO
  702. // if (!state.rom_size)
  703. // set to max possible >= 32 KB, or error if too many instructions
  704. // if (state.header.rom_size)
  705. // check reported rom size is <= actual rom size
  706. // if (!state.header.rom_size)
  707. // set to actual rom size
  708. return NULL;
  709. }
  710. /*
  711. Resolve symbol placeholders in instructions such as jumps and branches.
  712. On success, no new heap objects are allocated. On error, an ErrorInfo
  713. object is returned.
  714. */
  715. static ErrorInfo* resolve_symbols(AssemblerState *state)
  716. {
  717. // TODO
  718. return NULL;
  719. }
  720. /*
  721. Convert finalized ASMInstructions into a binary data block.
  722. This function should never fail.
  723. */
  724. static void serialize_binary(AssemblerState *state, uint8_t *binary)
  725. {
  726. // TODO
  727. for (size_t i = 0; i < state->rom_size; i++)
  728. binary[i] = 'X';
  729. }
  730. /*
  731. Assemble the z80 source code in the source code buffer into binary data.
  732. If successful, return the size of the assembled binary data and change
  733. *binary_ptr to point to the assembled ROM data buffer. *binary_ptr must be
  734. free()'d when finished.
  735. If an error occurred, return 0 and update *ei_ptr to point to an ErrorInfo
  736. object which can be shown to the user with error_info_print(). The
  737. ErrorInfo object must be destroyed with error_info_destroy() when finished.
  738. In either case, only one of *binary_ptr and *ei_ptr is modified.
  739. */
  740. size_t assemble(const LineBuffer *source, uint8_t **binary_ptr, ErrorInfo **ei_ptr)
  741. {
  742. AssemblerState state;
  743. ErrorInfo *error_info;
  744. size_t retval = 0;
  745. init_state(&state);
  746. if ((error_info = preprocess(&state, source)))
  747. goto error;
  748. if (!(state.symtable = malloc(sizeof(ASMSymbolTable))))
  749. OUT_OF_MEMORY()
  750. for (size_t bucket = 0; bucket < SYMBOL_TABLE_BUCKETS; bucket++)
  751. state.symtable->buckets[bucket] = NULL;
  752. if ((error_info = tokenize(&state)))
  753. goto error;
  754. if ((error_info = resolve_defaults(&state)))
  755. goto error;
  756. if ((error_info = resolve_symbols(&state)))
  757. goto error;
  758. uint8_t *binary = malloc(sizeof(uint8_t) * state.rom_size);
  759. if (!binary)
  760. OUT_OF_MEMORY()
  761. serialize_binary(&state, binary);
  762. *binary_ptr = binary;
  763. retval = state.rom_size;
  764. goto cleanup;
  765. error:
  766. *ei_ptr = error_info;
  767. cleanup:
  768. free_asm_lines(state.lines);
  769. free_asm_includes(state.includes);
  770. free_asm_instructions(state.instructions);
  771. free_asm_symtable(state.symtable);
  772. return retval;
  773. }
  774. /*
  775. Assemble the z80 source code at the input path into a binary file.
  776. Return true if the operation was a success and false if it was a failure.
  777. Errors are printed to STDOUT; if the operation was successful then nothing
  778. is printed.
  779. */
  780. bool assemble_file(const char *src_path, const char *dst_path)
  781. {
  782. LineBuffer *source = read_source_file(src_path, true);
  783. if (!source)
  784. return false;
  785. uint8_t *binary;
  786. ErrorInfo *error_info;
  787. size_t size = assemble(source, &binary, &error_info);
  788. free_line_buffer(source);
  789. if (!size) {
  790. error_info_print(error_info, stderr);
  791. error_info_destroy(error_info);
  792. return false;
  793. }
  794. bool success = write_binary_file(dst_path, binary, size);
  795. free(binary);
  796. return success;
  797. }