Logo Search packages:      
Sourcecode: z80asm version File versions

z80asm.c

/* Z80 assembler by shevek

   Copyright (C) 2002-2007 Bas Wijnen <wijnen@debian.org>
   Copyright (C) 2005 Jan Wilmans <jw@dds.nl>

   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 2
   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, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "z80asm.h"

/* global variables */
/* mnemonics, used as argument to indx() in assemble */
const char *mnemonics[] = {
  "call", "cpdr", "cpir", "djnz", "halt", "indr", "inir", "lddr", "ldir",
  "otdr", "otir", "outd", "outi", "push", "reti", "retn", "rlca", "rrca",
  "defb", "defw", "defs", "defm",
  "adc", "add", "and", "bit", "ccf", "cpd", "cpi", "cpl", "daa", "dec", "equ",
  "exx", "inc", "ind", "ini", "ldd", "ldi", "neg", "nop", "out", "pop",
  "res", "ret", "rla", "rlc", "rld", "rra", "rrc", "rrd", "rst", "sbc",
  "scf", "set", "sla", "sll", "sli", "sra", "srl", "sub", "xor", "org",
  "cp", "di", "ei", "ex", "im", "in", "jp", "jr", "ld", "or", "rl", "rr",
  "db", "dw", "ds", "dm",
  "include", "incbin", "if", "else", "endif", "end", "macro", "endm",
  "seek", NULL
};

/* linked lists */
struct reference *firstreference = NULL;
struct label *firstlabel = NULL, *lastlabel = NULL;
struct name *firstname = NULL;
struct includedir *firstincludedir = NULL;
struct macro *firstmacro = NULL;

/* files */
FILE *realoutputfile, *outfile, *reallistfile, *listfile, *labelfile;
const char *realoutputfilename;
const char *labelfilename;
struct infile *infile;
/* prefix for labels in labelfile */
const char *labelprefix = "";
/* bools to see if files are opened */
int havelist = 0, label = 0;
/* number of infiles in array */
int infilecount;

/* number of errors seen so far */
int errors = 0;

/* current line, address and file */
int addr = 0, file;
/* current number of characters in list file, for indentation */
int listdepth;

/* use readbyte instead of (hl) if writebyte is true */
int writebyte;
const char *readbyte;
/* variables which are filled by rd_* functions and used later,
 * like readbyte */
const char *readword, *indexjmp, *bitsetres;

/* 0, 0xdd or 0xfd depening on which index prefix should be given */
int indexed;

/* increased for every -v option on the command line */
int verbose = 0;

/* read commas after indx() if comma > 1. increase for every call */
int comma;

/* address at start of line (for references) */
int baseaddr;

/* set by readword and readbyte, used for new_reference */
char mem_delimiter;

/* line currently being parsed */
char *buffer = NULL;

/* if a macro is currently being defined */
int define_macro = 0;

/* file (and macro) stack */
int sp;
struct stack stack[MAX_INCLUDE];    /* maximum level of includes */

/* Produce output even with errors.  */
int use_force = 0;

/* print an error message, including current line and file */
void
printerr (int error, const char *fmt, ...)
{
  va_list l;
  va_start (l, fmt);
  if ((sp < 0) || (stack[sp].name == 0))
    {
      fprintf (stderr, "internal assembler error, sp == %i\n", sp);
      vfprintf (stderr, fmt, l);
      exit (2);
    }
  fprintf (stderr, "%s%s:%d: %s: ", stack[sp].dir ? stack[sp].dir->name : "",
         stack[sp].name, stack[sp].line, error ? "error" : "warning");
  vfprintf (stderr, fmt, l);
  va_end (l);
  if (error)
    errors++;
}

/* skip over spaces in string */
const char *
delspc (const char *ptr)
{
  while (*ptr && isspace (*ptr))
    ptr++;
  if (*ptr == ';')
    ptr = "";
  return ptr;
}

/* read away a comma, error if there is none */
static void
rd_comma (const char **p)
{
  *p = delspc (*p);
  if (**p != ',')
    {
      printerr (1, "`,' expected. Remainder of line: %s\n", *p);
      return;
    }
  *p = delspc ((*p) + 1);
}

/* look ahead for a comma, no error if not found */
static int
has_argument (const char **p)
{
  const char *q = delspc (*p);
  return (*q == ',');
}

/* During assembly, many literals are not parsed.  Instead, they are saved
 * until all labels are read.  After that, they are parsed.  This function
 * is used during assembly, to find the place where the command continues. */
static void
skipword (const char **pos, char delimiter)
{
  /* rd_expr will happily read the expression, and possibly return
   * an invalid result.  It will update pos, which is what we need.  */
  /* Pass valid to allow using undefined labels without errors.  */
  int valid;
  rd_expr (pos, delimiter, &valid, sp, 0);
}

/* callback function for argument parser, used to open output files. */
static FILE *
openfile (int *done,          /* flag to check that a file is opened only once. */
        const char *type,     /* name of filetype for error message */
        FILE * def,           /* default value, in case "-" is specified */
        const char *name,     /* filename to open */
        const char *flags)    /* open flags */
{
  FILE *retval;
  if (*done)
    {
      fprintf (stderr, "Error: more than one %s specified\n", type);
      exit (1);
    }
  *done = 1;
  if (def && (!name || (name[0] == '-' && name[1] == 0)))
    {
      return def;
    }
  if (!name || !name[0])
    {
      fprintf (stderr, "Error: no %s specified\n", type);
      exit (1);
    }
  if (!(retval = fopen (name, flags)))
    {
      fprintf (stderr, "Unable to open %s %s: %s\n",
             type, name, strerror (errno));
      exit (1);
    }
  return retval;
}

/* open an included file, searching the path */
static FILE *
open_include_file (const char *name, struct includedir **dir,
               const char *flags)
{
  FILE *result;
  struct includedir *i;
  /* always try the current directory first */
  result = fopen (name, flags);
  if (result)
    {
      if (dir)
      *dir = NULL;
      return result;
    }
  for (i = firstincludedir; i != NULL; i = i->next)
    {
      char *tmp = malloc (strlen (i->name) + strlen (name) + 1);
      if (!tmp)
      {
        printerr (1, "not enough memory trying to open include file\n");
        return NULL;
      }
      strcpy (tmp, i->name);
      strcat (tmp, name);
      result = fopen (tmp, flags);
      free (tmp);
      if (result)
      {
        if (dir)
          *dir = i;
        return result;
      }
    }
  return NULL;
}

/* queue a file to be opened for reading */
static void
open_infile (const char *name)
{
  infile = realloc (infile, sizeof (struct infile) * (infilecount + 1));
  if (!infile)
    {
      fprintf (stderr, "Error: insufficient memory\n");
      exit (1);
    }
  /* only asm is currently supported */
  infile[infilecount].type = FILETYPE_ASM;
  infile[infilecount].name = name;
  if (verbose >= 5)
    fprintf (stderr, "queued inputfile %s\n", infile[infilecount].name);
  infilecount++;
}

/* add a directory to the include search path */
static void
add_include (const char *name)
{
  struct includedir *i;
  i = malloc (sizeof (struct includedir) + strlen (name) + 1);
  if (!i)
    {
      fprintf (stderr, "Error: insufficient memory\n");
      exit (1);
    }
  strcpy (i->name, name);
  if (name[strlen (name) - 1] != '/')
    strcat (i->name, "/");
  i->next = firstincludedir;
  firstincludedir = i;
}

static void
try_use_real_file (FILE * real, FILE ** backup)
{
  fpos_t pos;
  if (fgetpos (real, &pos) == 0)
    {
      *backup = real;
      return;
    }
  if (!(*backup = tmpfile ()))
    {
      fprintf (stderr, "Error: Unable to open temporary file: %s\n",
             strerror (errno));
      exit (1);
    }
}

static void
flush_to_real_file (FILE * real, FILE * tmp)
{
  int l, size, len = 0;
  char buf[BUFLEN];
  if (tmp == real)
    {
      return;
    }
  rewind (tmp);
  while (1)
    {
      clearerr (tmp);
      errno = 0;
      len = fread (buf, 1, BUFLEN, tmp);
      if (len == 0 && feof (tmp))
      break;
      if (len <= 0)
      {
        fprintf (stderr, "error reading temp file: %s\n", strerror (errno));
        exit (1);
      }
      l = 0;
      while (l < len)
      {
        clearerr (real);
        size = fwrite (&buf[l], 1, len - l, real);
        if (size <= 0)
          {
            fprintf (stderr, "error writing final file: %s\n",
                   strerror (errno));
            exit (1);
          }
        l += size;
      }
    }
}

/* parse commandline arguments */
static void
parse_commandline (int argc, char **argv)
{
  const struct option opts[] = {
    {"help", no_argument, NULL, 'h'},
    {"version", no_argument, NULL, 'V'},
    {"verbose", no_argument, NULL, 'v'},
    {"list", optional_argument, NULL, 'l'},
    {"label", optional_argument, NULL, 'L'},
    {"input", required_argument, NULL, 'i'},
    {"output", required_argument, NULL, 'o'},
    {"label-prefix", required_argument, NULL, 'p'},
    {"includepath", required_argument, NULL, 'I'},
    {"force", no_argument, NULL, 'f'},
    {NULL, 0, NULL, 0}
  };
  const char *short_opts = "hVvl::L::i:o:p:I:f";
  int done = 0, i, out = 0;
  infile = NULL;
  while (!done)
    {
      switch (getopt_long (argc, argv, short_opts, opts, NULL))
      {
      case 'h':
        /* split in two, to avoid too long string constant */
        printf ("Usage: %s [options] [input files]\n"
              "\n"
              "Possible options are:\n"
              "-h\t--help\t\tDisplay this help text and exit.\n"
              "-V\t--version\tDisplay version information and exit.\n"
              "-v\t--verbose\tBe verbose.  "
              "Specify again to be more verbose.\n"
              "-l\t--list\t\tWrite a list file.\n"
              "-L\t--label\t\tWrite a label file.\n", argv[0]);
        printf ("-p\t--label-prefix\tprefix all labels with this prefix.\n"
              "-i\t--input\t\tSpecify an input file (-i may be omitted).\n"
              "-o\t--output\tSpecify the output file.\n"
              "-I\t--includepath\tAdd a directory to the include path.\n"
              "Please send bug reports and feature requests to "
              "<shevek@fmf.nl>\n");
        exit (0);
      case 'V':
        printf ("Z80 assembler version " VERSION "\n"
              "Copyright (C) 2002-2007 Bas Wijnen "
              "<shevek@fmf.nl>.\n"
              "Copyright (C) 2005 Jan Wilmans "
              "<jw@dds.nl>.\n"
              "This program comes with ABSOLUTELY NO WARRANTY.\n"
              "You may distribute copies of the program under the terms\n"
              "of the GNU General Public License as published by\n"
              "the Free Software Foundation; either version 2 of the\n"
              "License, or (at your option) any later version.\n\n"
              "The complete text of the GPL can be found in\n"
              "/usr/share/common-licenses/GPL.\n");
        exit (0);
      case 'v':
        verbose++;
        if (verbose >= 5)
          fprintf (stderr, "Verbosity increased to level %d\n", verbose);
        break;
      case 'o':
        realoutputfile
          = openfile (&out, "output file", stdout, optarg, "wb");
        realoutputfilename = optarg;
        if (verbose >= 5)
          fprintf (stderr, "Opened outputfile\n");
        break;
      case 'i':
        open_infile (optarg);
        break;
      case 'l':
        reallistfile
          = openfile (&havelist, "list file", stderr, optarg, "w");
        if (verbose >= 5)
          fprintf (stderr, "Opened list file\n");
        break;
      case 'L':
        labelfile = openfile (&label, "label file", stderr, optarg, "w");
        labelfilename = optarg;
        if (verbose >= 5)
          fprintf (stderr, "Opened label file\n");
        break;
      case 'p':
        labelprefix = optarg;
        break;
      case 'I':
        add_include (optarg);
        break;
      case 'f':
        use_force = 1;
        break;
      case -1:
        done = 1;
        break;
      default:
        /* errors are handled by getopt_long */
        break;
      }
    }
  for (i = optind; i < argc; ++i)
    open_infile (argv[i]);
  if (!infilecount)
    open_infile ("-");
  if (!out)
    realoutputfile = openfile (&out, "output file", stdout, "a.bin", "wb");
  try_use_real_file (realoutputfile, &outfile);
  if (havelist)
    try_use_real_file (reallistfile, &listfile);
}

/* find any of the list[] entries as the start of ptr and return index */
static int
indx (const char **ptr, const char **list, int error, const char **expr)
{
  int i, l;
  *ptr = delspc (*ptr);
  if (!**ptr)
    {
      if (error)
      {
        printerr (1, "unexpected end of line\n");
        return 0;
      }
      else
      return 0;
    }
  if (comma > 1)
    rd_comma (ptr);
  for (i = 0; list[i]; i++)
    {
      const char *input = *ptr;
      const char *check = list[i];
      int had_expr = 0;
      if (!list[i][0])
      continue;
      l = strlen (list[i]);
      while (*check)
      {
        if (*check == ' ')
          {
            input = delspc (input);
          }
        else if (*check == '*')
          {
            *expr = input;
            mem_delimiter = check[1];
            rd_expr (&input, mem_delimiter, NULL, sp, 0);
            had_expr = 1;
          }
        else if (*check == '+')
          {
            if (*input == '+' || *input == '-')
            {
              *expr = input;
              mem_delimiter = check[1];
              rd_expr (&input, mem_delimiter, NULL, sp, 0);
            }
          }
        else if (*check == *input || (*check >= 'a' && *check <= 'z'
                              && *check - 'a' + 'A' == *input))
          ++input;
        else
          break;

        ++check;
      }
      if (*check || (isalnum (check[-1]) && isalnum (input[0])))
      continue;
      if (had_expr)
      {
        input = delspc (input);
        if (*input && *input != ',')
          continue;
      }
      *ptr = input;
      if (verbose >= 4)
      fprintf (stderr, "%5d (0x%04x): Piece of code found:%s\n",
             stack[sp].line, addr, list[i]);
      if (verbose >= 6)
      fprintf (stderr, "%5d (0x%04x): Remainder of line=%s.\n",
             stack[sp].line, addr, *ptr);
      comma++;
      return i + 1;
    }
  if (error)
    {
      printerr (1, "parse error. Remainder of line=%s\n", *ptr);
      if (verbose >= 3)
      {
        fprintf (stderr, "When looking for any of:\n");
        for (i = 0; list[i]; i++)
          fprintf (stderr, "%s\t", list[i]);
        fprintf (stderr, "\n");
      }
    }
  return 0;
}

/* read a mnemonic */
static int
readcommand (const char **p)
{
  return indx (p, mnemonics, 0, NULL);
}

/* try to read a label and optionally store it in the list */
static void
readlabel (const char **p, int store)
{
  const char *c, *d, *pos, *dummy;
  int i, j;
  struct label *buf, *previous, **thefirstlabel;
  for (d = *p; *d && *d != ';'; ++d)
    {
    }
  for (c = *p; !strchr (" \r\n\t", *c) && c < d; ++c)
    {
    }
  pos = strchr (*p, ':');
  if (!pos || pos >= c)
    return;
  if (pos == *p)
    {
      printerr (1, "`:' found without a label");
      return;
    }
  if (!store)
    {
      *p = pos + 1;
      return;
    }
  c = pos + 1;
  dummy = *p;
  j = rd_label (&dummy, &i, &previous, sp, 0);
  if (i || j)
    {
      printerr (1, "duplicate definition of label %s\n", *p);
      *p = c;
      return;
    }
  if (NULL == (buf = malloc (sizeof (struct label) + c - *p)))
    {
      printerr (1, "not enough memory to store label %s\n", *p);
      *p = c;
      return;
    }
  strncpy (buf->name, *p, c - *p - 1);
  buf->name[c - *p - 1] = 0;
  if (verbose >= 3)
    fprintf (stderr, "%5d (0x%04x): Label found: %s\n", stack[sp].line,
           addr, buf->name);
  *p = c;
  buf->value = addr;
  lastlabel = buf;
  if (buf->name[0] == '.')
    thefirstlabel = &stack[sp].labels;
  else
    thefirstlabel = &firstlabel;
  if (previous)
    buf->next = previous->next;
  else
    buf->next = *thefirstlabel;
  buf->prev = previous;
  buf->valid = 1;
  buf->busy = 0;
  buf->ref = NULL;
  if (buf->prev)
    buf->prev->next = buf;
  else
    *thefirstlabel = buf;
  if (buf->next)
    buf->next->prev = buf;
}

static void new_reference (const char *data, int type, char delimiter,
                     int ds_count);

/* write one byte to the outfile, and add it to the list file as well */
static void
write_one_byte (int b, int list)
{
  if (verbose >= 4)
    fprintf (stderr,
           "%5d (0x%04x): write_one_byte called with argument 0x%02x\n",
           stack[sp].line, addr, b);
  b &= 0xff;
  putc (b, outfile);
  if (list && havelist)
    {
      fprintf (listfile, " %02x", b);
      listdepth += 3;
    }
  addr++;
}

/* write byte to outfile and possibly some index things as well */
static void
wrtb (int b)
{
  if (verbose >= 4)
    fprintf (stderr, "%5d (0x%04x): wrtb called with argument 0x%02x\n",
           stack[sp].line, addr, b);
  if (indexed)
    {
      if (verbose >= 5)
      fprintf (stderr, "%5d (0x%04x): writing indexed byte 0x%02x\n",
             stack[sp].line, addr, indexed);
      write_one_byte (indexed, 1);
      indexed = 0;
    }
  if (writebyte)
    {
      if (verbose >= 5)
      fprintf (stderr, "%5d (0x%04x): using a xor on byte because there is "
             "a writebyte.\n", stack[sp].line, addr);
      b ^= 0x40;
    }
  if (verbose >= 5)
    fprintf (stderr, "%5d (0x%04x): writing byte 0x%02x\n", stack[sp].line,
           addr, b);
  if (bitsetres && b != 0xCB)
    {
      new_reference (bitsetres, TYPE_BSR, ',', b);
      bitsetres = NULL;
    }
  else
    {
      write_one_byte (b, 1);
    }
  if (indexjmp)
    {
      if (verbose >= 5)
      fprintf (stderr, "%5d (0x%04x): Making reference for index/jump %s\n",
             stack[sp].line, addr, indexjmp);
      new_reference (indexjmp, TYPE_ABSB, ')', 1);
      indexjmp = NULL;
    }
  if (writebyte)
    {
      if (verbose >= 5)
      fprintf (stderr, "%5d (0x%04x): writing argument byte for padding\n",
             stack[sp].line, addr);
      writebyte = 0;
      new_reference (readbyte, TYPE_ABSB, mem_delimiter, 1);
    }
}

int
compute_ref (struct reference *ref, int allow_invalid)
{
  const char *ptr;
  int valid = 0;
  int backup_addr = addr;
  int backup_baseaddr = baseaddr;
  int backup_comma = comma;
  int backup_file = file;
  int backup_sp = sp;
  sp = ref->level;
  addr = ref->addr;
  baseaddr = ref->baseaddr;
  comma = ref->comma;
  file = ref->infile;
  if (verbose >= 3)
    fprintf (stderr, "%5d (0x%04x): Making reference to %s (done=%d, "
           "computed=%d)\n",
           stack[sp].line, addr, ref->input, ref->done,
           ref->computed_value);
  ptr = ref->input;
  if (!ref->done)
    {
      ref->computed_value = rd_expr (&ptr, ref->delimiter,
                             allow_invalid ? &valid : NULL,
                             ref->level, 1);
      if (valid)
      ref->done = 1;
    }
  if (verbose >= 4)
    fprintf (stderr, "%5d (0x%04x): Reference is %d (0x%04x).\n",
           stack[sp].line, addr, ref->computed_value, ref->computed_value);
  sp = backup_sp;
  addr = backup_addr;
  baseaddr = backup_baseaddr;
  comma = backup_comma;
  file = backup_file;
  return ref->computed_value;
}

static void wrt_ref (int val, int type, int count);

/* Create a new reference, to be resolved after assembling (so all labels are
 * known.) */
static void
new_reference (const char *p, int type, char delimiter, int ds_count)
{
  struct reference *tmp = NULL;
  long opos, lpos;
  int valid, value;
  const char *c;
  c = p;
  value = rd_expr (&c, delimiter, &valid, sp, 1);
  if (valid)
    {
      if (verbose >= 5)
      {
        fprintf (stderr, "%5d (0x%04x): Using calculated value %d (%x) "
               "immediately.\n", stack[sp].line, addr, value, value);
      }
    }
  else
    {
      /* the expression is not valid (yet), we need to make a real reference.  */
      if (NULL == (tmp = malloc (sizeof (struct reference) + strlen (p))))
      {
        printerr (1, "unable to allocate memory for reference %s\n", p);
        return;
      }
      opos = ftell (outfile);
      lpos = havelist ? ftell (listfile) : 0;
      if (verbose >= 3)
      fprintf (stderr, "%5d (0x%04x): reference set to %s (delimiter=%c, "
             "sp=%d)\n", stack[sp].line, addr, p, delimiter, sp);
      strcpy (tmp->input, p);
      tmp->addr = addr;
      tmp->baseaddr = baseaddr;
      tmp->count = ds_count;
      tmp->infile = file;
      tmp->comma = comma;
      tmp->oseekpos = opos;
      tmp->lseekpos = lpos;
      tmp->delimiter = delimiter;
      tmp->type = type;
      tmp->next = firstreference;
      tmp->done = 0;
      tmp->level = sp;
      if (type != TYPE_LABEL)
      {
        if (firstreference)
          firstreference->prev = tmp;
        tmp->prev = NULL;
        firstreference = tmp;
      }
      /* Dummy value which should not give warnings */
      value = (type == TYPE_RELB) ? ds_count : 0;
    }
  if (type != TYPE_LABEL)
    {
      wrt_ref (value, type, ds_count);
    }
  else
    {
      lastlabel->ref = tmp;
      lastlabel->valid = valid;
      lastlabel->value = value;
    }
}

/* write the last read word to file */
static void
write_word (void)
{
  new_reference (readword, TYPE_ABSW, mem_delimiter, 1);
}

/* write the last read byte to file (relative) */
static void
write_rel (void)
{
  new_reference (readbyte, TYPE_RELB, mem_delimiter, (addr + 1) & 0xffff);
  writebyte = 0;
}

/* read a word from input and store it in readword. return 1 on success */
static int
rd_word (const char **p, char delimiter)
{
  *p = delspc (*p);
  if (**p == 0)
    return 0;
  readword = *p;
  mem_delimiter = delimiter;
  skipword (p, delimiter);
  return 1;
}

/* read a byte from input and store it in readbyte. return 1 on success */
static int
rd_byte (const char **p, char delimiter)
{
  *p = delspc (*p);
  if (**p == 0)
    return 0;
  readbyte = *p;
  writebyte = 1;
  mem_delimiter = delimiter;
  skipword (p, delimiter);
  return 1;
}

/* read an address from infile and put it in reference table.
 * so that it will be written here afterwards */
static void
rd_wrt_addr (const char **p, char delimiter)
{
  if (!rd_word (p, delimiter))
    return;
  write_word ();
}

/* like rd_wrt_addr, but for a relative jump */
static void
rd_wrt_jr (const char **p, char delimiter)
{
  if (!rd_byte (p, delimiter))
    return;
  write_rel ();
}

/* read (SP), DE, or AF */
static int
rd_ex1 (const char **p)
{
#define DE 2
#define AF 3
  const char *list[] = { "( sp )", "de", "af", NULL };
  return indx (p, list, 1, NULL);
}

/* read first argument of IN */
static int
rd_in (const char **p)
{
#define A 8
  const char *list[] = { "b", "c", "d", "e", "h", "l", "f", "a", NULL };
  return indx (p, list, 1, NULL);
}

/* read second argument of out (c),x */
static int
rd_out (const char **p)
{
  const char *list[] = { "b", "c", "d", "e", "h", "l", "0", "a", NULL };
  return indx (p, list, 1, NULL);
}

/* read (c) or (nn) */
static int
rd_nnc (const char **p)
{
#define C 1
  int i;
  const char *list[] = { "( c )", "(*)", "a , (*)", NULL };
  i = indx (p, list, 1, &readbyte);
  if (i < 2)
    return i;
  return 2;
}

/* read (C) */
static int
rd_c (const char **p)
{
  const char *list[] = { "( c )", "( bc )", NULL };
  return indx (p, list, 1, NULL);
}

/* read a or hl */
static int
rd_a_hl (const char **p)
{
#define HL 2
  const char *list[] = { "a", "hl", NULL };
  return indx (p, list, 1, NULL);
}

/* read first argument of ld */
static int
rd_ld (const char **p)
{
#define ldBC      1
#define ldDE      2
#define ldHL      3
#define ldSP      4
#define ldIX      5
#define ldIY      6
#define ldB 7
#define ldC 8
#define ldD 9
#define ldE 10
#define ldH 11
#define ldL 12
#define ld_HL     13
#define ldA 14
#define ldI 15
#define ldR 16
#define ld_BC     17
#define ld_DE     18
#define ld_IX     19
#define ld_IY     20
#define ld_NN     21
  int i;
  const char *list[] = {
    "ixh", "ixl", "iyh", "iyl", "bc", "de", "hl", "sp", "ix",
    "iy", "b", "c", "d", "e", "h", "l", "( hl )", "a", "i",
    "r", "( bc )", "( de )", "( ix +)", "(iy +)", "(*)", NULL
  };
  const char *nn;
  i = indx (p, list, 1, &nn);
  if (!i)
    return 0;
  if (i <= 2)
    {
      indexed = 0xdd;
      return ldH + (i == 2);
    }
  if (i <= 4)
    {
      indexed = 0xfd;
      return ldH + (i == 4);
    }
  i -= 4;
  if (i == ldIX || i == ldIY)
    {
      indexed = i == ldIX ? 0xDD : 0xFD;
      return ldHL;
    }
  if (i == ld_IX || i == ld_IY)
    {
      indexjmp = nn;
      indexed = i == ld_IX ? 0xDD : 0xFD;
      return ld_HL;
    }
  if (i == ld_NN)
    readword = nn;
  return i;
}

/* read first argument of JP */
static int
rd_jp (const char **p)
{
  int i;
  const char *list[] = {
    "nz", "z", "nc", "c", "po", "pe", "p", "m", "( ix )", "( iy )",
    "(hl)", NULL
  };
  i = indx (p, list, 0, NULL);
  if (i < 9)
    return i;
  if (i == 11)
    return -1;
  indexed = 0xDD + 0x20 * (i - 9);
  return -1;
}

/* read first argument of JR */
static int
rd_jr (const char **p)
{
  const char *list[] = { "nz", "z", "nc", "c", NULL };
  return indx (p, list, 0, NULL);
}

/* read A */
static int
rd_a (const char **p)
{
  const char *list[] = { "a", NULL };
  return indx (p, list, 1, NULL);
}

/* read bc,de,hl,af */
static int
rd_stack (const char **p)
{
  int i;
  const char *list[] = { "bc", "de", "hl", "af", "ix", "iy", NULL };
  i = indx (p, list, 1, NULL);
  if (i < 5)
    return i;
  indexed = 0xDD + 0x20 * (i - 5);
  return 3;
}

#if 0
/* read a or hl(2) or i[xy](2) with variables set */
static int
rd_a_hlx (const char **p)
{
  int i;
  const char *list[] = { "a", "hl", "ix", "iy", NULL };
  i = indx (p, list, 1, NULL);
  if (i < 2)
    return i;
  if (i == 2)
    return 2;
  indexed = 0xDD + 0x20 * (i - 3);
  return 2;
}
#endif

/* read b,c,d,e,h,l,(hl),a,(ix+nn),(iy+nn),nn 
 * but now with extra hl or i[xy](15) for add-instruction
 * and set variables accordingly */
static int
rd_r_add (const char **p)
{
#define addHL     15
  int i;
  const char *list[] = {
    "ixl", "ixh", "iyl", "iyh", "b", "c", "d", "e", "h", "l",
    "( hl )", "a", "( ix +)", "( iy +)", "hl", "ix", "iy", "*", NULL
  };
  const char *nn;
  i = indx (p, list, 0, &nn);
  if (i == 18)    /* expression */
    {
      readbyte = nn;
      writebyte = 1;
      return 7;
    }
  if (i > 14)     /* hl, ix, iy */
    {
      if (i > 15)
      indexed = 0xDD + 0x20 * (i - 16);
      return addHL;
    }
  if (i <= 4)     /* i[xy][hl]  */
    {
      indexed = 0xdd + 0x20 * (i > 2);
      return 6 - (i & 1);
    }
  i -= 4;
  if (i < 9)
    return i;
  indexed = 0xDD + 0x20 * (i - 9);  /* (i[xy] +) */
  indexjmp = nn;
  return 7;
}

/* read bc,de,hl, or sp */
static int
rd_rr_ (const char **p)
{
  const char *list[] = { "bc", "de", "hl", "sp", NULL };
  return indx (p, list, 1, NULL);
}

/* read bc,de,hl|ix|iy,sp. hl|ix|iy only if it is already indexed the same. */
static int
rd_rrxx (const char **p)
{
  const char *listx[] = { "bc", "de", "ix", "sp", NULL };
  const char *listy[] = { "bc", "de", "iy", "sp", NULL };
  const char *list[] = { "bc", "de", "hl", "sp", NULL };
  switch (indexed)
    {
    case 0xDD:
      return indx (p, listx, 1, NULL);
    case 0xFD:
      return indx (p, listy, 1, NULL);
    default:
      return indx (p, list, 1, NULL);
    }
}

/* read b,c,d,e,h,l,(hl),a,(ix+nn),(iy+nn),nn
 * and set variables accordingly */
static int
rd_r (const char **p)
{
  int i;
  const char *list[] = {
    "ixl", "ixh", "iyl", "iyh", "b", "c", "d", "e", "h", "l", "( hl )",
    "a", "( ix +)", "( iy +)", "*", NULL
  };
  const char *nn;
  i = indx (p, list, 0, &nn);
  if (i == 15)    /* expression */
    {
      readbyte = nn;
      writebyte = 1;
      return 7;
    }
  if (i <= 4)
    {
      indexed = 0xdd + 0x20 * (i > 2);
      return 6 - (i & 1);
    }
  i -= 4;
  if (i < 9)
    return i;
  indexed = 0xDD + 0x20 * (i - 9);
  indexjmp = nn;
  return 7;
}

/* like rd_r(), but without nn */
static int
rd_r_ (const char **p)
{
  int i;
  const char *list[] = {
    "b", "c", "d", "e", "h", "l", "( hl )", "a", "( ix +)", "( iy +)", NULL
  };
  i = indx (p, list, 1, &indexjmp);
  if (i < 9)
    return i;
  indexed = 0xDD + 0x20 * (i - 9);
  return 7;
}

/* read a number from 0 to 7, for bit, set or res */
static int
rd_0_7 (const char **p)
{
  *p = delspc (*p);
  if (**p == 0)
    return 0;
  bitsetres = *p;
  skipword (p, ',');
  return 1;
}

/* read long condition. do not error if not found. */
static int
rd_cc (const char **p)
{
  const char *list[] = { "nz", "z", "nc", "c", "po", "pe", "p", "m", NULL };
  return indx (p, list, 0, NULL);
}

/* read long or short register,  */
static int
rd_r_rr (const char **p)
{
  int i;
  const char *list[] = {
    "iy", "ix", "sp", "hl", "de", "bc", "", "b", "c", "d", "e", "h",
    "l", "( hl )", "a", "( ix +)", "( iy +)", NULL
  };
  i = indx (p, list, 1, &indexjmp);
  if (!i)
    return 0;
  if (i < 16 && i > 2)
    return 7 - i;
  if (i > 15)
    {
      indexed = 0xDD + (i - 16) * 0x20;
      return -7;
    }
  indexed = 0xDD + (2 - i) * 0x20;
  return 3;
}

/* read hl */
static int
rd_hl (const char **p)
{
  const char *list[] = { "hl", NULL };
  return indx (p, list, 1, NULL);
}

/* read hl, ix, or iy */
static int
rd_hlx (const char **p)
{
  int i;
  const char *list[] = { "hl", "ix", "iy", NULL };
  i = indx (p, list, 1, NULL);
  if (i < 2)
    return i;
  indexed = 0xDD + 0x20 * (i - 2);
  return 1;
}

/* read af' */
static int
rd_af_ (const char **p)
{
  const char *list[] = { "af'", NULL };
  return indx (p, list, 1, NULL);
}

/* read 0(1), 1(3), or 2(4) */
static int
rd_0_2 (const char **p)
{
  const char *list[] = { "0", "", "1", "2", NULL };
  return indx (p, list, 1, NULL);
}

/* read argument of ld (hl), */
static int
rd_ld_hl (const char **p)
{
  int i;
  const char *list[] = { "b", "c", "d", "e", "h", "l", "", "a", "*", NULL };
  i = indx (p, list, 0, &readbyte);
  if (i < 9)
    return i;
  writebyte = 1;
  return 7;
}

/* read argument of ld (nnnn), */
static int
rd_ld_nn (const char **p)
{
#define ld_nnHL 5
#define ld_nnA 6
  int i;
  const char *list[] = { "bc", "de", "", "sp", "hl", "a", "ix", "iy", NULL };
  i = indx (p, list, 1, NULL);
  if (i < 7)
    return i;
  indexed = 0xdd + 0x20 * (i == 8);
  return ld_nnHL;
}

/* read argument of ld a, */
static int
rd_lda (const char **p)
{
#define A_I 9
#define A_R 10
#define A_NN 11
  int i;
  const char *list[] = {
    "( sp )", "( iy +)", "( de )", "( bc )", "( ix +)", "b", "c", "d", "e", "h",
    "l", "( hl )", "a", "i", "r", "(*)", "*", NULL
  };
  const char *nn;
  i = indx (p, list, 0, &nn);
  if (i == 2 || i == 5)
    {
      indexed = (i == 2) ? 0xFD : 0xDD;
      indexjmp = nn;
      return 7;
    }
  if (i == 17)
    {
      readbyte = nn;
      writebyte = 1;
      return 7;
    }
  if (i == 16)
    {
      readword = nn;
    }
  return i - 5;
}

/* read argument of ld b|c|d|e|h|l */
static int
rd_ldbcdehla (const char **p)
{
  int i;
  const char *list[] = {
    "b", "c", "d", "e", "h", "l", "( hl )", "a", "( ix +)", "( iy +)", "ixh",
    "ixl", "iyh", "iyl", "*", NULL
  };
  const char *nn;
  i = indx (p, list, 0, &nn);
  if (i == 15)
    {
      readbyte = nn;
      writebyte = 1;
      return 7;
    }
  if (i > 10)
    {
      int x;
      x = 0xdd + 0x20 * (i > 12);
      if (indexed && indexed != x)
      {
        printerr (1, "illegal use of index registers\n");
        return 0;
      }
      indexed = x;
      return 6 - (i & 1);
    }
  if (i > 8)
    {
      if (indexed)
      {
        printerr (1, "illegal use of index registers\n");
        return 0;
      }
      indexed = 0xDD + 0x20 * (i == 10);
      indexjmp = nn;
      return 7;
    }
  return i;
}

/* read nnnn, or (nnnn) */
static int
rd_nn_nn (const char **p)
{
#define _NN 1
  const char *list[] = { "(*)", "*", NULL };
  return 2 - indx (p, list, 0, &readword);
}

/* read {HL|IX|IY},nnnn, or (nnnn) */
static int
rd_sp (const char **p)
{
#define SPNN 0
#define SPHL 1
  int i;
  const char *list[] = { "hl", "ix", "iy", "(*)", "*", NULL };
  const char *nn;
  i = indx (p, list, 0, &nn);
  if (i > 3)
    {
      readword = nn;
      return i == 4 ? 2 : 0;
    }
  if (i != 1)
    indexed = 0xDD + 0x20 * (i - 2);
  return 1;
}

/* write a reference after it has been computed */
static void
wrt_ref (int val, int type, int count)
{
  switch (type)
    {
    case TYPE_RST:
      if ((val & 0x38) != val)
      {
        printerr (1, "incorrect RST value %d (0x%02x)\n", val, val);
        return;
      }
      write_one_byte (val + 0xC7, 1);
      return;
    case TYPE_ABSW:
      if (val < -0x8000 || val >= 0x10000)
      printerr (0, "word value %d (0x%x) truncated\n", val, val);
      write_one_byte (val & 0xff, 1);
      write_one_byte ((val >> 8) & 0xff, 1);
      return;
    case TYPE_ABSB:
      if (val < -0x80 || val >= 0x100)
      printerr (0, "byte value %d (0x%x) truncated\n", val, val);
      write_one_byte (val & 0xff, 1);
      return;
    case TYPE_DS:
      if (val < -0x80 || val >= 0x100)
      printerr (0, "byte value %d (0x%x) truncated\n", val, val);
      if (havelist)
      {
        fprintf (listfile, " 0x%02x...", val & 0xff);
        listdepth += 6;
      }
      while (count--)
      {
        write_one_byte (val & 0xff, 0);
      }
      return;
    case TYPE_BSR:
      if (val & ~7)
      {
        printerr (1, "incorrect BIT/SET/RES value %d\n", val);
        return;
      }
      write_one_byte (0x08 * val + count, 1);
      return;
    case TYPE_RELB:
      val -= count;
      if (val < -128 || val > 127)
      {
        printerr (1, "relative jump out of range (%d)\n", val);
      }
      write_one_byte (val & 0xff, 1);
      return;
    case TYPE_LABEL:
      printerr (1, "bug in the assembler: trying to write label reference.  "
            "Please report.\n");
      return;
    }
}

static char *
get_include_name (const char **ptr)
{
  int pos = 0;
  char quote;
  char *name;
  *ptr = delspc (*ptr);
  name = malloc (strlen (*ptr));
  if (!name)
    {
      printerr (1, "unable to allocate memory for filename %.*s\n",
            strlen (*ptr) - 1, *ptr);
      return NULL;
    }
  if (!**ptr)
    {
      printerr (1, "include without filename\n");
      free (name);
      return NULL;
    }
  quote = *(*ptr)++;
  while (**ptr != quote)
    {
      if (!**ptr)
      {
        printerr (1, "filename without closing quote (%c)\n", quote);
        free (name);
        return NULL;
      }
      name[pos++] = *(*ptr)++;
    }
  name[pos] = 0;
  ++*ptr;
  return name;
}

static int
read_line (void)
{
  unsigned pos, newpos, size;
  struct macro_arg *arg;
  if (stack[sp].file)
    {
      FILE *f = stack[sp].file;
      static char short_buffer[BUFLEN + 1];
      if (buffer && buffer != short_buffer)
      free (buffer);
      buffer = NULL;
      if (!fgets (short_buffer, BUFLEN + 1, f))
      return 0;
      if (strlen (short_buffer) < BUFLEN)
      {
        buffer = short_buffer;
        return 1;
      }
      size = 2 * BUFLEN;
      buffer = malloc (size + 1);
      if (!buffer)
      {
        printerr (1, "out of memory reading line\n");
        return 0;
      }
      memcpy (buffer, short_buffer, BUFLEN + 1);
      while (1)
      {
        char *b;
        if (!fgets (&buffer[size - BUFLEN], BUFLEN + 1, f)
            || (buffer[strlen (buffer) - 1] == '\n'))
          {
            return 1;
          }
        size += BUFLEN;
        b = realloc (buffer, size + 1);
        if (!b)
          {
            printerr (1, "out of memory reading line\n");
            return 0;
          }
        buffer = b;
      }
    }
  /* macro line */
  if (!stack[sp].macro_line)
    {
      unsigned i;
      for (i = 0; i < stack[sp].macro->numargs; ++i)
      free (stack[sp].macro_args[i]);
      free (stack[sp].macro_args);
      return 0;
    }
  size = strlen (stack[sp].macro_line->line) + 1;
  for (arg = stack[sp].macro_line->args; arg; arg = arg->next)
    size += strlen (stack[sp].macro_args[arg->which]);
  buffer = malloc (size);
  if (!buffer)
    {
      printerr (1, "out of memory\n");
      return 0;
    }
  pos = 0;
  newpos = 0;
  for (arg = stack[sp].macro_line->args; arg; arg = arg->next)
    {
      memcpy (&buffer[newpos], &stack[sp].macro_line->line[pos],
            arg->pos - pos);
      newpos += arg->pos - pos;
      strcpy (&buffer[newpos], stack[sp].macro_args[arg->which]);
      newpos += strlen (stack[sp].macro_args[arg->which]);
      pos = arg->pos + 1;
    }
  strcpy (&buffer[newpos], &stack[sp].macro_line->line[pos]);
  stack[sp].macro_line = stack[sp].macro_line->next;
  return 1;
}

static unsigned
get_macro_args (const char **ptr, char ***ret_args, int allow_empty)
{
  unsigned numargs = 0;
  *ret_args = NULL;
  while (1)
    {
      char **args;
      const char *c;
      *ptr = delspc (*ptr);
      if (!**ptr)
      break;
      c = *ptr;
      for (; **ptr && !strchr (" \r\n\t,;", **ptr); ++*ptr)
      {
      }
      if (*ptr == c && !allow_empty)
      {
        printerr (1, "empty macro argument\n");
        break;
      }
      ++numargs;
      args = realloc (*ret_args, sizeof (char *) * numargs);
      if (!args)
      {
        printerr (1, "out of memory\n");
        --numargs;
        break;
      }
      *ret_args = args;
      args[numargs - 1] = malloc (*ptr - c + 1);
      if (!args[numargs - 1])
      {
        printerr (1, "out of memory\n");
        --numargs;
        break;
      }
      memcpy (args[numargs - 1], c, *ptr - c);
      args[numargs - 1][*ptr - c] = 0;
    }
  return numargs;
}

/* do the actual work */
static void
assemble (void)
{
  int ifcount = 0, noifcount = 0;
  const char *ptr;
  struct label *l;
  char *bufptr;
  int r, s;             /* registers */
  /* continue assembling until the last input file is done */
  for (file = 0; file < infilecount; ++file)
    {
      int file_ended = 0;
      sp = 0;                 /* clear stack */
      stack[sp].line = 0;
      stack[sp].shouldclose = 0;
      stack[sp].name = infile[file].name;
      stack[sp].dir = NULL;
      if (infile[file].name[0] == '-' && infile[file].name[1] == 0)
      {
        stack[sp].file = stdin;
      }
      else
      {
        stack[sp].file = fopen (infile[file].name, "r");
        if (!stack[sp].file)
          {
            printerr (1, "unable to open %s. skipping\n", infile[file].name);
            continue;
          }
        stack[sp].shouldclose = 1;
      }
      if (havelist)
      fprintf (listfile, "# File %s\n", stack[sp].name);
      if (buffer)
      buffer[0] = 0;
      /* loop until this source file is done */
      while (1)
      {
        int cmd, cont = 1;
        if (havelist)
          {
            if (buffer && buffer[0] != 0)
            {
              int i, tabs;
              ptr = delspc (ptr);
              if (*ptr != 0)
                {
                  printerr (1, "junk at end of line: %s\n", ptr);
                }
              if (listdepth < 8)
                tabs = 3;
              else if (listdepth < 16)
                tabs = 2;
              else
                tabs = 1;
              for (i = 0; i < tabs; ++i)
                fputc ('\t', listfile);
              fprintf (listfile, "%s\n", buffer);
            }
            listdepth = 4;
          }
        /* throw away the rest of the file after end */
        if (file_ended)
          {
            while (read_line ())
            {
              if (havelist)
                fprintf (listfile, "\t\t\t%s\n", buffer);
            }
            file_ended = 0;
          }
        while (!read_line ())
          {
            struct reference *ref, *nextref;
            struct label *next;
            if (verbose >= 6)
            fprintf (stderr, "finished reading file %s\n",
                   stack[sp].name);
            if (havelist)
            {
              if (stack[sp].file)
                fprintf (listfile, "# End of file %s\n", stack[sp].name);
              else
                fprintf (listfile, "# End of macro %s\n", stack[sp].name);
            }
            if (stack[sp].shouldclose)
            fclose (stack[sp].file);
            /* the top of stack is about to be popped off, throwing all
             * local labels out of scope.  All references at this level
             * which aren't computable are errors.  */
            for (ref = firstreference; ref; ref = nextref)
            {
              nextref = ref->next;
              compute_ref (ref, 1);
              if (ref->done)
                continue;
              if (ref->level == sp)
                if (!ref->level--)
                  {
                  printerr (1, "unable to resolve reference: %s\n",
                          ref->input);
                  if (ref->prev)
                    ref->prev->next = ref->next;
                  else
                    firstreference = ref->next;
                  if (ref->next)
                    ref->next->prev = ref->prev;
                  free (ref);
                  }
            }
            /* Ok, now junk all local labels of the top stack level */
            for (l = stack[sp].labels; l; l = next)
            {
              next = l->next;
              if (l->ref)
                free (l->ref);
              free (l);
            }
            stack[sp].labels = NULL;
            if (!sp--)
            {
              cont = 0;
              break;
            }
          }
        if (!cont)
          break;        /* break to next source file */
        if (havelist)
          fprintf (listfile, "%04x", addr);
        for (bufptr = buffer; (bufptr = strchr (bufptr, '\n'));)
          *bufptr = ' ';
        for (bufptr = buffer; (bufptr = strchr (bufptr, '\r'));)
          *bufptr = ' ';
        ptr = buffer;
        lastlabel = NULL;
        baseaddr = addr;
        ++stack[sp].line;
        ptr = delspc (ptr);
        if (!*ptr)
          continue;
        if (!noifcount && !define_macro)
          readlabel (&ptr, 1);
        else
          readlabel (&ptr, 0);
        ptr = delspc (ptr);
        if (!*ptr)
          continue;
        comma = 0;
        indexed = 0;
        indexjmp = 0;
        writebyte = 0;
        readbyte = 0;
        readword = 0;
        cmd = readcommand (&ptr) - 1;
        if (noifcount)
          {
            switch (cmd)
            {
            case IF:
              noifcount++;
              break;
            case ELSE:
              if (noifcount == 1)
                {
                  noifcount = 0;
                  ifcount++;
                }
              break;
            case ENDIF:
              noifcount--;
            }
            ptr = "";
            continue;
          }
        if (define_macro)
          {
            char *newptr;
            struct macro_line **current_line;
            for (current_line = &firstmacro->lines; *current_line;
               current_line = &(*current_line)->next)
            {
            }
            *current_line = malloc (sizeof (struct macro_line));
            if (!*current_line)
            {
              printerr (1, "out of memory\n");
              continue;
            }
            (*current_line)->next = NULL;
            (*current_line)->args = NULL;
            (*current_line)->line = malloc (strlen (buffer) + 1);
            if (!(*current_line)->line)
            {
              printerr (1, "out of memory\n");
              free (*current_line);
              *current_line = NULL;
              continue;
            }
            ptr = buffer;
            newptr = (*current_line)->line;
            while (*ptr)
            {
              unsigned p;
              struct macro_arg **last_arg = &(*current_line)->args;
              for (p = 0; p < firstmacro->numargs; ++p)
                {
                  if (strncmp (ptr, firstmacro->args[p],
                           strlen (firstmacro->args[p])) == 0)
                  {
                    struct macro_arg *newarg;
                    newarg = malloc (sizeof (struct macro_arg));
                    if (!newarg)
                      {
                        printerr (1, "out of memory\n");
                        break;
                      }
                    newarg->next = NULL;
                    *last_arg = newarg;
                    last_arg = &newarg->next;
                    newarg->pos = newptr - (*current_line)->line;
                    newarg->which = p;
                    /* leave one character so two macros following each
                     * other keep their order. */
                    ptr += strlen (firstmacro->args[p]) - 1;
                    break;
                  }
                }
              *newptr++ = *ptr++;
            }
            *newptr = 0;
            if (verbose >= 7)
            fprintf (stderr, "added line to macro (cmd = %d): %s\n", cmd,
                   (*current_line)->line);
            if (cmd == ENDM)
            define_macro = 0;
            continue;
          }
        switch (cmd)
          {
            int i, have_quote;
          case ADC:
            if (!(r = rd_a_hl (&ptr)))
            break;
            if (r == HL)
            {
              if (!(r = rd_rr_ (&ptr)))
                break;
              wrtb (0xED);
              wrtb (0x4A + 0x10 * --r);
              break;
            }
            if (!(r = rd_r (&ptr)))
            break;
            wrtb (0x88 + --r);
            break;
          case ADD:
            if (!(r = rd_r_add (&ptr)))
            break;
            if (r == addHL)
            {
              if (!(r = rd_rrxx (&ptr)))
                break;
              wrtb (0x09 + 0x10 * --r);   /* ADD HL/IX/IY, qq  */
              break;
            }
            if (has_argument (&ptr))
            {
              if (r != A)
                {
                  printerr (1, "parse error before: %s\n", ptr);
                  break;
                }
              if (!(r = rd_r (&ptr)))
                break;
              wrtb (0x80 + --r);    /* ADD A,r  */
              break;
            }
            wrtb (0x80 + --r);      /* ADD r  */
            break;
          case AND:
            if (!(r = rd_r (&ptr)))
            break;
            wrtb (0xA0 + --r);
            break;
          case BIT:
            if (!rd_0_7 (&ptr))
            break;
            rd_comma (&ptr);
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x40 + (r - 1));
            break;
          case CALL:
            if (!(r = rd_cc (&ptr)))
            {
              wrtb (0xCD);
            }
            else
            {
              wrtb (0xC4 + 8 * --r);
              rd_comma (&ptr);
            }
            rd_wrt_addr (&ptr, '\0');
            break;
          case CCF:
            wrtb (0x3F);
            break;
          case CP:
            if (!(r = rd_r (&ptr)))
            break;
            wrtb (0xB8 + --r);
            break;
          case CPD:
            wrtb (0xED);
            wrtb (0xA9);
            break;
          case CPDR:
            wrtb (0xED);
            wrtb (0xB9);
            break;
          case CPI:
            wrtb (0xED);
            wrtb (0xA1);
            break;
          case CPIR:
            wrtb (0xED);
            wrtb (0xB1);
            break;
          case CPL:
            wrtb (0x2F);
            break;
          case DAA:
            wrtb (0x27);
            break;
          case DEC:
            if (!(r = rd_r_rr (&ptr)))
            break;
            if (r < 0)
            {
              wrtb (0x05 - 8 * ++r);
              break;
            }
            wrtb (0x0B + 0x10 * --r);
            break;
          case DI:
            wrtb (0xF3);
            break;
          case DJNZ:
            wrtb (0x10);
            rd_wrt_jr (&ptr, '\0');
            break;
          case EI:
            wrtb (0xFB);
            break;
          case EQU:
            if (!lastlabel)
            {
              printerr (1, "EQU without label\n");
              break;
            }
            new_reference (ptr, TYPE_LABEL, 0, 0);
            if (verbose >= 4)
            {
              if (lastlabel->valid)
                fprintf (stderr, "Assigned value %d to label %s.\n",
                       lastlabel->value, lastlabel->name);
              else
                fprintf (stderr,
                       "Scheduled label %s for later computation.\n",
                       lastlabel->name);
            }
            ptr = "";
            break;
          case EX:
            if (!(r = rd_ex1 (&ptr)))
            break;
            switch (r)
            {
            case DE:
              if (!rd_hl (&ptr))
                break;
              wrtb (0xEB);
              break;
            case AF:
              if (!rd_af_ (&ptr))
                break;
              wrtb (0x08);
              break;
            default:
              if (!rd_hlx (&ptr))
                break;
              wrtb (0xE3);
            }
            break;
          case EXX:
            wrtb (0xD9);
            break;
          case HALT:
            wrtb (0x76);
            break;
          case IM:
            if (!(r = rd_0_2 (&ptr)))
            break;
            wrtb (0xED);
            wrtb (0x46 + 8 * --r);
            break;
          case IN:
            if (!(r = rd_in (&ptr)))
            break;
            if (r == A)
            {
              const char *tmp;
              if (!(r = rd_nnc (&ptr)))
                break;
              if (r == C)
                {
                  wrtb (0xED);
                  wrtb (0x40 + 8 * (A - 1));
                  break;
                }
              tmp = readbyte;
              wrtb (0xDB);
              new_reference (tmp, TYPE_ABSB, ')', 1);
              break;
            }
            if (!rd_c (&ptr))
            break;
            wrtb (0xED);
            wrtb (0x40 + 8 * --r);
            break;
          case INC:
            if (!(r = rd_r_rr (&ptr)))
            break;
            if (r < 0)
            {
              wrtb (0x04 - 8 * ++r);
              break;
            }
            wrtb (0x03 + 0x10 * --r);
            break;
          case IND:
            wrtb (0xED);
            wrtb (0xAA);
            break;
          case INDR:
            wrtb (0xED);
            wrtb (0xBA);
            break;
          case INI:
            wrtb (0xED);
            wrtb (0xA2);
            break;
          case INIR:
            wrtb (0xED);
            wrtb (0xB2);
            break;
          case JP:
            r = rd_jp (&ptr);
            if (r < 0)
            {
              wrtb (0xE9);
              break;
            }
            if (r == 0)
            {
              wrtb (0xC3);
            }
            else
            {
              wrtb (0xC2 + 8 * --r);
              rd_comma (&ptr);
            }
            rd_wrt_addr (&ptr, '\0');
            break;
          case JR:
            r = rd_jr (&ptr);
            if (r)
            rd_comma (&ptr);
            wrtb (0x18 + 8 * r);
            rd_wrt_jr (&ptr, '\0');
            break;
          case LD:
            if (!(r = rd_ld (&ptr)))
            break;
            switch (r)
            {
            case ld_BC:
            case ld_DE:
              if (!rd_a (&ptr))
                break;
              wrtb (0x02 + 0x10 * (r == ld_DE));
              break;
            case ld_HL:
              r = rd_ld_hl (&ptr);
              wrtb (0x70 + --r);
              break;
            case ld_NN:
              if (!(r = rd_ld_nn (&ptr)))
                break;
              if (r == ld_nnA || r == ld_nnHL)
                {
                  wrtb (0x22 + 0x10 * (r == ld_nnA));
                  write_word ();
                  break;
                }
              wrtb (0xED);
              wrtb (0x43 + 0x10 * --r);
              write_word ();
              break;
            case ldA:
              if (!(r = rd_lda (&ptr)))
                break;
              if (r == A_NN)
                {
                  wrtb (0x3A);
                  write_word ();
                  break;
                }
              if (r == A_I || r == A_R)
                {
                  wrtb (0xED);
                  wrtb (0x57 + 8 * (r == A_R));
                  break;
                }
              if (r < 0)
                {
                  wrtb (0x0A - 0x10 * ++r);
                  break;
                }
              wrtb (0x78 + --r);
              break;
            case ldB:
            case ldC:
            case ldD:
            case ldE:
            case ldH:
            case ldL:
              if (!(s = rd_ldbcdehla (&ptr)))
                break;
              wrtb (0x40 + 0x08 * (r - 7) + (s - 1));
              break;
            case ldBC:
            case ldDE:
              s = rd_nn_nn (&ptr);
              if (s == _NN)
                {
                  wrtb (0xED);
                  wrtb (0x4B + 0x10 * (r == ldDE));
                  write_word ();
                  break;
                }
              wrtb (0x01 + (r == ldDE) * 0x10);
              write_word ();
              break;
            case ldHL:
              r = rd_nn_nn (&ptr);
              wrtb (0x21 + (r == _NN) * 9);
              write_word ();
              break;
            case ldI:
            case ldR:
              if (!rd_a (&ptr))
                break;
              wrtb (0xED);
              wrtb (0x47 + 0x08 * (r == ldR));
              break;
            case ldSP:
              r = rd_sp (&ptr);
              if (r == SPHL)
                {
                  wrtb (0xF9);
                  break;
                }
              if (r == SPNN)
                {
                  wrtb (0x31);
                  write_word ();
                  break;
                }
              wrtb (0xED);
              wrtb (0x7B);
              write_word ();
              break;
            }
            break;
          case LDD:
            wrtb (0xED);
            wrtb (0xA8);
            break;
          case LDDR:
            wrtb (0xED);
            wrtb (0xB8);
            break;
          case LDI:
            wrtb (0xED);
            wrtb (0xA0);
            break;
          case LDIR:
            wrtb (0xED);
            wrtb (0xB0);
            break;
          case NEG:
            wrtb (0xED);
            wrtb (0x44);
            break;
          case NOP:
            wrtb (0x00);
            break;
          case OR:
            if (!(r = rd_r (&ptr)))
            break;
            wrtb (0xB0 + --r);
            break;
          case OTDR:
            wrtb (0xED);
            wrtb (0xBB);
            break;
          case OTIR:
            wrtb (0xED);
            wrtb (0xB3);
            break;
          case OUT:
            if (!(r = rd_nnc (&ptr)))
            break;
            if (r == C)
            {
              if (!(r = rd_out (&ptr)))
                break;
              wrtb (0xED);
              wrtb (0x41 + 8 * --r);
              break;
            }
            if (!rd_a (&ptr))
            break;
            {
            const char *tmp = readbyte;
            wrtb (0xD3);
            new_reference (tmp, TYPE_ABSB, ')', 1);
            }
            break;
          case OUTD:
            wrtb (0xED);
            wrtb (0xAB);
            break;
          case OUTI:
            wrtb (0xED);
            wrtb (0xA3);
            break;
          case POP:
            if (!(r = rd_stack (&ptr)))
            break;
            wrtb (0xC1 + 0x10 * --r);
            break;
          case PUSH:
            if (!(r = rd_stack (&ptr)))
            break;
            wrtb (0xC5 + 0x10 * --r);
            break;
          case RES:
            if (!rd_0_7 (&ptr))
            break;
            rd_comma (&ptr);
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x80 + --r);
            break;
          case RET:
            if (!(r = rd_cc (&ptr)))
            {
              wrtb (0xC9);
              break;
            }
            wrtb (0xC0 + 8 * --r);
            break;
          case RETI:
            wrtb (0xED);
            wrtb (0x4D);
            break;
          case RETN:
            wrtb (0xED);
            wrtb (0x45);
            break;
          case RL:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x10 + --r);
            break;
          case RLA:
            wrtb (0x17);
            break;
          case RLC:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x00 + --r);
            break;
          case RLCA:
            wrtb (0x07);
            break;
          case RLD:
            wrtb (0xED);
            wrtb (0x6F);
            break;
          case RR:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x18 + --r);
            break;
          case RRA:
            wrtb (0x1F);
            break;
          case RRC:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x08 + --r);
            break;
          case RRCA:
            wrtb (0x0F);
            break;
          case RRD:
            wrtb (0xED);
            wrtb (0x67);
            break;
          case RST:
            new_reference (ptr, TYPE_RST, '\0', 1);
            ptr = "";
            break;
          case SBC:
            if (!(r = rd_a_hl (&ptr)))
            break;
            if (r == HL)
            {
              if (!(r = rd_rr_ (&ptr)))
                break;
              wrtb (0xED);
              wrtb (0x42 + 0x10 * --r);
              break;
            }
            if (!(r = rd_r (&ptr)))
            break;
            wrtb (0x98 + --r);
            break;
          case SCF:
            wrtb (0x37);
            break;
          case SET:
            if (!rd_0_7 (&ptr))
            break;
            rd_comma (&ptr);
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0xC0 + --r);
            break;
          case SLA:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x20 + --r);
            break;
          case SLI:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x30 + --r);
            break;
          case SRA:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x28 + --r);
            break;
          case SRL:
            if (!(r = rd_r_ (&ptr)))
            break;
            wrtb (0xCB);
            wrtb (0x38 + --r);
            break;
          case SUB:
            if (!(r = rd_r (&ptr)))
            break;
            if (has_argument (&ptr))      /* SUB A,r ?  */
            {
              if (r != A)
                {
                  printerr (1, "parse error before: %s\n", ptr);
                  break;
                }
              if (!(r = rd_r (&ptr)))
                break;
            }
            wrtb (0x90 + --r);
            break;
          case XOR:
            if (!(r = rd_r (&ptr)))
            break;
            wrtb (0xA8 + --r);
            break;
          case DEFB:
          case DB:
          case DEFM:
          case DM:
            ptr = delspc (ptr);
            while (1)
            {
              have_quote = (*ptr == '"' || *ptr == '\'');
              if (have_quote)
                {
                  /* Read string.  */
                  int quote = *ptr;
                  if (listfile)
                  {
                    fprintf (listfile, " ..");
                    listdepth += 3;
                  }
                  ++ptr;
                  while (*ptr != quote)
                  {
                    write_one_byte (rd_character (&ptr, NULL, 1), 0);
                    if (*ptr == 0)
                      {
                        printerr (1, "end of line in quoted string\n");
                        break;
                      }
                  }
                  ++ptr;
                }
              else
                {
                  /* Read expression.  */
                  new_reference (ptr, TYPE_ABSB, ',', 1);
                  skipword (&ptr, ',');
                }
              ptr = delspc (ptr);
              if (*ptr == ',')
                {
                  ++ptr;
                  continue;
                }
              if (*ptr != 0)
                printerr (1, "junk in byte definition: %s\n", ptr);
              break;
            }
            break;
          case DEFW:
          case DW:
            if (!(r = rd_word (&ptr, ',')))
            {
              printerr (1, "No data for word definition\n");
              break;
            }
            do
            {
              new_reference (readword, TYPE_ABSW, ',', 1);
            }
            while ((r = rd_word (&ptr, ',')));
            break;
          case DEFS:
          case DS:
            r = rd_expr (&ptr, ',', NULL, sp, 1);
            if (r < 0)
            {
              printerr (1, "ds should have its first argument >=0"
                      " (not -0x%x)\n", -r);
              break;
            }
            ptr = delspc (ptr);
            if (*ptr)
            {
              rd_comma (&ptr);
              readbyte = 0;
              rd_byte (&ptr, '\0');
              writebyte = 0;
              new_reference (readbyte, TYPE_DS, '\0', r);
              break;
            }
            if (havelist)
            {
              fprintf (listfile, " 00...");
              listdepth += 6;
            }
            for (i = 0; i < r; i++)
            {
              write_one_byte (0, 0);
            }
            break;
          case END:
            file_ended = 1;
            break;
          case ORG:
            addr = rd_expr (&ptr, '\0', NULL, sp, 1);
            break;
          case INCLUDE:
            if (sp + 1 >= MAX_INCLUDE)
            {
              printerr (1, "stack overflow (circular include?)");
              if (verbose >= 5)
                {
                  int x;
                  fprintf (stderr, "Stack dump:\nframe  line file\n");
                  for (x = 0; x < MAX_INCLUDE; ++x)
                  fprintf (stderr, "%5d %5d %s\n", x, stack[x].line,
                         stack[x].name);
                }
              break;
            }
            {
            struct name *name;
            char *nm = get_include_name (&ptr);
            if (!nm)
              break;
            name = malloc (sizeof (struct name) + strlen (nm));
            if (!name)
              {
                printerr (1, "out of memory while allocating name\n");
                free (nm);
                break;
              }
            strcpy (name->name, nm);
            free (nm);
            ++sp;
            stack[sp].name = name->name;
            stack[sp].shouldclose = 1;
            stack[sp].line = 0;
            stack[sp].file = open_include_file (name->name,
                                        &stack[sp].dir, "r");
            if (!stack[sp].file)
              {
                printerr (1, "unable to open file %s\n", name->name);
                free (name);
                --sp;
                break;
              }
            name->next = firstname;
            name->prev = NULL;
            if (name->next)
              name->next->prev = name;
            firstname = name;
            if (verbose >= 4)
              fprintf (stderr, "Reading file %s\n", name->name);
            }
            break;
          case INCBIN:
            {
            FILE *incfile;
            char *name = get_include_name (&ptr);
            if (!name)
              break;
            incfile = open_include_file (name, NULL, "rb");
            if (!incfile)
              {
                printerr (1, "unable to open binary file %s\n", name);
                free (name);
                break;
              }
            while (1)
              {
                char filebuffer[4096];
                size_t num = fread (filebuffer, 1, 4096, incfile);
                if (num == 0)
                  break;
                if (num != fwrite (filebuffer, 1, num, outfile))
                  {
                  printerr (1, "error including binary file %s: %s\n",
                          name, strerror (errno));
                  break;
                  }
                addr += num;
              }
            fclose (incfile);
            free (name);
            break;
            }
          case IF:
            if (rd_expr (&ptr, '\0', NULL, sp, 1))
            ifcount++;
            else
            noifcount++;
            break;
          case ELSE:
            if (ifcount == 0)
            {
              printerr (1, "else without if\n");
              break;
            }
            noifcount = 1;
            ifcount--;
            break;
          case ENDIF:
            if (noifcount == 0 && ifcount == 0)
            {
              printerr (1, "endif without if\n");
              break;
            }
            if (noifcount)
            noifcount--;
            else
            ifcount--;
            break;
          case MACRO:
            if (!lastlabel)
            {
              printerr (1, "macro without label\n");
              break;
            }
            if (define_macro)
            {
              printerr (1, "nested macro definition\n");
              break;
            }
            {
            struct macro *m;
            for (m = firstmacro; m; m = m->next)
              {
                if (strcmp (m->name, lastlabel->name) == 0)
                  {
                  printerr (1, "duplicate macro definition\n");
                  break;
                  }
              }
            m = malloc (sizeof (struct macro));
            if (!m)
              {
                printerr (1, "out of memory\n");
                break;
              }
            m->name = malloc (strlen (lastlabel->name) + 1);
            if (!m->name)
              {
                printerr (1, "out of memory\n");
                free (m);
                break;
              }
            strcpy (m->name, lastlabel->name);
            if (lastlabel->prev)
              lastlabel->prev->next = lastlabel->next;
            else
              firstlabel = lastlabel->next;
            if (lastlabel->next)
              lastlabel->next->prev = lastlabel->prev;
            free (lastlabel);
            m->next = firstmacro;
            firstmacro = m;
            m->lines = NULL;
            m->numargs = get_macro_args (&ptr, &m->args, 0);
            define_macro = 1;
            }
            break;
          case ENDM:
            if (stack[sp].file)
            printerr (1, "endm outside macro definition\n");
            break;
          case SEEK:
            {
            unsigned int seekaddr = rd_expr (&ptr, '\0', NULL, sp, 1);
            if (verbose >= 2)
              {
                fprintf (stderr, "%s%s:%d: ",
                       stack[sp].dir ? stack[sp].dir->name : "",
                       stack[sp].name, stack[sp].line);
                fprintf (stderr, "[Message] seeking to 0x%0X \n",
                       seekaddr);
              }
            fseek (outfile, seekaddr, SEEK_SET);
            break;
            }
          default:
            {
            struct macro *m;
            for (m = firstmacro; m; m = m->next)
              {
                if (strncmp (m->name, ptr, strlen (m->name)) == 0)
                  {
                  unsigned numargs;
                  if (sp + 1 >= MAX_INCLUDE)
                    {
                      printerr (1, "stack overflow (circular include?)\n");
                      if (verbose >= 5)
                        {
                        int x;
                        fprintf (stderr,
                               "Stack dump:\nframe  line file\n");
                        for (x = 0; x < MAX_INCLUDE; ++x)
                          fprintf (stderr, "%5d %5d %s\n", x,
                                 stack[x].line, stack[x].name);
                        }
                      break;
                    }
                  ++sp;
                  ptr += strlen (m->name);
                  numargs = get_macro_args (&ptr, &stack[sp].macro_args,
                                      1);
                  if (numargs != m->numargs)
                    {
                      unsigned a;
                      printerr (1, "invalid number of arguments for macro "
                              "(is %d, must be %d)\n", numargs,
                              m->numargs);
                      for (a = 0; a < numargs; ++a)
                        free (stack[sp].macro_args[a]);
                      free (stack[sp].macro_args);
                      break;
                    }
                  stack[sp].name = m->name;
                  stack[sp].file = NULL;
                  stack[sp].line = 0;
                  stack[sp].macro = m;
                  stack[sp].macro_line = m->lines;
                  stack[sp].shouldclose = 0;
                  stack[sp].dir = NULL;
                  break;
                  }
              }
            if (m)
              break;
            }
            printerr (1, "command or comment expected (was %s)\n", ptr);
          }
      }
    }
  if (ifcount || noifcount)
    {
      printerr (1, "reached EOF at IF level %d\n", ifcount + noifcount);
    }
  if (havelist)
    {
      fprintf (listfile, "%04x\n", addr);
    }
  {
    struct reference *next;
    struct reference *tmp;
    for (tmp = firstreference; tmp; tmp = next)
      {
      int ref;
      next = tmp->next;
      fseek (outfile, tmp->oseekpos, SEEK_SET);
      if (havelist)
        fseek (listfile, tmp->lseekpos, SEEK_SET);
      ref = compute_ref (tmp, 0);
      wrt_ref (ref, tmp->type, tmp->count);
      free (tmp);
      }
  }
  if (!errors || use_force)
    {
      flush_to_real_file (realoutputfile, outfile);
      if (havelist)
      flush_to_real_file (reallistfile, listfile);
    }
  /* write all labels */
  if (label)
    fseek (labelfile, 0, SEEK_END);
  for (l = firstlabel; l; l = l->next)
    {
      if (l->ref)
      {
        compute_ref (l->ref, 0);
      }
      if (label)
      {
        fprintf (labelfile, "%s%s:\tequ $%04x\n", labelprefix, l->name,
               l->value);
      }
    }
  if (label)
    fclose (labelfile);
  while (firstlabel)
    {
      l = firstlabel->next;
      free (firstlabel);
      firstlabel = l;
    }
  fclose (outfile);
  if (outfile != realoutputfile)
    fclose (realoutputfile);
  if (havelist)
    {
      fclose (listfile);
      if (listfile != reallistfile && reallistfile != stderr)
      fclose (reallistfile);
    }
  free (infile);
}

int
main (int argc, char **argv)
{
  /* default include file location */
  add_include ("/usr/share/z80asm/headers/");
  parse_commandline (argc, argv);
  if (verbose >= 1)
    fprintf (stderr, "Assembling....\n");
  assemble ();
  if (errors)
    {
      if (errors == 1)
      fprintf (stderr, "*** 1 error found ***\n");
      else
      fprintf (stderr, "*** %d errors found ***\n", errors);
      if (realoutputfile == outfile && !use_force)
      {
        unlink (realoutputfilename);
        unlink (labelfilename);
      }
      return 1;
    }
  else
    {
      if (verbose >= 1)
      fprintf (stderr, "Assembly succesful.\n");
      return 0;
    }
}

Generated by  Doxygen 1.6.0   Back to index