/* tc-z80.c -- Assemble code for the Zilog Z80 and ASCII R800
   Copyright 2005 Free Software Foundation, Inc.
   Contributed by Arnold Metselaar <arnold_m@operamail.com>

   This file is part of GAS, the GNU Assembler.

   GAS 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, or (at your option)
   any later version.

   GAS 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 GAS; see the file COPYING.  If not, write to the Free
   Software Foundation, 51 Franklin Street - Fifth Floor, Boston, MA
   02110-1301, USA.  */

#include "as.h"
#include "listing.h"
#include "bfd.h"
#include "safe-ctype.h"
#include "subsegs.h"
#include "symbols.h"
#include "libiberty.h"

/* Exported constants.  */
const char comment_chars[] = ";\0";
const char line_comment_chars[] = "#;\0";
const char line_separator_chars[] = "\0";
const char EXP_CHARS[] = "eE\0";
const char FLT_CHARS[] = "RrFf\0";

/* For machine specific options.  */
const char * md_shortopts = ""; /* None yet.  */

enum options
{
  OPTION_MACH_Z80 = OPTION_MD_BASE,
  OPTION_MACH_R800,
  OPTION_MACH_IUD,
  OPTION_MACH_WUD,
  OPTION_MACH_FUD,
  OPTION_MACH_IUP,
  OPTION_MACH_WUP,
  OPTION_MACH_FUP
};

#define INS_Z80    1
#define INS_UNDOC  2
#define INS_UNPORT 4
#define INS_R800   8

struct option md_longopts[] =
{
  { "z80",       no_argument, NULL, OPTION_MACH_Z80},
  { "r800",      no_argument, NULL, OPTION_MACH_R800},
  { "ignore-undocumented-instructions", no_argument, NULL, OPTION_MACH_IUD },
  { "Wnud",  no_argument, NULL, OPTION_MACH_IUD },
  { "warn-undocumented-instructions",  no_argument, NULL, OPTION_MACH_WUD },
  { "Wud",  no_argument, NULL, OPTION_MACH_WUD },
  { "forbid-undocumented-instructions", no_argument, NULL, OPTION_MACH_FUD },
  { "Fud",  no_argument, NULL, OPTION_MACH_FUD },
  { "ignore-unportable-instructions", no_argument, NULL, OPTION_MACH_IUP },
  { "Wnup",  no_argument, NULL, OPTION_MACH_IUP },
  { "warn-unportable-instructions",  no_argument, NULL, OPTION_MACH_WUP },
  { "Wup",  no_argument, NULL, OPTION_MACH_WUP },
  { "forbid-unportable-instructions", no_argument, NULL, OPTION_MACH_FUP },
  { "Fup",  no_argument, NULL, OPTION_MACH_FUP },

  { NULL, no_argument, NULL, 0 }
} ;

size_t md_longopts_size = sizeof (md_longopts);

extern int coff_flags;
/* Instruction classes that silently assembled.  */
static int ins_ok = INS_Z80 | INS_UNDOC;
/* Instruction classes that generate errors.  */
static int ins_err = INS_R800;
/* Instruction classes actually used, determines machine type.  */
static int ins_used = INS_Z80;

int
md_parse_option (int c, char* arg ATTRIBUTE_UNUSED)
{
  switch (c)
    {
    default:
      return 0;
    case OPTION_MACH_Z80:
      ins_ok &= ~INS_R800;
      ins_err |= INS_R800;
      break;
    case OPTION_MACH_R800:
      ins_ok = INS_Z80 | INS_UNDOC | INS_R800;
      ins_err = INS_UNPORT;
      break;
    case OPTION_MACH_IUD:
      ins_ok |= INS_UNDOC;
      ins_err &= ~INS_UNDOC;
      break;
    case OPTION_MACH_IUP:
      ins_ok |= INS_UNDOC | INS_UNPORT;
      ins_err &= ~(INS_UNDOC | INS_UNPORT);
      break;
    case OPTION_MACH_WUD:
      if ((ins_ok & INS_R800) == 0)
	{
	  ins_ok &= ~(INS_UNDOC|INS_UNPORT);
	  ins_err &= ~INS_UNDOC;
	}
      break;
    case OPTION_MACH_WUP:
      ins_ok &= ~INS_UNPORT;
      ins_err &= ~(INS_UNDOC|INS_UNPORT);
      break;
    case OPTION_MACH_FUD:
      if ((ins_ok & INS_R800) == 0)
	{
	  ins_ok &= (INS_UNDOC | INS_UNPORT);
	  ins_err |= INS_UNDOC | INS_UNPORT;
	}
      break;
    case OPTION_MACH_FUP:
      ins_ok &= ~INS_UNPORT;
      ins_err |= INS_UNPORT;
      break;
    }

  return 1;
}

void
md_show_usage (FILE * f)
{
  fprintf (f, "\n\
CPU model/instruction set options:\n\
\n\
  -z80\t\t  assemble for Z80\n\
  -ignore-undocumented-instructions\n\
  -Wnud\n\
\tsilently assemble undocumented Z80-instructions that work on R800\n\
  -ignore-unportable-instructions\n\
  -Wnup\n\
\tsilently assemble all undocumented Z80-instructions\n\
  -warn-undocumented-instructions\n\
  -Wud\n\
\tissue warnings for undocumented Z80-instructions that work on R800\n\
  -warn-unportable-instructions\n\
  -Wup\n\
\tissue warnings for other undocumented Z80-instructions\n\
  -forbid-undocumented-instructions\n\
  -Fud\n\
\ttreat all undocumented z80-instructions as errors\n\
  -forbid-unportable-instructions\n\
  -Fup\n\
\ttreat undocumented z80-instructions that do not work on R800 as errors\n\
  -r800\t  assemble for R800\n\n\
Default: -z80 -ignore-undocument-instructions -warn-unportable-instructions.\n");
}

static symbolS * zero;

void
md_begin (void)
{
  expressionS nul;
  char * p;

  p = input_line_pointer;
  input_line_pointer = "0";
  nul.X_md=0;
  expression (& nul);
  input_line_pointer = p;
  zero = make_expr_symbol (& nul);
  /* We do not use relaxation (yet).  */
  linkrelax = 0;
}

void
z80_md_end (void)
{
  int mach_type;

  if (ins_used & (INS_UNPORT | INS_R800))
    ins_used |= INS_UNDOC;

  switch (ins_used)
    {
    case INS_Z80:
      mach_type = bfd_mach_z80strict;
      break;
    case INS_Z80|INS_UNDOC:
      mach_type = bfd_mach_z80;
      break;
    case INS_Z80|INS_UNDOC|INS_UNPORT:
      mach_type = bfd_mach_z80full;
      break;
    case INS_Z80|INS_UNDOC|INS_R800:
      mach_type = bfd_mach_r800;
      break;
    default:
      mach_type = 0;
    }

  bfd_set_arch_mach (stdoutput, TARGET_ARCH, mach_type);
}

/* Port specific features.  */
const pseudo_typeS md_pseudo_table[] =
{
  { "defs",  s_space, 1}, /* Synonym for ds on some assemblers.  */
  { "ds",    s_space, 1}, /* Fill with bytes rather than words.  */
  { "psect", obj_coff_section, 0}, /* TODO: Translate attributes.  */
  { "set", 0, 0}, 		/* Real instruction on z80.  */
  { NULL, 0, 0 }
} ;

static const char *
skip_space (const char *s)
{
  while (*s == ' ' || *s == '\t')
    ++s;
  return s;
}

/* A non-zero return-value causes a continue in the
   function read_a_source_file () in ../read.c.  */
int
z80_start_line_hook (void)
{
  char *p, quote;
  char buf[4];

  /* Convert one character constants.  */
  for (p = input_line_pointer; *p && *p != '\n'; ++p)
    {
      switch (*p)
	{
	case '\'':
	  if (p[1] != 0 && p[1] != '\'' && p[2] == '\'')
	    {
	      snprintf (buf, 4, "%3d", (unsigned char)p[1]);
	      *p++ = buf[0];
	      *p++ = buf[1];
	      *p++ = buf[2];
	      break;
	    }
	case '"':
	  for (quote = *p++; quote != *p && '\n' != *p; ++p)
	    /* No escapes.  */ ;
	  if (quote != *p)
	    {
	      as_bad (_("-- unterminated string"));
	      ignore_rest_of_line ();
	      return 1;
	    }
	  break;
	}
    }
  /* Check for <label>[:] (EQU|DEFL) <value>.  */
  if (is_name_beginner (*input_line_pointer))
    {
      char c, *rest, *line_start;
      int len;
      symbolS * symbolP;

      line_start = input_line_pointer;
      LISTING_NEWLINE ();
      if (ignore_input ())
	return 0;

      c = get_symbol_end ();
      rest = input_line_pointer + 1;

      if (*rest == ':')
	++rest;
      if (*rest == ' ' || *rest == '\t')
	++rest;
      if (strncasecmp (rest, "EQU", 3) == 0)
	len = 3;
      else if (strncasecmp (rest, "DEFL", 4) == 0)
	len = 4;
      else
	len = 0;
      if (len && (rest[len] == ' ' || rest[len] == '\t'))
	{
	  /* Handle assignment here.  */
	  input_line_pointer = rest + len;
	  if (line_start[-1] == '\n')
	    bump_line_counters ();
	  /* Most Z80 assemblers require the first definition of a
             label to use "EQU" and redefinitions to have "DEFL".  */
	  if (len == 3 && (symbolP = symbol_find (line_start)) != NULL) 
	    {
	      if (S_IS_DEFINED (symbolP) || symbol_equated_p (symbolP))
		as_bad (_("symbol `%s' is already defined"), line_start);
	    }
	  /* All symbols may be redefined.  */
	  equals (line_start, 1);
	  return 1;
	}
      else
	{
	  /* Restore line and pointer.  */
	  *input_line_pointer = c;
	  input_line_pointer = line_start;
	}
    }
  return 0;
}

symbolS *
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
{
  return NULL;
}

char *
md_atof (int type ATTRIBUTE_UNUSED, char *litP ATTRIBUTE_UNUSED,
	 int *sizeP ATTRIBUTE_UNUSED)
{
  return _("floating point numbers are not implemented");
}

valueT
md_section_align (segT seg ATTRIBUTE_UNUSED, valueT size)
{
  return size;
}

long
md_pcrel_from (fixS * fixp)
{
  return fixp->fx_where +
    fixp->fx_frag->fr_address + 1;
}

typedef const char * (asfunc)(char, char, const char*);

typedef struct _table_t
{
  char* name;
  char prefix;
  char opcode;
  asfunc * fp;
} table_t;

/* Compares the key for structs that start with a char * to the key.  */
static int
key_cmp (const void * a, const void * b)
{
  const char *str_a, *str_b;

  str_a = *((const char**)a);
  str_b = *((const char**)b);
  return strcmp (str_a, str_b);
}

#define BUFLEN 8 /* Large enough for any keyword.  */

char buf[BUFLEN];
const char *key = buf;

#define R_STACKABLE (0x80)
#define R_ARITH     (0x40)
#define R_IX        (0x20)
#define R_IY        (0x10)
#define R_INDEX     (R_IX | R_IY)

#define REG_A (7)
#define REG_B (0)
#define REG_C (1)
#define REG_D (2)
#define REG_E (3)
#define REG_H (4)
#define REG_L (5)
#define REG_F (6 | 8)
#define REG_I (9)
#define REG_R (10)

#define REG_AF (3 | R_STACKABLE)
#define REG_BC (0 | R_STACKABLE | R_ARITH)
#define REG_DE (1 | R_STACKABLE | R_ARITH)
#define REG_HL (2 | R_STACKABLE | R_ARITH)
#define REG_SP (3 | R_ARITH)

static const struct reg_entry
{
  char* name;
  int number;
} regtable[] =
{
  {"a",  REG_A },
  {"af", REG_AF },
  {"b",  REG_B },
  {"bc", REG_BC },
  {"c",  REG_C },
  {"d",  REG_D },
  {"de", REG_DE },
  {"e",  REG_E },
  {"f",  REG_F },
  {"h",  REG_H },
  {"hl", REG_HL },
  {"i",  REG_I },
  {"ix", REG_HL | R_IX },
  {"ixh",REG_H | R_IX },
  {"ixl",REG_L | R_IX },
  {"iy", REG_HL | R_IY },
  {"iyh",REG_H | R_IY },
  {"iyl",REG_L | R_IY },
  {"l",  REG_L },
  {"r",  REG_R },
  {"sp", REG_SP },
} ;

/* Prevent an error on a line from also generating
   a "junk at end of line" error message.  */
static char err_flag;

static void
error (const char * message)
{
  as_bad (message);
  err_flag = 1;
}

static void
ill_op (void)
{
  error (_("illegal operand"));
}

static void
wrong_mach (int ins_type)
{
  const char *p;

  switch (ins_type)
    {
    case INS_UNDOC:
      p = "undocumented instruction";
      break;
    case INS_UNPORT:
      p = "instruction does not work on R800";
      break;
    case INS_R800:
      p = "instruction only works R800";
      break;
    default:
      p = 0; /* Not reachables.  */
    }

  if (ins_type & ins_err)
    error (_(p));
  else
    as_warn (_(p));
}

static void
check_mach (int ins_type)
{
  if ((ins_type & ins_ok) == 0)
    wrong_mach (ins_type);
  ins_used |= ins_type;
}

/* This function tries to subtract two symbols, the generic code does
   that too, but this function tries harder.
   The behaviour of this function is not altered by extra
   fragmentations caused by the code to produce listings.  */
int
z80_optimize_expr (expressionS *resultP, operatorT left_op,
		   expressionS *right)
{
  int res, swap, som;
  fragS *lfrag, *rfrag, *cur;

  res = 0;
  if (left_op == O_subtract
      && right->X_op == O_symbol
      && resultP->X_op == O_symbol)
    {
      lfrag = symbol_get_frag (resultP->X_add_symbol);
      rfrag = symbol_get_frag (right->X_add_symbol);

      if (S_GET_SEGMENT (right->X_add_symbol) !=  undefined_section
	  && (S_GET_SEGMENT (right->X_add_symbol)
	      == S_GET_SEGMENT (resultP->X_add_symbol)))
	{
	  for (swap = 0; (res == 0) && (swap < 2); ++swap)
	    {
	      if (swap)
		{
		  cur = lfrag;
		  lfrag = rfrag;
		  rfrag = cur;
		}
	      else
		cur = rfrag;

	      /* Now som == cur->fr_address - rfrag->address, except
		 the latter may not have been computed yet.  */
	      for (som = 0; cur && cur != lfrag; cur = cur->fr_next)
		{
		  if (cur->fr_type == rs_fill) /* Is the size fized?  */
		    som += cur->fr_fix+cur->fr_offset*cur->fr_var;
		  else
		    break;
		}

	      if  (cur == lfrag)
		{
		  resultP->X_add_number -= right->X_add_number;
		  resultP->X_add_number
		    += (S_GET_VALUE (resultP->X_add_symbol)
			- S_GET_VALUE (right->X_add_symbol));
		  som -= lfrag->fr_address - rfrag->fr_address;
		  /* Correct the result if the fr_address
		     fields are not computed yet.  */
		  resultP->X_add_number += (swap ? -som : som);
		  resultP->X_op = O_constant;
		  resultP->X_add_symbol = 0;
		  res = 1;
		}
	    }
	}
    }
  return res;
}

/* Check whether an expression is indirect.  */
static int
is_indir (const char *s)
{
  char quote;
  const char *p;
  int indir, depth;

  /* Indirection is indicated with parentheses.  */
  indir = (*s == '(');

  for (p = s, depth = 0; *p && *p != ','; ++p)
    {
      switch (*p)
	{
	case '"':
	case '\'':
	  for (quote = *p++; quote != *p && *p != '\n'; ++p)
	    if (*p == '\\' && p[1])
	      ++p;
	  break;
	case '(':
	  ++ depth;
	  break;
	case ')':
	  -- depth;
	  if (depth == 0)
	    {
	      p = skip_space (p + 1);
	      if (*p && *p != ',')
		indir = 0;
	      --p;
	    }
	  if (depth < 0)
	    error (_("mismatched parentheses"));
	  break;
	}
    }

  if (depth != 0)
    error (_("mismatched parentheses"));

  return indir;
}

/* Parse general expression.  */
static const char *
parse_exp2 (const char *s, expressionS *op, segT *pseg)
{
  const char *p;
  int indir;
  int i;
  const struct reg_entry * regp;
  expressionS offset;

  p = skip_space (s);
  op->X_md = indir = is_indir (p);
  if (indir)
    p = skip_space (p + 1);

  for (i = 0; i < BUFLEN; ++i)
    {
      if (!ISALPHA (p[i])) /* Register names consist of letters only.  */
	break;
      buf[i] = TOLOWER (p[i]);
    }

  if ((i < BUFLEN) && ((p[i] == 0) || (strchr (")+-, \t", p[i]))))
    {
      buf[i] = 0;
      regp = bsearch (& key, regtable, ARRAY_SIZE (regtable),
		      sizeof (regtable[0]), key_cmp);
      if (regp)
	{
	  *pseg = reg_section;
	  op->X_add_symbol = op->X_op_symbol = 0;
	  op->X_add_number = regp->number;
	  op->X_op = O_register;
	  p += strlen (regp->name);
	  p = skip_space (p);
	  if (indir)
	    {
	      if (*p == ')')
		++p;
	      if ((regp->number & R_INDEX) && (regp->number & R_ARITH))
		{
		  op->X_op = O_md1;

		  if  ((*p == '+') || (*p == '-'))
		    {
		      input_line_pointer = (char*) p;
		      expression (& offset);
		      p = skip_space (input_line_pointer);
		      if (*p != ')')
			error (_("bad offset expression syntax"));
		      else
			++ p;
		      op->X_add_symbol = make_expr_symbol (& offset);
		      return p;
		    }

		  /* We treat (i[xy]) as (i[xy]+0), which is how it will
		     end up anyway, unless we're processing jp (i[xy]).  */
		  op->X_add_symbol = zero;
		}
	    }
	  p = skip_space (p);

	  if ((*p == 0) || (*p == ','))
	    return p;
	}
    }
  /* Not an argument involving a register; use the generic parser.  */
  input_line_pointer = (char*) s ;
  *pseg = expression (op);
  if (op->X_op == O_absent)
    error (_("missing operand"));
  if (op->X_op == O_illegal)
    error (_("bad expression syntax"));
  return input_line_pointer;
}

static const char *
parse_exp (const char *s, expressionS *op)
{
  segT dummy;
  return parse_exp2 (s, op, & dummy);
}

/* Condition codes, including some synonyms provided by HiTech zas.  */
static const struct reg_entry cc_tab[] =
{
  { "age", 6 << 3 },
  { "alt", 7 << 3 },
  { "c",   3 << 3 },
  { "di",  4 << 3 },
  { "ei",  5 << 3 },
  { "lge", 2 << 3 },
  { "llt", 3 << 3 },
  { "m",   7 << 3 },
  { "nc",  2 << 3 },
  { "nz",  0 << 3 },
  { "p",   6 << 3 },
  { "pe",  5 << 3 },
  { "po",  4 << 3 },
  { "z",   1 << 3 },
} ;

/* Parse condition code.  */
static const char *
parse_cc (const char *s, char * op)
{
  const char *p;
  int i;
  struct reg_entry * cc_p;

  for (i = 0; i < BUFLEN; ++i)
    {
      if (!ISALPHA (s[i])) /* Condition codes consist of letters only.  */
	break;
      buf[i] = TOLOWER (s[i]);
    }

  if ((i < BUFLEN)
      && ((s[i] == 0) || (s[i] == ',')))
    {
      buf[i] = 0;
      cc_p = bsearch (&key, cc_tab, ARRAY_SIZE (cc_tab),
		      sizeof (cc_tab[0]), key_cmp);
    }
  else
    cc_p = NULL;

  if (cc_p)
    {
      *op = cc_p->number;
      p = s + i;
    }
  else
    p = NULL;

  return p;
}

static const char *
emit_insn (char prefix, char opcode, const char * args)
{
  char *p;

  if (prefix)
    {
      p = frag_more (2);
      *p++ = prefix;
    }
  else
    p = frag_more (1);
  *p = opcode;
  return args;
}

static void
emit_byte (expressionS * val, bfd_reloc_code_real_type r_type)
{
  char *p;
  int lo, hi;
  fixS * fixp;

  p = frag_more (1);
  *p = val->X_add_number;
  if ((r_type != BFD_RELOC_8_PCREL) && (val->X_op == O_constant))
    {
      lo = -128;
      hi = (BFD_RELOC_8 == r_type) ? 255 : 127;

      if ((val->X_add_number < lo) || (val->X_add_number > hi))
	{
	  if (r_type == BFD_RELOC_Z80_DISP8)
	    as_bad (_("offset too large"));
	  else
	    as_warn (_("overflow"));
	}
    }
  else
    {
      fixp = fix_new_exp (frag_now, p - frag_now->fr_literal, 1, val,
			  (r_type == BFD_RELOC_8_PCREL) ? TRUE : FALSE, r_type);
      /* FIXME : Process constant offsets immediately.  */
    }
}

static void
emit_word (expressionS * val)
{
  char *p;

  p = frag_more (2);
  if (   (val->X_op == O_register)
      || (val->X_op == O_md1))
    ill_op ();
  else
    {
      *p = val->X_add_number;
      p[1] = (val->X_add_number>>8);
      if (val->X_op != O_constant)
	fix_new_exp (frag_now, p - frag_now->fr_literal, 2,
		     val, FALSE, BFD_RELOC_16);
    }
}

static void
emit_mx (char prefix, char opcode, int shift, expressionS * arg)
     /* The operand m may be r, (hl), (ix+d), (iy+d),
	if 0 == prefix m may also be ixl, ixh, iyl, iyh.  */
{
  char *q;
  int rnum;

  rnum = arg->X_add_number;
  switch (arg->X_op)
    {
    case O_register:
      if (arg->X_md)
	{
	  if (rnum != REG_HL)
	    {
	      ill_op ();
	      break;
	    }
	  else
	    rnum = 6;
	}
      else
	{
	  if ((prefix == 0) && (rnum & R_INDEX))
	    {
	      prefix = (rnum & R_IX) ? 0xDD : 0xFD;
	      check_mach (INS_UNDOC);
	      rnum &= ~R_INDEX;
	    }
	  if (rnum > 7)
	    {
	      ill_op ();
	      break;
	    }
	}
      q = frag_more (prefix ? 2 : 1);
      if (prefix)
	* q ++ = prefix;
      * q ++ = opcode + (rnum << shift);
      break;
    case O_md1:
      q = frag_more (2);
      *q++ = (rnum & R_IX) ? 0xDD : 0xFD;
      *q = (prefix) ? prefix : (opcode + (6 << shift));
      emit_byte (symbol_get_value_expression (arg->X_add_symbol),
		 BFD_RELOC_Z80_DISP8);
      if (prefix)
	{
	  q = frag_more (1);
	  *q = opcode+(6<<shift);
	}
      break;
    default:
      abort ();
    }
}

/* The operand m may be r, (hl), (ix+d), (iy+d),
   if 0 = prefix m may also be ixl, ixh, iyl, iyh.  */
static const char *
emit_m (char prefix, char opcode, const char *args)
{
  expressionS arg_m;
  const char *p;

  p = parse_exp (args, &arg_m);
  switch (arg_m.X_op)
    {
    case O_md1:
    case O_register:
      emit_mx (prefix, opcode, 0, &arg_m);
      break;
    default:
      ill_op ();
    }
  return p;
}

/* The operand m may be as above or one of the undocumented
   combinations (ix+d),r and (iy+d),r (if unportable instructions
   are allowed).  */
static const char *
emit_mr (char prefix, char opcode, const char *args)
{
  expressionS arg_m, arg_r;
  const char *p;

  p = parse_exp (args, & arg_m);

  switch (arg_m.X_op)
    {
    case O_md1:
      if (*p == ',')
	{
	  p = parse_exp (p + 1, & arg_r);

	  if ((arg_r.X_md == 0)
	      && (arg_r.X_op == O_register)
	      && (arg_r.X_add_number < 8))
	    opcode += arg_r.X_add_number-6; /* Emit_mx () will add 6.  */
	  else
	    {
	      ill_op ();
	      break;
	    }
	  check_mach (INS_UNPORT);
	}
    case O_register:
      emit_mx (prefix, opcode, 0, & arg_m);
      break;
    default:
      ill_op ();
    }
  return p;
}

static void
emit_sx (char prefix, char opcode, expressionS * arg_p)
{
  char *q;

  switch (arg_p->X_op)
    {
    case O_register:
    case O_md1:
      emit_mx (prefix, opcode, 0, arg_p);
      break;
    default:
      if (arg_p->X_md)
	ill_op ();
      else
	{
	  q = frag_more (prefix ? 2 : 1);
	  if (prefix)
	    *q++ = prefix;
	  *q = opcode ^ 0x46;
	  emit_byte (arg_p, BFD_RELOC_8);
	}
    }
}

/* The operand s may be r, (hl), (ix+d), (iy+d), n.  */
static const char *
emit_s (char prefix, char opcode, const char *args)
{
  expressionS arg_s;
  const char *p;

  p = parse_exp (args, & arg_s);
  emit_sx (prefix, opcode, & arg_s);
  return p;
}

static const char *
emit_call (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  expressionS addr;
  const char *p;  char *q;

  p = parse_exp (args, &addr);
  if (addr.X_md)
    ill_op ();
  else
    {
      q = frag_more (1);
      *q = opcode;
      emit_word (& addr);
    }
  return p;
}

/* Operand may be rr, r, (hl), (ix+d), (iy+d).  */
static const char *
emit_incdec (char prefix, char opcode, const char * args)
{
  expressionS operand;
  int rnum;
  const char *p;  char *q;

  p = parse_exp (args, &operand);
  rnum = operand.X_add_number;
  if ((! operand.X_md)
      && (operand.X_op == O_register)
      && (R_ARITH&rnum))
    {
      q = frag_more ((rnum & R_INDEX) ? 2 : 1);
      if (rnum & R_INDEX)
	*q++ = (rnum & R_IX) ? 0xDD : 0xFD;
      *q = prefix + ((rnum & 3) << 4);
    }
  else
    {
      if ((operand.X_op == O_md1) || (operand.X_op == O_register))
	emit_mx (0, opcode, 3, & operand);
      else
	ill_op ();
    }
  return p;
}

static const char *
emit_jr (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  expressionS addr;
  const char *p;
  char *q;

  p = parse_exp (args, &addr);
  if (addr.X_md)
    ill_op ();
  else
    {
      q = frag_more (1);
      *q = opcode;
      emit_byte (&addr, BFD_RELOC_8_PCREL);
    }
  return p;
}

static const char *
emit_jp (char prefix, char opcode, const char * args)
{
  expressionS addr;
  const char *p;
  char *q;
  int rnum;

  p = parse_exp (args, & addr);
  if (addr.X_md)
    {
      rnum = addr.X_add_number;
      if ((addr.X_op == O_register && (rnum & ~R_INDEX) == REG_HL)
	 /* An operand (i[xy]) would have been rewritten to (i[xy]+0)
            in parse_exp ().  */
	  || (addr.X_op == O_md1 && addr.X_add_symbol == zero))
	{
	  q = frag_more ((rnum & R_INDEX) ? 2 : 1);
	  if (rnum & R_INDEX)
	    *q++ = (rnum & R_IX) ? 0xDD : 0xFD;
	  *q = prefix;
	}
      else
	ill_op ();
    }
  else
    {
      q = frag_more (1);
      *q = opcode;
      emit_word (& addr);
    }
  return p;
}

static const char *
emit_im (char prefix, char opcode, const char * args)
{
  expressionS mode;
  const char *p;
  char *q;

  p = parse_exp (args, & mode);
  if (mode.X_md || (mode.X_op != O_constant))
    ill_op ();
  else
    switch (mode.X_add_number)
      {
      case 1:
      case 2:
	++mode.X_add_number;
	/* Fall through.  */
      case 0:
	q = frag_more (2);
	*q++ = prefix;
	*q = opcode + 8*mode.X_add_number;
	break;
      default:
	ill_op ();
      }
  return p;
}

static const char *
emit_pop (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  expressionS regp;
  const char *p;
  char *q;

  p = parse_exp (args, & regp);
  if ((!regp.X_md)
      && (regp.X_op == O_register)
      && (regp.X_add_number & R_STACKABLE))
    {
      int rnum;

      rnum = regp.X_add_number;
      if (rnum&R_INDEX)
	{
	  q = frag_more (2);
	  *q++ = (rnum&R_IX)?0xDD:0xFD;
	}
      else
	q = frag_more (1);
      *q = opcode + ((rnum & 3) << 4);
    }
  else
    ill_op ();

  return p;
}

static const char *
emit_retcc (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  char cc, *q;
  const char *p;

  p = parse_cc (args, &cc);
  q = frag_more (1);
  if (p)
    *q = opcode + cc;
  else
    *q = prefix;
  return p ? p : args;
}

static const char *
emit_adc (char prefix, char opcode, const char * args)
{
  expressionS term;
  int rnum;
  const char *p;
  char *q;

  p = parse_exp (args, &term);
  if (*p++ != ',')
    {
      error (_("bad intruction syntax"));
      return p;
    }

  if ((term.X_md) || (term.X_op != O_register))
    ill_op ();
  else
    switch (term.X_add_number)
      {
      case REG_A:
	p = emit_s (0, prefix, p);
	break;
      case REG_HL:
	p = parse_exp (p, &term);
	if ((!term.X_md) && (term.X_op == O_register))
	  {
	    rnum = term.X_add_number;
	    if (R_ARITH == (rnum & (R_ARITH | R_INDEX)))
	      {
		q = frag_more (2);
		*q++ = 0xED;
		*q = opcode + ((rnum & 3) << 4);
		break;
	      }
	  }
	/* Fall through.  */
      default:
	ill_op ();
      }
  return p;
}

static const char *
emit_add (char prefix, char opcode, const char * args)
{
  expressionS term;
  int lhs, rhs;
  const char *p;
  char *q;

  p = parse_exp (args, &term);
  if (*p++ != ',')
    {
      error (_("bad intruction syntax"));
      return p;
    }

  if ((term.X_md) || (term.X_op != O_register))
    ill_op ();
  else
    switch (term.X_add_number & ~R_INDEX)
      {
      case REG_A:
	p = emit_s (0, prefix, p);
	break;
      case REG_HL:
	lhs = term.X_add_number;
	p = parse_exp (p, &term);
	if ((!term.X_md) && (term.X_op == O_register))
	  {
	    rhs = term.X_add_number;
	    if ((rhs & R_ARITH)
		&& ((rhs == lhs) || ((rhs & ~R_INDEX) != REG_HL)))
	      {
		q = frag_more ((lhs & R_INDEX) ? 2 : 1);
		if (lhs & R_INDEX)
		  *q++ = (lhs & R_IX) ? 0xDD : 0xFD;
		*q = opcode + ((rhs & 3) << 4);
		break;
	      }
	  }
	/* Fall through.  */
      default:
	ill_op ();
      }
  return p;
}

static const char *
emit_bit (char prefix, char opcode, const char * args)
{
  expressionS b;
  int bn;
  const char *p;

  p = parse_exp (args, &b);
  if (*p++ != ',')
    error (_("bad intruction syntax"));

  bn = b.X_add_number;
  if ((!b.X_md)
      && (b.X_op == O_constant)
      && (0 <= bn)
      && (bn < 8))
    {
      if (opcode == 0x40)
	/* Bit : no optional third operand.  */
	p = emit_m (prefix, opcode + (bn << 3), p);
      else
	/* Set, res : resulting byte can be copied to register.  */
	p = emit_mr (prefix, opcode + (bn << 3), p);
    }
  else
    ill_op ();
  return p;
}

static const char *
emit_jpcc (char prefix, char opcode, const char * args)
{
  char cc;
  const char *p;

  p = parse_cc (args, & cc);
  if (p && *p++ == ',')
    p = emit_call (0, opcode + cc, p);
  else
    p = (prefix == (char)0xC3)
      ? emit_jp (0xE9, prefix, args)
      : emit_call (0, prefix, args);
  return p;
}

static const char *
emit_jrcc (char prefix, char opcode, const char * args)
{
  char cc;
  const char *p;

  p = parse_cc (args, &cc);
  if (p && *p++ == ',')
    {
      if (cc > (3 << 3))
	error (_("condition code invalid for jr"));
      else
	p = emit_jr (0, opcode + cc, p);
    }
  else
    p = emit_jr (0, prefix, args);

  return p;
}

static const char *
emit_ex (char prefix_in ATTRIBUTE_UNUSED,
	 char opcode_in ATTRIBUTE_UNUSED, const char * args)
{
  expressionS op;
  const char * p;
  char prefix, opcode;

  p = parse_exp (args, &op);
  p = skip_space (p);
  if (*p++ != ',')
    {
      error (_("bad instruction syntax"));
      return p;
    }

  prefix = opcode = 0;
  if (op.X_op == O_register)
    switch (op.X_add_number | (op.X_md ? 0x8000 : 0))
      {
      case REG_AF:
	if (TOLOWER (*p++) == 'a' && TOLOWER (*p++) == 'f')
	  {
	    /* The scrubber changes '\'' to '`' in this context.  */
	    if (*p == '`')
	      ++p;
	    opcode = 0x08;
	  }
	break;
      case REG_DE:
	if (TOLOWER (*p++) == 'h' && TOLOWER (*p++) == 'l')
	  opcode = 0xEB;
	break;
      case REG_SP|0x8000:
	p = parse_exp (p, & op);
	if (op.X_op == O_register
	    && op.X_md == 0
	    && (op.X_add_number & ~R_INDEX) == REG_HL)
	  {
	    opcode = 0xE3;
	    if (R_INDEX & op.X_add_number)
	      prefix = (R_IX & op.X_add_number) ? 0xDD : 0xFD;
	  }
	break;
      }
  if (opcode)
    emit_insn (prefix, opcode, p);
  else
    ill_op ();

  return p;
}

static const char *
emit_in (char prefix ATTRIBUTE_UNUSED, char opcode ATTRIBUTE_UNUSED,
	const char * args)
{
  expressionS reg, port;
  const char *p;
  char *q;

  p = parse_exp (args, &reg);
  if (*p++ != ',')
    {
      error (_("bad intruction syntax"));
      return p;
    }

  p = parse_exp (p, &port);
  if (reg.X_md == 0
      && reg.X_op == O_register
      && (reg.X_add_number <= 7 || reg.X_add_number == REG_F)
      && (port.X_md))
    {
      if (port.X_op != O_md1 && port.X_op != O_register)
	{
	  if (REG_A == reg.X_add_number)
	    {
	      q = frag_more (1);
	      *q = 0xDB;
	      emit_byte (&port, BFD_RELOC_8);
	    }
	  else
	    ill_op ();
	}
      else
	{
	  if (port.X_add_number == REG_C)
	    {
	      if (reg.X_add_number == REG_F)
		check_mach (INS_UNDOC);
	      else
		{
		  q = frag_more (2);
		  *q++ = 0xED;
		  *q = 0x40|((reg.X_add_number&7)<<3);
		}
	    }
	  else
	    ill_op ();
	}
    }
  else
    ill_op ();
  return p;
}

static const char *
emit_out (char prefix ATTRIBUTE_UNUSED, char opcode ATTRIBUTE_UNUSED,
	 const char * args)
{
  expressionS reg, port;
  const char *p;
  char *q;

  p = parse_exp (args, & port);
  if (*p++ != ',')
    {
      error (_("bad intruction syntax"));
      return p;
    }
  p = parse_exp (p, &reg);
  if (!port.X_md)
    { ill_op (); return p; }
  /* Allow "out (c), 0" as unportable instruction.  */
  if (reg.X_op == O_constant && reg.X_add_number == 0)
    {
      check_mach (INS_UNPORT);
      reg.X_op = O_register;
      reg.X_add_number = 6;
    }
  if (reg.X_md
      || reg.X_op != O_register
      || reg.X_add_number > 7)
    ill_op ();
  else
    if (port.X_op != O_register && port.X_op != O_md1)
      {
	if (REG_A == reg.X_add_number)
	  {
	    q = frag_more (1);
	    *q = 0xD3;
	    emit_byte (&port, BFD_RELOC_8);
	  }
	else
	  ill_op ();
      }
    else
      {
	if (REG_C == port.X_add_number)
	  {
	    q = frag_more (2);
	    *q++ = 0xED;
	    *q = 0x41 | (reg.X_add_number << 3);
	  }
	else
	  ill_op ();
      }
  return p;
}

static const char *
emit_rst (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  expressionS addr;
  const char *p;
  char *q;

  p = parse_exp (args, &addr);
  if (addr.X_op != O_constant)
    {
      error ("rst needs constant address");
      return p;
    }

  if (addr.X_add_number & ~(7 << 3))
    ill_op ();
  else
    {
      q = frag_more (1);
      *q = opcode + (addr.X_add_number & (7 << 3));
    }
  return p;
}

static void
emit_ldxhl (char prefix, char opcode, expressionS *src, expressionS *d)
{
  char *q;

  if (src->X_md)
    ill_op ();
  else
    {
      if (src->X_op == O_register)
	{
	  if (src->X_add_number>7)
	    ill_op ();
	  if (prefix)
	    {
	      q = frag_more (2);
	      *q++ = prefix;
	    }
	  else
	q = frag_more (1);
	  *q = opcode + src->X_add_number;
	  if (d)
	    emit_byte (d, BFD_RELOC_Z80_DISP8);
	}
      else
	{
	  if (prefix)
	    {
	      q = frag_more (2);
	      *q++ = prefix;
	    }
	  else
	    q = frag_more (1);
	  *q = opcode^0x46;
	  if (d)
	    emit_byte (d, BFD_RELOC_Z80_DISP8);
	  emit_byte (src, BFD_RELOC_8);
	}
    }
}

static void
emit_ldreg (int dest, expressionS * src)
{
  char *q;
  int rnum;

  switch (dest)
    {
      /* 8 Bit ld group:  */
    case REG_I:
    case REG_R:
      if (src->X_md == 0 && src->X_op == O_register && src->X_add_number == REG_A)
	{
	  q = frag_more (2);
	  *q++ = 0xED;
	  *q = (dest == REG_I) ? 0x47 : 0x4F;
	}
      else
	ill_op ();
      break;

    case REG_A:
      if ((src->X_md) && src->X_op != O_register && src->X_op != O_md1)
	{
	  q = frag_more (1);
	  *q = 0x3A;
	  emit_word (src);
	  break;
	}

      if ((src->X_md)
	  && src->X_op == O_register
	  && (src->X_add_number == REG_BC || src->X_add_number == REG_DE))
	{
	  q = frag_more (1);
	  *q = 0x0A + ((dest & 1) << 4);
	  break;
	}

      if ((!src->X_md)
	  && src->X_op == O_register
	  && (src->X_add_number == REG_R || src->X_add_number == REG_I))
	{
	  q = frag_more (2);
	  *q++ = 0xED;
	  *q = (src->X_add_number == REG_I) ? 0x57 : 0x5F;
	  break;
	}
      /* Fall through.  */
    case REG_B:
    case REG_C:
    case REG_D:
    case REG_E:
      emit_sx (0, 0x40 + (dest << 3), src);
      break;

    case REG_H:
    case REG_L:
      if ((src->X_md == 0)
	  && (src->X_op == O_register)
	  && (src->X_add_number & R_INDEX))
	ill_op ();
      else
	emit_sx (0, 0x40 + (dest << 3), src);
      break;

    case R_IX | REG_H:
    case R_IX | REG_L:
    case R_IY | REG_H:
    case R_IY | REG_L:
      if (src->X_md)
	{
	  ill_op ();
	  break;
	}
      check_mach (INS_UNDOC);
      if (src-> X_op == O_register)
	{
	  rnum = src->X_add_number;
	  if ((rnum & ~R_INDEX) < 8
	      && ((rnum & R_INDEX) == (dest & R_INDEX)
		   || (   (rnum & ~R_INDEX) != REG_H
		       && (rnum & ~R_INDEX) != REG_L)))
	    {
	      q = frag_more (2);
	      *q++ = (dest & R_IX) ? 0xDD : 0xFD;
	      *q = 0x40 + ((dest & 0x07) << 3) + (rnum & 7);
	    }
	  else
	    ill_op ();
	}
      else
	{
	  q = frag_more (2);
	  *q++ = (dest & R_IX) ? 0xDD : 0xFD;
	  *q = 0x06 + ((dest & 0x07) << 3);
	  emit_byte (src, BFD_RELOC_8);
	}
      break;

      /* 16 Bit ld group:  */
    case REG_SP:
      if (src->X_md == 0
	  && src->X_op == O_register
	  && REG_HL == (src->X_add_number &~ R_INDEX))
	{
	  q = frag_more ((src->X_add_number & R_INDEX) ? 2 : 1);
	  if (src->X_add_number & R_INDEX)
	    *q++ = (src->X_add_number & R_IX) ? 0xDD : 0xFD;
	  *q = 0xF9;
	  break;
	}
      /* Fall through.  */
    case REG_BC:
    case REG_DE:
      if (src->X_op == O_register || src->X_op == O_md1)
	ill_op ();
      q = frag_more (src->X_md ? 2 : 1);
      if (src->X_md)
	{
	  *q++ = 0xED;
	  *q = 0x4B + ((dest & 3) << 4);
	}
      else
	*q = 0x01 + ((dest & 3) << 4);
      emit_word (src);
      break;

    case REG_HL:
    case REG_HL | R_IX:
    case REG_HL | R_IY:
      if (src->X_op == O_register || src->X_op == O_md1)
	ill_op ();
      q = frag_more ((dest & R_INDEX) ? 2 : 1);
      if (dest & R_INDEX)
	* q ++ = (dest & R_IX) ? 0xDD : 0xFD;
      *q = (src->X_md) ? 0x2A : 0x21;
      emit_word (src);
      break;

    case REG_AF:
    case REG_F:
      ill_op ();
      break;

    default:
      abort ();
    }
}

static const char *
emit_ld (char prefix_in ATTRIBUTE_UNUSED, char opcode_in ATTRIBUTE_UNUSED,
	const char * args)
{
  expressionS dst, src;
  const char *p;
  char *q;
  char prefix, opcode;

  p = parse_exp (args, &dst);
  if (*p++ != ',')
    error (_("bad intruction syntax"));
  p = parse_exp (p, &src);

  switch (dst.X_op)
    {
    case O_md1:
      emit_ldxhl ((dst.X_add_number & R_IX) ? 0xDD : 0xFD, 0x70,
		  &src, symbol_get_value_expression (dst.X_add_symbol));
      break;

    case O_register:
      if (dst.X_md)
	{
	  switch (dst.X_add_number)
	    {
	    case REG_BC:
	    case REG_DE:
	      if (src.X_md == 0 && src.X_op == O_register && src.X_add_number == REG_A)
		{
		  q = frag_more (1);
		  *q = 0x02 + ( (dst.X_add_number & 1) << 4);
		}
	      else
		ill_op ();
	      break;
	    case REG_HL:
	      emit_ldxhl (0, 0x70, &src, NULL);
	      break;
	    default:
	      ill_op ();
	    }
	}
      else
	emit_ldreg (dst.X_add_number, &src);
      break;

    default:
      if (src.X_md != 0 || src.X_op != O_register)
	ill_op ();
      prefix = opcode = 0;
      switch (src.X_add_number)
	{
	case REG_A:
	  opcode = 0x32; break;
	case REG_BC: case REG_DE: case REG_SP:
	  prefix = 0xED; opcode = 0x43 + ((src.X_add_number&3)<<4); break;
	case REG_HL:
	  opcode = 0x22; break;
	case REG_HL|R_IX:
	  prefix = 0xDD; opcode = 0x22; break;
	case REG_HL|R_IY:
	  prefix = 0xFD; opcode = 0x22; break;
	}
      if (opcode)
	{
	  q = frag_more (prefix?2:1);
	  if (prefix)
	    *q++ = prefix;
	  *q = opcode;
	  emit_word (&dst);
	}
      else
	ill_op ();
    }
  return p;
}

static const char *
emit_data (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  const char *p, *q;
  char *u, quote;
  int cnt;
  expressionS exp;

  p = skip_space (args);
  if (!*p)
    error (_("missing operand"));

  while (*p)
    {
      if (*p == '\"' || *p == '\'')
	{
	  if (opcode == 1)
	    {
	      for (quote = *p, q = ++p, cnt = 0; *p && quote != *p; ++p, ++cnt)
		;
	      u = frag_more (cnt);
	      memcpy (u, q, cnt);
	      if (!*p)
		as_warn (_("unterminated string"));
	      else
		p = skip_space (p+1);
	    }
	  else
	    {
	      ill_op ();
	      break;
	    }
	}
      else
	{
	  p = parse_exp (p, &exp);
	  if (exp.X_op == O_md1 || exp.X_op == O_register)
	    {
	      ill_op ();
	      break;
	    }
	  if (exp.X_md)
	    as_warn (_("parentheses ignored"));
	  if (opcode == 1)
	    emit_byte (&exp, BFD_RELOC_8);
	  else
	    emit_word (&exp);
	  p = skip_space (p);
	}
      if (*p)
	{
	  if (*p != ',')
	    as_warn (_("missing ','"));
	  else
	    ++p;
	}
    }
  return p;
}

static const char *
emit_mulub (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  const char *p;

  p = skip_space (args);
  if (TOLOWER (*p++) != 'a' || *p++ != ',')
    ill_op ();
  else
    {
      char *q, reg;

      reg = TOLOWER (*p++);
      switch (reg)
	{
	case 'b':
	case 'c':
	case 'd':
	case 'e':
	  check_mach (INS_R800);
	  if (!*skip_space (p))
	    {
	      q = frag_more (2);
	      *q++ = prefix;
	      *q = opcode + ((reg - 'b') << 3);
	      break;
	    }
	default:
	  ill_op ();
	}
    }
  return p;
}

static const char *
emit_muluw (char prefix ATTRIBUTE_UNUSED, char opcode, const char * args)
{
  const char *p;

  p = skip_space (args);
  if (TOLOWER (*p++) != 'h' || TOLOWER (*p++) != 'l' || *p++ != ',')
    ill_op ();
  else
    {
      expressionS reg;
      char *q;

      p = parse_exp (p, & reg);

      if ((!reg.X_md) && reg.X_op == O_register)
	switch (reg.X_add_number)
	  {
	  case REG_BC:
	  case REG_SP:
	    check_mach (INS_R800);
	    q = frag_more (2);
	    *q++ = prefix;
	    *q = opcode + ((reg.X_add_number & 3) << 4);
	    break;
	  default:
	    ill_op ();
	  }
    }
  return p;
}

static table_t instab[] =
{
  { "adc",  0x88, 0x4A, emit_adc },
  { "add",  0x80, 0x09, emit_add },
  { "and",  0x00, 0xA0, emit_s },
  { "bit",  0xCB, 0x40, emit_bit },
  { "call", 0xCD, 0xC4, emit_jpcc },
  { "ccf",  0x00, 0x3F, emit_insn },
  { "cp",   0x00, 0xB8, emit_s },
  { "cpd",  0xED, 0xA9, emit_insn },
  { "cpdr", 0xED, 0xB9, emit_insn },
  { "cpi",  0xED, 0xA1, emit_insn },
  { "cpir", 0xED, 0xB1, emit_insn },
  { "cpl",  0x00, 0x2F, emit_insn },
  { "daa",  0x00, 0x27, emit_insn },
  { "db",   0x00, 0x01, emit_data },
  { "dec",  0x0B, 0x05, emit_incdec },
  { "defb", 0x00, 0x01, emit_data },
  { "defw", 0x00, 0x02, emit_data },
  { "di",   0x00, 0xF3, emit_insn },
  { "djnz", 0x00, 0x10, emit_jr },
  { "dw",   0x00, 0x02, emit_data },
  { "ei",   0x00, 0xFB, emit_insn },
  { "ex",   0x00, 0x00, emit_ex},
  { "exx",  0x00, 0xD9, emit_insn },
  { "halt", 0x00, 0x76, emit_insn },
  { "im",   0xED, 0x46, emit_im },
  { "in",   0x00, 0x00, emit_in },
  { "inc",  0x03, 0x04, emit_incdec },
  { "ind",  0xED, 0xAA, emit_insn },
  { "indr", 0xED, 0xBA, emit_insn },
  { "ini",  0xED, 0xA2, emit_insn },
  { "inir", 0xED, 0xB2, emit_insn },
  { "jp",   0xC3, 0xC2, emit_jpcc },
  { "jr",   0x18, 0x20, emit_jrcc },
  { "ld",   0x00, 0x00, emit_ld },
  { "ldd",  0xED, 0xA8, emit_insn },
  { "lddr", 0xED, 0xB8, emit_insn },
  { "ldi",  0xED, 0xA0, emit_insn },
  { "ldir", 0xED, 0xB0, emit_insn },
  { "mulub", 0xED, 0xC5, emit_mulub }, /* R800 only.  */
  { "muluw", 0xED, 0xC3, emit_muluw }, /* R800 only.  */
  { "neg",  0xed, 0x44, emit_insn },
  { "nop",  0x00, 0x00, emit_insn },
  { "or",   0x00, 0xB0, emit_s },
  { "otdr", 0xED, 0xBB, emit_insn },
  { "otir", 0xED, 0xB3, emit_insn },
  { "out",  0x00, 0x00, emit_out },
  { "outd", 0xED, 0xAB, emit_insn },
  { "outi", 0xED, 0xA3, emit_insn },
  { "pop",  0x00, 0xC1, emit_pop },
  { "push", 0x00, 0xC5, emit_pop },
  { "res",  0xCB, 0x80, emit_bit },
  { "ret",  0xC9, 0xC0, emit_retcc },
  { "reti", 0xED, 0x4D, emit_insn },
  { "retn", 0xED, 0x45, emit_insn },
  { "rl",   0xCB, 0x10, emit_mr },
  { "rla",  0x00, 0x17, emit_insn },
  { "rlc",  0xCB, 0x00, emit_mr },
  { "rlca", 0x00, 0x07, emit_insn },
  { "rld",  0xED, 0x6F, emit_insn },
  { "rr",   0xCB, 0x18, emit_mr },
  { "rra",  0x00, 0x1F, emit_insn },
  { "rrc",  0xCB, 0x08, emit_mr },
  { "rrca", 0x00, 0x0F, emit_insn },
  { "rrd",  0xED, 0x67, emit_insn },
  { "rst",  0x00, 0xC7, emit_rst},
  { "sbc",  0x98, 0x42, emit_adc },
  { "scf",  0x00, 0x37, emit_insn },
  { "set",  0xCB, 0xC0, emit_bit },
  { "sla",  0xCB, 0x20, emit_mr },
  { "sli",  0xCB, 0x30, emit_mr },
  { "sll",  0xCB, 0x30, emit_mr },
  { "sra",  0xCB, 0x28, emit_mr },
  { "srl",  0xCB, 0x38, emit_mr },
  { "sub",  0x00, 0x90, emit_s },
  { "xor",  0x00, 0xA8, emit_s },
} ;

void
md_assemble (char* str)
{
  const char *p;
  char * old_ptr;
  int i;
  table_t *insp;

  err_flag = 0;
  old_ptr = input_line_pointer;
  p = skip_space (str);
  for (i = 0; (i < BUFLEN) && (ISALPHA (*p));)
    buf[i++] = TOLOWER (*p++);

  if ((i == BUFLEN)
      || ((*p) && (!ISSPACE (*p))))
    as_bad (_("illegal instruction '%s'"), buf);

  buf[i] = 0;
  p = skip_space (p);
  key = buf;

  insp = bsearch (&key, instab, ARRAY_SIZE (instab),
		  sizeof (instab[0]), key_cmp);
  if (!insp)
    as_bad (_("illegal instruction '%s'"), buf);
  else
    {
      p = insp->fp (insp->prefix, insp->opcode, p);
      p = skip_space (p);
      if ((!err_flag) && *p)
	as_bad (_("junk at end of line, first unrecognized character is `%c'"),
		*p);
    }
  input_line_pointer = old_ptr;
}

void
md_apply_fix (fixS * fixP, valueT* valP, segT seg ATTRIBUTE_UNUSED)
{
  long val = * (long *) valP;
  char *buf = fixP->fx_where + fixP->fx_frag->fr_literal;

  switch (fixP->fx_r_type)
    {
    case BFD_RELOC_8_PCREL:
      if (fixP->fx_addsy)
        {
          fixP->fx_no_overflow = 1;
          fixP->fx_done = 0;
        }
      else
        {
	  fixP->fx_no_overflow = (-128 <= val && val < 128);
	  if (!fixP->fx_no_overflow)
            as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("relative jump out of range"));
	  *buf++ = val;
          fixP->fx_done = 1;
        }
      break;

    case BFD_RELOC_Z80_DISP8:
      if (fixP->fx_addsy)
        {
          fixP->fx_no_overflow = 1;
          fixP->fx_done = 0;
        }
      else
        {
	  fixP->fx_no_overflow = (-128 <= val && val < 128);
	  if (!fixP->fx_no_overflow)
            as_bad_where (fixP->fx_file, fixP->fx_line,
			  _("index offset  out of range"));
	  *buf++ = val;
          fixP->fx_done = 1;
        }
      break;

    case BFD_RELOC_8:
      if (val > 255 || val < -128)
	as_warn_where (fixP->fx_file, fixP->fx_line, _("overflow"));
      *buf++ = val;
      if (fixP->fx_addsy == NULL)
	fixP->fx_done = 1;
      break;

    case BFD_RELOC_16:
      *buf++ = val;
      *buf++ = (val >> 8);
      if (fixP->fx_addsy == NULL)
	fixP->fx_done = 1;
      break;

    case BFD_RELOC_32: /* .Long may produce this.  */
      *buf++ = val;
      *buf++ = (val >> 8);
      *buf++ = (val >> 16);
      *buf++ = (val >> 24);
      if (fixP->fx_addsy == NULL)
	fixP->fx_done = 1;
      break;

    default:
      printf (_("md_apply_fix: unknown r_type 0x%x\n"), fixP->fx_r_type);
      abort ();
    }
}

/* GAS will call this to generate a reloc.  GAS will pass the
   resulting reloc to `bfd_install_relocation'.  This currently works
   poorly, as `bfd_install_relocation' often does the wrong thing, and
   instances of `tc_gen_reloc' have been written to work around the
   problems, which in turns makes it difficult to fix
   `bfd_install_relocation'.  */

/* If while processing a fixup, a reloc really
   needs to be created then it is done here.  */

arelent *
tc_gen_reloc (asection *seg ATTRIBUTE_UNUSED , fixS *fixp)
{
  arelent *reloc;

  if (! bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type))
    {
      as_bad_where (fixp->fx_file, fixp->fx_line,
		    _("reloc %d not supported by object file format"),
		    (int) fixp->fx_r_type);
      return NULL;
    }

  reloc               = xmalloc (sizeof (arelent));
  reloc->sym_ptr_ptr  = xmalloc (sizeof (asymbol *));
  *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
  reloc->address      = fixp->fx_frag->fr_address + fixp->fx_where;
  reloc->howto        = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
  reloc->addend       = fixp->fx_offset;

  return reloc;
}

