/* tc-blarg.c -- Assembler code for blargCPU
   Copyright (c) 2005 Joshua Wise.

   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.  */

#include <stdio.h>
#include "as.h"
#include "safe-ctype.h"
#include "subsegs.h"

const char comment_chars[] = ";@";
const char line_comment_chars[] = "";
const char line_separator_chars[] = "$";

const char EXP_CHARS[] = "eE";
const char FLT_CHARS[] = "dD";

const char *md_shortopts = "";

/* The target specific pseudo-ops which we support.  */
const pseudo_typeS md_pseudo_table[] =
{
  { NULL,	NULL,		0}
};

static char *skip_space PARAMS ((char *));
static char *extract_word PARAMS ((char *, char *, int));

#define EXP_MOD_NAME(i) exp_mod[i].name
#define EXP_MOD_RELOC(i) exp_mod[i].reloc
#define EXP_MOD_NEG_RELOC(i) exp_mod[i].neg_reloc
#define HAVE_PM_P(i) exp_mod[i].have_pm

struct option md_longopts[] =
{
  { NULL, no_argument, NULL, 0 }
};

size_t md_longopts_size = sizeof (md_longopts);

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

/* Extract one word from FROM and copy it to TO.  */

static char *
extract_word (char *from, char *to, int limit)
{
  char *op_start;
  char *op_end;
  int size = 0;

  /* Drop leading whitespace.  */
  from = skip_space (from);
  *to = 0;

  /* Find the op code end.  */
  for (op_start = op_end = from; *op_end != 0 && is_part_of_name (*op_end);)
    {
      to[size++] = *op_end++;
      if (size + 1 >= limit)
	break;
    }

  to[size] = 0;
  return op_end;
}

int
md_estimate_size_before_relax (fragp, seg)
     fragS *fragp ATTRIBUTE_UNUSED;
     asection *seg ATTRIBUTE_UNUSED;
{
  abort ();
  return 0;
}

void
md_show_usage (stream)
     FILE *stream;
{
  fprintf (stream,
      _("blargCPU options:\n"
	"  no options for blargCPU\n"));
}

int
md_parse_option (c, arg)
     int c ATTRIBUTE_UNUSED;
     char *arg ATTRIBUTE_UNUSED;
{
  return 0;
}

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

/* 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)
     int type;
     char *litP;
     int *sizeP;
{
  int prec;
  LITTLENUM_TYPE words[4];
  LITTLENUM_TYPE *wordP;
  char *t;

  switch (type)
    {
    case 'f':
      prec = 2;
      break;
    case 'd':
      prec = 4;
      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);

  /* This loop outputs the LITTLENUMs in REVERSE order.  */
  for (wordP = words + prec - 1; prec--;)
    {
      md_number_to_chars (litP, (valueT) (*wordP--), sizeof (LITTLENUM_TYPE));
      litP += sizeof (LITTLENUM_TYPE);
    }

  return NULL;
}

void
md_convert_frag (abfd, sec, fragP)
     bfd *abfd ATTRIBUTE_UNUSED;
     asection *sec ATTRIBUTE_UNUSED;
     fragS *fragP ATTRIBUTE_UNUSED;
{
  abort ();
}

void
md_begin ()
{
  bfd_set_arch_mach (stdoutput, TARGET_ARCH, bfd_mach_blarg);
}

/* GAS will call this function for each section at the end of the assembly,
   to permit the CPU backend to adjust the alignment of a section.  */

valueT
md_section_align (asection *seg, valueT addr)
{
  int align = bfd_get_section_alignment (stdoutput, seg);
  return ((addr + (1 << align) - 1) & (-1 << align));
}

/* If you define this macro, it should return the offset between the
   address of a PC relative fixup and the position from which the PC
   relative adjustment should be made.  On many processors, the base
   of a PC relative instruction is the next instruction, so this
   macro would return the length of an instruction.  */

long
md_pcrel_from_section (fixp, sec)
     fixS *fixp;
     segT sec;
{
  if (fixp->fx_addsy != (symbolS *) NULL
      && (!S_IS_DEFINED (fixp->fx_addsy)
	  || (S_GET_SEGMENT (fixp->fx_addsy) != sec)))
    return 0;

  return fixp->fx_frag->fr_address + fixp->fx_where;
}

/* GAS will call this for each fixup.  It should store the correct
   value in the object file.  */

void
md_apply_fix (fixS *fixP, valueT *valP, segT seg)
{
  unsigned char *where;
  unsigned long insn;
  long value = *valP;

  if (fixP->fx_addsy == (symbolS *) NULL)
    fixP->fx_done = 1;

  else if (fixP->fx_pcrel)
    {
      segT s = S_GET_SEGMENT (fixP->fx_addsy);

      if (s == seg || s == absolute_section)
	{
	  value += S_GET_VALUE (fixP->fx_addsy);
	  fixP->fx_done = 1;
	}
    }

  /* We don't actually support subtracting a symbol.  */
  if (fixP->fx_subsy != (symbolS *) NULL)
    as_bad_where (fixP->fx_file, fixP->fx_line, _("expression too complex"));

  switch (fixP->fx_r_type)
    {
    default:
      fixP->fx_no_overflow = 1;
      break;
    }

  if (fixP->fx_done)
    {
      /* Fetch the instruction, insert the fully resolved operand
	 value, and stuff the instruction back again.  */
      where = (unsigned char *) fixP->fx_frag->fr_literal + fixP->fx_where;
      insn = bfd_getl16 (where);

      switch (fixP->fx_r_type)
	{
	case BFD_RELOC_16:
	  bfd_putl16 ((bfd_vma) value, where);
	  break;

	default:
	  as_fatal (_("line %d: unknown relocation type: 0x%x"),
		    fixP->fx_line, fixP->fx_r_type);
	  break;
	}
    }
}

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

  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);
  if (reloc->howto == (reloc_howto_type *) NULL)
    {
      as_bad_where (fixp->fx_file, fixp->fx_line,
		    _("reloc %d not supported by object file format"),
		    (int) fixp->fx_r_type);
      return NULL;
    }

  if (fixp->fx_r_type == BFD_RELOC_VTABLE_INHERIT
      || fixp->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
    reloc->address = fixp->fx_offset;

  reloc->addend = fixp->fx_offset;

  return reloc;
}

#define GETREG_EXPECT_BRACKETS 0x1
#define GETREG_COMMA_OK 0x2

static int blarg_getreg(char** r, int flags)
{
  int retval = -1;
  
  *r = skip_space(*r);
  if (flags & GETREG_EXPECT_BRACKETS)
  {
    if ((*r)[0] != '[')
    {
      as_bad(_("expected open bracket before register"));
      return -1;
    }
    (*r)++;
    *r = skip_space(*r);
 }
  
  if ((*r)[0] == 'r') {
    (*r)++;
    if ((*r)[0] < '0' || (*r)[0] > '3')
      as_bad(_("out of bounds register `r%c'"), *r[0]);
    else
      retval = (*r)[0] - '0';
    (*r)++;
  } else if (!strncmp(*r, "fr", 2)) {
    (*r) += 2;
    retval = 4;
  } else if (!strncmp(*r, "pc", 2)) {
    (*r) += 2;
    retval = 5;
  } else if (!strncmp(*r, "sp", 2)) {
    (*r) += 2;
    retval = 6;
  } else {
    as_bad(_("expected register, got garbage"));
    return -1;
  }
  
  if ((*r)[0] != '\r' && 
      (*r)[0] != ' ' && 
      (*r)[0] != '\0' &&
      (*r)[0] != '\t' &&
      (*r)[0] != '\n' && 
      ((flags & GETREG_EXPECT_BRACKETS) ? ((*r)[0] != ']') : 1) &&
      ((flags & GETREG_COMMA_OK) ? ((*r)[0] != ',') : 1))
  {
    as_bad(_("garbage after register"));
    return -1;
  }
  
  *r = skip_space(*r);
  if (flags & GETREG_EXPECT_BRACKETS)
  { 
    if ((*r)[0] != ']')
    {
      as_bad(_("expected close bracket after register"));
      return -1;
    }
    (*r)++;
    *r = skip_space(*r);
  }
  
  return retval;
}

static void blarg_expect_comma(char** r)
{
  *r = skip_space(*r);
  if ((*r)[0] != ',')
  {
    as_bad(_("garbage after first operand; expected comma"));
    return;
  }
  (*r)++;
  *r = skip_space(*r);
}

static void blarg_get_expression(char** r, expressionS* op)
{
  char* old_ilp;
  
  (*r)++;
  *r = skip_space(*r);
      
  old_ilp = input_line_pointer;
  input_line_pointer = *r;
  expression(op);
  if (op->X_op == O_absent)
    as_bad(_("missing operand"));
  *r = input_line_pointer;
  input_line_pointer = old_ilp;
  
  *r = skip_space(*r);
}

static int blarg_get_pred(char* pr)
{
  if (*pr == '\0') return 0x7;	/* al */
  if (!strcmp(pr, "nv")) return 0x0;
  if (!strcmp(pr, "ne")) return 0x1;
  if (!strcmp(pr, "eq")) return 0x2;
  if (!strcmp(pr, "lt")) return 0x3;
  if (!strcmp(pr, "gt")) return 0x4;
  if (!strcmp(pr, "al")) return 0x7;
  as_bad("garbage predicate following instruction");
  return 0x0;
}

#define MAKE_INSN(insn, pred, treg, sreg) (((insn) << 12) | ((pred) << 9) | ((treg) << 4) | ((sreg)))

void
md_assemble (str)
     char *str;
{
  char op[11];

  str = skip_space (extract_word (str, op, sizeof (op)));

  if (!op[0])
    as_bad (_("can't find opcode "));

  if        (!strncmp(op, "mov", 3)) {
    /* mov is a special case - there are two forms */
    int pred = blarg_get_pred(op+3);
    int opcode = 0x0;
    int treg = 0x0, sreg = 0x0;
    char* frag;
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    if (*str == '#')	/* literal form */
    {
      int where;
      expressionS op;
      
      opcode = 0x0;
      sreg = 0x0;
      frag = frag_more(4);
      where = frag - frag_now->fr_literal;
      blarg_get_expression(&str, &op);
      fix_new_exp(frag_now, where+2, 2, &op, 0, BFD_RELOC_16);
    } else {		/* register form */
      opcode = 0x3;
      sreg = blarg_getreg(&str, 0);
      frag = frag_more(2);
    }
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "sto", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x2;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_EXPECT_BRACKETS);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "ldr", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x1;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, GETREG_EXPECT_BRACKETS);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "add", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x4;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "tst", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x5;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "and", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x6;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strcmp(op, "not")) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x7;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "push", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x8;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "pop", 3)) {
    int pred = blarg_get_pred(op+3);
    int opcode = 0x9;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else if (!strncmp(op, "call", 3)) {
    int pred = blarg_get_pred(op+4);
    int opcode = 0xA;
    int treg = 0x0, sreg = 0x0;
    char* frag = frag_more(2);
    
    treg = blarg_getreg(&str, GETREG_COMMA_OK);
    blarg_expect_comma(&str);
    sreg = blarg_getreg(&str, 0);
    
    bfd_putl16(MAKE_INSN(opcode, pred, treg, sreg), frag);
  } else {
    as_bad (_("unknown opcode `%s'"), op);
    return;
  }
  
  if (*skip_space(str))
    as_bad (_("garbage at end of line"));
}

