#define X_APP 1
#include <stdio.h>

// defined in uparse.c already
//#define AST_idx_mask 0x7FFFFFFU
//#define AST_type_shift 27U
//#define AST_type_mask  31U
//#define AST_BIP     (16U << AST_type_shift)
//#define AST_PHRASE  (17U << AST_type_shift)
//#define AST_LITERAL (18U << AST_type_shift)

//extern SPEC(CTRL_STACK, CTRL_STACK_ENUM);
//$undef CTRL_STACK
//$undef _CTRL_STACK

  $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 NOT_USED
static int printlit(int sx) {
  int ch;
#ifdef NEVER
  fprintf(stderr, "{@%08x}", sx);
  for (;;) {
    ch = Stringpool(sx);
    if (ch == '\0') break;
    fprintf(stderr, "%lc", ch);
    sx++;
  }
#else
  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");
#endif
  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)

#define compile(P,d) compile_inner(P,d,__FILE__,__LINE__)

int compile_inner(int Ph, int depth, const char *file, const int line);
int generate_c(int Ph, int depth);


// This should maybe be done entirely within generate_c(...).  I'm still working on the structure of this stuff.
int handle_ast_phrase(int Ph, int depth) { // int or void?
  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"
#define Accept(A, P, type) AST_struct *A = (AST_struct *)&AST(P&AST_idx_mask); if (op != A->op) fprintf(stderr, "*ERROR TYPE MISMATCH: %d vs %d\n", op, A->op); else

  if (AST_type == AST_PHRASE) {
      switch (op) {
      case AST_LVALUE:
        {
          Accept(A, Ph, lvalue);
          VarDeclIDX VarIDX = A->lvalue.atom; // what was added to "decl" was Tag2Str(Tag)
          int Tag = VarDecl[VarIDX].varname;
          fprintf(stdout, "%s", Tag2Str(Tag));
        }
        break;

      case AST_RVALUE:
        {
          Accept(A, Ph, rvalue);
          if (A->alt == 0) { // VAR
          VarDeclIDX VarIDX = A->rvalue.atom; // what was added to "decl" was Tag2Str(Tag)
          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;
          fprintf(stdout, "%s", Tag2Str(Tag));
          } else if (A->alt == 1) { // CONST
            int ConstP = A->rvalue.atom;
            //fprintf(stdout, "<%08x>", ConstP);
            generate_c(ConstP, depth+1);
          } else { // BUG
            fprintf(stderr, "* Invalid alt for AST_RVALUE (%d)\n", A->alt);
          }
        }
        break;

      case AST_EXPRESSION:
        {
          Accept(A, Ph, expression);
          generate_c(A->expression.whatever, depth+1);
          break;
        }
        
      case AST_TYPE_INFORMATION:
        fprintf(stdout, "/*TYPE*/");
        break;
          
      case AST_USER_PARENS:
        {
          Accept(A, Ph, user_parens);
          fprintf(stdout, "(");
          generate_c(A->user_parens.Expr, depth+1);
          fprintf(stdout, ")");
          break;          
        }

      case AST_UNARY_ARITHMETIC_OPERATION:
        {
          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.
          fprintf(stdout, "%s", OpDetails[A->unary_arithmetic_operation.monop].C_left);
          fprintf(stdout, "%s", OpDetails[A->unary_arithmetic_operation.monop].C_mid);
          generate_c(A->unary_arithmetic_operation.arg, depth+1);
          fprintf(stdout, "%s", OpDetails[A->unary_arithmetic_operation.monop].C_right);
        }
        break;
          
      case AST_BINARY_ARITHMETIC_OPERATION:
        {
          // C operator priorities are different from Imp's...
          
          Accept(A, Ph, binary_arithmetic_operation);
          
          int mid_op = A->binary_arithmetic_operation.binop;
          int mid_op_prec = OpDetails[mid_op].C_prec;

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

          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_prec = ATOMPRIO;
          if (left_is_binary) {           
            Accept(L, left_operand, binary_arithmetic_operation);
            left_op = L->binary_arithmetic_operation.binop;
            //fprintf(stderr, "leftop=%s\n", OpDetails[left_op].C_mid); // see unary
            left_op_prec = OpDetails[left_op].C_prec;
          }
          
          int right_op = -1;
          int right_op_prec = ATOMPRIO;
          if (right_is_binary) {
            Accept(R, right_operand, binary_arithmetic_operation);
            right_op = R->binary_arithmetic_operation.binop;
            //fprintf(stderr, "rightop=%s\n", OpDetails[right_op].C_mid);
            right_op_prec = OpDetails[right_op].C_prec;
          }        

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

      case AST_ASSIGN:
        {
          Accept(A, Ph, assign);
          fprintf(stdout, "  ");
          generate_c(A->assign.LHS, depth+1);
          fprintf(stdout, " = "); // should be <assop>
          // fprintf(stderr, "RHS: %08x -> ", A->assign.RHS);
          generate_c(A->assign.RHS, depth+1);
          //fprintf(stdout, ";\n");
        }
        break;

      case AST_PROC_CALL:
        {
          Accept(A, Ph, proc_call);


          VarDeclIDX VarIDX = A->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;
          fprintf(stdout, "%s", Tag2Str(Tag));
          
          //generate_c(A->proc_call.Proc, depth+1); // VarIDX

          fprintf(stdout, "(");
          generate_c(A->proc_call.Args, depth+1); // P<APP>
          fprintf(stdout, ")\n");
        }
        break;

      case AST_ARRAYACCESS:
        {
          Accept(A, Ph, array_access);

          VarDeclIDX VarIDX = A->array_access.Array;
          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;
          fprintf(stdout, "%s", Tag2Str(Tag));
          
          //generate_c(A->array_access.Array, depth+1); // VarIDX

          //generate_c(A->array_access.Indices, depth+1); // P<APP>

          if (P_alt(A->array_access.Indices) == 0) {  // P<APP>              = '('<EXPR><RESTOFAPP>')',''
            fprintf(stdout, "[");
            int EXPR = P_P(A->array_access.Indices, 2);
            generate_c(compile(EXPR, depth+1), depth+1); // <EXPR>
            fprintf(stdout, "]");
            int RESTOFAPP = P_P(A->array_access.Indices, 3);
            //compile(RESTOFAPP, depth+1);                      // <RESTOFAPP> = ','<READLINEP><EXPR><RESTOFAPP>, ''

            for (;;) {
              if (P_alt(RESTOFAPP) == 1) break;
              EXPR = P_P(RESTOFAPP, 3);
              fprintf(stdout, "[");
              generate_c(compile(EXPR, depth+1), depth+1);
              fprintf(stdout, "]");
              RESTOFAPP = P_P(RESTOFAPP, 4);
            }
            fprintf(stdout, " ");
            
          }
          
        }
        break;

      default:
        {
          int i;
          //fprintf(stdout, "/*regurgitate original Imp: ");
          for (i = 1; i <= count; i++) generate_c(P(i), depth+1); // show imp
                                    // ^^^^^^^^^^ or 'compile()'
          //fprintf(stdout, " */");
        }
        break;
      }
  } else {
    fprintf(stderr, "* handle_ast was passed something other than an AST_PHRASE.  (%d)\n", AST_type);
  }
  return -1;    
}

// generate_c is intended to *only* output its parameter in C form,
// it should *not* attempt to compile anything.  Its parameter should
// already have been compiled and be ready to use. (Although the lines
// may be a little blurred during the development phase)
int generate_c(int Ph, int depth) { // int or void?
  if (Ph == -1) return Ph;
  #ifdef NEVER
    // temp for debugging
    #define _textof(x) #x
    #define textof(x) _textof(x)
    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_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"

  if (AST_type == AST_LITERAL) {
    PrintAtom(AST_index);
    return -1;
  } else if (AST_type == AST_BIP) {
    fprintf(stderr, "{TO DO:AST_BIP in imp80-compile.h}");
    PrintAtom(AST_index);
    return -1;
  } else if (AST_type == AST_PHRASE) {
    // Possible the entire body of handle_ast_phrase(...) should be placed here...
    handle_ast_phrase(Ph, depth);
    return -1;
  } else if (AST_type != PHRASE_TYPE) {
    fprintf(stderr, "{TO DO:AST_type = %d in imp80-compile.h/generate_c}", (AST_type>>AST_type_shift)&AST_type_mask);
    return -1;
  }

  // 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)) {
      handle_ast_phrase(Ph, depth);
    } else {
      fprintf(stderr, "I overlooked something. Could not find phrase number G_{%d}\n", op);
      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.");
      if (op < NUM_PHRASES) {
        fprintf(stderr, "(check the grammar code for a rule which uses <%ls> which is %d)", phrasename[op], op);
      }
      fprintf(stderr, "\n");
      exit(1);
    }
    return -1;
  }

  
  if (0) fprintf(stdout, "/* Unhandled by AST_*: Ph=%08x  AST_type = %d (%s)  AST_index = %d  Grammar index=%d  Phrase=G_%ls  alt=%d  count=%d */\n",
                      Ph,              AST_type>>AST_type_shift,
                                           TypeName[(AST_type>>AST_type_shift)&AST_type_mask],
                                                           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) {

  case P_APP: // P<APP>              = '('<EXPR><RESTOFAPP>')',''
    //fprintf(stdout, "/*<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:
    fprintf(stdout, "<p_varname ph=%d>", PHRASE+NUM_BIPS);
    break;

  case G_VARNAME:
    fprintf(stdout, "<g_varname ph=%d>", PHRASE+NUM_BIPS);
    break;

  case P_LITCONST:
    fprintf(stdout, "<p_litconst ph=%d>", PHRASE+NUM_BIPS);
    break;

  case G_LITCONST:
    fprintf(stdout, "<g_litconst ph=%d>", PHRASE+NUM_BIPS);
    break;

  default:
    fprintf(stderr, "*** generate_c: Switch entry %d not found.\n", PHRASE+NUM_BIPS); // ! *not* op
   return r;
  }
  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 == -1) return Ph;
  if (Ph == 0) {
    fprintf(stderr, "Compile(0,%d) called from %s, line %d\n", depth, file, line);
    return -1;
  }
  // AST format not properly designed yet.  These are placeholders:

// WARNING: this wlit overrides the one in uparse.c
//#define wlit(x) (printlit((x)&AST_idx_mask),x) // used to print in tree walk
                                               // But we also want to *return* the string as well. (eg via printlit result in this case)
                                               // the ",x" was because the AST_LITERAL tag was dropped before being passed to printlit and
                                               // therefore not passed back as its result.
  // to avoid printing the tree, just do:
#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;
  int alt;
  //int count;

  if (debug_compiler) fprintf(stderr, "compile(%ls, %d) in %s, line %d\n", Decode(Ph), depth, file, line);
  
  op    = AST(AST_index+1);
  alt   = AST(AST_index+2);
  //count = AST(AST_index+3);

  // e.g. on initial call, ...
  // P<SS> = ...;
  // /*785*/ COUNT_OF_ALTS    | 0x000001,
  // /*786*/ COUNT_OF_PHRASES | 0x000004,
  // /*787*/ SEMANTIC_TYPE    | S_init,
  // /*788*/ SEMANTIC_TYPE    | S_Imp77_stropping,
  // /*789*/ PHRASE_TYPE      | G_STATEMENTS,
  // /*790*/ SEMANTIC_TYPE    | S_terminate,

  // Ph=20043fa0  AST_type = 2  AST_index = 278432  op=785[58000001]  alt=0  count=4

  if (AST_type == AST_LITERAL) {
    PrintAtom(AST_index);
    return -1;
  }
  if (AST_type == AST_BIP) {
    fprintf(stderr, "{TO DO:AST_BIP in imp80-compile.h}");
    //PrintAtom(AST_index);
    return -1;
  }
  if (AST_type != AST_PHRASE && AST_type != PHRASE_TYPE) {
    fprintf(stderr, "{imp80-compile.h TO DO: AST_type = %d  AST_index = %d}", 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, "Most likely coding error is that compile() was called with a\n");
    fprintf(stderr, "P_x phrase number instead of a G_x one.");
    if (op < NUM_PHRASES) {
      fprintf(stderr, "(check the grammar code for a rule which uses <%ls> which is %d)", phrasename[op], op);
    }
    fprintf(stderr, "\n");
    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) {

$include "imp80-switch.h"

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