/* addrmode.c */
/* parse address mode of operand */

#include "glulxa.h"

unsigned char ParseOperand (char * buf, long * value)
{
  char tbuf [64];
  int tlen;
  
  /* check for null addressing mode */
  if (strlen(buf) == 0)
  {
    (*value) = 0;
    return ADDR_NULL;
  }

  if (strcmp(buf, "~") == 0)
  {
    (*value) = 0;
    return ADDR_NULL;
  }

  /* check for (sp) */

  if (strcmp(buf, "(sp)") == 0)
  {
    (*value) = 0;
    return ADDR_SP;
  }

  /* Now check for the various indirect addressing modes */
  switch (buf[0])
  {
  case '[':
    /* RAM Address */
    
    /* Get the address section */
    tlen = strcspn (buf+1, "]");
    tbuf[0] = '\0';
    strncat (tbuf, buf+1, tlen);

    /* what is its value ? */
    ParseValue (tbuf, value);

    /* little hack to change the value - RAM addresses are relative to RAMSTART */
    (*value) -= headsize;
    (*value) -= romdsize;
    (*value) -= romrsize;
    
    switch (buf[strlen(buf)-1])
    {
    case 'b':
      return ADDR_RAMB;
    case 'w':
      return ADDR_RAMW;
    case 'l':
      return ADDR_RAML;
    default:
      return ADDR_ERR;
    }
    
  case '{':
    /* Call Frame Local */
    
    /* Get the address section */
    tlen = strcspn (buf+1, "}");
    tbuf[0] = '\0';
    strncat (tbuf, buf+1, tlen);

    /* what is its value ? */
    ParseValue (tbuf, value);

    switch (buf[strlen(buf)-1])
    {
    case 'b':
      return ADDR_CFLB;
    case 'w':
      return ADDR_CFLW;
    case 'l':
      return ADDR_CFLL;
    default:
      return ADDR_ERR;
    }
    
  case '(':
    /* Generic Address */
    
    /* Get the address section */
    tlen = strcspn (buf+1, ")");
    tbuf[0] = '\0';
    strncat (tbuf, buf+1, tlen);

    /* what is its value ? */
    ParseValue (tbuf, value);

    switch (buf[strlen(buf)-1])
    {
    case 'b':
      return ADDR_ADDB;
    case 'w':
      return ADDR_ADDW;
    case 'l':
      return ADDR_ADDL;
    default:
      return ADDR_ERR;
    }
    
  default:
        
    /* Get the address section */
    tlen = strcspn (buf, ".");
    tbuf[0] = '\0';
    strncat (tbuf, buf, tlen);

    /* what is its value ? */
    ParseValue (tbuf, value);

    switch (buf[strlen(buf)-1])
    {
    case 'b':
      return ADDR_ABSB;
    case 'w':
      return ADDR_ABSW;
    case 'l':
      return ADDR_ABSL;
    default:
      /* Determine the most efficient addressing mode.
       * Labels are always referred to by ABSL, because of limitations
       * elsewhere. */
      if (tbuf[0] == ':')
        return ADDR_ABSL;
      else if (*value == 0)
        return ADDR_NULL;
      else if ((*value <= 0x7F) && (*value >= -0x80))
        return ADDR_ABSB;
      else if ((*value <= 0x7FFF) && (*value >= -0x8000))
        return ADDR_ABSW;
      else
        return ADDR_ABSL;
    }
  }
}

    
void ParseValue (char * buf, long * value)
{
  long myvalue;
  label * mylabel;
  char* offset;
  
  offset = strchr(buf+1, ':');
  if (offset)
  {
  	*offset = '\0';
	offset++;
  }

  if (IsLabel(buf, &mylabel))
  {
    if (mylabel)
      (*value) = mylabel->offset;
    else
      (*value) = 0;
    
    /*printf("label lookup %s @ %x\n", buf, *value);*/
    if (offset)
    {
        long nval;
	ParseValue(offset, &nval);
	*value += nval;
    }
    return;
  }
  
  if (ParseNumber (buf, value))
    return;

  else
  {
    printf ("ERROR - Unparseable operand - File %s, line %d\n", filename,linenum);
    asserrors++;
  }
  
}
