/*
   Code generator routines for CC65.  These are called by various
   parts of the compiler to generate primitive operations.
*/

#include <stdio.h>
#include "cc65.h"

#define UNSIGNED_FIX

/* output a string */
#ifdef not_M6502
/* defined in xobj.m65 */
#else
static char xxc;
ot(str)
char * str;
{
  while (xxc = *str)
    {
      outchar(xxc);
      ++str;
    }
}

#endif

#ifdef M6502
/* defined in xobj.m65 */
#else
nl()
{
  outchar('\n');
}

#endif

/* output a string, add a newline */
#ifdef M6502
/* defined in xobj.m65 */
#else
ol(str)
{
  ot(str);
  nl();
}

#endif

#ifdef M6502
/* defined in xobj.m65 */
#else
oljsr(str)
char * str;
{
  ot("\tjsr\t");
  ol(str);
}

#endif

oljsrpop(str)
char * str;
{
  oljsr(str);
  popsp();
}

/* helper for konst1 */
olstrdnl(str, num)
char * str;
int num;
{
  ot(str);
  outdecnl(num);
}

static char lda[] = "\tlda\t#";
static char ldx[] = "\tldx\t#";
#ifdef M6502
extern int xxint;
extern char xxhi, xxlo;
#asm
_xxint:
_xxlo:
	.byte 0
_xxhi:
	.byte 0
#endasm
#else
static char xxhi, xxlo;
#endif

/* load konst into AX in such a way as to set cond codes correctly */
olxxxhi()
{
  olstrdnl(ldx, xxhi);
}

olxxxlo()
{
  olstrdnl(lda, xxlo);
}

konst1(k)
int k;
{
#ifdef M6502
  xxint = k;
#else
  xxhi = k >> 8;
  xxlo = k & 0xFF;
#endif
  if (xxhi == xxlo)
    {
      olxxxlo();
      ol("\ttax");
    }
  else if (xxhi)
    {
      olxxxlo();
      olxxxhi();
    }
  else
    {
      olxxxhi();
      olxxxlo(); /* this loses some; generates spurious negative... */
    }
}

konst3(k)
int k;
{
#ifdef M6502
  flushout();			/* keep output buf from filling up */
#endif
  olstrdnl("\tldy\t#", k);
}

char efn[] = {'e', 'n', 't', 'e', 'r', 'f', 'u', 'n'};
char efn_n = ' ';
char efn_0 = 0;

startfun(name)
char * name;
{
#ifndef M6502
  if (n_funargs == -1)
    printf("Internal error! no argcount?\n");
#endif
  outgblc(name); /* print a global name with colon */
  if (findmac("FIXARGC"))
    efn_n = n_funargs + '0';
  else
    efn_n = 0;
  oljsr(efn);
}

/* util function */
int char_t_p(tptr)
char * tptr;
{
  return ((tptr[0] & ~T_UNSIGNED) == T_CHAR);
}

/* Fetch a static memory cell into the primary register */
getmem(lbl, tptr, test_p)	/* jrd added test_p */
char * lbl;
char * tptr;
char test_p;
{
  if (char_t_p(tptr))
    {
      /* load X with a 0.  later deal with sgn ext */
/*	if (test_p)	/* only load 0 in X if not testing */
      {
	ol("\tldx\t#0");
      }
      ot("\tlda\t");		/* load A from the label */
      outgblnl(lbl);
    }
  else				/* loading word-sized thing */
    {
      ot("\tlda\t");
      outgblnl(lbl);
      if (test_p)
        ot("\tora\t");
      else
        {
          ot("\tldx\t");
        }
      outgbl(lbl);
      ol("+1");
    }
}

/* and another... */
static konst3s(offs)
int offs;
{
  konst3(offs - oursp);
}

#ifdef UNSIGNED_FIX
/* 
   this is another helper fun for generating the right variant of
   ldaxxx frob.  given a typespec and an extension, generate
   "lda" followed by <ext> for signed char, 'u'<ext> for unsigned 
   char, or 'x'<ext> for fixnum 
*/
lda_helper(typestring, ext)
unsigned char * typestring;
char * ext;
{
  ot("\tjsr\tlda");
  if (char_t_p(typestring))	/* some kind of char-sized thing? */
    {
      if (typestring[0] & T_UNSIGNED)
	outchar('u');		/* call unsigned variant */
    }
  else
    outchar('x');		/* call word-sized variant */
  ol(ext);
}
#endif

/*
  getladr( offs )
  Fetch the address of the specified symbol
  into the primary register
*/
getladr(offs)
int offs;
{
  konst3s(offs);		/* load Y with offset value */
  oljsr("locysp");		/* and compute location of SP@(Y) */
}

/*
  getloc( offs, tptr )
  Fetch specified local object (local var).
*/
getloc(offs, tptr)
int offs;
char * tptr;
{
  konst3s(offs);		/* load Y with offset value */
#ifdef old_way
  if (tptr[0] == T_CHAR)
    oljsr("ldaysp");		/* load a byte from SP@(Y) */
  else
    oljsr("ldaxysp");		/* load a word from SP@(Y) */
#else
#ifdef UNSIGNED_FIX
  lda_helper(tptr, "ysp");
#else
  oljsr((char_t_p(tptr) ? "ldaysp" : "ldaxysp"));
#endif
#endif
}

/*
  putloc( offs, tptr )
  Put data into local object.
*/
putloc(offs, tptr)
int offs;
char * tptr;
{
  konst3s(offs);		/* load Y with offset value */
#ifdef old_way
  if (tptr[0] == T_CHAR)
    oljsr("staysp");		/* load a byte from SP@(Y) */
  else
    oljsr("staxysp");		/* load a word from SP@(Y) */
#else
  oljsr((char_t_p(tptr) ? "staysp" : "staxysp"));
#endif
}

/*
  Store the primary register into the specified
  static memory cell 
*/
putmem(lab, tptr)
char * lab;
char * tptr;
{
  ot("\tsta\t");
  outgblnl(lab);
  if (!char_t_p(tptr))
    {
      ot("\tstx\t");
      outgbl(lab);
      ol("+1");
    }
}

/*
  putstk( tptr )
  Store the specified object type in the primary register
  at the address on the top of the stack
*/
putstk(lval)
struct expent * lval;
{
  if ((lval->e_flags == E_MEOFFS) && /* some kind of structure */
      (lval->e_const != 0))	/* and not first slot... */
    {
      konst3(lval->e_const);	/* load Y with structure offset */
#ifdef old_way
      if (lval->e_tptr[0] == T_CHAR)
	oljsr("staspidx");	/* store A at ((SP))+Y */
      else
	oljsr("staxspidx");	/* store AX at ((SP))+Y */
#else
      oljsr((char_t_p(lval->e_tptr) ? "staspidx" : "staxspidx"));
#endif
    }
  else
    {
#ifdef old_way
      if (lval->e_tptr[0] == T_CHAR)
	oljsr("staspp"); /* store A at ((SP)) */
      else
	oljsr("staxspp");	/* store AX at ((SP)) */
#else
      oljsr((char_t_p(lval->e_tptr) ? "staspp" : "staxspp"));
#endif
    }
  popsp();
}

/*
  indirect( lval )
  Fetch the specified object type indirect through the
  primary register into the primary register
*/
indirect(lval)
struct expent * lval;
{
  if ((lval->e_flags == E_MEOFFS) && /* some kind of structure */
      (lval->e_const != 0))	/* and not slot 0 */
    {
      konst3(lval->e_const);	/* load Y with offset */
#ifdef old_way
      if (lval->e_tptr[0] == T_CHAR)
	oljsr("ldaidx");	/* load A from AX@(Y) */
      else
	oljsr("ldaxidx");	/* load AX from AX@(Y) */
#else
#ifdef UNSIGNED_FIX
      lda_helper(lval->e_tptr, "idx");
#else
      oljsr((char_t_p(lval->e_tptr) ? "ldaidx" : "ldaxidx"));
#endif
#endif
    }
  else
    {
#ifdef old_way
      if (lval->e_tptr[0] == T_CHAR)
	oljsr("ldai");		/* load A from AX@ */
      else
	oljsr("ldaxi");		/* load AX from AX@ */
#else
#ifdef UNSIGNED_FIX
      lda_helper(lval->e_tptr, "i");
#else
      oljsr((char_t_p(lval->e_tptr) ? "ldai" : "ldaxi"));
#endif
#endif
    }
}

/*
  swap()
  Swap the primary and secondary registers
*/
#if 0
 /* not used? */
#ifdef M6502
/* defined in xobj.m65 */
#else
swap()
{
  oljsr("swaptos");
}

#endif
#endif

/*
  save()
  Copy AX to hold register.
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
save()
{
  oljsr("saveax");
}

#endif

/*
  rstr()
  Copy hold register to P.
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
rstr()
{
  oljsr("restax");
}

#endif

/*
  immed()
  Print partial instruction to get an immediate value
  into the primary register.
*/
immed()
{
  ot("\tldax\t#");
}

/* added by jrd.  force a test to set cond codes right */
#ifdef M6502
/* defined in xobj.m65 */
#else
tst()
{
  oljsr("tstax");
}

#endif

/*
  push()
  Push the primary register onto the stack
*/
push()
{
  oljsr("pushax");
  pushsp();
}

push1()
{
/* what's the difference? */
#ifndef M6502
  ol(";; push1");
#endif
  push();
}


/* Swap the primary register and the top of the stack */
#ifdef M6502
/* defined in xobj.m65 */
#else
swapstk()
{
  oljsr("swapstk");
}

#endif

/* Call the specified subroutine name */
call(lbl, narg)
/* int	lbl; -- not any more */
char * lbl;
int narg;
{
  if (!findmac("NOARGC")) /* don't generate argc if suppressed */
    konst3(narg >> 1);		/* want n args, not nbytes args */
  ot("\tjsr\t");
  outgblnl(lbl);

  oursp = oursp + narg;		/* callee pops args */
#ifdef M6502
  flushout();			/* keep output buf from filling up */
#endif
}

/* Return from subroutine */
ret()
{
  ol("\tjmp\texitfun");
}

/*
  Perform subroutine call to value on top of stack.
  This really calls value in AX 
*/
callstk(narg)
int narg;
{
  if (!findmac("NOARGC"))	/* don't generate argc if suppressed */
    konst3(narg >> 1);		/* load Y with arg count */
  oljsr("callax");		/* do the call */
  oursp = oursp + narg;		/* callee pops args */
/*  popsp();			/* pop subr addr */
#ifdef M6502
  flushout();			/* keep output buf from filling up */
#endif
}

/* Jump to specified internal label number */
jump(label)
int label;
{
  ot("\tjmp\t");
  outlabnl(label);
}

/* output case-jump preample */
#ifdef M6502
/* defined in xobj.m65 */
#else
casejump()
{
  oljsr("casejump");
}

#endif

/*
  truejump -- jump to lable if p nz
*/
truejump(label, invert)
int label;
int invert;			/* jrd added this */
{
#ifndef M6502
  ol(";;; truejump");
#endif
  if (invert)
    falsejump(label, 0);
  else
    {
      ot("\tlbne\t");
      outlabnl(label);
    }
}

/*
  falsejump -- jump to lable if AX is zero
*/
falsejump(label, invert)
int label;
int invert; /* jrd added this */
{
#ifndef M6502
  ol(";;; falsejump");
#endif
  if (invert)
    truejump(label, 0);
  else
    {
      ot("\tlbeq\t");
      outlabnl(label);
    }
}

/*
  cmpjump -- compare p to constant
  and jump not-equal to label
*/
/* cmpjump(constant, label)
int	constant;
int	label;
{
  ol(";;; cmpjump");
} */

/*
  Modify the stack pointer to the
  new value indicated
*/
otx(str)
char * str;
{
  ot("\tjsr\t");
  ot(str);
  ot("sp");
}

mod_internal(k, verb1, verb2)
int k;
char * verb1, * verb2;
{
  if (k <= 8)
    {
      otx(verb1);
      outchar(k + '0');
    }
  else
    {
      konst3(k);
      otx(verb2);
    }
  nl();
}

modstk(newsp)
int newsp;
{
int k;

  if ((k = (newsp - oursp)) > 0)
    {
      mod_internal(k, "inc", "addy");
    }
  else if (k < 0)
    {
      mod_internal(-k, "dec", "suby");
    }
  return (newsp);
}

/* Double the primary register */
#ifdef M6502
/* defined in xobj.m65 */
#else
doublereg()
{
  oljsr("aslax");
}

#endif

/*
  Add the primary and secondary registers 
  (results in primary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
add()
{
/*
  oljsr("addtos");
  popsp();		/* zzz is this still right??? */

  oljsrpop("addtos");
}

#endif

/* 
  Subtract the primary register from the secondary 
  (results in primary)
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
sub()
{
/*
  oljsr("subtos");
  popsp();
*/
  oljsrpop("subtos");
}

#endif

/*
  Multiply the primary and secondary registers 
  (results in primary 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
mult()
{
/*
  oljsr("multos");
  popsp();
*/
  oljsrpop("multos");
}

#endif

/*
  Divide the secondary register by the primary 
  (quotient in primary, remainder in secondary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
div()
{
/*
  oljsr("divtos");
  popsp();
*/
  oljsrpop("divtos");
}

#endif

/*
  Compute remainder (mod) of secondary register divided
  by the primary (remainder in primary, quotient in secondary)
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
mod()
{
/*
  oljsr("modtos");
  popsp();
*/
  oljsrpop("modtos");
}

#endif

/*
   Inclusive 'or' the primary and the secondary registers
   (results in primary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
or()
{
/*
  oljsr("ortos");
  popsp();
*/
  oljsrpop("ortos");
}

#endif

/*
  Exclusive 'or' the primary and seconday registers 
  (results in primary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
xor()
{
/*
  oljsr("xortos");
  popsp();
*/
  oljsrpop("xortos");
}

#endif

/* 
  'And' the primary and secondary registers 
  (results in primary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
and()
{
/*
  oljsr("andtos");
  popsp();
*/
  oljsrpop("andtos");
}

#endif

/*
  Arithmetic shift right the secondary register number of 
  times in primary (results in primary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
asr()
{
/*
  oljsr("asrtos");
  popsp();
*/
  oljsrpop("asrtos");
}

#endif

/*
  Arithmetic left shift the secondary register number of 
  times in primary (results in primary) 
*/
#ifdef M6502
/* defined in xobj.m65 */
#else
asl()
{
/*
  oljsr("asltos");
  popsp();
*/
  oljsrpop("asltos");
}

#endif

/* Form two's complement of primary register */
#ifdef M6502
/* defined in xobj.m65 */
#else
neg()
{
  oljsr("negax");
}

#endif

/* Form logical complement of primary register */
#ifdef M6502
/* defined in xobj.m65 */
#else
lneg()
{
  oljsr("lnegax");
}

#endif

/* Form one's complement of primary register */
#ifdef M6502
/* defined in xobj.m65 */
#else
com()
{
  oljsr("complax");
}

#endif

/* Increment the primary register by one */
#ifdef M6502
/* defined in xobj.m65 */
#else
inc()
{
  oljsr("incax1");
}

#endif

/* Decrement the primary register by one */
#ifdef M6502
/* defined in xobj.m65 */
#else
dec()
{
  oljsr("decax1");
}

#endif

/* 
  Following are the conditional operators 
  They compare the secondary register against the primary 
  and put a literal 1 in the primary if the condition is 
  true, otherwise they clear the primary register 
*/

/* Test for equal */
#ifdef M6502
/* defined in xobj.m65 */
#else
eq()
{
/*
  oljsr("toseqax");
  popsp();
*/
  oljsrpop("toseqax");
}

#endif

/* zero-p */
#ifdef M6502
/* defined in xobj.m65 */
#else
eq0()
{
/*
  oljsr("axzerop");
  popsp();	... bug???
*/
  oljsr("axzerop");
}

#endif

/* Test for not equal */
#ifdef M6502
/* defined in xobj.m65 */
#else
ne()
{
/*
  oljsr("tosneax");
  popsp();
*/
  oljsrpop("tosneax");
}

#endif

/* Test for less than */
lt(uns)
int uns;   /* unsigned_p */
{
/*
  oljsr(uns ? "tosultax" : "tosltax");
  popsp();
*/
  oljsrpop(uns ? "tosultax" : "tosltax");
}

/* Test for less than or equal to */
le(uns)
int uns;		/* unsigned_p */
{
/*
  oljsr(uns ? "tosuleax" : "tosleax");
  popsp();
*/
  oljsrpop(uns ? "tosuleax" : "tosleax");
}

/* Test for greater than */
gt(uns)
int uns;		/* unsigned_p */
{
/*
  oljsr(uns ? "tosugtax" : "tosgtax");
  popsp();
*/
  oljsrpop(uns ? "tosugtax" : "tosgtax");
}

/* Test for greater than or equal to */
ge(uns)
int uns;		/* unsigned_p */
{
/*
  oljsr(uns ? "tosugeax" : "tosgeax");
  popsp();
*/
  oljsrpop(uns ? "tosugeax" : "tosgeax");
}

#ifdef old_cruft
...no longer need unsigned stuff...
/* Test for less than (unsigned) */
ult()
{
  oljsr("tosultax");
  popsp();
}

/* Test for less than or equal to (unsigned) */
ule()
{
  oljsr("tosuleax");
  popsp();
}

/* Test for greater than (unsigned) */
ugt()
{
  oljsr("tosugtax");
  popsp();
}

/* Test for greater than or equal to (unsigned) */
uge()
{
  oljsr("tosugeax");
  popsp();
}

#endif

#ifdef busted
static char dollr = '$';
static char xdig[4];
static char xd0 = '\0';
#endif

outdec(numbr)
int numbr;
{
char digit[8];
int i;
#ifdef busted
/* use bummed assembler routine */
/* OOps!  Can't use itoa here, cause it uses the FP routines, and
  they trash page 5, which we use for the symbol table.  print it in hex.
  itoa(numbr, digit);
*/
  digit[0] = '$';
  for (i = 3; i >= 0; --i)
    {
      xdig[i] = (numbr & 0x0F) + '0';
      numbr >> = 4;
    }
/*  ot(digit); */
  ot(&dollr);
#else

  if (numbr < 0)
    {
      numbr = -numbr;
      outchar('-');
    }
  digit[0] = 0;
  for (i = 0; numbr > 0;)
    {
      digit[i] = numbr % 10;
      numbr = numbr / 10;
      ++i;
    }
  if (i == 0)
    i = 1;
  while (i > 0)
    outchar(digit[--i] + '0');
#endif
}

outdecnl(n)
int n;
{
  outdec(n);
  nl();
}

outcch(numbr)
int numbr;
{
  outdec(numbr);
}

outlab(lbl)
int lbl;
{
  outchar('L');
  outdec(lbl);
}

outlabnl(lbl)
char * lbl;
{
  outlab(lbl);
  nl();
}

outcdf(labl)
int labl;
{
  outlab(labl);
  ol(":");
}

outdat(n)
int n;
{
  ol(";;; outdat");
}

outslt(n)
int n;
{
/* this seems to get used for constant string values? */
  outlab(litlab); /* output current lit pool labl */
  outchar('+');
  outdec(n);
  nl();
}

/* reserve static storage, n bytes */
outsp(n)
int n;
{
  ot("\t.blkb\t");
  outdec(n);
  nl();
#ifdef M6502
  flushout();
#endif
}

/* output a row of bytes as a constant */
outbv(bytes, nbytes)
char * bytes;
int nbytes;
{
int bpl;

  while (nbytes)
    {
      flushout(); /* keep output buf from overflowing */
      nbytes -= (bpl = (nbytes > 16) ? 16 : nbytes);
      bytepref();
    kludge1:
      outdec(*bytes++);
      if (--bpl)
	{
	  outchar(',');
	  goto kludge1;
	}
      outchar('\n');
    }
}

/* prefix for word-sized constant */
wordpref()
{
  ot("\t.word\t");
}

/* prefix for byte-sized constant */
bytepref()
{
  ot("\t.byte\t");
}

/* print a global name into the asm file.  This probly supercedes the
   one below... */
outgbl(name)
char * name;
{
  outchar('_');
  ot(name);
}

outgblnl(name)
{
  outgbl(name);
  nl();
}

outgblc(name)
char * name;
{
  outgbl(name);
  ol(":");
}

/* output a global or external name */

outgoe(sname)
char * sname;
{
  ot("\t.globl\t");
  outgblnl(sname);
#ifdef M6502
  flushout();
#endif
}

#ifdef M6502
/* defined in xobj.m65 */
#else
popsp()
{
  return (oursp += 2);
}

#endif

#ifdef M6502
/* defined in xobj.m65 */
#else
pushsp()
{
  return (oursp -= 2);
}

#endif
