/*


TO DO: before filling in types of variables, we have to fill in types
of declarations!  Especially the partial fields of structs! - which was
the initial purpose of this module; doing the same for the uses of the
data was an add-on.  Should separate the two functions into two files!

(so what to call them?  flesh_out_declarations()?  fixup_object_types()?  )

   The purpose of this module is to:

   Fill in the offsets to record fields, and the sizes of array elements,
   so that when we code-generate, the data is already in place.  (Part of
   this is done by converting simple tags into proper ast objects, eg
   replacing the tag for a record field with the ast object describing the
   record field)


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


  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.

*/

int locate_tag_within_a_scope(int p, int tag)
{
  int t;
  int disp;
  int this;
  int i;

  //fprintf(stdout, "locate tag %s within scope %d\n", c[tag].s, p);

  // The place where this currently falls down is where we are compiling inside this block
  // but have not yet reached the relevant declaration - we'll get the one in this block
  // when in fact we *should* get the one from the enclosing block, eg:
  //  {
  //  int i = 3;
  //   {
  //    printf("i = %d\n", i);
  //    int i = 10;
  //    // blah blah blah
  //   }
  //  }
  //     .. this will print i = 10 (or possibly undefined or random value) as it will
  // find the second i within the current scope :-(
  // Hack solution: when treewalking, stop when you get to the current ap.

  if (astop(p) == AST_Declare) {
    //fprintf(stdout, "p5  compare %s vs %s\n", c[leftchild(leftchild(p))].s, c[tag].s);
    if (c[leftchild(leftchild(p))].s == c[tag].s) {
      //fprintf(stdout, "tag %s located in AST_Declare at %d\n", c[tag].s, rightchild(p));
      return rightchild(p);
    }
  } else if (astop(p) == AST_DefProc) { // DefProc: scope/qual/type/ptr-seq, id, params, body
    if (c[leftchild(rightchild(p))].s == c[tag].s) {
      //fprintf(stdout, "tag %s located in AST_DefProc at %d\n", c[tag].s, p);
      return p;
    }
  } else if (astop(p) == AST_ReceiveParam) { // name type link
    if (c[leftchild(leftchild(p))].s == c[tag].s) { // just like Declare
      //fprintf(stdout, "tag %s located in AST_ReceiveParam at %d\n", c[tag].s, rightchild(p));
      return rightchild(p);
    }
    // now recurse on rest of paramlist
  } else if (astop(p) == AST_TYPE_Struct) {
    //fprintf(stdout, "p1\n");
    if (rightchild(p) == -1) return -1; // recursion killer :-)  This is another incomplete subfield reference.
    //fprintf(stdout, "p2\n");
    if (c[leftchild(leftchild(p))].s == c[tag].s) {
      //fprintf(stdout, "p3\n");
      //fprintf(stdout, "tag %s located in AST_TYPE_Struct at %d\n", c[tag].s, p);
      return p;
    }
    //fprintf(stdout, "p4\n");
  } else if (astop(p) == AST_Scope) {
    return -1; // AST_Scope is effectively the start of a sub-block that we want to skip.
  }
  // recursively search for more declarations within this block
  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)) {
      //fprintf(stderr, "%d: nthchild(%d,%d)\n", nthchild(p,i), p, i);
      if ((astop(p) != AST_TYPE_PointerTo) && (astop(nthchild(p, i)) != AST_TYPE_StructMember))
        {t = locate_tag_within_a_scope(nthchild(p, i), tag); if (t != -1) return t;} // See above
    }
    this = this>>1;
  }
  return -1; // NOT REACHED
}


int lookup_scalar_type(int p) // may need an extra param, to restrict to one
{                             // of C's 4 separate namespaces.
  // search in current block first, if not found, start going upwards through enclosing blocks.
  int loops = 0;
  int t, tag;
  int scope = current_scope;
  int current_block = leftchild(scope);

  // p is the index of an astop of type AST_TAG; tag is the contents of that ast entry
  tag = leftchild(p);

  for (;;) {

    loops += 1;
    if (loops == 10) {
      fprintf(stderr, "! Implementation error: max scopes = 10 for now.  Possibly a bad data structure.\n");
      exit(1);
    }

    t = locate_tag_within_a_scope(current_block, tag); // finds declns or struct types -- again, needs namespace restriction
    if (t != -1) return t;

    if (scope == rightchild(scope)) break;
    scope = rightchild(scope);
    if (scope == -1) break;
    current_block = leftchild(scope); // expand search to parent
  }
  error_line(c[tag].l);
  fprintf(stderr, "\"%s\", Line %d: Error: %s not declared\n", c[tag].f, c[tag].l, c[tag].s);
  exit(1);
  /* NOT REACHED */ return -1;
}

int lookup_struct_field(int struct_type, int fieldname_tag)
{
  int field;
  return -1;
  assert(astop(struct_type) == AST_TYPE_Struct);  // structtypename childlinks structsize
  assert(astop(fieldname_tag) == AST_TAG);

  // now find the field in the struct, and return an ast cell describing that field
  field = rightchild(struct_type);

  for (;;) {
    if (field == -1) break;
    if (c[leftchild(rightchild(field))].s == c[leftchild(fieldname_tag)].s) { // no strcmp
      fprintf(stderr, "Member %s found in struct %s: %d\n",
                      c[leftchild(fieldname_tag)].s,
	              c[leftchild(leftchild(struct_type))].s,
	              nthchild(field, 2));
      return nthchild(field, 2);
    }
    field = leftchild(field); // next_member tag this_element_type
  }
  fprintf(stderr, "* Error: Member %s not found in struct %s\n", c[leftchild(fieldname_tag)].s, c[leftchild(leftchild(struct_type))].s);
  return -1;
}


void assign_offsets(int p, int type_entry)
{
  int i;
  int disp;
  int this;
  int lhs, rhs;

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

  //fprintf(stdout, "> assign_offsets(%d (%s), %d)\n", p, op[AST[p]-AST_BASE].Diag_Name, type_entry);

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

  switch (astop(p)) {

  case AST_TYPE_Struct:
    // we do NOT want to apply type checking rules to declarations of tags, only *uses* of tags
    return;

  case AST_Member:

    // struct sb {  int b; };
    // struct sa {  struct sb a; } s;

    // in the case of 's' . 'a', lhs type would be "struct sa",
    // when we get to 's.a' . 'b', lhs type would be "struct sb".

    // s.a.b = 1;


    //                =
    //              /   \
    //            .      1
    //          /   \
    //        .      b   <== lookup 'b' in struct 'sb'
    //      /   \
    //     s     a   <== lookup 'a' in struct 'sa'

    // recurse down,

    // type_entry should be 'int' from the '=' AST_AssignTo operator in "<lhs> = 1"
    // assign type info as we return from the recursion


    // look up the type of p, then compare to the passed-in type_entry
    lhs = leftchild(p); rhs = rightchild(p);

    if (lhs < 0) {
      fprintf(stdout, "Member: lhs = %d\n"); exit(0);
    }
    //fprintf(stdout, "> assign_offsets(%d, type_entry);\n", lhs);
    assign_offsets(lhs, type_entry);
    //fprintf(stdout, "< assign_offsets(%d, type_entry);\n", lhs);

    lhs = TYPEINFO(lhs);
    // lhs should be a struct object (not necessarily a top-level one) and rhs should be a field or array

    TYPEINFO(p) = lookup_struct_field(lhs, rhs);  // lhs = s, rhs = a; or lhs = s.a, rhs = b  (as in s.a.b = 1)
    // I think we also need to plug in offset of field into RHS?

    break;


  case AST_Ptr:
    // very similar to AST_Member

    // struct sb {  int b; };
    // struct sa {  struct sb *a; } *s;

    //                =
    //              /   \
    //            ->     1
    //          /   \
    //        ->     b
    //      /   \
    //    s      a

    // in the case of 's' -> 'a', lhs type would be "struct *sa",
    // when we get to 's->a' -> 'b', lhs type would be "struct *sb".

    // s->a->b = 1;

    // look up the type of p, then compare to the passed-in type_entry
    lhs = leftchild(p); rhs = rightchild(p);

    if (lhs < 0) {
      fprintf(stdout, "Member: lhs = %d\n"); exit(0);
    }
    //fprintf(stdout, "> assign_offsets(%d, type_entry);\n", lhs);
    assign_offsets(lhs, type_entry);
    //fprintf(stdout, "< assign_offsets(%d, type_entry);\n", lhs);

    lhs = TYPEINFO(lhs);
    // lhs should be a struct object (not necessarily a top-level one) and rhs should be a field or array

    TYPEINFO(p) = lookup_struct_field(lhs, rhs);  // lhs = s, rhs = a; or lhs = s->a, rhs = b  (as in s->a->b = 1)
    // I think we also need to plug in offset of field into RHS?

    break;


  case AST_Idx:    // (remember that the index of an array element has to be a non-negative integer.)
    // roughly similar to Ptr and Member, but we now have an added item for the index which
    // spawns a second new set of independent checks.

    // look up the type of p, then compare to the passed-in type_entry
    lhs = leftchild(p); rhs = rightchild(p); // nthchild(p, 2) is the index variable... TO DO

    if (lhs < 0) {
      fprintf(stdout, "Member: lhs = %d\n"); exit(0);
    }
    //fprintf(stdout, "> assign_offsets(%d, type_entry);\n", lhs);
    assign_offsets(lhs, type_entry);
    //fprintf(stdout, "< assign_offsets(%d, type_entry);\n", lhs);

    lhs = TYPEINFO(lhs);
    // lhs should be a struct object (not necessarily a top-level one) and rhs should be a field or array

    TYPEINFO(p) = lookup_struct_field(lhs, rhs);  // probably wrong.  to do...
    // I think we also need to plug in offset of field into RHS?

    break;


  case AST_AddressOf:
    if (TYPEINFO(p) == -1) {
      assign_offsets(leftchild(p), type_entry);
      TYPEINFO(p) = mkmonop(AST_TYPE_PointerTo, TYPEINFO(leftchild(p)));
    }
    break;


  case AST_TAG:    // atom/scalar
    if (TYPEINFO(p) == -1) TYPEINFO(p) = lookup_scalar_type(p);
    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)) {
        assign_offsets(nthchild(p, i), -1);
      }
      this = this>>1;
    }

  }

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

void insert_offsets(int p)
{
  assign_offsets(p, -1);
}
