/*
* 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;
}