/* * Mimas conversion tools * * Copyright (C) 2010 Benjamin Moody * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include "mimas.h" #include "symtab.h" #include "parse.h" #include "convert.h" #include "pack.h" #include "output.h" #include "utils.h" #include "insts.h" #include "itempl.h" typedef struct _mimas_prgm { symbol_tab *symtab; unsigned int symrefs[16384]; char *comment; int nsemicolons, nspaces; int blankline; ti8x_var_entry *var; unsigned int symbol_start; unsigned int string_start; } mimas_prgm; static const char *infilename; static int linenum; /* Print an error message. */ void print_error(const char *fmt, ...) { va_list ap; fprintf(stderr, "%s:%d: ", infilename, linenum); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "\n"); } /* Convert string to lowercase */ static void downcase(char *s) { while (*s) { if (*s >= 'A' && *s <= 'Z') *s += 'a' - 'A'; s++; } } /* Convert string to uppercase */ static void upcase(char *s) { while (*s) { if (*s >= 'a' && *s <= 'z') *s += 'A' - 'a'; s++; } } /**************** Output ****************/ /* Add a Mimas instruction to the output file */ static void emit_instr(mimas_prgm *prg, const unsigned char *instr) { int n = (instr[0] & 0x3f) + 2, i, s; if (instr[0] == T_COMMENT && instr[1] == 0) { if (prg->blankline) return; prg->blankline = 1; } else { prg->blankline = 0; } ti8x_var_append_data(prg->var, instr, n); /* increment symbol reference counts */ if ((instr[0] & 0xc0) == T_COMMENT || ((instr[0] & 0xc0) == T_SPECIAL && (instr[1] & 0x80))) return; if ((instr[0] & 0xc0) == T_LABEL) i = 1; else i = 2; while (i < n) { if ((instr[i] & 0xf0) == X_BYTE) i += 2; else if ((instr[i] & 0xf0) == X_WORD) i += 3; else if (instr[i] < 0x40) { s = instr[i] << 8 | instr[i + 1]; prg->symrefs[s]++; i += 2; } else i++; } } /* Create output variable and pre-allocate space for program header */ static void init_output_program(mimas_prgm *prg, ti8x_file *outfile, const char *varname, int archived) { char *converted; prg->symtab = symbol_tab_new(); memset(prg->symrefs, 0, sizeof(prg->symrefs)); prg->comment = NULL; prg->nsemicolons = 0; prg->nspaces = 0; prg->blankline = 0; converted = ascii_to_ti83p(varname); prg->var = ti8x_file_add_var(outfile, 0x15, converted, archived); xfree(converted); ti8x_var_append_data(prg->var, NULL, PROGHEADER_LENGTH + 2); } /* Write a symbol to the symbol and string tables */ static void add_output_symbol(symbol *sym, void *data) { mimas_prgm *prg = data; unsigned int strpos, sympos; unsigned char *packedstr; unsigned char hintbyte; int packedlen; if (sym->id >= 16384 || !prg->symrefs[sym->id]) return; hintbyte = (sym->id << 1) + 1; ti8x_var_append_data(prg->var, &hintbyte, 1); strpos = prg->var->length - 2 - prg->string_start; sympos = prg->symbol_start + 2 + 3 * sym->id; prg->var->data[sympos] = strpos; prg->var->data[sympos + 1] = strpos >> 8; if (prg->symrefs[sym->id] > 255) prg->var->data[sympos + 2] = 255; else prg->var->data[sympos + 2] = prg->symrefs[sym->id]; pack_symbol_string(&packedstr, &packedlen, sym->name); ti8x_var_append_data(prg->var, packedstr, packedlen); xfree(packedstr); } /* Write program header, symbols, and section table to output variable */ static void finish_output_program(mimas_prgm *prg) { unsigned char progheader[PROGHEADER_LENGTH]; unsigned char sectheader[SECTHEADER_LENGTH]; unsigned int pos; memset(progheader, 0, sizeof(progheader)); memcpy(progheader, "MimS\1", 5); progheader[PROGHEADER_TYPE] = PROGTYPE_TI83P; memset(sectheader, 0, sizeof(sectheader)); sectheader[SECTHEADER_SECTION_TYPE] = SECTION_CODE; memcpy(sectheader + SECTHEADER_NAME, "MAIN", 4); /* single section, starts at offset PROGHEADER_LENGTH and ends at current end of file */ pos = PROGHEADER_LENGTH; sectheader[SECTHEADER_DATA_START] = pos; sectheader[SECTHEADER_DATA_START + 1] = pos >> 8; prg->symbol_start = pos = prg->var->length - 2; sectheader[SECTHEADER_DATA_END] = pos; sectheader[SECTHEADER_DATA_END + 1] = pos >> 8; progheader[PROGHEADER_SYMBOL_START] = pos; progheader[PROGHEADER_SYMBOL_START + 1] = pos >> 8; ti8x_var_append_data(prg->var, NULL, symbol_tab_count(prg->symtab) * 3); prg->string_start = pos = prg->var->length - 2; progheader[PROGHEADER_SYMBOL_STRING_START] = pos; progheader[PROGHEADER_SYMBOL_STRING_START + 1] = pos >> 8; ti8x_var_append_data(prg->var, NULL, 1); symbol_tab_foreach(prg->symtab, &add_output_symbol, prg); pos = prg->var->length - 2; progheader[PROGHEADER_CONSTANT_START] = pos; progheader[PROGHEADER_CONSTANT_START + 1] = pos >> 8; progheader[PROGHEADER_IMPORT_START] = pos; progheader[PROGHEADER_IMPORT_START + 1] = pos >> 8; progheader[PROGHEADER_SECTION_TABLE_START] = pos; progheader[PROGHEADER_SECTION_TABLE_START + 1] = pos >> 8; ti8x_var_append_data(prg->var, sectheader, SECTHEADER_LENGTH); pos = prg->var->length - 2; progheader[PROGHEADER_SECTION_TABLE_END] = pos; progheader[PROGHEADER_SECTION_TABLE_END + 1] = pos >> 8; memcpy(prg->var->data + 2, progheader, PROGHEADER_LENGTH); prg->var->data[0] = prg->var->length - 2; prg->var->data[1] = (prg->var->length - 2) >> 8; symbol_tab_free(prg->symtab); prg->symtab = NULL; } /**************** Expressions ****************/ /* Get length of a bytecode expression */ static int expr_length(const unsigned char *exp) { int i, depth; depth = 1; i = 0; while (depth != 0) { if (exp[i] < 0x40) { /* symbol */ i += 2; depth--; } else if (exp[i] < 0x50) { /* special */ i++; depth--; } else if (exp[i] < 0x60) { /* byte */ i += 2; depth--; } else if (exp[i] < 0x70) { /* word */ i += 3; depth--; } else if (exp[i] < 0x90) { /* reserved expression codes */ return 99; } else if (exp[i] < 0xa0) { /* unary operator */ i++; } else if (exp[i] < 0xc0) { /* binary operator */ i++; depth++; } else { /* dec6 constant */ i++; depth--; } } return i; } /* Check if parse tree is constant */ static int tree_is_constant(const parse_node *tree, int allow_strs) { if (tree->type > 256 && tree->type != XX_STRING) return 0; if (!allow_strs && tree->type == XX_STRING && tree->longval != 1) return 0; if (tree->type == X_REGVAL) return 0; if (tree->left && !tree_is_constant(tree->left, allow_strs)) return 0; if (tree->right && !tree_is_constant(tree->right, allow_strs)) return 0; return 1; } /* Check if tree is a particular small integer value */ static int tree_is_int(const parse_node *tree, int v) { return (tree->type == X_DEC6 && tree->longval == (v & 0x3f)); } /* Check if sub-template matches parse tree */ static int subtemplate_matches_tree(const unsigned char *template, const parse_node *tree, parse_node **args) { unsigned int an, ac; const parse_node *lhs; parse_node *rhs, *parent; if (!tree) return 0; if (template[0] == X_ARGVAL) { /* constant argument */ an = template[1] & 3; ac = template[1] & AC_mask; if (ac != AC_CONSTANT) { print_error("INTERNAL ERROR: unhandled arg category %02x", ac); return 0; } if (!tree_is_constant(tree, 0)) return 0; free_parse_tree(args[an]); args[an] = dup_parse_tree(tree); return 1; } else if (template[0] == X_REGVAL && template[1] == X_ARGVAL) { /* register argument */ an = template[2] & 3; ac = template[2] & AC_mask; if (ac == AC_BCDEHLMA && tree->type == X_PAREN && tree->left && tree->left->type == X_REGVAL && tree->left->left && tree_is_int(tree->left->left, X_REG_HL)) { free_parse_tree(args[an]); args[an] = new_parse_node_long(X_DEC6, X_REG_iHL); return 1; } if (tree->type != X_REGVAL || !tree->left) return 0; free_parse_tree(args[an]); args[an] = dup_parse_tree(tree->left); switch (ac) { case AC_BCDEHLMA: case AC_BCDEHLA: if (tree_is_int(tree->left, X_REG_H) || tree_is_int(tree->left, X_REG_L)) return 1; case AC_BCDEA: if (tree_is_int(tree->left, X_REG_B) || tree_is_int(tree->left, X_REG_C) || tree_is_int(tree->left, X_REG_D) || tree_is_int(tree->left, X_REG_E) || tree_is_int(tree->left, X_REG_A)) return 1; else return 0; case AC_BDHS: if (tree_is_int(tree->left, X_REG_BC) || tree_is_int(tree->left, X_REG_DE) || tree_is_int(tree->left, X_REG_HL) || tree_is_int(tree->left, X_REG_SP)) return 1; else return 0; case AC_BDHA: if (tree_is_int(tree->left, X_REG_BC) || tree_is_int(tree->left, X_REG_DE) || tree_is_int(tree->left, X_REG_HL) || tree_is_int(tree->left, X_REG_AF)) return 1; else return 0; case AC_BDS: if (tree_is_int(tree->left, X_REG_BC) || tree_is_int(tree->left, X_REG_DE) || tree_is_int(tree->left, X_REG_SP)) return 1; else return 0; case AC_IXHL: if (tree_is_int(tree->left, X_REG_IXH) || tree_is_int(tree->left, X_REG_IXL)) return 1; else return 0; case AC_IYHL: if (tree_is_int(tree->left, X_REG_IYH) || tree_is_int(tree->left, X_REG_IYL)) return 1; else return 0; case AC_ZCPS: if (tree_is_int(tree->left, X_COND_PO) || tree_is_int(tree->left, X_COND_PE) || tree_is_int(tree->left, X_COND_P) || tree_is_int(tree->left, X_COND_M)) return 1; case AC_ZC: if (tree_is_int(tree->left, X_COND_NZ) || tree_is_int(tree->left, X_COND_Z) || tree_is_int(tree->left, X_COND_NC)) return 1; else if (tree_is_int(tree->left, X_REG_C)) { args[an]->longval = X_COND_C & 0x3f; return 1; } else return 0; default: print_error("INTERNAL ERROR: unhandled arg category %02x", ac); return 0; } } else if (template[0] == X_ADD) { /* check leftmost term (because "IX + 1 - 2" is ((IX + 1) - 2); we want to match this with the pattern (IX + *)) */ lhs = tree; while (lhs->left && (lhs->type == X_ADD || lhs->type == X_SUB)) lhs = lhs->left; if (!subtemplate_matches_tree(template + 1, lhs, args)) return 0; if (lhs == tree) { /* right hand side = 0 */ rhs = new_parse_node_long(X_DEC6, 0); } else { /* right hand side is what we get by removing the leftmost term: rhs of ((IX + 1) - 2) is (1 - 2), while rhs of ((IX - 1) + 2) is ((-1) + 2) */ rhs = dup_parse_tree(tree); parent = rhs; while (parent->left && parent->left->left && (parent->left->type == X_ADD || parent->left->type == X_SUB)) parent = parent->left; if (parent->type == X_ADD) parent->type = X_PAREN; else parent->type = X_MINUS; free_parse_tree(parent->left); parent->left = parent->right; parent->right = NULL; } if (!subtemplate_matches_tree(template + 1 + expr_length(template + 1), rhs, args)) { free_parse_tree(rhs); return 0; } free_parse_tree(rhs); return 1; } else if (template[0] == X_REGVAL || template[0] == X_PAREN) { if (tree->type != template[0]) return 0; return subtemplate_matches_tree(template + 1, tree->left, args); } else if (template[0] == X_REG_AF2) { if (tree_is_int(tree, X_REG_AF) || tree_is_int(tree, X_REG_AF2)) return 1; else return 0; } else if (template[0] >= X_DEC6) { return tree_is_int(tree, template[0]); } else { print_error("INTERNAL ERROR: unhandled template bytecode %02x", template[0]); return 0; } } /* Check if instruction template matches parse tree */ static int template_matches_tree(const unsigned char *template, const parse_node *tree, parse_node **args) { unsigned int i, n; free_parse_tree(args[0]); free_parse_tree(args[1]); free_parse_tree(args[2]); args[0] = args[1] = args[2] = NULL; if (!template[0] && !tree) return 1; else if (!template[0] || !tree) return 0; i = 0; while (i < template[0]) { n = expr_length(template + i + 1); if (i + n >= template[0]) { return subtemplate_matches_tree(template + i + 1, tree, args); } else { if (tree->type != XX_COMMA || !tree->left || !tree->right) return 0; if (!subtemplate_matches_tree(template + i + 1, tree->left, args)) return 0; tree = tree->right; } i += n; } return 1; } /**************** Instructions ****************/ /* Check if given string is a normal mnemonic. */ static int find_normal_code(const char *mnem) { int start, end, i, d; if (!strcmp(mnem, "sll") || !strcmp(mnem, "sl1")) mnem = "slia"; start = 1; end = I_last + 1; while (start < end) { i = (start + end) / 2; d = strcmp(mnem, normal_mnemonics[i]); if (d > 0) start = i + 1; else end = i; } if (start <= I_last && !strcmp(mnem, normal_mnemonics[start])) return start; else return 0; } /* Check if given string is a special mnemonic. */ static int find_special_code(const char *mnem) { if (*mnem == '.' || *mnem == '#') mnem++; if (!strcmp(mnem, "end") || !strcmp(mnem, "processor") || !strcmp(mnem, "nolist") || !strcmp(mnem, "list")) return -1; if (!strcmp(mnem, "define") || !strcmp(mnem, "include") || !strcmp(mnem, "elif") || !strcmp(mnem, "macro") || !strcmp(mnem, "endm")) return -2; if (!strcmp(mnem, "ascii")) return S_ASCII; if (!strcmp(mnem, "asciz")) return S_ASCIZ; if (!strcmp(mnem, "incbin")) return S_INCBIN; if (!strcmp(mnem, "bcall") || !strcmp(mnem, "b_call")) return S_BCALL; if (!strcmp(mnem, "bjump") || !strcmp(mnem, "b_jump")) return S_BJUMP; if (!strcmp(mnem, "break")) return S_BREAK; if (!strcmp(mnem, "jq")) return S_JQ_n; if (!strcmp(mnem, "align")) return S_ALIGN; if (!strcmp(mnem, "assert")) return S_ASSERT; if (!strcmp(mnem, "block") || !strcmp(mnem, "ds")) return S_BLOCK; if (!strcmp(mnem, "byte") || !strcmp(mnem, "db") || !strcmp(mnem, "defb")) return S_BYTE; if (!strcmp(mnem, "else")) return S_ELSE; if (!strcmp(mnem, "endif")) return S_ENDIF; if (!strcmp(mnem, "if")) return S_IF; if (!strcmp(mnem, "ifdef")) return S_IFDEF; if (!strcmp(mnem, "ifndef")) return S_IFNDEF; if (!strcmp(mnem, "org")) return S_ORG; if (!strcmp(mnem, "rorg")) return S_RORG; if (!strcmp(mnem, "word") || !strcmp(mnem, "dw") || !strcmp(mnem, "defw")) return S_WORD; return 0; } /**************** Bytecode ****************/ /* Append byte to a buffer */ static int append_expr_1(unsigned char **outptr, int *outcount, unsigned int v) { if (*outcount == 0) { print_error("expression too complex"); return 1; } **outptr = v; (*outptr)++; (*outcount)--; return 0; } /* Append 2 bytes to a buffer */ static int append_expr_2(unsigned char **outptr, int *outcount, unsigned int a, unsigned int b) { return (append_expr_1(outptr, outcount, a) || append_expr_1(outptr, outcount, b)); } /* Append 3 bytes to a buffer */ static int append_expr_3(unsigned char **outptr, int *outcount, unsigned int a, unsigned int b, unsigned int c) { return (append_expr_1(outptr, outcount, a) || append_expr_1(outptr, outcount, b) || append_expr_1(outptr, outcount, c)); } /* Convert parsed expression to bytecode */ static int expr_to_bytecode(unsigned char **outptr, int *outcount, const parse_node *tree) { if (!tree) { print_error("INTERNAL ERROR: null expression"); return 1; } if (tree->type == XX_STRING) { if (tree->longval == 1) return append_expr_2(outptr, outcount, X_CHAR, tree->strval[0]); else { print_error("cannot use string constants in an expression"); return 1; } } if (tree->type > 255 || tree->type == X_REGVAL || tree->type == X_ARGVAL) { print_error("INTERNAL ERROR: bad expression type"); return 1; } if (tree->type == X_PAREN) return expr_to_bytecode(outptr, outcount, tree->left); if (tree->type >= X_DEC6) return append_expr_1(outptr, outcount, tree->longval | X_DEC6); else if (tree->type >= X_BINARY) return (append_expr_1(outptr, outcount, tree->type) || expr_to_bytecode(outptr, outcount, tree->left) || expr_to_bytecode(outptr, outcount, tree->right)); else if (tree->type >= X_UNARY) return (append_expr_1(outptr, outcount, tree->type) || expr_to_bytecode(outptr, outcount, tree->left)); else if (tree->type >= X_WORD) return append_expr_3(outptr, outcount, tree->type, tree->longval, tree->longval >> 8); else if (tree->type >= X_BYTE) return append_expr_2(outptr, outcount, tree->type, tree->longval); else if (tree->type >= X_SPECIAL) return append_expr_1(outptr, outcount, tree->type); else { if (!tree->symval) { print_error("INTERNAL ERROR: symbol value undefined"); return 1; } else { return append_expr_2(outptr, outcount, tree->symval->id >> 8, tree->symval->id); } } } /* Convert normal insruction to bytecode */ static int instr_to_bytecode(unsigned char *instr, unsigned int type, unsigned int op, parse_node **args) { unsigned char *iptr = instr + 2; int icount = 63; int j; for (j = 0; j < 3; j++) { if (args && args[j]) { if (expr_to_bytecode(&iptr, &icount, args[j])) return 1; } } instr[0] = type | (63 - icount); instr[1] = op; return 0; } /* Write a comment line */ static void write_comment_line(mimas_prgm *prg, const char *text) { int i, n, packedlen; char *s; unsigned char *packedstr; unsigned char instr_buf[65]; n = strlen(text); if (n > 64) { print_error("warning: comment line too long"); for (i = 0; i < n; i += 64) { s = xstrndup(text + i, 64); write_comment_line(prg, s); xfree(s); } } else { s = ascii_to_ti83p(text); pack_comment_string(&packedstr, &packedlen, s); xfree(s); while (packedlen > 1 && packedstr[packedlen - 1] == 0) packedlen--; if (packedlen > 0) { instr_buf[0] = T_COMMENT | (packedlen - 1); memcpy(instr_buf + 1, packedstr, packedlen); } else { instr_buf[0] = T_COMMENT; instr_buf[1] = 0; } emit_instr(prg, instr_buf); xfree(packedstr); } } #define MAX_COMMENT_WIDTH 89 /* Write out queued comment text, splitting up long lines to fit on the calculator screen */ static void flush_comments(mimas_prgm *prg) { int end, start, lineend, width, i; char *conv, *s; if (prg->comment) { start = 0; end = strlen(prg->comment); conv = ascii_to_ti83p(prg->comment); while (start < end) { lineend = end; width = 0; i = start; while (width <= MAX_COMMENT_WIDTH) { if (prg->comment[i] == 0) { lineend = i; break; } else if (is_whitespace(prg->comment[i])) lineend = i; width += get_comment_char_width(conv[i]); i++; } if (lineend > start) { s = xstrndup(prg->comment + start, lineend - start); write_comment_line(prg, s); xfree(s); } start = lineend; while (is_whitespace(prg->comment[start])) start++; } xfree(conv); xfree(prg->comment); prg->comment = NULL; prg->nsemicolons = prg->nspaces = 0; } } /* Add comment text to the queue */ static void write_comment(mimas_prgm *prg, const char *text, unsigned int flags) { char *s; int n, m; int nsemicolons = 0, nspaces = 0; int hastabs = (strchr(text, '\t') ? 1 : 0); while (*text == ';') { nsemicolons++; text++; } while (is_whitespace(*text)) { nspaces++; text++; } if (!*text) { flush_comments(prg); if (flags & PARSE_INCLUDE_EMPTY_COMMENTS) write_comment_line(prg, ""); return; } m = strlen(text); while (m > 0 && is_whitespace(text[m - 1])) m--; if ((flags & PARSE_REFLOW_COMMENTS) && !hastabs) { if (prg->nsemicolons != nsemicolons || prg->nspaces > nspaces || text[0] == '-' || text[0] == '*') flush_comments(prg); prg->nsemicolons = nsemicolons; prg->nspaces = nspaces; if (prg->comment) { n = strlen(prg->comment); s = xnew(char, n + m + 2); memcpy(s, prg->comment, n); s[n] = ' '; memcpy(s + n + 1, text, m); s[n + m + 1] = 0; xfree(prg->comment); prg->comment = s; } else { prg->comment = xstrndup(text, m); } } else { flush_comments(prg); s = xstrndup(text, m); write_comment_line(prg, s); xfree(s); } } /* Write an unparseable instruction as a comment */ static void write_error_comment(mimas_prgm *prg, const char *text, int length) { char *p; p = xnew(char, length + 2); p[0] = '*'; memcpy(p + 1, text, length); p[length + 1] = 0; flush_comments(prg); write_comment_line(prg, p); xfree(p); } /* Write a label */ static void write_label(mimas_prgm *prg, const char *name, unsigned int flags) { unsigned char instr_buf[65]; parse_node *node; flush_comments(prg); node = parse_label(name, prg->symtab, flags); if (node && node->type == X_NEXT_ANON) { instr_buf[0] = T_LABEL; instr_buf[1] = X_NEXT_ANON; emit_instr(prg, instr_buf); } else if (node && node->symval) { instr_buf[0] = T_LABEL | 1; instr_buf[1] = node->symval->id >> 8; instr_buf[2] = node->symval->id; emit_instr(prg, instr_buf); } else { write_error_comment(prg, name, strlen(name)); } if (node) free_parse_tree(node); } /* Write an EQU directive */ static int write_equate(mimas_prgm *prg, const char *name, parse_node *argtree, unsigned int flags) { unsigned char instr_buf[65]; parse_node *args[4]; flush_comments(prg); args[0] = parse_label(name, prg->symtab, flags); if (!args[0]) return 1; args[1] = args[2] = args[3] = NULL; if (!template_matches_tree(templ__n, argtree, args + 1) || instr_to_bytecode(instr_buf, T_SPECIAL, S_EQU, args)) { free_parse_tree(args[0]); free_parse_tree(args[1]); free_parse_tree(args[2]); free_parse_tree(args[3]); return 1; } emit_instr(prg, instr_buf); free_parse_tree(args[0]); free_parse_tree(args[1]); free_parse_tree(args[2]); free_parse_tree(args[3]); return 0; } /* Write an ASCII/ASCIZ directive (or multiple directives if necessary) */ static void write_ascii(mimas_prgm *prg, const parse_node *arg, int addzero) { unsigned char instr_buf[65]; int n, m; n = 0; while (n < arg->longval) { m = arg->longval - n; if (m > 63) { m = 63; instr_buf[1] = S_ASCII; } else { instr_buf[1] = (addzero ? S_ASCIZ : S_ASCII); } instr_buf[0] = T_SPECIAL | m; memcpy(instr_buf + 2, arg->strval + n, m); emit_instr(prg, instr_buf); n += m; } } /* Write an instruction or directive */ static int write_instruction(mimas_prgm *prg, const char *mnem, parse_node *argtree, unsigned int flags) { int special, first, i, j, icount; parse_node *args[3], *arg, *nextarg; unsigned char instr_buf[65], expr_buf[63], *iptr; flush_comments(prg); args[0] = args[1] = args[2] = NULL; first = find_normal_code(mnem); if (first) { /* search for a template that matches the given args */ for (i = first; i <= I_last && !strcmp(normal_mnemonics[i], normal_mnemonics[first]); i++) { if (template_matches_tree(normal_templates[i], argtree, args)) { if (instr_to_bytecode(instr_buf, T_NORMAL, i, args)) { free_parse_tree(args[0]); free_parse_tree(args[1]); free_parse_tree(args[2]); return 1; } emit_instr(prg, instr_buf); free_parse_tree(args[0]); free_parse_tree(args[1]); free_parse_tree(args[2]); return 0; } } free_parse_tree(args[0]); free_parse_tree(args[1]); free_parse_tree(args[2]); print_error("Invalid arguments to '%s'", mnem); return 1; } special = find_special_code(mnem); if (special == -1) return 0; switch (special) { case S_ASCII: case S_ASCIZ: if (!argtree || argtree->type != XX_STRING) { print_error("Invalid arguments to '%s'", mnem); return 1; } write_ascii(prg, argtree, (special == S_ASCIZ)); return 0; case S_INCBIN: if (!argtree || argtree->type != XX_STRING) { print_error("Invalid arguments to '%s'", mnem); return 1; } if (argtree->longval > 4 && !strncmp(argtree->strval, "prgm", 4)) { instr_buf[2] = 5; /* ProgObj */ for (i = 0; i < 8 && i + 4 < argtree->longval; i++) { if (argtree->strval[i + 4] == '.') break; instr_buf[i + 3] = argtree->strval[i + 4]; } } else if (argtree->longval == 4 && !strncmp(argtree->strval, "Pic", 3) && argtree->strval[3] >= '0' && argtree->strval[3] <= '9') { instr_buf[2] = 7; /* PictObj */ instr_buf[3] = 0x60; /* tVarPict */ if (argtree->strval[3] == '0') instr_buf[4] = 9; else instr_buf[4] = argtree->strval[3] - '1'; i = 2; } else { instr_buf[2] = 0x15; /* AppVarObj */ for (i = 0; i < 8 && i < argtree->longval; i++) { if (argtree->strval[i] == '.') break; instr_buf[i + 3] = argtree->strval[i]; } } instr_buf[0] = T_SPECIAL | (i + 1); instr_buf[1] = S_INCBIN; emit_instr(prg, instr_buf); return 0; case S_BREAK: if (!argtree) { instr_to_bytecode(instr_buf, T_SPECIAL, special, NULL); emit_instr(prg, instr_buf); return 0; } else if (template_matches_tree(templ__ccc, argtree, args)) { if (instr_to_bytecode(instr_buf, T_SPECIAL, S_BREAK_ccc, args)) { free_parse_tree(args[0]); return 1; } emit_instr(prg, instr_buf); free_parse_tree(args[0]); return 0; } else { print_error("Invalid arguments to '%s'", mnem); return 1; } case S_BYTE: i = 0; while (argtree) { if (argtree->type == XX_COMMA && argtree->left) { arg = argtree->left; argtree = argtree->right; if (argtree && argtree->type == XX_COMMA) nextarg = argtree->left; else nextarg = argtree; } else { arg = argtree; argtree = NULL; nextarg = NULL; } if (arg->type == XX_STRING && arg->longval != 1) { /* write out string args as ASCII/ASCIZ directives */ if (i) emit_instr(prg, instr_buf); i = 0; if (nextarg && nextarg->type == X_DEC6 && nextarg->longval == 0) { write_ascii(prg, arg, 1); argtree = argtree->right; } else { write_ascii(prg, arg, 0); } } else { /* write out others as BYTE directives; try to fit as many args into one line as we can, but split them up if necessary */ iptr = expr_buf; icount = 63; if (expr_to_bytecode(&iptr, &icount, arg)) return 1; j = 63 - icount; if (i + j > 63) { emit_instr(prg, instr_buf); i = 0; } memcpy(instr_buf + i + 2, expr_buf, j); i += j; instr_buf[0] = T_SPECIAL | i; instr_buf[1] = S_BYTE; } } if (i) emit_instr(prg, instr_buf); return 0; case S_WORD: i = 0; /* write out args as WORD directives; try to fit as many args into each line as we can, but split them up if necessary */ while (argtree) { if (argtree->type == XX_COMMA) { arg = argtree->left; argtree = argtree->right; } else { arg = argtree; argtree = NULL; } iptr = expr_buf; icount = 63; if (expr_to_bytecode(&iptr, &icount, arg)) return 1; j = 63 - icount; if (i + j > 63) { emit_instr(prg, instr_buf); i = 0; } memcpy(instr_buf + i + 2, expr_buf, j); i += j; instr_buf[0] = T_SPECIAL | i; instr_buf[1] = S_WORD; } if (i) emit_instr(prg, instr_buf); return 0; case S_JQ_n: if (template_matches_tree(templ__cc_n, argtree, args)) { if (instr_to_bytecode(instr_buf, T_SPECIAL, S_JQ_cc_n, args)) { free_parse_tree(args[0]); free_parse_tree(args[1]); return 1; } emit_instr(prg, instr_buf); free_parse_tree(args[0]); free_parse_tree(args[1]); return 0; } case S_BCALL: case S_BJUMP: case S_ALIGN: case S_ASSERT: case S_BLOCK: case S_IF: case S_ORG: case S_RORG: if (template_matches_tree(templ__n, argtree, args)) { if (special == S_ORG && (flags & PARSE_ORG_IS_RORG)) special = S_RORG; if (instr_to_bytecode(instr_buf, T_SPECIAL, special, args)) { free_parse_tree(args[0]); return 1; } emit_instr(prg, instr_buf); free_parse_tree(args[0]); return 0; } else { print_error("Invalid arguments to '%s'", mnem); return 1; } case S_ELSE: case S_ENDIF: if (!argtree) { instr_to_bytecode(instr_buf, T_SPECIAL, special, NULL); emit_instr(prg, instr_buf); return 0; } else { print_error("Invalid arguments to '%s'", mnem); return 1; } default: print_error("Unknown instruction '%s'", mnem); return 1; } } /**************** Line parsing ****************/ /* Parse label, if any, at start of (sub)-line */ static void parse_subline_label(const char **in, char **lbl) { const char *p, *q, *r; char *l; p = *in; while (is_whitespace(*p)) p++; q = p; while (*q == '@' || *q == '$' || *q == '_' || *q == '.' || (*q >= '0' && *q <= '9') || (*q >= 'A' && *q <= 'Z') || (*q >= 'a' && *q <= 'z') || (*q & 0x80)) q++; if (q == p) return; r = q; while (is_whitespace(*r)) r++; if (*r == ':') { *lbl = xstrndup(p, q - p); *in = r + 1; } else if (p == *in) { l = xstrndup(p, q - p); downcase(l); if (!find_normal_code(l) && !find_special_code(l)) { *lbl = xstrndup(p, q - p); *in = r; } xfree(l); } } /* Parse instruction mnemonic and arguments, if any */ static void parse_subline_instruction(mimas_prgm *prg, const char **in, const char *lbl, unsigned int flags) { const char *p, *q; char *mnem; parse_node *argtree; p = *in; while (is_whitespace(*p)) p++; if (*p == '=') { mnem = xstrdup("equ"); *in = p + 1; } else { q = p; while (*q == '@' || *q == '#' || *q == '$' || *q == '_' || *q == '.' || (*q >= '0' && *q <= '9') || (*q >= 'A' && *q <= 'Z') || (*q >= 'a' && *q <= 'z') || (*q & 0x80)) q++; if (p == q) mnem = NULL; else { mnem = xstrndup(p, q - p); downcase(mnem); } while (is_whitespace(*q)) q++; *in = q; } if (!mnem) { if (lbl) write_label(prg, lbl, flags); return; } if (!strcmp(mnem, "include") || !strcmp(mnem, ".include") || !strcmp(mnem, "#include")) { if (lbl) write_label(prg, lbl, flags); print_error("#INCLUDE directive is not supported"); write_error_comment(prg, p, strlen(p)); *in = ""; xfree(mnem); return; } if (!strcmp(mnem, "#define") || !strcmp(mnem, "#defcont")) { if (lbl) write_label(prg, lbl, flags); print_error("#DEFINE directive is not supported"); write_error_comment(prg, p, strlen(p)); *in = ""; xfree(mnem); return; } if (!strcmp(mnem, "bcall") || !strcmp(mnem, "b_call") || !strcmp(mnem, "bjump") || !strcmp(mnem, "b_jump")) flags |= PARSE_ROMCALLS_NO_UNDERSCORE; if (**in && **in != ';' && **in != '\\') { argtree = parse_expr_list(in, prg->symtab, flags); if (!argtree) { *in = ""; if (lbl) write_label(prg, lbl, flags); write_error_comment(prg, p, strlen(p)); xfree(mnem); return; } } else { argtree = NULL; } if (!strcmp(mnem, "equ") || !strcmp(mnem, ".equ")) { if (!lbl) { print_error("Missing label before .EQU"); write_error_comment(prg, p, *in - p); } else { if (write_equate(prg, lbl, argtree, flags)) { write_error_comment(prg, lbl, strlen(lbl)); write_error_comment(prg, p, *in - p); } } free_parse_tree(argtree); xfree(mnem); return; } else if (!strcmp(mnem, ".set")) { if (!lbl) { print_error("Missing label before .SET"); write_error_comment(prg, p, *in - p); } else { print_error(".SET directive is not supported (using .EQU instead)"); if (write_equate(prg, lbl, argtree, flags)) { write_error_comment(prg, lbl, strlen(lbl)); write_error_comment(prg, p, *in - p); } } free_parse_tree(argtree); xfree(mnem); return; } if (lbl) write_label(prg, lbl, flags); if (write_instruction(prg, mnem, argtree, flags)) write_error_comment(prg, p, *in - p); free_parse_tree(argtree); xfree(mnem); } /* Parse comment, if any */ static void parse_subline_comment(mimas_prgm *prg, const char **in, unsigned int flags) { while (is_whitespace(**in)) (*in)++; if (**in == ';') { write_comment(prg, *in, flags); *in = ""; } } /* Parse a complete subline */ static void parse_asm_subline(mimas_prgm *prg, const char **in, unsigned int flags) { char *lbl = NULL; parse_subline_label(in, &lbl); parse_subline_instruction(prg, in, lbl, flags); parse_subline_comment(prg, in, flags); if (lbl) xfree(lbl); } /* Parse a complete line of code */ static void parse_asm_line(mimas_prgm *prg, const char *line, unsigned int flags) { const char *p; p = line; while (is_whitespace(*p)) p++; if (!*p) { flush_comments(prg); if (flags & PARSE_INCLUDE_EMPTY_LINES) { write_comment_line(prg, ""); } return; } p = line; while (*p) { parse_asm_subline(prg, &p, flags); if (*p == '\\') p++; else if (*p) { print_error("Unexpected '%c' in instruction", *p); break; } } } static int write_output(ti8x_file *f) { FILE *outf; if (f->name && strcmp(f->name, "-")) { outf = fopen(f->name, "wb"); if (!outf) { perror(f->name); return 1; } if (ti8x_file_write(outf, f)) { fclose(outf); return 1; } else { fclose(outf); return 0; } } else { return ti8x_file_write(stdout, f); } } static const char usage[] = "Usage: %s [options] asm-file ...\n" "Options may include:\n" " -o, --output FILE: write output to FILE\n" " -a, --archive: send variables to archive\n" " -f, --format NAME: use the given source format\n" " (supported formats: tasm, spasm,\n" " zmasm, tpasm, miniasm)\n" " -b, --blank-lines: include blank lines (as empty comments)\n" " -l, --long-comments: do not wrap comments to fit calc screen\n" " --no-precedence: ignore operator precedence\n" " ('1+2*3' = 9; default for tasm/spasm)\n" " --normal-precedence: use normal operator precedence\n" " ('1+2*3' = 7; default for other formats)\n"; static int checkarg(int argc, char **argv, int *pos, char shortopt, const char *longopt, const char **arg) { const char *a; if (*pos >= argc || argv[*pos][0] != '-') return 0; if (shortopt && argv[*pos][1] == shortopt) a = argv[*pos][2] ? &argv[*pos][2] : NULL; else if (argv[*pos][1] != '-') return 0; else if (longopt && !strcasecmp(&argv[*pos][2], longopt)) a = NULL; else if (longopt && !strncasecmp(&argv[*pos][2], longopt, strlen(longopt)) && argv[*pos][2 + strlen(longopt)] == '=') a = &argv[*pos][3 + strlen(longopt)]; else return 0; if (!arg) return 1; else if (a) { *arg = a; return 1; } else { (*pos)++; if (*pos >= argc) { fprintf(stderr, "%s: %s: requires an argument\n", argv[0], argv[*pos - 1]); exit(1); } else { *arg = argv[*pos]; argv[*pos] = NULL; return 1; } } } int main(int argc, char **argv) { const char *outfilename = NULL, *arg; char *varname, *thisfilename, *p; FILE *inf; int archive = 0; unsigned int formatflags = (PARSE_OCTAL_AT); unsigned int miscflags = (PARSE_INCLUDE_EMPTY_COMMENTS | PARSE_REFLOW_COMMENTS); unsigned int formatmask = ~0; ti8x_file *outfile; mimas_prgm prg; int i, status = 0; char *line; if (argc < 2) { fprintf(stderr, usage, argv[0]); return 1; } for (i = 1; i < argc; i++) { if (argv[i][0] == '-' && argv[i][1]) { if (checkarg(argc, argv, &i, 'o', "output", &arg)) outfilename = arg; else if (checkarg(argc, argv, &i, 'f', "format", &arg)) { if (!strcasecmp(arg, "t") || !strcasecmp(arg, "tasm")) formatflags = (PARSE_OCTAL_AT | PARSE_NO_PRECEDENCE); else if (!strcasecmp(arg, "s") || !strcasecmp(arg, "spasm")) formatflags = (PARSE_OCTAL_AT | PARSE_NO_PRECEDENCE | PARSE_ORG_IS_RORG); else if (!strcasecmp(arg, "z") || !strcasecmp(arg, "zmasm") || !strcasecmp(arg, "zds")) formatflags = (PARSE_ZMASM_LABELS); else if (!strcasecmp(arg, "m") || !strcasecmp(arg, "miniasm")) formatflags = (0); else if (!strcasecmp(arg, "tp") || !strcasecmp(arg, "tpasm")) formatflags = (0); else if (!strcasecmp(arg, "mimas")) formatflags = (0); else { fprintf(stderr, "%s: unknown format '%s'\n", argv[0], arg); return 1; } } else if (checkarg(argc, argv, &i, 'a', "archive", NULL)) { archive = 1; } else if (checkarg(argc, argv, &i, 'b', "blank-lines", NULL)) { miscflags |= PARSE_INCLUDE_EMPTY_LINES; } else if (checkarg(argc, argv, &i, 'l', "long-comments", NULL)) { miscflags &= ~PARSE_REFLOW_COMMENTS; } else if (checkarg(argc, argv, &i, 0, "no-precedence", NULL)) { formatmask &= ~PARSE_NO_PRECEDENCE; miscflags |= PARSE_NO_PRECEDENCE; } else if (checkarg(argc, argv, &i, 0, "normal-precedence", NULL)) { formatmask &= ~PARSE_NO_PRECEDENCE; miscflags &= ~PARSE_NO_PRECEDENCE; } else if (checkarg(argc, argv, &i, 0, "help", NULL)) { printf(usage, argv[0]); return 0; } else if (checkarg(argc, argv, &i, 0, "version", NULL)) { printf("asmto8xv (Mimas %s)\n" "Copyright (C) 2010 Benjamin Moody\n" "This program is free software. " "There is NO WARRANTY of any kind.\n", PACKAGE_VERSION); return 0; } else { fprintf(stderr, "%s: unknown option %s\n", argv[0], argv[i]); fprintf(stderr, usage, argv[0]); return 1; } } } if (outfilename) outfile = ti8x_file_new(outfilename); else outfile = NULL; for (i = 1; !status && i < argc; i++) { if (!argv[i] || (argv[i][0] == '-' && argv[i][1])) continue; else if (argv[i][0] == '-') { inf = stdin; infilename = "standard input"; varname = xstrdup("out"); } else { infilename = argv[i]; inf = fopen(infilename, "rt"); if (!inf) { perror(infilename); status = 2; break; } if ((p = strrchr(infilename, '/')) || (p = strrchr(infilename, '\\'))) varname = xstrdup(p + 1); else varname = xstrdup(infilename); if ((p = strrchr(varname, '.'))) *p = 0; } if (!outfilename) { thisfilename = xnew(char, strlen(varname) + 5); strcpy(thisfilename, varname); strcat(thisfilename, ".8xv"); outfile = ti8x_file_new(thisfilename); xfree(thisfilename); } upcase(varname); init_output_program(&prg, outfile, varname, archive); xfree(varname); linenum = 1; while ((line = read_line(inf))) { parse_asm_line(&prg, line, (formatflags & formatmask) | miscflags); xfree(line); linenum++; } if (inf != stdin) fclose(inf); flush_comments(&prg); finish_output_program(&prg); if (!outfilename) { if (write_output(outfile)) status = 4; ti8x_file_free(outfile); outfile = NULL; } } if (!status && outfilename) { if (write_output(outfile)) status = 4; } if (outfile) ti8x_file_free(outfile); return status; }