/********************************************************************
** Copyright (c) 2018-2020 Guan Wenliang
** This file is part of the Berry default interpreter.
** skiars@qq.com, https://github.com/Skiars/berry
** See Copyright Notice in the LICENSE file or at
** https://github.com/Skiars/berry/blob/master/LICENSE
********************************************************************/
#ifndef BE_OBJECT_H
#define BE_OBJECT_H

#include "berry.h"

/* basic types, do not change value */
#define BE_NONE        -256     /* unknown type */
#define BE_NIL          0
#define BE_INT          1
#define BE_REAL         2
#define BE_BOOL         3
#define BE_COMPTR       4       /* common pointer */
#define BE_INDEX        5       /* index for instance variable, previously BE_INT */
#define BE_FUNCTION     6

#define BE_GCOBJECT     16      /* from this type can be gced */
#define BE_GCOBJECT_MAX (3<<5)  /* 96 - from this type can't be gced */

#define BE_STRING       16
#define BE_CLASS        17
#define BE_INSTANCE     18
#define BE_PROTO        19
#define BE_LIST         20
#define BE_MAP          21
#define BE_MODULE       22
#define BE_COMOBJ       23      /* common object */

#define BE_NTVFUNC      ((0 << 5) | BE_FUNCTION)    /* 6 - cannot be gced */
#define BE_CLOSURE      ((1 << 5) | BE_FUNCTION)    /* 38 - can be gced */
#define BE_NTVCLOS      ((2 << 5) | BE_FUNCTION)    /* 70 - can be gced*/
#define BE_CTYPE_FUNC   ((3 << 5) | BE_FUNCTION)    /* 102 - cannot be gced */
#define BE_STATIC       (1 << 7)                    /* 128 */

/* values for bproto.varg */
#define BE_VA_VARARG            (1 << 0)    /* function has variable number of arguments */
#define BE_VA_METHOD            (1 << 1)    /* function is a method (this is only a hint) */
#define BE_VA_STATICMETHOD      (1 << 2)    /* the function is a static method and has the class as implicit '_class' variable */
#define BE_VA_SHARED_KTAB       (1 << 3)    /* the funciton has a shared consolidated ktab */
#define BE_VA_NOCOMPACT         (1 << 4)    /* the funciton has a shared consolidated ktab */
#define array_count(a)   (sizeof(a) / sizeof((a)[0]))

#define bcommon_header          \
    struct bgcobject *next;     \
    bbyte type;                 \
    bbyte marked

#define bstring_header          \
    bcommon_header;             \
    bbyte extra;                \
    bbyte slen

typedef struct bgcobject {
    bcommon_header;
} bgcobject;

typedef struct bclosure bclosure;
typedef struct bntvclos bntvclos;
typedef struct bclass bclass;
typedef struct binstance binstance;
typedef struct binstance_arg3 binstance_arg3;
typedef struct blist blist;
typedef struct bmap bmap;
typedef struct bupval bupval;

typedef uint32_t binstruction;

typedef struct bstring {
    bstring_header;
} bstring;

/* the definition of the vector and stack data structures.
 * in fact, the stack is implemented by vector. */
typedef struct bvector {
    int capacity;   /* the number of elements that the vector can store */
    int size;       /* the size of each element (bytes) */
    int count;      /* number of elements of the vector */
    void *data;     /* the data block pointer, if vector is empty,
                       it will point to the first element */
    void *end;      /* pointer to the last element, if the vector is empty,
                       the end pointer will be smaller than the data pointer */
} bvector, bstack;

/* berry value data union, a berry value is always described
 * by the data structure contained in the bvaldata union. */
union bvaldata {
    bbool b;        /* boolean */
    breal r;        /* real number */
    bint i;         /* integer number */
    void *p;        /* object pointer */
    const void *c;  /* const object pointer */
    bstring *s;     /* string pointer */
    bgcobject *gc;  /* GC object */
    bntvfunc nf;    /* native C function */
#ifdef __cplusplus
    BE_CONSTEXPR bvaldata(bbool v) : b(v) {}
    BE_CONSTEXPR bvaldata(breal v) : r(v) {}
    BE_CONSTEXPR bvaldata(bint v) : i(v) {}
    BE_CONSTEXPR bvaldata(void *v) : p(v) {}
    BE_CONSTEXPR bvaldata(const void *v) : c(v) {}
    BE_CONSTEXPR bvaldata(bntvfunc v) : nf(v) {}
#endif
};

/* berry value. for simple types, the value of the data is stored,
 * while the complex type stores a reference to the data. */
typedef struct bvalue {
    union bvaldata v; /* the value data */
    int type;         /* the value type */
} bvalue;

typedef struct {
#if BE_DEBUG_VAR_INFO
    bstring *name;
#endif
    bbyte instack;
    bbyte idx;
} bupvaldesc;

typedef struct {
#if BE_DEBUG_RUNTIME_INFO > 1
    uint16_t linenumber;
    uint16_t endpc;
#else
    int linenumber;
    int endpc;
#endif
} blineinfo;

typedef struct {
    bstring *name;
#if BE_DEBUG_RUNTIME_INFO > 1
    uint16_t beginpc;
    uint16_t endpc;
#else
    int beginpc;
    int endpc;
#endif
} bvarinfo;

typedef struct bproto {
    bcommon_header;
    bbyte nstack; /* number of stack size by this function */
    bbyte nupvals; /* upvalue count */
    bbyte argc; /* argument count */
    bbyte varg; /* variable argument position + 1 */
    int16_t codesize; /* code size */
    int16_t nconst; /* constants count */
    int16_t nproto; /* proto count */
    bgcobject *gray; /* for gc gray list */
    bupvaldesc *upvals;
#if BE_USE_COMPACT_KTAB
    /* compact constant table: structure-of-arrays, the `type` byte is split
     * out of the `bvalue` to avoid its 3 bytes of padding on 32-bit targets.
     * `kval`/`ktype` are parallel arrays of `nconst` entries. For runtime
     * protos both arrays live in a single allocation (kval first, ktype
     * right after); for solidified protos they are two `const` flash arrays.
     * Declared non-const (like the legacy `ktab`) so flash macros cast and
     * the rare runtime mutations (bytecode loader, ktab sharing) compile. */
    union bvaldata *kval; /* constants payload words */
    bbyte *ktype;         /* constants type bytes (parallel to kval) */
#else
    bvalue *ktab; /* constants table */
#endif
    struct bproto **ptab; /* proto table */
    binstruction *code; /* instructions sequence */
    bstring *name; /* function name */
#if BE_DEBUG_SOURCE_FILE
    bstring *source; /* source file name */
#endif
#if BE_DEBUG_RUNTIME_INFO /* debug information */
    blineinfo *lineinfo;
    int nlineinfo;
#endif
#if BE_DEBUG_VAR_INFO
    bvarinfo *varinfo;
    int nvarinfo;
#endif
} bproto;

/* berry closure */
struct bclosure {
    bcommon_header;
    bbyte nupvals;
    bgcobject *gray; /* for gc gray list */
    bproto *proto;
    bupval *upvals[1];
};

/* C native function or closure */
struct bntvclos {
    bcommon_header;
    bbyte nupvals;
    bgcobject *gray; /* for gc gray list */
    bntvfunc f;
};

/* common object */
typedef struct {
    bcommon_header;
    void *data;
    bntvfunc destroy;
} bcommomobj;

struct blexer;
typedef const char* (*breader)(struct blexer*, void*, size_t*);

#define cast(_T, _v)            ((_T)(_v))
#define cast_int(_v)            cast(int, _v)
#define cast_bool(_v)           cast(bbool, _v)
#define basetype(_t)            ((_t) & 0x1F)

#define var_type(_v)            ((_v)->type)
#define var_basetype(_v)        basetype((_v)->type)
#define var_primetype(_v)       (var_type(_v) & ~BE_STATIC)
#define var_isstatic(_v)        ((var_type(_v) & BE_STATIC) == BE_STATIC)
#define var_istype(_v, _t)      (var_primetype(_v) == _t)
#define var_settype(_v, _t)     ((_v)->type = _t)
#define var_markstatic(_v)      var_settype(_v, var_type(_v) | BE_STATIC)
#define var_clearstatic(_v)     var_settype(_v, var_type(_v) & ~BE_STATIC)
#define var_setobj(_v, _t, _o)  { (_v)->v.p = _o; var_settype(_v, _t); }

#define var_isnil(_v)           var_istype(_v, BE_NIL)
#define var_isbool(_v)          var_istype(_v, BE_BOOL)
#define var_isint(_v)           var_istype(_v, BE_INT)
#define var_isreal(_v)          var_istype(_v, BE_REAL)
#define var_isstr(_v)           var_istype(_v, BE_STRING)
#define var_isclosure(_v)       var_istype(_v, BE_CLOSURE)
#define var_isntvclos(_v)       var_istype(_v, BE_NTVCLOS)
#define var_isntvfunc(_v)       var_istype(_v, BE_NTVFUNC)
#define var_isfunction(_v)      (var_basetype(_v) == BE_FUNCTION)
#define var_isproto(_v)         var_istype(_v, BE_PROTO)
#define var_isclass(_v)         var_istype(_v, BE_CLASS)
#define var_isinstance(_v)      var_istype(_v, BE_INSTANCE)
#define var_islist(_v)          var_istype(_v, BE_LIST)
#define var_ismap(_v)           var_istype(_v, BE_MAP)
#define var_ismodule(_v)        var_istype(_v, BE_MODULE)
#define var_isindex(_v)         var_istype(_v, BE_INDEX)
#define var_iscomptr(_v)        var_istype(_v, BE_COMPTR)
#define var_isnumber(_v)        (var_isint(_v) || var_isreal(_v))

#define var_setnil(_v)          var_settype(_v, BE_NIL)
#define var_setval(_v, _s)      (*(_v) = *(_s))
#define var_setbool(_v, _b)     { var_settype(_v, BE_BOOL); (_v)->v.b = (bbool)(_b); }
#define var_setint(_v, _i)      { var_settype(_v, BE_INT); (_v)->v.i = (_i); }
#define var_setreal(_v, _r)     { var_settype(_v, BE_REAL); (_v)->v.r = (_r); }
#define var_setstr(_v, _s)      var_setobj(_v, BE_STRING, _s)
#define var_setinstance(_v, _o) var_setobj(_v, BE_INSTANCE, _o)
#define var_setclass(_v, _o)    var_setobj(_v, BE_CLASS, _o)
#define var_setclosure(_v, _o)  var_setobj(_v, BE_CLOSURE, _o)
#define var_setntvclos(_v, _o)  var_setobj(_v, BE_NTVCLOS, _o)
#define var_setntvfunc(_v, _o)  { (_v)->v.nf = (_o); var_settype(_v, BE_NTVFUNC); }
#define var_setlist(_v, _o)     var_setobj(_v, BE_LIST, _o)
#define var_setmap(_v, _o)      var_setobj(_v, BE_MAP, _o)
#define var_setmodule(_v, _o)   var_setobj(_v, BE_MODULE, _o)
#define var_setindex(_v, _i)    { var_settype(_v, BE_INDEX); (_v)->v.i = (_i); }
#define var_setproto(_v, _o)    var_setobj(_v, BE_PROTO, _o)
#define var_setcomptr(_v, _o)   var_setobj(_v, BE_COMPTR, _o)

#define var_tobool(_v)          ((_v)->v.b)
#define var_toint(_v)           ((_v)->v.i)
#define var_toreal(_v)          ((_v)->v.r)
#define var_tostr(_v)           ((_v)->v.s)
#define var_togc(_v)            ((_v)->v.gc)
#define var_toobj(_v)           ((_v)->v.p)
#define var_tontvfunc(_v)       ((_v)->v.nf)
#define var_toidx(_v)           cast_int(var_toint(_v))

/* Generic read accessors for a proto's constant table. They work the same
 * whether the build uses the compact (structure-of-arrays) representation or
 * the legacy `bvalue[]`. `proto_const_get` materializes constant `_idx` of
 * proto `_pr` into the `bvalue` lvalue `_dst`. `proto_const_type` returns the
 * type byte. These are used on the cold paths (GC mark, bytecode save,
 * solidify). The VM hot path inlines the equivalent logic directly. */
#if BE_USE_COMPACT_KTAB
#define proto_const_get(_pr, _idx, _dst) do {       \
        (_dst).v = (_pr)->kval[_idx];               \
        (_dst).type = (_pr)->ktype[_idx];           \
    } while (0)
#define proto_const_type(_pr, _idx)     ((_pr)->ktype[_idx])
#else
#define proto_const_get(_pr, _idx, _dst) ((_dst) = (_pr)->ktab[_idx])
#define proto_const_type(_pr, _idx)     ((_pr)->ktab[_idx].type)
#endif

const char* be_vtype2str(bvalue *v);
bstring* be_vtype2bstring(bvalue *v);
bvalue* be_indexof(bvm *vm, int idx);
void be_commonobj_delete(bvm *vm, bgcobject *obj);
int be_commonobj_destroy_generic(bvm* vm);

#endif
