/* tc-z8k.c -- Assemble code for the Zilog Z800n
   Copyright (C) 1992, 93, 94, 95, 96, 97, 98, 1999 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"));
}

void
tc_aout_fix_to_chars ()
{
  printf (_("call to tc_aout_fix_to_chars \n"));
  abort ();
}

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_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)
     symbolS *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;
}

