/*

   The purpose of this module is to:

1) Insert any widening operations (or narrowing operations, with overflow
   tests) into the AST, so that things like adding a char to a long work
   correctly.

2) Warn of incompatible types in assignments etc

We can only do this after the declarations have been fixed up (pointers to
structs inserted etc) and the variable objects have had their types and
offsets etc attached to them.  So this has to follow insert_offsets.c

---------------------------------------------------------------------------


  When building the AST, we construct the AST items very superficially,
plugging in only the identifying TAGS etc, rather than the types and offsets
etc.

As soon as the tree is built, we need to run a pass over it to insert
'scope' objects so that when we later look for identifiers, we can restrict
the search to relevant scopes.

Once the full tree is built, we then run a pass over the tree and build out
the details of the declarations, so that when we get to the code generation
stage, we have all the offsets, sizes etc in the declarations.

Now we do a pass over the data, pushing down structures from the known types
at the top of the tree, down to the leaves; we match the expected and known
types as we return back up the tree, and signal any incompatibility errors -
or perform implicit casts (usually widening/narrowing) to the <expected,
actual> pairs.

At the top level, we know what to expect from 1) assignments; 2) procedure
parameters.

Not so sure about statements such as x->field = y->field - all we can check
is that the types of the final assigned field are the same.
*/

int get_object_type(int p)
{
  // returns the type of a single object, eg i, or fred[24]->whatever.data
  // At the moment, an array object is not a valid result, but that may be added later
  // (however a pointer is OK)
  return TYPEINFO(p);
}

void check_expression_type(int lhsp, int lhst, int rhsp, int rhst)
{
  // confirm that expression p is assignable to an object of type ty.
  // insert any casts into the tree if needed.  Or print an error
  // and exit if not possible.
  if (lhst == -1) {
    fprintf(stdout, "! Error: Assignment type of AST[%d] (lhs) is unknown! (-1)\n", lhsp); print_all_trips(); exit(1);
  }
  if (rhst == -1) {
    fprintf(stdout, "! Error: Assignment type of AST[%d] (rhs) is unknown! (-1)\n", rhsp); print_all_trips(); exit(1);
  }
  if ((astop(lhst) != AST_TYPE_Atom) && (astop(lhst) != AST_TYPE_PointerTo)) {
    fprintf(stdout, "! Error: Assignment type of AST[%d] (lhs) is not a scalar or a pointer! (%d)\n", lhsp, astop(lhst)); print_all_trips(); exit(1);
  }
  if ((astop(rhst) != AST_TYPE_Atom) && (astop(rhst) != AST_TYPE_PointerTo)) {
    fprintf(stdout, "! Error: Assignment type of AST[%d] (rhs) is not a scalar! (%d)\n", rhsp, astop(rhst)); print_all_trips(); exit(1);
  }
  if (lhst == rhst) {
    //fprintf(stdout, "Wow! Assignment types of AST[%d] and AST[%d] actually were identical! (%d)\n", lhsp, rhsp, lhst);
  } else if (/*signed?*/(leftchild(lhst) == leftchild(rhst)) &&
             /*size?*/(rightchild(lhst) == rightchild(rhst))) { // exact equality check, not proper compatibility check

    // WE NEED A BETTER TYPE COMPARISON TEST HERE BECAUSE OF PointerTo ...

    //fprintf(stdout, "Assignment types of AST[%d] and AST[%d] are compatible! (%d)\n", lhsp, rhsp, rightchild(lhst));
  } else {
    fprintf(stdout, "* Error: Assignment types of AST[%d] and AST[%d] are not compatible! (%d vs %d)\n", lhsp, rhsp, rightchild(lhst), rightchild(rhst)); print_all_trips(); exit(1);
    // for certain combinations, inserting code to widen may be OK here.  Or to range-check dynamically and narrow?
  } 
}

void xpoptypes(int p, int type_entry)
{
  int i;
  int disp;
  int this;

  if ((p == -1) || (astop(p) == -1)) return; // need to check type_entry too?

  //fprintf(stdout, "> xpoptypes(%d, %d)\n", p, type_entry);

  if (astop(p) == AST_Scope) {
    push_scope(p);
  }

  switch (astop(p)) {
  case AST_AddAss:
  case AST_SubAss:
  case AST_MulAss:
  case AST_DivAss:
  case AST_ModAss:
  case AST_BitAndAss:
  case AST_BitOrAss:
  case AST_BitXorAss:
  case AST_BitLshAss:
  case AST_BitRshAss:
    // the X += Y style is not yet properly handled.
  case AST_AssignTo:
    // if p is an assignment, compare type of lhs to rhs, and fail or cast as appropriate
    // (This also include declaration initializers)
    //fprintf(stdout, "Assignment.  Type of lhs should match type of rhs.\n");
    check_expression_type(leftchild(p), get_object_type(leftchild(p)),
                          rightchild(p), get_object_type(rightchild(p))); // or should this be: xpoptypes(rightchild(p), i);
    TYPEINFO(p) = get_object_type(leftchild(p));
    break;

  case AST_UseParam:
    // if p is being passed as a parameter, compare type of p to param, and fail or cast as appropriate
    break;

  // do we need function-call in here too? (We assume by induction that the object returned by a function
  // really is the exact type as declared for the function, since it has already been fixed up at 'returnresult'...

  case AST_ReturnResult: // leftchild: result   rightchild: pointer to enclosing function (may not be complete yet?)
    // make sure value being passed back as a result is compatible with the function type.
    // if p is an assignment, compare type of lhs to rhs, and fail or cast as appropriate
    // (This also include declaration initializers)
    fprintf(stdout, "return x.  Type of fn should match type of result.\n");
    check_expression_type(p, get_object_type(p), // the function result type, already inserted here
                          leftchild(p), get_object_type(leftchild(p))); // or should this be: xpoptypes(rightchild(p), i);
    break;

    // these three will only be accessed if we recursively call xpoptypes rather than check_expression_type...
  case AST_Idx:    // (remember that the index of an array element has to be a non-negative integer.)
  case AST_Member:
  case AST_Ptr:
    // look up the type of p, then compare to the passed-in type_entry, and recurse
    break;

  case AST_Cond:
    // for various operators, ensure that the operands are appropriate, eg scalars, booleans, non-negative numbers
    // Cond, for example, requires a condition as its first parameter, but also the type of the second and third
    // parameters have to be compatible with whereever the conditional expression is being used, eg x = (a ? b : c)
    // or x = a << (b ? 2 : 3).  It is quite possible that one branch of the conditional expression has to be
    // widened but the other branch does not...
    break;

  default:
    // if object is of no interest, recurse with type_entry = -1

    disp = op[astop(p)-AST_BASE].Display_Children;
    this = 1<<(children(p)-1);
    for (i = 0; i < children(p); i++) {
      if ((nthchild(p, i) != -1) && (disp&this)) {
        xpoptypes(nthchild(p, i), -1);
      }
      this = this>>1;
    }

  }

  if (astop(p) == AST_Scope) {
    pop_scope();
  }
 
  //fprintf(stdout, "< xpoptypes(%d, %d)\n", p, type_entry);
 
}

void insert_casts(int p)
{
  xpoptypes(p, -1);
}
