/*****
* coder.h
* Andy Hammerlindl 2004/11/06
*
* Handles encoding of syntax into programs.  Its methods are called by
* abstract syntax objects during translation to construct the virtual machine
* code.
*****/

#ifndef CODER_H
#define CODER_H

#include "errormsg.h"
#include "entry.h"
#include "types.h"
#include "record.h"
#include "frame.h"
#include "program.h"
#include "util.h"
#include "modifier.h"
#include "inst.h"

namespace trans {

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

using vm::bltin;
using vm::inst;
using vm::item;

#ifdef DEBUG_BLTIN
void assertBltinLookup(inst::opcode op, item it);
#endif

// Labels used by the coder class to denote where in the code a jump
// instruction should go to.  Label can be used before their exact location is
// known.
// Declared outside of the coder class, so that it can be declared in exp.h.
struct label_t : public gc {
 vm::program::label location;
 vm::program::label firstUse;

 // Most labels are used only once, and so we optimize for that case.  We do,
 // however, have to handle labels which are used multiple times (such as
 // break locations in a loop), and so a useVector is allocated to store
 // these if necessary.
 typedef mem::vector<vm::program::label> useVector;
 useVector *moreUses;

 // Only the constructor is defined.  Everything else is handles by methods
 // of the coder class.
 label_t() : location(), firstUse(), moreUses(0) {}
};
typedef label_t *label;

class coder {
 // The frame of the function we are currently encoding.  This keeps
 // track of local variables, and parameters with respect to the stack.
 frame *level;

 // The frame of the enclosing record that the "this" expression yields.  ie.
 // the highest frame that is a record, not a function.
 frame *recordLevel;

 // The type of the enclosing record.  Also needed for the "this" expression.
 record *recordType;

 // Are we translating a codelet?
 bool isCodelet;

 // The lambda being constructed. In some cases, this lambda is needed
 // before full translation of the function, so it is stored,
 // incomplete, here.
 vm::lambda *l;

 // The type of the function being translated.
 const function *funtype;

 // The enclosing environment.  Null if this is a file-level module.
 coder *parent;

 // The mode of encoding, either static or dynamic. sord is used as an
 // acronym for Static OR Dynamic.
 // Once something is static, no amount of dynamic modifiers can change
 // that, so once a stack is EXPLICIT_STATIC, additional modifiers will
 // be pushed on as EXPLICIT_STATIC.
 modifier sord;
 std::stack<modifier> sord_stack;

 // What permissions will be given to a new access.
 // TODO: Ensure private fields don't show up calling lookup for a
 // record.
 permission perm;

 // The function code as its being written.  Code points to next place in
 // array to write.
 vm::program *program;

 // Some loops allocate nested frames, in case variables in an
 // iteration escape in a closure.  This stack keeps track of where the
 // pushframe instructions are, so the size of the frame can be encoded.
 std::stack<vm::program::label> pushframeLabels;

 // Loops need to store labels to where break and continue statements
 // should pass control.  Since loops can be nested, this needs to
 // be stored as a stack.  We also store which of the loops are being encoded
 // with an additional frame for variables.  This is needed to know if the
 // break and continue statements need to pop the frame.
 struct loopdata_t : gc {
   label continueLabel;
   label breakLabel;
   bool pushedFrame;

   loopdata_t(label c, label b)
     : continueLabel(c), breakLabel(b), pushedFrame(false) {}
 };
 mem::stack<loopdata_t> loopdata;

 // Current File Position
 position curPos;

public:
 // Define a new function coder.  If reframe is true, this gives the function
 // its own frame, which is the usual (sensible) thing to do.  It is set to
 // false for a line-at-a-time codelet, where variables should be allocated in
 // the lower frame.
 coder(position pos,
       string name, function *t, coder *parent,
       modifier sord = DEFAULT_DYNAMIC,
       bool reframe=true);

 // Start encoding the body of the record.  The function being encoded
 // is the record's initializer.
 coder(position pos,
       record *t, coder *parent, modifier sord = DEFAULT_DYNAMIC);

 coder(position pos,
       string name, modifier sord = DEFAULT_DYNAMIC);

 coder(const coder&);

 /* Add a static or dynamic modifier. */
 void pushModifier(modifier s)
 {
   /* Default setting should only be used in the constructor. */
   assert(s != DEFAULT_STATIC && s != DEFAULT_DYNAMIC);

   /* Non-default static overrules. */
   if (s == AUTOUNRAVEL || sord == AUTOUNRAVEL) {
     sord = AUTOUNRAVEL;
   } else if (s == EXPLICIT_STATIC || sord == EXPLICIT_STATIC) {
     sord = EXPLICIT_STATIC;
   } else {
     sord = s;
   }

   sord_stack.push(sord);
 }

 /* Tests if encoding mode is currently static. */
 bool isStatic()
 {
   switch(sord) {
     case DEFAULT_STATIC:
     case EXPLICIT_STATIC:
     case AUTOUNRAVEL:
       return true;
     case DEFAULT_DYNAMIC:
     case EXPLICIT_DYNAMIC:
       return false;
     default:
       assert(False);
       return false;
   }
 }

 bool isAutoUnravel()
 {
   return sord == AUTOUNRAVEL;
 }


 /* Remove a modifier. */
 void popModifier()
 {
   assert(!sord_stack.empty());
   sord_stack.pop();

   assert(!sord_stack.empty());
   sord = sord_stack.top();
 }

 /* Set/get/clear permissions. */
 void setPermission(permission p)
 {
   perm = p;
 }
 permission getPermission()
 {
   return perm;
 }
 void clearPermission()
 {
   perm = DEFAULT_PERM;
 }


 // Says what the return type of the function is.
 ty *getReturnType() {
   return funtype->result;
 }

 bool isRecord();

 // Creates a new coder to handle the translation of a new function.
 coder newFunction(position pos,
                   string name, function *t, modifier sord=DEFAULT_DYNAMIC);

 // Creates a new record type.
 record *newRecord(symbol id);

 // Create a coder for the initializer of the record.
 coder newRecordInit(position pos, record *r, modifier sord=DEFAULT_DYNAMIC);

 // Create a coder for translating a small piece of code.  Used for
 // line-at-a-time mode.
 coder newCodelet(position pos);

 frame *getFrame()
 {
   if (isStatic() && !isTopLevel()) {
     frame *result = parent->getFrame();
     assert(result);
     return result;
   }
   else
     return level;
 }

 // Tests if the function or record with the given frame is currently under
 // translation (either by this coder or an ancestor).
 bool inTranslation(frame *f) {
   frame *level=this->level;
   while (level) {
     if (f==level)
       return true;
     level=level->getParent();
   }
   return parent && parent->inTranslation(f);
 }

 // Allocates space in the function or record frame for a new local variable.
 access *allocLocal()
 {
   return getFrame()->allocLocal();
 }

 // Get the access in the frame for a specified formal parameter.
 access *accessFormal(Int index)
 {
   // NOTE: This hasn't been extended to handle frames for loops, but is
   // currently only called when starting to translate a function, where there
   // can be no loops.
   return level->accessFormal(index);
 }

 // Checks if we are at the top level, which is true for a file-level module or
 // a codelet.
 bool isTopLevel() {
   return parent==0 || isCodelet;
 }

 // The encode functions add instructions and operands on to the code array.
private:
 void encode(inst i)
 {
   i.pos = curPos;
   // Static code is put into the enclosing coder, unless we are translating a
   // codelet.
   if (isStatic() && !isTopLevel()) {
     assert(parent);
     parent->encode(i);
   }
   else {
     program->encode(i);
   }
 }

 // Encode a jump to a not yet known location.
 vm::program::label encodeEmptyJump(inst::opcode op);

public:
 void encode(inst::opcode op)
 {
   inst i; i.op = op; i.pos = nullPos;
   encode(i);
 }
 void encode(inst::opcode op, item it)
 {
#ifdef DEBUG_BLTIN
   assertBltinLookup(op, it);
#endif
   inst i; i.op = op; i.pos = nullPos; i.ref = it;
   encode(i);
 }

 // Encodes a pop instruction, or merges the pop into the previous
 // instruction (ex. varsave+pop becomes varpop).
 void encodePop();

 // Puts the requested frame on the stack.  If the frame is not that of
 // this coder or its ancestors, false is returned.
 bool encode(frame *f);

 // Puts the frame corresponding to the expression "this" on the stack.
 bool encodeThis()
 {
   assert(recordLevel);
   return encode(recordLevel);
 }

 // Puts the frame corresponding to the parent of 'ent' on the stack. Triggers
 // an error if 'ent' does not correspond to a record (a.k.a. asy struct) type.
 bool encodeParent(position pos, trans::tyEntry *ent);

 // An access that encodes the frame corresponding to "this".
 access *thisLocation()
 {
   assert(recordLevel);
   return new frameAccess(recordLevel);
 }

 // Returns the type of the enclosing record.
 record *thisType()
 {
   return recordType;
 }

 // Puts the 'dest' frame on the stack, assuming the frame 'top' is on
 // top of the stack.  If 'dest' is not an ancestor frame of 'top',
 // false is returned.
 bool encode(frame *dest, frame *top);


 // Assigns a handle to the current point in the list of bytecode
 // instructions and returns that handle.
 label defNewLabel();

 // Sets the handle given by label to the current point in the list of
 // instructions.
 label defLabel(label label);

 // Encodes the address pointed to by the handle label into the
 // sequence of instructions.  This is useful for a jump instruction to
 // jump to where a label was defined.
 void useLabel(inst::opcode op, label label);

 // If an address has to be used for a jump instruction before it is
 // actually encoded, a handle can be given to it by this function.
 // When that handle's label is later defined, the proper address will
 // be inserted into the code where the handle was used.
 label fwdLabel();

 void pushLoop(label c, label b) {
   loopdata.push(loopdata_t(c,b));
 }
 void popLoop() {
   loopdata.pop();
 }
 void loopPushesFrame()
 {
   assert(!loopdata.empty());
   loopdata_t& d = loopdata.top();
   d.pushedFrame = true;
 }
 bool encodeBreak() {
   if (loopdata.empty())
     return false;
   else {
     loopdata_t& d = loopdata.top();
     if (d.pushedFrame)
       encode(inst::popframe);
     useLabel(inst::jmp,d.breakLabel);
     return true;
   }
 }
 bool encodeContinue() {
   if (loopdata.empty())
     return false;
   else {
     loopdata_t& d = loopdata.top();
     if (d.pushedFrame)
       encode(inst::popframe);
     useLabel(inst::jmp,d.continueLabel);
     return true;
   }
 }

 // Returns true if a pushclosure has been encoded since the definition of
 // the label.
 bool usesClosureSinceLabel(label l);

 // Turn a no-op into a jump to bypass incorrect code.
 void encodePatch(label from, label to);

 void encodePushFrame() {
   pushframeLabels.push(program->end());
   encode(inst::pushframe, (Int)0);

   level = new frame("encodePushFrame", level, 0);
 }

 void encodePopFrame() {
   pushframeLabels.top()->ref = level->size();
   pushframeLabels.pop();

   encode(inst::popframe);

   level = level->getParent();
 }

 // Adds an entry into the position list, linking the given point in the
 // source code to the current position in the virtual machine code.  This is
 // used to print positions at runtime.
 void markPos(position pos);

 // When translation of the function is finished, this ties up loose ends
 // and returns the lambda.
 vm::lambda *close();

 // Finishes translating the initializer of a record.
 void closeRecord();

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

} // namespace trans

#endif