/* tc-z8k.c -- Assemble code for the Zilog Z800n
   Copyright (C) 1992, 93, 94, 95, 96, 1997 Free Software Foundation.

   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, 59 Temple Place - Suite 330, Boston, MA
   02111-1307, USA.  */

/*
  Written By Steve Chamberlain
  sac@cygnus.com
  */
#define DEFINE_TABLE
#include <stdio.h>

#include "opcodes/z8k-opc.h"

#include "as.h"
#include "bfd.h"
#include <ctype.h>

const char comment_chars[] =
{'!', 0};
const char line_separator_chars[] =
{';', 0};
const char line_comment_chars[] =
{'#', 0};

extern int machine;
extern int coff_flags;
int segmented_mode;
const int md_reloc_size;

/* This table describes all the machine specific pseudo-ops the assembler
   has to support.  The fields are:
   pseudo-op name without dot
   function to call to execute this pseudo-op
   Integer arg to pass to the function
   */

void cons ();

void
s_segm ()
{
  segmented_mode = 1;
  machine = bfd_mach_z8001;
  coff_flags = F_Z8001;
}

void
s_unseg ()
{
  segmented_mode = 0;
  machine = bfd_mach_z8002;
  coff_flags = F_Z8002;
}

static
void
even ()
{
  frag_align (1, 0, 0);
  record_alignment (now_seg, 1);
}

void obj_coff_section ();

int
tohex (c)
     int c;
{
  if (isdigit (c))
    return c - '0';
  if (islower (c))
    return c - 'a' + 10;
  return c - 'A' + 10;
}

void
sval ()
{

  SKIP_WHITESPACE ();
  if (*input_line_pointer == '\'')
    {
      int c;
      input_line_pointer++;
      c = *input_line_pointer++;
      while (c != '\'')
	{
	  if (c == '%')
	    {
	      c = (tohex (input_line_pointer[0]) << 4)
		| tohex (input_line_pointer[1]);
	      input_line_pointer += 2;
	    }
	  FRAG_APPEND_1_CHAR (c);
	  c = *input_line_pointer++;
	}
      demand_empty_rest_of_line ();
    }

}
const pseudo_typeS md_pseudo_table[] =
{
  {"int", cons, 2},
  {"data.b", cons, 1},
  {"data.w", cons, 2},
  {"data.l", cons, 4},
  {"form", listing_psize, 0},
  {"heading", listing_title, 0},
  {"import", s_ignore, 0},
  {"page", listing_eject, 0},
  {"program", s_ignore, 0},
  {"z8001", s_segm, 0},
  {"z8002", s_unseg, 0},


  {"segm", s_segm, 0},
  {"unsegm", s_unseg, 0},
  {"unseg", s_unseg, 0},
  {"name", s_app_file, 0},
  {"global", s_globl, 0},
  {"wval", cons, 2},
  {"lval", cons, 4},
  {"bval", cons, 1},
  {"sval", sval, 0},
  {"rsect", obj_coff_section, 0},
  {"sect", obj_coff_section, 0},
  {"block", s_space, 0},
  {"even", even, 0},
  {0, 0, 0}
};

const char EXP_CHARS[] = "eE";

/* Chars that mean this number is a floating point constant */
/* As in 0f12.456 */
/* or    0d1.2345e12 */
const char FLT_CHARS[] = "rRsSfFdDxXpP";

static struct hash_control *opcode_hash_control;	/* Opcode mnemonics */

void
md_begin ()
{
  opcode_entry_type *opcode;
  char *prev_name = "";
  int idx = 0;

  opcode_hash_control = hash_new ();

  for (opcode = z8k_table; opcode->name; opcode++)
    {
      /* Only enter unique codes into the table */
      char *src = opcode->name;

      if (strcmp (opcode->name, prev_name))
	{
	  hash_insert (opcode_hash_control, opcode->name, (char *) opcode);
	  idx++;
	}
      opcode->idx = idx;
      prev_name = opcode->name;
    }

  /* default to z8002 */
  s_unseg ();

  /* insert the pseudo ops too */
  for (idx = 0; md_pseudo_table[idx].poc_name; idx++)
    {
      opcode_entry_type *fake_opcode;
      fake_opcode = (opcode_entry_type *) malloc (sizeof (opcode_entry_type));
      fake_opcode->name = md_pseudo_table[idx].poc_name,
	fake_opcode->func = (void *) (md_pseudo_table + idx);
      fake_opcode->opcode = 250;
      hash_insert (opcode_hash_control, fake_opcode->name, fake_opcode);
    }

  linkrelax = 1;
}

struct z8k_exp
{
  char *e_beg;
  char *e_end;
  expressionS e_exp;
};
typedef struct z8k_op
{
  char regsize;			/* 'b','w','r','q' */
  unsigned int reg;		/* 0..15 */

  int mode;

  unsigned int x_reg;		/* any other register associated with the mode */
  expressionS exp;		/* any expression */
}

op_type;

static expressionS *da_operand;
static expressionS *imm_operand;

int reg[16];
int the_cc;
int the_ctrl;
int the_flags;
int the_interrupt;

char *
DEFUN (whatreg, (reg, src),
       int *reg AND
       char *src)
{
  if (isdigit (src[1]))
    {
      *reg = (src[0] - '0') * 10 + src[1] - '0';
      return src + 2;
    }
  else
    {
      *reg = (src[0] - '0');
      return src + 1;
    }
}

/*
  parse operands

  rh0-rh7, rl0-rl7
  r0-r15
  rr0-rr14
  rq0--rq12
  WREG r0,r1,r2,r3,r4,r5,r6,r7,fp,sp
  r0l,r0h,..r7l,r7h
  @WREG
  @WREG+
  @-WREG
  #const

  */

/* try and parse a reg name, returns number of chars consumed */
char *
DEFUN (parse_reg, (src, mode, reg),
       char *src AND
       int *mode AND
       unsigned int *reg)
{
  char *res = 0;
  char regno;

  if (src[0] == 's' && src[1] == 'p')
    {
      if (segmented_mode)
        {
          *mode = CLASS_REG_LONG;
          *reg = 14;
        }
      else
        {
          *mode = CLASS_REG_WORD;
          *reg = 15;
        }
      return src + 2;
    }
  if (src[0] == 'r')
    {
      if (src[1] == 'r')
        {
          *mode = CLASS_REG_LONG;
          res = whatreg (reg, src + 2);
	  regno = *reg;
	  if (regno > 14)
          	as_warn ("register rr%d, out of range.",regno);
        }
      else if (src[1] == 'h')
        {
          *mode = CLASS_REG_BYTE;
          res = whatreg (reg, src + 2);
	  regno = *reg;
	  if (regno > 7)
          	as_warn ("register rh%d, out of range.",regno);
        }
      else if (src[1] == 'l')
        {
          *mode = CLASS_REG_BYTE;
          res = whatreg (reg, src + 2);
	  regno = *reg;
	  if (regno > 7)
          	as_warn ("register rl%d, out of range.",regno);
          *reg += 8;
        }
      else if (src[1] == 'q')
        {
          *mode = CLASS_REG_QUAD;
          res = whatreg (reg, src + 2);
	  regno = *reg;
	  if (regno > 12)
          	as_warn ("register rq%d, out of range.",regno);
        }
      else
        {
          *mode = CLASS_REG_WORD;
          res = whatreg (reg, src + 1);
	  regno = *reg;
	  if (regno > 15)
          	as_warn ("register r%d, out of range.",regno);
        }
    }
  return res;

}

char *
DEFUN (parse_exp, (s, op),
       char *s AND
       expressionS * op)
{
  char *save = input_line_pointer;
  char *new;

  input_line_pointer = s;
  expression (op);
  if (op->X_op == O_absent)
    as_bad ("missing operand");
  new = input_line_pointer;
  input_line_pointer = save;
  return new;
}

/* The many forms of operand:

   <rb>
   <r>
   <rr>
   <rq>
   @r
   #exp
   exp
   exp(r)
   r(#exp)
   r(r)



   */

static
char *
DEFUN (checkfor, (ptr, what),
       char *ptr AND
       char what)
{
  if (*ptr == what)
    ptr++;
  else
    {
      as_bad ("expected %c", what);
    }
  return ptr;
}

/* Make sure the mode supplied is the size of a word */
static void
DEFUN (regword, (mode, string),
       int mode AND
       char *string)
{
  int ok;

  ok = CLASS_REG_WORD;
  if (ok != mode)
    {
      as_bad ("register is wrong size for a word %s", string);
    }
}

/* Make sure the mode supplied is the size of an address */
static void
DEFUN (regaddr, (mode, string),
       int mode AND
       char *string)
{
  int ok;

  ok = segmented_mode ? CLASS_REG_LONG : CLASS_REG_WORD;
  if (ok != mode)
    {
      as_bad ("register is wrong size for address %s", string);
    }
}

struct ctrl_names
{
   int value;
   char *name;
};

struct ctrl_names ctrl_table[] =
{
   0x2, "fcw",
   0X3, "refresh",
   0x4, "psapseg",
   0x5, "psapoff",
   0x5, "psap",
   0x6, "nspseg",
   0x7, "nspoff",
   0x7, "nsp",
   0, 0
};
   
static void
DEFUN (get_ctrl_operand, (ptr, mode, dst),
       char **ptr AND
       struct z8k_op *mode AND
       unsigned int dst)
{
  char *src = *ptr;
  int r;
  int i;

  while (*src == ' ')
    src++;

  mode->mode = CLASS_CTRL;
  for (i = 0; ctrl_table[i].name; i++)
    {
      int j;

      for (j = 0; ctrl_table[i].name[j]; j++)
        {
          if (ctrl_table[i].name[j] != src[j])
            goto fail;
        }
      the_ctrl = ctrl_table[i].value;
      *ptr = src + j;
      return;
    fail:;
    }
  the_ctrl = 0;
  return;
}

struct flag_names
{
  int value;
  char *name;

};

struct flag_names flag_table[] =
{
  0x1, "p",
  0x1, "v",
  0x2, "s",
  0x4, "z",
  0x8, "c",
  0x0, "+",
  0, 0
};

static void
DEFUN (get_flags_operand, (ptr, mode, dst),
       char **ptr AND
       struct z8k_op *mode AND
       unsigned int dst)
{
  char *src = *ptr;
  int r;
  int i;
  int j;

  while (*src == ' ')
    src++;

  mode->mode = CLASS_FLAGS;
  the_flags = 0;
  for (j = 0; j <= 9; j++)
    {
     if (!src[j])
	goto done;
     for (i = 0; flag_table[i].name; i++)
        {
          if (flag_table[i].name[0] == src[j])
		{
	        the_flags = the_flags | flag_table[i].value;
		goto match;
		}
        }
      goto done;
    match:
     ;
    }
  done:
  *ptr = src + j;
  return;
}


struct interrupt_names
{
  int value;
  char *name;

};

struct interrupt_names intr_table[] =
{
  0x1, "nvi",
  0x2, "vi",
  0x3, "both",
  0x3, "all",
  0, 0
};

static void
DEFUN (get_interrupt_operand, (ptr, mode, dst),
       char **ptr AND
       struct z8k_op *mode AND
       unsigned int dst)
{
  char *src = *ptr;
  int r;
  int i;

  while (*src == ' ')
    src++;

  mode->mode = CLASS_IMM;
  for (i = 0; intr_table[i].name; i++)
    {
      int j;

      for (j = 0; intr_table[i].name[j]; j++)
        {
          if (intr_table[i].name[j] != src[j])
            goto fail;
        }
      the_interrupt = intr_table[i].value;
      *ptr = src + j;
      return;
    fail:;
    }
  the_interrupt = 0x0;
  return;
}

struct cc_names
{
  int value;
  char *name;

};

struct cc_names table[] =
{
  0x0, "f",
  0x1, "lt",
  0x2, "le",
  0x3, "ule",
  0x4, "ov",
  0x4, "pe",
  0x5, "mi",
  0x6, "eq",
  0x6, "z",
  0x7, "c",
  0x7, "ult",
  0x8, "t",
  0x9, "ge",
  0xa, "gt",
  0xb, "ugt",
  0xc, "nov",
  0xc, "po",
  0xd, "pl",
  0xe, "ne",
  0xe, "nz",
  0xf, "nc",
  0xf, "uge",
  0, 0
};

static void
DEFUN (get_cc_operand, (ptr, mode, dst),
       char **ptr AND
       struct z8k_op *mode AND
       unsigned int dst)
{
  char *src = *ptr;
  int r;
  int i;

  while (*src == ' ')
    src++;

  mode->mode = CLASS_CC;
  for (i = 0; table[i].name; i++)
    {
      int j;

      for (j = 0; table[i].name[j]; j++)
	{
	  if (table[i].name[j] != src[j])
	    goto fail;
	}
      the_cc = table[i].value;
      *ptr = src + j;
      return;
    fail:;
    }
  the_cc = 0x8;
}

static void
get_operand (ptr, mode, dst)
     char **ptr;
     struct z8k_op *mode;
     unsigned int dst;
{
  char *src = *ptr;
  char *end;
  unsigned int num;
  unsigned int len;
  unsigned int size;

  mode->mode = 0;

  while (*src == ' ')
    src++;
  if (*src == '#')
    {
      mode->mode = CLASS_IMM;
      imm_operand = &(mode->exp);
      src = parse_exp (src + 1, &(mode->exp));
    }
  else if (*src == '@')
    {
      int d;

      mode->mode = CLASS_IR;
      src = parse_reg (src + 1, &d, &mode->reg);
    }
  else
    {
      int regn;

      end = parse_reg (src, &mode->mode, &regn);

      if (end)
	{
	  int nw, nr;

	  src = end;
	  if (*src == '(')
	    {
	      src++;
	      end = parse_reg (src, &nw, &nr);
	      if (end)
		{
		  /* Got Ra(Rb) */
		  src = end;

		  if (*src != ')')
		    {
		      as_bad ("Missing ) in ra(rb)");
		    }
		  else
		    {
		      src++;
		    }

		  regaddr (mode->mode, "ra(rb) ra");
/*		  regword (mode->mode, "ra(rb) rb");*/
		  mode->mode = CLASS_BX;
		  mode->reg = regn;
		  mode->x_reg = nr;
		  reg[ARG_RX] = nr;
		}
	      else
		{
		  /* Got Ra(disp) */
		  if (*src == '#')
		    src++;
		  src = parse_exp (src, &(mode->exp));
		  src = checkfor (src, ')');
		  mode->mode = CLASS_BA;
		  mode->reg = regn;
		  mode->x_reg = 0;
		  imm_operand = &(mode->exp);
		}
	    }
	  else
	    {
	      mode->reg = regn;
	      mode->x_reg = 0;
	    }
	}
      else
	{
	  /* No initial reg */
	  src = parse_exp (src, &(mode->exp));
	  if (*src == '(')
	    {
	      src++;
	      end = parse_reg (src, &(mode->mode), &regn);
	      regword (mode->mode, "addr(Ra) ra");
	      mode->mode = CLASS_X;
	      mode->reg = regn;
	      mode->x_reg = 0;
	      da_operand = &(mode->exp);
	      src = checkfor (end, ')');
	    }
	  else
	    {
	      /* Just an address */
	      mode->mode = CLASS_DA;
	      mode->reg = 0;
	      mode->x_reg = 0;
	      da_operand = &(mode->exp);
	    }
	}
    }
  *ptr = src;
}

static
char *
get_operands (opcode, op_end, operand)
     opcode_entry_type *opcode;
     char *op_end;
     op_type *operand;
{
  char *ptr = op_end;
char *savptr;
  switch (opcode->noperands)
    {
    case 0:
      operand[0].mode = 0;
      operand[1].mode = 0;
      break;

    case 1:
      ptr++;
      if (opcode->arg_info[0] == CLASS_CC)
        {
          get_cc_operand (&ptr, operand + 0, 0);
        }
      else if (opcode->arg_info[0] == CLASS_FLAGS)
        {
          get_flags_operand (&ptr, operand + 0, 0);
        }
      else if (opcode->arg_info[0] == (CLASS_IMM +(ARG_IMM2)))
        {
          get_interrupt_operand (&ptr, operand + 0, 0);
        }
      else
        {
          get_operand (&ptr, operand + 0, 0);
        }
      operand[1].mode = 0;
      break;

    case 2:
      ptr++;
      savptr = ptr;
      if (opcode->arg_info[0] == CLASS_CC)
        {
          get_cc_operand (&ptr, operand + 0, 0);
        }
      else if (opcode->arg_info[0] == CLASS_CTRL)
             {
               get_ctrl_operand (&ptr, operand + 0, 0);
               if (the_ctrl == 0)
                 {
                   ptr = savptr;
                   get_operand (&ptr, operand + 0, 0);
                   if (ptr == 0)
                     return;
                   if (*ptr == ',')
                     ptr++;
                   get_ctrl_operand (&ptr, operand + 1, 1);
                   return ptr;
                 }
             }
      else
        {
          get_operand (&ptr, operand + 0, 0);
        }
      if (ptr == 0)
        return;
      if (*ptr == ',')
        ptr++;
      get_operand (&ptr, operand + 1, 1);
      break;

    case 3:
      ptr++;
      get_operand (&ptr, operand + 0, 0);
      if (*ptr == ',')
	ptr++;
      get_operand (&ptr, operand + 1, 1);
      if (*ptr == ',')
	ptr++;
      get_operand (&ptr, operand + 2, 2);
      break;

    case 4:
      ptr++;
      get_operand (&ptr, operand + 0, 0);
      if (*ptr == ',')
	ptr++;
      get_operand (&ptr, operand + 1, 1);
      if (*ptr == ',')
	ptr++;
      get_operand (&ptr, operand + 2, 2);
      if (*ptr == ',')
	ptr++;
      get_cc_operand (&ptr, operand + 3, 3);
      break;
    default:
      abort ();
    }

  return ptr;
}

/* Passed a pointer to a list of opcodes which use different
   addressing modes, return the opcode which matches the opcodes
   provided
   */

static
opcode_entry_type *
DEFUN (get_specific, (opcode, operands),
       opcode_entry_type * opcode AND
       op_type * operands)

{
  opcode_entry_type *this_try = opcode;
  int found = 0;
  unsigned int noperands = opcode->noperands;

  unsigned int dispreg;
  unsigned int this_index = opcode->idx;

  while (this_index == opcode->idx && !found)
    {
      unsigned int i;

      this_try = opcode++;
      for (i = 0; i < noperands; i++)
	{
	  int mode = operands[i].mode;

	  if ((mode & CLASS_MASK) != (this_try->arg_info[i] & CLASS_MASK))
	    {
	      /* it could be an pc rel operand, if this is a da mode and
	   we like disps, then insert it */

	      if (mode == CLASS_DA && this_try->arg_info[i] == CLASS_DISP)
		{
		  /* This is the case */
		  operands[i].mode = CLASS_DISP;
		}
	      else if (mode == CLASS_BA && this_try->arg_info[i])
		{
		  /* Can't think of a way to turn what we've been given into
	     something that's ok */
		  goto fail;
		}
	      else if (this_try->arg_info[i] & CLASS_PR)
		{
		  if (mode == CLASS_REG_LONG && segmented_mode)
		    {
		      /* ok */
		    }
		  else if (mode == CLASS_REG_WORD && !segmented_mode)
		    {
		      /* ok */
		    }
		  else
		    goto fail;
		}
	      else
		goto fail;
	    }
	  switch (mode & CLASS_MASK)
	    {
	    default:
	      break;
	    case CLASS_X:
	    case CLASS_IR:
	    case CLASS_BA:
	    case CLASS_BX:
	    case CLASS_DISP:
	    case CLASS_REG:
	    case CLASS_REG_WORD:
	    case CLASS_REG_BYTE:
	    case CLASS_REG_QUAD:
	    case CLASS_REG_LONG:
	    case CLASS_REGN0:
	      reg[this_try->arg_info[i] & ARG_MASK] = operands[i].reg;
	      break;
	    }
	}

      found = 1;
    fail:;
    }
  if (found)
    return this_try;
  else
    return 0;
}

static void
DEFUN (check_operand, (operand, width, string),
       struct z8k_op *operand AND
       unsigned int width AND
       char *string)
{
  if (operand->exp.X_add_symbol == 0
      && operand->exp.X_op_symbol == 0)
    {

      /* No symbol involved, let's look at offset, it's dangerous if any of
       the high bits are not 0 or ff's, find out by oring or anding with
       the width and seeing if the answer is 0 or all fs*/
      if ((operand->exp.X_add_number & ~width) != 0 &&
	  (operand->exp.X_add_number | width) != (~0))
	{
	  as_warn ("operand %s0x%x out of range.", string, operand->exp.X_add_number);
	}
    }

}

static char buffer[20];

static void
DEFUN (newfix, (ptr, type, operand),
       int ptr AND
       int type AND
       expressionS * operand)
{
  if (operand->X_add_symbol
      || operand->X_op_symbol
      || operand->X_add_number)
    {
      fix_new_exp (frag_now,
		   ptr,
		   1,
		   operand,
		   0,
		   type);
    }
}

static char *
DEFUN (apply_fix, (ptr, type, operand, size),
       char *ptr AND
       int type AND
       expressionS * operand AND
       int size)
{
  int n = operand->X_add_number;

  operand->X_add_number = n;
  newfix ((ptr - buffer) / 2, type, operand);
#if 1
  switch (size)
    {
    case 8:			/* 8 nibbles == 32 bits */
      *ptr++ = n >> 28;
      *ptr++ = n >> 24;
      *ptr++ = n >> 20;
      *ptr++ = n >> 16;
    case 4:			/* 4 niblles == 16 bits */
      *ptr++ = n >> 12;
      *ptr++ = n >> 8;
    case 2:
      *ptr++ = n >> 4;
    case 1:
      *ptr++ = n >> 0;
      break;
    }
#endif
  return ptr;

}

/* Now we know what sort of opcodes it is, lets build the bytes -
 */
#define INSERT(x,y) *x++ = y>>24; *x++ = y>> 16; *x++=y>>8; *x++ =y;
static void
build_bytes (this_try, operand)
     opcode_entry_type * this_try;
     struct z8k_op *operand;
{
  unsigned int i;

  int length;
  char *output;
  char *output_ptr = buffer;
  char part;
  int c;
  char high;
  int nib;
  int nibble;
  unsigned int *class_ptr;

  frag_wane (frag_now);
  frag_new (0);

  memset (buffer, 20, 0);
  class_ptr = this_try->byte_info;
top:;

  for (nibble = 0; c = *class_ptr++; nibble++)
    {

      switch (c & CLASS_MASK)
	{
	default:

	  abort ();
	case CLASS_ADDRESS:
	  /* Direct address, we don't cope with the SS mode right now */
	  if (segmented_mode)
	    {
	      da_operand->X_add_number |= 0x80000000;
	      output_ptr = apply_fix (output_ptr, R_IMM32, da_operand, 8);
	    }
	  else
	    {
	      output_ptr = apply_fix (output_ptr, R_IMM16, da_operand, 4);
	    }
	  da_operand = 0;
	  break;
	case CLASS_DISP8:
	  /* pc rel 8 bit */
	  output_ptr = apply_fix (output_ptr, R_JR, da_operand, 2);
	  da_operand = 0;
	  break;

	case CLASS_0DISP7:
	  /* pc rel 7 bit */
	  *output_ptr = 0;
	  output_ptr = apply_fix (output_ptr, R_DISP7, da_operand, 2);
	  da_operand = 0;
	  break;

	case CLASS_1DISP7:
	  /* pc rel 7 bit */
	  *output_ptr = 0x80;
	  output_ptr = apply_fix (output_ptr, R_DISP7, da_operand, 2);
          output_ptr[-2] =  0x8;
	  da_operand = 0;
	  break;

	case CLASS_BIT_1OR2:
	  *output_ptr = c & 0xf;
	  if (imm_operand)
	    {
	      if (imm_operand->X_add_number == 2)
		{
		  *output_ptr |= 2;
		}
	      else if (imm_operand->X_add_number != 1)
		{
		  as_bad ("immediate must be 1 or 2");
		}
	    }
	  else
	    {
	      as_bad ("immediate 1 or 2 expected");
	    }
	  output_ptr++;
	  break;
	case CLASS_CC:
	  *output_ptr++ = the_cc;
	  break;
        case CLASS_0CCC:
          *output_ptr++ = the_ctrl;
          break;
        case CLASS_1CCC:
          *output_ptr++ = the_ctrl | 0x8;
          break;
        case CLASS_00II:
          *output_ptr++ = (~the_interrupt & 0x3);
          break;
        case CLASS_01II:
          *output_ptr++ = (~the_interrupt & 0x3) | 0x4;
          break;
        case CLASS_FLAGS:
          *output_ptr++ = the_flags;
          break;
	case CLASS_BIT:
	  *output_ptr++ = c & 0xf;
	  break;
	case CLASS_REGN0:
	  if (reg[c & 0xf] == 0)
	    {
	      as_bad ("can't use R0 here");
	    }
	case CLASS_REG:
	case CLASS_REG_BYTE:
	case CLASS_REG_WORD:
	case CLASS_REG_LONG:
	case CLASS_REG_QUAD:
	  /* Insert bit mattern of
	 right reg */
	  *output_ptr++ = reg[c & 0xf];
	  break;
	case CLASS_DISP:
	  output_ptr = apply_fix (output_ptr, R_IMM16, da_operand, 4);
	  da_operand = 0;
	  break;

	case CLASS_IMM:
	  {
	    nib = 0;
	    switch (c & ARG_MASK)
	      {
	      case ARG_IMM4:
		output_ptr = apply_fix (output_ptr, R_IMM4L, imm_operand, 1);
		break;
	      case ARG_IMM4M1:
		imm_operand->X_add_number--;
		output_ptr = apply_fix (output_ptr, R_IMM4L, imm_operand, 1);
		break;
	      case ARG_IMMNMINUS1:
		imm_operand->X_add_number--;
		output_ptr = apply_fix (output_ptr, R_IMM4L, imm_operand, 1);
		break;
	      case ARG_NIM8:
		imm_operand->X_add_number = -imm_operand->X_add_number;
	      case ARG_IMM8:
		output_ptr = apply_fix (output_ptr, R_IMM8, imm_operand, 2);
		break;
	      case ARG_IMM16:
		output_ptr = apply_fix (output_ptr, R_IMM16, imm_operand, 4);
		break;

	      case ARG_IMM32:
		output_ptr = apply_fix (output_ptr, R_IMM32, imm_operand, 8);
		break;

	      default:
		abort ();
	      }
	  }
	}
    }

  /* Copy from the nibble buffer into the frag */

  {
    int length = (output_ptr - buffer) / 2;
    char *src = buffer;
    char *fragp = frag_more (length);

    while (src < output_ptr)
      {
	*fragp = (src[0] << 4) | src[1];
	src += 2;
	fragp++;
      }

  }

}

/* This is the guts of the machine-dependent assembler.  STR points to a
   machine dependent instruction.  This funciton is supposed to emit
   the frags/bytes it assembles to.
   */

void
DEFUN (md_assemble, (str),
       char *str)
{
  char *op_start;
  char *op_end;
  unsigned int i;
  struct z8k_op operand[3];
  opcode_entry_type *opcode;
  opcode_entry_type *prev_opcode;

  char *dot = 0;
  char c;

  /* Drop leading whitespace */
  while (*str == ' ')
    str++;

  /* find the op code end */
  for (op_start = op_end = str;
       *op_end != 0 && *op_end != ' ';
       op_end++)
    {
    }

  ;

  if (op_end == op_start)
    {
      as_bad ("can't find opcode ");
    }
  c = *op_end;

  *op_end = 0;

  opcode = (opcode_entry_type *) hash_find (opcode_hash_control,
					    op_start);


  if (opcode == NULL)
    {
      as_bad ("unknown opcode");
      return;
    }

  if (opcode->opcode == 250)
    {
      /* was really a pseudo op */

      pseudo_typeS *p;
      char oc;

      char *old = input_line_pointer;
      *op_end = c;


      input_line_pointer = op_end;

      oc = *old;
      *old = '\n';
      while (*input_line_pointer == ' ')
	input_line_pointer++;
      p = (pseudo_typeS *) (opcode->func);

      (p->poc_handler) (p->poc_val);
      input_line_pointer = old;
      *old = oc;
    }
  else
    {
      input_line_pointer = get_operands (opcode, op_end,
					 operand);
      prev_opcode = opcode;

      opcode = get_specific (opcode, operand);

      if (opcode == 0)
	{
	  /* Couldn't find an opcode which matched the operands */
	  char *where = frag_more (2);

	  where[0] = 0x0;
	  where[1] = 0x0;

	  as_bad ("Can't find opcode to match operands");
	  return;
	}

      build_bytes (opcode, operand);
    }
}

void
DEFUN (tc_crawl_symbol_chain, (headers),
       object_headers * headers)
{
  printf ("call to tc_crawl_symbol_chain \n");
}

symbolS *
DEFUN (md_undefined_symbol, (name),
       char *name)
{
  return 0;
}

void
DEFUN (tc_headers_hook, (headers),
       object_headers * headers)
{
  printf ("call to tc_headers_hook \n");
}

/* Various routines to kill one day */
/* Equal to MAX_PRECISION in atof-ieee.c */
#define MAX_LITTLENUMS 6

/* Turn a string in input_line_pointer into a floating point constant of type
   type, and store the appropriate bytes in *litP.  The number of LITTLENUMS
   emitted is stored in *sizeP .  An error message is returned, or NULL on OK.
   */
char *
md_atof (type, litP, sizeP)
     char type;
     char *litP;
     int *sizeP;
{
  int prec;
  LITTLENUM_TYPE words[MAX_LITTLENUMS];
  LITTLENUM_TYPE *wordP;
  char *t;
  char *atof_ieee ();

  switch (type)
    {
    case 'f':
    case 'F':
    case 's':
    case 'S':
      prec = 2;
      break;

    case 'd':
    case 'D':
    case 'r':
    case 'R':
      prec = 4;
      break;

    case 'x':
    case 'X':
      prec = 6;
      break;

    case 'p':
    case 'P':
      prec = 6;
      break;

    default:
      *sizeP = 0;
      return "Bad call to MD_ATOF()";
    }
  t = atof_ieee (input_line_pointer, type, words);
  if (t)
    input_line_pointer = t;

  *sizeP = prec * sizeof (LITTLENUM_TYPE);
  for (wordP = words; prec--;)
    {
      md_number_to_chars (litP, (long) (*wordP++), sizeof (LITTLENUM_TYPE));
      litP += sizeof (LITTLENUM_TYPE);
    }
  return 0;
}

CONST char *md_shortopts = "z:";
struct option md_longopts[] = {
  {NULL, no_argument, NULL, 0}
};
size_t md_longopts_size = sizeof(md_longopts);

int
md_parse_option (c, arg)
     int c;
     char *arg;
{
  switch (c)
    {
    case 'z':
      if (!strcmp (arg, "8001"))
	s_segm ();
      else if (!strcmp (arg, "8002"))
	s_unseg ();
      else
	{
	  as_bad ("invalid architecture -z%s", arg);
	  return 0;
	}
      break;

    default:
      return 0;
    }

  return 1;
}

void
md_show_usage (stream)
     FILE *stream;
{
  fprintf(stream, "\
Z8K options:\n\
-z8001			generate segmented code\n\
-z8002			generate unsegmented code\n");
}

int md_short_jump_size;

void
tc_aout_fix_to_chars ()
{
  printf ("call to tc_aout_fix_to_chars \n");
  abort ();
}

void
md_create_short_jump (ptr, from_addr, to_addr, frag, to_symbol)
     char *ptr;
     addressT from_addr;
     addressT to_addr;
     fragS *frag;
     symbolS *to_symbol;
{
  as_fatal ("failed sanity check.");
}

void
md_create_long_jump (ptr, from_addr, to_addr, frag, to_symbol)
     char *ptr;
     addressT from_addr, to_addr;
     fragS *frag;
     symbolS *to_symbol;
{
  as_fatal ("failed sanity check.");
}

void
md_convert_frag (headers, seg, fragP)
     object_headers *headers;
     segT seg;
     fragS *fragP;
{
  printf ("call to md_convert_frag \n");
  abort ();
}

valueT
DEFUN (md_section_align, (seg, size),
       segT seg AND
       valueT size)
{
  return ((size + (1 << section_alignment[(int) seg]) - 1) & (-1 << section_alignment[(int) seg]));

}

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

  switch (fixP->fx_r_type)
    {
    case R_IMM4L:
      buf[0] = (buf[0] & 0xf0) | ((buf[0] + val) & 0xf);
      break;

    case R_JR:

      *buf++ = val;
      /*    if (val != 0) abort();*/
      break;

    case R_DISP7:

      *buf++ += val;
      /*    if (val != 0) abort();*/
      break;

    case R_IMM8:
      buf[0] += val;
      break;
    case R_IMM16:
      *buf++ = (val >> 8);
      *buf++ = val;
      break;
    case R_IMM32:
      *buf++ = (val >> 24);
      *buf++ = (val >> 16);
      *buf++ = (val >> 8);
      *buf++ = val;
      break;
#if 0
    case R_DA | R_SEG:
      *buf++ = (val >> 16);
      *buf++ = 0x00;
      *buf++ = (val >> 8);
      *buf++ = val;
      break;
#endif

    case 0:
      md_number_to_chars (buf, val, fixP->fx_size);
      break;

    default:
      abort ();

    }
}

int md_long_jump_size;
int
md_estimate_size_before_relax (fragP, segment_type)
     register fragS *fragP;
     register segT segment_type;
{
  printf ("call tomd_estimate_size_before_relax \n");
  abort ();
}

/* Put number into target byte order */

void
DEFUN (md_number_to_chars, (ptr, use, nbytes),
       char *ptr AND
       valueT use AND
       int nbytes)
{
  number_to_chars_bigendian (ptr, use, nbytes);
}
long
md_pcrel_from (fixP)
     fixS *fixP;
{
  abort ();
}

void
tc_coff_symbol_emit_hook (s)
     struct symbol *s;
{
}

void
tc_reloc_mangle (fix_ptr, intr, base)
     fixS *fix_ptr;
     struct internal_reloc *intr;
     bfd_vma base;

{
  symbolS *symbol_ptr;

  if (fix_ptr->fx_addsy &&
      fix_ptr->fx_subsy) 
    {
      symbolS *add = fix_ptr->fx_addsy;
      symbolS *sub = fix_ptr->fx_subsy;
      if (S_GET_SEGMENT(add) != S_GET_SEGMENT(sub))
	{
	  as_bad("Can't subtract symbols in different sections %s %s",
		 S_GET_NAME(add), S_GET_NAME(sub));
	}
      else {
	int diff = S_GET_VALUE(add) - S_GET_VALUE(sub);
	fix_ptr->fx_addsy = 0;
	fix_ptr->fx_subsy = 0;
	fix_ptr->fx_offset += diff;
      }
    }
  symbol_ptr = fix_ptr->fx_addsy;

  /* If this relocation is attached to a symbol then it's ok
     to output it */
  if (fix_ptr->fx_r_type == 0)
    {
      /* cons likes to create reloc32's whatever the size of the reloc.. */
      switch (fix_ptr->fx_size)
	{
	case 2:
	  intr->r_type = R_IMM16;
	  break;
	case 1:
	  intr->r_type = R_IMM8;
	  break;
	case 4:
	  intr->r_type = R_IMM32;
	  break;
	default:
	  abort ();
	}

    }
  else
    {
      intr->r_type = fix_ptr->fx_r_type;
    }

  intr->r_vaddr = fix_ptr->fx_frag->fr_address + fix_ptr->fx_where + base;
  intr->r_offset = fix_ptr->fx_offset;

  if (symbol_ptr)
    intr->r_symndx = symbol_ptr->sy_number;
  else
    intr->r_symndx = -1;
}

