static char *rcs_version = "mktuple.c V$Revision: 1.3 $";
// This module is called from i2c to perform actions based on each ICODE.

#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 500
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>

#include "i2c.h"
#include "flex.h"
#include "stringpool.h"

// OK, THIS IS VERY IMPORTANT!!!!
// The propogation upwards of type information has to be done at the point of creating the tuple!
// It can either be done immedately after every call to mktuple, or it can be done here on the
// fly in mktuple itself.  The latter may cause some code duplication but may end up being a
// cleaner interface.  The sort of propogation I'm talking about is, for example, when a BINOP
// is constructed, and the type of the binop is determined by the type of the operands.  Or it
// could be an array access, or a record field access, with the resulting types requiring some
// local knowlege in order to be handled correctly.

void COPY_FIXED_FIELDS_to_from(ASTIDX new, ASTIDX old) {
  // defaults, but need to be overridden depending on the operation
  // **** NOTE **** userfields and extended fields are NOT copied over by default!
  // This is primarily for propogating type info.  I can probably avoid copying
  // a bunch of the fields.

  BASE_TYPE(new) = BASE_TYPE(old);  // T_*  eg T_INTEGER, T_REAL
  FORM(new) = FORM(old);            // F_*  eg F_FN
  BASE_SIZE_CODE(new) = BASE_SIZE_CODE(old);
  BASE_SIZE_BYTES(new) = BASE_SIZE_BYTES(old);

  LINKAGE(new) = LINKAGE(old);  // X_*  eg X_OWN, X_CONST etc
  SPECIAL(new) = SPECIAL(old);  // SPECIAL_*

  IMP_NAME_IDX(new) = IMP_NAME_IDX(old);
  C_NAME_IDX(new) = C_NAME_IDX(old);
  EXTERNAL_NAME_IDX(new) = EXTERNAL_NAME_IDX(old);

  IS_BASE_NAME(new) = IS_BASE_NAME(old);
  IS_ARRAY(new) = IS_ARRAY(old);
  IS_ARRAY_NAME(new) = IS_ARRAY_NAME(old);

  IS_PROC_PARAM(new) = IS_PROC_PARAM(old);  // this proc/fn/map/pred is in the FP list of another procedure
  IS_SPEC_ONLY(new) = IS_SPEC_ONLY(old);    // { 8,  " spec" }, // S=1
  NO_AUTO_DEREF(new) = NO_AUTO_DEREF(old);  // { 16, " {indirect-no-auto-deref, I=1}" }
  NO_UNASS(new) = NO_UNASS(old);            // { 32, " {NO UNASSIGNED CHECKS, U=1}" }

  STRING_CAPACITY(new) = STRING_CAPACITY(old);
  RECORD_FORMAT(new) = RECORD_FORMAT(old);
  FORMAL_PARAM_LIST(new) = FORMAL_PARAM_LIST(old);
  ACTUAL_PARAM_LIST(new) = ACTUAL_PARAM_LIST(old);
  INDEX_LIST(new) = INDEX_LIST(old); // n-D arrays
  BOUNDS1D(new) = BOUNDS1D(old);     // static etc 1-D arrays
  DOPEVECTOR(new) = DOPEVECTOR(old); // These two lines *ought* to have handled the case where an array is a field within a record but that does not yet seem to be working. *TO DO*
  INITVALUES(new) = INITVALUES(old);  // index into a InitValues[] array of AST indexes.  See INIT code.

  // Remember to update mktuple_inner when adding a new field
}

void set_type_info(ASTIDX var, int base_type, int base_size_code, int base_size_bytes) {
  BASE_TYPE(var) = base_type;
  BASE_SIZE_CODE(var) = base_size_code;
  BASE_SIZE_BYTES(var) = base_size_bytes;
}

void set_expr_info(ASTIDX var, int base_type, int base_size_code, int base_size_bytes) {
  set_type_info(var, base_type, base_size_code, base_size_bytes);
  FORM(var) = F_EXPR;
  NO_UNASS(var) = 1; // no need to unassigned-check an expression
  LINKAGE(var) = X_AUTO;
}

ASTIDX cast(ASTIDX var, char *cast_string, int base_type, int base_size_code, int base_size_bytes) {
  ASTIDX new = mktuple(AST_CAST, var, strtopool(cast_string));
debug_types(var);
  COPY_FIXED_FIELDS_to_from(new, var);
  // THIS WAS WRONG!  Caused cast variables to not be unassigned-checked: set_expr_info(new, base_type, base_size_code, base_size_bytes);
  set_type_info(var, base_type, base_size_code, base_size_bytes);  
debug_types(new);
  return new;
}

int compatible_types(ASTIDX opd1, ASTIDX opd2) {
  return BASE_TYPE(opd1) == BASE_TYPE(opd2);
}

int promotable(ASTIDX from, ASTIDX to, int *base_type, int *base_size_code, int *base_size_bytes) {
  if (compatible_types(from, to)) {
    if (BASE_SIZE_BYTES(from) <= BASE_SIZE_BYTES(to)) {
      // Widen.
      *base_type = BASE_TYPE(to);
      *base_size_code = BASE_SIZE_CODE(to);
      *base_size_bytes = BASE_SIZE_BYTES(to);
      //dump_code("/*widen*/");
      return TRUE;
    }
  } else if (BASE_TYPE(from) == T_INTEGER && BASE_TYPE(to) == T_REAL) {
    // Convert int to real.  Only int <= 32 bit to double preserves accuracy,
    //                       anything else could potentially cause loss of precision
    if (BASE_SIZE_BYTES(from) >= BASE_SIZE_BYTES(to)) {
      dump_code("/*Warning: potential loss of precision converting from integer %s (%N - %d bytes)"
                " to real %s (%N - %d bytes) detected at ast.c line %d */\n",
                safe_astname(OP(from)), from, BASE_SIZE_BYTES(from),
                safe_astname(OP(to)), to, BASE_SIZE_BYTES(to), __LINE__);

    } else dump_code("/*promote*/");
    *base_type = BASE_TYPE(to);
    *base_size_code = BASE_SIZE_CODE(to);
    *base_size_bytes = BASE_SIZE_BYTES(to);
    return TRUE;    
  } else {
    // Should I handle different string lengths here?  For now, won't be called with anything except int or real
    //fprintf(stderr, "? Warning: development code ('promotable()') needs attention.\n");
    //dump_code("/*BUG: promotable at line %d*/", __LINE__);
  }
  return FALSE;
}

char *cast_string(int base_type, int base_size_code, int base_size_bytes, int want_unsigned, int want_pointer) {
  // Leave as a string for now, use stringpool later.  Easier to debug as a char*.
  switch (base_type) {
  case T_INTEGER:
    {
      if (want_unsigned) {
        if (want_pointer) {
          switch (base_size_bytes) {
          case 1: return "(unsigned char *)";
          case 2: return "(unsigned short *)";
          case 4: return "(unsigned int *)";
          case 8: return "(unsigned long long *)";
          default: break;
          }
        } else {
          switch (base_size_bytes) {
          case 1: return "(unsigned char)";
          case 2: return "(unsigned short)";
          case 4: return /*cs*/"(unsigned int)";
          case 8: return "(unsigned long long)";
          default: break;
          }
        }
      } else {
        if (want_pointer) {
          switch (base_size_bytes) {
          case 1: return "(unsigned char *)";
          case 2: return "(short *)";
          case 4: return "(int *)";
          case 8: return "(long long *)";
          default: break;
          }
        } else {
          switch (base_size_bytes) {
          case 1: return "(unsigned char)";
          case 2: return "(short)";
          case 4: return "(int)";
          case 8: return "(long long)";
          default: break;
          }
        }
      }
    } break;
  case T_REAL:
    {
      if (want_pointer) {
        switch (base_size_bytes) {
        case 4:  return "(float *)";
        case 8:  return "(double *)";
        case 16: return "(long double *)";
        default: break;
        }
      } else {
        switch (base_size_bytes) {
        case 4:  return "(float)";
        case 8:  return "(double)";
        case 16: return "(long double)";
        default: break;
        }
      }
    } break;
  default:
    //fprintf(stderr, "! Warning: bad cast requested.\n");
    break;
  }
  return /*BUG: bad cast*/"";
}

// type promotion not fully working yet...
// 3130,3131c3130,3131
// < /*Warning: potential loss of precision converting from integer AST_VAR (RADIX - 4 bytes) to real AST_VAR (RVAL - 4 bytes) detected at ast.c line 845 */
// < /*Warning: potential loss of precision converting from integer AST_VAR (I - 4 bytes) to real AST_BINOP (((_UR(RVAL)) * (_U((float)RADIX))) - 0 bytes) detected at ast.c line 845 */
// ---
// > /*Warning: potential loss of precision converting from integer AST_VAR (RADIX - 4 bytes) to real AST_VAR (RVAL - 4 bytes) detected at ast.c line 844 */
// > /*Warning: potential loss of precision converting from integer AST_VAR (I - 4 bytes) to real AST_BINOP (((_UR(RVAL)) * (_U((float)RADIX))) - 0 bytes) detected at ast.c line 844 */


// 
//  #    #  #    #   #####  #    #  #####   #       ######
//  ##  ##  #   #      #    #    #  #    #  #       #
//  # ## #  ####       #    #    #  #    #  #       #####
//  #    #  #  #       #    #    #  #####   #       #
//  #    #  #   #      #    #    #  #       #       #
//  #    #  #    #     #     ####   #       ######  ######
// 

ASTIDX mktuple_inner(StrpoolIDX file, int line, ASTCODE AST_code, ...) {
  va_list ap;
  int count = 0;
  int tuple = NEXT_AST;

  if (NEXT_AST >= MAX_AST) {
    fprintf(stderr, "* Either bump up MAX_AST or move to flex arrays.\n");
    exit(EXIT_FAILURE);
  }
  int user_field_no = 0;
  for (int i = 0; i < FIXED_FIELDS; i++) AST[tuple + i] = (int)(/*1234000*/0x80808000U + (unsigned int)i);
  SOURCE_FILE(tuple) = file;
  SOURCE_LINE(tuple) = line;
  EXTENDED_FIELD_COUNT(tuple) = 0;
  OP(tuple) = AST_code;

  NEXT_AST += FIXED_FIELDS;
  count = 0;
  va_start(ap, AST_code);
  for (;;) {
    int i = va_arg(ap, int);
    if (i == END_MARKER) break;

    USERFIELD(tuple, user_field_no) = i;
    user_field_no += 1;
    count += 1;
    NEXT_AST += 1;  // (a little redundancy was needed while debugging. can be cleaned up now))
    if (NEXT_AST >= MAX_AST) {
      fprintf(stderr, "* Either bump up MAX_AST or move to flex arrays.\n");
      exit(EXIT_FAILURE);
    }
  }
  va_end(ap);

  assert(count <= MAX_USER_FIELDS);
  USER_FIELD_COUNT(tuple) = count;

  // so the default *currently* (before I sort this out everywhere) is to set all those
  // fields to 0, which almost always signals to the programmer (once detected) that the
  // field is essentially unassigned.  (although above you may have noticed an alternative
  // hint of setting a value to 1234000+n to be even more helpful in tracking down which
  // field we forgot to initialise!)

  // So just after these assignments is where we might do some of the simpler examples
  // of type promotion.
  
  BASE_TYPE(tuple) = 0;  // T_*  eg T_INTEGER, T_REAL
  FORM(tuple) = 0;       // F_*  eg F_FN
  BASE_SIZE_CODE(tuple) = 0;
  BASE_SIZE_BYTES(tuple) = 0;

  LINKAGE(tuple) = 0;  // X_*  eg X_OWN, X_CONST etc
  SPECIAL(tuple) = 0;  // SPECIAL_*

  IMP_NAME_IDX(tuple) = 0;
  C_NAME_IDX(tuple) = 0;
  EXTERNAL_NAME_IDX(tuple) = 0;

  IS_BASE_NAME(tuple) = 0;
  IS_ARRAY(tuple) = 0;
  IS_ARRAY_NAME(tuple) = 0;

  IS_PROC_PARAM(tuple) = 0;  // this proc/fn/map/pred is in the FP list of another procedure
  IS_SPEC_ONLY(tuple) = 0;   // { 8,  " spec" }, // S=1
  NO_AUTO_DEREF(tuple) = 0;  // { 16, " {indirect-no-auto-deref, I=1}" }
  NO_UNASS(tuple) = 0;       // { 32, " {NO UNASSIGNED CHECKS, U=1}" }

  STRING_CAPACITY(tuple) = 0;
  RECORD_FORMAT(tuple) = 0;
  FORMAL_PARAM_LIST(tuple) = 0;
  ACTUAL_PARAM_LIST(tuple) = 0;
  INDEX_LIST(tuple) = 0;
  BOUNDS1D(tuple) = 0;
  DOPEVECTOR(tuple) = 0;
  INITVALUES(tuple) = 0;

  // Remember to update COPY_FIXED_FIELDS_to_from when adding a new field
  ASTIDX param1 = USERFIELD(tuple, 0); // which may not exist but we'll only look at it for cases where it does exist (below)
  switch (OP(tuple)) {
    case AST_ICONST:
      BASE_TYPE(tuple) = T_INTEGER;  // BASETYPE_INTEGER;
      long long int value = param1;
      if (((value & 0xFFFFFFFFFFFFFF00) == 0xFFFFFFFFFFFFFF00) || ((value & 0xFFFFFFFFFFFFFF00) == 0)) {
        BASE_SIZE_BYTES(tuple) = 1;
      } else if (((value & 0xFFFFFFFFFFFF0000) == 0xFFFFFFFFFFFF0000) || ((value & 0xFFFFFFFFFFFF0000) == 0)) {
        BASE_SIZE_BYTES(tuple) = 2;
      } else if (((value & 0xFFFFFFFF00000000) == 0xFFFFFFFF00000000) || ((value & 0xFFFFFFFF00000000) == 0)) {
        BASE_SIZE_BYTES(tuple) = 4;
      } else {
        BASE_SIZE_BYTES(tuple) = 8;
      }
      // TO DO: BASE_SIZE_CODE
      FORM(tuple) = F_SIMPLE;
      break;

    case AST_RCONST:
      BASE_TYPE(tuple) = T_REAL;  // BASETYPE_REAL;
      BASE_SIZE_BYTES(tuple) = 8;  // Most imp systems work in long real internally and truncate to real on loading and saving
      // TO DO: BASE_SIZE_CODE
      FORM(tuple) = F_SIMPLE;
      break;

    // TO DO: string consts. once we start supporting string lengths properly

    case AST_BRACKET:
      if (OP(param1) == AST_BRACKET) {
        // Might as well remove redundant brackets
        tuple = param1;
      }
      break;
    case AST_UNASS_CHECK:
      COPY_FIXED_FIELDS_to_from(tuple, param1);
      break;
    case AST_DIVZERO_CHECK:
      if (IS_ICONST(param1) && !IS_ZERO(param1)) {
        tuple = param1; // don't apply to a non-0 const
      } else {
        COPY_FIXED_FIELDS_to_from(tuple, param1);
      }
      break;

    case AST_ADDRESS_OF:
      if (OP(param1) == AST_INDIRECT_THROUGH) {
        tuple = USERFIELD(param1, 0); // &*X -> X
      }
      break;

    case AST_INDIRECT_THROUGH:
      if (OP(param1) == AST_ADDRESS_OF) {
        tuple = USERFIELD(param1, 0); // *&X -> X
      } else {
        COPY_FIXED_FIELDS_to_from(tuple, param1);
        // TO DO: remove %name part that causes dereferencing from the type information now that it has actually been dereferenced
        if (FORM(tuple) == F_NAME_ARRAY_NAME) {
          FORM(tuple) = F_NAME_ARRAY;
        } else if (FORM(tuple) == F_NAME) {
          FORM(tuple) = F_SIMPLE;
        } else if (FORM(tuple) == F_ARRAY_NAME) {
          FORM(tuple) = F_ARRAY;
        } else if (FORM(tuple) == F_MAP) {
          FORM(tuple) = F_SIMPLE;
        }
      }
      break;
      
    case AST_FIELDSELECT:     // mktuple(AST_FIELDSELECT, OLD_DECL_AST_TAG, subfield);
      COPY_FIXED_FIELDS_to_from(tuple, USERFIELD(tuple, 1)/*subfield*/); // So the resulting object *ought* to have all the properties of the subfield!?...
      break;
      
    case AST_ARRAYACCESS:
    case AST_DYNAMICARRAYACCESS:

      COPY_FIXED_FIELDS_to_from(tuple, param1 /* the array */);
      
      switch (FORM(tuple)) {
        case  F_ARRAY:
        case  F_ARRAY_NAME:
          FORM(tuple) = F_SIMPLE;
          break;
          
        case  F_NAME_ARRAY:
        case  F_NAME_ARRAY_NAME:
          FORM(tuple) = F_NAME;
          break;

        default: break;
      }
      
      break;
    case AST_CALL:
      COPY_FIXED_FIELDS_to_from(tuple, param1 /* the fn */);
      break;
    case AST_RESULT:
      COPY_FIXED_FIELDS_to_from(tuple, param1);
      break;
    case AST_VAR:
      // creating a var from a saved Descriptor[tag].  Done at the start of icode '@'
debug_types(tuple);
      COPY_FIXED_FIELDS_to_from(tuple, Descriptor[param1]);
debug_types(tuple);
      break;
    case AST_MONOP: {

      // These icode operations may need integer/real type handling:

        // INIT (handled elsewhere, but may not currently be handling int to real conversion explicitly, while letting C do it.)

        // FLOAT (REAL)  I'm not at all sure where expicit integer to real conversion is done in ICode.
      
        // INT    // I think all these come via perms in current ICode?
        // INPTPT
        // ROUND
        // TRUNC
      
        // v MODULUS (Absolute)
        // U Unary '-'

      // Currently only 'v' (MOD), '\' (NOT) and 'U' (NEGATE) ops are directed here.
      // Of those, only 'v' and 'U' can take real operands.
      
      int opsym = USERFIELD(tuple, 0);
      int param1 = USERFIELD(tuple, 1);

      // Negative constant folding:
      if (opsym == 'U' && IS_ICONST(param1)) {                             // U: Unary '-'
        USERFIELD(param1,0) = -USERFIELD(param1,0);  // (-(n))  =>  -n
        tuple = param1; // elide the unary '-' operation ('U') entirely.
      } else if (opsym == 'U' && IS_RCONST(param1)) {
        StrpoolIDX real = USERFIELD(param1, 0);
        char tmp[64];
        sprintf(tmp, "%s", pooltostr(real));
        // elide the unary '-' operation ('U') entirely:
        if (*tmp == '-') {
          // REMOVE THE MINUS
          tuple = mktuple(AST_RCONST, strtopool(tmp+1));
        } else {
          // ADD A MINUS
          sprintf(tmp, "-%s", pooltostr(real));
          tuple = mktuple(AST_RCONST, strtopool(tmp));
        }
      }

      param1 = USERFIELD(tuple, 1);
      // Now do any type promotions needed

      // Finally, unassigned variable checking:
      if (( BASE_TYPE(param1) == T_INTEGER || BASE_TYPE(param1) == T_REAL ) && FORM(param1) == F_SIMPLE) param1 = mktuple(AST_UNASS_CHECK, param1);
    } break;
    case AST_BINOP: {
      // detuple(tuple, &AST_check_op, 0: &opsym, 1: &param1, 2: &param2);
      BASE_TYPE(tuple) = BASE_TYPE(USERFIELD(tuple, 1));

      int opsym = USERFIELD(tuple, 0);
      int param1 = USERFIELD(tuple, 1);
      int param2 = USERFIELD(tuple, 2);
      
      // We'll also do a trivial bit of constant folding just to make the listings a smidgeon more readable.

      // Need to be careful here about replacing tuple (returned at end) and returning a tuple early!
      // There are errors showing up when run on the 64 bit linux caused by USERFIELD(??,0) being 0x80808000 etc. (i.e. an uninitialised user field)
      // I'm going to change some of these optimisations below so that they return the tuple immediately and don't go on to process it more:
      
      if ((opsym == '+' || opsym == '-') && IS_ICONST(param1) && IS_ICONST(param2)) { // fold const expression
        if (IS_ZERO(param2)) {
return          tuple = param1;
        } else  {
          int result;
          if (opsym == '+') result = USERFIELD(param1,0) + USERFIELD(param2,0); else result = USERFIELD(param1,0) - USERFIELD(param2,0);
return          tuple = mktuple(AST_ICONST, result);
        }
        
      } else if ((opsym == '-') && IS_ICONST(param2) && (USERFIELD(param2,0) < 0)) {             // x - (-n)  =>  x+n
return        tuple = mktuple(AST_BINOP, '+', param1, mktuple(AST_ICONST, -USERFIELD(param2,0)));
        
      } else if ((opsym == '+' || opsym == '-') && IS_ZERO(param2)) {                            // x+0, x-0  =>  x
return        tuple = param1;
        
      } else if (opsym == '/') { // INTEGER divide
        char *cast1, *cast2;
        cast1 = cast_string(T_INTEGER, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1), 0 /*want_unsigned*/, REQUIRES_AUTO_DEREF(param1));
        cast2 = cast_string(T_INTEGER, BASE_SIZE_CODE(param2), BASE_SIZE_BYTES(param2), 0 /*want_unsigned*/, REQUIRES_AUTO_DEREF(param2));
        
        USERFIELD(tuple, 1) = param1 = cast(param1, cast1, T_INTEGER, 0, 4);                     // These are not ideal as they assume int/double - they need tweaking...
        USERFIELD(tuple, 2) = param2 = mktuple(AST_DIVZERO_CHECK, param2);
        USERFIELD(tuple, 2) = param2 = cast(param2, cast2, T_INTEGER, 0, 4);
        return mktuple(AST_BRACKET, tuple);
        
      } else if (opsym == 'Q') { // REAL divide
        char *cast1, *cast2;
        cast1 = cast_string(T_REAL, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1), 0 /*want_unsigned*/, REQUIRES_AUTO_DEREF(param1)); /* testing */
        cast2 = cast_string(T_REAL, BASE_SIZE_CODE(param2), BASE_SIZE_BYTES(param2), 0 /*want_unsigned*/, REQUIRES_AUTO_DEREF(param2));
        USERFIELD(tuple, 1) = param1 = cast(param1, cast1, T_REAL, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1));
        USERFIELD(tuple, 2) = param2 = cast(param2, cast2, T_REAL, BASE_SIZE_CODE(param2), BASE_SIZE_BYTES(param2));
        // TO DO: Insert a test for param2 being 0.0 at runtime  _ZR(%N)
        return mktuple(AST_BRACKET, tuple);
        
      } else if (opsym == ']') { // UNSIGNED >>
        char *cast1, *cast_result;
        cast1 = cast_string(T_INTEGER, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1), 1 /*want_unsigned*/, REQUIRES_AUTO_DEREF(param1)); /* testing */
        cast_result = cast_string(T_INTEGER, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1), 0 /*want_unsigned*/, 0);
        USERFIELD(tuple, 1) = param1 = cast(param1, cast1, T_INTEGER, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1));
        // TO DO: insert a test for param2 being >= bits in word size, or < 0, at runtime.
        //USERFIELD(tuple, 2) = param2 = cast(param2, "(int)", T_INTEGER, 0, 4);
        return cast(tuple, cast_result, T_INTEGER, BASE_SIZE_CODE(param1), BASE_SIZE_BYTES(param1));
      } else if (opsym == 'u' || opsym == 'q') { // ADDA (Add number of items to address)
        // Occurs in VAX pass1.imp at line 915:  current char == current char++1
        // and is supported by this pass1, so it *does* need to be implemented...
        // In C we can probably safely just output "+<n>" and ignore the type checking
        // that a simple ADD should do.  Promoted type is same as LHS.
        // Unlike all the other BINOPs, this one must be an LVALUE so may
        // need special handling.
      }
      
      // operations that can be performed with REALs

        
      // to do: This is where the BINOP type promotion logic must be added...
        
      // Tools available are:
      // void set_type_info(ASTIDX var, int base_type, int base_size_code, int base_size_bytes);
      // void set_expr_info(ASTIDX var, int base_type, int base_size_code, int base_size_bytes);
      // ASTIDX cast(ASTIDX var, char *cast_string, int base_type, int base_size_code, int base_size_bytes);
      // int compatible_types(ASTIDX opd1, ASTIDX opd2);
      // int promotable(ASTIDX from, ASTIDX to, int *base_type, int *base_size_code, int *base_size_bytes);
      // char *cast_string(int base_type, int base_size_code, int base_size_bytes, int want_unsigned, int want_pointer);

        
      // These ICode operations could potentially be handled via BINOP
      // and can all take integer/real parameters. They do not however
      // currently all come via this path:

      // (So any operations in this list that don't come via this path
      // will probably need to have type promotion logic added explicitly)

      // 'u' ADDA
      // 'q' SUBA
      // + Add
      // - SUB
      // S ASSVAL          (AST_ASSIGN)
      // j Jam Transfer
      // " IFDOUBLE
      // ? IFGOTO
      // * MUL
      // Q DIV
      // x Real-Power
      // V return value

      // Currently these are the ICodes which end up here:
      // 'u' ADDA (in progress)
      // 'q' SUBA (in progress)
      // - SUB
      // + ADD
      // ! OR
      // % XOR
      // & AND
      // . CONCAT
      // + ADD
      // * MUL
      // - SUB
      // Q DIVIDE - real divide
      // / QUOT - integer divide
      // [ LSH
      // ] RSH
      // X IEXP
      // x REXP

      param1 = USERFIELD(tuple, 1);
      param2 = USERFIELD(tuple, 2);
      if (opsym == '+' || opsym == '-' || opsym == '*') {
        int base_type, base_size_code, base_size_bytes;
        // integer to real promotion?
        if (promotable(param1, param2, &base_type, &base_size_code, &base_size_bytes)) {
          USERFIELD(tuple, 1) = param1 = cast(param1, cast_string(base_type, base_size_code, base_size_bytes, 0 /*force unsigned?*/, REQUIRES_AUTO_DEREF(param1) /*pointer?*/), /*lookingforcastbug*/
                                base_type, base_size_code, base_size_bytes);
        } else if (promotable(param2, param1, &base_type, &base_size_code, &base_size_bytes)) {
          USERFIELD(tuple, 2) = param2 = cast(param2, cast_string(base_type, base_size_code, base_size_bytes, 0 /*force unsigned?*/, REQUIRES_AUTO_DEREF(param2) /*pointer?*/), /*lookingforcastbug*/
                                base_type, base_size_code, base_size_bytes);
        }
      }

      // Finally, unassigned variable checking:
      param1 = USERFIELD(tuple, 1);
      param2 = USERFIELD(tuple, 2);
      // WARNING: Currently FORM(param1) is coming across as 15 (error) not T_REAL. (probably in all 3 places where it is checked).
      // Currently looking for where that value got corrupted, since it *was* initially set correctly on the declaration
      if (( BASE_TYPE(param1) == T_INTEGER || BASE_TYPE(param1) == T_REAL ) && FORM(param1) == F_SIMPLE) USERFIELD(tuple, 1) = param1 = mktuple(AST_UNASS_CHECK, param1);
debug_types(param1);
      if (( BASE_TYPE(param2) == T_INTEGER || BASE_TYPE(param2) == T_REAL ) && FORM(param2) == F_SIMPLE) USERFIELD(tuple, 2) = param2 = mktuple(AST_UNASS_CHECK, param2);
      
    } break;
      
    default: break;
  }
  
  if (opt_verbose) {
    (void)dump_code("// %d: mktuple(%s", tuple, astname[AST_code]);
    for (int i = 0; i < count; i++) {
      (void)dump_code(", %d", USERFIELD(tuple, i));
    }
    (void)dump_code(")\n");
  }
  return tuple;
}

// Note: any destination may be passed as NULL and value will be skipped.
void detuple_inner(StrpoolIDX file, int line, ASTIDX tuple, void *AST_code, ...) {
  va_list ap;
  int count = 0;
  int *ptr;

  *(ASTCODE *)AST_code = OP(tuple);
  va_start(ap, AST_code);
  for (;;) {
    ptr = va_arg(ap, int *);
    if (ptr == &END_MARKER) break;
    if (ptr) *ptr = USERFIELD(tuple, count);  // Don't write to a NULL field
    count += 1;
  }
  va_end(ap);
}

