/*****
 * fundec.h
 * Andy Hammerlindl 2002/8/29
 *
 * Defines the semantics for defining functions.  Both the newexp syntax, and
 * the abbreviated C-style function definition.
 *****/

#include "fundec.h"
#include "errormsg.h"
#include "coenv.h"
#include "stm.h"
#include "runtime.h"

namespace absyntax {

using namespace trans;
using namespace types;
using mem::list;

varinit *Default=new definit(nullPos);

void formal::prettyprint(ostream &out, Int indent)
{
  prettyname(
      out, keywordOnly ? "formal (keyword only)" : "formal", indent, getPos()
  );

  base->prettyprint(out, indent+1);
  if (start) start->prettyprint(out, indent+1);
  if (defval) defval->prettyprint(out, indent+1);
}

types::formal formal::trans(coenv &e, bool encodeDefVal) {
  return types::formal(getType(e),
                       getName(),
                       encodeDefVal ? (bool) getDefaultValue() : 0,
                       getExplicit());
}

types::ty *formal::getType(coenv &e) {
  types::ty *bt = base->trans(e);
  types::ty *t = start ? start->getType(bt, e) : bt;
  if (t->kind == ty_void && !em.isSuppressed()) {
    em.error(getPos());
    em << "cannot declare parameters of type void";
    return primError();
  }

  return t;
}

void formal::addOps(coenv &e, record *r) {
  base->addOps(e, r);
  if (start)
  {
    types::ty *bt;
    { // Suppress errors while probing the base type.
      auto modeGuard = em.modeGuard(ErrorMode::SUPPRESS);
      bt = base->trans(e);
    }
    start->addOps(bt, e, r);
  }
}

void formals::prettyprint(ostream &out, Int indent)
{
  prettyname(out, "formals",indent, getPos());

  for (list<formal *>::iterator p = fields.begin(); p != fields.end(); ++p)
    (*p)->prettyprint(out, indent+1);
}

void formals::addToSignature(signature& sig,
                             coenv &e, bool encodeDefVal)
{
  for (list<formal *>::iterator p = fields.begin(); p != fields.end(); ++p) {
    formal& f=**p;
    types::formal tf = f.trans(e, encodeDefVal);

    if (f.isKeywordOnly())
      sig.addKeywordOnly(tf);
    else
      sig.add(tf);
  }

  if (rest) {
    if (rest->getDefaultValue()) {
      em.error(rest->getPos());
      em << "rest parameters cannot have default values";
    }
    sig.addRest(rest->trans(e, encodeDefVal));
  }
}

// Returns the types of each parameter as a signature.
// encodeDefVal means that it will also encode information regarding
// the default values into the signature
signature *formals::getSignature(coenv &e, bool encodeDefVal)
{
  signature *sig = new signature;
  addToSignature(*sig,e,encodeDefVal);
  return sig;
}


// Returns the corresponding function type, assuming it has a return
// value of types::ty *result.
function *formals::getType(types::ty *result, coenv &e,
                           bool encodeDefVal)
{
  function *ft = new function(result);
  addToSignature(ft->sig,e,encodeDefVal);
  return ft;
}

void formals::addOps(coenv &e, record *r)
{
  for(list<formal *>::iterator p = fields.begin(); p != fields.end(); ++p)
    (*p)->addOps(e, r);

  if (rest)
    rest->addOps(e, r);
}

mem::vector<tySymbolPair> *formals::getFields() {
  auto *lst = new mem::vector<tySymbolPair>();
  for (auto frml : fields) {
    lst->emplace_back(frml->getAbsyntaxType(), frml->getName());
  }
  return lst;
}

// Another helper class. Does an assignment, but relying only on the
// destination for the type.
class basicAssignExp : public exp {
  varEntry *dest;
  varinit *value;
public:
  basicAssignExp(position pos, varEntry *dest, varinit *value)
    : exp(pos), dest(dest), value(value) {}

  void prettyprint(ostream &out, Int indent) {
    prettyname(out, "basicAssignExp", indent, getPos());
  }

  types::ty *getType(coenv &) {
    return dest->getType();
  }

  types::ty *trans(coenv &e) {
    // This doesn't handle overloaded types for the destination.
    value->transToType(e, getType(e));
    dest->encode(WRITE, pos, e.c);
    return getType(e);
  }
};

void transDefault(coenv &e, position pos, varEntry *v, varinit *init) {
  // This roughly translates into the statement
  //   if (isDefault(x))
  //     x=init;
  // where x is the variable in v and isDefault is a function that tests
  // whether x is the default argument token.

  v->encode(READ, pos, e.c);

  label end = e.c.fwdLabel();
  e.c.useLabel(inst::jump_if_not_default, end);

  init->transToType(e, v->getType());
  v->encode(WRITE, pos, e.c);
  e.c.encodePop();

  e.c.defLabel(end);
}

void formal::transAsVar(coenv &e, Int index) {
  symbol name = getName();
  if (name) {
    trans::access *a = e.c.accessFormal(index);
    assert(a);

    types::ty *t;
    { // Suppress error messages because they will already be reported
      // when the formals are translated to yield the type earlier.
      auto modeGuard = em.modeGuard(ErrorMode::SUPPRESS);
      t = getType(e);
    }
    varEntry *v = new varEntry(t, a, 0, getPos());

    // Translate the default argument before adding the formal to the
    // environment, consistent with the initializers of variables.
    if (defval)
      transDefault(e, getPos(), v, defval);

    e.e.addVar(name, v);
  }
}


void formals::trans(coenv &e)
{
  Int index = 0;

  for (list<formal *>::iterator p=fields.begin(); p!=fields.end(); ++p) {
    (*p)->transAsVar(e, index);
    ++index;
  }

  if (rest) {
    rest->transAsVar(e, index);
    ++index;
  }
}

void fundef::prettyprint(ostream &out, Int indent)
{
  result->prettyprint(out, indent+1);
  params->prettyprint(out, indent+1);
  body->prettyprint(out, indent+1);
}

function *fundef::transType(coenv &e) {
  bool encodeDefVal=true;
  return params->getType(result->trans(e), e, encodeDefVal);
}

function *fundef::transTypeAndAddOps(coenv &e, record *r) {
  result->addOps(e,r);
  params->addOps(e,r);

  function *ft=transType(e);
  // No function ops to add.

  return ft;
}

varinit *fundef::makeVarInit(function *ft) {
  struct initializer : public varinit {
    fundef *f;
    function *ft;

    initializer(fundef *f, function *ft)
      : varinit(f->getPos()), f(f), ft(ft) {}

    void prettyprint(ostream &out, Int indent) {
      prettyname(out, "initializer", indent, getPos());
    }

    void transToType(coenv &e, types::ty *target) {
      assert(ft==target);
      f->baseTrans(e, ft);
    }
  };

  return new initializer(this, ft);
}

void fundef::baseTrans(coenv &e, types::function *ft)
{
  string name = id ? string(id) : string("<anonymous function>");

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

  // Translate the function.
  fe.e.beginScope();
  params->trans(fe);

  body->trans(fe);

  types::ty *rt = ft->result;
  if (rt->kind != ty_void &&
      rt->kind != ty_error &&
      !body->returns()) {
    em.error(body->getPos());
    em << "function must return a value";
  }

  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);
}

types::ty *fundef::trans(coenv &e) {
  // We must addOps for the return type and formals.  ex:
  //
  //   new guide[] (guide f(int)) {
  //     return sequence(f, 10);
  //   };
  function *ft=transTypeAndAddOps(e, (record *)0);
  assert(ft);

  baseTrans(e, ft);

  return ft;
}

void fundec::prettyprint(ostream &out, Int indent)
{
  prettyindent(out, indent);
  out << "fundec '" << id << "'\n";

  fun.prettyprint(out, indent);
}

void fundec::trans(coenv &e)
{
  transAsField(e,0);
}

void fundec::transAsField(coenv &e, record *r)
{
  function *ft = fun.transTypeAndAddOps(e, r);
  assert(ft);

  createVar(getPos(), e, r, id, ft, fun.makeVarInit(ft));
}



} // namespace absyntax
