//
// coder.h
// Code generator interface
// F.J. Alberti
//
// FA  07/08/2001   Add writeString() method
// FA  22/11/2001   Bug correction: backPatch() did not test the writer state!
//                  CODER_GETINT32() redefined in terms of new get() method.
//                  BackPatch() was modified and moved to implementation file
//

#ifndef _CODER_H_
#define _CODER_H_

#if defined(SCOL_DEBUG)
// Include disassembler
//#define SCOL_DISASSEMBLER
#endif


#include "base.h"
extern "C" {
#include "mmemory.h"
}


// The following macros are used in the compiler, instead of directly using 
// the interface of the BytecodeStream class below. This is due to the fact
// that error management still agrees with the old convention.
#define CODER_RESET             theCode.reset();

#define CODER_WRITEINT16(n)     { theCode.writeInt16(n);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_WRITEINT32(n)     { theCode.writeInt32(n);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_WRITEFLOAT(x)     { theCode.writeFloat(x);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_WRITEBYTE(n)      { theCode.writeByte(n);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_WRITEOPCODE(n)    { theCode.writeOpcode(n);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_WRITEOFFSET(n)    { theCode.writeOffset(n);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_WRITESTRING(s)    { theCode.writeString(s);\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_GETINT32(off)     (theCode.get(off))
  // used only for compiling 'match'

#define CODER_LASTOFFSET        (theCode.lastOffset())
  // not really needed with new convention

#define CODER_OFFSET            (theCode.offset())

#define CODER_WRITEHOLE(offp)   { *(offp) = theCode.writeHole();\
  if (theCode.err != MERROK) return theCode.err; }

#define CODER_BACKPATCH(off, n) theCode.backPatch((off), (n));

#define CODER_SAVE(m, blkp)     ((*(blkp) = theCode.save(m)) == NIL)

#define CODER_DUMPASSEMBLER     theCode.dumpAssembler();

#define CODER_OPTIMISE          theCode.optimise();

#define CODER_SETWRITERSTATE(s) theCode.setWriterState(s);


class BytecodeStream {
// The heart of the bytecode generator: The bytecode stream class
public:
  BytecodeStream(uint32 capacity);
  ~BytecodeStream();

  uint32 writeByte(byte n);
  uint32 writeOpcode(byte opcode);
  uint32 writeInt16(uint16 n);
  uint32 writeInt32(uint32 n);
  uint32 writeFloat(float32 x);
  uint32 writeOperand16(uint16 opn);
  uint32 writeOperand32(uint32 opn);
  uint32 writeOffset(uint32 offset);
  uint32 writeHole();
  uint32 writeString(const char* s);       // to be deprecated
  uint32 get(uint32 offset) const;         // to be deprecated
  void backPatch(uint32 offset, uint32 n);
  uint32 offset() const;
  uint32 lastOffset() const;
  uint32 size() const;
  void reset();
  int save(mmachine m) const;              // (old convention)
  void optimise();                         // support for tail call optimisations
  bool getWriterState() const;
  void setWriterState(bool state);
#if defined(SCOL_DISASSEMBLER)
  void dumpAssembler();
#endif

  static const bool kWriterStateEnabled;
  static const bool kWriterStateDisabled;

  int err;                                 // error code (old convention)

private:
  int _check();

  byte*  _stream;      // the array of bytecodes
  uint32 _capacity;    // maximum capacity (in bytes)
  uint32 _offset;      // current offset
  uint32 _lastOffset;  // offset of last opcode or operand generated
  bool   _state;       // writer state (enabled/disabled)
};


// Most common abbreviations

inline uint32 BytecodeStream::writeOpcode(byte opcode)
{
  return writeByte(opcode);
}


inline uint32 BytecodeStream::writeOperand16(uint16 opn)
{
  return writeInt16(opn);
}


inline uint32 BytecodeStream::writeOperand32(uint32 opn)
{
  return writeInt32(opn);
}


inline uint32 BytecodeStream::writeOffset(uint32 offset)
{
  return writeInt32(offset);
}


inline uint32 BytecodeStream::writeHole()
{
  return writeInt32(0);
}


inline uint32 BytecodeStream::get(uint32 offset) const
{
  return *(uint32*)&_stream[offset];
}


inline uint32 BytecodeStream::offset() const
{
  return _offset;
}


inline uint32 BytecodeStream::lastOffset() const
{
  return _lastOffset;
}


inline uint32 BytecodeStream::size() const
{
  return _offset;
}


inline bool BytecodeStream::getWriterState() const
{
  return _state;
}


inline void BytecodeStream::setWriterState(bool state)
{
  _state = state;
}


// Constants defining possible l-values
enum Accessor {
  kAccessorLocal,
  kAccessorGlobal,
  kAccessorField,
  kAccessorArray
};


// Common utility functions to all compiler modules
int writeAccessor(Accessor accessor, bool getter, uint32 opn);
int writePushint(int32 n);
int writePushfloat(float32 x);
int writeNewtuple(byte size);


// The bytecode stream
extern BytecodeStream theCode;


#endif // coder.h