/*****
 * env.h
 * Andy Hammerlindl 2002/6/20
 *
 * Keeps track of the namespaces of variables and types when traversing
 * the abstract syntax.
 *****/

#ifndef ENV_H
#define ENV_H

#include "errormsg.h"
#include "entry.h"
#include "types.h"
#include "util.h"

namespace types {
class record;
}

namespace absyntax {
class namedTy;
}

namespace trans {

using sym::symbol;
using types::ty;
using types::function;
using types::record;

class genv;
class coenv;

// Keeps track of the name bindings of variables and types.  This is used for
// the fields of a record, whereas the derived class env is used for
// unqualified names in translation.
class protoenv {
//protected:
public:
  // These tables keep track of type and variable definitions.
  tenv te;
  venv ve;

  access *baseLookupCast(ty *target, ty *source, symbol name);

public:
  // Start an environment for a file-level module.
  protoenv() {}

  protoenv(venv::file_env_tag tag)
    : ve(tag) {}

  protoenv(const protoenv&);

  void beginScope()
  {
    te.beginScope(); ve.beginScope();
  }
  void endScope()
  {
    te.endScope(); ve.endScope();
  }
  void collapseScope()
  {
    te.collapseScope(); ve.collapseScope();
  }

  tyEntry *lookupTyEntry(symbol s)
  {
    return te.look(s);
  }

  ty *lookupType(symbol s)
  {
    tyEntry *ent=lookupTyEntry(s);
    return ent ? ent->t : 0;
  }

  varEntry *lookupVarByType(symbol name, ty *t)
  {
    // Search in local vars.
    return ve.lookByType(name, t);
  }

  varEntry *lookupVarBySignature(symbol name, types::signature *sig)
  {
    return ve.lookBySignature(name, sig);
  }

  access *lookupInitializer(ty *t)
  {
    // The initializer's type is a function returning the desired type.
    function *it=new function(t);
    varEntry *v=lookupVarByType(symbol::initsym,it);

    // If not in the environment, try the type itself.
    return v ? v->getLocation() : t->initializer();
  }

  // Find the function that handles casting between the types.
  // The name is "operator cast" for implicit casting and "operator ecast" for
  // explicit.
  access *lookupCast(ty *target, ty *source, symbol name);
  bool castable(ty *target, ty *source, symbol name);

  // A cast lookup designed to work quickly with the application matching
  // code.  The target type must not be overloaded.
  bool fastCastable(ty *target, ty *source);

  // For the lookup, neither target nor source may be overloaded.
  access *fastLookupCast(ty *target, ty *source);

  // Given overloaded types, this resolves which types should be the target and
  // the source of the cast.
  ty *castTarget(ty *target, ty *source, symbol name);
  ty *castSource(ty *target, ty *source, symbol name);

  ty *varGetType(symbol name)
  {
    return ve.getType(name);
  }

  void addType(symbol name, tyEntry *desc)
  {
    te.enter(name, desc);
  }

  void addVar(symbol name, varEntry *desc)
  {
    // Don't check for multiple variables, as this makes adding casts
    // and initializers harder.
    ve.enter(name, desc);
  }

  // Add another environment, say from a record.  If destRegistry is non-null
  // and c.isAutoUnravel(), newly added variables are also registered there.
  void add(protoenv &source, varEntry *qualifier, coder &c,
           AutoUnravelRegistry *destRegistry=nullptr)
  {
    te.add(source.te, qualifier, c);
    ve.add(source.ve, qualifier, c, destRegistry);
  }

  struct Added : public gc {
    tyEntry *typeAdded;
    mem::vector<varEntry*> varsAdded;
    bool empty() { return !typeAdded && varsAdded.empty(); }
  };

  // Add variables and types of name src from another environment under the
  // name dest in this environment.
  Added *add(symbol src, symbol dest,
           protoenv &source, varEntry *qualifier, coder &c)
  {
    Added *retv = new Added();
    retv->typeAdded=te.add(src, dest, source.te, qualifier, c);
    bool varAdded=ve.add(src, dest, source.ve, qualifier, c, &retv->varsAdded);
    assert(varAdded == (retv->varsAdded.size() > 0));
    return retv;
  }

  // Add the standard functions for a new type.
  void addArrayOps(types::array *t);
  void addRecordOps(types::record *r);

  void list(record *r=0)
  {
    ve.list(r);
  }

  // Adds to a list the keywords in the environment that start with the given
  // prefix.  Used for automatic completion at the interactive prompt.
  typedef mem::list<symbol> symbol_list;
  void completions(symbol_list &l, string start)
  {
    te.completions(l, start);
    ve.completions(l, start);
  }

private: // Non-copyable
  void operator=(const protoenv&);
};

// Environment used in translating statements and expressions at all scopes.  As
// opposed to protoenv which is suitable for keeping track of the fields of
// records, this also keeps track of the global env, for loading modules.
class env : public protoenv {
  // The global environment - keeps track of modules.
  genv &ge;
public:
  // Start an environment for a file-level module.
  env(genv &ge);

  ~env();

  record *getModule(symbol id, string filename);
  record *getTemplatedModule(string filename,
                             mem::vector<absyntax::namedTy*> *args);
  record *getLoadedModule(symbol id);
};

} // namespace trans

#endif
