Source file: /~heha/hs/gputils64-210929.zip/gpasm/util.cpp

/* Some helpful utility functions for gpasm.
   Copyright 1998-2005	James Bowman, Craig Franklin
   Copyright 2015-2016	Molnár Károly
*/

#include "stdhdr.h"

#include "libgputils.h"
#include "gpasm.h"
#include "gpmsg.h"
#include "directive.h"
#include "coff.h"

#define STR_INHX8M                  "inhx8m"
#define STR_INHX8S                  "inhx8s"
#define STR_INHX16                  "inhx16"
#define STR_INHX32                  "inhx32"

#define HVM_NONE	            0
#define HVM_BEGIN	            1
#define HVM_NAME	            2

/*------------------------------------------------------------------------------------------------*/
// convert character to numerical code for conversion.
// Returns a value greater than base for illegal character.
// On Z80, it was tricky "add 0x90, daa, adc 0x40, daa" for [0-9A-F]
static unsigned _toN(int c) {
  if (c<='9') c-='0';
  else if ((c&=~0x20)>='A') c-='A'-10;
  return (unsigned)c;
}
/*------------------------------------------------------------------------------------------------*/

/* MPASM compatible stripped version of strtoul:
 * The _strtoi returns low sizeof(int) * 8 bits of the value:
 * value & ((2 ^ (sizeof(int) * 8)) - 1);
 * The strtoul returns ULONG_MAX if the correct value is outside the range
 * of representable values (see man strtoul). The behavior is also
 * platform dependent: (int)strtoul("123456789", 16) returns -1 on 32 bit
 * platforms and 0x23456789 on 64 bit platforms. */

static int _strtoi(const char *s, char **endptr, unsigned radix) {
  unsigned value = 0;
  int sign  = 1;

  if (*s == '-') {
    sign = -1;
    ++s;
  }

  for(;;++s) {
    unsigned digit = _toN(*s);
    if (digit >= radix) break;
    value = value * radix + digit;
  }

  if (endptr) *endptr = (char *)s;
  return (int)value * sign;
}

/*------------------------------------------------------------------------------------------------*/
void _error_illegal_char(char c) {
  char  buf[80];
  snprintf(buf, sizeof(buf),
	isprint((unsigned char)c)
	? "Illegal character '%c' in numeric constant."
	: "Illegal character %#x in numeric constant.",c);
  gpmsg0(GPE_UNKNOWN, buf);
}

int string_to_int(const char *String, int Radix) {
  char *endptr;
  char  ch;
  int value = _strtoi(String, &endptr, Radix);
  if (endptr && !!(ch = *endptr)) _error_illegal_char(ch);
  return value;
}

/*------------------------------------------------------------------------------------------------*/

/*
    String: An text which is probably represents a number.
    Type  : An address of type of number: bin, dec, oct or hex
*/

long gp_strtol(const char *String, numstring_t *Type) {

  assert(String);
  assert(Type);

  *Type  = NUM_STR_UNKNOWN;
  int length = strlen(String);
  if (!length) return 0;

  char ch0 = String[0];
  char ch1 = String[1];
  const char*strlast = String+length-1;
  char ch_last = *strlast;
  int base = 0;

  if (ch1 == '\'' && ch_last==ch1 && length>3) {
    switch (ch0|0x20) {
      case 'b':	base = 2; break;	// b'___' OR B'___'
      case 'o':
      case 'q':	base = 8; break;	// q'___' OR Q'___'
      case 'd':	base = 10; break;	// d'___' OR D'___'
      case 'h':	base = 16; break;	// h'___' OR H'___'
    }
    if (base) {String += 2; goto conv;}
  }

  if (ch0 == '.' && length>1) {		// .___
    ++String; ++strlast;
    base = 10; goto conv;
  }

  if (ch0 == '0' && length>2) {
    switch (ch1|0x20) {
      case 'x': base = 16; break;	// 0x___ OR 0X___
      case 'b': base = 2; break;
      case 'o':
      case 'q': base = 8; break;
      case 'd': base = 10; break;
    }
    if (base) {
      String += 2;
      ++strlast;
      goto conv;
    }
  }

  if (length>1) switch (ch_last|0x20) {
    case 'b': base = 2; break;	// ___b OR ___B
    case 'o':			// ___o OR ___O
    case 'q': base = 8; break;	// ___q OR ___Q
    case 'd': base = 10; break;	// ___d OR ___D (overrides hexadecimal radix setting!! A bug?)
    case 'h': base = 16; break;	// ___h OR ___H 
  }
  if (base) goto conv;	// no change of String an strend

  base = state.radix;	// ___
  ++strlast;
conv:
  char*end;
  long val = strtol(String, &end, base);
  if (end==strlast) *Type = (numstring_types)base;	// report success
  return val;
}

/*------------------------------------------------------------------------------------------------*/

static bool _find_hv_macro_start(const char *String, const char **Start, const char **Body) {
  const char *ptr;
  char        ch;
  int         _state;

  if ((String == NULL) || (*String == '\0')) {
    return false;
  }

  ptr = String;
  do {
    *Start = NULL;
    *Body  = NULL;
    _state = HVM_NONE;
    do {
      /* Find the beginner '#' character. */
      while (true) {
        if ((ch = *ptr) == '\0') {
          /* Too soon it's over the string. */
          return false;
        }

        if (ch == '#') {
          /* This is the beginner '#' character. */
          *Start = ptr;
          ++ptr;
          _state = HVM_BEGIN;
          break;
        }

        ++ptr;
      }

      /* Skip the ' ' characters. */
      while (true) {
        if ((ch = *ptr) == '\0') {
          /* Too soon it's over the string. */
          return false;
        }

        if (ch != ' ') {
          break;
        }

        ++ptr;
      }
      /* Next if this a '#' character. */
    } while (ch == '#');

    /* Find the 'v' or 'V' character. */
    do {
      if ((ch = *ptr) == '\0') {
        /* Too soon it's over the string. */
        return false;
      }

      ++ptr;

      if ((ch == 'v') || (ch == 'V')) {
        /* This a "v" macro. */
        _state = HVM_NAME;
        break;
      }
    } while (ch == ' ');
  } while (_state != HVM_NAME);

  /* Find the '(' character. */
  while (true) {
    ch = *ptr;

    if (ch == '(') {
      /* This a body of "v" macro. */
      *Body = ptr;
      return true;
    }

    if ((ch == '\0') || (ch != ' ')) {
      /* Too soon it's over the string or this no space character. */
      return false;
    }

  ++ptr;
  }
}

/*------------------------------------------------------------------------------------------------*/

bool find_hv_macro(const char *String, const char **Start, const char **End) {
  const char *ptr;
  int         bracket_count;
  char        ch;
  const char *body;

  if (!String || !*String) return false;

  if (!_find_hv_macro_start(String, Start, &body)) return false;

  bracket_count = 1;
  ptr = body;
  while (true) {
    ++ptr;
    if ((ch = *ptr) == '\0') {
      break;
    }

    if (ch == '(') {
      ++bracket_count;
    }
    else if (ch == ')') {
      --bracket_count;
    }

    if (bracket_count == 0) {
      /* Found the ending bracket, finish. */
      ++ptr;
      *End = ptr;
      return true;
    }
  }

  return false;
}

/*------------------------------------------------------------------------------------------------*/
static const char escapes[]="abtnvfr";	// well-known codes 7 .. 13

char gpasm_escape(char c) {
  if (!c) return c;
  if (c=='e') return 0x27;
  {
    char*p=strchr(escapes,c);
    if (p) return '\a'+(p-escapes);
  }
  return c;
}

/*------------------------------------------------------------------------------------------------*/

/*
  convert_escaped_char(char *src,char c)
  Input: pointer to a string
  Output returns the input string with escaped char converted to a regular char
  For example if escaped character is a double quote then:
  This is a escaped quote: \"
  is translated to:
  This is a escaped quote: "
  This function performs in-place modification.
*/

char* convert_escaped_char(char *Str, char Ch) {
  const char *s=Str;
  char       *d=Str;
  if (!Str) return Str;
  while (*s) {
    if (*s == '\\' && s[1] == Ch) ++s;
    *d++ = *s++;
  }
  *d = 0;
  return Str;
}


static bool isutf8trail(uint8_t c) {
  return 0x80<=c && c<0xC0;
}

/* Get the next character code of C-style-escaped string.
   With <utf>, <Ps> is expected to be an UTF-8 string,
   and the function returns true (21-bit) characters.
   Use case 1: UTF8 input is converted to ISO-Latin1 for character displays.
   Use case 2: USB where "du" statement generates UTF-16 string descriptors.
   Otherwise, encoding is out of concern, and bytes are returned as-is.
   Return a pointer to the next character. */
const char* convert_escape_chars(const char *Ps, int *Value, bool utf) {
  uint8_t ch = *Ps;
  if (ch=='\\') {	// escape char, convert its value and write to the pnew string
    ch = *++Ps;
    *Value = 0;
    if (!ch) gpmsg0(GPE_UNKNOWN, "Missing value in \\ escape character.");
    else if ('0'<=ch && ch<='7') {	// octal number as character code
      unsigned count = ch<'4' ? 3 : 2;	// allow maximum of 255 by lenght-limit
      do{
        *Value = *Value<<3 | ch - '0';
        ch = *++Ps;		// get next character
      }while (--count && '0'<=ch && ch<='7');
    }else if (ch=='x' || utf && ch=='u') {	// two-digit hex number / unicode constant
      int count = ch=='u' ? 6 : 2;	// "\x" allow maximum of 255 by length-limit
      ch = *++Ps;
      if (!ch) gpmsg0(GPE_UNKNOWN, "Missing hex value in \\x escape character.");
      else{
        unsigned n = _toN(ch);
        if (n>=16) _error_illegal_char(ch);
        else do{
	  *Value = *Value<<4 | n;
	  ch = *++Ps;		// get next character
	}while (--count && (n=_toN(ch))<16);
      }
    }else{
      *Value = gpasm_escape(ch);
      ++Ps;	// point to next character to parse
    }  
  }else{
    int u;
// check input string for valid UTF-8 entity by lookahead, and return character if so
    if (utf && ch>=0xC2) {
      if (ch<0xE0
      && isutf8trail(Ps[1])) {
        *Value=ch<<6&0x7C0|Ps[1]&0x3F;
        ++Ps;
      }else if (0xE0<=ch && ch<0xF0
      && isutf8trail(Ps[1])
      && isutf8trail(Ps[2])
      && (u=ch<<12&0xF000|Ps[1]<<6&0xFC0|Ps[2]&0x3F)>=0x800) {
        *Value=u;
        Ps+=2;
      }else if (0xF0<=ch && ch<0xF8
      && isutf8trail(Ps[1])
      && isutf8trail(Ps[2])
      && isutf8trail(Ps[3])
      && (u=ch<<18&0x1C0000|Ps[1]<<12&0x3F000|Ps[2]<<6&0xFC0|Ps[3]&0x3F)>=0x10000
      && u<0x110000) {
        *Value=u;
        Ps+=3;
      }else *Value = ch;
    }else *Value = ch;
    if (ch) ++Ps;
  }
  return Ps;
}

/*------------------------------------------------------------------------------------------------*/

/* In some contexts, such as in the operand to a literal instruction, a
 * single-character string literal in an expression can be coerced to a
 * character literal. coerce_str1 converts a string-type pnode to a
 * constant-type pnode in-place. */

void coerce_str1(pnode_t *Exp) {
  int         value;
  const char *pc;

  if ((Exp) && PnIsString(Exp)) {
    pc = convert_escape_chars(PnString(Exp), &value,true);

    if (!*pc) {
      /* castable string, make the conversion */
      Exp->tag        = PTAG_CONSTANT;
      PnConstant(Exp) = value;
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

bool
set_symbol_attr(int *Section_number, unsigned *Class, enum gpasmValTypes Type)
{
  int          n_section;
  unsigned cl;
  bool   valid;

  switch (Type) {
    case VAL_EXTERNAL:
      n_section = N_UNDEF;
      cl        = C_EXT;
      valid     = true;
      break;

    case VAL_GLOBAL:
      n_section = state.obj.section_num;
      cl        = C_EXT;
      valid     = true;
      break;

    case VAL_STATIC:
      n_section = state.obj.section_num;
      cl        = C_STAT;
      valid     = true;
      break;

    case VAL_ADDRESS:
      n_section = state.obj.section_num;
      cl        = C_LABEL;
      valid     = true;
      break;

    case VAL_ABSOLUTE:
      n_section = N_ABS;
      cl        = C_NULL;
      valid     = true;
      break;

    case VAL_DEBUG:
      n_section = N_DEBUG;
      cl        = C_NULL;
      valid     = true;
      break;

    default:
      valid     = false;
  }

  if (valid) {
    if (Section_number) {
      *Section_number = n_section;
    }

    if (Class) {
      *Class = cl;
    }
  }

  return valid;
}

/*------------------------------------------------------------------------------------------------*/

void set_global(const char *Name, gpasmVal Value, enum gpasmValTypes Type,
	bool Proc_dependent, bool Has_no_value) {
  symbol_t*    sym;
  variable_t*  var;
  unsigned flags;
  int          section_number;
  unsigned pclass;
  char*        coff_name;

  /* Search the entire stack (i.e. include macro's local symbol tables) for the symbol.
     If not found, then add it to the global symbol table.  */
  sym = gp_sym_get_symbol(state.stTop, Name);

  if (!sym) {
    sym = gp_sym_add_symbol(state.stGlobal, Name);
  }

  var = (variable_t *)gp_sym_get_symbol_annotation(sym);

  if (!var) {
    /* pnew symbol */
    flags  = (Proc_dependent) ? VATRR_PROC_DEPENDENT : 0;
    flags |= (Has_no_value)   ? VATRR_HAS_NO_VALUE   : 0;

    var = new variable_t;
    var->value              = Value;
    var->coff_symbol_num    = state.obj.symbol_num;
    var->coff_section_num   = state.obj.section_num;
    var->coff_section_flags = state.obj.new_sect_flags;
    var->type               = Type;
    var->previous_type      = Type;
    var->flags              = flags;
    gp_sym_annotate_symbol(sym, var);

    if (set_symbol_attr(&section_number, &pclass, Type)) {
      /* Increment the index into the coff symbol table for the relocations. */
      state.obj.symbol_num++;
    }
  }
  else if (Type == VAL_VARIABLE) {
    /*
     * TSD - the following embarrassing piece of code is a hack
     *       to fix a problem when global variables are changed
     *       during the expansion of a macro. Macros are expanded
     *       by running through them twice. If you have a statement
     *       like:
     *       some_var set some_var + 1
     *       Then this is incremented twice! So the if statement
     *       makes sure that the value is assigned on the second
     *       pass only in the macro. Jeez this really sucks....
     */
     var->value = Value;
     FlagClr(var->flags, VATRR_HAS_NO_VALUE);
  }
  else if (state.pass == 2) {
    if (FlagIsSet(var->flags, VATRR_HAS_NO_VALUE)) {
      msg_has_no_value(NULL, Name);
    }
    else if (var->value != Value) {
      gpmsg(GPE_DIFFLAB, NULL, Name);
    }

    coff_name = coff_local_name(Name);
    coff_add_sym(coff_name, Value, var->type, state.obj.section_num);

    if (coff_name) {
      free(coff_name);
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

/* Returns descriptor of a constant or variable. */

variable_t *
get_global_constant(const char *Name)
{
  const symbol_t *sym;
  variable_t     *var;

  if (((sym = gp_sym_get_symbol(state.stGlobal, Name))) &&
      ((var = (variable_t*)gp_sym_get_symbol_annotation(sym))) &&
      ((var->type == VAL_CONSTANT) || (var->type == VAL_VARIABLE))) {

    if (FlagIsSet(var->flags, VATRR_HAS_NO_VALUE)) {
      gpmsg(GPW_SYM_NO_VALUE, NULL, Name);
    }

    return var;
  }

  return NULL;
}

/*------------------------------------------------------------------------------------------------*/

/* Clean out the variables. */

void
delete_variable_symbols(symbol_table_t *Table)
{
  size_t            i;
  symbol_t         *sym;
  const variable_t *var;

  if (Table == NULL) {
    return;
  }

  for (i = 0; i < gp_sym_get_symbol_count(Table); ) {
    sym = gp_sym_get_symbol_with_index(Table, i);
    assert(sym);

    var = (const variable_t *)gp_sym_get_symbol_annotation(sym);

    if ((var) && (var->type == VAL_VARIABLE)) {
      gp_sym_remove_symbol_with_index(Table, i);
    }
    else {
      ++i;
    }
  }

  delete_variable_symbols(gp_sym_get_guest_table(Table));
}

/*------------------------------------------------------------------------------------------------*/

/* Clean out the processor dependent variables. */

void
delete_processor_variable_symbols(symbol_table_t *Table)
{
  size_t            i;
  symbol_t         *sym;
  const variable_t *var;

  if (Table == NULL) {
    return;
  }

  for (i = 0; i < gp_sym_get_symbol_count(Table); ) {
    sym = gp_sym_get_symbol_with_index(Table, i);
    assert(sym);

    var = (const variable_t *)gp_sym_get_symbol_annotation(sym);

    if ((var) && (var->type == VAL_VARIABLE) && FlagIsSet(var->flags, VATRR_PROC_DEPENDENT)) {
      gp_sym_remove_symbol_with_index(Table, i);
    }
    else {
      ++i;
    }
  }

  delete_processor_variable_symbols(gp_sym_get_guest_table(Table));
}

/*------------------------------------------------------------------------------------------------*/

void
select_error_level(int Level)
{
  if (state.cmd_line.error_level) {
    gpmsg(GPM_SUPVAL, NULL);
  }
  else {
    if ((Level >= 0) && (Level <= 2)) {
      if (state.cmd_line.strict_level && (state.strict_level > 0)) {
        /*
          The "strict messages" more important than the other messages, therefore
          enable each messages.
        */
        state.error_level = 0;
      }
      else {
        state.error_level = Level;
      }
    }
    else {
      if (state.pass == 0) {
        fprintf(stderr, "Error: Invalid warning level \"%i\".\n", Level);
      }
      else {
        gpmsg0(GPE_ILLEGAL_ARGU, "Expected w= 0, 1, 2");
      }
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

void
select_strict_level(int Level)
{
  if (state.cmd_line.strict_level) {
    gpmsg(GPM_SUPVAL, NULL);
  }
  else {
    if ((Level >= 0) && (Level <= 2)) {
      state.strict_level = Level;

      if ((Level > 0) && state.cmd_line.error_level) {
        /* Enable each messages. */
        state.error_level = 0;
      }
    }
    else {
      if (state.pass == 0) {
        fprintf(stderr, "Error: Invalid strict level \"%i\".\n", Level);
      }
      else {
        gpmsg0(GPE_ILLEGAL_ARGU, "Expected S= 0, 1, 2");
      }
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

void
select_expand(const char *Expand)
{
  if (state.cmd_line.macro_expand) {
    gpmsg(GPM_SUPLIN, NULL);
  }
  else {
    if (strcasecmp(Expand, "on") == 0) {
      state.lst.expand = true;
    }
    else if (strcasecmp(Expand, "off") == 0) {
      state.lst.expand = false;
    }
    else {
      state.lst.expand = true;

      if (state.pass == 0) {
        fprintf(stderr, "Error: Invalid option: \"%s\"\n", Expand);
      }
      else {
        gpmsg0(GPE_ILLEGAL_ARGU, "Expected ON or OFF.");
      }
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

void
select_hex_format(const char *Format_name)
{
  if (state.cmd_line.hex_format) {
    gpmsg(GPW_CMDLINE_HEXFMT, NULL);
  }
  else {
    if (strcasecmp(Format_name, STR_INHX8M) == 0) {
      state.hex_format = INHX8M;
    }
    else if (strcasecmp(Format_name, STR_INHX8S) == 0) {
      state.hex_format = INHX8S;
    }
    else if (strcasecmp(Format_name, STR_INHX16) == 0) {
      state.hex_format = INHX16;
    }
    else if (strcasecmp(Format_name, STR_INHX32) == 0) {
      state.hex_format = INHX32;
    }
    else {
      state.hex_format = INHX8M;

      if (state.pass == 0) {
        fprintf(stderr, "Error: Invalid format: \"%s\"\n", Format_name);
      }
      else {
        gpmsg0(GPE_ILLEGAL_ARGU, "Expected " STR_INHX8M ", " STR_INHX8S ", " STR_INHX16 ", or " STR_INHX32 ".");
      }
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

void
select_radix(const char *Radix_name)
{
  if (state.cmd_line.radix) {
    gpmsg(GPW_CMDLINE_RADIX, NULL);
  }
  else {
    if ((strcasecmp(Radix_name, "h") == 0) ||
        (strcasecmp(Radix_name, "hex") == 0) ||
        (strcasecmp(Radix_name, "hexadecimal") == 0)) {
      state.radix = 16;
    }
    else if ((strcasecmp(Radix_name, "d") == 0) ||
             (strcasecmp(Radix_name, "dec") == 0) ||
             (strcasecmp(Radix_name, "decimal") == 0)) {
      state.radix = 10;
    }
    else if ((strcasecmp(Radix_name, "o") == 0) ||
             (strcasecmp(Radix_name, "oct") == 0) ||
             (strcasecmp(Radix_name, "octal") == 0)) {
      state.radix = 8;
    }
    else {
      state.radix = 16;

      if (state.pass == 0) {
        fprintf(stderr, "Error: Invalid radix \"%s\", will use hex.\n", Radix_name);
      }
      else {
        gpmsg(GPW_RADIX, NULL);
      }
    }
  }
}

/*------------------------------------------------------------------------------------------------*/

gpasmVal
do_or_append_insn(const char *Op, pnode_t *Parms)
{
  if (IN_MACRO_WHILE_DEFINITION) {
    if (strcasecmp(Op, "endm") == 0) {
      return do_insn(Op, Parms);
    }
    else if (IN_WHILE_DEFINITION) {
      if (strcasecmp(Op, "while") == 0) {
        assert(state.while_depth != 0);
        ++state.while_depth;
      }
      else if ((state.while_head) && (strcasecmp(Op, "endw") == 0)) {
        assert(state.while_depth != 0);
        if (--state.while_depth == 0) {
          return do_insn(Op, Parms);
        }
      }
    }

    macro_append();
    return 0;
  }
  else {
    return do_insn(Op, Parms);
  }
}

/*------------------------------------------------------------------------------------------------*/

/* Build string from a macro parameter list.*/

char *
macro_params_to_string(char *String, size_t String_max_length, size_t *Length, const pnode_t *Macro_params)
{
  size_t l;
  size_t size;

  if (Macro_params == NULL) {
    return String;
  }

  assert(String_max_length > 0);

  switch (Macro_params->tag) {
    case PTAG_LIST:
      String = macro_params_to_string(String, String_max_length, Length, PnListHead(Macro_params));
      String = macro_params_to_string(String, String_max_length, Length, PnListTail(Macro_params));
      break;

    case PTAG_SYMBOL: {
      if (!String) {
        String    = new char[String_max_length + 1];
        String[0] = '\0';
        *Length   = 0;
      }

      l = *Length;

      if (!l) {
        /* This is the first parameter. */
        l = snprintf(String, String_max_length, "%s", PnSymbol(Macro_params));
      }else{
        size = String_max_length > l ? String_max_length - l : 0;

        if (size > 0) {
          l += snprintf(String + l, size, ", %s", PnSymbol(Macro_params));
        }
      }

      *Length = l;
      break;
    }

    case PTAG_CONSTANT:
    case PTAG_STRING:
    case PTAG_OFFSET:
    case PTAG_BINOP:
    case PTAG_UNOP:
    default:
      break;
  }

  return String;
}

/*------------------------------------------------------------------------------------------------*/

const char *
variable_type_to_str(enum gpasmValTypes Type)
{
  switch (Type) {
    case VAL_CONSTANT: return "CONSTANT";
    case VAL_VARIABLE: return "VARIABLE";
    case VAL_ADDRESS:  return "ADDRESS";
    case VAL_CBLOCK:   return "CBLOCK";
    case VAL_EXTERNAL: return "EXTERNAL";
    case VAL_GLOBAL:   return "GLOBAL";
    case VAL_STATIC:   return "STATIC";
    case VAL_ABSOLUTE: return "ABSOLUTE";
    case VAL_DEBUG:    return "DEBUG";
    default:           return "UNKNOWN";
  }
}

/*------------------------------------------------------------------------------------------------*/

const char *
value_type_to_str(const variable_t *Variable, bool Previous)
{
  enum gpasmValTypes type;

  if (Variable == NULL) {
    return NULL;
  }

  type = (Previous) ? Variable->previous_type : Variable->type;
  return variable_type_to_str(type);
}

/*------------------------------------------------------------------------------------------------*/

const char *
pnode_symbol_name(const pnode_t *Pnode)
{
  return (PnIsSymbol(Pnode) ? PnSymbol(Pnode) : NULL);
}

/*------------------------------------------------------------------------------------------------*/

gpasmVal pnode_symbol_value(const pnode_t* Pnode) {
  if (!PnIsSymbol(Pnode)) return 0;
/*
clang-3.7.0, 3.8.0 bug.
    if (strcmp(PnSymbol(Pnode), "$")) {
*/
  const char* name = PnSymbol(Pnode);
  if ((name[0] == '$') && (name[1] == '\0')) {
    int addr = (IS_RAM_ORG ? state.byte_addr :
                           gp_processor_insn_from_byte_p(state.processor, state.byte_addr));
    return addr;
  }
  const symbol_t* sym = gp_sym_get_symbol(state.stTop, PnSymbol(Pnode));
  assert(sym);
  const variable_t* var = (const variable_t*)gp_sym_get_symbol_annotation(sym);
  assert(var);
  return var->value;
}

/*------------------------------------------------------------------------------------------------*/

const char* pnode_string(const pnode_t *Pnode) {
  return (PnIsString(Pnode) ? PnString(Pnode) : NULL);
}

/*------------------------------------------------------------------------------------------------*/

void msg_has_no_value(const char *Optional_text, const char *Symbol_name) {
  if (state.mpasm_compatible) return;

  switch (state.strict_level) {
    case 1:
      gpmsg(GPW_SYM_NO_VALUE, Optional_text, Symbol_name);
      break;

    case 2:
      gpmsg(GPE_SYM_NO_VALUE, Optional_text, Symbol_name);
      break;
  }
}

/*------------------------------------------------------------------------------------------------*/

/*
static void
_print_macro_line(const macro_body_t *Mac)
{
  if (Mac->src_line) {
    printf(" src_line = %s\n", Mac->src_line);
  }
}

static void
_print_macro_body(const macro_body_t *Mac)
{
  const macro_body_t *mb = Mac;

  printf("{\n");
  while(mb) {
    _print_macro_line(mb);
    mb = mb->next;
  }
  printf("}\n");
}*/

/*------------------------------------------------------------------------------------------------*/

/* Function to append a line to an ongoing macro definition. */

void macro_append() {
  macro_body_t* body = new macro_body_t;

  body->src_line = NULL;
  body->next     = NULL;            /* make sure it's terminated */

  *state.mac_prev = body;           /* append this to the chain */
  state.mac_prev  = &body->next;    /* this is the pnew end of the chain */
  state.mac_body  = body;
}

/*------------------------------------------------------------------------------------------------*/

void hex_create() {
  if (state.hex_file == OUT_SUPPRESS) {
    /* Must delete hex file when suppressed. */
    gp_writehex(state.base_file_name, state.i_memory, state.hex_format, 1, state.dos_newlines, 1);
    return;
  }

  if (!gp_writehex_check(state.i_memory, state.hex_format)) {
    gpmsg(GPE_IHEX, NULL);
    gp_writehex(state.base_file_name, state.i_memory, state.hex_format, 1, state.dos_newlines, 1);
  }else if (state.device.pclass) {
    if (!gp_writehex(state.base_file_name, state.i_memory, state.hex_format, state.num.errors,
                     state.dos_newlines, state.device.pclass->core_mask)) {
      gpmsg0(GPE_UNKNOWN, "Error generating hex file.");
      exit(1);
    }
  }else{
    /* Won't have anything to write, just remove any old files. */
    gp_writehex(state.base_file_name, state.i_memory, state.hex_format, 1, state.dos_newlines, 1);
  }
}
Detected encoding: UTF-80