/*****
 * constructor.cc
 * Andy Hammerlindl 2007/05/12
 *
 * Using
 *
 *   void operator init(<args>)
 *
 * as a field in the definition structure named Foo implicitly creates a
 * function that could be explicitly defined with code similar to
 *
 *    static Foo Foo(<args>) {
 *      Foo a=new Foo;
 *      a.operator init(<args>);
 *      return a;
 *    }
 *
 * This function is usable within further code in the structure definition and,
 * after the end of the structure definition, is carried over into the enclosing
 * scope so that it lasts as long as the type definition.
 *****/

#include "stack.h"
#include "entry.h"
#include "coenv.h"
#include "dec.h"
#include "newexp.h"

namespace absyntax {

using namespace trans;
using namespace types;

// Defined in dec.cc.
varEntry *makeVarEntry(position pos, coenv &e, record *r, types::ty *t);

bool definesImplicitConstructor(coenv &e, record *r, varEntry *v, symbol id)
{
  if (id == symbol::initsym &&
      r != 0 &&
      v->getType()->kind == ty_function &&
      e.c.isStatic() == false &&
      e.c.isTopLevel() == false)
    {
      function *ft=dynamic_cast<function *>(v->getType());
      if (ft->getResult()->kind == ty_void)
        return true;
    }

  return false;
}

// Given the coenv of the body of the constructor, encode the necessary
// instructions to make a new initialized object.
void transConstructorBody(position pos, coenv &e, record *r, varEntry *init)
{
  assert(r);
  assert(init);

  // Create a varEntry to hold the new object.  Foo a;
  varEntry *v=makeVarEntry(pos, e, 0 /* not a field */, r);

  // Initialize the object.  a=new Foo;
  newRecordExp::transFromTyEntry(pos, e, new tyEntry(r, 0, 0, nullPos));
  v->encode(WRITE, pos, e.c);
  e.c.encodePop();

  // Push the args onto the stack.
  size_t numArgs=init->getSignature()->getNumFormals();
  for (size_t i=0; i<numArgs; ++i) {
    access *a=e.c.accessFormal((Int)i);
    a->encode(READ, pos, e.c);
  }

  // Push the object on the stack.
  v->encode(READ, pos, e.c);

  // Call the 'operator init' field of the object.
  init->encode(action::CALL, pos, e.c, v->getLevel());

  // Push the object again.
  v->encode(READ, pos, e.c);

  // Return the initialized object.
  e.c.encode(inst::ret);
}

varEntry *constructorFromInitializer(position pos, coenv &e, record *r,
                                     varEntry *init)
{
  assert(r);

  types::function *ft=new types::function(r, init->getSignature());

  ostringstream out;
  ft->printVar(out, symbol::trans("<constructor>"));

  // Create a new function environment.
  coder fc = e.c.newFunction(pos, out.str(), ft);
  coenv fe(fc,e.e);

  // Translate the function.
  fe.e.beginScope();

  transConstructorBody(pos, fe, r, init);

  fe.e.endScope();

  // Put an instance of the new function on the stack.
  vm::lambda *l = fe.c.close();
  e.c.encode(inst::pushclosure);
  e.c.encode(inst::makefunc, l);

  // Save it into a varEntry.
  varEntry *v=makeVarEntry(pos, e, r, ft);
  v->encode(WRITE, pos, e.c);
  e.c.encodePop();

  return v;
}

void addConstructorFromInitializer(position pos, coenv &e, record *r,
                                   varEntry *init)
{
  assert(r);

  // Constructors are declared statically.
  e.c.pushModifier(EXPLICIT_STATIC);

  varEntry *v=constructorFromInitializer(pos, e, r, init);

  // Add the constructor function under the same name as the record.
  addVar(e, r, v, r->getName());

  // Add to the "post definition environment" of the record, so it will also be
  // added to the enclosing scope when the record definition ends.
  r->postdefenv.addVar(r->getName(), v);

  e.c.popModifier();
}

} // namespace absyntax

