#define X_APP 1
#include <stdio.h>

// non-standard gtcpp allows macros that look like array elements rather than
// looking like procedure calls.
  $define _CTRL_STACK[x] WRITE(x, CTRL_STACK, CTRL_STACK_ENUM)
  $define  CTRL_STACK[x]  READ(x, CTRL_STACK, CTRL_STACK_ENUM)
  $define _TypeDecl[x] WRITE(x, TypeDeclRA, TypeDecl)
  $define  TypeDecl[x]  READ(x, TypeDeclRA, TypeDecl)
  $define _VarDecl[x] WRITE(x, VarDeclRA, VarDecl)
  $define  VarDecl[x]  READ(x, VarDeclRA, VarDecl)
  $define _BoundType[x] WRITE(x, BoundTypeRA, BoundType)
  $define  BoundType[x]  READ(x, BoundTypeRA, BoundType)
  $define _Bound[x] WRITE(x, BoundRA, Bound)
  $define  Bound[x]  READ(x, BoundRA, Bound)
  $define _BoundsPair[x] WRITE(x, BoundsPairRA, BoundsPair)
  $define  BoundsPair[x]  READ(x, BoundsPairRA, BoundsPair)
  $define _Bounds[x] WRITE(x, BoundsRA, Bounds)
  $define  Bounds[x]  READ(x, BoundsRA, Bounds)
  $define _recfm[x] WRITE(x, recfmRA, recfm)
  $define  recfm[x]  READ(x, recfmRA, recfm)
  $define _arrfm[x] WRITE(x, arrfmRA, arrfm)
  $define  arrfm[x]  READ(x, arrfmRA, arrfm)
  $define _strfm[x] WRITE(x, strfmRA, strfm)
  $define  strfm[x]  READ(x, strfmRA, strfm)
  $define _Param[x] WRITE(x, ParamRA, ParamList)
  $define  Param[x]  READ(x, ParamRA, ParamList)

#ifdef NEVER
// no longer needed: codegen("%s", &Cpool(sx))
static int print_cpool(int sx) {
  wchar_t wch;
  for (;;) {
    wch = Cpool(sx);
    if (wch == '\0') break;
    fprintf(stdout, "%lc", (wint_t)wch);
    sx++;
  }
}

static int printlit(int sx) {
  int ch;

  source_descriptor ap = atom(sx);
  if (debug_literals) fprintf(stderr, "@%d: start=%d end=%d skipped=%08x canon=%08x t=\"", sx,
          ap.start, ap.end, ap.skipped, ap.canon);
  int p = ap.start;
  for (;;) {
    if (p == ap.end) break;
    ch = source(p).ch;
    if (ch == '\0') {
      fprintf(stderr, "[ERROR]");
    }
    fprintf(stderr, "%lc", ch);
    p++;
  }
  if (debug_literals) fprintf(stderr, "\"\n");

  return sx;
}
#endif

// In Imp80 to C, compile() is the procedure which outputs C code directly from the Concrete Syntax Tree
// *unless* the programmer (me) choses to intercept the default generated code for any specific phrase,
// and generate new AST records with it instead.  Having done so, those AST records can be walked and
// corresponding C code output by the same compile() procedure.  However the entries to output AST_*
// phrases have to be added here, not in the .g grammar file, because the grammar file only provides
// a place for code corresponding to grammar phrases.  AST tuples have no defining syntax so the code
// than handled them has to keep track of the virtual struct format manually (or we could map a record
// format over the AST array, which would be a safer implementation...)

// Although the AST does link sequential <STATEMENT>s, it does not build them into a proper tree structure
// in the way that the previous imptoc did, with routines being identifiable as a single object or cycles
// and if/then/else blocks be able to be handled as units.  These *could* be rebuilt with the data available
// but I first am going to try to see if I can generate valid C programs without doing so.

// This choice is also relevant to whether C code can be output on the fly or has to come from walking the
// completed tree.  The only place that springs to mind where having an entire block accessible via a tree
// item is with the older Imp compilers where %until <cond> %cycle ... %repeat had to be mapped to
// %cycle ... %repeat %until <cond>.  However Imp80 disallowed that old syntax, and right now I can't
// think of anything else that *forces* creation of blocks rather than merely takes advantage of them
// if they're available.

#define P(x) AST((Ph&AST_idx_mask)+4+TUPLE_RESULT_FIELDS+(x)-1)

// now defined elsewhere:
//#define compile(P,d) compile_inner(P,d,__FILE__,__LINE__)
//int compile_inner(int Ph, int depth, const char *file, const int line);
//#define generate_c(P, d) generate_c_inner(P, d, __FILE__, __LINE__)
//int generate_c_inner(int Ph, int depth, const char *file, const int line);

// Output C code to output file or a string.
// By adding this wrapper we can divert or erase C output, or write it to a string.

static FILE *nullfile = NULL;
int Divert_C_to_Cpool = 0; // If set, writes to CPool at current offset instead of
                           // stdout.  This can be used to divert calls to generate_c() etc

#ifdef NEVER
// scodegen was a version of codegen that output to a regular C string.
// It has now been replaced by allowing codegen to divert its output...
// So this can be deleted after having been commented out for a few weeks.
int scodegen(char *buff, char *s, ...) {
  va_list ap;          
  int string_length = 0;
  va_start(ap, s);
  string_length = vsprintf(buff, s, ap);
  va_end(ap);
  return string_length;
}
#endif

int codegen(char *s, ...) {
  va_list ap;          
  int string_length = 0;
  if (nullfile == NULL) {
    nullfile = fopen("/dev/null", "w");
    if (nullfile == NULL) {
      fprintf(stderr, "codegen: cannot open /dev/null\n");
      exit(1);
    }
  }

  va_start(ap, s);
  if (Divert_C_to_Cpool) {
    // NOTE: In order to avoid complications when other parts of the code create strings in the
    // string pool during code generation, we use a private string pool (Cpool) specifically
    // for the generated C, rather than the common 'Stringpool' that everything else uses.
    int i;
    
    char *tmp; // This hack to determine the length of the output should not be needed and we *ought*
    // to be outputting UTF32 directly to the C pool, but I'm not sure how to do that at the moment
    // so this hack (with UTF8-encoded Unicode instead) must suffice for now.  Truth be told, there
    // are still far too many places in this code that still use C's "char *" strings.  Eventually
    // I would like to migrate to UTF32 everywhere. (Despite the well-argued suggestions from
    // https://utf8everywhere.org/ - my counter proposal is UTF8 in all files and external interfaces,
    // but UTF32 internally for all data structures, so that a single wchar_t represents a single
    // Unicode code-point aka a single character.) 
    string_length = vfprintf(nullfile, s, ap);
    tmp = malloc(string_length+1);
    if (tmp == NULL) {
      fprintf(stderr, "codegen: malloc(%d) fails\n", string_length+1); exit(1);
    }
    i = vsprintf(tmp, s, ap); // replace with vsnprintf
    if (i != string_length) {
      fprintf(stderr, "codegen: string length changed: %d != %d\n", string_length, i); exit(1);
    }
    int String = Cpool_nextfree;
    for (i = 0; i < string_length; i++) {
      _Cpool(Cpool_nextfree++) = tmp[i];
    }
    // This is tricky...  by *not* applying '++' to Cpool_nextfree, we can make multiple calls to codegen() and have all the
    // output be concatenated.  However it then becomes *essential* that the calling code increments that pointer at the end
    // of all the code generation.  We got away with not doing this at first because of the separation between Stringpool
    // and Cpool, but that was just luck.  It was bad design and has now been fixed by the creation of Start_Divert_C_to_Cpool()
    // and End_Divert_C_to_Cpool() below.
    _Cpool(Cpool_nextfree) = '\0';
    
    fprintf(stderr, "BUFFERED STRING [%s] CPOOL[0]=\"%ls\"  Cpool_nextfree=%d\n", tmp, &Cpool(0), Cpool_nextfree);
    free(tmp);
  } else {
    string_length = vfprintf(stdout, s, ap);
  }
  va_end(ap);

  return string_length;
}

void Start_Divert_C_to_Cpool(void) {
  Divert_C_to_Cpool = TRUE;
}

void End_Divert_C_to_Cpool(void) {
  Divert_C_to_Cpool = FALSE;
  Cpool_nextfree++; // ESSENTIAL to terminate multiple calls to codegen() that were all diverted to a single stringpool string.
}


#define handle_ast_phrase(Ph, depth) generate_c_inner(Ph, depth, __FILE__, __LINE__)

int generate_c_inner(int Ph, int depth, const char *file, const int line) { // int or void?
  if (Ph == -1) {
    // AND WARN
    fprintf(stderr, "? handle_ast_phrase_inner(-1, %d)\n", depth);
    return Ph;
  }

#ifdef NEVER
    // temp for debugging
    #define _textof(x) #x
    #define textof(x) _textof(x)
    fprintf(stderr, "%s = %d\n", textof(AST_DEFER_UNCOMPILED_PHRASE),AST_DEFER_UNCOMPILED_PHRASE);
    fprintf(stderr, "%s = %d\n", textof(AST_LVALUE),AST_LVALUE);
    fprintf(stderr, "%s = %d\n", textof(AST_RVALUE),AST_RVALUE);
    fprintf(stderr, "%s = %d\n", textof(AST_INDIRECT),AST_INDIRECT);
    fprintf(stderr, "%s = %d\n", textof(AST_ADDROF),AST_ADDROF);
    fprintf(stderr, "%s = %d\n", textof(AST_CONST),AST_CONST);
    fprintf(stderr, "%s = %d\n", textof(AST_COMMENT),AST_COMMENT);
    fprintf(stderr, "%s = %d\n", textof(AST_TOKEN),AST_TOKEN);
    fprintf(stderr, "%s = %d\n", textof(AST_EXPRESSION),AST_EXPRESSION);
    fprintf(stderr, "%s = %d\n", textof(AST_TYPE_INFORMATION),AST_TYPE_INFORMATION);
    fprintf(stderr, "%s = %d\n", textof(AST_USER_PARENS),AST_USER_PARENS);
    fprintf(stderr, "%s = %d\n", textof(AST_UNARY_ARITHMETIC_OPERATION),AST_UNARY_ARITHMETIC_OPERATION);
    fprintf(stderr, "%s = %d\n", textof(AST_BINARY_ARITHMETIC_OPERATION),AST_BINARY_ARITHMETIC_OPERATION);
#endif
    
  int AST_index = Ph&AST_idx_mask;
  int AST_type  = Ph & (AST_type_mask << AST_type_shift);
  int op    = AST(AST_index+1);
  int alt   = AST(AST_index+2);
  int count = AST(AST_index+3); // I get a warning that it is not used but it *is* used if you search below for "// show imp"
  int t[LARGEST_ALT];
  int r = -1; // failure code, (tuple)(-1) is ignored.


#define Accept(A, P, type) AST_struct *A = (AST_struct *)&AST(P&AST_idx_mask); struct ast_##type *type; if (op != A->op) fprintf(stderr, "*ERROR TYPE MISMATCH: %d vs %d\n", op, A->op); else type = &A->type
  

  if (AST_type == AST_ATOM_LIT) {
    PrintAtom(Ph);
    return -1;
  } else if (AST_type == AST_POOL_LIT) {
    PrintTag(Ph);
    return -1;
  } else if (AST_type == AST_BIP) {
    fprintf(stderr, "{TO DO:AST_BIP in imp80-compile.h from %s, line %d}", file, line);
    PrintAtom(Ph);
    return -1;
  } else if (AST_type == PHRASE_TYPE) {  // PHRASE_TYPE: (should be G_something)
    static int PHRASE;
    for (PHRASE = 0; PHRASE < NUM_SIMPLE_PHRASES; PHRASE++) {
      if (sequential_phrase_no_to_grammar_index[PHRASE] /* aka P_to_G_*/ == op) break;
    }
    if (PHRASE == NUM_SIMPLE_PHRASES) {
      // NOT FOUND!
      if ((op > NUM_GRAMMAR) && (op < AST_LAST_OP)) {
        fprintf(stderr, "*** THIS SHOULD NOT HAPPEN.  op=0x%08x\n", op);
        handle_ast_phrase(Ph, depth);
      } else {
        fprintf(stderr, "I overlooked something. Could not find phrase number G_{%d}\n", op);
        fprintf(stderr, "(Possibly from %s, line %d)\n", file, line);
        fprintf(stderr, "Most likely coding error is that generate_c() was called with a\n");
        fprintf(stderr, "P_x phrase number instead of a G_x one.\n");
        if (op < NUM_PHRASES) {
          fprintf(stderr, "(check the grammar code for a rule which uses <%ls> which is %d)\n", phrasename[op], op);
        }
        exit(1);
      }
      return -1;
    }
    op = PHRASE+NUM_BIPS;
  }
  if (AST_type == AST_PHRASE || AST_type == PHRASE_TYPE) {
    fprintf(stderr, "generate_c: op = %d  called from %s line %d\n", op, file, line);
    switch (op) {

    case AST_DEFER_UNCOMPILED_PHRASE:  //fprintf(stderr, "AST_DEFER_UNCOMPILED_PHRASE\n");
      compile(P(1), depth+1); // for phrases that are currently printed out by compile() - these should be phased out ... Currently only used by %UNTIL ... %CYCLE
      break;
      
    // AST_LVALUE and AST_RVALUE (when alt==0) are currently simple VarDecl[] objects rather than complex
    // objects such as array elements, members of records, or procedure calls.  Anything higher level should
    // be handled by its own AST_ type, such as AST_PROC_CALL, AST_FIELD, or AST_ARRAYACCESS etc.
    // NOTE THAT AST_FIELD has not yet been implemented below. (Doing it now!)
    case AST_LVALUE:  //fprintf(stderr, "AST_LVALUE\n");
      {
        Accept(A, Ph, lvalue);
        if (A->alt != 0) {
          // NOW DEPRECATED, USING AST_CONST FOR THIS INSTEAD:
          fprintf(stderr, "* Error: AST_LVALUE not a variable, from %s, line %d\n", pooltostr(P_FILE(Ph)|AST_POOL_LIT), P_LINE(Ph));
          break;
        }
        VarDeclIDX VarIDX = lvalue->atom;
        int Tag = VarDecl[VarIDX].varname;
        PrintAtom(Tag);
      }
      break;

    case AST_RVALUE:  //fprintf(stderr, "AST_RVALUE\n");
      {
        Accept(A, Ph, rvalue);
        if (A->alt == 0) { // VAR
          VarDeclIDX VarIDX = rvalue->atom;
          if (VarIDX == -1) { fprintf(stderr, "* Error: VarIDX == -1 in AST_VALUE case from %s, line %d\n", pooltostr(P_FILE(Ph)|AST_POOL_LIT), P_LINE(Ph)); break; }
          int Tag = VarDecl[VarIDX].varname;
          PrintAtom(Tag);
        } else if (A->alt == 1) { // CONST
          int ConstP = rvalue->atom;
          generate_c(ConstP, depth+1);
        } else { // BUG
          fprintf(stderr, "* Invalid alt for AST_RVALUE (%d)\n", A->alt);
        }
      }
      break;

    case AST_INDIRECT:  //fprintf(stderr, "AST_INDIRECT\n");
      {
        Accept(A, Ph, indirect);
        codegen("*"); // maybe later make into *(...) if C precedence rules require it.
        handle_ast_phrase(indirect->rvalue, depth+1);  // We now want something better than this!
      }
      break;

    case AST_ADDROF:  //fprintf(stderr, "AST_ADDROF\n");
      {
        Accept(A, Ph, addrof);
        handle_ast_phrase(addrof->lvalue, depth+1);  // We now want something better than this!
        codegen("&"); // maybe later make into &(...) if C precedence rules require it.
      }
      break;

    case AST_CONST:  //fprintf(stderr, "AST_CONST\n");
      {
        Accept(A, Ph, constval);
        int ConstP = constval->atom;
        int Type = constval->type;
        //codegen("/*CONST:%d,%d*/", ConstP, Type);
        handle_ast_phrase(ConstP, depth+1);  // We now want something better than this!
      }
      break;

      // Initially just print out the source...

    case P_Comment:       // P<Comment>          = '!' <TEXT>, '|' <TEXT>, "comment" <TEXT>
      fprintf(stderr, "* Internal error: should never call P_Comment in %s\n", __FILE__);
      break;
      
    case P_any:           // P<any>              = «.»
      // Escape characters in quoted strings
      if (source(atom(P(1)&AST_idx_mask).start).ch == '\\') {
        codegen("\\\\");
      } else if (source(atom(P(1)&AST_idx_mask).start).ch == '"') {
        codegen("\\\"");
      } else {
        handle_ast_phrase(P(1), depth+1);
      }      
      break;

    case P_CharConst:     // P<CharConst>        = «''''», «''», «'.'»; # <sq><sq><sq><sq>, <sq><sq>, <sq> <ch> <sq>
      if (alt == 0) {
        codegen("'\\''");
      } else if (alt == 1) {
        codegen("'\\0'");
      } else {
        if (source(atom(P(1)&AST_idx_mask).start+1).ch == '\\') {
          codegen("'\\\\'");
        } else {
          handle_ast_phrase(P(1), depth+1);
        }
      }
      break;
      
    case P_LITCONST: /*
                             P<LITCONST> = <N><OptDecimal><OptExponent>,
                                           <CharConst>,
                                           <ALIASTEXT>,
                                           'E'<ALIASTEXT>,           # EBCDIC
                                           «[MBKXRH]»<BigCharConst>
                      */
      if (alt == 4 && (source(atom(P(1)&AST_idx_mask).start).ch == 'X')) {
        codegen("0x");
        int BigCharConst = P(2);
        int i;
        // trim off the quotes around the atom text
        for (i = atom(P_P(BigCharConst,1)&AST_idx_mask).start+1; i < atom(P_P(BigCharConst,1)&AST_idx_mask).end-1; i++) {
          codegen("%lc", source(i).ch);
        }
        break;
      } else if (alt == 4 && (source(atom(P(1)&AST_idx_mask).start).ch == 'B')) {
        codegen("0b");
        int BigCharConst = P(2);
        int i;
        // trim off the quotes around the atom text
        for (i = atom(P_P(BigCharConst,1)&AST_idx_mask).start+1; i < atom(P_P(BigCharConst,1)&AST_idx_mask).end-1; i++) {
          codegen("%lc", source(i).ch);
        }
        break;
      } else if (alt == 4 && (source(atom(P(1)&AST_idx_mask).start).ch == 'K')) {
        codegen("0");
        int BigCharConst = P(2);
        int i;
        // trim off the quotes around the atom text
        for (i = atom(P_P(BigCharConst,1)&AST_idx_mask).start+1; i < atom(P_P(BigCharConst,1)&AST_idx_mask).end-1; i++) {
          codegen("%lc", source(i).ch);
        }
        break;
      } else if (alt == 4 && (source(atom(P(1)&AST_idx_mask).start).ch == 'M')) {
        handle_ast_phrase(P(2), depth+1);  // M'fred' -> 'fred'
        break;
      }
      // drop through for the rest

    case P_Based:         // P<Based>            = «[0-9A-Za-z][0-9A-Za-z]*»
    case P_N:             // P<N>                = «[0-9][0-9]*»
    case P_OptDecimal:    // P<OptDecimal>       = '.'<N>, ''
    case P_OptExponent:   // P<OptExponent>      = '@'<N>, ''
    case P_ALIASTEXT:     // P<ALIASTEXT>        = <dq> <stringchars> <dq>
    case P_dq:            // P<dq>               = '"'
    case P_stringchars:   // P<stringchars>      = <dq><dq> <stringchars>, <!dq> <any> <stringchars>, ''
    case P_TEXT:          // P<TEXT>             = <!S> «.» <TEXT>, ''
      {
        int i; for (i = 1; i <= count; i++) handle_ast_phrase(P(i), depth+1);
      }
      break;

    case P_BigCharConst:  // P<BigCharConst>     = «'([^\']|'')*'»; # <sq><sq><sq><sq>, <sq><sq>, <sq> <ch> <sq>
      {
        int BigCharConst = P(1);
        int i;
        codegen("'");
        for (i = atom(BigCharConst&AST_idx_mask).start+1; i < atom(BigCharConst&AST_idx_mask).end-1; i++) {
          if (source(i).ch == '\'') {
            codegen("\\'");
          } else if (source(i).ch == '\\') {
            codegen("\\\\");
          } else {
            codegen("%lc", source(i).ch);
          }
        }
        codegen("'");
      }
      break;

    case AST_COMMENT:  //fprintf(stderr, "AST_COMMENT\n");
      {
        codegen("//");
        handle_ast_phrase(P(2), depth+1);
        codegen("\n\n");
      }
      break;
      
    case AST_TOKEN:  //fprintf(stderr, "AST_TOKEN\n"); // Tag
      {
        Accept(A, Ph, token);
        // TO DO:
        int tag = token->name;
        //fprintf(stderr, "Tag = %d\n", tag);
        codegen("%ls", Atom2WStr(tag));
        break;
      }
      
    case AST_EXPRESSION:  //fprintf(stderr, "AST_EXPRESSION\n");
      {
        Accept(A, Ph, expression);
        generate_c(expression->whatever, depth+1);
        break;
      }

    case AST_PENDING_APP_OR_FIELD:  //fprintf(stderr, "AST_PENDING_APP_OR_FIELD\n");
      {
        // P<Opt_Record_Field> = '_'<TAG><APP><Opt_Record_Field>,
        //      (^ Object)          Field App     Subfields
        // or
        // P<VAR>              = <VARNAME><APP><Opt_Record_Field> {
        //                         Object  App    Subfields
        Accept(A, Ph, pending_app_or_field);
        int Object    = pending_app_or_field->Object;
        int Field     = pending_app_or_field->Field;
        int App       = pending_app_or_field->App;
        int Subfields = pending_app_or_field->Subfields;
        
        int Var = Object;

        if (Var == -1 && Field != -1) {
          fprintf(stderr, "? Field given but parent not yet identified.\n");
        }
        
        if (Var != -1 && Field != -1) {
          // Attach this field name to the current object
          //Diagnose("Object: (to have Field attached to) ", Var, 0, TRUE);
          Var = MkField(Var, Field); Field = -1;
        }

        if (Var == -1 && App != -1) {
          fprintf(stderr, "? Parameters or indexes given but parent not yet identified.\n");
        }
        
        if (Var != -1 && App != -1) {
          // This could be either an array element or a function/map call
          // depending on the type of <VARNAME>:
          if (ISARRAYQ_AST(Var)) {
            fprintf(stderr, "Constructing Var = MkIndex(Var, App); TypeIDX = %d\n", TYPEOF(Var));
            Var = MkIndex(Var, App);
          } else if (ISRTFNMAPQ_AST(Object)) {
            fprintf(stderr, "Constructing Var = MkCall(Var, App); TypeIDX = %d\n", TYPEOF(Var));
            Var = MkCall(Var, App);
          } else {
            fprintf(stderr, "* %ls is neither a function call nor an array element, so cannot take (...)\n", wNAMEOF_AST(Var));
          }
        }
        
        if (Subfields != -1) {
          
          if (P_P(Subfields, 1) != -1) {
            fprintf(stderr, "* Object field of subfields was not -1.  I had expected it to be -1.\n"); exit(1);
          }

          P_P(Subfields, 1) = Var; 
          Var = Subfields;
          
        } //else fprintf(stderr, "No subfields\n");
        
        //fprintf(stderr, "Generating code from rebuilt AST_PENDING_APP_OR_FIELD.  TypeIDX = %d\n", TYPEOF(Var));
        handle_ast_phrase(Var, depth+1);
        
        break;
      }
      
    case AST_TYPE_INFORMATION:  //fprintf(stderr, "AST_TYPE_INFORMATION\n");
      codegen("/*TYPE*/");
      break;
        
    case AST_USER_PARENS:  //fprintf(stderr, "AST_USER_PARENS\n");
      {
        Accept(A, Ph, user_parens);
        codegen("(");
        generate_c(user_parens->Expr, depth+1);
        codegen(")");
        break;          
      }

    case AST_BINARY_ARITHMETIC_OPERATION:  //fprintf(stderr, "AST_BINARY_ARITHMETIC_OPERATION\n");
      {
        // C operator priorities are different from Imp's...
        
        Accept(A, Ph, binary_arithmetic_operation);
        
        int mid_op = binary_arithmetic_operation->binop;
        int mid_op_C_prec = OpDetails[mid_op].C_prec;

        int left_operand = binary_arithmetic_operation->right;  // Yeah.  Sorry.
        int right_operand = binary_arithmetic_operation->left;

        if (left_operand == -1 || right_operand == -1) {
          fprintf(stderr, "* Error: operand of binary op is undefined.\n");
          //Diagnose("left_operand: ", left_operand, 0, TRUE);
          //Diagnose("right_operand: ", right_operand, 0, TRUE);
          return -1;
        }
        
        int left_is_binary  = P_op(left_operand) == AST_BINARY_ARITHMETIC_OPERATION;
        int right_is_binary = P_op(right_operand) == AST_BINARY_ARITHMETIC_OPERATION;

        int left_op = -1;
        int left_op_C_prec = ATOMPRIO;
        if (left_is_binary) {           
          Accept(L, left_operand, binary_arithmetic_operation);
          left_op = binary_arithmetic_operation->binop;
          //fprintf(stderr, "leftop=%s\n", OpDetails[left_op].C_mid); // see unary
          left_op_C_prec = OpDetails[left_op].C_prec;
        }
        
        int right_op = -1;
        int right_op_C_prec = ATOMPRIO;
        if (right_is_binary) {
          Accept(R, right_operand, binary_arithmetic_operation);
          right_op = binary_arithmetic_operation->binop;
          //fprintf(stderr, "rightop=%s\n", OpDetails[right_op].C_mid);
          right_op_C_prec = OpDetails[right_op].C_prec;
        }        

        //fprintf(stderr, "[left prio = %d  mid prio = %d  right prio = %d]\n", left_op_C_prec, mid_op_C_prec, right_op_C_prec);
        
        codegen("%s", OpDetails[mid_op].C_left); // see unary
        if (left_is_binary && (mid_op_C_prec < left_op_C_prec)) {              // EXCEPT where a^b is mapped to IEXP(A,B)
          codegen("(");
          generate_c(left_operand, depth+1);
          codegen(")");
        } else {
          generate_c(left_operand, depth+1);
        }
                
        codegen("%s", OpDetails[mid_op].C_mid); // see unary
        
        if (right_is_binary && (mid_op_C_prec < right_op_C_prec)) {            // ditto.
          codegen("(");
          generate_c(right_operand, depth+1);
          codegen(")");
        } else {
          generate_c(right_operand, depth+1);
        }
        codegen("%s", OpDetails[mid_op].C_right); // see unary
      }
      break;

    case AST_UNARY_ARITHMETIC_OPERATION:  //fprintf(stderr, "AST_UNARY_ARITHMETIC_OPERATION\n");
      {
        Accept(A, Ph, unary_arithmetic_operation);
        // I *think* putting "()" around a unary expression is safe but not 100% sure - depends if unaries in C have the highest precedence.
        // Point of the brackets is to avoid --a which is (-(-a))  But after some experimentation I don't think it will ever come up. So removed.
        codegen("%s", OpDetails[unary_arithmetic_operation->monop].C_left);
        codegen("%s", OpDetails[unary_arithmetic_operation->monop].C_mid);
        generate_c(unary_arithmetic_operation->arg, depth+1);
        codegen("%s", OpDetails[unary_arithmetic_operation->monop].C_right);
      }
      break;
        
    case AST_ASSIGN:  //fprintf(stderr, "AST_ASSIGN\n");
      {
        Accept(A, Ph, assign);
        codegen("  ");
        //Diagnose("Assign: ", assign->LHS, depth, debug_declarations || debug_ast);
        generate_c(assign->LHS, depth+1);
        codegen(" = "); // should be <assop>
        // fprintf(stderr, "RHS: %08x -> ", assign->RHS);
        generate_c(assign->RHS, depth+1);
        //codegen(";\n");
      }
      break;

    case AST_PROC_CALL:  //fprintf(stderr, "AST_PROC_CALL\n");  // ARRAY, FPP_LIST, APP_LIST   (TO DO: FPP_LIST not yet being passed in properly.)
      {
        Accept(A, Ph, proc_call); // int Proc, Types, Args;
        VarDeclIDX VarIDX = proc_call->Proc;
        if (VarIDX == -1) { fprintf(stderr, "* Error: VarIDX == -1 in AST_VALUE case at %s, line %d\n", __FILE__, __LINE__); break; }
        int Tag = VarDecl[VarIDX].varname; // procedure/fn name
        PrintAtom(Tag);
        codegen("(");
        handle_ast_phrase(proc_call->Args, depth+1);
        codegen(")\n");
      }
      break;

    case AST_APP_LIST:  //fprintf(stderr, "AST_APP_LIST\n");  // EXPR, APP_LIST
      {
        Accept(A, Ph, app_list); // int Expr, next // really need to step through a combined tuple with both FPP and APP
        int Args = app_list->next;
        handle_ast_phrase(app_list->Expr, depth+1);
        if (app_list->next != -1) {
          codegen(", "); 
          handle_ast_phrase(app_list->next, depth+1);
        }
      }
      break;

    case AST_ARRAYACCESS:  //fprintf(stderr, "AST_ARRAYACCESS\n");          // ARRAY ACCESSES NOT YET WORKING.
      {
        Accept(A, Ph, array_access);
        fprintf(stderr, "Expanding AST_ARRAYACCESS\n");

        int ARRAY = array_access->Array;
        int APP = array_access->Indices;

        int Tag = VarDecl[P_P(array_access->Array, 1)].varname;
        codegen("%s", Atom2Str(Tag));

        // Still to do - type checking etc
        while (APP != -1) {
          int EXPR = P_P(APP, 1);
          codegen("[");
          generate_c(compile(EXPR, depth+1), depth+1); // <EXPR>
          codegen("]");
          APP = P_P(APP, 2);
        }
        
      }
      break;

    case AST_FIELD:  //fprintf(stderr, "AST_FIELD\n");
      {
        Accept(A, Ph, field);
        //Diagnose("AST_FIELD: Field = ", field->Field, 0, TRUE);
        
        generate_c(field->Record, depth+1);
        codegen(".");                                // TO DO: AST_FIELD needs an indicator of whether it is a "->" or a "."
        generate_c(field->Field, depth+1);
        break;
      }
      
    case AST_BINOP:  //fprintf(stderr, "AST_BINOP\n"); // OP (str), OP (code)
      {
        Accept(A, Ph, binop);
        break;
      }
      
    case AST_MONOP:  //fprintf(stderr, "AST_MONOP\n"); // OP (str), OP (code)
      {
        Accept(A, Ph, monop);
        break;
      }
      
    case AST_ASSOP:  //fprintf(stderr, "AST_ASSOP\n"); // OP (str), OP (code)
      {
        Accept(A, Ph, assop);
        break;
      }

    case P_APP: // P<APP>              = '('<EXPR><RESTOFAPP>')',''
      //codegen("/*<p_app ph=%d>*/", PHRASE+NUM_BIPS);
      if (alt == 0) {
        generate_c(compile(P(2), depth+1), depth+1);
        compile(P(3), depth+1); // as I said, blurry lines.
      }
      break;
      
    case P_VARNAME:
      codegen("<p_varname ph=%d>", PHRASE+NUM_BIPS);
      break;
  
    case G_VARNAME:
      codegen("<g_varname ph=%d>", PHRASE+NUM_BIPS);
      break;
  
    case G_LITCONST:
      codegen("<g_litconst ph=%d>", PHRASE+NUM_BIPS);
      break;

    default:
      {
        int i;
//zzz        fprintf(stderr, "*** generate_c: Switch entry %d not found.  From %s, line %d\n", PHRASE+NUM_BIPS, file, line); // ! *not* op
        codegen("/* op=%d (P_%ls) needs to be replaced with an AST_* tuple.\n", op, phrasename[op]);
        codegen("   It should not have been tagged as an AST_PHRASE\n");
        codegen("   Possibly called from %s, line %d\n", file, line);
        codegen(" */\n");

        for (i = 1; i <= count; i++) {
          generate_c(P(i), depth+1);
        }

      }
      return r;
    }
  } else {
    // unknown AST_TYPE
//zzz  fprintf(stderr, "UNKNOWN AST TYPE  op=%d  PHRASE+NUM_BIPS=%d\n", op, PHRASE+NUM_BIPS);
    exit(1);
  }
  
  return -1;
}

int compile_inner(int Ph, int depth, const char *file, const int line) {
  //fprintf(stderr, "DEBUG: Entered compile(%08x, %d)\n", Ph, depth);
  if (Ph == 0 || Ph == -1) {
    fprintf(stderr, "Compile(%d,%d) called from %s, line %d\n", Ph, depth, file, line);
    return -1;
  }

// to avoid printing the tree:
#ifdef wlit
#undef wlit
#endif
#define wlit(x) (x)

  // In the process of replacing these with P_ macros
  int AST_index = Ph&AST_idx_mask;
  int AST_type  = Ph & (AST_type_mask << AST_type_shift);
  int op        = AST(AST_index+1);
  int alt       = AST(AST_index+2);
  int count     = AST(AST_index+3);

  if (AST_type == AST_ATOM_LIT) {
    PrintAtom(Ph);
    return -1;
  } else if (AST_type == AST_POOL_LIT) {
    PrintTag(AST_index);
    return -1;
  } else if (AST_type == AST_BIP) {
    fprintf(stderr, "\nimp80-compile.h: TO DO:AST_BIP from %s, line %d\n", file, line);
    //PrintAtom(AST_index);
    return -1;
  } else if (AST_type != AST_PHRASE && AST_type != PHRASE_TYPE) {
    fprintf(stderr, "\n%s, line %d: TO DO: compile() with AST_type = %d  AST_index = %d\n", file, line, AST_type, AST_index);
    return -1;
  }

  // converting from a grammar index to the more dense phrase index is
  // an expensive lookup.  There are two obvious ways to avoid it:
  //
  // 1) Index the switch below by G_* instead of P_*.
  // 2) Add another item to the grammar so that gram[G_x] contains P_x.
  //    - this is a good solution but there's a lot of places where
  //    changes will be needed to make it work.
  // 3) Not space efficient: have an array as large as the grammar
  //    to directly index G_x to P_x.  No good reason to do this other
  //    than simplicity.

  // working now but not pretty...

  static int PHRASE;
  for (PHRASE = 0; PHRASE < NUM_SIMPLE_PHRASES; PHRASE++) {
    if (sequential_phrase_no_to_grammar_index[PHRASE] /* aka P_to_G_*/ == op) break;
  }
  if (PHRASE == NUM_SIMPLE_PHRASES) {    // NOT FOUND!
    if ((op > NUM_GRAMMAR) && (op < AST_LAST_OP)) {      // AST_* phrase.
      generate_c(Ph, depth+1); // pretty much the default walk_ast code.
      return -1; // walking in order to output C, not compiling to return tuples.
    }
    fprintf(stderr, "I overlooked something. Could not find phrase number G_{%d}\n", op);
    fprintf(stderr, "(Possibly from %s, line %d)\n", file, line);
    fprintf(stderr, "Most likely coding error is that compile() was called with a\n");
    fprintf(stderr, "P_x phrase number instead of a G_x one.\n");
    if (op < NUM_PHRASES) fprintf(stderr, "(check the grammar code for a rule which uses <%ls> which is %d)\n", phrasename[op], op);
    exit(1);
  }
  /*  
  fprintf(stderr, "Ph=%08x  AST_type = %d  AST_index = %d  Grammar index=%d  Phrase=G_%ls  alt=%d  count=%d\n",
                      Ph,              AST_type>>AST_type_shift,
                                                       AST_index,
                                                                         op,
                                                                                    phrasename_c[PHRASE+NUM_BIPS],
                                                                                  alt,      count);
  */

  int t[LARGEST_ALT];
  int r = -1; // failure code, (tuple)(-1) is ignored.

  switch (PHRASE+NUM_BIPS) {

    // THIS IS THE MAIN WORKHORSE OF THE PARSER!
    
$include "imp80-switch.h"

   default:
     fprintf(stderr, "*** compile: Switch entry %d not found.  From %s, line %d\n", op, file, line);
     return r;
  }
  return -1;
}


#ifdef NEVER1
            // handle_ast_entry(Var) (or generate_c(Var)) will deconstruct the
            // AST_PENDING_APP_OR_FIELD tuple and generate code from it.
            //Diagnose("Record: ", Var, 0, TRUE);
            
            int T = TYPEOF(Var);
            int Nametable = TypeDecl[T].recfm;
            fprintf(stderr, "Anonymous scope table index for this record is %d\n", Nametable);
            
            P_P(Subfields, 1) = Var;
            Var = Subfields;
            
            fprintf(stderr, "Attached Var to subfields; TypeIDX = %d  (not yet handled)\n", TYPEOF(Var));
#endif
