/ Documentation
DEMO | DOWNLOAD | DEPLOY | SEARCH
Login
/* jsi.h : External API header file for Jsi. */
#ifndef __JSI_H__
#define __JSI_H__

#define JSI_VERSION_MAJOR   2
#define JSI_VERSION_MINOR   8
#define JSI_VERSION_RELEASE 38

#define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))

#ifndef JSI_EXTERN
#define JSI_EXTERN extern
#endif

#ifdef offsetof
#define Jsi_Offset(type, field) ((long) offsetof(type, field))
#else
#define Jsi_Offset(type, field) ((long) ((char *) &((type *) 0)->field))
#endif

#ifndef __GNUC__
#define __attribute__(X)
#endif

#ifndef __USE_XOPEN
#define __USE_XOPEN
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#ifdef __WIN32
#define __USE_MINGW_ANSI_STDIO 1
#endif

#include <stdbool.h>
#include <inttypes.h>
#include <stdint.h>
#include <time.h>
#include <sys/stat.h>
#include <stdio.h> 
#include <dirent.h>

/* --TYPEDEFS-- */
typedef int64_t Jsi_Wide;
typedef uint64_t Jsi_UWide;
typedef long double ldouble;
#ifdef JSI_USE_LONG_DOUBLE
typedef ldouble Jsi_Number;
#define JSI_NUMLMOD "L"
#else
typedef double Jsi_Number;
#define JSI_NUMLMOD
#endif
typedef double time_d;
typedef int64_t time_w;
typedef uint32_t Jsi_Sig; // Signature field

typedef unsigned int uint;
typedef unsigned char uchar;
typedef unsigned long ulong;
typedef unsigned short ushort;
#define JSI_NUMGFMT JSI_NUMLMOD "g"
#define JSI_NUMFFMT JSI_NUMLMOD "f"
#define JSI_NUMEFMT JSI_NUMLMOD "e"
/* -- */


/* --ENUMS-- */
typedef enum {
    /* Jsi Return Codes. */
    JSI_OK=0, JSI_ERROR=1, JSI_RETURN=2, JSI_BREAK=3,
    JSI_CONTINUE=4, JSI_SIGNAL=5, JSI_EXIT=6, JSI_EVAL=7,    
} Jsi_RC;

typedef enum {
    JSI_MAP_NONE, JSI_MAP_HASH, JSI_MAP_TREE, JSI_MAP_LIST /*, JSI_MAP_STACK */
} Jsi_Map_Type;

typedef enum {
    JSI_KEYS_STRING = 0,    // A string that gets stored in hash.
    JSI_KEYS_STRINGKEY = 1, // A pointer to strings in another hash such as Jsi_KeyLookup()
    JSI_KEYS_ONEWORD = 2,   // A pointer.
    JSI_KEYS_RESERVED = 3,  // Unused.
    JSI_KEYS_STRUCT_MINSIZE = 4 // Any number >= 4 is the number of bytes in a struct/key.
} Jsi_Key_Type;

typedef enum {
    JSI_OT_UNDEF,       /* Undefined */
    JSI_OT_BOOL,        /* Boolean object, use d.val */
    JSI_OT_NUMBER,      /* Number object, use d.num */
    JSI_OT_STRING,      /* String object, use d.str */
    JSI_OT_OBJECT,      /* Common object */
    JSI_OT_ARRAY,       /* NOT A REAL TYPE: is just an JSI_OT_OBJECT with array elements */
    JSI_OT_FUNCTION,    /* Function object, use d.fobj */
    JSI_OT_REGEXP,      /* RegExp object, use d.robj */
    JSI_OT_ITER,        /* Iter object, use d.iobj */
    JSI_OT_USEROBJ,     /* UserDefined object, use d.uobj */
    JSI_OT__MAX = JSI_OT_USEROBJ
} Jsi_otype;

typedef enum {          /* TYPE         CONSTRUCTOR JSI_VALUE-DATA  IMPLICIT-PROTOTYPE  */
    JSI_VT_UNDEF,       /* undefined    none        none            none                */
    JSI_VT_BOOL,        /* boolean      Boolean     d.val           none                */
    JSI_VT_NUMBER,      /* number       Number      d.num           Number.prototype    */
    JSI_VT_STRING,      /* string       String      d.str           String.prototype    */
    JSI_VT_OBJECT,      /* object       Jsi_Obj     d.obj           Jsi_Obj.prototype   */
    JSI_VT_NULL,        /* null         none        none            none                */
    JSI_VT_VARIABLE,    /* lvalue       none        d.lval          none                */
    JSI_VT__MAX = JSI_VT_VARIABLE
} Jsi_vtype;

typedef enum {
    JSI_TT_UNDEFINED= (1<<JSI_OT_UNDEF),    //  0x1
    JSI_TT_BOOLEAN  = (1<<JSI_OT_BOOL),     //  0x2
    JSI_TT_NUMBER   = (1<<JSI_OT_NUMBER),   //  0x4
    JSI_TT_STRING   = (1<<JSI_OT_STRING),   //  0x8
    JSI_TT_OBJECT   = (1<<JSI_OT_OBJECT),   //  0x10
    JSI_TT_ARRAY    = (1<<JSI_OT_ARRAY),    //  0x20
    JSI_TT_FUNCTION = (1<<JSI_OT_FUNCTION), //  0x40
    JSI_TT_REGEXP   = (1<<JSI_OT_REGEXP),   //  0x80
    JSI_TT_ITEROBJ  = (1<<JSI_OT_ITER),     //  0x100
    JSI_TT_USEROBJ  = (1<<JSI_OT_USEROBJ),  //  0x200
    JSI_TT_NULL     = (1<<(JSI_OT_USEROBJ+1)),//0x400
    JSI_TT_ANY      = (1<<(JSI_OT_USEROBJ+2)),//0x800
    JSI_TT_VOID     = (1<<(JSI_OT_USEROBJ+3)) //0x1000
} Jsi_ttype;

typedef enum {
    /* General flags. */
    JSI_NONE=0, JSI_NO_ERRMSG=1, 
    JSI_CMP_NOCASE=1, JSI_CMP_CHARSET_SCAN=2,
    JSI_CMP_EXACT=0x4,
    JSI_EVAL_ARGV0=0x1, JSI_EVAL_GLOBAL=0x2, JSI_EVAL_NOSKIPBANG=0x4, JSI_EVAL_AUTOINDEX=0x8,
    JSI_EVAL_RETURN         =0x10, // Return top of stack as result
    JSI_EVAL_ONCE           =0x20, // Source files only once.
    JSI_EVAL_ISMAIN         =0x40, // Set isMain to true.
    JSI_EVAL_EXISTS         =0x80, // Source if exists.
    JSI_EVAL_ERRIGNORE      =0x100,// Source ignores errors.

    /* Flags for Jsi_CmdProc */
    JSI_CALL_CONSTRUCTOR    =0x1,
    JSI_CALL_BUILTIN        =0x2,
    
    JSI_CMDSPEC_ISOBJ       = 0x1,
    JSI_CMDSPEC_PROTO       = 0x2,
    JSI_CMDSPEC_NONTHIS     = 0x4,
    JSI_CMDSPEC_SUBCMDS     = 0x8,      // Has sub-commands.
    
    JSI_CMD_HAS_ATTR        = 0x100,
    JSI_CMD_IS_CONSTRUCTOR  = 0x200,
    JSI_CMD_IS_OBJ          = 0x400,
    JSI_CMD_LOG_TEST        = 0x1000,
    JSI_CMD_LOG_DEBUG       = 0x2000,
    JSI_CMD_LOG_TRACE       = 0x4000,
    JSI_CMD_MASK            = 0xffff,
    
    JSI_OM_READONLY         = 0x01,     /* ecma read-only */
    JSI_OM_DONTENUM         = 0x02,     /* ecma emumerable */
    JSI_OM_DONTDEL          = 0x04,     /* ecma configurable */
    JSI_OM_INNERSHARED      = 0x08,
    JSI_OM_ISARRAYLIST      = 0x10,
    JSI_OM_ISSTRKEY         = 0x20,
    JSI_OM_UNUSED           = 0x40,
    JSI_OM_ISSTATIC         = 0x80,
    
    JSI_INTACCESS_READ      = 0x0,
    JSI_INTACCESS_WRITE     = 0x1,
    JSI_INTACCESS_NETWORK   = 0x2,
    JSI_INTACCESS_SETSSL    = 0x3,
    JSI_INTACCESS_MAININTERP= 0x4,
    
    JSI_LOG_BUG=0,   JSI_LOG_ERROR,   JSI_LOG_WARN,
    JSI_LOG_INFO,    JSI_LOG_UNUSED,  JSI_LOG_PARSE,
    JSI_LOG_TEST,    JSI_LOG_DEBUG,   JSI_LOG_TRACE,
    JSI__LOGLAST=JSI_LOG_TRACE,
    
    JSI_SORT_NOCASE = 0x1, JSI_SORT_DESCEND = 0x2, JSI_SORT_DICT = 0x4,
    
    JSI_NAME_FUNCTIONS = 0x1, JSI_NAME_DATA = 0x2,
    
    JSI_TREE_ORDER_IN=0, JSI_TREE_ORDER_PRE=0x10, JSI_TREE_ORDER_POST=0x20, // Jsi_TreeSearchFirst()
    JSI_TREE_ORDER_LEVEL=0x30, JSI_TREE_ORDER_MASK=0x30,
    JSI_TREE_SEARCH_KEY=0x10, // Use key even if NULL
    JSI_TREE_USERFLAG_MASK=0x7f,
    JSI_LIST_REVERSE=0x8, // Jsi_ListSearchFirst
    JSI_MUTEX_RECURSIVE=2,
    
    JSI_FS_NOCLOSE=0x1, JSI_FS_READONLY=0x2, JSI_FS_WRITEONLY=0x4, JSI_FS_APPEND=0x8,
    JSI_FS_COMPRESS=0x100,
    JSI_FSMODESIZE=15,
    JSI_FILE_TYPE_FILES=0x1, JSI_FILE_TYPE_DIRS=0x2,    JSI_FILE_TYPE_MOUNT=0x4,
    JSI_FILE_TYPE_LINK=0x8,  JSI_FILE_TYPE_PIPE=0x10,   JSI_FILE_TYPE_BLOCK=0x20,
    JSI_FILE_TYPE_CHAR=0x40, JSI_FILE_TYPE_SOCKET=0x80, JSI_FILE_TYPE_HIDDEN=0x100,
    
    JSI_OUTPUT_QUOTE = 0x1,
    JSI_OUTPUT_JSON = 0x2,
    JSI_OUTPUT_NEWLINES = 0x4,
    JSI_OUTPUT_STDERR = 0x8,
    JSI_JSON_STATIC_DEFAULT =100,
    JSI_JSON_STRICT   = 0x101, /* property names must be quoted. */
    JSI_STUBS_STRICT  = 0x1, JSI_STUBS_SIG = 0xdeadaa00, JSI_SIG_TYPEDEF,
    JSI_SIG_OPTS = 0xdeadab00,
    JSI_SIG_OPTS_STRUCT, JSI_SIG_OPTS_ENUM, JSI_SIG_OPTS_VARDEF, JSI_SIG_OPTS_FIELD,
    JSI_SIG_OPTS_USER1=0xdeadab20,

    JSI_EVENT_TIMER=0, JSI_EVENT_SIGNAL=1, JSI_EVENT_ALWAYS=2,
    JSI_ZIP_MAIN=0x1,  JSI_ZIP_INDEX=0x2,

    JSI_DBI_READONLY     =0x0001, /* Db is created readonly */
    JSI_DBI_NOCREATE     =0x0002, /* Db must already exist. */
    JSI_DBI_NO_MUTEX     =0x0004, /* Disable mutex. */
    JSI_DBI_FULL_MUTEX   =0x0008, /* Use full mutex. */
    
    JSI_MAX_NUMBER_STRING=50,
    JSI_BUFSIZ=8192

} Jsi_Enums; /* Debugging is easier with enums than #define. */

/* -- */


/* --STRUCTS-- */

typedef struct Jsi_Interp Jsi_Interp;
typedef struct Jsi_Obj Jsi_Obj;
typedef struct Jsi_Value Jsi_Value;
typedef struct Jsi_Func Jsi_Func;
typedef struct Jsi_IterObj Jsi_IterObj;
typedef struct Jsi_FuncObj Jsi_FuncObj;
typedef struct Jsi_UserObjReg Jsi_UserObjReg;
typedef struct Jsi_UserObj Jsi_UserObj;
typedef struct Jsi_HashEntry Jsi_HashEntry;
typedef struct Jsi_Hash Jsi_Hash;
typedef struct Jsi_HashSearch Jsi_HashSearch;
typedef struct Jsi_TreeEntry Jsi_TreeEntry;
typedef struct Jsi_Tree Jsi_Tree;
typedef struct Jsi_TreeSearch Jsi_TreeSearch;
typedef struct Jsi_List Jsi_List;
typedef struct Jsi_ListEntry Jsi_ListEntry;
typedef struct Jsi_ListSearch Jsi_ListSearch;
typedef struct Jsi_Map Jsi_Map;
typedef struct Jsi_MapEntry Jsi_MapEntry;
typedef struct Jsi_MapSearch Jsi_MapSearch;
typedef struct Jsi_Regex_ Jsi_Regex;
typedef struct Jsi_Db Jsi_Db;
typedef struct Jsi_DbBinds Jsi_DbBinds;
typedef struct Jsi_Mutex Jsi_Mutex;
typedef struct Jsi_ScopeStrs Jsi_ScopeStrs;
typedef struct Jsi_OpCodes Jsi_OpCodes;
typedef struct Jsi_Chan* Jsi_Channel;
typedef struct Jsi_CS_Ctx Jsi_CS_Ctx;
typedef struct Jsi_OptionSpec Jsi_OptionSpec;

typedef struct Jsi_OptionSpec Jsi_StructSpec;
typedef struct Jsi_OptionSpec Jsi_FieldSpec;
typedef struct Jsi_OptionSpec Jsi_EnumSpec;
typedef struct Jsi_OptionSpec Jsi_VarSpec;

typedef Jsi_RC (Jsi_InitProc)(Jsi_Interp *interp, int release); // When release>1, the main interp is exiting.
typedef Jsi_RC (Jsi_DeleteProc)(Jsi_Interp *interp, void *data);
typedef Jsi_RC (Jsi_EventHandlerProc)(Jsi_Interp *interp, void *data);
typedef Jsi_RC (Jsi_ValueHandlerProc)(Jsi_Interp *interp, Jsi_Value *v, struct Jsi_OptionSpec* spec, void *record);
typedef void (Jsi_DeleteVoidProc)(void *data);
typedef Jsi_RC (Jsi_csgset)(Jsi_Interp *interp, void *data, Jsi_Wide *s, Jsi_OptionSpec *spec, int idx, bool isSet);

/* -- */


/* --INTERP-- */

/* Options and flags for Jsi_InterpNew/Jsi_Main */
typedef struct {
    int argc;                   // Arguments from main().
    char **argv;                // ...
    Jsi_InitProc* initProc;     // Initialization proc
    uint mem_debug:2;           // Memory debug level;
    bool no_interactive:1;      // Jsi_Main: does not default to interactive mode when no script arg given.
    bool auto_delete:1;         // Jsi_Main: auto delete interp upon return.
    bool no_exit:1;             // Do not exit, even on error.
    uint reserved:11;           // Reserved for future use.
    int exitCode:16;            // Call exit with this code.
    Jsi_Interp* interp;         // Jsi_InterpNew sets this to let Jsi_Main use this interp.
    void *reserved2[8];         // Reserved for future
} Jsi_InterpOpts;

JSI_EXTERN Jsi_Interp* Jsi_InterpNew(Jsi_InterpOpts *opts); /*STUB = 1*/
JSI_EXTERN void Jsi_InterpDelete( Jsi_Interp* interp); /*STUB = 2*/
JSI_EXTERN void Jsi_InterpOnDelete(Jsi_Interp *interp, Jsi_DeleteProc *freeProc, void *ptr);  /*STUB = 3*/
JSI_EXTERN Jsi_RC Jsi_Interactive(Jsi_Interp* interp, int flags); /*STUB = 4*/
JSI_EXTERN bool Jsi_InterpGone( Jsi_Interp* interp); /*STUB = 5*/
JSI_EXTERN Jsi_Value* Jsi_InterpResult(Jsi_Interp *interp); /*STUB = 6*/
JSI_EXTERN const char* Jsi_InterpLastError(Jsi_Interp *interp, const char **errFilePtr, int *errLinePtr); /*STUB = 7*/
JSI_EXTERN void* Jsi_InterpGetData(Jsi_Interp *interp, const char *key, Jsi_DeleteProc **proc); /*STUB = 8*/
JSI_EXTERN void Jsi_InterpSetData(Jsi_Interp *interp, const char *key, void *data, Jsi_DeleteProc *proc); /*STUB = 9*/
JSI_EXTERN void Jsi_InterpFreeData(Jsi_Interp *interp, const char *key); /*STUB = 10*/
JSI_EXTERN bool Jsi_InterpSafe(Jsi_Interp *interp); /*STUB = 11*/
JSI_EXTERN Jsi_RC Jsi_InterpAccess(Jsi_Interp *interp, Jsi_Value* resource, int aflag); /*STUB = 12*/
JSI_EXTERN Jsi_Interp* Jsi_Main(Jsi_InterpOpts *opts); /*STUB = 13*/
/* -- */


/* --MEMORY-- */
JSI_EXTERN void* Jsi_Malloc(uint size); /*STUB = 14*/
JSI_EXTERN void* Jsi_Calloc(uint n, uint size); /*STUB = 15*/
JSI_EXTERN void* Jsi_Realloc(void *m, uint size); /*STUB = 16*/
JSI_EXTERN void  Jsi_Free(void *m); /*STUB = 17*/
JSI_EXTERN int Jsi_ObjIncrRefCount(Jsi_Interp* interp, Jsi_Obj *obj); /*STUB = 18*/
JSI_EXTERN int Jsi_ObjDecrRefCount(Jsi_Interp* interp, Jsi_Obj *obj); /*STUB = 19*/
JSI_EXTERN int Jsi_IncrRefCount(Jsi_Interp* interp, Jsi_Value *v); /*STUB = 20*/
JSI_EXTERN int Jsi_DecrRefCount(Jsi_Interp* interp, Jsi_Value *v); /*STUB = 21*/
JSI_EXTERN bool Jsi_IsShared(Jsi_Interp* interp, Jsi_Value *v); /*STUB = 22*/
JSI_EXTERN Jsi_RC Jsi_DeleteData(Jsi_Interp* interp, void *m); /*STUB = 23*/
/* -- */


/* --STRINGS-- */
JSI_EXTERN uint Jsi_Strlen(const char *str); /*STUB = 24*/
JSI_EXTERN uint Jsi_StrlenSet(const char *str, uint len); /*STUB = 25*/
JSI_EXTERN int Jsi_Strcmp(const char *str1, const char *str2); /*STUB = 26*/
JSI_EXTERN int Jsi_Strncmp(const char *str1, const char *str2, int n); /*STUB = 27*/
JSI_EXTERN int Jsi_Strncasecmp(const char *str1, const char *str2, int n); /*STUB = 28*/
JSI_EXTERN int Jsi_StrcmpDict(const char *str1, const char *str2, int nocase, int dict); /*STUB = 29*/
JSI_EXTERN char* Jsi_Strcpy(char *dst, const char *src); /*STUB = 30*/
JSI_EXTERN char* Jsi_Strncpy(char *dst, const char *src, int len); /*STUB = 31*/
JSI_EXTERN char* Jsi_Strdup(const char *n); /*STUB = 32*/
JSI_EXTERN char* Jsi_StrdupLen(const char *str, int len); /*STUB = 407*/
JSI_EXTERN char* Jsi_Strrchr(const char *str, int c); /*STUB = 33*/
JSI_EXTERN char* Jsi_Strstr(const char *str, const char *sub); /*STUB = 34*/
JSI_EXTERN char* Jsi_Strrstr(const char *str, const char *sub); /*STUB = 233*/ 
JSI_EXTERN int Jsi_ObjArraySizer(Jsi_Interp *interp, Jsi_Obj *obj, uint n); /*STUB = 35*/
JSI_EXTERN char* Jsi_Strchr(const char *str, int c); /*STUB = 36*/
JSI_EXTERN int Jsi_Strpos(const char *str, int start, const char *nid, int nocase); /*STUB = 37*/
JSI_EXTERN int Jsi_Strrpos(const char *str, int start, const char *nid, int nocase); /*STUB = 38*/
#define Jsi_Stzcpy(buf,src) Jsi_Strncpy(buf, src, sizeof(buf))

/* Dynamic strings. */
#ifndef JSI_DSTRING_STATIC_SIZE
#define JSI_DSTRING_STATIC_SIZE 200
#endif

typedef struct {
#define JSI_DSTRING_DECL_FIELDS(siz) \
    const char *strA; /* Allocated string, or = {"string"}.*/ \
    uint len;       /* Length of string. */ \
    uint spaceAvl;  /* Amount of space available or allocated. */ \
    uint staticSize;/* The sizeof "Str", or 0 if used "= {}" */ \
    char Str[siz];  /* Static string */
    JSI_DSTRING_DECL_FIELDS(JSI_DSTRING_STATIC_SIZE)
} Jsi_DString;

/* Declares a custom Jsi_DString* variable with other than default size... */
#define JSI_DSTRING_VAR(namPtr, siz) \
    struct { JSI_DSTRING_DECL_FIELDS(siz) } _STATIC_##namPtr; \
    Jsi_DString *namPtr = (Jsi_DString *)&_STATIC_##namPtr; \
    namPtr->staticSize = siz; namPtr->strA=0; \
    namPtr->Str[0] = 0; namPtr->spaceAvl = namPtr->len = 0

JSI_EXTERN char*   Jsi_DSAppendLen(Jsi_DString *dsPtr,const char *bytes, int length);  /*STUB = 39*/
JSI_EXTERN char*   Jsi_DSAppend(Jsi_DString *dsPtr, const char *str, ...)  /*STUB = 40*/  __attribute__((sentinel));
JSI_EXTERN void    Jsi_DSFree(Jsi_DString *dsPtr);  /*STUB = 41*/
JSI_EXTERN char*   Jsi_DSFreeDup(Jsi_DString *dsPtr);  /*STUB = 42*/
JSI_EXTERN void    Jsi_DSInit(Jsi_DString *dsPtr);  /*STUB = 43*/
JSI_EXTERN uint    Jsi_DSLength(Jsi_DString *dsPtr);  /*STUB = 44*/
JSI_EXTERN char*   Jsi_DSPrintf(Jsi_DString *dsPtr, const char *fmt, ...)  /*STUB = 45*/ __attribute__((format (printf,2,3)));
JSI_EXTERN char*   Jsi_DSSet(Jsi_DString *dsPtr, const char *str);  /*STUB = 46*/
JSI_EXTERN uint    Jsi_DSSetLength(Jsi_DString *dsPtr, uint length);  /*STUB = 47*/
JSI_EXTERN char*   Jsi_DSValue(Jsi_DString *dsPtr);  /*STUB = 48*/
/* -- */


/* --FUNC/VAR/CMD-- */
typedef void (Jsi_DelCmdProc)(Jsi_Interp *interp, void *privData);
typedef Jsi_RC (Jsi_CmdProc)(Jsi_Interp *interp, Jsi_Value *args, 
    Jsi_Value *_this, Jsi_Value **ret, Jsi_Func *funcPtr);
#define Jsi_CmdProcDecl(name,...) Jsi_RC name(Jsi_Interp *interp, Jsi_Value *args, \
    Jsi_Value *_this, Jsi_Value **ret, Jsi_Func *funcPtr, ##__VA_ARGS__)

typedef struct Jsi_CmdSpec {
    const char *name;       /* Cmd name */
    Jsi_CmdProc *proc;      /* Command handler */
    int minArgs;
    int maxArgs;            /* Max args or -1 */
    const char *argStr;     /* Argument description */
    const char *help;       /* Short help string. */
    uint retType;           /* Return type(s) or'ed Jsi_otype. */
    int flags;              /* JSI_CMD_* flags. */
    const char *info;       /* Detailed description. Use JSI_DETAIL macro. */
    Jsi_OptionSpec *opts;   /* Options for arg, default is first. */
    Jsi_DelCmdProc *delProc;/* Callback to handle command delete. */
    void *reserved[4];      /* Reserved for internal use. */
} Jsi_CmdSpec;

typedef struct {
    bool Test;
    bool Debug;
    bool Trace;
    int traceCall;
    bool coverage;
    bool profile;
} Jsi_ModuleConf;

typedef struct {
    struct Jsi_OptionSpec *spec;
    void *data;
    Jsi_CmdSpec *cmdSpec;
    Jsi_Value *info;
    void *reserved[3]; // Reserved for future use.
    Jsi_ModuleConf modConf;
    void *reserved2[3]; // Reserved for future use.
} Jsi_PkgOpts;

typedef struct {
    char *str;
    int32_t len;
    uint32_t flags;
} Jsi_String;

JSI_EXTERN Jsi_Value* Jsi_CommandCreate(Jsi_Interp *interp, const char *name, Jsi_CmdProc *cmdProc, void *privData); /*STUB = 49*/
JSI_EXTERN Jsi_Value* Jsi_CommandCreateSpecs(Jsi_Interp *interp, const char *name, Jsi_CmdSpec *cmdSpecs, void *privData, int flags); /*STUB = 50*/
JSI_EXTERN void* Jsi_CommandNewObj(Jsi_Interp *interp, const char *name, const char *arg1, const char *opts, const char *var);  /*STUB = 51*/
JSI_EXTERN Jsi_RC Jsi_CommandInvokeJSON(Jsi_Interp *interp, const char *cmd, const char *json, Jsi_Value **ret); /*STUB = 52*/
JSI_EXTERN Jsi_RC Jsi_CommandInvoke(Jsi_Interp *interp, const char *cmdstr, Jsi_Value *args, Jsi_Value **ret); /*STUB = 53*/
JSI_EXTERN Jsi_RC Jsi_CommandDelete(Jsi_Interp *interp, const char *name); /*STUB = 54*/
JSI_EXTERN Jsi_CmdSpec* Jsi_FunctionGetSpecs(Jsi_Func *funcPtr); /*STUB = 55*/
JSI_EXTERN bool Jsi_FunctionIsConstructor(Jsi_Func *funcPtr); /*STUB = 56*/
JSI_EXTERN bool Jsi_FunctionReturnIgnored(Jsi_Interp *interp, Jsi_Func *funcPtr); /*STUB = 57*/
JSI_EXTERN void* Jsi_FunctionPrivData(Jsi_Func *funcPtr); /*STUB = 58*/
JSI_EXTERN Jsi_RC Jsi_FunctionArguments(Jsi_Interp *interp, Jsi_Value *func, int *argcPtr); /*STUB = 59*/
JSI_EXTERN Jsi_RC Jsi_FunctionApply(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this, Jsi_Value **ret); /*STUB = 60*/
JSI_EXTERN Jsi_RC Jsi_FunctionInvoke(Jsi_Interp *interp, Jsi_Value *tocall, Jsi_Value *args, Jsi_Value **ret, Jsi_Value *_this); /*STUB = 61*/
JSI_EXTERN Jsi_RC Jsi_FunctionInvokeJSON(Jsi_Interp *interp, Jsi_Value *tocall, const char *json, Jsi_Value **ret); /*STUB = 62*/
JSI_EXTERN int Jsi_FunctionInvokeBool(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *arg); /*STUB = 63*/
JSI_EXTERN Jsi_RC Jsi_FunctionInvokeString(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *arg, Jsi_DString *dStr); /*STUB = 64*/
JSI_EXTERN Jsi_Value* Jsi_VarLookup(Jsi_Interp *interp, const char *varname); /*STUB = 65*/
JSI_EXTERN Jsi_Value* Jsi_NameLookup(Jsi_Interp *interp, const char *varname); /*STUB = 66*/
JSI_EXTERN Jsi_Value* Jsi_NameLookup2(Jsi_Interp *interp, const char *name, const char *inObj); /*STUB = 67*/
JSI_EXTERN Jsi_RC Jsi_PkgProvideEx(Jsi_Interp *interp, const char *name, Jsi_Number version, Jsi_InitProc *initProc, Jsi_PkgOpts* popts); /*STUB = 68*/
JSI_EXTERN Jsi_Number Jsi_PkgRequireEx(Jsi_Interp *interp, const char *name, Jsi_Number version, Jsi_PkgOpts **poptsPtr); /*STUB = 69*/
JSI_EXTERN Jsi_Number Jsi_PkgVersion(Jsi_Interp *interp, const char *name, const char **filePtr); /*STUB = 70*/
#define Jsi_PkgRequire(i,n,v) Jsi_PkgRequireEx(i,n,v,NULL)
#define Jsi_PkgProvide(i,n,v,p) Jsi_PkgProvideEx(i,n,v,p,NULL)
/* -- */

/* UTF-8 and Unicode */
typedef int32_t Jsi_UniChar;
JSI_EXTERN uint Jsi_NumUtfBytes(char c); /*STUB = 71*/
JSI_EXTERN uint Jsi_NumUtfChars(const char *utf, int length); /*STUB = 72*/
JSI_EXTERN uint Jsi_UtfGetIndex(const char *utf, int index, char outbuf[5]); /*STUB = 73*/
JSI_EXTERN const char* Jsi_UtfAtIndex(const char *utf, int index); /*STUB = 74*/
JSI_EXTERN uint Jsi_UniCharToUtf(Jsi_UniChar uc, char *dest); /*STUB = 75*/
JSI_EXTERN uint Jsi_UtfToUniChar(const char *utf, Jsi_UniChar *ch); /*STUB = 76*/
JSI_EXTERN uint Jsi_UtfToUniCharCase(const char *utf, Jsi_UniChar *ch, int upper); /*STUB = 77*/
JSI_EXTERN uint Jsi_UtfDecode(const char *str, char* oututf); /*STUB = 78*/
JSI_EXTERN uint Jsi_UtfEncode(const char *utf, char *outstr); /*STUB = 79*/
JSI_EXTERN char* Jsi_UtfSubstr(const char *str, int n, int len, Jsi_DString *dStr); /*STUB = 80*/
JSI_EXTERN int Jsi_UtfIndexToOffset(const char *utf, int index); /*STUB = 81*/
/* -- */


/* --OBJECT-- */
JSI_EXTERN Jsi_Obj* Jsi_ObjNew(Jsi_Interp* interp); /*STUB = 82*/
JSI_EXTERN Jsi_Obj* Jsi_ObjNewType(Jsi_Interp* interp, Jsi_otype type); /*STUB = 83*/
JSI_EXTERN void Jsi_ObjFree(Jsi_Interp* interp, Jsi_Obj *obj); /*STUB = 84*/
JSI_EXTERN Jsi_Obj* Jsi_ObjNewObj(Jsi_Interp *interp, Jsi_Value **items, int count); /*STUB = 85*/
JSI_EXTERN Jsi_Obj* Jsi_ObjNewArray(Jsi_Interp *interp, Jsi_Value **items, int count, int copy); /*STUB = 86*/

JSI_EXTERN bool      Jsi_ObjIsArray(Jsi_Interp *interp, Jsi_Obj *o); /*STUB = 87*/
JSI_EXTERN void     Jsi_ObjSetLength(Jsi_Interp *interp, Jsi_Obj *obj, uint len); /*STUB = 88*/
JSI_EXTERN int      Jsi_ObjGetLength(Jsi_Interp *interp, Jsi_Obj *obj); /*STUB = 89*/
JSI_EXTERN const char* Jsi_ObjTypeStr(Jsi_Interp *interp, Jsi_Obj *obj); /*STUB = 90*/
JSI_EXTERN Jsi_otype Jsi_ObjTypeGet(Jsi_Obj *obj); /*STUB = 91*/
JSI_EXTERN void     Jsi_ObjListifyArray(Jsi_Interp *interp, Jsi_Obj *obj); /*STUB = 92*/
JSI_EXTERN Jsi_RC      Jsi_ObjArraySet(Jsi_Interp *interp, Jsi_Obj *obj, Jsi_Value *value, int arrayindex); /*STUB = 93*/
JSI_EXTERN Jsi_RC      Jsi_ObjArrayAdd(Jsi_Interp *interp, Jsi_Obj *o, Jsi_Value *v); /*STUB = 94*/
JSI_EXTERN Jsi_TreeEntry* Jsi_ObjInsert(Jsi_Interp *interp, Jsi_Obj *obj, const char *key, Jsi_Value *nv, int flags); /*STUB = 95*/
JSI_EXTERN void    Jsi_ObjFromDS(Jsi_DString *dsPtr, Jsi_Obj *obj);  /*STUB = 96*/
/* -- */


/* --VALUE-- */
JSI_EXTERN Jsi_Value* Jsi_ValueNew(Jsi_Interp *interp); /*STUB = 97*/
JSI_EXTERN Jsi_Value* Jsi_ValueNew1(Jsi_Interp *interp); /*STUB = 98*/
JSI_EXTERN void Jsi_ValueFree(Jsi_Interp *interp, Jsi_Value* v); /*STUB = 99*/

JSI_EXTERN Jsi_Value* Jsi_ValueNewNull(Jsi_Interp *interp); /*STUB = 100*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewBoolean(Jsi_Interp *interp, int bval); /*STUB = 101*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewNumber(Jsi_Interp *interp, Jsi_Number n); /*STUB = 102*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewBlob(Jsi_Interp *interp, uchar *s, uint len); /*STUB = 103*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewString(Jsi_Interp *interp, const char *s, int len); /*STUB = 104*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewStringKey(Jsi_Interp *interp, const char *s); /*STUB = 105*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewStringConst(Jsi_Interp *interp, const char *s, int len); /*STUB = 409*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewStringDup(Jsi_Interp *interp, const char *s); /*STUB = 106*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewArray(Jsi_Interp *interp, const char **items, int count); /*STUB = 107*/
JSI_EXTERN Jsi_Value* Jsi_ValueNewObj(Jsi_Interp *interp, Jsi_Obj *o) ; /*STUB = 108*/
#define Jsi_ValueNewBlobString(interp, s) Jsi_ValueNewBlob(interp, (uchar*)s, Jsi_Strlen(s))
#define Jsi_ValueNewArrayObj(interp, items, count, copy) Jsi_ValueNewObj(interp, Jsi_ObjNewArray(interp, items, count, copy))

JSI_EXTERN Jsi_RC Jsi_GetStringFromValue(Jsi_Interp* interp, Jsi_Value *value, const char **s); /*STUB = 109*/
JSI_EXTERN Jsi_RC Jsi_GetNumberFromValue(Jsi_Interp* interp, Jsi_Value *value, Jsi_Number *n); /*STUB = 110*/
JSI_EXTERN Jsi_RC Jsi_GetBoolFromValue(Jsi_Interp* interp, Jsi_Value *value, bool *n); /*STUB = 111*/
JSI_EXTERN Jsi_RC Jsi_GetIntFromValue(Jsi_Interp* interp, Jsi_Value *value, int *n); /*STUB = 112*/
JSI_EXTERN Jsi_RC Jsi_GetLongFromValue(Jsi_Interp* interp, Jsi_Value *value, long *n); /*STUB = 113*/
JSI_EXTERN Jsi_RC Jsi_GetWideFromValue(Jsi_Interp* interp, Jsi_Value *value, Jsi_Wide *n); /*STUB = 114*/
JSI_EXTERN Jsi_RC Jsi_GetDoubleFromValue(Jsi_Interp* interp, Jsi_Value *value, Jsi_Number *n); /*STUB = 115*/
JSI_EXTERN Jsi_RC Jsi_GetIntFromValueBase(Jsi_Interp* interp, Jsi_Value *value, int *n, int base, int flags); /*STUB = 116*/
JSI_EXTERN Jsi_RC Jsi_ValueGetBoolean(Jsi_Interp *interp, Jsi_Value *pv, bool *val); /*STUB = 117*/
JSI_EXTERN Jsi_RC Jsi_ValueGetNumber(Jsi_Interp *interp, Jsi_Value *pv, Jsi_Number *val); /*STUB = 118*/

JSI_EXTERN bool Jsi_ValueIsType(Jsi_Interp *interp, Jsi_Value *pv, Jsi_vtype vtype); /*STUB = 119*/
JSI_EXTERN bool Jsi_ValueIsObjType(Jsi_Interp *interp, Jsi_Value *v, Jsi_otype otype); /*STUB = 120*/
JSI_EXTERN bool Jsi_ValueIsTrue(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 121*/
JSI_EXTERN bool Jsi_ValueIsFalse(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 122*/
JSI_EXTERN bool Jsi_ValueIsNumber(Jsi_Interp *interp, Jsi_Value *pv); /*STUB = 123*/
JSI_EXTERN bool Jsi_ValueIsArray(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 124*/
JSI_EXTERN bool Jsi_ValueIsBoolean(Jsi_Interp *interp, Jsi_Value *pv); /*STUB = 125*/
JSI_EXTERN bool Jsi_ValueIsNull(Jsi_Interp *interp, Jsi_Value *pv); /*STUB = 126*/
JSI_EXTERN bool Jsi_ValueIsUndef(Jsi_Interp *interp, Jsi_Value *pv); /*STUB = 127*/
JSI_EXTERN bool Jsi_ValueIsFunction(Jsi_Interp *interp, Jsi_Value *pv); /*STUB = 128*/
JSI_EXTERN bool Jsi_ValueIsString(Jsi_Interp *interp, Jsi_Value *pv); /*STUB = 129*/

JSI_EXTERN Jsi_Value* Jsi_ValueMakeObject(Jsi_Interp *interp, Jsi_Value **v, Jsi_Obj *o); /*STUB = 130*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeArrayObject(Jsi_Interp *interp, Jsi_Value **v, Jsi_Obj *o); /*STUB = 131*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeNumber(Jsi_Interp *interp, Jsi_Value **v, Jsi_Number n); /*STUB = 132*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeBool(Jsi_Interp *interp, Jsi_Value **v, int b); /*STUB = 133*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeString(Jsi_Interp *interp, Jsi_Value **v, const char *s); /*STUB = 134*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeStringKey(Jsi_Interp *interp, Jsi_Value **v, const char *s); /*STUB = 135*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeBlob(Jsi_Interp *interp, Jsi_Value **v, uchar *s, int len); /*STUB = 136*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeNull(Jsi_Interp *interp, Jsi_Value **v); /*STUB = 137*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeUndef(Jsi_Interp *interp, Jsi_Value **v); /*STUB = 138*/
JSI_EXTERN Jsi_Value* Jsi_ValueMakeDStringObject(Jsi_Interp *interp, Jsi_Value **v, Jsi_DString *dsPtr); /*STUB = 139*/
JSI_EXTERN bool Jsi_ValueIsStringKey(Jsi_Interp* interp, Jsi_Value *key); /*STUB = 140*/
#define Jsi_ValueMakeStringDup(interp, v, s) Jsi_ValueMakeString(interp, v, Jsi_Strdup(s))

JSI_EXTERN const char*  Jsi_ValueToString(Jsi_Interp *interp, Jsi_Value *v, int *lenPtr); /*STUB = 141*/
JSI_EXTERN Jsi_RC       Jsi_ValueToBool(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 142*/
JSI_EXTERN Jsi_RC       Jsi_ValueToNumber(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 143*/
JSI_EXTERN Jsi_Number   Jsi_ValueToNumberInt(Jsi_Interp *interp, Jsi_Value *v, int isInt); /*STUB = 144*/
JSI_EXTERN Jsi_RC       Jsi_ValueToObject(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 145*/

JSI_EXTERN void     Jsi_ValueReset(Jsi_Interp *interp, Jsi_Value **v); /*STUB = 146*/
JSI_EXTERN const char* Jsi_ValueGetDString(Jsi_Interp* interp, Jsi_Value* v, Jsi_DString *dStr, int quote); /*STUB = 147*/
JSI_EXTERN char*    Jsi_ValueString(Jsi_Interp* interp, Jsi_Value* v, int *lenPtr); /*STUB = 148*/
JSI_EXTERN uchar*   Jsi_ValueBlob(Jsi_Interp *interp, Jsi_Value* v, int *lenPtr); /*STUB = 149*/
JSI_EXTERN char*    Jsi_ValueGetStringLen(Jsi_Interp *interp, Jsi_Value *pv, int *lenPtr); /*STUB = 150*/
JSI_EXTERN int      Jsi_ValueStrlen(Jsi_Value* v); /*STUB = 151*/
JSI_EXTERN void     Jsi_ValueFromDS(Jsi_Interp *interp, Jsi_DString *dsPtr, Jsi_Value **ret);  /*STUB = 152*/
JSI_EXTERN int      Jsi_ValueInstanceOf( Jsi_Interp *interp, Jsi_Value* v1, Jsi_Value* v2); /*STUB = 153*/
JSI_EXTERN Jsi_Obj* Jsi_ValueGetObj(Jsi_Interp* interp, Jsi_Value* v); /*STUB = 154*/
JSI_EXTERN Jsi_vtype Jsi_ValueTypeGet(Jsi_Value *pv); /*STUB = 155*/
JSI_EXTERN const char* Jsi_ValueTypeStr(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 156*/
JSI_EXTERN int      Jsi_ValueCmp(Jsi_Interp *interp, Jsi_Value *v1, Jsi_Value* v2, int cmpFlags); /*STUB = 157*/
JSI_EXTERN Jsi_RC Jsi_ValueGetIndex( Jsi_Interp *interp, Jsi_Value *valPtr, const char **tablePtr, const char *msg, int flags, int *indexPtr); /*STUB = 158*/

JSI_EXTERN Jsi_RC Jsi_ValueArraySort(Jsi_Interp *interp, Jsi_Value *val, int sortFlags); /*STUB = 159*/
JSI_EXTERN Jsi_Value* Jsi_ValueArrayConcat(Jsi_Interp *interp, Jsi_Value *arg1, Jsi_Value *arg2); /*STUB = 160*/
JSI_EXTERN Jsi_RC Jsi_ValueArrayPush(Jsi_Interp *interp, Jsi_Value *arg1, Jsi_Value *arg2); /*STUB = 161*/
JSI_EXTERN Jsi_Value* Jsi_ValueArrayPop(Jsi_Interp *interp, Jsi_Value *arg1); /*STUB = 162*/
JSI_EXTERN void Jsi_ValueArrayShift(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 163*/
JSI_EXTERN Jsi_Value* Jsi_ValueArrayUnshift(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 164*/
JSI_EXTERN Jsi_Value* Jsi_ValueArrayIndex(Jsi_Interp *interp, Jsi_Value *args, int index); /*STUB = 165*/
JSI_EXTERN char* Jsi_ValueArrayIndexToStr(Jsi_Interp *interp, Jsi_Value *args, int index, int *lenPtr); /*STUB = 166*/
#define Jsi_ValueArraySet(interp, dest, value, index) Jsi_ObjArraySet(interp, Jsi_ValueGetObj(interp, dest), value, index)

#define Jsi_ValueInsertFixed(i,t,k,v) Jsi_ValueInsert(i,t,k,v,JSI_OM_READONLY | JSI_OM_DONTDEL | JSI_OM_DONTENUM)
JSI_EXTERN Jsi_RC Jsi_ValueInsert(Jsi_Interp *interp, Jsi_Value *target, const char *key, Jsi_Value *val, int flags); /*STUB = 167*/
JSI_EXTERN Jsi_RC Jsi_ValueInsertArray(Jsi_Interp *interp, Jsi_Value *target, int index, Jsi_Value *val, int flags); /*STUB = 411*/ /*LAST*/
JSI_EXTERN int Jsi_ValueGetLength(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 168*/
JSI_EXTERN Jsi_Value* Jsi_ValueObjLookup(Jsi_Interp *interp, Jsi_Value *target, const char *key, int iskeystr); /*STUB = 169*/
JSI_EXTERN bool Jsi_ValueKeyPresent(Jsi_Interp *interp, Jsi_Value *target, const char *k, int isstrkey); /*STUB = 170*/
JSI_EXTERN Jsi_RC Jsi_ValueGetKeys(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *ret); /*STUB = 171*/

JSI_EXTERN void Jsi_ValueCopy(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from ); /*STUB = 172*/
JSI_EXTERN void Jsi_ValueReplace(Jsi_Interp *interp, Jsi_Value **to, Jsi_Value *from ); /*STUB = 173*/
JSI_EXTERN void Jsi_ValueDup2(Jsi_Interp *interp, Jsi_Value **to, Jsi_Value *from); /*STUB = 174*/
JSI_EXTERN Jsi_Value* Jsi_ValueDupJSON(Jsi_Interp *interp, Jsi_Value *val); /*STUB = 175*/
JSI_EXTERN void Jsi_ValueMove(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from); /*STUB = 176*/
JSI_EXTERN bool  Jsi_ValueIsEqual(Jsi_Interp *interp, Jsi_Value *v1, Jsi_Value* v2); /*STUB = 177*/
/* -- */


/* --USEROBJ-- */
typedef bool (Jsi_UserObjIsTrueProc)(void *data);
typedef bool (Jsi_UserObjIsEquProc)(void *data1, void *data2);
typedef Jsi_Obj* (Jsi_UserGetObjProc)(Jsi_Interp *interp, void *data);

typedef struct Jsi_UserObjReg {
    const char *name;
    Jsi_CmdSpec *spec;
    Jsi_DeleteProc *freefun;
    Jsi_UserObjIsTrueProc *istrue;
    Jsi_UserObjIsEquProc *isequ;
} Jsi_UserObjReg;

JSI_EXTERN Jsi_Hash* Jsi_UserObjRegister    (Jsi_Interp *interp, Jsi_UserObjReg *reg); /*STUB = 178*/
JSI_EXTERN Jsi_RC Jsi_UserObjUnregister  (Jsi_Interp *interp, Jsi_UserObjReg *reg); /*STUB = 179*/
JSI_EXTERN int Jsi_UserObjNew    (Jsi_Interp *interp, Jsi_UserObjReg* reg, Jsi_Obj *obj, void *data); /*STUB = 180*/
JSI_EXTERN void* Jsi_UserObjGetData(Jsi_Interp *interp, Jsi_Value* value, Jsi_Func *funcPtr); /*STUB = 181*/
/* -- */


/* --UTILITY-- */
#define JSI_NOTUSED(n) (void)n /* Eliminate annoying compiler warning. */
JSI_EXTERN char* Jsi_NumberToString(Jsi_Interp *interp, Jsi_Number d, char *buf, int bsiz); /*STUB = 182*/
JSI_EXTERN Jsi_Number Jsi_Version(void); /*STUB = 183*/
JSI_EXTERN Jsi_Value* Jsi_ReturnValue(Jsi_Interp *interp); /*STUB = 184*/
JSI_EXTERN Jsi_RC Jsi_Mount( Jsi_Interp *interp, Jsi_Value *archive, Jsi_Value *mount, Jsi_Value **ret); /*STUB = 185*/
JSI_EXTERN Jsi_Value* Jsi_Executable(Jsi_Interp *interp); /*STUB = 186*/
JSI_EXTERN Jsi_Regex* Jsi_RegExpNew(Jsi_Interp *interp, const char *regtxt, int flag); /*STUB = 187*/
JSI_EXTERN void Jsi_RegExpFree(Jsi_Regex* re); /*STUB = 188*/
JSI_EXTERN Jsi_RC Jsi_RegExpMatch( Jsi_Interp *interp,  Jsi_Value *pattern, const char *str, int *rc, Jsi_DString *dStr); /*STUB = 189*/
JSI_EXTERN Jsi_RC Jsi_RegExpMatches(Jsi_Interp *interp, Jsi_Value *pattern, const char *str, int slen, Jsi_Value *ret); /*STUB = 190*/
JSI_EXTERN bool Jsi_GlobMatch(const char *pattern, const char *string, int nocase); /*STUB = 191*/
JSI_EXTERN char* Jsi_FileRealpath(Jsi_Interp *interp, Jsi_Value *path, char *newpath); /*STUB = 192*/
JSI_EXTERN char* Jsi_FileRealpathStr(Jsi_Interp *interp, const char *path, char *newpath); /*STUB = 193*/
JSI_EXTERN char* Jsi_NormalPath(Jsi_Interp *interp, const char *path, Jsi_DString *dStr); /*STUB = 194*/
JSI_EXTERN char* Jsi_ValueNormalPath(Jsi_Interp *interp, Jsi_Value *path, Jsi_DString *dStr); /*STUB = 195*/
JSI_EXTERN Jsi_RC Jsi_PathNormalize(Jsi_Interp *interp, Jsi_Value **pathPtr); /*STUB = 410*/
JSI_EXTERN Jsi_RC Jsi_JSONParse(Jsi_Interp *interp, const char *js, Jsi_Value **ret, int flags); /*STUB = 196*/
JSI_EXTERN Jsi_RC Jsi_JSONParseFmt(Jsi_Interp *interp, Jsi_Value **ret, const char *fmt, ...) /*STUB = 197*/ __attribute__((format (printf,3,4)));
JSI_EXTERN char* Jsi_JSONQuote(Jsi_Interp *interp, const char *str, int len, Jsi_DString *dStr); /*STUB = 198*/
JSI_EXTERN Jsi_RC Jsi_EvalString(Jsi_Interp* interp, const char *str, int flags); /*STUB = 199*/
JSI_EXTERN Jsi_RC Jsi_EvalFile(Jsi_Interp* interp, Jsi_Value *fname, int flags); /*STUB = 200*/
JSI_EXTERN Jsi_RC Jsi_EvalCmdJSON(Jsi_Interp *interp, const char *cmd, const char *jsonArgs, Jsi_DString *dStr, int flags); /*STUB = 201*/
JSI_EXTERN Jsi_RC Jsi_EvalZip(Jsi_Interp *interp, const char *exeFile, const char *mntDir, int *jsFound); /*STUB = 202*/
JSI_EXTERN int Jsi_DictionaryCompare(const char *left, const char *right); /*STUB = 203*/
JSI_EXTERN Jsi_RC Jsi_GetBool(Jsi_Interp* interp, const char *string, bool *n); /*STUB = 204*/
JSI_EXTERN Jsi_RC Jsi_GetInt(Jsi_Interp* interp, const char *string, int *n, int base); /*STUB = 205*/
JSI_EXTERN Jsi_RC Jsi_GetWide(Jsi_Interp* interp, const char *string, Jsi_Wide *n, int base); /*STUB = 206*/
JSI_EXTERN Jsi_RC Jsi_GetDouble(Jsi_Interp* interp, const char *string, Jsi_Number *n); /*STUB = 207*/
JSI_EXTERN Jsi_RC Jsi_FormatString(Jsi_Interp *interp, Jsi_Value *args, Jsi_DString *dStr); /*STUB = 208*/
JSI_EXTERN void Jsi_SplitStr(const char *str, int *argcPtr, char ***argvPtr,  const char *splitCh, Jsi_DString *dStr); /*STUB = 209*/
JSI_EXTERN Jsi_RC Jsi_Sleep(Jsi_Interp *interp, Jsi_Number dtim); /*STUB = 210*/
JSI_EXTERN void Jsi_Preserve(Jsi_Interp* interp, void *data); /*STUB = 211*/
JSI_EXTERN void Jsi_Release(Jsi_Interp* interp, void *data); /*STUB = 212*/
JSI_EXTERN void Jsi_EventuallyFree(Jsi_Interp* interp, void *data, Jsi_DeleteProc* proc); /*STUB = 213*/
JSI_EXTERN void Jsi_ShiftArgs(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 214*/
JSI_EXTERN Jsi_Value* Jsi_StringSplit(Jsi_Interp *interp, const char *str, const char *spliton); /*STUB = 215*/
JSI_EXTERN Jsi_RC Jsi_GetIndex( Jsi_Interp *interp, const char *str, const char **tablePtr, const char *msg, int flags, int *indexPtr); /*STUB = 216*/
JSI_EXTERN void* Jsi_PrototypeGet(Jsi_Interp *interp, const char *key); /*STUB = 217*/
JSI_EXTERN Jsi_RC  Jsi_PrototypeDefine(Jsi_Interp *interp, const char *key, Jsi_Value *proto); /*STUB = 218*/
JSI_EXTERN Jsi_RC Jsi_PrototypeObjSet(Jsi_Interp *interp, const char *key, Jsi_Obj *obj); /*STUB = 219*/
JSI_EXTERN Jsi_RC Jsi_ThisDataSet(Jsi_Interp *interp, Jsi_Value *_this, void *value); /*STUB = 220*/
JSI_EXTERN void* Jsi_ThisDataGet(Jsi_Interp *interp, Jsi_Value *_this); /*STUB = 221*/
JSI_EXTERN Jsi_RC Jsi_FuncObjToString(Jsi_Interp *interp, Jsi_Func *f, Jsi_DString *dStr, int flags); /*STUB = 222*/
JSI_EXTERN void* Jsi_UserObjDataFromVar(Jsi_Interp *interp, const char *var); /*STUB = 223*/
JSI_EXTERN const char* Jsi_KeyAdd(Jsi_Interp *interp, const char *str); /*STUB = 224*/
JSI_EXTERN const char* Jsi_KeyLookup(Jsi_Interp *interp, const char *str); /*STUB = 225*/
JSI_EXTERN Jsi_RC Jsi_DatetimeFormat(Jsi_Interp *interp, Jsi_Number date, const char *fmt, int isUtc, Jsi_DString *dStr);  /*STUB = 226*/
JSI_EXTERN Jsi_RC Jsi_DatetimeParse(Jsi_Interp *interp, const char *str, const char *fmt, int isUtc, Jsi_Number *datePtr, bool noMsg); /*STUB = 227*/
JSI_EXTERN Jsi_Number Jsi_DateTime(void); /*STUB = 228*/
#define JSI_DATE_JULIAN2UNIX(d)  (Jsi_Number)(((Jsi_Number)d - 2440587.5)*86400.0)
#define JSI_DATE_UNIX2JULIAN(d)  (Jsi_Number)((Jsi_Number)d/86400.0+2440587.5)

typedef enum { Jsi_CHash_SHA2_256, Jsi_CHash_SHA1, Jsi_CHash_MD5, Jsi_CHash_SHA3_224, 
    Jsi_CHash_SHA3_384, Jsi_CHash_SHA3_512, Jsi_CHash_SHA3_256 } Jsi_CryptoHashType;

JSI_EXTERN Jsi_RC Jsi_Encrypt(Jsi_Interp *interp, Jsi_DString *inout, const char *key, uint keyLen, bool decrypt); /*STUB = 229*/
JSI_EXTERN Jsi_RC Jsi_CryptoHash(char *outbuf, const char *str, int len, Jsi_CryptoHashType type, uint strength, bool noHex, int *sizPtr); /*STUB = 230*/
JSI_EXTERN Jsi_RC Jsi_Base64(const char *str, int len, Jsi_DString *buf, bool decode); /*STUB = 231*/
JSI_EXTERN int Jsi_HexStr(const uchar *data, int len, Jsi_DString *dStr, bool decode); /*STUB = 232*/
JSI_EXTERN uint32_t Jsi_Crc32(uint32_t crc, const void *ptr, size_t buf_len); /*STUB = 234*/
JSI_EXTERN Jsi_RC Jsi_FileRead(Jsi_Interp *interp, Jsi_Value *name, Jsi_DString *dStr); /*STUB = 408*/

JSI_EXTERN int Jsi_NumberIsInfinity(Jsi_Number a);  /*STUB = 235*/
JSI_EXTERN bool Jsi_NumberIsEqual(Jsi_Number n1, Jsi_Number n2);  /*STUB = 236*/
JSI_EXTERN bool Jsi_NumberIsFinite(Jsi_Number value);  /*STUB = 237*/
JSI_EXTERN bool Jsi_NumberIsInteger(Jsi_Number n);  /*STUB = 238*/
JSI_EXTERN bool Jsi_NumberIsNaN(Jsi_Number a);  /*STUB = 239*/
JSI_EXTERN bool Jsi_NumberIsNormal(Jsi_Number a);  /*STUB = 240*/
JSI_EXTERN bool Jsi_NumberIsSubnormal(Jsi_Number a);  /*STUB = 241*/
JSI_EXTERN bool Jsi_NumberIsWide(Jsi_Number n);  /*STUB = 242*/
JSI_EXTERN Jsi_Number Jsi_NumberInfinity(int i);  /*STUB = 243*/
JSI_EXTERN Jsi_Number Jsi_NumberNaN(void);  /*STUB = 244*/
JSI_EXTERN void Jsi_NumberDtoA(Jsi_Interp *interp, Jsi_Number value, char* buf, int bsiz, int prec);  /*STUB = 245*/
JSI_EXTERN void Jsi_NumberItoA10(Jsi_Wide value, char* buf, int bsiz);  /*STUB = 246*/
JSI_EXTERN void Jsi_NumberUtoA10(Jsi_UWide, char* buf, int bsiz);  /*STUB = 247*/

/* -- */

#define JSI_WORDKEY_CAST (void*)(uintptr_t)

struct Jsi_MapOpts;

typedef Jsi_RC (Jsi_HashDeleteProc)(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *data);
typedef Jsi_RC (Jsi_TreeDeleteProc)(Jsi_Interp *interp, Jsi_TreeEntry *hPtr, void *data);
typedef Jsi_RC (Jsi_MapDeleteProc)(Jsi_Interp *interp, Jsi_MapEntry *hPtr, void *data);
typedef Jsi_Value *(Jsi_MapFmtKeyProc)(Jsi_MapEntry* hPtr, struct Jsi_MapOpts *opts, int flags);
typedef Jsi_RC (Jsi_TreeWalkProc)(Jsi_Tree* treePtr, Jsi_TreeEntry* hPtr, void *data);
typedef int (Jsi_RBCompareProc)(Jsi_Tree *treePtr, const void *key1, const void *key2);

typedef struct Jsi_MapOpts {
    Jsi_Map_Type mapType; // Read-only
    Jsi_Key_Type keyType; // Read-only
    Jsi_Interp *interp;
    Jsi_Wide flags;
    void *user, *user2;
    Jsi_MapFmtKeyProc *fmtKeyProc;
    Jsi_RBCompareProc *compareTreeProc;
    union {
        Jsi_RC (*freeHashProc)(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *data);
        Jsi_RC (*freeTreeProc)(Jsi_Interp *interp, Jsi_TreeEntry *hPtr, void *data);
        Jsi_RC (*freeListProc)(Jsi_Interp *interp, Jsi_ListEntry *hPtr, void *data);
    };
    union {
        Jsi_RC (*lockHashProc) (Jsi_Hash *tablePtr, int lock);
        Jsi_RC (*lockTreeProc) (Jsi_Tree *tablePtr, int lock);
    };
} Jsi_MapOpts;

/* --HASH-- */
typedef struct Jsi_HashSearch {
    Jsi_Hash *tablePtr;
    unsigned long nextIndex; 
    Jsi_HashEntry *nextEntryPtr;
} Jsi_HashSearch;

JSI_EXTERN Jsi_Hash* Jsi_HashNew(Jsi_Interp *interp, uint keyType, Jsi_HashDeleteProc *freeProc); /*STUB = 248*/
JSI_EXTERN Jsi_RC Jsi_HashConf(Jsi_Hash *hashPtr, Jsi_MapOpts *opts, bool set); /*STUB = 249*/
JSI_EXTERN void Jsi_HashDelete(Jsi_Hash *hashPtr); /*STUB = 250*/
JSI_EXTERN void Jsi_HashClear(Jsi_Hash *hashPtr); /*STUB = 251*/
JSI_EXTERN Jsi_HashEntry* Jsi_HashSet(Jsi_Hash *hashPtr, const void *key, void *value); /*STUB = 252*/
JSI_EXTERN void* Jsi_HashGet(Jsi_Hash *hashPtr, const void *key, int flags); /*STUB = 253*/
JSI_EXTERN bool Jsi_HashUnset(Jsi_Hash *tbl, const void *key); /*STUB = 254*/
JSI_EXTERN void* Jsi_HashKeyGet(Jsi_HashEntry *h); /*STUB = 255*/
JSI_EXTERN Jsi_RC Jsi_HashKeysDump(Jsi_Interp *interp, Jsi_Hash *hashPtr, Jsi_Value **ret, int flags); /*STUB = 256*/
JSI_EXTERN void* Jsi_HashValueGet(Jsi_HashEntry *h); /*STUB = 257*/
JSI_EXTERN void Jsi_HashValueSet(Jsi_HashEntry *h, void *value); /*STUB = 258*/
JSI_EXTERN Jsi_HashEntry* Jsi_HashEntryFind (Jsi_Hash *hashPtr, const void *key); /*STUB = 259*/
JSI_EXTERN Jsi_HashEntry* Jsi_HashEntryNew (Jsi_Hash *hashPtr, const void *key, bool *isNew); /*STUB = 260*/
JSI_EXTERN int Jsi_HashEntryDelete (Jsi_HashEntry *entryPtr); /*STUB = 261*/
JSI_EXTERN Jsi_HashEntry* Jsi_HashSearchFirst (Jsi_Hash *hashPtr, Jsi_HashSearch *searchPtr); /*STUB = 262*/
JSI_EXTERN Jsi_HashEntry* Jsi_HashSearchNext (Jsi_HashSearch *searchPtr); /*STUB = 263*/
JSI_EXTERN uint Jsi_HashSize(Jsi_Hash *hashPtr); /*STUB = 264*/
/* -- */


/* --TREE-- */

typedef struct Jsi_TreeSearch {
    Jsi_Tree *treePtr;
    uint top, max, left, epoch; 
    int flags;
    Jsi_TreeEntry *staticPtrs[200], *current;
    Jsi_TreeEntry **Ptrs;
} Jsi_TreeSearch;

JSI_EXTERN Jsi_Tree* Jsi_TreeNew(Jsi_Interp *interp, uint keyType, Jsi_TreeDeleteProc *freeProc); /*STUB = 265*/
JSI_EXTERN Jsi_RC Jsi_TreeConf(Jsi_Tree *treePtr, Jsi_MapOpts *opts, bool set); /*STUB = 266*/
JSI_EXTERN void Jsi_TreeDelete(Jsi_Tree *treePtr); /*STUB = 267*/
JSI_EXTERN void Jsi_TreeClear(Jsi_Tree *treePtr); /*STUB = 268*/
JSI_EXTERN Jsi_TreeEntry* Jsi_TreeObjSetValue(Jsi_Obj* obj, const char *key, Jsi_Value *val, int isstrkey); /*STUB = 269*/
JSI_EXTERN Jsi_Value*     Jsi_TreeObjGetValue(Jsi_Obj* obj, const char *key, int isstrkey); /*STUB = 270*/
JSI_EXTERN void* Jsi_TreeValueGet(Jsi_TreeEntry *hPtr); /*STUB = 271*/
JSI_EXTERN void Jsi_TreeValueSet(Jsi_TreeEntry *hPtr, void *value); /*STUB = 272*/
JSI_EXTERN void* Jsi_TreeKeyGet(Jsi_TreeEntry *hPtr); /*STUB = 273*/
JSI_EXTERN Jsi_TreeEntry* Jsi_TreeEntryFind(Jsi_Tree *treePtr, const void *key); /*STUB = 274*/
JSI_EXTERN Jsi_TreeEntry* Jsi_TreeEntryNew(Jsi_Tree *treePtr, const void *key, bool *isNew); /*STUB = 275*/
JSI_EXTERN int Jsi_TreeEntryDelete(Jsi_TreeEntry *entryPtr); /*STUB = 276*/
JSI_EXTERN Jsi_TreeEntry* Jsi_TreeSearchFirst(Jsi_Tree *treePtr, Jsi_TreeSearch *searchPtr, int flags, const void *startKey); /*STUB = 277*/
JSI_EXTERN Jsi_TreeEntry* Jsi_TreeSearchNext(Jsi_TreeSearch *searchPtr); /*STUB = 278*/
JSI_EXTERN void Jsi_TreeSearchDone(Jsi_TreeSearch *searchPtr); /*STUB = 279*/
JSI_EXTERN int Jsi_TreeWalk(Jsi_Tree* treePtr, Jsi_TreeWalkProc* callback, void *data, int flags); /*STUB = 280*/
JSI_EXTERN Jsi_TreeEntry* Jsi_TreeSet(Jsi_Tree *treePtr, const void *key, void *value); /*STUB = 281*/
JSI_EXTERN void* Jsi_TreeGet(Jsi_Tree *treePtr, void *key, int flags); /*STUB = 282*/
JSI_EXTERN bool Jsi_TreeUnset(Jsi_Tree *treePtr, void *key); /*STUB = 283*/
JSI_EXTERN uint Jsi_TreeSize(Jsi_Tree *treePtr); /*STUB = 284*/ 
JSI_EXTERN Jsi_Tree* Jsi_TreeFromValue(Jsi_Interp *interp, Jsi_Value *v); /*STUB = 285*/
JSI_EXTERN Jsi_RC Jsi_TreeKeysDump(Jsi_Interp *interp, Jsi_Tree *hashPtr, Jsi_Value **ret, int flags); /*STUB = 286*/
/* -- */


/* --LIST-- */
typedef struct Jsi_List {
    uint sig;
    int numEntries;
    Jsi_ListEntry *head;
    Jsi_ListEntry *tail;
    Jsi_MapOpts opts;
} Jsi_List;

typedef struct Jsi_ListEntry {
    uint sig;
    Jsi_Map_Type typ;    
    struct Jsi_ListEntry *next;
    struct Jsi_ListEntry *prev;
    Jsi_List *list;
    void *value;
} Jsi_ListEntry;

typedef struct Jsi_ListSearch {
    int flags;
    Jsi_List *tablePtr;
    unsigned long nextIndex; 
    Jsi_ListEntry *nextEntryPtr;
} Jsi_ListSearch;

JSI_EXTERN Jsi_List* Jsi_ListNew(Jsi_Interp *interp, Jsi_Wide flags, Jsi_HashDeleteProc *freeProc); /*STUB = 287*/
JSI_EXTERN Jsi_RC Jsi_ListConf(Jsi_List *list, Jsi_MapOpts *opts, bool set); /*STUB = 288*/
JSI_EXTERN void Jsi_ListDelete(Jsi_List *list); /*STUB = 289*/
JSI_EXTERN void Jsi_ListClear(Jsi_List *list); /*STUB = 290*/
//#define Jsi_ListSet(l, before, value) Jsi_ListPush(l, before, Jsi_ListEntryNew(l, value))
//#define Jsi_ListGet(l, le) (le)->value 
//#define Jsi_ListKeyGet(le) (le)
//#define Jsi_ListKeysDump(interp, list, ret, flags) JSI_ERROR
JSI_EXTERN void* Jsi_ListValueGet(Jsi_ListEntry *list); /*STUB = 291*/
JSI_EXTERN void Jsi_ListValueSet(Jsi_ListEntry *list, const void *value); /*STUB = 292*/
//#define Jsi_ListEntryFind(l, le) (le)
JSI_EXTERN Jsi_ListEntry* Jsi_ListEntryNew(Jsi_List *list, const void *value, Jsi_ListEntry *before); /*STUB = 293*/
JSI_EXTERN int Jsi_ListEntryDelete(Jsi_ListEntry *entry); /*STUB = 294*/
JSI_EXTERN Jsi_ListEntry* Jsi_ListSearchFirst (Jsi_List *list, Jsi_ListSearch *search, int flags); /*STUB = 295*/
JSI_EXTERN Jsi_ListEntry* Jsi_ListSearchNext (Jsi_ListSearch *search); /*STUB = 296*/
JSI_EXTERN uint Jsi_ListSize(Jsi_List *list); /*STUB = 297*/
/* end of hash-compat functions. */

JSI_EXTERN Jsi_ListEntry* Jsi_ListPush(Jsi_List *list, Jsi_ListEntry *entry, Jsi_ListEntry *before); /*STUB = 298*/
JSI_EXTERN Jsi_ListEntry* Jsi_ListPop(Jsi_List *list, Jsi_ListEntry *entry); /*STUB = 299*/
#define Jsi_ListPushFront(list,entry)   Jsi_ListPush(list, entry, list->head)
#define Jsi_ListPushBack(list,entry)    Jsi_ListPush(list, entry, NULL)
#define Jsi_ListPushFrontNew(list,v)    Jsi_ListEntryNew(list, v, list->head)
#define Jsi_ListPushBackNew(list,v)     Jsi_ListEntryNew(list, v, NULL)
#define Jsi_ListPopFront(list)          Jsi_ListPop(list, list->head)
#define Jsi_ListPopBack(list)           Jsi_ListPop(list, list->tail)
#define Jsi_ListEntryNext(entry)        (entry)->next 
#define Jsi_ListEntryPrev(entry)        (entry)->prev
#define Jsi_ListGetFront(list)          (list)->head
#define Jsi_ListGetBack(list)           (list)->tail

/* -- */


/* --STACK-- */
typedef struct {
    int len;
    int maxlen;
    void **vector;
} Jsi_Stack;

JSI_EXTERN Jsi_Stack* Jsi_StackNew(void); /*STUB = 300*/
JSI_EXTERN void Jsi_StackFree(Jsi_Stack *stack); /*STUB = 301*/
JSI_EXTERN int Jsi_StackSize(Jsi_Stack *stack); /*STUB = 302*/
JSI_EXTERN void Jsi_StackPush(Jsi_Stack *stack, void *element); /*STUB = 303*/
JSI_EXTERN void* Jsi_StackPop(Jsi_Stack *stack); /*STUB = 304*/
JSI_EXTERN void* Jsi_StackPeek(Jsi_Stack *stack); /*STUB = 305*/
JSI_EXTERN void* Jsi_StackUnshift(Jsi_Stack *stack); /*STUB = 306*/
JSI_EXTERN void* Jsi_StackHead(Jsi_Stack *stack); /*STUB = 307*/
JSI_EXTERN void Jsi_StackFreeElements(Jsi_Interp *interp, Jsi_Stack *stack, Jsi_DeleteProc *freeFunc); /*STUB = 308*/
/* -- */

/* --MAP-- */
typedef struct Jsi_MapSearch {
    Jsi_Map_Type typ;
    union {
        Jsi_TreeSearch tree;
        Jsi_HashSearch hash;
        Jsi_ListSearch list;
    } v;
} Jsi_MapSearch;

JSI_EXTERN Jsi_Map* Jsi_MapNew(Jsi_Interp *interp, Jsi_Map_Type mapType, Jsi_Key_Type keyType, Jsi_MapDeleteProc *freeProc); /*STUB = 309*/
JSI_EXTERN Jsi_RC Jsi_MapConf(Jsi_Map *mapPtr, Jsi_MapOpts *opts, bool set); /*STUB = 310*/
JSI_EXTERN void Jsi_MapDelete (Jsi_Map *mapPtr); /*STUB = 311*/
JSI_EXTERN void Jsi_MapClear (Jsi_Map *mapPtr); /*STUB = 312*/
JSI_EXTERN Jsi_MapEntry* Jsi_MapSet(Jsi_Map *mapPtr, const void *key, const void *value); /*STUB = 313*/
JSI_EXTERN void* Jsi_MapGet(Jsi_Map *mapPtr, const void *key, int flags); /*STUB = 314*/
JSI_EXTERN void* Jsi_MapKeyGet(Jsi_MapEntry *h, int flags); /*STUB = 315*/
JSI_EXTERN Jsi_RC Jsi_MapKeysDump(Jsi_Interp *interp, Jsi_Map *mapPtr, Jsi_Value **ret, int flags); /*STUB = 316*/
JSI_EXTERN void* Jsi_MapValueGet(Jsi_MapEntry *h); /*STUB = 317*/
JSI_EXTERN void Jsi_MapValueSet(Jsi_MapEntry *h, const void *value); /*STUB = 318*/
JSI_EXTERN Jsi_MapEntry* Jsi_MapEntryFind (Jsi_Map *mapPtr, const void *key); /*STUB = 319*/
JSI_EXTERN Jsi_MapEntry* Jsi_MapEntryNew (Jsi_Map *mapPtr, const void *key, bool *isNew); /*STUB = 320*/
JSI_EXTERN int Jsi_MapEntryDelete (Jsi_MapEntry *entryPtr); /*STUB = 321*/
JSI_EXTERN Jsi_MapEntry* Jsi_MapSearchFirst (Jsi_Map *mapPtr, Jsi_MapSearch *searchPtr, int flags); /*STUB = 322*/
JSI_EXTERN Jsi_MapEntry* Jsi_MapSearchNext (Jsi_MapSearch *srchPtr); /*STUB = 323*/
JSI_EXTERN void Jsi_MapSearchDone (Jsi_MapSearch *searchPtr);  /*STUB = 324*/
JSI_EXTERN uint Jsi_MapSize(Jsi_Map *mapPtr); /*STUB = 325*/


// Define typed wrappers for 5 main Map functions: Set, Get, KeyGet, EntryFind, EntryNew
#define JSI_MAP_EXTN(Prefix, keyType, valType) \
JSI_EXTERN Jsi_MapEntry *Prefix ## _Set(Jsi_Map *mapPtr, keyType key, valType value); \
JSI_EXTERN valType Prefix ## _Get(Jsi_Map *mapPtr, keyType key); \
JSI_EXTERN keyType Prefix ## _KeyGet(Jsi_MapEntry *h); \
JSI_EXTERN Jsi_MapEntry* Prefix ## _EntryFind (Jsi_Map *mapPtr, keyType key); \
JSI_EXTERN Jsi_MapEntry* Prefix ## _EntryNew (Jsi_Map *mapPtr, keyType key, int *isNew);

#define JSI_MAP_DEFN(Prefix, keyType, valType) \
Jsi_MapEntry *Prefix ## _Set(Jsi_Map *mapPtr, keyType key, valType value) { return Jsi_MapSet(mapPtr, (void*)key, (void*)value); } \
valType Prefix ## _Get(Jsi_Map *mapPtr, keyType key) { return (valType)Jsi_MapGet(mapPtr, (void*)key); } \
keyType Prefix ## _KeyGet(Jsi_MapEntry *h) { return (keyType)Jsi_MapKeyGet(h); } \
Jsi_MapEntry* Prefix ## _EntryFind (Jsi_Map *mapPtr, keyType key) { return  Jsi_MapEntryFind(mapPtr, (void*)key); } \
Jsi_MapEntry* Prefix ## _EntryNew (Jsi_Map *mapPtr, keyType key, int *isNew) { return  Jsi_MapEntryNew(mapPtr, (void*)key, isNew); }
   
/* -- */


/* --OPTIONS-- */
typedef Jsi_RC (Jsi_OptionParseProc) (
    Jsi_Interp *interp, Jsi_OptionSpec *spec, Jsi_Value *value, const char *str, void *record, Jsi_Wide flags);
typedef Jsi_RC (Jsi_OptionFormatProc) (
    Jsi_Interp *interp, Jsi_OptionSpec *spec, Jsi_Value **retValue, Jsi_DString *retStr, void *record, Jsi_Wide flags);
typedef Jsi_RC (Jsi_OptionFormatStringProc) (
    Jsi_Interp *interp, Jsi_OptionSpec *spec, Jsi_DString **retValue, void *record);
typedef void (Jsi_OptionFreeProc) (Jsi_Interp *interp, Jsi_OptionSpec *spec, void *ptr);

typedef Jsi_RC (Jsi_OptionBitOp)(Jsi_Interp *interp, Jsi_OptionSpec *spec, void *data, Jsi_Wide *s, int isSet);

typedef struct {
    const char *name;
    Jsi_OptionParseProc *parseProc;
    Jsi_OptionFormatProc *formatProc;
    Jsi_OptionFreeProc *freeProc;
    const char *help;
    const char *info;
    void* data;
} Jsi_OptionCustom;

typedef enum {
    JSI_OPTION_BOOL=1,
    JSI_OPTION_INT8,  JSI_OPTION_INT16,  JSI_OPTION_INT32,  JSI_OPTION_INT64,
    JSI_OPTION_UINT8, JSI_OPTION_UINT16, JSI_OPTION_UINT32, JSI_OPTION_UINT64,
    JSI_OPTION_FLOAT,
    JSI_OPTION_DOUBLE,    // Same as NUMBER when !JSI_USE_LONG_DOUBLE.
    JSI_OPTION_LDOUBLE,   // A long double
    JSI_OPTION_STRBUF,    // Fixed size string buffer.
    JSI_OPTION_TIME_W,    // Jsi_Wide: milliseconds since Jan 1, 1970.
    JSI_OPTION_TIME_D,    // double: milliseconds since Jan 1, 1970.
    // Non-portable fields start here
    JSI_OPTION_TIME_T,    // time_t: seconds since Jan 1, 1970. 
    JSI_OPTION_SIZE_T,
    JSI_OPTION_SSIZE_T,
    JSI_OPTION_INTPTR_T,  // Int big enough to store a pointer.
    JSI_OPTION_UINTPTR_T, 
    JSI_OPTION_NUMBER,    // Same as DOUBLE when !JSI_USE_LONG_DOUBLE.
    JSI_OPTION_INT, JSI_OPTION_UINT,
    JSI_OPTION_LONG, JSI_OPTION_ULONG, JSI_OPTION_SHORT, JSI_OPTION_USHORT,
    JSI_OPTION_STRING, JSI_OPTION_DSTRING, JSI_OPTION_STRKEY,
    JSI_OPTION_VALUE, JSI_OPTION_VAR, JSI_OPTION_OBJ, JSI_OPTION_ARRAY, JSI_OPTION_REGEXP,
    JSI_OPTION_FUNC,      // Note: .data can contain string args to check
    JSI_OPTION_USEROBJ,   // Note: .data can contain string obj name to check
    JSI_OPTION_CUSTOM,    // Note: set .custom, .data, etc.
    JSI_OPTION_END
} Jsi_OptionId;

typedef const char* Jsi_Strkey;
#ifdef __cplusplus
typedef void* Jsi_Strbuf;
#else
typedef char Jsi_Strbuf[];
#endif

typedef union {
    bool           BOOL;
    int8_t         INT8;
    int16_t        INT16;
    int32_t        INT32;
    int64_t        INT64;
    uint8_t        UINT8;
    uint16_t       UINT16;
    uint32_t       UINT32;
    uint64_t       UINT64;
    float          FLOAT;
    double         DOUBLE;
    ldouble        LDOUBLE;
    Jsi_Number     NUMBER;
    char*          STRBUF;
    time_d         TIME_D;
    time_w         TIME_W;
    time_t         TIME_T;
    size_t         SIZE_T;
    ssize_t        SSIZE_T;
    intptr_t       INTPTR_T;
    uintptr_t      UINTPTR_T;
    int            INT;
    uint           UINT;
    long           LONG;
    ulong          ULONG;
    short          SHORT;
    ushort         USHORT;
    Jsi_DString    DSTRING;
    const char*    STRKEY;
    Jsi_Value*     STRING;
    Jsi_Value*     VALUE;
    Jsi_Value*     VAR;
    Jsi_Value*     OBJ;
    Jsi_Value*     ARRAY;
    Jsi_Value*     REGEXP;
    Jsi_Value*     FUNC;
    Jsi_Value*     USEROBJ;
    void*          CUSTOM;
    Jsi_csgset*    OPT_BITS;
    struct Jsi_OptionSpec* OPT_CARRAY;
} Jsi_OptionValue;

typedef union { /* Field used at compile-time by JSI_OPT() to provide type checking for the var */
    bool           *BOOL;
    int8_t         *INT8;
    int16_t        *INT16;
    int32_t        *INT32;
    int64_t        *INT64;
    uint8_t        *UINT8;
    uint16_t       *UINT16;
    uint32_t       *UINT32;
    uint64_t       *UINT64;
    float          *FLOAT;
    double         *DOUBLE;
    ldouble        *LDOUBLE;
    Jsi_Number     *NUMBER;
#ifdef __cplusplus
    Jsi_Strbuf      STRBUF;
#else
    Jsi_Strbuf      *STRBUF;
#endif
    time_t         *TIME_T;
    time_w         *TIME_W;
    time_d         *TIME_D;
    size_t         *SIZE_T;
    ssize_t        *SSIZE_T;
    intptr_t       *INTPTR_T;
    uintptr_t      *UINTPTR_T;
    int            *INT;
    uint           *UINT;
    long           *LONG;
    ulong          *ULONG;
    short          *SHORT;
    ushort         *USHORT;
    Jsi_DString    *DSTRING;
    const char*    *STRKEY;
    Jsi_Value*     *VALUE;
    Jsi_Value*     *STRING;
    Jsi_Value*     *VAR;
    Jsi_Value*     *OBJ;
    Jsi_Value*     *ARRAY;
    Jsi_Value*     *REGEXP;
    Jsi_Value*     *FUNC;
    Jsi_Value*     *USEROBJ;
    void           *CUSTOM;
    Jsi_csgset     *OPT_BITS;
    struct Jsi_OptionSpec *OPT_CARRAY;
} Jsi_OptionInitVal;

typedef struct {
    Jsi_Sig sig;
    Jsi_OptionId id;
    const char *idName, *cName;
    int size;
    const char *fmt, *xfmt, *sfmt, *help;
    Jsi_OptionInitVal init;
    Jsi_Wide flags;
    Jsi_Wide user;
    const char *userData;       /* User data. */ \
    uchar *extData;             /* Extension data. */
    uchar *extra;
    Jsi_HashEntry *hPtr;
} Jsi_OptionTypedef;

struct Jsi_OptionSpec {
    Jsi_Sig sig;                /* Signature field. */
    Jsi_OptionId id;
    const char *name;           /* The field name. */
    uint offset;                /* Jsi_Offset of field. */
    uint size;                  /* The sizeof() of field. */
    Jsi_OptionInitVal init;     /* Initialization value */
    const char *help;           /* A short one-line help string, without newlines. */
    Jsi_Wide flags;             /* Lower 32 bits: the JSI_OPTS_* flags below. Upper 32 for custom/other. */
    Jsi_OptionCustom *custom;   /* Custom handler. */
    void *data;                 /* User data for custom options: eg. the bit for BOOLBIT. */
    const char *info;           /* Longer command description. Use JSI_DETAIL macro to allow compile-out.*/
    const char *tname;          /* Type name for field or external name used by the DB interface. */
    Jsi_Wide value;             /* Value field. */
    uint32_t bits;              /* Size of bitfield */
    uint32_t boffset;           /* Bit offset of field (or struct) */
    uint32_t idx;               /* Index (of field) */
    uint32_t ssig;              /* Signature (for struct) */
    uint32_t crc;               /* Crc (for struct) */
    uint32_t arrSize;           /* Size of array */
    const char *userData;       /* User data. */
    uchar *extData;             /* Extension data. */
    uchar *extra;               /* Extra pointer (currently unused). */
    const Jsi_OptionTypedef *type;
};

/* JSI_OPT is a macro used for option definitions, eg:
 * 
 *      typedef struct { int debug; int bool; } MyStruct;
 * 
 *      Jsi_OptionSpec MyOptions[] = {
 *          JSI_OPT(BOOL,  MyStruct,  debug ),
 *          JSI_OPT(INT,   MyStruct,  max,   .help="Max value"),
 *          JSI_OPT_END(   MyStruct, .help="My first struct" )
 *      }
*/

#define JSI_OPT_(s, typ, strct, nam, ...) \
    { .sig=s, .id=JSI_OPTION_##typ, .name=#nam, .offset=Jsi_Offset(strct, nam), .size=sizeof(((strct *) 0)->nam), \
      .init={.typ=(&((strct *) 0)->nam)}, ##__VA_ARGS__ }

#define JSI_OPT_END_(s, strct, ...) { .sig=s, .id=JSI_OPTION_END, .name=#strct, .offset=__LINE__, .size=sizeof(strct), \
      .init={.CUSTOM=(void*)__FILE__}, ##__VA_ARGS__}

#define JSI_OPT_BITS_(s, strct, nam, hlp, flgs, bsget, fidx, tnam, bdata) \
    { .sig=s, .id=JSI_OPTION_CUSTOM, .name=#nam, .offset=0, .size=0, \
        .init={.OPT_BITS=&bsget}, .help=hlp, .flags=flgs, .custom=Jsi_Opt_SwitchBitfield, .data=bdata,\
        .info=0, .tname=#nam, .value=0, .bits=0, .boffset=0, .idx=fidx }

#define JSI_OPT_CARRAY_(s, strct, nam, hlp, flgs, aropt, asiz, tnam, sinit) \
    { .sig=s, .id=JSI_OPTION_CUSTOM, .name=#nam, .offset=Jsi_Offset(strct, nam), .size=sizeof(((strct *) 0)->nam), \
        .init={.OPT_CARRAY=aropt}, .help=hlp, .flags=flgs, .custom=Jsi_Opt_SwitchCArray, .data=0,\
        .info=0, .tname=tnam, .value=0, .bits=0, .boffset=0, .idx=0, .ssig=0, .crc=0, .arrSize=asiz, .extData=sinit, .extra=0 }

#define JSI_OPT_CARRAY_ITEM_(s, typ, strct, nam, ...) \
    { .sig=s, .id=JSI_OPTION_##typ, .name=#nam, .offset=0, .size=sizeof(((strct *) 0)->nam), \
      .init={.typ=(&((strct *) 0)->nam[0])}, ##__VA_ARGS__ }

#define JSI_OPT(typ, strct, nam, ...) JSI_OPT_(JSI_SIG_OPTS, typ, strct, nam, ##__VA_ARGS__) 
#define JSI_OPT_END(strct, ...) JSI_OPT_END_(JSI_SIG_OPTS, strct, ##__VA_ARGS__)
#define JSI_OPT_BITS(strct, nam, hlp, flgs, bsget, fidx, tnam, bdata) JSI_OPT_BITS_(JSI_SIG_OPTS, strct, nam, hlp, flgs, bsget, fidx, tnam, bdata)
#define JSI_OPT_CARRAY(strct, nam, hlp, flgs, aropt, asiz, tnam, sinit) JSI_OPT_CARRAY_(JSI_SIG_OPTS, strct, nam, hlp, flgs, aropt, asiz, tnam, sinit)
#define JSI_OPT_CARRAY_ITEM(typ, strct, nam, ...) JSI_OPT_CARRAY_ITEM_(JSI_SIG_OPTS, typ, strct, nam, ##__VA_ARGS__)

#define JSI_OPT_END_IDX(opt) ((sizeof(opt)/sizeof(opt[0]))-1)

/* builtin handler for Custom. */
#define Jsi_Opt_SwitchEnum          (Jsi_OptionCustom*)0x1 /* An Enum: choices are in .data=stringlist */
#define Jsi_Opt_SwitchBitset        (Jsi_OptionCustom*)0x2 /* Bits in an int: choices are in .data=stringlist */
#define Jsi_Opt_SwitchSuboption     (Jsi_OptionCustom*)0x3 /* Sub-structs: subspec is in .data={...} */
#define Jsi_Opt_SwitchBitfield      (Jsi_OptionCustom*)0x4 /* Struct bitfields: used by "jsish -c" */
#define Jsi_Opt_SwitchValueVerify   (Jsi_OptionCustom*)0x5 /* Callback to verify Jsi_Value* correctness in .data=func. */
#define Jsi_Opt_SwitchCArray        (Jsi_OptionCustom*)0x6 /* C Array described in .data=type. */
#define Jsi_Opt_SwitchNull          (Jsi_OptionCustom*)0x7 /* Set is ignored, and get returns null */
#define Jsi_Opt_SwitchParentFunc    (Jsi_OptionCustom*)0x8 /* Name of a func in parent. Sig string is in .data*/

enum {
    /* Jsi_OptionsProcess() flags */
    JSI_OPTS_PREFIX         =   (1<<27), /* Allow matching unique prefix of object members. */
    JSI_OPTS_IS_UPDATE      =   (1<<28), /* This is an update/conf (do not reset the specified flags) */
    JSI_OPTS_IGNORE_EXTRA   =   (1<<29), /* Ignore extra members not found in spec. */
    JSI_OPTS_FORCE_STRICT   =   (1<<30), /* Override Interp->compat to disable JSI_OPTS_IGNORE_EXTRA. */
    JSI_OPTS_VERBOSE        =   (1<<31), /* Dump verbose options */
    JSI_OPTS_INCR           =   (1<<7),  /* Options is an increment. */

    /* Jsi_OptionSpec flags. */
    JSI_OPT_IS_SPECIFIED    =   (1<<0),   /* User set the option. */
    JSI_OPT_INIT_ONLY       =   (1<<1),   /* Allow set only at init, disallowing update/conf. */
    JSI_OPT_READ_ONLY       =   (1<<2),   /* Value can not be set. */
    JSI_OPT_NO_DUPVALUE     =   (1<<3),   /* Values are not to be duped. */
    JSI_OPT_NO_CLEAR        =   (1<<4),   /* Values are not to be cleared: watch for memory leaks */
    JSI_OPT_REQUIRED        =   (1<<5),  /* Field must be specified (if not IS_UPDATE). */
    JSI_OPT_PASS2           =   (1<<6),   /* Options to be processed only on pass2. */
    JSI_OPT_DB_DIRTY        =   (1<<8),   /* Used to limit DB updates. */
    JSI_OPT_DB_IGNORE       =   (1<<9),   /* Field is not to be used for DB. */
    JSI_OPT_DB_ROWID        =   (1<<10),  /* Field used by DB to store rowid. */
    JSI_OPT_CUST_NOCASE     =   (1<<11),  /* Ignore case (eg. for ENUM and BITSET). */
    JSI_OPT_FORCE_INT       =   (1<<12),  /* Force int instead of text for enum/bitset. */
    JSI_OPT_BITSET_ENUM     =   (1<<13),  /* Mark field as a bitset/enum map custom field. */
    JSI_OPT_TIME_DATEONLY   =   (1<<14),  /* Time field is date only. */
    JSI_OPT_TIME_TIMEONLY   =   (1<<15),  /* Time field is time only. */
    JSI_OPT_IS_BITS         =   (1<<16),  /* Is a C bit-field. */
    JSI_OPT_FMT_STRING      =   (1<<17),  /* Format value (eg. time) as string. */
    JSI_OPT_FMT_NUMBER      =   (1<<18),  /* Format value (eg. enum) as number. */
    JSI_OPT_FMT_HEX         =   (1<<19),  /* Format number in hex. */
    JSI_OPT_STRICT          =   (1<<20),  /* Strict mode. */
    JSI_OPT_FIELDSETUP      =   (1<<21),  /* Field has been setup. */
    JSI_OPT_COERCE          =   (1<<22),  /* Coerce input value to required type. */
    JSI_OPT_NO_SIG          =   (1<<23),  /* No signature. */
    JSI_OPT_ENUM_SPEC       =   (1<<24),  /* Enum has spec rather than a list of strings. */
    JSI_OPT_ENUM_UNSIGNED   =   (1<<25),  /* Enum value is unsigned. */
    JSI_OPT_ENUM_EXACT      =   (1<<26),  /* Enum must be an exact match. */
    JSI_OPTIONS_USER_FIRSTBIT  =   48,    /* First bit of user flags: the lower 48 bits are internal. */
};

JSI_EXTERN const Jsi_OptionTypedef* Jsi_OptionTypeInfo(Jsi_OptionId typ); /*STUB = 326*/
JSI_EXTERN Jsi_OptionTypedef* Jsi_TypeLookup(Jsi_Interp* interp, const char *typ); /*STUB = 327*/
JSI_EXTERN int Jsi_OptionsProcess(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Value *value, Jsi_Wide flags); /*STUB = 328*/
JSI_EXTERN int Jsi_OptionsProcessJSON(Jsi_Interp *interp, Jsi_OptionSpec *opts, void *data, const char *json, Jsi_Wide flags); /*STUB = 329*/
JSI_EXTERN Jsi_RC Jsi_OptionsConf(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Value *value, Jsi_Value **ret, Jsi_Wide flags); /*STUB = 330*/
JSI_EXTERN void Jsi_OptionsFree(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Wide flags); /*STUB = 331*/
JSI_EXTERN Jsi_RC Jsi_OptionsGet(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, const char *option, Jsi_Value** valuePtr, Jsi_Wide flags); /*STUB = 332*/
JSI_EXTERN Jsi_RC Jsi_OptionsSet(Jsi_Interp *interp, Jsi_OptionSpec *specs, void* data, const char *option, Jsi_Value *valuePtr, Jsi_Wide flags); /*STUB = 333*/
JSI_EXTERN Jsi_RC Jsi_OptionsDump(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Value** ret, Jsi_Wide flags); /*STUB = 334*/
JSI_EXTERN int Jsi_OptionsChanged(Jsi_Interp *interp, Jsi_OptionSpec *specs, const char *pattern, ...) /*STUB = 335*/ __attribute__((sentinel));
JSI_EXTERN bool Jsi_OptionsValid(Jsi_Interp *interp, Jsi_OptionSpec* spec);  /*STUB = 336*/
JSI_EXTERN const char* Jsi_OptionsData(Jsi_Interp *interp, Jsi_OptionSpec *specs, Jsi_DString *dStr, bool schema);
JSI_EXTERN Jsi_OptionSpec* Jsi_OptionsFind(Jsi_Interp *interp, Jsi_OptionSpec *specs, const char *name, Jsi_Wide flags); /*STUB = 337*/
JSI_EXTERN Jsi_Value* Jsi_OptionsCustomPrint(void* clientData, Jsi_Interp *interp, const char *optionName, void *data, int offset); /*STUB = 338*/
JSI_EXTERN Jsi_OptionCustom* Jsi_OptionCustomBuiltin(Jsi_OptionCustom* cust); /*STUB = 339*/
/* Create a duplicate of static specs.   Use this for threaded access to Jsi_OptionsChanged(). */
JSI_EXTERN Jsi_OptionSpec* Jsi_OptionsDup(Jsi_Interp *interp, const Jsi_OptionSpec *staticSpecs); /*STUB = 340*/
JSI_EXTERN const Jsi_OptionSpec* Jsi_OptionSpecsCached(Jsi_Interp *interp, const Jsi_OptionSpec *staticSpecs);  /*STUB = 341*/
/* -- */


/* --THREADS/MUTEX-- */
JSI_EXTERN Jsi_RC Jsi_MutexLock(Jsi_Interp *interp, Jsi_Mutex *mtx); /*STUB = 342*/
JSI_EXTERN void Jsi_MutexUnlock(Jsi_Interp *interp, Jsi_Mutex *mtx); /*STUB = 343*/
JSI_EXTERN void Jsi_MutexDelete(Jsi_Interp *interp, Jsi_Mutex *mtx); /*STUB = 344*/
JSI_EXTERN Jsi_Mutex* Jsi_MutexNew(Jsi_Interp *interp, int timeout, int flags); /*STUB = 345*/
JSI_EXTERN void* Jsi_CurrentThread(void); /*STUB = 346*/
JSI_EXTERN void* Jsi_InterpThread(Jsi_Interp *interp); /*STUB = 347*/
/* -- */


/* --LOGGING-- */
#define Jsi_LogBug(fmt,...) Jsi_LogMsg(interp, JSI_LOG_BUG, fmt, ##__VA_ARGS__)
#define Jsi_LogError(fmt,...) Jsi_LogMsg(interp, JSI_LOG_ERROR, fmt, ##__VA_ARGS__)
#define Jsi_LogParse(fmt,...) Jsi_LogMsg(interp, JSI_LOG_PARSE, fmt, ##__VA_ARGS__)
#define Jsi_LogWarn(fmt,...) Jsi_LogMsg(interp, JSI_LOG_WARN, fmt, ##__VA_ARGS__)
#define Jsi_LogInfo(fmt,...) Jsi_LogMsg(interp, JSI_LOG_INFO, fmt, ##__VA_ARGS__)
#define Jsi_LogDebug(fmt,...) Jsi_LogMsg(interp, JSI_LOG_DEBUG, fmt, ##__VA_ARGS__)
#define Jsi_LogTrace(fmt,...) Jsi_LogMsg(interp, JSI_LOG_TRACE, fmt, ##__VA_ARGS__)
#define Jsi_LogTest(fmt,...) Jsi_LogMsg(interp, JSI_LOG_TEST, fmt, ##__VA_ARGS__)

JSI_EXTERN Jsi_RC Jsi_LogMsg(Jsi_Interp *interp, uint level, const char *format,...)  /*STUB = 348*/ __attribute__((format (printf,3,4)));


/* --EVENTS-- */
typedef struct {
    Jsi_Sig sig;
    uint id;
    int evType;                 /* Is signal handler. */
    int sigNum;
    int once;                   /* Execute once */
    long initialms;             /* initial relative timer value */
    long when_sec;              /* seconds */
    long when_ms;               /* milliseconds */
    bool busy;                  /* In event callback. */
    uint count;                 /* Times executed */
    Jsi_HashEntry *hPtr;
    Jsi_Value *funcVal;         /* JS Function to call. */
    Jsi_EventHandlerProc *handler;  /* C-function handler. */
    void *data;
} Jsi_Event;

JSI_EXTERN Jsi_Event* Jsi_EventNew(Jsi_Interp *interp, Jsi_EventHandlerProc *callback, void* data); /*STUB = 349*/
JSI_EXTERN void Jsi_EventFree(Jsi_Interp *interp, Jsi_Event* event); /*STUB = 350*/
JSI_EXTERN int Jsi_EventProcess(Jsi_Interp *interp, int maxEvents); /*STUB = 351*/
/* -- */


/* --JSON-- */
#define JSI_JSON_DECLARE(p, tokens, maxsz) \
    Jsi_JsonParser p = {0}; \
    Jsi_JsonTok tokens[maxsz>0?maxsz:JSI_JSON_STATIC_DEFAULT]; \
    Jsi_JsonInit(&p, tokens, maxsz>0?maxsz:JSI_JSON_STATIC_DEFAULT)

typedef enum {
    JSI_JTYPE_PRIMITIVE = 0,
    JSI_JTYPE_OBJECT = 1,
    JSI_JTYPE_ARRAY = 2,
    JSI_JTYPE_STRING = 3,
    JSI_JTYPE_INVALID=-1
} Jsi_JsonTypeEnum;

typedef enum {
    JSI_JSON_ERR_NOMEM = -1,
    JSI_JSON_ERR_INVAL = -2,
    JSI_JSON_ERR_PART = -3,
    JSI_JSON_ERR_NONE = 0
} Jsi_JsonErrEnum;

typedef struct {
    Jsi_JsonTypeEnum type;
    int start;
    int end;
    uint size;
    int parent;
} Jsi_JsonTok;

typedef struct {
    uint pos;           /* offset in the JSON string */
    uint toknext;       /* next token to allocate */
    int toksuper;       /* superior token node, e.g parent object or array */
    Jsi_JsonTok *tokens, *static_tokens;
    uint num_tokens;
    int no_malloc;      /* Disable parser dynamic growth tokens array. */
    bool strict;/* Strict parsing. */
    Jsi_Wide flags;
    const char *errStr;
    void *reserved[4];     /* Reserved for future */
} Jsi_JsonParser;


JSI_EXTERN void Jsi_JsonInit(Jsi_JsonParser *parser, Jsi_JsonTok *static_tokens, uint num_tokens); /*STUB = 352*/
JSI_EXTERN void Jsi_JsonReset(Jsi_JsonParser *parser); /*STUB = 353*/
JSI_EXTERN void Jsi_JsonFree(Jsi_JsonParser *parser); /*STUB = 354*/
JSI_EXTERN Jsi_JsonErrEnum Jsi_JsonParse(Jsi_JsonParser *parser, const char *js); /*STUB = 355*/
JSI_EXTERN Jsi_JsonTok* Jsi_JsonGetToken(Jsi_JsonParser *parser, uint index); /*STUB = 356*/
JSI_EXTERN Jsi_JsonTypeEnum Jsi_JsonGetType(Jsi_JsonParser *parser, uint index); /*STUB = 357*/
JSI_EXTERN int Jsi_JsonTokLen(Jsi_JsonParser *parser, uint index); /*STUB = 358*/
JSI_EXTERN const char* Jsi_JsonGetTokstr(Jsi_JsonParser *parser, const char *js, uint index, uint *len); /*STUB = 359*/
JSI_EXTERN const char* Jsi_JsonGetTypename(int type); /*STUB = 360*/
JSI_EXTERN const char* Jsi_JsonGetErrname(int code); /*STUB = 361*/
JSI_EXTERN void Jsi_JsonDump(Jsi_JsonParser *parser, const char *js); /*STUB = 362*/
/* -- */


/* --VFS-- */
struct Jsi_LoadHandle; struct Jsi_LoadHandle;

typedef struct Jsi_LoadHandle Jsi_LoadHandle;
typedef struct stat Jsi_StatBuf;
typedef struct dirent Jsi_Dirent;

typedef int (Jsi_FSStatProc) (Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf);
typedef int (Jsi_FSAccessProc) (Jsi_Interp *interp, Jsi_Value* path, int mode);
typedef int (Jsi_FSChmodProc) (Jsi_Interp *interp, Jsi_Value* path, int mode);
typedef Jsi_Channel (Jsi_FSOpenProc) (Jsi_Interp *interp, Jsi_Value* path, const char* modes);
typedef int (Jsi_FSLstatProc) (Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf);
typedef int (Jsi_FSCreateDirectoryProc) (Jsi_Interp *interp, Jsi_Value* path);
typedef int (Jsi_FSRemoveProc) (Jsi_Interp *interp, Jsi_Value* path, int flags);
typedef int (Jsi_FSCopyDirectoryProc) (Jsi_Interp *interp, Jsi_Value *srcPathPtr, Jsi_Value *destPathPtr, Jsi_Value **errorPtr);
typedef int (Jsi_FSCopyFileProc) (Jsi_Interp *interp, Jsi_Value *srcPathPtr, Jsi_Value *destPathPtr);
typedef int (Jsi_FSRemoveDirectoryProc) (Jsi_Interp *interp, Jsi_Value* path, int recursive, Jsi_Value **errorPtr);
typedef int (Jsi_FSRenameProc) (Jsi_Interp *interp, Jsi_Value *srcPathPtr, Jsi_Value *destPathPtr);
typedef Jsi_Value * (Jsi_FSListVolumesProc) (Jsi_Interp *interp);
typedef char* (Jsi_FSRealPathProc) (Jsi_Interp *interp, Jsi_Value* path, char *newPath);
typedef int (Jsi_FSLinkProc) (Jsi_Interp *interp, Jsi_Value* path, Jsi_Value *toPath, int linkType);
typedef int (Jsi_FSReadlinkProc)(Jsi_Interp *interp, Jsi_Value *path, char *buf, int size);
typedef int (Jsi_FSReadProc)(Jsi_Channel chan, char *buf, int size);
typedef int (Jsi_FSGetcProc)(Jsi_Channel chan);
typedef int (Jsi_FSEofProc)(Jsi_Channel chan);
typedef int (Jsi_FSTruncateProc)(Jsi_Channel chan, uint len);
typedef int (Jsi_FSUngetcProc)(Jsi_Channel chan, int ch);
typedef char *(Jsi_FSGetsProc)(Jsi_Channel chan, char *s, int size);
typedef int (Jsi_FSPutsProc)(Jsi_Channel chan, const char* str);
typedef int (Jsi_FSWriteProc)(Jsi_Channel chan, const char *buf, int size);
typedef int (Jsi_FSFlushProc)(Jsi_Channel chan);
typedef int (Jsi_FSSeekProc)(Jsi_Channel chan, Jsi_Wide offset, int mode);
typedef int (Jsi_FSTellProc)(Jsi_Channel chan);
typedef int (Jsi_FSCloseProc)(Jsi_Channel chan);
typedef int (Jsi_FSRewindProc)(Jsi_Channel chan);
typedef bool (Jsi_FSPathInFilesystemProc) (Jsi_Interp *interp, Jsi_Value* path,void* *clientDataPtr);
typedef int (Jsi_FSScandirProc)(Jsi_Interp *interp, Jsi_Value *path, Jsi_Dirent ***namelist,
  int (*filter)(const Jsi_Dirent *), int (*compar)(const Jsi_Dirent **, const Jsi_Dirent**));

typedef struct Jsi_Filesystem {
    const char *typeName;
    int structureLength;    
    int version;
    Jsi_FSPathInFilesystemProc *pathInFilesystemProc;
    Jsi_FSRealPathProc *realpathProc;
    Jsi_FSStatProc *statProc;
    Jsi_FSLstatProc *lstatProc;
    Jsi_FSAccessProc *accessProc;
    Jsi_FSChmodProc *chmodProc;
    Jsi_FSOpenProc *openProc;
    Jsi_FSScandirProc *scandirProc;
    Jsi_FSReadProc *readProc;
    Jsi_FSWriteProc *writeProc;
    Jsi_FSGetsProc *getsProc;
    Jsi_FSGetcProc *getcProc;
    Jsi_FSUngetcProc *ungetcProc;
    Jsi_FSPutsProc *putsProc;
    
    Jsi_FSFlushProc *flushProc;
    Jsi_FSSeekProc *seekProc;
    Jsi_FSTellProc *tellProc;
    Jsi_FSEofProc *eofProc;
    Jsi_FSTruncateProc *truncateProc;
    Jsi_FSRewindProc *rewindProc;
    Jsi_FSCloseProc *closeProc;
    Jsi_FSLinkProc *linkProc;
    Jsi_FSReadlinkProc *readlinkProc;
    Jsi_FSListVolumesProc *listVolumesProc;
    Jsi_FSCreateDirectoryProc *createDirectoryProc;
    Jsi_FSRemoveProc *removeProc;
    Jsi_FSRenameProc *renameProc;
    void *reserved[10];     /* Reserved for future */
} Jsi_Filesystem;

typedef struct Jsi_Chan {
    FILE *fp;
    const char *fname;  /* May be set by fs or by source */
    Jsi_Filesystem *fsPtr;
    int isNative;
    int flags;
    char modes[JSI_FSMODESIZE];
    void *data;
    void *reserved[4];     /* Reserved for future */
    ssize_t resInt[2];
} Jsi_Chan;

JSI_EXTERN Jsi_RC Jsi_FSRegister(Jsi_Filesystem *fsPtr, void *data); /*STUB = 363*/
JSI_EXTERN Jsi_RC Jsi_FSUnregister(Jsi_Filesystem *fsPtr); /*STUB = 364*/
JSI_EXTERN Jsi_Channel Jsi_FSNameToChannel(Jsi_Interp *interp, const char *name); /*STUB = 365*/
JSI_EXTERN char* Jsi_GetCwd(Jsi_Interp *interp, Jsi_DString *cwdPtr); /*STUB = 366*/
JSI_EXTERN int Jsi_Lstat(Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf); /*STUB = 367*/
JSI_EXTERN int Jsi_Stat(Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf); /*STUB = 368*/
JSI_EXTERN int Jsi_Access(Jsi_Interp *interp, Jsi_Value* path, int mode); /*STUB = 369*/
JSI_EXTERN int Jsi_Remove(Jsi_Interp *interp, Jsi_Value* path, int flags); /*STUB = 370*/
JSI_EXTERN int Jsi_Rename(Jsi_Interp *interp, Jsi_Value *src, Jsi_Value *dst); /*STUB = 371*/
JSI_EXTERN int Jsi_Chdir(Jsi_Interp *interp, Jsi_Value* path); /*STUB = 372*/
JSI_EXTERN Jsi_Channel Jsi_Open(Jsi_Interp *interp, Jsi_Value *file, const char *modeString); /*STUB = 373*/
JSI_EXTERN int Jsi_Eof(Jsi_Interp *interp, Jsi_Channel chan); /*STUB = 374*/
JSI_EXTERN int Jsi_Close(Jsi_Interp *interp, Jsi_Channel chan); /*STUB = 375*/
JSI_EXTERN int Jsi_Read(Jsi_Interp *interp, Jsi_Channel chan, char *bufPtr, int toRead); /*STUB = 376*/
JSI_EXTERN int Jsi_Write(Jsi_Interp *interp, Jsi_Channel chan, const char *bufPtr, int slen); /*STUB = 377*/
JSI_EXTERN Jsi_Wide Jsi_Seek(Jsi_Interp *interp, Jsi_Channel chan, Jsi_Wide offset, int mode); /*STUB = 378*/
JSI_EXTERN Jsi_Wide Jsi_Tell(Jsi_Interp *interp, Jsi_Channel chan); /*STUB = 379*/
JSI_EXTERN int Jsi_Truncate(Jsi_Interp *interp, Jsi_Channel chan, uint len); /*STUB = 380*/
JSI_EXTERN Jsi_Wide Jsi_Rewind(Jsi_Interp *interp, Jsi_Channel chan); /*STUB = 381*/
JSI_EXTERN int Jsi_Flush(Jsi_Interp *interp, Jsi_Channel chan); /*STUB = 382*/
JSI_EXTERN int Jsi_Getc(Jsi_Interp *interp, Jsi_Channel chan); /*STUB = 383*/
JSI_EXTERN int Jsi_Printf(Jsi_Interp *interp, Jsi_Channel chan, const char *fmt, ...) /*STUB = 384*/ __attribute__((format (printf,3,4))); 
JSI_EXTERN int Jsi_Ungetc(Jsi_Interp *interp, Jsi_Channel chan, int ch); /*STUB = 385*/
JSI_EXTERN char* Jsi_Gets(Jsi_Interp *interp, Jsi_Channel chan, char *s, int size); /*STUB = 386*/
JSI_EXTERN int Jsi_Puts(Jsi_Interp *interp, Jsi_Channel chan, const char *str, int size); /*STUB = 387*/

typedef int (Jsi_ScandirFilter)(const Jsi_Dirent *);
typedef int (Jsi_ScandirCompare)(const Jsi_Dirent **, const Jsi_Dirent**);
JSI_EXTERN int Jsi_Scandir(Jsi_Interp *interp, Jsi_Value *path, Jsi_Dirent ***namelist, Jsi_ScandirFilter *filter, Jsi_ScandirCompare *compare ); /*STUB = 388*/
JSI_EXTERN int Jsi_SetChannelOption(Jsi_Interp *interp, Jsi_Channel chan, const char *optionName, const char *newValue); /*STUB = 389*/
JSI_EXTERN char* Jsi_Realpath(Jsi_Interp *interp, Jsi_Value *path, char *newname); /*STUB = 390*/
JSI_EXTERN int Jsi_Readlink(Jsi_Interp *interp, Jsi_Value* path, char *ret, int len); /*STUB = 391*/
JSI_EXTERN Jsi_Channel Jsi_GetStdChannel(Jsi_Interp *interp, int id); /*STUB = 392*/
JSI_EXTERN bool Jsi_FSNative(Jsi_Interp *interp, Jsi_Value* path); /*STUB = 393*/
JSI_EXTERN int Jsi_Link(Jsi_Interp *interp, Jsi_Value* src, Jsi_Value *dest, int typ); /*STUB = 394*/
JSI_EXTERN int Jsi_Chmod(Jsi_Interp *interp, Jsi_Value* path, int mode); /*STUB = 395*/

JSI_EXTERN Jsi_RC Jsi_StubLookup(Jsi_Interp *interp, const char *name, void **ptr); /*STUB = 396*/
JSI_EXTERN Jsi_RC Jsi_DllLookup(Jsi_Interp *interp, const char *module, const char *name, void **ptr); /*STUB = 404*/
JSI_EXTERN Jsi_RC Jsi_LoadLibrary(Jsi_Interp *interp, const char *pathName, bool noInit); /*STUB = 405*/
JSI_EXTERN int Jsi_AddAutoFiles(Jsi_Interp *interp, const char *dir);  /*STUB = 397*/

/* -- */



/* --DATABASE-- */

JSI_EXTERN Jsi_Db* Jsi_DbNew(const char *zFile, int inFlags); /*STUB = 398*/
JSI_EXTERN void* Jsi_DbHandle(Jsi_Interp *interp, Jsi_Db* db); /*STUB = 399*/

/* -- */


/* --CData-- */

#define  JSI_CDATA_OPTION_CHANGED(name) \
        (vrPtr->spec && Jsi_OptionsChanged(interp, vrPtr->spec, #name, NULL))
#define  JSI_CDATA_OPTION_RESET(name) \
        (cmdPtr->queryOpts.mode && !options->name && JSI_CDATA_OPTION_CHANGED(name))
 
typedef struct Jsi_CData_Static {
    const char* name;
    Jsi_StructSpec* structs;
    Jsi_EnumSpec* enums;
    Jsi_VarSpec *vars;
    Jsi_OptionTypedef* types;
    struct Jsi_CData_Static* nextPtr;
} Jsi_CData_Static;

/* Struct for Carray to bind Data/Option pairs to individual SQLite binding chars. */
typedef struct Jsi_CDataDb {
#define JSI_DBDATA_FIELDS \
    Jsi_StructSpec *sf;     /* Struct fields for data. */ \
    void *data;             /* Data pointer for array/map */ \
    uint arrSize;           /* If an array, number of elements: 0 means 1. */ \
    char prefix;            /* Sqlite char bind prefix. One of: '@' '$' ':' '?' or 0 for any */ \
    Jsi_StructSpec* slKey;  /* Struct for key (for map using a struct key). */ \
    int (*callback)(Jsi_Interp *interp, struct Jsi_CDataDb* obPtr, void *data); /* C callback for select queries. */ \
    uint maxSize;           /* Limit size of array/map*/ \
    bool noAuto;            /* Do not auto-create map keys. */ \
    bool isPtrs;            /* "data" an array of pointers. */ \
    bool isPtr2;            /* "data" is pointer to pointers, which is updated. */ \
    bool isMap;             /* "data" is a map: use Jsi_MapConf() for details. */ \
    bool memClear;          /* Before query free and zero all data (eg. DStrings). */ \
    bool memFree;           /* Reset as per mem_clear, then free data items. Query may be empty. */ \
    bool dirtyOnly;         /* Sqlite dirty filter for UPDATE/INSERT/REPLACE. */ \
    bool noBegin;           /* Disable wrapping UPDATE in BEGIN/COMMIT. */ \
    bool noCache;           /* Disable Db caching statement. */ \
    bool noStatic;          /* Disable binding text with SQLITE_STATIC. */ \
    intptr_t reserved[4];   /* Internal use. */
JSI_DBDATA_FIELDS
} Jsi_CDataDb;

JSI_EXTERN int Jsi_DbQuery(Jsi_Db *jdb, Jsi_CDataDb *cd, const char *query); /*STUB = 400*/
JSI_EXTERN Jsi_CDataDb* Jsi_CDataLookup(Jsi_Interp *interp, const char *name); /*STUB = 401*/
JSI_EXTERN Jsi_RC Jsi_CDataRegister(Jsi_Interp *interp, Jsi_CData_Static *statics); /*STUB = 402*/
JSI_EXTERN Jsi_RC Jsi_CDataStructInit(Jsi_Interp *interp, uchar* data, const char *sname); /*STUB = 403*/
JSI_EXTERN Jsi_StructSpec* Jsi_CDataStruct(Jsi_Interp *interp, const char *name); /*STUB = 406*/
/* -- */


/* String */
typedef char STRING1[(1<<0)+1]; // Include a char for the null byte.
typedef char STRING2[(1<<1)+1];
typedef char STRING4[(1<<2)+1];
typedef char STRING8[(1<<3)+1];
typedef char STRING16[(1<<4)+1];
typedef char STRING32[(1<<5)+1];
typedef char STRING64[(1<<6)+1];
typedef char STRING128[(1<<7)+1];
typedef char STRING256[(1<<8)+1];
typedef char STRING512[(1<<9)+1];
typedef char STRING1024[(1<<10)+1];
typedef char STRING2048[(1<<11)+1];
typedef char STRING4096[(1<<12)+1];
typedef char STRING8192[(1<<13)+1];
typedef char STRING16384[(1<<14)+1];
typedef char STRING32768[(1<<15)+1];
typedef char STRING65536[(1<<16)+1];

/* -- */


#define JSI_STUBS_STRUCTSIZES (sizeof(Jsi_MapSearch)+sizeof(Jsi_TreeSearch) \
    +sizeof(Jsi_HashSearch)+sizeof(Jsi_Filesystem)+sizeof(Jsi_Chan)+sizeof(Jsi_Event) \
    +sizeof(Jsi_CDataDb)+sizeof(Jsi_Stack)+sizeof(Jsi_OptionSpec)+sizeof(Jsi_CmdSpec) \
    +sizeof(Jsi_UserObjReg)+sizeof(Jsi_String) + sizeof(Jsi_PkgOpts))

#ifndef JSI_OMIT_STUBS
#ifdef JSI_USE_STUBS
#ifndef JSISTUBCALL
#define JSISTUBCALL(ptr,func) ptr->func
#endif
#include "jsiStubs.h"
#else
#define JSI_EXTENSION_INI
#define Jsi_StubsInit(i,f) JSI_OK
#endif
#endif


/* Optional compile-out commands/options string information. */
#ifdef JSI_OMIT_INFO
#define JSI_INFO(n) NULL
#endif
#ifndef JSI_INFO
#define JSI_INFO(n) n
#endif

#endif /* __JSI_H__ */


#ifndef JSI_IN_AMALGAMATION
#define JSI_IN_AMALGAMATION
#define _GNU_SOURCE
#define JSI_AMALGAMATION
struct jsi_Pstate;
#ifndef __JSI_STUBS_H__
#define __JSI_STUBS_H__
#ifndef JSI_AMALGAMATION
#include "jsi.h"
#endif


#define JSI_STUBS_MD5 "54b3c19a995493d17414e173ea427a66"

#undef JSI_EXTENSION_INI
#define JSI_EXTENSION_INI Jsi_Stubs *jsiStubsPtr = NULL;

#ifdef JSI__MUSL
#define JSI_STUBS_BLDFLAGS 1
#else
#define JSI_STUBS_BLDFLAGS 0
#endif
#ifndef Jsi_StubsInit
#define Jsi_StubsInit(interp,flags) (jsiStubsPtr && jsiStubsPtr->sig == \
  JSI_STUBS_SIG?jsiStubsPtr->_Jsi_Stubs__initialize(interp, flags, "jsi", \
  JSI_VERSION, JSI_STUBS_MD5, JSI_STUBS_BLDFLAGS, sizeof(Jsi_Stubs), JSI_STUBS_STRUCTSIZES, (void**)&jsiStubsPtr):JSI_ERROR)
#endif

typedef struct Jsi_Stubs {
    uint sig;
    const char* name;
    int size;
    int bldFlags;
    const char* md5;
    void *hook;
    int(*_Jsi_Stubs__initialize)(Jsi_Interp *interp, double version, const char* name, int flags, const char *md5, int bldFlags, int stubSize, int structSizes, void **ptr);
    Jsi_Interp*(*_Jsi_InterpNew)(Jsi_InterpOpts *opts);
    void(*_Jsi_InterpDelete)( Jsi_Interp* interp);
    void(*_Jsi_InterpOnDelete)(Jsi_Interp *interp, Jsi_DeleteProc *freeProc, void *ptr);
    Jsi_RC(*_Jsi_Interactive)(Jsi_Interp* interp, int flags);
    bool(*_Jsi_InterpGone)( Jsi_Interp* interp);
    Jsi_Value*(*_Jsi_InterpResult)(Jsi_Interp *interp);
    const char*(*_Jsi_InterpLastError)(Jsi_Interp *interp, const char **errFilePtr, int *errLinePtr);
    void*(*_Jsi_InterpGetData)(Jsi_Interp *interp, const char *key, Jsi_DeleteProc **proc);
    void(*_Jsi_InterpSetData)(Jsi_Interp *interp, const char *key, void *data, Jsi_DeleteProc *proc);
    void(*_Jsi_InterpFreeData)(Jsi_Interp *interp, const char *key);
    bool(*_Jsi_InterpSafe)(Jsi_Interp *interp);
    Jsi_RC(*_Jsi_InterpAccess)(Jsi_Interp *interp, Jsi_Value* resource, int aflag);
    Jsi_Interp*(*_Jsi_Main)(Jsi_InterpOpts *opts);
    void*(*_Jsi_Malloc)(uint size);
    void*(*_Jsi_Calloc)(uint n, uint size);
    void*(*_Jsi_Realloc)(void *m, uint size);
    void (*_Jsi_Free)(void *m);
    int(*_Jsi_ObjIncrRefCount)(Jsi_Interp* interp, Jsi_Obj *obj);
    int(*_Jsi_ObjDecrRefCount)(Jsi_Interp* interp, Jsi_Obj *obj);
    int(*_Jsi_IncrRefCount)(Jsi_Interp* interp, Jsi_Value *v);
    int(*_Jsi_DecrRefCount)(Jsi_Interp* interp, Jsi_Value *v);
    bool(*_Jsi_IsShared)(Jsi_Interp* interp, Jsi_Value *v);
    Jsi_RC(*_Jsi_DeleteData)(Jsi_Interp* interp, void *m);
    uint(*_Jsi_Strlen)(const char *str);
    uint(*_Jsi_StrlenSet)(const char *str, uint len);
    int(*_Jsi_Strcmp)(const char *str1, const char *str2);
    int(*_Jsi_Strncmp)(const char *str1, const char *str2, int n);
    int(*_Jsi_Strncasecmp)(const char *str1, const char *str2, int n);
    int(*_Jsi_StrcmpDict)(const char *str1, const char *str2, int nocase, int dict);
    char*(*_Jsi_Strcpy)(char *dst, const char *src);
    char*(*_Jsi_Strncpy)(char *dst, const char *src, int len);
    char*(*_Jsi_Strdup)(const char *n);
    char*(*_Jsi_Strrchr)(const char *str, int c);
    char*(*_Jsi_Strstr)(const char *str, const char *sub);
    int(*_Jsi_ObjArraySizer)(Jsi_Interp *interp, Jsi_Obj *obj, uint n);
    char*(*_Jsi_Strchr)(const char *str, int c);
    int(*_Jsi_Strpos)(const char *str, int start, const char *nid, int nocase);
    int(*_Jsi_Strrpos)(const char *str, int start, const char *nid, int nocase);
    char*  (*_Jsi_DSAppendLen)(Jsi_DString *dsPtr,const char *bytes, int length);
    char*  (*_Jsi_DSAppend)(Jsi_DString *dsPtr, const char *str, ...);
    void   (*_Jsi_DSFree)(Jsi_DString *dsPtr);
    char*  (*_Jsi_DSFreeDup)(Jsi_DString *dsPtr);
    void   (*_Jsi_DSInit)(Jsi_DString *dsPtr);
    uint   (*_Jsi_DSLength)(Jsi_DString *dsPtr);
    char*  (*_Jsi_DSPrintf)(Jsi_DString *dsPtr, const char *fmt, ...);
    char*  (*_Jsi_DSSet)(Jsi_DString *dsPtr, const char *str);
    uint   (*_Jsi_DSSetLength)(Jsi_DString *dsPtr, uint length);
    char*  (*_Jsi_DSValue)(Jsi_DString *dsPtr);
    Jsi_Value*(*_Jsi_CommandCreate)(Jsi_Interp *interp, const char *name, Jsi_CmdProc *cmdProc, void *privData);
    Jsi_Value*(*_Jsi_CommandCreateSpecs)(Jsi_Interp *interp, const char *name, Jsi_CmdSpec *cmdSpecs, void *privData, int flags);
    void*(*_Jsi_CommandNewObj)(Jsi_Interp *interp, const char *name, const char *arg1, const char *opts, const char *var);
    Jsi_RC(*_Jsi_CommandInvokeJSON)(Jsi_Interp *interp, const char *cmd, const char *json, Jsi_Value **ret);
    Jsi_RC(*_Jsi_CommandInvoke)(Jsi_Interp *interp, const char *cmdstr, Jsi_Value *args, Jsi_Value **ret);
    Jsi_RC(*_Jsi_CommandDelete)(Jsi_Interp *interp, const char *name);
    Jsi_CmdSpec*(*_Jsi_FunctionGetSpecs)(Jsi_Func *funcPtr);
    bool(*_Jsi_FunctionIsConstructor)(Jsi_Func *funcPtr);
    bool(*_Jsi_FunctionReturnIgnored)(Jsi_Interp *interp, Jsi_Func *funcPtr);
    void*(*_Jsi_FunctionPrivData)(Jsi_Func *funcPtr);
    Jsi_RC(*_Jsi_FunctionArguments)(Jsi_Interp *interp, Jsi_Value *func, int *argcPtr);
    Jsi_RC(*_Jsi_FunctionApply)(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this, Jsi_Value **ret);
    Jsi_RC(*_Jsi_FunctionInvoke)(Jsi_Interp *interp, Jsi_Value *tocall, Jsi_Value *args, Jsi_Value **ret, Jsi_Value *_this);
    Jsi_RC(*_Jsi_FunctionInvokeJSON)(Jsi_Interp *interp, Jsi_Value *tocall, const char *json, Jsi_Value **ret);
    int(*_Jsi_FunctionInvokeBool)(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *arg);
    Jsi_RC(*_Jsi_FunctionInvokeString)(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *arg, Jsi_DString *dStr);
    Jsi_Value*(*_Jsi_VarLookup)(Jsi_Interp *interp, const char *varname);
    Jsi_Value*(*_Jsi_NameLookup)(Jsi_Interp *interp, const char *varname);
    Jsi_Value*(*_Jsi_NameLookup2)(Jsi_Interp *interp, const char *name, const char *inObj);
    Jsi_RC(*_Jsi_PkgProvideEx)(Jsi_Interp *interp, const char *name, Jsi_Number version, Jsi_InitProc *initProc, Jsi_PkgOpts* popts);
    Jsi_Number(*_Jsi_PkgRequireEx)(Jsi_Interp *interp, const char *name, Jsi_Number version, Jsi_PkgOpts **poptsPtr);
    Jsi_Number(*_Jsi_PkgVersion)(Jsi_Interp *interp, const char *name, const char **filePtr);
    uint(*_Jsi_NumUtfBytes)(char c);
    uint(*_Jsi_NumUtfChars)(const char *utf, int length);
    uint(*_Jsi_UtfGetIndex)(const char *utf, int index, char outbuf[5]);
    const char*(*_Jsi_UtfAtIndex)(const char *utf, int index);
    uint(*_Jsi_UniCharToUtf)(Jsi_UniChar uc, char *dest);
    uint(*_Jsi_UtfToUniChar)(const char *utf, Jsi_UniChar *ch);
    uint(*_Jsi_UtfToUniCharCase)(const char *utf, Jsi_UniChar *ch, int upper);
    uint(*_Jsi_UtfDecode)(const char *str, char* oututf);
    uint(*_Jsi_UtfEncode)(const char *utf, char *outstr);
    char*(*_Jsi_UtfSubstr)(const char *str, int n, int len, Jsi_DString *dStr);
    int(*_Jsi_UtfIndexToOffset)(const char *utf, int index);
    Jsi_Obj*(*_Jsi_ObjNew)(Jsi_Interp* interp);
    Jsi_Obj*(*_Jsi_ObjNewType)(Jsi_Interp* interp, Jsi_otype type);
    void(*_Jsi_ObjFree)(Jsi_Interp* interp, Jsi_Obj *obj);
    Jsi_Obj*(*_Jsi_ObjNewObj)(Jsi_Interp *interp, Jsi_Value **items, int count);
    Jsi_Obj*(*_Jsi_ObjNewArray)(Jsi_Interp *interp, Jsi_Value **items, int count, int copy);
    bool     (*_Jsi_ObjIsArray)(Jsi_Interp *interp, Jsi_Obj *o);
    void    (*_Jsi_ObjSetLength)(Jsi_Interp *interp, Jsi_Obj *obj, uint len);
    int     (*_Jsi_ObjGetLength)(Jsi_Interp *interp, Jsi_Obj *obj);
    const char*(*_Jsi_ObjTypeStr)(Jsi_Interp *interp, Jsi_Obj *obj);
    Jsi_otype(*_Jsi_ObjTypeGet)(Jsi_Obj *obj);
    void    (*_Jsi_ObjListifyArray)(Jsi_Interp *interp, Jsi_Obj *obj);
    Jsi_RC     (*_Jsi_ObjArraySet)(Jsi_Interp *interp, Jsi_Obj *obj, Jsi_Value *value, int arrayindex);
    Jsi_RC     (*_Jsi_ObjArrayAdd)(Jsi_Interp *interp, Jsi_Obj *o, Jsi_Value *v);
    Jsi_TreeEntry*(*_Jsi_ObjInsert)(Jsi_Interp *interp, Jsi_Obj *obj, const char *key, Jsi_Value *nv, int flags);
    void   (*_Jsi_ObjFromDS)(Jsi_DString *dsPtr, Jsi_Obj *obj);
    Jsi_Value*(*_Jsi_ValueNew)(Jsi_Interp *interp);
    Jsi_Value*(*_Jsi_ValueNew1)(Jsi_Interp *interp);
    void(*_Jsi_ValueFree)(Jsi_Interp *interp, Jsi_Value* v);
    Jsi_Value*(*_Jsi_ValueNewNull)(Jsi_Interp *interp);
    Jsi_Value*(*_Jsi_ValueNewBoolean)(Jsi_Interp *interp, int bval);
    Jsi_Value*(*_Jsi_ValueNewNumber)(Jsi_Interp *interp, Jsi_Number n);
    Jsi_Value*(*_Jsi_ValueNewBlob)(Jsi_Interp *interp, uchar *s, uint len);
    Jsi_Value*(*_Jsi_ValueNewString)(Jsi_Interp *interp, const char *s, int len);
    Jsi_Value*(*_Jsi_ValueNewStringKey)(Jsi_Interp *interp, const char *s);
    Jsi_Value*(*_Jsi_ValueNewStringDup)(Jsi_Interp *interp, const char *s);
    Jsi_Value*(*_Jsi_ValueNewArray)(Jsi_Interp *interp, const char **items, int count);
    Jsi_Value*(*_Jsi_ValueNewObj)(Jsi_Interp *interp, Jsi_Obj *o);
    Jsi_RC(*_Jsi_GetStringFromValue)(Jsi_Interp* interp, Jsi_Value *value, const char **s);
    Jsi_RC(*_Jsi_GetNumberFromValue)(Jsi_Interp* interp, Jsi_Value *value, Jsi_Number *n);
    Jsi_RC(*_Jsi_GetBoolFromValue)(Jsi_Interp* interp, Jsi_Value *value, bool *n);
    Jsi_RC(*_Jsi_GetIntFromValue)(Jsi_Interp* interp, Jsi_Value *value, int *n);
    Jsi_RC(*_Jsi_GetLongFromValue)(Jsi_Interp* interp, Jsi_Value *value, long *n);
    Jsi_RC(*_Jsi_GetWideFromValue)(Jsi_Interp* interp, Jsi_Value *value, Jsi_Wide *n);
    Jsi_RC(*_Jsi_GetDoubleFromValue)(Jsi_Interp* interp, Jsi_Value *value, Jsi_Number *n);
    Jsi_RC(*_Jsi_GetIntFromValueBase)(Jsi_Interp* interp, Jsi_Value *value, int *n, int base, int flags);
    Jsi_RC(*_Jsi_ValueGetBoolean)(Jsi_Interp *interp, Jsi_Value *pv, bool *val);
    Jsi_RC(*_Jsi_ValueGetNumber)(Jsi_Interp *interp, Jsi_Value *pv, Jsi_Number *val);
    bool(*_Jsi_ValueIsType)(Jsi_Interp *interp, Jsi_Value *pv, Jsi_vtype vtype);
    bool(*_Jsi_ValueIsObjType)(Jsi_Interp *interp, Jsi_Value *v, Jsi_otype otype);
    bool(*_Jsi_ValueIsTrue)(Jsi_Interp *interp, Jsi_Value *v);
    bool(*_Jsi_ValueIsFalse)(Jsi_Interp *interp, Jsi_Value *v);
    bool(*_Jsi_ValueIsNumber)(Jsi_Interp *interp, Jsi_Value *pv);
    bool(*_Jsi_ValueIsArray)(Jsi_Interp *interp, Jsi_Value *v);
    bool(*_Jsi_ValueIsBoolean)(Jsi_Interp *interp, Jsi_Value *pv);
    bool(*_Jsi_ValueIsNull)(Jsi_Interp *interp, Jsi_Value *pv);
    bool(*_Jsi_ValueIsUndef)(Jsi_Interp *interp, Jsi_Value *pv);
    bool(*_Jsi_ValueIsFunction)(Jsi_Interp *interp, Jsi_Value *pv);
    bool(*_Jsi_ValueIsString)(Jsi_Interp *interp, Jsi_Value *pv);
    Jsi_Value*(*_Jsi_ValueMakeObject)(Jsi_Interp *interp, Jsi_Value **v, Jsi_Obj *o);
    Jsi_Value*(*_Jsi_ValueMakeArrayObject)(Jsi_Interp *interp, Jsi_Value **v, Jsi_Obj *o);
    Jsi_Value*(*_Jsi_ValueMakeNumber)(Jsi_Interp *interp, Jsi_Value **v, Jsi_Number n);
    Jsi_Value*(*_Jsi_ValueMakeBool)(Jsi_Interp *interp, Jsi_Value **v, int b);
    Jsi_Value*(*_Jsi_ValueMakeString)(Jsi_Interp *interp, Jsi_Value **v, const char *s);
    Jsi_Value*(*_Jsi_ValueMakeStringKey)(Jsi_Interp *interp, Jsi_Value **v, const char *s);
    Jsi_Value*(*_Jsi_ValueMakeBlob)(Jsi_Interp *interp, Jsi_Value **v, uchar *s, int len);
    Jsi_Value*(*_Jsi_ValueMakeNull)(Jsi_Interp *interp, Jsi_Value **v);
    Jsi_Value*(*_Jsi_ValueMakeUndef)(Jsi_Interp *interp, Jsi_Value **v);
    Jsi_Value*(*_Jsi_ValueMakeDStringObject)(Jsi_Interp *interp, Jsi_Value **v, Jsi_DString *dsPtr);
    bool(*_Jsi_ValueIsStringKey)(Jsi_Interp* interp, Jsi_Value *key);
    const char* (*_Jsi_ValueToString)(Jsi_Interp *interp, Jsi_Value *v, int *lenPtr);
    Jsi_RC      (*_Jsi_ValueToBool)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_RC      (*_Jsi_ValueToNumber)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_Number  (*_Jsi_ValueToNumberInt)(Jsi_Interp *interp, Jsi_Value *v, int isInt);
    Jsi_RC      (*_Jsi_ValueToObject)(Jsi_Interp *interp, Jsi_Value *v);
    void    (*_Jsi_ValueReset)(Jsi_Interp *interp, Jsi_Value **v);
    const char*(*_Jsi_ValueGetDString)(Jsi_Interp* interp, Jsi_Value* v, Jsi_DString *dStr, int quote);
    char*   (*_Jsi_ValueString)(Jsi_Interp* interp, Jsi_Value* v, int *lenPtr);
    uchar*  (*_Jsi_ValueBlob)(Jsi_Interp *interp, Jsi_Value* v, int *lenPtr);
    char*   (*_Jsi_ValueGetStringLen)(Jsi_Interp *interp, Jsi_Value *pv, int *lenPtr);
    int     (*_Jsi_ValueStrlen)(Jsi_Value* v);
    void    (*_Jsi_ValueFromDS)(Jsi_Interp *interp, Jsi_DString *dsPtr, Jsi_Value **ret);
    int     (*_Jsi_ValueInstanceOf)( Jsi_Interp *interp, Jsi_Value* v1, Jsi_Value* v2);
    Jsi_Obj*(*_Jsi_ValueGetObj)(Jsi_Interp* interp, Jsi_Value* v);
    Jsi_vtype(*_Jsi_ValueTypeGet)(Jsi_Value *pv);
    const char*(*_Jsi_ValueTypeStr)(Jsi_Interp *interp, Jsi_Value *v);
    int     (*_Jsi_ValueCmp)(Jsi_Interp *interp, Jsi_Value *v1, Jsi_Value* v2, int cmpFlags);
    Jsi_RC(*_Jsi_ValueGetIndex)( Jsi_Interp *interp, Jsi_Value *valPtr, const char **tablePtr, const char *msg, int flags, int *indexPtr);
    Jsi_RC(*_Jsi_ValueArraySort)(Jsi_Interp *interp, Jsi_Value *val, int sortFlags);
    Jsi_Value*(*_Jsi_ValueArrayConcat)(Jsi_Interp *interp, Jsi_Value *arg1, Jsi_Value *arg2);
    Jsi_RC(*_Jsi_ValueArrayPush)(Jsi_Interp *interp, Jsi_Value *arg1, Jsi_Value *arg2);
    Jsi_Value*(*_Jsi_ValueArrayPop)(Jsi_Interp *interp, Jsi_Value *arg1);
    void(*_Jsi_ValueArrayShift)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_Value*(*_Jsi_ValueArrayUnshift)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_Value*(*_Jsi_ValueArrayIndex)(Jsi_Interp *interp, Jsi_Value *args, int index);
    char*(*_Jsi_ValueArrayIndexToStr)(Jsi_Interp *interp, Jsi_Value *args, int index, int *lenPtr);
    Jsi_RC(*_Jsi_ValueInsert)(Jsi_Interp *interp, Jsi_Value *target, const char *key, Jsi_Value *val, int flags);
    int(*_Jsi_ValueGetLength)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_Value*(*_Jsi_ValueObjLookup)(Jsi_Interp *interp, Jsi_Value *target, const char *key, int iskeystr);
    bool(*_Jsi_ValueKeyPresent)(Jsi_Interp *interp, Jsi_Value *target, const char *k, int isstrkey);
    Jsi_RC(*_Jsi_ValueGetKeys)(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *ret);
    void(*_Jsi_ValueCopy)(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from );
    void(*_Jsi_ValueReplace)(Jsi_Interp *interp, Jsi_Value **to, Jsi_Value *from );
    void(*_Jsi_ValueDup2)(Jsi_Interp *interp, Jsi_Value **to, Jsi_Value *from);
    Jsi_Value*(*_Jsi_ValueDupJSON)(Jsi_Interp *interp, Jsi_Value *val);
    void(*_Jsi_ValueMove)(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from);
    bool (*_Jsi_ValueIsEqual)(Jsi_Interp *interp, Jsi_Value *v1, Jsi_Value* v2);
    Jsi_Hash*(*_Jsi_UserObjRegister)(Jsi_Interp *interp, Jsi_UserObjReg *reg);
    Jsi_RC(*_Jsi_UserObjUnregister)(Jsi_Interp *interp, Jsi_UserObjReg *reg);
    int(*_Jsi_UserObjNew)(Jsi_Interp *interp, Jsi_UserObjReg* reg, Jsi_Obj *obj, void *data);
    void*(*_Jsi_UserObjGetData)(Jsi_Interp *interp, Jsi_Value* value, Jsi_Func *funcPtr);
    char*(*_Jsi_NumberToString)(Jsi_Interp *interp, Jsi_Number d, char *buf, int bsiz);
    Jsi_Number(*_Jsi_Version)(void);
    Jsi_Value*(*_Jsi_ReturnValue)(Jsi_Interp *interp);
    Jsi_RC(*_Jsi_Mount)( Jsi_Interp *interp, Jsi_Value *archive, Jsi_Value *mount, Jsi_Value **ret);
    Jsi_Value*(*_Jsi_Executable)(Jsi_Interp *interp);
    Jsi_Regex*(*_Jsi_RegExpNew)(Jsi_Interp *interp, const char *regtxt, int flag);
    void(*_Jsi_RegExpFree)(Jsi_Regex* re);
    Jsi_RC(*_Jsi_RegExpMatch)( Jsi_Interp *interp,  Jsi_Value *pattern, const char *str, int *rc, Jsi_DString *dStr);
    Jsi_RC(*_Jsi_RegExpMatches)(Jsi_Interp *interp, Jsi_Value *pattern, const char *str, int slen, Jsi_Value *ret);
    bool(*_Jsi_GlobMatch)(const char *pattern, const char *string, int nocase);
    char*(*_Jsi_FileRealpath)(Jsi_Interp *interp, Jsi_Value *path, char *newpath);
    char*(*_Jsi_FileRealpathStr)(Jsi_Interp *interp, const char *path, char *newpath);
    char*(*_Jsi_NormalPath)(Jsi_Interp *interp, const char *path, Jsi_DString *dStr);
    char*(*_Jsi_ValueNormalPath)(Jsi_Interp *interp, Jsi_Value *path, Jsi_DString *dStr);
    Jsi_RC(*_Jsi_JSONParse)(Jsi_Interp *interp, const char *js, Jsi_Value **ret, int flags);
    Jsi_RC(*_Jsi_JSONParseFmt)(Jsi_Interp *interp, Jsi_Value **ret, const char *fmt, ...);
    char*(*_Jsi_JSONQuote)(Jsi_Interp *interp, const char *str, int len, Jsi_DString *dStr);
    Jsi_RC(*_Jsi_EvalString)(Jsi_Interp* interp, const char *str, int flags);
    Jsi_RC(*_Jsi_EvalFile)(Jsi_Interp* interp, Jsi_Value *fname, int flags);
    Jsi_RC(*_Jsi_EvalCmdJSON)(Jsi_Interp *interp, const char *cmd, const char *jsonArgs, Jsi_DString *dStr, int flags);
    Jsi_RC(*_Jsi_EvalZip)(Jsi_Interp *interp, const char *exeFile, const char *mntDir, int *jsFound);
    int(*_Jsi_DictionaryCompare)(const char *left, const char *right);
    Jsi_RC(*_Jsi_GetBool)(Jsi_Interp* interp, const char *string, bool *n);
    Jsi_RC(*_Jsi_GetInt)(Jsi_Interp* interp, const char *string, int *n, int base);
    Jsi_RC(*_Jsi_GetWide)(Jsi_Interp* interp, const char *string, Jsi_Wide *n, int base);
    Jsi_RC(*_Jsi_GetDouble)(Jsi_Interp* interp, const char *string, Jsi_Number *n);
    Jsi_RC(*_Jsi_FormatString)(Jsi_Interp *interp, Jsi_Value *args, Jsi_DString *dStr);
    void(*_Jsi_SplitStr)(const char *str, int *argcPtr, char ***argvPtr,  const char *splitCh, Jsi_DString *dStr);
    Jsi_RC(*_Jsi_Sleep)(Jsi_Interp *interp, Jsi_Number dtim);
    void(*_Jsi_Preserve)(Jsi_Interp* interp, void *data);
    void(*_Jsi_Release)(Jsi_Interp* interp, void *data);
    void(*_Jsi_EventuallyFree)(Jsi_Interp* interp, void *data, Jsi_DeleteProc* proc);
    void(*_Jsi_ShiftArgs)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_Value*(*_Jsi_StringSplit)(Jsi_Interp *interp, const char *str, const char *spliton);
    Jsi_RC(*_Jsi_GetIndex)( Jsi_Interp *interp, const char *str, const char **tablePtr, const char *msg, int flags, int *indexPtr);
    void*(*_Jsi_PrototypeGet)(Jsi_Interp *interp, const char *key);
    Jsi_RC (*_Jsi_PrototypeDefine)(Jsi_Interp *interp, const char *key, Jsi_Value *proto);
    Jsi_RC(*_Jsi_PrototypeObjSet)(Jsi_Interp *interp, const char *key, Jsi_Obj *obj);
    Jsi_RC(*_Jsi_ThisDataSet)(Jsi_Interp *interp, Jsi_Value *_this, void *value);
    void*(*_Jsi_ThisDataGet)(Jsi_Interp *interp, Jsi_Value *_this);
    Jsi_RC(*_Jsi_FuncObjToString)(Jsi_Interp *interp, Jsi_Func *f, Jsi_DString *dStr, int flags);
    void*(*_Jsi_UserObjDataFromVar)(Jsi_Interp *interp, const char *var);
    const char*(*_Jsi_KeyAdd)(Jsi_Interp *interp, const char *str);
    const char*(*_Jsi_KeyLookup)(Jsi_Interp *interp, const char *str);
    Jsi_RC(*_Jsi_DatetimeFormat)(Jsi_Interp *interp, Jsi_Number date, const char *fmt, int isUtc, Jsi_DString *dStr);
    Jsi_RC(*_Jsi_DatetimeParse)(Jsi_Interp *interp, const char *str, const char *fmt, int isUtc, Jsi_Number *datePtr, bool noMsg);
    Jsi_Number(*_Jsi_DateTime)(void);
    Jsi_RC(*_Jsi_Encrypt)(Jsi_Interp *interp, Jsi_DString *inout, const char *key, uint keyLen, bool decrypt);
    Jsi_RC(*_Jsi_CryptoHash)(char *outbuf, const char *str, int len, Jsi_CryptoHashType type, uint strength, bool noHex, int *sizPtr);
    Jsi_RC(*_Jsi_Base64)(const char *str, int len, Jsi_DString *buf, bool decode);
    int(*_Jsi_HexStr)(const uchar *data, int len, Jsi_DString *dStr, bool decode);
    char*(*_Jsi_Strrstr)(const char *str, const char *sub);
    uint32_t(*_Jsi_Crc32)(uint32_t crc, const void *ptr, size_t buf_len);
    int(*_Jsi_NumberIsInfinity)(Jsi_Number a);
    bool(*_Jsi_NumberIsEqual)(Jsi_Number n1, Jsi_Number n2);
    bool(*_Jsi_NumberIsFinite)(Jsi_Number value);
    bool(*_Jsi_NumberIsInteger)(Jsi_Number n);
    bool(*_Jsi_NumberIsNaN)(Jsi_Number a);
    bool(*_Jsi_NumberIsNormal)(Jsi_Number a);
    bool(*_Jsi_NumberIsSubnormal)(Jsi_Number a);
    bool(*_Jsi_NumberIsWide)(Jsi_Number n);
    Jsi_Number(*_Jsi_NumberInfinity)(int i);
    Jsi_Number(*_Jsi_NumberNaN)(void);
    void(*_Jsi_NumberDtoA)(Jsi_Interp *interp, Jsi_Number value, char* buf, int bsiz, int prec);
    void(*_Jsi_NumberItoA10)(Jsi_Wide value, char* buf, int bsiz);
    void(*_Jsi_NumberUtoA10)(Jsi_UWide, char* buf, int bsiz);
    Jsi_Hash*(*_Jsi_HashNew)(Jsi_Interp *interp, uint keyType, Jsi_HashDeleteProc *freeProc);
    Jsi_RC(*_Jsi_HashConf)(Jsi_Hash *hashPtr, Jsi_MapOpts *opts, bool set);
    void(*_Jsi_HashDelete)(Jsi_Hash *hashPtr);
    void(*_Jsi_HashClear)(Jsi_Hash *hashPtr);
    Jsi_HashEntry*(*_Jsi_HashSet)(Jsi_Hash *hashPtr, const void *key, void *value);
    void*(*_Jsi_HashGet)(Jsi_Hash *hashPtr, const void *key, int flags);
    bool(*_Jsi_HashUnset)(Jsi_Hash *tbl, const void *key);
    void*(*_Jsi_HashKeyGet)(Jsi_HashEntry *h);
    Jsi_RC(*_Jsi_HashKeysDump)(Jsi_Interp *interp, Jsi_Hash *hashPtr, Jsi_Value **ret, int flags);
    void*(*_Jsi_HashValueGet)(Jsi_HashEntry *h);
    void(*_Jsi_HashValueSet)(Jsi_HashEntry *h, void *value);
    Jsi_HashEntry*(*_Jsi_HashEntryFind)(Jsi_Hash *hashPtr, const void *key);
    Jsi_HashEntry*(*_Jsi_HashEntryNew)(Jsi_Hash *hashPtr, const void *key, bool *isNew);
    int(*_Jsi_HashEntryDelete)(Jsi_HashEntry *entryPtr);
    Jsi_HashEntry*(*_Jsi_HashSearchFirst)(Jsi_Hash *hashPtr, Jsi_HashSearch *searchPtr);
    Jsi_HashEntry*(*_Jsi_HashSearchNext)(Jsi_HashSearch *searchPtr);
    uint(*_Jsi_HashSize)(Jsi_Hash *hashPtr);
    Jsi_Tree*(*_Jsi_TreeNew)(Jsi_Interp *interp, uint keyType, Jsi_TreeDeleteProc *freeProc);
    Jsi_RC(*_Jsi_TreeConf)(Jsi_Tree *treePtr, Jsi_MapOpts *opts, bool set);
    void(*_Jsi_TreeDelete)(Jsi_Tree *treePtr);
    void(*_Jsi_TreeClear)(Jsi_Tree *treePtr);
    Jsi_TreeEntry*(*_Jsi_TreeObjSetValue)(Jsi_Obj* obj, const char *key, Jsi_Value *val, int isstrkey);
    Jsi_Value*    (*_Jsi_TreeObjGetValue)(Jsi_Obj* obj, const char *key, int isstrkey);
    void*(*_Jsi_TreeValueGet)(Jsi_TreeEntry *hPtr);
    void(*_Jsi_TreeValueSet)(Jsi_TreeEntry *hPtr, void *value);
    void*(*_Jsi_TreeKeyGet)(Jsi_TreeEntry *hPtr);
    Jsi_TreeEntry*(*_Jsi_TreeEntryFind)(Jsi_Tree *treePtr, const void *key);
    Jsi_TreeEntry*(*_Jsi_TreeEntryNew)(Jsi_Tree *treePtr, const void *key, bool *isNew);
    int(*_Jsi_TreeEntryDelete)(Jsi_TreeEntry *entryPtr);
    Jsi_TreeEntry*(*_Jsi_TreeSearchFirst)(Jsi_Tree *treePtr, Jsi_TreeSearch *searchPtr, int flags, const void *startKey);
    Jsi_TreeEntry*(*_Jsi_TreeSearchNext)(Jsi_TreeSearch *searchPtr);
    void(*_Jsi_TreeSearchDone)(Jsi_TreeSearch *searchPtr);
    int(*_Jsi_TreeWalk)(Jsi_Tree* treePtr, Jsi_TreeWalkProc* callback, void *data, int flags);
    Jsi_TreeEntry*(*_Jsi_TreeSet)(Jsi_Tree *treePtr, const void *key, void *value);
    void*(*_Jsi_TreeGet)(Jsi_Tree *treePtr, void *key, int flags);
    bool(*_Jsi_TreeUnset)(Jsi_Tree *treePtr, void *key);
    uint(*_Jsi_TreeSize)(Jsi_Tree *treePtr);
    Jsi_Tree*(*_Jsi_TreeFromValue)(Jsi_Interp *interp, Jsi_Value *v);
    Jsi_RC(*_Jsi_TreeKeysDump)(Jsi_Interp *interp, Jsi_Tree *hashPtr, Jsi_Value **ret, int flags);
    Jsi_List*(*_Jsi_ListNew)(Jsi_Interp *interp, Jsi_Wide flags, Jsi_HashDeleteProc *freeProc);
    Jsi_RC(*_Jsi_ListConf)(Jsi_List *list, Jsi_MapOpts *opts, bool set);
    void(*_Jsi_ListDelete)(Jsi_List *list);
    void(*_Jsi_ListClear)(Jsi_List *list);
    void*(*_Jsi_ListValueGet)(Jsi_ListEntry *list);
    void(*_Jsi_ListValueSet)(Jsi_ListEntry *list, const void *value);
    Jsi_ListEntry*(*_Jsi_ListEntryNew)(Jsi_List *list, const void *value, Jsi_ListEntry *before);
    int(*_Jsi_ListEntryDelete)(Jsi_ListEntry *entry);
    Jsi_ListEntry*(*_Jsi_ListSearchFirst)(Jsi_List *list, Jsi_ListSearch *search, int flags);
    Jsi_ListEntry*(*_Jsi_ListSearchNext)(Jsi_ListSearch *search);
    uint(*_Jsi_ListSize)(Jsi_List *list);
    Jsi_ListEntry*(*_Jsi_ListPush)(Jsi_List *list, Jsi_ListEntry *entry, Jsi_ListEntry *before);
    Jsi_ListEntry*(*_Jsi_ListPop)(Jsi_List *list, Jsi_ListEntry *entry);
    Jsi_Stack*(*_Jsi_StackNew)(void);
    void(*_Jsi_StackFree)(Jsi_Stack *stack);
    int(*_Jsi_StackSize)(Jsi_Stack *stack);
    void(*_Jsi_StackPush)(Jsi_Stack *stack, void *element);
    void*(*_Jsi_StackPop)(Jsi_Stack *stack);
    void*(*_Jsi_StackPeek)(Jsi_Stack *stack);
    void*(*_Jsi_StackUnshift)(Jsi_Stack *stack);
    void*(*_Jsi_StackHead)(Jsi_Stack *stack);
    void(*_Jsi_StackFreeElements)(Jsi_Interp *interp, Jsi_Stack *stack, Jsi_DeleteProc *freeFunc);
    Jsi_Map*(*_Jsi_MapNew)(Jsi_Interp *interp, Jsi_Map_Type mapType, Jsi_Key_Type keyType, Jsi_MapDeleteProc *freeProc);
    Jsi_RC(*_Jsi_MapConf)(Jsi_Map *mapPtr, Jsi_MapOpts *opts, bool set);
    void(*_Jsi_MapDelete)(Jsi_Map *mapPtr);
    void(*_Jsi_MapClear)(Jsi_Map *mapPtr);
    Jsi_MapEntry*(*_Jsi_MapSet)(Jsi_Map *mapPtr, const void *key, const void *value);
    void*(*_Jsi_MapGet)(Jsi_Map *mapPtr, const void *key, int flags);
    void*(*_Jsi_MapKeyGet)(Jsi_MapEntry *h, int flags);
    Jsi_RC(*_Jsi_MapKeysDump)(Jsi_Interp *interp, Jsi_Map *mapPtr, Jsi_Value **ret, int flags);
    void*(*_Jsi_MapValueGet)(Jsi_MapEntry *h);
    void(*_Jsi_MapValueSet)(Jsi_MapEntry *h, const void *value);
    Jsi_MapEntry*(*_Jsi_MapEntryFind)(Jsi_Map *mapPtr, const void *key);
    Jsi_MapEntry*(*_Jsi_MapEntryNew)(Jsi_Map *mapPtr, const void *key, bool *isNew);
    int(*_Jsi_MapEntryDelete)(Jsi_MapEntry *entryPtr);
    Jsi_MapEntry*(*_Jsi_MapSearchFirst)(Jsi_Map *mapPtr, Jsi_MapSearch *searchPtr, int flags);
    Jsi_MapEntry*(*_Jsi_MapSearchNext)(Jsi_MapSearch *srchPtr);
    void(*_Jsi_MapSearchDone)(Jsi_MapSearch *searchPtr);
    uint(*_Jsi_MapSize)(Jsi_Map *mapPtr);
    const Jsi_OptionTypedef*(*_Jsi_OptionTypeInfo)(Jsi_OptionId typ);
    Jsi_OptionTypedef*(*_Jsi_TypeLookup)(Jsi_Interp* interp, const char *typ);
    int(*_Jsi_OptionsProcess)(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Value *value, Jsi_Wide flags);
    int(*_Jsi_OptionsProcessJSON)(Jsi_Interp *interp, Jsi_OptionSpec *opts, void *data, const char *json, Jsi_Wide flags);
    Jsi_RC(*_Jsi_OptionsConf)(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Value *value, Jsi_Value **ret, Jsi_Wide flags);
    void(*_Jsi_OptionsFree)(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Wide flags);
    Jsi_RC(*_Jsi_OptionsGet)(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, const char *option, Jsi_Value** valuePtr, Jsi_Wide flags);
    Jsi_RC(*_Jsi_OptionsSet)(Jsi_Interp *interp, Jsi_OptionSpec *specs, void* data, const char *option, Jsi_Value *valuePtr, Jsi_Wide flags);
    Jsi_RC(*_Jsi_OptionsDump)(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *data, Jsi_Value** ret, Jsi_Wide flags);
    int(*_Jsi_OptionsChanged)(Jsi_Interp *interp, Jsi_OptionSpec *specs, const char *pattern, ...);
    bool(*_Jsi_OptionsValid)(Jsi_Interp *interp, Jsi_OptionSpec* spec);
    Jsi_OptionSpec*(*_Jsi_OptionsFind)(Jsi_Interp *interp, Jsi_OptionSpec *specs, const char *name, Jsi_Wide flags);
    Jsi_Value*(*_Jsi_OptionsCustomPrint)(void* clientData, Jsi_Interp *interp, const char *optionName, void *data, int offset);
    Jsi_OptionCustom*(*_Jsi_OptionCustomBuiltin)(Jsi_OptionCustom* cust);
    Jsi_OptionSpec*(*_Jsi_OptionsDup)(Jsi_Interp *interp, const Jsi_OptionSpec *staticSpecs);
    const Jsi_OptionSpec*(*_Jsi_OptionSpecsCached)(Jsi_Interp *interp, const Jsi_OptionSpec *staticSpecs);
    Jsi_RC(*_Jsi_MutexLock)(Jsi_Interp *interp, Jsi_Mutex *mtx);
    void(*_Jsi_MutexUnlock)(Jsi_Interp *interp, Jsi_Mutex *mtx);
    void(*_Jsi_MutexDelete)(Jsi_Interp *interp, Jsi_Mutex *mtx);
    Jsi_Mutex*(*_Jsi_MutexNew)(Jsi_Interp *interp, int timeout, int flags);
    void*(*_Jsi_CurrentThread)(void);
    void*(*_Jsi_InterpThread)(Jsi_Interp *interp);
    Jsi_RC(*_Jsi_LogMsg)(Jsi_Interp *interp, uint level, const char *format,...);
    Jsi_Event*(*_Jsi_EventNew)(Jsi_Interp *interp, Jsi_EventHandlerProc *callback, void* data);
    void(*_Jsi_EventFree)(Jsi_Interp *interp, Jsi_Event* event);
    int(*_Jsi_EventProcess)(Jsi_Interp *interp, int maxEvents);
    void(*_Jsi_JsonInit)(Jsi_JsonParser *parser, Jsi_JsonTok *static_tokens, uint num_tokens);
    void(*_Jsi_JsonReset)(Jsi_JsonParser *parser);
    void(*_Jsi_JsonFree)(Jsi_JsonParser *parser);
    Jsi_JsonErrEnum(*_Jsi_JsonParse)(Jsi_JsonParser *parser, const char *js);
    Jsi_JsonTok*(*_Jsi_JsonGetToken)(Jsi_JsonParser *parser, uint index);
    Jsi_JsonTypeEnum(*_Jsi_JsonGetType)(Jsi_JsonParser *parser, uint index);
    int(*_Jsi_JsonTokLen)(Jsi_JsonParser *parser, uint index);
    const char*(*_Jsi_JsonGetTokstr)(Jsi_JsonParser *parser, const char *js, uint index, uint *len);
    const char*(*_Jsi_JsonGetTypename)(int type);
    const char*(*_Jsi_JsonGetErrname)(int code);
    void(*_Jsi_JsonDump)(Jsi_JsonParser *parser, const char *js);
    Jsi_RC(*_Jsi_FSRegister)(Jsi_Filesystem *fsPtr, void *data);
    Jsi_RC(*_Jsi_FSUnregister)(Jsi_Filesystem *fsPtr);
    Jsi_Channel(*_Jsi_FSNameToChannel)(Jsi_Interp *interp, const char *name);
    char*(*_Jsi_GetCwd)(Jsi_Interp *interp, Jsi_DString *cwdPtr);
    int(*_Jsi_Lstat)(Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf);
    int(*_Jsi_Stat)(Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf);
    int(*_Jsi_Access)(Jsi_Interp *interp, Jsi_Value* path, int mode);
    int(*_Jsi_Remove)(Jsi_Interp *interp, Jsi_Value* path, int flags);
    int(*_Jsi_Rename)(Jsi_Interp *interp, Jsi_Value *src, Jsi_Value *dst);
    int(*_Jsi_Chdir)(Jsi_Interp *interp, Jsi_Value* path);
    Jsi_Channel(*_Jsi_Open)(Jsi_Interp *interp, Jsi_Value *file, const char *modeString);
    int(*_Jsi_Eof)(Jsi_Interp *interp, Jsi_Channel chan);
    int(*_Jsi_Close)(Jsi_Interp *interp, Jsi_Channel chan);
    int(*_Jsi_Read)(Jsi_Interp *interp, Jsi_Channel chan, char *bufPtr, int toRead);
    int(*_Jsi_Write)(Jsi_Interp *interp, Jsi_Channel chan, const char *bufPtr, int slen);
    Jsi_Wide(*_Jsi_Seek)(Jsi_Interp *interp, Jsi_Channel chan, Jsi_Wide offset, int mode);
    Jsi_Wide(*_Jsi_Tell)(Jsi_Interp *interp, Jsi_Channel chan);
    int(*_Jsi_Truncate)(Jsi_Interp *interp, Jsi_Channel chan, uint len);
    Jsi_Wide(*_Jsi_Rewind)(Jsi_Interp *interp, Jsi_Channel chan);
    int(*_Jsi_Flush)(Jsi_Interp *interp, Jsi_Channel chan);
    int(*_Jsi_Getc)(Jsi_Interp *interp, Jsi_Channel chan);
    int(*_Jsi_Printf)(Jsi_Interp *interp, Jsi_Channel chan, const char *fmt, ...);
    int(*_Jsi_Ungetc)(Jsi_Interp *interp, Jsi_Channel chan, int ch);
    char*(*_Jsi_Gets)(Jsi_Interp *interp, Jsi_Channel chan, char *s, int size);
    int(*_Jsi_Puts)(Jsi_Interp *interp, Jsi_Channel chan, const char *str, int size);
    int(*_Jsi_Scandir)(Jsi_Interp *interp, Jsi_Value *path, Jsi_Dirent ***namelist, Jsi_ScandirFilter *filter, Jsi_ScandirCompare *compare );
    int(*_Jsi_SetChannelOption)(Jsi_Interp *interp, Jsi_Channel chan, const char *optionName, const char *newValue);
    char*(*_Jsi_Realpath)(Jsi_Interp *interp, Jsi_Value *path, char *newname);
    int(*_Jsi_Readlink)(Jsi_Interp *interp, Jsi_Value* path, char *ret, int len);
    Jsi_Channel(*_Jsi_GetStdChannel)(Jsi_Interp *interp, int id);
    bool(*_Jsi_FSNative)(Jsi_Interp *interp, Jsi_Value* path);
    int(*_Jsi_Link)(Jsi_Interp *interp, Jsi_Value* src, Jsi_Value *dest, int typ);
    int(*_Jsi_Chmod)(Jsi_Interp *interp, Jsi_Value* path, int mode);
    Jsi_RC(*_Jsi_StubLookup)(Jsi_Interp *interp, const char *name, void **ptr);
    int(*_Jsi_AddAutoFiles)(Jsi_Interp *interp, const char *dir);
    Jsi_Db*(*_Jsi_DbNew)(const char *zFile, int inFlags);
    void*(*_Jsi_DbHandle)(Jsi_Interp *interp, Jsi_Db* db);
    int(*_Jsi_DbQuery)(Jsi_Db *jdb, Jsi_CDataDb *cd, const char *query);
    Jsi_CDataDb*(*_Jsi_CDataLookup)(Jsi_Interp *interp, const char *name);
    Jsi_RC(*_Jsi_CDataRegister)(Jsi_Interp *interp, Jsi_CData_Static *statics);
    Jsi_RC(*_Jsi_CDataStructInit)(Jsi_Interp *interp, uchar* data, const char *sname);
    Jsi_RC(*_Jsi_DllLookup)(Jsi_Interp *interp, const char *module, const char *name, void **ptr);
    Jsi_RC(*_Jsi_LoadLibrary)(Jsi_Interp *interp, const char *pathName, bool noInit);
    Jsi_StructSpec*(*_Jsi_CDataStruct)(Jsi_Interp *interp, const char *name);
    char*(*_Jsi_StrdupLen)(const char *str, int len);
    Jsi_RC(*_Jsi_FileRead)(Jsi_Interp *interp, Jsi_Value *name, Jsi_DString *dStr);
    Jsi_Value*(*_Jsi_ValueNewStringConst)(Jsi_Interp *interp, const char *s, int len);
    Jsi_RC(*_Jsi_PathNormalize)(Jsi_Interp *interp, Jsi_Value **pathPtr);
    Jsi_RC(*_Jsi_ValueInsertArray)(Jsi_Interp *interp, Jsi_Value *target, int index, Jsi_Value *val, int flags);
    void *endPtr;
} Jsi_Stubs;

extern Jsi_Stubs* jsiStubsPtr;

#define __JSI_STUBS_INIT__\
    JSI_STUBS_SIG,    "jsi",    sizeof(Jsi_Stubs),     JSI_STUBS_BLDFLAGS,    JSI_STUBS_MD5,    NULL,\
    Jsi_Stubs__initialize,\
    Jsi_InterpNew,\
    Jsi_InterpDelete,\
    Jsi_InterpOnDelete,\
    Jsi_Interactive,\
    Jsi_InterpGone,\
    Jsi_InterpResult,\
    Jsi_InterpLastError,\
    Jsi_InterpGetData,\
    Jsi_InterpSetData,\
    Jsi_InterpFreeData,\
    Jsi_InterpSafe,\
    Jsi_InterpAccess,\
    Jsi_Main,\
    Jsi_Malloc,\
    Jsi_Calloc,\
    Jsi_Realloc,\
    Jsi_Free,\
    Jsi_ObjIncrRefCount,\
    Jsi_ObjDecrRefCount,\
    Jsi_IncrRefCount,\
    Jsi_DecrRefCount,\
    Jsi_IsShared,\
    Jsi_DeleteData,\
    Jsi_Strlen,\
    Jsi_StrlenSet,\
    Jsi_Strcmp,\
    Jsi_Strncmp,\
    Jsi_Strncasecmp,\
    Jsi_StrcmpDict,\
    Jsi_Strcpy,\
    Jsi_Strncpy,\
    Jsi_Strdup,\
    Jsi_Strrchr,\
    Jsi_Strstr,\
    Jsi_ObjArraySizer,\
    Jsi_Strchr,\
    Jsi_Strpos,\
    Jsi_Strrpos,\
    Jsi_DSAppendLen,\
    Jsi_DSAppend,\
    Jsi_DSFree,\
    Jsi_DSFreeDup,\
    Jsi_DSInit,\
    Jsi_DSLength,\
    Jsi_DSPrintf,\
    Jsi_DSSet,\
    Jsi_DSSetLength,\
    Jsi_DSValue,\
    Jsi_CommandCreate,\
    Jsi_CommandCreateSpecs,\
    Jsi_CommandNewObj,\
    Jsi_CommandInvokeJSON,\
    Jsi_CommandInvoke,\
    Jsi_CommandDelete,\
    Jsi_FunctionGetSpecs,\
    Jsi_FunctionIsConstructor,\
    Jsi_FunctionReturnIgnored,\
    Jsi_FunctionPrivData,\
    Jsi_FunctionArguments,\
    Jsi_FunctionApply,\
    Jsi_FunctionInvoke,\
    Jsi_FunctionInvokeJSON,\
    Jsi_FunctionInvokeBool,\
    Jsi_FunctionInvokeString,\
    Jsi_VarLookup,\
    Jsi_NameLookup,\
    Jsi_NameLookup2,\
    Jsi_PkgProvideEx,\
    Jsi_PkgRequireEx,\
    Jsi_PkgVersion,\
    Jsi_NumUtfBytes,\
    Jsi_NumUtfChars,\
    Jsi_UtfGetIndex,\
    Jsi_UtfAtIndex,\
    Jsi_UniCharToUtf,\
    Jsi_UtfToUniChar,\
    Jsi_UtfToUniCharCase,\
    Jsi_UtfDecode,\
    Jsi_UtfEncode,\
    Jsi_UtfSubstr,\
    Jsi_UtfIndexToOffset,\
    Jsi_ObjNew,\
    Jsi_ObjNewType,\
    Jsi_ObjFree,\
    Jsi_ObjNewObj,\
    Jsi_ObjNewArray,\
    Jsi_ObjIsArray,\
    Jsi_ObjSetLength,\
    Jsi_ObjGetLength,\
    Jsi_ObjTypeStr,\
    Jsi_ObjTypeGet,\
    Jsi_ObjListifyArray,\
    Jsi_ObjArraySet,\
    Jsi_ObjArrayAdd,\
    Jsi_ObjInsert,\
    Jsi_ObjFromDS,\
    Jsi_ValueNew,\
    Jsi_ValueNew1,\
    Jsi_ValueFree,\
    Jsi_ValueNewNull,\
    Jsi_ValueNewBoolean,\
    Jsi_ValueNewNumber,\
    Jsi_ValueNewBlob,\
    Jsi_ValueNewString,\
    Jsi_ValueNewStringKey,\
    Jsi_ValueNewStringDup,\
    Jsi_ValueNewArray,\
    Jsi_ValueNewObj,\
    Jsi_GetStringFromValue,\
    Jsi_GetNumberFromValue,\
    Jsi_GetBoolFromValue,\
    Jsi_GetIntFromValue,\
    Jsi_GetLongFromValue,\
    Jsi_GetWideFromValue,\
    Jsi_GetDoubleFromValue,\
    Jsi_GetIntFromValueBase,\
    Jsi_ValueGetBoolean,\
    Jsi_ValueGetNumber,\
    Jsi_ValueIsType,\
    Jsi_ValueIsObjType,\
    Jsi_ValueIsTrue,\
    Jsi_ValueIsFalse,\
    Jsi_ValueIsNumber,\
    Jsi_ValueIsArray,\
    Jsi_ValueIsBoolean,\
    Jsi_ValueIsNull,\
    Jsi_ValueIsUndef,\
    Jsi_ValueIsFunction,\
    Jsi_ValueIsString,\
    Jsi_ValueMakeObject,\
    Jsi_ValueMakeArrayObject,\
    Jsi_ValueMakeNumber,\
    Jsi_ValueMakeBool,\
    Jsi_ValueMakeString,\
    Jsi_ValueMakeStringKey,\
    Jsi_ValueMakeBlob,\
    Jsi_ValueMakeNull,\
    Jsi_ValueMakeUndef,\
    Jsi_ValueMakeDStringObject,\
    Jsi_ValueIsStringKey,\
    Jsi_ValueToString,\
    Jsi_ValueToBool,\
    Jsi_ValueToNumber,\
    Jsi_ValueToNumberInt,\
    Jsi_ValueToObject,\
    Jsi_ValueReset,\
    Jsi_ValueGetDString,\
    Jsi_ValueString,\
    Jsi_ValueBlob,\
    Jsi_ValueGetStringLen,\
    Jsi_ValueStrlen,\
    Jsi_ValueFromDS,\
    Jsi_ValueInstanceOf,\
    Jsi_ValueGetObj,\
    Jsi_ValueTypeGet,\
    Jsi_ValueTypeStr,\
    Jsi_ValueCmp,\
    Jsi_ValueGetIndex,\
    Jsi_ValueArraySort,\
    Jsi_ValueArrayConcat,\
    Jsi_ValueArrayPush,\
    Jsi_ValueArrayPop,\
    Jsi_ValueArrayShift,\
    Jsi_ValueArrayUnshift,\
    Jsi_ValueArrayIndex,\
    Jsi_ValueArrayIndexToStr,\
    Jsi_ValueInsert,\
    Jsi_ValueGetLength,\
    Jsi_ValueObjLookup,\
    Jsi_ValueKeyPresent,\
    Jsi_ValueGetKeys,\
    Jsi_ValueCopy,\
    Jsi_ValueReplace,\
    Jsi_ValueDup2,\
    Jsi_ValueDupJSON,\
    Jsi_ValueMove,\
    Jsi_ValueIsEqual,\
    Jsi_UserObjRegister,\
    Jsi_UserObjUnregister,\
    Jsi_UserObjNew,\
    Jsi_UserObjGetData,\
    Jsi_NumberToString,\
    Jsi_Version,\
    Jsi_ReturnValue,\
    Jsi_Mount,\
    Jsi_Executable,\
    Jsi_RegExpNew,\
    Jsi_RegExpFree,\
    Jsi_RegExpMatch,\
    Jsi_RegExpMatches,\
    Jsi_GlobMatch,\
    Jsi_FileRealpath,\
    Jsi_FileRealpathStr,\
    Jsi_NormalPath,\
    Jsi_ValueNormalPath,\
    Jsi_JSONParse,\
    Jsi_JSONParseFmt,\
    Jsi_JSONQuote,\
    Jsi_EvalString,\
    Jsi_EvalFile,\
    Jsi_EvalCmdJSON,\
    Jsi_EvalZip,\
    Jsi_DictionaryCompare,\
    Jsi_GetBool,\
    Jsi_GetInt,\
    Jsi_GetWide,\
    Jsi_GetDouble,\
    Jsi_FormatString,\
    Jsi_SplitStr,\
    Jsi_Sleep,\
    Jsi_Preserve,\
    Jsi_Release,\
    Jsi_EventuallyFree,\
    Jsi_ShiftArgs,\
    Jsi_StringSplit,\
    Jsi_GetIndex,\
    Jsi_PrototypeGet,\
    Jsi_PrototypeDefine,\
    Jsi_PrototypeObjSet,\
    Jsi_ThisDataSet,\
    Jsi_ThisDataGet,\
    Jsi_FuncObjToString,\
    Jsi_UserObjDataFromVar,\
    Jsi_KeyAdd,\
    Jsi_KeyLookup,\
    Jsi_DatetimeFormat,\
    Jsi_DatetimeParse,\
    Jsi_DateTime,\
    Jsi_Encrypt,\
    Jsi_CryptoHash,\
    Jsi_Base64,\
    Jsi_HexStr,\
    Jsi_Strrstr,\
    Jsi_Crc32,\
    Jsi_NumberIsInfinity,\
    Jsi_NumberIsEqual,\
    Jsi_NumberIsFinite,\
    Jsi_NumberIsInteger,\
    Jsi_NumberIsNaN,\
    Jsi_NumberIsNormal,\
    Jsi_NumberIsSubnormal,\
    Jsi_NumberIsWide,\
    Jsi_NumberInfinity,\
    Jsi_NumberNaN,\
    Jsi_NumberDtoA,\
    Jsi_NumberItoA10,\
    Jsi_NumberUtoA10,\
    Jsi_HashNew,\
    Jsi_HashConf,\
    Jsi_HashDelete,\
    Jsi_HashClear,\
    Jsi_HashSet,\
    Jsi_HashGet,\
    Jsi_HashUnset,\
    Jsi_HashKeyGet,\
    Jsi_HashKeysDump,\
    Jsi_HashValueGet,\
    Jsi_HashValueSet,\
    Jsi_HashEntryFind,\
    Jsi_HashEntryNew,\
    Jsi_HashEntryDelete,\
    Jsi_HashSearchFirst,\
    Jsi_HashSearchNext,\
    Jsi_HashSize,\
    Jsi_TreeNew,\
    Jsi_TreeConf,\
    Jsi_TreeDelete,\
    Jsi_TreeClear,\
    Jsi_TreeObjSetValue,\
    Jsi_TreeObjGetValue,\
    Jsi_TreeValueGet,\
    Jsi_TreeValueSet,\
    Jsi_TreeKeyGet,\
    Jsi_TreeEntryFind,\
    Jsi_TreeEntryNew,\
    Jsi_TreeEntryDelete,\
    Jsi_TreeSearchFirst,\
    Jsi_TreeSearchNext,\
    Jsi_TreeSearchDone,\
    Jsi_TreeWalk,\
    Jsi_TreeSet,\
    Jsi_TreeGet,\
    Jsi_TreeUnset,\
    Jsi_TreeSize,\
    Jsi_TreeFromValue,\
    Jsi_TreeKeysDump,\
    Jsi_ListNew,\
    Jsi_ListConf,\
    Jsi_ListDelete,\
    Jsi_ListClear,\
    Jsi_ListValueGet,\
    Jsi_ListValueSet,\
    Jsi_ListEntryNew,\
    Jsi_ListEntryDelete,\
    Jsi_ListSearchFirst,\
    Jsi_ListSearchNext,\
    Jsi_ListSize,\
    Jsi_ListPush,\
    Jsi_ListPop,\
    Jsi_StackNew,\
    Jsi_StackFree,\
    Jsi_StackSize,\
    Jsi_StackPush,\
    Jsi_StackPop,\
    Jsi_StackPeek,\
    Jsi_StackUnshift,\
    Jsi_StackHead,\
    Jsi_StackFreeElements,\
    Jsi_MapNew,\
    Jsi_MapConf,\
    Jsi_MapDelete,\
    Jsi_MapClear,\
    Jsi_MapSet,\
    Jsi_MapGet,\
    Jsi_MapKeyGet,\
    Jsi_MapKeysDump,\
    Jsi_MapValueGet,\
    Jsi_MapValueSet,\
    Jsi_MapEntryFind,\
    Jsi_MapEntryNew,\
    Jsi_MapEntryDelete,\
    Jsi_MapSearchFirst,\
    Jsi_MapSearchNext,\
    Jsi_MapSearchDone,\
    Jsi_MapSize,\
    Jsi_OptionTypeInfo,\
    Jsi_TypeLookup,\
    Jsi_OptionsProcess,\
    Jsi_OptionsProcessJSON,\
    Jsi_OptionsConf,\
    Jsi_OptionsFree,\
    Jsi_OptionsGet,\
    Jsi_OptionsSet,\
    Jsi_OptionsDump,\
    Jsi_OptionsChanged,\
    Jsi_OptionsValid,\
    Jsi_OptionsFind,\
    Jsi_OptionsCustomPrint,\
    Jsi_OptionCustomBuiltin,\
    Jsi_OptionsDup,\
    Jsi_OptionSpecsCached,\
    Jsi_MutexLock,\
    Jsi_MutexUnlock,\
    Jsi_MutexDelete,\
    Jsi_MutexNew,\
    Jsi_CurrentThread,\
    Jsi_InterpThread,\
    Jsi_LogMsg,\
    Jsi_EventNew,\
    Jsi_EventFree,\
    Jsi_EventProcess,\
    Jsi_JsonInit,\
    Jsi_JsonReset,\
    Jsi_JsonFree,\
    Jsi_JsonParse,\
    Jsi_JsonGetToken,\
    Jsi_JsonGetType,\
    Jsi_JsonTokLen,\
    Jsi_JsonGetTokstr,\
    Jsi_JsonGetTypename,\
    Jsi_JsonGetErrname,\
    Jsi_JsonDump,\
    Jsi_FSRegister,\
    Jsi_FSUnregister,\
    Jsi_FSNameToChannel,\
    Jsi_GetCwd,\
    Jsi_Lstat,\
    Jsi_Stat,\
    Jsi_Access,\
    Jsi_Remove,\
    Jsi_Rename,\
    Jsi_Chdir,\
    Jsi_Open,\
    Jsi_Eof,\
    Jsi_Close,\
    Jsi_Read,\
    Jsi_Write,\
    Jsi_Seek,\
    Jsi_Tell,\
    Jsi_Truncate,\
    Jsi_Rewind,\
    Jsi_Flush,\
    Jsi_Getc,\
    Jsi_Printf,\
    Jsi_Ungetc,\
    Jsi_Gets,\
    Jsi_Puts,\
    Jsi_Scandir,\
    Jsi_SetChannelOption,\
    Jsi_Realpath,\
    Jsi_Readlink,\
    Jsi_GetStdChannel,\
    Jsi_FSNative,\
    Jsi_Link,\
    Jsi_Chmod,\
    Jsi_StubLookup,\
    Jsi_AddAutoFiles,\
    Jsi_DbNew,\
    Jsi_DbHandle,\
    Jsi_DbQuery,\
    Jsi_CDataLookup,\
    Jsi_CDataRegister,\
    Jsi_CDataStructInit,\
    Jsi_DllLookup,\
    Jsi_LoadLibrary,\
    Jsi_CDataStruct,\
    Jsi_StrdupLen,\
    Jsi_FileRead,\
    Jsi_ValueNewStringConst,\
    Jsi_PathNormalize,\
    Jsi_ValueInsertArray,\
    NULL

#ifdef JSI_USE_STUBS

#define Jsi_InterpNew(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpNew(n0))
#define Jsi_InterpDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpDelete(n0))
#define Jsi_InterpOnDelete(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpOnDelete(n0,n1,n2))
#define Jsi_Interactive(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Interactive(n0,n1))
#define Jsi_InterpGone(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpGone(n0))
#define Jsi_InterpResult(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpResult(n0))
#define Jsi_InterpLastError(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpLastError(n0,n1,n2))
#define Jsi_InterpGetData(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpGetData(n0,n1,n2))
#define Jsi_InterpSetData(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpSetData(n0,n1,n2,n3))
#define Jsi_InterpFreeData(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpFreeData(n0,n1))
#define Jsi_InterpSafe(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpSafe(n0))
#define Jsi_InterpAccess(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpAccess(n0,n1,n2))
#define Jsi_Main(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Main(n0))
#define Jsi_Malloc(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Malloc(n0))
#define Jsi_Calloc(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Calloc(n0,n1))
#define Jsi_Realloc(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Realloc(n0,n1))
#define Jsi_Free(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Free(n0))
#define Jsi_ObjIncrRefCount(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjIncrRefCount(n0,n1))
#define Jsi_ObjDecrRefCount(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjDecrRefCount(n0,n1))
#define Jsi_IncrRefCount(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_IncrRefCount(n0,n1))
#define Jsi_DecrRefCount(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DecrRefCount(n0,n1))
#define Jsi_IsShared(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_IsShared(n0,n1))
#define Jsi_DeleteData(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DeleteData(n0,n1))
#define Jsi_Strlen(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Strlen(n0))
#define Jsi_StrlenSet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_StrlenSet(n0,n1))
#define Jsi_Strcmp(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Strcmp(n0,n1))
#define Jsi_Strncmp(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Strncmp(n0,n1,n2))
#define Jsi_Strncasecmp(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Strncasecmp(n0,n1,n2))
#define Jsi_StrcmpDict(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_StrcmpDict(n0,n1,n2,n3))
#define Jsi_Strcpy(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Strcpy(n0,n1))
#define Jsi_Strncpy(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Strncpy(n0,n1,n2))
#define Jsi_Strdup(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Strdup(n0))
#define Jsi_Strrchr(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Strrchr(n0,n1))
#define Jsi_Strstr(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Strstr(n0,n1))
#define Jsi_ObjArraySizer(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjArraySizer(n0,n1,n2))
#define Jsi_Strchr(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Strchr(n0,n1))
#define Jsi_Strpos(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Strpos(n0,n1,n2,n3))
#define Jsi_Strrpos(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Strrpos(n0,n1,n2,n3))
#define Jsi_DSAppendLen(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_DSAppendLen(n0,n1,n2))
#define Jsi_DSAppend(n0,n1,...) JSISTUBCALL(jsiStubsPtr, _Jsi_DSAppend(n0,n1,##__VA_ARGS__))
#define Jsi_DSFree(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_DSFree(n0))
#define Jsi_DSFreeDup(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_DSFreeDup(n0))
#define Jsi_DSInit(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_DSInit(n0))
#define Jsi_DSLength(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_DSLength(n0))
#define Jsi_DSPrintf(n0,n1,...) JSISTUBCALL(jsiStubsPtr, _Jsi_DSPrintf(n0,n1,##__VA_ARGS__))
#define Jsi_DSSet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DSSet(n0,n1))
#define Jsi_DSSetLength(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DSSetLength(n0,n1))
#define Jsi_DSValue(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_DSValue(n0))
#define Jsi_CommandCreate(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_CommandCreate(n0,n1,n2,n3))
#define Jsi_CommandCreateSpecs(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_CommandCreateSpecs(n0,n1,n2,n3,n4))
#define Jsi_CommandNewObj(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_CommandNewObj(n0,n1,n2,n3,n4))
#define Jsi_CommandInvokeJSON(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_CommandInvokeJSON(n0,n1,n2,n3))
#define Jsi_CommandInvoke(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_CommandInvoke(n0,n1,n2,n3))
#define Jsi_CommandDelete(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_CommandDelete(n0,n1))
#define Jsi_FunctionGetSpecs(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionGetSpecs(n0))
#define Jsi_FunctionIsConstructor(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionIsConstructor(n0))
#define Jsi_FunctionReturnIgnored(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionReturnIgnored(n0,n1))
#define Jsi_FunctionPrivData(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionPrivData(n0))
#define Jsi_FunctionArguments(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionArguments(n0,n1,n2))
#define Jsi_FunctionApply(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionApply(n0,n1,n2,n3))
#define Jsi_FunctionInvoke(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionInvoke(n0,n1,n2,n3,n4))
#define Jsi_FunctionInvokeJSON(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionInvokeJSON(n0,n1,n2,n3))
#define Jsi_FunctionInvokeBool(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionInvokeBool(n0,n1,n2))
#define Jsi_FunctionInvokeString(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_FunctionInvokeString(n0,n1,n2,n3))
#define Jsi_VarLookup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_VarLookup(n0,n1))
#define Jsi_NameLookup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_NameLookup(n0,n1))
#define Jsi_NameLookup2(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_NameLookup2(n0,n1,n2))
#define Jsi_PkgProvideEx(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_PkgProvideEx(n0,n1,n2,n3,n4))
#define Jsi_PkgRequireEx(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_PkgRequireEx(n0,n1,n2,n3))
#define Jsi_PkgVersion(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_PkgVersion(n0,n1,n2))
#define Jsi_NumUtfBytes(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumUtfBytes(n0))
#define Jsi_NumUtfChars(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_NumUtfChars(n0,n1))
#define Jsi_UtfGetIndex(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfGetIndex(n0,n1,n2))
#define Jsi_UtfAtIndex(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfAtIndex(n0,n1))
#define Jsi_UniCharToUtf(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UniCharToUtf(n0,n1))
#define Jsi_UtfToUniChar(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfToUniChar(n0,n1))
#define Jsi_UtfToUniCharCase(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfToUniCharCase(n0,n1,n2))
#define Jsi_UtfDecode(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfDecode(n0,n1))
#define Jsi_UtfEncode(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfEncode(n0,n1))
#define Jsi_UtfSubstr(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfSubstr(n0,n1,n2,n3))
#define Jsi_UtfIndexToOffset(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UtfIndexToOffset(n0,n1))
#define Jsi_ObjNew(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjNew(n0))
#define Jsi_ObjNewType(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjNewType(n0,n1))
#define Jsi_ObjFree(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjFree(n0,n1))
#define Jsi_ObjNewObj(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjNewObj(n0,n1,n2))
#define Jsi_ObjNewArray(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjNewArray(n0,n1,n2,n3))
#define Jsi_ObjIsArray(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjIsArray(n0,n1))
#define Jsi_ObjSetLength(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjSetLength(n0,n1,n2))
#define Jsi_ObjGetLength(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjGetLength(n0,n1))
#define Jsi_ObjTypeStr(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjTypeStr(n0,n1))
#define Jsi_ObjTypeGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjTypeGet(n0))
#define Jsi_ObjListifyArray(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjListifyArray(n0,n1))
#define Jsi_ObjArraySet(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjArraySet(n0,n1,n2,n3))
#define Jsi_ObjArrayAdd(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjArrayAdd(n0,n1,n2))
#define Jsi_ObjInsert(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjInsert(n0,n1,n2,n3,n4))
#define Jsi_ObjFromDS(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ObjFromDS(n0,n1))
#define Jsi_ValueNew(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNew(n0))
#define Jsi_ValueNew1(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNew1(n0))
#define Jsi_ValueFree(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueFree(n0,n1))
#define Jsi_ValueNewNull(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewNull(n0))
#define Jsi_ValueNewBoolean(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewBoolean(n0,n1))
#define Jsi_ValueNewNumber(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewNumber(n0,n1))
#define Jsi_ValueNewBlob(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewBlob(n0,n1,n2))
#define Jsi_ValueNewString(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewString(n0,n1,n2))
#define Jsi_ValueNewStringKey(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewStringKey(n0,n1))
#define Jsi_ValueNewStringDup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewStringDup(n0,n1))
#define Jsi_ValueNewArray(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewArray(n0,n1,n2))
#define Jsi_ValueNewObj(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewObj(n0,n1))
#define Jsi_GetStringFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetStringFromValue(n0,n1,n2))
#define Jsi_GetNumberFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetNumberFromValue(n0,n1,n2))
#define Jsi_GetBoolFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetBoolFromValue(n0,n1,n2))
#define Jsi_GetIntFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetIntFromValue(n0,n1,n2))
#define Jsi_GetLongFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetLongFromValue(n0,n1,n2))
#define Jsi_GetWideFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetWideFromValue(n0,n1,n2))
#define Jsi_GetDoubleFromValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetDoubleFromValue(n0,n1,n2))
#define Jsi_GetIntFromValueBase(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_GetIntFromValueBase(n0,n1,n2,n3,n4))
#define Jsi_ValueGetBoolean(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetBoolean(n0,n1,n2))
#define Jsi_ValueGetNumber(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetNumber(n0,n1,n2))
#define Jsi_ValueIsType(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsType(n0,n1,n2))
#define Jsi_ValueIsObjType(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsObjType(n0,n1,n2))
#define Jsi_ValueIsTrue(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsTrue(n0,n1))
#define Jsi_ValueIsFalse(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsFalse(n0,n1))
#define Jsi_ValueIsNumber(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsNumber(n0,n1))
#define Jsi_ValueIsArray(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsArray(n0,n1))
#define Jsi_ValueIsBoolean(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsBoolean(n0,n1))
#define Jsi_ValueIsNull(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsNull(n0,n1))
#define Jsi_ValueIsUndef(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsUndef(n0,n1))
#define Jsi_ValueIsFunction(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsFunction(n0,n1))
#define Jsi_ValueIsString(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsString(n0,n1))
#define Jsi_ValueMakeObject(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeObject(n0,n1,n2))
#define Jsi_ValueMakeArrayObject(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeArrayObject(n0,n1,n2))
#define Jsi_ValueMakeNumber(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeNumber(n0,n1,n2))
#define Jsi_ValueMakeBool(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeBool(n0,n1,n2))
#define Jsi_ValueMakeString(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeString(n0,n1,n2))
#define Jsi_ValueMakeStringKey(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeStringKey(n0,n1,n2))
#define Jsi_ValueMakeBlob(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeBlob(n0,n1,n2,n3))
#define Jsi_ValueMakeNull(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeNull(n0,n1))
#define Jsi_ValueMakeUndef(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeUndef(n0,n1))
#define Jsi_ValueMakeDStringObject(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMakeDStringObject(n0,n1,n2))
#define Jsi_ValueIsStringKey(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsStringKey(n0,n1))
#define Jsi_ValueToString(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueToString(n0,n1,n2))
#define Jsi_ValueToBool(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueToBool(n0,n1))
#define Jsi_ValueToNumber(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueToNumber(n0,n1))
#define Jsi_ValueToNumberInt(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueToNumberInt(n0,n1,n2))
#define Jsi_ValueToObject(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueToObject(n0,n1))
#define Jsi_ValueReset(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueReset(n0,n1))
#define Jsi_ValueGetDString(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetDString(n0,n1,n2,n3))
#define Jsi_ValueString(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueString(n0,n1,n2))
#define Jsi_ValueBlob(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueBlob(n0,n1,n2))
#define Jsi_ValueGetStringLen(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetStringLen(n0,n1,n2))
#define Jsi_ValueStrlen(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueStrlen(n0))
#define Jsi_ValueFromDS(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueFromDS(n0,n1,n2))
#define Jsi_ValueInstanceOf(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueInstanceOf(n0,n1,n2))
#define Jsi_ValueGetObj(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetObj(n0,n1))
#define Jsi_ValueTypeGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueTypeGet(n0))
#define Jsi_ValueTypeStr(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueTypeStr(n0,n1))
#define Jsi_ValueCmp(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueCmp(n0,n1,n2,n3))
#define Jsi_ValueGetIndex(n0,n1,n2,n3,n4,n5) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetIndex(n0,n1,n2,n3,n4,n5))
#define Jsi_ValueArraySort(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArraySort(n0,n1,n2))
#define Jsi_ValueArrayConcat(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayConcat(n0,n1,n2))
#define Jsi_ValueArrayPush(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayPush(n0,n1,n2))
#define Jsi_ValueArrayPop(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayPop(n0,n1))
#define Jsi_ValueArrayShift(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayShift(n0,n1))
#define Jsi_ValueArrayUnshift(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayUnshift(n0,n1))
#define Jsi_ValueArrayIndex(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayIndex(n0,n1,n2))
#define Jsi_ValueArrayIndexToStr(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueArrayIndexToStr(n0,n1,n2,n3))
#define Jsi_ValueInsert(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueInsert(n0,n1,n2,n3,n4))
#define Jsi_ValueGetLength(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetLength(n0,n1))
#define Jsi_ValueObjLookup(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueObjLookup(n0,n1,n2,n3))
#define Jsi_ValueKeyPresent(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueKeyPresent(n0,n1,n2,n3))
#define Jsi_ValueGetKeys(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueGetKeys(n0,n1,n2))
#define Jsi_ValueCopy(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueCopy(n0,n1,n2))
#define Jsi_ValueReplace(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueReplace(n0,n1,n2))
#define Jsi_ValueDup2(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueDup2(n0,n1,n2))
#define Jsi_ValueDupJSON(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueDupJSON(n0,n1))
#define Jsi_ValueMove(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueMove(n0,n1,n2))
#define Jsi_ValueIsEqual(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueIsEqual(n0,n1,n2))
#define Jsi_UserObjRegister(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UserObjRegister(n0,n1))
#define Jsi_UserObjUnregister(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UserObjUnregister(n0,n1))
#define Jsi_UserObjNew(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_UserObjNew(n0,n1,n2,n3))
#define Jsi_UserObjGetData(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_UserObjGetData(n0,n1,n2))
#define Jsi_NumberToString(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberToString(n0,n1,n2,n3))
#define Jsi_Version(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Version(n0))
#define Jsi_ReturnValue(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ReturnValue(n0))
#define Jsi_Mount(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Mount(n0,n1,n2,n3))
#define Jsi_Executable(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_Executable(n0))
#define Jsi_RegExpNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_RegExpNew(n0,n1,n2))
#define Jsi_RegExpFree(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_RegExpFree(n0))
#define Jsi_RegExpMatch(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_RegExpMatch(n0,n1,n2,n3,n4))
#define Jsi_RegExpMatches(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_RegExpMatches(n0,n1,n2,n3,n4))
#define Jsi_GlobMatch(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GlobMatch(n0,n1,n2))
#define Jsi_FileRealpath(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_FileRealpath(n0,n1,n2))
#define Jsi_FileRealpathStr(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_FileRealpathStr(n0,n1,n2))
#define Jsi_NormalPath(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_NormalPath(n0,n1,n2))
#define Jsi_ValueNormalPath(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNormalPath(n0,n1,n2))
#define Jsi_JSONParse(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_JSONParse(n0,n1,n2,n3))
#define Jsi_JSONParseFmt(n0,n1,n2,...) JSISTUBCALL(jsiStubsPtr, _Jsi_JSONParseFmt(n0,n1,n2,##__VA_ARGS__))
#define Jsi_JSONQuote(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_JSONQuote(n0,n1,n2,n3))
#define Jsi_EvalString(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_EvalString(n0,n1,n2))
#define Jsi_EvalFile(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_EvalFile(n0,n1,n2))
#define Jsi_EvalCmdJSON(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_EvalCmdJSON(n0,n1,n2,n3,n4))
#define Jsi_EvalZip(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_EvalZip(n0,n1,n2,n3))
#define Jsi_DictionaryCompare(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DictionaryCompare(n0,n1))
#define Jsi_GetBool(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetBool(n0,n1,n2))
#define Jsi_GetInt(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_GetInt(n0,n1,n2,n3))
#define Jsi_GetWide(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_GetWide(n0,n1,n2,n3))
#define Jsi_GetDouble(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_GetDouble(n0,n1,n2))
#define Jsi_FormatString(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_FormatString(n0,n1,n2))
#define Jsi_SplitStr(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_SplitStr(n0,n1,n2,n3,n4))
#define Jsi_Sleep(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Sleep(n0,n1))
#define Jsi_Preserve(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Preserve(n0,n1))
#define Jsi_Release(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Release(n0,n1))
#define Jsi_EventuallyFree(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_EventuallyFree(n0,n1,n2))
#define Jsi_ShiftArgs(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ShiftArgs(n0,n1))
#define Jsi_StringSplit(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_StringSplit(n0,n1,n2))
#define Jsi_GetIndex(n0,n1,n2,n3,n4,n5) JSISTUBCALL(jsiStubsPtr, _Jsi_GetIndex(n0,n1,n2,n3,n4,n5))
#define Jsi_PrototypeGet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_PrototypeGet(n0,n1))
#define Jsi_PrototypeDefine(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_PrototypeDefine(n0,n1,n2))
#define Jsi_PrototypeObjSet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_PrototypeObjSet(n0,n1,n2))
#define Jsi_ThisDataSet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ThisDataSet(n0,n1,n2))
#define Jsi_ThisDataGet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ThisDataGet(n0,n1))
#define Jsi_FuncObjToString(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_FuncObjToString(n0,n1,n2,n3))
#define Jsi_UserObjDataFromVar(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_UserObjDataFromVar(n0,n1))
#define Jsi_KeyAdd(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_KeyAdd(n0,n1))
#define Jsi_KeyLookup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_KeyLookup(n0,n1))
#define Jsi_DatetimeFormat(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_DatetimeFormat(n0,n1,n2,n3,n4))
#define Jsi_DatetimeParse(n0,n1,n2,n3,n4,n5) JSISTUBCALL(jsiStubsPtr, _Jsi_DatetimeParse(n0,n1,n2,n3,n4,n5))
#define Jsi_DateTime(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_DateTime(n0))
#define Jsi_Encrypt(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_Encrypt(n0,n1,n2,n3,n4))
#define Jsi_CryptoHash(n0,n1,n2,n3,n4,n5,n6) JSISTUBCALL(jsiStubsPtr, _Jsi_CryptoHash(n0,n1,n2,n3,n4,n5,n6))
#define Jsi_Base64(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Base64(n0,n1,n2,n3))
#define Jsi_HexStr(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_HexStr(n0,n1,n2,n3))
#define Jsi_Strrstr(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Strrstr(n0,n1))
#define Jsi_Crc32(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Crc32(n0,n1,n2))
#define Jsi_NumberIsInfinity(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsInfinity(n0))
#define Jsi_NumberIsEqual(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsEqual(n0,n1))
#define Jsi_NumberIsFinite(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsFinite(n0))
#define Jsi_NumberIsInteger(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsInteger(n0))
#define Jsi_NumberIsNaN(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsNaN(n0))
#define Jsi_NumberIsNormal(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsNormal(n0))
#define Jsi_NumberIsSubnormal(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsSubnormal(n0))
#define Jsi_NumberIsWide(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberIsWide(n0))
#define Jsi_NumberInfinity(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberInfinity(n0))
#define Jsi_NumberNaN(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberNaN(n0))
#define Jsi_NumberDtoA(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberDtoA(n0,n1,n2,n3,n4))
#define Jsi_NumberItoA10(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberItoA10(n0,n1,n2))
#define Jsi_NumberUtoA10(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_NumberUtoA10(n0,n1,n2))
#define Jsi_HashNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_HashNew(n0,n1,n2))
#define Jsi_HashConf(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_HashConf(n0,n1,n2))
#define Jsi_HashDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashDelete(n0))
#define Jsi_HashClear(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashClear(n0))
#define Jsi_HashSet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_HashSet(n0,n1,n2))
#define Jsi_HashGet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_HashGet(n0,n1,n2))
#define Jsi_HashUnset(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_HashUnset(n0,n1))
#define Jsi_HashKeyGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashKeyGet(n0))
#define Jsi_HashKeysDump(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_HashKeysDump(n0,n1,n2,n3))
#define Jsi_HashValueGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashValueGet(n0))
#define Jsi_HashValueSet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_HashValueSet(n0,n1))
#define Jsi_HashEntryFind(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_HashEntryFind(n0,n1))
#define Jsi_HashEntryNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_HashEntryNew(n0,n1,n2))
#define Jsi_HashEntryDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashEntryDelete(n0))
#define Jsi_HashSearchFirst(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_HashSearchFirst(n0,n1))
#define Jsi_HashSearchNext(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashSearchNext(n0))
#define Jsi_HashSize(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_HashSize(n0))
#define Jsi_TreeNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeNew(n0,n1,n2))
#define Jsi_TreeConf(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeConf(n0,n1,n2))
#define Jsi_TreeDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeDelete(n0))
#define Jsi_TreeClear(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeClear(n0))
#define Jsi_TreeObjSetValue(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeObjSetValue(n0,n1,n2,n3))
#define Jsi_TreeObjGetValue(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeObjGetValue(n0,n1,n2))
#define Jsi_TreeValueGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeValueGet(n0))
#define Jsi_TreeValueSet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeValueSet(n0,n1))
#define Jsi_TreeKeyGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeKeyGet(n0))
#define Jsi_TreeEntryFind(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeEntryFind(n0,n1))
#define Jsi_TreeEntryNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeEntryNew(n0,n1,n2))
#define Jsi_TreeEntryDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeEntryDelete(n0))
#define Jsi_TreeSearchFirst(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeSearchFirst(n0,n1,n2,n3))
#define Jsi_TreeSearchNext(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeSearchNext(n0))
#define Jsi_TreeSearchDone(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeSearchDone(n0))
#define Jsi_TreeWalk(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeWalk(n0,n1,n2,n3))
#define Jsi_TreeSet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeSet(n0,n1,n2))
#define Jsi_TreeGet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeGet(n0,n1,n2))
#define Jsi_TreeUnset(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeUnset(n0,n1))
#define Jsi_TreeSize(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeSize(n0))
#define Jsi_TreeFromValue(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeFromValue(n0,n1))
#define Jsi_TreeKeysDump(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_TreeKeysDump(n0,n1,n2,n3))
#define Jsi_ListNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ListNew(n0,n1,n2))
#define Jsi_ListConf(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ListConf(n0,n1,n2))
#define Jsi_ListDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ListDelete(n0))
#define Jsi_ListClear(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ListClear(n0))
#define Jsi_ListValueGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ListValueGet(n0))
#define Jsi_ListValueSet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ListValueSet(n0,n1))
#define Jsi_ListEntryNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ListEntryNew(n0,n1,n2))
#define Jsi_ListEntryDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ListEntryDelete(n0))
#define Jsi_ListSearchFirst(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ListSearchFirst(n0,n1,n2))
#define Jsi_ListSearchNext(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ListSearchNext(n0))
#define Jsi_ListSize(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_ListSize(n0))
#define Jsi_ListPush(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ListPush(n0,n1,n2))
#define Jsi_ListPop(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_ListPop(n0,n1))
#define Jsi_StackNew(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackNew(n0))
#define Jsi_StackFree(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackFree(n0))
#define Jsi_StackSize(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackSize(n0))
#define Jsi_StackPush(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_StackPush(n0,n1))
#define Jsi_StackPop(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackPop(n0))
#define Jsi_StackPeek(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackPeek(n0))
#define Jsi_StackUnshift(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackUnshift(n0))
#define Jsi_StackHead(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_StackHead(n0))
#define Jsi_StackFreeElements(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_StackFreeElements(n0,n1,n2))
#define Jsi_MapNew(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_MapNew(n0,n1,n2,n3))
#define Jsi_MapConf(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_MapConf(n0,n1,n2))
#define Jsi_MapDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapDelete(n0))
#define Jsi_MapClear(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapClear(n0))
#define Jsi_MapSet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_MapSet(n0,n1,n2))
#define Jsi_MapGet(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_MapGet(n0,n1,n2))
#define Jsi_MapKeyGet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_MapKeyGet(n0,n1))
#define Jsi_MapKeysDump(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_MapKeysDump(n0,n1,n2,n3))
#define Jsi_MapValueGet(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapValueGet(n0))
#define Jsi_MapValueSet(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_MapValueSet(n0,n1))
#define Jsi_MapEntryFind(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_MapEntryFind(n0,n1))
#define Jsi_MapEntryNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_MapEntryNew(n0,n1,n2))
#define Jsi_MapEntryDelete(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapEntryDelete(n0))
#define Jsi_MapSearchFirst(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_MapSearchFirst(n0,n1,n2))
#define Jsi_MapSearchNext(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapSearchNext(n0))
#define Jsi_MapSearchDone(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapSearchDone(n0))
#define Jsi_MapSize(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_MapSize(n0))
#define Jsi_OptionTypeInfo(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionTypeInfo(n0))
#define Jsi_TypeLookup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_TypeLookup(n0,n1))
#define Jsi_OptionsProcess(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsProcess(n0,n1,n2,n3,n4))
#define Jsi_OptionsProcessJSON(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsProcessJSON(n0,n1,n2,n3,n4))
#define Jsi_OptionsConf(n0,n1,n2,n3,n4,n5) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsConf(n0,n1,n2,n3,n4,n5))
#define Jsi_OptionsFree(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsFree(n0,n1,n2,n3))
#define Jsi_OptionsGet(n0,n1,n2,n3,n4,n5) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsGet(n0,n1,n2,n3,n4,n5))
#define Jsi_OptionsSet(n0,n1,n2,n3,n4,n5) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsSet(n0,n1,n2,n3,n4,n5))
#define Jsi_OptionsDump(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsDump(n0,n1,n2,n3,n4))
#define Jsi_OptionsChanged(n0,n1,n2,...) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsChanged(n0,n1,n2,##__VA_ARGS__))
#define Jsi_OptionsValid(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsValid(n0,n1))
#define Jsi_OptionsFind(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsFind(n0,n1,n2,n3))
#define Jsi_OptionsCustomPrint(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsCustomPrint(n0,n1,n2,n3,n4))
#define Jsi_OptionCustomBuiltin(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionCustomBuiltin(n0))
#define Jsi_OptionsDup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionsDup(n0,n1))
#define Jsi_OptionSpecsCached(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_OptionSpecsCached(n0,n1))
#define Jsi_MutexLock(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_MutexLock(n0,n1))
#define Jsi_MutexUnlock(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_MutexUnlock(n0,n1))
#define Jsi_MutexDelete(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_MutexDelete(n0,n1))
#define Jsi_MutexNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_MutexNew(n0,n1,n2))
#define Jsi_CurrentThread(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_CurrentThread(n0))
#define Jsi_InterpThread(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_InterpThread(n0))
#define Jsi_LogMsg(n0,n1,n2,...) JSISTUBCALL(jsiStubsPtr, _Jsi_LogMsg(n0,n1,n2,##__VA_ARGS__))
#define Jsi_EventNew(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_EventNew(n0,n1,n2))
#define Jsi_EventFree(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_EventFree(n0,n1))
#define Jsi_EventProcess(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_EventProcess(n0,n1))
#define Jsi_JsonInit(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonInit(n0,n1,n2))
#define Jsi_JsonReset(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonReset(n0))
#define Jsi_JsonFree(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonFree(n0))
#define Jsi_JsonParse(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonParse(n0,n1))
#define Jsi_JsonGetToken(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonGetToken(n0,n1))
#define Jsi_JsonGetType(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonGetType(n0,n1))
#define Jsi_JsonTokLen(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonTokLen(n0,n1))
#define Jsi_JsonGetTokstr(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonGetTokstr(n0,n1,n2,n3))
#define Jsi_JsonGetTypename(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonGetTypename(n0))
#define Jsi_JsonGetErrname(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonGetErrname(n0))
#define Jsi_JsonDump(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_JsonDump(n0,n1))
#define Jsi_FSRegister(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_FSRegister(n0,n1))
#define Jsi_FSUnregister(n0) JSISTUBCALL(jsiStubsPtr, _Jsi_FSUnregister(n0))
#define Jsi_FSNameToChannel(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_FSNameToChannel(n0,n1))
#define Jsi_GetCwd(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_GetCwd(n0,n1))
#define Jsi_Lstat(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Lstat(n0,n1,n2))
#define Jsi_Stat(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Stat(n0,n1,n2))
#define Jsi_Access(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Access(n0,n1,n2))
#define Jsi_Remove(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Remove(n0,n1,n2))
#define Jsi_Rename(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Rename(n0,n1,n2))
#define Jsi_Chdir(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Chdir(n0,n1))
#define Jsi_Open(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Open(n0,n1,n2))
#define Jsi_Eof(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Eof(n0,n1))
#define Jsi_Close(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Close(n0,n1))
#define Jsi_Read(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Read(n0,n1,n2,n3))
#define Jsi_Write(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Write(n0,n1,n2,n3))
#define Jsi_Seek(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Seek(n0,n1,n2,n3))
#define Jsi_Tell(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Tell(n0,n1))
#define Jsi_Truncate(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Truncate(n0,n1,n2))
#define Jsi_Rewind(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Rewind(n0,n1))
#define Jsi_Flush(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Flush(n0,n1))
#define Jsi_Getc(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_Getc(n0,n1))
#define Jsi_Printf(n0,n1,n2,...) JSISTUBCALL(jsiStubsPtr, _Jsi_Printf(n0,n1,n2,##__VA_ARGS__))
#define Jsi_Ungetc(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Ungetc(n0,n1,n2))
#define Jsi_Gets(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Gets(n0,n1,n2,n3))
#define Jsi_Puts(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Puts(n0,n1,n2,n3))
#define Jsi_Scandir(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_Scandir(n0,n1,n2,n3,n4))
#define Jsi_SetChannelOption(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_SetChannelOption(n0,n1,n2,n3))
#define Jsi_Realpath(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Realpath(n0,n1,n2))
#define Jsi_Readlink(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Readlink(n0,n1,n2,n3))
#define Jsi_GetStdChannel(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_GetStdChannel(n0,n1))
#define Jsi_FSNative(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_FSNative(n0,n1))
#define Jsi_Link(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_Link(n0,n1,n2,n3))
#define Jsi_Chmod(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_Chmod(n0,n1,n2))
#define Jsi_StubLookup(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_StubLookup(n0,n1,n2))
#define Jsi_AddAutoFiles(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_AddAutoFiles(n0,n1))
#define Jsi_DbNew(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DbNew(n0,n1))
#define Jsi_DbHandle(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_DbHandle(n0,n1))
#define Jsi_DbQuery(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_DbQuery(n0,n1,n2))
#define Jsi_CDataLookup(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_CDataLookup(n0,n1))
#define Jsi_CDataRegister(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_CDataRegister(n0,n1))
#define Jsi_CDataStructInit(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_CDataStructInit(n0,n1,n2))
#define Jsi_DllLookup(n0,n1,n2,n3) JSISTUBCALL(jsiStubsPtr, _Jsi_DllLookup(n0,n1,n2,n3))
#define Jsi_LoadLibrary(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_LoadLibrary(n0,n1,n2))
#define Jsi_CDataStruct(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_CDataStruct(n0,n1))
#define Jsi_StrdupLen(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_StrdupLen(n0,n1))
#define Jsi_FileRead(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_FileRead(n0,n1,n2))
#define Jsi_ValueNewStringConst(n0,n1,n2) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueNewStringConst(n0,n1,n2))
#define Jsi_PathNormalize(n0,n1) JSISTUBCALL(jsiStubsPtr, _Jsi_PathNormalize(n0,n1))
#define Jsi_ValueInsertArray(n0,n1,n2,n3,n4) JSISTUBCALL(jsiStubsPtr, _Jsi_ValueInsertArray(n0,n1,n2,n3,n4))

#endif

#endif
#ifndef _REGEX_H
#define _REGEX_H

#ifdef __cplusplus
extern "C" {
#endif

#if defined(__WIN32) || defined(__FreeBSD__) || defined(__CYGWIN__)
#define CHARCLASS_NAME_MAX 14
#define RE_DUP_MAX 255
#else
#include <features.h>
#endif

#if __STDC_VERSION__ < 199901L
#define restrict /* nothing */
#endif

#define __NEED_regoff_t
#define __NEED_size_t

//#include <bits/alltypes.h>

typedef struct re_pattern_buffer {
    size_t re_nsub;
    void *__opaque, *__padding[4];
    size_t __nsub2;
    char __padding2;
} regex_t;

#ifndef regoff_t
#define regoff_t int
#endif

typedef struct {
    regoff_t rm_so;
    regoff_t rm_eo;
} regmatch_t;

#define REG_EXTENDED    1
#define REG_ICASE       2
#define REG_NEWLINE     4
#define REG_NOSUB       8

#define REG_NOTBOL      1
#define REG_NOTEOL      2

#define REG_OK          0
#define REG_NOMATCH     1
#define REG_BADPAT      2
#define REG_ECOLLATE    3
#define REG_ECTYPE      4
#define REG_EESCAPE     5
#define REG_ESUBREG     6
#define REG_EBRACK      7
#define REG_EPAREN      8
#define REG_EBRACE      9
#define REG_BADBR       10
#define REG_ERANGE      11
#define REG_ESPACE      12
#define REG_BADRPT      13

#define REG_ENOSYS      -1

int regcomp(regex_t *__restrict, const char *__restrict, int);
int regexec(const regex_t *__restrict, const char *__restrict, size_t, regmatch_t *__restrict, int);
void regfree(regex_t *);

size_t regerror(int, const regex_t *__restrict, char *__restrict, size_t);

#ifdef __cplusplus
}
#endif

#endif
/*
  tre-internal.h - TRE internal definitions

  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef JSI_AMALGAMATION
#include <regex.h>
#endif
#include <wchar.h>
#include <wctype.h>

#undef  TRE_MBSTATE

//#define NDEBUG

#define TRE_REGEX_T_FIELD __opaque
typedef int reg_errcode_t;

typedef wchar_t tre_char_t;

#define DPRINT(msg) do { } while(0)

#define elementsof(x)	( sizeof(x) / sizeof(x[0]) )

#define tre_mbrtowc(pwc, s, n, ps) (mbtowc((pwc), (s), (n)))

/* Wide characters. */
typedef wint_t tre_cint_t;
#define TRE_CHAR_MAX 0x10ffff

#define tre_isalnum iswalnum
#define tre_isalpha iswalpha
#define tre_isblank iswblank
#define tre_iscntrl iswcntrl
#define tre_isdigit iswdigit
#define tre_isgraph iswgraph
#define tre_islower iswlower
#define tre_isprint iswprint
#define tre_ispunct iswpunct
#define tre_isspace iswspace
#define tre_isupper iswupper
#define tre_isxdigit iswxdigit

#define tre_tolower towlower
#define tre_toupper towupper
#define tre_strlen  wcslen

/* Use system provided iswctype() and wctype(). */
typedef wctype_t tre_ctype_t;
#define tre_isctype iswctype
#define tre_ctype   wctype

/* Returns number of bytes to add to (char *)ptr to make it
   properly aligned for the type. */
#define ALIGN(ptr, type) \
  ((((long)ptr) % sizeof(type)) \
   ? (sizeof(type) - (((long)ptr) % sizeof(type))) \
   : 0)

#undef MAX
#undef MIN
#define MAX(a, b) (((a) >= (b)) ? (a) : (b))
#define MIN(a, b) (((a) <= (b)) ? (a) : (b))

/* TNFA transition type. A TNFA state is an array of transitions,
   the terminator is a transition with NULL `state'. */
typedef struct tnfa_transition tre_tnfa_transition_t;

struct tnfa_transition {
  /* Range of accepted characters. */
  tre_cint_t code_min;
  tre_cint_t code_max;
  /* Pointer to the destination state. */
  tre_tnfa_transition_t *state;
  /* ID number of the destination state. */
  int state_id;
  /* -1 terminated array of tags (or NULL). */
  int *tags;
  /* Assertion bitmap. */
  int assertions;
  /* Assertion parameters. */
  union {
    /* Character class assertion. */
    tre_ctype_t rclass;
    /* Back reference assertion. */
    int backref;
  } u;
  /* Negative character class assertions. */
  tre_ctype_t *neg_classes;
};


/* Assertions. */
#define ASSERT_AT_BOL		  1   /* Beginning of line. */
#define ASSERT_AT_EOL		  2   /* End of line. */
#define ASSERT_CHAR_CLASS	  4   /* Character class in `class'. */
#define ASSERT_CHAR_CLASS_NEG	  8   /* Character classes in `neg_classes'. */
#define ASSERT_AT_BOW		 16   /* Beginning of word. */
#define ASSERT_AT_EOW		 32   /* End of word. */
#define ASSERT_AT_WB		 64   /* Word boundary. */
#define ASSERT_AT_WB_NEG	128   /* Not a word boundary. */
#define ASSERT_BACKREF		256   /* A back reference in `backref'. */
#define ASSERT_LAST		256

/* Tag directions. */
typedef enum {
  TRE_TAG_MINIMIZE = 0,
  TRE_TAG_MAXIMIZE = 1
} tre_tag_direction_t;

/* Instructions to compute submatch register values from tag values
   after a successful match.  */
struct tre_submatch_data {
  /* Tag that gives the value for rm_so (submatch start offset). */
  int so_tag;
  /* Tag that gives the value for rm_eo (submatch end offset). */
  int eo_tag;
  /* List of submatches this submatch is contained in. */
  int *parents;
};

typedef struct tre_submatch_data tre_submatch_data_t;


/* TNFA definition. */
typedef struct tnfa tre_tnfa_t;

struct tnfa {
  tre_tnfa_transition_t *transitions;
  unsigned int num_transitions;
  tre_tnfa_transition_t *initial;
  tre_tnfa_transition_t *final;
  tre_submatch_data_t *submatch_data;
  char *firstpos_chars;
  int first_char;
  unsigned int num_submatches;
  tre_tag_direction_t *tag_directions;
  int *minimal_tags;
  int num_tags;
  int num_minimals;
  int end_tag;
  int num_states;
  int cflags;
  int have_backrefs;
  int have_approx;
};

/* from tre-mem.h: */

#define TRE_MEM_BLOCK_SIZE 1024

typedef struct tre_list {
  void *data;
  struct tre_list *next;
} tre_list_t;

typedef struct tre_mem_struct {
  tre_list_t *blocks;
  tre_list_t *current;
  char *ptr;
  size_t n;
  int failed;
  void **provided;
} *tre_mem_t;

#define tre_mem_new_impl   __tre_mem_new_impl
#define tre_mem_alloc_impl __tre_mem_alloc_impl
#define tre_mem_destroy    __tre_mem_destroy

tre_mem_t tre_mem_new_impl(int provided, void *provided_block);
void *tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
			 int zero, size_t size);

/* Returns a new memory allocator or NULL if out of memory. */
#define tre_mem_new()  tre_mem_new_impl(0, NULL)

/* Allocates a block of `size' bytes from `mem'.  Returns a pointer to the
   allocated block or NULL if an underlying malloc() failed. */
#define tre_mem_alloc(mem, size) tre_mem_alloc_impl((tre_mem_t)mem, 0, NULL, 0, size)

/* Allocates a block of `size' bytes from `mem'.  Returns a pointer to the
   allocated block or NULL if an underlying malloc() failed.  The memory
   is set to zero. */
#define tre_mem_calloc(mem, size) tre_mem_alloc_impl((tre_mem_t)mem, 0, NULL, 1, size)

#ifdef TRE_USE_ALLOCA
/* alloca() versions.  Like above, but memory is allocated with alloca()
   instead of malloc(). */

#define tre_mem_newa() \
  tre_mem_new_impl(1, alloca(sizeof(struct tre_mem_struct)))

#define tre_mem_alloca(mem, size)					      \
  ((mem)->n >= (size)							      \
   ? tre_mem_alloc_impl((mem), 1, NULL, 0, (size))			      \
   : tre_mem_alloc_impl((mem), 1, alloca(TRE_MEM_BLOCK_SIZE), 0, (size)))
#endif /* TRE_USE_ALLOCA */


/* Frees the memory allocator and all memory allocated with it. */
void tre_mem_destroy(tre_mem_t mem);

#define xmalloc malloc
#define xcalloc calloc
#define xfree free
#define xrealloc realloc

/*
  regcomp.c - TRE POSIX compatible regex compilation functions.

  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <stdint.h>
#include <ctype.h>

#ifndef JSI_AMALGAMATION
#include <regex.h>
#include "tre.h"
#endif

#include <assert.h>

/***********************************************************************
 from tre-compile.h
***********************************************************************/

typedef struct {
  int position;
  int code_min;
  int code_max;
  int *tags;
  int assertions;
  tre_ctype_t rclass;
  tre_ctype_t *neg_classes;
  int backref;
} tre_pos_and_tags_t;


/***********************************************************************
 from tre-ast.c and tre-ast.h
***********************************************************************/

/* The different AST node types. */
typedef enum {
  LITERAL,
  CATENATION,
  ITERATION,
  UNION
} tre_ast_type_t;

/* Special subtypes of TRE_LITERAL. */
#define EMPTY     -1   /* Empty leaf (denotes empty string). */
#define ASSERTION -2   /* Assertion leaf. */
#define TAG   -3   /* Tag leaf. */
#define BACKREF   -4   /* Back reference leaf. */

#define IS_SPECIAL(x)   ((x)->code_min < 0)
#define IS_EMPTY(x) ((x)->code_min == EMPTY)
#define IS_ASSERTION(x) ((x)->code_min == ASSERTION)
#define IS_TAG(x)   ((x)->code_min == TAG)
#define IS_BACKREF(x)   ((x)->code_min == BACKREF)


/* A generic AST node.  All AST nodes consist of this node on the top
   level with `obj' pointing to the actual content. */
typedef struct {
  tre_ast_type_t type;   /* Type of the node. */
  void *obj;             /* Pointer to actual node. */
  int nullable;
  int submatch_id;
  int num_submatches;
  int num_tags;
  tre_pos_and_tags_t *firstpos;
  tre_pos_and_tags_t *lastpos;
} tre_ast_node_t;


/* A "literal" node.  These are created for assertions, back references,
   tags, matching parameter settings, and all expressions that match one
   character. */
typedef struct {
  long code_min;
  long code_max;
  int position;
  tre_ctype_t rclass;
  tre_ctype_t *neg_classes;
} tre_literal_t;

/* A "catenation" node.  These are created when two regexps are concatenated.
   If there are more than one subexpressions in sequence, the `left' part
   holds all but the last, and `right' part holds the last subexpression
   (catenation is left associative). */
typedef struct {
  tre_ast_node_t *left;
  tre_ast_node_t *right;
} tre_catenation_t;

/* An "iteration" node.  These are created for the "*", "+", "?", and "{m,n}"
   operators. */
typedef struct {
  /* Subexpression to match. */
  tre_ast_node_t *arg;
  /* Minimum number of consecutive matches. */
  int min;
  /* Maximum number of consecutive matches. */
  int max;
  /* If 0, match as many characters as possible, if 1 match as few as
     possible.  Note that this does not always mean the same thing as
     matching as many/few repetitions as possible. */
  unsigned int minimal:1;
} tre_iteration_t;

/* An "union" node.  These are created for the "|" operator. */
typedef struct {
  tre_ast_node_t *left;
  tre_ast_node_t *right;
} tre_union_t;


static tre_ast_node_t *
tre_ast_new_node(tre_mem_t mem, int type, void *obj)
{
    tre_ast_node_t *node = (tre_ast_node_t *)tre_mem_calloc(mem, sizeof *node);
    if (!node || !obj)
        return 0;
    node->obj = obj;
    node->type = (tre_ast_type_t)type;
    node->nullable = -1;
    node->submatch_id = -1;
    return node;
}

static tre_ast_node_t *
tre_ast_new_literal(tre_mem_t mem, int code_min, int code_max, int position)
{
    tre_ast_node_t *node;
    tre_literal_t *lit;

    lit = (tre_literal_t *)tre_mem_calloc(mem, sizeof *lit);
    node = tre_ast_new_node(mem, LITERAL, lit);
    if (!node)
        return 0;
    lit->code_min = code_min;
    lit->code_max = code_max;
    lit->position = position;
    return node;
}

static tre_ast_node_t *
tre_ast_new_iter(tre_mem_t mem, tre_ast_node_t *arg, int min, int max, int minimal)
{
    tre_ast_node_t *node;
    tre_iteration_t *iter;

    iter = (tre_iteration_t *)tre_mem_calloc(mem, sizeof *iter);
    node = tre_ast_new_node(mem, ITERATION, iter);
    if (!node)
        return 0;
    iter->arg = arg;
    iter->min = min;
    iter->max = max;
    iter->minimal = minimal;
    node->num_submatches = arg->num_submatches;
    return node;
}

static tre_ast_node_t *
tre_ast_new_union(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right)
{
    tre_ast_node_t *node;
    tre_union_t *un;

    if (!left)
        return right;
    un = (tre_union_t *)tre_mem_calloc(mem, sizeof *un);
    node = tre_ast_new_node(mem, UNION, un);
    if (!node || !right)
        return 0;
    un->left = left;
    un->right = right;
    node->num_submatches = left->num_submatches + right->num_submatches;
    return node;
}

static tre_ast_node_t *
tre_ast_new_catenation(tre_mem_t mem, tre_ast_node_t *left, tre_ast_node_t *right)
{
    tre_ast_node_t *node;
    tre_catenation_t *cat;

    if (!left)
        return right;
    cat =(tre_catenation_t *) tre_mem_calloc(mem, sizeof *cat);
    node = tre_ast_new_node(mem, CATENATION, cat);
    if (!node)
        return 0;
    cat->left = left;
    cat->right = right;
    node->num_submatches = left->num_submatches + right->num_submatches;
    return node;
}


/***********************************************************************
 from tre-stack.c and tre-stack.h
***********************************************************************/

typedef struct tre_stack_rec tre_stack_t;

/* Creates a new stack object.  `size' is initial size in bytes, `max_size'
   is maximum size, and `increment' specifies how much more space will be
   allocated with realloc() if all space gets used up.  Returns the stack
   object or NULL if out of memory. */
static tre_stack_t *
tre_stack_new(int size, int max_size, int increment);

/* Frees the stack object. */
static void
tre_stack_destroy(tre_stack_t *s);

/* Returns the current number of objects in the stack. */
static int
tre_stack_num_objects(tre_stack_t *s);

/* Each tre_stack_push_*(tre_stack_t *s, <type> value) function pushes
   `value' on top of stack `s'.  Returns REG_ESPACE if out of memory.
   This tries to realloc() more space before failing if maximum size
   has not yet been reached.  Returns REG_OK if successful. */
#define declare_pushf(typetag, type)                          \
  static reg_errcode_t tre_stack_push_ ## typetag(tre_stack_t *s, type value)

declare_pushf(voidptr, void *);
declare_pushf(int, int);

/* Each tre_stack_pop_*(tre_stack_t *s) function pops the topmost
   element off of stack `s' and returns it.  The stack must not be
   empty. */
#define declare_popf(typetag, type)       \
  static type tre_stack_pop_ ## typetag(tre_stack_t *s)

declare_popf(voidptr, void *);
declare_popf(int, int);

/* Just to save some typing. */
#define STACK_PUSH(s, typetag, value)                         \
  do                                          \
    {                                         \
      status = tre_stack_push_ ## typetag(s, value);                  \
    }                                         \
  while (/*CONSTCOND*/0)

#define STACK_PUSHX(s, typetag, value)                        \
  {                                       \
    status = tre_stack_push_ ## typetag(s, value);                \
    if (status != REG_OK)                             \
      break;                                      \
  }

#define STACK_PUSHR(s, typetag, value)                        \
  {                                       \
    reg_errcode_t _status;                            \
    _status = tre_stack_push_ ## typetag(s, value);               \
    if (_status != REG_OK)                            \
      return _status;                                 \
  }

union tre_stack_item {
  void *voidptr_value;
  int int_value;
};

struct tre_stack_rec {
  int size;
  int max_size;
  int increment;
  int ptr;
  union tre_stack_item *stack;
};


static tre_stack_t *
tre_stack_new(int size, int max_size, int increment)
{
  tre_stack_t *s;

  s = (tre_stack_t *)xmalloc(sizeof(*s));
  if (s != NULL)
    {
      s->stack = (union tre_stack_item*)xmalloc(sizeof(*s->stack) * size);
      if (s->stack == NULL)
    {
      xfree(s);
      return NULL;
    }
      s->size = size;
      s->max_size = max_size;
      s->increment = increment;
      s->ptr = 0;
    }
  return s;
}

static void
tre_stack_destroy(tre_stack_t *s)
{
  xfree(s->stack);
  xfree(s);
}

static int
tre_stack_num_objects(tre_stack_t *s)
{
  return s->ptr;
}

static reg_errcode_t
tre_stack_push(tre_stack_t *s, union tre_stack_item value)
{
  if (s->ptr < s->size)
    {
      s->stack[s->ptr] = value;
      s->ptr++;
    }
  else
    {
      if (s->size >= s->max_size)
    {
      return REG_ESPACE;
    }
      else
    {
      union tre_stack_item *new_buffer;
      int new_size;
      new_size = s->size + s->increment;
      if (new_size > s->max_size)
        new_size = s->max_size;
      new_buffer = (union tre_stack_item *)xrealloc(s->stack, sizeof(*new_buffer) * new_size);
      if (new_buffer == NULL)
        {
          return REG_ESPACE;
        }
      assert(new_size > s->size);
      s->size = new_size;
      s->stack = new_buffer;
      tre_stack_push(s, value);
    }
    }
  return REG_OK;
}

#define define_pushf(typetag, type)  \
  declare_pushf(typetag, type) {     \
    union tre_stack_item item;       \
    item.typetag ## _value = value;  \
    return tre_stack_push(s, item);  \
}

define_pushf(int, int)
define_pushf(voidptr, void *)

#define define_popf(typetag, type)          \
  declare_popf(typetag, type) {             \
    return s->stack[--s->ptr].typetag ## _value;    \
  }

define_popf(int, int)
define_popf(voidptr, void *)


/***********************************************************************
 from tre-parse.c and tre-parse.h
***********************************************************************/

/* Parse context. */
typedef struct {
    /* Memory allocator. The AST is allocated using this. */
    tre_mem_t mem;
    /* Stack used for keeping track of regexp syntax. */
    tre_stack_t *stack;
    /* The parsed node after a parse function returns. */
    tre_ast_node_t *n;
    /* Position in the regexp pattern after a parse function returns. */
    const char *s;
    /* The first character of the regexp. */
    const char *re;
    /* Current submatch ID. */
    int submatch_id;
    /* Current position (number of literal). */
    int position;
    /* The highest back reference or -1 if none seen so far. */
    int max_backref;
    /* Compilation flags. */
    int cflags;
} tre_parse_ctx_t;

/* Some macros for expanding \w, \s, etc. */
static const struct {
    char c;
    const char *expansion;
} tre_macros[] = {
    {'t', "\t"}, {'n', "\n"}, {'r', "\r"},
    {'f', "\f"}, {'a', "\a"}, {'e', "\033"},
    {'w', "[[:alnum:]_]"}, {'W', "[^[:alnum:]_]"}, {'s', "[[:space:]]"},
    {'S', "[^[:space:]]"}, {'d', "[[:digit:]]"}, {'D', "[^[:digit:]]"},
    { 0, 0 }
};

/* Expands a macro delimited by `regex' and `regex_end' to `buf', which
   must have at least `len' items.  Sets buf[0] to zero if the there
   is no match in `tre_macros'. */
static const char *tre_expand_macro(const char *s)
{
    int i;
    for (i = 0; tre_macros[i].c && tre_macros[i].c != *s; i++);
    return tre_macros[i].expansion;
}

static int
tre_compare_lit(const void *a, const void *b)
{
    const tre_literal_t *const *la = (const tre_literal_t *const *)a;
    const tre_literal_t *const *lb = (const tre_literal_t *const *)b;
    /* assumes the range of valid code_min is < INT_MAX */
    return la[0]->code_min - lb[0]->code_min;
}

struct literals {
    tre_mem_t mem;
    tre_literal_t **a;
    int len;
    int cap;
};

static tre_literal_t *tre_new_lit(struct literals *p)
{
    tre_literal_t **a;
    if (p->len >= p->cap) {
        if (p->cap >= 1<<15)
            return 0;
        p->cap *= 2;
        a = (tre_literal_t**)xrealloc(p->a, p->cap * sizeof *p->a);
        if (!a)
            return 0;
        p->a = a;
    }
    a = p->a + p->len++;
    *a = (tre_literal_t*)tre_mem_calloc((tre_literal_t*)p->mem, sizeof **a);
    return *a;
}

static int add_icase_literals(struct literals *ls, int min, int max)
{
    tre_literal_t *lit;
    int b, e, c;
    for (c=min; c<=max; ) {
        /* assumes islower(c) and isupper(c) are exclusive
           and toupper(c)!=c if islower(c).
           multiple opposite case characters are not supported */
        if (tre_islower(c)) {
            b = e = tre_toupper(c);
            for (c++, e++; c<=max; c++, e++)
                if ((int)tre_toupper(c) != e) break;
        } else if (tre_isupper(c)) {
            b = e = tre_tolower(c);
            for (c++, e++; c<=max; c++, e++)
                if ((int)tre_tolower(c) != e) break;
        } else {
            c++;
            continue;
        }
        lit = tre_new_lit(ls);
        if (!lit)
            return -1;
        lit->code_min = b;
        lit->code_max = e-1;
        lit->position = -1;
    }
    return 0;
}


/* Maximum number of character classes in a negated bracket expression. */
#define MAX_NEG_CLASSES 64

struct neg {
    int negate;
    int len;
    tre_ctype_t a[MAX_NEG_CLASSES];
};

// TODO: parse bracket into a set of non-overlapping [lo,hi] ranges

/*
bracket grammar:
Bracket  =  '[' List ']'  |  '[^' List ']'
List     =  Term  |  List Term
Term     =  Char  |  Range  |  Chclass  |  Eqclass
Range    =  Char '-' Char  |  Char '-' '-'
Char     =  Coll  |  coll_single
Meta     =  ']'  |  '-'
Coll     =  '[.' coll_single '.]'  |  '[.' coll_multi '.]'  |  '[.' Meta '.]'
Eqclass  =  '[=' coll_single '=]'  |  '[=' coll_multi '=]'
Chclass  =  '[:' rclass ':]'

coll_single is a single char collating element but it can be
 '-' only at the beginning or end of a List and
 ']' only at the beginning of a List and
 '^' anywhere except after the openning '['
*/

static reg_errcode_t parse_bracket_terms(tre_parse_ctx_t *ctx, const char *s, struct literals *ls, struct neg *neg)
{
    const char *start = s;
    tre_ctype_t rclass;
    int min, max;
    wchar_t wc;
    int len;

    for (;;) {
        rclass = 0;
        len = mbtowc(&wc, s, -1);
        if (len <= 0)
            return *s ? REG_BADPAT : REG_EBRACK;
        if (*s == ']' && s != start) {
            ctx->s = s+1;
            return REG_OK;
        }
        if (*s == '-' && s != start && s[1] != ']' &&
            /* extension: [a-z--@] is accepted as [a-z]|[--@] */
            (s[1] != '-' || s[2] == ']'))
            return REG_ERANGE;
        if (*s == '[' && (s[1] == '.' || s[1] == '='))
            /* collating symbols and equivalence classes are not supported */
            return REG_ECOLLATE;
        if (*s == '[' && s[1] == ':') {
            char tmp[CHARCLASS_NAME_MAX+1];
            s += 2;
            for (len=0; len < CHARCLASS_NAME_MAX && s[len]; len++) {
                if (s[len] == ':') {
                    memcpy(tmp, s, len);
                    tmp[len] = 0;
                    rclass = tre_ctype(tmp);
                    break;
                }
            }
            if (!rclass || s[len+1] != ']')
                return REG_ECTYPE;
            min = 0;
            max = TRE_CHAR_MAX;
            s += len+2;
        } else {
            min = max = wc;
            s += len;
            if (*s == '-' && s[1] != ']') {
                s++;
                len = mbtowc(&wc, s, -1);
                max = wc;
                /* XXX - Should use collation order instead of
                   encoding values in character ranges. */
                if (len <= 0 || min > max)
                    return REG_ERANGE;
                s += len;
            }
        }

        if (rclass && neg->negate) {
            if (neg->len >= MAX_NEG_CLASSES)
                return REG_ESPACE;
            neg->a[neg->len++] = rclass;
        } else  {
            tre_literal_t *lit = tre_new_lit(ls);
            if (!lit)
                return REG_ESPACE;
            lit->code_min = min;
            lit->code_max = max;
            lit->rclass = rclass;
            lit->position = -1;

            /* Add opposite-case codepoints if REG_ICASE is present.
               It seems that POSIX requires that bracket negation
               should happen before case-folding, but most practical
               implementations do it the other way around. Changing
               the order would need efficient representation of
               case-fold ranges and bracket range sets even with
               simple patterns so this is ok for now. */
            if (ctx->cflags & REG_ICASE && !rclass)
                if (add_icase_literals(ls, min, max))
                    return REG_ESPACE;
        }
    }
}

static reg_errcode_t parse_bracket(tre_parse_ctx_t *ctx, const char *s)
{
    int i, max, min, negmax, negmin;
    tre_ast_node_t *node = 0, *n;
    tre_ctype_t *nc = 0;
    tre_literal_t *lit;
    struct literals ls;
    struct neg neg;
    reg_errcode_t err;

    ls.mem = ctx->mem;
    ls.len = 0;
    ls.cap = 32;
    ls.a = (tre_literal_t**)xmalloc(ls.cap * sizeof *ls.a);
    if (!ls.a)
        return REG_ESPACE;
    neg.len = 0;
    neg.negate = *s == '^';
    if (neg.negate)
        s++;

    err = parse_bracket_terms(ctx, s, &ls, &neg);
    if (err != REG_OK)
        goto parse_bracket_done;

    if (neg.negate) {
        /* Sort the array if we need to negate it. */
        qsort(ls.a, ls.len, sizeof *ls.a, tre_compare_lit);
        /* extra lit for the last negated range */
        lit = tre_new_lit(&ls);
        if (!lit) {
            err = REG_ESPACE;
            goto parse_bracket_done;
        }
        lit->code_min = TRE_CHAR_MAX+1;
        lit->code_max = TRE_CHAR_MAX+1;
        lit->position = -1;
        /* negated classes */
        if (neg.len) {
            nc = (tre_ctype_t*)tre_mem_alloc(ctx->mem, (neg.len+1)*sizeof *neg.a);
            if (!nc) {
                err = REG_ESPACE;
                goto parse_bracket_done;
            }
            memcpy(nc, neg.a, neg.len*sizeof *neg.a);
            nc[neg.len] = 0;
        }
    }

    /* Build a union of the items in the array, negated if necessary. */
    negmax = negmin = 0;
    for (i = 0; i < ls.len; i++) {
        lit = ls.a[i];
        min = lit->code_min;
        max = lit->code_max;
        if (neg.negate) {
            if (min <= negmin) {
                /* Overlap. */
                negmin = MAX(max + 1, negmin);
                continue;
            }
            negmax = min - 1;
            lit->code_min = negmin;
            lit->code_max = negmax;
            negmin = max + 1;
        }
        lit->position = ctx->position;
        lit->neg_classes = nc;
        n = tre_ast_new_node(ctx->mem, LITERAL, lit);
        node = tre_ast_new_union(ctx->mem, node, n);
        if (!node) {
            err = REG_ESPACE;
            break;
        }
    }

parse_bracket_done:
    xfree(ls.a);
    ctx->position++;
    ctx->n = node;
    return err;
}

static const char *parse_dup_count(const char *s, int *n)
{
    *n = -1;
    if (!isdigit(*s))
        return s;
    *n = 0;
    for (;;) {
        *n = 10 * *n + (*s - '0');
        s++;
        if (!isdigit(*s) || *n > RE_DUP_MAX)
            break;
    }
    return s;
}

static reg_errcode_t parse_dup(tre_parse_ctx_t *ctx, const char *s)
{
    int min, max;

    s = parse_dup_count(s, &min);
    if (*s == ',')
        s = parse_dup_count(s+1, &max);
    else
        max = min;

    if (
        (max < min && max >= 0) ||
        max > RE_DUP_MAX ||
        min > RE_DUP_MAX ||
        min < 0 ||
        (!(ctx->cflags & REG_EXTENDED) && *s++ != '\\') ||
        *s++ != '}'
    )
        return REG_BADBR;

    if (min == 0 && max == 0)
        ctx->n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1);
    else
        ctx->n = tre_ast_new_iter(ctx->mem, ctx->n, min, max, 0);
    if (!ctx->n)
        return REG_ESPACE;
    ctx->s = s;
    return REG_OK;
}

static int hexval(unsigned c)
{
    if (c-'0'<10) return c-'0';
    c |= 32;
    if (c-'a'<6) return c-'a'+10;
    return -1;
}

static reg_errcode_t marksub(tre_parse_ctx_t *ctx, tre_ast_node_t *node, int subid)
{
    if (node->submatch_id >= 0) {
        tre_ast_node_t *n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1);
        if (!n)
            return REG_ESPACE;
        n = tre_ast_new_catenation(ctx->mem, n, node);
        if (!n)
            return REG_ESPACE;
        n->num_submatches = node->num_submatches;
        node = n;
    }
    node->submatch_id = subid;
    node->num_submatches++;
    ctx->n = node;
    return REG_OK;
}

/*
BRE grammar:
Regex  =  Branch  |  '^'  |  '$'  |  '^$'  |  '^' Branch  |  Branch '$'  |  '^' Branch '$'
Branch =  Atom  |  Branch Atom
Atom   =  char  |  quoted_char  |  '.'  |  Bracket  |  Atom Dup  |  '\(' Branch '\)'  |  back_ref
Dup    =  '*'  |  '\{' Count '\}'  |  '\{' Count ',\}'  |  '\{' Count ',' Count '\}'

(leading ^ and trailing $ in a sub expr may be an anchor or literal as well)

ERE grammar:
Regex  =  Branch  |  Regex '|' Branch
Branch =  Atom  |  Branch Atom
Atom   =  char  |  quoted_char  |  '.'  |  Bracket  |  Atom Dup  |  '(' Regex ')'  |  '^'  |  '$'
Dup    =  '*'  |  '+'  |  '?'  |  '{' Count '}'  |  '{' Count ',}'  |  '{' Count ',' Count '}'

(a*+?, ^*, $+, \X, {, (|a) are unspecified)
*/

static reg_errcode_t parse_atom(tre_parse_ctx_t *ctx, const char *s)
{
    int len, ere = ctx->cflags & REG_EXTENDED;
    const char *p;
    tre_ast_node_t *node;
    wchar_t wc;
    switch (*s) {
    case '[':
        return parse_bracket(ctx, s+1);
    case '\\':
        p = tre_expand_macro(s+1);
        if (p) {
            /* assume \X expansion is a single atom */
            reg_errcode_t err = parse_atom(ctx, p);
            ctx->s = s+2;
            return err;
        }
        /* extensions: \b, \B, \<, \>, \xHH \x{HHHH} */
        switch (*++s) {
        case 0:
            return REG_EESCAPE;
        case 'b':
            node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_WB, -1);
            break;
        case 'B':
            node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_WB_NEG, -1);
            break;
        case '<':
            node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_BOW, -1);
            break;
        case '>':
            node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_EOW, -1);
            break;
        case 'x': {
            s++;
            int i, v = 0, c;
            len = 2;
            if (*s == '{') {
                len = 8;
                s++;
            }
            for (i=0; i<len && v<0x110000; i++) {
                c = hexval(s[i]);
                if (c < 0) break;
                v = 16*v + c;
            }
            s += i;
            if (len == 8) {
                if (*s != '}')
                    return REG_EBRACE;
                s++;
            }
            node = tre_ast_new_literal(ctx->mem, v, v, ctx->position);
            ctx->position++;
            s--;
            break;
        }
        default:
            if (isdigit(*s)) {
                /* back reference */
                int val = *s - '0';
                node = tre_ast_new_literal(ctx->mem, BACKREF, val, ctx->position);
                ctx->max_backref = MAX(val, ctx->max_backref);
            } else {
                /* extension: accept unknown escaped char
                   as a literal */
                node = tre_ast_new_literal(ctx->mem, *s, *s, ctx->position);
            }
            ctx->position++;
        }
        s++;
        break;
    case '.':
        if (ctx->cflags & REG_NEWLINE) {
            tre_ast_node_t *tmp1, *tmp2;
            tmp1 = tre_ast_new_literal(ctx->mem, 0, '\n'-1, ctx->position++);
            tmp2 = tre_ast_new_literal(ctx->mem, '\n'+1, TRE_CHAR_MAX, ctx->position++);
            if (tmp1 && tmp2)
                node = tre_ast_new_union(ctx->mem, tmp1, tmp2);
            else
                node = 0;
        } else {
            node = tre_ast_new_literal(ctx->mem, 0, TRE_CHAR_MAX, ctx->position++);
        }
        s++;
        break;
    case '^':
        /* '^' has a special meaning everywhere in EREs, and at beginning of BRE. */
        if (!ere && s != ctx->re)
            goto parse_literal;
        node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_BOL, -1);
        s++;
        break;
    case '$':
        /* '$' is special everywhere in EREs, and in the end of the string in BREs. */
        if (!ere && s[1])
            goto parse_literal;
        node = tre_ast_new_literal(ctx->mem, ASSERTION, ASSERT_AT_EOL, -1);
        s++;
        break;
    case '*':
    case '|':
    case '{':
    case '+':
    case '?':
        if (!ere)
            goto parse_literal;
    case 0:
        node = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1);
        break;
    default:
parse_literal:
        len = mbtowc(&wc, s, -1);
        if (len < 0)
            return REG_BADPAT;
        if (ctx->cflags & REG_ICASE && (tre_isupper(wc) || tre_islower(wc))) {
            tre_ast_node_t *tmp1, *tmp2;
            /* multiple opposite case characters are not supported */
            tmp1 = tre_ast_new_literal(ctx->mem, tre_toupper(wc), tre_toupper(wc), ctx->position);
            tmp2 = tre_ast_new_literal(ctx->mem, tre_tolower(wc), tre_tolower(wc), ctx->position);
            if (tmp1 && tmp2)
                node = tre_ast_new_union(ctx->mem, tmp1, tmp2);
            else
                node = 0;
        } else {
            node = tre_ast_new_literal(ctx->mem, wc, wc, ctx->position);
        }
        ctx->position++;
        s += len;
        break;
    }
    if (!node)
        return REG_ESPACE;
    ctx->n = node;
    ctx->s = s;
    return REG_OK;
}

#define PUSHPTR(err, s, v) do { \
    if ((err = tre_stack_push_voidptr(s, v)) != REG_OK) \
        return err; \
} while(0)

#define PUSHINT(err, s, v) do { \
    if ((err = tre_stack_push_int(s, v)) != REG_OK) \
        return err; \
} while(0)

static reg_errcode_t tre_parse(tre_parse_ctx_t *ctx)
{
    tre_ast_node_t *nbranch=0, *nunion=0;
    int ere = ctx->cflags & REG_EXTENDED;
    const char *s = ctx->re;
    int subid = 0;
    int depth = 0;
    reg_errcode_t err;
    tre_stack_t *stack = ctx->stack;

    PUSHINT(err, stack, subid++);
    for (;;) {
        if ((!ere && *s == '\\' && s[1] == '(') ||
            (ere && *s == '(')) {
            PUSHPTR(err, stack, nunion);
            PUSHPTR(err, stack, nbranch);
            PUSHINT(err, stack, subid++);
            s++;
            if (!ere)
                s++;
            depth++;
            nbranch = nunion = 0;
            continue;
        }
        if ((!ere && *s == '\\' && s[1] == ')') ||
            (ere && *s == ')' && depth)) {
            ctx->n = tre_ast_new_literal(ctx->mem, EMPTY, -1, -1);
            if (!ctx->n)
                return REG_ESPACE;
        } else {
            err = parse_atom(ctx, s);
            if (err != REG_OK)
                return err;
            s = ctx->s;
        }

    parse_iter:
        /* extension: repetitions are accepted after an empty node
           eg. (+), ^*, a$?, a|{2} */
        switch (*s) {
        case '+':
        case '?':
            if (!ere)
                break;
            /* fallthrough */
        case '*':; {
            int min=0, max=-1;
            if (*s == '+')
                min = 1;
            if (*s == '?')
                max = 1;
            s++;
            ctx->n = tre_ast_new_iter(ctx->mem, ctx->n, min, max, 0);
            if (!ctx->n)
                return REG_ESPACE;
            /* extension: multiple consecutive *+?{,} is unspecified,
               but (a+)+ has to be supported so accepting a++ makes
               sense, note however that the RE_DUP_MAX limit can be
               circumvented: (a{255}){255} uses a lot of memory.. */
            goto parse_iter;
        }
        case '\\':
            if (ere || s[1] != '{')
                break;
            s++;
            goto parse_brace;
        case '{':
            if (!ere)
                break;
        parse_brace:
            err = parse_dup(ctx, s+1);
            if (err != REG_OK)
                return err;
            s = ctx->s;
            goto parse_iter;
        }

        nbranch = tre_ast_new_catenation(ctx->mem, nbranch, ctx->n);
        if ((ere && *s == '|') ||
            (ere && *s == ')' && depth) ||
            (!ere && *s == '\\' && s[1] == ')') ||
            !*s) {
            /* extension: empty branch is unspecified (), (|a), (a|)
               here they are not rejected but match on empty string */
            int c = *s;
            nunion = tre_ast_new_union(ctx->mem, nunion, nbranch);
            nbranch = 0;
            if (c != '|') {
                if (c == '\\') {
                    if (!depth) return REG_EPAREN;
                    s+=2;
                } else if (c == ')')
                    s++;
                depth--;
                err = marksub(ctx, nunion, tre_stack_pop_int(stack));
                if (err != REG_OK)
                    return err;
                if (!c && depth<0) {
                    ctx->submatch_id = subid;
                    return REG_OK;
                }
                if (!c || depth<0)
                    return REG_EPAREN;
                nbranch = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
                nunion = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
                goto parse_iter;
            }
            s++;
        }
    }
}


/***********************************************************************
 from tre-compile.c
***********************************************************************/


/*
  TODO:
   - Fix tre_ast_to_tnfa() to recurse using a stack instead of recursive
     function calls.
*/

/*
  Algorithms to setup tags so that submatch addressing can be done.
*/


/* Inserts a catenation node to the root of the tree given in `node'.
   As the left child a new tag with number `tag_id' to `node' is added,
   and the right child is the old root. */
static reg_errcode_t
tre_add_tag_left(tre_mem_t mem, tre_ast_node_t *node, int tag_id)
{
  tre_catenation_t *c;

  c = (tre_catenation_t*)tre_mem_alloc(mem, sizeof(*c));
  if (c == NULL)
    return REG_ESPACE;
  c->left = tre_ast_new_literal(mem, TAG, tag_id, -1);
  if (c->left == NULL)
    return REG_ESPACE;
  c->right = (tre_ast_node_t*)tre_mem_alloc(mem, sizeof(tre_ast_node_t));
  if (c->right == NULL)
    return REG_ESPACE;

  c->right->obj = node->obj;
  c->right->type = node->type;
  c->right->nullable = -1;
  c->right->submatch_id = -1;
  c->right->firstpos = NULL;
  c->right->lastpos = NULL;
  c->right->num_tags = 0;
  node->obj = c;
  node->type = CATENATION;
  return REG_OK;
}

/* Inserts a catenation node to the root of the tree given in `node'.
   As the right child a new tag with number `tag_id' to `node' is added,
   and the left child is the old root. */
static reg_errcode_t
tre_add_tag_right(tre_mem_t mem, tre_ast_node_t *node, int tag_id)
{
  tre_catenation_t *c;

  c = (tre_catenation_t*)tre_mem_alloc(mem, sizeof(*c));
  if (c == NULL)
    return REG_ESPACE;
  c->right = tre_ast_new_literal(mem, TAG, tag_id, -1);
  if (c->right == NULL)
    return REG_ESPACE;
  c->left = (tre_ast_node_t*)tre_mem_alloc(mem, sizeof(tre_ast_node_t));
  if (c->left == NULL)
    return REG_ESPACE;

  c->left->obj = node->obj;
  c->left->type = node->type;
  c->left->nullable = -1;
  c->left->submatch_id = -1;
  c->left->firstpos = NULL;
  c->left->lastpos = NULL;
  c->left->num_tags = 0;
  node->obj = c;
  node->type = CATENATION;
  return REG_OK;
}

typedef enum {
  ADDTAGS_RECURSE,
  ADDTAGS_AFTER_ITERATION,
  ADDTAGS_AFTER_UNION_LEFT,
  ADDTAGS_AFTER_UNION_RIGHT,
  ADDTAGS_AFTER_CAT_LEFT,
  ADDTAGS_AFTER_CAT_RIGHT,
  ADDTAGS_SET_SUBMATCH_END
} tre_addtags_symbol_t;


typedef struct {
  int tag;
  int next_tag;
} tre_tag_states_t;


/* Go through `regset' and set submatch data for submatches that are
   using this tag. */
static void
tre_purge_regset(int *regset, tre_tnfa_t *tnfa, int tag)
{
  int i;

  for (i = 0; regset[i] >= 0; i++)
    {
      int id = regset[i] / 2;
      int start = !(regset[i] % 2);
      if (start)
    tnfa->submatch_data[id].so_tag = tag;
      else
    tnfa->submatch_data[id].eo_tag = tag;
    }
  regset[0] = -1;
}


/* Adds tags to appropriate locations in the parse tree in `tree', so that
   subexpressions marked for submatch addressing can be traced. */
static reg_errcode_t
tre_add_tags(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree,
         tre_tnfa_t *tnfa)
{
  reg_errcode_t status = REG_OK;
  tre_addtags_symbol_t symbol;
  tre_ast_node_t *node = tree; /* Tree node we are currently looking at. */
  int bottom = tre_stack_num_objects(stack);
  /* True for first pass (counting number of needed tags) */
  int first_pass = (mem == NULL || tnfa == NULL);
  int *regset, *orig_regset;
  int num_tags = 0; /* Total number of tags. */
  int num_minimals = 0;  /* Number of special minimal tags. */
  int tag = 0;      /* The tag that is to be added next. */
  int next_tag = 1; /* Next tag to use after this one. */
  int *parents;     /* Stack of submatches the current submatch is
               contained in. */
  int minimal_tag = -1; /* Tag that marks the beginning of a minimal match. */
  tre_tag_states_t *saved_states;

  tre_tag_direction_t direction = TRE_TAG_MINIMIZE;
  if (!first_pass)
    {
      tnfa->end_tag = 0;
      tnfa->minimal_tags[0] = -1;
    }

  regset = (int*)xmalloc(sizeof(*regset) * ((tnfa->num_submatches + 1) * 2));
  if (regset == NULL)
    return REG_ESPACE;
  regset[0] = -1;
  orig_regset = regset;

  parents = (int*)xmalloc(sizeof(*parents) * (tnfa->num_submatches + 1));
  if (parents == NULL)
    {
      xfree(regset);
      return REG_ESPACE;
    }
  parents[0] = -1;

  saved_states = (tre_tag_states_t*)xmalloc(sizeof(*saved_states) * (tnfa->num_submatches + 1));
  if (saved_states == NULL)
    {
      xfree(regset);
      xfree(parents);
      return REG_ESPACE;
    }
  else
    {
      unsigned int i;
      for (i = 0; i <= tnfa->num_submatches; i++)
    saved_states[i].tag = -1;
    }

  STACK_PUSH(stack, voidptr, node);
  STACK_PUSH(stack, int, ADDTAGS_RECURSE);

  while (tre_stack_num_objects(stack) > bottom)
    {
      if (status != REG_OK)
    break;

      symbol = (tre_addtags_symbol_t)tre_stack_pop_int(stack);
      switch (symbol)
    {

    case ADDTAGS_SET_SUBMATCH_END:
      {
        int id = tre_stack_pop_int(stack);
        int i;

        /* Add end of this submatch to regset. */
        for (i = 0; regset[i] >= 0; i++);
        regset[i] = id * 2 + 1;
        regset[i + 1] = -1;

        /* Pop this submatch from the parents stack. */
        for (i = 0; parents[i] >= 0; i++);
        parents[i - 1] = -1;
        break;
      }

    case ADDTAGS_RECURSE:
      node = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);

      if (node->submatch_id >= 0)
        {
          int id = node->submatch_id;
          int i;


          /* Add start of this submatch to regset. */
          for (i = 0; regset[i] >= 0; i++);
          regset[i] = id * 2;
          regset[i + 1] = -1;

          if (!first_pass)
        {
          for (i = 0; parents[i] >= 0; i++);
          tnfa->submatch_data[id].parents = NULL;
          if (i > 0)
            {
              int *p = (int*)xmalloc(sizeof(*p) * (i + 1));
              if (p == NULL)
            {
              status = REG_ESPACE;
              break;
            }
              assert(tnfa->submatch_data[id].parents == NULL);
              tnfa->submatch_data[id].parents = p;
              for (i = 0; parents[i] >= 0; i++)
            p[i] = parents[i];
              p[i] = -1;
            }
        }

          /* Add end of this submatch to regset after processing this
         node. */
          STACK_PUSHX(stack, int, node->submatch_id);
          STACK_PUSHX(stack, int, ADDTAGS_SET_SUBMATCH_END);
        }

      switch (node->type)
        {
        case LITERAL:
          {
        tre_literal_t *lit = (tre_literal_t*)node->obj;

        if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
          {
            int i;
            if (regset[0] >= 0)
              {
            /* Regset is not empty, so add a tag before the
               literal or backref. */
            if (!first_pass)
              {
                status = tre_add_tag_left(mem, node, tag);
                tnfa->tag_directions[tag] = direction;
                if (minimal_tag >= 0)
                  {
                for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
                tnfa->minimal_tags[i] = tag;
                tnfa->minimal_tags[i + 1] = minimal_tag;
                tnfa->minimal_tags[i + 2] = -1;
                minimal_tag = -1;
                num_minimals++;
                  }
                tre_purge_regset(regset, tnfa, tag);
              }
            else
              {
                node->num_tags = 1;
              }

            regset[0] = -1;
            tag = next_tag;
            num_tags++;
            next_tag++;
              }
          }
        else
          {
            assert(!IS_TAG(lit));
          }
        break;
          }
        case CATENATION:
          {
        tre_catenation_t *cat = (tre_catenation_t*)node->obj;
        tre_ast_node_t *left = cat->left;
        tre_ast_node_t *right = cat->right;
        int reserved_tag = -1;


        /* After processing right child. */
        STACK_PUSHX(stack, voidptr, node);
        STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_RIGHT);

        /* Process right child. */
        STACK_PUSHX(stack, voidptr, right);
        STACK_PUSHX(stack, int, ADDTAGS_RECURSE);

        /* After processing left child. */
        STACK_PUSHX(stack, int, next_tag + left->num_tags);
        if (left->num_tags > 0 && right->num_tags > 0)
          {
            /* Reserve the next tag to the right child. */
            reserved_tag = next_tag;
            next_tag++;
          }
        STACK_PUSHX(stack, int, reserved_tag);
        STACK_PUSHX(stack, int, ADDTAGS_AFTER_CAT_LEFT);

        /* Process left child. */
        STACK_PUSHX(stack, voidptr, left);
        STACK_PUSHX(stack, int, ADDTAGS_RECURSE);

        }
          break;
        case ITERATION:
          {
        tre_iteration_t *iter = (tre_iteration_t*)node->obj;

        if (first_pass)
          {
            STACK_PUSHX(stack, int, regset[0] >= 0 || iter->minimal);
          }
        else
          {
            STACK_PUSHX(stack, int, tag);
            STACK_PUSHX(stack, int, iter->minimal);
          }
        STACK_PUSHX(stack, voidptr, node);
        STACK_PUSHX(stack, int, ADDTAGS_AFTER_ITERATION);

        STACK_PUSHX(stack, voidptr, iter->arg);
        STACK_PUSHX(stack, int, ADDTAGS_RECURSE);

        /* Regset is not empty, so add a tag here. */
        if (regset[0] >= 0 || iter->minimal)
          {
            if (!first_pass)
              {
            int i;
            status = tre_add_tag_left(mem, node, tag);
            if (iter->minimal)
              tnfa->tag_directions[tag] = TRE_TAG_MAXIMIZE;
            else
              tnfa->tag_directions[tag] = direction;
            if (minimal_tag >= 0)
              {
                for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
                tnfa->minimal_tags[i] = tag;
                tnfa->minimal_tags[i + 1] = minimal_tag;
                tnfa->minimal_tags[i + 2] = -1;
                minimal_tag = -1;
                num_minimals++;
              }
            tre_purge_regset(regset, tnfa, tag);
              }

            regset[0] = -1;
            tag = next_tag;
            num_tags++;
            next_tag++;
          }
        direction = TRE_TAG_MINIMIZE;
          }
          break;
        case UNION:
          {
        tre_union_t *uni = (tre_union_t*)node->obj;
        tre_ast_node_t *left = uni->left;
        tre_ast_node_t *right = uni->right;
        int left_tag;
        int right_tag;

        if (regset[0] >= 0)
          {
            left_tag = next_tag;
            right_tag = next_tag + 1;
          }
        else
          {
            left_tag = tag;
            right_tag = next_tag;
          }

        /* After processing right child. */
        STACK_PUSHX(stack, int, right_tag);
        STACK_PUSHX(stack, int, left_tag);
        STACK_PUSHX(stack, voidptr, regset);
        STACK_PUSHX(stack, int, regset[0] >= 0);
        STACK_PUSHX(stack, voidptr, node);
        STACK_PUSHX(stack, voidptr, right);
        STACK_PUSHX(stack, voidptr, left);
        STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_RIGHT);

        /* Process right child. */
        STACK_PUSHX(stack, voidptr, right);
        STACK_PUSHX(stack, int, ADDTAGS_RECURSE);

        /* After processing left child. */
        STACK_PUSHX(stack, int, ADDTAGS_AFTER_UNION_LEFT);

        /* Process left child. */
        STACK_PUSHX(stack, voidptr, left);
        STACK_PUSHX(stack, int, ADDTAGS_RECURSE);

        /* Regset is not empty, so add a tag here. */
        if (regset[0] >= 0)
          {
            if (!first_pass)
              {
            int i;
            status = tre_add_tag_left(mem, node, tag);
            tnfa->tag_directions[tag] = direction;
            if (minimal_tag >= 0)
              {
                for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
                tnfa->minimal_tags[i] = tag;
                tnfa->minimal_tags[i + 1] = minimal_tag;
                tnfa->minimal_tags[i + 2] = -1;
                minimal_tag = -1;
                num_minimals++;
              }
            tre_purge_regset(regset, tnfa, tag);
              }

            regset[0] = -1;
            tag = next_tag;
            num_tags++;
            next_tag++;
          }

        if (node->num_submatches > 0)
          {
            /* The next two tags are reserved for markers. */
            next_tag++;
            tag = next_tag;
            next_tag++;
          }

        break;
          }
        }

      if (node->submatch_id >= 0)
        {
          int i;
          /* Push this submatch on the parents stack. */
          for (i = 0; parents[i] >= 0; i++);
          parents[i] = node->submatch_id;
          parents[i + 1] = -1;
        }

      break; /* end case: ADDTAGS_RECURSE */

    case ADDTAGS_AFTER_ITERATION:
      {
        int minimal = 0;
        int enter_tag;
        node = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
        if (first_pass)
          {
        node->num_tags = ((tre_iteration_t *)node->obj)->arg->num_tags
          + tre_stack_pop_int(stack);
        minimal_tag = -1;
          }
        else
          {
        minimal = tre_stack_pop_int(stack);
        enter_tag = tre_stack_pop_int(stack);
        if (minimal)
          minimal_tag = enter_tag;
          }

        if (!first_pass)
          {
        if (minimal)
          direction = TRE_TAG_MINIMIZE;
        else
          direction = TRE_TAG_MAXIMIZE;
          }
        break;
      }

    case ADDTAGS_AFTER_CAT_LEFT:
      {
        int new_tag = tre_stack_pop_int(stack);
        next_tag = tre_stack_pop_int(stack);
        if (new_tag >= 0)
          {
        tag = new_tag;
          }
        break;
      }

    case ADDTAGS_AFTER_CAT_RIGHT:
      node = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
      if (first_pass)
        node->num_tags = ((tre_catenation_t *)node->obj)->left->num_tags
          + ((tre_catenation_t *)node->obj)->right->num_tags;
      break;

    case ADDTAGS_AFTER_UNION_LEFT:
      /* Lift the bottom of the `regset' array so that when processing
         the right operand the items currently in the array are
         invisible.  The original bottom was saved at ADDTAGS_UNION and
         will be restored at ADDTAGS_AFTER_UNION_RIGHT below. */
      while (*regset >= 0)
        regset++;
      break;

    case ADDTAGS_AFTER_UNION_RIGHT:
      {
        int added_tags, tag_left, tag_right;
        tre_ast_node_t *left = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
        tre_ast_node_t *right = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
        node = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
        added_tags = tre_stack_pop_int(stack);
        if (first_pass)
          {
        node->num_tags = ((tre_union_t *)node->obj)->left->num_tags
          + ((tre_union_t *)node->obj)->right->num_tags + added_tags
          + ((node->num_submatches > 0) ? 2 : 0);
          }
        regset = (int*)tre_stack_pop_voidptr(stack);
        tag_left = tre_stack_pop_int(stack);
        tag_right = tre_stack_pop_int(stack);

        /* Add tags after both children, the left child gets a smaller
           tag than the right child.  This guarantees that we prefer
           the left child over the right child. */
        /* XXX - This is not always necessary (if the children have
           tags which must be seen for every match of that child). */
        /* XXX - Check if this is the only place where tre_add_tag_right
           is used.  If so, use tre_add_tag_left (putting the tag before
           the child as opposed after the child) and throw away
           tre_add_tag_right. */
        if (node->num_submatches > 0)
          {
        if (!first_pass)
          {
            status = tre_add_tag_right(mem, left, tag_left);
            tnfa->tag_directions[tag_left] = TRE_TAG_MAXIMIZE;
            status = tre_add_tag_right(mem, right, tag_right);
            tnfa->tag_directions[tag_right] = TRE_TAG_MAXIMIZE;
          }
        num_tags += 2;
          }
        direction = TRE_TAG_MAXIMIZE;
        break;
      }

    default:
      assert(0);
      break;

    } /* end switch(symbol) */
    } /* end while(tre_stack_num_objects(stack) > bottom) */

  if (!first_pass)
    tre_purge_regset(regset, tnfa, tag);

  if (!first_pass && minimal_tag >= 0)
    {
      int i;
      for (i = 0; tnfa->minimal_tags[i] >= 0; i++);
      tnfa->minimal_tags[i] = tag;
      tnfa->minimal_tags[i + 1] = minimal_tag;
      tnfa->minimal_tags[i + 2] = -1;
      minimal_tag = -1;
      num_minimals++;
    }

  assert(tree->num_tags == num_tags);
  tnfa->end_tag = num_tags;
  tnfa->num_tags = num_tags;
  tnfa->num_minimals = num_minimals;
  xfree(orig_regset);
  xfree(parents);
  xfree(saved_states);
  return status;
}



/*
  AST to TNFA compilation routines.
*/

typedef enum {
  COPY_RECURSE,
  COPY_SET_RESULT_PTR
} tre_copyast_symbol_t;

/* Flags for tre_copy_ast(). */
#define COPY_REMOVE_TAGS     1
#define COPY_MAXIMIZE_FIRST_TAG  2

static reg_errcode_t
tre_copy_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
         int flags, int *pos_add, tre_tag_direction_t *tag_directions,
         tre_ast_node_t **copy, int *max_pos)
{
  reg_errcode_t status = REG_OK;
  int bottom = tre_stack_num_objects(stack);
  int num_copied = 0;
  int first_tag = 1;
  tre_ast_node_t **result = copy;
  tre_copyast_symbol_t symbol;

  STACK_PUSH(stack, voidptr, ast);
  STACK_PUSH(stack, int, COPY_RECURSE);

  while (status == REG_OK && tre_stack_num_objects(stack) > bottom)
    {
      tre_ast_node_t *node;
      if (status != REG_OK)
    break;

      symbol = (tre_copyast_symbol_t)tre_stack_pop_int(stack);
      switch (symbol)
    {
    case COPY_SET_RESULT_PTR:
      result = (tre_ast_node_t**)tre_stack_pop_voidptr(stack);
      break;
    case COPY_RECURSE:
      node = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
      switch (node->type)
        {
        case LITERAL:
          {
        tre_literal_t *lit = (tre_literal_t *)node->obj;
        int pos = lit->position;
        int min = lit->code_min;
        int max = lit->code_max;
        if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
          {
            /* XXX - e.g. [ab] has only one position but two
               nodes, so we are creating holes in the state space
               here.  Not fatal, just wastes memory. */
            pos += *pos_add;
            num_copied++;
          }
        else if (IS_TAG(lit) && (flags & COPY_REMOVE_TAGS))
          {
            /* Change this tag to empty. */
            min = EMPTY;
            max = pos = -1;
          }
        else if (IS_TAG(lit) && (flags & COPY_MAXIMIZE_FIRST_TAG)
             && first_tag)
          {
            /* Maximize the first tag. */
            tag_directions[max] = TRE_TAG_MAXIMIZE;
            first_tag = 0;
          }
        *result = tre_ast_new_literal(mem, min, max, pos);
        if (*result == NULL)
          status = REG_ESPACE;

        if (pos > *max_pos)
          *max_pos = pos;
        break;
          }
        case UNION:
          {
        tre_union_t *uni = (tre_union_t *)node->obj;
        tre_union_t *tmp;
        *result = tre_ast_new_union(mem, uni->left, uni->right);
        if (*result == NULL)
          {
            status = REG_ESPACE;
            break;
          }
        tmp = (tre_union_t *)((*result)->obj);
        result = &tmp->left;
        STACK_PUSHX(stack, voidptr, uni->right);
        STACK_PUSHX(stack, int, COPY_RECURSE);
        STACK_PUSHX(stack, voidptr, &tmp->right);
        STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR);
        STACK_PUSHX(stack, voidptr, uni->left);
        STACK_PUSHX(stack, int, COPY_RECURSE);
        break;
          }
        case CATENATION:
          {
        tre_catenation_t *cat = (tre_catenation_t *)node->obj;
        tre_catenation_t *tmp;
        *result = tre_ast_new_catenation(mem, cat->left, cat->right);
        if (*result == NULL)
          {
            status = REG_ESPACE;
            break;
          }
        tmp = (tre_catenation_t *)((*result)->obj);
        tmp->left = NULL;
        tmp->right = NULL;
        result = &tmp->left;

        STACK_PUSHX(stack, voidptr, cat->right);
        STACK_PUSHX(stack, int, COPY_RECURSE);
        STACK_PUSHX(stack, voidptr, &tmp->right);
        STACK_PUSHX(stack, int, COPY_SET_RESULT_PTR);
        STACK_PUSHX(stack, voidptr, cat->left);
        STACK_PUSHX(stack, int, COPY_RECURSE);
        break;
          }
        case ITERATION:
          {
        tre_iteration_t *iter = (tre_iteration_t *)node->obj;
        STACK_PUSHX(stack, voidptr, iter->arg);
        STACK_PUSHX(stack, int, COPY_RECURSE);
        *result = tre_ast_new_iter(mem, iter->arg, iter->min,
                       iter->max, iter->minimal);
        if (*result == NULL)
          {
            status = REG_ESPACE;
            break;
          }
        iter = (tre_iteration_t *)((*result)->obj);
        result = &iter->arg;
        break;
          }
        default:
          assert(0);
          break;
        }
      break;
    }
    }
  *pos_add += num_copied;
  return status;
}

typedef enum {
  EXPAND_RECURSE,
  EXPAND_AFTER_ITER
} tre_expand_ast_symbol_t;

/* Expands each iteration node that has a finite nonzero minimum or maximum
   iteration count to a catenated sequence of copies of the node. */
static reg_errcode_t
tre_expand_ast(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *ast,
           int *position, tre_tag_direction_t *tag_directions)
{
  reg_errcode_t status = REG_OK;
  int bottom = tre_stack_num_objects(stack);
  int pos_add = 0;
  int pos_add_total = 0;
  int max_pos = 0;
  int iter_depth = 0;

  STACK_PUSHR(stack, voidptr, ast);
  STACK_PUSHR(stack, int, EXPAND_RECURSE);
  while (status == REG_OK && tre_stack_num_objects(stack) > bottom)
    {
      tre_ast_node_t *node;
      tre_expand_ast_symbol_t symbol;

      if (status != REG_OK)
    break;

      symbol = (tre_expand_ast_symbol_t)tre_stack_pop_int(stack);
      node = (tre_ast_node_t*)tre_stack_pop_voidptr(stack);
      switch (symbol)
    {
    case EXPAND_RECURSE:
      switch (node->type)
        {
        case LITERAL:
          {
        tre_literal_t *lit= (tre_literal_t *)node->obj;
        if (!IS_SPECIAL(lit) || IS_BACKREF(lit))
          {
            lit->position += pos_add;
            if (lit->position > max_pos)
              max_pos = lit->position;
          }
        break;
          }
        case UNION:
          {
        tre_union_t *uni = (tre_union_t *)node->obj;
        STACK_PUSHX(stack, voidptr, uni->right);
        STACK_PUSHX(stack, int, EXPAND_RECURSE);
        STACK_PUSHX(stack, voidptr, uni->left);
        STACK_PUSHX(stack, int, EXPAND_RECURSE);
        break;
          }
        case CATENATION:
          {
        tre_catenation_t *cat = (tre_catenation_t *)node->obj;
        STACK_PUSHX(stack, voidptr, cat->right);
        STACK_PUSHX(stack, int, EXPAND_RECURSE);
        STACK_PUSHX(stack, voidptr, cat->left);
        STACK_PUSHX(stack, int, EXPAND_RECURSE);
        break;
          }
        case ITERATION:
          {
        tre_iteration_t *iter = (tre_iteration_t *)node->obj;
        STACK_PUSHX(stack, int, pos_add);
        STACK_PUSHX(stack, voidptr, node);
        STACK_PUSHX(stack, int, EXPAND_AFTER_ITER);
        STACK_PUSHX(stack, voidptr, iter->arg);
        STACK_PUSHX(stack, int, EXPAND_RECURSE);
        /* If we are going to expand this node at EXPAND_AFTER_ITER
           then don't increase the `pos' fields of the nodes now, it
           will get done when expanding. */
        if (iter->min > 1 || iter->max > 1)
          pos_add = 0;
        iter_depth++;
        break;
          }
        default:
          assert(0);
          break;
        }
      break;
    case EXPAND_AFTER_ITER:
      {
        tre_iteration_t *iter = (tre_iteration_t *)node->obj;
        int pos_add_last;
        pos_add = tre_stack_pop_int(stack);
        pos_add_last = pos_add;
        if (iter->min > 1 || iter->max > 1)
          {
        tre_ast_node_t *seq1 = NULL, *seq2 = NULL;
        int j;
        int pos_add_save = pos_add;

        /* Create a catenated sequence of copies of the node. */
        for (j = 0; j < iter->min; j++)
          {
            tre_ast_node_t *copy;
            /* Remove tags from all but the last copy. */
            int flags = ((j + 1 < iter->min)
                 ? COPY_REMOVE_TAGS
                 : COPY_MAXIMIZE_FIRST_TAG);
            pos_add_save = pos_add;
            status = tre_copy_ast(mem, stack, iter->arg, flags,
                      &pos_add, tag_directions, &copy,
                      &max_pos);
            if (status != REG_OK)
              return status;
            if (seq1 != NULL)
              seq1 = tre_ast_new_catenation(mem, seq1, copy);
            else
              seq1 = copy;
            if (seq1 == NULL)
              return REG_ESPACE;
          }

        if (iter->max == -1)
          {
            /* No upper limit. */
            pos_add_save = pos_add;
            status = tre_copy_ast(mem, stack, iter->arg, 0,
                      &pos_add, NULL, &seq2, &max_pos);
            if (status != REG_OK)
              return status;
            seq2 = tre_ast_new_iter(mem, seq2, 0, -1, 0);
            if (seq2 == NULL)
              return REG_ESPACE;
          }
        else
          {
            for (j = iter->min; j < iter->max; j++)
              {
            tre_ast_node_t *tmp, *copy;
            pos_add_save = pos_add;
            status = tre_copy_ast(mem, stack, iter->arg, 0,
                          &pos_add, NULL, &copy, &max_pos);
            if (status != REG_OK)
              return status;
            if (seq2 != NULL)
              seq2 = tre_ast_new_catenation(mem, copy, seq2);
            else
              seq2 = copy;
            if (seq2 == NULL)
              return REG_ESPACE;
            tmp = tre_ast_new_literal(mem, EMPTY, -1, -1);
            if (tmp == NULL)
              return REG_ESPACE;
            seq2 = tre_ast_new_union(mem, tmp, seq2);
            if (seq2 == NULL)
              return REG_ESPACE;
              }
          }

        pos_add = pos_add_save;
        if (seq1 == NULL)
          seq1 = seq2;
        else if (seq2 != NULL)
          seq1 = tre_ast_new_catenation(mem, seq1, seq2);
        if (seq1 == NULL)
          return REG_ESPACE;
        node->obj = seq1->obj;
        node->type = seq1->type;
          }

        iter_depth--;
        pos_add_total += pos_add - pos_add_last;
        if (iter_depth == 0)
          pos_add = pos_add_total;

        break;
      }
    default:
      assert(0);
      break;
    }
    }

  *position += pos_add_total;

  /* `max_pos' should never be larger than `*position' if the above
     code works, but just an extra safeguard let's make sure
     `*position' is set large enough so enough memory will be
     allocated for the transition table. */
  if (max_pos > *position)
    *position = max_pos;

  return status;
}

static tre_pos_and_tags_t *
tre_set_empty(tre_mem_t mem)
{
  tre_pos_and_tags_t *new_set;

  new_set = (tre_pos_and_tags_t *)tre_mem_calloc(mem, sizeof(*new_set));
  if (new_set == NULL)
    return NULL;

  new_set[0].position = -1;
  new_set[0].code_min = -1;
  new_set[0].code_max = -1;

  return new_set;
}

static tre_pos_and_tags_t *
tre_set_one(tre_mem_t mem, int position, int code_min, int code_max,
        tre_ctype_t rclass, tre_ctype_t *neg_classes, int backref)
{
  tre_pos_and_tags_t *new_set;

  new_set = (tre_pos_and_tags_t *)tre_mem_calloc(mem, sizeof(*new_set) * 2);
  if (new_set == NULL)
    return NULL;

  new_set[0].position = position;
  new_set[0].code_min = code_min;
  new_set[0].code_max = code_max;
  new_set[0].rclass = rclass;
  new_set[0].neg_classes = neg_classes;
  new_set[0].backref = backref;
  new_set[1].position = -1;
  new_set[1].code_min = -1;
  new_set[1].code_max = -1;

  return new_set;
}

static tre_pos_and_tags_t *
tre_set_union(tre_mem_t mem, tre_pos_and_tags_t *set1, tre_pos_and_tags_t *set2,
          int *tags, int assertions)
{
  int s1, s2, i, j;
  tre_pos_and_tags_t *new_set;
  int *new_tags;
  int num_tags;

  for (num_tags = 0; tags != NULL && tags[num_tags] >= 0; num_tags++);
  for (s1 = 0; set1[s1].position >= 0; s1++);
  for (s2 = 0; set2[s2].position >= 0; s2++);
  new_set = (tre_pos_and_tags_t *)tre_mem_calloc(mem, sizeof(*new_set) * (s1 + s2 + 1));
  if (!new_set )
    return NULL;

  for (s1 = 0; set1[s1].position >= 0; s1++)
    {
      new_set[s1].position = set1[s1].position;
      new_set[s1].code_min = set1[s1].code_min;
      new_set[s1].code_max = set1[s1].code_max;
      new_set[s1].assertions = set1[s1].assertions | assertions;
      new_set[s1].rclass = set1[s1].rclass;
      new_set[s1].neg_classes = set1[s1].neg_classes;
      new_set[s1].backref = set1[s1].backref;
      if (set1[s1].tags == NULL && tags == NULL)
    new_set[s1].tags = NULL;
      else
    {
      for (i = 0; set1[s1].tags != NULL && set1[s1].tags[i] >= 0; i++);
      new_tags = (int *)tre_mem_alloc(mem, (sizeof(*new_tags)
                     * (i + num_tags + 1)));
      if (new_tags == NULL)
        return NULL;
      for (j = 0; j < i; j++)
        new_tags[j] = set1[s1].tags[j];
      for (i = 0; i < num_tags; i++)
        new_tags[j + i] = tags[i];
      new_tags[j + i] = -1;
      new_set[s1].tags = new_tags;
    }
    }

  for (s2 = 0; set2[s2].position >= 0; s2++)
    {
      new_set[s1 + s2].position = set2[s2].position;
      new_set[s1 + s2].code_min = set2[s2].code_min;
      new_set[s1 + s2].code_max = set2[s2].code_max;
      /* XXX - why not | assertions here as well? */
      new_set[s1 + s2].assertions = set2[s2].assertions;
      new_set[s1 + s2].rclass = set2[s2].rclass;
      new_set[s1 + s2].neg_classes = set2[s2].neg_classes;
      new_set[s1 + s2].backref = set2[s2].backref;
      if (set2[s2].tags == NULL)
    new_set[s1 + s2].tags = NULL;
      else
    {
      for (i = 0; set2[s2].tags[i] >= 0; i++);
      new_tags = (int *)tre_mem_alloc(mem, sizeof(*new_tags) * (i + 1));
      if (new_tags == NULL)
        return NULL;
      for (j = 0; j < i; j++)
        new_tags[j] = set2[s2].tags[j];
      new_tags[j] = -1;
      new_set[s1 + s2].tags = new_tags;
    }
    }
  new_set[s1 + s2].position = -1;
  return new_set;
}

/* Finds the empty path through `node' which is the one that should be
   taken according to POSIX.2 rules, and adds the tags on that path to
   `tags'.   `tags' may be NULL.  If `num_tags_seen' is not NULL, it is
   set to the number of tags seen on the path. */
static reg_errcode_t
tre_match_empty(tre_stack_t *stack, tre_ast_node_t *node, int *tags,
        int *assertions, int *num_tags_seen)
{
  tre_literal_t *lit;
  tre_union_t *uni;
  tre_catenation_t *cat;
  tre_iteration_t *iter;
  int i;
  int bottom = tre_stack_num_objects(stack);
  reg_errcode_t status = REG_OK;
  if (num_tags_seen)
    *num_tags_seen = 0;

  status = tre_stack_push_voidptr(stack, node);

  /* Walk through the tree recursively. */
  while (status == REG_OK && tre_stack_num_objects(stack) > bottom)
    {
      node = (tre_ast_node_t *)tre_stack_pop_voidptr(stack);

      switch (node->type)
    {
    case LITERAL:
      lit = (tre_literal_t *)node->obj;
      switch (lit->code_min)
        {
        case TAG:
          if (lit->code_max >= 0)
        {
          if (tags != NULL)
            {
              /* Add the tag to `tags'. */
              for (i = 0; tags[i] >= 0; i++)
            if (tags[i] == lit->code_max)
              break;
              if (tags[i] < 0)
            {
              tags[i] = lit->code_max;
              tags[i + 1] = -1;
            }
            }
          if (num_tags_seen)
            (*num_tags_seen)++;
        }
          break;
        case ASSERTION:
          assert(lit->code_max >= 1
             || lit->code_max <= ASSERT_LAST);
          if (assertions != NULL)
        *assertions |= lit->code_max;
          break;
        case EMPTY:
          break;
        default:
          assert(0);
          break;
        }
      break;

    case UNION:
      /* Subexpressions starting earlier take priority over ones
         starting later, so we prefer the left subexpression over the
         right subexpression. */
      uni = (tre_union_t *)node->obj;
      if (uni->left->nullable)
        STACK_PUSHX(stack, voidptr, uni->left)
      else if (uni->right->nullable)
        STACK_PUSHX(stack, voidptr, uni->right)
      else
        assert(0);
      break;

    case CATENATION:
      /* The path must go through both children. */
      cat = (tre_catenation_t *)node->obj;
      assert(cat->left->nullable);
      assert(cat->right->nullable);
      STACK_PUSHX(stack, voidptr, cat->left);
      STACK_PUSHX(stack, voidptr, cat->right);
      break;

    case ITERATION:
      /* A match with an empty string is preferred over no match at
         all, so we go through the argument if possible. */
      iter = (tre_iteration_t *)node->obj;
      if (iter->arg->nullable)
        STACK_PUSHX(stack, voidptr, iter->arg);
      break;

    default:
      assert(0);
      break;
    }
    }

  return status;
}


typedef enum {
  NFL_RECURSE,
  NFL_POST_UNION,
  NFL_POST_CATENATION,
  NFL_POST_ITERATION
} tre_nfl_stack_symbol_t;


/* Computes and fills in the fields `nullable', `firstpos', and `lastpos' for
   the nodes of the AST `tree'. */
static reg_errcode_t
tre_compute_nfl(tre_mem_t mem, tre_stack_t *stack, tre_ast_node_t *tree)
{
  int bottom = tre_stack_num_objects(stack);

  STACK_PUSHR(stack, voidptr, tree);
  STACK_PUSHR(stack, int, NFL_RECURSE);

  while (tre_stack_num_objects(stack) > bottom)
    {
      tre_nfl_stack_symbol_t symbol;
      tre_ast_node_t *node;

      symbol = (tre_nfl_stack_symbol_t)tre_stack_pop_int(stack);
      node = (tre_ast_node_t *)tre_stack_pop_voidptr(stack);
      switch (symbol)
    {
    case NFL_RECURSE:
      switch (node->type)
        {
        case LITERAL:
          {
        tre_literal_t *lit = (tre_literal_t *)node->obj;
        if (IS_BACKREF(lit))
          {
            /* Back references: nullable = false, firstpos = {i},
               lastpos = {i}. */
            node->nullable = 0;
            node->firstpos = tre_set_one(mem, lit->position, 0,
                         TRE_CHAR_MAX, 0, NULL, -1);
            if (!node->firstpos)
              return REG_ESPACE;
            node->lastpos = tre_set_one(mem, lit->position, 0,
                        TRE_CHAR_MAX, 0, NULL,
                        (int)lit->code_max);
            if (!node->lastpos)
              return REG_ESPACE;
          }
        else if (lit->code_min < 0)
          {
            /* Tags, empty strings, params, and zero width assertions:
               nullable = true, firstpos = {}, and lastpos = {}. */
            node->nullable = 1;
            node->firstpos = tre_set_empty(mem);
            if (!node->firstpos)
              return REG_ESPACE;
            node->lastpos = tre_set_empty(mem);
            if (!node->lastpos)
              return REG_ESPACE;
          }
        else
          {
            /* Literal at position i: nullable = false, firstpos = {i},
               lastpos = {i}. */
            node->nullable = 0;
            node->firstpos =
              tre_set_one(mem, lit->position, (int)lit->code_min,
                  (int)lit->code_max, 0, NULL, -1);
            if (!node->firstpos)
              return REG_ESPACE;
            node->lastpos = tre_set_one(mem, lit->position,
                        (int)lit->code_min,
                        (int)lit->code_max,
                        lit->rclass, lit->neg_classes,
                        -1);
            if (!node->lastpos)
              return REG_ESPACE;
          }
        break;
          }

        case UNION:
          /* Compute the attributes for the two subtrees, and after that
         for this node. */
          STACK_PUSHR(stack, voidptr, node);
          STACK_PUSHR(stack, int, NFL_POST_UNION);
          STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->right);
          STACK_PUSHR(stack, int, NFL_RECURSE);
          STACK_PUSHR(stack, voidptr, ((tre_union_t *)node->obj)->left);
          STACK_PUSHR(stack, int, NFL_RECURSE);
          break;

        case CATENATION:
          /* Compute the attributes for the two subtrees, and after that
         for this node. */
          STACK_PUSHR(stack, voidptr, node);
          STACK_PUSHR(stack, int, NFL_POST_CATENATION);
          STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->right);
          STACK_PUSHR(stack, int, NFL_RECURSE);
          STACK_PUSHR(stack, voidptr, ((tre_catenation_t *)node->obj)->left);
          STACK_PUSHR(stack, int, NFL_RECURSE);
          break;

        case ITERATION:
          /* Compute the attributes for the subtree, and after that for
         this node. */
          STACK_PUSHR(stack, voidptr, node);
          STACK_PUSHR(stack, int, NFL_POST_ITERATION);
          STACK_PUSHR(stack, voidptr, ((tre_iteration_t *)node->obj)->arg);
          STACK_PUSHR(stack, int, NFL_RECURSE);
          break;
        }
      break; /* end case: NFL_RECURSE */

    case NFL_POST_UNION:
      {
        tre_union_t *uni = (tre_union_t *)node->obj;
        node->nullable = uni->left->nullable || uni->right->nullable;
        node->firstpos = tre_set_union(mem, uni->left->firstpos,
                       uni->right->firstpos, NULL, 0);
        if (!node->firstpos)
          return REG_ESPACE;
        node->lastpos = tre_set_union(mem, uni->left->lastpos,
                      uni->right->lastpos, NULL, 0);
        if (!node->lastpos)
          return REG_ESPACE;
        break;
      }

    case NFL_POST_ITERATION:
      {
        tre_iteration_t *iter = (tre_iteration_t *)node->obj;

        if (iter->min == 0 || iter->arg->nullable)
          node->nullable = 1;
        else
          node->nullable = 0;
        node->firstpos = iter->arg->firstpos;
        node->lastpos = iter->arg->lastpos;
        break;
      }

    case NFL_POST_CATENATION:
      {
        int num_tags, *tags, assertions;
        reg_errcode_t status;
        tre_catenation_t *cat = (tre_catenation_t *)node->obj;
        node->nullable = cat->left->nullable && cat->right->nullable;

        /* Compute firstpos. */
        if (cat->left->nullable)
          {
        /* The left side matches the empty string.  Make a first pass
           with tre_match_empty() to get the number of tags and
           parameters. */
        status = tre_match_empty(stack, cat->left,
                     NULL, NULL, &num_tags);
        if (status != REG_OK)
          return status;
        /* Allocate arrays for the tags and parameters. */
        tags = (int*)xmalloc(sizeof(*tags) * (num_tags + 1));
        if (!tags)
          return REG_ESPACE;
        tags[0] = -1;
        assertions = 0;
        /* Second pass with tre_mach_empty() to get the list of
           tags and parameters. */
        status = tre_match_empty(stack, cat->left, tags,
                     &assertions, NULL);
        if (status != REG_OK)
          {
            xfree(tags);
            return status;
          }
        node->firstpos =
          tre_set_union(mem, cat->right->firstpos, cat->left->firstpos,
                tags, assertions);
        xfree(tags);
        if (!node->firstpos)
          return REG_ESPACE;
          }
        else
          {
        node->firstpos = cat->left->firstpos;
          }

        /* Compute lastpos. */
        if (cat->right->nullable)
          {
        /* The right side matches the empty string.  Make a first pass
           with tre_match_empty() to get the number of tags and
           parameters. */
        status = tre_match_empty(stack, cat->right,
                     NULL, NULL, &num_tags);
        if (status != REG_OK)
          return status;
        /* Allocate arrays for the tags and parameters. */
        tags = (int*)xmalloc(sizeof(int) * (num_tags + 1));
        if (!tags)
          return REG_ESPACE;
        tags[0] = -1;
        assertions = 0;
        /* Second pass with tre_mach_empty() to get the list of
           tags and parameters. */
        status = tre_match_empty(stack, cat->right, tags,
                     &assertions, NULL);
        if (status != REG_OK)
          {
            xfree(tags);
            return status;
          }
        node->lastpos =
          tre_set_union(mem, cat->left->lastpos, cat->right->lastpos,
                tags, assertions);
        xfree(tags);
        if (!node->lastpos)
          return REG_ESPACE;
          }
        else
          {
        node->lastpos = cat->right->lastpos;
          }
        break;
      }

    default:
      assert(0);
      break;
    }
    }

  return REG_OK;
}


/* Adds a transition from each position in `p1' to each position in `p2'. */
static reg_errcode_t
tre_make_trans(tre_pos_and_tags_t *p1, tre_pos_and_tags_t *p2,
           tre_tnfa_transition_t *transitions,
           int *counts, int *offs)
{
  tre_pos_and_tags_t *orig_p2 = p2;
  tre_tnfa_transition_t *trans;
  int i, j, k, l, dup, prev_p2_pos;

  if (transitions != NULL)
    while (p1->position >= 0)
      {
    p2 = orig_p2;
    prev_p2_pos = -1;
    while (p2->position >= 0)
      {
        /* Optimization: if this position was already handled, skip it. */
        if (p2->position == prev_p2_pos)
          {
        p2++;
        continue;
          }
        prev_p2_pos = p2->position;
        /* Set `trans' to point to the next unused transition from
           position `p1->position'. */
        trans = transitions + offs[p1->position];
        while (trans->state != NULL)
          {
#if 0
        /* If we find a previous transition from `p1->position' to
           `p2->position', it is overwritten.  This can happen only
           if there are nested loops in the regexp, like in "((a)*)*".
           In POSIX.2 repetition using the outer loop is always
           preferred over using the inner loop.  Therefore the
           transition for the inner loop is useless and can be thrown
           away. */
        /* XXX - The same position is used for all nodes in a bracket
           expression, so this optimization cannot be used (it will
           break bracket expressions) unless I figure out a way to
           detect it here. */
        if (trans->state_id == p2->position)
          {
            break;
          }
#endif
        trans++;
          }

        if (trans->state == NULL)
          (trans + 1)->state = NULL;
        /* Use the character ranges, assertions, etc. from `p1' for
           the transition from `p1' to `p2'. */
        trans->code_min = p1->code_min;
        trans->code_max = p1->code_max;
        trans->state = transitions + offs[p2->position];
        trans->state_id = p2->position;
        trans->assertions = p1->assertions | p2->assertions
          | (p1->rclass ? ASSERT_CHAR_CLASS : 0)
          | (p1->neg_classes != NULL ? ASSERT_CHAR_CLASS_NEG : 0);
        if (p1->backref >= 0)
          {
        assert((trans->assertions & ASSERT_CHAR_CLASS) == 0);
        assert(p2->backref < 0);
        trans->u.backref = p1->backref;
        trans->assertions |= ASSERT_BACKREF;
          }
        else
          trans->u.rclass = p1->rclass;
        if (p1->neg_classes != NULL)
          {
        for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++);
        trans->neg_classes =
          (tre_ctype_t*)xmalloc(sizeof(*trans->neg_classes) * (i + 1));
        if (trans->neg_classes == NULL)
          return REG_ESPACE;
        for (i = 0; p1->neg_classes[i] != (tre_ctype_t)0; i++)
          trans->neg_classes[i] = p1->neg_classes[i];
        trans->neg_classes[i] = (tre_ctype_t)0;
          }
        else
          trans->neg_classes = NULL;

        /* Find out how many tags this transition has. */
        i = 0;
        if (p1->tags != NULL)
          while(p1->tags[i] >= 0)
        i++;
        j = 0;
        if (p2->tags != NULL)
          while(p2->tags[j] >= 0)
        j++;

        /* If we are overwriting a transition, free the old tag array. */
        if (trans->tags != NULL)
          xfree(trans->tags);
        trans->tags = NULL;

        /* If there were any tags, allocate an array and fill it. */
        if (i + j > 0)
          {
        trans->tags = (int*)xmalloc(sizeof(*trans->tags) * (i + j + 1));
        if (!trans->tags)
          return REG_ESPACE;
        i = 0;
        if (p1->tags != NULL)
          while(p1->tags[i] >= 0)
            {
              trans->tags[i] = p1->tags[i];
              i++;
            }
        l = i;
        j = 0;
        if (p2->tags != NULL)
          while (p2->tags[j] >= 0)
            {
              /* Don't add duplicates. */
              dup = 0;
              for (k = 0; k < i; k++)
            if (trans->tags[k] == p2->tags[j])
              {
                dup = 1;
                break;
              }
              if (!dup)
            trans->tags[l++] = p2->tags[j];
              j++;
            }
        trans->tags[l] = -1;
          }

        p2++;
      }
    p1++;
      }
  else
    /* Compute a maximum limit for the number of transitions leaving
       from each state. */
    while (p1->position >= 0)
      {
    p2 = orig_p2;
    while (p2->position >= 0)
      {
        counts[p1->position]++;
        p2++;
      }
    p1++;
      }
  return REG_OK;
}

/* Converts the syntax tree to a TNFA.  All the transitions in the TNFA are
   labelled with one character range (there are no transitions on empty
   strings).  The TNFA takes O(n^2) space in the worst case, `n' is size of
   the regexp. */
static reg_errcode_t
tre_ast_to_tnfa(tre_ast_node_t *node, tre_tnfa_transition_t *transitions,
        int *counts, int *offs)
{
  tre_union_t *uni;
  tre_catenation_t *cat;
  tre_iteration_t *iter;
  reg_errcode_t errcode = REG_OK;

  /* XXX - recurse using a stack!. */
  switch (node->type)
    {
    case LITERAL:
      break;
    case UNION:
      uni = (tre_union_t *)node->obj;
      errcode = tre_ast_to_tnfa(uni->left, transitions, counts, offs);
      if (errcode != REG_OK)
    return errcode;
      errcode = tre_ast_to_tnfa(uni->right, transitions, counts, offs);
      break;

    case CATENATION:
      cat = (tre_catenation_t *)node->obj;
      /* Add a transition from each position in cat->left->lastpos
     to each position in cat->right->firstpos. */
      errcode = tre_make_trans(cat->left->lastpos, cat->right->firstpos,
                   transitions, counts, offs);
      if (errcode != REG_OK)
    return errcode;
      errcode = tre_ast_to_tnfa(cat->left, transitions, counts, offs);
      if (errcode != REG_OK)
    return errcode;
      errcode = tre_ast_to_tnfa(cat->right, transitions, counts, offs);
      break;

    case ITERATION:
      iter = (tre_iteration_t *)node->obj;
      assert(iter->max == -1 || iter->max == 1);

      if (iter->max == -1)
    {
      assert(iter->min == 0 || iter->min == 1);
      /* Add a transition from each last position in the iterated
         expression to each first position. */
      errcode = tre_make_trans(iter->arg->lastpos, iter->arg->firstpos,
                   transitions, counts, offs);
      if (errcode != REG_OK)
        return errcode;
    }
      errcode = tre_ast_to_tnfa(iter->arg, transitions, counts, offs);
      break;
    }
  return errcode;
}


#define ERROR_EXIT(err)       \
  do                  \
    {                 \
      errcode = err;          \
      if (/*CONSTCOND*/1)     \
        goto error_exit;      \
    }                 \
 while (/*CONSTCOND*/0)

#ifdef __cplusplus
#define restrict
#endif
int
regcomp(regex_t *restrict preg, const char *restrict regex, int cflags)
{
  tre_stack_t *stack;
  tre_ast_node_t *tree, *tmp_ast_l, *tmp_ast_r;
  tre_pos_and_tags_t *p;
  int *counts = NULL, *offs = NULL;
  int i, add = 0;
  tre_tnfa_transition_t *transitions, *initial;
  tre_tnfa_t *tnfa = NULL;
  tre_submatch_data_t *submatch_data;
  tre_tag_direction_t *tag_directions = NULL;
  reg_errcode_t errcode;
  tre_mem_t mem;

  /* Parse context. */
  tre_parse_ctx_t parse_ctx;

  /* Allocate a stack used throughout the compilation process for various
     purposes. */
  stack = tre_stack_new(512, 10240, 128);
  if (!stack)
    return REG_ESPACE;
  /* Allocate a fast memory allocator. */
  mem = tre_mem_new();
  if (!mem)
    {
      tre_stack_destroy(stack);
      return REG_ESPACE;
    }

  /* Parse the regexp. */
  memset(&parse_ctx, 0, sizeof(parse_ctx));
  parse_ctx.mem = mem;
  parse_ctx.stack = stack;
  parse_ctx.re = regex;
  parse_ctx.cflags = cflags;
  parse_ctx.max_backref = -1;
  errcode = tre_parse(&parse_ctx);
  if (errcode != REG_OK)
    ERROR_EXIT(errcode);
  preg->re_nsub = parse_ctx.submatch_id - 1;
  tree = parse_ctx.n;

#ifdef TRE_DEBUG
  tre_ast_print(tree);
#endif /* TRE_DEBUG */

  /* Referring to nonexistent subexpressions is illegal. */
  if (parse_ctx.max_backref > (int)preg->re_nsub)
    ERROR_EXIT(REG_ESUBREG);

  /* Allocate the TNFA struct. */
  tnfa = (tre_tnfa_t*)xcalloc(1, sizeof(tre_tnfa_t));
  if (tnfa == NULL)
    ERROR_EXIT(REG_ESPACE);
  tnfa->have_backrefs = parse_ctx.max_backref >= 0;
  tnfa->have_approx = 0;
  tnfa->num_submatches = parse_ctx.submatch_id;

  /* Set up tags for submatch addressing.  If REG_NOSUB is set and the
     regexp does not have back references, this can be skipped. */
  if (tnfa->have_backrefs || !(cflags & REG_NOSUB))
    {

      /* Figure out how many tags we will need. */
      errcode = tre_add_tags(NULL, stack, tree, tnfa);
      if (errcode != REG_OK)
    ERROR_EXIT(errcode);

      if (tnfa->num_tags > 0)
    {
      tag_directions = (tre_tag_direction_t*)xmalloc(sizeof(*tag_directions)
                   * (tnfa->num_tags + 1));
      if (tag_directions == NULL)
        ERROR_EXIT(REG_ESPACE);
      tnfa->tag_directions = tag_directions;
      memset(tag_directions, -1,
         sizeof(*tag_directions) * (tnfa->num_tags + 1));
    }
      tnfa->minimal_tags = (int*)xcalloc((unsigned)tnfa->num_tags * 2 + 1,
                   sizeof(*tnfa->minimal_tags));
      if (tnfa->minimal_tags == NULL)
    ERROR_EXIT(REG_ESPACE);

      submatch_data = (tre_submatch_data_t*)xcalloc((unsigned)parse_ctx.submatch_id,
                  sizeof(*submatch_data));
      if (submatch_data == NULL)
    ERROR_EXIT(REG_ESPACE);
      tnfa->submatch_data = submatch_data;

      errcode = tre_add_tags(mem, stack, tree, tnfa);
      if (errcode != REG_OK)
    ERROR_EXIT(errcode);

    }

  /* Expand iteration nodes. */
  errcode = tre_expand_ast(mem, stack, tree, &parse_ctx.position,
               tag_directions);
  if (errcode != REG_OK)
    ERROR_EXIT(errcode);

  /* Add a dummy node for the final state.
     XXX - For certain patterns this dummy node can be optimized away,
       for example "a*" or "ab*".   Figure out a simple way to detect
       this possibility. */
  tmp_ast_l = tree;
  tmp_ast_r = tre_ast_new_literal(mem, 0, 0, parse_ctx.position++);
  if (tmp_ast_r == NULL)
    ERROR_EXIT(REG_ESPACE);

  tree = tre_ast_new_catenation(mem, tmp_ast_l, tmp_ast_r);
  if (tree == NULL)
    ERROR_EXIT(REG_ESPACE);

  errcode = tre_compute_nfl(mem, stack, tree);
  if (errcode != REG_OK)
    ERROR_EXIT(errcode);

  counts = (int*)xmalloc(sizeof(int) * parse_ctx.position);
  if (counts == NULL)
    ERROR_EXIT(REG_ESPACE);

  offs = (int*)xmalloc(sizeof(int) * parse_ctx.position);
  if (offs == NULL)
    ERROR_EXIT(REG_ESPACE);

  for (i = 0; i < parse_ctx.position; i++)
    counts[i] = 0;
  tre_ast_to_tnfa(tree, NULL, counts, NULL);

  add = 0;
  for (i = 0; i < parse_ctx.position; i++)
    {
      offs[i] = add;
      add += counts[i] + 1;
      counts[i] = 0;
    }
  transitions = (tre_tnfa_transition_t*)xcalloc((unsigned)add + 1, sizeof(*transitions));
  if (transitions == NULL)
    ERROR_EXIT(REG_ESPACE);
  tnfa->transitions = transitions;
  tnfa->num_transitions = add;

  errcode = tre_ast_to_tnfa(tree, transitions, counts, offs);
  if (errcode != REG_OK)
    ERROR_EXIT(errcode);

  tnfa->firstpos_chars = NULL;

  p = tree->firstpos;
  i = 0;
  while (p->position >= 0)
    {
      i++;
      p++;
    }

  initial = (tre_tnfa_transition_t*)xcalloc((unsigned)i + 1, sizeof(tre_tnfa_transition_t));
  if (initial == NULL)
    ERROR_EXIT(REG_ESPACE);
  tnfa->initial = initial;

  i = 0;
  for (p = tree->firstpos; p->position >= 0; p++)
    {
      initial[i].state = transitions + offs[p->position];
      initial[i].state_id = p->position;
      initial[i].tags = NULL;
      /* Copy the arrays p->tags, and p->params, they are allocated
     from a tre_mem object. */
      if (p->tags)
    {
      int j;
      for (j = 0; p->tags[j] >= 0; j++);
      initial[i].tags = (int*)xmalloc(sizeof(*p->tags) * (j + 1));
      if (!initial[i].tags)
        ERROR_EXIT(REG_ESPACE);
      memcpy(initial[i].tags, p->tags, sizeof(*p->tags) * (j + 1));
    }
      initial[i].assertions = p->assertions;
      i++;
    }
  initial[i].state = NULL;

  tnfa->num_transitions = add;
  tnfa->final = transitions + offs[tree->lastpos[0].position];
  tnfa->num_states = parse_ctx.position;
  tnfa->cflags = cflags;

  tre_mem_destroy(mem);
  tre_stack_destroy(stack);
  xfree(counts);
  xfree(offs);

  preg->TRE_REGEX_T_FIELD = (void *)tnfa;
  return REG_OK;

 error_exit:
  /* Free everything that was allocated and return the error code. */
  tre_mem_destroy(mem);
  if (stack != NULL)
    tre_stack_destroy(stack);
  if (counts != NULL)
    xfree(counts);
  if (offs != NULL)
    xfree(offs);
  preg->TRE_REGEX_T_FIELD = (void *)tnfa;
  regfree(preg);
  return errcode;
}

#ifdef __cplusplus
#undef restrict
#endif


void
regfree(regex_t *preg)
{
  tre_tnfa_t *tnfa;
  unsigned int i;
  tre_tnfa_transition_t *trans;

  tnfa = (tre_tnfa_t*)preg->TRE_REGEX_T_FIELD;
  if (!tnfa)
    return;

  for (i = 0; i < tnfa->num_transitions; i++)
    if (tnfa->transitions[i].state)
      {
    if (tnfa->transitions[i].tags)
      xfree(tnfa->transitions[i].tags);
    if (tnfa->transitions[i].neg_classes)
      xfree(tnfa->transitions[i].neg_classes);
      }
  if (tnfa->transitions)
    xfree(tnfa->transitions);

  if (tnfa->initial)
    {
      for (trans = tnfa->initial; trans->state; trans++)
    {
      if (trans->tags)
        xfree(trans->tags);
    }
      xfree(tnfa->initial);
    }

  if (tnfa->submatch_data)
    {
      for (i = 0; i < tnfa->num_submatches; i++)
    if (tnfa->submatch_data[i].parents)
      xfree(tnfa->submatch_data[i].parents);
      xfree(tnfa->submatch_data);
    }

  if (tnfa->tag_directions)
    xfree(tnfa->tag_directions);
  if (tnfa->firstpos_chars)
    xfree(tnfa->firstpos_chars);
  if (tnfa->minimal_tags)
    xfree(tnfa->minimal_tags);
  xfree(tnfa);
}
#include <string.h>
#ifndef JSI_AMALGAMATION
#include <regex.h>
#endif
#include <stdio.h>
//#include "locale_impl.h"

/* Error message strings for error codes listed in `regex.h'.  This list
   needs to be in sync with the codes listed there, naturally. */

/* Converted to single string by Rich Felker to remove the need for
 * data relocations at runtime, 27 Feb 2006. */

static const char messages[] = {
  "No error\0"
  "No match\0"
  "Invalid regexp\0"
  "Unknown collating element\0"
  "Unknown character class name\0"
  "Trailing backslash\0"
  "Invalid back reference\0"
  "Missing ']'\0"
  "Missing ')'\0"
  "Missing '}'\0"
  "Invalid contents of {}\0"
  "Invalid character range\0"
  "Out of memory\0"
  "Repetition not preceded by valid expression\0"
  "\0Unknown error"
};

#ifdef __cplusplus
#define restrict
#endif
size_t regerror(int e, const regex_t *restrict preg, char *restrict buf, size_t size)
{
    const char *s;
    for (s=messages; e && *s; e--, s+=strlen(s)+1);
    if (!*s) s++;
    //s = LCTRANS_CUR(s);
    return 1+snprintf(buf, size, "%s", s);
}
#ifdef __cplusplus
#undef restrict
#endif
/*
  regexec.c - TRE POSIX compatible matching functions (and more).

  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <wctype.h>
#include <limits.h>

#ifndef JSI_AMALGAMATION
#include <regex.h>
#include "tre.h"
#endif

#include <assert.h>

static void
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
		const tre_tnfa_t *tnfa, int *tags, int match_eo);

/***********************************************************************
 from tre-match-utils.h
***********************************************************************/

#define GET_NEXT_WCHAR() do {                                                 \
    prev_c = next_c; pos += pos_add_next;                                     \
    if ((pos_add_next = mbtowc(&next_c, str_byte, MB_LEN_MAX)) <= 0) {        \
        if (pos_add_next < 0) { ret = REG_NOMATCH; goto error_exit; }         \
        else pos_add_next++;                                                  \
    }                                                                         \
    str_byte += pos_add_next;                                                 \
  } while (0)

#define IS_WORD_CHAR(c)	 ((c) == L'_' || tre_isalnum(c))

#define CHECK_ASSERTIONS(assertions)					      \
  (((assertions & ASSERT_AT_BOL)					      \
    && (pos > 0 || reg_notbol)						      \
    && (prev_c != L'\n' || !reg_newline))				      \
   || ((assertions & ASSERT_AT_EOL)					      \
       && (next_c != L'\0' || reg_noteol)				      \
       && (next_c != L'\n' || !reg_newline))				      \
   || ((assertions & ASSERT_AT_BOW)					      \
       && (IS_WORD_CHAR(prev_c) || !IS_WORD_CHAR(next_c)))	              \
   || ((assertions & ASSERT_AT_EOW)					      \
       && (!IS_WORD_CHAR(prev_c) || IS_WORD_CHAR(next_c)))		      \
   || ((assertions & ASSERT_AT_WB)					      \
       && (pos != 0 && next_c != L'\0'					      \
	   && IS_WORD_CHAR(prev_c) == IS_WORD_CHAR(next_c)))		      \
   || ((assertions & ASSERT_AT_WB_NEG)					      \
       && (pos == 0 || next_c == L'\0'					      \
	   || IS_WORD_CHAR(prev_c) != IS_WORD_CHAR(next_c))))

#define CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)                             \
  (((trans_i->assertions & ASSERT_CHAR_CLASS)                                 \
       && !(tnfa->cflags & REG_ICASE)                                         \
       && !tre_isctype((tre_cint_t)prev_c, trans_i->u.rclass))                 \
    || ((trans_i->assertions & ASSERT_CHAR_CLASS)                             \
        && (tnfa->cflags & REG_ICASE)                                         \
        && !tre_isctype(tre_tolower((tre_cint_t)prev_c),trans_i->u.rclass)     \
	&& !tre_isctype(tre_toupper((tre_cint_t)prev_c),trans_i->u.rclass))    \
    || ((trans_i->assertions & ASSERT_CHAR_CLASS_NEG)                         \
        && tre_neg_char_classes_match(trans_i->neg_classes,(tre_cint_t)prev_c,\
                                      tnfa->cflags & REG_ICASE)))




/* Returns 1 if `t1' wins `t2', 0 otherwise. */
static int
tre_tag_order(int num_tags, tre_tag_direction_t *tag_directions,
	      int *t1, int *t2)
{
  int i;
  for (i = 0; i < num_tags; i++)
    {
      if (tag_directions[i] == TRE_TAG_MINIMIZE)
	{
	  if (t1[i] < t2[i])
	    return 1;
	  if (t1[i] > t2[i])
	    return 0;
	}
      else
	{
	  if (t1[i] > t2[i])
	    return 1;
	  if (t1[i] < t2[i])
	    return 0;
	}
    }
  /*  assert(0);*/
  return 0;
}

static int
tre_neg_char_classes_match(tre_ctype_t *classes, tre_cint_t wc, int icase)
{
  while (*classes != (tre_ctype_t)0)
    if ((!icase && tre_isctype(wc, *classes))
	|| (icase && (tre_isctype(tre_toupper(wc), *classes)
		      || tre_isctype(tre_tolower(wc), *classes))))
      return 1; /* Match. */
    else
      classes++;
  return 0; /* No match. */
}


/***********************************************************************
 from tre-match-parallel.c
***********************************************************************/

/*
  This algorithm searches for matches basically by reading characters
  in the searched strval one by one, starting at the beginning.	 All
  matching paths in the TNFA are traversed in parallel.	 When two or
  more paths reach the same state, exactly one is chosen according to
  tag ordering rules; if returning submatches is not required it does
  not matter which path is chosen.

  The worst case time required for finding the leftmost and longest
  match, or determining that there is no match, is always linearly
  dependent on the length of the text being searched.

  This algorithm cannot handle TNFAs with back referencing nodes.
  See `tre-match-backtrack.c'.
*/

typedef struct {
  tre_tnfa_transition_t *state;
  int *tags;
} tre_tnfa_reach_t;

typedef struct {
  int pos;
  int **tags;
} tre_reach_pos_t;


static reg_errcode_t
tre_tnfa_run_parallel(const tre_tnfa_t *tnfa, const void *strval,
		      int *match_tags, int eflags,
		      int *match_end_ofs)
{
  /* State variables required by GET_NEXT_WCHAR. */
  tre_char_t prev_c = 0, next_c = 0;
  const char *str_byte = (char *)strval;
  int pos = -1;
  int pos_add_next = 1;
#ifdef TRE_MBSTATE
  mbstate_t mbstate;
#endif /* TRE_MBSTATE */
  int reg_notbol = eflags & REG_NOTBOL;
  int reg_noteol = eflags & REG_NOTEOL;
  int reg_newline = tnfa->cflags & REG_NEWLINE;
  reg_errcode_t ret;

  char *buf;
  tre_tnfa_transition_t *trans_i;
  tre_tnfa_reach_t *reach, *reach_next, *reach_i, *reach_next_i;
  tre_reach_pos_t *reach_pos;
  int *tag_i;
  int num_tags, i;

  int match_eo = -1;	   /* end offset of match (-1 if no match found yet) */
  int new_match = 0;
  int *tmp_tags = NULL;
  int *tmp_iptr;

#ifdef TRE_MBSTATE
  memset(&mbstate, '\0', sizeof(mbstate));
#endif /* TRE_MBSTATE */

  if (!match_tags)
    num_tags = 0;
  else
    num_tags = tnfa->num_tags;

  /* Allocate memory for temporary data required for matching.	This needs to
     be done for every matching operation to be thread safe.  This allocates
     everything in a single large block from the stack frame using alloca()
     or with malloc() if alloca is unavailable. */
  {
    int tbytes, rbytes, pbytes, xbytes, total_bytes;
    char *tmp_buf;
    /* Compute the length of the block we need. */
    tbytes = sizeof(*tmp_tags) * num_tags;
    rbytes = sizeof(*reach_next) * (tnfa->num_states + 1);
    pbytes = sizeof(*reach_pos) * tnfa->num_states;
    xbytes = sizeof(int) * num_tags;
    total_bytes =
      (sizeof(long) - 1) * 4 /* for alignment paddings */
      + (rbytes + xbytes * tnfa->num_states) * 2 + tbytes + pbytes;

    /* Allocate the memory. */
    buf = (char *)xmalloc((unsigned)total_bytes);
    if (buf == NULL)
      return REG_ESPACE;
    memset(buf, 0, (size_t)total_bytes);

    /* Get the various pointers within tmp_buf (properly aligned). */
    tmp_tags = (int *)buf;
    tmp_buf = buf + tbytes;
    tmp_buf += ALIGN(tmp_buf, long);
    reach_next = (tre_tnfa_reach_t *)tmp_buf;
    tmp_buf += rbytes;
    tmp_buf += ALIGN(tmp_buf, long);
    reach = (tre_tnfa_reach_t *)tmp_buf;
    tmp_buf += rbytes;
    tmp_buf += ALIGN(tmp_buf, long);
    reach_pos = (tre_reach_pos_t *)tmp_buf;
    tmp_buf += pbytes;
    tmp_buf += ALIGN(tmp_buf, long);
    for (i = 0; i < tnfa->num_states; i++)
      {
	reach[i].tags = (int *)tmp_buf;
	tmp_buf += xbytes;
	reach_next[i].tags = (int *)tmp_buf;
	tmp_buf += xbytes;
      }
  }

  for (i = 0; i < tnfa->num_states; i++)
    reach_pos[i].pos = -1;

  GET_NEXT_WCHAR();
  pos = 0;

  reach_next_i = reach_next;
  while (1)
    {
      /* If no match found yet, add the initial states to `reach_next'. */
      if (match_eo < 0)
	{
	  trans_i = tnfa->initial;
	  while (trans_i->state != NULL)
	    {
	      if (reach_pos[trans_i->state_id].pos < pos)
		{
		  if (trans_i->assertions
		      && CHECK_ASSERTIONS(trans_i->assertions))
		    {
		      trans_i++;
		      continue;
		    }

		  reach_next_i->state = trans_i->state;
		  for (i = 0; i < num_tags; i++)
		    reach_next_i->tags[i] = -1;
		  tag_i = trans_i->tags;
		  if (tag_i)
		    while (*tag_i >= 0)
		      {
			if (*tag_i < num_tags)
			  reach_next_i->tags[*tag_i] = pos;
			tag_i++;
		      }
		  if (reach_next_i->state == tnfa->final)
		    {
		      match_eo = pos;
		      new_match = 1;
		      for (i = 0; i < num_tags; i++)
			match_tags[i] = reach_next_i->tags[i];
		    }
		  reach_pos[trans_i->state_id].pos = pos;
		  reach_pos[trans_i->state_id].tags = &reach_next_i->tags;
		  reach_next_i++;
		}
	      trans_i++;
	    }
	  reach_next_i->state = NULL;
	}
      else
	{
	  if (num_tags == 0 || reach_next_i == reach_next)
	    /* We have found a match. */
	    break;
	}

      /* Check for end of strval. */
      if (!next_c) break;

      GET_NEXT_WCHAR();

      /* Swap `reach' and `reach_next'. */
      reach_i = reach;
      reach = reach_next;
      reach_next = reach_i;

      /* For each state in `reach', weed out states that don't fulfill the
	 minimal matching conditions. */
      if (tnfa->num_minimals && new_match)
	{
	  new_match = 0;
	  reach_next_i = reach_next;
	  for (reach_i = reach; reach_i->state; reach_i++)
	    {
	      int skip = 0;
	      for (i = 0; tnfa->minimal_tags[i] >= 0; i += 2)
		{
		  int end = tnfa->minimal_tags[i];
		  int start = tnfa->minimal_tags[i + 1];
		  if (end >= num_tags)
		    {
		      skip = 1;
		      break;
		    }
		  else if (reach_i->tags[start] == match_tags[start]
			   && reach_i->tags[end] < match_tags[end])
		    {
		      skip = 1;
		      break;
		    }
		}
	      if (!skip)
		{
		  reach_next_i->state = reach_i->state;
		  tmp_iptr = reach_next_i->tags;
		  reach_next_i->tags = reach_i->tags;
		  reach_i->tags = tmp_iptr;
		  reach_next_i++;
		}
	    }
	  reach_next_i->state = NULL;

	  /* Swap `reach' and `reach_next'. */
	  reach_i = reach;
	  reach = reach_next;
	  reach_next = reach_i;
	}

      /* For each state in `reach' see if there is a transition leaving with
	 the current input symbol to a state not yet in `reach_next', and
	 add the destination states to `reach_next'. */
      reach_next_i = reach_next;
      for (reach_i = reach; reach_i->state; reach_i++)
	{
	  for (trans_i = reach_i->state; trans_i->state; trans_i++)
	    {
	      /* Does this transition match the input symbol? */
	      if (trans_i->code_min <= (tre_cint_t)prev_c &&
		  trans_i->code_max >= (tre_cint_t)prev_c)
		{
		  if (trans_i->assertions
		      && (CHECK_ASSERTIONS(trans_i->assertions)
			  || CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
		    {
		      continue;
		    }

		  /* Compute the tags after this transition. */
		  for (i = 0; i < num_tags; i++)
		    tmp_tags[i] = reach_i->tags[i];
		  tag_i = trans_i->tags;
		  if (tag_i != NULL)
		    while (*tag_i >= 0)
		      {
			if (*tag_i < num_tags)
			  tmp_tags[*tag_i] = pos;
			tag_i++;
		      }

		  if (reach_pos[trans_i->state_id].pos < pos)
		    {
		      /* Found an unvisited node. */
		      reach_next_i->state = trans_i->state;
		      tmp_iptr = reach_next_i->tags;
		      reach_next_i->tags = tmp_tags;
		      tmp_tags = tmp_iptr;
		      reach_pos[trans_i->state_id].pos = pos;
		      reach_pos[trans_i->state_id].tags = &reach_next_i->tags;

		      if (reach_next_i->state == tnfa->final
			  && (match_eo == -1
			      || (num_tags > 0
				  && reach_next_i->tags[0] <= match_tags[0])))
			{
			  match_eo = pos;
			  new_match = 1;
			  for (i = 0; i < num_tags; i++)
			    match_tags[i] = reach_next_i->tags[i];
			}
		      reach_next_i++;

		    }
		  else
		    {
		      assert(reach_pos[trans_i->state_id].pos == pos);
		      /* Another path has also reached this state.  We choose
			 the winner by examining the tag values for both
			 paths. */
		      if (tre_tag_order(num_tags, tnfa->tag_directions,
					tmp_tags,
					*reach_pos[trans_i->state_id].tags))
			{
			  /* The new path wins. */
			  tmp_iptr = *reach_pos[trans_i->state_id].tags;
			  *reach_pos[trans_i->state_id].tags = tmp_tags;
			  if (trans_i->state == tnfa->final)
			    {
			      match_eo = pos;
			      new_match = 1;
			      for (i = 0; i < num_tags; i++)
				match_tags[i] = tmp_tags[i];
			    }
			  tmp_tags = tmp_iptr;
			}
		    }
		}
	    }
	}
      reach_next_i->state = NULL;
    }

  *match_end_ofs = match_eo;
  ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
error_exit:
  xfree(buf);
  return ret;
}



/***********************************************************************
 from tre-match-backtrack.c
***********************************************************************/

/*
  This matcher is for regexps that use back referencing.  Regexp matching
  with back referencing is an NP-complete problem on the number of back
  references.  The easiest way to match them is to use a backtracking
  routine which basically goes through all possible paths in the TNFA
  and chooses the one which results in the best (leftmost and longest)
  match.  This can be spectacularly expensive and may run out of stack
  space, but there really is no better known generic algorithm.	 Quoting
  Henry Spencer from comp.compilers:
  <URL: http://compilers.iecc.com/comparch/article/93-03-102>

    POSIX.2 REs require longest match, which is really exciting to
    implement since the obsolete ("basic") variant also includes
    \<digit>.  I haven't found a better way of tackling this than doing
    a preliminary match using a DFA (or simulation) on a modified RE
    that just replicates subREs for \<digit>, and then doing a
    backtracking match to determine whether the subRE matches were
    right.  This can be rather slow, but I console myself with the
    thought that people who use \<digit> deserve very slow execution.
    (Pun unintentional but very appropriate.)

*/

typedef struct {
  int pos;
  const char *str_byte;
  tre_tnfa_transition_t *state;
  int state_id;
  int next_c;
  int *tags;
#ifdef TRE_MBSTATE
  mbstate_t mbstate;
#endif /* TRE_MBSTATE */
} tre_backtrack_item_t;

typedef struct tre_backtrack_struct {
  tre_backtrack_item_t item;
  struct tre_backtrack_struct *prev;
  struct tre_backtrack_struct *next;
} *tre_backtrack_t;

#ifdef TRE_MBSTATE
#define BT_STACK_MBSTATE_IN  stack->item.mbstate = (mbstate)
#define BT_STACK_MBSTATE_OUT (mbstate) = stack->item.mbstate
#else /* !TRE_MBSTATE */
#define BT_STACK_MBSTATE_IN
#define BT_STACK_MBSTATE_OUT
#endif /* !TRE_MBSTATE */

#define tre_bt_mem_new		  tre_mem_new
#define tre_bt_mem_alloc	  tre_mem_alloc
#define tre_bt_mem_destroy	  tre_mem_destroy


#define BT_STACK_PUSH(_pos, _str_byte, _str_wide, _state, _state_id, _next_c, _tags, _mbstate) \
  do									      \
    {									      \
      int i;								      \
      if (!stack->next)							      \
	{								      \
	  tre_backtrack_t s;						      \
	  s = (tre_backtrack_t)tre_bt_mem_alloc(mem, sizeof(*s));			      \
	  if (!s)							      \
	    {								      \
	      tre_bt_mem_destroy(mem);					      \
	      if (tags)							      \
		xfree(tags);						      \
	      if (pmatch)						      \
		xfree(pmatch);						      \
	      if (states_seen)						      \
		xfree(states_seen);					      \
	      return REG_ESPACE;					      \
	    }								      \
	  s->prev = stack;						      \
	  s->next = NULL;						      \
	  s->item.tags = (int*)tre_bt_mem_alloc(mem,				      \
					  sizeof(*tags) * tnfa->num_tags);    \
	  if (!s->item.tags)						      \
	    {								      \
	      tre_bt_mem_destroy(mem);					      \
	      if (tags)							      \
		xfree(tags);						      \
	      if (pmatch)						      \
		xfree(pmatch);						      \
	      if (states_seen)						      \
		xfree(states_seen);					      \
	      return REG_ESPACE;					      \
	    }								      \
	  stack->next = s;						      \
	  stack = s;							      \
	}								      \
      else								      \
	stack = stack->next;						      \
      stack->item.pos = (_pos);						      \
      stack->item.str_byte = (_str_byte);				      \
      stack->item.state = (_state);					      \
      stack->item.state_id = (_state_id);				      \
      stack->item.next_c = (_next_c);					      \
      for (i = 0; i < tnfa->num_tags; i++)				      \
	stack->item.tags[i] = (_tags)[i];				      \
      BT_STACK_MBSTATE_IN;						      \
    }									      \
  while (0)

#define BT_STACK_POP()							      \
  do									      \
    {									      \
      int i;								      \
      assert(stack->prev);						      \
      pos = stack->item.pos;						      \
      str_byte = stack->item.str_byte;					      \
      state = stack->item.state;					      \
      next_c = stack->item.next_c;					      \
      for (i = 0; i < tnfa->num_tags; i++)				      \
	tags[i] = stack->item.tags[i];					      \
      BT_STACK_MBSTATE_OUT;						      \
      stack = stack->prev;						      \
    }									      \
  while (0)

#undef MIN
#define MIN(a, b) ((a) <= (b) ? (a) : (b))

static reg_errcode_t
tre_tnfa_run_backtrack(const tre_tnfa_t *tnfa, const void *strval,
		       int *match_tags, int eflags, int *match_end_ofs)
{
  /* State variables required by GET_NEXT_WCHAR. */
  tre_char_t prev_c = 0, next_c = 0;
  const char *str_byte = (char*)strval;
  int pos = 0;
  int pos_add_next = 1;
#ifdef TRE_MBSTATE
  mbstate_t mbstate;
#endif /* TRE_MBSTATE */
  int reg_notbol = eflags & REG_NOTBOL;
  int reg_noteol = eflags & REG_NOTEOL;
  int reg_newline = tnfa->cflags & REG_NEWLINE;

  /* These are used to remember the necessary values of the above
     variables to return to the position where the current search
     started from. */
  int next_c_start;
  const char *str_byte_start;
  int pos_start = -1;
#ifdef TRE_MBSTATE
  mbstate_t mbstate_start;
#endif /* TRE_MBSTATE */

  /* End offset of best match so far, or -1 if no match found yet. */
  int match_eo = -1;
  /* Tag arrays. */
  int *next_tags, *tags = NULL;
  /* Current TNFA state. */
  tre_tnfa_transition_t *state;
  int *states_seen = NULL;

  /* Memory allocator to for allocating the backtracking stack. */
  tre_mem_t mem = tre_bt_mem_new();

  /* The backtracking stack. */
  tre_backtrack_t stack;

  tre_tnfa_transition_t *trans_i;
  regmatch_t *pmatch = NULL;
  int ret;

#ifdef TRE_MBSTATE
  memset(&mbstate, '\0', sizeof(mbstate));
#endif /* TRE_MBSTATE */

  if (!mem)
    return REG_ESPACE;
  stack = (tre_backtrack_t)tre_bt_mem_alloc(mem, sizeof(*stack));
  if (!stack)
    {
      ret = REG_ESPACE;
      goto error_exit;
    }
  stack->prev = NULL;
  stack->next = NULL;

  if (tnfa->num_tags)
    {
      tags = (int*)xmalloc(sizeof(*tags) * tnfa->num_tags);
      if (!tags)
	{
	  ret = REG_ESPACE;
	  goto error_exit;
	}
    }
  if (tnfa->num_submatches)
    {
      pmatch = (regmatch_t*)xmalloc(sizeof(*pmatch) * tnfa->num_submatches);
      if (!pmatch)
	{
	  ret = REG_ESPACE;
	  goto error_exit;
	}
    }
  if (tnfa->num_states)
    {
      states_seen = (int*)xmalloc(sizeof(*states_seen) * tnfa->num_states);
      if (!states_seen)
	{
	  ret = REG_ESPACE;
	  goto error_exit;
	}
    }

 retry:
  {
    int i;
    for (i = 0; i < tnfa->num_tags; i++)
      {
	tags[i] = -1;
	if (match_tags)
	  match_tags[i] = -1;
      }
    for (i = 0; i < tnfa->num_states; i++)
      states_seen[i] = 0;
  }

  state = NULL;
  pos = pos_start;
  GET_NEXT_WCHAR();
  pos_start = pos;
  next_c_start = next_c;
  str_byte_start = str_byte;
#ifdef TRE_MBSTATE
  mbstate_start = mbstate;
#endif /* TRE_MBSTATE */

  /* Handle initial states. */
  next_tags = NULL;
  for (trans_i = tnfa->initial; trans_i->state; trans_i++)
    {
      if (trans_i->assertions && CHECK_ASSERTIONS(trans_i->assertions))
	{
	  continue;
	}
      if (state == NULL)
	{
	  /* Start from this state. */
	  state = trans_i->state;
	  next_tags = trans_i->tags;
	}
      else
	{
	  /* Backtrack to this state. */
	  BT_STACK_PUSH(pos, str_byte, 0, trans_i->state,
			trans_i->state_id, next_c, tags, mbstate);
	  {
	    int *tmp = trans_i->tags;
	    if (tmp)
	      while (*tmp >= 0)
		stack->item.tags[*tmp++] = pos;
	  }
	}
    }

  if (next_tags)
    for (; *next_tags >= 0; next_tags++)
      tags[*next_tags] = pos;


  if (state == NULL)
    goto backtrack;

  while (1)
    {
      tre_tnfa_transition_t *next_state;
      int empty_br_match;

      if (state == tnfa->final)
	{
	  if (match_eo < pos
	      || (match_eo == pos
		  && match_tags
		  && tre_tag_order(tnfa->num_tags, tnfa->tag_directions,
				   tags, match_tags)))
	    {
	      int i;
	      /* This match wins the previous match. */
	      match_eo = pos;
	      if (match_tags)
		for (i = 0; i < tnfa->num_tags; i++)
		  match_tags[i] = tags[i];
	    }
	  /* Our TNFAs never have transitions leaving from the final state,
	     so we jump right to backtracking. */
	  goto backtrack;
	}

      /* Go to the next character in the input strval. */
      empty_br_match = 0;
      trans_i = state;
      if (trans_i->state && trans_i->assertions & ASSERT_BACKREF)
	{
	  /* This is a back reference state.  All transitions leaving from
	     this state have the same back reference "assertion".  Instead
	     of reading the next character, we match the back reference. */
	  int so, eo, bt = trans_i->u.backref;
	  int bt_len;
	  int result;

	  /* Get the substring we need to match against.  Remember to
	     turn off REG_NOSUB temporarily. */
	  tre_fill_pmatch(bt + 1, pmatch, tnfa->cflags & ~REG_NOSUB,
			  tnfa, tags, pos);
	  so = pmatch[bt].rm_so;
	  eo = pmatch[bt].rm_eo;
	  bt_len = eo - so;

	  result = strncmp((const char*)strval + so, str_byte - 1,
				 (size_t)bt_len);

	  if (result == 0)
	    {
	      /* Back reference matched.  Check for infinite loop. */
	      if (bt_len == 0)
		empty_br_match = 1;
	      if (empty_br_match && states_seen[trans_i->state_id])
		{
		  goto backtrack;
		}

	      states_seen[trans_i->state_id] = empty_br_match;

	      /* Advance in input strval and resync `prev_c', `next_c'
		 and pos. */
	      str_byte += bt_len - 1;
	      pos += bt_len - 1;
	      GET_NEXT_WCHAR();
	    }
	  else
	    {
	      goto backtrack;
	    }
	}
      else
	{
	  /* Check for end of strval. */
	  if (next_c == L'\0')
		goto backtrack;

	  /* Read the next character. */
	  GET_NEXT_WCHAR();
	}

      next_state = NULL;
      for (trans_i = state; trans_i->state; trans_i++)
	{
	  if (trans_i->code_min <= (tre_cint_t)prev_c
	      && trans_i->code_max >= (tre_cint_t)prev_c)
	    {
	      if (trans_i->assertions
		  && (CHECK_ASSERTIONS(trans_i->assertions)
		      || CHECK_CHAR_CLASSES(trans_i, tnfa, eflags)))
		{
		  continue;
		}

	      if (next_state == NULL)
		{
		  /* First matching transition. */
		  next_state = trans_i->state;
		  next_tags = trans_i->tags;
		}
	      else
		{
		  /* Second matching transition.  We may need to backtrack here
		     to take this transition instead of the first one, so we
		     push this transition in the backtracking stack so we can
		     jump back here if needed. */
		  BT_STACK_PUSH(pos, str_byte, 0, trans_i->state,
				trans_i->state_id, next_c, tags, mbstate);
		  {
		    int *tmp;
		    for (tmp = trans_i->tags; tmp && *tmp >= 0; tmp++)
		      stack->item.tags[*tmp] = pos;
		  }
#if 0 /* XXX - it's important not to look at all transitions here to keep
	 the stack small! */
		  break;
#endif
		}
	    }
	}

      if (next_state != NULL)
	{
	  /* Matching transitions were found.  Take the first one. */
	  state = next_state;

	  /* Update the tag values. */
	  if (next_tags)
	    while (*next_tags >= 0)
	      tags[*next_tags++] = pos;
	}
      else
	{
	backtrack:
	  /* A matching transition was not found.  Try to backtrack. */
	  if (stack->prev)
	    {
	      if (stack->item.state->assertions & ASSERT_BACKREF)
		{
		  states_seen[stack->item.state_id] = 0;
		}

	      BT_STACK_POP();
	    }
	  else if (match_eo < 0)
	    {
	      /* Try starting from a later position in the input strval. */
	      /* Check for end of strval. */
	      if (next_c == L'\0')
		    {
		      break;
		    }
	      next_c = next_c_start;
#ifdef TRE_MBSTATE
	      mbstate = mbstate_start;
#endif /* TRE_MBSTATE */
	      str_byte = str_byte_start;
	      goto retry;
	    }
	  else
	    {
	      break;
	    }
	}
    }

  ret = match_eo >= 0 ? REG_OK : REG_NOMATCH;
  *match_end_ofs = match_eo;

 error_exit:
  tre_bt_mem_destroy(mem);
#ifndef TRE_USE_ALLOCA
  if (tags)
    xfree(tags);
  if (pmatch)
    xfree(pmatch);
  if (states_seen)
    xfree(states_seen);
#endif /* !TRE_USE_ALLOCA */

  return ret;
}

/***********************************************************************
 from regexec.c
***********************************************************************/

/* Fills the POSIX.2 regmatch_t array according to the TNFA tag and match
   endpoint values. */
static void
tre_fill_pmatch(size_t nmatch, regmatch_t pmatch[], int cflags,
		const tre_tnfa_t *tnfa, int *tags, int match_eo)
{
  tre_submatch_data_t *submatch_data;
  unsigned int i, j;
  int *parents;

  i = 0;
  if (match_eo >= 0 && !(cflags & REG_NOSUB))
    {
      /* Construct submatch offsets from the tags. */
      submatch_data = tnfa->submatch_data;
      while (i < tnfa->num_submatches && i < nmatch)
	{
	  if (submatch_data[i].so_tag == tnfa->end_tag)
	    pmatch[i].rm_so = match_eo;
	  else
	    pmatch[i].rm_so = tags[submatch_data[i].so_tag];

	  if (submatch_data[i].eo_tag == tnfa->end_tag)
	    pmatch[i].rm_eo = match_eo;
	  else
	    pmatch[i].rm_eo = tags[submatch_data[i].eo_tag];

	  /* If either of the endpoints were not used, this submatch
	     was not part of the match. */
	  if (pmatch[i].rm_so == -1 || pmatch[i].rm_eo == -1)
	    pmatch[i].rm_so = pmatch[i].rm_eo = -1;

	  i++;
	}
      /* Reset all submatches that are not within all of their parent
	 submatches. */
      i = 0;
      while (i < tnfa->num_submatches && i < nmatch)
	{
	  if (pmatch[i].rm_eo == -1)
	    assert(pmatch[i].rm_so == -1);
	  assert(pmatch[i].rm_so <= pmatch[i].rm_eo);

	  parents = submatch_data[i].parents;
	  if (parents != NULL)
	    for (j = 0; parents[j] >= 0; j++)
	      {
		if (pmatch[i].rm_so < pmatch[parents[j]].rm_so
		    || pmatch[i].rm_eo > pmatch[parents[j]].rm_eo)
		  pmatch[i].rm_so = pmatch[i].rm_eo = -1;
	      }
	  i++;
	}
    }

  while (i < nmatch)
    {
      pmatch[i].rm_so = -1;
      pmatch[i].rm_eo = -1;
      i++;
    }
}

#ifdef __cplusplus
#define restrict
#endif
/*
  Wrapper functions for POSIX compatible regexp matching.
*/

int
regexec(const regex_t *restrict preg, const char *restrict strval,
	  size_t nmatch, regmatch_t pmatch[restrict], int eflags)
{
  tre_tnfa_t *tnfa = (tre_tnfa_t *)preg->TRE_REGEX_T_FIELD;
  reg_errcode_t status;
  int *tags = NULL, eo;
  if (tnfa->cflags & REG_NOSUB) nmatch = 0;
  if (tnfa->num_tags > 0 && nmatch > 0)
    {
      tags = (int*)xmalloc(sizeof(*tags) * tnfa->num_tags);
      if (tags == NULL)
	return REG_ESPACE;
    }

  /* Dispatch to the appropriate matcher. */
  if (tnfa->have_backrefs)
    {
      /* The regex has back references, use the backtracking matcher. */
      status = tre_tnfa_run_backtrack(tnfa, strval, tags, eflags, &eo);
    }
  else
    {
      /* Exact matching, no back references, use the parallel matcher. */
      status = tre_tnfa_run_parallel(tnfa, strval, tags, eflags, &eo);
    }

  if (status == REG_OK)
    /* A match was found, so fill the submatch registers. */
    tre_fill_pmatch(nmatch, pmatch, tnfa->cflags, tnfa, tags, eo);
  if (tags)
    xfree(tags);
  return status;
}
#ifdef __cplusplus
#undef restrict
#endif
/*
  tre-mem.c - TRE memory allocator

  Copyright (c) 2001-2009 Ville Laurikari <vl@iki.fi>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions
  are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
  ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

/*
  This memory allocator is for allocating small memory blocks efficiently
  in terms of memory overhead and execution speed.  The allocated blocks
  cannot be freed individually, only all at once.  There can be multiple
  allocators, though.
*/

#include <stdlib.h>
#include <string.h>

#ifndef JSI_AMALGAMATION
#include "tre.h"
#endif

/*
  This memory allocator is for allocating small memory blocks efficiently
  in terms of memory overhead and execution speed.  The allocated blocks
  cannot be freed individually, only all at once.  There can be multiple
  allocators, though.
*/

/* Returns a new memory allocator or NULL if out of memory. */
tre_mem_t
tre_mem_new_impl(int provided, void *provided_block)
{
  tre_mem_t mem;
  if (provided)
    {
      mem = (tre_mem_t)provided_block;
      memset(mem, 0, sizeof(*mem));
    }
  else
    mem = (tre_mem_t)xcalloc(1, sizeof(*mem));
  if (mem == NULL)
    return NULL;
  return mem;
}


/* Frees the memory allocator and all memory allocated with it. */
void
tre_mem_destroy(tre_mem_t mem)
{
  tre_list_t *tmp, *l = mem->blocks;

  while (l != NULL)
    {
      xfree(l->data);
      tmp = l->next;
      xfree(l);
      l = tmp;
    }
  xfree(mem);
}


/* Allocates a block of `size' bytes from `mem'.  Returns a pointer to the
   allocated block or NULL if an underlying malloc() failed. */
void *
tre_mem_alloc_impl(tre_mem_t mem, int provided, void *provided_block,
		   int zero, size_t size)
{
  void *ptr;

  if (mem->failed)
    {
      return NULL;
    }

  if (mem->n < size)
    {
      /* We need more memory than is available in the current block.
	 Allocate a new block. */
      tre_list_t *l;
      if (provided)
	{
	  if (provided_block == NULL)
	    {
	      mem->failed = 1;
	      return NULL;
	    }
	  mem->ptr = (char*)provided_block;
	  mem->n = TRE_MEM_BLOCK_SIZE;
	}
      else
	{
	  int block_size;
	  if (size * 8 > TRE_MEM_BLOCK_SIZE)
	    block_size = size * 8;
	  else
	    block_size = TRE_MEM_BLOCK_SIZE;
	  l = (tre_list_t*)xmalloc(sizeof(*l));
	  if (l == NULL)
	    {
	      mem->failed = 1;
	      return NULL;
	    }
	  l->data = xmalloc(block_size);
	  if (l->data == NULL)
	    {
	      xfree(l);
	      mem->failed = 1;
	      return NULL;
	    }
	  l->next = NULL;
	  if (mem->current != NULL)
	    mem->current->next = l;
	  if (mem->blocks == NULL)
	    mem->blocks = l;
	  mem->current = l;
	  mem->ptr = (char*)l->data;
	  mem->n = block_size;
	}
    }

  /* Make sure the next pointer will be aligned. */
  size += ALIGN(mem->ptr + size, long);

  /* Allocate from current block. */
  ptr = mem->ptr;
  mem->ptr += size;
  mem->n -= size;

  /* Set to zero if needed. */
  if (zero)
    memset(ptr, 0, size);

  return ptr;
}
/* A Bison parser, made by GNU Bison 3.0.4.  */

/* Bison interface for Yacc-like parsers in C

   Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc.

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* As a special exception, you may create a larger work that contains
   part or all of the Bison parser skeleton and distribute that work
   under terms of your choice, so long as that work isn't itself a
   parser generator using the skeleton or a modified version thereof
   as a parser skeleton.  Alternatively, if you modify or redistribute
   the parser skeleton itself, you may (at your option) remove this
   special exception, which will cause the skeleton and the resulting
   Bison output files to be licensed under the GNU General Public
   License without this special exception.

   This special exception was added by the Free Software Foundation in
   version 2.2 of Bison.  */

#ifndef YY_YY_SRC_PARSER_H_INCLUDED
# define YY_YY_SRC_PARSER_H_INCLUDED
/* Debug traces.  */
#ifndef YYDEBUG
# define YYDEBUG 0
#endif
#if YYDEBUG
extern int yydebug;
#endif

/* Token type.  */
#ifndef YYTOKENTYPE
# define YYTOKENTYPE
  enum yytokentype
  {
    IDENTIFIER = 258,
    STRING = 259,
    IF = 260,
    ELSE = 261,
    FOR = 262,
    IN = 263,
    WHILE = 264,
    DO = 265,
    CONTINUE = 266,
    SWITCH = 267,
    CASE = 268,
    DEFAULT = 269,
    BREAK = 270,
    FUNC = 271,
    RETURN = 272,
    LOCAL = 273,
    OF = 274,
    NEW = 275,
    DELETE = 276,
    TRY = 277,
    CATCH = 278,
    FINALLY = 279,
    THROW = 280,
    WITH = 281,
    UNDEF = 282,
    _TRUE = 283,
    _FALSE = 284,
    _THIS = 285,
    ARGUMENTS = 286,
    FNUMBER = 287,
    REGEXP = 288,
    TYPESTRING = 289,
    TYPENUMBER = 290,
    TYPENULL = 291,
    TYPEOBJECT = 292,
    TYPEBOOLEAN = 293,
    TYPEUSEROBJ = 294,
    TYPEITEROBJ = 295,
    TYPEREGEXP = 296,
    TYPEANY = 297,
    TYPEARRAY = 298,
    ELLIPSIS = 299,
    __DEBUG = 300,
    MIN_PRI = 301,
    ARGCOMMA = 302,
    ADDAS = 303,
    MNSAS = 304,
    MULAS = 305,
    MODAS = 306,
    LSHFAS = 307,
    RSHFAS = 308,
    URSHFAS = 309,
    BANDAS = 310,
    BORAS = 311,
    BXORAS = 312,
    DIVAS = 313,
    OR = 314,
    AND = 315,
    EQU = 316,
    NEQ = 317,
    EEQU = 318,
    NNEQ = 319,
    LEQ = 320,
    GEQ = 321,
    INSTANCEOF = 322,
    LSHF = 323,
    RSHF = 324,
    URSHF = 325,
    NEG = 326,
    INC = 327,
    DEC = 328,
    TYPEOF = 329,
    VOID = 330,
    MAX_PRI = 331
  };
#endif

/* Value type.  */
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED

union YYSTYPE
{

    Jsi_OpCodes *opcodes;
    Jsi_Value *value;
    const char *sstr;
    Jsi_String *vstr;
    Jsi_Regex* regex;
    Jsi_Number *num;
    Jsi_ScopeStrs *scopes;
    int inum;
    struct jsi_CaseExprStat* caseitem;
    struct jsi_CaseList* caselist;

};

typedef union YYSTYPE YYSTYPE;
# define YYSTYPE_IS_TRIVIAL 1
# define YYSTYPE_IS_DECLARED 1
#endif

/* Location type.  */
#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED
typedef struct YYLTYPE YYLTYPE;
struct YYLTYPE
{
  int first_line;
  int first_column;
  int last_line;
  int last_column;
};
# define YYLTYPE_IS_DECLARED 1
# define YYLTYPE_IS_TRIVIAL 1
#endif



int yyparse (struct jsi_Pstate *pstate);

#endif /* !YY_YY_SRC_PARSER_H_INCLUDED  */
#ifndef __JSIINT_H__
#define __JSIINT_H__

#ifdef JSI_CONFIG_H_FILE
#include JSI_CONFIG_H_FILE
#endif

#if JSI__ALL
#define JSI__SQLITE 1
#define JSI__WEBSOCKET 1
#define JSI__READLINE 1
#endif
// Define the defaults
#ifndef JSI__EVENT
#define JSI__EVENT 1
#endif
#ifndef JSI__DEBUG
#define JSI__DEBUG 1
#endif
#ifndef JSI__LOAD
#define JSI__LOAD 1
#endif
#ifndef JSI__SIGNAL
#define JSI__SIGNAL 1
#endif
#ifndef JSI__FILESYS
#define JSI__FILESYS 1
#endif
#ifndef JSI__ZVFS
#define JSI__ZVFS 1
#endif
#ifndef JSI__STUBS
#define JSI__STUBS 1
#endif
#ifndef JSI__THREADS
#define JSI__THREADS 1
#endif
#ifndef JSI__INFO
#define JSI__INFO 1
#endif
#ifndef JSI__CDATA
#define JSI__CDATA 1
#endif
#ifndef JSI__SOCKET
#ifndef __WIN32 
#define JSI__SOCKET 1
#endif
#endif
#ifndef JSI__MATH
#define JSI__MATH 1
#endif
#ifndef JSI__UTF8
#define JSI__UTF8 1
#endif
#ifndef JSI__MINIZ
#define JSI__MINIZ 1
#endif

#if (JSI__STUBS!=1)
#ifndef JSI_OMIT_STUBS
#define JSI_OMIT_STUBS
#endif
#endif
#if (JSI__THREADS!=1)
#define JSI_OMIT_THREADS
#endif
#if (JSI__SIGNAL!=1)
#define JSI_OMIT_SIGNAL
#endif

#if defined(JSI__MD5) && JSI__MD5==0
#define JSI_OMIT_MD5 1
#endif
#if defined(JSI__SHA1) && JSI__SHA1==0
#define JSI_OMIT_SHA1 1
#endif
#if defined(JSI__SHA256) && JSI__SHA256==0
#define JSI_OMIT_SHA256 1
#endif
#if defined(JSI__ENCRYPT) && JSI__ENCRYPT==0
#define JSI_OMIT_ENCRYPT 1
#endif
#if defined(JSI__BASE64) && JSI__BASE64==0
#define JSI_OMIT_BASE64 1
#endif
#if defined(JSI__LOAD) && JSI__LOAD==0
#define JSI_OMIT_LOAD 1
#endif
#if defined(JSI__EVENT) && JSI__EVENT==0
#define JSI_OMIT_EVENT 1
#endif
#if defined(JSI__DEBUG) && JSI__DEBUG==0
#define JSI_OMIT_DEBUG 1
#endif
#if defined(JSI__CDATA) && JSI__CDATA==0
#define JSI_OMIT_CDATA 1
#endif
#if defined(JSI__MATH) && JSI__MATH==0
#define JSI_OMIT_MATH 1
#endif
#ifndef JSI__MARKDOWN
#define JSI__MARKDOWN 1
#endif

//#define JSI__MEMDEBUG 1
#if JSI__MEMDEBUG
#define JSI_MEM_DEBUG
#define Assert(n) assert(n)
#else
#define Assert(n)
#endif
#ifndef JSI_OMIT_SIGNATURES
#define JSI_HAS_SIG
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#define __USE_GNU
#endif
#define VAL_REFCNT
#define VAL_REFCNT2

#ifndef JSI_ZVFS_DIR
#define JSI_ZVFS_DIR "/zvfs"
#endif
#ifndef JSI_VFS_DIR
#define JSI_VFS_DIR "/vfs"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <ctype.h>
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <float.h>

#ifdef __WIN32 /* TODO: support windows signal??? */
#define JSI__MINIZ 1
#define JSI_OMIT_SIGNAL
#endif

#if (JSI_VERSION_MAJOR>=10)
#define JSI_VERFMT_LEN "6"
#else
#define JSI_VERFMT_LEN "5"
#endif

#ifndef JSI_AMALGAMATION

#if JSI__REGEX
#include "regex/regex.h"
#else
#include <regex.h>
#endif
#ifdef __WIN32
#include "win/compat.h"
//#include "win/regex.h"
//#include "regex/regex.h"
#else
#define JSI__REGCOMP
#include <sys/time.h>
#endif
#endif

#include <time.h>

#ifndef JSI_IS64BIT
#ifdef __GNUC__
#ifdef __X86_64__
#define JSI_IS64BIT 1
#endif
#else /* GCC */
#if _WIN64 || __amd64__
#define JSI_IS64BIT 1
#endif
#endif /* GCC */
#endif /* JSI_IS64BIT */

#ifndef JSI_IS64BIT
#define JSI_IS64BIT 0
#endif

#define JSMN_FREE(p) Jsi_Free(p)
#define JSMN_MALLOC(l) Jsi_Malloc(l)
#define JSMN_REALLOC(p,l) Jsi_Realloc(p,l)

#ifndef NDEBUG
#define SIGASSERTDO(s, ret) assert(s);
#else
#define SIGASSERTDO(s, ret) if (!(s)) return ret
#endif
#define SIGASSERTRET(s,n,ret) SIGASSERTDO((s) && (s)->sig == (uint)JSI_SIG_##n, ret);

#define JSI_HAS_SIG /* Signatures to help with debugging */
#ifdef JSI_HAS_SIG
#ifndef SIGASSERT
#define SIGASSERTV(s,n) SIGASSERTRET(s, n, /*void*/);
#define SIGASSERT(s,n) SIGASSERTRET(s, n, JSI_OK);
#define SIGASSERTMASK(s,n,m) assert((s) && ((s)->sig&(~(m))) == (uint)JSI_SIG_##n);
#endif
#define SIGINIT(s,n) (s)->sig = JSI_SIG_##n;
#define __VALSIG__ .sig=JSI_SIG_VALUE,
#else
#define SIGASSERTV(s,n)
#define SIGASSERT(s,n)
#define SIGASSERTMASK(s,n,m)
#define SIGINIT(s,n)
#define __VALSIG__
#endif

#ifndef JSI_HAS___PROTO__
#define JSI_HAS___PROTO__ 1  // Enable setting and getting prototypes. 1=set/get funcs, 2=__proto__ assign.
#endif

#ifdef NDEBUG
#define JSI_NOWARN(v) v=v
#else
#define JSI_NOWARN(v)
#endif

#ifdef __FreeBSD__
#define _JSICASTINT(s) (int)(s)
#else
#define _JSICASTINT(s) (s)
#endif

#ifndef __DBL_DECIMAL_DIG__
#define __DBL_DECIMAL_DIG__ 17
#endif

#if 0
#ifndef uint
typedef unsigned int uint;
#endif
#ifndef uchar
typedef unsigned char uchar;
#endif
#endif

#ifndef JSI_AMALGAMATION
#include "jsi.h"
#else
//#define JSI_OMIT_STUBS
#endif

#define ALLOC_MOD_SIZE 16      /* Increase allocs by increments of 16. */
#define MAX_ARRAY_LIST 100000  /* Default Max size of an array convertable to list form */
#define MAX_LOOP_COUNT 10000000 /* Limit infinite loops */
#define JSI_MAX_ALLOC_BUF  100000000 /* Limit for dynamic memory allocation hunk */
#define JSI_MAX_SCOPE (JSI_BUFSIZ/2)
typedef enum {
    JSI_SIG_ITEROBJ=0xdeadbaa0, JSI_SIG_FUNCOBJ, JSI_SIG_SCOPE, JSI_SIG_VALUE,
    JSI_SIG_OBJ, JSI_SIG_USERDATA, JSI_SIG_INTERP, JSI_SIG_PARSER,
    JSI_SIG_FILEOBJ, JSI_SIG_INTERPOBJ, JSI_SIG_FUNC, JSI_SIG_CMDSPECITEM, JSI_SIG_HASH,
    JSI_SIG_HASHENTRY, JSI_SIG_TREE, JSI_SIG_TREEENTRY, JSI_SIG_LIST, JSI_SIG_LISTENTRY,
    JSI_SIG_USER_REG, JSI_SIG_EVENT, JSI_SIG_MAP, JSI_SIG_REGEXP,
    JSI_SIG_ARGTYPE, JSI_SIG_FORINVAR, JSI_SIG_CASELIST, JSI_SIG_CASESTAT,
    JSI_SIG_FASTVAR, JSI_SIG_INTERPSTREVENT, JSI_SIG_ALIASCMD, JSI_SIG_SOCKET, JSI_SIG_SOCKETPSS,
    JSI_SIG_NAMEDATA
} jsi_Sig;

#define Jsi_LogType(fmt,...) Jsi_LogMsg(interp, (interp->typeCheck.strict || interp->typeCheck.error)?JSI_LOG_ERROR:JSI_LOG_WARN, fmt, ##__VA_ARGS__)

struct jsi_OpCode;

#if  JSI__MEMDEBUG
extern void jsi_VALCHK(Jsi_Value *v);
extern void jsi_OBJCHK(Jsi_Obj *o);
#define VALCHK(val) jsi_VALCHK(val)
#define OBJCHK(val) jsi_OBJCHK(val)
#else
#define VALCHK(val)
#define OBJCHK(val)
#endif

enum {  jsi_callTraceFuncs = 1, jsi_callTraceCmds = 2, jsi_callTraceNew = 4,
        jsi_callTraceReturn = 8, jsi_callTraceArgs = 16, 
        jsi_callTraceNoTrunc = 32,  jsi_callTraceNoParent = 64,
        jsi_callTraceFullPath = 128, jsi_callTraceBefore = 256
};

/* Scope chain */
typedef struct jsi_ScopeChain_ {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Interp *interp;
    Jsi_Value **chains;  /* values(objects) */
    int chains_cnt;         /* count */
} jsi_ScopeChain;

/* Function obj */
/* a Jsi_FuncObj is a raw function with own scope chain */
struct Jsi_FuncObj {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Interp *interp;
    Jsi_Func *func;
    jsi_ScopeChain *scope;
    Jsi_Value *bindArgs;
    Jsi_Value *bindFunc;
};

typedef int (Jsi_IterProc)(Jsi_IterObj *iterObj, Jsi_Value *val, Jsi_Value *var, int index);

/* Jsi_IterObj, use only in for-in statement */
struct Jsi_IterObj {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Interp *interp;
    const char **keys;
    uint size; 
    uint count;
    uint iter;
    bool isArrayList;            /* If an array list do not store keys. */
    bool isof;
    Jsi_Obj *obj;
    uint cur;                    /* Current array cursor. */
    int depth;                  /* Used to create list of keys. */
    Jsi_IterProc *iterCmd;
};

typedef struct UserObjReg_ { /* Per interp userobj registration. */
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_UserObjReg *reg;
    Jsi_Hash* hashPtr;
    int idx;
} UserObjReg;

/* User defined object */
typedef struct Jsi_UserObj {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Interp *interp;
    Jsi_Hash *id;
    void *data;
    const char *prefix;
    Jsi_UserObjReg *reg;
    struct UserObjReg_ *ureg;
    uintptr_t idx;
    Jsi_HashEntry* hPtr;
} Jsi_UserObj;

typedef struct {
    int valueCnt;
    int objCnt;
    int valueAllocCnt;
    int objAllocCnt;
#ifdef JSI_MEM_DEBUG
    Jsi_Hash *valueDebugTbl;
    Jsi_Hash *objDebugTbl;
    uint memDebugCallIdx;
#endif
} Jsi_InterpDebug;


/* stack change */
/* 0  nothing change */
/* +1 push */
/* -1 pop */
typedef enum {      /* SC   type of data    comment                             */
    OP_NOP,         /* 0 */
    OP_PUSHNUM,     /* +1   *double         number                              */
    OP_PUSHSTR,     /* +1   *char           string                              */
    OP_PUSHVSTR,    /* +1   Jsi_String*     string                              */
    OP_PUSHVAR,     /* +1   *FastVar        variable name                       */
    OP_PUSHUND,     /* +1   -               undefined                           */
    OP_PUSHNULL,    /* +1   -               null                                */
    OP_PUSHBOO,     /* +1   int             bool                                */
    OP_PUSHFUN,     /* +1   *Jsi_Func           function                            */
    OP_PUSHREG,     /* +1   *regex_t        regex                               */
    OP_PUSHARG,     /* +1   -               push arguments(cur scope)           */
    OP_PUSHTHS,     /* +1   -               push this                           */
    OP_PUSHTOP,     /* +1   -               duplicate top                       */
    OP_PUSHTOP2,    /* +2   -               duplicate toq and top               */
    OP_UNREF,       /* 0    -               make top be right value             */
    OP_POP,         /* -n   int             pop n elements                      */
    OP_LOCAL,       /* 0    *char        add a var to current scope          */
    OP_NEG,         /* 0    -               make top = - top                    */
    OP_POS,         /* 0    -               make top = + top, (conv to number)  */
    OP_NOT,         /* 0    -               reserve top                         */
    OP_BNOT,        /* 0    -               bitwise not                         */
    OP_ADD,         /* -1   -               all math opr pop 2 elem from stack, */
    OP_SUB,         /* -1   -                calc and push back in to the stack */
    OP_MUL,         /* -1   -                                                   */
    OP_DIV,         /* -1   -                                                   */
    OP_MOD,         /* -1   -                                                   */
    OP_LESS,        /* -1   -               logical opr, same as math opr       */
    OP_GREATER,     /* -1   -                                                   */
    OP_LESSEQU,     /* -1   -                                                   */
    OP_GREATEREQU,  /* -1   -                                                   */
    OP_EQUAL,       /* -1   -                                                   */
    OP_NOTEQUAL,    /* -1   -                                                   */
    OP_STRICTEQU,   /* -1   -                                                   */
    OP_STRICTNEQ,   /* -1   -                                                   */
    OP_BAND,        /* -1   -               bitwise and                         */
    OP_BOR,         /* -1   -               bitwise or                          */
    OP_BXOR,        /* -1   -               bitwise xor                         */
    OP_SHF,         /* -1   int(right)      signed shift left or shift right    */
    OP_INSTANCEOF,  /* -1 */
    OP_ASSIGN,      /* -n   int             if n = 1, assign to lval,           */
                    /*                      n = 2, assign to object member      */
    OP_SUBSCRIPT,   /* -1   -               do subscript TOQ[TOP]               */
    OP_INC,         /* 0    int             data indicate prefix/postfix inc/dec                */
    OP_TYPEOF,      /* 0    obj                                                                 */
    OP_IN,          /* 0    obj                                                                 */
    OP_DEC,         /* 0    int                                                                 */
    OP_KEY,         /* +1   -               push an iter object that contain all key in top     */
    OP_NEXT,        /* -1   -               assign next key to top, make top be res of this opr */
    OP_JTRUE,       /* -1   int             jmp to offset if top is true,                       */
    OP_JFALSE,      /* -1   int             jmp to offset if top is false,                      */
    OP_JTRUE_NP,    /* 0    int             jtrue no pop version                                */
    OP_JFALSE_NP,   /* 0    int             jfalse no pop version                               */
    OP_JMP,         /* 0    int             jmp to offset                                       */
    OP_JMPPOP,      /* -n   *jsi_JmpPopInfo     jmp to offset with pop n                            */
    OP_FCALL,       /* -n+1 int             call func with n args, pop then, make ret to be top */
    OP_NEWFCALL,    /* -n+1 int             same as fcall, call as a constructor                */
    OP_RET,         /* -n   int             n = 0|1, return with arg                            */
    OP_DELETE,      /* -n   int             n = 1, delete var, n = 2, delete object member      */
    OP_CHTHIS,      /* 0,   -               make toq as new 'this'                              */
    OP_OBJECT,      /* -n*2+1   int         create object from stack, and push back in          */
    OP_ARRAY,       /* -n+1 int             create array object from stack, and push back in    */
    OP_EVAL,        /* -n+1 int             eval can not be assign to other var                 */
    OP_STRY,        /* 0    *jsi_TryInfo        push try statment poses Jsi_LogWarn to trylist             */
    OP_ETRY,        /* 0    -               end of try block, jmp to finally                    */
    OP_SCATCH,      /* 0    *char        create new scope, assign to current excption        */
    OP_ECATCH,      /* 0    -               jmp to finally                                      */
    OP_SFINAL,      /* 0    -               restore scope chain create by Scatch                */
    OP_EFINAL,      /* 0    -               end of finally, any unfinish code in catch, do it   */
    OP_THROW,       /* 0    -               make top be last exception, pop trylist till catched*/
    OP_WITH,        /* -1   -               make top be top of scopechain, add to trylist       */
    OP_EWITH,       /* 0    -               pop trylist                                         */
    OP_RESERVED,    /* 0    jsi_ReservedInfo*   reserved, be replaced by iterstat by jmp/jmppop     */
    OP_DEBUG,       /* 0    -               DEBUG OPCODE, output top                            */
    OP_LASTOP       /* 0    -               END OF OPCODE                                       */
} jsi_Eopcode;

typedef enum { jsi_Oplf_none=0, jsi_Oplf_assert=1, jsi_Oplf_debug=2, jsi_Oplf_trace=3, jsi_Oplf_test=4 } jsi_OpLogFlags;

typedef struct jsi_OpCode {
    jsi_Eopcode op;
    void *data;
    unsigned int Line:16;
    unsigned int Lofs:8;
    unsigned char alloc:1;
    unsigned char nodebug:1;
    unsigned char hit:1;
    unsigned char isof:1;
    unsigned char local:1;
    jsi_OpLogFlags logflag:3;
    const char *fname;
} jsi_OpCode;


typedef struct Jsi_OpCodes {
    jsi_OpCode *codes;
    int code_len;
    int code_size;          // Used by malloc.
    
    int expr_counter;           /* context related expr count */
    int lvalue_flag;            /* left value count/flag */
    const char *lvalue_name; /* left value name */
    int line;  // Used in Lemon
#ifdef JSI_MEM_DEBUG
    Jsi_HashEntry *hPtr;
    int id;
#endif
} Jsi_OpCodes;


typedef struct jsi_TryInfo {
    int trylen;
    int catchlen;
    int finallen;
} jsi_TryInfo;

typedef struct jsi_ReservedInfo {
    int type;
    const char *label;
    int topop;
} jsi_ReservedInfo;

typedef struct jsi_JmpPopInfo {
    int off;
    int topop;
} jsi_JmpPopInfo;

#define RES_CONTINUE    1
#define RES_BREAK       2
typedef struct YYLTYPE jsi_Pline;

//void jsi_codes_print(Jsi_OpCodes *ops);
void jsi_code_decode(Jsi_Interp *interp, jsi_OpCode *op, int currentip, char *buf, int bsiz);
const char* jsi_opcode_string(uint opCode);

#ifdef JSI_MEM_DEBUG
typedef struct 
{
    const char *fname;
    int line;
    const char *func;
    const char *label;
    const char *label2;
    const char *label3;
    uint Idx;
    uint flags;
    struct jsi_OpCode *ip;
    int ipLine;
    jsi_Eopcode ipOp;
    const char* ipFname;
    Jsi_HashEntry *hPtr;
    Jsi_Interp *interp;
} jsi_ValueDebug;
#endif

struct Jsi_Obj {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int refcnt;                 /* reference count */
    Jsi_otype ot:8;             /* object type */
    uint isarrlist:1;           /* Array type. */
    uint isstrkey:1;            /* Key string registered in interp->strKeyTbl (do not free) */
    uint isJSONstr:1;
    uint clearProto:1;          /* Prototype changed, clean it up at exit. */
    uint isNoOp:1;
    uint isBlob:1;
    uint unused2:16;
    union {                     /* switched on by value of "ot" */
        int val;
        Jsi_Number num;
        Jsi_String s;
        Jsi_Regex *robj;
        Jsi_FuncObj *fobj;
        Jsi_IterObj *iobj;
        Jsi_UserObj *uobj;
    } d;
    uint arrMaxSize;                 /* Max allocated space for array. */
    uint arrCnt;                     /* Count of actually set keys. */
    Jsi_Value **arr;   /* Array values. */  
    Jsi_Tree *tree;                 /* Tree storage (should be union with array). */
    Jsi_Value *__proto__;           /* TODO: memory leaks when this is changed */
    struct Jsi_Obj *constructor;
#ifdef JSI_MEM_DEBUG
    jsi_ValueDebug VD;
#endif
};

/*#pragma pack(1)*/


struct Jsi_Value {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int32_t refCnt;
    Jsi_vtype vt:8;             /* value type */
    union {
        uint flag:8;
        struct vflagbit {
            uint readonly:1;
            uint dontenum:1;  /* Dont enumerate. */
            uint dontdel:1;
            uint innershared:1; /* All above used only for objkeys. */
            uint isarrlist:1;
            uint isstrkey:1;    /* Key string registered in interp->strKeyTbl (do not free) */
            uint local:1;       // Used to detect a function creating a global var.
            uint lookupfailed:1;// Indicates failed lookup, string is stored in lookupFail below.
        } bits;
    } f;
    union {                     /* see above */
        int val;
        Jsi_Number num;
        Jsi_String s;
        Jsi_Obj *obj;
        struct Jsi_Value *lval;
        const char *lookupFail;
    } d;
#ifdef JSI_MEM_DEBUG
    jsi_ValueDebug VD;
#endif
};

#ifndef JSI_SMALL_HASH_TABLE
#define JSI_SMALL_HASH_TABLE 0x10
#endif

typedef uintptr_t jsi_Hash;

typedef union jsi_HashKey {
    char string[sizeof(jsi_Hash)];  // STRING, STRUCT
    void *oneWordValue;             // ONEWORD, STRINGKEY
} jsi_HashKey;

typedef struct Jsi_HashEntry {
    jsi_Sig sig;
    int typ; // JSI_MAP_HASH
    struct Jsi_HashEntry *nextPtr;
    Jsi_Hash *tablePtr;
    jsi_Hash hval;
    void* clientData;
    jsi_HashKey key;
} Jsi_HashEntry;


typedef struct Jsi_Hash {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int typ; // JSI_MAP_HASH
    Jsi_HashEntry **buckets;
    Jsi_HashEntry *staticBuckets[JSI_SMALL_HASH_TABLE];
    int numBuckets;
    int numEntries;
    int rebuildSize;
    jsi_Hash mask;
    unsigned int downShift;
    int keyType;
    Jsi_HashEntry *(*createProc) (Jsi_Hash *tablePtr, const void *key, bool *newPtr);
    Jsi_HashEntry *(*findProc) (Jsi_Hash *tablePtr, const void *key);
    Jsi_MapOpts opts;
} Jsi_Hash;

struct Jsi_Tree {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int typ; // JSI_MAP_TREE
    //Jsi_Interp *interp;
    Jsi_TreeEntry *root;
    uint numEntries, keyType, epoch;
    struct {
        uint 
            inserting:1, destroyed:1,
            nonredblack:1,  /* Disable red/black handling on insert/delete. */
            internstr:1,    /* STRINGPTR keys are stored in strHash */
            valuesonly:1,   /* Values must be of type JSI_VALUE */
            unused:28;
    } flags;
    Jsi_Hash* strHash;  /* String hash table to use if INTERNSTR; setup on first Create if not defined. */
    Jsi_TreeEntry* (*createProc)(Jsi_Tree *treePtr, const void *key, bool *newPtr);
    Jsi_MapOpts opts;
};

typedef struct Jsi_TreeEntry {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int typ; // JSI_MAP_TREE
    Jsi_Tree *treePtr;
    struct Jsi_TreeEntry* left;
    struct Jsi_TreeEntry* right;
    struct Jsi_TreeEntry* parent;
    union { /* FLAGS: bottom 16 bits for JSI, upper 16 bits for users. First 7 map to JSI_OM_ above. */
        struct { 
            unsigned int readonly:1, dontenum:1, dontdel:1, innershared:1, isarrlist:1, isstrkey:1, unused:1,
                color:1,
                reserve:8,
                user0:8,
                user1:1, user2:1, user3:1, user4:1, user5:1, user6:1, user7:1, user8:1;
        } bits;
        int flags;
    } f;
    void* value;
    jsi_HashKey key;
} Jsi_TreeEntry;

typedef struct Jsi_Map {  // Wrapped Tree/Hash/List.
    uint sig;
    Jsi_Map_Type typ;
    union {
        Jsi_Hash *hash;
        Jsi_Tree *tree;
        Jsi_List *list;
    } v;
} Jsi_Map;

typedef struct jsi_ArgValue_ {
    char *name;
    uint type;  // or'ed Jsi_otype
    Jsi_Value *defValue;
} jsi_ArgValue;

typedef struct Jsi_ScopeStrs {
    jsi_ArgValue *args;
    int count;
    int _size;  // Used in allocation only.
    int varargs;
    int typeCnt;
    int firstDef;
    int argCnt;
    int retType;
} Jsi_ScopeStrs;

// Eval stack-frame.
typedef struct jsi_Frame {
    int level;
    const char *fileName;
    const char *funcName;
    const char *dirName;
    int line;
    jsi_OpCode *ip;
    int Sp;
    int tryDepth;
    int withDepth;
    jsi_ScopeChain* ingsc;
    Jsi_Value *incsc;
    Jsi_Value *inthis;
    Jsi_OpCodes *opcodes;
    struct jsi_Pstate *ps;
    int logflag;
    Jsi_Func *evalFuncPtr;
    struct jsi_Frame *parent, *child;
    Jsi_Value *arguments; // Set when arguments are accessed.
} jsi_Frame;

/* Program/parse state(context) */
typedef struct jsi_Pstate {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int err_count;              /* Jsi_LogError count after parse */
    int eval_flag;              /* 1 if currently executing in an eval function */
    int funcDefs;               /* Count of functions defined. 0 means we can delete this cache (eventually). */
    Jsi_OpCodes *opcodes;       /* Execution codes. */
    struct jsi_Lexer *lexer;        /* seq provider */

    int _context_id;            /* used in FastVar-locating */
    Jsi_Value *last_exception;
    Jsi_Interp *interp;
    Jsi_HashEntry *hPtr;
    Jsi_Hash *argsTbl;
    Jsi_Hash *fastVarTbl;
    Jsi_Hash *strTbl;
    int argType;                // Used during parsing to aggregate type.
    Jsi_ScopeStrs *args;        // Last push.
} jsi_Pstate;


Jsi_ScopeStrs *jsi_ScopeStrsNew(void);
void jsi_ScopeStrsPush(Jsi_Interp *interp, Jsi_ScopeStrs *ss, const char *string, int argType);
void jsi_ScopeStrsFree(Jsi_Interp *interp, Jsi_ScopeStrs *ss);
const char *jsi_ScopeStrsGet(Jsi_ScopeStrs *ss, int i);

void jsi_PstatePush(jsi_Pstate *ps);
void jsi_PstatePop(jsi_Pstate *ps);
void jsi_PstateAddVar(jsi_Pstate *ps, jsi_Pline *line, const char *str);
Jsi_ScopeStrs *jsi_ScopeGetVarlist(jsi_Pstate *ps);

void jsi_PstateFree(jsi_Pstate *ps);
jsi_Pstate *jsi_PstateNew(Jsi_Interp *interp);
void jsi_PstateClear(jsi_Pstate *ps);
const char * jsi_PstateGetFilename(jsi_Pstate *ps);
int jsi_PstateSetFile(jsi_Pstate *ps, Jsi_Channel fp, int skipbang);
int jsi_PstateSetString(jsi_Pstate *ps, const char *str);

extern int yyparse(jsi_Pstate *ps);

#ifndef JSI_AMALGAMATION
#include "parser.h"
#endif

typedef struct jsi_ForinVar {
    jsi_Sig sig;
    const char *varname;
    Jsi_OpCodes *local;
    Jsi_OpCodes *lval;
} jsi_ForinVar;


typedef struct jsi_CaseExprStat {
    jsi_Sig sig;
    Jsi_OpCodes *expr;
    Jsi_OpCodes *stat;
    int isdefault;
} jsi_CaseExprStat;


typedef struct jsi_CaseList {
    jsi_Sig sig;
    jsi_CaseExprStat *es;
    int off;
    struct jsi_CaseList *tail;
    struct jsi_CaseList *next;
} jsi_CaseList;


typedef enum {
    LT_NONE,
    LT_FILE,            /* read from file */
    LT_STRING           /* read from a string */
} Jsi_Lexer_Type;

/* jsi_Lexer, where input seq provided */
typedef struct jsi_Lexer {
    Jsi_Lexer_Type ltype;
    union {
        Jsi_Channel fp;           /* LT_FILE, where to read */
        char *str;          /* LT_STRING */
    } d;
    int last_token;         /* last token returned */
    int ungot, unch[100];
    int cur;                /* LT_STRING, current char */
    int cur_line;           /* current line no. */
    int cur_char;           /* current column no. */
    int inStr;
    jsi_Pstate *pstate;
} jsi_Lexer;

int yylex (YYSTYPE *yylvalp, YYLTYPE *yyllocp, jsi_Pstate *pstate);
void yyerror(YYLTYPE *yylloc, jsi_Pstate *ps, const char *msg);

typedef struct {
    jsi_Sig sig;
    int context_id:31;
    unsigned int local:1;
    jsi_Pstate *ps;
    char *varname;
    struct Jsi_Value *lval;
} jsi_FastVar;

typedef enum { FC_NORMAL, FC_BUILDIN } Jsi_Func_Type;
struct jsi_PkgInfo;

/* raw function data, with script function or system Jsi_CmdProc */
struct Jsi_Func {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Func_Type type;                         /* type */
    struct Jsi_OpCodes *opcodes;    /* FC_NORMAL, codes of this function */
    Jsi_CmdProc *callback;            /* FC_BUILDIN, callback */

    Jsi_ScopeStrs *argnames;                 /* FC_NORMAL, argument names */
    Jsi_ScopeStrs *localnames;               /* FC_NORMAL, local var names */
    union {
        uint flags;
        struct {
            uint res:8, hasattr:1, isobj:1 , iscons:1, res2:4;
        } bits;
    } f;
    union {
        uint i;
        struct {
            uint addargs:1 , iscons:1, isdiscard:1, res:5;
        } bits;
    } callflags;
    int refCnt;
    void *privData;                 /* context data given in create. */
    Jsi_CmdSpec *cmdSpec;
    const char *name, *parentName;  /* Name for non-anonymous function. */
    Jsi_CmdSpec *parentSpec;
    uint retType;  /* Type name: or of Jsi_otype*/
    int callCnt;
    const char *script, *scriptFile;  /* Script created in. */
    jsi_Pline bodyline; /* Body line info. */
    const char *bodyStr; // Non-builtin func script body.
    int endPos, startPos;
    Jsi_HashEntry *hPtr;
    double subTime, allTime;
    Jsi_FuncObj *fobj;
    struct jsi_PkgInfo *pkg;
};

typedef struct {
    char *origFile; /* Short file name. */
    char *fileName; /* Fully qualified name. */
    char *dirName;  /* Directory name. */
    const char *str; /* File data. */
    int useCnt;
} jsi_FileInfo;

enum {
    STACK_INIT_SIZE=1024, STACK_INCR_SIZE=1024, STACK_MIN_PAD=100,
    JSI_MAX_EVAL_DEPTH=200, /* default max nesting depth for eval */
    JSI_MAX_INCLUDE_DEPTH=50,  JSI_MAX_SUBINTERP_DEPTH=10,
    JSI_IS_UTF=1,
    JSI_UTF_CHECKED=2
    /*,JSI_ON_STACK=0x80*/
};

typedef struct InterpStrEvent_ {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int rc, isExec, tryDepth, errLine;
    const char *errFile;
    Jsi_Value *objData;
    Jsi_DString func;
    Jsi_DString data;
    struct InterpStrEvent_ *next;
    void *mutex;
} InterpStrEvent;

typedef void (*jsiCallTraceProc)(Jsi_Interp *interp, const char *funcName, const char *file, 
    int line, Jsi_CmdSpec* spec, Jsi_Value* _this, Jsi_Value* args, Jsi_Value *ret);

typedef struct {
    const char *file;
    const char *func;
    int line;
    int id;
    int hits;
    bool enabled;
    bool temp;
} jsi_BreakPoint;

typedef struct jsi_PkgInfo {
    Jsi_Number version, lastReq;
    const char *loadFile;  // Full path of file name loaded.
    Jsi_InitProc *initProc; // For C-extensions.
    bool needInit;  // If a C-extension and _Init func needs calling in this interp.
    Jsi_Value *info;
    Jsi_PkgOpts popts;
} jsi_PkgInfo;

typedef struct {
    bool isDebugger; // Set to 1 if we are the debugger, debugging a sub-interp.
    bool noFilter;
    bool doContinue;
    bool forceBreak;
    bool bpLast; // Last break was a breakpoint.
    bool includeOnce;
    bool includeTrace;
    int bpOpCnt;
    int minLevel;
    Jsi_Value *putsCallback;
    Jsi_Value *msgCallback;
    Jsi_Value *traceCallback;
    Jsi_Value *debugCallback;
    Jsi_Value *testFmtCallback;
    int lastLine;
    int lastLevel;
    const char *lastFile;
    bool pkgTrace;
    int breakIdx;
    Jsi_RC (*hook)(struct Jsi_Interp* interp, const char *curFile, int curLine, int curLevel, const char *curFunc, const char *opCode, jsi_OpCode *op, const char *msg);
} Jsi_DebugInterp;

typedef union jsi_numUnion {
    bool       BOOL;
    int            INT;
    uint           UINT;
    int8_t         INT8;
    uint8_t        UINT8;
    int16_t        INT16;
    uint16_t       UINT16;
    int32_t        INT32;
    uint32_t       UINT32;
    int64_t        INT64;
    uint64_t       UINT64;
    Jsi_Number     DOUBLE;
    time_t         TIME_T;
    time_w         TIME_W;
    time_d         TIME_D;
} jsi_numUnion;

typedef struct {
    uint parse:1;
    uint run:1;
    uint all:1;     
    uint error:1;
    uint strict:1;
    uint noundef:1;
    uint nowith:1;
    uint funcsig:1;
    uint unused:24;
} Jsi_TypeCheck;

typedef enum {
    jsi_AssertModeThrow,
    jsi_AssertModeLog,
    jsi_AssertModePuts
} jsi_AssertMode;

typedef struct {
    uint isSpecified:1; /* User set the option. */
    uint initOnly:1;    /* Allow set only at init, disallowing update/conf. */
    uint readOnly:1;    /* Value can not be set. */
    uint noDupValue:1;  /* Values are not to be duped. */
    uint noClear:1;     /* Values are not to be cleared: watch for memory leaks */
    uint dbDirty:1;     /* Used to limit DB updates. */
    uint dbIgnore:1;    /* Field is not to be used for DB. */
    uint dbRowid:1 ;    /* Field used by DB to store rowid. */
    uint custNoCase:1;  /* Ignore case (eg. for ENUM and BITSET). */
    uint forceInt:1;    /* Force int instead of text for enum/bitset. */
    uint bitsetBool:1;  /* Treat bitset custom field as bool instead of an int. */
    uint timeDateOnly:1;/* Time field is date only. */
    uint timeTimeOnly:1;/* Time field is time only. */
    uint isBits:1;      /* Is a C bit-field. */
    uint fmtString:1;   /* Format value (eg. time) as string. */
    uint fmtNumber:1;   /* Format value (eg. enum) as number. */
    uint fmtHext:1;     /* Format number in hex. */
    uint strict:1;      /* Strict mode. */
    uint fieldSetup:1;  /* Field has been setup. */
    uint coerce:1;      /* Coerce input value to required type. */
    uint noSig:1;       /* No signature. */
    uint enumSpec:1;    /* Enum has spec rather than a list of strings. */
    uint enumUnsigned:1;/* Enum value is unsigned. */
    uint enumExact:1;   /* Enum must be an exact match. */
    uint required:1;    /* Field must be specified (if not IS_UPDATE). */
    uint prefix:1;      /* Allow matching unique prefix of object members. */
    uint isUpdate:1;    /* This is an update/conf (do not reset the specified flags) */
    uint ignoreExtra:1; /* Ignore extra members not found in spec. */
    uint forceStrict:1; /* Override Interp->compat to disable JSI_OPTS_IGNORE_EXTRA. */
    uint verbose:1;     /* Dump verbose options */
    uint userBits:32;
} jsi_OptionFlags;

typedef struct {
    bool file;    // Ouput file:line information: default is at end.
    bool full;    // Show full file path.
    bool ftail;   // Show tail of file only, even in LogWarn, etc.
    bool func;    // Ouput function at end.
    bool Debug;
    bool Trace;
    bool Test;
    bool Info;
    bool Warn;
    bool Error;
    bool time;    // Prefix with time
    bool date;    // Prefix with date
    bool before;  // Print file:line before message instead of at end.
    bool isUTC;
    const char* timeFmt;
    Jsi_Value *chan;
} jsi_LogOptions;

typedef struct {
    bool istty;
    bool noRegex;
    bool noReadline;
    bool noproto;
    bool outUndef;
    bool logAllowDups;
    bool logColNums;
    bool privKeys;
    bool compat;
    bool mutexUnlock;
    bool noFuncString;
    int dblPrec;
    const char *blacklist;
    const char *prompt, *prompt2;
} jsi_SubOptions;

extern Jsi_OptionSpec jsi_InterpLogOptions[];

typedef enum {
        jsi_TL_TRY,
        jsi_TL_WITH,
} jsi_try_op_type;                            /* type of try */

typedef enum { jsi_LOP_NOOP, jsi_LOP_THROW, jsi_LOP_JMP } jsi_last_try_op_t; 

typedef struct jsi_TryList {
    jsi_try_op_type type;
    union {
        struct {                    /* try data */
            jsi_OpCode *tstart;         /* try start ip */
            jsi_OpCode *tend;           /* try end ip */
            jsi_OpCode *cstart;         /* ...*/
            jsi_OpCode *cend;
            jsi_OpCode *fstart;
            jsi_OpCode *fend;
            int tsp;
            jsi_last_try_op_t last_op;              /* what to do after finally block */
                                    /* depend on last jmp code in catch block */
            union {
                jsi_OpCode *tojmp;
            } ld;                   /* jmp out of catch (target)*/
        } td;
        struct {                    /* with data */
            jsi_OpCode *wstart;         /* with start */
            jsi_OpCode *wend;           /* with end */
        } wd;
    } d;
    
    jsi_ScopeChain *scope_save;         /* saved scope (used in catch block/with block)*/
    Jsi_Value *curscope_save;           /* saved current scope */
    struct jsi_TryList *next;
    bool inCatch;
    bool inFinal;
} jsi_TryList;

typedef enum {
    jsi_safe_None,
    jsi_safe_Read,
    jsi_safe_Write,
    jsi_safe_Write2
} jsi_safe_mode;

struct Jsi_Interp {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    bool isSafe, startSafe;
    jsi_safe_mode safeMode;
    int iskips;
    Jsi_Value *safeReadDirs;
    Jsi_Value *safeWriteDirs;
    const char *safeExecPattern;
    Jsi_DebugInterp debugOpts;
    struct jsi_TryList *tryList;
    bool deleting;
    bool destroying;
    uint EventHdlId;
    uint autoLoaded;
    int exited;
    int exitCode;
    int interrupted;
    int refCount;
    int traceCall;
    int opTrace;
    int memDebug;
    int coverAll, coverHit;
    bool coverage;
    bool profile;
    int profileCnt;
    bool asserts;
    bool noNetwork;
    bool noInput;
    jsi_AssertMode assertMode;
    uint unitTest;
    const char *jsppChars;
    Jsi_Value *jsppCallback;
    bool noConfig;
    bool noLoad;
    bool noAutoLoad;
    bool noStderr;
    bool noSubInterps;
    bool tracePuts;
    bool isMain;
    bool hasCallee;
    bool subthread;
    bool strict;
    bool protoInit;
    bool hasOpenSSL;
    bool isHelp;
    bool callerErr;
    bool interactive;
    jsi_SubOptions subOpts;
    bool isInteractive;
    const char *confArgs;
    Jsi_Value *stdinStr;
    Jsi_Value *stdoutStr;
    Jsi_TypeCheck typeCheck;
    jsi_LogOptions logOpts;
    int typeWarnMax;
    int typeMismatchCnt;
    Jsi_InterpOpts opts;
    Jsi_Value *inopts;
    int evalFlags;
    Jsi_InterpDebug dbStatic;
    Jsi_InterpDebug *dbPtr;
    jsiCallTraceProc traceHook;
    int opCnt;  /* Count of instructions eval'ed */
    int maxOpCnt;
    int maxUserObjs;
    int userObjCnt;
    int funcCnt;
    int level;  /* Nesting level of eval/func calls. */
    int maxDepth;/* Max allowed eval recursion. */
    int callDepth;
    int maxIncDepth;
    int includeDepth;
    int includeCnt;
    int maxInterpDepth;
    int interpDepth;
    int pkgReqDepth;
    int didReturn;
    uint codeCacheHit;
    uint funcCallCnt;
    uint cmdCallCnt;
    uintptr_t eventIdx;
#ifdef JSI_MEM_DEBUG
    uint valueDebugIdx;
    Jsi_Hash *codesTbl;
#endif
    jsi_ScopeChain *gsc;
    Jsi_Value *csc;
    struct Jsi_Interp *parent, *topInterp, *mainInterp;
    Jsi_Value *onComplete;
    Jsi_Value *onEval;
    Jsi_Value *onExit;
    Jsi_Value *execZip;
    void (*logHook)(char *buf, va_list va);
    const char *name;
    Jsi_Value *pkgDirs;
    bool selfZvfs;
    int inParse;
    Jsi_Value *retValue;       /* Return value from eval */
    jsi_Pstate *ps, *parsePs;
    Jsi_Value *argv0;
    Jsi_Value *args;
    Jsi_Value *console;
    Jsi_Value *scriptFile;  /* Start script returned by info.argv0(). */
    const char *scriptStr;
    const char *curFile;
    const char *curFunction;
    const char *homeDir;
    const char *historyFile;
    char *curDir;
    int maxStack;
    double timesStart;

    Jsi_Map *strKeyTbl;  /* Global strings table. */
    Jsi_Map *cmdSpecTbl; /* Jsi_CmdSpecs registered. */
    Jsi_Hash *onDeleteTbl;  /* Cleanup funcs to call on interp delete. */
    Jsi_Hash *assocTbl;
    Jsi_Hash *codeTbl; /* Scripts compiled table. */
    Jsi_Hash *eventTbl;
    Jsi_Hash *genValueTbl;
    Jsi_Hash *genObjTbl;
    Jsi_Hash *funcObjTbl;
    Jsi_Hash *funcsTbl;
    Jsi_Hash *bindTbl;
    Jsi_Hash *fileTbl;    // The "source"ed files.
    Jsi_Hash *lexkeyTbl;
    Jsi_Hash *protoTbl;
    Jsi_Hash *regexpTbl;    
    Jsi_Hash *thisTbl;
    Jsi_Hash *userdataTbl;
    Jsi_Hash *varTbl;
    Jsi_Hash *preserveTbl;
    Jsi_Hash *loadTbl;
    Jsi_Hash *staticFuncsTbl; // For debugOpts.typeProto
    Jsi_Hash *breakpointHash;
    Jsi_Hash *packageHash;
    Jsi_Hash *aliasHash;
    Jsi_Hash* vfsMountHash;
    Jsi_Hash* vfsDefHash;
#ifdef VAL_REFCNT
    Jsi_Value **Stack;
    Jsi_Value **Obj_this;
#else
    Jsi_Value *Stack;
    Jsi_Value *Obj_this;
#endif
            
    Jsi_Value *Object_prototype;
    Jsi_Value *Function_prototype_prototype;
    Jsi_Value *Function_prototype;
    Jsi_Value *String_prototype;
    Jsi_Value *Number_prototype;
    Jsi_Value *Boolean_prototype;
    Jsi_Value *Array_prototype;
    Jsi_Value *RegExp_prototype;
    Jsi_Value *Date_prototype;
    
    Jsi_Value *NaNValue;
    Jsi_Value *InfValue;
    Jsi_Value *NullValue;
    Jsi_Value *nullFuncArg; /* Efficient call of no-arg func */
    Jsi_Value *nullFuncRet;
    Jsi_Value *autoFiles;
    Jsi_Obj* cleanObjs[4];

    Jsi_Value *busyCallback;
    int busyInterval;
    int isInCallback;
    int objId;
    Jsi_Value *Top_object;
    Jsi_ScopeStrs *scopes[JSI_MAX_SCOPE];
    int cur_scope;
    int maxArrayList;
    int delRBCnt;
    Jsi_Func *activeFunc;  // Currently active function call.
    Jsi_Func *prevActiveFunc;  // Prev active function call.
    jsi_OpCode *curIp;  /* Used for debug Log msgs. */
    
    char *lastPushStr;  // Used by error handling and Jsi_LogMsg.   TODO: cleanup/rationalize.
    Jsi_Value* lastParseOpt;
    Jsi_Value* lastSubscriptFail;
    const char* lastSubscriptFailStr;
    int logErrorCnt;
    Jsi_OptionSpec *parseMsgSpec;


    Jsi_Wide sigmask;
    char errMsgBuf[JSI_BUFSIZ];  /* Error message space for when in try. */
    int errLine;
    int errCol;
    const char *errFile;
    Jsi_Mutex* Mutex;
    Jsi_Mutex* QMutex; /* For threads queues */
    void* threadId;
    int threadCnt;
    int threadShrCnt;
    int lockTimeout; /* in milliseconds. */
    uint lockRefCnt;
    int psEpoch;
    int mountCnt;
    Jsi_DString interpEvalQ;
    Jsi_DString interpMsgQ;
    InterpStrEvent *interpStrEvents;

    bool typeInit;
    Jsi_Number cdataIncrVal;
    Jsi_CData_Static *statics;
    Jsi_VarSpec *cdataNewVal;
    Jsi_Hash *StructHash;
    Jsi_Hash *SigHash;
    Jsi_Hash *EnumHash;
    Jsi_Hash *EnumItemHash;
    Jsi_Hash *CTypeHash;
    Jsi_Hash *TYPEHash;

    Jsi_Value *recvCallback;
    uint threadErrCnt;  /* Count of bad thread event return codes. */
    uint threadEvalCnt;
    uint threadMsgCnt;
    void *sleepData;
    jsi_PkgInfo *pkgRequiring, *pkgProviding;
    jsi_Pline *parseLine;
    jsi_Frame *framePtr;
    struct jsi_DbVfs **dbVfsPtrPtr;
    double subTime, startTime, funcSelfTime, cmdSelfTime;
};


enum { JSI_REG_GLOB=0x1, JSI_REG_NEWLINE=0x2, JSI_REG_DOT_NEWLINE=0x4, JSI_REG_STATIC=0x100 };

struct Jsi_Regex_ {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    regex_t reg;
    int eflags;
    int flags;
    char *pattern;
    int lastIndex;
};


/* Entries in interp->cmdSpecTbl. */
typedef struct Jsi_CmdSpecItem_ {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    const char *name;  /* Parent cmd. */
    Jsi_CmdSpec *spec;
    Jsi_Value *proto;
    int flags;
    void *privData;
    Jsi_MapEntry *hPtr;
    struct Jsi_CmdSpecItem_ *next; /* TODO: support user-added sub-commands. */
    const char *help;
    const char *info;
    int isCons;
} Jsi_CmdSpecItem;

//extern Jsi_OptionTypedef jsi_OptTypeInfo[];
extern const char* jsi_OptionTypeStr(Jsi_OptionId typ, bool cname);
extern const Jsi_OptionTypedef* Jsi_OptionsStr2Type(const char *str, bool cname);

typedef struct {
    const char *topLink;
    bool getTitle;
    bool returnStr;
    Jsi_DString titleStr;
} jsi_MarkdownOpts;

extern void jsi_markdown_to_html(
    Jsi_DString *input_markdown,   /* Markdown content to be rendered */
    Jsi_DString *output_body,      /* Put document body here. */
    jsi_MarkdownOpts *opts
);

/* SCOPE */
//typedef struct jsi_ScopeChain jsi_ScopeChain;

extern jsi_ScopeChain* jsi_ScopeChainNew(Jsi_Interp *interp, int cnt); /*STUB = 176*/
extern Jsi_Value* jsi_ScopeChainObjLookupUni(jsi_ScopeChain *sc, char *key); /*STUB = 177*/
extern jsi_ScopeChain* jsi_ScopeChainDupNext(Jsi_Interp *interp, jsi_ScopeChain *sc, Jsi_Value *next); /*STUB = 178*/
extern void jsi_ScopeChainFree(Jsi_Interp *interp, jsi_ScopeChain *sc); /*STUB = 179*/

extern void jsi_CmdSpecDelete(Jsi_Interp *interp, void *ptr);

Jsi_RC jsi_InitFilesys(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitLexer(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitLoad(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitCmds(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitInterp(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitFileCmds(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitString(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitValue(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitNumber(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitArray(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitBoolean(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitMath(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitProto(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitRegexp(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitJSON(Jsi_Interp *interp, int release);
Jsi_RC Jsi_InitSqlite(Jsi_Interp *interp, int release);
Jsi_RC Jsi_initSqlite(Jsi_Interp *interp, int release);
Jsi_RC Jsi_InitMySql(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitTree(Jsi_Interp *interp, int release);
Jsi_RC Jsi_InitWebSocket(Jsi_Interp *interp, int release);
Jsi_RC Jsi_InitSocket(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitSignal(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitOptions(Jsi_Interp *interp, int release);
Jsi_RC Jsi_InitZvfs(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitCData(Jsi_Interp *interp, int release);
Jsi_RC jsi_InitVfs(Jsi_Interp *interp, int release);
Jsi_RC jsi_execCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_DString *dStr, Jsi_DString *cStr, int *code);

void jsi_SignalClear(Jsi_Interp *interp, int sigNum);
bool jsi_SignalIsSet(Jsi_Interp *interp, int sigNum);
/* excute opcodes
 * 1. ps, program execution context
 * 2. opcodes, codes to be executed
 * 3. scope, current scopechain, not include current scope
 * 4. currentScope, current scope
 * 5. _this, where 'this' indicated
 * 6. vret, return value
 */
extern Jsi_RC jsi_evalcode(jsi_Pstate *ps, Jsi_Func *func, Jsi_OpCodes *opcodes, 
        jsi_ScopeChain *scope, Jsi_Value *currentScope,
        Jsi_Value *_this,
        Jsi_Value **vret);
        
typedef Jsi_RC (*Jsi_Constructor)(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, int flags, void *privData);
extern Jsi_RC jsi_SharedArgs(Jsi_Interp *interp, Jsi_Value *args, Jsi_Func *func, int alloc);
extern void jsi_SetCallee(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *tocall);
extern Jsi_RC jsi_AssertCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
extern Jsi_RC jsi_NoOpCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
extern Jsi_RC jsi_InterpInfo(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
    
enum {StrKeyAny = 0, StrKeyFunc = 0x1, StrKeyCmd = 0x2, StrKeyVar = 0x2};

//char* jsi_KeyLookup(Jsi_Interp *interp, const char *str);
extern char* jsi_KeyFind(Jsi_Interp *interp, const char *str, int nocreate, int *isKey);
extern void jsi_InitLocalVar(Jsi_Interp *interp, Jsi_Value *arguments, Jsi_Func *who);
extern Jsi_Value *jsi_GlobalContext(Jsi_Interp *interp);
extern void jsi_AddEventHandler(Jsi_Interp *interp);
extern Jsi_RC jsi_SetOption(Jsi_Interp *interp, Jsi_OptionSpec *specPtr, const char *string /*UNUSED*/, void* rec, Jsi_Value *argValue, Jsi_Wide flags);
extern Jsi_RC jsi_GetOption(Jsi_Interp *interp, Jsi_OptionSpec *specPtr, void* record, const char *option, Jsi_Value **valuePtr, Jsi_Wide flags);
extern const char *jsi_ObjectTypeName(Jsi_Interp *interp, Jsi_otype otyp);
extern const char *jsi_ValueTypeName(Jsi_Interp *interp, Jsi_Value *val);
extern const char *jsi_TypeName(Jsi_Interp *interp, Jsi_ttype otyp);
extern Jsi_RC jsi_ObjectToStringCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
extern Jsi_RC jsi_HasOwnPropertyCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
extern Jsi_Value* jsi_ValueMakeBlobDup(Jsi_Interp *interp, Jsi_Value **vPtr, unsigned char *s, int len);

extern const char *jsi_GetHomeDir(Jsi_Interp *interp);
extern Jsi_RC jsi_RegExpValueNew(Jsi_Interp *interp, const char *regtxt, Jsi_Value *ret);
extern void jsi_DumpOptionSpecs(Jsi_Interp *interp, Jsi_Obj *nobj, Jsi_OptionSpec* spec);
extern Jsi_Func *jsi_FuncMake(jsi_Pstate *pstate, Jsi_ScopeStrs *args, Jsi_OpCodes *ops, jsi_Pline *line, const char *name);
extern Jsi_Func *jsi_FuncNew(Jsi_Interp *interp);
extern void jsi_FreeOpcodes(Jsi_OpCodes *ops);
extern void jsi_DelAssocData(Jsi_Interp *interp, void *data);

extern void jsi_UserObjFree (Jsi_Interp *interp, Jsi_UserObj *uobj);
extern bool jsi_UserObjIsTrue (Jsi_Interp *interp, Jsi_UserObj *uobj);
extern Jsi_RC jsi_UserObjDump   (Jsi_Interp *interp, const char *argStr, Jsi_Obj *obj);
extern Jsi_RC jsi_UserObjDelete (Jsi_Interp *interp, void *data);
extern void jsi_UserObjToName(Jsi_Interp *interp, Jsi_UserObj *uobj, Jsi_DString *dStr);
extern Jsi_Obj *jsi_UserObjFromName(Jsi_Interp *interp, const char *name);

extern Jsi_RC Zvfs_Mount( Jsi_Interp *interp, Jsi_Value *archive, Jsi_Value *mount, Jsi_Value **ret);
extern Jsi_Value* jsi_ObjArraySetDup(Jsi_Interp *interp, Jsi_Obj *obj, Jsi_Value *value, int arrayindex);
extern void jsi_ValueObjSet(Jsi_Interp *interp, Jsi_Value *target, const char *key, Jsi_Value *value, int flags, int isstrkey);
extern void jsi_ValueSubscriptLen(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *key, Jsi_Value **ret, int right_val);
extern Jsi_Value* jsi_ValueSubscript(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *key, Jsi_Value **ret);
extern Jsi_Value* jsi_ValueObjKeyAssign(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *key, Jsi_Value *value, int flag);
extern void jsi_ValueObjGetKeys(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *ret, bool isof);
extern Jsi_Value* jsi_ObjArrayLookup(Jsi_Interp *interp, Jsi_Obj *obj, const char *key);
extern Jsi_Value* jsi_ProtoObjValueNew1(Jsi_Interp *interp, const char *name);
extern Jsi_Value* jsi_ProtoValueNew(Jsi_Interp *interp, const char *name, const char *parent);
extern Jsi_Value* jsi_ObjValueNew(Jsi_Interp *interp);
extern Jsi_Value* Jsi_ValueDup(Jsi_Interp *interp, Jsi_Value *v);
extern int jsi_ValueToOInt32(Jsi_Interp *interp, Jsi_Value *v);
extern Jsi_RC jsi_FreeOneLoadHandle(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *handle);
extern Jsi_Value* jsi_MakeFuncValue(Jsi_Interp *interp, Jsi_CmdProc *callback, const char *name, Jsi_Value** toVal, Jsi_CmdSpec *cspec);
extern Jsi_Value* jsi_MakeFuncValueSpec(Jsi_Interp *interp, Jsi_CmdSpec *cmdSpec, void *privData);
extern bool jsi_FuncArgCheck(Jsi_Interp *interp, Jsi_Func *f, const char *argStr);
extern bool jsi_CommandArgCheck(Jsi_Interp *interp, Jsi_CmdSpec *cmdSpec, Jsi_Func *f, const char *parent);
extern Jsi_RC jsi_FileStatCmd(Jsi_Interp *interp, Jsi_Value *fnam, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, int islstat);
extern Jsi_RC jsi_LoadLoadCmd(Jsi_Interp *interp, Jsi_Value *args, 
    Jsi_Value *_this, Jsi_Value **ret, Jsi_Func *funcPtr);
extern Jsi_RC jsi_LoadUnloadCmd(Jsi_Interp *interp, Jsi_Value *args, 
    Jsi_Value *_this, Jsi_Value **ret, Jsi_Func *funcPtr);
extern void jsi_ValueToPrimitive(Jsi_Interp *interp, Jsi_Value **vPtr);
extern Jsi_RC jsi_HashFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr);
extern Jsi_RC jsi_evalStrFile(Jsi_Interp* interp, Jsi_Value *path, char *str, int flags, int level);
extern Jsi_RC jsi_FuncArgsToString(Jsi_Interp *interp, Jsi_Func *f, Jsi_DString *dStr, int flags);
extern Jsi_Value *jsi_LoadFunction(Jsi_Interp *interp, const char *str, Jsi_Value *tret);
extern Jsi_RC jsi_SysExecCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, bool restricted);

extern Jsi_IterObj *jsi_IterObjNew(Jsi_Interp *interp, Jsi_IterProc *iterProc);
extern void jsi_IterObjFree(Jsi_IterObj *iobj);
extern Jsi_FuncObj *jsi_FuncObjNew(Jsi_Interp *interp, Jsi_Func *func);
extern void jsi_FuncObjFree(Jsi_FuncObj *fobj);
extern Jsi_RC jsi_ArglistFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr);
extern void jsi_FuncFree(Jsi_Interp *interp, Jsi_Func *func);
extern void jsi_ToHexStr(const uchar *indata, int dlen, char *out);
extern bool jsi_StrIsBalanced(char *str);

#ifndef _JSI_MEMCLEAR
#ifndef JSI_MEM_DEBUG
#define _JSI_MEMCLEAR(ptr)
#else
#define _JSI_MEMCLEAR(ptr) memset(ptr, 0, sizeof(*ptr)) /* To aid debugging memory.*/
#endif
#endif

#define MAX_SUBREGEX    256
#define JSI__LONG_LONG
#define UCHAR(s) (unsigned char)(s)
extern char* jsi_SubstrDup(const char *a, int alen, int start, int len, int *olen);
extern int jsi_typeGet(Jsi_Interp *interp , const char *tname);
extern const char *jsi_typeName(Jsi_Interp *interp, int typ, Jsi_DString *dStr);
extern Jsi_RC jsi_ArgTypeCheck(Jsi_Interp *interp, int typ, Jsi_Value *arg, const char *p1, const char *p2, int index, Jsi_Func *func, bool isdefault);
extern void jsi_FuncCallCheck(jsi_Pstate *p, jsi_Pline *line, int argc, bool isNew, const char *name, const char *namePre, Jsi_OpCodes *argCodes);
extern Jsi_RC jsi_RunFuncCallCheck(Jsi_Interp *interp, Jsi_Func *func, int argc, const char *name, jsi_Pline *line, Jsi_OpCodes *argCodes, bool isParse);
extern Jsi_RC jsi_FunctionSubCall(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this, Jsi_Value **ret, Jsi_Value *tocall, int discard);
extern Jsi_ScopeStrs *jsi_ArgsOptAdd(jsi_Pstate *pstate, Jsi_ScopeStrs *a);
extern Jsi_ScopeStrs *jsi_argInsert(jsi_Pstate *pstate, Jsi_ScopeStrs *a, const char *name, Jsi_Value *defValue, jsi_Pline *lPtr);
extern Jsi_ScopeStrs* jsi_ParseArgStr(Jsi_Interp *interp, const char *argStr);
extern Jsi_Value* jsi_AccessFile(Jsi_Interp *interp, const char *name, int mode);
extern double jsi_GetTimestamp(void);
extern const char *jsi_GetCurFile(Jsi_Interp *interp);
extern void jsi_TypeMismatch(Jsi_Interp* interp);
extern void jsi_SortDString(Jsi_Interp *interp, Jsi_DString *dStr, const char *sep);
extern const char* jsi_GetDirective(Jsi_Interp *interp, Jsi_OpCodes *ops, const char *str);
extern Jsi_Value* jsi_CommandCreate(Jsi_Interp *interp, const char *name, Jsi_CmdProc *cmdProc, void *privData, int flags, Jsi_CmdSpec *cspec);
extern int jsi_GetDefaultType(const char *cp);
extern Jsi_RC jsi_ParseTypeCheckStr(Jsi_Interp *interp, const char *str);
extern Jsi_Interp *jsi_DoExit(Jsi_Interp *interp, int rc);
extern Jsi_RC jsi_CDataDataSetCmdSub(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this, Jsi_Value **ret, Jsi_Func *funcPtr, int flags);
extern Jsi_RC jsi_AliasInvoke(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this, Jsi_Value **ret, Jsi_Func *funcPtr);
extern Jsi_Number jsi_VersionNormalize(Jsi_Number ver, char *obuf, size_t osiz);
extern const char* jsi_FuncGetCode(Jsi_Interp *interp, Jsi_Func *func, int *lenPtr);
extern Jsi_RC jsi_RegExpMatches(Jsi_Interp *interp, Jsi_Value *pattern, const char *str, int slen, Jsi_Value *ret, int *ofs, bool match);

extern Jsi_RC Jsi_CleanValue(Jsi_Interp *interp, Jsi_Interp *tointerp, Jsi_Value *val, Jsi_Value **ret); //TODO: EXPORT

extern char jsi_toHexChar(char code);
extern char jsi_fromHexChar(char ch);
extern bool jsi_IsAlnumStr(const char *cp);
extern char *jsi_TrimStr(char *str);
extern bool jsi_ModBlacklisted(Jsi_Interp *interp, const char *mod);
extern bool jsi_FuncIsNoop(Jsi_Interp* interp, Jsi_Value *func);

typedef enum {
    _JSI_CDATA_INFO=0,
    _JSI_CDATA_GET=1,
    _JSI_CDATA_SET=2,
    _JSI_CDATA_SIZE=3,
    _JSI_CDATA_SCHEMA=4,
    _JSI_CDATA_STRUCT=6
} jsi_cdatasub;

//extern Jsi_RC jsi_cdataMapsubCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
//    Jsi_Value **ret, Jsi_Func *funcPtr, jsi_cdatasub sub);

#define jsi_ValueString(pv) (pv->vt == JSI_VT_STRING ? &pv->d.s : \
  ((pv->vt == JSI_VT_OBJECT && pv->d.obj->ot == JSI_OT_STRING) ? &pv->d.obj->d.s : NULL))
                 
#define jsi_PrefixMatch(str, cstr) (!Jsi_Strncmp(str, cstr, sizeof(cstr)-1))

#ifdef JSI_MEM_DEBUG
#define jsi_ValueDebugUpdate(interp, v, tbl, file, line, func) jsi_ValueDebugUpdate_(interp, &v->VD, v, interp->dbPtr->tbl, file, line, func)
#define jsi_ValueDebugLabel(v,l1,l2) jsi_ValueDebugLabel_(&v->VD,l1,l2)

#define Jsi_ValueNew(interp) jsi_ValueNew(interp, __FILE__, __LINE__,__PRETTY_FUNCTION__)
#define Jsi_ValueNew1(interp) jsi_ValueNew1(interp, __FILE__, __LINE__,__PRETTY_FUNCTION__)
#define Jsi_ValueDup(interp,v) jsi_ValueDup(interp, v,__FILE__, __LINE__,__PRETTY_FUNCTION__)
#define Jsi_ObjNew(interp) jsi_ObjNew(interp, __FILE__, __LINE__,__PRETTY_FUNCTION__)

extern Jsi_Value *jsi_ValueNew(Jsi_Interp *interp, const char *fname, int line, const char *func);
extern Jsi_Value *jsi_ValueNew1(Jsi_Interp *interp, const char *fname, int line, const char *func);
extern Jsi_Value *jsi_ValueDup(Jsi_Interp *interp, Jsi_Value *ov, const char *fname, int line, const char *func);
extern Jsi_Obj *jsi_ObjNew(Jsi_Interp *interp, const char *fname, int line, const char *func);
extern void jsi_ValueDebugLabel_(jsi_ValueDebug *vd, const char *l1, const char *l2);
extern void jsi_ValueDebugUpdate_(Jsi_Interp *interp, jsi_ValueDebug *vd, void *v, Jsi_Hash* tbl, const char *fname, int line, const char *func);
extern void jsi_DebugValue(Jsi_Value* v, const char *reason, uint idx, Jsi_Interp *interp);
extern void jsi_DebugObj(Jsi_Obj* o, const char *reason, uint idx, Jsi_Interp *interp);

#define jsi_DebugValueCallIdx() ++interp->dbPtr->memDebugCallIdx
#define VALINIT { __VALSIG__ .refCnt=1, .vt=JSI_VT_UNDEF, .f={.flag=JSI_OM_ISSTATIC}, .d={}, .VD={.fname=__FILE__, .line=__LINE__,.func=__PRETTY_FUNCTION__}  }
#else
#define VALINIT { __VALSIG__ .refCnt=1, .vt=JSI_VT_UNDEF, .f={.flag=JSI_OM_ISSTATIC}  }
#define jsi_ValueDebugUpdate(interp, vd, v, tbl, file, line, func)
#define jsi_ValueDebugLabel(v,l1,l2)
#define jsi_DebugValue(v,r,i,t)
#define jsi_DebugObj(o,r,i,t)
#define jsi_DebugValueCallIdx() 0
#define jsi_ValueDebugLabel_(v,l1,l2)
#endif

#define DECL_VALINIT(n) Jsi_Value n = VALINIT

void jsi_TraceFuncCall(Jsi_Interp *interp, Jsi_Func *func, jsi_OpCode *iPtr, 
    Jsi_Value *_this, Jsi_Value* args, Jsi_Value *ret, int tc);


#if JSI__SANITIZE
#define Jsi_Malloc(sz) malloc(sz)
#define Jsi_Calloc(nm, sz) calloc(nm,sz)
#define Jsi_Realloc(ptr, sz) realloc(ptr,sz)
#define Jsi_Free(ptr) free(ptr)
#endif

struct Jsi_Stubs;
extern struct Jsi_Stubs *jsiStubsTblPtr;
extern const char *jsi_AssertModeStrs[];
extern const char *jsi_callTraceStrs[];
extern Jsi_CmdSpec cDataArrayCmds[];

// Global Jsi internal state.
typedef struct {
    Jsi_Interp *mainInterp;
    Jsi_Interp *delInterp;
    Jsi_Hash *interpsTbl;
    bool isInit;
    char *execName;
    Jsi_Value *execValue;
    Jsi_Chan stdChans[3];
    Jsi_Filesystem *cwdFsPtr;
    Jsi_DString pwdStr;
    char *pwd;
    int tolowerZvfs;
    struct {
        Jsi_Hash* fileHash;
        Jsi_Hash *archiveHash;
        int isInit;
        Jsi_Interp *interp;
    } zvfslocal;
} jsi_IntData;

extern jsi_IntData jsiIntData;

#define jsi_Stdin jsiIntData.stdChans
#define jsi_Stdout (jsiIntData.stdChans+1)
#define jsi_Stderr (jsiIntData.stdChans+2)

#define jsi_IIOF .flags=JSI_OPT_INIT_ONLY
#define jsi_IIRO .flags=JSI_OPT_READ_ONLY

#endif /* __JSIINT_H__ */
/* linenoise.h -- VERSION 1.0
 *
 * Guerrilla line editing library against the idea that a line editing lib
 * needs to be 20,000 lines of C code.
 *
 * See linenoise.c for more information.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __LINENOISE_H
#define __LINENOISE_H

#ifdef __cplusplus
extern "C" {
#endif

typedef struct linenoiseCompletions {
  size_t len;
  char **cvec;
} linenoiseCompletions;

typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
typedef void(linenoiseFreeHintsCallback)(void *);
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
void linenoiseSetHintsCallback(linenoiseHintsCallback *);
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
void linenoiseAddCompletion(linenoiseCompletions *, const char *);

char *linenoise(const char *prompt);
void linenoiseFree(void *ptr);
int linenoiseHistoryAdd(const char *line);
int linenoiseHistorySetMaxLen(int len);
int linenoiseHistorySave(const char *filename);
int linenoiseHistoryLoad(const char *filename);
void linenoiseClearScreen(void);
void linenoiseSetMultiLine(int ml);
void linenoisePrintKeyCodes(void);

#ifdef __cplusplus
}
#endif

#endif /* __LINENOISE_H */
#if JSI__MINIZ
/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
   See "unlicense" statement at the end of this file.
   Rich Geldreich <richgel99@gmail.com>, last updated Oct. 13, 2013
   Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt

   Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define
   MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros).

   * Change History
     10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!):
       - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug
        would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place()
        (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag).
       - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size
       - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries.
         Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice).
       - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes
       - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed
       - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6.
       - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti
       - Merged MZ_FORCEINLINE fix from hdeanclark
       - Fix <time.h> include before config #ifdef, thanks emil.brink
       - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can
        set it to 1 for real-time compression).
       - Merged in some compiler fixes from paulharris's github repro.
       - Retested this build under Windows (VS 2010, including static analysis), tcc  0.9.26, gcc v4.6 and clang v3.3.
       - Added example6.c, which dumps an image of the mandelbrot set to a PNG file.
       - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more.
       - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled
         - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch
     5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include <time.h> (thanks fermtect).
     5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit.
       - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files.
       - Eliminated a bunch of warnings when compiling with GCC 32-bit/64.
       - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly
        "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning).
       - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64.
       - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test.
       - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives.
       - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.)
       - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself).
     4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's.
      level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson <bruced@valvesoftware.com> for the feedback/bug report.
     5/28/11 v1.11 - Added statement from unlicense.org
     5/27/11 v1.10 - Substantial compressor optimizations:
      - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a
      - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86).
      - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types.
      - Refactored the compression code for better readability and maintainability.
      - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large
       drop in throughput on some files).
     5/15/11 v1.09 - Initial stable release.

   * Low-level Deflate/Inflate implementation notes:

     Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or
     greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses
     approximately as well as zlib.

     Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function
     coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory
     block large enough to hold the entire file.

     The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation.

   * zlib-style API notes:

     miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in
     zlib replacement in many apps:
        The z_stream struct, optional memory allocation callbacks
        deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound
        inflateInit/inflateInit2/inflate/inflateEnd
        compress, compress2, compressBound, uncompress
        CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines.
        Supports raw deflate streams or standard zlib streams with adler-32 checking.

     Limitations:
      The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries.
      I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but
      there are no guarantees that miniz.c pulls this off perfectly.

   * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by
     Alex Evans. Supports 1-4 bytes/pixel images.

   * ZIP archive API notes:

     The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to
     get the job done with minimal fuss. There are simple API's to retrieve file information, read files from
     existing archives, create new archives, append new files to existing archives, or clone archive data from
     one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h),
     or you can specify custom file read/write callbacks.

     - Archive reading: Just call this function to read a single file from a disk archive:

      void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name,
        size_t *pSize, mz_uint zip_flags);

     For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central
     directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files.

     - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file:

     int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);

     The locate operation can optionally check file comments too, which (as one example) can be used to identify
     multiple versions of the same file in an archive. This function uses a simple linear search through the central
     directory, so it's not very fast.

     Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and
     retrieve detailed info on each file by calling mz_zip_reader_file_stat().

     - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data
     to disk and builds an exact image of the central directory in memory. The central directory image is written
     all at once at the end of the archive file when the archive is finalized.

     The archive writer can optionally align each file's local header and file data to any power of 2 alignment,
     which can be useful when the archive will be read from optical media. Also, the writer supports placing
     arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still
     readable by any ZIP tool.

     - Archive appending: The simple way to add a single file to an archive is to call this function:

      mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name,
        const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);

     The archive will be created if it doesn't already exist, otherwise it'll be appended to.
     Note the appending is done in-place and is not an atomic operation, so if something goes wrong
     during the operation it's possible the archive could be left without a central directory (although the local
     file headers and file data will be fine, so the archive will be recoverable).

     For more complex archive modification scenarios:
     1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to
     preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the
     compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and
     you're done. This is safe but requires a bunch of temporary disk space or heap memory.

     2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(),
     append new files as needed, then finalize the archive which will write an updated central directory to the
     original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a
     possibility that the archive's central directory could be lost with this method if anything goes wrong, though.

     - ZIP archive support limitations:
     No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files.
     Requires streams capable of seeking.

   * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the
     below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it.

   * Important: For best perf. be sure to customize the below macros for your target platform:
     #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
     #define MINIZ_LITTLE_ENDIAN 1
     #define MINIZ_HAS_64BIT_REGISTERS 1

   * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz
     uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files
     (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes).
*/

#ifndef MINIZ_HEADER_INCLUDED
#define MINIZ_HEADER_INCLUDED

#include <stdlib.h>

// Defines to completely disable specific portions of miniz.c:
// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl.

// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O.
//#define MINIZ_NO_STDIO

// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or
// get/set file times, and the C run-time funcs that get/set times won't be called.
// The current downside is the times written to your archives will be from 1979.
//#define MINIZ_NO_TIME

// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's.
//#define MINIZ_NO_ARCHIVE_APIS

// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's.
//#define MINIZ_NO_ARCHIVE_WRITING_APIS

// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's.
//#define MINIZ_NO_ZLIB_APIS

// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib.
//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES

// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc.
// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc
// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user
// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work.
//#define MINIZ_NO_MALLOC

#if defined(__TINYC__) && (defined(__linux) || defined(__linux__))
  // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux
  #define MINIZ_NO_TIME
#endif

#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS)
  #include <time.h>
#endif

#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
// MINIZ_X86_OR_X64_CPU is only used to help set the below macros.
#define MINIZ_X86_OR_X64_CPU 1
#endif

#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU
// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian.
#define MINIZ_LITTLE_ENDIAN 1
#endif

#if MINIZ_X86_OR_X64_CPU
// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses.
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1
#endif

#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__)
// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions).
#define MINIZ_HAS_64BIT_REGISTERS 1
#endif

#ifdef __cplusplus
extern "C" {
#endif

// ------------------- zlib-style API Definitions.

// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits!
typedef unsigned long mz_ulong;

// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap.
void mz_free(void *p);

#define MZ_ADLER32_INIT (1)
// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL.
mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len);

#define MZ_CRC32_INIT (0)
// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL.
mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len);

// Compression strategies.
enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 };

// Method
#define MZ_DEFLATED 8

#ifndef MINIZ_NO_ZLIB_APIS

// Heap allocation callbacks.
// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long.
typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size);
typedef void (*mz_free_func)(void *opaque, void *address);
typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size);

#define MZ_VERSION          "9.1.15"
#define MZ_VERNUM           0x91F0
#define MZ_VER_MAJOR        9
#define MZ_VER_MINOR        1
#define MZ_VER_REVISION     15
#define MZ_VER_SUBREVISION  0

// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs).
enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 };

// Return status codes. MZ_PARAM_ERROR is non-standard.
enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 };

// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL.
enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 };

// Window bits
#define MZ_DEFAULT_WINDOW_BITS 15

struct mz_internal_state;

// Compression/decompression stream struct.
typedef struct mz_stream_s
{
  /*const*/ unsigned char *next_in;     // pointer to next byte to read
  unsigned int avail_in;            // number of bytes available at next_in
  mz_ulong total_in;                // total number of bytes consumed so far

  unsigned char *next_out;          // pointer to next byte to write
  unsigned int avail_out;           // number of bytes that can be written to next_out
  mz_ulong total_out;               // total number of bytes produced so far

  char *msg;                        // error msg (unused)
  struct mz_internal_state *state;  // internal state, allocated by zalloc/zfree

  mz_alloc_func zalloc;             // optional heap allocation function (defaults to malloc)
  mz_free_func zfree;               // optional heap free function (defaults to free)
  void *opaque;                     // heap alloc function user pointer

  int data_type;                    // data_type (unused)
  mz_ulong adler;                   // adler32 of the source or uncompressed data
  mz_ulong reserved;                // not used
} mz_stream;

typedef mz_stream *mz_streamp;

// Returns the version string of miniz.c.
const char *mz_version(void);

// mz_deflateInit() initializes a compressor with default options:
// Parameters:
//  pStream must point to an initialized mz_stream struct.
//  level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION].
//  level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio.
//  (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.)
// Return values:
//  MZ_OK on success.
//  MZ_STREAM_ERROR if the stream is bogus.
//  MZ_PARAM_ERROR if the input parameters are bogus.
//  MZ_MEM_ERROR on out of memory.
int mz_deflateInit(mz_streamp pStream, int level);

// mz_deflateInit2() is like mz_deflate(), except with more control:
// Additional parameters:
//   method must be MZ_DEFLATED
//   window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer)
//   mem_level must be between [1, 9] (it's checked but ignored by miniz.c)
int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy);

// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2().
int mz_deflateReset(mz_streamp pStream);

// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible.
// Parameters:
//   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members.
//   flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH.
// Return values:
//   MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full).
//   MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore.
//   MZ_STREAM_ERROR if the stream is bogus.
//   MZ_PARAM_ERROR if one of the parameters is invalid.
//   MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.)
int mz_deflate(mz_streamp pStream, int flush);

// mz_deflateEnd() deinitializes a compressor:
// Return values:
//  MZ_OK on success.
//  MZ_STREAM_ERROR if the stream is bogus.
int mz_deflateEnd(mz_streamp pStream);

// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH.
mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len);

// Single-call compression functions mz_compress() and mz_compress2():
// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure.
int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);
int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level);

// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress().
mz_ulong mz_compressBound(mz_ulong source_len);

// Initializes a decompressor.
int mz_inflateInit(mz_streamp pStream);

// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer:
// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate).
int mz_inflateInit2(mz_streamp pStream, int window_bits);

// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible.
// Parameters:
//   pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members.
//   flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH.
//   On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster).
//   MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data.
// Return values:
//   MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full.
//   MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified.
//   MZ_STREAM_ERROR if the stream is bogus.
//   MZ_DATA_ERROR if the deflate stream is invalid.
//   MZ_PARAM_ERROR if one of the parameters is invalid.
//   MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again
//   with more input data, or with more room in the output buffer (except when using single call decompression, described above).
int mz_inflate(mz_streamp pStream, int flush);

// Deinitializes a decompressor.
int mz_inflateEnd(mz_streamp pStream);

// Single-call decompression.
// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure.
int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len);

// Returns a string description of the specified error code, or NULL if the error code is invalid.
const char *mz_error(int err);

// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports.
// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project.
#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES
  typedef unsigned char Byte;
  typedef unsigned int uInt;
  typedef mz_ulong uLong;
  typedef Byte Bytef;
  typedef uInt uIntf;
  typedef char charf;
  typedef int intf;
  typedef void *voidpf;
  typedef uLong uLongf;
  typedef void *voidp;
  typedef void *const voidpc;
  #define Z_NULL                0
  #define Z_NO_FLUSH            MZ_NO_FLUSH
  #define Z_PARTIAL_FLUSH       MZ_PARTIAL_FLUSH
  #define Z_SYNC_FLUSH          MZ_SYNC_FLUSH
  #define Z_FULL_FLUSH          MZ_FULL_FLUSH
  #define Z_FINISH              MZ_FINISH
  #define Z_BLOCK               MZ_BLOCK
  #define Z_OK                  MZ_OK
  #define Z_STREAM_END          MZ_STREAM_END
  #define Z_NEED_DICT           MZ_NEED_DICT
  #define Z_ERRNO               MZ_ERRNO
  #define Z_STREAM_ERROR        MZ_STREAM_ERROR
  #define Z_DATA_ERROR          MZ_DATA_ERROR
  #define Z_MEM_ERROR           MZ_MEM_ERROR
  #define Z_BUF_ERROR           MZ_BUF_ERROR
  #define Z_VERSION_ERROR       MZ_VERSION_ERROR
  #define Z_PARAM_ERROR         MZ_PARAM_ERROR
  #define Z_NO_COMPRESSION      MZ_NO_COMPRESSION
  #define Z_BEST_SPEED          MZ_BEST_SPEED
  #define Z_BEST_COMPRESSION    MZ_BEST_COMPRESSION
  #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION
  #define Z_DEFAULT_STRATEGY    MZ_DEFAULT_STRATEGY
  #define Z_FILTERED            MZ_FILTERED
  #define Z_HUFFMAN_ONLY        MZ_HUFFMAN_ONLY
  #define Z_RLE                 MZ_RLE
  #define Z_FIXED               MZ_FIXED
  #define Z_DEFLATED            MZ_DEFLATED
  #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS
  #define alloc_func            mz_alloc_func
  #define free_func             mz_free_func
  #define internal_state        mz_internal_state
  #define z_stream              mz_stream
  #define deflateInit           mz_deflateInit
  #define deflateInit2          mz_deflateInit2
  #define deflateReset          mz_deflateReset
  #define deflate               mz_deflate
  #define deflateEnd            mz_deflateEnd
  #define deflateBound          mz_deflateBound
  #define compress              mz_compress
  #define compress2             mz_compress2
  #define compressBound         mz_compressBound
  #define inflateInit           mz_inflateInit
  #define inflateInit2          mz_inflateInit2
  #define inflate               mz_inflate
  #define inflateEnd            mz_inflateEnd
  #define uncompress            mz_uncompress
  #define crc32                 mz_crc32
  #define adler32               mz_adler32
  #define MAX_WBITS             15
  #define MAX_MEM_LEVEL         9
  #define zError                mz_error
  #define ZLIB_VERSION          MZ_VERSION
  #define ZLIB_VERNUM           MZ_VERNUM
  #define ZLIB_VER_MAJOR        MZ_VER_MAJOR
  #define ZLIB_VER_MINOR        MZ_VER_MINOR
  #define ZLIB_VER_REVISION     MZ_VER_REVISION
  #define ZLIB_VER_SUBREVISION  MZ_VER_SUBREVISION
  #define zlibVersion           mz_version
  #define zlib_version          mz_version()
#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES

#endif // MINIZ_NO_ZLIB_APIS

// ------------------- Types and macros

typedef unsigned char mz_uint8;
typedef signed short mz_int16;
typedef unsigned short mz_uint16;
typedef unsigned int mz_uint32;
typedef unsigned int mz_uint;
typedef long long mz_int64;
typedef unsigned long long mz_uint64;
typedef int mz_bool;

#define MZ_FALSE (0)
#define MZ_TRUE (1)

// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message.
#ifdef _MSC_VER
   #define MZ_MACRO_END while (0, 0)
#else
   #define MZ_MACRO_END while (0)
#endif

// ------------------- ZIP archive reading/writing

#ifndef MINIZ_NO_ARCHIVE_APIS

enum
{
  MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024,
  MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260,
  MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256
};

typedef struct
{
  mz_uint32 m_file_index;
  mz_uint32 m_central_dir_ofs;
  mz_uint16 m_version_made_by;
  mz_uint16 m_version_needed;
  mz_uint16 m_bit_flag;
  mz_uint16 m_method;
#ifndef MINIZ_NO_TIME
  time_t m_time;
#endif
  mz_uint32 m_crc32;
  mz_uint64 m_comp_size;
  mz_uint64 m_uncomp_size;
  mz_uint16 m_internal_attr;
  mz_uint32 m_external_attr;
  mz_uint64 m_local_header_ofs;
  mz_uint32 m_comment_size;
  char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE];
  char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE];
} mz_zip_archive_file_stat;

typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n);
typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n);

struct mz_zip_internal_state_tag;
typedef struct mz_zip_internal_state_tag mz_zip_internal_state;

typedef enum
{
  MZ_ZIP_MODE_INVALID = 0,
  MZ_ZIP_MODE_READING = 1,
  MZ_ZIP_MODE_WRITING = 2,
  MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3
} mz_zip_mode;

typedef struct mz_zip_archive_tag
{
  mz_uint64 m_archive_size;
  mz_uint64 m_central_directory_file_ofs;
  mz_uint m_total_files;
  mz_zip_mode m_zip_mode;

  mz_uint m_file_offset_alignment;

  mz_alloc_func m_pAlloc;
  mz_free_func m_pFree;
  mz_realloc_func m_pRealloc;
  void *m_pAlloc_opaque;

  mz_file_read_func m_pRead;
  mz_file_write_func m_pWrite;
  void *m_pIO_opaque;

  mz_zip_internal_state *m_pState;

} mz_zip_archive;

typedef enum
{
  MZ_ZIP_FLAG_CASE_SENSITIVE                = 0x0100,
  MZ_ZIP_FLAG_IGNORE_PATH                   = 0x0200,
  MZ_ZIP_FLAG_COMPRESSED_DATA               = 0x0400,
  MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800
} mz_zip_flags;

// ZIP archive reading

// Inits a ZIP archive reader.
// These functions read and validate the archive's central directory.
mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags);
mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags);

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags);
#endif

// Returns the total number of files in the archive.
mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip);

// Returns detailed information about an archive file entry.
mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);

// Determines if an archive file entry is a directory entry.
mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index);
mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index);

// Retrieves the filename of an archive file entry.
// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename.
mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size);

// Attempts to locates a file in the archive's central directory.
// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH
// Returns -1 if the file cannot be found.
int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);

// Extracts a archive file to a memory buffer using no memory allocation.
mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);
mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size);

// Extracts a archive file to a memory buffer.
mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags);

// Extracts a archive file to a dynamically allocated heap buffer.
void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags);
void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags);

// Extracts a archive file using a callback function to output the file's data.
mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags);

#ifndef MINIZ_NO_STDIO
// Extracts a archive file to a disk file and sets its last accessed and modified times.
// This function only extracts files, not archive directory records.
mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags);
mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags);
#endif

// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used.
mz_bool mz_zip_reader_end(mz_zip_archive *pZip);

// ZIP archive writing

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

// Inits a ZIP archive writer.
mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size);
mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size);

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning);
#endif

// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive.
// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called.
// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it).
// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL.
// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before
// the archive is finalized the file's central directory will be hosed.
mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename);

// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive.
// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer.
// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION.
mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags);
mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32);

#ifndef MINIZ_NO_STDIO
// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive.
// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION.
mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);
#endif

// Adds a file to an archive by fully cloning the data from another archive.
// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields.
mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index);

// Finalizes the archive by writing the central directory records followed by the end of central directory record.
// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end().
// An archive must be manually finalized by calling this function for it to be valid.
mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip);
mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize);

// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used.
// Note for the archive to be valid, it must have been finalized before ending.
mz_bool mz_zip_writer_end(mz_zip_archive *pZip);

// Misc. high-level helper functions:

// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive.
// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION.
mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags);

// Reads a single file from an archive into a heap block.
// Returns NULL on failure.
void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags);

#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

#endif // #ifndef MINIZ_NO_ARCHIVE_APIS

// ------------------- Low-level Decompression API Definitions

// Decompression flags used by tinfl_decompress().
// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream.
// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input.
// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB).
// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes.
enum
{
  TINFL_FLAG_PARSE_ZLIB_HEADER = 1,
  TINFL_FLAG_HAS_MORE_INPUT = 2,
  TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4,
  TINFL_FLAG_COMPUTE_ADLER32 = 8
};

// High level decompression functions:
// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc().
// On entry:
//  pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress.
// On return:
//  Function returns a pointer to the decompressed data, or NULL on failure.
//  *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data.
//  The caller must call mz_free() on the returned block when it's no longer needed.
void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);

// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory.
// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success.
#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1))
size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);

// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer.
// Returns 1 on success or 0 on failure.
typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser);
int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor;

// Max size of LZ dictionary.
#define TINFL_LZ_DICT_SIZE 32768

// Return status.
typedef enum
{
  TINFL_STATUS_BAD_PARAM = -3,
  TINFL_STATUS_ADLER32_MISMATCH = -2,
  TINFL_STATUS_FAILED = -1,
  TINFL_STATUS_DONE = 0,
  TINFL_STATUS_NEEDS_MORE_INPUT = 1,
  TINFL_STATUS_HAS_MORE_OUTPUT = 2
} tinfl_status;

// Initializes the decompressor to its initial state.
#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END
#define tinfl_get_adler32(r) (r)->m_check_adler32

// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability.
// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output.
tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);

// Internal/private bits follow.
enum
{
  TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19,
  TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS
};

typedef struct
{
  mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0];
  mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2];
} tinfl_huff_table;

#if MINIZ_HAS_64BIT_REGISTERS
  #define TINFL_USE_64BIT_BITBUF 1
#endif

#if TINFL_USE_64BIT_BITBUF
  typedef mz_uint64 tinfl_bit_buf_t;
  #define TINFL_BITBUF_SIZE (64)
#else
  typedef mz_uint32 tinfl_bit_buf_t;
  #define TINFL_BITBUF_SIZE (32)
#endif

struct tinfl_decompressor_tag
{
  mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES];
  tinfl_bit_buf_t m_bit_buf;
  size_t m_dist_from_out_buf_start;
  tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES];
  mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137];
};

// ------------------- Low-level Compression API Definitions

// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently).
#define TDEFL_LESS_MEMORY 0

// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search):
// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression).
enum
{
  TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF
};

// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data.
// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers).
// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing.
// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory).
// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1)
// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled.
// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables.
// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks.
// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK).
enum
{
  TDEFL_WRITE_ZLIB_HEADER             = 0x01000,
  TDEFL_COMPUTE_ADLER32               = 0x02000,
  TDEFL_GREEDY_PARSING_FLAG           = 0x04000,
  TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000,
  TDEFL_RLE_MATCHES                   = 0x10000,
  TDEFL_FILTER_MATCHES                = 0x20000,
  TDEFL_FORCE_ALL_STATIC_BLOCKS       = 0x40000,
  TDEFL_FORCE_ALL_RAW_BLOCKS          = 0x80000
};

// High level compression functions:
// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc().
// On entry:
//  pSrc_buf, src_buf_len: Pointer and size of source block to compress.
//  flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression.
// On return:
//  Function returns a pointer to the compressed data, or NULL on failure.
//  *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data.
//  The caller must free() the returned block when it's no longer needed.
void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags);

// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory.
// Returns 0 on failure.
size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags);

// Compresses an image to a compressed PNG file in memory.
// On entry:
//  pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. 
//  The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory.
//  level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL
//  If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps).
// On return:
//  Function returns a pointer to the compressed data, or NULL on failure.
//  *pLen_out will be set to the size of the PNG image file.
//  The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed.
void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip);
void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out);

// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time.
typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser);

// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally.
mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 };

// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes).
#if TDEFL_LESS_MEMORY
enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS };
#else
enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS };
#endif

// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions.
typedef enum
{
  TDEFL_STATUS_BAD_PARAM = -2,
  TDEFL_STATUS_PUT_BUF_FAILED = -1,
  TDEFL_STATUS_OKAY = 0,
  TDEFL_STATUS_DONE = 1,
} tdefl_status;

// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums
typedef enum
{
  TDEFL_NO_FLUSH = 0,
  TDEFL_SYNC_FLUSH = 2,
  TDEFL_FULL_FLUSH = 3,
  TDEFL_FINISH = 4
} tdefl_flush;

// tdefl's compression state structure.
typedef struct
{
  tdefl_put_buf_func_ptr m_pPut_buf_func;
  void *m_pPut_buf_user;
  mz_uint m_flags, m_max_probes[2];
  int m_greedy_parsing;
  mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size;
  mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end;
  mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer;
  mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish;
  tdefl_status m_prev_return_status;
  const void *m_pIn_buf;
  void *m_pOut_buf;
  size_t *m_pIn_buf_size, *m_pOut_buf_size;
  tdefl_flush m_flush;
  const mz_uint8 *m_pSrc;
  size_t m_src_buf_left, m_out_buf_ofs;
  mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1];
  mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
  mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
  mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS];
  mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE];
  mz_uint16 m_next[TDEFL_LZ_DICT_SIZE];
  mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE];
  mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE];
} tdefl_compressor;

// Initializes the compressor.
// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory.
// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression.
// If pBut_buf_func is NULL the user should always call the tdefl_compress() API.
// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.)
tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags);

// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible.
tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush);

// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr.
// tdefl_compress_buffer() always consumes the entire input buffer.
tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush);

tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d);
mz_uint32 tdefl_get_adler32(tdefl_compressor *d);

// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros.
#ifndef MINIZ_NO_ZLIB_APIS
// Create tdefl_compress() flags given zlib-style compression parameters.
// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files)
// window_bits may be -15 (raw deflate) or 15 (zlib)
// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED
mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy);
#endif // #ifndef MINIZ_NO_ZLIB_APIS

#ifdef __cplusplus
}
#endif

#endif // MINIZ_HEADER_INCLUDED

// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.)

#ifndef MINIZ_HEADER_FILE_ONLY

typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1];
typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1];
typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1];

#include <string.h>
#include <assert.h>

#define MZ_ASSERT(x) assert(x)

#ifdef MINIZ_NO_MALLOC
  #define MZ_MALLOC(x) NULL
  #define MZ_FREE(x) (void)x, ((void)0)
  #define MZ_REALLOC(p, x) NULL
#else
  #define MZ_MALLOC(x) malloc(x)
  #define MZ_FREE(x) free(x)
  #define MZ_REALLOC(p, x) realloc(p, x)
#endif

#define MZ_MAX(a,b) (((a)>(b))?(a):(b))
#define MZ_MIN(a,b) (((a)<(b))?(a):(b))
#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
  #define MZ_READ_LE16(p) *((const mz_uint16 *)(p))
  #define MZ_READ_LE32(p) *((const mz_uint32 *)(p))
#else
  #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U))
  #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U))
#endif

#ifdef _MSC_VER
  #define MZ_FORCEINLINE __forceinline
#elif defined(__GNUC__)
  #define MZ_FORCEINLINE inline __attribute__((__always_inline__))
#else
  #define MZ_FORCEINLINE inline
#endif

#ifdef __cplusplus
  extern "C" {
#endif

// ------------------- zlib-style API's

mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len)
{
  mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552;
  if (!ptr) return MZ_ADLER32_INIT;
  while (buf_len) {
    for (i = 0; i + 7 < block_len; i += 8, ptr += 8) {
      s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
      s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
    }
    for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
    s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
  }
  return (s2 << 16) + s1;
}

// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/
mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len)
{
  static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
    0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c };
  mz_uint32 crcu32 = (mz_uint32)crc;
  if (!ptr) return MZ_CRC32_INIT;
  crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; }
  return ~crcu32;
}

void mz_free(void *p)
{
  MZ_FREE(p);
}

#ifndef MINIZ_NO_ZLIB_APIS

static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); }
static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); }
static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); }

const char *mz_version(void)
{
  return MZ_VERSION;
}

int mz_deflateInit(mz_streamp pStream, int level)
{
  return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY);
}

int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy)
{
  tdefl_compressor *pComp;
  mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy);

  if (!pStream) return MZ_STREAM_ERROR;
  if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR;

  pStream->data_type = 0;
  pStream->adler = MZ_ADLER32_INIT;
  pStream->msg = NULL;
  pStream->reserved = 0;
  pStream->total_in = 0;
  pStream->total_out = 0;
  if (!pStream->zalloc) pStream->zalloc = def_alloc_func;
  if (!pStream->zfree) pStream->zfree = def_free_func;

  pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor));
  if (!pComp)
    return MZ_MEM_ERROR;

  pStream->state = (struct mz_internal_state *)pComp;

  if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY)
  {
    mz_deflateEnd(pStream);
    return MZ_PARAM_ERROR;
  }

  return MZ_OK;
}

int mz_deflateReset(mz_streamp pStream)
{
  if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR;
  pStream->total_in = pStream->total_out = 0;
  tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags);
  return MZ_OK;
}

int mz_deflate(mz_streamp pStream, int flush)
{
  size_t in_bytes, out_bytes;
  mz_ulong orig_total_in, orig_total_out;
  int mz_status = MZ_OK;

  if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR;
  if (!pStream->avail_out) return MZ_BUF_ERROR;

  if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH;

  if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE)
    return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR;

  orig_total_in = pStream->total_in; orig_total_out = pStream->total_out;
  for ( ; ; )
  {
    tdefl_status defl_status;
    in_bytes = pStream->avail_in; out_bytes = pStream->avail_out;

    defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush);
    pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes;
    pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state);

    pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes;
    pStream->total_out += (mz_uint)out_bytes;

    if (defl_status < 0)
    {
      mz_status = MZ_STREAM_ERROR;
      break;
    }
    else if (defl_status == TDEFL_STATUS_DONE)
    {
      mz_status = MZ_STREAM_END;
      break;
    }
    else if (!pStream->avail_out)
      break;
    else if ((!pStream->avail_in) && (flush != MZ_FINISH))
    {
      if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out))
        break;
      return MZ_BUF_ERROR; // Can't make forward progress without some input.
    }
  }
  return mz_status;
}

int mz_deflateEnd(mz_streamp pStream)
{
  if (!pStream) return MZ_STREAM_ERROR;
  if (pStream->state)
  {
    pStream->zfree(pStream->opaque, pStream->state);
    pStream->state = NULL;
  }
  return MZ_OK;
}

mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len)
{
  (void)pStream;
  // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.)
  return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5);
}

int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level)
{
  int status;
  mz_stream stream;
  memset(&stream, 0, sizeof(stream));

  // In case mz_ulong is 64-bits (argh I hate longs).
  if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR;

  stream.next_in = (unsigned char *)pSource;
  stream.avail_in = (mz_uint32)source_len;
  stream.next_out = pDest;
  stream.avail_out = (mz_uint32)*pDest_len;

  status = mz_deflateInit(&stream, level);
  if (status != MZ_OK) return status;

  status = mz_deflate(&stream, MZ_FINISH);
  if (status != MZ_STREAM_END)
  {
    mz_deflateEnd(&stream);
    return (status == MZ_OK) ? MZ_BUF_ERROR : status;
  }

  *pDest_len = stream.total_out;
  return mz_deflateEnd(&stream);
}

int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
  return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION);
}

mz_ulong mz_compressBound(mz_ulong source_len)
{
  return mz_deflateBound(NULL, source_len);
}

typedef struct
{
  tinfl_decompressor m_decomp;
  mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits;
  mz_uint8 m_dict[TINFL_LZ_DICT_SIZE];
  tinfl_status m_last_status;
} inflate_state;

int mz_inflateInit2(mz_streamp pStream, int window_bits)
{
  inflate_state *pDecomp;
  if (!pStream) return MZ_STREAM_ERROR;
  if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR;

  pStream->data_type = 0;
  pStream->adler = 0;
  pStream->msg = NULL;
  pStream->total_in = 0;
  pStream->total_out = 0;
  pStream->reserved = 0;
  if (!pStream->zalloc) pStream->zalloc = def_alloc_func;
  if (!pStream->zfree) pStream->zfree = def_free_func;

  pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state));
  if (!pDecomp) return MZ_MEM_ERROR;

  pStream->state = (struct mz_internal_state *)pDecomp;

  tinfl_init(&pDecomp->m_decomp);
  pDecomp->m_dict_ofs = 0;
  pDecomp->m_dict_avail = 0;
  pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT;
  pDecomp->m_first_call = 1;
  pDecomp->m_has_flushed = 0;
  pDecomp->m_window_bits = window_bits;

  return MZ_OK;
}

int mz_inflateInit(mz_streamp pStream)
{
   return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS);
}

int mz_inflate(mz_streamp pStream, int flush)
{
  inflate_state* pState;
  mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32;
  size_t in_bytes, out_bytes, orig_avail_in;
  tinfl_status status;

  if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR;
  if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH;
  if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR;

  pState = (inflate_state*)pStream->state;
  if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER;
  orig_avail_in = pStream->avail_in;

  first_call = pState->m_first_call; pState->m_first_call = 0;
  if (pState->m_last_status < 0) return MZ_DATA_ERROR;

  if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR;
  pState->m_has_flushed |= (flush == MZ_FINISH);

  if ((flush == MZ_FINISH) && (first_call))
  {
    // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file.
    decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF;
    in_bytes = pStream->avail_in; out_bytes = pStream->avail_out;
    status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags);
    pState->m_last_status = status;
    pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes;
    pStream->adler = tinfl_get_adler32(&pState->m_decomp);
    pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes;

    if (status < 0)
      return MZ_DATA_ERROR;
    else if (status != TINFL_STATUS_DONE)
    {
      pState->m_last_status = TINFL_STATUS_FAILED;
      return MZ_BUF_ERROR;
    }
    return MZ_STREAM_END;
  }
  // flush != MZ_FINISH then we must assume there's more input.
  if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT;

  if (pState->m_dict_avail)
  {
    n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
    memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
    pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n;
    pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);
    return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
  }

  for ( ; ; )
  {
    in_bytes = pStream->avail_in;
    out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs;

    status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags);
    pState->m_last_status = status;

    pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes;
    pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp);

    pState->m_dict_avail = (mz_uint)out_bytes;

    n = MZ_MIN(pState->m_dict_avail, pStream->avail_out);
    memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n);
    pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n;
    pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1);

    if (status < 0)
       return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well).
    else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in))
      return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH.
    else if (flush == MZ_FINISH)
    {
       // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH.
       if (status == TINFL_STATUS_DONE)
          return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END;
       // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong.
       else if (!pStream->avail_out)
          return MZ_BUF_ERROR;
    }
    else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail))
      break;
  }

  return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK;
}

int mz_inflateEnd(mz_streamp pStream)
{
  if (!pStream)
    return MZ_STREAM_ERROR;
  if (pStream->state)
  {
    pStream->zfree(pStream->opaque, pStream->state);
    pStream->state = NULL;
  }
  return MZ_OK;
}

int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len)
{
  mz_stream stream;
  int status;
  memset(&stream, 0, sizeof(stream));

  // In case mz_ulong is 64-bits (argh I hate longs).
  if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR;

  stream.next_in = (unsigned char *)pSource;
  stream.avail_in = (mz_uint32)source_len;
  stream.next_out = pDest;
  stream.avail_out = (mz_uint32)*pDest_len;

  status = mz_inflateInit(&stream);
  if (status != MZ_OK)
    return status;

  status = mz_inflate(&stream, MZ_FINISH);
  if (status != MZ_STREAM_END)
  {
    mz_inflateEnd(&stream);
    return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status;
  }
  *pDest_len = stream.total_out;

  return mz_inflateEnd(&stream);
}

const char *mz_error(int err)
{
  static struct { int m_err; const char *m_pDesc; } s_error_descs[] =
  {
    { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" },
    { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" }
  };
  mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc;
  return NULL;
}

#endif //MINIZ_NO_ZLIB_APIS

// ------------------- Low-level Decompression (completely independent from all compression API's)

#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l)
#define TINFL_MEMSET(p, c, l) memset(p, c, l)

#define TINFL_CR_BEGIN switch(r->m_state) { case 0:
#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END
#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END
#define TINFL_CR_FINISH }

// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never
// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario.
#define TINFL_GET_BYTE(state_index, c) do { \
  if (pIn_buf_cur >= pIn_buf_end) { \
    for ( ; ; ) { \
      if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \
        TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \
        if (pIn_buf_cur < pIn_buf_end) { \
          c = *pIn_buf_cur++; \
          break; \
        } \
      } else { \
        c = 0; \
        break; \
      } \
    } \
  } else c = *pIn_buf_cur++; } MZ_MACRO_END

#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n))
#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END
#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END

// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2.
// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a
// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the
// bit buffer contains >=15 bits (deflate's max. Huffman code size).
#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \
  do { \
    temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \
    if (temp >= 0) { \
      code_len = temp >> 9; \
      if ((code_len) && (num_bits >= code_len)) \
      break; \
    } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \
       code_len = TINFL_FAST_LOOKUP_BITS; \
       do { \
          temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \
       } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \
    } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \
  } while (num_bits < 15);

// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read
// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully
// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32.
// The slow path is only executed at the very end of the input buffer.
#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \
  int temp; mz_uint code_len, c; \
  if (num_bits < 15) { \
    if ((pIn_buf_end - pIn_buf_cur) < 2) { \
       TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \
    } else { \
       bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \
    } \
  } \
  if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \
    code_len = temp >> 9, temp &= 511; \
  else { \
    code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \
  } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END

tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags)
{
  static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 };
  static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
  static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
  static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
  static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
  static const int s_min_table_sizes[3] = { 257, 1, 4 };

  tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf;
  const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size;
  mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size;
  size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start;

  // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter).
  if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; }

  num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start;
  TINFL_CR_BEGIN

  bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1;
  if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
  {
    TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1);
    counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8));
    if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4)))));
    if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); }
  }

  do
  {
    TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1;
    if (r->m_type == 0)
    {
      TINFL_SKIP_BITS(5, num_bits & 7);
      for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); }
      if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); }
      while ((counter) && (num_bits))
      {
        TINFL_GET_BITS(51, dist, 8);
        while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); }
        *pOut_buf_cur++ = (mz_uint8)dist;
        counter--;
      }
      while (counter)
      {
        size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); }
        while (pIn_buf_cur >= pIn_buf_end)
        {
          if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT)
          {
            TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT);
          }
          else
          {
            TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED);
          }
        }
        n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter);
        TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n;
      }
    }
    else if (r->m_type == 3)
    {
      TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED);
    }
    else
    {
      if (r->m_type == 1)
      {
        mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i;
        r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32);
        for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8;
      }
      else
      {
        for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; }
        MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; }
        r->m_table_sizes[2] = 19;
      }
      for ( ; (int)r->m_type >= 0; r->m_type--)
      {
        int tree_next, tree_cur; tinfl_huff_table *pTable;
        mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree);
        for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++;
        used_syms = 0, total = 0; next_code[0] = next_code[1] = 0;
        for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); }
        if ((65536 != total) && (used_syms > 1))
        {
          TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED);
        }
        for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index)
        {
          mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue;
          cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1);
          if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; }
          if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; }
          rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1);
          for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--)
          {
            tree_cur -= ((rev_code >>= 1) & 1);
            if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1];
          }
          tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index;
        }
        if (r->m_type == 2)
        {
          for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); )
          {
            mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; }
            if ((dist == 16) && (!counter))
            {
              TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED);
            }
            num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16];
            TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s;
          }
          if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter)
          {
            TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED);
          }
          TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]);
        }
      }
      for ( ; ; )
      {
        mz_uint8 *pSrc;
        for ( ; ; )
        {
          if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2))
          {
            TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]);
            if (counter >= 256)
              break;
            while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); }
            *pOut_buf_cur++ = (mz_uint8)counter;
          }
          else
          {
            int sym2; mz_uint code_len;
#if TINFL_USE_64BIT_BITBUF
            if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; }
#else
            if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
#endif
            if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
              code_len = sym2 >> 9;
            else
            {
              code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0);
            }
            counter = sym2; bit_buf >>= code_len; num_bits -= code_len;
            if (counter & 256)
              break;

#if !TINFL_USE_64BIT_BITBUF
            if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; }
#endif
            if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0)
              code_len = sym2 >> 9;
            else
            {
              code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0);
            }
            bit_buf >>= code_len; num_bits -= code_len;

            pOut_buf_cur[0] = (mz_uint8)counter;
            if (sym2 & 256)
            {
              pOut_buf_cur++;
              counter = sym2;
              break;
            }
            pOut_buf_cur[1] = (mz_uint8)sym2;
            pOut_buf_cur += 2;
          }
        }
        if ((counter &= 511) == 256) break;

        num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257];
        if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; }

        TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]);
        num_extra = s_dist_extra[dist]; dist = s_dist_base[dist];
        if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; }

        dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start;
        if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))
        {
          TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED);
        }

        pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask);

        if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end)
        {
          while (counter--)
          {
            while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); }
            *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask];
          }
          continue;
        }
#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
        else if ((counter >= 9) && (counter <= dist))
        {
          const mz_uint8 *pSrc_end = pSrc + (counter & ~7);
          do
          {
            ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0];
            ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1];
            pOut_buf_cur += 8;
          } while ((pSrc += 8) < pSrc_end);
          if ((counter &= 7) < 3)
          {
            if (counter)
            {
              pOut_buf_cur[0] = pSrc[0];
              if (counter > 1)
                pOut_buf_cur[1] = pSrc[1];
              pOut_buf_cur += counter;
            }
            continue;
          }
        }
#endif
        do
        {
          pOut_buf_cur[0] = pSrc[0];
          pOut_buf_cur[1] = pSrc[1];
          pOut_buf_cur[2] = pSrc[2];
          pOut_buf_cur += 3; pSrc += 3;
        } while ((int)(counter -= 3) > 2);
        if ((int)counter > 0)
        {
          pOut_buf_cur[0] = pSrc[0];
          if ((int)counter > 1)
            pOut_buf_cur[1] = pSrc[1];
          pOut_buf_cur += counter;
        }
      }
    }
  } while (!(r->m_final & 1));
  if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER)
  {
    TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; }
  }
  TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE);
  TINFL_CR_FINISH

common_exit:
  r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start;
  *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next;
  if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0))
  {
    const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size;
    mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552;
    while (buf_len)
    {
      for (i = 0; i + 7 < block_len; i += 8, ptr += 8)
      {
        s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1;
        s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1;
      }
      for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1;
      s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552;
    }
    r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH;
  }
  return status;
}

// Higher level helper functions.
void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
{
  tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0;
  *pOut_len = 0;
  tinfl_init(&decomp);
  for ( ; ; )
  {
    size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity;
    tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size,
      (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
    if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT))
    {
      MZ_FREE(pBuf); *pOut_len = 0; return NULL;
    }
    src_buf_ofs += src_buf_size;
    *pOut_len += dst_buf_size;
    if (status == TINFL_STATUS_DONE) break;
    new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128;
    pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity);
    if (!pNew_buf)
    {
      MZ_FREE(pBuf); *pOut_len = 0; return NULL;
    }
    pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity;
  }
  return pBuf;
}

size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
{
  tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp);
  status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
  return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len;
}

int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
  int result = 0;
  tinfl_decompressor decomp;
  mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0;
  if (!pDict)
    return TINFL_STATUS_FAILED;
  tinfl_init(&decomp);
  for ( ; ; )
  {
    size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs;
    tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size,
      (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)));
    in_buf_ofs += in_buf_size;
    if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user)))
      break;
    if (status != TINFL_STATUS_HAS_MORE_OUTPUT)
    {
      result = (status == TINFL_STATUS_DONE);
      break;
    }
    dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1);
  }
  MZ_FREE(pDict);
  *pIn_buf_size = in_buf_ofs;
  return result;
}

// ------------------- Low-level Compression (independent from all decompression API's)

// Purposely making these tables static for faster init and thread safety.
static const mz_uint16 s_tdefl_len_sym[256] = {
  257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272,
  273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276,
  277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
  279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,
  281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,
  282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,
  283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,
  284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 };

static const mz_uint8 s_tdefl_len_extra[256] = {
  0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
  4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 };

static const mz_uint8 s_tdefl_small_dist_sym[512] = {
  0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,
  11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,
  13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14,
  14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
  14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
  15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,
  16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
  16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
  16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
  17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
  17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
  17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 };

static const mz_uint8 s_tdefl_small_dist_extra[512] = {
  0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,
  5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
  6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
  6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
  7,7,7,7,7,7,7,7 };

static const mz_uint8 s_tdefl_large_dist_sym[128] = {
  0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26,
  26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,
  28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 };

static const mz_uint8 s_tdefl_large_dist_extra[128] = {
  0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,
  12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,
  13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 };

// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values.
typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq;
static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1)
{
  mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist);
  for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; }
  while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--;
  for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
  {
    const mz_uint32* pHist = &hist[pass << 8];
    mz_uint offsets[256], cur_ofs = 0;
    for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; }
    for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
    { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; }
  }
  return pCur_syms;
}

// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996.
static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n)
{
  int root, leaf, next, avbl, used, dpth;
  if (n==0) return; else if (n==1) { A[0].m_key = 1; return; }
  A[0].m_key += A[1].m_key; root = 0; leaf = 2;
  for (next=1; next < n-1; next++)
  {
    if (leaf>=n || A[root].m_key<A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (mz_uint16)next; } else A[next].m_key = A[leaf++].m_key;
    if (leaf>=n || (root<next && A[root].m_key<A[leaf].m_key)) { A[next].m_key = (mz_uint16)(A[next].m_key + A[root].m_key); A[root++].m_key = (mz_uint16)next; } else A[next].m_key = (mz_uint16)(A[next].m_key + A[leaf++].m_key);
  }
  A[n-2].m_key = 0; for (next=n-3; next>=0; next--) A[next].m_key = A[A[next].m_key].m_key+1;
  avbl = 1; used = dpth = 0; root = n-2; next = n-1;
  while (avbl>0)
  {
    while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; }
    while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; }
    avbl = 2*used; dpth++; used = 0;
  }
}

// Limits canonical Huffman code table's max code size.
enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 };
static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size)
{
  int i; mz_uint32 total = 0; if (code_list_len <= 1) return;
  for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i];
  for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i));
  while (total != (1UL << max_code_size))
  {
    pNum_codes[max_code_size]--;
    for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; }
    total--;
  }
}

static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table)
{
  int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes);
  if (static_table)
  {
    for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++;
  }
  else
  {
    tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms;
    int num_used_syms = 0;
    const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0];
    for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; }

    pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms);

    for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++;

    tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);

    MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]);
    for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
      for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i);
  }

  next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1);

  for (i = 0; i < table_len; i++)
  {
    mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue;
    code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1);
    d->m_huff_codes[table_num][i] = (mz_uint16)rev_code;
  }
}

#define TDEFL_PUT_BITS(b, l) do { \
  mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \
  d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \
  while (d->m_bits_in >= 8) { \
    if (d->m_pOutput_buf < d->m_pOutput_buf_end) \
      *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \
      d->m_bit_buffer >>= 8; \
      d->m_bits_in -= 8; \
  } \
} MZ_MACRO_END

#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \
  if (rle_repeat_count < 3) { \
    d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
    while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \
  } else { \
    d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \
} rle_repeat_count = 0; } }

#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \
  if (rle_z_count < 3) { \
    d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \
  } else if (rle_z_count <= 10) { \
    d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \
  } else { \
    d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \
} rle_z_count = 0; } }

static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };

static void tdefl_start_dynamic_block(tdefl_compressor *d)
{
  int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
  mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;

  d->m_huff_count[0][256] = 1;

  tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE);
  tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE);

  for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break;
  for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break;

  memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
  memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
  total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0;

  memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2);
  for (i = 0; i < total_code_sizes_to_pack; i++)
  {
    mz_uint8 code_size = code_sizes_to_pack[i];
    if (!code_size)
    {
      TDEFL_RLE_PREV_CODE_SIZE();
      if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); }
    }
    else
    {
      TDEFL_RLE_ZERO_CODE_SIZE();
      if (code_size != prev_code_size)
      {
        TDEFL_RLE_PREV_CODE_SIZE();
        d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size;
      }
      else if (++rle_repeat_count == 6)
      {
        TDEFL_RLE_PREV_CODE_SIZE();
      }
    }
    prev_code_size = code_size;
  }
  if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); }

  tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE);

  TDEFL_PUT_BITS(2, 2);

  TDEFL_PUT_BITS(num_lit_codes - 257, 5);
  TDEFL_PUT_BITS(num_dist_codes - 1, 5);

  for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break;
  num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4);
  for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3);

  for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; )
  {
    mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2);
    TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
    if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
  }
}

static void tdefl_start_static_block(tdefl_compressor *d)
{
  mz_uint i;
  mz_uint8 *p = &d->m_huff_code_sizes[0][0];

  for (i = 0; i <= 143; ++i) *p++ = 8;
  for ( ; i <= 255; ++i) *p++ = 9;
  for ( ; i <= 279; ++i) *p++ = 7;
  for ( ; i <= 287; ++i) *p++ = 8;

  memset(d->m_huff_code_sizes[1], 5, 32);

  tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE);
  tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE);

  TDEFL_PUT_BITS(1, 2);
}

static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS
static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
{
  mz_uint flags;
  mz_uint8 *pLZ_codes;
  mz_uint8 *pOutput_buf = d->m_pOutput_buf;
  mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf;
  mz_uint64 bit_buffer = d->m_bit_buffer;
  mz_uint bits_in = d->m_bits_in;

#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); }

  flags = 1;
  for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1)
  {
    if (flags == 1)
      flags = *pLZ_codes++ | 0x100;

    if (flags & 1)
    {
      mz_uint s0, s1, n0, n1, sym, num_extra_bits;
      mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3;

      MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
      TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
      TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);

      // This sequence coaxes MSVC into using cmov's vs. jmp's.
      s0 = s_tdefl_small_dist_sym[match_dist & 511];
      n0 = s_tdefl_small_dist_extra[match_dist & 511];
      s1 = s_tdefl_large_dist_sym[match_dist >> 8];
      n1 = s_tdefl_large_dist_extra[match_dist >> 8];
      sym = (match_dist < 512) ? s0 : s1;
      num_extra_bits = (match_dist < 512) ? n0 : n1;

      MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
      TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
      TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
    }
    else
    {
      mz_uint lit = *pLZ_codes++;
      MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
      TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);

      if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
      {
        flags >>= 1;
        lit = *pLZ_codes++;
        MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
        TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);

        if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end))
        {
          flags >>= 1;
          lit = *pLZ_codes++;
          MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
          TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
        }
      }
    }

    if (pOutput_buf >= d->m_pOutput_buf_end)
      return MZ_FALSE;

    *(mz_uint64*)pOutput_buf = bit_buffer;
    pOutput_buf += (bits_in >> 3);
    bit_buffer >>= (bits_in & ~7);
    bits_in &= 7;
  }

#undef TDEFL_PUT_BITS_FAST

  d->m_pOutput_buf = pOutput_buf;
  d->m_bits_in = 0;
  d->m_bit_buffer = 0;

  while (bits_in)
  {
    mz_uint32 n = MZ_MIN(bits_in, 16);
    TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n);
    bit_buffer >>= n;
    bits_in -= n;
  }

  TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);

  return (d->m_pOutput_buf < d->m_pOutput_buf_end);
}
#else
static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d)
{
  mz_uint flags;
  mz_uint8 *pLZ_codes;

  flags = 1;
  for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1)
  {
    if (flags == 1)
      flags = *pLZ_codes++ | 0x100;
    if (flags & 1)
    {
      mz_uint sym, num_extra_bits;
      mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3;

      MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
      TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]);
      TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]);

      if (match_dist < 512)
      {
        sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist];
      }
      else
      {
        sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8];
      }
      MZ_ASSERT(d->m_huff_code_sizes[1][sym]);
      TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]);
      TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits);
    }
    else
    {
      mz_uint lit = *pLZ_codes++;
      MZ_ASSERT(d->m_huff_code_sizes[0][lit]);
      TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]);
    }
  }

  TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]);

  return (d->m_pOutput_buf < d->m_pOutput_buf_end);
}
#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS

static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block)
{
  if (static_block)
    tdefl_start_static_block(d);
  else
    tdefl_start_dynamic_block(d);
  return tdefl_compress_lz_codes(d);
}

static int tdefl_flush_block(tdefl_compressor *d, int flush)
{
  mz_uint saved_bit_buf, saved_bits_in;
  mz_uint8 *pSaved_output_buf;
  mz_bool comp_block_succeeded = MZ_FALSE;
  int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size;
  mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf;

  d->m_pOutput_buf = pOutput_buf_start;
  d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16;

  MZ_ASSERT(!d->m_output_flush_remaining);
  d->m_output_flush_ofs = 0;
  d->m_output_flush_remaining = 0;

  *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left);
  d->m_pLZ_code_buf -= (d->m_num_flags_left == 8);

  if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index))
  {
    TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8);
  }

  TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1);

  pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in;

  if (!use_raw_block)
    comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48));

  // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead.
  if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) &&
       ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) )
  {
    mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
    TDEFL_PUT_BITS(0, 2);
    if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); }
    for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF)
    {
      TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16);
    }
    for (i = 0; i < d->m_total_lz_bytes; ++i)
    {
      TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8);
    }
  }
  // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes.
  else if (!comp_block_succeeded)
  {
    d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in;
    tdefl_compress_block(d, MZ_TRUE);
  }

  if (flush)
  {
    if (flush == TDEFL_FINISH)
    {
      if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); }
      if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } }
    }
    else
    {
      mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); }
    }
  }

  MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end);

  memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
  memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);

  d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++;

  if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0)
  {
    if (d->m_pPut_buf_func)
    {
      *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
      if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user))
        return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED);
    }
    else if (pOutput_buf_start == d->m_output_buf)
    {
      int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs));
      memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy);
      d->m_out_buf_ofs += bytes_to_copy;
      if ((n -= bytes_to_copy) != 0)
      {
        d->m_output_flush_ofs = bytes_to_copy;
        d->m_output_flush_remaining = n;
      }
    }
    else
    {
      d->m_out_buf_ofs += n;
    }
  }

  return d->m_output_flush_remaining;
}

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES
#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p)
static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
{
  mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
  mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
  const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q;
  mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s);
  MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return;
  for ( ; ; )
  {
    for ( ; ; )
    {
      if (--num_probes_left == 0) return;
      #define TDEFL_PROBE \
        next_probe_pos = d->m_next[probe_pos]; \
        if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \
        probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \
        if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break;
      TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE;
    }
    if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32;
    do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) &&
                   (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) );
    if (!probe_len)
    {
      *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break;
    }
    else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len)
    {
      *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break;
      c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]);
    }
  }
}
#else
static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len)
{
  mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len;
  mz_uint num_probes_left = d->m_max_probes[match_len >= 32];
  const mz_uint8 *s = d->m_dict + pos, *p, *q;
  mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1];
  MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return;
  for ( ; ; )
  {
    for ( ; ; )
    {
      if (--num_probes_left == 0) return;
      #define TDEFL_PROBE \
        next_probe_pos = d->m_next[probe_pos]; \
        if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \
        probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \
        if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break;
      TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE;
    }
    if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break;
    if (probe_len > match_len)
    {
      *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return;
      c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1];
    }
  }
}
#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
static mz_bool tdefl_compress_fast(tdefl_compressor *d)
{
  // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio.
  mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left;
  mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags;
  mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;

  while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size)))
  {
    const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096;
    mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
    mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size);
    d->m_src_buf_left -= num_bytes_to_process;
    lookahead_size += num_bytes_to_process;

    while (num_bytes_to_process)
    {
      mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process);
      memcpy(d->m_dict + dst_pos, d->m_pSrc, n);
      if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
        memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos));
      d->m_pSrc += n;
      dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK;
      num_bytes_to_process -= n;
    }

    dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size);
    if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break;

    while (lookahead_size >= 4)
    {
      mz_uint cur_match_dist, cur_match_len = 1;
      mz_uint8 *pCur_dict = d->m_dict + cur_pos;
      mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF;
      mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK;
      mz_uint probe_pos = d->m_hash[hash];
      d->m_hash[hash] = (mz_uint16)lookahead_pos;

      if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram))
      {
        const mz_uint16 *p = (const mz_uint16 *)pCur_dict;
        const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos);
        mz_uint32 probe_len = 32;
        do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) &&
          (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) );
        cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q);
        if (!probe_len)
          cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0;

        if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)))
        {
          cur_match_len = 1;
          *pLZ_code_buf++ = (mz_uint8)first_trigram;
          *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
          d->m_huff_count[0][(mz_uint8)first_trigram]++;
        }
        else
        {
          mz_uint32 s0, s1;
          cur_match_len = MZ_MIN(cur_match_len, lookahead_size);

          MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE));

          cur_match_dist--;

          pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN);
          *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist;
          pLZ_code_buf += 3;
          *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80);

          s0 = s_tdefl_small_dist_sym[cur_match_dist & 511];
          s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8];
          d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++;

          d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++;
        }
      }
      else
      {
        *pLZ_code_buf++ = (mz_uint8)first_trigram;
        *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
        d->m_huff_count[0][(mz_uint8)first_trigram]++;
      }

      if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; }

      total_lz_bytes += cur_match_len;
      lookahead_pos += cur_match_len;
      dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE);
      cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK;
      MZ_ASSERT(lookahead_size >= cur_match_len);
      lookahead_size -= cur_match_len;

      if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
      {
        int n;
        d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size;
        d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left;
        if ((n = tdefl_flush_block(d, 0)) != 0)
          return (n < 0) ? MZ_FALSE : MZ_TRUE;
        total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left;
      }
    }

    while (lookahead_size)
    {
      mz_uint8 lit = d->m_dict[cur_pos];

      total_lz_bytes++;
      *pLZ_code_buf++ = lit;
      *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1);
      if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; }

      d->m_huff_count[0][lit]++;

      lookahead_pos++;
      dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE);
      cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK;
      lookahead_size--;

      if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8])
      {
        int n;
        d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size;
        d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left;
        if ((n = tdefl_flush_block(d, 0)) != 0)
          return (n < 0) ? MZ_FALSE : MZ_TRUE;
        total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left;
      }
    }
  }

  d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size;
  d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left;
  return MZ_TRUE;
}
#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN

static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit)
{
  d->m_total_lz_bytes++;
  *d->m_pLZ_code_buf++ = lit;
  *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; }
  d->m_huff_count[0][lit]++;
}

static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist)
{
  mz_uint32 s0, s1;

  MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE));

  d->m_total_lz_bytes += match_len;

  d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN);

  match_dist -= 1;
  d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF);
  d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3;

  *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; }

  s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127];
  d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++;

  if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++;
}

static mz_bool tdefl_compress_normal(tdefl_compressor *d)
{
  const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left;
  tdefl_flush flush = d->m_flush;

  while ((src_buf_left) || ((flush) && (d->m_lookahead_size)))
  {
    mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos;
    // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN.
    if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1))
    {
      mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2;
      mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK];
      mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size);
      const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process;
      src_buf_left -= num_bytes_to_process;
      d->m_lookahead_size += num_bytes_to_process;
      while (pSrc != pSrc_end)
      {
        mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
        hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
        d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos);
        dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++;
      }
    }
    else
    {
      while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
      {
        mz_uint8 c = *pSrc++;
        mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK;
        src_buf_left--;
        d->m_dict[dst_pos] = c;
        if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1))
          d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c;
        if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN)
        {
          mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2;
          mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1);
          d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos);
        }
      }
    }
    d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size);
    if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN))
      break;

    // Simple lazy/greedy parsing state machine.
    len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK;
    if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS))
    {
      if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS)))
      {
        mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK];
        cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; }
        if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1;
      }
    }
    else
    {
      tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len);
    }
    if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5)))
    {
      cur_match_dist = cur_match_len = 0;
    }
    if (d->m_saved_match_len)
    {
      if (cur_match_len > d->m_saved_match_len)
      {
        tdefl_record_literal(d, (mz_uint8)d->m_saved_lit);
        if (cur_match_len >= 128)
        {
          tdefl_record_match(d, cur_match_len, cur_match_dist);
          d->m_saved_match_len = 0; len_to_move = cur_match_len;
        }
        else
        {
          d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len;
        }
      }
      else
      {
        tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist);
        len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0;
      }
    }
    else if (!cur_match_dist)
      tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]);
    else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128))
    {
      tdefl_record_match(d, cur_match_len, cur_match_dist);
      len_to_move = cur_match_len;
    }
    else
    {
      d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len;
    }
    // Move the lookahead forward by len_to_move bytes.
    d->m_lookahead_pos += len_to_move;
    MZ_ASSERT(d->m_lookahead_size >= len_to_move);
    d->m_lookahead_size -= len_to_move;
    d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE);
    // Check if it's time to flush the current LZ codes to the internal output buffer.
    if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) ||
         ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) )
    {
      int n;
      d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left;
      if ((n = tdefl_flush_block(d, 0)) != 0)
        return (n < 0) ? MZ_FALSE : MZ_TRUE;
    }
  }

  d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left;
  return MZ_TRUE;
}

static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d)
{
  if (d->m_pIn_buf_size)
  {
    *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf;
  }

  if (d->m_pOut_buf_size)
  {
    size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining);
    memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n);
    d->m_output_flush_ofs += (mz_uint)n;
    d->m_output_flush_remaining -= (mz_uint)n;
    d->m_out_buf_ofs += n;

    *d->m_pOut_buf_size = d->m_out_buf_ofs;
  }

  return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY;
}

tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush)
{
  if (!d)
  {
    if (pIn_buf_size) *pIn_buf_size = 0;
    if (pOut_buf_size) *pOut_buf_size = 0;
    return TDEFL_STATUS_BAD_PARAM;
  }

  d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size;
  d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size;
  d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0;
  d->m_out_buf_ofs = 0;
  d->m_flush = flush;

  if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) ||
        (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) )
  {
    if (pIn_buf_size) *pIn_buf_size = 0;
    if (pOut_buf_size) *pOut_buf_size = 0;
    return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM);
  }
  d->m_wants_to_finish |= (flush == TDEFL_FINISH);

  if ((d->m_output_flush_remaining) || (d->m_finished))
    return (d->m_prev_return_status = tdefl_flush_output_buffer(d));

#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
  if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) &&
      ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) &&
      ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0))
  {
    if (!tdefl_compress_fast(d))
      return d->m_prev_return_status;
  }
  else
#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN
  {
    if (!tdefl_compress_normal(d))
      return d->m_prev_return_status;
  }

  if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf))
    d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf);

  if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining))
  {
    if (tdefl_flush_block(d, flush) < 0)
      return d->m_prev_return_status;
    d->m_finished = (flush == TDEFL_FINISH);
    if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; }
  }

  return (d->m_prev_return_status = tdefl_flush_output_buffer(d));
}

tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush)
{
  MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush);
}

tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
  d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user;
  d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0;
  d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3;
  if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash);
  d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0;
  d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0;
  d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8;
  d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY;
  d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1;
  d->m_pIn_buf = NULL; d->m_pOut_buf = NULL;
  d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL;
  d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0;
  memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
  memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
  return TDEFL_STATUS_OKAY;
}

tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d)
{
  return d->m_prev_return_status;
}

mz_uint32 tdefl_get_adler32(tdefl_compressor *d)
{
  return d->m_adler32;
}

mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags)
{
  tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE;
  pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE;
  succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY);
  succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE);
  MZ_FREE(pComp); return succeeded;
}

typedef struct
{
  size_t m_size, m_capacity;
  mz_uint8 *m_pBuf;
  mz_bool m_expandable;
} tdefl_output_buffer;

static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser)
{
  tdefl_output_buffer *p = (tdefl_output_buffer *)pUser;
  size_t new_size = p->m_size + len;
  if (new_size > p->m_capacity)
  {
    size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE;
    do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity);
    pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE;
    p->m_pBuf = pNew_buf; p->m_capacity = new_capacity;
  }
  memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size;
  return MZ_TRUE;
}

void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags)
{
  tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf);
  if (!pOut_len) return MZ_FALSE; else *pOut_len = 0;
  out_buf.m_expandable = MZ_TRUE;
  if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL;
  *pOut_len = out_buf.m_size; return out_buf.m_pBuf;
}

size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags)
{
  tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf);
  if (!pOut_buf) return 0;
  out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len;
  if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0;
  return out_buf.m_size;
}

#ifndef MINIZ_NO_ZLIB_APIS
static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32,  16, 32, 128, 256,  512, 768, 1500 };

// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files).
mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy)
{
  mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0);
  if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER;

  if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS;
  else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES;
  else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK;
  else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
  else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES;

  return comp_flags;
}
#endif //MINIZ_NO_ZLIB_APIS

#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal)
#endif

// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at
// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/.
// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck.
void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip)
{
  // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined.
  static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32,  16, 32, 128, 256,  512, 768, 1500 };
  tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0;
  if (!pComp) return NULL;
  MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; }
  // write dummy header
  for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf);
  // compress image data
  tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER);
  for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); }
  if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; }
  // write real header
  *pLen_out = out_buf.m_size-41;
  {
    static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06};
    mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52,
      0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0,
      (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54};
    c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24);
    memcpy(out_buf.m_pBuf, pnghdr, 41);
  }
  // write footer (IDAT CRC-32, followed by IEND chunk)
  if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; }
  c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24);
  // compute final size of file, grab compressed data buffer and return
  *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf;
}
void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out)
{
  // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out)
  return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE);
}

#ifdef _MSC_VER
#pragma warning (pop)
#endif

// ------------------- .ZIP archive reading

#ifndef MINIZ_NO_ARCHIVE_APIS

#ifdef MINIZ_NO_STDIO
  #define MZ_FILE void *
#else
  #include <stdio.h>
  #include <sys/stat.h>

  #if defined(_MSC_VER) || defined(__MINGW64__)
    static FILE *mz_fopen(const char *pFilename, const char *pMode)
    {
      FILE* pFile = NULL;
      fopen_s(&pFile, pFilename, pMode);
      return pFile;
    }
    static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
    {
      FILE* pFile = NULL;
      if (freopen_s(&pFile, pPath, pMode, pStream))
        return NULL;
      return pFile;
    }
    #ifndef MINIZ_NO_TIME
      #include <sys/utime.h>
    #endif
    #define MZ_FILE FILE
    #define MZ_FOPEN mz_fopen
    #define MZ_FCLOSE fclose
    #define MZ_FREAD fread
    #define MZ_FWRITE fwrite
    #define MZ_FTELL64 _ftelli64
    #define MZ_FSEEK64 _fseeki64
    #define MZ_FILE_STAT_STRUCT _stat
    #define MZ_FILE_STAT _stat
    #define MZ_FFLUSH fflush
    #define MZ_FREOPEN mz_freopen
    #define MZ_DELETE_FILE remove
  #elif defined(__MINGW32__)
    #ifndef MINIZ_NO_TIME
      #include <sys/utime.h>
    #endif
    #define MZ_FILE FILE
    #define MZ_FOPEN(f, m) fopen(f, m)
    #define MZ_FCLOSE fclose
    #define MZ_FREAD fread
    #define MZ_FWRITE fwrite
    #define MZ_FTELL64 ftello64
    #define MZ_FSEEK64 fseeko64
    #define MZ_FILE_STAT_STRUCT _stat
    #define MZ_FILE_STAT _stat
    #define MZ_FFLUSH fflush
    #define MZ_FREOPEN(f, m, s) freopen(f, m, s)
    #define MZ_DELETE_FILE remove
  #elif defined(__TINYC__)
    #ifndef MINIZ_NO_TIME
      #include <sys/utime.h>
    #endif
    #define MZ_FILE FILE
    #define MZ_FOPEN(f, m) fopen(f, m)
    #define MZ_FCLOSE fclose
    #define MZ_FREAD fread
    #define MZ_FWRITE fwrite
    #define MZ_FTELL64 ftell
    #define MZ_FSEEK64 fseek
    #define MZ_FILE_STAT_STRUCT stat
    #define MZ_FILE_STAT stat
    #define MZ_FFLUSH fflush
    #define MZ_FREOPEN(f, m, s) freopen(f, m, s)
    #define MZ_DELETE_FILE remove
  #elif defined(__GNUC__) && _LARGEFILE64_SOURCE
    #ifndef MINIZ_NO_TIME
      #include <utime.h>
    #endif
    #define MZ_FILE FILE
    #define MZ_FOPEN(f, m) fopen64(f, m)
    #define MZ_FCLOSE fclose
    #define MZ_FREAD fread
    #define MZ_FWRITE fwrite
    #define MZ_FTELL64 ftello64
    #define MZ_FSEEK64 fseeko64
    #define MZ_FILE_STAT_STRUCT stat64
    #define MZ_FILE_STAT stat64
    #define MZ_FFLUSH fflush
    #define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
    #define MZ_DELETE_FILE remove
  #else
    #ifndef MINIZ_NO_TIME
      #include <utime.h>
    #endif
    #define MZ_FILE FILE
    #define MZ_FOPEN(f, m) fopen(f, m)
    #define MZ_FCLOSE fclose
    #define MZ_FREAD fread
    #define MZ_FWRITE fwrite
    #define MZ_FTELL64 ftello
    #define MZ_FSEEK64 fseeko
    #define MZ_FILE_STAT_STRUCT stat
    #define MZ_FILE_STAT stat
    #define MZ_FFLUSH fflush
    #define MZ_FREOPEN(f, m, s) freopen(f, m, s)
    #define MZ_DELETE_FILE remove
  #endif // #ifdef _MSC_VER
#endif // #ifdef MINIZ_NO_STDIO

#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c))

// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff.
enum
{
  // ZIP archive identifiers and record sizes
  MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50,
  MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22,
  // Central directory header record offsets
  MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8,
  MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16,
  MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30,
  MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42,
  // Local directory header offsets
  MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10,
  MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22,
  MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28,
  // End of central directory offsets
  MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8,
  MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20,
};

typedef struct
{
  void *m_p;
  size_t m_size, m_capacity;
  mz_uint m_element_size;
} mz_zip_array;

struct mz_zip_internal_state_tag
{
  mz_zip_array m_central_dir;
  mz_zip_array m_central_dir_offsets;
  mz_zip_array m_sorted_central_dir_offsets;
  MZ_FILE *m_pFile;
  void *m_pMem;
  size_t m_mem_size;
  size_t m_mem_capacity;
};

#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size
#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index]

static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray)
{
  pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p);
  memset(pArray, 0, sizeof(mz_zip_array));
}

static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing)
{
  void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE;
  if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; }
  if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE;
  pArray->m_p = pNew_p; pArray->m_capacity = new_capacity;
  return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing)
{
  if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; }
  return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing)
{
  if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; }
  pArray->m_size = new_size;
  return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n)
{
  return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE);
}

static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n)
{
  size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE;
  memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size);
  return MZ_TRUE;
}

#ifndef MINIZ_NO_TIME
static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date)
{
  struct tm tm;
  memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1;
  tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31;
  tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62;
  return mktime(&tm);
}

static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
{
#ifdef _MSC_VER
  struct tm tm_struct;
  struct tm *tm = &tm_struct;
  errno_t err = localtime_s(tm, &time);
  if (err)
  {
    *pDOS_date = 0; *pDOS_time = 0;
    return;
  }
#else
  struct tm *tm = localtime(&time);
#endif
  *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1));
  *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday);
}
#endif

#ifndef MINIZ_NO_STDIO
static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date)
{
#ifdef MINIZ_NO_TIME
  (void)pFilename; *pDOS_date = *pDOS_time = 0;
#else
  struct MZ_FILE_STAT_STRUCT file_stat;
  // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh.
  if (MZ_FILE_STAT(pFilename, &file_stat) != 0)
    return MZ_FALSE;
  mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date);
#endif // #ifdef MINIZ_NO_TIME
  return MZ_TRUE;
}

#ifndef MINIZ_NO_TIME
static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time)
{
  struct utimbuf t; t.actime = access_time; t.modtime = modified_time;
  return !utime(pFilename, &t);
}
#endif // #ifndef MINIZ_NO_TIME
#endif // #ifndef MINIZ_NO_STDIO

static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags)
{
  (void)flags;
  if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
    return MZ_FALSE;

  if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func;
  if (!pZip->m_pFree) pZip->m_pFree = def_free_func;
  if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func;

  pZip->m_zip_mode = MZ_ZIP_MODE_READING;
  pZip->m_archive_size = 0;
  pZip->m_central_directory_file_ofs = 0;
  pZip->m_total_files = 0;

  if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
    return MZ_FALSE;
  memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
  return MZ_TRUE;
}

static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index)
{
  const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
  const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index));
  mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS);
  mz_uint8 l = 0, r = 0;
  pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
  pE = pL + MZ_MIN(l_len, r_len);
  while (pL < pE)
  {
    if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
      break;
    pL++; pR++;
  }
  return (pL == pE) ? (l_len < r_len) : (l < r);
}

#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END

// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.)
static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip)
{
  mz_zip_internal_state *pState = pZip->m_pState;
  const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
  const mz_zip_array *pCentral_dir = &pState->m_central_dir;
  mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
  const int size = pZip->m_total_files;
  int start = (size - 2) >> 1, end;
  while (start >= 0)
  {
    int child, root = start;
    for ( ; ; )
    {
      if ((child = (root << 1) + 1) >= size)
        break;
      child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])));
      if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
        break;
      MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child;
    }
    start--;
  }

  end = size - 1;
  while (end > 0)
  {
    int child, root = 0;
    MZ_SWAP_UINT32(pIndices[end], pIndices[0]);
    for ( ; ; )
    {
      if ((child = (root << 1) + 1) >= end)
        break;
      child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]));
      if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child]))
        break;
      MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child;
    }
    end--;
  }
}

static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags)
{
  mz_uint cdir_size, num_this_disk, cdir_disk_index;
  mz_uint64 cdir_ofs;
  mz_int64 cur_file_ofs;
  const mz_uint8 *p;
  mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32;
  mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0);
  // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there.
  if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
    return MZ_FALSE;
  // Find the end of central directory record by scanning the file from the end towards the beginning.
  cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0);
  for ( ; ; )
  {
    int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs);
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n)
      return MZ_FALSE;
    for (i = n - 4; i >= 0; --i)
      if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG)
        break;
    if (i >= 0)
    {
      cur_file_ofs += i;
      break;
    }
    if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)))
      return MZ_FALSE;
    cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0);
  }
  // Read and verify the end of central directory record.
  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
    return MZ_FALSE;
  if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) ||
      ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS)))
    return MZ_FALSE;

  num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS);
  cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS);
  if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1)))
    return MZ_FALSE;

  if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)
    return MZ_FALSE;

  cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS);
  if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size)
    return MZ_FALSE;

  pZip->m_central_directory_file_ofs = cdir_ofs;

  if (pZip->m_total_files)
  {
     mz_uint i, n;

    // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices.
    if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) ||
        (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE)))
      return MZ_FALSE;

    if (sort_central_dir)
    {
      if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE))
        return MZ_FALSE;
    }

    if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size)
      return MZ_FALSE;

    // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported).
    p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p;
    for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i)
    {
      mz_uint total_header_size, comp_size, decomp_size, disk_index;
      if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG))
        return MZ_FALSE;
      MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p);
      if (sort_central_dir)
        MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i;
      comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
      decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
      if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF))
        return MZ_FALSE;
      disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS);
      if ((disk_index != num_this_disk) && (disk_index != 1))
        return MZ_FALSE;
      if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size)
        return MZ_FALSE;
      if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n)
        return MZ_FALSE;
      n -= total_header_size; p += total_header_size;
    }
  }

  if (sort_central_dir)
    mz_zip_reader_sort_central_dir_offsets_by_filename(pZip);

  return MZ_TRUE;
}

mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags)
{
  if ((!pZip) || (!pZip->m_pRead))
    return MZ_FALSE;
  if (!mz_zip_reader_init_internal(pZip, flags))
    return MZ_FALSE;
  pZip->m_archive_size = size;
  if (!mz_zip_reader_read_central_dir(pZip, flags))
  {
    mz_zip_reader_end(pZip);
    return MZ_FALSE;
  }
  return MZ_TRUE;
}

static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
  size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n);
  memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s);
  return s;
}

mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags)
{
  if (!mz_zip_reader_init_internal(pZip, flags))
    return MZ_FALSE;
  pZip->m_archive_size = size;
  pZip->m_pRead = mz_zip_mem_read_func;
  pZip->m_pIO_opaque = pZip;
#ifdef __cplusplus
  pZip->m_pState->m_pMem = const_cast<void *>(pMem);
#else
  pZip->m_pState->m_pMem = (void *)pMem;
#endif
  pZip->m_pState->m_mem_size = size;
  if (!mz_zip_reader_read_central_dir(pZip, flags))
  {
    mz_zip_reader_end(pZip);
    return MZ_FALSE;
  }
  return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n)
{
  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
  mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
  if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
    return 0;
  return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile);
}

mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags)
{
  mz_uint64 file_size;
  MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb");
  if (!pFile)
    return MZ_FALSE;
  if (MZ_FSEEK64(pFile, 0, SEEK_END))
  {
    MZ_FCLOSE(pFile);
    return MZ_FALSE;
  }
  file_size = MZ_FTELL64(pFile);
  if (!mz_zip_reader_init_internal(pZip, flags))
  {
    MZ_FCLOSE(pFile);
    return MZ_FALSE;
  }
  pZip->m_pRead = mz_zip_file_read_func;
  pZip->m_pIO_opaque = pZip;
  pZip->m_pState->m_pFile = pFile;
  pZip->m_archive_size = file_size;
  if (!mz_zip_reader_read_central_dir(pZip, flags))
  {
    mz_zip_reader_end(pZip);
    return MZ_FALSE;
  }
  return MZ_TRUE;
}
#endif // #ifndef MINIZ_NO_STDIO

mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip)
{
  return pZip ? pZip->m_total_files : 0;
}

static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index)
{
  if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
    return NULL;
  return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
}

mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index)
{
  mz_uint m_bit_flag;
  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
  if (!p)
    return MZ_FALSE;
  m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
  return (m_bit_flag & 1);
}

mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index)
{
  mz_uint filename_len, external_attr;
  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
  if (!p)
    return MZ_FALSE;

  // First see if the filename ends with a '/' character.
  filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
  if (filename_len)
  {
    if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/')
      return MZ_TRUE;
  }

  // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct.
  // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field.
  // FIXME: Remove this check? Is it necessary - we already check the filename.
  external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
  if ((external_attr & 0x10) != 0)
    return MZ_TRUE;

  return MZ_FALSE;
}

mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
{
  mz_uint n;
  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
  if ((!p) || (!pStat))
    return MZ_FALSE;

  // Unpack the central directory record.
  pStat->m_file_index = file_index;
  pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index);
  pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS);
  pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS);
  pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS);
  pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS);
#ifndef MINIZ_NO_TIME
  pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS));
#endif
  pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS);
  pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
  pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);
  pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS);
  pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS);
  pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS);

  // Copy as much of the filename and comment as possible.
  n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1);
  memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0';

  n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1);
  pStat->m_comment_size = n;
  memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0';

  return MZ_TRUE;
}

mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size)
{
  mz_uint n;
  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
  if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; }
  n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
  if (filename_buf_size)
  {
    n = MZ_MIN(n, filename_buf_size - 1);
    memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n);
    pFilename[n] = '\0';
  }
  return n + 1;
}

static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags)
{
  mz_uint i;
  if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE)
    return 0 == memcmp(pA, pB, len);
  for (i = 0; i < len; ++i)
    if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i]))
      return MZ_FALSE;
  return MZ_TRUE;
}

static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len)
{
  const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE;
  mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS);
  mz_uint8 l = 0, r = 0;
  pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
  pE = pL + MZ_MIN(l_len, r_len);
  while (pL < pE)
  {
    if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR)))
      break;
    pL++; pR++;
  }
  return (pL == pE) ? (int)(l_len - r_len) : (l - r);
}

static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename)
{
  mz_zip_internal_state *pState = pZip->m_pState;
  const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets;
  const mz_zip_array *pCentral_dir = &pState->m_central_dir;
  mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0);
  const int size = pZip->m_total_files;
  const mz_uint filename_len = (mz_uint)strlen(pFilename);
  int l = 0, h = size - 1;
  while (l <= h)
  {
    int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len);
    if (!comp)
      return file_index;
    else if (comp < 0)
      l = m + 1;
    else
      h = m - 1;
  }
  return -1;
}

int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags)
{
  mz_uint file_index; size_t name_len, comment_len;
  if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
    return -1;
  if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size))
    return mz_zip_reader_locate_file_binary_search(pZip, pName);
  name_len = strlen(pName); if (name_len > 0xFFFF) return -1;
  comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1;
  for (file_index = 0; file_index < pZip->m_total_files; file_index++)
  {
    const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index));
    mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS);
    const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE;
    if (filename_len < name_len)
      continue;
    if (comment_len)
    {
      mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS);
      const char *pFile_comment = pFilename + filename_len + file_extra_len;
      if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags)))
        continue;
    }
    if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len))
    {
      int ofs = filename_len - 1;
      do
      {
        if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':'))
          break;
      } while (--ofs >= 0);
      ofs++;
      pFilename += ofs; filename_len -= ofs;
    }
    if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags)))
      return file_index;
  }
  return -1;
}

mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
{
  int status = TINFL_STATUS_DONE;
  mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail;
  mz_zip_archive_file_stat file_stat;
  void *pRead_buf;
  mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
  tinfl_decompressor inflator;

  if ((buf_size) && (!pBuf))
    return MZ_FALSE;

  if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
    return MZ_FALSE;

  // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes)
  if (!file_stat.m_comp_size)
    return MZ_TRUE;

  // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers).
  // I'm torn how to handle this case - should it fail instead?
  if (mz_zip_reader_is_file_a_directory(pZip, file_index))
    return MZ_TRUE;

  // Encryption and patch files are not supported.
  if (file_stat.m_bit_flag & (1 | 32))
    return MZ_FALSE;

  // This function only supports stored and deflate.
  if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
    return MZ_FALSE;

  // Ensure supplied output buffer is large enough.
  needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size;
  if (buf_size < needed_size)
    return MZ_FALSE;

  // Read and parse the local directory entry.
  cur_file_ofs = file_stat.m_local_header_ofs;
  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
    return MZ_FALSE;
  if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
    return MZ_FALSE;

  cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
  if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
    return MZ_FALSE;

  if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
  {
    // The file is stored or the caller has requested the compressed data.
    if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size)
      return MZ_FALSE;
    return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32);
  }

  // Decompress the file either directly from memory or from a file input buffer.
  tinfl_init(&inflator);

  if (pZip->m_pState->m_pMem)
  {
    // Read directly from the archive in memory.
    pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
    read_buf_size = read_buf_avail = file_stat.m_comp_size;
    comp_remaining = 0;
  }
  else if (pUser_read_buf)
  {
    // Use a user provided read buffer.
    if (!user_read_buf_size)
      return MZ_FALSE;
    pRead_buf = (mz_uint8 *)pUser_read_buf;
    read_buf_size = user_read_buf_size;
    read_buf_avail = 0;
    comp_remaining = file_stat.m_comp_size;
  }
  else
  {
    // Temporarily allocate a read buffer.
    read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE);
#ifdef _MSC_VER
    if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
#else
    if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF))
#endif
      return MZ_FALSE;
    if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
      return MZ_FALSE;
    read_buf_avail = 0;
    comp_remaining = file_stat.m_comp_size;
  }

  do
  {
    size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs);
    if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
    {
      read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
      if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
      {
        status = TINFL_STATUS_FAILED;
        break;
      }
      cur_file_ofs += read_buf_avail;
      comp_remaining -= read_buf_avail;
      read_buf_ofs = 0;
    }
    in_buf_size = (size_t)read_buf_avail;
    status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0));
    read_buf_avail -= in_buf_size;
    read_buf_ofs += in_buf_size;
    out_buf_ofs += out_buf_size;
  } while (status == TINFL_STATUS_NEEDS_MORE_INPUT);

  if (status == TINFL_STATUS_DONE)
  {
    // Make sure the entire file was decompressed, and check its CRC.
    if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32))
      status = TINFL_STATUS_FAILED;
  }

  if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf))
    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);

  return status == TINFL_STATUS_DONE;
}

mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size)
{
  int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
  if (file_index < 0)
    return MZ_FALSE;
  return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size);
}

mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags)
{
  return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0);
}

mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags)
{
  return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0);
}

void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags)
{
  mz_uint64 comp_size, uncomp_size, alloc_size;
  const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index);
  void *pBuf;

  if (pSize)
    *pSize = 0;
  if (!p)
    return NULL;

  comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);
  uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS);

  alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size;
#ifdef _MSC_VER
  if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
#else
  if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF))
#endif
    return NULL;
  if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size)))
    return NULL;

  if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags))
  {
    pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
    return NULL;
  }

  if (pSize) *pSize = (size_t)alloc_size;
  return pBuf;
}

void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags)
{
  int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
  if (file_index < 0)
  {
    if (pSize) *pSize = 0;
    return MZ_FALSE;
  }
  return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags);
}

mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
{
  int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT;
  mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs;
  mz_zip_archive_file_stat file_stat;
  void *pRead_buf = NULL; void *pWrite_buf = NULL;
  mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;

  if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
    return MZ_FALSE;

  // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes)
  if (!file_stat.m_comp_size)
    return MZ_TRUE;

  // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers).
  // I'm torn how to handle this case - should it fail instead?
  if (mz_zip_reader_is_file_a_directory(pZip, file_index))
    return MZ_TRUE;

  // Encryption and patch files are not supported.
  if (file_stat.m_bit_flag & (1 | 32))
    return MZ_FALSE;

  // This function only supports stored and deflate.
  if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED))
    return MZ_FALSE;

  // Read and parse the local directory entry.
  cur_file_ofs = file_stat.m_local_header_ofs;
  if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
    return MZ_FALSE;
  if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
    return MZ_FALSE;

  cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
  if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size)
    return MZ_FALSE;

  // Decompress the file either directly from memory or from a file input buffer.
  if (pZip->m_pState->m_pMem)
  {
    pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs;
    read_buf_size = read_buf_avail = file_stat.m_comp_size;
    comp_remaining = 0;
  }
  else
  {
    read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE);
    if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size)))
      return MZ_FALSE;
    read_buf_avail = 0;
    comp_remaining = file_stat.m_comp_size;
  }

  if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method))
  {
    // The file is stored or the caller has requested the compressed data.
    if (pZip->m_pState->m_pMem)
    {
#ifdef _MSC_VER
      if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF))
#else
      if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF))
#endif
        return MZ_FALSE;
      if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size)
        status = TINFL_STATUS_FAILED;
      else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
        file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size);
      cur_file_ofs += file_stat.m_comp_size;
      out_buf_ofs += file_stat.m_comp_size;
      comp_remaining = 0;
    }
    else
    {
      while (comp_remaining)
      {
        read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
        if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
        {
          status = TINFL_STATUS_FAILED;
          break;
        }

        if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
          file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail);

        if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
        {
          status = TINFL_STATUS_FAILED;
          break;
        }
        cur_file_ofs += read_buf_avail;
        out_buf_ofs += read_buf_avail;
        comp_remaining -= read_buf_avail;
      }
    }
  }
  else
  {
    tinfl_decompressor inflator;
    tinfl_init(&inflator);

    if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE)))
      status = TINFL_STATUS_FAILED;
    else
    {
      do
      {
        mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
        size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1));
        if ((!read_buf_avail) && (!pZip->m_pState->m_pMem))
        {
          read_buf_avail = MZ_MIN(read_buf_size, comp_remaining);
          if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail)
          {
            status = TINFL_STATUS_FAILED;
            break;
          }
          cur_file_ofs += read_buf_avail;
          comp_remaining -= read_buf_avail;
          read_buf_ofs = 0;
        }

        in_buf_size = (size_t)read_buf_avail;
        status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0);
        read_buf_avail -= in_buf_size;
        read_buf_ofs += in_buf_size;

        if (out_buf_size)
        {
          if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size)
          {
            status = TINFL_STATUS_FAILED;
            break;
          }
          file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size);
          if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size)
          {
            status = TINFL_STATUS_FAILED;
            break;
          }
        }
      } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT));
    }
  }

  if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
  {
    // Make sure the entire file was decompressed, and check its CRC.
    if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32))
      status = TINFL_STATUS_FAILED;
  }

  if (!pZip->m_pState->m_pMem)
    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
  if (pWrite_buf)
    pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf);

  return status == TINFL_STATUS_DONE;
}

mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags)
{
  int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags);
  if (file_index < 0)
    return MZ_FALSE;
  return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags);
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n)
{
  (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque);
}

mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags)
{
  mz_bool status;
  mz_zip_archive_file_stat file_stat;
  MZ_FILE *pFile;
  if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat))
    return MZ_FALSE;
  pFile = MZ_FOPEN(pDst_filename, "wb");
  if (!pFile)
    return MZ_FALSE;
  status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags);
  if (MZ_FCLOSE(pFile) == EOF)
    return MZ_FALSE;
#ifndef MINIZ_NO_TIME
  if (status)
    mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time);
#endif
  return status;
}
#endif // #ifndef MINIZ_NO_STDIO

mz_bool mz_zip_reader_end(mz_zip_archive *pZip)
{
  if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
    return MZ_FALSE;

  if (pZip->m_pState)
  {
    mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL;
    mz_zip_array_clear(pZip, &pState->m_central_dir);
    mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
    mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);

#ifndef MINIZ_NO_STDIO
    if (pState->m_pFile)
    {
      MZ_FCLOSE(pState->m_pFile);
      pState->m_pFile = NULL;
    }
#endif // #ifndef MINIZ_NO_STDIO

    pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
  }
  pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;

  return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags)
{
  int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags);
  if (file_index < 0)
    return MZ_FALSE;
  return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags);
}
#endif

// ------------------- .ZIP archive writing

#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); }
static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); }
#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v))
#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v))

mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size)
{
  if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID))
    return MZ_FALSE;

  if (pZip->m_file_offset_alignment)
  {
    // Ensure user specified file offset alignment is a power of 2.
    if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1))
      return MZ_FALSE;
  }

  if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func;
  if (!pZip->m_pFree) pZip->m_pFree = def_free_func;
  if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func;

  pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;
  pZip->m_archive_size = existing_size;
  pZip->m_central_directory_file_ofs = 0;
  pZip->m_total_files = 0;

  if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state))))
    return MZ_FALSE;
  memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state));
  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8));
  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32));
  MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32));
  return MZ_TRUE;
}

static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
  mz_zip_internal_state *pState = pZip->m_pState;
  mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size);
#ifdef _MSC_VER
  if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)))
#else
  if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF)))
#endif
    return 0;
  if (new_size > pState->m_mem_capacity)
  {
    void *pNew_block;
    size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2;
    if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity)))
      return 0;
    pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity;
  }
  memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n);
  pState->m_mem_size = (size_t)new_size;
  return n;
}

mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size)
{
  pZip->m_pWrite = mz_zip_heap_write_func;
  pZip->m_pIO_opaque = pZip;
  if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning))
    return MZ_FALSE;
  if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning)))
  {
    if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size)))
    {
      mz_zip_writer_end(pZip);
      return MZ_FALSE;
    }
    pZip->m_pState->m_mem_capacity = initial_allocation_size;
  }
  return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n)
{
  mz_zip_archive *pZip = (mz_zip_archive *)pOpaque;
  mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile);
  if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET))))
    return 0;
  return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile);
}

mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning)
{
  MZ_FILE *pFile;
  pZip->m_pWrite = mz_zip_file_write_func;
  pZip->m_pIO_opaque = pZip;
  if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning))
    return MZ_FALSE;
  if (NULL == (pFile = MZ_FOPEN(pFilename, "wb")))
  {
    mz_zip_writer_end(pZip);
    return MZ_FALSE;
  }
  pZip->m_pState->m_pFile = pFile;
  if (size_to_reserve_at_beginning)
  {
    mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf);
    do
    {
      size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning);
      if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n)
      {
        mz_zip_writer_end(pZip);
        return MZ_FALSE;
      }
      cur_ofs += n; size_to_reserve_at_beginning -= n;
    } while (size_to_reserve_at_beginning);
  }
  return MZ_TRUE;
}
#endif // #ifndef MINIZ_NO_STDIO

mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename)
{
  mz_zip_internal_state *pState;
  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING))
    return MZ_FALSE;
  // No sense in trying to write to an archive that's already at the support max size
  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF))
    return MZ_FALSE;

  pState = pZip->m_pState;

  if (pState->m_pFile)
  {
#ifdef MINIZ_NO_STDIO
    pFilename; return MZ_FALSE;
#else
    // Archive is being read from stdio - try to reopen as writable.
    if (pZip->m_pIO_opaque != pZip)
      return MZ_FALSE;
    if (!pFilename)
      return MZ_FALSE;
    pZip->m_pWrite = mz_zip_file_write_func;
    if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile)))
    {
      // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it.
      mz_zip_reader_end(pZip);
      return MZ_FALSE;
    }
#endif // #ifdef MINIZ_NO_STDIO
  }
  else if (pState->m_pMem)
  {
    // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback.
    if (pZip->m_pIO_opaque != pZip)
      return MZ_FALSE;
    pState->m_mem_capacity = pState->m_mem_size;
    pZip->m_pWrite = mz_zip_heap_write_func;
  }
  // Archive is being read via a user provided read function - make sure the user has specified a write function too.
  else if (!pZip->m_pWrite)
    return MZ_FALSE;

  // Start writing new files at the archive's current central directory location.
  pZip->m_archive_size = pZip->m_central_directory_file_ofs;
  pZip->m_zip_mode = MZ_ZIP_MODE_WRITING;
  pZip->m_central_directory_file_ofs = 0;

  return MZ_TRUE;
}

mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags)
{
  return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0);
}

typedef struct
{
  mz_zip_archive *m_pZip;
  mz_uint64 m_cur_archive_file_ofs;
  mz_uint64 m_comp_size;
} mz_zip_writer_add_state;

static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser)
{
  mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser;
  if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len)
    return MZ_FALSE;
  pState->m_cur_archive_file_ofs += len;
  pState->m_comp_size += len;
  return MZ_TRUE;
}

static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date)
{
  (void)pZip;
  memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE);
  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date);
  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32);
  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size);
  MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size);
  MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size);
  return MZ_TRUE;
}

static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
{
  (void)pZip;
  memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date);
  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32);
  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size);
  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size);
  MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size);
  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes);
  MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs);
  return MZ_TRUE;
}

static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes)
{
  mz_zip_internal_state *pState = pZip->m_pState;
  mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size;
  size_t orig_central_dir_size = pState->m_central_dir.m_size;
  mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];

  // No zip64 support yet
  if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF))
    return MZ_FALSE;

  if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes))
    return MZ_FALSE;

  if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) ||
      (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) ||
      (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) ||
      (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) ||
      (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &central_dir_ofs, 1)))
  {
    // Try to push the central directory array back into its original state.
    mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
    return MZ_FALSE;
  }

  return MZ_TRUE;
}

static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name)
{
  // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes.
  if (*pArchive_name == '/')
    return MZ_FALSE;
  while (*pArchive_name)
  {
    if ((*pArchive_name == '\\') || (*pArchive_name == ':'))
      return MZ_FALSE;
    pArchive_name++;
  }
  return MZ_TRUE;
}

static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip)
{
  mz_uint32 n;
  if (!pZip->m_file_offset_alignment)
    return 0;
  n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1));
  return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1);
}

static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n)
{
  char buf[4096];
  memset(buf, 0, MZ_MIN(sizeof(buf), n));
  while (n)
  {
    mz_uint32 s = MZ_MIN(sizeof(buf), n);
    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s)
      return MZ_FALSE;
    cur_file_ofs += s; n -= s;
  }
  return MZ_TRUE;
}

mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32)
{
  mz_uint16 method = 0, dos_time = 0, dos_date = 0;
  mz_uint level, ext_attributes = 0, num_alignment_padding_bytes;
  mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0;
  size_t archive_name_size;
  mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
  tdefl_compressor *pComp = NULL;
  mz_bool store_data_uncompressed;
  mz_zip_internal_state *pState;

  if ((int)level_and_flags < 0)
    level_and_flags = MZ_DEFAULT_LEVEL;
  level = level_and_flags & 0xF;
  store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));

  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION))
    return MZ_FALSE;

  pState = pZip->m_pState;

  if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size))
    return MZ_FALSE;
  // No zip64 support yet
  if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF))
    return MZ_FALSE;
  if (!mz_zip_writer_validate_archive_name(pArchive_name))
    return MZ_FALSE;

#ifndef MINIZ_NO_TIME
  {
    time_t cur_time; time(&cur_time);
    mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date);
  }
#endif // #ifndef MINIZ_NO_TIME

  archive_name_size = strlen(pArchive_name);
  if (archive_name_size > 0xFFFF)
    return MZ_FALSE;

  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

  // no zip64 support yet
  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF))
    return MZ_FALSE;

  if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/'))
  {
    // Set DOS Subdirectory attribute bit.
    ext_attributes |= 0x10;
    // Subdirectories cannot contain data.
    if ((buf_size) || (uncomp_size))
      return MZ_FALSE;
  }

  // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.)
  if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1)))
    return MZ_FALSE;

  if ((!store_data_uncompressed) && (buf_size))
  {
    if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor))))
      return MZ_FALSE;
  }

  if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header)))
  {
    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
    return MZ_FALSE;
  }
  local_dir_header_ofs += num_alignment_padding_bytes;
  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }
  cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header);

  MZ_CLEAR_OBJ(local_dir_header);
  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
  {
    pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
    return MZ_FALSE;
  }
  cur_archive_file_ofs += archive_name_size;

  if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA))
  {
    uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size);
    uncomp_size = buf_size;
    if (uncomp_size <= 3)
    {
      level = 0;
      store_data_uncompressed = MZ_TRUE;
    }
  }

  if (store_data_uncompressed)
  {
    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size)
    {
      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
      return MZ_FALSE;
    }

    cur_archive_file_ofs += buf_size;
    comp_size = buf_size;

    if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
      method = MZ_DEFLATED;
  }
  else if (buf_size)
  {
    mz_zip_writer_add_state state;

    state.m_pZip = pZip;
    state.m_cur_archive_file_ofs = cur_archive_file_ofs;
    state.m_comp_size = 0;

    if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) ||
        (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE))
    {
      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
      return MZ_FALSE;
    }

    comp_size = state.m_comp_size;
    cur_archive_file_ofs = state.m_cur_archive_file_ofs;

    method = MZ_DEFLATED;
  }

  pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
  pComp = NULL;

  // no zip64 support yet
  if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF))
    return MZ_FALSE;

  if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date))
    return MZ_FALSE;

  if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
    return MZ_FALSE;

  if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes))
    return MZ_FALSE;

  pZip->m_total_files++;
  pZip->m_archive_size = cur_archive_file_ofs;

  return MZ_TRUE;
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
{
  mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes;
  mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0;
  mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0;
  size_t archive_name_size;
  mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE];
  MZ_FILE *pSrc_file = NULL;

  if ((int)level_and_flags < 0)
    level_and_flags = MZ_DEFAULT_LEVEL;
  level = level_and_flags & 0xF;

  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION))
    return MZ_FALSE;
  if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)
    return MZ_FALSE;
  if (!mz_zip_writer_validate_archive_name(pArchive_name))
    return MZ_FALSE;

  archive_name_size = strlen(pArchive_name);
  if (archive_name_size > 0xFFFF)
    return MZ_FALSE;

  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

  // no zip64 support yet
  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF))
    return MZ_FALSE;

  if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date))
    return MZ_FALSE;
    
  pSrc_file = MZ_FOPEN(pSrc_filename, "rb");
  if (!pSrc_file)
    return MZ_FALSE;
  MZ_FSEEK64(pSrc_file, 0, SEEK_END);
  uncomp_size = MZ_FTELL64(pSrc_file);
  MZ_FSEEK64(pSrc_file, 0, SEEK_SET);

  if (uncomp_size > 0xFFFFFFFF)
  {
    // No zip64 support yet
    MZ_FCLOSE(pSrc_file);
    return MZ_FALSE;
  }
  if (uncomp_size <= 3)
    level = 0;

  if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header)))
  {
    MZ_FCLOSE(pSrc_file);
    return MZ_FALSE;
  }
  local_dir_header_ofs += num_alignment_padding_bytes;
  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }
  cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header);

  MZ_CLEAR_OBJ(local_dir_header);
  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size)
  {
    MZ_FCLOSE(pSrc_file);
    return MZ_FALSE;
  }
  cur_archive_file_ofs += archive_name_size;

  if (uncomp_size)
  {
    mz_uint64 uncomp_remaining = uncomp_size;
    void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE);
    if (!pRead_buf)
    {
      MZ_FCLOSE(pSrc_file);
      return MZ_FALSE;
    }

    if (!level)
    {
      while (uncomp_remaining)
      {
        mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining);
        if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n))
        {
          pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
          MZ_FCLOSE(pSrc_file);
          return MZ_FALSE;
        }
        uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n);
        uncomp_remaining -= n;
        cur_archive_file_ofs += n;
      }
      comp_size = uncomp_size;
    }
    else
    {
      mz_bool result = MZ_FALSE;
      mz_zip_writer_add_state state;
      tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor));
      if (!pComp)
      {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
        MZ_FCLOSE(pSrc_file);
        return MZ_FALSE;
      }

      state.m_pZip = pZip;
      state.m_cur_archive_file_ofs = cur_archive_file_ofs;
      state.m_comp_size = 0;

      if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY)
      {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
        MZ_FCLOSE(pSrc_file);
        return MZ_FALSE;
      }

      for ( ; ; )
      {
        size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE);
        tdefl_status status;

        if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size)
          break;

        uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size);
        uncomp_remaining -= in_buf_size;

        status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH);
        if (status == TDEFL_STATUS_DONE)
        {
          result = MZ_TRUE;
          break;
        }
        else if (status != TDEFL_STATUS_OKAY)
          break;
      }

      pZip->m_pFree(pZip->m_pAlloc_opaque, pComp);

      if (!result)
      {
        pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
        MZ_FCLOSE(pSrc_file);
        return MZ_FALSE;
      }

      comp_size = state.m_comp_size;
      cur_archive_file_ofs = state.m_cur_archive_file_ofs;

      method = MZ_DEFLATED;
    }

    pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf);
  }

  MZ_FCLOSE(pSrc_file); pSrc_file = NULL;

  // no zip64 support yet
  if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF))
    return MZ_FALSE;

  if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date))
    return MZ_FALSE;

  if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header))
    return MZ_FALSE;

  if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes))
    return MZ_FALSE;

  pZip->m_total_files++;
  pZip->m_archive_size = cur_archive_file_ofs;

  return MZ_TRUE;
}
#endif // #ifndef MINIZ_NO_STDIO

mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index)
{
  mz_uint n, bit_flags, num_alignment_padding_bytes;
  mz_uint64 comp_bytes_remaining, local_dir_header_ofs;
  mz_uint64 cur_src_file_ofs, cur_dst_file_ofs;
  mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32;
  mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE];
  size_t orig_central_dir_size;
  mz_zip_internal_state *pState;
  void *pBuf; const mz_uint8 *pSrc_central_header;

  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
    return MZ_FALSE;
  if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index)))
    return MZ_FALSE;
  pState = pZip->m_pState;

  num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip);

  // no zip64 support yet
  if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF))
    return MZ_FALSE;

  cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS);
  cur_dst_file_ofs = pZip->m_archive_size;

  if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
    return MZ_FALSE;
  if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG)
    return MZ_FALSE;
  cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;

  if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes))
    return MZ_FALSE;
  cur_dst_file_ofs += num_alignment_padding_bytes;
  local_dir_header_ofs = cur_dst_file_ofs;
  if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); }

  if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE)
    return MZ_FALSE;
  cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE;

  n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS);
  comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS);

  if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining)))))
    return MZ_FALSE;

  while (comp_bytes_remaining)
  {
    n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining);
    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n)
    {
      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
      return MZ_FALSE;
    }
    cur_src_file_ofs += n;

    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
    {
      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
      return MZ_FALSE;
    }
    cur_dst_file_ofs += n;

    comp_bytes_remaining -= n;
  }

  bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS);
  if (bit_flags & 8)
  {
    // Copy data descriptor
    if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4)
    {
      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
      return MZ_FALSE;
    }

    n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3);
    if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n)
    {
      pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);
      return MZ_FALSE;
    }

    cur_src_file_ofs += n;
    cur_dst_file_ofs += n;
  }
  pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf);

  // no zip64 support yet
  if (cur_dst_file_ofs > 0xFFFFFFFF)
    return MZ_FALSE;

  orig_central_dir_size = pState->m_central_dir.m_size;

  memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE);
  MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs);
  if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE))
    return MZ_FALSE;

  n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS);
  if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n))
  {
    mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
    return MZ_FALSE;
  }

  if (pState->m_central_dir.m_size > 0xFFFFFFFF)
    return MZ_FALSE;
  n = (mz_uint32)orig_central_dir_size;
  if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1))
  {
    mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE);
    return MZ_FALSE;
  }

  pZip->m_total_files++;
  pZip->m_archive_size = cur_dst_file_ofs;

  return MZ_TRUE;
}

mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip)
{
  mz_zip_internal_state *pState;
  mz_uint64 central_dir_ofs, central_dir_size;
  mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE];

  if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING))
    return MZ_FALSE;

  pState = pZip->m_pState;

  // no zip64 support yet
  if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF))
    return MZ_FALSE;

  central_dir_ofs = 0;
  central_dir_size = 0;
  if (pZip->m_total_files)
  {
    // Write central directory
    central_dir_ofs = pZip->m_archive_size;
    central_dir_size = pState->m_central_dir.m_size;
    pZip->m_central_directory_file_ofs = central_dir_ofs;
    if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size)
      return MZ_FALSE;
    pZip->m_archive_size += central_dir_size;
  }

  // Write end of central directory record
  MZ_CLEAR_OBJ(hdr);
  MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG);
  MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files);
  MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files);
  MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size);
  MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs);

  if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr))
    return MZ_FALSE;
#ifndef MINIZ_NO_STDIO
  if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF))
    return MZ_FALSE;
#endif // #ifndef MINIZ_NO_STDIO

  pZip->m_archive_size += sizeof(hdr);

  pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED;
  return MZ_TRUE;
}

mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize)
{
  if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize))
    return MZ_FALSE;
  if (pZip->m_pWrite != mz_zip_heap_write_func)
    return MZ_FALSE;
  if (!mz_zip_writer_finalize_archive(pZip))
    return MZ_FALSE;

  *pBuf = pZip->m_pState->m_pMem;
  *pSize = pZip->m_pState->m_mem_size;
  pZip->m_pState->m_pMem = NULL;
  pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0;
  return MZ_TRUE;
}

mz_bool mz_zip_writer_end(mz_zip_archive *pZip)
{
  mz_zip_internal_state *pState;
  mz_bool status = MZ_TRUE;
  if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED)))
    return MZ_FALSE;

  pState = pZip->m_pState;
  pZip->m_pState = NULL;
  mz_zip_array_clear(pZip, &pState->m_central_dir);
  mz_zip_array_clear(pZip, &pState->m_central_dir_offsets);
  mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets);

#ifndef MINIZ_NO_STDIO
  if (pState->m_pFile)
  {
    MZ_FCLOSE(pState->m_pFile);
    pState->m_pFile = NULL;
  }
#endif // #ifndef MINIZ_NO_STDIO

  if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem))
  {
    pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem);
    pState->m_pMem = NULL;
  }

  pZip->m_pFree(pZip->m_pAlloc_opaque, pState);
  pZip->m_zip_mode = MZ_ZIP_MODE_INVALID;
  return status;
}

#ifndef MINIZ_NO_STDIO
mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags)
{
  mz_bool status, created_new_archive = MZ_FALSE;
  mz_zip_archive zip_archive;
  struct MZ_FILE_STAT_STRUCT file_stat;
  MZ_CLEAR_OBJ(zip_archive);
  if ((int)level_and_flags < 0)
     level_and_flags = MZ_DEFAULT_LEVEL;
  if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION))
    return MZ_FALSE;
  if (!mz_zip_writer_validate_archive_name(pArchive_name))
    return MZ_FALSE;
  if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0)
  {
    // Create a new archive.
    if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0))
      return MZ_FALSE;
    created_new_archive = MZ_TRUE;
  }
  else
  {
    // Append to an existing archive.
    if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
      return MZ_FALSE;
    if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename))
    {
      mz_zip_reader_end(&zip_archive);
      return MZ_FALSE;
    }
  }
  status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0);
  // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.)
  if (!mz_zip_writer_finalize_archive(&zip_archive))
    status = MZ_FALSE;
  if (!mz_zip_writer_end(&zip_archive))
    status = MZ_FALSE;
  if ((!status) && (created_new_archive))
  {
    // It's a new archive and something went wrong, so just delete it.
    int ignoredStatus = MZ_DELETE_FILE(pZip_filename);
    (void)ignoredStatus;
  }
  return status;
}

void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags)
{
  int file_index;
  mz_zip_archive zip_archive;
  void *p = NULL;

  if (pSize)
    *pSize = 0;

  if ((!pZip_filename) || (!pArchive_name))
    return NULL;

  MZ_CLEAR_OBJ(zip_archive);
  if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY))
    return NULL;

  if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0)
    p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags);

  mz_zip_reader_end(&zip_archive);
  return p;
}

#endif // #ifndef MINIZ_NO_STDIO

#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS

#endif // #ifndef MINIZ_NO_ARCHIVE_APIS

#ifdef __cplusplus
}
#endif

#endif // MINIZ_HEADER_FILE_ONLY

/*
  This is free and unencumbered software released into the public domain.

  Anyone is free to copy, modify, publish, use, compile, sell, or
  distribute this software, either in source code form or as a compiled
  binary, for any purpose, commercial or non-commercial, and by any
  means.

  In jurisdictions that recognize copyright laws, the author or authors
  of this software dedicate any and all copyright interest in the
  software to the public domain. We make this dedication for the benefit
  of the public at large and to the detriment of our heirs and
  successors. We intend this dedication to be an overt act of
  relinquishment in perpetuity of all present and future rights to this
  software under copyright law.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.

  For more information, please refer to <http://unlicense.org/>
*/
#endif //JSI__MINIZ 
#if JSI__READLINE==1
/* linenoise.c -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * You can find the latest source code at:
 *
 *   http://github.com/antirez/linenoise
 *
 * Does a number of crazy assumptions that happen to be true in 99.9999% of
 * the 2010 UNIX computers around.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ------------------------------------------------------------------------
 *
 * References:
 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
 *
 * Todo list:
 * - Filter bogus Ctrl+<char> combinations.
 * - Win32 support
 *
 * Bloat:
 * - History search like Ctrl+r in readline?
 *
 * List of escape sequences used by this program, we do everything just
 * with three sequences. In order to be so cheap we may have some
 * flickering effect with some slow terminal, but the lesser sequences
 * the more compatible.
 *
 * EL (Erase Line)
 *    Sequence: ESC [ n K
 *    Effect: if n is 0 or missing, clear from cursor to end of line
 *    Effect: if n is 1, clear from beginning of line to cursor
 *    Effect: if n is 2, clear entire line
 *
 * CUF (CUrsor Forward)
 *    Sequence: ESC [ n C
 *    Effect: moves cursor forward n chars
 *
 * CUB (CUrsor Backward)
 *    Sequence: ESC [ n D
 *    Effect: moves cursor backward n chars
 *
 * The following is used to get the terminal width if getting
 * the width with the TIOCGWINSZ ioctl fails
 *
 * DSR (Device Status Report)
 *    Sequence: ESC [ 6 n
 *    Effect: reports the current cusor position as ESC [ n ; m R
 *            where n is the row and m is the column
 *
 * When multi line mode is enabled, we also use an additional escape
 * sequence. However multi line editing is disabled by default.
 *
 * CUU (Cursor Up)
 *    Sequence: ESC [ n A
 *    Effect: moves cursor up of n chars.
 *
 * CUD (Cursor Down)
 *    Sequence: ESC [ n B
 *    Effect: moves cursor down of n chars.
 *
 * When linenoiseClearScreen() is called, two additional escape sequences
 * are used in order to clear the screen and position the cursor at home
 * position.
 *
 * CUP (Cursor position)
 *    Sequence: ESC [ H
 *    Effect: moves the cursor to upper left corner
 *
 * ED (Erase display)
 *    Sequence: ESC [ 2 J
 *    Effect: clear the whole screen
 *
 */

#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include "linenoise.h"

#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096
static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
static linenoiseCompletionCallback *completionCallback = NULL;
static linenoiseHintsCallback *hintsCallback = NULL;
static linenoiseFreeHintsCallback *freeHintsCallback = NULL;

static struct termios orig_termios; /* In order to restore at exit.*/
static int rawmode = 0; /* For atexit() function to check if restore is needed*/
static int mlmode = 0;  /* Multi line mode. Default is single line. */
static int atexit_registered = 0; /* Register atexit just 1 time. */
static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0;
static char **history = NULL;

/* The linenoiseState structure represents the state during line editing.
 * We pass this state to functions implementing specific editing
 * functionalities. */
struct linenoiseState {
    int ifd;            /* Terminal stdin file descriptor. */
    int ofd;            /* Terminal stdout file descriptor. */
    char *buf;          /* Edited line buffer. */
    size_t buflen;      /* Edited line buffer size. */
    const char *prompt; /* Prompt to display. */
    size_t plen;        /* Prompt length. */
    size_t pos;         /* Current cursor position. */
    size_t oldpos;      /* Previous refresh cursor position. */
    size_t len;         /* Current edited line length. */
    size_t cols;        /* Number of columns in terminal. */
    size_t maxrows;     /* Maximum num of rows used so far (multiline mode) */
    int history_index;  /* The history index we are currently editing. */
};

enum KEY_ACTION{
    KEY_NULL = 0,       /* NULL */
    CTRL_A = 1,         /* Ctrl+a */
    CTRL_B = 2,         /* Ctrl-b */
    CTRL_C = 3,         /* Ctrl-c */
    CTRL_D = 4,         /* Ctrl-d */
    CTRL_E = 5,         /* Ctrl-e */
    CTRL_F = 6,         /* Ctrl-f */
    CTRL_H = 8,         /* Ctrl-h */
    TAB = 9,            /* Tab */
    CTRL_K = 11,        /* Ctrl+k */
    CTRL_L = 12,        /* Ctrl+l */
    ENTER = 13,         /* Enter */
    CTRL_N = 14,        /* Ctrl-n */
    CTRL_P = 16,        /* Ctrl-p */
    CTRL_T = 20,        /* Ctrl-t */
    CTRL_U = 21,        /* Ctrl+u */
    CTRL_W = 23,        /* Ctrl+w */
    ESC = 27,           /* Escape */
    BACKSPACE =  127    /* Backspace */
};

static void linenoiseAtExit(void);
int linenoiseHistoryAdd(const char *line);
static void refreshLine(struct linenoiseState *l);

/* Debugging macro. */
#if 0
FILE *lndebug_fp = NULL;
#define lndebug(...) \
    do { \
        if (lndebug_fp == NULL) { \
            lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
            fprintf(lndebug_fp, \
            "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
            (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
            (int)l->maxrows,old_rows); \
        } \
        fprintf(lndebug_fp, ", " __VA_ARGS__); \
        fflush(lndebug_fp); \
    } while (0)
#else
#define lndebug(fmt, ...)
#endif

/* ======================= Low level terminal handling ====================== */

/* Set if to use or not the multi line mode. */
void linenoiseSetMultiLine(int ml) {
    mlmode = ml;
}

/* Return true if the terminal name is in the list of terminals we know are
 * not able to understand basic escape sequences. */
static int isUnsupportedTerm(void) {
    char *term = getenv("TERM");
    int j;

    if (term == NULL) return 0;
    for (j = 0; unsupported_term[j]; j++)
        if (!strcasecmp(term,unsupported_term[j])) return 1;
    return 0;
}

/* Raw mode: 1960 magic shit. */
static int enableRawMode(int fd) {
    struct termios raw;

    if (!isatty(STDIN_FILENO)) goto fatal;
    if (!atexit_registered) {
        atexit(linenoiseAtExit);
        atexit_registered = 1;
    }
    if (tcgetattr(fd,&orig_termios) == -1) goto fatal;

    raw = orig_termios;  /* modify the original mode */
    /* input modes: no break, no CR to NL, no parity check, no strip char,
     * no start/stop output control. */
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* output modes - disable post processing */
    raw.c_oflag &= ~(OPOST);
    /* control modes - set 8 bit chars */
    raw.c_cflag |= (CS8);
    /* local modes - choing off, canonical off, no extended functions,
     * no signal chars (^Z,^C) */
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    /* control chars - set return condition: min number of bytes and timer.
     * We want read to return every single byte, without timeout. */
    raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

    /* put terminal in raw mode after flushing */
    if (tcsetattr(fd,TCSADRAIN,&raw) < 0) goto fatal;
    rawmode = 1;
    return 0;

fatal:
    errno = ENOTTY;
    return -1;
}

static void disableRawMode(int fd) {
    /* Don't even check the return value as it's too late. */
    if (rawmode && tcsetattr(fd,TCSADRAIN,&orig_termios) != -1)
        rawmode = 0;
}

/* Use the ESC [6n escape sequence to query the horizontal cursor position
 * and return it. On error -1 is returned, on success the position of the
 * cursor. */
static int getCursorPosition(int ifd, int ofd) {
    char buf[32];
    int cols, rows;
    unsigned int i = 0;

    /* Report cursor location */
    if (write(ofd, "\x1b[6n", 4) != 4) return -1;

    /* Read the response: ESC [ rows ; cols R */
    while (i < sizeof(buf)-1) {
        if (read(ifd,buf+i,1) != 1) break;
        if (buf[i] == 'R') break;
        i++;
    }
    buf[i] = '\0';

    /* Parse it. */
    if (buf[0] != ESC || buf[1] != '[') return -1;
    if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
    return cols;
}

/* Try to get the number of columns in the current terminal, or assume 80
 * if it fails. */
static int getColumns(int ifd, int ofd) {
    struct winsize ws;

    if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
        /* ioctl() failed. Try to query the terminal itself. */
        int start, cols;

        /* Get the initial position so we can restore it later. */
        start = getCursorPosition(ifd,ofd);
        if (start == -1) goto failed;

        /* Go to right margin and get position. */
        if (write(ofd,"\x1b[999C",6) != 6) goto failed;
        cols = getCursorPosition(ifd,ofd);
        if (cols == -1) goto failed;

        /* Restore position. */
        if (cols > start) {
            char seq[32];
            snprintf(seq,32,"\x1b[%dD",cols-start);
            if (write(ofd,seq,strlen(seq)) == -1) {
                /* Can't recover... */
            }
        }
        return cols;
    } else {
        return ws.ws_col;
    }

failed:
    return 80;
}

/* Clear the screen. Used to handle ctrl+l */
void linenoiseClearScreen(void) {
    if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
        /* nothing to do, just to avoid warning. */
    }
}

/* Beep, used for completion when there is nothing to complete or when all
 * the choices were already shown. */
static void linenoiseBeep(void) {
    fprintf(stderr, "\x7");
    fflush(stderr);
}

/* ============================== Completion ================================ */

/* Free a list of completion option populated by linenoiseAddCompletion(). */
static void freeCompletions(linenoiseCompletions *lc) {
    size_t i;
    for (i = 0; i < lc->len; i++)
        free(lc->cvec[i]);
    if (lc->cvec != NULL)
        free(lc->cvec);
}

/* This is an helper function for linenoiseEdit() and is called when the
 * user types the <tab> key in order to complete the string currently in the
 * input.
 *
 * The state of the editing is encapsulated into the pointed linenoiseState
 * structure as described in the structure definition. */
static int completeLine(struct linenoiseState *ls) {
    linenoiseCompletions lc = { 0, NULL };
    int nread, nwritten;
    char c = 0;

    completionCallback(ls->buf,&lc);
    if (lc.len == 0) {
        linenoiseBeep();
    } else {
        size_t stop = 0, i = 0;

        while(!stop) {
            /* Show completion or original buffer */
            if (i < lc.len) {
                struct linenoiseState saved = *ls;

                ls->len = ls->pos = strlen(lc.cvec[i]);
                ls->buf = lc.cvec[i];
                refreshLine(ls);
                ls->len = saved.len;
                ls->pos = saved.pos;
                ls->buf = saved.buf;
            } else {
                refreshLine(ls);
            }

            nread = read(ls->ifd,&c,1);
            if (nread <= 0) {
                freeCompletions(&lc);
                return -1;
            }

            switch(c) {
                case 9: /* tab */
                    i = (i+1) % (lc.len+1);
                    if (i == lc.len) linenoiseBeep();
                    break;
                case 27: /* escape */
                    /* Re-show original buffer */
                    if (i < lc.len) refreshLine(ls);
                    stop = 1;
                    break;
                default:
                    /* Update buffer and return */
                    if (i < lc.len) {
                        nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
                        ls->len = ls->pos = nwritten;
                    }
                    stop = 1;
                    break;
            }
        }
    }

    freeCompletions(&lc);
    return c; /* Return last read character */
}

/* Register a callback function to be called for tab-completion. */
void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
    completionCallback = fn;
}

/* Register a hits function to be called to show hits to the user at the
 * right of the prompt. */
void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
    hintsCallback = fn;
}

/* Register a function to free the hints returned by the hints callback
 * registered with linenoiseSetHintsCallback(). */
void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
    freeHintsCallback = fn;
}

/* This function is used by the callback function registered by the user
 * in order to add completion options given the input string when the
 * user typed <tab>. See the example.c source code for a very easy to
 * understand example. */
void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
    size_t len = strlen(str);
    char *copy, **cvec;

    copy = (char*)malloc(len+1);
    if (copy == NULL) return;
    memcpy(copy,str,len+1);
    cvec = (char**)realloc(lc->cvec,sizeof(char*)*(lc->len+1));
    if (cvec == NULL) {
        free(copy);
        return;
    }
    lc->cvec = cvec;
    lc->cvec[lc->len++] = copy;
}

/* =========================== Line editing ================================= */

/* We define a very simple "append buffer" structure, that is an heap
 * allocated string where we can append to. This is useful in order to
 * write all the escape sequences in a buffer and flush them to the standard
 * output in a single call, to avoid flickering effects. */
struct abuf {
    char *b;
    int len;
};

static void abInit(struct abuf *ab) {
    ab->b = NULL;
    ab->len = 0;
}

static void abAppend(struct abuf *ab, const char *s, int len) {
    char *newc = (char*)realloc(ab->b,ab->len+len);

    if (newc == NULL) return;
    memcpy(newc+ab->len,s,len);
    ab->b = newc;
    ab->len += len;
}

static void abFree(struct abuf *ab) {
    free(ab->b);
}

/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
 * to the right of the prompt. */
void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
    char seq[64];
    if (hintsCallback && plen+l->len < l->cols) {
        int color = -1, bold = 0;
        char *hint = hintsCallback(l->buf,&color,&bold);
        if (hint) {
            int hintlen = strlen(hint);
            int hintmaxlen = l->cols-(plen+l->len);
            if (hintlen > hintmaxlen) hintlen = hintmaxlen;
            if (bold == 1 && color == -1) color = 37;
            if (color != -1 || bold != 0)
                snprintf(seq,64,"\033[%d;%d;49m",bold,color);
            abAppend(ab,seq,strlen(seq));
            abAppend(ab,hint,hintlen);
            if (color != -1 || bold != 0)
                abAppend(ab,"\033[0m",4);
            /* Call the function to free the hint returned. */
            if (freeHintsCallback) freeHintsCallback(hint);
        }
    }
}

/* Single line low level line refresh.
 *
 * Rewrite the currently edited line accordingly to the buffer content,
 * cursor position, and number of columns of the terminal. */
static void refreshSingleLine(struct linenoiseState *l) {
    char seq[64];
    size_t plen = strlen(l->prompt);
    int fd = l->ofd;
    char *buf = l->buf;
    size_t len = l->len;
    size_t pos = l->pos;
    struct abuf ab;

    while((plen+pos) >= l->cols) {
        buf++;
        len--;
        pos--;
    }
    while (plen+len > l->cols) {
        len--;
    }

    abInit(&ab);
    /* Cursor to left edge */
    snprintf(seq,64,"\r");
    abAppend(&ab,seq,strlen(seq));
    /* Write the prompt and the current buffer content */
    abAppend(&ab,l->prompt,strlen(l->prompt));
    abAppend(&ab,buf,len);
    /* Show hits if any. */
    refreshShowHints(&ab,l,plen);
    /* Erase to right */
    snprintf(seq,64,"\x1b[0K");
    abAppend(&ab,seq,strlen(seq));
    /* Move cursor to original position. */
    snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
    abAppend(&ab,seq,strlen(seq));
    if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
    abFree(&ab);
}

/* Multi line low level line refresh.
 *
 * Rewrite the currently edited line accordingly to the buffer content,
 * cursor position, and number of columns of the terminal. */
static void refreshMultiLine(struct linenoiseState *l) {
    char seq[64];
    int plen = strlen(l->prompt);
    int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
    int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
    int rpos2; /* rpos after refresh. */
    int col; /* colum position, zero-based. */
    int old_rows = l->maxrows;
    int fd = l->ofd, j;
    struct abuf ab;

    /* Update maxrows if needed. */
    if (rows > (int)l->maxrows) l->maxrows = rows;

    /* First step: clear all the lines used before. To do so start by
     * going to the last row. */
    abInit(&ab);
    if (old_rows-rpos > 0) {
        lndebug("go down %d", old_rows-rpos);
        snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
        abAppend(&ab,seq,strlen(seq));
    }

    /* Now for every row clear it, go up. */
    for (j = 0; j < old_rows-1; j++) {
        lndebug("clear+up");
        snprintf(seq,64,"\r\x1b[0K\x1b[1A");
        abAppend(&ab,seq,strlen(seq));
    }

    /* Clean the top line. */
    lndebug("clear");
    snprintf(seq,64,"\r\x1b[0K");
    abAppend(&ab,seq,strlen(seq));

    /* Write the prompt and the current buffer content */
    abAppend(&ab,l->prompt,strlen(l->prompt));
    abAppend(&ab,l->buf,l->len);

    /* Show hits if any. */
    refreshShowHints(&ab,l,plen);

    /* If we are at the very end of the screen with our prompt, we need to
     * emit a newline and move the prompt to the first column. */
    if (l->pos &&
        l->pos == l->len &&
        (l->pos+plen) % l->cols == 0)
    {
        lndebug("<newline>");
        abAppend(&ab,"\n",1);
        snprintf(seq,64,"\r");
        abAppend(&ab,seq,strlen(seq));
        rows++;
        if (rows > (int)l->maxrows) l->maxrows = rows;
    }

    /* Move cursor to right position. */
    rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */
    lndebug("rpos2 %d", rpos2);

    /* Go up till we reach the expected positon. */
    if (rows-rpos2 > 0) {
        lndebug("go-up %d", rows-rpos2);
        snprintf(seq,64,"\x1b[%dA", rows-rpos2);
        abAppend(&ab,seq,strlen(seq));
    }

    /* Set column. */
    col = (plen+(int)l->pos) % (int)l->cols;
    lndebug("set col %d", 1+col);
    if (col)
        snprintf(seq,64,"\r\x1b[%dC", col);
    else
        snprintf(seq,64,"\r");
    abAppend(&ab,seq,strlen(seq));

    lndebug("\n");
    l->oldpos = l->pos;

    if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
    abFree(&ab);
}

/* Calls the two low level functions refreshSingleLine() or
 * refreshMultiLine() according to the selected mode. */
static void refreshLine(struct linenoiseState *l) {
    if (mlmode)
        refreshMultiLine(l);
    else
        refreshSingleLine(l);
}

/* Insert the character 'c' at cursor current position.
 *
 * On error writing to the terminal -1 is returned, otherwise 0. */
int linenoiseEditInsert(struct linenoiseState *l, char c) {
    if (l->len < l->buflen) {
        if (l->len == l->pos) {
            l->buf[l->pos] = c;
            l->pos++;
            l->len++;
            l->buf[l->len] = '\0';
            if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
                /* Avoid a full update of the line in the
                 * trivial case. */
                if (write(l->ofd,&c,1) == -1) return -1;
            } else {
                refreshLine(l);
            }
        } else {
            memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
            l->buf[l->pos] = c;
            l->len++;
            l->pos++;
            l->buf[l->len] = '\0';
            refreshLine(l);
        }
    }
    return 0;
}

/* Move cursor on the left. */
void linenoiseEditMoveLeft(struct linenoiseState *l) {
    if (l->pos > 0) {
        l->pos--;
        refreshLine(l);
    }
}

/* Move cursor on the right. */
void linenoiseEditMoveRight(struct linenoiseState *l) {
    if (l->pos != l->len) {
        l->pos++;
        refreshLine(l);
    }
}

/* Move cursor to the start of the line. */
void linenoiseEditMoveHome(struct linenoiseState *l) {
    if (l->pos != 0) {
        l->pos = 0;
        refreshLine(l);
    }
}

/* Move cursor to the end of the line. */
void linenoiseEditMoveEnd(struct linenoiseState *l) {
    if (l->pos != l->len) {
        l->pos = l->len;
        refreshLine(l);
    }
}

/* Substitute the currently edited line with the next or previous history
 * entry as specified by 'dir'. */
#define LINENOISE_HISTORY_NEXT 0
#define LINENOISE_HISTORY_PREV 1
void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
    if (history_len > 1) {
        /* Update the current history entry before to
         * overwrite it with the next one. */
        free(history[history_len - 1 - l->history_index]);
        history[history_len - 1 - l->history_index] = strdup(l->buf);
        /* Show the new entry */
        l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
        if (l->history_index < 0) {
            l->history_index = 0;
            return;
        } else if (l->history_index >= history_len) {
            l->history_index = history_len-1;
            return;
        }
        strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
        l->buf[l->buflen-1] = '\0';
        l->len = l->pos = strlen(l->buf);
        refreshLine(l);
    }
}

/* Delete the character at the right of the cursor without altering the cursor
 * position. Basically this is what happens with the "Delete" keyboard key. */
void linenoiseEditDelete(struct linenoiseState *l) {
    if (l->len > 0 && l->pos < l->len) {
        memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
        l->len--;
        l->buf[l->len] = '\0';
        refreshLine(l);
    }
}

/* Backspace implementation. */
void linenoiseEditBackspace(struct linenoiseState *l) {
    if (l->pos > 0 && l->len > 0) {
        memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
        l->pos--;
        l->len--;
        l->buf[l->len] = '\0';
        refreshLine(l);
    }
}

/* Delete the previosu word, maintaining the cursor at the start of the
 * current word. */
void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
    size_t old_pos = l->pos;
    size_t diff;

    while (l->pos > 0 && l->buf[l->pos-1] == ' ')
        l->pos--;
    while (l->pos > 0 && l->buf[l->pos-1] != ' ')
        l->pos--;
    diff = old_pos - l->pos;
    memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
    l->len -= diff;
    refreshLine(l);
}

/* This function is the core of the line editing capability of linenoise.
 * It expects 'fd' to be already in "raw mode" so that every key pressed
 * will be returned ASAP to read().
 *
 * The resulting string is put into 'buf' when the user type enter, or
 * when ctrl+d is typed.
 *
 * The function returns the length of the current buffer. */
static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
{
    struct linenoiseState l;

    /* Populate the linenoise state that we pass to functions implementing
     * specific editing functionalities. */
    l.ifd = stdin_fd;
    l.ofd = stdout_fd;
    l.buf = buf;
    l.buflen = buflen;
    l.prompt = prompt;
    l.plen = strlen(prompt);
    l.oldpos = l.pos = 0;
    l.len = 0;
    l.cols = getColumns(stdin_fd, stdout_fd);
    l.maxrows = 0;
    l.history_index = 0;

    /* Buffer starts empty. */
    l.buf[0] = '\0';
    l.buflen--; /* Make sure there is always space for the nulterm */

    /* The latest history entry is always our current buffer, that
     * initially is just an empty string. */
    linenoiseHistoryAdd("");

    if (write(l.ofd,prompt,l.plen) == -1) return -1;
    while(1) {
        char c;
        int nread;
        char seq[3];

        nread = read(l.ifd,&c,1);
        if (nread <= 0) return l.len;

        /* Only autocomplete when the callback is set. It returns < 0 when
         * there was an error reading from fd. Otherwise it will return the
         * character that should be handled next. */
        if (c == 9 && completionCallback != NULL) {
            c = completeLine(&l);
            /* Return on errors */
            if (c < 0) return l.len;
            /* Read next character when 0 */
            if (c == 0) continue;
        }

        switch(c) {
        case ENTER:    /* enter */
            history_len--;
            free(history[history_len]);
            if (mlmode) linenoiseEditMoveEnd(&l);
            if (hintsCallback) {
                /* Force a refresh without hints to leave the previous
                 * line as the user typed it after a newline. */
                linenoiseHintsCallback *hc = hintsCallback;
                hintsCallback = NULL;
                refreshLine(&l);
                hintsCallback = hc;
            }
            return (int)l.len;
        case CTRL_C:     /* ctrl-c */
            errno = EAGAIN;
            return -1;
        case BACKSPACE:   /* backspace */
        case 8:     /* ctrl-h */
            linenoiseEditBackspace(&l);
            break;
        case CTRL_D:     /* ctrl-d, remove char at right of cursor, or if the
                            line is empty, act as end-of-file. */
            if (l.len > 0) {
                linenoiseEditDelete(&l);
            } else {
                history_len--;
                free(history[history_len]);
                return -1;
            }
            break;
        case CTRL_T:    /* ctrl-t, swaps current character with previous. */
            if (l.pos > 0 && l.pos < l.len) {
                int aux = buf[l.pos-1];
                buf[l.pos-1] = buf[l.pos];
                buf[l.pos] = aux;
                if (l.pos != l.len-1) l.pos++;
                refreshLine(&l);
            }
            break;
        case CTRL_B:     /* ctrl-b */
            linenoiseEditMoveLeft(&l);
            break;
        case CTRL_F:     /* ctrl-f */
            linenoiseEditMoveRight(&l);
            break;
        case CTRL_P:    /* ctrl-p */
            linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
            break;
        case CTRL_N:    /* ctrl-n */
            linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
            break;
        case ESC:    /* escape sequence */
            /* Read the next two bytes representing the escape sequence.
             * Use two calls to handle slow terminals returning the two
             * chars at different times. */
            if (read(l.ifd,seq,1) == -1) break;
            if (read(l.ifd,seq+1,1) == -1) break;

            /* ESC [ sequences. */
            if (seq[0] == '[') {
                if (seq[1] >= '0' && seq[1] <= '9') {
                    /* Extended escape, read additional byte. */
                    if (read(l.ifd,seq+2,1) == -1) break;
                    if (seq[2] == '~') {
                        switch(seq[1]) {
                        case '3': /* Delete key. */
                            linenoiseEditDelete(&l);
                            break;
                        }
                    }
                } else {
                    switch(seq[1]) {
                    case 'A': /* Up */
                        linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
                        break;
                    case 'B': /* Down */
                        linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
                        break;
                    case 'C': /* Right */
                        linenoiseEditMoveRight(&l);
                        break;
                    case 'D': /* Left */
                        linenoiseEditMoveLeft(&l);
                        break;
                    case 'H': /* Home */
                        linenoiseEditMoveHome(&l);
                        break;
                    case 'F': /* End*/
                        linenoiseEditMoveEnd(&l);
                        break;
                    }
                }
            }

            /* ESC O sequences. */
            else if (seq[0] == 'O') {
                switch(seq[1]) {
                case 'H': /* Home */
                    linenoiseEditMoveHome(&l);
                    break;
                case 'F': /* End*/
                    linenoiseEditMoveEnd(&l);
                    break;
                }
            }
            break;
        default:
            if (linenoiseEditInsert(&l,c)) return -1;
            break;
        case CTRL_U: /* Ctrl+u, delete the whole line. */
            buf[0] = '\0';
            l.pos = l.len = 0;
            refreshLine(&l);
            break;
        case CTRL_K: /* Ctrl+k, delete from current to end of line. */
            buf[l.pos] = '\0';
            l.len = l.pos;
            refreshLine(&l);
            break;
        case CTRL_A: /* Ctrl+a, go to the start of the line */
            linenoiseEditMoveHome(&l);
            break;
        case CTRL_E: /* ctrl+e, go to the end of the line */
            linenoiseEditMoveEnd(&l);
            break;
        case CTRL_L: /* ctrl+l, clear screen */
            linenoiseClearScreen();
            refreshLine(&l);
            break;
        case CTRL_W: /* ctrl+w, delete previous word */
            linenoiseEditDeletePrevWord(&l);
            break;
        }
    }
    return l.len;
}

/* This special mode is used by linenoise in order to print scan codes
 * on screen for debugging / development purposes. It is implemented
 * by the linenoise_example program using the --keycodes option. */
void linenoisePrintKeyCodes(void) {
    char quit[4];

    printf("Linenoise key codes debugging mode.\n"
            "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
    if (enableRawMode(STDIN_FILENO) == -1) return;
    memset(quit,' ',4);
    while(1) {
        char c;
        int nread;

        nread = read(STDIN_FILENO,&c,1);
        if (nread <= 0) continue;
        memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
        quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
        if (memcmp(quit,"quit",sizeof(quit)) == 0) break;

        printf("'%c' %02x (%d) (type quit to exit)\n",
            isprint(c) ? c : '?', (int)c, (int)c);
        printf("\r"); /* Go left edge manually, we are in raw mode. */
        fflush(stdout);
    }
    disableRawMode(STDIN_FILENO);
}

/* This function calls the line editing function linenoiseEdit() using
 * the STDIN file descriptor set in raw mode. */
static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
    int count;

    if (buflen == 0) {
        errno = EINVAL;
        return -1;
    }

    if (enableRawMode(STDIN_FILENO) == -1) return -1;
    count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
    disableRawMode(STDIN_FILENO);
    printf("\n");
    return count;
}

/* This function is called when linenoise() is called with the standard
 * input file descriptor not attached to a TTY. So for example when the
 * program using linenoise is called in pipe or with a file redirected
 * to its standard input. In this case, we want to be able to return the
 * line regardless of its length (by default we are limited to 4k). */
static char *linenoiseNoTTY(void) {
    char *line = NULL;
    size_t len = 0, maxlen = 0;

    while(1) {
        if (len == maxlen) {
            if (maxlen == 0) maxlen = 16;
            maxlen *= 2;
            char *oldval = line;
            line = (char*)realloc(line,maxlen);
            if (line == NULL) {
                if (oldval) free(oldval);
                return NULL;
            }
        }
        int c = fgetc(stdin);
        if (c == EOF || c == '\n') {
            if (c == EOF && len == 0) {
                free(line);
                return NULL;
            } else {
                line[len] = '\0';
                return line;
            }
        } else {
            line[len] = c;
            len++;
        }
    }
}

/* The high level function that is the main API of the linenoise library.
 * This function checks if the terminal has basic capabilities, just checking
 * for a blacklist of stupid terminals, and later either calls the line
 * editing function or uses dummy fgets() so that you will be able to type
 * something even in the most desperate of the conditions. */
char *linenoise(const char *prompt) {
    char buf[LINENOISE_MAX_LINE];
    int count;

    if (!isatty(STDIN_FILENO)) {
        /* Not a tty: read from file / pipe. In this mode we don't want any
         * limit to the line size, so we call a function to handle that. */
        return linenoiseNoTTY();
    } else if (isUnsupportedTerm()) {
        size_t len;

        printf("%s",prompt);
        fflush(stdout);
        if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
        len = strlen(buf);
        while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
            len--;
            buf[len] = '\0';
        }
        return strdup(buf);
    } else {
        count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
        if (count == -1) return NULL;
        return strdup(buf);
    }
}

/* This is just a wrapper the user may want to call in order to make sure
 * the linenoise returned buffer is freed with the same allocator it was
 * created with. Useful when the main program is using an alternative
 * allocator. */
void linenoiseFree(void *ptr) {
    free(ptr);
}

/* ================================ History ================================= */

/* Free the history, but does not reset it. Only used when we have to
 * exit() to avoid memory leaks are reported by valgrind & co. */
static void freeHistory(void) {
    if (history) {
        int j;

        for (j = 0; j < history_len; j++)
            free(history[j]);
        free(history);
    }
}

/* At exit we'll try to fix the terminal to the initial conditions. */
static void linenoiseAtExit(void) {
    disableRawMode(STDIN_FILENO);
    freeHistory();
}

/* This is the API call to add a new entry in the linenoise history.
 * It uses a fixed array of char pointers that are shifted (memmoved)
 * when the history max length is reached in order to remove the older
 * entry and make room for the new one, so it is not exactly suitable for huge
 * histories, but will work well for a few hundred of entries.
 *
 * Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(const char *line) {
    char *linecopy;

    if (history_max_len == 0) return 0;

    /* Initialization on first call. */
    if (history == NULL) {
        history = (char**)malloc(sizeof(char*)*history_max_len);
        if (history == NULL) return 0;
        memset(history,0,(sizeof(char*)*history_max_len));
    }

    /* Don't add duplicated lines. */
    if (history_len && !strcmp(history[history_len-1], line)) return 0;

    /* Add an heap allocated copy of the line in the history.
     * If we reached the max length, remove the older line. */
    linecopy = strdup(line);
    if (!linecopy) return 0;
    if (history_len == history_max_len) {
        free(history[0]);
        memmove(history,history+1,sizeof(char*)*(history_max_len-1));
        history_len--;
    }
    history[history_len] = linecopy;
    history_len++;
    return 1;
}

/* Set the maximum length for the history. This function can be called even
 * if there is already some history, the function will make sure to retain
 * just the latest 'len' elements if the new history length value is smaller
 * than the amount of items already inside the history. */
int linenoiseHistorySetMaxLen(int len) {
    char **newc;

    if (len < 1) return 0;
    if (history) {
        int tocopy = history_len;

        newc = (char**)malloc(sizeof(char*)*len);
        if (newc == NULL) return 0;

        /* If we can't copy everything, free the elements we'll not use. */
        if (len < tocopy) {
            int j;

            for (j = 0; j < tocopy-len; j++) free(history[j]);
            tocopy = len;
        }
        memset(newc,0,sizeof(char*)*len);
        memcpy(newc,history+(history_len-tocopy), sizeof(char*)*tocopy);
        free(history);
        history = newc;
    }
    history_max_len = len;
    if (history_len > history_max_len)
        history_len = history_max_len;
    return 1;
}

/* Save the history in the specified file. On success 0 is returned
 * otherwise -1 is returned. */
int linenoiseHistorySave(const char *filename) {
    mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
    FILE *fp;
    int j;

    fp = fopen(filename,"w");
    umask(old_umask);
    if (fp == NULL) return -1;
    chmod(filename,S_IRUSR|S_IWUSR);
    for (j = 0; j < history_len; j++)
        fprintf(fp,"%s\n",history[j]);
    fclose(fp);
    return 0;
}

/* Load the history from the specified file. If the file does not exist
 * zero is returned and no operation is performed.
 *
 * If the file exists and the operation succeeded 0 is returned, otherwise
 * on error -1 is returned. */
int linenoiseHistoryLoad(const char *filename) {
    FILE *fp = fopen(filename,"r");
    char buf[LINENOISE_MAX_LINE];

    if (fp == NULL) return -1;

    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
        char *p;

        p = strchr(buf,'\r');
        if (!p) p = strchr(buf,'\n');
        if (p) *p = '\0';
        linenoiseHistoryAdd(buf);
    }
    fclose(fp);
    return 0;
}
#endif //JSI__READLINE==1
#if defined(_WIN32) || defined(WIN32)
#ifndef JSI_WIN32COMPAT_H
#define JSI_WIN32COMPAT_H

typedef unsigned int uint;
/* TODO: bring in external regex ..
typedef struct { int n; } regex_t;
typedef struct { int n; } regmatch_t; */

enum {
    DT_UNKNOWN = 0, DT_FIFO = 1, DT_CHR = 2, DT_DIR = 4,
    DT_BLK = 6, DT_REG = 8, DT_LNK = 10, DT_SOCK = 12, DT_WHT = 14
};


/* Compatibility for Windows (mingw and msvc, not cygwin */

#include <time.h>
#include <dirent.h>
char * strptime(const char *buf, const char *fmt, struct tm *tm);

#define MAXNAMLEN FILENAME_MAX 
int scandir( const char *dirname, struct dirent ***namelist, int (*select)(const struct dirent *), 
     int (*compar)( const struct dirent **, const struct dirent ** ) 
);
int alphasort( const struct dirent **d1, const struct dirent **d2 ); 
int istrcmp( const char *s1, const char *s2 ); 

#define HAVE_DLOPEN
void *dlopen(const char *path, int mode);
int dlclose(void *handle);
void *dlsym(void *handle, const char *symbol);
char *dlerror(void);

extern time_t internal_timegm(struct tm *t);

/* MS CRT always uses three digits after 'e' */
#define JSI_SPRINTF_DOUBLE_NEEDS_FIX

#ifdef _MSC_VER
/* These are msvc vs gcc */

#if _MSC_VER >= 1000
    #pragma warning(disable:4146)
#endif

#include <limits.h>
#define jsi_wide _int64
#ifndef LLONG_MAX
    #define LLONG_MAX    9223372036854775807I64
#endif
#ifndef LLONG_MIN
    #define LLONG_MIN    (-LLONG_MAX - 1I64)
#endif
#define JSI_WIDE_MIN LLONG_MIN
#define JSI_WIDE_MAX LLONG_MAX
#define JSI_WIDE_MODIFIER "I64d"
#define strcasecmp _stricmp
#define strtoull _strtoui64
#define snprintf _snprintf

#include <io.h>

struct timeval {
    long tv_sec;
    long tv_usec;
};

int gettimeofday(struct timeval *tv, void *unused);

#define HAVE_OPENDIR
struct dirent {
    char *d_name;
};

typedef struct DIR {
    long                handle; /* -1 for failed rewind */
    struct _finddata_t  info;
    struct dirent       result; /* d_name null iff first time */
    char                *name;  /* null-terminated char string */
} DIR;

DIR *opendir(const char *name);
int closedir(DIR *dir);
struct dirent *readdir(DIR *dir);
#endif /* _MSC_VER */

#endif


#endif /* WIN32 */
#ifndef JSI_LITE_ONLY
#ifndef _JSI_CODE_C_
#define _JSI_CODE_C_
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif

/* replace continue/break(coded as OP_RESERVED) jmp
 * |------------------| \
 * |                  | \\ where 'continue' jmp (jmp to step code)
 * |       ops        |  / 
 * |                  | / \
 * |------------------|    \ where 'break' jmp (jmp after step code)
 * |                  |    /
 * |       step       |   /
 * |                  |  /
 * |------------------| /
 * 1. break_only used only in switch
 * 2. desire_label, only replace if current iter statement has the same label with opcode
 * 3. topop, if not replace in current iter statment, make sure when jmp out of this loop/switch
 *    corrent stack elems poped(for in always has 2 elem, while switch has 1)
 */

static const char *jsi_op_names[OP_LASTOP] = {
    "NOP",
    "PUSHNUM",
    "PUSHSTR",
    "PUSHVSTR",
    "PUSHVAR",
    "PUSHUND",
    "PUSHNULL",
    "PUSHBOO",
    "PUSHFUN",
    "PUSHREG",
    "PUSHARG",
    "PUSHTHS",
    "PUSHTOP",
    "PUSHTOP2",
    "UNREF",
    "POP",
    "LOCAL",
    "NEG",
    "POS",
    "NOT",
    "BNOT",
    "ADD",
    "SUB",
    "MUL",
    "DIV",
    "MOD",
    "LESS",
    "GREATER",
    "LESSEQU",
    "GREATEREQU",
    "EQUAL",
    "NOTEQUAL",
    "STRICTEQU",
    "STRICTNEQ",
    "BAND",
    "BOR",
    "BXOR",
    "SHF",
    "INSTANCEOF",
    "ASSIGN",
    "SUBSCRIPT",
    "INC",
    "TYPEOF",
    "IN",
    "DEC",
    "KEY",
    "NEXT",
    "JTRUE",
    "JFALSE",
    "JTRUE_NP",
    "JFALSE_NP",
    "JMP",
    "JMPPOP",
    "FCALL",
    "NEWFCALL",
    "RET",
    "DELETE",
    "CHTHIS",
    "OBJECT",
    "ARRAY",
    "EVAL",
    "STRY",
    "ETRY",
    "SCATCH",
    "ECATCH",
    "SFINAL",
    "EFINAL",
    "THROW",
    "WITH",
    "EWITH",
    "RESERVED",
    "DEBUG"
};

static int jsiOpCodesCnt[3] = {0,0,0};

void jsi_FreeOpcodes(Jsi_OpCodes *ops) {
    int i;
    if (!ops) return;
    for (i=0; i<ops->code_len; i++) {
        jsi_OpCode *op = ops->codes+i;
        if (op->data && op->alloc)
            Jsi_Free(op->data);
        _JSI_MEMCLEAR(op);
    }
    jsiOpCodesCnt[1]++;
    jsiOpCodesCnt[2]--;
#ifdef JSI_MEM_DEBUG
    if (ops->hPtr)
        Jsi_HashEntryDelete(ops->hPtr);
#endif
    Jsi_Free(ops->codes);
    Jsi_Free(ops);
}

static Jsi_OpCodes *codes_new(int size)
{
    Jsi_OpCodes *ops = (Jsi_OpCodes *)Jsi_Calloc(1, sizeof(*ops));
    jsiOpCodesCnt[0]++;
    jsiOpCodesCnt[2]++;
    ops->codes = (jsi_OpCode *)Jsi_Calloc(size, sizeof(jsi_OpCode));
    ops->code_size = size;
#ifdef JSI_MEM_DEBUG
    static int idNum = 0;
    ops->hPtr = Jsi_HashSet(jsiIntData.mainInterp->codesTbl, ops, ops);
    ops->id = idNum++;
#endif
    return ops;
}

static int codes_insert(Jsi_OpCodes *c, jsi_Eopcode code, void *extra, int doalloc)
{
    if (c->code_size - c->code_len <= 0) {
        c->code_size += 100;
        c->codes = (jsi_OpCode *)Jsi_Realloc(c->codes, c->code_size * sizeof(jsi_OpCode));
    }
    c->codes[c->code_len].op = code;
    c->codes[c->code_len].data = extra;
    c->codes[c->code_len].alloc = doalloc;
    c->code_len ++;
    return 0;
}

static int codes_insertln(Jsi_OpCodes *c, jsi_Eopcode code, void *extra, jsi_Pstate *pstate, jsi_Pline *line, int doalloc)
{
    if (c->code_size - c->code_len <= 0) {
        c->code_size += 100;
        c->codes = (jsi_OpCode *)Jsi_Realloc(c->codes, c->code_size * sizeof(jsi_OpCode));
    }
    c->codes[c->code_len].op = code;
    c->codes[c->code_len].data = extra;
    c->codes[c->code_len].Line = line->first_line;
    c->codes[c->code_len].Lofs = line->first_column;
    c->codes[c->code_len].fname = jsi_PstateGetFilename(pstate);
    c->codes[c->code_len].alloc = doalloc;
    c->code_len ++;
    return 0;
}


static Jsi_OpCodes *codes_join(Jsi_OpCodes *a, Jsi_OpCodes *b)
{
    Jsi_OpCodes *ret = codes_new(a->code_len + b->code_len);
    memcpy(ret->codes, a->codes, a->code_len * sizeof(jsi_OpCode));
    memcpy(&ret->codes[a->code_len], b->codes, b->code_len * sizeof(jsi_OpCode));
    ret->code_size = a->code_len + b->code_len;
    ret->code_len = ret->code_size;
    ret->expr_counter = a->expr_counter + b->expr_counter;
#if 0
    a->code_len=0;
    jsi_FreeOpcodes(a);
    b->code_len=0;
    jsi_FreeOpcodes(b);
#else
    Jsi_Free(a->codes);
    Jsi_Free(b->codes);
#ifdef JSI_MEM_DEBUG
    if (a->hPtr)
        Jsi_HashEntryDelete(a->hPtr);
    if (b->hPtr)
        Jsi_HashEntryDelete(b->hPtr);
#endif
    Jsi_Free(a);
    Jsi_Free(b);
#endif
    jsiOpCodesCnt[1]++;
    jsiOpCodesCnt[2]-=2;
    return ret;
}

static Jsi_OpCodes *codes_join3(Jsi_OpCodes *a, Jsi_OpCodes *b, Jsi_OpCodes *c)
{
    return codes_join(codes_join(a, b), c);
}

static Jsi_OpCodes *codes_join4(Jsi_OpCodes *a, Jsi_OpCodes *b, Jsi_OpCodes *c, Jsi_OpCodes *d)
{
    return codes_join(codes_join(a, b), codes_join(c, d));
}

#define JSI_NEW_CODES(doalloc,code, extra) do {                 \
        Jsi_OpCodes *r = codes_new(3);                  \
        codes_insert(r, (code), (void *)(uintptr_t)(extra), doalloc);   \
        return r;                                   \
    } while(0)

#define JSI_NEW_CODESLN(doalloc,code, extra) do {                 \
        Jsi_OpCodes *r = codes_new(3);                  \
        codes_insertln(r, (code), (void *)(uintptr_t)(extra), p, line, doalloc);   \
        return r;                                   \
    } while(0)

static Jsi_OpCodes *code_push_undef() { JSI_NEW_CODES(0,OP_PUSHUND, 0); }
static Jsi_OpCodes *code_push_null() { JSI_NEW_CODES(0,OP_PUSHNULL, 0); }
static Jsi_OpCodes *code_push_bool(int v) { JSI_NEW_CODES(0,OP_PUSHBOO, v); }
static Jsi_OpCodes *code_push_num(Jsi_Number *v) { JSI_NEW_CODES(1,OP_PUSHNUM, v); }
static Jsi_OpCodes *code_push_string(jsi_Pstate *p, jsi_Pline *line, const char *str) {
    if (*str == 'c' && !Jsi_Strcmp(str,"callee"))
        p->interp->hasCallee = 1;
    JSI_NEW_CODESLN(0,OP_PUSHSTR, str);
}

static Jsi_OpCodes *code_push_vstring(jsi_Pstate *p, jsi_Pline *line, Jsi_String *s) {
    JSI_NEW_CODESLN(0,OP_PUSHVSTR, s);
}

static Jsi_OpCodes *code_push_index(jsi_Pstate *p, jsi_Pline *line, const char *varname, int local)
{
    jsi_FastVar *n = (typeof(n))Jsi_Calloc(1, sizeof(*n));
    n->sig = JSI_SIG_FASTVAR;
    n->ps = p;
    n->context_id = -1;
    n->local = local;
    n->varname = (char*)Jsi_KeyAdd(p->interp, varname);
    Jsi_HashSet(p->fastVarTbl, n, n);
    JSI_NEW_CODESLN(1,OP_PUSHVAR, n);
}

static Jsi_OpCodes *code_push_this(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_PUSHTHS, 0); }
static Jsi_OpCodes *code_push_top() { JSI_NEW_CODES(0,OP_PUSHTOP, 0); }
static Jsi_OpCodes *code_push_top2() { JSI_NEW_CODES(0,OP_PUSHTOP2, 0); }
static Jsi_OpCodes *code_unref() { JSI_NEW_CODES(0,OP_UNREF, 0); }
static Jsi_OpCodes *code_push_args() { JSI_NEW_CODES(0,OP_PUSHARG, 0); }
static Jsi_OpCodes *code_push_func_sub(jsi_Pstate *p, jsi_Pline *line, Jsi_Func *fun) { p->funcDefs++; JSI_NEW_CODESLN(0,OP_PUSHFUN, fun); }
static Jsi_OpCodes *code_push_func(jsi_Pstate *p, jsi_Pline *line, Jsi_Func *fun) {
    Jsi_OpCodes* codes = code_push_func_sub(p, line, fun);
    if (codes && fun && fun->name)
        codes->codes[0].local = 1;
    return codes;
}
static Jsi_OpCodes *code_push_regex(jsi_Pstate *p, jsi_Pline *line, Jsi_Regex *reg) { JSI_NEW_CODESLN(0,OP_PUSHREG, reg); }

static Jsi_OpCodes *code_local(jsi_Pstate *p, jsi_Pline *line, const char *varname) { JSI_NEW_CODESLN(0,OP_LOCAL, varname); }

static Jsi_OpCodes *code_nop() { JSI_NEW_CODES(0,OP_NOP, 0); }
static Jsi_OpCodes *code_neg() { JSI_NEW_CODES(0,OP_NEG, 0); }
static Jsi_OpCodes *code_pos() { JSI_NEW_CODES(0,OP_POS, 0); }
static Jsi_OpCodes *code_bnot() { JSI_NEW_CODES(0,OP_BNOT, 0); }
static Jsi_OpCodes *code_not() { JSI_NEW_CODES(0,OP_NOT, 0); }
static Jsi_OpCodes *code_mul() { JSI_NEW_CODES(0,OP_MUL, 0); }
static Jsi_OpCodes *code_div() { JSI_NEW_CODES(0,OP_DIV, 0); }
static Jsi_OpCodes *code_mod() { JSI_NEW_CODES(0,OP_MOD, 0); }
static Jsi_OpCodes *code_add() { JSI_NEW_CODES(0,OP_ADD, 0); }
static Jsi_OpCodes *code_sub() { JSI_NEW_CODES(0,OP_SUB, 0); }
static Jsi_OpCodes *code_in() { JSI_NEW_CODES(0,OP_IN, 0); }
static Jsi_OpCodes *code_less() { JSI_NEW_CODES(0,OP_LESS, 0); }
static Jsi_OpCodes *code_greater() { JSI_NEW_CODES(0,OP_GREATER, 0); }
static Jsi_OpCodes *code_lessequ() { JSI_NEW_CODES(0,OP_LESSEQU, 0); }
static Jsi_OpCodes *code_greaterequ() { JSI_NEW_CODES(0,OP_GREATEREQU, 0); }
static Jsi_OpCodes *code_equal() { JSI_NEW_CODES(0,OP_EQUAL, 0); } 
static Jsi_OpCodes *code_notequal() { JSI_NEW_CODES(0,OP_NOTEQUAL, 0); }
static Jsi_OpCodes *code_eequ() { JSI_NEW_CODES(0,OP_STRICTEQU, 0); }
static Jsi_OpCodes *code_nneq() { JSI_NEW_CODES(0,OP_STRICTNEQ, 0); }
static Jsi_OpCodes *code_band() { JSI_NEW_CODES(0,OP_BAND, 0); }
static Jsi_OpCodes *code_bor() { JSI_NEW_CODES(0,OP_BOR, 0); }
static Jsi_OpCodes *code_bxor() { JSI_NEW_CODES(0,OP_BXOR, 0); }
static Jsi_OpCodes *code_shf(int right) { JSI_NEW_CODES(0,OP_SHF, right); }
static Jsi_OpCodes *code_instanceof() { JSI_NEW_CODES(0,OP_INSTANCEOF, 0); }
static Jsi_OpCodes *code_assign(jsi_Pstate *p, jsi_Pline *line, int h) { JSI_NEW_CODESLN(0,OP_ASSIGN, h); }
static Jsi_OpCodes *code_subscript(jsi_Pstate *p, jsi_Pline *line, int right_val) { JSI_NEW_CODESLN(0,OP_SUBSCRIPT, right_val); }
static Jsi_OpCodes *code_inc(jsi_Pstate *p, jsi_Pline *line, int e) { JSI_NEW_CODESLN(0,OP_INC, e); }
static Jsi_OpCodes *code_dec(jsi_Pstate *p, jsi_Pline *line, int e) { JSI_NEW_CODESLN(0,OP_DEC, e); }
static Jsi_OpCodes *code_typeof(jsi_Pstate *p, jsi_Pline *line, int e) { JSI_NEW_CODESLN(0,OP_TYPEOF, e); }

static Jsi_OpCodes *code__fcall(jsi_Pstate *p, jsi_Pline *line, int argc, const char *name, const char *namePre, Jsi_OpCodes *argCodes) {
    jsi_FuncCallCheck(p,line,argc,1, name, namePre, argCodes);JSI_NEW_CODESLN(0,OP_FCALL, argc);
}
static Jsi_OpCodes *code_fcall(jsi_Pstate *p, jsi_Pline *line, int argc, const char *name, const char *namePre, Jsi_OpCodes *argCodes, Jsi_OpCodes* pref) {
    Jsi_OpCodes *codes = code__fcall(p, line, argc, name, namePre, argCodes);
    int i;
    if (!name || !codes || !pref)
        return codes;
    jsi_OpLogFlags logflag = jsi_Oplf_none;
    if (name[0] == 'a' && !Jsi_Strcmp(name, "assert"))
        logflag = jsi_Oplf_assert;
    else if (name[0] == 'L' && name[1] == 'o') {
        if (!Jsi_Strcmp(name, "LogDebug"))
            logflag = jsi_Oplf_debug;
        else if (!Jsi_Strcmp(name, "LogTrace"))
            logflag = jsi_Oplf_trace;
        else if (!Jsi_Strcmp(name, "LogTest"))
            logflag = jsi_Oplf_test;
    }
    if (logflag) {
        codes->codes[0].logflag = logflag;
        if (argCodes)
            for (i=0; i<argCodes->code_len; i++)
                argCodes->codes[i].logflag = logflag;
        for (i=0; i<pref->code_len; i++)
            pref->codes[i].logflag = logflag;
    }
    return codes;
}
static Jsi_OpCodes *code_newfcall(jsi_Pstate *p, jsi_Pline *line, int argc, const char *name, Jsi_OpCodes *argCodes) {
    jsi_FuncCallCheck(p,line,argc,1, name, NULL, argCodes); JSI_NEW_CODESLN(0,OP_NEWFCALL, argc);
}
static Jsi_OpCodes *code_ret(jsi_Pstate *p, jsi_Pline *line, int n) { JSI_NEW_CODESLN(0,OP_RET, n); }
static Jsi_OpCodes *code_delete(int n) { JSI_NEW_CODES(0,OP_DELETE, n); }
static Jsi_OpCodes *code_chthis(jsi_Pstate *p, jsi_Pline *line, int n) { JSI_NEW_CODESLN(0,OP_CHTHIS, n); }
static Jsi_OpCodes *code_pop(int n) { JSI_NEW_CODES(0,OP_POP, n); }
static Jsi_OpCodes *code_jfalse(int off) { JSI_NEW_CODES(0,OP_JFALSE, off); }
static Jsi_OpCodes *code_jtrue(int off) { JSI_NEW_CODES(0,OP_JTRUE, off); }
static Jsi_OpCodes *code_jfalse_np(int off) { JSI_NEW_CODES(0,OP_JFALSE_NP, off); }
static Jsi_OpCodes *code_jtrue_np(int off) { JSI_NEW_CODES(0,OP_JTRUE_NP, off); }
static Jsi_OpCodes *code_jmp(int off) { JSI_NEW_CODES(0,OP_JMP, off); }
static Jsi_OpCodes *code_object(jsi_Pstate *p, jsi_Pline *line, int c) { JSI_NEW_CODESLN(0,OP_OBJECT, c); }
static Jsi_OpCodes *code_array(jsi_Pstate *p, jsi_Pline *line, int c) { JSI_NEW_CODESLN(0,OP_ARRAY, c); }
static Jsi_OpCodes *code_key() { JSI_NEW_CODES(0,OP_KEY, 0); }
static Jsi_OpCodes *code_next() { JSI_NEW_CODES(0,OP_NEXT, 0); }

static Jsi_OpCodes *code_eval(jsi_Pstate *p, jsi_Pline *line, int argc, Jsi_OpCodes *c) {
    jsi_FreeOpcodes(c); // Eliminate leak of unused opcodes.
    JSI_NEW_CODESLN(0,OP_EVAL, argc);
}

static Jsi_OpCodes *code_stry(jsi_Pstate *p, jsi_Pline *line, int trylen, int catchlen, int finlen)
{ 
    jsi_TryInfo *ti = (jsi_TryInfo *)Jsi_Calloc(1,sizeof(*ti));
    ti->trylen = trylen;
    ti->catchlen = catchlen;
    ti->finallen = finlen;
    JSI_NEW_CODESLN(1,OP_STRY, ti); 
}
static Jsi_OpCodes *code_etry(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_ETRY, 0); }
static Jsi_OpCodes *code_scatch(jsi_Pstate *p, jsi_Pline *line, const char *var) { JSI_NEW_CODESLN(0,OP_SCATCH, var); }
static Jsi_OpCodes *code_ecatch(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_ECATCH, 0); }
static Jsi_OpCodes *code_sfinal(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_SFINAL, 0); }
static Jsi_OpCodes *code_efinal(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_EFINAL, 0); }
static Jsi_OpCodes *code_throw(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_THROW, 0); }
static Jsi_OpCodes *code_with(jsi_Pstate *p, jsi_Pline *line, int withlen) { JSI_NEW_CODESLN(0,OP_WITH, withlen); }
static Jsi_OpCodes *code_ewith(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_EWITH, 0); }

static Jsi_OpCodes *code_debug(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_DEBUG, 0); }
static Jsi_OpCodes *code_reserved(jsi_Pstate *p, jsi_Pline *line, int type, const char *id)
{
    jsi_ReservedInfo *ri = (jsi_ReservedInfo*)Jsi_Calloc(1, sizeof(*ri));
    ri->type = type;
    ri->label = id;
    ri->topop = 0;
    JSI_NEW_CODESLN(1,OP_RESERVED, ri);
}

static jsi_JmpPopInfo *jpinfo_new(int off, int topop)
{
    jsi_JmpPopInfo *r = (jsi_JmpPopInfo *)Jsi_Calloc(1, sizeof(*r));
    r->off = off;
    r->topop = topop;
    return r;
}

static void code_reserved_replace(Jsi_OpCodes *ops, int step_len, int break_only,
                           const char *desire_label, int topop)
{
    int i;
    for (i = 0; i < ops->code_len; ++i) {
        if (ops->codes[i].op != OP_RESERVED) continue;
        jsi_ReservedInfo *ri = (jsi_ReservedInfo *)ops->codes[i].data;

        if (ri->label) {
            if (!desire_label || Jsi_Strcmp(ri->label, desire_label) != 0) {
                ri->topop += topop;
                continue;
            }
        }
        
        if (ri->type == RES_CONTINUE) {
            if (break_only) {
                ri->topop += topop;
                continue;
            } else {
                int topop = ri->topop;
                Jsi_Free(ri);       /* kill reserved Warn, replace with other opcode */
 /*               if (ops->codes[i].data && ops->codes[i].alloc) //TODO: memory leak?
                    Jsi_Free(ops->codes[i].data);*/
                if (topop) {
                    ops->codes[i].data = jpinfo_new(ops->code_len - i, topop);
                    ops->codes[i].op = OP_JMPPOP;
                    ops->codes[i].alloc = 1;
                } else {
                    ops->codes[i].data = (void *)(uintptr_t)(ops->code_len - i);
                    ops->codes[i].op = OP_JMP;
                    ops->codes[i].alloc = 0;
                }
            }
        } else if (ri->type == RES_BREAK) {
            int topop = ri->topop;
            Jsi_Free(ri);
/*           if (ops->codes[i].data && ops->codes[i].alloc)
                Jsi_Free(ops->codes[i].data); */
            if (topop) {
                ops->codes[i].data = jpinfo_new(step_len + ops->code_len - i, topop);
                ops->codes[i].op = OP_JMPPOP;
                ops->codes[i].alloc = 1;
            } else {
                ops->codes[i].data = (void *)(uintptr_t)(step_len + ops->code_len - i);
                ops->codes[i].op = OP_JMP;
                ops->codes[i].alloc = 0;
            }
        }
    }
}

const char* jsi_opcode_string(uint opCode)
{
    if (opCode >= (sizeof(jsi_op_names)/sizeof(jsi_op_names[0])))
        return "NULL";
    return jsi_op_names[opCode];
}

void jsi_code_decode(Jsi_Interp *interp, jsi_OpCode *op, int currentip, char *buf, int bsiz)
{
    if (_JSICASTINT(op->op) < 0 || op->op >= OP_LASTOP) {
        snprintf(buf, bsiz, "Bad opcode[%d] at %d", op->op, currentip);
    }
    char nbuf[100];
    snprintf(nbuf, sizeof(nbuf), "%d#%d", currentip, op->Line);
    snprintf(buf, bsiz, "%-8s %s ", nbuf, jsi_op_names[op->op]);

    int sl = Jsi_Strlen(buf);
    char *bp = buf + sl;
    bsiz -= sl;
    if (op->op == OP_PUSHBOO || op->op == OP_FCALL || op->op == OP_EVAL ||
        op->op == OP_POP || op->op == OP_ASSIGN ||
        op->op == OP_RET || op->op == OP_NEWFCALL ||
        op->op == OP_DELETE || op->op == OP_CHTHIS ||
        op->op == OP_OBJECT || op->op == OP_ARRAY ||
        op->op == OP_SHF ||
        op->op == OP_INC || op->op == OP_DEC) snprintf(bp, bsiz, "%" PRId64, (Jsi_Wide)(uintptr_t)op->data);
    else if (op->op == OP_PUSHNUM) Jsi_NumberDtoA(interp, *((Jsi_Number *)op->data), bp, bsiz, 0);
    else if (op->op == OP_PUSHVSTR) {
        Jsi_String *ss = (Jsi_String*)op->data;
        snprintf(bp, bsiz, "\"%s\"", ss->str);
    } else if (op->op == OP_PUSHSTR || op->op == OP_LOCAL ||
             op->op == OP_SCATCH) snprintf(bp, bsiz, "\"%s\"", op->data ? (char*)op->data:"(NoCatch)");
    else if (op->op == OP_PUSHVAR) snprintf(bp, bsiz, "var: \"%s\"", ((jsi_FastVar *)op->data)->varname);
    else if (op->op == OP_PUSHFUN) snprintf(bp, bsiz, "func: 0x%" PRIx64, (Jsi_Wide)(uintptr_t)op->data);
    else if (op->op == OP_JTRUE || op->op == OP_JFALSE ||
             op->op == OP_JTRUE_NP || op->op == OP_JFALSE_NP ||
             op->op == OP_JMP) snprintf(bp, bsiz, "{%" PRIu64 "}\t#%" PRIu64 "", (Jsi_Wide)(uintptr_t)op->data, (Jsi_Wide)((uintptr_t)currentip + (uintptr_t)op->data));
    else if (op->op == OP_JMPPOP) {
        jsi_JmpPopInfo *jp = (jsi_JmpPopInfo*)op->data;
        snprintf(bp, bsiz, "{%d},%d\t#%d", jp->off, jp->topop, currentip + jp->off);
    }
    else if (op->op == OP_STRY) {
        jsi_TryInfo *t = (jsi_TryInfo *)op->data;
        snprintf(bp, bsiz, "{try:%d, catch:%d, final:%d}", t->trylen, t->catchlen, t->finallen);
    }
}

void jsi_mark_local(Jsi_OpCodes *ops) // Mark variables as declared with "var"
{
    return;
    int i = 0;
    if (ops == NULL || ops->codes == NULL)
        return;
    while (i < ops->code_len) {
        if (ops->codes[i].op == OP_PUSHVAR)
            ops->codes[i].local = 1;
        i++;
    }
}

static jsi_ForinVar *forinvar_new(jsi_Pstate *pstate, const char *varname, Jsi_OpCodes *local, Jsi_OpCodes *lval)
{
    jsi_ForinVar *r = (jsi_ForinVar*)Jsi_Calloc(1,sizeof(*r));
    r->sig = JSI_SIG_FORINVAR;
    r->varname = varname;
    r->local = local;
    r->lval = lval;
    return r;
}

static Jsi_OpCodes *make_forin(Jsi_OpCodes *lval, jsi_Pline *line, Jsi_OpCodes *expr, Jsi_OpCodes *stat, const char *label, int isof)
{
    Jsi_OpCodes *keycode = code_key();
    keycode->codes[0].isof = isof;
    keycode->codes[0].Line = line->first_line;
    Jsi_OpCodes *init = codes_join(expr, keycode);
    Jsi_OpCodes *cond = codes_join3(lval, code_next(),
                                   code_jfalse(stat->code_len + 2));
    Jsi_OpCodes *stat_jmp = code_jmp(-(cond->code_len + stat->code_len));
    code_reserved_replace(stat, 1, 0, label, 2);
    return codes_join3(codes_join(init, cond), 
                          codes_join(stat, stat_jmp), code_pop(2));
}

static jsi_CaseExprStat *exprstat_new(jsi_Pstate *pstate, Jsi_OpCodes *expr, Jsi_OpCodes *stat, int isdef)
{
    jsi_CaseExprStat *r = (jsi_CaseExprStat*)Jsi_Calloc(1,sizeof(*r));
    r->sig = JSI_SIG_CASESTAT;
    r->expr = expr;
    r->stat = stat;
    r->isdefault = isdef;
    return r;
}

static jsi_CaseList *caselist_new(jsi_Pstate *pstate, jsi_CaseExprStat *es)
{
    jsi_CaseList *a = (jsi_CaseList*)Jsi_Calloc(1,sizeof(*a));
    a->sig = JSI_SIG_CASELIST;
    a->es = es;
    a->tail = a;
    return a;
}

static jsi_CaseList *caselist_insert(jsi_Pstate *pstate, jsi_CaseList *a, jsi_CaseExprStat *es)
{
    jsi_CaseList *b = (jsi_CaseList*)Jsi_Calloc(1,sizeof(*b));
    a->sig = JSI_SIG_CASELIST;
    b->es = es;
    a->tail->next = b;
    a->tail = b;
    return a;
}

static void caselist_free(jsi_CaseList *c)
{
    jsi_CaseList *a = c;
    while (a) {
        a = c->next;
        if (c->es) Jsi_Free(c->es);
        Jsi_Free(c);
        c = a;
    }
}

static Jsi_OpCodes *opassign(jsi_Pstate *pstate, jsi_Pline *line, Jsi_OpCodes *lval, Jsi_OpCodes *oprand, Jsi_OpCodes *op)
{
    Jsi_OpCodes *ret;
    if ((lval)->lvalue_flag == 1) {
        ret = codes_join3(lval, 
                             codes_join3(code_push_top(), oprand, op),
                             code_assign(pstate, line, 1));
    } else {
        ret = codes_join3(lval,
                             codes_join4(code_push_top2(), code_subscript(pstate, line, 1), oprand, op),
                             code_assign(pstate, line, 2));
    }
    return ret;
}

#endif
#endif
#ifndef JSI_LITE_ONLY
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif
#define COMMENT (-128)

static int lexer_getchar(jsi_Lexer *lex)
{
    if (!lex || !lex->pstate) { fprintf(stderr, "No lexer init\n"); return -1; }
    Jsi_Interp *interp = lex->pstate->interp;
    int c = 0;
    if (lex->ltype == LT_FILE) {
        c = Jsi_Getc(interp, lex->d.fp);
        if (c == EOF) c = 0;
    } else {
        if (lex->ungot) {
            c = lex->unch[--lex->ungot];
        } else {
            c = lex->d.str[lex->cur];
            if (c != 0) lex->cur++;
        }
        if (lex->inStr && c == '\\') {
            int nc = (lex->ungot ? lex->unch[lex->ungot-1] : lex->d.str[lex->cur-1]);
            if (nc == '\n') {
                return lexer_getchar(lex);
                return lexer_getchar(lex);
            }
        }
    }
    //printf("%c", c);
    if (c == '\n') {
        //printf("!!!!!!!!!!\n");
        lex->cur_line++;
        lex->cur_char = 0;
    }
    lex->cur_char++;
    //printf("<%2x\n",c);
    return c;
}

static void lexer_ungetc(int c, jsi_Lexer *lex)
{
    if (!lex || !lex->pstate) { fprintf(stderr, "No lexer init\n"); return; }
    Jsi_Interp *interp = lex->pstate->interp;
    if (!c) return;
    if (c == '\n') {
        lex->cur_line--;
    }

    //printf("^<%c>", c);
    if (lex->ltype == LT_FILE) {
        Jsi_Ungetc(interp, lex->d.fp, c);
    } else if (lex->ungot<=0 && lex->cur>0 && c == lex->d.str[lex->cur-1]) {
        lex->cur--;
    } else if ((lex->ungot+2)<(int)sizeof(lex->unch)) {
        lex->unch[lex->ungot++] = c;
    }
    //printf(">%2x\n",c);
}

Jsi_RC jsi_InitLexer(Jsi_Interp *interp, int release)
{
    static struct st_kw {
        const char *name;
        uintptr_t value;
    } keywords[] = {
        { "if", IF },
        { "else", ELSE },
        { "for", FOR },
        { "in", IN },
        { "while", WHILE },
        { "do", DO },
        { "continue", CONTINUE },
        { "switch", SWITCH },
        { "case", CASE },
        { "default", DEFAULT },
        { "break", BREAK },
        { "function", FUNC },
        { "return", RETURN },
        { "var", LOCAL },
        { "of", OF },
        { "new", NEW },
        { "delete", DELETE },
        { "try", TRY },
        { "catch", CATCH },
        { "throw", THROW },
        { "finally", FINALLY },
        { "with", WITH },
        { "undefined", UNDEF },
        { "true", _TRUE },
        { "false", _FALSE },
        { "this", _THIS },
        { "arguments", ARGUMENTS },
        { "void", VOID },
        { "typeof", TYPEOF },
        { "instanceof", INSTANCEOF },
        { "string", TYPESTRING },
        { "number", TYPENUMBER },
        { "regexp", TYPEREGEXP },
        { "any", TYPEANY },
        { "userobj", TYPEUSEROBJ },
        { "iterobj", TYPEITEROBJ },
        { "object", TYPEOBJECT },
        { "boolean", TYPEBOOLEAN },
        { "array", TYPEARRAY },
        { "null", TYPENULL },
        { "...", ELLIPSIS },
        { "debugger", __DEBUG }
    };
    uint i;
    Jsi_HashEntry *hPtr;
    if (release) return JSI_OK;
    if (!interp->lexkeyTbl->numEntries) {
        bool isNew;
        for (i = 0; i < sizeof(keywords) / sizeof(struct st_kw); ++i) {
            hPtr = Jsi_HashEntryNew(interp->lexkeyTbl, keywords[i].name, &isNew);
            assert(hPtr);
            if (hPtr)
                Jsi_HashValueSet(hPtr, (void*)keywords[i].value);
        }
    }
    return JSI_OK;
}

static int jsi_iskey(const char *word, jsi_Lexer *lex)
{
    Jsi_Interp *interp = lex->pstate->interp;
    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->lexkeyTbl, word);
    if (hPtr)
        return (uintptr_t)Jsi_HashValueGet(hPtr);
    return 0;
}

static Jsi_String* jsi_do_string(jsi_Lexer *lex)
{
    Jsi_Interp *interp = lex->pstate->interp;
    lex->inStr = 1;
    int n, c = lexer_getchar(lex);
    int endchar = c, isnull = 0;
    uint32_t flags = 0;
    
    int bufi = 0, bsiz, done = 0;
    char unibuf[bsiz=JSI_BUFSIZ], *buf = unibuf, *ret = NULL;
    
    while (!done) {
        if (bufi >= (bsiz-5)) {
            int nsiz = bsiz+=JSI_BUFSIZ;
            if (buf!=unibuf) 
                buf = (char*)Jsi_Realloc(buf, nsiz);
            else {
                buf = (char*)Jsi_Malloc(nsiz);
                memcpy(buf, unibuf, sizeof(unibuf));
            }
            bsiz = nsiz;
        }
        c = lexer_getchar(lex);
        if (c == EOF || c == 0) {
            goto saw_eof;
        }
        if (c == '\\') {
            n = lexer_getchar(lex);
            switch(n) {
                case 'b': buf[bufi++] = '\b'; break;
                case 'f': buf[bufi++] = '\f'; break;
                case 'n': buf[bufi++] = '\n'; break;
                case 'r': buf[bufi++] = '\r'; break;
                case 't': buf[bufi++] = '\t'; break;
                case '0': buf[bufi++] = 0; isnull=1; break;
                case 'u': {
                    char ibuf[5];
                    int ui;
                    for (ui=0; ui<4; ui++)
                        ibuf[ui] = lexer_getchar(lex);
                    ibuf[4] = 0;
                    ui = Jsi_UtfDecode(ibuf, buf+bufi);
                    if (ui>0) {
                        if (!buf[bufi])
                            isnull = 1;
                        bufi+=ui;
                    } else {
                        Jsi_LogError("Unexpected utf encoding.");
                        buf[bufi++] = 0;
                        goto done;
                    }
                    break;
                }
                case EOF: 
                case 0:
saw_eof:
                    Jsi_LogError("Unexpected EOF parsing string.");
                    buf[bufi++] = 0;
                    goto done;
                case '"':
                case '\'':
                case '\\':
                case '\n':
                    buf[bufi++] = n;
                    break;
                default: 
                    buf[bufi++] = n;
                    if (!interp->unitTest)
                        Jsi_LogParse("Unsupported string escape: \\%c", n);
            }
        } else {
            buf[bufi++] = c;
        }
        if (c == endchar) {
            bufi --;
            break;
        }
    }
    buf[bufi] = 0;
    if (!isnull)
        ret = (char*)Jsi_KeyAdd(lex->pstate->interp, buf);
    else {
        flags |= 1;
        if (buf == unibuf) {
            buf = (char*)Jsi_Malloc(bufi+1);
            memcpy(buf, unibuf, bufi+1);
        }
        ret = buf;
        buf = unibuf;
    }
done:
    if (buf != unibuf)
        Jsi_Free(buf);
    lex->inStr = 0;
    if (!ret)
        return NULL;
    Jsi_String *s = (typeof(s))Jsi_Calloc(1, sizeof(*s));
    s->str = ret;
    s->len = bufi;
    s->flags = flags;
    Jsi_HashSet(lex->pstate->strTbl, s, s);
    return s;
}

static char *jsi_do_regex(jsi_Lexer *lex)
{
    Jsi_Interp *interp = lex->pstate->interp;
    int n, bufi = 0, bsiz;
    char unibuf[bsiz=JSI_BUFSIZ], *buf = unibuf, *ret = NULL;
    
    buf[bufi++] = lexer_getchar(lex);     /* first '/'*/
    while (1) {
        if (bufi >= (bsiz-5)) {
            int nsiz = bsiz+=JSI_BUFSIZ;
            if (buf!=unibuf) 
                buf = (char*)Jsi_Realloc(buf, nsiz);
            else {
                buf = (char*)Jsi_Malloc(nsiz);
                memcpy(buf, unibuf, sizeof(unibuf));
            }
            bsiz = nsiz;
        }
        int c = lexer_getchar(lex);
        if (c == EOF || c == 0) {
            goto saw_eof;
        }
        if (c == '\\') {
            n = lexer_getchar(lex);
            if (n == EOF || c == 0) {
saw_eof:
                Jsi_LogError("Unexpected overflow or EOF parsing regular expression.");
                buf[bufi++] = 0;
                goto done;
            }
            
            buf[bufi++] = c;
            buf[bufi++] = n;
        } else if (c == '/') {
            buf[bufi++] = '/';
            while (1) {
                if (bufi >= (bsiz-5))
                    goto saw_eof;
                buf[bufi++] = c = lexer_getchar(lex);
                if (!isalnum(c)) break;
            }
            buf[bufi-1] = 0;
            lexer_ungetc(c, lex);
            break;
        } else {
            buf[bufi++] = c;
        }
    }
    ret = (char*)Jsi_KeyAdd(lex->pstate->interp, buf);
    ret = Jsi_Strdup(buf);
done:
    if (buf != unibuf)
        Jsi_Free(buf);
    return ret;
}

static int jsi_do_sign(jsi_Lexer *lex)
{
    static struct st_sn {
        const char *name;
        int len;
        int value;
    } signs[] = {
        { ">>>=", 4, URSHFAS },
        { "<<=", 3, LSHFAS },
        { ">>=", 3, RSHFAS },
        { "===", 3, EEQU },
        { "!==", 3, NNEQ },
        { ">>>", 3, URSHF },
        { "==", 2, EQU },
        { "!=", 2, NEQ },
        { "<=", 2, LEQ },
        { ">=", 2, GEQ },
        { "++", 2, INC },
        { "--", 2, DEC },
        { "&&", 2, AND },
        { "||", 2, OR },
        { "+=", 2, ADDAS },
        { "-=", 2, MNSAS },
        { "*=", 2, MULAS },
        { "/=", 2, DIVAS },
        { "%=", 2, MODAS },
        { "&=", 2, BANDAS },
        { "|=", 2, BORAS },
        { "^=", 2, BXORAS },
        { "<<", 2, LSHF },
        { ">>", 2, RSHF }
    };

    int bufi;
    char buf[4];
    uint i;
    for (bufi = 0; bufi < 4; ++bufi) {
        int c = lexer_getchar(lex);
        if (c == 0 || c == '\n') break;
        buf[bufi] = c;
    }
    if (!bufi) return 0;
    
    for (i = 0; i < sizeof(signs)/sizeof(struct st_sn); ++i) {
        if (bufi < signs[i].len) continue;
        if (strncmp(buf, signs[i].name, signs[i].len) == 0) {
            int j;
            for (j = bufi - 1; j >= signs[i].len; --j)
                lexer_ungetc(buf[j], lex);

            return signs[i].value;
        }
    }
    
    for (i = bufi - 1; i >= 1; --i)
        lexer_ungetc(buf[i], lex);
    
    return buf[0];
}

#define LOCATION_START(loc, lex) do {       \
    (loc)->first_line = (lex)->cur_line;    \
    (loc)->first_column = (lex)->cur_char;  \
    } while(0)
#define LOCATION_END(loc, lex) do {         \
    (loc)->last_line = (lex)->cur_line;     \
    (loc)->last_column = (lex)->cur_char;   \
    } while(0)

static void jsi_eat_comment(jsi_Lexer *lex)
{
    Jsi_Interp *interp = lex->pstate->interp;
    int c;
    while((c = lexer_getchar(lex))) {
        if (c == '*') {
            c = lexer_getchar(lex);
            if (c == '/') return;
            lexer_ungetc(c, lex);
        }
    }
    Jsi_LogError("Comment reach end of file");
}

static int jsi_yylex (YYSTYPE *yylvalp, YYLTYPE *yyllocp, jsi_Lexer *lex)
{
    int c, c2;
    Jsi_Interp *interp = lex->pstate->interp;
    
    char word[JSI_BUFSIZ];
    int wi = 0;

    while ((c = lexer_getchar(lex)) == ' ' || c == '\t' || c == '\n' || c == '\r');
    LOCATION_START(yyllocp, lex);
    
    if (c=='.') {
        c2 = lexer_getchar(lex);
        if (!isdigit(c2)) {
            lexer_ungetc(c2, lex);
        } else {
            word[wi++] = c;
            c = c2;
        }
    }
            
    if (isdigit(c)) {
        int base = 10, digCnt = 1, isdig, cpre=0;
        Jsi_Number fval;
        char *eptr = NULL;
        word[wi++] = c;
        while (wi < 1020) {
            c = lexer_getchar(lex);
            isdig = isxdigit(c);
            if (isdig)
                digCnt++;
            if (isdig || c == '.' || toupper(c)=='P' || toupper(c)=='E'
                || (toupper(c)=='X' && wi==1 && word[0] == '0')
                || ((c == '-' || c == '+') && toupper(cpre)=='E')
                || (base == 16 && isxdigit(c))) {
                if (toupper(c)=='X')
                    base = 16;
                word[wi++] = c;
                cpre = c;
                continue;
            }
            lexer_ungetc(c, lex);
            break;
        }
        word[wi] = 0;

        if (word[0] == '0' && isdigit(word[1]) && base!=16)
            fval = (Jsi_Number)strtoll(word, &eptr, 8);
        else if (word[0] == '0' && (digCnt+(base==16)) == wi)
            fval = (Jsi_Number)strtoll(word, &eptr, base);
        else
            fval = (Jsi_Number)strtod(word, &eptr);
        LOCATION_END(yyllocp, lex);
        if (eptr == NULL || *eptr) {
            Jsi_LogError("invalid number: %s", word);
            return 0;
        }
        Jsi_Number *db = (Jsi_Number *)Jsi_Malloc(sizeof(Jsi_Number));
        *db = fval;
        yylvalp->num = db;
        return FNUMBER;
    } else if (c == '"' || c == '\'') {
        lexer_ungetc(c, lex);
        yylvalp->vstr = jsi_do_string(lex);
        if (!yylvalp->vstr)
            return 0;
        LOCATION_END(yyllocp, lex);
        return STRING;
    } else if (isalpha(c) || c == '_' || c == '$') {
        lexer_ungetc(c, lex);
        while (wi < 1020) {
            c = lexer_getchar(lex);
            if (!isalnum(c) && c != '_' && c != '$') break;
            word[wi++] = c;
        }
        lexer_ungetc(c, lex);
        
        word[wi] = 0;
        int r = jsi_iskey(word, lex);
        if (r) return r;
        yylvalp->sstr = (char*)Jsi_KeyAdd(interp,word);
        LOCATION_END(yyllocp, lex);
        return IDENTIFIER;
    } else if (c == '/') {
        int d = lexer_getchar(lex);
        if (d == '/') {
            while ((d = lexer_getchar(lex)) != '\r' && d != '\n' && d != 0);
            return COMMENT;
        } else if (d == '*') {
            jsi_eat_comment(lex);
            return COMMENT;
        } else lexer_ungetc(d, lex);
        
        if (lex->last_token == '=' || lex->last_token == '(' || lex->last_token == ':'
            || lex->last_token == '?' || lex->last_token == ',' || lex->last_token == 0
            || lex->last_token == '[' || lex->last_token == '{')
        {
            lexer_ungetc(c, lex);
            
            char *regtxt = jsi_do_regex(lex);
            if (!regtxt)
                return 0;
            Jsi_Regex *re = Jsi_RegExpNew(interp, regtxt, JSI_REG_STATIC);
            if (!(yylvalp->regex = re)) {
                 Jsi_Free(regtxt);
                 return -1;
            }
            Jsi_Free(regtxt);
            return REGEXP;
        }
    }
    
    lexer_ungetc(c, lex);
    
    int r = jsi_do_sign(lex);
    LOCATION_END(yyllocp, lex);
    return r;
}

int yylex (YYSTYPE *yylvalp, YYLTYPE *yyllocp, jsi_Pstate *pstate)
{
    int ret;
    do {
        ret = jsi_yylex(yylvalp, yyllocp, pstate->lexer);
    } while (ret == COMMENT);
/*
    if (ret < 128 && ret > 0) printf("%c\n", ret);
    else printf("%d\n", ret);
*/
    pstate->lexer->last_token = ret;
    return ret;
}

void yyerror(YYLTYPE *yylloc, jsi_Pstate *ps, const char *msg)
{
    Jsi_Interp *interp = ps->interp;
    interp->errLine = yylloc->first_line;
    interp->errCol = yylloc->first_column;
    Jsi_LogParse("%s:%d.%d: error: %s", interp->curFile?interp->curFile:"@", yylloc->first_line, 
        yylloc->first_column, msg);
    /*if (interp->curFile)
        fprintf(stderr, "%s:%d.%d: %s\n",  interp->curFile, yylloc->first_line, yylloc->first_column, msg);
    else
        fprintf(stderr, "@%d.%d: %s\n", yylloc->first_line, yylloc->first_column, msg);*/
    ps->err_count++;
}
#endif
#ifndef JSI_LITE_ONLY
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif

/* Return value from call to function will is not used. */
bool Jsi_FunctionReturnIgnored(Jsi_Interp *interp, Jsi_Func *funcPtr) {
    return funcPtr->callflags.bits.isdiscard;
}

bool Jsi_FunctionIsConstructor(Jsi_Func *funcPtr)
{
    return (funcPtr->f.bits.iscons);
}

Jsi_CmdSpec *Jsi_FunctionGetSpecs(Jsi_Func *funcPtr)
{
    return funcPtr->cmdSpec;
}

void *Jsi_FunctionPrivData(Jsi_Func *funcPtr)
{
    return funcPtr->privData;
}

const char *jsi_TypeName(Jsi_Interp *interp, Jsi_ttype otyp)
{
    switch (otyp) {
        case JSI_TT_NUMBER:     return "number"; 
        case JSI_TT_STRING:     return "string"; 
        case JSI_TT_BOOLEAN:    return "boolean"; 
        case JSI_TT_ARRAY:      return "array"; 
        case JSI_TT_FUNCTION:   return "function"; 
        case JSI_TT_OBJECT:     return "object"; 
        case JSI_TT_REGEXP:     return "regexp"; 
        case JSI_TT_ANY:        return "any"; 
        case JSI_TT_USEROBJ:    return "userobj"; 
        case JSI_TT_ITEROBJ:    return "iterobj"; 
        case JSI_TT_UNDEFINED:      return "undefined";
        case JSI_TT_VOID:       return "void";
        case JSI_TT_NULL:       return "null"; 
    }
    return "undefined";
}
const char *jsi_ObjectTypeName(Jsi_Interp *interp, Jsi_otype otyp)
{
    switch (otyp) {
        case JSI_OT_NUMBER:     return "number"; 
        case JSI_OT_STRING:     return "string"; 
        case JSI_OT_BOOL:       return "boolean"; 
        case JSI_OT_ARRAY:      return "array"; 
        case JSI_OT_FUNCTION:   return "function"; 
        case JSI_OT_OBJECT:     return "object"; 
        case JSI_OT_REGEXP:     return "regexp"; 
        case JSI_OT_ITER:       return "iter"; 
        case JSI_OT_USEROBJ:    return "userobj"; 
        case JSI_OT_UNDEF:      return "any";
    }
    return "undefined";
}

const char *jsi_ValueTypeName(Jsi_Interp *interp, Jsi_Value *val)
{
    switch (val->vt) {
        case JSI_VT_NUMBER:     return "number"; 
        case JSI_VT_STRING:     return "string"; 
        case JSI_VT_BOOL:       return "boolean"; 
        case JSI_VT_OBJECT:     if (val->d.obj->ot == JSI_OT_OBJECT && val->d.obj->isarrlist) return "array"; return jsi_ObjectTypeName(interp, val->d.obj->ot); 
        case JSI_VT_VARIABLE:   return "variable"; 
        case JSI_VT_NULL:       return "null"; 
        case JSI_VT_UNDEF:      break;
    }
    return "undefined";
}

int jsi_typeGet(Jsi_Interp *interp, const char *tname) {
    if (!tname)
        return 0;
    if (Jsi_Strchr(tname, '|')) {
        int argc, i, rc, val = 0;
        char **argv;
        Jsi_DString dStr;
        Jsi_DSInit(&dStr);
        Jsi_SplitStr(tname, &argc, &argv, "|", &dStr);
        for (i=0; i<argc; i++) {
            rc = jsi_typeGet(interp, argv[i]);
            if (rc < 0)
                break;
            val |= rc;
        }
        Jsi_DSFree(&dStr);
        if (i<argc)
            return -1;
        return val;
    }
    switch (tname[0]) {
        case 'b': if (Jsi_Strcmp(tname, "boolean")==0) return JSI_TT_BOOLEAN; break;
        case 's': if (Jsi_Strcmp(tname, "string")==0) return JSI_TT_STRING; break;
        case 'n': if (Jsi_Strcmp(tname, "null")==0) return JSI_TT_NULL;
                  if (Jsi_Strcmp(tname, "number")==0) return JSI_TT_NUMBER; break;
        case 'o': if (Jsi_Strcmp(tname, "object")==0) return JSI_TT_OBJECT; break;
        case 'r': if (Jsi_Strcmp(tname, "regexp")==0) return JSI_TT_REGEXP; break;
        case 'f': if (Jsi_Strcmp(tname, "function")==0) return JSI_TT_FUNCTION; break;
        case 'i': if (Jsi_Strcmp(tname, "iterobj")==0) return JSI_TT_ITEROBJ;
        case 'u': if (Jsi_Strcmp(tname, "userobj")==0) return JSI_TT_USEROBJ;
                  if (Jsi_Strcmp(tname, "undefined")==0) return JSI_TT_UNDEFINED; break;
        case 'a': if (Jsi_Strcmp(tname, "array")==0) return JSI_TT_ARRAY;
                  if (Jsi_Strcmp(tname, "any")==0) return JSI_TT_ANY; break;
        case 'v': if (Jsi_Strcmp(tname, "void")==0) return JSI_TT_VOID; break;
    }
    Jsi_LogWarn("Type \"%s\" is not one of boolean, string, number, function, array, object, regexp, userobj, null, undefined, void or any", tname);
    return 0;
}

const char *jsi_typeName(Jsi_Interp *interp, int typ, Jsi_DString *dStr) {
    if (typ<=0 || (typ&JSI_TT_ANY)) {
        Jsi_DSAppend(dStr, "any", NULL);
        return Jsi_DSValue(dStr);
    }
    int i = 0;
    if (typ&JSI_TT_NUMBER) Jsi_DSAppend(dStr, (i++?"|":""), "number", NULL);
    if (typ&JSI_TT_STRING) Jsi_DSAppend(dStr, (i++?"|":""), "string", NULL);
    if (typ&JSI_TT_BOOLEAN)  Jsi_DSAppend(dStr, (i++?"|":""), "boolean", NULL);
    if (typ&JSI_TT_ARRAY)   Jsi_DSAppend(dStr, (i++?"|":""), "array", NULL);
    if (typ&JSI_TT_FUNCTION) Jsi_DSAppend(dStr, (i++?"|":""), "function", NULL);
    if (typ&JSI_TT_OBJECT) Jsi_DSAppend(dStr, (i++?"|":""), "object", NULL);
    if (typ&JSI_TT_REGEXP) Jsi_DSAppend(dStr, (i++?"|":""), "regexp", NULL);
    if (typ&JSI_TT_USEROBJ) Jsi_DSAppend(dStr, (i++?"|":""), "userobj", NULL);
    if (typ&JSI_TT_ITEROBJ) Jsi_DSAppend(dStr, (i++?"|":""), "iterobj", NULL);
    if (typ&JSI_TT_NULL) Jsi_DSAppend(dStr, (i++?"|":""), "null", NULL);
    if (typ&JSI_TT_UNDEFINED) Jsi_DSAppend(dStr, (i++?"|":""), "undefined", NULL);
    if (typ&JSI_TT_VOID) Jsi_DSAppend(dStr, (i++?"|":""), "void", NULL);
    return Jsi_DSValue(dStr);
}

const char* jsi_FuncGetCode(Jsi_Interp *interp, Jsi_Func *func, int *lenPtr) {
    if (interp->subOpts.noFuncString || !func->bodyStr)
        return NULL;
    const char *cp, *cp2;
    if (func->startPos == -1) {
        cp = func->bodyStr;
        int cplin = func->bodyline.last_line-1;
        while (*cp && cplin>0) {
            if (*cp=='\n' && --cplin<=0)
                break;
            cp++;
        }
        while (*cp && isspace(*cp))
            cp++;
        func->startPos = (*cp?(cp - func->bodyStr):-2);
    }
    if (func->startPos >= 0) {
        int len = func->endPos - func->startPos;
        cp = func->bodyStr + func->startPos;
        while (len>0 && (isspace(cp[len-1]) || cp[len-1]==';')) len--;
        if (*cp != 'f' && Jsi_Strncmp(cp, "function", 8) && (cp2=Jsi_Strstr(cp, "function"))) {
            len -= (cp2-cp);
            cp = cp2;
        }
        *lenPtr = len;
        return cp;
    }
    return NULL;
}

const char *jsiFuncInfo(Jsi_Interp *interp, Jsi_DString *dStr, Jsi_Func* func, Jsi_Value *arg) {
    if (!func) return "";
    if (func->name)
        Jsi_DSPrintf(dStr, ", in call to '%s'", func->name);
    else
        Jsi_DSPrintf(dStr, ", in call to function");
    if (func->script) {
        const char *cp = Jsi_Strrchr(func->script, '/');
        if (cp)
            cp++;
        else
            cp = func->script;
        Jsi_DSPrintf(dStr, " declared at %s:%d.%d", cp, func->bodyline.first_line, func->bodyline.first_column);
    }
        if (arg) {
        Jsi_DSAppend(dStr, " <", NULL);
        Jsi_ValueGetDString(interp, arg, dStr, 0);
        Jsi_DSAppend(dStr, ">.", NULL);
    }
    return Jsi_DSValue(dStr);
}

// Check argument matches type.  If func is null, this is a parse. An index of 0 is the return value.
Jsi_RC jsi_ArgTypeCheck(Jsi_Interp *interp, int typ,  Jsi_Value *arg, const char *p1,
    const char *p2, int index, Jsi_Func *func, bool isdefault) {
    Jsi_RC rc = JSI_OK;
    char idxBuf[200];
    idxBuf[0] = 0;
    if (func && arg->vt == JSI_VT_UNDEF && !interp->typeCheck.noundef && index>0 && !isdefault && !(typ&JSI_TT_UNDEFINED)) {
        snprintf(idxBuf, sizeof(idxBuf), " arg %d", index);
        jsi_TypeMismatch(interp);
       
        Jsi_DString fStr = {};
        rc = Jsi_LogType("call with undefined var %s%s '%s'%s", p1, idxBuf, p2, jsiFuncInfo(interp, &fStr, func, arg));
        Jsi_DSFree(&fStr);
        return rc;
    }
    if (typ <= 0)
        return JSI_OK;
    //if (typ&JSI_TT_VOID)
    //    return JSI_OK;
    if (interp->typeCheck.all==0) {
        if (func ? (interp->typeCheck.run==0) : (interp->typeCheck.parse==0))
            return JSI_OK;
    }
    if (index == 0 && func && func->type == FC_BUILDIN && 
        interp->typeCheck.all == 0) // Normally do not check return types for builtins.
        return JSI_OK; 
    if ((typ&JSI_TT_ANY)) return JSI_OK;
    if (index == 0 && arg->vt == JSI_VT_UNDEF) {
        if (!(typ&JSI_TT_VOID)) 
            goto done;
        return JSI_OK;
    }
    if (isdefault && index && typ&JSI_TT_VOID && arg->vt == JSI_VT_UNDEF)
        return JSI_OK;
    if (typ&JSI_TT_UNDEFINED && Jsi_ValueIsUndef(interp, arg)) return rc;
    if (typ&JSI_TT_NUMBER && Jsi_ValueIsNumber(interp, arg)) return rc;
    if (typ&JSI_TT_STRING && Jsi_ValueIsString(interp, arg)) return rc;
    if (typ&JSI_TT_BOOLEAN && Jsi_ValueIsBoolean(interp, arg))  return rc;
    if (typ&JSI_TT_ARRAY && Jsi_ValueIsArray(interp, arg))   return rc;
    if (typ&JSI_TT_FUNCTION && Jsi_ValueIsFunction(interp, arg)) return rc;
    if (typ&JSI_TT_REGEXP && Jsi_ValueIsObjType(interp, arg, JSI_OT_REGEXP)) return rc;
    if (typ&JSI_TT_USEROBJ && Jsi_ValueIsObjType(interp, arg, JSI_OT_USEROBJ)) return rc;
    if (typ&JSI_TT_ITEROBJ && Jsi_ValueIsObjType(interp, arg, JSI_OT_ITER)) return rc;
    if (typ&JSI_TT_OBJECT && (
        Jsi_ValueIsObjType(interp, arg, JSI_OT_OBJECT) && Jsi_ValueIsArray(interp, arg)==0))
        return rc;
    if (typ&JSI_TT_NULL && Jsi_ValueIsNull(interp, arg)) return rc;
done:
    {
        Jsi_DString dStr = {};
        const char *exp = jsi_typeName(interp, typ, &dStr);
        const char *vtyp = jsi_ValueTypeName(interp, arg);
        if (index>0)
            snprintf(idxBuf, sizeof(idxBuf), " arg %d", index);
        if (interp->typeCheck.error)
            rc = JSI_ERROR;
        jsi_TypeMismatch(interp);
        Jsi_DString fStr = {};
        rc = Jsi_LogType("type mismatch %s%s '%s': expected \"%s\" but got \"%s\"%s",
            p1, idxBuf, p2, exp, vtyp, jsiFuncInfo(interp, &fStr, func, arg));
        Jsi_DSFree(&fStr);
        Jsi_DSFree(&dStr);
    }
    return rc;
}

Jsi_RC jsi_StaticArgTypeCheck(Jsi_Interp *interp, int atyp, const char *p1, const char *p2, int index, Jsi_Func *func, jsi_Pline *line) {
    Assert(index>0);
    Jsi_RC rc;
    if (interp->typeCheck.parse==0 && interp->typeCheck.all==0)
        return JSI_OK;
    int ai = index-1+func->callflags.bits.addargs;
    if (func->argnames == NULL || ai>=func->argnames->count || ai<0)
        return JSI_OK;
    int typ = func->argnames->args[ai].type;
    if (typ <= 0)
        return JSI_OK;
    if (index == 0 && func && func->type == FC_BUILDIN && 
        interp->typeCheck.all==0) // Normally do not check return types for builtins.
        return JSI_OK; 
    if ((typ&JSI_TT_ANY)) return JSI_OK;
    if (index == 0 && atyp == JSI_VT_UNDEF) {
        if (!(typ&JSI_TT_VOID)) 
            goto done;
        return JSI_OK;
    }
/*    if (index == 0 && (typ&JSI_TT_VOID)) {
        if (atyp != JSI_VT_UNDEF && !(typ&JSI_TT_UNDEFINED))
            goto done;
        return JSI_OK;
    }*/
    if (atyp == JSI_VT_UNDEF)
        return JSI_OK;
    rc = JSI_OK;
    if (typ&JSI_TT_UNDEFINED && atyp == JSI_TT_UNDEFINED) return rc;
    if (typ&JSI_TT_NUMBER && atyp==JSI_TT_NUMBER) return rc;
    if (typ&JSI_TT_STRING && atyp==JSI_TT_STRING) return rc;
    if (typ&JSI_TT_BOOLEAN && atyp==JSI_TT_BOOLEAN)  return rc;
    if (typ&JSI_TT_ARRAY && atyp==JSI_TT_ARRAY)   return rc;
    if (typ&JSI_TT_FUNCTION && atyp==JSI_TT_FUNCTION) return rc;
    if (typ&JSI_TT_REGEXP && atyp==JSI_TT_REGEXP) return rc;
    if (typ&JSI_TT_USEROBJ && atyp==JSI_TT_USEROBJ) return rc;
    if (typ&JSI_TT_ITEROBJ && atyp==JSI_TT_ITEROBJ) return rc;
    if (typ&JSI_TT_OBJECT && atyp==JSI_TT_OBJECT) return rc;
    if (typ&JSI_TT_NULL && atyp==JSI_TT_NULL) return rc;
done:
    {
        Jsi_DString dStr = {};
        const char *exp = jsi_typeName(interp, typ, &dStr);
        const char *vtyp = jsi_TypeName(interp, (Jsi_ttype)atyp);
        char idxBuf[200];
        idxBuf[0] = 0;
        if (index>0)
            snprintf(idxBuf, sizeof(idxBuf), " arg %d", index);
        if (line)
            interp->parseLine = line;
        if (interp->typeCheck.error)
            rc = JSI_ERROR;
        jsi_TypeMismatch(interp);
        Jsi_DString fStr = {};
        rc = Jsi_LogType("type mismatch %s%s '%s': expected \"%s\" but got \"%s\"%s",
            p1, idxBuf, p2, exp, vtyp, jsiFuncInfo(interp, &fStr, func, NULL));
        Jsi_DSFree(&fStr);
        Jsi_DSFree(&dStr);
    }
    return rc;
}

int jsiPopArgs(Jsi_OpCodes *argCodes, int i)
{
    int m=i-1, n = (uintptr_t)argCodes->codes[i].data, cnt = 0;
    if (argCodes->codes[i].op == OP_OBJECT)
        n *= 2;
    for (; m>=0 && cnt<n; m--, cnt++) {
        int op = argCodes->codes[m].op;
        if (op == OP_ARRAY || op == OP_OBJECT)
            m = jsiPopArgs(argCodes, m);
    }
    return m+1;
}

Jsi_RC jsi_RunFuncCallCheck(Jsi_Interp *interp, Jsi_Func *func, int argc, const char *name, jsi_Pline *line, Jsi_OpCodes *argCodes, bool isParse)
{
    Jsi_RC rc = JSI_OK;
    if (interp->typeCheck.all==0) {
        if (!argCodes ? (interp->typeCheck.run==0) : (interp->typeCheck.parse==0))
            return JSI_OK;
    }

    Jsi_CmdSpec *spec = func->cmdSpec;
    Jsi_ScopeStrs *ss = func->argnames;
    if (ss==NULL && spec == NULL)
        return JSI_OK;
    int i, minArgs, maxArgs, mis = 0, varargs = 0;
    char nbuf[100];
    if (func->type == FC_BUILDIN) {
        varargs =  (spec->maxArgs<0);
        maxArgs = spec->maxArgs + func->callflags.bits.addargs;
        minArgs = spec->minArgs + func->callflags.bits.addargs;
    } else {
        varargs = ss->varargs;
        minArgs = (ss->firstDef>0 ? ss->firstDef-1 : ss->count);
        maxArgs = ss->count;
        mis = (argc != ss->count);
        if (func->retType == 0 && ss && ss->typeCnt == 0 && interp->typeCheck.all==0)
            return JSI_OK;
    }
    if (varargs) {
        if (argc >= minArgs)
            return JSI_OK;
        mis = (argc<minArgs);
    } else 
        mis = (argc<minArgs || argc>maxArgs);
    if (mis) {
        if (varargs)
            snprintf(nbuf, sizeof(nbuf), "%d or more", minArgs);
        else if (maxArgs > minArgs)
            snprintf(nbuf, sizeof(nbuf), "%d-%d", minArgs, maxArgs);
        else
            snprintf(nbuf, sizeof(nbuf), "%d", maxArgs);
        if (line)
            interp->parseLine = line;
        if (interp->typeCheck.error)
            rc = JSI_ERROR;
        Jsi_DString dStr = {};
        Jsi_FuncObjToString(interp, func, &dStr, 2);
        if (isParse)
            Jsi_LogWarn("got %d args, expected %s, calling %s", argc, nbuf, Jsi_DSValue(&dStr));
        else
            rc = Jsi_LogType("got %d args, expected %s, calling %s", argc, nbuf, Jsi_DSValue(&dStr));
        jsi_TypeMismatch(interp);
        Jsi_DSFree(&dStr);
        if (line)
            interp->parseLine = NULL;
        return rc;
    }
    if (argCodes && argCodes->code_len>=argc) {
        int cl = argCodes->code_len;
        int aind=argc-1;
        for (i=cl-1; rc == JSI_OK && i>=0 && aind>=0; i--,aind--) {
            Jsi_ttype atyp = JSI_TT_ANY;
            switch (argCodes->codes[i].op) {
                case OP_PUSHSTR: atyp=JSI_TT_STRING; break;
                case OP_PUSHNUM: atyp=JSI_TT_NUMBER; break;
                case OP_PUSHBOO: atyp=JSI_TT_BOOLEAN; break;
                case OP_PUSHFUN: atyp=JSI_TT_FUNCTION; break;
                case OP_PUSHTHS: atyp=JSI_TT_OBJECT; break;
                case OP_PUSHREG: atyp=JSI_TT_REGEXP; break;
                case OP_PUSHUND: atyp=JSI_TT_VOID; break;
                case OP_PUSHNULL: atyp=JSI_TT_NULL; break;
                case OP_PUSHARG: atyp=JSI_TT_ARRAY; break;
                case OP_SUBSCRIPT: i++; break;
                case OP_ARRAY: atyp=JSI_TT_ARRAY; i=jsiPopArgs(argCodes, i); break;
                case OP_OBJECT: atyp=JSI_TT_OBJECT; i=jsiPopArgs(argCodes, i); break;
                default: break;
            }
            if (atyp == JSI_TT_ANY) continue;
            rc = jsi_StaticArgTypeCheck(interp, atyp, "for argument", name, aind+1, func, line);  
        }
    }
    return rc;
}

int jsi_BuiltinCmd(Jsi_Interp *interp, const char *name)
{
    Jsi_Value *val = Jsi_NameLookup(interp, name);
    if (!name)
        return 0;
    if (!Jsi_ValueIsFunction(interp, val))
        return 0;
    Jsi_Func *f = val->d.obj->d.fobj->func;
    return (f->type == FC_BUILDIN);
}

// Parse time function call checker.
void jsi_FuncCallCheck(jsi_Pstate *p, jsi_Pline *line, int argc, bool isNew, const char *name, const char *namePre, Jsi_OpCodes *argCodes)
{
    Jsi_Interp *interp = p->interp;
    if (name == NULL || !(interp->typeCheck.funcsig|interp->typeCheck.all|interp->typeCheck.parse))
        return;
    if (name && isdigit(name[0]))
        return;
    Jsi_Value *val;
    val = Jsi_NameLookup2(interp, name, namePre);
    Jsi_Func *f = NULL;
    if (val != NULL) {
        if (Jsi_ValueIsFunction(interp, val))
            f = val->d.obj->d.fobj->func;
    } else if (interp->staticFuncsTbl) {
        f = (Jsi_Func*)Jsi_HashGet(interp->staticFuncsTbl, (void*)name, 0);
    }
    if (f)
        jsi_RunFuncCallCheck(interp, f, argc, name, line, argCodes, 1);
    else if (interp->typeCheck.funcsig && (namePre==NULL || jsi_BuiltinCmd(interp, namePre))) {
        if (line)
            interp->parseLine = line;
        Jsi_LogWarn("called function '%s' with no previous definition", name);
        jsi_TypeMismatch(interp);
        if (line)
            interp->parseLine = NULL;
    }
}

int jsi_FuncSigsMatch(jsi_Pstate *pstate, Jsi_Func *f1, Jsi_Func *f2)
{
    // Skip where both functions have no types.
    if (f1->retType==0 && f1->argnames->typeCnt==0 && f1->argnames->varargs==0 &&
        f2->retType==0 && f2->argnames->typeCnt==0 && f2->argnames->varargs==0 &&
        pstate->interp->typeCheck.all==0)
        return 1;
    if (f1->retType != f2->retType)
        return 0;
    if (f1->argnames->count != f2->argnames->count)
        return 0;
    if (f1->argnames->typeCnt != f2->argnames->typeCnt)
        return 0;
    if (f1->argnames->varargs != f2->argnames->varargs)
        return 0;
    int i;
    for (i=0; i<f1->argnames->count; i++) {
        Jsi_ScopeStrs *a1 = f1->argnames, *a2 = f2->argnames;
        if (a1->args[i].type != a2->args[i].type)
            return 0;
        Jsi_Value *v1, *v2;
        v1 = a1->args[i].defValue;
        v2 = a2->args[i].defValue;
        if (v1==NULL && v2 == NULL)
            continue;
        if (v1==NULL || v2 == NULL)
            return 0;
        if (v1->vt != v2->vt)
            return 0;
        if (Jsi_ValueCmp(pstate->interp, v1, v2, 0))
            return 0;
    }
    return 1;
}

// Return directive from first instruction.
const char* jsi_GetDirective(Jsi_Interp *interp, Jsi_OpCodes *ops, const char *str) {
    if (!ops) return NULL;
    if (!ops->code_len) return NULL;
    if (ops->codes[0].op != OP_PUSHSTR || !ops->codes[0].data) return NULL;
    if (Jsi_Strncmp((char*)ops->codes[0].data, str, Jsi_Strlen(str))) return NULL;
    return (char*)ops->codes[0].data;
}

/* TODO: if not in a file (an eval) save copy of body string from pstate->lexer??? */
Jsi_Func *jsi_FuncMake(jsi_Pstate *pstate, Jsi_ScopeStrs *args, Jsi_OpCodes *ops, jsi_Pline* line, const char *name)
{
    Jsi_Interp *interp = pstate->interp;
    Jsi_ScopeStrs *localvar = jsi_ScopeGetVarlist(pstate);
    Jsi_Func *f = jsi_FuncNew(interp);
    jsi_Lexer *l = pstate->lexer;
    f->type = FC_NORMAL;
    f->opcodes = ops;
    f->argnames = args;
    f->localnames = localvar;
    f->script = interp->curFile;
    f->bodyline = *line;
    f->retType = (Jsi_otype)args->retType;
    if (!pstate->eval_flag) {
        f->scriptFile = f->script;
    }
    if (l->ltype == LT_STRING)
        f->bodyStr = l->d.str;
    f->endPos = l->cur;
    f->startPos = -1; // Have to get these from newline count.
    if (f->retType & JSI_TT_UNDEFINED)
        Jsi_LogWarn("illegal use of 'undefined' in a return type: %s", name?name:"");
    
    //f->strict = (jsi_GetDirective(interp, ops, "use strict") != NULL);
    pstate->argType = 0;
    if (localvar && args && (interp->strict)) {
        int i, j;
        for (i=0; i<args->count; i++) {
            for (j=0; j<args->count; j++) {
                if (i != j && !Jsi_Strcmp(args->args[i].name, args->args[j].name)) {
                        if (line)
                            interp->parseLine = line;
                        Jsi_LogWarn("function %s():  duplicate parameter name '%s'", name?name:"", args->args[i].name);
                        if (line)
                            interp->parseLine = NULL;
                        jsi_TypeMismatch(interp);
                        if (interp->typeCheck.error)
                            pstate->err_count++;
                }
            }
            for (j=0; j<localvar->count; j++) {
                if (!Jsi_Strcmp(localvar->args[j].name, args->args[i].name)) {
                        if (line)
                            interp->parseLine = line;
                        Jsi_LogWarn("function %s():  parameter name conflicts with 'var %s'", name?name:"", localvar->args[j].name);
                        if (line)
                            interp->parseLine = NULL;
                        jsi_TypeMismatch(interp);
                        if (interp->typeCheck.error)
                            pstate->err_count++;
                }
            }
        }
    }
    if (name) {
        f->name = Jsi_KeyAdd(interp, name);
        if ((interp->typeCheck.run|interp->typeCheck.parse|interp->typeCheck.all|interp->typeCheck.funcsig)) {
            
            if (f->retType && !(f->retType&JSI_TT_VOID) && ops && ops->code_len && ops->codes[ops->code_len-1].op != OP_RET) {
                if (line)
                    interp->parseLine = line;
                Jsi_LogWarn("missing return at end of function '%s'", name);
                if (line)
                    interp->parseLine = NULL;
                //if (interp->typeCheck.error)
                 //   pstate->err_count++;
            }
             
            if (interp->staticFuncsTbl) {
                Jsi_Func *fo = (Jsi_Func*)Jsi_HashGet(interp->staticFuncsTbl, (void*)name, 0);
                
                // Forward declaration signature compare (indicated by an empty body).
                if (interp->typeCheck.funcsig && fo && fo->opcodes && fo->opcodes->code_len == 1 && fo->opcodes->codes->op == OP_NOP) {
                    if (!jsi_FuncSigsMatch(pstate, f, fo)) {
                        if (line)
                            interp->parseLine = line;
                        Jsi_LogWarn("possible signature mismatch for function '%s' at %.120s:%d", name, fo->script, fo->bodyline.first_line);
                        if (line)
                            interp->parseLine = NULL;
                        jsi_TypeMismatch(interp);
                    }
                    //printf("OLD: %s\n", name);
                }
                Jsi_HashSet(interp->staticFuncsTbl, name, f);
            }
        }
    }
    return f;
}

Jsi_RC Jsi_FunctionArguments(Jsi_Interp *interp, Jsi_Value *func, int *argcPtr)
{
    Jsi_FuncObj *funcPtr;
    Jsi_Func *f;
    if (!Jsi_ValueIsFunction(interp, func))
        return JSI_ERROR;
    funcPtr = func->d.obj->d.fobj;
    f = funcPtr->func;
    SIGASSERT(f, FUNC);
    *argcPtr = f->argnames->count;
    return JSI_OK;
}

bool jsi_FuncIsNoop(Jsi_Interp* interp, Jsi_Value *func) {
    Jsi_FuncObj *funcPtr;
    Jsi_Func *f;
    if (func->vt != JSI_VT_OBJECT || func->d.obj->ot != JSI_OT_FUNCTION)
        return 0;
    funcPtr = func->d.obj->d.fobj;
    f = funcPtr->func;
    return (f->callback == jsi_NoOpCmd);
}

void jsi_InitLocalVar(Jsi_Interp *interp, Jsi_Value *arguments, Jsi_Func *who)
{
    SIGASSERTV(who, FUNC);
    if (who->localnames) {
        int i;
        for (i = 0; i < who->localnames->count; ++i) {
            const char *argkey = jsi_ScopeStrsGet(who->localnames, i);
            if (argkey) {
                DECL_VALINIT(key);// = VALINIT;
                Jsi_Value *v __attribute__((unused));
                Jsi_Value *kPtr = &key; // Note: a string key so no reset needed.
                Jsi_ValueMakeStringKey(interp, &kPtr, argkey);
                v = jsi_ValueObjKeyAssign(interp, arguments, kPtr, NULL, JSI_OM_DONTENUM);
                jsi_ValueDebugLabel(v, "locals", who->name);
            }
        }
    }
}

Jsi_RC jsi_FuncArgsToString(Jsi_Interp *interp, Jsi_Func *f, Jsi_DString *dStr, int withTypes)
{
    if (f->type == FC_NORMAL) {
        int i;
        for (i = 0; i < f->argnames->count; ++i) {
            jsi_ArgValue *av = f->argnames->args+i;
            if (i) Jsi_DSAppend(dStr, ", ", NULL);
            Jsi_DSAppend(dStr,  jsi_ScopeStrsGet(f->argnames, i), NULL);
            if (withTypes && av) {
                Jsi_DString tStr = {};
                int atyp = av->type;
                if (av->defValue)
                    atyp &= ~(av->defValue->vt==JSI_VT_NULL?JSI_TT_NULL:(1<<av->defValue->vt));
                if (atyp) {
                    Jsi_DSAppend(dStr, ":", jsi_typeName(interp, atyp, &tStr), NULL);
                }
                Jsi_DSSetLength(&tStr, 0);
                if (av->defValue)
                    Jsi_DSAppend(dStr, "=", Jsi_ValueGetDString(interp, av->defValue, &tStr, 1), NULL);
                Jsi_DSFree(&tStr);
            }
        }
    } else if (f->cmdSpec && f->cmdSpec->argStr)
        Jsi_DSAppend(dStr, f->cmdSpec->argStr, NULL);
    return JSI_OK;
}

Jsi_RC Jsi_FuncObjToString(Jsi_Interp *interp, Jsi_Func *f, Jsi_DString *dStr, int flags)
{
    int withBody = flags&1;
    int withTypes = flags&2;
    int withJSON = flags&4;
    int withFull = (flags&8 && !withJSON);
    if (withFull && f->type == FC_NORMAL && f->opcodes) {
        int len;
        const char *cp = jsi_FuncGetCode(interp, f, &len);
        if (cp) {
            Jsi_DSAppendLen(dStr,cp, len);
            return JSI_OK;
        }
    }
    Jsi_CmdSpec *spec = f->cmdSpec;
    if (withJSON)
        Jsi_DSAppend(dStr, "\"", NULL);
    if (f->type == FC_NORMAL) {
        Jsi_DSAppend(dStr, "function ", f->name?f->name:"", "(", NULL);
        jsi_FuncArgsToString(interp, f, dStr, withTypes);
        Jsi_DSAppend(dStr, ")", NULL);
        if (withTypes && f->retType) {
            Jsi_DString tStr;
            Jsi_DSInit(&tStr);
            Jsi_DSAppend(dStr, ":", jsi_typeName(interp, f->retType, &tStr), NULL);
            Jsi_DSFree(&tStr);
        }
        if (withBody)
            Jsi_DSAppend(dStr, " {...}", NULL);
    } else {
        Jsi_DSAppend(dStr, "function ", f->name?f->name:"", "(",
            (spec&&spec->argStr)?spec->argStr:"", ")", NULL);
        if (withBody)
            Jsi_DSAppend(dStr, " { [native code] }", NULL);
    }
    if (withJSON)
        Jsi_DSAppend(dStr, "\"", NULL);
    return JSI_OK;
}

Jsi_Value *jsi_MakeFuncValue(Jsi_Interp *interp, Jsi_CmdProc *callback, const char *name, Jsi_Value** toVal, Jsi_CmdSpec *cspec)
{
    Jsi_Obj *o = Jsi_ObjNew(interp);
    Jsi_Func *f = jsi_FuncNew(interp);
    Jsi_ObjIncrRefCount(interp, o);
    o->ot = JSI_OT_FUNCTION;
    f->type = FC_BUILDIN;
    f->callback = callback;
    f->privData = NULL;
    o->d.fobj = jsi_FuncObjNew(interp, f);
    f->cmdSpec = cspec;
    if (!cspec) {
        f->cmdSpec = (Jsi_CmdSpec*)Jsi_Calloc(2,sizeof(Jsi_CmdSpec));
        f->cmdSpec->reserved[3] = (void*)0x1;
        f->cmdSpec->maxArgs = -1;
        if (name)
            f->cmdSpec->name = (char*)Jsi_KeyAdd(interp, name);
    }
    f->script = interp->curFile;
    f->callback = callback;
    return Jsi_ValueMakeObject(interp, toVal, o);
}

Jsi_Value *jsi_MakeFuncValueSpec(Jsi_Interp *interp, Jsi_CmdSpec *cmdSpec, void *privData)
{
    Jsi_Obj *o = Jsi_ObjNew(interp);
    Jsi_Func *f = jsi_FuncNew(interp);
    o->ot = JSI_OT_FUNCTION;
    f->type = FC_BUILDIN;
    f->cmdSpec = cmdSpec;
    f->callback = cmdSpec->proc;
    f->privData = privData;
    f->f.flags = (cmdSpec->flags & JSI_CMD_MASK);
    f->script = interp->curFile;
    o->d.fobj = jsi_FuncObjNew(interp, f);
    return Jsi_ValueMakeObject(interp, NULL, o);
}


/* Call a function with args: args and/or ret can be NULL. */
static Jsi_RC jsi_FunctionInvoke(Jsi_Interp *interp, Jsi_Value *tocall, Jsi_Value *args, Jsi_Value **ret, Jsi_Value *_this)
{
    if (interp->maxDepth>0 && interp->maxDepth && interp->callDepth>=interp->maxDepth)
        return Jsi_LogError("max call depth exceeded");
    if (interp->deleting)
        return JSI_ERROR;
    if (!Jsi_ValueIsFunction(interp, tocall)) 
        return Jsi_LogError("can not execute expression, expression is not a function");
    if (!tocall->d.obj->d.fobj) {   /* empty function */
        return JSI_OK;
    }
    if (!ret) {
        if (!interp->nullFuncRet) {
            interp->nullFuncRet = Jsi_ValueNew(interp);
            Jsi_IncrRefCount(interp, interp->nullFuncRet);
        }
        ret = &interp->nullFuncRet;
        Jsi_ValueMakeUndef(interp, ret);
    }
    if (!args) {
        if (!interp->nullFuncArg) {
            interp->nullFuncArg = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, NULL, 0, 0));
            Jsi_IncrRefCount(interp, interp->nullFuncArg);
        }
        args = interp->nullFuncArg;
    }
    /* func to call */
    Jsi_Func *funcPtr = tocall->d.obj->d.fobj->func;
    SIGASSERT(funcPtr, FUNC);
    
    /* prepare args */
    if (args->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, args->d.obj)) 
        return Jsi_LogError("argument must be an array");
    /* new this */
    Jsi_Value *fthis = Jsi_ValueDup(interp, _this ? _this : tocall);
    Jsi_Func *prevActive = interp->activeFunc;
    Jsi_RC res = jsi_SharedArgs(interp, args, funcPtr, 1);
    bool isalloc = 0;
    int calltrc = 0;
    int tc = interp->traceCall | (funcPtr->pkg?funcPtr->pkg->popts.modConf.traceCall:0);
    interp->callDepth++;
    if (res == JSI_OK) {
        jsi_InitLocalVar(interp, args, funcPtr);
        jsi_SetCallee(interp, args, tocall);
        isalloc = 1;
        Jsi_IncrRefCount(interp, args);
        if (funcPtr->type == FC_NORMAL) {
            if ((tc&jsi_callTraceFuncs) && funcPtr->name)
                calltrc = 1;
        } else {
            if ((tc&jsi_callTraceCmds) && funcPtr->name)
                calltrc = 1;
        }
        interp->activeFunc = funcPtr;
        if (funcPtr->type == FC_NORMAL) {
            if (calltrc)
                jsi_TraceFuncCall(interp, funcPtr, NULL, fthis, args, NULL, tc);
            res = jsi_evalcode(interp->ps, funcPtr, funcPtr->opcodes, tocall->d.obj->d.fobj->scope, 
                args, fthis, ret);
        } else {
            if (calltrc)
                jsi_TraceFuncCall(interp, funcPtr, NULL, fthis, args, NULL, tc);
            res = funcPtr->callback(interp, args, fthis, ret, funcPtr);
        }
        funcPtr->callCnt++;
    }
    interp->callDepth--;
    if (res == JSI_OK && funcPtr->retType)
        res = jsi_ArgTypeCheck(interp, funcPtr->retType, *ret, "returned from", funcPtr->name, 0, funcPtr, 0);
    if (calltrc && (tc&jsi_callTraceReturn))
        jsi_TraceFuncCall(interp, funcPtr, NULL, fthis, NULL, *ret, tc);
    interp->activeFunc = prevActive;
    jsi_SharedArgs(interp, args, funcPtr, 0);
    if (isalloc) 
        Jsi_DecrRefCount(interp, args);
    Jsi_DecrRefCount(interp, fthis);
    return res;
}

Jsi_RC Jsi_FunctionInvoke(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *args, Jsi_Value **ret, Jsi_Value *_this)
{
    // Arrange for error reporting to point to called function.
    Jsi_Func *fstatic = func->d.obj->d.fobj->func;
    jsi_OpCode *oldops = interp->curIp;
    if (fstatic->opcodes)
        interp->curIp = fstatic->opcodes->codes;
    Jsi_RC rc = jsi_FunctionInvoke(interp, func, args, ret, _this);
    interp->curIp = oldops;
    if (Jsi_InterpGone(interp))
        return JSI_ERROR;
    return rc;
}

// Do typechecking for callback using argStr from .data in builtin Jsi_Options: may not use = or ...
bool jsi_FuncArgCheck(Jsi_Interp *interp, Jsi_Func *f, const char *argStr)
{
    int i, atyp, ftyp, rc = 0, acnt;
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    int argc = 0;
    char **argv, *sname, *stype, *cp;
    if (!argStr)
        goto done;
    if (f->type == FC_BUILDIN) {
        // Check builtin cmd
        jsi_CommandArgCheck(interp, f->cmdSpec, f, f->parentName);
        goto done;
    }
    if ((cp=Jsi_Strchr(argStr, '='))) {
        Jsi_LogWarn("may not have default value in option, expected: %s", argStr);
        goto done;
    }
    if (Jsi_Strstr(argStr, "...")) {
        Jsi_LogWarn("may not have ... in args, expected: %s", argStr);
        goto done;
    }
    if (argStr[0]) {
        Jsi_SplitStr(argStr, &argc, &argv, ",", &dStr);
        if (argc<=0)
            goto done;
    }
    if (!f->argnames) {
        if (argStr[0])
            Jsi_LogWarn("function has no args, expected: %s", argStr);
        else
            rc = 1;
        goto done;
    } else {
        if (f->argnames->varargs) { // TODO: could allow varargs...
            if (argc < f->argnames->argCnt) {
                Jsi_LogWarn("vararg argument mismatch, expected: %s", argStr);
                goto done;
            }
        }
        else if (f->argnames->argCnt != argc) {
            if (argc)
                Jsi_LogWarn("argument mismatch, expected: %s", argStr);
            else
                Jsi_LogWarn("function should have no arguments");
            goto done;
        }

    }
    acnt = f->argnames->argCnt;
    for (i=0; i<argc && i<acnt; i++) {
        sname = argv[i];
        stype = NULL;
        while (sname && *sname && isspace(*sname)) { sname++; }
        if ((cp=Jsi_Strchr(sname, ':')))
        {
            stype = cp+1;
            *cp = 0;
            while (*stype && isspace(*stype)) { stype++; }
            if (*stype) {
                cp = stype+Jsi_Strlen(stype)-1;
                while (cp>=stype && isspace(*cp)) { *cp = 0; cp--; }
            }
        }
        if (sname && *sname) {
            cp = sname+Jsi_Strlen(sname)-1;
            while (cp>=sname && isspace(*cp)) { *cp = 0; cp--; }
        }
        ftyp = f->argnames->args[i].type;
        if (ftyp<=0 || (ftyp&JSI_TT_ANY))
            continue;
        atyp = jsi_typeGet(interp, stype);
        if (ftyp != atyp && atyp) {
            Jsi_LogWarn("argument %d of function \"%s\" does not match \"func(%s)\"" ,
                i+1, f->name, argStr);
            goto done;
        }
    }
    rc = 1;
done:
    Jsi_DSFree(&dStr);
    if (!rc)
        jsi_TypeMismatch(interp);
    return rc;
}

/* Call function that returns a bool with a single argument. Returns -1, else 0/1 for false/true,  */
int Jsi_FunctionInvokeBool(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *arg)
{
    if (interp->deleting)
        return JSI_ERROR;
    Jsi_Value *vpargs, *frPtr = Jsi_ValueNew1(interp);
    Jsi_RC rc;
    int bres = 0;
    if (!arg) {
        if (!interp->nullFuncArg) {
            interp->nullFuncArg = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, NULL, 0, 0));
            Jsi_IncrRefCount(interp, interp->nullFuncArg);
        }
        vpargs = interp->nullFuncArg;
    } else {
        vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, &arg, 1, 1));
    }
    Jsi_IncrRefCount(interp, vpargs);
    rc = Jsi_FunctionInvoke(interp, func, vpargs, &frPtr, NULL);
    Jsi_DecrRefCount(interp, vpargs);
    if (rc == JSI_OK)
        bres = Jsi_ValueIsTrue(interp, frPtr);
    else {
        bres = 2;
        Jsi_LogError("function call failed");
    }
    Jsi_DecrRefCount(interp, frPtr);
    if (Jsi_InterpGone(interp))
        return JSI_ERROR;
    return bres;
}

// Invoke function with one string argument.
Jsi_RC Jsi_FunctionInvokeString(Jsi_Interp *interp, Jsi_Value *func, Jsi_Value *arg, Jsi_DString *dStr)
{
    if (interp->deleting)
        return JSI_ERROR;
    Jsi_Value *vpargs, *frPtr = Jsi_ValueNew1(interp);
    Jsi_RC rc;
    if (!arg) {
        if (!interp->nullFuncArg) {
            interp->nullFuncArg = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, NULL, 0, 0));
            Jsi_IncrRefCount(interp, interp->nullFuncArg);
        }
        vpargs = interp->nullFuncArg;
    } else {
        vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, &arg, 1, 1));
    }
    Jsi_IncrRefCount(interp, vpargs);
    rc = Jsi_FunctionInvoke(interp, func, vpargs, &frPtr, NULL);
    Jsi_DecrRefCount(interp, vpargs);
    if (rc != JSI_OK)
        Jsi_LogError("function call failed");
    else
        Jsi_ValueGetDString(interp, frPtr, dStr, 0);
    Jsi_DecrRefCount(interp, frPtr);
    return rc;
}
       

Jsi_FuncObj *jsi_FuncObjNew(Jsi_Interp *interp, Jsi_Func *func)
{
    Jsi_FuncObj *f = (Jsi_FuncObj*)Jsi_Calloc(1,sizeof(Jsi_FuncObj));
    f->interp = interp;
    SIGINIT(f,FUNCOBJ);
    f->func = func;
    func->refCnt++;
    return f;
}

void jsi_FuncFree(Jsi_Interp *interp, Jsi_Func *func)
{
    if (--func->refCnt > 0)
        return;
    jsi_PkgInfo *pkg = func->pkg;
    bool profile = (interp->profile || (pkg?pkg->popts.modConf.profile:0)), 
        cover = (interp->coverage || (pkg?pkg->popts.modConf.coverage:0));
    if (profile || cover) {
        Jsi_DString dStr;
        Jsi_DSInit(&dStr);
        const char *file = func->script;
        if (!file)
            file = "";
        int line = func->bodyline.last_line;
        if (!func->callCnt) {
            if (cover && func->type == FC_NORMAL && func->name)
                Jsi_DSPrintf(&dStr, "COVERAGE: func=%s  file=%s:%d  cover=0%%\n", func->name, file, line );
        } else {
            char buf[JSI_BUFSIZ];
            if (func->type == FC_BUILDIN) {
                const char *fnam = func->parentName;
                snprintf(buf, sizeof(buf), "cmd=%s%s%s", fnam, fnam[0]?".":"", func->name);
                interp->cmdSelfTime += (func->allTime-func->subTime);
            } else {
                
                double coverage = 0; // Calculate hits/all.
                if (func->opcodes && func->opcodes->code_len>0) {
                    int i, cchit=0, ccall=0, ccline=0, cchitline=0;
                    Jsi_OpCodes *oc = func->opcodes;
                    for (i=0; i<oc->code_len; i++) {
                        if (oc->codes[i].Line<=0) continue;
                        if (ccline != oc->codes[i].Line) {
                            ccline = oc->codes[i].Line;
                            ccall++;
                            interp->coverAll++;
                        }
                        if (cchitline != oc->codes[i].Line && oc->codes[i].hit) {
                            cchitline = oc->codes[i].Line;
                            cchit++;
                            interp->coverHit++;
                        }
                    }
                    if (ccall)
                        coverage = (int)(100.0*cchit/ccall);
                        
                    if (cover && cchit<ccall) { // Generate the misses list.
                        char cbuf[JSI_BUFSIZ];
                        int lastout = 0, lastpos=0, dupcnt=0, cccnt=0;
                        cbuf[0] = 0;
                        ccline=cchitline=0;
                        for (i=0; i<oc->code_len; i++) {
                            int ismiss = 0;
                            if (i==oc->code_len) {
                                ismiss = (ccline>0 && cchitline != ccline);
                            } else {
                                if (oc->codes[i].Line<=0) continue;
                                ismiss = (ccline>0 && ccline != oc->codes[i].Line && cchitline != ccline);
                            }
                            if (ismiss) {
                                cccnt++;
                                const char *sep = (cccnt>1?",":"");
                                if (lastout && (lastout+1)==ccline) {
                                    sep = "-";
                                    dupcnt++;
                                    if (dupcnt>1)
                                        cbuf[lastpos]=0; // Inefficient, but reliable.
                                    else
                                        lastpos = Jsi_Strlen(cbuf);
                                } else 
                                    dupcnt = 0;
                                int cbl = Jsi_Strlen(cbuf);
                                snprintf(cbuf+cbl, sizeof(cbuf)-cbl, "%s%d", sep, ccline);
                                lastout = ccline;
                            }
                            ccline = oc->codes[i].Line;
                            if (oc->codes[i].hit)
                                cchitline = ccline;
                        }
                        Jsi_DSPrintf(&dStr, "COVERAGE: func=%s  file=%s:%d  cover=%2.1f%%  hits=%d,  all=%d,  misses=%s\n", 
                            func->name, file, line, coverage, cchit, ccall, cbuf);
                    }
                }
                snprintf(buf, sizeof(buf), "cover=%#5.1f%%  func=%s file=%s:%d", coverage, func->name, file, line);
                interp->funcSelfTime += (func->allTime-func->subTime);
            }
            if (profile)
                Jsi_DSPrintf(&dStr, "PROFILE:  self=%6.6f  all=%6.6f  #calls=%-8d  self/call=%6.6f  all/call=%6.6f  %s %s%s\n",
                    (func->allTime-func->subTime), (double)(func->allTime), func->callCnt, 
                    (double)(func->allTime-func->subTime)/func->callCnt,  (double)(func->allTime)/func->callCnt, 
                    buf, interp->parent?" ::":"", (interp->parent&&interp->name?interp->name:""));
        }
        if (Jsi_DSLength(&dStr))
            Jsi_Puts(interp, jsi_Stderr, Jsi_DSValue(&dStr), -1);
        Jsi_DSFree(&dStr);
    }

    if (func->opcodes)
        jsi_FreeOpcodes(func->opcodes);
    if (func->hPtr)
        Jsi_HashEntryDelete(func->hPtr);
    if (func->localnames)
        jsi_ScopeStrsFree(interp, func->localnames);
    if (func->argnames)
        jsi_ScopeStrsFree(interp, func->argnames);
    if (func->cmdSpec && (intptr_t)func->cmdSpec->reserved[3]& 0x1)
        Jsi_Free(func->cmdSpec);
    _JSI_MEMCLEAR(func);
    Jsi_Free(func);
    interp->funcCnt--;
}

Jsi_Func *jsi_FuncNew(Jsi_Interp *interp)
{
     Jsi_Func *func = (Jsi_Func*)Jsi_Calloc(1, sizeof(Jsi_Func));
     SIGINIT(func, FUNC);
     func->hPtr = Jsi_HashSet(interp->funcsTbl, func, func);
     func->refCnt = 1;
     interp->funcCnt++;
     return func;
}

void jsi_FuncObjFree(Jsi_FuncObj *fobj)
{
    if (fobj->scope)
        jsi_ScopeChainFree(fobj->interp, fobj->scope);
    if (fobj->bindArgs)
        Jsi_DecrRefCount(fobj->interp, fobj->bindArgs);
    if (fobj->bindFunc)
        Jsi_DecrRefCount(fobj->interp, fobj->bindFunc);
    if (fobj->func)
        jsi_FuncFree(fobj->interp, fobj->func);
    _JSI_MEMCLEAR(fobj);
    Jsi_Free(fobj);
}

#endif
#ifndef JSI_LITE_ONLY
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif

#define bits_set(who, mask)     ((who) |= (mask))
#define bits_unset(who, mask)   ((who) &= (~(mask)))
#define bits_get(who, mask)     ((who) & (mask))

static void IterGetKeys(Jsi_Interp *interp, Jsi_Value *target, Jsi_IterObj *iterobj, int depth);

#if  JSI__MEMDEBUG
void jsi_VALCHK(Jsi_Value *val) {
    SIGASSERTV(val,VALUE);
    assert(val->vt <= JSI_VT__MAX);
    if (val->vt == JSI_VT_OBJECT)
        OBJCHK(val->d.obj);
}

void jsi_OBJCHK(Jsi_Obj *obj) {
    SIGASSERTV(obj,OBJ);
    assert(obj->ot <= JSI_OT__MAX);
}
#endif

/*********************************************/

bool Jsi_IsShared(Jsi_Interp* interp, Jsi_Value *v)
{
    SIGASSERT(v,VALUE);
    return (v->refCnt > 1);
}

int Jsi_IncrRefCount(Jsi_Interp* interp, Jsi_Value *v)
{
    SIGASSERT(v,VALUE);
    assert(v->refCnt>=0);
    jsi_DebugValue(v,"Incr", jsi_DebugValueCallIdx(), interp);
    return ++(v->refCnt);
}

int Jsi_DecrRefCount(Jsi_Interp* interp, Jsi_Value *v) {
    SIGASSERT(v,VALUE);
    if (v->refCnt<=0) {
#ifdef JSI_MEM_DEBUG
        fprintf(stderr, "Value decr with ref %d: VD.Idx=%d\n", v->refCnt, v->VD.Idx);
#endif
        return -2;
    }
    int ref;
    jsi_DebugValue(v,"Decr", jsi_DebugValueCallIdx(), interp);
    if ((ref = --(v->refCnt)) <= 0) {
        v->refCnt = -1;
        Jsi_ValueFree(interp, v);
    }
    return ref;
}

static Jsi_Value *ValueNew(Jsi_Interp *interp)
{
    interp->dbPtr->valueCnt++;
    interp->dbPtr->valueAllocCnt++;
    Jsi_Value *v = (Jsi_Value *)Jsi_Calloc(1,sizeof(*v));
    SIGINIT(v,VALUE)
    v->vt = JSI_VT_UNDEF;
    jsi_DebugValue(v,"New", jsi_DebugValueCallIdx(), interp);
    return v;
}

static Jsi_Value *ValueNew1(Jsi_Interp *interp)
{
    Jsi_Value *v = ValueNew(interp);
    Jsi_IncrRefCount(interp, v);
    return v;
}

static Jsi_Value *ValueDup(Jsi_Interp *interp, Jsi_Value *v)
{
    Jsi_Value *r = ValueNew1(interp);
    Jsi_ValueCopy(interp,r, v);
#ifdef JSI_MEM_DEBUG
    r->VD.label2 = "ValueDup";
#endif
    return r;
}
#ifndef JSI_MEM_DEBUG
Jsi_Value* Jsi_ValueNew(Jsi_Interp *interp) {
    return ValueNew(interp);
}
Jsi_Value* Jsi_ValueNew1(Jsi_Interp *interp) {
    return ValueNew1(interp);
}
Jsi_Value *Jsi_ValueDup(Jsi_Interp *interp, Jsi_Value *v) {
    return ValueDup(interp, v);
}
#else
// Debugging functions: set breakpoint with "cond B v == 0xNNN"
void jsi_DebugValue(Jsi_Value* v, const char *reason, uint idx, Jsi_Interp *interp)
{
    return;
}
void jsi_DebugObj(Jsi_Obj* o, const char *reason, uint idx, Jsi_Interp *interp)
{
    return;
}

static uint jsi_memDebugBreakIdx = 0;  // Debug memory by setting this, and adding BP on following func.
void jsi_memDebugBreak() {
}

void jsi_ValueDebugUpdate_(Jsi_Interp *interp, jsi_ValueDebug *vd, void *v, Jsi_Hash* tbl, const char *fname, int line, const char *func)
{
    vd->fname = fname;
    vd->line = line;
    vd->func = func;
    if (!vd->Idx)
        vd->Idx = interp->dbPtr->memDebugCallIdx;
    vd->hPtr = Jsi_HashSet(tbl, v, 0);
    vd->ip = interp->curIp;
    if (vd->ip) {
        vd->ipLine = vd->ip->Line;
        vd->ipOp = vd->ip->op;
        vd->ipFname = vd->ip->fname;
    }
    vd->interp = interp;
    if (jsi_memDebugBreakIdx == vd->Idx)
        jsi_memDebugBreak();
}

void jsi_ValueDebugLabel_(jsi_ValueDebug *vd, const char *l1, const char *l2)
{
    if (l1)
        vd->label = l1;
    if (l2)
        vd->label2 = l2;
}


Jsi_Value * jsi_ValueNew(Jsi_Interp *interp, const char *fname, int line, const char *func) {
    Jsi_Value *v = ValueNew(interp);
    jsi_ValueDebugUpdate(interp, v, valueDebugTbl, fname, line, func);
    return v;
}

Jsi_Value * jsi_ValueNew1(Jsi_Interp *interp, const char *fname, int line, const char *func) {
    Jsi_Value *v = ValueNew1(interp);
    jsi_ValueDebugUpdate(interp, v, valueDebugTbl, fname, line, func);
    return v;
}
Jsi_Value * jsi_ValueDup(Jsi_Interp *interp, Jsi_Value *ov, const char *fname, int line, const char *func) {
    Jsi_Value *v = ValueDup(interp, ov);
    jsi_ValueDebugUpdate(interp, v, valueDebugTbl, fname, line, func);
    return v;
}

#ifndef JSI_OMIT_STUBS
#undef Jsi_ValueNew
#undef Jsi_ValueNew1
Jsi_Value *Jsi_ValueNew(Jsi_Interp *interp) { return ValueNew(interp); }
Jsi_Value *Jsi_ValueNew1(Jsi_Interp *interp) { return ValueNew1(interp); }
#define Jsi_ValueNew(interp) jsi_ValueNew(interp, __FILE__, __LINE__,__PRETTY_FUNCTION__)
#define Jsi_ValueNew1(interp) jsi_ValueNew1(interp, __FILE__, __LINE__,__PRETTY_FUNCTION__)
#endif

#endif

Jsi_Hash *strDebug = NULL;

static void ValueFree(Jsi_Interp *interp, Jsi_Value* v)
{
    SIGASSERTV(v,VALUE);
    //printf("FREE: %d\n", interp->valueCnt);
    switch (v->vt) {
        case JSI_VT_OBJECT:
            Jsi_ObjDecrRefCount(interp, v->d.obj);
            break;
        case JSI_VT_VARIABLE:
            assert(v->d.lval != v);
            Jsi_DecrRefCount(interp, v->d.lval);
            break;
        case JSI_VT_STRING:
            if (v->d.s.str && !v->f.bits.isstrkey) {
                Jsi_Free(v->d.s.str);
                /*Jsi_HashEntry *hPtr;
                if ((hPtr = Jsi_HashEntryFind(strDebug, v->d.s.str)))
                    Jsi_HashEntryDelete(hPtr);*/
            }
            break;
        default:
            break;
    }
    v->vt = JSI_VT_UNDEF;
}

void Jsi_ValueFree(Jsi_Interp *interp, Jsi_Value* v)
{
    interp->dbPtr->valueCnt--;
    jsi_DebugValue(v, "Free", jsi_DebugValueCallIdx(), interp);
    ValueFree(interp, v);
#ifdef JSI_MEM_DEBUG
    //if (v->VD.interp != interp)  //TODO: InterpAliasCmd leaking Values.
     //   fprintf(stderr, "cross interp delete: %p\n", v);
    if (v->VD.hPtr) {
        if (!Jsi_HashEntryDelete(v->VD.hPtr))
            fprintf(stderr, "Value not in hash\n");
    }
    memset(v, 0, (sizeof(*v)-sizeof(v->VD)));
#endif
    Jsi_Free(v);
}

/* Reset a value back to undefined, releasing string/obj if necessary. */
void Jsi_ValueReset(Jsi_Interp *interp, Jsi_Value **vPtr) {
    Jsi_Value *v = *vPtr;
    SIGASSERTV(v,VALUE);
    Assert(v->vt <= JSI_VT__MAX);
    jsi_DebugValue(v, "Reset", jsi_DebugValueCallIdx(), interp);
    Assert(v->refCnt>=0);
    v->f.bits.lookupfailed = 0; // TODO: rework lookup-fail mechanism.
    if (v->vt == JSI_VT_UNDEF)
        return;
    ValueFree(interp, v);
    v->f.flag = 0;
}

// Assign value ptrs (to=from). Decr old to, and Incr from ref count.
void Jsi_ValueReplace(Jsi_Interp *interp, Jsi_Value **to, Jsi_Value *from )  {
    VALCHK(from);
    if( *to == from) return;
    if (*to)
        Jsi_DecrRefCount(interp, *to);
    *to = from;
    if (from)
        Jsi_IncrRefCount(interp, from);
}


static void jsi_ValueCopyMove(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from, int isCopy )  {
    if (!from) {
        Jsi_ValueMakeUndef(interp, &to);
        return;
    }
    VALCHK(from);
    if( to == from) return;
    int ocnt = to->refCnt;
    Jsi_Value *ovt = NULL;
    assert(ocnt>0);
    int toVt = to->vt;
    if (toVt == JSI_VT_VARIABLE) {
        ovt = to->d.lval;
        Jsi_IncrRefCount(interp, ovt);
    }
    Jsi_ValueMakeUndef(interp, &to);
#ifdef JSI_MEM_DEBUG
    memcpy(to, from, sizeof(*to)-sizeof(to->VD));
    to->VD.label3 = from->VD.func;
#else
    *to = *from;
#endif
    if (isCopy) {
        if (to->refCnt) {
            switch (to->vt) {
                case JSI_VT_STRING:
                    if (!to->f.bits.isstrkey) {
                        to->d.s.str = Jsi_StrdupLen(to->d.s.str, to->d.s.len);
                    }
                    break;
                case JSI_VT_OBJECT:
                    Jsi_ObjIncrRefCount(interp,to->d.obj);
                    break;
                case JSI_VT_VARIABLE:
                    Jsi_IncrRefCount(interp,to->d.lval);
                    break;
                default:
                    break;
            }
        }
        to->refCnt = ocnt;
        if (ovt)
            Jsi_DecrRefCount(interp, ovt);
    } else {
        to->refCnt = ocnt;
        if (ovt)
            Jsi_DecrRefCount(interp, ovt);
        ocnt = from->refCnt;
#ifdef JSI_MEM_DEBUG
        memset(from, 0, sizeof(*to)-sizeof(to->VD));
#else
        memset(from, 0, sizeof(*to));
#endif
        SIGINIT(from, VALUE);
        from->refCnt = ocnt;
    }
}

void Jsi_ValueMove(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from )  {
    return jsi_ValueCopyMove(interp, to, from, 0);
}

void Jsi_ValueCopy(Jsi_Interp *interp, Jsi_Value *to, Jsi_Value *from ) {
    return jsi_ValueCopyMove(interp, to, from, 1);
}

void Jsi_ValueDup2(Jsi_Interp *interp, Jsi_Value **to, Jsi_Value *from )
{
    if (!*to)
        *to = Jsi_ValueNew1(interp);
#ifdef JSI_MEM_DEBUG
    (*to)->VD.label3 = "ValueDup2";
#endif
    Jsi_ValueCopy(interp, *to, from);
    (*to)->f.bits.readonly = 0;
}

Jsi_Value *Jsi_ValueDupJSON(Jsi_Interp *interp, Jsi_Value *val)
{
    Jsi_DString pStr;
    Jsi_DSInit(&pStr);
    Jsi_ValueGetDString(interp, val, &pStr, JSI_OUTPUT_JSON);
    Jsi_Value *valPtr = NULL;
    if (Jsi_JSONParse(interp, Jsi_DSValue(&pStr), &valPtr, 0) != JSI_OK)
        Jsi_LogBug("bad json parse");
    Jsi_DSFree(&pStr);
    return valPtr;
}

Jsi_Value *Jsi_ValueObjLookup(Jsi_Interp *interp, Jsi_Value *target, const char *key, int isstrkey)
{
    Jsi_Obj *obj;
    Jsi_Value *v = NULL;
    if (interp->subOpts.noproto && key) {
        if (key[0] == 'p' && Jsi_Strcmp(key, "prototype")==0) {
            Jsi_LogError("inheritance is disabled in interp");
            return NULL;
        }
    }
    if (target->vt != JSI_VT_OBJECT) {
        if (interp->strict)
            Jsi_LogWarn("Target is not object: %d", target->vt);
        return NULL;
    }
    obj = target->d.obj;
    
#if (defined(JSI_HAS___PROTO__) && JSI_HAS___PROTO__==2)
    if (*key == '_' && Jsi_Strcmp(key, "__proto__")==0 && interp->noproto==0)
        return obj->__proto__;
#endif

    if (*key == 't' && Jsi_Strcmp(key, "this")==0)
        return interp->framePtr->inthis;
    if (obj->arr)
        v = jsi_ObjArrayLookup(interp, obj, key);
    if (!v)
        v= Jsi_TreeObjGetValue(obj, key, isstrkey);
    return v;  /* TODO: redo with copy */
}

Jsi_Value *Jsi_ValueArrayIndex(Jsi_Interp *interp, Jsi_Value *args, int index)
{
    Jsi_Obj *obj = args->d.obj;
    Jsi_Value *v;
    assert(args->vt == JSI_VT_OBJECT);
    if (obj->isarrlist && obj->arr)
        return ((index < 0 || (uint)index >= obj->arrCnt) ? NULL : obj->arr[index]);
    char unibuf[100];
    Jsi_NumberItoA10(index, unibuf, sizeof(unibuf));
    v = Jsi_TreeObjGetValue(args->d.obj, unibuf, 0);
    return v;
}

/**************************************************/

Jsi_RC Jsi_ValueGetBoolean(Jsi_Interp *interp, Jsi_Value *pv, bool *val)
{
    if (!pv) return JSI_ERROR;
    if (pv->vt == JSI_VT_BOOL)
        *val = pv->d.val;
    else if (pv->vt == JSI_VT_OBJECT && pv->d.obj->ot == JSI_OT_BOOL)
        *val = pv->d.obj->d.val;
    else 
        return JSI_ERROR;
    return JSI_OK;
}

bool Jsi_ValueIsArray(Jsi_Interp *interp, Jsi_Value *v)  {
    return (v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_OBJECT && v->d.obj->isarrlist);
}

bool Jsi_ValueIsBoolean(Jsi_Interp *interp, Jsi_Value *pv)
{
    return (pv->vt == JSI_VT_BOOL || (pv->vt == JSI_VT_OBJECT && pv->d.obj->ot == JSI_OT_BOOL));
}

bool Jsi_ValueIsNull(Jsi_Interp *interp, Jsi_Value *pv)
{
    return (pv->vt == JSI_VT_NULL);
}

bool Jsi_ValueIsUndef(Jsi_Interp *interp, Jsi_Value *pv)
{
    return (pv->vt == JSI_VT_UNDEF);
}

Jsi_RC Jsi_ValueGetNumber(Jsi_Interp *interp, Jsi_Value *pv, Jsi_Number *val)
{
    if (!pv) return JSI_ERROR;
    if (pv->vt == JSI_VT_NUMBER)
        *val = pv->d.num;
    else if (pv->vt == JSI_VT_OBJECT && pv->d.obj->ot == JSI_OT_NUMBER)
        *val = pv->d.obj->d.num;
    else 
        return JSI_ERROR;
    return JSI_OK;
}
bool Jsi_ValueIsNumber(Jsi_Interp *interp, Jsi_Value *pv)
{
    return (pv->vt == JSI_VT_NUMBER || (pv->vt == JSI_VT_OBJECT && pv->d.obj->ot == JSI_OT_NUMBER));
}

bool Jsi_ValueIsStringKey(Jsi_Interp* interp, Jsi_Value *key)
{
    if (key->vt == JSI_VT_STRING && key->f.bits.isstrkey)
        return 1;
    if (key->vt == JSI_VT_OBJECT && key->d.obj->ot == JSI_OT_STRING && key->d.obj->isstrkey)
        return 1;
    return 0;
}

bool Jsi_ValueIsString(Jsi_Interp *interp, Jsi_Value *pv)
{
    return (pv->vt == JSI_VT_STRING || (pv->vt == JSI_VT_OBJECT && pv->d.obj->ot == JSI_OT_STRING));
}

bool Jsi_ValueIsFunction(Jsi_Interp *interp, Jsi_Value *v)
{
    int rc = (v!=NULL && v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_FUNCTION);
    if (!rc) return rc;
    if (interp == v->d.obj->d.fobj->interp)
        return 1;
    fprintf(stderr, "OOPS: function in wrong interp %s: %s\n", 
        interp->parent?"(string came in from parent interp?)":"",
        v->d.obj->d.fobj->func->name);
    return 0;
}

bool Jsi_ValueIsType(Jsi_Interp *interp, Jsi_Value *pv, Jsi_vtype vtype) {
    if (!pv) return 0;
    return pv->vt == vtype;
}

Jsi_vtype Jsi_ValueTypeGet(Jsi_Value *pv) { return pv->vt; }


bool Jsi_ValueIsObjType(Jsi_Interp *interp, Jsi_Value *v, Jsi_otype otype)
{
    if (v == NULL || v->vt != JSI_VT_OBJECT)
        return 0;
    if (otype != JSI_OT_ARRAY)
        return (v->d.obj->ot == otype);
    if (v->d.obj->ot != JSI_OT_OBJECT || !v->d.obj->isarrlist)
        return 0;
    return 1;
}

char* Jsi_NumberToString(Jsi_Interp *interp, Jsi_Number d, char *buf, int bsiz)
{
     if (Jsi_NumberIsInteger(d)) {
        Jsi_NumberItoA10((Jsi_Wide)d, buf, bsiz);
    } else if (Jsi_NumberIsNormal(d)) {
        Jsi_NumberDtoA(interp, d, buf, bsiz, 0);
    } else if (Jsi_NumberIsNaN(d)) {
        Jsi_Strcpy(buf, "NaN");
    } else {
        int s = Jsi_NumberIsInfinity(d);
        if (s > 0) Jsi_Strcpy(buf,  "Infinity");
        else if (s < 0) Jsi_Strcpy(buf, "-Infinity");
        else {
            buf[0] = 0;
        }
    }
    return buf;
}

/* Return the string value.  Coerce value to a string type. */
const char* Jsi_ValueToString(Jsi_Interp *interp, Jsi_Value *v, int *lenPtr)
{
    Jsi_Number d;
    const char *ntxt = "undefined";
    int kflag = 1;
    int isKey = 0;
    char *key = NULL;
    if (!v)
        goto done;
    if (lenPtr) *lenPtr = 0;
    char unibuf[200];
    switch(v->vt) {
        case JSI_VT_STRING:
            ntxt = v->d.s.str;
            goto done;
        case JSI_VT_UNDEF:
            break;
        case JSI_VT_BOOL:
            ntxt = v->d.val ? "true":"false";
            break;
        case JSI_VT_NULL:
            ntxt = "null";
            break;
        case JSI_VT_NUMBER: {
            d = v->d.num;
fmtnum:
            if (Jsi_NumberIsInteger(d)) {
                Jsi_NumberItoA10((Jsi_Wide)d, unibuf, sizeof(unibuf));
                kflag = 0;
                ntxt = unibuf;
            } else if (Jsi_NumberIsNormal(d)) {
                Jsi_NumberDtoA(interp, d, unibuf, sizeof(unibuf), 0);
                kflag = 0;
                ntxt = unibuf;
            } else if (Jsi_NumberIsNaN(v->d.num)) {
                ntxt = "NaN";
            } else {
                int s = Jsi_NumberIsInfinity(d);
                if (s > 0) ntxt = "Infinity";
                else if (s < 0) ntxt = "-Infinity";
                else Jsi_LogBug("Ieee function got problem");
            }
            break;
        }
        case JSI_VT_OBJECT: {
            Jsi_Obj *obj = v->d.obj;
            switch(obj->ot) {
                case JSI_OT_STRING:
                    ntxt = obj->d.s.str;
                    goto done;
                case JSI_OT_BOOL:
                    ntxt = obj->d.val ? "true":"false";
                    break;
                case JSI_OT_NUMBER:
                    d = obj->d.num;
                    goto fmtnum;
                    break;
                default:
                    ntxt = "[object Object]";
                    break;
            }
            break;
        }
        default:
            Jsi_LogBug("Convert a unknown type: 0x%x to string", v->vt);
            break;
    }
    Jsi_ValueReset(interp, &v);
    if (!kflag) {
        Jsi_ValueMakeStringDup(interp, &v, ntxt);
        return Jsi_ValueString(interp, v, lenPtr);
    }
    
    key = jsi_KeyFind(interp, ntxt, 0, &isKey);
    if (key)
        Jsi_ValueMakeStringKey(interp, &v, key);
    else
        Jsi_ValueMakeString(interp, &v, ntxt);
    ntxt = v->d.s.str;
    
done:
    if (lenPtr) *lenPtr = Jsi_Strlen(ntxt);
    return ntxt;
}

Jsi_Number Jsi_ValueToNumberInt(Jsi_Interp *interp, Jsi_Value *v, int isInt)
{
    char *endPtr = NULL, *sptr;
    Jsi_Number a = 0;
    switch(v->vt) {
        case JSI_VT_BOOL:
            a = (Jsi_Number)(v->d.val ? 1.0: 0);
            break;
        case JSI_VT_NULL:
            a = 0;
            break;
        case JSI_VT_OBJECT: {
            Jsi_Obj *obj = v->d.obj;
            switch(obj->ot) {
                case JSI_OT_BOOL:
                    a = (Jsi_Number)(obj->d.val ? 1.0: 0);
                    break;
                case JSI_OT_NUMBER:
                    a = obj->d.num;
                    break;
                case JSI_OT_STRING:
                    sptr = obj->d.s.str;
                    goto donum;
                    break;
                default:
                    a = 0;
                break;
            }
            break;
        }
        case JSI_VT_UNDEF:
            a = Jsi_NumberNaN();
            break;
        case JSI_VT_NUMBER:
            a = v->d.num;
            break;
        case JSI_VT_STRING:
            sptr = v->d.s.str;
donum:
            if (!isInt) {
                a = strtod(sptr, &endPtr);
                if (endPtr && *endPtr) {
                    a = interp->NaNValue->d.num;
                }
            } else {
                a = (Jsi_Number)strtol(sptr, &endPtr, 0);
                if (!isdigit(*sptr))
                    a = interp->NaNValue->d.num;
            }
            break;
        default:
            Jsi_LogBug("Convert a unknown type: 0x%x to number", v->vt);
            break;
    }
    if (isInt && Jsi_NumberIsNormal(a))
        a = (Jsi_Number)((int64_t)(a));
    return a;
}

Jsi_RC Jsi_ValueToNumber(Jsi_Interp *interp, Jsi_Value *v)
{
    if (v->vt == JSI_VT_NUMBER) return JSI_OK;
    Jsi_Number a = Jsi_ValueToNumberInt(interp, v, 0);
    Jsi_ValueReset(interp, &v);
    Jsi_ValueMakeNumber(interp, &v, a);
    return JSI_OK;
}

Jsi_RC Jsi_ValueToBool(Jsi_Interp *interp, Jsi_Value *v)
{
    Jsi_RC rc = JSI_OK;
    bool a = 0;
    switch(v->vt) {
        case JSI_VT_BOOL:
            a = v->d.val;
            break;
        case JSI_VT_NULL:
            break;
        case JSI_VT_UNDEF:
            break;
        case JSI_VT_NUMBER:
            a = (v->d.num ? 1: 0);
            break;
        case JSI_VT_STRING:     /* TODO: NaN, and accept true/false string? */
            a = atoi(v->d.s.str);
            a = (a ? 1 : 0);
            break;
        case JSI_VT_OBJECT: {
            Jsi_Obj *obj = v->d.obj;
            switch(obj->ot) {
                case JSI_OT_BOOL:
                    a = (obj->d.val ? 1.0: 0);
                    break;
                case JSI_OT_NUMBER:
                    a = obj->d.num;
                    a = (a ? 1 : 0);
                    break;
                case JSI_OT_STRING:
                    a = atoi(obj->d.s.str);
                    a = (a ? 1 : 0);
                    break;
                default:
                    break;
            }
            break;
        }
        default:
            Jsi_LogBug("Convert a unknown type: 0x%x to number", v->vt);
            return JSI_ERROR;
    }
    Jsi_ValueReset(interp,&v);
    Jsi_ValueMakeBool(interp, &v, a);
    return rc;
}

int jsi_ValueToOInt32(Jsi_Interp *interp, Jsi_Value *v)
{
    Jsi_Number a = Jsi_ValueToNumberInt(interp, v, 1);
    Jsi_ValueReset(interp,&v);
    Jsi_ValueMakeNumber(interp, &v, a);
    return (int)a;
}

Jsi_RC Jsi_ValueToObject(Jsi_Interp *interp, Jsi_Value *v)
{
    Jsi_RC rc = JSI_OK;
    if (v->vt == JSI_VT_OBJECT) return rc;
    Jsi_Obj *o = Jsi_ObjNew(interp);
    switch(v->vt) {
        case JSI_VT_UNDEF:
        case JSI_VT_NULL:
            if (interp->strict) {
                Jsi_LogError("converting a undefined/null value to object");
                rc = JSI_ERROR;
            }
            o->d.num = 0;
            o->ot = JSI_OT_NUMBER;
            o->__proto__ = interp->Number_prototype;
            break;
        case JSI_VT_BOOL: {
            o->d.val = v->d.val;
            o->ot = JSI_OT_BOOL;
            o->__proto__ = interp->Boolean_prototype;
            break;
        }
        case JSI_VT_NUMBER: {
            o->d.num = v->d.num;
            o->ot = JSI_OT_NUMBER;
            o->__proto__ = interp->Number_prototype;
            break;
        }
        case JSI_VT_STRING: {
            o->d.s = v->d.s;
            if (!v->f.bits.isstrkey)
                o->d.s.str = (char*)Jsi_KeyAdd(interp, v->d.s.str);
            o->isstrkey = 1;
            o->ot = JSI_OT_STRING;
            o->__proto__ = interp->String_prototype;
            break;
        }
        default:
            Jsi_LogBug("toobject, not suppose to reach here");
    }
    Jsi_ValueReset(interp,&v);
    Jsi_ValueMakeObject(interp, &v, o);
    return rc;
}

/* also toBoolean here, in ecma */
bool Jsi_ValueIsTrue(Jsi_Interp *interp, Jsi_Value *v)
{
    switch(v->vt) {
        case JSI_VT_UNDEF:
        case JSI_VT_NULL:   return 0;
        case JSI_VT_BOOL:   return v->d.val ? 1:0;
        case JSI_VT_NUMBER: 
            if (v->d.num == 0.0 || Jsi_NumberIsNaN(v->d.num)) return 0;
            return 1;
        case JSI_VT_STRING: return (Jsi_ValueStrlen(v)!=0);
        case JSI_VT_OBJECT: {
            Jsi_Obj *o = v->d.obj;
            if (o->ot == JSI_OT_STRING)
                return (Jsi_ValueStrlen(v)!=0);
            if (o->ot == JSI_OT_NUMBER)
                return (o->d.num != 0);
            if (o->ot == JSI_OT_BOOL)
                return (o->d.val != 0);
            if (o->ot == JSI_OT_USEROBJ && o->d.uobj->interp == interp) {
                return jsi_UserObjIsTrue(interp, o->d.uobj);
            }
            return 1;
        }
        default: Jsi_LogBug("TOP is type incorrect: %d", v->vt);
    }
    return 0;
}

bool Jsi_ValueIsFalse(Jsi_Interp *interp, Jsi_Value *v)
{
    if (v->vt == JSI_VT_BOOL)  return v->d.val ? 0:1;
    return 0;
}

bool Jsi_ValueIsEqual(Jsi_Interp* interp, Jsi_Value* v1, Jsi_Value* v2)
{
    int eq = 0;
    if (v1->vt == JSI_VT_OBJECT && v2->vt == JSI_VT_OBJECT && v1->d.obj == v2->d.obj)
        eq = 1;
    else if (Jsi_ValueIsNull(interp, v1) && Jsi_ValueIsNull(interp, v2))
        eq = 1;
    else if (Jsi_ValueIsUndef(interp, v1) && Jsi_ValueIsUndef(interp, v2))
        eq = 1;
    else if (Jsi_ValueIsBoolean(interp, v1) && Jsi_ValueIsBoolean(interp, v2)) {
        bool b1, b2;
        eq = (Jsi_GetBoolFromValue(interp, v1, &b1) == JSI_OK
            && Jsi_GetBoolFromValue(interp, v2, &b2) == JSI_OK
            && b1 == b2);
    } else if (Jsi_ValueIsNumber(interp, v1) && Jsi_ValueIsNumber(interp, v2)) {
        Jsi_Number n1, n2;
        eq = (Jsi_GetNumberFromValue(interp, v1, &n1) == JSI_OK
            && Jsi_GetNumberFromValue(interp, v2, &n2) == JSI_OK
            && n1 == n2);
    } else if (Jsi_ValueIsString(interp, v1) && Jsi_ValueIsString(interp, v2)) {
        const char *s1, *s2;
        int l1, l2;
        eq = (((s1=Jsi_ValueString(interp, v1, &l1)) && ((s2=Jsi_ValueString(interp, v2, &l2)))
            && l1 == l2 && Jsi_Strcmp(s1, s2)==0));
    }
    return eq;
}

void jsi_ValueToPrimitive(Jsi_Interp *interp, Jsi_Value **vPtr)
{
    Jsi_Value *v = *vPtr;
    if (v->vt != JSI_VT_OBJECT)
        return;
    DECL_VALINIT(res);
    Jsi_Value *rPtr = &res;
    Jsi_Obj *obj = v->d.obj;
    //rPtr = v;
    switch(obj->ot) {
        case JSI_OT_BOOL:
            Jsi_ValueMakeBool(interp,&rPtr, obj->d.val);
            break;
        case JSI_OT_NUMBER:
            Jsi_ValueMakeNumber(interp,&rPtr, obj->d.num);
            break;
        case JSI_OT_STRING:
            if (obj->isstrkey) {
                res.d.s = obj->d.s;
                res.f.bits.isstrkey = 1;
                obj->d.s.str = NULL;
            } else {
                if (obj->refcnt==1) {
                    Jsi_ValueMakeString(interp, &rPtr, obj->d.s.str);
                    res.d.s = obj->d.s;
                    obj->d.s.str = NULL;
                } else if (obj->d.s.len >= 0) 
                {
                    Assert(obj->refcnt>=1);
                    obj->refcnt--;
                    int bytes = obj->d.s.len;
                    jsi_ValueMakeBlobDup(interp, &rPtr, (uchar*)obj->d.s.str, bytes);
                } else
                    Jsi_ValueMakeStringDup(interp, &rPtr, obj->d.s.str);
            }
            break;
        case JSI_OT_FUNCTION: {
            Jsi_DString dStr;
            Jsi_DSInit(&dStr);
            Jsi_FuncObjToString(interp, obj->d.fobj->func, &dStr, 3);
            Jsi_ValueFromDS(interp, &dStr, &rPtr);
            break;
        }
        case JSI_OT_USEROBJ: {
            Jsi_DString dStr;
            Jsi_DSInit(&dStr);
            jsi_UserObjToName(interp, obj->d.uobj, &dStr);
            Jsi_ValueFromDS(interp, &dStr, &rPtr);
            break;
        }
        default:
            Jsi_ValueMakeStringKey(interp,&rPtr, "[object Object]");
            break;
    }
    Jsi_ValueReset(interp, vPtr);
    res.refCnt = v->refCnt;
#ifdef JSI_MEM_DEBUG
    memcpy(v, &res, sizeof(res)-sizeof(res.VD));
#else
    *v = res;
#endif
}

static void jsi_ValueToPrimitiveRes(Jsi_Interp *interp, Jsi_Value *v, Jsi_Value *rPtr)
{
    if (v->vt != JSI_VT_OBJECT) {
#ifdef JSI_MEM_DEBUG
    memcpy(rPtr, v, sizeof(*v)-sizeof(v->VD));
#else
    *rPtr = *v; //TODO: usde only by ValueCompare, so refCnt doesn't matter?
#endif
        return;
    }
    Jsi_Obj *obj = v->d.obj;
    switch(obj->ot) {
        case JSI_OT_BOOL:
            Jsi_ValueMakeBool(interp, &rPtr, obj->d.val);
            break;
        case JSI_OT_NUMBER:
            Jsi_ValueMakeNumber(interp, &rPtr, obj->d.num);
            break;
        case JSI_OT_STRING:
            rPtr->vt = JSI_VT_STRING;
            rPtr->d.s = obj->d.s;
            rPtr->f.bits.isstrkey = 1;
            break;
        default:
            break;
    }
}

int Jsi_ValueCmp(Jsi_Interp *interp, Jsi_Value *v1, Jsi_Value* v2, int flags)
{
    DECL_VALINIT(res1);
    DECL_VALINIT(res2);
    int r = 1;
    int nocase = (flags&JSI_SORT_NOCASE), dict = ((flags & JSI_SORT_DICT));
    if (v1 == v2)
        return 1;
    if (v1->vt != v2->vt) {
        jsi_ValueToPrimitiveRes(interp, v1, &res1);
        jsi_ValueToPrimitiveRes(interp, v2, &res2);
        v1 = &res1;
        v2 = &res2;
    }
    if (v1->vt != v2->vt) {
        if ((flags&JSI_CMP_EXACT))
            return 1;
        if ((v1->vt == JSI_VT_UNDEF || v1->vt == JSI_VT_NULL) && 
            (v2->vt == JSI_VT_UNDEF || v2->vt == JSI_VT_NULL)) {
            r = 0;
        } else {
            Jsi_Number n1, n2;
            n1 = Jsi_ValueToNumberInt(interp, v1, 0);
            n2 = Jsi_ValueToNumberInt(interp, v2, 0);
            r = (n2 - n1);
        }
    } else {
        switch (v1->vt) {
            case JSI_VT_NUMBER:
                if (v2->d.num == v1->d.num) return 0;
                r = (v2->d.num < v1->d.num ? -1 : 1);
                break;
            case JSI_VT_BOOL:
                r = (v2->d.val - v1->d.val);
                break;
            case JSI_VT_STRING:
                r = (Jsi_StrcmpDict(v2->d.s.str, v1->d.s.str, nocase, dict));
                break;
            case JSI_VT_OBJECT:
                /* TODO: refer to objects joined to each other */
                if (v2->vt != JSI_VT_OBJECT)
                    r = 1;
                else if (v1->d.obj->ot == JSI_OT_STRING && v2->d.obj->ot == JSI_OT_STRING)
                    r = (Jsi_StrcmpDict(v2->d.obj->d.s.str, v1->d.obj->d.s.str, nocase, dict));
                else
                    r = (v2->d.obj - v1->d.obj);
                break;
            case JSI_VT_UNDEF:
            case JSI_VT_NULL:
                r = 0;
                break;
            default:
                Jsi_LogBug("Unexpected value type");
        }
    }
    return r;
}

/**
 * @brief Split a string.
 * @param interp 
 * @param str - input string to split
 * @param split - to split on
 * @returns an array of string values
 * 
 * 
 */
Jsi_Value *Jsi_StringSplit(Jsi_Interp *interp, const char *str, const char *spliton)
{
    char **argv; int argc;
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    Jsi_SplitStr(str, &argc, &argv, spliton, &dStr);
    Jsi_Value *nret = Jsi_ValueNewArray(interp, NULL, 0);
    Jsi_Obj *obj = nret->d.obj;
    int i;
    for (i = 0; i < argc; ++i) {
        Jsi_ObjArraySet(interp, obj, Jsi_ValueNewStringDup(interp, argv[i]), i);
    }
    Jsi_ObjSetLength(interp, obj, argc);
    Jsi_ValueMakeArrayObject(interp, &nret, obj);
    Jsi_DSFree(&dStr);
    return nret;
}

void jsi_ValueObjSet(Jsi_Interp *interp, Jsi_Value *target, const char *key, Jsi_Value *value, int flags, int isstrkey)
{
    Jsi_TreeEntry *hPtr;
    if (target->vt != JSI_VT_OBJECT) {
        if (interp->strict)
            Jsi_LogWarn("Target is not object: %d", target->vt);
        return;
    }
    hPtr = Jsi_ObjInsert(interp, target->d.obj, key, value, (isstrkey?JSI_OM_ISSTRKEY:0));
    if (!hPtr)
        return;
    hPtr->f.flags |= (flags&JSI_TREE_USERFLAG_MASK);
}

Jsi_Value *jsi_ValueObjKeyAssign(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *keyval, Jsi_Value *value, int flag)
{
    int arrayindex = -1;

    if (keyval->vt == JSI_VT_NUMBER && Jsi_NumberIsInteger(keyval->d.num) && keyval->d.num >= 0) {
        arrayindex = (int)keyval->d.num;
    }
    /* TODO: array["1"] also extern the length of array */
    
    if (arrayindex >= 0 && arrayindex < MAX_ARRAY_LIST &&
        target->vt == JSI_VT_OBJECT && target->d.obj->arr) {
        return jsi_ObjArraySetDup(interp, target->d.obj, value, arrayindex);
    }
    const char *kstr = Jsi_ValueToString(interp, keyval, NULL);
    
#if (defined(JSI_HAS___PROTO__) && JSI_HAS___PROTO__==2)
    if (Jsi_Strcmp(kstr, "__proto__")==0) {
        Jsi_Obj *obj = target->d.obj;
        obj->__proto__ = Jsi_ValueDup(interp, value);
        //obj->clearProto = 1;
        return obj->__proto__;
    }
#endif
    Jsi_Value *v = Jsi_ValueNew1(interp);
    if (value)
        Jsi_ValueCopy(interp, v, value);

    jsi_ValueObjSet(interp, target, kstr, v, flag, (Jsi_ValueIsStringKey(interp, keyval)? JSI_OM_ISSTRKEY:0));
    Jsi_DecrRefCount(interp, v);
    return v;
}

static Jsi_Value *jsi_ValueLookupBase(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *key, Jsi_Value **ret)
{
    if (!target)
        return NULL;
    if (target->vt != JSI_VT_OBJECT) {
        Jsi_LogError("subscript operand is not object");
        return NULL;
    }
    const char *keyStr = Jsi_ValueToString(interp, key, NULL);
    if (!keyStr)
        return NULL;
    bool isStrKey = (key->vt == JSI_VT_STRING && key->f.bits.isstrkey);
    Jsi_Value *v = Jsi_ValueObjLookup(interp, target, (char*)keyStr, isStrKey);
    if (v)
        return v;
    if (target->d.obj->__proto__)
        return jsi_ValueLookupBase(interp, target->d.obj->__proto__, key, ret);
    return NULL;
}


Jsi_Value* jsi_ValueSubscript(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *key, Jsi_Value **ret)
{
    int len;
    Jsi_ValueReset(interp, ret);
    Jsi_Value *v = jsi_ValueLookupBase(interp, target, key, ret);
    if (v)
        return v;
    const char *keyStr = Jsi_ValueString(interp, key, NULL);
    if (!keyStr)
        return NULL;
    // Special cases such as "length", "constructor", etc...
    if (Jsi_Strcmp(keyStr,"length")==0) {
        if (Jsi_ValueIsString(interp, target)) {
            len = Jsi_ValueStrlen(target);
        } else if (target->vt == JSI_VT_OBJECT && target->d.obj->isarrlist) {
            len = target->d.obj->arrCnt;
        } else if (target->vt == JSI_VT_OBJECT && target->d.obj->ot == JSI_OT_FUNCTION) {
            Jsi_Func *fo = target->d.obj->d.fobj->func;
            if (fo->type == FC_NORMAL)
                len = fo->argnames->count;
            else
                len = fo->cmdSpec->maxArgs, len = (len>=0?len:fo->cmdSpec->minArgs);
        } else if (target->vt == JSI_VT_OBJECT && target->d.obj->tree) {
            len = target->d.obj->tree->numEntries;
        } else {
            return NULL;
        }
        (*ret)->vt = JSI_VT_NUMBER;
        (*ret)->d.num = (Jsi_Number)len;
        return *ret;
    }

    if (target->vt == JSI_VT_OBJECT && (interp->subOpts.noproto==0 && Jsi_Strcmp(keyStr,"constructor")==0)) {
        const char *cp;
        Jsi_Obj *o = target->d.obj->constructor;
        if (o) {
            if (o->ot == JSI_OT_FUNCTION) {
                Jsi_Value *proto = Jsi_TreeObjGetValue(o, "prototype", 0);
                if (proto && proto->vt == JSI_VT_OBJECT && proto->d.obj->constructor) {
                    o = proto->d.obj->constructor;
                }
            }
        } else {
            switch(target->d.obj->ot) {
                case JSI_OT_NUMBER:
                    cp = "Number";
                    break;
                case JSI_OT_BOOL:
                    cp = "Boolean";
                    break;
                case JSI_OT_STRING:
                    cp = "String";
                    break;
                case JSI_OT_REGEXP:
                    cp = "RegExp";
                    break;
                case JSI_OT_OBJECT:
                    if (target->d.obj->isarrlist) {
                        cp = "Array";
                        break;
                    }
                    cp = "Object";
                    break;
                default:
                    Jsi_ValueMakeUndef(interp, ret);
                    return *ret;
            }
            v = Jsi_ValueObjLookup(interp, interp->csc, cp, 0);
            if (v==NULL || v->vt != JSI_VT_OBJECT)
                return NULL;
            o = target->d.obj->constructor = v->d.obj;
        }
        Jsi_ValueMakeObject(interp, ret, o);
        return *ret;
    }

    if (target->vt == JSI_VT_OBJECT && target->d.obj->ot == JSI_OT_FUNCTION) {
        /* Looking up something like "String.substr" */
        Jsi_Func* func = target->d.obj->d.fobj->func;
        if (func->type == FC_BUILDIN) {
            if (func->f.bits.iscons && func->name) {
                Jsi_Value *v = Jsi_ValueObjLookup(interp, interp->csc, (char*)func->name, 0);
                if (!v) {
                } else {
                    bool ooo = interp->subOpts.noproto;
                    interp->subOpts.noproto = 0;
                    v = Jsi_ValueObjLookup(interp, v, "prototype", 0);
                    interp->subOpts.noproto = ooo;
                    
                    if (v && ((v = Jsi_ValueObjLookup(interp, v, (char*)keyStr, 0)))) {
                        if (v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_FUNCTION && Jsi_Strcmp(func->name,"Interp")) {
                            Jsi_Func* sfunc = v->d.obj->d.fobj->func;
                            /* Handle "Math.pow(2,3)", "String.fromCharCode(0x21)", ... */
                            sfunc->callflags.bits.addargs = 1;
                        }
                        return v;
                    }
                }
            }
            if (Jsi_ValueIsString(interp, key)) {
                char *kstr = Jsi_ValueString(interp, key, NULL);
                if (!Jsi_Strcmp(kstr,"call") || !Jsi_Strcmp(kstr,"apply") || !Jsi_Strcmp(kstr,"bind")) {
                    char fbuf[100];
                    snprintf(fbuf, sizeof(fbuf), "Function.%s", kstr);
                    Jsi_Value *vv = Jsi_NameLookup(interp, fbuf);
                    if (vv)
                        return vv;
                }
            }
        }
    }
    return NULL;
}

bool Jsi_ValueKeyPresent(Jsi_Interp *interp, Jsi_Value *target, const char *key, int isstrkey)
{
    SIGASSERT(interp,INTERP);
    //SIGASSERT(target,VALUE);
    if (Jsi_TreeObjGetValue(target->d.obj, key, isstrkey))
        return 1;
    if (target->d.obj->__proto__ == NULL || target->d.obj->__proto__ == target)
        return 0;
    return Jsi_ValueKeyPresent(interp, target->d.obj->__proto__, key, isstrkey);
}

void jsi_ValueObjGetKeys(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *ret, bool isof)
{
    Jsi_IterObj *io = jsi_IterObjNew(interp, NULL);
    Jsi_Obj *to = target->d.obj;
    
    if (target->vt != JSI_VT_UNDEF && target->vt != JSI_VT_NULL) {

        if (target->vt == JSI_VT_OBJECT && to->arr) {
            io->isArrayList = 1;
            io->count = to->arrCnt;
        } else {
            if (isof &&interp->strict)
                Jsi_LogWarn("non-array in 'for...of'");
            IterGetKeys(interp, target, io, 0);
        }
    }
    io->obj = to;
    io->isof = isof;
    Jsi_Obj *r = Jsi_ObjNew(interp);
    r->ot = JSI_OT_ITER;
    r->d.iobj = io;
    Jsi_ValueMakeObject(interp, &ret, r);
}

Jsi_RC Jsi_ValueGetKeys(Jsi_Interp *interp, Jsi_Value *target, Jsi_Value *ret)
{
    uint i, n = 0;
    Jsi_IterObj *io;
    if (target->vt != JSI_VT_OBJECT)
        return JSI_ERROR;
    Jsi_Obj *to = target->d.obj;
    Jsi_Obj *r = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
    Jsi_ValueMakeArrayObject(interp, &ret, r);
    if (to->arr) {
        for (i=0; i<to->arrCnt; i++)
            if (to->arr[i]) n++;
        if (Jsi_ObjArraySizer(interp, r, n) <= 0) {
            Jsi_LogError("too long");
            Jsi_ValueMakeUndef(interp, &ret);
            return JSI_ERROR;
        }
        for (i=0, n=0; i<to->arrCnt; i++) {
            if (to->arr[i]) {
                r->arr[n] = Jsi_ValueNewNumber(interp, (Jsi_Number)i);
                Jsi_IncrRefCount(interp, r->arr[n]);
                n++;
            }
        }
        r->arrCnt = n;
        return JSI_OK;
    }
    io = jsi_IterObjNew(interp, NULL);
    IterGetKeys(interp, target, io, 0);
    if (Jsi_ObjArraySizer(interp, r, io->count) <= 0) {
        Jsi_LogError("too long");
        Jsi_ValueMakeUndef(interp, &ret);
        return JSI_ERROR;
    }
    for (i=0; i<io->count; i++) {
        r->arr[i] = (io->keys[i] ? Jsi_ValueNewStringKey(interp, io->keys[i]) : NULL);
        Jsi_IncrRefCount(interp, r->arr[i]);
    }
    io->count = 0;
    r->arrCnt = i;
    jsi_IterObjFree(io);
    return JSI_OK;
}

jsi_ScopeChain *jsi_ScopeChainNew(Jsi_Interp *interp, int cnt)
{
    jsi_ScopeChain *r = (jsi_ScopeChain *)Jsi_Calloc(1, sizeof(*r));
    r->interp = interp;
    SIGINIT(r,SCOPE);
    r->chains = (Jsi_Value **)Jsi_Calloc(cnt, sizeof(r->chains[0]));
    r->chains_cnt = cnt;
    return r;
}

Jsi_Value *jsi_ScopeChainObjLookupUni(jsi_ScopeChain *sc, char *key)
{
    int i;
    Jsi_Value *ret;
    for (i = sc->chains_cnt - 1; i >= 0; --i) {
        if ((ret = Jsi_ValueObjLookup(sc->interp, sc->chains[i], key, 0))) {
            return ret;
        }
    }
    return NULL;
}

jsi_ScopeChain *jsi_ScopeChainDupNext(Jsi_Interp *interp, jsi_ScopeChain *sc, Jsi_Value *next)
{
    if (!sc) {
        jsi_ScopeChain *nr = jsi_ScopeChainNew(interp, 1);
        nr->chains[0] = next;
        Jsi_IncrRefCount(interp, next);
        nr->chains_cnt = 1;
        return nr;
    }
    jsi_ScopeChain *r = jsi_ScopeChainNew(interp, sc->chains_cnt + 1);
    int i;
    for (i = 0; i < sc->chains_cnt; ++i) {
        r->chains[i] = sc->chains[i];
        Jsi_IncrRefCount(interp, sc->chains[i]);
    }
    r->chains[i] =  next;
    Jsi_IncrRefCount(interp, next);
    r->chains_cnt = i + 1;
    return r;
}

void jsi_ScopeChainFree(Jsi_Interp *interp, jsi_ScopeChain *sc)
{
    int i;
    for (i = 0; i < sc->chains_cnt; ++i) {
        Jsi_DecrRefCount(interp, sc->chains[i]);
    }
    Jsi_Free(sc->chains);
    _JSI_MEMCLEAR(sc);
    Jsi_Free(sc);
}

int Jsi_ValueGetLength(Jsi_Interp *interp, Jsi_Value *v) {
    if (Jsi_ValueIsArray(interp, v))
        return v->d.obj->arrCnt;
    Jsi_LogWarn("expected array");
    return 0;
}

char *Jsi_ValueArrayIndexToStr(Jsi_Interp *interp, Jsi_Value *args, int index, int *lenPtr)
{
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, index);
    if (!arg)
        return NULL;
    char *res = Jsi_ValueString(interp, arg, lenPtr);
    if (res)
        return res;
    res = (char*)Jsi_ValueToString(interp, arg, NULL);
    if (res && lenPtr)
        *lenPtr = Jsi_Strlen(res);
    return res;
}

Jsi_RC Jsi_ValueInsert(Jsi_Interp *interp, Jsi_Value *target, const char *key, Jsi_Value *val, int flags)
{
    if (target == NULL)
        target = interp->csc;
    if (target->vt != JSI_VT_OBJECT) {
        if (interp->strict)
            Jsi_LogWarn("Target is not object");
        return JSI_ERROR;
    }
    target->f.flag |= flags;
    if (Jsi_ObjInsert(interp, target->d.obj, key, val, flags))
        return JSI_OK;
    return JSI_ERROR;
}

Jsi_RC Jsi_ValueInsertArray(Jsi_Interp *interp, Jsi_Value *target, int key, Jsi_Value *val, int flags)
{
    if (target->vt != JSI_VT_OBJECT) {
        if (interp->strict)
            Jsi_LogWarn("Target is not object");
        return JSI_ERROR;
    }
    Jsi_Obj *obj = target->d.obj;
    
    if (obj->isarrlist) {
        if (key >= 0 && key < interp->maxArrayList) {
            Jsi_ObjArraySet(interp, obj, val, key);
            return JSI_OK;
        }
        return JSI_ERROR;
    }
    char unibuf[100];
    Jsi_NumberItoA10(key, unibuf, sizeof(unibuf));
    Jsi_ObjInsert(interp, obj, unibuf, val, flags);
    return JSI_OK;
}

/* OBJ INTERFACE TO BTREE */

static void IterObjInsertKey(Jsi_IterObj *io, const char *key)
{
    assert(!io->isArrayList);
    if (io->depth) {
        uint i;
        for (i=0; i<io->count; i++) {
            if (!Jsi_Strcmp(key, io->keys[i]))
                return;
        }
    }

    if (io->count >= io->size) {
        io->size += 10;
        io->keys = (const char**)Jsi_Realloc(io->keys, io->size * sizeof(io->keys[0]));
    }
    io->keys[io->count] = key;
    io->count++;
}
static void IterObjInsert(Jsi_IterObj *io, Jsi_TreeEntry *hPtr)
{
    IterObjInsertKey(io, (const char*)Jsi_TreeKeyGet(hPtr));
}

Jsi_TreeEntry * Jsi_ObjInsert(Jsi_Interp *interp, Jsi_Obj *obj, const char *key, Jsi_Value *val, int flags)
{
    Jsi_TreeEntry *hPtr;
    SIGASSERT(val, VALUE);
    /*if (val)
        Jsi_IncrRefCount(interp, val);*/
    hPtr = Jsi_TreeObjSetValue(obj, key, val, (flags&JSI_OM_ISSTRKEY));
    if ((flags&JSI_OM_DONTDEL))
        val->f.bits.dontdel = hPtr->f.bits.dontdel = 1;
    if ((flags&JSI_OM_READONLY))
        val->f.bits.readonly =hPtr->f.bits.readonly = 1;
    if ((flags&JSI_OM_DONTENUM))
        val->f.bits.dontenum =hPtr->f.bits.dontenum = 1;
    return hPtr;
}

static Jsi_RC IterGetKeysCallback(Jsi_Tree* tree, Jsi_TreeEntry *hPtr, void *data)
{
    Jsi_IterObj *io = (Jsi_IterObj *)data;
    if (!hPtr->f.bits.dontenum) {
        IterObjInsert(io, hPtr);
    }
    return JSI_OK;
}

static void IterGetKeys(Jsi_Interp *interp, Jsi_Value *target, Jsi_IterObj *iterobj, int depth)
{
    if (!target) return;
    if (target->vt != JSI_VT_OBJECT) {
        if (interp->strict)
            Jsi_LogWarn("operand is not a object");
        return;
    }
    Jsi_Obj *to = target->d.obj;
    Jsi_CmdSpec *cs = NULL;
    if (to->ot == JSI_OT_USEROBJ) {
        Jsi_UserObj *uobj = to->d.uobj;
        cs = uobj->reg->spec;
    } else if (to->ot == JSI_OT_FUNCTION) {
        Jsi_FuncObj *fobj = to->d.fobj;
        if (fobj->func->type == FC_BUILDIN)
            cs = fobj->func->cmdSpec;
    }
    if (cs) {
        while (cs->name) {
            IterObjInsertKey(iterobj, cs->name);
            cs++;
        }
        return;
    }
    iterobj->depth = depth;
    Jsi_TreeWalk(target->d.obj->tree, IterGetKeysCallback, iterobj, 0);
    if (target->d.obj->__proto__ && target != target->d.obj->__proto__)
        IterGetKeys(interp, target->d.obj->__proto__, iterobj, depth+1);
    iterobj->depth = depth;
}

Jsi_Value* Jsi_ValueMakeDStringObject(Jsi_Interp *interp, Jsi_Value **vPtr, Jsi_DString *dsPtr)  {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    Jsi_Obj *obj;
    if (!v)
        v = Jsi_ValueNew(interp);
    else {
        assert(v->vt <= JSI_VT__MAX);
        if (v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_STRING
            && v->d.obj->refcnt == 1
        ) {
            Jsi_ObjFromDS(dsPtr, v->d.obj);
            return v;
        }
        Jsi_ValueReset(interp, &v);
    }
    obj = Jsi_ObjNewType(interp, JSI_OT_STRING);
    Jsi_ObjFromDS(dsPtr, obj);
    Jsi_ValueMakeObject(interp, &v, obj);
    return v;
}

Jsi_Value* Jsi_ValueMakeObject(Jsi_Interp *interp, Jsi_Value **vPtr, Jsi_Obj *o)  {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (v && v->vt == JSI_VT_OBJECT && o == v->d.obj)
        return v;
    if (v)
        Jsi_ValueReset(interp, vPtr);
    else
        v = Jsi_ValueNew(interp);
    //Jsi_IncrRefCount(interp, v);
    if (!o)
        o = Jsi_ObjNewType(interp, JSI_OT_OBJECT);
    v->vt = JSI_VT_OBJECT;
    v->d.obj = o;
    Jsi_ObjIncrRefCount(interp,v->d.obj);
    return v;
}

Jsi_Value* Jsi_ValueMakeArrayObject(Jsi_Interp *interp, Jsi_Value **vPtr, Jsi_Obj *o)  {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!o)
        o = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
    if (!v)
        v = Jsi_ValueNew(interp);
    else {
       if (v->vt == JSI_VT_OBJECT && o == v->d.obj) {
            if (!o->isarrlist) {
                if (o->tree)
                    Jsi_TreeDelete( o->tree);
                o->tree = NULL;
                o->__proto__ = interp->Array_prototype;
                o->isarrlist = 1;
            }
            return v;
        }
        Jsi_ValueReset(interp, vPtr);
    }
    v->vt = JSI_VT_OBJECT;
    v->d.obj = o;
    o->ot = JSI_OT_OBJECT;
    o->__proto__ = interp->Array_prototype;
    o->isarrlist = 1;
    Jsi_ObjArraySizer(interp, o, 0);
    Jsi_ObjIncrRefCount(interp,v->d.obj);
    return v;
}

Jsi_Value* Jsi_ValueMakeNumber(Jsi_Interp *interp, Jsi_Value **vPtr, Jsi_Number n) {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!v)
        v = Jsi_ValueNew(interp);
    else
        Jsi_ValueReset(interp, vPtr);
    v->vt = JSI_VT_NUMBER;
    v->d.num = n;
    return v;
}

Jsi_Value* Jsi_ValueMakeBool(Jsi_Interp *interp, Jsi_Value **vPtr, int b) {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!v)
        v = Jsi_ValueNew(interp);
    else
        Jsi_ValueReset(interp, vPtr);
    v->vt = JSI_VT_BOOL;
    v->d.val = b;
    return v;
}

Jsi_Value* Jsi_ValueMakeBlob(Jsi_Interp *interp, Jsi_Value **vPtr, unsigned char *s, int len) {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!v)
        v = Jsi_ValueNew(interp);
    else
        Jsi_ValueReset(interp, vPtr);
    Jsi_Obj *obj = Jsi_ObjNewType(interp, JSI_OT_STRING);
    Jsi_ValueMakeObject(interp, &v, obj);
    obj->d.s.str = (char*)s;
    obj->d.s.len = len;
    obj->isBlob = 1;
    return v;
}
Jsi_Value* jsi_ValueMakeBlobDup(Jsi_Interp *interp, Jsi_Value **ret, unsigned char *s, int len) {
    if (len<0) len = Jsi_Strlen((char*)s);
    uchar *dp = (uchar*)Jsi_Malloc(len+1);
    memcpy(dp, s, len);
    dp[len] = 0;
    return Jsi_ValueMakeBlob(interp, ret, dp, len);
}


Jsi_Value* Jsi_ValueMakeString(Jsi_Interp *interp, Jsi_Value **vPtr, const char *s) {
    return Jsi_ValueMakeBlob(interp, vPtr, (unsigned char *)s, Jsi_Strlen(s));
}

Jsi_Value* Jsi_ValueMakeStringKey(Jsi_Interp *interp, Jsi_Value **vPtr, const char *s) {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!v)
        v = Jsi_ValueNew(interp);
    else
        Jsi_ValueReset(interp, vPtr);
    v->vt = JSI_VT_STRING;
    v->d.s.str = (char*)Jsi_KeyAdd(interp,s);
    v->d.s.len = Jsi_Strlen(s);
    v->f.bits.isstrkey = 1;
    return v;
}

Jsi_Value* Jsi_ValueMakeNull(Jsi_Interp *interp, Jsi_Value **vPtr) {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!v)
        v = Jsi_ValueNew(interp);
    else
        Jsi_ValueReset(interp, vPtr);
    v->vt = JSI_VT_NULL;
    return v;
}

Jsi_Value* Jsi_ValueMakeUndef(Jsi_Interp *interp, Jsi_Value **vPtr) {
    Jsi_Value *v = (vPtr?*vPtr:NULL);
    if (!v)
        v = Jsi_ValueNew(interp);
    else {
        if (v->vt == JSI_VT_UNDEF) return v;
        Jsi_ValueReset(interp, vPtr);
    }
    return v;
}

Jsi_Value* Jsi_ValueNewNumber(Jsi_Interp *interp, Jsi_Number n) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    v->vt = JSI_VT_NUMBER;
    v->d.num = n;
    return v;
}

Jsi_Value* Jsi_ValueNewObj(Jsi_Interp *interp, Jsi_Obj *o) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    Jsi_ValueMakeObject(interp, &v, o);
    return v;
}

Jsi_Value* Jsi_ValueNewString(Jsi_Interp *interp, const char *s, int len) {
    assert(s);
    Jsi_Value *v = Jsi_ValueNew(interp);
    Jsi_Obj *obj = Jsi_ObjNewType(interp, JSI_OT_STRING);
    Jsi_ValueMakeObject(interp, &v, obj);
    obj->d.s.str = (char*)s;
    obj->d.s.len = (len<0?Jsi_Strlen(s):(uint)len);
    return v;
}

Jsi_Value* Jsi_ValueNewStringDup(Jsi_Interp *interp, const char *s) {
    return Jsi_ValueNewString(interp, Jsi_Strdup(s), -1);
}

Jsi_Value* Jsi_ValueNewStringKey(Jsi_Interp *interp, const char *s) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    v->vt = JSI_VT_STRING;
    v->d.s.str = (char*)Jsi_KeyAdd(interp,s);
    v->d.s.len = Jsi_Strlen(s);
    v->f.bits.isstrkey = 1;
    return v;
}


Jsi_Value* Jsi_ValueNewStringConst(Jsi_Interp *interp, const char *s, int len) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    v->vt = JSI_VT_STRING;
    v->d.s.str = (char*)s;
    v->d.s.len = (len<0?Jsi_Strlen(s):(uint)len);
    v->f.bits.isstrkey = 1;
    return v;
}

Jsi_Value* Jsi_ValueNewBlob(Jsi_Interp *interp, unsigned char *s, uint len) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    Jsi_Obj *o = Jsi_ObjNewType(interp, JSI_OT_STRING);
    Jsi_ValueMakeObject(interp, &v, o);
    o->d.s.str = (char*)Jsi_Malloc(len+1);
    memcpy(o->d.s.str, (char*)s, len);
    o->d.s.str[len] = 0;
    o->d.s.len = len;
    o->isBlob = 1;
    return v;
}

Jsi_Value* Jsi_ValueNewBoolean(Jsi_Interp *interp, int bval) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    v->vt = JSI_VT_BOOL;
    v->d.val = bval;
    return v;
}

Jsi_Value* Jsi_ValueNewNull(Jsi_Interp *interp) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    v->vt = JSI_VT_NULL;
    return v;
}

Jsi_Value *Jsi_ValueNewArray(Jsi_Interp *interp, const char **items, int count)
{
    Jsi_Obj *obj = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
    int i = 0;
    if (count<0) {
        count = 0;
        while (items[count])
            count++;
    }
    if (Jsi_ObjArraySizer(interp, obj, count) <= 0) {
        Jsi_ObjFree(interp, obj);
        return NULL;
    }
    for (i = 0; i < count; ++i) {
        obj->arr[i] = Jsi_ValueNewStringDup(interp, items[i]);
        Jsi_IncrRefCount(interp, obj->arr[i]);
    }
    obj->arrCnt = count;
    assert(obj->arrCnt<=obj->arrMaxSize);
    return Jsi_ValueMakeArrayObject(interp, NULL, obj);
}

Jsi_Obj *Jsi_ValueGetObj(Jsi_Interp *interp, Jsi_Value* v)
{
    if (v->vt == JSI_VT_OBJECT) {
        return v->d.obj;
    }
    return NULL;
}

int Jsi_ValueStrlen(Jsi_Value* v) {
    //if (v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_STRING && v->d.obj->isBlob)
    //    return v->d.obj->d.s.len;
    Jsi_String *s = jsi_ValueString(v);
    if (s == 0 || s->str == 0)
        return 0;
#if JSI__UTF8
    return (int)Jsi_NumUtfChars(s->str, s->len);
#else
    if (s->len>=0) return s->len;
    return (int)Jsi_NumUtfChars(s->str, s->len);
#endif
}

char *Jsi_ValueString(Jsi_Interp *interp, Jsi_Value* v, int *lenPtr)
{
    if (!v) return NULL;
    Jsi_String *s = jsi_ValueString(v);
    if (s) {
        if (lenPtr)
            *lenPtr = (s->len<0 ? (int)Jsi_Strlen(s->str) : s->len);
        return s->str;
    }
    if (lenPtr)
        *lenPtr = 0;
    return NULL;
}

unsigned char *Jsi_ValueBlob(Jsi_Interp *interp, Jsi_Value* v, int *lenPtr)
{
    return (unsigned char*)Jsi_ValueString(interp, v, lenPtr);
}

char* Jsi_ValueGetStringLen(Jsi_Interp *interp, Jsi_Value *pv, int *lenPtr)
{
    if (!pv)
        return NULL;
    Jsi_String *s = jsi_ValueString(pv);
    if (!s)
        return NULL;
    if (lenPtr)
        *lenPtr = (s->len<0 ? (int)Jsi_Strlen(s->str) : s->len);
    return s->str;
}

int Jsi_ValueInstanceOf( Jsi_Interp *interp, Jsi_Value* v1, Jsi_Value* v2)
{
    Jsi_Value *proto, *sproto;
    if (v1->vt != JSI_VT_OBJECT || v2->vt != JSI_VT_OBJECT  || v2->d.obj->ot != JSI_OT_FUNCTION)
        return 0;
    proto = Jsi_ValueObjLookup(interp, v2, "prototype", 0);
    if (!proto)
        return 0;
    sproto = v1->d.obj->__proto__ ;
    while (sproto) {
        if (sproto == proto)
            return 1;
        if (sproto->vt != JSI_VT_OBJECT)
            return 0;
        sproto = sproto->d.obj->__proto__;
    }
    return 0;
}


Jsi_RC jsi_InitValue(Jsi_Interp *interp, int release)
{
    return JSI_OK;
}

void  Jsi_ValueFromDS(Jsi_Interp *interp, Jsi_DString *dsPtr, Jsi_Value **ret)
{
    char *cp = NULL;
    int len = Jsi_DSLength(dsPtr);
    if (len && !(cp=(char*)dsPtr->strA)) 
        cp = Jsi_StrdupLen(dsPtr->Str, len);
    dsPtr->strA = NULL;
    dsPtr->Str[0] = 0;
    dsPtr->len = 0;
    dsPtr->spaceAvl = dsPtr->staticSize;
    if (!cp)
        Jsi_ValueMakeStringDup(interp, ret, "");
    else
        Jsi_ValueMakeBlob(interp, ret, (uchar*)cp, len);
}

#endif
#ifndef JSI_LITE_ONLY
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif
/* TODO: handle delete */

#define ChkRegexp(_this, funcPtr, dest) \
    if (_this->vt == JSI_VT_OBJECT && _this->d.obj->ot == JSI_OT_FUNCTION &&  \
       _this->d.obj->__proto__ == interp->RegExp_prototype->d.obj->__proto__ ) { \
        skip = 1; \
        dest = Jsi_ValueArrayIndex(interp, args, 0); \
    } else if (_this->vt != JSI_VT_OBJECT || _this->d.obj->ot != JSI_OT_REGEXP) { \
        return Jsi_LogError("apply Regexp.%s to a non-regex object", funcPtr->cmdSpec->name); \
    } else  { \
        dest = _this; \
    }

void Jsi_RegExpFree(Jsi_Regex* re) {
    regfree(&re->reg);
    _JSI_MEMCLEAR(re);
    Jsi_Free(re);
}

Jsi_Regex* Jsi_RegExpNew(Jsi_Interp *interp, const char *regtxt, int eflag)
{
    bool isNew;
    Jsi_HashEntry *hPtr;
    int flag = REG_EXTENDED;
    char c, *cm, *ce;
    const char *cp;
    Jsi_Regex *re;
    if (interp->subOpts.noRegex) {
        Jsi_LogError("regex disabled for interp");
        return NULL;
    }

    eflag |= JSI_REG_STATIC;
    if (!regtxt[0])
        return NULL;
    hPtr = Jsi_HashEntryFind(interp->regexpTbl, regtxt);
    if (hPtr) {
        re = (Jsi_Regex*)Jsi_HashValueGet(hPtr);
        if (JSI_REG_STATIC & eflag)
            re->eflags |= JSI_REG_STATIC;
        return re;
    }
    cp = regtxt+1;
    if (regtxt[0] != '/')
        return NULL;
    ce = (char*)Jsi_Strrchr(cp, '/');
    if (ce == cp || !ce)
        return NULL;
    cm = ce + 1;    
    while (*cm) {
        c = *cm++;
        if (c == 'i') flag |= REG_ICASE;
        else if (c == 'g') eflag |= JSI_REG_GLOB;
        else if (c == 'm') { /* PERL NON-STANDARD */
            eflag |= JSI_REG_NEWLINE;
            flag |= REG_NEWLINE;
        }
#ifdef RE_DOT_NEWLINE
        else if (c == 's') { /* PERL NON-STANDARD */
            eflag |= JSI_REG_DOT_NEWLINE;
            flag |= RE_DOT_NEWLINE;
        }
#endif
    }
    *ce = 0;
    regex_t reg;
    if (regcomp(&reg, cp, flag)) {
        *ce++ = '/';
        Jsi_LogError("Invalid regex string '%s'", cp);
        return NULL;
    }
    *ce++ = '/';
    re = (Jsi_Regex*)Jsi_Calloc(1, sizeof(Jsi_Regex));
    SIGINIT(re, REGEXP);
    assert (re);
    re->reg = reg;
    re->eflags = eflag;
    re->flags = flag;
    hPtr = Jsi_HashEntryNew(interp->regexpTbl, regtxt, &isNew);
    assert(hPtr);
    Jsi_HashValueSet(hPtr, re);
    re->pattern = (char*)Jsi_HashKeyGet(hPtr);
    return re;

}

Jsi_RC jsi_RegExpValueNew(Jsi_Interp *interp, const char *regtxt, Jsi_Value *ret)
{
    
    Jsi_DString dStr = {};
    Jsi_DSAppend(&dStr, "/", regtxt, "/", NULL);
    Jsi_Regex *re = Jsi_RegExpNew(interp, Jsi_DSValue(&dStr), 0);
    Jsi_DSFree(&dStr);
    if (re == NULL)
        return JSI_ERROR;
    Jsi_Obj *o = Jsi_ObjNewType(interp, JSI_OT_REGEXP);
    Jsi_ValueMakeObject(interp, &ret, o);
    ret->d.obj->d.robj = re;
    ret->d.obj->ot = JSI_OT_REGEXP;
    return JSI_OK;
}


static Jsi_RC RegExp_constructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *target;
    
    if (Jsi_FunctionIsConstructor(funcPtr))
        target = _this;
    else {
        Jsi_Obj *o = Jsi_ObjNewType(interp, JSI_OT_REGEXP);
        Jsi_ValueMakeObject(interp, ret, o);
        target = *ret;
    }
    
    Jsi_Value *v = Jsi_ValueArrayIndex(interp, args, 0);
    const char *regtxt = "";
    const char *mods = NULL;
    if (v) {
        if (v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_REGEXP) {
            Jsi_ValueCopy(interp,target, v);
            return JSI_OK;
        } else if (!(regtxt = Jsi_ValueString(interp, v, NULL))) {
            return JSI_ERROR;
        }
    }
    Jsi_Value *f = Jsi_ValueArrayIndex(interp, args, 1);
    if (f)
        mods = Jsi_ValueString(interp, f, NULL);
    Jsi_DString dStr = {};
    Jsi_DSAppend(&dStr, "/", regtxt, "/", mods, NULL);
    Jsi_Regex *re = Jsi_RegExpNew(interp, Jsi_DSValue(&dStr), 0);
    Jsi_DSFree(&dStr);
    if (re == NULL)
        return JSI_ERROR;
    target->d.obj->d.robj = re;
    target->d.obj->ot = JSI_OT_REGEXP;
    return JSI_OK;
}

// Preform regexc setting *rc to 1 if match occurs.  If dStr != NULL, it is used to return matching strings.
Jsi_RC Jsi_RegExpMatch(Jsi_Interp *interp, Jsi_Value *pattern, const char *v, int *rc, Jsi_DString *dStr)
{
    Jsi_Regex *re;
    int regexec_flags = 0;
    if (rc)
        *rc = 0;
    if (pattern == NULL || pattern->vt != JSI_VT_OBJECT || pattern->d.obj->ot != JSI_OT_REGEXP) 
        return Jsi_LogError("expected pattern");
    re = pattern->d.obj->d.robj;
    regex_t *reg = &re->reg;
    
    regmatch_t pos = {};
    if (dStr)
        Jsi_DSInit(dStr);
        
    int r  = regexec(reg, v, 1, &pos, regexec_flags);

    if (r >= REG_BADPAT) {
        char buf[100];

        regerror(r, reg, buf, sizeof(buf));
        return Jsi_LogError("error while matching pattern: %s", buf);
    }
    if (r != REG_NOMATCH) {
        if (rc) *rc = 1;
        if (dStr && pos.rm_so >= 0 && pos.rm_eo >= 0 &&  pos.rm_eo >= pos.rm_so)
            Jsi_DSAppendLen(dStr, v + pos.rm_so, pos.rm_eo - pos.rm_so);
    }
    
    return JSI_OK;
}



Jsi_RC jsi_RegExpMatches(Jsi_Interp *interp, Jsi_Value *pattern, const char *str, int n, Jsi_Value *ret, int *ofs, bool match)
{
    Jsi_Regex *re;
    int regexec_flags = 0;
    Jsi_Value *seq = pattern;

    if (seq == NULL || seq->vt != JSI_VT_OBJECT || seq->d.obj->ot != JSI_OT_REGEXP) {
        Jsi_ValueMakeNull(interp, &ret);
        return JSI_OK;
    }
    re = seq->d.obj->d.robj;
    regex_t *reg = &re->reg;
    
    regmatch_t pos[MAX_SUBREGEX] = {};
    int num_matches = 0, r;
    int isglob = (re->eflags&JSI_REG_GLOB);
    Jsi_Obj *obj;
    
    do {
        r = regexec(reg, str, MAX_SUBREGEX, pos, regexec_flags);

        if (r >= REG_BADPAT) {
            char buf[JSI_BUFSIZ];

            regerror(r, reg, buf, sizeof(buf));
            return Jsi_LogError("error while matching pattern: %s", buf);
        }
        if (r == REG_NOMATCH) {
            if (num_matches == 0) {
                Jsi_ValueMakeNull(interp, &ret);
                return JSI_OK;
            }
            break;
        }

        if (num_matches == 0) {
            obj = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
            obj->__proto__ = interp->Array_prototype;
            Jsi_ValueMakeObject(interp, &ret, obj);
            Jsi_ObjSetLength(interp, ret->d.obj, 0);
        }
    
        int i;
        for (i = 0; i < MAX_SUBREGEX; ++i) {
            if (pos[i].rm_so <= 0 && pos[i].rm_eo <= 0)
                break;
            if (i && pos[i].rm_so == pos[i-1].rm_so && pos[i].rm_eo == pos[i-1].rm_eo)
                continue;
    
            int olen = -1;
            char *ostr = jsi_SubstrDup(str, -1, pos[i].rm_so, pos[i].rm_eo - pos[i].rm_so, &olen);
            Jsi_Value *val = Jsi_ValueMakeBlob(interp, NULL, (uchar*)ostr, olen);
            if (ofs)
                *ofs = pos[i].rm_eo;
            Jsi_ValueInsertArray(interp, ret, num_matches, val, 0);
            num_matches++;
            if ( match && isglob)
                break;
        }
        if (num_matches && match && !isglob)
            return JSI_OK;
        if (num_matches == 1 && (ofs || !isglob))
            break;
        
        str += pos[0].rm_eo;
        n -= pos[0].rm_eo;

        regexec_flags |= REG_NOTBOL;
    } while (n && pos[0].rm_eo>0);
    
    return JSI_OK;
}

Jsi_RC Jsi_RegExpMatches(Jsi_Interp *interp, Jsi_Value *pattern, const char *str, int slen, Jsi_Value *ret)
{
    return jsi_RegExpMatches(interp, pattern, str, slen, ret, NULL, 0);
}


#define FN_regexec JSI_INFO("\
Perform regexp match checking.  Returns the array of matches.\
With the global flag g, sets lastIndex and returns next match.")
static Jsi_RC RegexpExecCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int skip = 0;
    Jsi_Value *v;
    ChkRegexp(_this, funcPtr, v);
    /* TODO: add lastIndex support. */
    int slen;
    char *str = Jsi_ValueString(interp,Jsi_ValueArrayIndex(interp, args, skip), &slen);
    if (!str) 
        return Jsi_LogError("expected string");
    if (v == NULL || v->vt != JSI_VT_OBJECT || v->d.obj->ot != JSI_OT_REGEXP) 
        return Jsi_LogError("expected pattern");
    Jsi_Regex *re = v->d.obj->d.robj;
    int isglob = (re->eflags&JSI_REG_GLOB);
    Jsi_Value *l = NULL;
    Jsi_Number lv = 0;
    if (isglob) {
        l = Jsi_ValueObjLookup(interp, v, "lastIndex", 0);
        if (l && Jsi_ValueGetNumber(interp, l, &lv) != JSI_OK) 
            return Jsi_LogError("lastIndex not a number");
        if (l)
            re->lastIndex = (int)lv;
    }
    int ofs = 0;
    Jsi_RC rc = jsi_RegExpMatches(interp, v, re->lastIndex<slen?str+re->lastIndex:"", -1, *ret, isglob?&ofs:NULL, 0);
    if (isglob) {
        if (rc != JSI_OK)
            return rc;
        re->lastIndex += ofs;
        if (Jsi_ValueIsNull(interp, *ret))
            re->lastIndex = 0;
        lv = (Jsi_Number)re->lastIndex;
        if (!l)
            Jsi_ValueInsert(interp, v, "lastIndex", Jsi_ValueNewNumber(interp, lv), JSI_OM_DONTDEL);
        else if (l->vt == JSI_VT_NUMBER)
            l->d.num = lv;
        else if (l->vt == JSI_VT_OBJECT && l->d.obj->ot == JSI_OT_NUMBER)
            l->d.obj->d.num = lv;
    }
    return rc;
}

static Jsi_RC RegexpTestCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int skip = 0, rc = 0;
    Jsi_Value *v;
    ChkRegexp(_this, funcPtr, v);
    char *str = Jsi_ValueString(interp,Jsi_ValueArrayIndex(interp, args, skip), NULL);
    if (!str) 
        return Jsi_LogError("expected string");
    if (Jsi_RegExpMatch(interp, v, str, &rc, NULL) != JSI_OK)
        return JSI_ERROR;    
    Jsi_ValueMakeBool(interp, ret, rc != 0);
    return JSI_OK;
}

static Jsi_CmdSpec regexpCmds[] = {
    { "RegExp",  RegExp_constructor,    1, 2, "val:regexp|string, flags:string", .help="Create a regexp object", .retType=(uint)JSI_TT_REGEXP, .flags=JSI_CMD_IS_CONSTRUCTOR  },
    { "exec",    RegexpExecCmd,         1, 1, "val:string", .help="return matching string", .retType=(uint)JSI_TT_ARRAY|JSI_TT_OBJECT|JSI_TT_NULL, 0, .info=FN_regexec  },
    { "test",    RegexpTestCmd,         1, 1, "val:string", .help="test if a string matches", .retType=(uint)JSI_TT_BOOLEAN },
    { NULL, 0,0,0,0,.help="Commands for managing reqular expression objects" }
};

Jsi_RC jsi_InitRegexp(Jsi_Interp *interp, int release)
{
    if (!release)
        interp->RegExp_prototype = Jsi_CommandCreateSpecs(interp, "RegExp", regexpCmds, NULL, JSI_CMDSPEC_ISOBJ);
    return JSI_OK;
}

#endif
#ifndef JSI_LITE_ONLY
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif

// SCOPESTRS

Jsi_ScopeStrs *jsi_ScopeStrsNew()
{
    Jsi_ScopeStrs *ret = (Jsi_ScopeStrs *)Jsi_Calloc(1, sizeof(*ret));
    return ret;
}

void jsi_ScopeStrsFree(Jsi_Interp *interp, Jsi_ScopeStrs *ss)
{
    if (!ss) return;
    int i;
    for (i=0; i<ss->count; i++)
        if (ss->args[i].defValue)
            Jsi_DecrRefCount(interp, ss->args[i].defValue);
    if (ss->args)
        Jsi_Free(ss->args);
    Jsi_Free(ss);
}

void jsi_ScopeStrsPush(Jsi_Interp *interp, Jsi_ScopeStrs *ss, const char *string, int argType)
{
    if (ss->count >= ss->_size) {
        int osz = ss->_size, isold = (ss->args!=NULL);
        ss->_size += ALLOC_MOD_SIZE;
        if (!isold)
            ss->args = (jsi_ArgValue*)Jsi_Calloc(ss->_size,  sizeof(ss->args[0]));
        else {
            ss->args = (jsi_ArgValue*)Jsi_Realloc(ss->args, (ss->_size) * sizeof(ss->args[0]));
            memset(ss->args+osz, 0, (ss->_size-osz)* sizeof(ss->args[0]));
        }
    }
    ss->args[ss->count].name = (char*)Jsi_KeyAdd(interp, string);
    ss->args[ss->count].type = argType;
    if (argType)
        ss->typeCnt++;
    ss->count++;
}

static Jsi_ScopeStrs *jsi_ScopeStrsDup(jsi_Pstate *ps, Jsi_ScopeStrs *ss)
{
    Jsi_ScopeStrs *n = jsi_ScopeStrsNew();
    int i;
    if (!ss) return n;
    *n = *ss;
    if (!ss->args) return n;
    n->args = (jsi_ArgValue*)Jsi_Calloc(n->count, sizeof(ss->args[0]));
    n->_size = n->count;
    memcpy(n->args, ss->args, (n->count *  sizeof(ss->args[0])));
    for (i = 0; i < ss->count; ++i) {
        if (ss->args[i].defValue)
            Jsi_IncrRefCount(ps->interp, ss->args[i].defValue);
    }
    return n;
}

const char *jsi_ScopeStrsGet(Jsi_ScopeStrs *ss, int i)
{
    if (i < 0 || i >= ss->count)
        return NULL;
    return ss->args[i].name;
}

Jsi_ScopeStrs *jsi_ArgsOptAdd(jsi_Pstate *pstate, Jsi_ScopeStrs *a)
{
    jsi_PstatePush(pstate);
    return a;
}

Jsi_ScopeStrs *jsi_argInsert(jsi_Pstate *pstate, Jsi_ScopeStrs *a, const char *name, Jsi_Value *defValue, jsi_Pline *lPtr)
{
    Jsi_Interp *interp = pstate->interp;
    if (!a)
        a = jsi_ScopeStrsNew();
    pstate->args = a;
    int atyp = pstate->argType;
    if (defValue) {
        int vt = defValue->vt;
        if (vt == JSI_VT_NULL)
            vt = JSI_TT_NULL;
        else if (vt == JSI_VT_UNDEF && defValue->d.num==1)
            vt = JSI_TT_VOID;
        else if (vt == JSI_VT_OBJECT && defValue->d.obj->ot==JSI_OT_STRING)
            vt = JSI_TT_STRING;
        else
            vt = (1<<defValue->vt);
        atyp |= vt;
    }
    jsi_ScopeStrsPush(interp, a, name, atyp);
    pstate->argType = 0;
    a->args[a->count-1].defValue = defValue;
    a->argCnt++;
    jsi_Pline *opl = interp->parseLine;
    interp->parseLine = lPtr;
    if (defValue) {
        Jsi_IncrRefCount(interp, defValue);
        if (a->firstDef==0)
            a->firstDef = a->argCnt;
            if (atyp)
                jsi_ArgTypeCheck(interp, atyp, defValue, "default value", name, a->argCnt, NULL, 1);
    } else {
        if (a->firstDef && (interp->typeCheck.run || interp->typeCheck.all) )
            Jsi_LogWarn("expected default value in argument list: \"%s\"", name);
    }
    interp->parseLine = opl;
    return a;
}


// PSTATE 

void jsi_PstatePush(jsi_Pstate *ps)
{
    Jsi_Interp *interp = ps->interp;
    if (interp->cur_scope >= (int)(JSI_MAX_SCOPE - 1)) {
        Jsi_LogBug("Scope chain too short");
        return;
    }
    interp->cur_scope++;
}

void jsi_PstatePop(jsi_Pstate *ps)
{
    Jsi_Interp *interp = ps->interp;
    if (interp->cur_scope <= 0) {
        Jsi_LogBug("No more scope to pop");
        return;
    }
    jsi_ScopeStrsFree(interp, interp->scopes[interp->cur_scope]);
    interp->scopes[interp->cur_scope] = NULL;
    interp->cur_scope--;
}

void jsi_PstateAddVar(jsi_Pstate *ps, jsi_Pline *line, const char *str)
{
    Jsi_Interp *interp = ps->interp;
    int i;
    if (interp->scopes[interp->cur_scope] == NULL)
        interp->scopes[interp->cur_scope] = (Jsi_ScopeStrs *)jsi_ScopeStrsNew();
    
    for (i = 0; i < interp->scopes[interp->cur_scope]->count; ++i) {
        if (Jsi_Strcmp(str, interp->scopes[interp->cur_scope]->args[i].name) == 0) {
            Jsi_Interp *interp = ps->interp;
            if (interp && interp->strict) {
                interp->parseLine = line;
                Jsi_LogWarn("duplicate var: %s", str);
                interp->parseLine = NULL;
            }
            return;
        }
    }
    jsi_ScopeStrsPush(ps->interp, interp->scopes[interp->cur_scope], str, JSI_VT_UNDEF);
}

Jsi_ScopeStrs *jsi_ScopeGetVarlist(jsi_Pstate *ps)
{
    Jsi_Interp *interp = ps->interp;
    return jsi_ScopeStrsDup(ps, interp->scopes[interp->cur_scope]);
}

#if 0
static int fastVarFree(Jsi_Interp *interp, void *ptr) {
    FastVar *fv = ptr;
    Jsi_Value *v = fv->var.lval;
    if (v) {
        //printf("FV FREE: %p (%d/%d)\n", fv, v->refCnt, v->vt == JSI_VT_OBJECT?v->d.obj->refcnt:-99);
        //Jsi_DecrRefCount(interp, v);
    }
    return JSI_OK;
}
#endif


static Jsi_RC jsi_StringFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *d) {
    Jsi_String *s = (Jsi_String *)d;
    if (s->flags&1)
        Jsi_Free(s->str);
    Jsi_Free(s);
    return JSI_OK;
}

jsi_Pstate *jsi_PstateNew(Jsi_Interp *interp)
{
    jsi_Pstate *ps = (jsi_Pstate *)Jsi_Calloc(1,sizeof(*ps));
    SIGINIT(ps,PARSER);
    ps->lexer = (jsi_Lexer*)Jsi_Calloc(1,sizeof(*ps->lexer));
    ps->lexer->pstate = ps;
    ps->interp = interp;
    ps->fastVarTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, NULL /*fastVarFree*/);
    ps->strTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, jsi_StringFree);
    return ps;
}

const char *jsi_PstateGetFilename(jsi_Pstate *ps)
{
    Jsi_Interp *interp = ps->interp;
    return interp->curFile;
}

void jsi_PstateClear(jsi_Pstate *ps)
{
    jsi_Lexer* l = ps->lexer;
    if (l->ltype == LT_FILE)
    {
        if (l->d.fp)
            Jsi_Close(ps->interp, l->d.fp);
        l->d.fp = NULL;
    }
    if (l->ltype == LT_STRING)
    {
        l->d.str = NULL;
    }
    l->ltype = LT_NONE;
    l->last_token = 0;
    l->cur_line = 1;
    l->cur_char = 0;
    l->cur = 0;
    ps->err_count = 0;
}

int jsi_PstateSetFile(jsi_Pstate *ps, Jsi_Channel fp, int skipbang)
{
    jsi_Lexer *l = ps->lexer;
    jsi_PstateClear(ps);
    l->ltype = LT_FILE;
    l->d.fp = fp;
    Jsi_Rewind(ps->interp, fp);
    if (skipbang) {
        char buf[1000];
        if (Jsi_Gets(ps->interp, fp, buf, 1000) && (buf[0] != '#' || buf[1] != '!')) {
            Jsi_Rewind(ps->interp, fp);
        }
    }
            
    return JSI_OK;
}


int jsi_PstateSetString(jsi_Pstate *ps, const char *str)
{
    Jsi_Interp *interp = ps->interp;
    jsi_Lexer *l = ps->lexer;
    jsi_PstateClear(ps);
    l->ltype = LT_STRING;
    Jsi_HashEntry *hPtr = Jsi_HashEntryNew(interp->codeTbl, (void*)str, NULL);
    assert(hPtr);
    l->d.str = (char*)Jsi_HashKeyGet(hPtr);
    return JSI_OK;
}

void jsi_PstateFree(jsi_Pstate *ps)
{
    /* TODO: when do we free opcodes */
    jsi_PstateClear(ps);
    Jsi_Free(ps->lexer);
    if (ps->opcodes)
        jsi_FreeOpcodes(ps->opcodes);
    if (ps->hPtr)
        Jsi_HashEntryDelete(ps->hPtr);
    if (ps->argsTbl)
        Jsi_HashDelete(ps->argsTbl);
    if (ps->fastVarTbl)
        Jsi_HashDelete(ps->fastVarTbl);
    if (ps->strTbl)
        Jsi_HashDelete(ps->strTbl);
    if (ps->last_exception)
        Jsi_DecrRefCount(ps->interp, ps->last_exception);
    _JSI_MEMCLEAR(ps);
    Jsi_Free(ps);
}

#endif
#ifndef JSI_LITE_ONLY
#define __JSIINT_C__
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <assert.h>

#ifdef __WIN32
#include <windows.h>
#include <shlwapi.h>
#ifndef JSI_OMIT_THREADS
#include <process.h>
#endif
#else
#ifndef JSI_OMIT_THREADS
#include <pthread.h>
#endif
#endif

#if (JSI_VERSION_MINOR<0 || JSI_VERSION_MINOR>99 || JSI_VERSION_RELEASE<0 || JSI_VERSION_RELEASE>99)
#error "VERSION MINOR/RELEASE not between 0-99 inclusive"
#endif

static Jsi_OptionSpec InterpDebugOptions[] = {
    JSI_OPT(CUSTOM,Jsi_DebugInterp, debugCallback,  .help="Command in parent interp for handling debugging", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"file:string, line:number, level:number, func:string, opstr:string, bpId:number, emsg:string" ),
    JSI_OPT(BOOL,  Jsi_DebugInterp, doContinue,     .help="Continue execution until breakpoint" ),
    JSI_OPT(BOOL,  Jsi_DebugInterp, forceBreak,     .help="Force debugger to break" ),
    JSI_OPT(BOOL,  Jsi_DebugInterp, includeOnce,    .help="Source the file only if not already sourced" ),
    JSI_OPT(BOOL,  Jsi_DebugInterp, includeTrace,   .help="Trace includes" ),
    JSI_OPT(INT,   Jsi_DebugInterp, minLevel,       .help="Disable eval callback for level higher than this" ),
    JSI_OPT(CUSTOM,Jsi_DebugInterp, msgCallback,    .help="Comand in parent interp to handle Jsi_LogError/Jsi_LogWarn,..", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"msg:string, type:string, file:string, line:number, col:number" ),
    JSI_OPT(BOOL,  Jsi_DebugInterp, pkgTrace,       .help="Trace package loads" ),
    JSI_OPT(CUSTOM,Jsi_DebugInterp, putsCallback,   .help="Comand in parent interp to handle puts output", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"msg:string, isStderr:number" ),
    JSI_OPT(CUSTOM,Jsi_DebugInterp, traceCallback,  .help="Comand in parent interp to handle traceCall", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"cmd:string, args:string, ret:string, file:string, line:number, col:number" ),
    JSI_OPT(CUSTOM,Jsi_DebugInterp, testFmtCallback,.help="Comand in parent interp to format unittest string", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"cmd:string, line:number" ),
    JSI_OPT_END(Jsi_DebugInterp, .help="Interp options for debugging")
};

Jsi_OptionSpec jsi_InterpLogOptions[] = {
    JSI_OPT(BOOL,   jsi_LogOptions, Test,    .help="Enable LogTest messages" ),
    JSI_OPT(BOOL,   jsi_LogOptions, Debug,   .help="Enable LogDebug messages" ),
    JSI_OPT(BOOL,   jsi_LogOptions, Trace,   .help="Enable LogTrace messages" ),
    JSI_OPT(BOOL,   jsi_LogOptions, Info,    .help="Enable LogInfo messages" ),
    JSI_OPT(BOOL,   jsi_LogOptions, Warn,    .help="Enable LogWarn messages" ),
    JSI_OPT(BOOL,   jsi_LogOptions, Error,   .help="Enable LogError messages" ),
    JSI_OPT(BOOL,   jsi_LogOptions, time,    .help="Prefix with time" ),
    JSI_OPT(BOOL,   jsi_LogOptions, date,    .help="Prefix with date" ),
    JSI_OPT(BOOL,   jsi_LogOptions, file,    .help="Ouptut contains file:line" ),
    JSI_OPT(BOOL,   jsi_LogOptions, func,    .help="Output function" ),
    JSI_OPT(BOOL,   jsi_LogOptions, full,    .help="Show full file path" ),
    JSI_OPT(BOOL,   jsi_LogOptions, ftail,   .help="Show tail of file only, even in LogWarn, etc" ),
    JSI_OPT(BOOL,   jsi_LogOptions, before,  .help="Output file:line before message string" ),
    JSI_OPT(BOOL,   jsi_LogOptions, isUTC,   .help="Time is to be UTC" ),
    JSI_OPT(STRKEY, jsi_LogOptions, timeFmt, .help="A format string to use with strftime" ),
    JSI_OPT(USEROBJ,jsi_LogOptions, chan,    .help="Channel to send output to", .flags=0, .custom=0, .data=(void*)"Channel" ),
    JSI_OPT_END(jsi_LogOptions, .help="Interp options for logging")
};
static Jsi_OptionSpec InterpSubOptions[] = {
    JSI_OPT(STRKEY,jsi_SubOptions, blacklist,   .help="Comma separated modules to disable loading for", jsi_IIOF ),
    JSI_OPT(BOOL,  jsi_SubOptions, compat,      .help="Ignore unknown options via JSI_OPTS_IGNORE_EXTRA in option parser" ),
    JSI_OPT(INT,   jsi_SubOptions, dblPrec,     .help="Format precision of double where 0=max, -1=max-1, ... (max-1)" ),
    JSI_OPT(BOOL,  jsi_SubOptions, istty,       .help="Indicates interp is in interactive mode", jsi_IIRO),
    JSI_OPT(BOOL,  jsi_SubOptions, logColNums,  .help="Display column numbers in error messages"),
    JSI_OPT(BOOL,  jsi_SubOptions, logAllowDups,.help="Log should not filter out duplicate messages"),
    JSI_OPT(BOOL,  jsi_SubOptions, mutexUnlock, .help="Unlock own mutex when evaling in other interps (true)", jsi_IIOF),
    JSI_OPT(BOOL,  jsi_SubOptions, noproto,     .help="Disable support of the OOP symbols:  __proto__, prototype, constructor, etc"),
    JSI_OPT(BOOL,  jsi_SubOptions, noFuncString,.help="Disable viewing code body for functions", jsi_IIOF),
    JSI_OPT(BOOL,  jsi_SubOptions, noRegex,     .help="Disable viewing code for functions", jsi_IIOF),
    JSI_OPT(BOOL,  jsi_SubOptions, noReadline,  .help="In interactive mode disable use of readline" ),
    JSI_OPT(BOOL,  jsi_SubOptions, outUndef,    .help="In interactive mode output result values that are undefined"),
    JSI_OPT(STRKEY,jsi_SubOptions, prompt,      .help="Prompt for interactive mode ('$ ')" ),
    JSI_OPT(STRKEY,jsi_SubOptions, prompt2,     .help="Prompt for interactive mode line continue ('> ')" ),
    JSI_OPT_END(jsi_SubOptions, .help="Lesser sub-feature options")
};

static const char *jsi_SafeModeStrs[] = { "none", "read", "write", "write2", NULL };
static const char *jsi_TypeChkStrs[] = { "parse", "run", "all", "error", "strict", "noundef", "nowith", "funcsig", NULL };
const char *jsi_callTraceStrs[] = { "funcs", "cmds", "new", "return", "args", "notrunc", "noparent", "full", "before", NULL};
const char *jsi_AssertModeStrs[] = { "throw", "log", "puts", NULL};

static Jsi_OptionSpec InterpOptions[] = {
    JSI_OPT(ARRAY, Jsi_Interp, args,        .help="The console.arguments for interp", jsi_IIOF),
    JSI_OPT(BOOL,  Jsi_Interp, asserts,     .help="Enable assert" ),
    JSI_OPT(CUSTOM,Jsi_Interp, assertMode,  .help="Action upon assert failure", .flags=0, .custom=Jsi_Opt_SwitchEnum, .data=jsi_AssertModeStrs ),
    JSI_OPT(ARRAY, Jsi_Interp, autoFiles,   .help="File(s) to source for loading Jsi_Auto to handle unknown commands"),
    JSI_OPT(CUSTOM,Jsi_Interp, busyCallback,.help="Command in parent interp (or noOp) to periodically call", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"interpName:string, opCnt:number"),
    JSI_OPT(INT   ,Jsi_Interp, busyInterval,.help="Call busyCallback command after this many op-code evals (100000)"),
    JSI_OPT(BOOL,  Jsi_Interp, coverage,    .help="On exit generate detailed code coverage for function calls (with profile)"),
    JSI_OPT(CUSTOM,Jsi_Interp, debugOpts,   .help="Options for debugging", .flags=0, .custom=Jsi_Opt_SwitchSuboption, .data=InterpDebugOptions),
    JSI_OPT(BOOL,  Jsi_Interp, interactive, .help="Force interactive mode. ie. ignore no_interactive flag", jsi_IIOF),
    JSI_OPT(BOOL,  Jsi_Interp, hasOpenSSL,  .help="Is SSL available in WebSocket", jsi_IIOF),
    JSI_OPT(STRKEY,Jsi_Interp, historyFile, .help="In interactive mode, file to use for history (~/.jsish_history)", jsi_IIOF),
    JSI_OPT(BOOL,  Jsi_Interp, isSafe,      .help="Is this a safe interp (ie. with limited or no file access)", jsi_IIOF),
    JSI_OPT(STRKEY,Jsi_Interp, jsppChars,   .help="Line preprocessor when sourcing files. Line starts with first char, and either ends with it, or matches string"),
    JSI_OPT(FUNC,  Jsi_Interp, jsppCallback,.help="Command to preprocess lines that match jsppChars. Call func(interpName:string, opCnt:number)"),
    JSI_OPT(INT,   Jsi_Interp, lockTimeout, .help="Thread time-out for mutex lock acquires (milliseconds)" ),
    JSI_OPT(CUSTOM,Jsi_Interp, logOpts,     .help="Options for log output to add file/line/time", .flags=0, .custom=Jsi_Opt_SwitchSuboption, .data=jsi_InterpLogOptions),
    JSI_OPT(INT,   Jsi_Interp, maxDepth,    .help="Depth limit of recursive function calls (1000)"),
    JSI_OPT(INT,   Jsi_Interp, maxArrayList,.help="Maximum array convertable to list (100000)" ),
    JSI_OPT(INT,   Jsi_Interp, maxIncDepth, .help="Maximum allowed source/require nesting depth (50)" ),
    JSI_OPT(INT,   Jsi_Interp, maxInterpDepth,.help="Maximum nested subinterp create depth (10)" ),
    JSI_OPT(INT,   Jsi_Interp, maxUserObjs, .help="Maximum number of 'new' object calls, eg. File, RegExp, etc" ),
    JSI_OPT(INT,   Jsi_Interp, maxOpCnt,    .help="Execution limit for op-code evaluation", jsi_IIOF ),
    JSI_OPT(INT,   Jsi_Interp, memDebug,    .help="Memory debugging level: 1=summary, 2=detail", .flags=JSI_OPT_NO_CLEAR),
    JSI_OPT(STRKEY,Jsi_Interp, name,        .help="Optional text name for this interp"),
    JSI_OPT(BOOL,  Jsi_Interp, noAutoLoad,  .help="Disable autoload" ),
    JSI_OPT(BOOL,  Jsi_Interp, noConfig,    .help="Disable use of Interp.conf to change options after create", jsi_IIOF),
    JSI_OPT(BOOL,  Jsi_Interp, noInput,     .help="Disable use of console.input()" ),
    JSI_OPT(BOOL,  Jsi_Interp, noLoad,      .help="Disable load of shared libs"),
    JSI_OPT(BOOL,  Jsi_Interp, noNetwork,   .help="Disable new Socket/WebSocket, or load of builtin MySql" ),
    JSI_OPT(BOOL,  Jsi_Interp, noStderr,    .help="Make puts, log, assert, etc use stdout" ),
    JSI_OPT(BOOL,  Jsi_Interp, noSubInterps,.help="Disallow sub-interp creation"),
    JSI_OPT(FUNC,  Jsi_Interp, onComplete,  .help="Function to return commands completions for interactive mode.  Default uses Info.completions ", .flags=0, .custom=0, .data=(void*)"prefix:string, start:number, end:number" ),
    JSI_OPT(FUNC,  Jsi_Interp, onEval,      .help="Function to get control for interactive evals", .flags=0, .custom=0, .data=(void*)"cmd:string" ),
    JSI_OPT(FUNC,  Jsi_Interp, onExit,      .help="Command to call in parent on exit, returns true to continue", jsi_IIOF , .custom=0, .data=(void*)""),
    JSI_OPT(INT,   Jsi_Interp, opTrace,     .help="Set debugging level for OPCODE execution"),
    JSI_OPT(ARRAY, Jsi_Interp, pkgDirs,     .help="list of library directories for require() to search" ),
    JSI_OPT(BOOL,  Jsi_Interp, profile,     .help="On exit generate profile of function calls"),
    JSI_OPT(CUSTOM,Jsi_Interp, recvCallback,.help="Command to recv 'send' msgs from parent interp", .flags=0, .custom=Jsi_Opt_SwitchParentFunc, .data=(void*)"msg:string"),
    JSI_OPT(VALUE, Jsi_Interp, retValue,    .help="Return value from last eval", jsi_IIRO),
    JSI_OPT(CUSTOM,Jsi_Interp, safeMode,    .help="Set isSafe mode and setup safeReadDirs and/or safeWriteDirs for pwd and script-dir", jsi_IIOF, .custom=Jsi_Opt_SwitchEnum, .data=jsi_SafeModeStrs ),
    JSI_OPT(ARRAY, Jsi_Interp, safeReadDirs,.help="In safe mode, files/dirs to allow reads from", jsi_IIOF),
    JSI_OPT(ARRAY, Jsi_Interp, safeWriteDirs,.help="In safe mode, files/dirs to allow writes to", jsi_IIOF),
    JSI_OPT(STRKEY, Jsi_Interp,safeExecPattern,.help="In safe mode, regexp pattern allow exec of commands", jsi_IIOF),
    JSI_OPT(STRKEY,Jsi_Interp, scriptStr,   .help="Interp init script string", jsi_IIOF),
    JSI_OPT(STRING,Jsi_Interp, scriptFile,  .help="Interp init script file"),
    JSI_OPT(STRING,Jsi_Interp, stdinStr,    .help="String to use as stdin for console.input()"),
    JSI_OPT(STRING,Jsi_Interp, stdoutStr,   .help="String to collect stdout for puts()"),
    JSI_OPT(BOOL,  Jsi_Interp, strict,      .help="Globally enable strict: same as 'use strict' in main program"),
    JSI_OPT(CUSTOM,Jsi_Interp, subOpts,     .help="Infrequently used sub-options", .flags=0, .custom=Jsi_Opt_SwitchSuboption, .data=InterpSubOptions),
    JSI_OPT(BOOL,  Jsi_Interp, subthread,   .help="Create a threaded Interp", jsi_IIOF),
    JSI_OPT(CUSTOM,Jsi_Interp, traceCall,   .help="Trace commands", .flags=0,  .custom=Jsi_Opt_SwitchBitset,  .data=jsi_callTraceStrs),
    JSI_OPT(BOOL,  Jsi_Interp, tracePuts,   .help="Trace puts by making it use logOpts" ),
    JSI_OPT(CUSTOM,Jsi_Interp, typeCheck,   .help="Type-check control options", .flags=0, .custom=Jsi_Opt_SwitchBitset, .data=jsi_TypeChkStrs),
    JSI_OPT(INT,   Jsi_Interp, typeWarnMax, .help="Type checking is silently disabled after this many warnings (50)" ),
    JSI_OPT(UINT,  Jsi_Interp, unitTest,    .help="Unit test control bits: 1=subst, 2=Puts with file:line prefix" ),
    JSI_OPT_END(Jsi_Interp, .help="Options for the Jsi interpreter")
};

/* Object for each interp created. */
typedef struct InterpObj {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Interp *subinterp;
    Jsi_Interp *parent;
    //char *interpname;
    char *mode;
    Jsi_Obj *fobj;
    int objId;
    int deleting;
} InterpObj;

/* Global state of interps. */

typedef struct {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    int refCount;
    const char *cmdName;
    Jsi_Value *args;
    Jsi_Value *func;
    Jsi_Value *cmdVal;
    InterpObj *intobj;
    Jsi_Interp *dinterp; // Dest interp.
    Jsi_Interp *subinterp;
} AliasCmd;


static void interpObjErase(InterpObj *fo);
static Jsi_RC interpObjFree(Jsi_Interp *interp, void *data);
static bool interpObjIsTrue(void *data);
static bool interpObjEqual(void *data1, void *data2);

static Jsi_RC jsi_InterpConfFiles(Jsi_Interp *interp);

static void ConvertReturn(Jsi_Interp *tointerp, Jsi_Value **toret, Jsi_Interp *frominterp, Jsi_Value **fromret)
{
    Jsi_DString dStr = {};

    switch ((*fromret)->vt) {
        case JSI_VT_UNDEF:
        case JSI_VT_BOOL:
        case JSI_VT_NUMBER:
        case JSI_VT_NULL:
            Jsi_ValueCopy(tointerp, *toret, *fromret);
            break;
        default:
            Jsi_DSInit(&dStr);
            char *cp = (char*)Jsi_ValueGetDString(frominterp, *fromret, &dStr, JSI_OUTPUT_JSON);
            Jsi_JSONParse(tointerp, cp, toret, 0);
            Jsi_DSFree(&dStr);
    }
}

/* Call a command with JSON args.  Returned string by using Jsi_ValueGetDString(..., flags). */
Jsi_RC Jsi_EvalCmdJSON(Jsi_Interp *interp, const char *cmd, const char *jsonArgs, Jsi_DString *dStr, int flags)
{
    if (Jsi_MutexLock(interp, interp->Mutex) != JSI_OK)
        return JSI_ERROR;
    Jsi_Value *nrPtr = Jsi_ValueNew1(interp);
    Jsi_RC rc = Jsi_CommandInvokeJSON(interp, cmd, jsonArgs, &nrPtr);
    Jsi_DSInit(dStr);
    Jsi_ValueGetDString(interp, nrPtr, dStr, flags /*JSI_OUTPUT_JSON*/);
    Jsi_DecrRefCount(interp, nrPtr);
    Jsi_MutexUnlock(interp, interp->Mutex);
    return rc;
}

/* Call a function with JSON args.  Return a primative. */
Jsi_RC Jsi_FunctionInvokeJSON(Jsi_Interp *interp, Jsi_Value *func, const char *json, Jsi_Value **ret)
{
    if (!Jsi_ValueIsFunction(interp, func))
        return JSI_ERROR;
    Jsi_Value *aPtr = Jsi_ValueNew1(interp);
    Jsi_RC rc = Jsi_JSONParse(interp, json, &aPtr, 0);
    if (rc == JSI_OK)
        rc = Jsi_FunctionInvoke(interp, func, aPtr, ret, NULL);
    Jsi_DecrRefCount(interp, aPtr);
    return rc;
}
/* Lookup cmd from cmdstr and invoke with JSON args. */
/*
 *   Jsi_CommandInvokeJSON(interp, "info.cmds", "[\"*\",true]", ret);
 */
Jsi_RC Jsi_CommandInvokeJSON(Jsi_Interp *interp, const char *cmdstr, const char *json, Jsi_Value **ret)
{
    Jsi_Value *func = Jsi_NameLookup(interp, cmdstr);
    if (func)
        return Jsi_FunctionInvokeJSON(interp, func, json, ret);
    return Jsi_LogError("can not find cmd: %s", cmdstr);
}

/* Clean-copy value to interp: convert to JSON and back if required. */
Jsi_RC Jsi_CleanValue(Jsi_Interp *interp, Jsi_Interp *tointerp, Jsi_Value *val, Jsi_Value **ret)
{
    Jsi_RC rc = JSI_OK;
    const char *cp;
    int len, iskey;
    Jsi_Obj *obj;
    switch (val->vt) {
        case JSI_VT_UNDEF: Jsi_ValueMakeUndef(interp, ret); return rc;
        case JSI_VT_NULL: Jsi_ValueMakeNull(tointerp, ret); return rc;
        case JSI_VT_BOOL: Jsi_ValueMakeBool(tointerp, ret, val->d.val); return rc;
        case JSI_VT_NUMBER: Jsi_ValueMakeNumber(tointerp, ret, val->d.num); return rc;
        case JSI_VT_STRING:
            iskey = val->f.bits.isstrkey;
            cp = val->d.s.str;
            len = val->d.s.len;
makestr:
            if (iskey) {
                Jsi_ValueMakeStringKey(interp, ret, cp);
                return rc;
            }
            jsi_ValueMakeBlobDup(tointerp, ret, (uchar*)cp, len);
            return rc;
        case JSI_VT_OBJECT:
            obj = val->d.obj;
            switch (obj->ot) {
                case JSI_OT_BOOL: Jsi_ValueMakeBool(tointerp, ret, obj->d.val); return rc;
                case JSI_OT_NUMBER: Jsi_ValueMakeNumber(tointerp, ret, obj->d.num); return rc;
                case JSI_OT_STRING:
                    cp = obj->d.s.str;
                    len = obj->d.s.len;
                    iskey = obj->isstrkey;
                    goto makestr;
                default: break;
            }
            break;
        default:
            break;
    }
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    cp = Jsi_ValueGetDString(interp, val, &dStr, JSI_OUTPUT_JSON);
    if (Jsi_JSONParse(tointerp, cp, ret, 0) != JSI_OK) {
        Jsi_DSFree(&dStr);
        return Jsi_LogError("bad subinterp parse");
    }
    Jsi_DSFree(&dStr);
    return rc;
}

/* Invoke command in target interp. */
Jsi_RC jsi_AliasInvoke(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    AliasCmd *ac = (AliasCmd *)funcPtr->cmdSpec->reserved[2];
    Jsi_Interp *dinterp = ac->dinterp;
    Jsi_Value *nargs = NULL;
    int argc = Jsi_ValueGetLength(interp, args);
    if (!ac) {
        Jsi_LogBug("BAD ALIAS INVOKE OF DELETED");
        return JSI_ERROR;
    }
    SIGASSERT(ac,ALIASCMD);
    Jsi_Value *nrPtr = Jsi_ValueNew1(interp);
    if (argc == 0 && ac->args)
        nargs = ac->args;
    else if (argc) {
        if (dinterp == interp)
            Jsi_ValueCopy(interp, nrPtr, args);
        else if (Jsi_CleanValue(interp, dinterp, args, &nrPtr) != JSI_OK)
            return JSI_ERROR;
        if (ac->args) {
            nargs = Jsi_ValueArrayConcat(dinterp, ac->args, nrPtr);
        } else {
            nargs = nrPtr;
        }
    }
    if (dinterp != interp) {
        if (interp->subOpts.mutexUnlock) Jsi_MutexUnlock(interp, interp->Mutex);
        if (Jsi_MutexLock(interp, dinterp->Mutex) != JSI_OK) {
            if (interp->subOpts.mutexUnlock) Jsi_MutexLock(interp, interp->Mutex);
            return JSI_ERROR;
        }
    }
    ac->refCount++;
    //int oref = 0;
    if (nargs && nargs != nrPtr) {
        Jsi_IncrRefCount(interp, nargs);
        //oref = nargs->d.obj->refcnt;
    }
    Jsi_Value *srPtr, **srpPtr = ret;
    if (dinterp != interp) {
        srPtr = Jsi_ValueNew1(interp);
        srpPtr = &srPtr;
    }
    Jsi_RC rc = Jsi_FunctionInvoke(dinterp, ac->func, nargs, srpPtr, NULL);
    ac->refCount--;
    if (dinterp != interp) {
        Jsi_MutexUnlock(interp, dinterp->Mutex);
        if (interp->subOpts.mutexUnlock && Jsi_MutexLock(interp, interp->Mutex) != JSI_OK) {
            return JSI_ERROR;
        }
    }
    Jsi_DecrRefCount(interp, nrPtr);
    if (nargs && nargs != nrPtr)
        Jsi_DecrRefCount(interp, nargs);
    if (dinterp != interp) {
        ConvertReturn(dinterp, ret, interp, srpPtr);
        Jsi_DecrRefCount(interp, srPtr);
        if (rc != JSI_OK && dinterp->errMsgBuf[0] && interp != dinterp) {
            Jsi_Strcpy(interp->errMsgBuf, dinterp->errMsgBuf);
            interp->errLine = dinterp->errLine;
            interp->errFile = dinterp->errFile;
            dinterp->errMsgBuf[0] = 0;
        }
    }
    return rc;
}


static Jsi_RC AliasFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *data) {
    /* TODO: deal with other copies of func may be floating around (refCount). */
    AliasCmd *ac = (AliasCmd *)data;
    if (!ac) return JSI_ERROR;
    SIGASSERT(ac,ALIASCMD);
    if (ac->func)
        Jsi_DecrRefCount(ac->dinterp, ac->func);
    if (ac->args)
        Jsi_DecrRefCount(ac->dinterp, ac->args);
    if (!ac->cmdVal)
        return JSI_OK;
    Jsi_Func *fobj = ac->cmdVal->d.obj->d.fobj->func;
    fobj->cmdSpec->reserved[2] = NULL;
    fobj->cmdSpec->proc = NULL;
    if (ac->intobj && ac->intobj->subinterp) {
        Jsi_CommandDelete(ac->intobj->subinterp, ac->cmdName);
        if (Jsi_Strchr(ac->cmdName, '.'))
            Jsi_LogBug("alias free with X.Y dot name leaks memory: %s", ac->cmdName);
    } else
        Jsi_DecrRefCount(ac->subinterp, ac->cmdVal);
    _JSI_MEMCLEAR(ac);
    Jsi_Free(ac);
    return JSI_OK;
}

#define FN_intalias JSI_INFO("With 0 args, returns list of all aliases in interp.\n\
With 1 arg returns func for given alias name.\n\
With 2 args, returns args for given alias name (args must be null).\n\
With 3 args, create/update an alias for func and args. Delete an alias by creating it with null for both func and args.")
static Jsi_RC InterpAliasCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    InterpObj *udf = (InterpObj *)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_Interp *sinterp = (udf ? udf->subinterp : interp );
    Jsi_Hash *aliases = sinterp->aliasHash;
    if (!aliases)
        return Jsi_LogError("Sub-interp gone");
    int argc = Jsi_ValueGetLength(interp, args);
    if (argc == 0)
        return Jsi_HashKeysDump(interp, aliases, ret, 0);
    Jsi_HashEntry *hPtr;
    char *key = Jsi_ValueArrayIndexToStr(interp, args, 0, NULL);
    if (!key)
        return Jsi_LogError("expected string");
    AliasCmd* ac;
    if (argc == 1) {
        hPtr = Jsi_HashEntryFind(aliases, (void*)key);
        if (!hPtr)
            return JSI_OK;
        ac = (AliasCmd*)Jsi_HashValueGet(hPtr);
        if (!ac) return JSI_ERROR;
        SIGASSERT(ac,ALIASCMD);
        Jsi_ValueDup2(interp, ret, ac->func);
        return JSI_OK;
    }
    Jsi_Value *afunc = Jsi_ValueArrayIndex(interp, args, 1);
    if (argc == 2) {
        hPtr = Jsi_HashEntryFind(aliases, (void*)key);
        if (!hPtr)
            return JSI_OK;
        ac = (AliasCmd*)Jsi_HashValueGet(hPtr);
        if (!Jsi_ValueIsNull(interp, afunc))
            return Jsi_LogError("arg 2: expected null to query args");
        if (!ac) return JSI_ERROR;
        SIGASSERT(ac,ALIASCMD);
        Jsi_ValueDup2(interp, ret, ac->args); //TODO: JSON??
        return JSI_OK;
    }
    if (interp->threadId != sinterp->threadId)
        return Jsi_LogError("alias not supported with threads");
    if (argc == 3) {
        bool isNew;
        Jsi_Value *aargs = Jsi_ValueArrayIndex(interp, args, 2);
        if (Jsi_ValueIsNull(interp, afunc) && Jsi_ValueIsNull(interp, aargs)) {
            hPtr = Jsi_HashEntryFind(aliases, (void*)key);
            if (hPtr == NULL)
                return JSI_OK;
            ac = (AliasCmd*)Jsi_HashValueGet(hPtr);
            if (!ac) return JSI_ERROR;
            if (0 && ac->cmdVal)
                Jsi_DecrRefCount(interp, ac->cmdVal);
            AliasFree(interp, NULL, ac);
            Jsi_HashValueSet(hPtr, NULL);
            Jsi_HashEntryDelete(hPtr);
            return JSI_OK;
        }
        hPtr = Jsi_HashEntryNew(aliases, (void*)key, &isNew);
        if (!hPtr)
            return Jsi_LogError("create failed: %s", key);
        if (!Jsi_ValueIsFunction(interp, afunc))
            return Jsi_LogError("arg 2: expected function");
        if (Jsi_ValueIsNull(interp, aargs) == 0 && Jsi_ValueIsArray(interp, aargs) == 0)
            return Jsi_LogError("arg 3: expected array or null");
        if (!isNew) {
            AliasFree(interp, NULL, Jsi_HashValueGet(hPtr));
        }
        ac = (AliasCmd*)Jsi_Calloc(1, sizeof(AliasCmd));
        SIGINIT(ac, ALIASCMD);
        ac->cmdName = (const char*)Jsi_HashKeyGet(hPtr);
        ac->func = afunc;
        Jsi_IncrRefCount(interp, afunc);
        if (!Jsi_ValueIsNull(interp, aargs)) {
            ac->args = aargs;
            Jsi_IncrRefCount(interp, aargs);
        }
        ac->intobj = udf;
        ac->dinterp = interp;
        ac->subinterp = sinterp;
        Jsi_HashValueSet(hPtr, ac);
        Jsi_Value *cmd = jsi_CommandCreate(sinterp, key, jsi_AliasInvoke, NULL, 0, 0);
        if (!cmd)
            return Jsi_LogBug("command create failure");
        ac->cmdVal = cmd;
        Jsi_Func *fobj = cmd->d.obj->d.fobj->func;
        fobj->cmdSpec->reserved[2] = ac;
        cmd->d.obj->isNoOp = (afunc->d.obj->d.fobj->func->callback == jsi_NoOpCmd);
    }
    return JSI_OK;
}

static Jsi_RC freeCodeTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    jsi_Pstate *ps = (jsi_Pstate *)ptr;
    if (!ps) return JSI_OK;
    ps->hPtr = NULL;
    jsi_PstateFree(ps);
    return JSI_OK;
}

static Jsi_RC freeOnDeleteTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    if (!ptr) return JSI_OK;
    Jsi_DeleteProc *proc = (Jsi_DeleteProc *)ptr;
    proc(interp, NULL);
    return JSI_OK;
}

static Jsi_RC freeAssocTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    if (!ptr) return JSI_OK;
    jsi_DelAssocData(interp, ptr);
    return JSI_OK;
}

static Jsi_RC freeEventTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Event *event = (Jsi_Event *)ptr;
    SIGASSERT(event,EVENT);
    if (!ptr) return JSI_OK;
    Jsi_HashValueSet(event->hPtr, NULL);
    event->hPtr = NULL;
    Jsi_EventFree(interp, event);
    return JSI_OK;
}
Jsi_RC jsi_HashFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Free(ptr);
    return JSI_OK;
}


static Jsi_RC packageHashFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    jsi_PkgInfo *p = (jsi_PkgInfo*)ptr;
    if (p->popts.info) Jsi_DecrRefCount(interp, p->popts.info);
    Jsi_Free(p);
    return JSI_OK;
}

static Jsi_RC regExpFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_RegExpFree((Jsi_Regex*)ptr);
    return JSI_OK;
}

static Jsi_RC freeCmdSpecTbl(Jsi_Interp *interp, Jsi_MapEntry *hPtr, void *ptr) {
    if (!ptr) return JSI_OK;
    jsi_CmdSpecDelete(interp, ptr);
    return JSI_OK;
}

static Jsi_RC freeGenObjTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Obj *obj = (Jsi_Obj *)ptr;
    SIGASSERT(obj,OBJ);
    if (!obj) return JSI_OK;
    Jsi_ObjDecrRefCount(interp, obj);
    return JSI_OK;
}


static Jsi_RC freeFuncsTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Func *func = (Jsi_Func *)ptr;
    if (!func) return JSI_OK;
    SIGASSERT(func,FUNC);
    func->hPtr = NULL;
    jsi_FuncFree(interp, func);
    return JSI_OK;
}

static Jsi_RC freeFuncObjTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Obj *v = (Jsi_Obj *)ptr;
    if (!v) return JSI_OK;
    SIGASSERT(v,OBJ);
    if (v->ot != JSI_OT_FUNCTION)
        fprintf(stderr, "invalid func obj\n");
    else if (v->d.fobj) {
        if (v->d.fobj->scope) {
            jsi_ScopeChain *scope = v->d.fobj->scope;
            v->d.fobj->scope = NULL;
            jsi_ScopeChainFree(interp, scope);
        }
    }
    Jsi_ObjDecrRefCount(interp, v);
    return JSI_OK;
}

static Jsi_RC freeBindObjTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Obj *v = (Jsi_Obj *)ptr;
    if (!v) return JSI_OK;
    SIGASSERT(v,OBJ);
    if (v->ot != JSI_OT_FUNCTION)
        fprintf(stderr, "invalid func obj\n");
    else if (v->d.fobj && v->d.fobj->scope) {
        v->d.fobj->scope = NULL;
    }
    Jsi_ObjDecrRefCount(interp, v);
    return JSI_OK;
}

/* TODO: incr ref before add then just decr till done. */
static Jsi_RC freeValueTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_Value *val = (Jsi_Value *)ptr;
    if (!val) return JSI_OK;
    SIGASSERT(val,VALUE);
    //printf("GEN: %p\n", val);
   /* if (val->refCnt>1)
        Jsi_DecrRefCount(interp, val);*/
    Jsi_DecrRefCount(interp, val);
    return JSI_OK;
}

static Jsi_RC freeUserdataTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    if (ptr)
        jsi_UserObjDelete(interp, ptr);
    return JSI_OK;
}

void Jsi_ShiftArgs(Jsi_Interp *interp, Jsi_Value *v) {
    if (!v)
        v = interp->args;
    if (v==NULL || v->vt != JSI_VT_OBJECT || v->d.obj->arr == NULL || v->d.obj->arrCnt <= 0)
        return;
    Jsi_Obj *obj = v->d.obj;
    int n = v->d.obj->arrCnt;
    n--;
    v = obj->arr[0];
    if (v)
        Jsi_DecrRefCount(interp, v);
    if (n>0)
        memmove(obj->arr, obj->arr+1, n*sizeof(Jsi_Value*));
    obj->arr[n] = NULL;
    Jsi_ObjSetLength(interp, obj, n);
}

Jsi_Value *Jsi_Executable(Jsi_Interp *interp)
{
    return jsiIntData.execValue;
}

static Jsi_RC KeyLocker(Jsi_Hash* tbl, int lock)
{
    if (!lock)
        Jsi_MutexUnlock(jsiIntData.mainInterp, jsiIntData.mainInterp->Mutex);
    else
        return Jsi_MutexLock(jsiIntData.mainInterp, jsiIntData.mainInterp->Mutex);
    return JSI_OK;
}

#ifdef JSI_USE_MANY_STRKEY
static Jsi_RC KeyLockerTree(Jsi_Tree* tree, int lock) { return KeyLocker((Jsi_Hash*)tree, lock); }
#endif

static int jsi_deleted = 0, jsi_exitCode = 0; // TODO: move to jsiIntData
static Jsi_Value *jsi_vf = NULL;

static Jsi_RC jsi_InterpDelete(Jsi_Interp *interp, void *ptr) {
    if (jsi_vf)
        Jsi_DecrRefCount(interp, jsi_vf);
    jsi_vf = NULL;
    jsi_exitCode = interp->exitCode;
    jsi_deleted = 1;
    return JSI_OK;
}

Jsi_Interp* Jsi_Main(Jsi_InterpOpts *opts)
{
    int rc = 0;
    Jsi_Interp* interp = NULL;
    int argc = 0, first = 1;
    char **argv = NULL;
    if (opts) {
        interp = opts->interp;
        argc = opts->argc;
        argv = opts->argv;
    }
    if (!interp)
        interp = Jsi_InterpNew(opts);
    if (!interp)
        return NULL;
    Jsi_InterpOnDelete(interp, &jsi_InterpDelete, (void*)&jsi_InterpDelete);
    argc -= interp->iskips;
    argv += interp->iskips;

#ifndef NO_JAZ
    /* Mount zip at end of executable */
    Jsi_Value *v = Jsi_Executable(interp);
    const char *exeFile = (v?Jsi_ValueString(interp, v, NULL):NULL);
    int jsFound = 0;
    if (v && (argc != 2 || Jsi_Strcmp(argv[1], "--nozvfs"))) {
        rc = Jsi_EvalZip(interp, exeFile, JSI_ZVFS_DIR, &jsFound);
        if (rc == JSI_OK) {
            interp->selfZvfs = 1;
            if (!jsFound) {
#if (JSI__FILESYS && JSI__ZVFS)
                fprintf(stderr, "warning: no main.jsi or autoload.jsi\n");
#endif
            }
            if (jsi_deleted)
                return jsi_DoExit(interp, jsi_exitCode);
            else if (rc != 0) {
                fprintf(stderr, "Error\n");
                return jsi_DoExit(interp, 1);
            }
        }
    }
#endif
    const char *ext = NULL, *ai1, *iext = (argc<=1?NULL:Jsi_Strrchr(argv[1], '.'));
    if (interp->selfZvfs && iext && Jsi_Strcmp(iext,".fossil")==0) {
        rc = Jsi_EvalString(interp, "runModule('Archive');", JSI_EVAL_ISMAIN);
        goto done;
    }
    Jsi_ShiftArgs(interp, NULL);
    if (argc <= 1) {
        if (interp->opts.no_interactive && !interp->interactive)
            return interp;
        rc = Jsi_Interactive(interp, JSI_OUTPUT_QUOTE|JSI_OUTPUT_NEWLINES);
        goto done;
    }
    ai1 = argv[1];
    if ((!Jsi_Strcmp(ai1, "-help") || !Jsi_Strcmp(ai1, "-h")) && argc<=3) {
        if (argc>2) {
            if (Jsi_PkgRequire(interp, "Help", 0)>=0) {
                char tbuf[BUFSIZ];
                snprintf(tbuf, sizeof(tbuf), "return runModule('Help', '%s'.trim().split(null));", argv[2]);
                Jsi_RC rc = Jsi_EvalString(interp, tbuf, 0);
                const char *hstr = Jsi_ValueToString(interp, interp->retValue, NULL);
                if (rc == JSI_OK)
                    puts(hstr);
                return jsi_DoExit(interp, 1);
            }
        }
        dohelp:
        puts("USAGE:\n  jsish [PREFIX-OPTS] [COMMAND-OPTS|FILE] ...\n"
          "\nPREFIX-OPTS:\n"
          "  --F\t\tTrace all function calls and returns.\n"
          "  --I OPT:VAL\tInterp option: equivalent to Interp.conf({OPT:VAL}).\n"
          "  --T OPT\tTypecheck option: equivalent to \"use OPT\".\n"
          "  --U\t\tDisplay unittest output, minus pass/fail compare.\n"
          "  --V\t\tSame as --U, but adds file and line number to output.\n"
          "\nCOMMAND-OPTS:\n"
          "  -a\t\tArchive: mount an archive (zip, sqlar or fossil repo) and run module.\n"
          "  -c\t\tCData: generate .c or JSON output from a .jsc description.\n"
          "  -d\t\tDebug: console script debugger.\n"
          "  -e CODE ...\tEvaluate javascript CODE.\n"
          "  -g\t\tGendeep: generate html output from markdeep source.\n"
          "  -h ?CMD?\tHelp: show help for jsish or its commands.\n"
          "  -m\t\tModule: utility create/manage/invoke a Module.\n"
          "  -s\t\tSafe: runs script in safe sub-interp.\n"
          "  -u\t\tUnitTest: test script file(s) or directories .js/.jsi files.\n"
          "  -w\t\tWget: web client to download file from url.\n"
          "  -v\t\tVersion: show version detail: add an arg to show only X.Y.Z\n"
          "  -z\t\tZip: append/manage zip files at end of executable.\n"
          "  -D\t\tDebugUI: web-gui script debugger.\n"
          "  -J\t\tJSpp: preprocess javascript for web.\n"
          "  -S\t\tSqliteUI: web-gui for sqlite database file.\n"
          "  -W\t\tWebsrv: web server to serve out content.\n"
          "\nInterp options may also be set via the environment eg. JSI_INTERP_OPTS='{coverage:true}'\n"
           );
        return jsi_DoExit(interp, 1);
    }
    if (!Jsi_Strcmp(ai1, "-version"))
        ai1 = "-v";
    if (ai1[0] == '-') {
        switch (ai1[1]) {
            case 'a':
                rc = Jsi_EvalString(interp, "runModule('Archive');", JSI_EVAL_ISMAIN);
                break;
            case 'c':
                rc = Jsi_EvalString(interp, "runModule('Cdata');", JSI_EVAL_ISMAIN);
                break;
            case 'd':
                interp->debugOpts.isDebugger = 1;
                rc = Jsi_EvalString(interp, "runModule('Debug');", JSI_EVAL_ISMAIN);
                break;
            case 'D':
                interp->debugOpts.isDebugger = 1;
                rc = Jsi_EvalString(interp, "runModule('DebugUI');", JSI_EVAL_ISMAIN);
                break;
            case 'e':
                if (argc < 3)
                    rc = Jsi_LogError("missing argument");
                else {
                    rc = Jsi_EvalString(interp, argv[2], JSI_EVAL_ISMAIN|JSI_EVAL_NOSKIPBANG);
                    if (rc == JSI_OK && argc>3) {
                        first += 2;
                        Jsi_ShiftArgs(interp, NULL);
                        Jsi_ShiftArgs(interp, NULL);
                        goto dofile;
                    }
                }
                break;
            case 'g':
                rc = Jsi_EvalString(interp, "runModule('GenDeep');", JSI_EVAL_ISMAIN);
                break;
            case 'h':
                goto dohelp;
            case 'J':
                rc = Jsi_EvalString(interp, "runModule('Jspp');", JSI_EVAL_ISMAIN);
                break;
            case 'm':
                if (argc <= 2 || argv[2][0] == '-')
                    rc = Jsi_EvalString(interp, "runModule('Module');", JSI_EVAL_ISMAIN);
                else {
                    Jsi_DString dStr = {}, eStr = {};
                    const char *cps, *cpe;
                    cps = Jsi_Strrchr(argv[2], '/');
                    if (cps) cps++; else cps = argv[2];
                    cpe = Jsi_Strrchr(cps, '.');
                    int len = (cpe?cpe-cps:(int)Jsi_Strlen(cps));
                    if (cpe)
                        Jsi_DSPrintf(&dStr, "source(\"%s\");", argv[2]);
                    else
                        Jsi_DSPrintf(&dStr, "require(\"%s\");", argv[2]);
                    Jsi_DSPrintf(&dStr, "puts(runModule(\"%.*s\",console.args.slice(1)));", len, cps);
                    rc = Jsi_EvalString(interp, Jsi_DSValue(&dStr), JSI_EVAL_NOSKIPBANG);
                    Jsi_DSFree(&dStr);
                    Jsi_DSFree(&eStr);
                }
                break;
            case 's':
                rc = Jsi_EvalString(interp, "runModule('Safe');", JSI_EVAL_ISMAIN);
                break;
            case 'S':
                rc = Jsi_EvalString(interp, "runModule('SqliteUI');", JSI_EVAL_ISMAIN);
                break;
            case 'u':
                rc = Jsi_EvalString(interp, "exit(runModule('UnitTest'));", JSI_EVAL_ISMAIN);
                break;
            case 'v': {
                char str[200] = "\n";
                    
#ifndef JSI__JSIMIN
                Jsi_Channel chan = Jsi_Open(interp, Jsi_ValueNewStringKey(interp, "/zvfs/lib/sourceid.txt"), "r");
                if (chan)
                    Jsi_Read(interp, chan, str, sizeof(str));
#endif
                if (argc>2)
                    printf("%u.%u.%u\n", JSI_VERSION_MAJOR, JSI_VERSION_MINOR, JSI_VERSION_RELEASE);
                else 
                    printf("%u.%u.%u %." JSI_VERFMT_LEN JSI_NUMGFMT " %s", JSI_VERSION_MAJOR, JSI_VERSION_MINOR, JSI_VERSION_RELEASE, Jsi_Version(), str);
                return jsi_DoExit(interp, 1);
            }
            case 'w':
                rc = Jsi_EvalString(interp, "runModule('Wget');", JSI_EVAL_ISMAIN);
                break;
            case 'W':
                rc = Jsi_EvalString(interp, "runModule('Websrv');", JSI_EVAL_ISMAIN);
                break;
            case 'z':
                rc = Jsi_EvalString(interp, "runModule('Zip');", JSI_EVAL_ISMAIN);
                break;
            default:
                puts("usage: jsish [ --I OPT:VAL | --T OPT | --U | --V | --F ] | -e STRING |\n\t"
                "| -a | -c | -d | -D | -h | -m | -s | -S | -u | -v | -w | -W | -z | FILE ...\nUse -help for long help.");
                return jsi_DoExit(interp, 1);
        }
    } else {
dofile:
        ext = Jsi_Strrchr(argv[first], '.');

        /* Support running "main.jsi" from a zip file. */
        if (ext && (Jsi_Strcmp(ext,".zip")==0 ||Jsi_Strcmp(ext,".jsz")==0 ) ) {
            rc = Jsi_EvalZip(interp, argv[first], NULL, &jsFound);
            if (rc<0) {
                fprintf(stderr, "zip mount failed\n");
                return jsi_DoExit(interp, 1);
            }
            if (!(jsFound&JSI_ZIP_MAIN)) {
                fprintf(stderr, "main.jsi not found\n");
                return jsi_DoExit(interp, 1);
            }
        } else if (ext && !Jsi_Strcmp(ext,".jsc")) {
            Jsi_DString dStr = {};
            Jsi_DSPrintf(&dStr, "console.args.unshift('%s'); runModule('CData');", argv[first]);
            rc = Jsi_EvalString(interp, Jsi_DSValue(&dStr), JSI_EVAL_ISMAIN|JSI_EVAL_NOSKIPBANG);
            Jsi_DSFree(&dStr);

        } else {
            if (argc>1) {
                jsi_vf = Jsi_ValueNewStringKey(interp, argv[first]);
                Jsi_IncrRefCount(interp, jsi_vf);
            }
            rc = Jsi_EvalFile(interp, jsi_vf, JSI_EVAL_ARGV0|JSI_EVAL_AUTOINDEX|JSI_EVAL_ISMAIN);
            if (jsi_vf) {
                Jsi_DecrRefCount(interp, jsi_vf);
                jsi_vf = NULL;
            }

        }
    }
    if (jsi_deleted) //TODO: rationalize jsi_deleted, jsi_exitCode, etc
        return jsi_DoExit(rc==JSI_EXIT?NULL:interp, jsi_exitCode);
    if (rc == 0) {
        /* Skip output from an ending semicolon which evaluates to undefined */
        Jsi_Value *ret = Jsi_ReturnValue(interp);
        if (!Jsi_ValueIsType(interp, ret, JSI_VT_UNDEF)) {
            Jsi_DString dStr = {};
            fputs(Jsi_ValueGetDString(interp, ret, &dStr, 0), stdout);
            Jsi_DSFree(&dStr);
            fputs("\n", stdout);
        }
    } else {
        if (!interp->parent && !interp->isHelp)
            fputs("ERROR\n", stderr);
        return jsi_DoExit(interp, 1);
    }

done:
    if (rc == JSI_EXIT) {
        if (opts)
            opts->exitCode = jsi_exitCode;
        return NULL;
    }
    if (jsi_deleted == 0 && interp->opts.auto_delete) {
        Jsi_InterpDelete(interp);
        return NULL;
    }
    return interp;
}

bool jsi_ModBlacklisted(Jsi_Interp *interp, const char *mod) {
    if (!interp->subOpts.blacklist) return false;
    const char *blstr =Jsi_Strstr(interp->subOpts.blacklist, mod);
    if (!blstr) return false;
    if ((blstr==interp->subOpts.blacklist || !isalnum(blstr[-1])) && !isalnum(blstr[Jsi_Strlen(mod)]))
        return false;
    return true;
}

// Get control during script evaluation to support debugging.
static Jsi_RC jsi_InterpDebugHook(struct Jsi_Interp* interp, const char *curFile,
    int curLine, int curLevel, const char *curFunc, const char *opCode, jsi_OpCode *op, const char *emsg)
{
    // TODO: when code is run in debugger, parser.y should attribute op for case stmt to skip str compares, etc.
    int isfun=0;
    if (interp->isInCallback || curLine<=0)
        return JSI_OK;
    if (op && op->nodebug)
        return JSI_OK;
    int isbp = 0, bpId = 0, cont = interp->debugOpts.doContinue,
        stop = (interp->debugOpts.noFilter || interp->debugOpts.forceBreak);
    if (!curFunc)
        curFunc = "";

    if (interp->parent && interp->parent->sigmask) {
        interp->parent->sigmask = 0;
        opCode = "SIGINT";

    } else if (Jsi_Strcmp(opCode, "DEBUG") || !interp->parent) {

        // Avoid overhead of multiple ops on same line of code.
        int sameLine = (interp->debugOpts.lastLine == curLine && interp->debugOpts.lastLevel == curLevel
            && interp->debugOpts.lastFile == curFile);

        if (sameLine && stop==0 && (interp->debugOpts.bpLast==0
            || (interp->debugOpts.bpOpCnt+10) >= interp->opCnt)) //TODO: need better way to detect bp dups.
            goto done;

        if (!interp->debugOpts.debugCallback || !interp->parent) {
            fprintf(stderr, "FILE %s:%d (%d) %s %s\n", curFile, curLine, curLevel, curFunc, opCode);
            return JSI_OK;
        }

        // Check for breakpoints.
        if (interp->breakpointHash) {
            Jsi_HashEntry *hPtr;
            Jsi_HashSearch search;
            for (hPtr = Jsi_HashSearchFirst(interp->breakpointHash, &search);
                hPtr != NULL && stop == 0; hPtr = Jsi_HashSearchNext(&search)) {
                jsi_BreakPoint* bptr = (jsi_BreakPoint*)Jsi_HashValueGet(hPtr);
                if (bptr == NULL || bptr->enabled == 0) continue;
                if (bptr->func)
                    stop = (!Jsi_Strcmp(bptr->func, curFunc));
                else
                    stop = (bptr->line == curLine && !Jsi_Strcmp(bptr->file, curFile));
                if (stop) {
                    isbp = 1;
                    bpId = bptr->id;
                    bptr->hits++;
                    if (bptr->temp)
                        bptr->enabled = 0;
                }
            }
        }

        if (stop == 0) { // No breakpoint.
            if (cont  // Cmd is "continue"
                // Handle "next" by skipping calls into functions.
                || (interp->debugOpts.minLevel>0 && curLevel>interp->debugOpts.minLevel)
                || (isfun=(Jsi_Strcmp(opCode, "PUSHVAR")==0 && op[1].op == OP_PUSHFUN)))
            {
                if (isfun) {
                    interp->debugOpts.lastLine = curLine;
                    interp->debugOpts.lastLevel = curLevel;
                    interp->debugOpts.lastFile = curFile;
                }
done:
                return JSI_OK;
            }
        }
    }
    interp->debugOpts.bpLast = isbp;
    interp->debugOpts.bpOpCnt = interp->opCnt;
    interp->debugOpts.lastLine = curLine;
    interp->debugOpts.lastLevel = curLevel;
    interp->debugOpts.lastFile = curFile;
    interp->debugOpts.forceBreak = 0;

    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    if (emsg && Jsi_Strchr(emsg,'\"'))
        emsg = 0;
    Jsi_DSPrintf(&dStr, "[\"%s\", %d, %d, \"%s\", \"%s\", %d, \"%s\"]", curFile?curFile:"", curLine, curLevel, curFunc, opCode, bpId, emsg?emsg:"");
    interp->isInCallback = 1;
    Jsi_RC rc = JSI_ERROR;
    if (interp->debugOpts.debugCallback)
        rc = Jsi_FunctionInvokeJSON(interp->parent, interp->debugOpts.debugCallback, Jsi_DSValue(&dStr), &interp->retValue);
    interp->isInCallback = 0;
    if (interp->parent->exited == 0 && rc != JSI_OK)
        Jsi_LogError("debugger failure");
    return rc;
}

Jsi_RC jsi_ParseTypeCheckStr(Jsi_Interp *interp, const char *str) {
    uint *iptr = (uint*)&interp->typeCheck;
    const char *wcp = str, *wcn = wcp;
    while (wcn && wcp) {
        int isnot = 0;
        if (*wcp == '!') { isnot = 1; wcp++; }
        wcn = Jsi_Strchr(wcp, ',');
        int ti, wlen = (wcn?(wcn-wcp):(int)Jsi_Strlen(wcp));
#define _JSIPARSETYPES(nam, field) \
        if (wlen == (sizeof(#nam)-1) && !Jsi_Strncmp(#nam, wcp, (sizeof(#nam)-1))) { \
            interp->field = (1-isnot); \
            wcp = (wcn?wcn+1:NULL); \
            continue; \
        }
        _JSIPARSETYPES(Debug, logOpts.Debug)
        _JSIPARSETYPES(Trace, logOpts.Trace)
        _JSIPARSETYPES(Test,  logOpts.Test)
        _JSIPARSETYPES(Info, logOpts.Info)
        _JSIPARSETYPES(Warn, logOpts.Warn)
        _JSIPARSETYPES(Error,  logOpts.Error)
        _JSIPARSETYPES(asserts, asserts)
        _JSIPARSETYPES(assert, asserts)
        _JSIPARSETYPES(noproto, subOpts.noproto)

        const char **tstrs = jsi_TypeChkStrs;
        for (ti=0; tstrs[ti]; ti++) {
            wlen = Jsi_Strlen(tstrs[ti]);
            if (!Jsi_Strncmp(tstrs[ti], wcp, wlen) && (!tstrs[ti][wlen] || tstrs[ti][wlen] == ',')) break;
        }
        if (tstrs[ti]) {
            if (isnot)
                *iptr &= ~(1<<ti);
            else {
                *iptr |= (1<<ti);
                if (!Jsi_Strcmp(tstrs[ti], "all"))
                    interp->typeCheck.parse = interp->typeCheck.run = 1;
                if (!Jsi_Strcmp(tstrs[ti], "strict")) {
                    interp->typeCheck.parse = interp->typeCheck.run = interp->typeCheck.all = 1;
                    if (interp->framePtr->level<=0 || interp->isMain)
                        interp->strict = 1;
                }
            }
        } else {
            Jsi_DString wStr = {};
            int i;
            tstrs = jsi_TypeChkStrs;
            for (i=0; tstrs[i]; i++) Jsi_DSAppend(&wStr, i?", ":"", tstrs[i], NULL);
            Jsi_LogWarn("unknown typeCheck warn option(s) \"%s\" not in: Debug, Trace, Test, Info, Warn, Error, assert, %s, noproto", str, Jsi_DSValue(&wStr));
            Jsi_DSFree(&wStr);
            return JSI_ERROR;
        }
        wcp = (wcn?wcn+1:NULL);
    }
    return JSI_OK;
}

static Jsi_Interp* jsi_InterpNew(Jsi_Interp *parent, Jsi_Value *opts, Jsi_InterpOpts *iopts)
{
    Jsi_Interp* interp;
    if (parent && parent->noSubInterps) {
        interp = parent;
        Jsi_LogError("subinterps disallowed");
        return NULL;
    }
    if (opts && parent && (Jsi_ValueIsObjType(parent, opts, JSI_OT_OBJECT)==0 ||
        Jsi_TreeSize(opts->d.obj->tree)<=0))
        opts = NULL;
    interp = (Jsi_Interp *)Jsi_Calloc(1,sizeof(*interp) + sizeof(jsi_Frame));
    interp->framePtr = (jsi_Frame*)(((uchar*)interp)+sizeof(*interp));
    if (!parent)
        interp->maxInterpDepth = JSI_MAX_SUBINTERP_DEPTH;
    else {
        interp->maxInterpDepth = parent->maxInterpDepth;
        interp->interpDepth = parent->interpDepth+1;
        if (interp->interpDepth > interp->maxInterpDepth) {
            Jsi_Free(interp);
            interp = parent;
            Jsi_LogError("exceeded max subinterp depth");
            return NULL;
        }
    }
    interp->maxDepth = JSI_MAX_EVAL_DEPTH;
    interp->maxIncDepth = JSI_MAX_INCLUDE_DEPTH;
    interp->typeWarnMax = 50;
    interp->subOpts.dblPrec = __DBL_DECIMAL_DIG__-1;
    interp->subOpts.prompt = "$ ";
    interp->subOpts.prompt2 = "> ";

    int iocnt;
    if (iopts) {
        iopts->interp = interp;
        interp->opts = *iopts;
    }
    interp->logOpts.file = 1;
    interp->logOpts.func = 1;
    interp->logOpts.Info = 1;
    interp->logOpts.Warn = 1;
    interp->logOpts.Error = 1;
    int argc = interp->opts.argc;
    char **argv = interp->opts.argv;
    char *argv0 = (argv?argv[0]:NULL);
    interp->parent = parent;
    interp->topInterp = (parent == NULL ? interp: parent->topInterp);
    if (jsiIntData.mainInterp == NULL)
        jsiIntData.mainInterp = interp->topInterp;
    interp->mainInterp = jsiIntData.mainInterp; // The first interps handles exit.
    interp->memDebug = interp->opts.mem_debug;
    if (parent) {
        interp->dbPtr = parent->dbPtr;
    } else {
        interp->dbPtr = &interp->dbStatic;
    }
#ifdef JSI_MEM_DEBUG
    if (!interp->dbPtr->valueDebugTbl) {
        interp->dbPtr->valueDebugTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, NULL);
        interp->dbPtr->objDebugTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, NULL);
    }
#endif
    if (parent) {
        if (parent->pkgDirs)
            interp->pkgDirs = Jsi_ValueDupJSON(interp, parent->pkgDirs);
    } else {
#ifdef JSI_PKG_DIRS
        interp->pkgDirs = Jsi_StringSplit(interp, JSI_PKG_DIRS, ",");
        Jsi_IncrRefCount(interp, interp->pkgDirs);
#endif
    }
#ifdef JSI_USE_COMPAT
    interp->compat = JSI_USE_COMPAT;
#endif
    const char *ocp = NULL;
#ifndef JSI_CONF_ARGS
#define JSI_CONF_ARGS ""
#endif
    interp->confArgs = JSI_CONF_ARGS;
#ifndef JSI_OMIT_INTERP_ENV
    ocp = getenv("JSI_INTERP_OPTS");
#endif
    const char *ocp2;
    if (ocp && ((ocp2=Jsi_Strstr(ocp,"memDebug:"))))
        interp->memDebug=strtol(ocp+sizeof("memDebug:"), NULL, 0);
    if (ocp && ((ocp2=Jsi_Strstr(ocp,"compat:"))))
        interp->subOpts.compat=(ocp[sizeof("compat:")]=='t');
    for (iocnt = 1; (iocnt+1)<argc; iocnt+=2)
    {
        const char *aio = argv[iocnt];
        if (Jsi_Strcmp(aio, "--T") == 0) {
            continue;
        }
        if (Jsi_Strcmp(aio, "--F") == 0 || Jsi_Strcmp(aio, "--U") == 0 || Jsi_Strcmp(aio, "--V") == 0) {
            iocnt--;
            continue;
        }
        if (!Jsi_Strcmp(aio, "--I")) {
            const char *aio2 = argv[iocnt+1];
            if (!Jsi_Strncmp("memDebug:", aio2, sizeof("memDebug")))
                interp->memDebug=strtol(aio2+sizeof("memDebug"), NULL, 0);
            else if (!Jsi_Strncmp("compat", aio2, sizeof("compat")))
                interp->subOpts.compat=strtol(aio2+sizeof("compat"), NULL, 0);
            continue;
        }
        break;
    }
    SIGINIT(interp,INTERP);
    interp->NullValue = Jsi_ValueNewNull(interp);
    Jsi_IncrRefCount(interp, interp->NullValue);
#ifdef __WIN32
    Jsi_DString cwdStr;
    Jsi_DSInit(&cwdStr);
    interp->curDir = Jsi_Strdup(Jsi_GetCwd(interp, &cwdStr));
    Jsi_DSFree(&cwdStr);
#else
    char buf[JSI_BUFSIZ];
    interp->curDir = getcwd(buf, sizeof(buf));
    interp->curDir = Jsi_Strdup(interp->curDir?interp->curDir:".");
#endif
    interp->onDeleteTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeOnDeleteTbl);
    interp->assocTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, freeAssocTbl);
    interp->cmdSpecTbl = Jsi_MapNew(interp, JSI_MAP_TREE, JSI_KEYS_STRING, freeCmdSpecTbl);
    interp->eventTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeEventTbl);
    interp->fileTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, jsi_HashFree);
    interp->funcObjTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeFuncObjTbl);
    interp->funcsTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeFuncsTbl);
    interp->bindTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeBindObjTbl);
    interp->protoTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL/*freeValueTbl*/);
    interp->regexpTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, regExpFree);
    interp->preserveTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, jsi_HashFree);
    interp->loadTbl = (parent?parent->loadTbl:Jsi_HashNew(interp, JSI_KEYS_STRING, jsi_FreeOneLoadHandle));
    interp->packageHash = Jsi_HashNew(interp, JSI_KEYS_STRING, packageHashFree);
    interp->aliasHash = Jsi_HashNew(interp, JSI_KEYS_STRING, AliasFree);

    interp->lockTimeout = -1;
#ifdef JSI_LOCK_TIMEOUT
    interp->lockTimeout JSI_LOCK_TIMEOUT;
#endif
#ifndef JSI_DO_UNLOCK
#define JSI_DO_UNLOCK 1
#endif
    interp->subOpts.mutexUnlock = JSI_DO_UNLOCK;
    Jsi_Map_Type mapType = JSI_MAP_HASH;
#ifdef JSI_USE_MANY_STRKEY
    mapType = JSI_MAP_TREE;
#endif

    if (interp == jsiIntData.mainInterp || interp->threadId != jsiIntData.mainInterp->threadId) {
        interp->strKeyTbl = Jsi_MapNew(interp,  mapType, JSI_KEYS_STRING, NULL);
        interp->subOpts.privKeys = 1;
    }
    // Handle interp options: -T value and -Ixxx value
    for (iocnt = 1; (iocnt+1)<argc && !interp->parent; iocnt+=2)
    {
        const char *aio = argv[iocnt];
        if (Jsi_Strcmp(aio, "--F") == 0) {
            interp->traceCall |= (jsi_callTraceFuncs |jsi_callTraceArgs |jsi_callTraceReturn | jsi_callTraceBefore | jsi_callTraceFullPath);
            iocnt--;
            interp->iskips++;
            continue;
        }
        if (Jsi_Strcmp(aio, "--U") == 0) {
            interp->asserts = 1;
            interp->unitTest = 1;
            iocnt--;
            interp->iskips++;
            continue;
        }
        if (Jsi_Strcmp(aio, "--V") == 0) {
            interp->asserts = 1;
            interp->unitTest = 5;
            interp->tracePuts = 1;
            iocnt--;
            interp->iskips++;
            continue;
        }
        if (Jsi_Strcmp(aio, "--T") == 0) {
            if (jsi_ParseTypeCheckStr(interp, argv[iocnt+1]) != JSI_OK) {
                Jsi_InterpDelete(interp);
                return NULL;
            }
            interp->iskips+=2;
            continue;
        }
        if (!Jsi_Strcmp(aio, "--I"))  {
            bool bv = 1;
            char *aio2 = argv[iocnt+1], *aioc = Jsi_Strchr(aio2, ':'),
                argNamS[50], *argNam = aio2;
            const char *argVal;
            if (!Jsi_Strcmp("traceCall", aio2))
                interp->traceCall |= (jsi_callTraceFuncs |jsi_callTraceArgs |jsi_callTraceReturn | jsi_callTraceBefore | jsi_callTraceFullPath);
            else {
                if (aioc) {
                    argNam = argNamS;
                    argVal = aioc+1;
                    snprintf(argNamS, sizeof(argNamS), "%.*s", (int)(aioc-aio2), aio2);
                }
                
                DECL_VALINIT(argV);
                Jsi_Value *argValue = &argV;
                Jsi_Number dv;
                if (!aioc || Jsi_GetBool(interp, argVal, &bv) == JSI_OK) {
                    Jsi_ValueMakeBool(interp, &argValue, bv);
                } else if (!Jsi_Strcmp("null", argVal)) {
                    Jsi_ValueMakeNull(interp, &argValue);
                } else if (Jsi_GetDouble(interp, argVal, &dv) == JSI_OK) {
                    Jsi_ValueMakeNumber(interp, &argValue, dv);
                } else {
                    Jsi_ValueMakeStringKey(interp, &argValue, argVal);
                }
                if (JSI_OK != Jsi_OptionsSet(interp, InterpOptions, interp, argNam, argValue, 0)) {
                    Jsi_InterpDelete(interp);
                    return NULL;
                }
            }
            interp->iskips+=2;
            continue;
        }
        break;
    }
    if (!interp->strKeyTbl)
        interp->strKeyTbl = jsiIntData.mainInterp->strKeyTbl;
    if (opts) {
        interp->inopts = opts = Jsi_ValueDupJSON(interp, opts);
        if (Jsi_OptionsProcess(interp, InterpOptions, interp, opts, 0) < 0) {
            Jsi_DecrRefCount(interp, opts);
            interp->inopts = NULL;
            Jsi_InterpDelete(interp);
            return NULL;
        }
    }
    if (interp == jsiIntData.mainInterp) {
        interp->subthread = 0;
    } else {
        if (opts) {
            if (interp->subOpts.privKeys && interp->strKeyTbl == jsiIntData.mainInterp->strKeyTbl) {
                //Jsi_HashDelete(interp->strKeyTbl);
                Jsi_OptionsFree(interp, InterpOptions, interp, 0); /* Reparse options to populate new key table. */
                interp->strKeyTbl = Jsi_MapNew(interp, mapType, JSI_KEYS_STRING, NULL);
                if (opts->vt != JSI_VT_NULL) Jsi_OptionsProcess(interp, InterpOptions, interp, opts, 0);
            } else if (interp->subOpts.privKeys == 0 && interp->strKeyTbl != jsiIntData.mainInterp->strKeyTbl) {
                Jsi_OptionsFree(interp, InterpOptions, interp, 0); /* Reparse options to populate new key table. */
                Jsi_MapDelete(interp->strKeyTbl);
                interp->strKeyTbl = jsiIntData.mainInterp->strKeyTbl;
                if (opts->vt != JSI_VT_NULL) Jsi_OptionsProcess(interp, InterpOptions, interp, opts, 0);
            }
        }
        if (parent && parent->isSafe)
            interp->isSafe = 1;
        if (interp->subthread && interp->isSafe)
            interp->subthread = 0;
        if (interp->subthread)
            jsiIntData.mainInterp->threadCnt++;
        if (interp->subthread && interp->strKeyTbl == jsiIntData.mainInterp->strKeyTbl)
            jsiIntData.mainInterp->threadShrCnt++;
        if (jsiIntData.mainInterp->threadShrCnt)
#ifdef JSI_USE_MANY_STRKEY
            jsiIntData.mainInterp->strKeyTbl->v.tree->opts.lockTreeProc = KeyLockerTree;
#else
            jsiIntData.mainInterp->strKeyTbl->v.hash->opts.lockHashProc = KeyLocker;
#endif
    }
    if (parent && parent->isSafe) {
        interp->isSafe = 1;
        interp->maxOpCnt = parent->maxOpCnt;
        if (interp->safeWriteDirs || interp->safeReadDirs || interp->safeExecPattern) {
            Jsi_LogWarn("ignoring safe* options in safe sub-sub-interp");
            if (interp->safeWriteDirs) Jsi_DecrRefCount(interp, interp->safeWriteDirs);
            if (interp->safeReadDirs) Jsi_DecrRefCount(interp, interp->safeReadDirs);
            interp->safeWriteDirs = interp->safeReadDirs = NULL;
            interp->safeExecPattern = NULL;
        }
    }

    Jsi_DString oStr = {};
#ifdef JSI_INTERP_OPTS  /* Omits curley braces: eg. "nonStrict: true, maxOpCnt:1000000" */
    if (ocp && *ocp)
        Jsi_DSAppend(&oStr, "{", JSI_INTERP_OPTS, ", ", ocp+1, NULL);
    else
        Jsi_DSAppend(&oStr, "{", JSI_INTERP_OPTS, "}", NULL);
#else
    Jsi_DSAppend(&oStr, ocp, NULL);
#endif
    ocp = Jsi_DSValue(&oStr);
    if (
#ifdef JSI_ENV_OPTS_MAINONLY
        interp == jsiIntData.mainInterp  &&
#endif
     *ocp) {
        Jsi_Value *popts = Jsi_ValueNew1(interp);
        if (Jsi_JSONParse(interp, ocp, &popts, 0) != JSI_OK ||
            Jsi_OptionsProcess(interp, InterpOptions, interp, popts, JSI_OPTS_IS_UPDATE) < 0) {
            Jsi_DecrRefCount(interp, popts);
            Jsi_InterpDelete(interp);
            Jsi_DSFree(&oStr);
            return NULL;
        }
        Jsi_DecrRefCount(interp, popts);
    }
    Jsi_DSFree(&oStr);
    jsi_InterpConfFiles(interp);

#ifndef JSI_MEM_DEBUG
    static int warnNoDebug = 0;
    if (interp->memDebug && warnNoDebug == 0) {
        Jsi_LogWarn("ignoring memDebug as jsi was compiled without memory debugging");
        warnNoDebug = 1;
    }
#endif
    interp->threadId = Jsi_CurrentThread();
    if (interp->parent && interp->subthread==0 && interp->threadId != interp->parent->threadId) {
        interp->threadId = interp->parent->threadId;
#ifndef JSI_MEM_DEBUG
        Jsi_LogWarn("non-threaded sub-interp created by different thread than parent");
#endif
    }
    if (interp->safeMode != jsi_safe_None)
        interp->isSafe = interp->startSafe = 1;
    if (!interp->parent) {
        if (interp->isSafe)
            interp->startSafe = 1;
        if (interp->debugOpts.msgCallback)
            Jsi_LogWarn("ignoring msgCallback");
        if (interp->debugOpts.putsCallback)
            Jsi_LogWarn("ignoring putsCallback");
        if (interp->busyCallback)
            Jsi_LogWarn("ignoring busyCallback");
        if (interp->recvCallback)
            Jsi_LogWarn("ignoring recvCallback");
        if (interp->debugOpts.traceCallback)
            Jsi_LogWarn("ignoring traceCallback");
    } else if (interp->busyCallback && interp->threadId != interp->parent->threadId) {
        Jsi_LogWarn("disabling busyCallback due to threads");
        interp->busyCallback = NULL;
    }
    if (interp == jsiIntData.mainInterp)
        interp->lexkeyTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL);
    else
        interp->lexkeyTbl = jsiIntData.mainInterp->lexkeyTbl;
    interp->thisTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeValueTbl);
    interp->userdataTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, freeUserdataTbl);
    interp->varTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL);
    interp->codeTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, freeCodeTbl);
    interp->genValueTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD,freeValueTbl);
    interp->genObjTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, freeGenObjTbl);
#ifdef JSI_MEM_DEBUG
    interp->codesTbl = (interp == jsiIntData.mainInterp ? Jsi_HashNew(interp, JSI_KEYS_ONEWORD, NULL) : jsiIntData.mainInterp->codesTbl);
#endif
    if (interp->typeCheck.all|interp->typeCheck.parse|interp->typeCheck.funcsig)
        interp->staticFuncsTbl = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL);
    interp->maxArrayList = MAX_ARRAY_LIST;
    if (!jsiIntData.isInit) {
        jsiIntData.isInit = 1;
        jsi_InitValue(interp, 0);
        jsiIntData.interpsTbl = Jsi_HashNew(interp, JSI_KEYS_ONEWORD, 0);
    }

    /* current scope, also global */
    interp->csc = Jsi_ValueNew1(interp);
    Jsi_ValueMakeObject(interp, &interp->csc, Jsi_ObjNew(interp));
    interp->framePtr->incsc = interp->csc;

#define JSIDOINIT(nam) if (!jsi_ModBlacklisted(interp,#nam)) { if (jsi_Init##nam(interp, 0) != JSI_OK) { Jsi_LogBug("Init failure in %s", #nam); } }
#define JSIDOINIT2(nam) if (!jsi_ModBlacklisted(interp,#nam)) { if (Jsi_Init##nam(interp, 0) != JSI_OK) { Jsi_LogBug("Init failure in %s", #nam); } }

    JSIDOINIT(Proto);

    if (interp->pkgDirs) // Fix-up because above, array was not yet initialized.
        interp->pkgDirs->d.obj->__proto__ = interp->Array_prototype;

    Jsi_Value *modObj = Jsi_ValueNewObj(interp, Jsi_ObjNewType(interp, JSI_OT_OBJECT));
    Jsi_ValueInsert(interp, interp->csc, "Jsi_Auto", modObj, JSI_OM_DONTDEL);

    /* initial scope chain, nothing */
    interp->framePtr->ingsc = interp->gsc = jsi_ScopeChainNew(interp, 0);

    interp->ps = jsi_PstateNew(interp); /* Default parser. */
    if (interp->unitTest&2) {
        interp->logOpts.before = 1;
        interp->logOpts.full = 1;
        interp->tracePuts = 1;
        interp->noStderr = 1;
    }
    if (interp->args && argc) {
        Jsi_LogBug("args may not be specified both as options and parameter");
        Jsi_InterpDelete(interp);
        return NULL;
    }
    if (interp->maxDepth>JSI_MAX_EVAL_DEPTH)
        interp->maxDepth = JSI_MAX_EVAL_DEPTH;

    // Create the args array.
    if (argc >= 0 && !interp->args) {
        Jsi_Value *iargs = Jsi_ValueNew1(interp);
        iargs->f.bits.dontdel = 1;
        iargs->f.bits.readonly = 1;
        Jsi_Obj *iobj = Jsi_ObjNew(interp);
        Jsi_ValueMakeArrayObject(interp, &iargs, iobj);
        int i = 1, ii = (iocnt>1 ? iocnt : 1);
        int msiz = (argc?argc-iocnt:0);
        Jsi_ObjArraySizer(interp, iobj, msiz);
        iobj->arrMaxSize = msiz;
        iocnt--;
        iobj->arrCnt = argc-iocnt;
        for (i = 1; ii < argc; ++ii, i++) {
            iobj->arr[i-1] = Jsi_ValueNewStringKey(interp, argv[ii]);
            Jsi_IncrRefCount(interp, iobj->arr[i-1]);
            jsi_ValueDebugLabel(iobj->arr[i-1], "InterpCreate", "args");
        }
        Jsi_ObjSetLength(interp, iobj, msiz);
        interp->args = iargs;
    } else if (interp->parent && interp->args) {
        // Avoid strings from sneeking in with options from parent...
        Jsi_Value *nar = Jsi_ValueDupJSON(interp, interp->args);
        Jsi_DecrRefCount(interp, interp->args);
        interp->args = nar;
    }
    JSIDOINIT(Options);
    JSIDOINIT(Cmds);
    JSIDOINIT(Interp);
    JSIDOINIT(JSON);

    interp->retValue = Jsi_ValueNew1(interp);
    interp->Mutex = Jsi_MutexNew(interp, -1, JSI_MUTEX_RECURSIVE);
    if (1 || interp->subthread) {
        interp->QMutex = Jsi_MutexNew(interp, -1, JSI_MUTEX_RECURSIVE);
        Jsi_DSInit(&interp->interpEvalQ);
        Jsi_DSInit(&interp->interpMsgQ);
    }
    JSIDOINIT(Lexer);
    if (interp != jsiIntData.mainInterp && !parent)
        Jsi_HashSet(jsiIntData.interpsTbl, interp, NULL);

    if (!interp->isSafe) {
        JSIDOINIT(Load);
#if JSI__SIGNAL==1
        JSIDOINIT(Signal);
#endif
    }
    if (interp->isSafe == 0 || interp->startSafe || interp->safeWriteDirs!=NULL || interp->safeReadDirs!=NULL) {
#if JSI__FILESYS==1
        JSIDOINIT(FileCmds);
        JSIDOINIT(Filesys);
#endif
    }
#if JSI__SQLITE==1
    JSIDOINIT2(Sqlite);
#else
    Jsi_initSqlite(interp, 0);
#endif
#if JSI__MYSQL==1
    if (!interp->noNetwork) {
        JSIDOINIT2(MySql);
    }
#endif
#if JSI__SOCKET==1
    JSIDOINIT2(Socket);
#endif
#if JSI__WEBSOCKET==1
    JSIDOINIT2(WebSocket);
#endif

#if JSI__CDATA==1
    JSIDOINIT(CData);
#endif

#ifdef JSI_USER_EXTENSION
    extern int JSI_USER_EXTENSION(Jsi_Interp *interp, int release);
    if (JSI_USER_EXTENSION (interp, 0) != JSI_OK) {
        fprintf(stderr, "extension load failed");
        return jsi_DoExit(interp, 1);
    }
#endif
    Jsi_PkgProvide(interp, "Jsi", JSI_VERSION, NULL);
    if (argc > 0) {
        char *ss = argv0;
        char epath[PATH_MAX] = ""; // Path of executable
#ifdef __WIN32

        if (GetModuleFileName(NULL, epath, sizeof(epath))>0)
            ss = epath;
#else
#ifndef PROC_SELF_DIR
#define PROC_SELF_DIR "/proc/self/exe"
#endif
        if (ss && *ss != '/' && readlink(PROC_SELF_DIR, epath, sizeof(epath)) && epath[0])
            ss = epath;
#endif
        Jsi_Value *src = Jsi_ValueNewStringDup(interp, ss);
        Jsi_IncrRefCount(interp, src);
        jsiIntData.execName = Jsi_Realpath(interp, src, NULL);
        Jsi_DecrRefCount(interp, src);
        if (!jsiIntData.execName) jsiIntData.execName = Jsi_Strdup("");
        jsiIntData.execValue = Jsi_ValueNewString(interp, jsiIntData.execName, -1);
        Jsi_IncrRefCount(interp, jsiIntData.execValue);
        Jsi_HashSet(interp->genValueTbl, jsiIntData.execValue, jsiIntData.execValue);
    }

    //interp->nocacheOpCodes = 1;
    if (interp->debugOpts.debugCallback && !interp->debugOpts.hook) {
        interp->debugOpts.hook = jsi_InterpDebugHook;
    }
    interp->startTime = jsi_GetTimestamp();
#ifdef JSI_INTERP_EXTENSION_CODE // For extending interp from jsi.c
    JSI_INTERP_EXTENSION_CODE
#endif
    if (interp->opts.initProc && (*interp->opts.initProc)(interp, 0) != JSI_OK)
        Jsi_LogBug("Init failure in initProc");

    return interp;
}

Jsi_Interp* Jsi_InterpNew(Jsi_InterpOpts *opts)
{
    return jsi_InterpNew(NULL, NULL, opts);
}

bool Jsi_InterpGone( Jsi_Interp* interp)
{
    return (interp == NULL || interp->deleting || interp->destroying || interp->exited);
}

static void DeleteAllInterps() { /* Delete toplevel interps. */
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch search;
    if (!jsiIntData.interpsTbl)
        return;
    for (hPtr = Jsi_HashSearchFirst(jsiIntData.interpsTbl, &search); hPtr; hPtr = Jsi_HashSearchNext(&search)) {
        Jsi_Interp *interp = (Jsi_Interp *)Jsi_HashKeyGet(hPtr);
        Jsi_HashEntryDelete(hPtr);
        interp->destroying = 1;
        Jsi_InterpDelete(interp);
    }
    Jsi_HashDelete(jsiIntData.interpsTbl);
    jsiIntData.interpsTbl = NULL;
    jsiIntData.isInit = 0;
}

#ifdef JSI_MEM_DEBUG

typedef enum { MDB_INOBJ=1, MDB_VISITED=2 } jsi_MDB;

void jsiFlagDebugValues(Jsi_Interp *interp, Jsi_Obj *obj)
{
    Jsi_Value *v;
    int oflags;
    if (obj->ot != JSI_OT_OBJECT && obj->ot != JSI_OT_ARRAY)
        return;
    if (obj->tree) {
        Jsi_TreeEntry *hPtr;
        Jsi_TreeSearch srch;
        for (hPtr=Jsi_TreeSearchFirst(obj->tree, &srch,  JSI_TREE_ORDER_IN, NULL); hPtr;
            hPtr=Jsi_TreeSearchNext(&srch)) {
            v = (Jsi_Value*)Jsi_TreeValueGet(hPtr);
            if (v == NULL || v->sig != JSI_SIG_VALUE) continue;
            oflags = v->VD.flags;
            v->VD.flags |= (MDB_VISITED|MDB_INOBJ);
            if (oflags&MDB_VISITED || v->vt != JSI_VT_OBJECT)
                continue;
            jsiFlagDebugValues(interp, v->d.obj);
        }
    }
    if (obj->arr) {
        uint i;
        for (i=0; i<obj->arrCnt; i++) {
            v = obj->arr[i];
            if (v == NULL || v->sig != JSI_SIG_VALUE) continue;
            oflags = v->VD.flags;
            v->VD.flags |= (MDB_VISITED|MDB_INOBJ);
            if (oflags&MDB_VISITED || v->vt != JSI_VT_OBJECT)
                continue;
            jsiFlagDebugValues(interp, v->d.obj);
        }
    }
}

void jsi_DebugDumpValues(Jsi_Interp *interp)
{
    if (jsiIntData.mainInterp != interp) return;
    int vdLev = interp->memDebug;
    int have = (interp->dbPtr->valueDebugTbl->numEntries || interp->dbPtr->objDebugTbl->numEntries);
    if ((have && vdLev>0) || vdLev>=3) {
        // First traverse all Object trees/arrays and mark all values contained therein.
        Jsi_HashSearch search;
        Jsi_HashEntry *hPtr;
        for (hPtr = Jsi_HashSearchFirst(interp->dbPtr->objDebugTbl, &search);
            hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
            Jsi_Obj *vp = (Jsi_Obj *)Jsi_HashKeyGet(hPtr);
            if (vp!=NULL && vp->sig == JSI_SIG_OBJ) {
                jsiFlagDebugValues(interp, vp);
            }
        }
        if (interp->dbPtr->valueDebugTbl->numEntries != interp->dbPtr->valueCnt)
            fprintf(stderr, "\n\nValues table/alloc mismatch: table=%d, alloc=%d\n",
                interp->dbPtr->valueDebugTbl->numEntries, interp->dbPtr->valueCnt);
        // Dump unfreed values and objs.
        int refSum=0, refsum=0;
        int bcnt[4] = {};
        if (vdLev>1 && interp->dbPtr->valueDebugTbl->numEntries)
            fprintf(stderr, "\n\nUNFREED VALUES \"[*ptr,#refCnt,type,idx:label,label2]: @file:line in func() ...\"\n");
        for (hPtr = Jsi_HashSearchFirst(interp->dbPtr->valueDebugTbl, &search);
            hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
            Jsi_Value *vp = (Jsi_Value *)Jsi_HashKeyGet(hPtr);
            if (vp==NULL || vp->sig != JSI_SIG_VALUE) {
                bcnt[0]++;
                if (vdLev>1)
                    fprintf(stderr, "BAD VALUE: %p\n", vp);
            } else {
                bcnt[1]++;
                refSum += vp->refCnt;
                if (vdLev>1) {
                    char ebuf[JSI_BUFSIZ], ebuf2[JSI_MAX_NUMBER_STRING];
                    ebuf[0] = 0;
                    if (vp->vt==JSI_VT_OBJECT)
                        snprintf(ebuf, sizeof(ebuf), " {obj=%p, otype=%s}", vp->d.obj, Jsi_ObjTypeStr(interp, vp->d.obj));
                    else if (vp->vt==JSI_VT_NUMBER)
                        snprintf(ebuf, sizeof(ebuf), " {num=%s}", Jsi_NumberToString(interp, vp->d.num, ebuf2, sizeof(ebuf2)));
                    else if (vp->vt==JSI_VT_BOOL)
                        snprintf(ebuf, sizeof(ebuf), " {bool=%s}", vp->d.val?"true":"false");
                    else if (vp->vt==JSI_VT_STRING) {
                        const char *sbuf = ((vp->d.s.str && Jsi_Strlen(vp->d.s.str)>40)?"...":"");
                        snprintf(ebuf, sizeof(ebuf), " {string=\"%.40s%s\"}", (vp->d.s.str?vp->d.s.str:""), sbuf);
                    }
                    const char *pfx = "";
                    if (!(vp->VD.flags&MDB_INOBJ))
                        pfx = "!"; // Value is not contained in an object.
                    fprintf(stderr, "[%s*%p,#%d,%s,%d:%s%s%s]:%s @%s:%d in %s()%s\n", pfx,
                        vp, vp->refCnt, Jsi_ValueTypeStr(interp, vp), vp->VD.Idx,
                        (vp->VD.label?vp->VD.label:""), (vp->VD.label2?":":""),
                        (vp->VD.label2?vp->VD.label2:""), vp->VD.interp==jsiIntData.mainInterp?"":"!",
                        vp->VD.fname, vp->VD.line, vp->VD.func, ebuf);
                }
            }
        }
        if (interp->dbPtr->objDebugTbl->numEntries != interp->dbPtr->objCnt)
            fprintf(stderr, "\n\nObject table/alloc mismatch: table=%d, alloc=%d\n",
                interp->dbPtr->objDebugTbl->numEntries, interp->dbPtr->objCnt);
        if (vdLev>1 && interp->dbPtr->objDebugTbl->numEntries)
            fprintf(stderr, "\n\nUNFREED OBJECTS \"[*ptr,#refCnt,type,idx:label,label2]: @file:line in func() ...\"\n");
        for (hPtr = Jsi_HashSearchFirst(interp->dbPtr->objDebugTbl, &search);
            hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
            Jsi_Obj *vp = (Jsi_Obj *)Jsi_HashKeyGet(hPtr);
            if (vp==NULL || vp->sig != JSI_SIG_OBJ) {
                bcnt[2]++;
                fprintf(stderr, "BAD OBJ: %p\n", vp);
            } else {
                bcnt[3]++;
                refsum += vp->refcnt;
                if (vdLev>1) {
                    char ebuf[JSI_BUFSIZ], ebuf2[JSI_MAX_NUMBER_STRING];
                    ebuf[0] = 0;
                    if (vp->ot==JSI_OT_OBJECT) {
                        if (vp->isarrlist)
                            snprintf(ebuf, sizeof(ebuf), "tree#%d, array#%d", (vp->tree?vp->tree->numEntries:0), vp->arrCnt);
                        else
                            snprintf(ebuf, sizeof(ebuf), "tree#%d", (vp->tree?vp->tree->numEntries:0));
                    } else if (vp->ot==JSI_OT_NUMBER)
                        snprintf(ebuf, sizeof(ebuf), "num=%s", Jsi_NumberToString(interp, vp->d.num, ebuf2, sizeof(ebuf2)));
                    else if (vp->ot==JSI_OT_BOOL)
                        snprintf(ebuf, sizeof(ebuf), "bool=%s", vp->d.val?"true":"false");
                    else if (vp->ot==JSI_OT_STRING) {
                        const char *sbuf = ((vp->d.s.str && Jsi_Strlen(vp->d.s.str)>40)?"...":"");
                        snprintf(ebuf, sizeof(ebuf), "string=\"%.40s%s\"", (vp->d.s.str?vp->d.s.str:""), sbuf);
                    }
                    fprintf(stderr, "[*%p,#%d,%s,%d:%s%s%s]:%s @%s:%d in %s() {%s}\n",
                        vp, vp->refcnt, Jsi_ObjTypeStr(interp, vp), vp->VD.Idx, vp->VD.label?vp->VD.label:"",
                        vp->VD.label2?":":"",vp->VD.label2?vp->VD.label2:"", vp->VD.interp==jsiIntData.mainInterp?"":"!",
                        vp->VD.fname, vp->VD.line,
                        vp->VD.func, ebuf);
                }
            }
        }
        fprintf(stderr, "\nVALUES: bad=%d,unfreed=%d,allocs=%d,refsum=%d  | OBJECTS: bad=%d,unfreed=%d,allocs=%d,refsum=%d  interp=%p\n",
            bcnt[0], bcnt[1], interp->dbPtr->valueAllocCnt, refSum, bcnt[2], bcnt[3], interp->dbPtr->objAllocCnt, refsum, interp);

        if (interp->codesTbl)
            for (hPtr = Jsi_HashSearchFirst(interp->codesTbl, &search);
                hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
                Jsi_OpCodes *vp = (Jsi_OpCodes *)Jsi_HashKeyGet(hPtr);
                fprintf(stderr, "unfreed opcodes: %d\n", vp->id);
            }
    }
    Jsi_HashDelete(interp->dbPtr->valueDebugTbl);
    Jsi_HashDelete(interp->dbPtr->objDebugTbl);
    Jsi_HashDelete(interp->codesTbl);
    bool isMainInt = (interp == jsiIntData.mainInterp);
    if (isMainInt && vdLev>3)
        _exit(1); // Avoid sanitize output.
}
#endif

static Jsi_RC jsiInterpDelete(Jsi_Interp* interp, void *unused)
{
    SIGASSERT(interp,INTERP);
    bool isMainInt = (interp == jsiIntData.mainInterp);
    int mainFlag = (isMainInt ? 2 : 1);
    if (isMainInt)
        DeleteAllInterps();
    if (interp->opts.initProc)
        (*interp->opts.initProc)(interp, mainFlag);
    jsiIntData.delInterp = interp;
    if (interp->gsc) jsi_ScopeChainFree(interp, interp->gsc);
    if (interp->csc) Jsi_DecrRefCount(interp, interp->csc);
    if (interp->ps) jsi_PstateFree(interp->ps);
    int i;
    for (i=0; i<interp->maxStack; i++) {
        if (interp->Stack[i]) Jsi_DecrRefCount(interp, interp->Stack[i]);
        if (interp->Obj_this[i]) Jsi_DecrRefCount(interp, interp->Obj_this[i]);
    }
    if (interp->Stack) {
        Jsi_Free(interp->Stack);
        Jsi_Free(interp->Obj_this);
    }

    if (interp->argv0)
        Jsi_DecrRefCount(interp, interp->argv0);
    if (interp->console)
        Jsi_DecrRefCount(interp, interp->console);
    if (interp->lastSubscriptFail)
        Jsi_DecrRefCount(interp, interp->lastSubscriptFail);
    if (interp->nullFuncRet)
        Jsi_DecrRefCount(interp, interp->nullFuncRet);
    Jsi_HashDelete(interp->codeTbl);
    Jsi_MapDelete(interp->cmdSpecTbl);
    Jsi_HashDelete(interp->fileTbl);
    Jsi_HashDelete(interp->funcObjTbl);
    Jsi_HashDelete(interp->funcsTbl);
    if (interp->profileCnt) { // TODO: resolve some values from dbPtr, others not.
        double endTime = jsi_GetTimestamp();
        double coverage = (int)(100.0*interp->coverHit/interp->coverAll);
        Jsi_DString dStr;
        Jsi_DSInit(&dStr);
        Jsi_DSPrintf(&dStr, "PROFILE: TOTAL: time=%.6f, func=%.6f, cmd=%.6f, #funcs=%d, #cmds=%d, cover=%2.1f%%, #values=%d, #objs=%d %s%s\n",
            endTime-interp->startTime, interp->funcSelfTime, interp->cmdSelfTime, interp->funcCallCnt, interp->cmdCallCnt,
            coverage, interp->dbPtr->valueAllocCnt,  interp->dbPtr->objAllocCnt,
            interp->parent?" ::":"", (interp->parent&&interp->name?interp->name:""));
        Jsi_Puts(interp, jsi_Stderr, Jsi_DSValue(&dStr), -1);
        Jsi_DSFree(&dStr);
    }
    if (isMainInt)
        Jsi_HashDelete(interp->lexkeyTbl);
    Jsi_HashDelete(interp->protoTbl);
    if (interp->subthread)
        jsiIntData.mainInterp->threadCnt--;
    if (interp->subthread && interp->strKeyTbl == jsiIntData.mainInterp->strKeyTbl)
        jsiIntData.mainInterp->threadShrCnt--;
    if (!jsiIntData.mainInterp->threadShrCnt)
#ifdef JSI_USE_MANY_STRKEY
        jsiIntData.mainInterp->strKeyTbl->v.tree->opts.lockTreeProc = NULL;
#else
        jsiIntData.mainInterp->strKeyTbl->v.hash->opts.lockHashProc = NULL;
#endif
    //Jsi_ValueMakeUndef(interp, &interp->ret);
    Jsi_HashDelete(interp->thisTbl);
    Jsi_HashDelete(interp->varTbl);
    Jsi_HashDelete(interp->genValueTbl);
    Jsi_HashDelete(interp->genObjTbl);
    Jsi_HashDelete(interp->aliasHash);
    if (interp->staticFuncsTbl)
        Jsi_HashDelete(interp->staticFuncsTbl);
    if (interp->breakpointHash)
        Jsi_HashDelete(interp->breakpointHash);
    if (interp->preserveTbl->numEntries!=0)
        Jsi_LogBug("Preserves unbalanced");
    Jsi_HashDelete(interp->preserveTbl);
    if (interp->curDir)
        Jsi_Free(interp->curDir);
    if (isMainInt) {
        jsi_InitFilesys(interp, mainFlag);
    }
#ifndef JSI_OMIT_VFS
    jsi_InitVfs(interp, 1);
#endif
#ifndef JSI_OMIT_CDATA
        jsi_InitCData(interp, mainFlag);
#endif
#if JSI__MYSQL==1
        Jsi_InitMySql(interp, mainFlag);
#endif
    if (interp->Mutex)
        Jsi_MutexDelete(interp, interp->Mutex);
    if (interp->QMutex) {
        Jsi_MutexDelete(interp, interp->QMutex);
        Jsi_DSFree(&interp->interpEvalQ);
        Jsi_DSFree(&interp->interpMsgQ);
    }
    if (interp->nullFuncArg)
        Jsi_DecrRefCount(interp, interp->nullFuncArg);
    if (interp->NullValue)
        Jsi_DecrRefCount(interp, interp->NullValue);
    if (interp->Function_prototype_prototype) {
        if (interp->Function_prototype_prototype->refCnt>1)
            Jsi_DecrRefCount(interp, interp->Function_prototype_prototype);
        Jsi_DecrRefCount(interp, interp->Function_prototype_prototype);
    }
    if (interp->Object_prototype) {
        Jsi_DecrRefCount(interp, interp->Object_prototype);
    }
    Jsi_HashDelete(interp->regexpTbl);
    Jsi_OptionsFree(interp, InterpOptions, interp, 0);
    Jsi_HashDelete(interp->userdataTbl);
    Jsi_HashDelete(interp->eventTbl);
    if (interp->inopts)
        Jsi_DecrRefCount(interp, interp->inopts);
    if (interp->safeWriteDirs)
        Jsi_DecrRefCount(interp, interp->safeWriteDirs);
    if (interp->safeReadDirs)
        Jsi_DecrRefCount(interp, interp->safeReadDirs);
    if (interp->pkgDirs)
        Jsi_DecrRefCount(interp, interp->pkgDirs);
    for (i=0; interp->cleanObjs[i]; i++) {
        interp->cleanObjs[i]->tree->opts.freeHashProc = 0;
        Jsi_ObjFree(interp, interp->cleanObjs[i]);
    }
    Jsi_HashDelete(interp->bindTbl);
    for (i = 0; i <= interp->cur_scope; i++)
        jsi_ScopeStrsFree(interp, interp->scopes[i]);
#if JSI__ZVFS==1
    Jsi_InitZvfs(interp, mainFlag);
#endif
    if (!interp->parent)
        Jsi_HashDelete(interp->loadTbl);
    if (interp->packageHash)
        Jsi_HashDelete(interp->packageHash);
    Jsi_HashDelete(interp->assocTbl);
#ifdef JSI_MEM_DEBUG
    jsi_DebugDumpValues(interp);
#endif
    if (isMainInt || interp->strKeyTbl != jsiIntData.mainInterp->strKeyTbl)
        Jsi_MapDelete(interp->strKeyTbl);
    if (isMainInt)
        jsiIntData.mainInterp = NULL;

    _JSI_MEMCLEAR(interp);
    jsiIntData.delInterp = NULL;
    Jsi_Free(interp);
    return JSI_OK;
}

void Jsi_InterpDelete(Jsi_Interp* interp)
{
    if (interp->deleting || interp->level > 0 || !interp->onDeleteTbl)
        return;
    interp->deleting = 1;
    Jsi_HashDelete(interp->onDeleteTbl);
    interp->onDeleteTbl = NULL;
    Jsi_EventuallyFree(interp, interp, jsiInterpDelete);
}

typedef struct {
    void *data;
    Jsi_Interp *interp;
    int refCnt;
    Jsi_DeleteProc* proc;
} PreserveData;

void Jsi_Preserve(Jsi_Interp* interp, void *data) {
    bool isNew;
    PreserveData *ptr;
    Jsi_HashEntry *hPtr = Jsi_HashEntryNew(interp->preserveTbl, data, &isNew);
    assert(hPtr);
    if (!isNew) {
        ptr = (PreserveData*)Jsi_HashValueGet(hPtr);
        if (ptr) {
            assert(interp == ptr->interp);
            ptr->refCnt++;
        }
    } else {
        ptr = (PreserveData*)Jsi_Calloc(1,sizeof(*ptr));
        Jsi_HashValueSet(hPtr, ptr);
        ptr->interp = interp;
        ptr->data = data;
        ptr->refCnt = 1;
    }
}

void Jsi_Release(Jsi_Interp* interp, void *data) {
    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->preserveTbl, data);
    if (!hPtr) return;
    PreserveData *ptr = (PreserveData*)Jsi_HashValueGet(hPtr);
    if (!ptr) return;
    assert(ptr->interp == interp);
    if (--ptr->refCnt > 0) return;
    if (ptr->proc)
        (*ptr->proc)(interp, data);
    Jsi_Free(ptr);
    Jsi_HashEntryDelete(hPtr);
}

void Jsi_EventuallyFree(Jsi_Interp* interp, void *data, Jsi_DeleteProc* proc) {
    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->preserveTbl, data);
    if (!hPtr) {
        (*proc)(interp, data);
        return;
    }
    PreserveData *ptr = (PreserveData*)Jsi_HashValueGet(hPtr);
    assert(ptr && ptr->interp == interp);
    JSI_NOWARN(ptr);
    Jsi_HashEntryDelete(hPtr);
}

void Jsi_InterpOnDelete(Jsi_Interp *interp, Jsi_DeleteProc *freeProc, void *ptr)
{
    if (freeProc)
        Jsi_HashSet(interp->onDeleteTbl, ptr, (void*)freeProc);
    else {
        Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->onDeleteTbl, ptr);
        if (hPtr)
            Jsi_HashEntryDelete(hPtr);
    }
}

static void interpObjErase(InterpObj *fo)
{
    SIGASSERTV(fo,INTERPOBJ);
    if (fo->subinterp) {
        Jsi_Interp *interp = fo->subinterp;
        fo->subinterp = NULL;
        Jsi_InterpDelete(interp);
        /*fclose(fo->fp);
        Jsi_Free(fo->interpname);
        Jsi_Free(fo->mode);*/
    }
    fo->subinterp = NULL;
}

static Jsi_RC interpObjFree(Jsi_Interp *interp, void *data)
{
    InterpObj *fo = (InterpObj *)data;
    SIGASSERT(fo,INTERPOBJ);
    if (fo->deleting) return JSI_OK;
    fo->deleting = 1;
    interpObjErase(fo);
    Jsi_Free(fo);
    return JSI_OK;
}

static bool interpObjIsTrue(void *data)
{
    InterpObj *fo = (InterpObj *)data;
    SIGASSERT(fo,INTERPOBJ);
    if (!fo->subinterp) return 0;
    else return 1;
}

static bool interpObjEqual(void *data1, void *data2)
{
    return (data1 == data2);
}

/* TODO: possibly support async func-callback.  Also for call/send. */
static Jsi_RC InterpEvalCmd_(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, int flags)
{
    int isFile = flags&2;
    int isUplevel = flags&1;
    int lev = 0;
    bool async = 0;
    Jsi_RC rc = JSI_OK;
    int isthrd;
    Jsi_Interp *sinterp = interp;
    Jsi_ValueMakeUndef(interp, ret);
    InterpObj *udf = (InterpObj *)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (udf)
        sinterp = udf->subinterp;
    if (Jsi_InterpGone(interp) || Jsi_InterpGone(sinterp))
        return Jsi_LogError("Sub-interp gone");
    isthrd = (interp->threadId != sinterp->threadId);
    jsi_Frame *f = sinterp->framePtr;
    Jsi_Value *nw = Jsi_ValueArrayIndex(interp, args, 1);
    if (!isUplevel) {
        if (nw && Jsi_GetBoolFromValue(interp, nw, &async))
            return JSI_ERROR;
    } else {
        if (isthrd)
            return Jsi_LogError("can not use uplevel() with threaded interp");
        Jsi_Number nlev = sinterp->framePtr->level;
        if (nw && Jsi_GetNumberFromValue(interp, nw, &nlev)!=JSI_OK)
            return Jsi_LogError("expected number");
        lev = (int)nlev;
        if (lev <= 0)
            lev = f->level+lev;
        if (lev <= 0 || lev > f->level)
            return Jsi_LogError("level %d not between 1 and %d", (int)nlev, f->level);
    }

    char *cp = Jsi_ValueArrayIndexToStr(interp, args, 0, NULL);
    if (cp==NULL || *cp == 0)
        return JSI_OK;
    if (async && isthrd) {
        /* Post to thread event in sub-interps queue. TODO: could just use event like below... */
        if (Jsi_MutexLock(interp, sinterp->QMutex) != JSI_OK)
            return JSI_ERROR;
        Jsi_DSAppend(&sinterp->interpEvalQ, Jsi_Strlen(Jsi_DSValue(&sinterp->interpEvalQ))?";":"", cp, NULL);
        Jsi_MutexUnlock(interp, sinterp->QMutex);
        return JSI_OK;
    }
    if (interp->subOpts.mutexUnlock) Jsi_MutexUnlock(interp, interp->Mutex);
    if (!isthrd) {
        int ostrict = sinterp->strict;
        sinterp->strict = 0;
        sinterp->level++;
        if (interp->framePtr->tryDepth)
            sinterp->framePtr->tryDepth++;
        if (isFile) {
            int sflags = 0;
            if (!sinterp->includeCnt) {
                sflags = JSI_EVAL_ARGV0|JSI_EVAL_AUTOINDEX;
                sinterp->isMain = 1;
            }
            if (sinterp->debugOpts.debugCallback && !sinterp->includeCnt)  // TODO: safe debugging can't use "source"
                // TODO: we do this in debugger, even though it is illegal for interps to share objects.
                sinterp->autoFiles = Jsi_ValueDup(sinterp, interp->autoFiles);
            sinterp->includeCnt++;
            rc = Jsi_EvalFile(sinterp, Jsi_ValueArrayIndex(interp, args, 0), sflags);
        } else if (isUplevel == 0 || lev <= 1)
            rc = (Jsi_EvalString(sinterp, cp, 0) == 0 ? JSI_OK : JSI_ERROR);
        else {
            rc = (jsi_evalStrFile(sinterp, NULL, cp, 0, lev) == 0 ? JSI_OK : JSI_ERROR);
        }
        sinterp->strict = ostrict;
        if (interp->framePtr->tryDepth) {
            sinterp->framePtr->tryDepth--;
            if (rc != JSI_OK && interp != sinterp) {
                Jsi_Strcpy(interp->errMsgBuf, sinterp->errMsgBuf);
                interp->errLine = sinterp->errLine;
                interp->errFile = sinterp->errFile;
                sinterp->errMsgBuf[0] = 0;
            }
        }
        sinterp->level--;
    } else {
        if (Jsi_MutexLock(interp, sinterp->QMutex) != JSI_OK)
            return JSI_ERROR;
        InterpStrEvent *se, *s = (InterpStrEvent *)Jsi_Calloc(1, sizeof(*s));
        SIGINIT(s,INTERPSTREVENT);
        s->isExec = 1;
        s->tryDepth = interp->framePtr->tryDepth;
        Jsi_DSInit(&s->data);
        Jsi_DSAppend(&s->data, cp, NULL);
        Jsi_DSInit(&s->func);
        //s->mutex = Jsi_MutexNew(interp, -1, JSI_MUTEX_RECURSIVE);
        //Jsi_MutexLock(s->mutex);
        se = sinterp->interpStrEvents;
        if (!se)
            sinterp->interpStrEvents = s;
        else {
            while (se->next)
                se = se->next;
            se->next = s;
        }

        Jsi_MutexUnlock(interp, sinterp->QMutex);
        while (s->isExec)      /* Wait until done. TODO: timeout??? */
            Jsi_Sleep(interp, 1);
        rc = (s->rc == 0 ? JSI_OK : JSI_ERROR);
        if (rc != JSI_OK)
            Jsi_LogError("eval failed: %s", Jsi_DSValue(&s->data));
        Jsi_DSFree(&s->func);
        Jsi_DSFree(&s->data);
        Jsi_Free(s);
    }

    if (interp->subOpts.mutexUnlock && Jsi_MutexLock(interp, interp->Mutex) != JSI_OK) {
        return JSI_ERROR;
    }

    if (Jsi_InterpGone(sinterp))
    {
        /* TODO: perhaps exit() be able to delete. */
        //Jsi_InterpDelete(sinterp);
        return JSI_OK;
    }
    /*if (rc != JSI_OK && !async)
        return rc;*/
    if (sinterp->retValue->vt != JSI_VT_UNDEF) {
        if (sinterp == interp)
            Jsi_ValueCopy(interp, *ret, sinterp->retValue);
        else
            Jsi_CleanValue(sinterp, interp, sinterp->retValue, ret);
    }
    return rc;
}

Jsi_Interp *jsi_DoExit(Jsi_Interp *interp, int rc)
{
    if (rc<0 || rc>127) rc = 127;
    if (!interp || !interp->opts.no_exit) {
        if (rc) {
            Jsi_Flush(interp, jsi_Stdout);
            Jsi_Flush(interp, jsi_Stderr);
        }
        exit(rc);
    }
    fprintf(stderr, "ignoring attempted exit: may cause a crash\n");
    if (interp) interp->deleting = 1;
    return NULL;
}

#define FN_interpeval JSI_INFO("\
When the 'async' option is used on a threaded interp, the script is queued as an Event.")

static Jsi_RC InterpEvalCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return InterpEvalCmd_(interp, args, _this, ret, funcPtr, 0);
}


#define FN_interpuplevel JSI_INFO("\
The level argument is as returned by Info.level().  Not supported with threads.")
static Jsi_RC InterpUplevelCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return InterpEvalCmd_(interp, args, _this, ret, funcPtr, 1);
}

static Jsi_RC InterpSourceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return InterpEvalCmd_(interp, args, _this, ret, funcPtr, 2);
}

static Jsi_RC InterpValueCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    InterpObj *udf = (InterpObj *)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_Interp *sinterp = interp;
    if (udf) {
        if (!udf->subinterp)
        return Jsi_LogError("Sub-interp gone");
        sinterp = udf->subinterp;
        if (interp->threadId != udf->subinterp->threadId)
            return Jsi_LogError("value not supported with threads");
    }
    Jsi_Value *nw = Jsi_ValueArrayIndex(interp, args, 1);
    jsi_Frame *f = sinterp->framePtr;
    Jsi_Number nlev = sinterp->framePtr->level;
    if (nw && Jsi_GetNumberFromValue(interp, nw, &nlev))
        return JSI_ERROR;
    int lev = (int)nlev;
    if (lev <= 0)
        lev = f->level+lev;
    if (lev <= 0 || lev > f->level)
        return Jsi_LogError("level %d not between 1 and %d", (int)nlev, f->level);
    while (f->level != lev  && f->parent)
        f = f->parent;

    const char* arg = Jsi_ValueArrayIndexToStr(interp, args, 0, NULL);
    Jsi_Value *val = NULL;
    if (arg) {
        if (f == sinterp->framePtr)
            val = Jsi_NameLookup(sinterp, arg);
        else {
            jsi_Frame *of = sinterp->framePtr;
            sinterp->framePtr = f;
            val = Jsi_NameLookup(sinterp, arg);
            sinterp->framePtr = of;
        }
    }
    if (!val)
        return Jsi_LogError("unknown var: %s", arg);
    if (sinterp == interp) {
        Jsi_ValueCopy(interp, *ret, val);
        return JSI_OK;
    }
    Jsi_CleanValue(sinterp, interp, val, ret);
    return JSI_OK;
}

static Jsi_RC InterpInfoCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    InterpObj *udf = (InterpObj *)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_Interp *subinterp = interp;
    if (udf) {
        if (!udf->subinterp)
            return Jsi_LogError("Sub-interp gone");
        subinterp = udf->subinterp;
    }
    return jsi_InterpInfo(subinterp, args, _this, ret, funcPtr);
}

Jsi_RC jsi_InterpInfo(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Interp *sinterp = interp;
    Jsi_DString dStr = {}, cStr = {};
    char tbuf[1024];
    Jsi_Value *v = Jsi_ValueArrayIndex(interp, args, 0);
    tbuf[0] = 0;
    if (v) {
        InterpObj *udf = NULL;
        if (v && v->vt == JSI_VT_OBJECT && v->d.obj->ot == JSI_OT_USEROBJ)
            udf = (InterpObj *)v->d.obj->d.uobj->data;
        if (udf && udf->subinterp) {
            SIGASSERT(udf, INTERPOBJ);
            sinterp = udf->subinterp;
        } else
            return Jsi_LogError("unknown interp");
    }
    if (sinterp->subthread)
        snprintf(tbuf, sizeof(tbuf), ", thread:{errorCnt:%u, evalCnt:%u, msgCnt:%u }",
            sinterp->threadErrCnt, sinterp->threadEvalCnt, sinterp->threadMsgCnt );
    const char *funcstr = sinterp->framePtr->funcName;
    if (!funcstr)
        funcstr = "";
    int curLine = (sinterp->curIp?sinterp->curIp->Line:0);
    Jsi_DSPrintf(&dStr, "{curLevel:%d, curLine:%d, curFile:\"%s\", curFunc:\"%s\", hasExited:%d, "
        "opCnt:%d, isSafe:%s, depth:%d, codeCacheHits: %d, typeMismatchCnt:%d, "
        "funcCallCnt:%d, cmdCallCnt:%d, includeCnt:%d, includeDepth:%d, pkgReqDepth:%d, "
        "cwd:\"%s\", lockTimeout: %d, name, \"%s\" %s%s};",
        sinterp->level, curLine, jsi_GetCurFile(sinterp), funcstr?funcstr:"",
        sinterp->exited, sinterp->opCnt, sinterp->isSafe?"true":"false",
        sinterp->interpDepth, sinterp->codeCacheHit, sinterp->typeMismatchCnt,
        sinterp->funcCallCnt, sinterp->cmdCallCnt, sinterp->includeCnt, sinterp->includeDepth, sinterp->pkgReqDepth,
        (sinterp->curDir?sinterp->curDir:Jsi_GetCwd(sinterp,&cStr)),
        sinterp->lockTimeout, sinterp->name?sinterp->name:"", tbuf[0]?",":"", tbuf);
    Jsi_RC rc = Jsi_JSONParse(interp, Jsi_DSValue(&dStr), ret, 0);
    if (rc != JSI_OK)
        puts(Jsi_DSValue(&dStr));
    Jsi_DSFree(&dStr);
    Jsi_DSFree(&cStr);
    return rc;
}

static Jsi_RC SubInterpEvalCallback(Jsi_Interp *interp, void* data)
{
    Jsi_RC rc = JSI_OK;
    Jsi_DString dStr = {};
    if (!interp->recvCallback)
        return JSI_ERROR;
    if (Jsi_MutexLock(interp, interp->QMutex) != JSI_OK)
        return JSI_ERROR;
    char *cp = Jsi_DSValue(&interp->interpEvalQ);
    if (*cp) {
        Jsi_DSAppend(&dStr, cp, NULL);
        Jsi_DSSetLength(&interp->interpEvalQ, 0);
        interp->threadEvalCnt++;
        Jsi_MutexUnlock(interp, interp->QMutex);
        if (Jsi_EvalString(interp, Jsi_DSValue(&dStr), 0) != JSI_OK)
            rc = JSI_ERROR;
        Jsi_DSSetLength(&dStr, 0);
        if (Jsi_MutexLock(interp, interp->QMutex) != JSI_OK)
            return JSI_ERROR;
    }
    cp = Jsi_DSValue(&interp->interpMsgQ);
    if (*cp) {
        Jsi_DSAppend(&dStr, cp, NULL);
        Jsi_DSSetLength(&interp->interpMsgQ, 0);
        interp->threadMsgCnt++;
        Jsi_MutexUnlock(interp, interp->QMutex);
        if (Jsi_FunctionInvokeJSON(interp, interp->recvCallback, Jsi_DSValue(&dStr), NULL) != JSI_OK)
            rc = JSI_ERROR;
        Jsi_DSSetLength(&interp->interpMsgQ, 0);
    } else
        Jsi_MutexUnlock(interp, interp->QMutex);
    Jsi_DSFree(&dStr);
    if (Jsi_MutexLock(interp, interp->QMutex) != JSI_OK)
        return JSI_ERROR;

    /* Process subevents. */
    InterpStrEvent *oldse, *se = interp->interpStrEvents;
    Jsi_MutexUnlock(interp, interp->QMutex);
    while (se) {
        oldse = se;
        int isExec = se->isExec;
        if (isExec) {
            if (se->tryDepth)
                interp->framePtr->tryDepth++;
            se->rc = Jsi_EvalString(interp, Jsi_DSValue(&se->data), 0);
            Jsi_DSSetLength(&se->data, 0);
            if (se->rc != JSI_OK && se->tryDepth) {
                Jsi_DSAppend(&se->data, interp->errMsgBuf, NULL);
                se->errLine = interp->errLine;
                se->errFile = interp->errFile;
            } else {
                Jsi_ValueGetDString(interp, interp->retValue, &se->data, JSI_OUTPUT_JSON);
            }
            if (se->tryDepth)
                interp->framePtr->tryDepth--;

        /* Otherwise, async calls. */
        } else if (se->objData) {
            if (JSI_OK != Jsi_CommandInvoke(interp, Jsi_DSValue(&se->func), se->objData, NULL))
                rc = JSI_ERROR;
        } else {
            if (JSI_OK != Jsi_CommandInvokeJSON(interp, Jsi_DSValue(&se->func), Jsi_DSValue(&se->data), NULL))
                rc = JSI_ERROR;
        }
        if (!isExec) {
            Jsi_DSFree(&se->func);
            Jsi_DSFree(&se->data);
        }
        if (Jsi_MutexLock(interp, interp->QMutex) != JSI_OK)
            return JSI_ERROR;
        interp->interpStrEvents = se->next;
        if (!isExec) Jsi_Free(se);
        se = interp->interpStrEvents;
        Jsi_MutexUnlock(interp, interp->QMutex);
        if (isExec)
            oldse->isExec = 0;
    }

    return rc;
}


static Jsi_RC ThreadEvalCallback(Jsi_Interp *interp, void* data) {
    Jsi_RC rc;

    if ((rc=SubInterpEvalCallback(interp, data)) != JSI_OK)
        interp->threadErrCnt++;
    return rc;
}

/* Create an event handler in interp to handle call/eval/send asyncronously via 'Sys.update()'. */
void jsi_AddEventHandler(Jsi_Interp *interp)
{
    Jsi_Event *ev;
    while (!interp->EventHdlId) { /* Find an empty event slot. */
        bool isNew;
        uintptr_t id = ++interp->eventIdx;
        Jsi_HashEntry *hPtr = Jsi_HashEntryNew(interp->eventTbl, (void*)id, &isNew);
        if (!isNew)
            continue;
        ev = (Jsi_Event*)Jsi_Calloc(1, sizeof(*ev));
        SIGINIT(ev,EVENT);
        ev->id = id;
        ev->handler = ThreadEvalCallback;
        ev->hPtr = hPtr;
        ev->evType = JSI_EVENT_ALWAYS;
        Jsi_HashValueSet(hPtr, ev);
        interp->EventHdlId = id;
    }
}

#define FN_call JSI_INFO("\
Invoke function in sub-interp with arguments.\n\
Since interps are not allowed to share objects, \
data is automatically cleansed by encoding/decoding to/from JSON if required.\n\
Unless an 'async' parameter of true is given, we wait until the sub-interp is idle, \
make the call, and return the result.  Otherwise the call is acyncronous.")

static Jsi_RC InterpCallCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    InterpObj *udf = (InterpObj *)Jsi_UserObjGetData(interp, _this, funcPtr);
    int isthrd;
    Jsi_Interp *sinterp;
    if (udf)
        sinterp = udf->subinterp;
    else
        return Jsi_LogError("Apply Interp.call in a non-subinterp");
    if (Jsi_InterpGone(sinterp))
        return Jsi_LogError("Sub-interp gone");
    isthrd = (interp->threadId != sinterp->threadId);

    Jsi_Value *func = NULL;
    char *fname = NULL;
    func = Jsi_ValueArrayIndex(interp, args, 0);
    fname = Jsi_ValueString(interp, func, NULL);
    if (!fname)
        return Jsi_LogError("function name must be a string");
    if (Jsi_MutexLock(interp, sinterp->Mutex) != JSI_OK)
        return JSI_ERROR;
    Jsi_Value *namLU = Jsi_NameLookup(sinterp, fname);
    Jsi_MutexUnlock(interp, sinterp->Mutex);
    if (namLU == NULL)
        return Jsi_LogError("unknown function: %s", fname);

    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 1);
    char *cp = Jsi_ValueString(interp, arg, NULL);

    if (cp == NULL && !Jsi_ValueIsArray(interp, arg))
        return Jsi_LogError("expected string or array");

    Jsi_Value *vasync = Jsi_ValueArrayIndex(interp, args, 2);
    Jsi_RC rc = JSI_OK;
    bool async = 0;
    if (vasync && Jsi_GetBoolFromValue(interp, vasync, &async))
        return JSI_ERROR;

    if (!async) {
        Jsi_DString dStr = {};
        if (cp == NULL)
            cp = (char*)Jsi_ValueGetDString(interp, arg, &dStr, JSI_OUTPUT_JSON);
        if (interp->subOpts.mutexUnlock) Jsi_MutexUnlock(interp, interp->Mutex);
        if (Jsi_MutexLock(interp, sinterp->Mutex) != JSI_OK) {
            if (interp->subOpts.mutexUnlock) Jsi_MutexLock(interp, interp->Mutex);
            return JSI_ERROR;
        }
        Jsi_Value *srPtr = Jsi_ValueNew1(interp);
        /* TODO: call from this interp may not be safe if threaded.
         * Could instead use async code below then wait for unlock on se->mutex. */
        rc = Jsi_CommandInvokeJSON(sinterp, fname, cp, &srPtr);
        Jsi_DSSetLength(&dStr, 0);
        ConvertReturn(interp, ret, sinterp, &srPtr);
        //Jsi_ValueCopy(interp, *ret, srPtr);
        Jsi_DecrRefCount(interp, srPtr);
        Jsi_DSFree(&dStr);
        Jsi_MutexUnlock(interp, sinterp->Mutex);
        if (interp->subOpts.mutexUnlock && Jsi_MutexLock(interp, interp->Mutex) != JSI_OK) {
            Jsi_LogBug("mutex re-get failed");
            return JSI_ERROR;
        }
        return rc;
    }

    /* Post to thread event in sub-interps queue. */
    if (Jsi_MutexLock(interp, sinterp->QMutex) != JSI_OK)
        return JSI_ERROR;

    /* Is an async call. */
    InterpStrEvent *se, *s = (InterpStrEvent *)Jsi_Calloc(1, sizeof(*s));
    // TODO: is s->data inited?
    Jsi_DSInit(&s->data);
    if (!cp) {
        Jsi_ValueGetDString(interp, arg, &s->data, JSI_OUTPUT_JSON);
    }
    Jsi_DSSetLength(&s->data, 0);
    Jsi_DSAppend(&s->data, cp, NULL);
    Jsi_DSInit(&s->func);
    Jsi_DSAppend(&s->func, fname, NULL);
    se = sinterp->interpStrEvents;
    if (!se)
        sinterp->interpStrEvents = s;
    else {
        while (se->next)
            se = se->next;
        se->next = s;
    }

    Jsi_MutexUnlock(interp, sinterp->QMutex);
    if (!isthrd) {
        if (SubInterpEvalCallback(sinterp, NULL) != JSI_OK)
            sinterp->threadErrCnt++;
    }
    return JSI_OK;
}

Jsi_RC Jsi_Mount( Jsi_Interp *interp, Jsi_Value *archive, Jsi_Value *mount, Jsi_Value **ret)
{
#if JSI__ZVFS==1
    return Zvfs_Mount(interp, archive, mount, ret);
#else
    return JSI_ERROR;
#endif
}

/* Looks in dir for autoload.jsi or lib/autoload.jsi to add to autoFiles list. */
int Jsi_AddAutoFiles(Jsi_Interp *interp, const char *dir) {
    Jsi_DString dStr = {};
    Jsi_StatBuf stat;
    int i, cnt = 0;
    for (i=0; i<2; i++) {
        Jsi_DSAppend(&dStr, dir, (i==0?"/lib":""),"/autoload.jsi", NULL);
        Jsi_Value *v = Jsi_ValueNewStringKey(interp, Jsi_DSValue(&dStr));
        if (Jsi_Stat(interp, v, &stat) != 0)
            Jsi_ValueFree(interp, v);
        else {
            if (!interp->autoFiles) {
                interp->autoFiles = Jsi_ValueNewArray(interp, 0, 0);
                Jsi_IncrRefCount(interp, interp->autoFiles);
            }
            Jsi_ObjArrayAdd(interp, interp->autoFiles->d.obj, v);
            cnt++;
            interp->autoLoaded = 0;
        }
        Jsi_DSSetLength(&dStr, 0);
    }
    Jsi_DSFree(&dStr);
    return cnt;
}

Jsi_RC Jsi_EvalZip(Jsi_Interp *interp, const char *exeFile, const char *mntDir, int *jsFound)
{
#if JSI__ZVFS==1
    Jsi_StatBuf stat;
    Jsi_Value *vinit, *linit, *vmnt = NULL;
    Jsi_Value *vexe = Jsi_ValueNewStringKey(interp, exeFile);
    Jsi_Value *ret = NULL;
    Jsi_RC rc;
    const char *omntDir = mntDir;
    int pass = 0;
    Jsi_IncrRefCount(interp, vexe);
    Jsi_HashSet(interp->genValueTbl, vexe, vexe);
    if (jsFound)
        *jsFound = 0;
    if (!mntDir)
        ret = Jsi_ValueNew(interp);
    else {
        vmnt = Jsi_ValueNewStringKey(interp, mntDir);
        Jsi_IncrRefCount(interp, vmnt);
        Jsi_HashSet(interp->genValueTbl, vmnt, vmnt);
    }
    bool osafe = interp->isSafe;
    if (interp->startSafe)
        interp->isSafe = 0;
    rc =Jsi_Mount(interp, vexe, vmnt, &ret);
    interp->isSafe = osafe;
    if (rc != JSI_OK)
        return rc;
    Jsi_DString dStr, bStr;
    Jsi_DSInit(&dStr);
    Jsi_DSInit(&bStr);
    if (!mntDir) {
        mntDir = Jsi_KeyAdd(interp, Jsi_ValueString(interp, ret, NULL));
        Jsi_DecrRefCount(interp, ret);
    }
dochk:
    Jsi_DSAppend(&dStr, mntDir, "/main.jsi", NULL);
    if (interp->pkgDirs)
        Jsi_ValueArrayPush(interp, interp->pkgDirs, Jsi_ValueNewStringKey(interp, mntDir));
    else {
        interp->pkgDirs = Jsi_ValueNewArray(interp, &mntDir, 1);
        Jsi_IncrRefCount(interp, interp->pkgDirs);
    }
    vinit = Jsi_ValueNewStringKey(interp,  Jsi_DSValue(&dStr));
    Jsi_IncrRefCount(interp, vinit);
    Jsi_HashSet(interp->genValueTbl, vinit, vinit);
    Jsi_DSFree(&dStr);
    Jsi_DSAppend(&dStr, mntDir, "/lib", NULL);
    const char *str = Jsi_DSValue(&dStr);
    linit = Jsi_ValueNewStringKey(interp, str);
    Jsi_IncrRefCount(interp, linit);
    if (Jsi_Stat(interp, linit, &stat) == 0)
        Jsi_ValueArrayPush(interp, interp->pkgDirs, linit);
    Jsi_DecrRefCount(interp, linit);
    Jsi_DSFree(&dStr);
    Jsi_DSFree(&bStr);
    if (Jsi_Stat(interp, vinit, &stat) == 0) {
        if (jsFound)
            *jsFound |= JSI_ZIP_MAIN;
        interp->execZip = vexe;
        return Jsi_EvalFile(interp, vinit, JSI_EVAL_ARGV0|JSI_EVAL_AUTOINDEX);
    }
    if (Jsi_AddAutoFiles(interp, mntDir) && omntDir)
        *jsFound = JSI_ZIP_INDEX;
    if (!pass++) {
        str = Jsi_Strrchr(exeFile, '/');
        if (str) str++;
        else str = exeFile;
        char *bn = Jsi_DSAppend(&bStr, mntDir, "/", str, NULL);
        bn = Jsi_Strrchr(bn, '.');
        if (bn) *bn = 0;
        mntDir = Jsi_DSValue(&bStr);
        linit = Jsi_ValueNewStringKey(interp, mntDir);
        Jsi_IncrRefCount(interp, linit);
        int bsi = Jsi_Stat(interp, linit, &stat);
        Jsi_DecrRefCount(interp, linit);
        if (bsi == 0)
            goto dochk;
        Jsi_DSFree(&bStr);
    }
#endif
    return JSI_OK;
}

#define FN_send JSI_INFO("\
Add messages to queue to be processed by the 'recvCallback' interp option.")

static Jsi_RC InterpSendCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    //return SendCmd(interp, args, _this, ret, funcPtr, 1);
    InterpObj *udf = (InterpObj *)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_Interp *sinterp = NULL;
    int isthrd;
    if (udf) {
        sinterp = udf->subinterp;
    } else {
        sinterp = interp->parent;
        if (!sinterp)
            return Jsi_LogError("Apply Interp.send in a non-subinterp");
    }

    if (Jsi_InterpGone(sinterp))
        return Jsi_LogError("Sub-interp gone");
    isthrd = (interp->threadId != sinterp->threadId);
    if (!sinterp->recvCallback)
        return Jsi_LogError("interp was not created with 'recvCallback' option");

    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);
    char *cp = Jsi_ValueString(interp, arg, NULL);

    /* Post to thread event in sub-interps queue. */
    if (Jsi_MutexLock(interp, sinterp->QMutex) != JSI_OK)
        return JSI_ERROR;

    int slen = Jsi_Strlen(Jsi_DSValue(&sinterp->interpMsgQ));
    Jsi_DString eStr = {};
    if (!cp) {
        cp = (char*)Jsi_ValueGetDString(interp, arg, &eStr, JSI_OUTPUT_JSON);
    }
    if (!slen)
        Jsi_DSAppend(&sinterp->interpMsgQ, "[", cp, "]", NULL);
    else {
        Jsi_DSSetLength(&sinterp->interpMsgQ, slen-1);
        Jsi_DSAppend(&sinterp->interpMsgQ, ", ", cp, "]", NULL);
    }
    // if (interp->parent) printf("SENDING: %s\n", Jsi_DSValue(&sinterp->interpMsgQ));
    Jsi_DSFree(&eStr);

    Jsi_MutexUnlock(interp, sinterp->QMutex);
    if (!isthrd) {
        if (SubInterpEvalCallback(sinterp, NULL) != JSI_OK)
            sinterp->threadErrCnt++;
    }
    return JSI_OK;

}

#ifndef JSI_OMIT_THREADS

#ifdef __WIN32
#define JSITHREADRET void
#else
#define JSITHREADRET void*
#endif

static JSITHREADRET NewInterpThread(void* iPtr)
{
    int rc = JSI_OK;
    InterpObj *udf = (InterpObj *)iPtr;
    Jsi_Interp *interp = udf->subinterp;
    interp->threadId = Jsi_CurrentThread();
    if (interp->scriptStr)
        rc = Jsi_EvalString(interp, interp->scriptStr, 0);
    else if (interp->scriptFile) {
        if (!interp->debugOpts.debugCallback) // Debug will use Interp.source() instead.
            Jsi_EvalFile(interp, interp->scriptFile, 0);
    } else {
        jsi_AddEventHandler(interp);
        int mrc = Jsi_MutexLock(interp, interp->Mutex);
        while (mrc == JSI_OK) {
            if (Jsi_EventProcess(interp, -1)<0)
                break;
            Jsi_Sleep(interp, 1);
        }
    }
    if (rc != JSI_OK) {
        Jsi_LogError("eval failure");
        interp->threadErrCnt++;
    }
    interpObjErase(udf);
#ifndef __WIN32
    /* TODO: should we wait/notify parent??? */
    pthread_detach(pthread_self());
    return NULL;
#endif
}
#endif

static Jsi_RC InterpConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
static Jsi_RC InterpConfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);

static Jsi_CmdSpec interpCmds[] = {
    { "Interp", InterpConstructor,0,  1, "options:object=void", .help="Create a new interp", .retType=(uint)JSI_TT_USEROBJ, .flags=JSI_CMD_IS_CONSTRUCTOR, .info=0, .opts=InterpOptions},
    { "alias",  InterpAliasCmd,   0,  3, "name:string=void, func:function|null=void, args:array|null=void",.help="Set/get global alias bindings for command in an interp", .retType=(uint)JSI_TT_ANY, .flags=0, .info=FN_intalias },
    { "call",   InterpCallCmd,    2,  3, "funcName:string, args:string|array, async:boolean=false", .help="Call named function in subinterp", .retType=(uint)JSI_TT_ANY, .flags=0, .info=FN_call },
    { "conf",   InterpConfCmd,    0,  1, "options:string|object=void",.help="Configure option(s)" , .retType=(uint)JSI_TT_ANY,.flags=0,.info=0,.opts=InterpOptions},
    { "eval",   InterpEvalCmd,    1,  2, "js:string, async:boolean=false", .help="Interpret script within sub-interp", .retType=(uint)JSI_TT_ANY, .flags=0, .info=FN_interpeval },
    { "info",   InterpInfoCmd,    0,  0, "", .help="Returns internal statistics about interp", .retType=(uint)JSI_TT_OBJECT },
    { "send",   InterpSendCmd,    1,  1, "msg:any", .help="Enqueue message onto a sub-interps recvCallback handler", .retType=(uint)JSI_TT_ANY, .flags=0, .info=FN_send },
    { "source", InterpSourceCmd,  1,  2, "file:string, async:boolean=false", .help="Interpret file within sub-interp", .retType=(uint)JSI_TT_ANY, .flags=0, .info=FN_interpeval },
    { "uplevel",InterpUplevelCmd, 2,  2, "js:string, level:number=0", .help="Interpret code at the given stack level: see Info.level()", .retType=(uint)JSI_TT_ANY, .flags=0, .info=FN_interpuplevel },
    { "value",  InterpValueCmd,   1,  2, "var:string, level:number=0", .help="Lookup value of variable at stack level: see Info.level()", .retType=(uint)JSI_TT_ANY },
    { NULL,     0,0,0,0, .help="Commands for accessing interps" }
};

static Jsi_UserObjReg interpobject = {
    "Interp",
    interpCmds,
    interpObjFree,
    interpObjIsTrue,
    interpObjEqual
};


static Jsi_RC InterpConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Obj *fobj;
    Jsi_Value *toacc;
    InterpObj *cmdPtr = (InterpObj *)Jsi_Calloc(1,sizeof(*cmdPtr));
    int rc = JSI_OK;
    SIGINIT(cmdPtr,INTERPOBJ);
    cmdPtr->parent = interp;

    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);

    if (!(cmdPtr->subinterp = jsi_InterpNew(interp, arg, NULL))) {
        Jsi_Free(cmdPtr);
        return JSI_ERROR;
    }
    Jsi_Interp *sinterp = cmdPtr->subinterp;
    sinterp->opts.no_exit = interp->opts.no_exit;
    toacc = NULL;
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        toacc = _this;
    } else {
        Jsi_Obj *o = Jsi_ObjNew(interp);
        Jsi_PrototypeObjSet(interp, "Interp", o);
        Jsi_ValueMakeObject(interp, ret, o);
        toacc = *ret;
    }

    fobj = Jsi_ValueGetObj(interp, toacc);
    if ((cmdPtr->objId = Jsi_UserObjNew(interp, &interpobject, fobj, cmdPtr))<0)
        goto bail;
    sinterp->objId = cmdPtr->objId;
    cmdPtr->fobj = fobj;
#ifndef JSI_OMIT_THREADS
    if (sinterp->subthread) {
#ifdef __WIN32
        if (!_beginthread( NewInterpThread, 0, cmdPtr )) {
            Jsi_LogError("thread create failed");
            goto bail;
        }
#else
        pthread_t nthread;
        if (pthread_create(&nthread, NULL, NewInterpThread, cmdPtr) != 0) {
            Jsi_LogError("thread create failed");
            goto bail;
        }
#endif //__WIN32
#else
    if (0) {
#endif //JSI_OMIT_THREADS
    } else {
        //sinterp->framePtr->tryDepth++;
        if (sinterp->scriptStr != 0) {
            if (sinterp->scriptFile && !interp->curFile)
                sinterp->curFile = Jsi_ValueString(sinterp, sinterp->scriptFile, NULL);
            rc = Jsi_EvalString(sinterp, sinterp->scriptStr, JSI_EVAL_ISMAIN);
        } else if (sinterp->scriptFile && !sinterp->debugOpts.debugCallback) {
            int len;
            if (Jsi_ValueString(interp, sinterp->scriptFile, &len) && len==0)
                Jsi_Interactive(sinterp, JSI_OUTPUT_QUOTE|JSI_OUTPUT_NEWLINES);
            else
                rc = Jsi_EvalFile(sinterp, sinterp->scriptFile, JSI_EVAL_ISMAIN);
        }
        //sinterp->framePtr->tryDepth--;
        if (rc == JSI_EXIT)
            return JSI_OK;
        if (rc != JSI_OK) {
            /*Jsi_Strcpy(interp->errMsgBuf, sinterp->errMsgBuf);
            interp->errLine = sinterp->errLine;
            interp->errFile = sinterp->errFile;
            sinterp->errMsgBuf[0] = 0;*/
            goto bail;
        }
    }
    return JSI_OK;

bail:
    interpObjErase(cmdPtr);
    Jsi_ValueMakeUndef(interp, ret);
    return JSI_ERROR;
}

static Jsi_RC Jsi_DoneInterp(Jsi_Interp *interp)
{
    Jsi_UserObjUnregister(interp, &interpobject);
    return JSI_OK;
}

static Jsi_RC jsi_InterpConfFile(Jsi_Interp *interp, const char *fname)
{
    Jsi_RC rc;
    Jsi_DString dStr = {};
    Jsi_Value *opts = NULL, *fn = Jsi_ValueNewStringConst(interp, fname, -1);
    Jsi_IncrRefCount(interp, fn);
    rc = Jsi_FileRead(interp, fn, &dStr);
    if (rc != JSI_OK)
        goto done;
    opts = Jsi_ValueNew1(interp);
    if (rc == JSI_OK)
        rc = Jsi_JSONParse(interp, Jsi_DSValue(&dStr), &opts, 0);
    if (rc == JSI_OK
        && Jsi_OptionsProcess(interp, InterpOptions, interp, opts, 0) < 0)
        rc = JSI_ERROR;
done:
    if (opts)
        Jsi_DecrRefCount(interp, opts);
    Jsi_DecrRefCount(interp, fn);
    Jsi_DSFree(&dStr);
    return rc;
}

static Jsi_RC jsi_InterpConfFiles(Jsi_Interp *interp)
{
    Jsi_RC rc = JSI_OK;
    const char *fn = "/etc/jsish.conf";
    if (access(fn, R_OK)==0)
        rc = jsi_InterpConfFile(interp, fn);
    if (rc != JSI_OK)
        Jsi_LogWarn("parse failure: %s", fn);
    if (interp->jsppChars && Jsi_Strlen(interp->jsppChars)!=2) {
        Jsi_LogWarn("jsppChars ignored: length not 2: %s", interp->jsppChars);
        interp->jsppChars = NULL;
    }
    return rc;
}

static Jsi_RC InterpConfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    InterpObj *udf = (typeof(udf))Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_RC rc;
    Jsi_Value *opts = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Interp *sinterp = interp;
    if (!udf || udf->subinterp == interp) {
        if (interp->noConfig && opts && !Jsi_ValueIsString(interp, opts))
            return Jsi_LogError("Interp conf() is disabled for set");
        rc = Jsi_OptionsConf(interp, InterpOptions, interp, opts, ret, 0);
    } else {
        sinterp = udf->subinterp;
        Jsi_Value *popts = opts;
        if (opts && opts->vt != JSI_VT_NULL && !Jsi_ValueString(interp, opts, NULL) && opts->vt == JSI_VT_OBJECT) {
            popts = Jsi_ValueNew1(sinterp);
            Jsi_CleanValue(interp, sinterp, opts, &popts);
        }
        rc = Jsi_OptionsConf(sinterp, InterpOptions, sinterp, popts, ret, 0);
        if (popts && popts != opts)
            Jsi_DecrRefCount(sinterp, popts);
        Jsi_CleanValue(sinterp, interp, *ret, ret);
    }
    jsi_InterpConfFiles(interp);
    return rc;
}

Jsi_Value *Jsi_ReturnValue(Jsi_Interp *interp) {
    return interp->retValue;
}

Jsi_RC jsi_InitInterp(Jsi_Interp *interp, int release)
{
    if (release) return Jsi_DoneInterp(interp);
    Jsi_Hash *isys;
    if (!(isys = Jsi_UserObjRegister(interp, &interpobject)))
        Jsi_LogBug("Can not init interp");

    Jsi_CommandCreateSpecs(interp, interpobject.name, interpCmds, isys, JSI_CMDSPEC_ISOBJ);
    return JSI_OK;
}

bool Jsi_InterpSafe(Jsi_Interp *interp)
{
    return interp->isSafe;
}

Jsi_RC Jsi_InterpAccess(Jsi_Interp *interp, Jsi_Value* resource, int aflag)
{
    switch (aflag) {
        case JSI_INTACCESS_NETWORK:
            return (interp->noNetwork?JSI_ERROR:JSI_OK);
        case JSI_INTACCESS_MAININTERP:
            return (interp->parent?JSI_ERROR:JSI_OK);
        case JSI_INTACCESS_SETSSL:
            interp->hasOpenSSL = 1;
            return JSI_OK;
        case JSI_INTACCESS_WRITE:
        case JSI_INTACCESS_READ:
            break;
        default:
            return JSI_ERROR;
    }
    if (!resource)
        return JSI_ERROR;
    Jsi_Value *v, *dirs = ((aflag==JSI_INTACCESS_WRITE) ? interp->safeWriteDirs : interp->safeReadDirs);
    if (!interp->isSafe)
        return JSI_OK;
    if (!dirs)
        return JSI_ERROR;
    char pbuf[PATH_MAX];
    int i, m, argc = Jsi_ValueGetLength(interp, dirs);
    char *str, *dstr = Jsi_Realpath(interp, resource, pbuf);
    if (!dstr)
        return JSI_ERROR;
    for (i=0; i<argc; i++) {
        v = Jsi_ValueArrayIndex(interp, dirs, i);
        if (!v) continue;
        str = Jsi_ValueString(interp, v, &m);
        if (!str || m<=0) continue;
        if (!Jsi_Strcmp(str, dstr)) // Exact match.
            return JSI_OK;
        if (Jsi_Strncmp(str, dstr, m))
            continue;
        if (m>1 && str[m-1]=='/')
            return JSI_OK;
        if (dstr[m] == '/')
            return JSI_OK;
    }
    return JSI_ERROR;
}

Jsi_Value *Jsi_InterpResult(Jsi_Interp *interp)
{
    return interp->retValue;
}

const char *Jsi_InterpLastError(Jsi_Interp *interp, const char **errFilePtr, int *errLinePtr)
{
    if (errFilePtr)
        *errFilePtr = interp->errFile;
    if (errLinePtr)
        *errLinePtr = interp->errLine;
    return interp->errMsgBuf;
}

#ifdef __WIN32
void bzero(void *s, size_t n) {
    memset(s, 0, n);
}
#endif
#endif
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif
#include <errno.h>
#include <sys/time.h>


#if (defined(JSI__READLINE) && JSI__READLINE==1)
#define JSI_HAS_READLINE 1
//#define USE_GNU_READLINE
#ifdef USE_GNU_READLINE
# include <readline/readline.h>
# include <readline/history.h>

# define jsi_sh_add_history(X) add_history(X)
# define jsi_sh_read_history(X) read_history(X)
# define jsi_sh_write_history(X) write_history(X)
# define jsi_sh_stifle_history(X) stifle_history(X)
# define jsi_sh_readline(X) readline(X)
#else
#ifndef JSI_AMALGAMATION
#include "linenoise.h"
#endif
# define jsi_sh_add_history(X) linenoiseHistoryAdd(X)
# define jsi_sh_read_history(X) linenoiseHistoryLoad(X)
# define jsi_sh_write_history(X) linenoiseHistorySave(X)
# define jsi_sh_stifle_history(X) linenoiseHistorySetMaxLen(X)
# define jsi_sh_readline(X) linenoise(X)
#endif
#else

# define jsi_sh_read_history(X)
# define jsi_sh_write_history(X)
# define jsi_sh_stifle_history(X)

# define JSI_SH_USE_LOCAL_GETLINE 1
#endif
#include <math.h>

#ifdef Jsi_Free
#undef Jsi_Free
#undef Jsi_Malloc
#undef Jsi_Calloc
#undef Jsi_Realloc
#endif
void* Jsi_Realloc(void *m,unsigned int size) {
    void *v = realloc(m,size);
    Assert(v);
    return v;
}
void* Jsi_Malloc(unsigned int size) {
    void *v = malloc(size);
    Assert(v);
    return v;
}
void* Jsi_Calloc(unsigned int n,unsigned int size) {
    void *v = calloc(n,size);
    Assert(v);
    return v;
}
void  Jsi_Free(void *n) { Assert(n); free(n); }

#if JSI__SANITIZE
#define Jsi_Malloc(sz) malloc(sz)
#define Jsi_Calloc(nm, sz) calloc(nm,sz)
#define Jsi_Realloc(ptr, sz) realloc(ptr,sz)
#define Jsi_Free(ptr) free(ptr)
#endif

static const char *jsi_LogCodes[] = { "bug", "error", "warn", "info", "unused", "parse", "test", "debug", "trace", 0 };
jsi_IntData jsiIntData = {};

#ifdef JSI_LITE_ONLY
Jsi_RC Jsi_LogMsg(Jsi_Interp *interp, uint code, const char *format,...) {
    va_list va;
    va_start (va, format);
    const char *mt = (code <= JSI__LOGLAST ? jsi_LogCodes[code] : "");
    fputs(mt, stderr);
    vfprintf(stderr, format, va);
    fputs("\n", stderr);
    va_end(va);
    return JSI_ERROR;
}

#else // JSI_LITE_ONLY

int jsi_fatalexit = JSI_LOG_BUG;
const char *jsi_GetCurFile(Jsi_Interp *interp)
{
    const char *curFile = NULL;
    if (!interp)
        return NULL;
    if (interp->inParse)
        curFile = interp->curFile;
    else
        curFile = (interp->curIp && interp->curIp->fname? interp->curIp->fname:interp->curFile);
    if (!curFile) curFile = interp->framePtr->fileName;
    if (!curFile) curFile = "";
    return curFile;
}
extern void jsi_TypeMismatch(Jsi_Interp* interp)
{
    interp->typeMismatchCnt++;
    if (interp->typeWarnMax<=0)
        return;
    if (interp->typeMismatchCnt>=interp->typeWarnMax) {
        memset(&interp->typeCheck, 0, sizeof(interp->typeCheck));
        Jsi_LogWarn("Max warnings exceeded %d: typeCheck disabled", interp->typeWarnMax);
    }
}

static bool jsi_LogEnabled(Jsi_Interp *interp, uint code) {
    if (!interp->activeFunc) return 0;
    Jsi_CmdSpec *cs = interp->activeFunc->cmdSpec;
    if (!cs)
        return 0;
    if (interp->activeFunc->parentSpec)
        cs = interp->activeFunc->parentSpec;
    int cofs = (code - JSI_LOG_TEST);
    int ac = (cs->flags & (JSI_CMD_LOG_TEST<<cofs));
    return (ac)!=0;
}

static void (*logHook)(const char *buf, va_list va) = NULL;

// Format message: always returns JSI_ERROR.
Jsi_RC Jsi_LogMsg(Jsi_Interp *interp, uint code, const char *format,...) {
    if (Jsi_InterpGone(interp))
        return JSI_ERROR;
    va_list va;
    va_start (va, format);
    char pbuf[JSI_BUFSIZ/8] = "";
    char buf[JSI_BUFSIZ/2];
    const char *term = "", *pterm=pbuf;
    static char lastMsg[JSI_BUFSIZ/2] = "";
    static int lastCnt = 0;
    static Jsi_Interp *LastInterp = NULL;
    Jsi_Interp *lastInterp = LastInterp;
    const char *emsg = buf, *mt;
    int islog, line = 0, lofs = 0, noDups=0;
    bool isHelp = (format[0]=='.' && !Jsi_Strncmp(format, "...", 3));
    Jsi_OptionSpec *oep = interp->parseMsgSpec;
    const char *pps = "", *curFile = "";
    char *ss = interp->lastPushStr;
    
    if (interp==NULL)
        interp = jsiIntData.mainInterp;
    LastInterp = interp;
    if (lastInterp != interp)
        noDups = 1;
    
    /* Filter out try/catch (TODO: and non-syntax errors??). */
    if (interp == NULL) {
//nullInterp:
        if (logHook)
            (*logHook)(format, va);
        else {
            vfprintf(stderr, format, va);
            fputc('\n', stderr);
        }
        va_end(va);
        return JSI_ERROR;
    }
    curFile = jsi_GetCurFile(interp);
    switch (code) {
        case JSI_LOG_INFO:  if (!interp->logOpts.Info) goto bail; break;
        case JSI_LOG_WARN:  if (!interp->logOpts.Warn) goto bail; break;
        case JSI_LOG_DEBUG: if (!interp->logOpts.Debug && !jsi_LogEnabled(interp, code)) goto bail; break;
        case JSI_LOG_TRACE: if (!interp->logOpts.Trace && !jsi_LogEnabled(interp, code)) goto bail; break;
        case JSI_LOG_TEST:  if (!interp->logOpts.Test && !jsi_LogEnabled(interp, code)) goto bail; break;
        case JSI_LOG_PARSE: break; //if (!interp->parent) goto nullInterp; break;
        case JSI_LOG_ERROR: {
            if (!interp->logOpts.Error) goto bail;
            if ((interp->framePtr->tryDepth - interp->framePtr->withDepth)>0 && interp->inParse<=0 
                && (!interp->tryList || !(interp->tryList->inCatch|interp->tryList->inFinal))) { 
                /* Should only do the first or traceback? */
                if (!interp->errMsgBuf[0]) {
                    vsnprintf(interp->errMsgBuf, sizeof(interp->errMsgBuf), format, va);
                    //interp->errMsgBuf[sizeof(interp->errMsgBuf)-1] = 0;
                    interp->errFile =  jsi_GetCurFile(interp);
                    interp->errLine = (interp->curIp?interp->curIp->Line:0);
                    emsg = interp->errMsgBuf;
                }
                goto done;
            }
            interp->logErrorCnt++;
            break;
        }
    }
    mt = (code <= JSI__LOGLAST ? jsi_LogCodes[code] : "");
    if (isHelp) mt = "help";
    assert((JSI__LOGLAST+2) == (sizeof(jsi_LogCodes)/sizeof(jsi_LogCodes[0])));
    if (!Jsi_Strchr(format,'\n')) term = "\n";
    if (interp->strict && interp->lastParseOpt)
        ss = (char*)Jsi_ValueToString(interp, interp->lastParseOpt, NULL);
    if (code != JSI_LOG_INFO && code < JSI_LOG_TEST && interp && ss && ss[0]) {
        char psbuf[JSI_BUFSIZ/6];
        if (Jsi_Strchr(ss,'%')) {
            char *s = ss, *sd = psbuf;
            int plen=0, llen = sizeof(psbuf)-2;
            while (*s && plen<llen) {
                if (*s == '%')
                    sd[plen++] = '%';
                sd[plen++] = *s;
                s++;
            }
            sd[plen] = 0;
            ss = psbuf;
        }
        while (*ss && isspace(*ss))
            ss++;
        if (*ss && !isHelp)
            snprintf(pbuf, sizeof(pbuf), "    (at or near \"%s\")\n", ss);
    }
    pbuf[sizeof(pbuf)-1] = 0;
    if (interp->inParse && interp->parseLine) {
        line = interp->parseLine->first_line;
        lofs = interp->parseLine->first_column;
    } else if (interp->inParse && interp->parsePs) {
        line = interp->parsePs->lexer->cur_line;
        lofs = interp->parsePs->lexer->cur_char;
    } else if (interp->curIp) {
        if (interp->callerErr && interp->framePtr && interp->framePtr->parent) {
            jsi_Frame *fptr = interp->framePtr->parent;
            line = fptr->line;
            lofs = 0;
            curFile = fptr->fileName;
        } else {
            line = interp->curIp->Line;
            lofs = interp->curIp->Lofs;
            if (line<=0)
                line = interp->framePtr->line;
        }
    }
    islog = (interp->parent && interp->debugOpts.msgCallback && code != JSI_LOG_BUG);
    Jsi_DString pStr;
    Jsi_DSInit(&pStr);
    if (oep) {
        if (oep->id != JSI_OPTION_CUSTOM || !oep->custom)
            pps = Jsi_DSPrintf(&pStr, "for option \"%s\": ", oep->name);
        else {
            Jsi_OptionCustom* cust = Jsi_OptionCustomBuiltin(oep->custom);
            pps = Jsi_DSPrintf(&pStr, "for %s option \"%s\": ", (cust?cust->name:""), oep->name);
        }
    }
    char *cpt;
    if (curFile && interp->logOpts.ftail && (cpt =Jsi_Strrchr(curFile, '/')) && cpt[1])
        curFile = cpt+1;
    if (curFile && curFile[0] && Jsi_Strchr(curFile,'%')==0 && !islog) {
        if (!interp->subOpts.logColNums)
            snprintf(buf, sizeof(buf), "%s:%d: %s: %s%s%s%s",  curFile, line, mt, pps, format, pterm, term);
        else
            snprintf(buf, sizeof(buf), "%s:%d.%d: %s: %s%s%s%s",  curFile, line, lofs, pps, mt,format, pterm, term);
    } else {
        snprintf(buf, sizeof(buf), "%s: %s%s%s%s", mt, pps, format, pterm, term);
    }
    Jsi_DSFree(&pStr);
    buf[sizeof(buf)-1]=0;

    if (logHook)
        (*logHook)(buf, va);
    else if (interp->subOpts.logAllowDups)
        vfprintf(stderr, buf, va);
    else {
        char buf1[JSI_BUFSIZ/2];
        vsnprintf(buf1, sizeof(buf1), buf, va);
        if (!isHelp && !noDups) {
            if (buf1[0] && lastCnt && Jsi_Strcmp(buf1, lastMsg)==0) {
                lastCnt++;
                goto done;
            } else if (lastMsg[0] && lastCnt>1 ) {
                fprintf(stderr, "REPEAT: Last msg repeated %d times...\"\n" ,lastCnt);
            }
            if (buf1[0] == 0 || (buf1[0] == '.' && buf1[1] == 0))
                goto done;
        }
        lastCnt = 1;
        Jsi_Strcpy(lastMsg, buf1);
        if (!islog)
            Jsi_Puts(interp, jsi_Stderr, buf1, -1);
            //fputs(buf1, stderr);
        else {
            Jsi_DString jStr={}, kStr={};
            Jsi_DSPrintf(&kStr, "[%s, \"%s\", \"%s\", %d, %d ]",
                Jsi_JSONQuote(interp, buf1, -1, &jStr), mt, curFile, line, lofs);
            if (Jsi_FunctionInvokeJSON(interp->parent, interp->debugOpts.msgCallback, Jsi_DSValue(&kStr), NULL) != JSI_OK)
                code = 1;
            Jsi_DSFree(&jStr);
            Jsi_DSFree(&kStr);
        }
    }
done:
    va_end(va);
    if (interp->debugOpts.hook) {
        static int inhook = 0;
        if (!inhook) {
            inhook = 1;
            (*interp->debugOpts.hook)(interp, curFile, interp->curIp?interp->curIp->Line:0, interp->level, interp->curFunction, "DEBUG", NULL, emsg);
        }
        inhook = 0;
    }
    if ((code & jsi_fatalexit) && !interp->opts.no_exit)
        jsi_DoExit(interp, 1);
    return (code==JSI_LOG_ERROR?JSI_ERROR:JSI_OK);
bail:
    va_end(va);
    return JSI_OK;
}

const char* Jsi_KeyAdd(Jsi_Interp *interp, const char *str)
{
    Jsi_MapEntry *hPtr;
    bool isNew;
    hPtr = Jsi_MapEntryNew(interp->strKeyTbl, str, &isNew);
    assert(hPtr) ;
    return (const char*)Jsi_MapKeyGet(hPtr, 0);
}

const char* Jsi_KeyLookup(Jsi_Interp *interp, const char *str)
{
    Jsi_MapEntry *hPtr;
    hPtr = Jsi_MapEntryFind(interp->strKeyTbl, str);
    if (!hPtr) {
        return NULL;
    }
    return (const char*)Jsi_MapKeyGet(hPtr, 0);
}


Jsi_Value *Jsi_VarLookup(Jsi_Interp *interp, const char *varname)
{
    Jsi_Value *v;
    v = Jsi_ValueObjLookup(interp, interp->framePtr->incsc, (char*)varname, 0);
    if (!v)
        v = jsi_ScopeChainObjLookupUni(interp->framePtr->ingsc, (char*)varname);
    return v;
}

static char *FindEndB(char *cp) {
    
    if (*cp == '\"'||*cp == '\'') {
        char endc = *cp;
        cp++;
        while (*cp && *cp != endc) {
            if (*cp == '\\' && cp[1]) cp++;
            cp++;
        }
        if (*cp == endc)
            cp++;
        if (*cp != ']')
            return NULL;
        return cp;
    } else
        return Jsi_Strchr(cp, ']');
}


/* Lookup "name" within object "inObj", ie.  "inObj.name"  */
Jsi_Value *Jsi_NameLookup2(Jsi_Interp *interp, const char *name, const char *inObj)
{
    Jsi_Value *v;
    if (!name)
        return NULL;
    if (!inObj)
        return Jsi_VarLookup(interp, name);
    v = Jsi_VarLookup(interp, inObj);
    if (!v)
        return NULL;
    if (Jsi_ValueIsArray(interp, v)) {
        int n;
        if (!isdigit(name[0]))
            return NULL;
        if (Jsi_GetInt(interp, name, &n, 0) != JSI_OK)
            return NULL;
        if (n>=0 && n<(int)v->d.obj->arrCnt)
            return v->d.obj->arr[n];
        return NULL;
    }
    if (v->vt != JSI_VT_OBJECT || (v->d.obj->ot != JSI_OT_OBJECT && v->d.obj->ot != JSI_OT_FUNCTION))
        return NULL;
    return Jsi_ValueObjLookup(interp, v, name, 0);
}

/* Lookup a name, eg.  "a[b].c  a.b.c  a[b][c]  a.b[c]  a["b"].c  a[1].c  */
Jsi_Value *Jsi_NameLookup(Jsi_Interp *interp, const char *name)
{
    uint cnt = 0, len, isq;
    char *nam = (char*)name, *cp, *cp2, *ocp, *kstr;
    //DECL_VALINIT(tv);
    DECL_VALINIT(nv);
    DECL_VALINIT(key);
    Jsi_Value *v = NULL, *nvPtr = &nv;
    Jsi_Value *kPtr = &key; // Note: a string key so no reset needed.
    Jsi_DString dStr = {};
    cp2 = Jsi_Strchr(nam,'[');
    cp = Jsi_Strchr(nam, '.');
    if (cp2 && (cp==0 || cp2<cp))
        cp = cp2;
    if (!cp)
        return Jsi_VarLookup(interp, nam);
    //fprintf(stderr, "NAM: %s\n", nam);
    Jsi_DSSetLength(&dStr, 0);
    Jsi_DSAppendLen(&dStr, nam, cp-nam);
    v = Jsi_VarLookup(interp, Jsi_DSValue(&dStr));
    if (!v)
        goto bail;
    while (v && cnt++ < 1000) {
        ocp = cp;
        nam = cp+1;
        isq = 0;
        if (*cp == '[') {
            cp = FindEndB(cp+1); /* handle [] in strings. */
            if (!cp) goto bail;
            len = cp-nam;
            cp++;
            if (len>=2 && ((nam[0] == '\"' && nam[len-1] == '\"') || (nam[0] == '\'' && nam[len-1] == '\''))) {
                nam += 1;
                len -= 2;
                isq = 1;
            }
        } else if (*cp == '.') {
            cp2 = Jsi_Strchr(nam,'[');
            cp = Jsi_Strchr(nam, '.');
            if (cp2 && (cp==0 || cp2<cp))
                cp = cp2;
            len = (cp ? (uint)(cp-nam) : Jsi_Strlen(nam));
        } else {
            goto bail;
        }
        Jsi_DSSetLength(&dStr, 0);
        Jsi_DSAppendLen(&dStr, nam, len);
        kstr = Jsi_DSValue(&dStr);
        if (*ocp == '[' && isq == 0 && isdigit(kstr[0]) && Jsi_ValueIsArray(interp, v)) {
            int nn;
            if (Jsi_GetInt(interp, kstr, &nn, 0) != JSI_OK)
                goto bail;
            v = Jsi_ValueArrayIndex(interp, v, nn);
            if (!v)
                goto bail;
        } else if (*ocp == '[' && isq == 0) {
            Jsi_Value *kv = Jsi_VarLookup(interp, kstr);
            if (!kv)
                goto bail;
            v = jsi_ValueSubscript(interp, v, kv, &nvPtr);
            goto keyon;
        } else {
            Jsi_ValueMakeStringKey(interp, &kPtr, kstr);
            v = jsi_ValueSubscript(interp, v, kPtr, &nvPtr);
keyon:
            if (!v)
                goto bail;
        }
        if (cp == 0 || *cp == 0) break;
    }
    //Jsi_ValueReset(interp, &ret);
    Jsi_DSFree(&dStr);
    if (v && v == nvPtr) {
        v = Jsi_ValueNew(interp);
        //Jsi_ValueMove(interp, v, &tv);
#ifdef JSI_MEM_DEBUG
        memcpy(v, &nv, sizeof(nv)-sizeof(nv.VD));
        v->VD.label3 = nv.VD.func;
        if (interp->memDebug>1)
            v->VD.label2 = Jsi_KeyAdd(interp, name);
#else
        *v = nv;
#endif
    }
    return v;
bail:
    Jsi_DSFree(&dStr);
    return NULL;
}

Jsi_Value *jsi_GlobalContext(Jsi_Interp *interp)
{
    return interp->csc;
}

typedef struct {
    Jsi_DString *dStr;
    int quote; /* Set to JSI_OUTPUT_JSON, etc*/
    int depth;
} objwalker;

bool jsi_IsAlnumStr(const char *cp)
{
    if (!cp || !*cp) return 0;
    while (*cp)
        if (isalnum(*cp) || *cp == '_')
            cp++;
        else
            return 0;
    return 1;
}

char *jsi_TrimStr(char *str) {
    while (isspace(*str)) str++;
    if (!str) return str;
    int len = Jsi_Strlen(str);
    while (--len>=0 && isspace(str[len]))
        str[len] = 0;
    return str;
}

static Jsi_RC jsiValueGetString(Jsi_Interp *interp, Jsi_Value* v, Jsi_DString *dStr, objwalker *owPtr);

static Jsi_RC _object_get_callback(Jsi_Tree *tree, Jsi_TreeEntry *hPtr, void *data)
{
    Jsi_Value *v;
    objwalker *ow = (objwalker *)data;
    Jsi_DString *dStr = ow->dStr;
    int len;
    char *str;
    if ((hPtr->f.bits.dontenum))
        return JSI_OK;
    v =(Jsi_Value*) Jsi_TreeValueGet(hPtr);
    if ((ow->quote&JSI_OUTPUT_JSON) && v && v->vt == JSI_VT_UNDEF)
        return JSI_OK;
    str = (char*)Jsi_TreeKeyGet(hPtr);
    char *cp = Jsi_DSValue(dStr);
    len = Jsi_DSLength(dStr);
    if (len>=2 && (cp[len-2] != '{' || cp[len-1] == '}'))
        Jsi_DSAppend(dStr, ", ", NULL);
    if (((ow->quote&JSI_OUTPUT_JSON) == 0 || (ow->quote&JSI_JSON_STRICT) == 0) && jsi_IsAlnumStr(str)
        && !Jsi_HashEntryFind(tree->opts.interp->lexkeyTbl, str))
        Jsi_DSAppend(dStr, str, NULL);
    else
        /* JSON/spaces, etc requires quoting the name. */
        Jsi_DSAppend(dStr, "\"", str, "\"", NULL);
    Jsi_DSAppend(dStr, ":", NULL);
    ow->depth++;
    Jsi_RC rc = jsiValueGetString(tree->opts.interp, v, dStr, ow);
    ow->depth--;
    return rc;
}

/* Format value into dStr.  Toplevel caller does init/free. */
static Jsi_RC jsiValueGetString(Jsi_Interp *interp, Jsi_Value* v, Jsi_DString *dStr, objwalker *owPtr)
{
    char buf[100], *str;
    Jsi_DString eStr;
    Jsi_DSInit(&eStr);
    if (interp->maxDepth>0 && owPtr->depth > interp->maxDepth)
        return Jsi_LogError("recursive ToString");
    int quote = owPtr->quote;
    int isjson = owPtr->quote&JSI_OUTPUT_JSON;
    Jsi_Number num;
    switch(v->vt) {
        case JSI_VT_UNDEF:
            Jsi_DSAppend(dStr, "undefined", NULL);
            return JSI_OK;
        case JSI_VT_NULL:
            Jsi_DSAppend(dStr, "null", NULL);
            return JSI_OK;
        case JSI_VT_VARIABLE:
            Jsi_DSAppend(dStr, "variable", NULL);
            return JSI_OK;
        case JSI_VT_BOOL:
            Jsi_DSAppend(dStr, (v->d.val ? "true":"false"), NULL);
            return JSI_OK;
        case JSI_VT_NUMBER:
            num = v->d.num;
outnum:
            if (isjson && !Jsi_NumberIsNormal(num)) {
                Jsi_DSAppend(dStr, "null", NULL);
            } else if (Jsi_NumberIsInteger(num)) {
                Jsi_NumberItoA10((Jsi_Wide)num, buf, sizeof(buf));
                Jsi_DSAppend(dStr, buf, NULL);
            } else if (Jsi_NumberIsWide(num)) {
                snprintf(buf, sizeof(buf), "%" PRId64, (Jsi_Wide)num);
                Jsi_DSAppend(dStr, buf, NULL);
            } else if (Jsi_NumberIsNormal(num) || Jsi_NumberIsSubnormal(num)) {
                Jsi_NumberDtoA(interp, num, buf, sizeof(buf), 0);
                Jsi_DSAppend(dStr, buf, NULL);
            } else if (Jsi_NumberIsNaN(num)) {
                Jsi_DSAppend(dStr, "NaN", NULL);
            } else {
                int s = Jsi_NumberIsInfinity(num);
                if (s > 0) Jsi_DSAppend(dStr, "+Infinity", NULL);
                else if (s < 0) Jsi_DSAppend(dStr, "-Infinity", NULL);
                else Jsi_LogBug("Ieee function problem: %d", fpclassify(num));
            }
            return JSI_OK;
        case JSI_VT_STRING:
            str = v->d.s.str;
outstr:
            if (!quote) {
                Jsi_DSAppend(dStr, str, NULL);
                return JSI_OK;
            }
            Jsi_DSAppend(dStr,"\"", NULL);
            while (*str) {
                if ((*str == '\'' && (!isjson)) || *str == '\\'|| *str == '\"'|| (*str == '\n'
                    && (!(owPtr->quote&JSI_OUTPUT_NEWLINES)))
                    || *str == '\r' || *str == '\t' || *str == '\f' || *str == '\b'  ) {
                    char pcp[2];
                    *pcp = *str;
                    pcp[1] = 0;
                    Jsi_DSAppendLen(dStr,"\\", 1);
                    switch (*str) {
                        case '\r': *pcp = 'r'; break;
                        case '\n': *pcp = 'n'; break;
                        case '\t': *pcp = 't'; break;
                        case '\f': *pcp = 'f'; break;
                        case '\b': *pcp = 'b'; break;
                    }
                    Jsi_DSAppendLen(dStr,pcp, 1);
                } else if (isprint(*str) || !isjson)
                    Jsi_DSAppendLen(dStr,str, 1);
                else {
                    char ubuf[10];
                    int l = Jsi_UtfEncode(str, ubuf);
                    Jsi_DSAppend(dStr,ubuf, NULL);
                    str += l-1;
                }
                str++;
            }
            Jsi_DSAppend(dStr,"\"", NULL);
            Jsi_DSFree(&eStr);
            return JSI_OK;
        case JSI_VT_OBJECT: {
            Jsi_Obj *o = v->d.obj;
            switch(o->ot) {
                case JSI_OT_BOOL:
                    Jsi_DSAppend(dStr, (o->d.val ? "true":"false"), NULL);
                    return JSI_OK;
                case JSI_OT_NUMBER:
                    num = o->d.num;
                    goto outnum;
                    return JSI_OK;
                case JSI_OT_STRING:
                    str = o->d.s.str;
                    goto outstr;
                case JSI_OT_FUNCTION:
                    Jsi_FuncObjToString(interp, o->d.fobj->func, &eStr, 3 | ((owPtr->depth==0 && owPtr->quote)?8:0));
                    str = Jsi_DSValue(&eStr);
                    goto outstr;
                case JSI_OT_REGEXP:
                    str = o->d.robj->pattern;
                    goto outstr;
                case JSI_OT_USEROBJ:
                    jsi_UserObjToName(interp, o->d.uobj, &eStr);
                    str = Jsi_DSValue(&eStr);
                    goto outstr;
                case JSI_OT_ITER:
                    Jsi_DSAppend(dStr, (isjson?"null":"*ITER*"), NULL);
                    return JSI_OK;
                default:
                    break;
            }
                        
            if (o->isarrlist)
            {
                Jsi_Value *nv;
                int i, len = o->arrCnt;
                
                if (!o->arr)
                    len = Jsi_ValueGetLength(interp, v);
                Jsi_DSAppend(dStr,"[",len?" ":"", NULL);
                for (i = 0; i < len; ++i) {
                    nv = Jsi_ValueArrayIndex(interp, v, i);
                    if (i) Jsi_DSAppend(dStr,", ", NULL);
                    owPtr->depth++;
                    if (nv) {
                        if (jsiValueGetString(interp, nv, dStr, owPtr) != JSI_OK) {
                            owPtr->depth--;
                            return JSI_ERROR;
                        }
                    }
                    else Jsi_DSAppend(dStr, "undefined", NULL);
                    owPtr->depth--;
                }
                Jsi_DSAppend(dStr,len?" ":"","]", NULL);
            } else {
                int len = Jsi_TreeSize(o->tree);
                Jsi_DSAppend(dStr,"{",len?" ":"", NULL);
                owPtr->depth++;
                Jsi_TreeWalk(o->tree, _object_get_callback, owPtr, 0);
                owPtr->depth--;
                Jsi_DSAppend(dStr,len?" ":"","}", NULL);
            }
            return JSI_OK;
        }
#ifndef __cplusplus
        default:
            Jsi_LogBug("Unexpected value type: %d", v->vt);
#endif
    }
    return JSI_OK;
}

/* Format value into dStr.  Toplevel caller does init/free. */
const char* Jsi_ValueGetDString(Jsi_Interp *interp, Jsi_Value* v, Jsi_DString *dStr, int quote)
{
    objwalker ow;
    ow.quote = quote;
    ow.depth = 0;
    ow.dStr = dStr;
    jsiValueGetString(interp, v, dStr, &ow);
    return Jsi_DSValue(dStr);
}

char* jsi_KeyFind(Jsi_Interp *interp, const char *str, int nocreate, int *isKey)
{
    Jsi_MapEntry *hPtr;
    if (isKey) *isKey = 0;
    if (!nocreate) {
        *isKey = 1;
         if (isKey) *isKey = 1;
        return (char*)Jsi_KeyAdd(interp, str);
    }
    hPtr = Jsi_MapEntryFind(interp->strKeyTbl, str);
    if (!hPtr) {
        return Jsi_Strdup(str);;
    }
    if (isKey) *isKey = 1;
    *isKey = 1;
    return (char*)Jsi_MapKeyGet(hPtr, 0);
}

bool jsi_StrIsBalanced(char *str) {
    int cnt = 0, quote = 0;
    char *cp = str;
    while (*cp) {
        switch (*cp) {
        case '\\':
            cp++;
            break;
        case '{': case '(': case '[':
            cnt++;
            break;
        case '\'': case '\"':
            quote++;
            break;
        case '}': case ')': case ']':
            cnt--;
            break;
        }
        if (*cp == 0)
            break;
        cp++;
    }
    return ((quote%2) == 0 && cnt <= 0);
}

static char *get_inputline(Jsi_Interp *interp, int istty, const char *prompt)
{
    char *res;
#ifdef JSI_HAS_READLINE
    if (istty && interp->subOpts.noReadline==0) {
        res = jsi_sh_readline(prompt);
        if (res && *res) jsi_sh_add_history(res);
        return res;
    }
#endif
    int done = 0;
    char bbuf[JSI_BUFSIZ];
    Jsi_DString dStr = {};
    if (istty)
        fputs(prompt, stdout);
    fflush(stdout);
    while (!done) { /* Read a line. */
        bbuf[0] = 0;
        if (fgets(bbuf, sizeof(bbuf), stdin) == NULL)
            return NULL;
        Jsi_DSAppend(&dStr, bbuf, NULL);
        if (Jsi_Strlen(bbuf) < (sizeof(bbuf)-1) || bbuf[sizeof(bbuf)-1] == '\n')
            break;
    }
    res = Jsi_Strdup(Jsi_DSValue(&dStr));
    Jsi_DSFree(&dStr);
    return res;
}

static Jsi_Interp* jsi_interactiveInterp = NULL;
#ifdef JSI_HAS_READLINE
static Jsi_Value *completeValues = NULL;

#ifdef USE_GNU_READLINE
static int jsiRlStart = 0;

static char *jsiRlCmdMatches(const char *text, int state) {
    static int idx, len;
    const char *name;
    Jsi_Interp* interp = jsi_interactiveInterp;
    if (completeValues == NULL || !Jsi_ValueIsArray(interp, completeValues))
        return NULL;
    Jsi_Value **arr = completeValues->d.obj->arr;
    int aLen = completeValues->d.obj->arrCnt;

    if (!state)
    {
        idx = 0;
        len = Jsi_Strlen(text)-jsiRlStart;
    }
    while (idx<aLen)
    {
        name = Jsi_ValueString(interp, arr[idx], NULL);
        if (!name) name = "";
        idx++;
        if (Jsi_Strncmp(name, text+jsiRlStart, len) == 0)
            return (Jsi_Strdup(name));
    }
    return NULL;
}

static char **jsiRlGetMatches(const char *cstr, int start, int end) {
    char **matches = NULL;
    char *str = rl_line_buffer;
    jsiRlStart = start;
    if (1 || start == 0 || !completeValues) {
        int rc;
        Jsi_Interp* interp = jsi_interactiveInterp;
        if (!completeValues)
            completeValues = Jsi_ValueNew1(interp);
        Jsi_Value *func = interp->onComplete;
        if (func == NULL || !Jsi_ValueIsFunction(interp, func))
            func = Jsi_NameLookup(interp, "Info.completions");
        if (func && Jsi_ValueIsFunction(interp, func)) {
            Jsi_Value *items[3] = {};
            items[0] = Jsi_ValueNewStringDup(interp, str);
            items[1] = Jsi_ValueNewNumber(interp, (Jsi_Number)start);
            items[2] = Jsi_ValueNewNumber(interp, (Jsi_Number)end);;
            Jsi_Value *args = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, items, 3, 0));
            Jsi_IncrRefCount(interp, args);
            rc = Jsi_FunctionInvoke(interp, func, args, &completeValues, interp->csc);
            Jsi_DecrRefCount(interp, args);
            if (rc != JSI_OK)
                fprintf(stderr, "bad completion: %s %d %d\n", str?str:"", start, end);
        }
        matches = rl_completion_matches(str, jsiRlCmdMatches);
    }
    return matches;
}
#else
static const char *jsiFilePreCmds[] = {
    "File.", "source", "load", "new Channel", "new Sqlite", NULL
};

char *jsiLNhints(const char *buf, int *color, int *bold) {
    int i, len = Jsi_Strlen(buf);
    for (i=0; jsiFilePreCmds[i]; i++)
        if (!Jsi_Strncmp(buf, jsiFilePreCmds[i], Jsi_Strlen(jsiFilePreCmds[i]))) break;
    if (jsiFilePreCmds[i]) {
        const char *ce = buf+len-1, *cp = "('<file>";
        if ((*ce =='\'' || *ce =='\"') && buf[len-2]=='(') cp+=2;
        else if (*ce=='(') cp++;
        else return NULL;
        
        *color = 35;
        *bold = 0;
        return (char*)cp;
    }
    return NULL;
}

static void jsiLNGetMatches(const char *str, linenoiseCompletions *lc) {
    char buf[1000], pre[1000], hpre[6] = {};
    const char *cp, *fnam = "Info.completions";
    int i = 0, len;
    int rc, isfile = 0, start = 0, end = Jsi_Strlen(str);
    Jsi_Interp* interp = jsi_interactiveInterp;
    if (!Jsi_Strncmp(str, "help ", 5)) {
        Jsi_Strcpy(hpre, "help ");
        str += 5;
        end -= 5;
    }
    if (end<=0) return;
    Jsi_Strncpy(buf, str, sizeof(buf)-1);
    buf[sizeof(buf)-1] = 0;
    pre[0] = 0;
    if (end<=3 && !Jsi_Strncmp(str, "help", end)) {
        linenoiseAddCompletion(lc, "help");
        return;
    }
    if (!completeValues)
        completeValues = Jsi_ValueNew1(interp);
    Jsi_Value *func = interp->onComplete;
    if (func == NULL || !Jsi_ValueIsFunction(interp, func)) {
        for (i=0; jsiFilePreCmds[i]; i++)
            if (!Jsi_Strncmp(buf, jsiFilePreCmds[i], Jsi_Strlen(jsiFilePreCmds[i]))) break;
        if (jsiFilePreCmds[i] && ((cp=Jsi_Strrchr(buf, '(')) && (cp[1]=='\"' || cp[1]=='\''))) {
            Jsi_Strcpy(pre, buf);
            pre[cp-buf+2] = 0;
            snprintf(buf, sizeof(buf), "%s*%s", cp+2, (buf[0]=='s'?".js*":""));
            isfile = 1;
            fnam = "File.glob";
        }
    }
    func = Jsi_NameLookup(interp, fnam);
    if (func && Jsi_ValueIsFunction(interp, func)) {
        //printf("PATTERN: %s\n", str);
        Jsi_Value *items[3] = {};;
        i = 0;
        items[i++] = Jsi_ValueNewStringDup(interp, buf);
        if (!isfile) {
            items[i++] = Jsi_ValueNewNumber(interp, (Jsi_Number)start);
            items[i++] = Jsi_ValueNewNumber(interp, (Jsi_Number)end);
        }
        Jsi_Value *args = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, items, i, 0));
        Jsi_IncrRefCount(interp, args);
        rc = Jsi_FunctionInvoke(interp, func, args, &completeValues, interp->csc);
        Jsi_DecrRefCount(interp, args);
        if (rc != JSI_OK) {
            fprintf(stderr, "bad completion: %s %d %d\n", str?str:"", start, end);
            return;
        }
        const char *name;
        Jsi_Interp* interp = jsi_interactiveInterp;
        if (completeValues == NULL || !Jsi_ValueIsArray(interp, completeValues))
            return;
        Jsi_Value **arr = completeValues->d.obj->arr;
        int aLen = completeValues->d.obj->arrCnt;
        i = 0;
        while (i<aLen)
        {
            name = Jsi_ValueString(interp, arr[i], &len);
            if (name) {
                if (!pre[0] && !hpre[0])
                    linenoiseAddCompletion(lc, name);
                else {
                    snprintf(buf, sizeof(buf), "%s%s%s", hpre, pre, name);
                    linenoiseAddCompletion(lc, buf);
                }
            }
            i++;
        }
    }
}
#endif
#endif
#if JSI__SIGNAL
#include <signal.h> //  our new library 
#endif

#if JSI__SIGNAL
static void jsi_InteractiveSignal(int sig){
    if (jsi_interactiveInterp)
        jsi_interactiveInterp->interrupted = 1;
}
#endif
 
/* Collect and execute code from stdin.  The first byte of flags are passed to Jsi_ValueGetDString(). */
Jsi_RC Jsi_Interactive(Jsi_Interp* interp, int flags) 
{
    Jsi_RC rc = JSI_OK;
    int done = 0, len, quote = (flags & 0xff), istty = 1, chkHelp=0, hasHelp=0;
    const char *prompt = interp->subOpts.prompt;
    char *buf;
    if (jsi_interactiveInterp) 
        return Jsi_LogError("multiple interactive not supported");
#if JSI__SIGNAL
  signal(SIGINT, jsi_InteractiveSignal); 
#endif
    interp->typeCheck.parse = interp->typeCheck.run = interp->typeCheck.all = 1;
    interp->strict = 1;
    interp->isInteractive = 1;
    jsi_interactiveInterp = interp;
    interp->subOpts.istty = 1;
    interp->subOpts.logAllowDups = 1;
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
#ifndef __WIN32
    istty = isatty(fileno(stdin));
#else
    istty = _isatty(_fileno(stdin));
#endif
#ifdef JSI_HAS_READLINE
    Jsi_DString dHist = {}, sHist = {};
    char *hist = NULL;
#ifdef USE_GNU_READLINE
    rl_attempted_completion_function = jsiRlGetMatches;
#else
    linenoiseSetCompletionCallback(jsiLNGetMatches);
    linenoiseSetHintsCallback(jsiLNhints);
#endif
    if(interp->subOpts.noReadline == 0 && !interp->parent)
    {
        const char *hfile = (interp->historyFile ? interp->historyFile : "~/.jsish_history");
        hist = Jsi_NormalPath(interp, hfile, &dHist);
        if (hist)
            jsi_sh_read_history(hist);
    }
#endif
    interp->level++;
    if (!interp->iskips)
        puts("Jsish interactive: see 'help [cmd]' or 'history'.  \\ cancels > input."
#if JSI__SIGNAL
        "  ctrl-c aborts running script."
#endif
        );
    while (done==0 && interp->exited==0) {
        buf = get_inputline(interp, istty, (prompt?prompt:"$ "));
        if (buf) {
            if (buf[0] == '\\' && !buf[1]) {
                 Jsi_DSSetLength(&dStr, 0);
                 prompt = interp->subOpts.prompt;
                 fprintf(stderr, "abandoned input");
            } else
                Jsi_DSAppend(&dStr, buf, NULL);
            free(buf);
        } else {
            done = 1;
        }
        len = Jsi_DSLength(&dStr);
        if (done && len == 0)
            break;
        if (!len) continue;
        Jsi_DSAppendLen(&dStr, " ", 1); // Allow for added space.
        buf = Jsi_DSValue(&dStr);
        if (done == 0 && (!jsi_StrIsBalanced(buf))) {
            prompt = interp->subOpts.prompt2;
            continue;
        }
        prompt = interp->subOpts.prompt;
        while ((len = Jsi_Strlen(buf))>0 && (isspace(buf[len-1])))
            buf[len-1] = 0;
        if (buf[0] == 0) {
            Jsi_DSSetLength(&dStr, 0);
            continue;
        }
        bool wantHelp = 0;
        if (interp->onEval == NULL) {
            /* Convenience: add semicolon to "var" statements (required by parser). */
#ifdef JSI_HAS_READLINE
            if (!Jsi_Strncmp(buf, "history", 7) && buf[7] == 0) {
                fputs(Jsi_DSValue(&sHist), stdout);
                Jsi_DSSetLength(&dStr, 0);
                continue;
            }
#endif
            if (!Jsi_Strncmp(buf, "help", 4) && (buf[4] == 0 || isspace(buf[4]))) {
                if (!chkHelp++)
                    hasHelp = (Jsi_PkgRequire(interp, "Help", 0)>=0);
                if (hasHelp) {
                    wantHelp = 1;
                    char tbuf[BUFSIZ];
                    snprintf(tbuf, sizeof(tbuf), "return runModule('Help', '%s'.trim().split(null));", buf+4);
                    rc = Jsi_EvalString(interp, tbuf, JSI_RETURN);
                }
            }
            if (!wantHelp) {
                if (!Jsi_Strncmp(buf,"var ", 4) && Jsi_Strchr(buf, '\n')==NULL && Jsi_Strchr(buf, ';')==NULL)
                    Jsi_Strcpy(buf+Jsi_Strlen(buf), ";"); // Added space above so strcat ok.
                rc = Jsi_EvalString(interp, buf, JSI_EVAL_RETURN);
                prompt = interp->subOpts.prompt;
#ifdef JSI_HAS_READLINE
                if (rc == JSI_OK)
                    Jsi_DSAppend(&sHist, buf, "\n", NULL);
#endif
            }
        }
        else
        {
            Jsi_Value *func = interp->onEval;
            if (func && Jsi_ValueIsFunction(interp, func)) {
                Jsi_Value *items[1] = {};
                items[0] = Jsi_ValueNewStringDup(interp, buf);
                Jsi_Value *args = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, items, 1, 0));
                Jsi_IncrRefCount(interp, args);
                rc = Jsi_FunctionInvoke(interp, func, args, &interp->retValue, interp->csc);
                Jsi_DecrRefCount(interp, args);
                if (rc != JSI_OK)
                    fprintf(stderr, "bad eval");
            }
        }
        if (interp->exited)
            break;
        jsi_interactiveInterp->interrupted = 0;
        if (rc == JSI_OK) {
             if (interp->retValue->vt != JSI_VT_UNDEF || interp->subOpts.outUndef) {
                Jsi_DString eStr = {};
                fputs(Jsi_ValueGetDString(interp, interp->retValue, &eStr, hasHelp?0:quote), stdout);
                Jsi_DSFree(&eStr);
                fputs("\n", stdout);
             }
        } else if (!interp->exited && !wantHelp) {
            fputs("ERROR\n", stderr);
        }
        Jsi_DSSetLength(&dStr, 0);
        len = 0;
    }
    interp->level--;
#ifdef JSI_HAS_READLINE
    if (hist) {
        jsi_sh_stifle_history(100);
        jsi_sh_write_history(hist);
    }
    Jsi_DSFree(&dHist);
    Jsi_DSFree(&sHist);
#endif
    Jsi_DSFree(&dStr);
    if (interp->retValue) {
        Jsi_DecrRefCount(interp, interp->retValue);
        interp->retValue = NULL;
    }
    if (interp->exited && interp->level <= 0)
    {
        rc = JSI_EXIT;
        Jsi_InterpDelete(interp);
    }
    jsi_interactiveInterp = NULL;
    return rc;
}

Jsi_RC Jsi_ThisDataSet(Jsi_Interp *interp, Jsi_Value *_this, void *value)
{
    bool isNew;
    Jsi_HashEntry *hPtr = Jsi_HashEntryNew(interp->thisTbl, _this, &isNew);
    if (!hPtr)
        return JSI_ERROR;
    Jsi_HashValueSet(hPtr, value);
    return JSI_OK;
}

void *Jsi_ThisDataGet(Jsi_Interp *interp, Jsi_Value *_this)
{
    Jsi_HashEntry *hPtr;
    hPtr = Jsi_HashEntryFind(interp->thisTbl, _this);
    if (!hPtr)
        return NULL;
    return Jsi_HashValueGet(hPtr);
}

Jsi_RC Jsi_PrototypeDefine(Jsi_Interp *interp, const char *key, Jsi_Value *value)
{
    bool isNew;
    Jsi_HashEntry *hPtr = Jsi_HashEntryNew(interp->protoTbl, key, &isNew);
    if (!hPtr)
        return JSI_ERROR;
    Jsi_HashValueSet(hPtr, value);
    return JSI_OK;
}

void *Jsi_PrototypeGet(Jsi_Interp *interp, const char *key)
{
    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->protoTbl, key);
    if (!hPtr)
        return NULL;
    return Jsi_HashValueGet(hPtr);
}

Jsi_RC Jsi_PrototypeObjSet(Jsi_Interp *interp, const char *key, Jsi_Obj *obj)
{
    Jsi_Value *val;
    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->protoTbl, key);
    if (!hPtr)
        return JSI_ERROR;
    val = (Jsi_Value *)Jsi_HashValueGet(hPtr);
    obj->__proto__ = val;
    return JSI_OK;
}

const char *Jsi_ObjTypeStr(Jsi_Interp *interp, Jsi_Obj *o)
{
     switch (o->ot) {
        case JSI_OT_BOOL: return "boolean"; break;
        case JSI_OT_FUNCTION: return "function"; break;
        case JSI_OT_NUMBER: return "number"; break;
        case JSI_OT_STRING: return "string"; break;  
        case JSI_OT_REGEXP: return "regexp"; break;  
        case JSI_OT_ITER: return "iter"; break;  
        case JSI_OT_OBJECT: if (!o->isarrlist) return "object";
        case JSI_OT_ARRAY: return "array"; break;  
        case JSI_OT_USEROBJ:
            if (o->__proto__) {
                Jsi_HashEntry *hPtr;
                Jsi_HashSearch search;
                            
                for (hPtr = Jsi_HashSearchFirst(interp->thisTbl,&search); hPtr != NULL;
                    hPtr = Jsi_HashSearchNext(&search))
                    if (Jsi_HashValueGet(hPtr) == o->__proto__)
                        return (char*)Jsi_HashKeyGet(hPtr);
            }
            
            return "userobj";
            break;
            //return Jsi_ObjGetType(interp, v->d.obj);
        default:
            break;
     }
     return "";
}

extern Jsi_otype Jsi_ObjTypeGet(Jsi_Obj *obj)
{
    return obj->ot;
}

const char *Jsi_ValueTypeStr(Jsi_Interp *interp, Jsi_Value *v)
{
    switch (v->vt) {
        case JSI_VT_BOOL: return "boolean"; break;
        case JSI_VT_UNDEF: return "undefined"; break;
        case JSI_VT_NULL: return "null"; break;
        case JSI_VT_NUMBER: return "number"; break;
        case JSI_VT_STRING: return "string"; break;  
        case JSI_VT_VARIABLE: return "variable"; break;  
        case JSI_VT_OBJECT: return Jsi_ObjTypeStr(interp, v->d.obj);
    }
    return "";
}

/* For user defined object  "name", invokes "new"  with "arg" + "opts".  Returns userobj data pointer for C use. */
void *Jsi_CommandNewObj(Jsi_Interp *interp, const char *name, const char *arg1, const char *opts, const char *var) {
    char buf[JSI_BUFSIZ];
    if (arg1)
        snprintf(buf, sizeof(buf), "%s%snew %s('%s', %s);", var?var:"", var?"=":"return ", name, arg1, opts?opts:"null");
    else
        snprintf(buf, sizeof(buf), "%s%snew %s(%s);", var?var:"", var?"=":"return ", name, opts?opts:"null");
    int rc = Jsi_EvalString(interp, buf, 0);
    if (rc != JSI_OK)
        return NULL;
    Jsi_Value *vObj = interp->retValue;
    if (var)
        vObj = Jsi_NameLookup(interp, var);
    if (!vObj)
        return NULL;
    return Jsi_UserObjGetData(interp, vObj, NULL);
}

#endif

// List

Jsi_List *Jsi_ListNew(Jsi_Interp *interp, Jsi_Wide flags, Jsi_HashDeleteProc *freeProc)
{
    Jsi_List *list = (Jsi_List *)Jsi_Calloc(1, sizeof(Jsi_List));
    list->sig = JSI_SIG_LIST;
    list->opts.flags = flags;
    list->opts.freeHashProc = freeProc;
    list->opts.interp = interp;
    list->opts.mapType = JSI_MAP_LIST;
    list->opts.keyType = (Jsi_Key_Type)-1;
    return list;
}

Jsi_RC Jsi_ListConf(Jsi_List *listPtr, Jsi_MapOpts *opts, bool set)
{
    if (set) {
        listPtr->opts = *opts;
    } else {
        *opts = listPtr->opts;
    }
    return JSI_OK;
}

void Jsi_ListDelete(Jsi_List *list) {
    Jsi_ListClear(list);
    free(list);
}

void Jsi_ListClear(Jsi_List *list) {
    Jsi_ListEntry *l;
    while (list->head) {
        l = list->head;
        list->head = list->head->next;
        l->next = l->prev = NULL;
        if (list->opts.freeListProc && l->value)
            (list->opts.freeListProc)(list->opts.interp, l, l->value);
        Jsi_ListEntryDelete(l);
    }
    list->numEntries = 0;
}
 
Jsi_ListEntry* Jsi_ListPush(Jsi_List *list, Jsi_ListEntry *item, Jsi_ListEntry *before)
{
    Assert(item && list);
    if (item->list && (item->list->head == item || item->prev || item->next)) {
        Assert(list->opts.freeListProc == item->list->opts.freeListProc);
        Jsi_ListPop(item->list, item);
    }
        
    if (!item->list)
         item->list = list;
    else if (list != item->list) {
        Assert(list->opts.freeListProc == item->list->opts.freeListProc);
        item->list = list;
    }
    if (!list->head) {
        list->head = list->tail = item;
    } else if (item == list->head) {
        assert(0);
    } else if (before == NULL) {
        item->prev = list->tail;
        list->tail->next = item;
        list->tail = item;
    } else if (before == list->head) {
        item->next = list->head;
        list->head->prev = item;
        list->head = item;
    } else {
        item->next = before;
        item->prev = before->prev;
        before->prev->next = item;
        before->prev = item;
    }
    list->numEntries++;
    item->list = list;
    return item;
}
 
Jsi_ListEntry* Jsi_ListPop(Jsi_List *list, Jsi_ListEntry *item)
{
    Assert(item && list->head && list->tail && item->list);
    SIGASSERT(list, LIST);
    SIGASSERT(item, LISTENTRY);
    if (item == list->head) {
        if (list->head == list->tail)
            list->head = list->tail = NULL;
        else
            list->head = list->head->next;
    }
    else if (item == list->tail) {
        list->tail = list->tail->prev;
        list->tail->next = NULL;
    } else {
        item->prev->next = item->next;
        if (item->next)
            item->next->prev = item->prev;
    }
    list->numEntries--;
    item->next = item->prev = NULL;
    return item;
}

Jsi_ListEntry *Jsi_ListEntryNew(Jsi_List* list, const void *value, Jsi_ListEntry *before) {
    SIGASSERT(list, LIST);
    Jsi_ListEntry *l = (Jsi_ListEntry*)Jsi_Calloc(1, sizeof(Jsi_ListEntry));
    l->sig = JSI_SIG_LISTENTRY;
    l->typ = JSI_MAP_LIST;
    l->list = list;
    l->value = (void*)value;
    Jsi_ListPush(list, l, before);
    return l;
}

int Jsi_ListEntryDelete(Jsi_ListEntry *l) {
    SIGASSERT(l, LISTENTRY);
    if (l->next || l->prev)
        Jsi_ListPop(l->list, l);
    Jsi_Free(l);
    return 1;
}

Jsi_ListEntry* Jsi_ListSearchFirst (Jsi_List *list, Jsi_ListSearch *searchPtr, int flags)
{
    SIGASSERT(list, LIST);
    searchPtr->flags = flags;
    Jsi_ListEntry *lptr;
    if (flags & JSI_LIST_REVERSE) {
        lptr = Jsi_ListGetBack(list);
        searchPtr->nextEntryPtr = (lptr?Jsi_ListEntryPrev(lptr):NULL);
    } else {
        lptr = Jsi_ListGetFront(list);
        searchPtr->nextEntryPtr = (lptr?Jsi_ListEntryNext(lptr):NULL);
    }
    return lptr;
}

Jsi_ListEntry* Jsi_ListSearchNext (Jsi_ListSearch *searchPtr)
{
    Jsi_ListEntry *lptr = searchPtr->nextEntryPtr;
    searchPtr->nextEntryPtr = (lptr?(searchPtr->flags & JSI_LIST_REVERSE ? Jsi_ListEntryPrev(lptr): Jsi_ListEntryNext(lptr)):NULL);
    return lptr;
}


uint Jsi_ListSize(Jsi_List *list) {
    SIGASSERT(list, LIST);
    return list->numEntries;
}

void* Jsi_ListValueGet(Jsi_ListEntry *l) {
    SIGASSERT(l, LISTENTRY);
    return l?l->value:NULL;
}
void Jsi_ListValueSet(Jsi_ListEntry *l, const void *value) {
    SIGASSERTV(l, LISTENTRY);
    l->value = (void*)value;
}


// Map

Jsi_Map* Jsi_MapNew (Jsi_Interp *interp, Jsi_Map_Type listType, Jsi_Key_Type keyType, Jsi_MapDeleteProc *freeProc)
{
    Jsi_Map *lPtr, lval = {.sig=JSI_SIG_MAP};
    lval.typ = listType;
    switch (listType) {
        case JSI_MAP_HASH: lval.v.hash = Jsi_HashNew(interp, keyType, (Jsi_HashDeleteProc*)freeProc); break;
        case JSI_MAP_TREE: lval.v.tree = Jsi_TreeNew(interp, keyType, (Jsi_TreeDeleteProc*)freeProc); break;
        case JSI_MAP_LIST: lval.v.list = Jsi_ListNew(interp, keyType, (Jsi_HashDeleteProc*)freeProc); break;
        default: return NULL;
    }
    if (!lval.v.hash) return NULL;
    lPtr = (Jsi_Map*)Jsi_Malloc(sizeof(*lPtr));
    *lPtr = lval;
    return lPtr;
}

Jsi_RC Jsi_MapConf(Jsi_Map *mapPtr, Jsi_MapOpts *opts, bool set)
{
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return Jsi_HashConf(mapPtr->v.hash, opts, set);
        case JSI_MAP_TREE: return Jsi_TreeConf(mapPtr->v.tree, opts, set);
        case JSI_MAP_LIST: return Jsi_ListConf(mapPtr->v.list, opts, set);
        case JSI_MAP_NONE: break;
    }
    return JSI_ERROR;
}

void Jsi_MapClear (Jsi_Map *mapPtr) {
    SIGASSERTV(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: Jsi_HashClear(mapPtr->v.hash); break;
        case JSI_MAP_TREE: Jsi_TreeClear(mapPtr->v.tree); break;
        case JSI_MAP_LIST: Jsi_ListClear(mapPtr->v.list); break;
        default: return;
    }
}

void Jsi_MapDelete (Jsi_Map *mapPtr) {
    SIGASSERTV(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: Jsi_HashDelete(mapPtr->v.hash); break;
        case JSI_MAP_TREE: Jsi_TreeDelete(mapPtr->v.tree); break;
        case JSI_MAP_LIST: Jsi_ListDelete(mapPtr->v.list); break;
        default: return;
    }
    Jsi_Free(mapPtr);
}
Jsi_MapEntry* Jsi_MapSet(Jsi_Map *mapPtr, const void *key, const void *value){
    SIGASSERT(mapPtr, MAP);
    Jsi_MapEntry* mptr = NULL;
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: mptr = (Jsi_MapEntry*)Jsi_HashSet(mapPtr->v.hash, (void*)key, (void*)value); break;
        case JSI_MAP_TREE: mptr = (Jsi_MapEntry*)Jsi_TreeSet(mapPtr->v.tree, (void*)key, (void*)value); break;
        case JSI_MAP_LIST: {
            mptr = Jsi_MapEntryNew(mapPtr, key, NULL);
            Jsi_MapValueSet(mptr, (void*)value);
            break;
        }
        case JSI_MAP_NONE: break;
    }
    return mptr;
}
void* Jsi_MapGet(Jsi_Map *mapPtr, const void *key, int flags){
    SIGASSERT(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return Jsi_HashGet(mapPtr->v.hash, (void*)key, flags);
        case JSI_MAP_TREE: return Jsi_TreeGet(mapPtr->v.tree, (void*)key, flags);
        case JSI_MAP_LIST: {
            Jsi_ListEntry* lptr = (key == NULL? Jsi_ListGetFront(mapPtr->v.list) : Jsi_ListGetBack(mapPtr->v.list));
            if (lptr)
                return Jsi_ListValueGet(lptr);
            break;
        }
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
bool Jsi_MapUnset(Jsi_Map *mapPtr, const void *key){
    SIGASSERT(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return Jsi_HashUnset(mapPtr->v.hash, (void*)key);
        case JSI_MAP_TREE: return Jsi_TreeUnset(mapPtr->v.tree, (void*)key);
        case JSI_MAP_LIST: {
            /*Jsi_ListEntry* lptr = (key == NULL? Jsi_ListGetFront(mapPtr->v.list) : Jsi_ListGetBack(mapPtr->v.list));
            if (lptr)
                return Jsi_ListUnset(lptr);*/
            break;
        }
        case JSI_MAP_NONE: break;
    }
    return false;
}
static int jsi_GetListType(Jsi_MapEntry *h) {
    Jsi_HashEntry *hPtr =(Jsi_HashEntry *)h;
    return hPtr->typ;
}
void* Jsi_MapKeyGet(Jsi_MapEntry *h, int flags){
    switch (jsi_GetListType(h)) {
        case JSI_MAP_HASH: return Jsi_HashKeyGet((Jsi_HashEntry*)h);
        case JSI_MAP_TREE: return Jsi_TreeKeyGet((Jsi_TreeEntry*)h);
        case JSI_MAP_LIST: break;
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
#ifndef JSI_LITE_ONLY
Jsi_RC Jsi_MapKeysDump(Jsi_Interp *interp, Jsi_Map *mapPtr, Jsi_Value **ret, int flags){
    SIGASSERT(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return Jsi_HashKeysDump(interp, mapPtr->v.hash, ret, flags);
        case JSI_MAP_TREE: return Jsi_TreeKeysDump(interp, mapPtr->v.tree, ret, flags);
        case JSI_MAP_LIST: break; // TODO: dump numbers?
        case JSI_MAP_NONE: break;
    }
    return JSI_ERROR;
}
#endif
void* Jsi_MapValueGet(Jsi_MapEntry *h){
    switch (jsi_GetListType(h)) {
        case JSI_MAP_HASH: return Jsi_HashValueGet((Jsi_HashEntry*)h);
        case JSI_MAP_TREE: return Jsi_TreeValueGet((Jsi_TreeEntry*)h);
        case JSI_MAP_LIST: return Jsi_ListValueGet((Jsi_ListEntry*)h);
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
void Jsi_MapValueSet(Jsi_MapEntry *h, const void *value){
    switch (jsi_GetListType(h)) {
        case JSI_MAP_HASH: return Jsi_HashValueSet((Jsi_HashEntry*)h, (void*)value);
        case JSI_MAP_TREE: return Jsi_TreeValueSet((Jsi_TreeEntry*)h, (void*)value);
        case JSI_MAP_LIST: return Jsi_ListValueSet((Jsi_ListEntry*)h, (void*)value);
        case JSI_MAP_NONE: break;
    }
}
Jsi_MapEntry* Jsi_MapEntryFind (Jsi_Map *mapPtr, const void *key){
    SIGASSERT(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return (Jsi_MapEntry*)Jsi_HashEntryFind(mapPtr->v.hash, key);
        case JSI_MAP_TREE: return (Jsi_MapEntry*)Jsi_TreeEntryFind(mapPtr->v.tree, key);
        case JSI_MAP_LIST:
            return (Jsi_MapEntry*) (key == NULL? Jsi_ListGetFront(mapPtr->v.list) : Jsi_ListGetBack(mapPtr->v.list));
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
Jsi_MapEntry* Jsi_MapEntryNew (Jsi_Map *mapPtr, const void *key, bool *isNew){
    SIGASSERT(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return (Jsi_MapEntry*)Jsi_HashEntryNew(mapPtr->v.hash, key, isNew);
        case JSI_MAP_TREE: return (Jsi_MapEntry*)Jsi_TreeEntryNew(mapPtr->v.tree, key, isNew);
        case JSI_MAP_LIST: {
            Jsi_ListEntry *lptr = Jsi_ListEntryNew(mapPtr->v.list, NULL, (key?mapPtr->v.list->head:NULL));
            if (isNew) *isNew = 1;
            return (Jsi_MapEntry*)lptr;
        }
        break;
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
int Jsi_MapEntryDelete (Jsi_MapEntry *entryPtr){
    switch (jsi_GetListType(entryPtr)) {
        case JSI_MAP_HASH: return Jsi_HashEntryDelete((Jsi_HashEntry*)entryPtr);
        case JSI_MAP_TREE: return Jsi_TreeEntryDelete((Jsi_TreeEntry*)entryPtr);
        case JSI_MAP_LIST: {
            Jsi_ListEntry *lptr = (Jsi_ListEntry*)entryPtr;
            Jsi_ListPop(lptr->list, lptr);
            Jsi_ListEntryDelete(lptr);
            return 1;
        }
    }
    return JSI_OK;
}
Jsi_MapEntry* Jsi_MapSearchFirst (Jsi_Map *mapPtr, Jsi_MapSearch *searchPtr, int flags){
    SIGASSERT(mapPtr, MAP);
    searchPtr->typ = mapPtr->typ;
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return (Jsi_MapEntry*)Jsi_HashSearchFirst(mapPtr->v.hash, &searchPtr->v.hash);
        case JSI_MAP_TREE: return (Jsi_MapEntry*)Jsi_TreeSearchFirst(mapPtr->v.tree, &searchPtr->v.tree, flags, NULL);
        case JSI_MAP_LIST: return (Jsi_MapEntry*)Jsi_ListSearchFirst(mapPtr->v.list, &searchPtr->v.list, flags);
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
Jsi_MapEntry* Jsi_MapSearchNext (Jsi_MapSearch *searchPtr){
    switch (searchPtr->typ) {
        case JSI_MAP_HASH: return (Jsi_MapEntry*)Jsi_HashSearchNext(&searchPtr->v.hash);
        case JSI_MAP_TREE: return (Jsi_MapEntry*)Jsi_TreeSearchNext(&searchPtr->v.tree);
        case JSI_MAP_LIST: return (Jsi_MapEntry*)Jsi_ListSearchNext(&searchPtr->v.list);
        case JSI_MAP_NONE: break;
    }
    return NULL;
}
void Jsi_MapSearchDone (Jsi_MapSearch *searchPtr){
    switch (searchPtr->typ) {
        case JSI_MAP_HASH: break;
        case JSI_MAP_TREE: Jsi_TreeSearchDone(&searchPtr->v.tree); break;
        case JSI_MAP_LIST: break;
        case JSI_MAP_NONE: break;
    }
}
uint Jsi_MapSize(Jsi_Map *mapPtr) {
    SIGASSERT(mapPtr, MAP);
    switch (mapPtr->typ) {
        case JSI_MAP_HASH: return Jsi_HashSize(mapPtr->v.hash);
        case JSI_MAP_TREE: return Jsi_TreeSize(mapPtr->v.tree);
        case JSI_MAP_LIST: return Jsi_ListSize(mapPtr->v.list);
        case JSI_MAP_NONE: break;
    }
    return -1;
}


#ifndef JSI_OMIT_THREADS

#ifdef __WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif

typedef struct Jsi_Mutex {
    int flags;
    int lockTimeout;
    int threadErrCnt;
#ifdef __WIN32
    CRITICAL_SECTION mtx;
#else
    pthread_mutex_t mtx;
#endif
} Jsi_Mutex;

#ifdef __WIN32
#include <windows.h>

static Jsi_RC MutexLock(Jsi_Interp *interp, Jsi_Mutex* mtx) {
    int timeout = mtx->lockTimeout;
    if (interp && timeout<0)
        timeout = interp->lockTimeout;
    if (timeout<=0)
        EnterCriticalSection(&mtx->mtx);
    else {
        int cnt = timeout;
        while (cnt-- >= 0) {
            if (TryEnterCriticalSection(&mtx->mtx))
                return JSI_OK;
            usleep(1000);
        }
        Jsi_LogError("lock timed out");
        if (interp)
            interp->threadErrCnt++;
        mtx->threadErrCnt++;
        return JSI_ERROR;
    }
    return JSI_OK;
}
static void MutexUnlock(Jsi_Mutex* mtx) { LeaveCriticalSection(&mtx->mtx); }
static void MutexInit(Jsi_Mutex *mtx) {  InitializeCriticalSection(&mtx->mtx); }

static void MutexDone(Jsi_Mutex *mtx) { DeleteCriticalSection(&mtx->mtx); }
#else /* ! __WIN32 */

#include <pthread.h>
static Jsi_RC MutexLock(Jsi_Interp *interp, Jsi_Mutex *mtx) {
    int timeout = mtx->lockTimeout;
    if (interp && timeout<0)
        timeout = interp->lockTimeout;
    if (timeout<=0)
        pthread_mutex_lock(&mtx->mtx);
    else {
        struct timespec ts;
        ts.tv_sec = timeout/1000;
        ts.tv_nsec = 1000 * (timeout%1000);
        int rc = pthread_mutex_timedlock(&mtx->mtx, &ts);
        if (rc != 0) {
            Jsi_LogError("lock timed out");
            if (interp)
                interp->threadErrCnt++;
            mtx->threadErrCnt++;
            return JSI_ERROR;
        }
    }
    return JSI_OK;
}
static void MutexUnlock(Jsi_Mutex *mtx) { pthread_mutex_unlock(&mtx->mtx); }
static void MutexInit(Jsi_Mutex *mtx) {
    pthread_mutexattr_t Attr;
    pthread_mutexattr_init(&Attr);
    if (mtx->flags & JSI_MUTEX_RECURSIVE)
        pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mtx->mtx, &Attr);
}

static void MutexDone(Jsi_Mutex *mtx) { pthread_mutex_destroy(&mtx->mtx); }
#endif /* ! __WIN32 */

Jsi_RC Jsi_MutexLock(Jsi_Interp *interp, Jsi_Mutex *mtx) { if (interp) interp->lockRefCnt++; return MutexLock(interp, mtx);}
void Jsi_MutexUnlock(Jsi_Interp *interp, Jsi_Mutex *mtx) { MutexUnlock(mtx); if (interp) interp->lockRefCnt--; }
Jsi_Mutex* Jsi_MutexNew(Jsi_Interp *interp, int timeout, int flags) {
     Jsi_Mutex *mtx = (Jsi_Mutex *)Jsi_Calloc(1,sizeof(Jsi_Mutex));
     mtx->lockTimeout = timeout;
     mtx->flags = flags;
     MutexInit(mtx);
     return mtx;
}
void Jsi_MutexDelete(Jsi_Interp *interp, Jsi_Mutex *mtx) { MutexDone(mtx); Jsi_Free(mtx);}
//void Jsi_MutexInit(Jsi_Interp *interp, Jsi_Mutex *mtx) { MutexInit(mtx); }
void* Jsi_InterpThread(Jsi_Interp *interp) { return interp->threadId; }
void* Jsi_CurrentThread(void) {
#ifdef __WIN32
    return (void*)(uintptr_t)GetCurrentThreadId();
#else
    return (void*)pthread_self();
#endif
}

#else /* ! JSI_OMIT_THREADS */
Jsi_RC Jsi_MutexLock(Jsi_Interp *interp, Jsi_Mutex *mtx) { return JSI_OK; }
void Jsi_MutexUnlock(Jsi_Interp *interp, Jsi_Mutex *mtx) { }
Jsi_Mutex* Jsi_MutexNew(Jsi_Interp *interp, int timeout, int flags) { return NULL; }
void Jsi_MutexDelete(Jsi_Interp *interp, Jsi_Mutex *mtx) { }
void* Jsi_CurrentThread(void) { return NULL; }
void* Jsi_InterpThread(Jsi_Interp *interp) { return NULL; }
#endif

Jsi_Number Jsi_Version(void) {
    Jsi_Number d = JSI_VERSION;
    return d;
}

static const char *JsiCharsetMatch(const char *pattern, int c, int flags)
{
    int inot = 0;
    int pchar;
    int match = 0;
    int nocase = 0;

    if (flags & JSI_CMP_NOCASE) {
        nocase++;
        c = toupper(c);
    }

    if (flags & JSI_CMP_CHARSET_SCAN) {
        if (*pattern == '^') {
            inot++;
            pattern++;
        }

        /* Special case. If the first char is ']', it is part of the set */
        if (*pattern == ']') {
            goto first;
        }
    }

    while (*pattern && *pattern != ']') {
        /* Exact match */
        if (pattern[0] == '\\') {
first:
            pattern += Jsi_UtfToUniCharCase(pattern, &pchar, nocase);
        }
        else {
            /* Is this a range? a-z */
            int start;
            int end;
            pattern += Jsi_UtfToUniCharCase(pattern, &start, nocase);
            if (pattern[0] == '-' && pattern[1]) {
                /* skip '-' */
                pattern += Jsi_UtfToUniChar(pattern, &pchar);
                pattern += Jsi_UtfToUniCharCase(pattern, &end, nocase);

                /* Handle reversed range too */
                if ((c >= start && c <= end) || (c >= end && c <= start)) {
                    match = 1;
                }
                continue;
            }
            pchar = start;
        }

        if (pchar == c) {
            match = 1;
        }
    }
    if (inot) {
        match = !match;
    }

    return match ? pattern : NULL;
}


/* Split on char, or whitespace if ch==0. */
static void SplitChar(const char *str, int *argcPtr,
              char ***argvPtr, char ch, Jsi_DString *dStr)
{
    char *cp, *ep, *p, **argv;
    int cnt = 1, len, i;

    len = Jsi_Strlen(str);
    cp = (char*)str;
    while (*cp) {
        if (ch)
            cp = Jsi_Strchr(cp,ch);
        else {
            while (*cp && !isspace(*cp))
                cp++;
        }
        if (cp == NULL || *cp == 0) break;
        cp++;
        cnt++;
    }
    //argv = (char**)Jsi_Calloc(1,(sizeof(char*)*(cnt+3) + sizeof(char)*(len+6)));
    Jsi_DSSetLength(dStr, (sizeof(char*)*(cnt+3) + sizeof(char)*(len+6)));
    argv = (char**)Jsi_DSValue(dStr);
    *argvPtr = argv;
    *argcPtr = cnt;
    p = (char*)&(argv[cnt+2]);
    argv[cnt+1] = p;
    Jsi_Strcpy(p, str);
    cp = p;
    i = 0;
    argv[i++] = p;
    while (*cp) {
        if (ch)
            ep = Jsi_Strchr(cp,ch);
        else {
            ep = cp;
            while (*ep && !isspace(*ep))
                ep++;
        }
        if (ep == NULL || *ep == 0) break;
        *ep = 0;
        cp = ep+1;
        argv[i++] = cp;
    }
    argv[cnt] = NULL;
}

Jsi_RC
Jsi_GetIndex( Jsi_Interp *interp, const char *str,
    const char **tablePtr, const char *msg, int flags,
    int *indexPtr)
{
  const char *msg2 = "unknown ";
  char **cp, *c;
  int cond, index = -1, slen, i, dup = 0;
  int exact = (flags & JSI_CMP_EXACT);
  int nocase = (flags & JSI_CMP_NOCASE);
  slen = Jsi_Strlen(str);
 /* if (slen==0) 
        return Jsi_LogError("empty option %s %s", msg, str);*/
  cp = (char**)tablePtr;
  i = -1;
  while (*cp != 0) {
    i++;
    c = *cp;
    if (c[0] != str[0]) { cp++; continue; }
    if (!nocase)
        cond = (exact ? Jsi_Strcmp(c,str) : Jsi_Strncmp(c,str,slen));
    else {
        cond = (exact ? Jsi_Strncasecmp(c,str, -1) : Jsi_Strncasecmp(c,str,slen));
    }
    if (cond == 0) {
      if (index<0) {
        index = i;
      } else {
        dup = 1;
        break;
      }
    }
    cp++;
  }
  if (index >= 0 && dup == 0) {
    *indexPtr = index;
    return JSI_OK;
  }
  if (exact && (dup || index<=0)) {
    if (interp != NULL) {
      msg2 = (index>=0? "unknown ":"duplicate ");
    }
    goto err;
  }
  cp = (char**)tablePtr;
  i = -1;
  dup = 0;
  index = -1;
  while (*cp != 0) {
    i++;
    c = *cp;
    if (c[0] == str[0] && Jsi_Strncmp(c,str, slen) == 0) {
      if (index<0) {
        index = i;
        if (slen == (int)Jsi_Strlen(c))
            break;
      } else {
        if (interp != NULL) {
          msg2 = "ambiguous ";
        }
        goto err;
      }
    }
    cp++;
  }
  if (index >= 0 && dup == 0) {
    *indexPtr = index;
    return JSI_OK;
  }
err:
  if (interp != NULL) {
    Jsi_DString dStr = {};
    Jsi_DSAppend(&dStr, msg2, msg, " \"", str, "\" not one of: ", NULL);
    cp = (char**)tablePtr;
    while (*cp != 0) {
      c = *cp;
      Jsi_DSAppend(&dStr, c, NULL);
      Jsi_DSAppend(&dStr, " ", NULL);
      cp++;
    }
    Jsi_LogError("%s", Jsi_DSValue(&dStr));
    Jsi_DSFree(&dStr);
  }
  return JSI_ERROR;
}

bool Jsi_GlobMatch(const char *pattern, const char *string, int nocase)
{
    int c;
    int pchar;
    while (*pattern) {
        switch (pattern[0]) {
            case '*':
                while (pattern[1] == '*') {
                    pattern++;
                }
                pattern++;
                if (!pattern[0]) {
                    return 1;   /* match */
                }
                while (*string) {
                    if (Jsi_GlobMatch(pattern, string, nocase))
                        return 1;       /* match */
                    string += Jsi_UtfToUniChar(string, &c);
                }
                return 0;       /* no match */

            case '?':
                string += Jsi_UtfToUniChar(string, &c);
                break;

            case '[': {
                    string += Jsi_UtfToUniChar(string, &c);
                    pattern = JsiCharsetMatch(pattern + 1, c, nocase ? JSI_CMP_NOCASE : 0);
                    if (!pattern) {
                        return 0;
                    }
                    if (!*pattern) {
                        /* Ran out of pattern (no ']') */
                        continue;
                    }
                    break;
                }
            case '\\':
                if (pattern[1]) {
                    pattern++;
                }
                /* fall through */
            default:
                string += Jsi_UtfToUniCharCase(string, &c, nocase);
                Jsi_UtfToUniCharCase(pattern, &pchar, nocase);
                if (pchar != c) {
                    return 0;
                }
                break;
        }
        pattern += Jsi_UtfToUniCharCase(pattern, &pchar, nocase);
        if (!*string) {
            while (*pattern == '*') {
                pattern++;
            }
            break;
        }
    }
    if (!*pattern && !*string) {
        return 1;
    }
    return 0;
}

Jsi_Stack* Jsi_StackNew(void)
{
    Jsi_Stack *stack = (Jsi_Stack*)Jsi_Calloc(1, sizeof(Jsi_Stack));
    return stack;
}

void Jsi_StackFree(Jsi_Stack *stack)
{
    Jsi_Free(stack->vector);
    Jsi_Free(stack);
}

int Jsi_StackSize(Jsi_Stack *stack)
{
    return stack->len;
}

void Jsi_StackPush(Jsi_Stack *stack, void *element)
{
    int neededLen = stack->len + 1;

    if (neededLen > stack->maxlen) {
        stack->maxlen = neededLen < 20 ? 20 : neededLen * 2;
        stack->vector = (void**)Jsi_Realloc(stack->vector, sizeof(void *) * stack->maxlen);
    }
    stack->vector[stack->len] = element;
    stack->len++;
}

void *Jsi_StackPop(Jsi_Stack *stack)
{
    if (stack->len == 0)
        return NULL;
    stack->len--;
    return stack->vector[stack->len];
}

void *Jsi_StackUnshift(Jsi_Stack *stack)
{
    if (stack->len == 0)
        return NULL;
    stack->len--;
    void *rc = stack->vector[0];
    memmove(stack->vector, stack->vector+1, sizeof(void*)*stack->len);
    return rc;
}

void *Jsi_StackPeek(Jsi_Stack *stack)
{
    if (stack->len == 0)
        return NULL;
    return stack->vector[stack->len - 1];
}

void *Jsi_StackHead(Jsi_Stack *stack)
{
    if (stack->len == 0)
        return NULL;
    return stack->vector[0];
}

void Jsi_StackFreeElements(Jsi_Interp *interp, Jsi_Stack *stack, Jsi_DeleteProc *freeProc)
{
    int i;
    for (i = 0; i < stack->len; i++)
        freeProc(interp, stack->vector[i]);
    stack->len = 0;
}

typedef struct {
    void *data;
    Jsi_DeleteProc *delProc;
} AssocData;

/* Split on string. */
void Jsi_SplitStr(const char *str, int *argcPtr,
              char ***argvPtr, const char *ch, Jsi_DString *dStr)
{
    char *cp, *ep, *p, **argv;
    int cnt = 1, len, i, clen;
    if (!ch)
        ch = "";
    clen = Jsi_Strlen(ch);
    if (clen<=0)
        return SplitChar(str, argcPtr, argvPtr, *ch, dStr);
    len = Jsi_Strlen(str);
    cp = (char*)str;
    while (*cp) {
        cp = Jsi_Strstr(cp,ch);
 
        if (cp == NULL || *cp == 0) break;
        cp += clen;
        cnt++;
    }
    //argv = (char**)Jsi_Calloc(1,(sizeof(char*)*(cnt+3) + sizeof(char)*(len+6)));
    Jsi_DSSetLength(dStr, (sizeof(char*)*(cnt+3) + sizeof(char)*(len+6)));
    argv = (char**)Jsi_DSValue(dStr);
    *argvPtr = argv;
    *argcPtr = cnt;
    p = (char*)&(argv[cnt+2]);
    argv[cnt+1] = p;
    Jsi_Strcpy(p, str);
    cp = p;
    i = 0;
    argv[i++] = p;
    while (*cp) {
        ep = Jsi_Strstr(cp,ch);
        if (ep == NULL || *ep == 0) break;
        *ep = 0;
        cp = ep+clen;
        argv[i++] = cp;
    }
    argv[cnt] = NULL;
}

static Jsi_RC JsiCheckConversion(const char *str, const char *endptr)
{
    if (str[0] == '\0' || str == endptr) {
        return JSI_ERROR;
    }

    if (endptr[0] != '\0') {
        while (*endptr) {
            if (!isspace(UCHAR(*endptr))) {
                return JSI_ERROR;
            }
            endptr++;
        }
    }
    return JSI_OK;
}

static int JsiNumberBase(const char *str, int *base, int *sign)
{
    int i = 0;

    *base = 10;

    while (isspace(UCHAR(str[i]))) {
        i++;
    }

    if (str[i] == '-') {
        *sign = -1;
        i++;
    }
    else {
        if (str[i] == '+') {
            i++;
        }
        *sign = 1;
    }

    if (str[i] != '0') {
        /* base 10 */
        return 0;
    }

    /* We have 0<x>, so see if we can convert it */
    switch (str[i + 1]) {
        case 'x': case 'X': *base = 16; break;
        case 'o': case 'O': *base = 8; break;
        case 'b': case 'B': *base = 2; break;
        default: return 0;
    }
    i += 2;
    /* Ensure that (e.g.) 0x-5 fails to parse */
    if (str[i] != '-' && str[i] != '+' && !isspace(UCHAR(str[i]))) {
        /* Parse according to this base */
        return i;
    }
    /* Parse as base 10 */
    return 10;
}

/* Converts a number as per strtoull(..., 0) except leading zeros do *not*
 * imply octal. Instead, decimal is assumed unless the number begins with 0x, 0o or 0b
 */
static Jsi_Wide jsi_strtoull(const char *str, char **endptr)
{
#ifdef JSI__LONG_LONG
    int sign;
    int base;
    int i = JsiNumberBase(str, &base, &sign);

    if (base != 10) {
        Jsi_Wide value = strtoull(str + i, endptr, base);
        if (endptr == NULL || *endptr != str + i) {
            return value * sign;
        }
    }

    /* Can just do a regular base-10 conversion */
    return strtoull(str, endptr, 10);
#else
    return (unsigned long)jsi_strtol(str, endptr);
#endif
}

static Jsi_Wide jsi_strtoul(const char *str, char **endptr)
{
#ifdef JSI__LONG_LONG
    int sign;
    int base;
    int i = JsiNumberBase(str, &base, &sign);

    if (base != 10) {
        Jsi_Wide value = strtoul(str + i, endptr, base);
        if (endptr == NULL || *endptr != str + i) {
            return value * sign;
        }
    }

    /* Can just do a regular base-10 conversion */
    return strtoul(str, endptr, 10);
#else
    return (unsigned long)jsi_strtol(str, endptr);
#endif
}


Jsi_RC Jsi_GetWide(Jsi_Interp* interp, const char *string, Jsi_Wide *widePtr, int base)
{
    char *endptr;

    if (base) {
        *widePtr = strtoull(string, &endptr, base);
    }
    else {
        *widePtr = jsi_strtoull(string, &endptr);
    }

    return JsiCheckConversion(string, endptr);
}

Jsi_RC Jsi_GetInt(Jsi_Interp* interp, const char *string, int *n, int base)
{
    char *endptr;
    if (base) {
        *n = strtoul(string, &endptr, base);
    }
    else {
        *n = (int)jsi_strtoul(string, &endptr);
    }
    return JsiCheckConversion(string, endptr);
}

Jsi_RC Jsi_GetDouble(Jsi_Interp* interp, const char *string, Jsi_Number *n)
{
    char *endptr;

    /* Callers can check for underflow via ERANGE */
    errno = 0;

    *n = strtod(string, &endptr);

    return JsiCheckConversion(string, endptr);
}

Jsi_RC Jsi_GetBool(Jsi_Interp* interp, const char *string, bool *n)
{
    int len = Jsi_Strlen(string);
    if (len && (Jsi_Strncasecmp(string, "true", len)==0 && len<=4)) {
        *n = 1;
        return JSI_OK;
    }
    if (len && (Jsi_Strncasecmp(string, "false", len)==0 && len<=5)) {
        *n = 0;
        return JSI_OK;
    }
    return JSI_ERROR;
}

/* Converts a hex character to its integer value */
char jsi_fromHexChar(char ch) {
    return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10;
}

/* Converts an integer value to its hex character*/
char jsi_toHexChar(char code) {
    static char hex[] = "0123456789abcdef";
    return hex[code & 15];
}

void jsi_ToHexStr(const uchar *indata, int dlen, char *out) {
    static char hex[] = "0123456789abcdef";
    int i, n=0;
    for (i=0; i<dlen; i++) {
        int c = indata[i];
        out[n++] = hex[(c>>4)&0xf];
        out[n++] = hex[c&0xf];
    }
    out[n] = 0;
}

static int jsi_FromHexStr(const char *in, uchar *outdata) {
    int n = 0;
    while (in[0] && in[1]) {
        if (!isxdigit(in[0]) || isxdigit(in[0]))
            return -1;
        outdata[n++] = jsi_fromHexChar(in[0]) << 4 | jsi_fromHexChar(in[1]);
        in+=2;
    }
    return n;
}


int Jsi_HexStr(const uchar *data, int len, Jsi_DString *dStr, bool decode) {
    int olen = (decode?(len/2+1):(len*2+1));
    Jsi_DSSetLength(dStr, olen);
    if (!decode)
        return jsi_FromHexStr((const char*)data, (uchar*)Jsi_DSValue(dStr));
    jsi_ToHexStr((const uchar*)data, len, Jsi_DSValue(dStr));
    return olen-1;
}
#ifndef JSI_LITE_ONLY

void *Jsi_InterpGetData(Jsi_Interp *interp, const char *key, Jsi_DeleteProc **proc)
{
    Jsi_HashEntry *hPtr;
    AssocData *ptr;
    hPtr = Jsi_HashEntryFind(interp->assocTbl, key);
    if (!hPtr)
        return NULL;
    ptr = (AssocData *)Jsi_HashValueGet(hPtr);
    if (!ptr)
        return NULL;
    if (proc)
        *proc = ptr->delProc;
    return ptr->data;
}
void Jsi_InterpSetData(Jsi_Interp *interp, const char *key, void *data, Jsi_DeleteProc *proc)
{
    bool isNew;
    Jsi_HashEntry *hPtr;
    AssocData *ptr;
    hPtr = Jsi_HashEntryNew(interp->assocTbl, key, &isNew);
    if (!hPtr)
        return;
    if (isNew) {
        ptr = (AssocData *)Jsi_Calloc(1,sizeof(*ptr));
        Jsi_HashValueSet(hPtr, ptr);
    } else
        ptr = (AssocData *)Jsi_HashValueGet(hPtr);
    ptr->data = data;
    ptr->delProc = proc;
}

void jsi_DelAssocData(Jsi_Interp *interp, void *data) {
    AssocData *ptr = (AssocData *)data;
    if (!ptr) return;
    if (ptr->delProc)
        ptr->delProc(interp, ptr->data);
    Jsi_Free(ptr);
}

Jsi_RC Jsi_DeleteData(Jsi_Interp* interp, void *m)
{
    Jsi_Free(m);
    return JSI_OK;
}

void Jsi_InterpFreeData(Jsi_Interp *interp, const char *key)
{
    Jsi_HashEntry *hPtr;
    hPtr = Jsi_HashEntryFind(interp->assocTbl, key);
    if (!hPtr)
        return;
    Jsi_HashEntryDelete(hPtr);
}

Jsi_RC Jsi_GetStringFromValue(Jsi_Interp* interp, Jsi_Value *value, const char **n)
{
    if (!value)
        return JSI_ERROR;
    if (value->vt == JSI_VT_STRING)
    {
        *n = (const char*)value->d.s.str;
         return JSI_OK;
    }
    if (value->vt == JSI_VT_OBJECT && value->d.obj->ot == JSI_OT_STRING) {
        *n = value->d.obj->d.s.str;
        return JSI_OK;
    }
    Jsi_LogError("invalid string");
    return JSI_ERROR;
}

Jsi_RC Jsi_GetBoolFromValue(Jsi_Interp* interp, Jsi_Value *value, bool *n)
{
    if (!value)
        return JSI_ERROR;

    if (value->vt == JSI_VT_BOOL) {
        *n = value->d.val;
        return JSI_OK;
    }
    if (value->vt == JSI_VT_OBJECT && value->d.obj->ot == JSI_OT_BOOL) {
        *n = value->d.obj->d.val;
        return JSI_OK;
    }
    Jsi_LogError("invalid bool");
    return JSI_ERROR;
}


Jsi_RC Jsi_GetNumberFromValue(Jsi_Interp* interp, Jsi_Value *value, Jsi_Number *n)
{
    if (!value)
        return JSI_ERROR;

    if (value->vt == JSI_VT_NUMBER) {
        *n = value->d.num;
        return JSI_OK;
    }
    if (value->vt == JSI_VT_OBJECT && value->d.obj->ot == JSI_OT_NUMBER) {
        *n = value->d.obj->d.num;
        return JSI_OK;
    }
    if (interp)
        Jsi_LogError("invalid number");
    return JSI_ERROR;
}

Jsi_RC Jsi_GetIntFromValueBase(Jsi_Interp* interp, Jsi_Value *value, int *n, int base, int flags)
{
    int noMsg = (flags & JSI_NO_ERRMSG);
    /* TODO: inefficient to convert to double then back. */
    if (!value)
        return JSI_ERROR;
    Jsi_Number d = Jsi_ValueToNumberInt(interp, value, 1);
    if (!Jsi_NumberIsFinite(d))
    {
        if (!noMsg)
            Jsi_LogError("invalid number");
        return JSI_ERROR;
    }
    Jsi_ValueReset(interp,&value);
    Jsi_ValueMakeNumber(interp, &value, d);
    *n = (int)d;
    return JSI_OK;
}

Jsi_RC Jsi_GetIntFromValue(Jsi_Interp* interp, Jsi_Value *value, int *n)
{
    if (!Jsi_ValueIsNumber(interp, value)) 
        return Jsi_LogError("invalid number");
    return Jsi_GetIntFromValueBase(interp, value, n, 0, 0);
}

Jsi_RC Jsi_GetLongFromValue(Jsi_Interp* interp, Jsi_Value *value, long *n)
{
    /* TODO: inefficient to convert to double then back. */
    if (!value)
        return JSI_ERROR;
    if (!interp->strict)
        jsi_ValueToOInt32(interp, value);
    if (!Jsi_ValueIsNumber(interp, value))
    
        return Jsi_LogError("invalid number");
    *n = (long)(value->vt == JSI_VT_NUMBER ? value->d.num : value->d.obj->d.num);
    return JSI_OK;
}

Jsi_RC Jsi_GetWideFromValue(Jsi_Interp* interp, Jsi_Value *value, Jsi_Wide *n)
{
    if (!value)
        return JSI_ERROR;
    if (!interp->strict)
        jsi_ValueToOInt32(interp, value);
    if (!Jsi_ValueIsNumber(interp, value))
    
        return Jsi_LogError("invalid number");
    *n = (Jsi_Wide)(value->vt == JSI_VT_NUMBER ? value->d.num : value->d.obj->d.num);
    return JSI_OK;

}

Jsi_RC Jsi_GetDoubleFromValue(Jsi_Interp* interp, Jsi_Value *value, Jsi_Number *n)
{
    if (!value)
        return JSI_ERROR;
    if (!interp->strict)
        Jsi_ValueToNumber(interp, value);
    if (!Jsi_ValueIsNumber(interp, value))
    
        return Jsi_LogError("invalid number");
    *n = (value->vt == JSI_VT_NUMBER ? value->d.num : value->d.obj->d.num);
    return JSI_OK;
}

Jsi_RC
Jsi_ValueGetIndex( Jsi_Interp *interp, Jsi_Value *valPtr,
    const char **tablePtr, const char *msg, int flags, int *indexPtr)
{
    char *val = Jsi_ValueString(interp, valPtr, NULL);
    if (val == NULL) 
        return Jsi_LogError("expected string");
    return Jsi_GetIndex(interp, val, tablePtr, msg, flags, indexPtr);
}

#endif // JSI_LITE_ONLY
#ifndef JSI_LITE_ONLY
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif

/* Jsi_Obj constructor */
static Jsi_RC ObjectConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        /* new operator will do the rest */
        return JSI_OK;
    }
    
    if (Jsi_ValueGetLength(interp, args) <= 0) {
        Jsi_Obj *o = Jsi_ObjNew(interp);
        o->__proto__ = interp->Object_prototype;
        Jsi_ValueMakeObject(interp, ret, o);
        return JSI_OK;
    }
    Jsi_Value *v = Jsi_ValueArrayIndex(interp, args, 0);
    if (!v || v->vt == JSI_VT_UNDEF || v->vt == JSI_VT_NULL) {
        Jsi_Obj *o = Jsi_ObjNewType(interp, JSI_OT_OBJECT);
        Jsi_ValueMakeObject(interp, ret, o);
        return JSI_OK;
    }
    Jsi_ValueDup2(interp, ret, v);
    Jsi_ValueToObject(interp, *ret);
    return JSI_OK;
}

/* Function.prototype pointed to a empty function */
static Jsi_RC jsi_FunctionPrototypeConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return JSI_OK;
}

static Jsi_RC jsi_Function_constructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return Jsi_LogError("Calling Function is unsupported");
  /*  if (Jsi_FunctionIsConstructor(funcPtr)) {
        _this->d.obj->ot = JSI_OT_FUNCTION;
        return JSI_OK;
    }
    Jsi_Obj *o = Jsi_ObjNewType(interp, JSI_OT_FUNCTION);
    Jsi_ValueMakeObject(interp, ret, o);
    return JSI_OK;*/
}

// Guesstimate type based on default value.
int jsi_GetDefaultType(const char *cp) {
    if (isdigit(*cp) || *cp == '-' || *cp == '.') return JSI_TT_NUMBER;
    if (*cp == 'f' || *cp == 't') return JSI_TT_BOOLEAN;
    if (*cp == 'n') return JSI_TT_NULL;
    if (*cp == 'v') return JSI_TT_VOID;
    if (*cp == '\'' || *cp == '\"') return JSI_TT_STRING;
    return 0;
}

// Extract typechecking info from argStr for builtin Jsi_CmdSpec on first call
Jsi_ScopeStrs* jsi_ParseArgStr(Jsi_Interp *interp, const char *argStr)
{
    int i;
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    int argc;
    char **argv, *sname, *stype, *cp, *ap = NULL;
    Jsi_SplitStr(argStr, &argc, &argv, ",", &dStr);
    if (argc<=0)
        return NULL;
    Jsi_ScopeStrs *ss = jsi_ScopeStrsNew();
    for (i=0; i<argc; i++) {
        sname = argv[i];
        stype = NULL;
        ap = NULL;
        while (sname && *sname && isspace(*sname)) { sname++; }
        if ((cp=Jsi_Strchr(sname, '='))) {
            ap = cp+1;
            *cp = 0;
        }
        if ((cp=Jsi_Strchr(sname, ':'))) {
            stype = cp+1;
            *cp = 0;
            while (*stype && isspace(*stype)) { stype++; }
            if (*stype) {
                cp = stype+Jsi_Strlen(stype)-1;
                while (cp>=stype && isspace(*cp)) { *cp = 0; cp--; }
            }
        }
        int atyp = jsi_typeGet(interp, stype);
        if (ap) {
            int datyp = jsi_GetDefaultType(ap);
            if (datyp != JSI_TT_VOID)
                atyp |= datyp;
        }
        if (sname && *sname) {
            cp = sname+Jsi_Strlen(sname)-1;
            while (cp>=sname && isspace(*cp)) { *cp = 0; cp--; }
        }
        jsi_ScopeStrsPush(interp, ss, sname, atyp);
    }
    Jsi_DSFree(&dStr);
    return ss;
}

// Runtime function call checker.
Jsi_RC jsi_SharedArgs(Jsi_Interp *interp, Jsi_Value *args, Jsi_Func *func, int alloc)
{
    int i;
    Jsi_RC rc = JSI_OK, nrc = JSI_OK;
    Jsi_ScopeStrs *argnames;
    // Extract typechecking info from argStr for builtin commands on first call
    const char *argStr = (func->cmdSpec ? func->cmdSpec->argStr: NULL);
    if (alloc && func->type == FC_BUILDIN && func->callCnt == 0 && func->argnames==NULL
        && argStr && Jsi_Strchr(argStr, ':'))
        func->argnames = jsi_ParseArgStr(interp, argStr);
    argnames = func->argnames;
    int argc = Jsi_ValueGetLength(interp, args);
    if (alloc && (interp->typeCheck.all|interp->typeCheck.run) && jsi_RunFuncCallCheck(interp, func, argc, func->name, NULL, NULL, 0) != JSI_OK
        && (interp->typeCheck.strict || interp->typeCheck.error))
        nrc = JSI_ERROR;
    if (!argnames)
        return nrc;
    
    int addargs = func->callflags.bits.addargs;
    for (i = 0; i < argnames->count; ++i) {
        int n = i-addargs;
        const char *argkey = jsi_ScopeStrsGet(argnames, i);
        if (!argkey) break;
        
        Jsi_Value *dv = NULL, *v = Jsi_ValueArrayIndex(interp, args, i);
        if (!alloc) {
            if (func->type == FC_BUILDIN)
                continue;
            if (v==NULL  && i >= addargs) {
                v = argnames->args[n].defValue;
                if (v) {
                    dv = Jsi_ValueObjLookup(interp, args, argkey, 1);
                    if (dv)
                        v = dv;
                }
            }
            if (v)
                Jsi_DecrRefCount(interp, v);
        } else {
            if (v==NULL  && i >= addargs)
                dv = v = argnames->args[n].defValue;
            if (v && rc == JSI_OK && i >= addargs) {
                int typ = argnames->args[n].type;
                if ((typ && interp->typeCheck.run) || interp->typeCheck.all)
                    rc = jsi_ArgTypeCheck(interp, typ, v, "for argument", argkey, i+1, func, (dv!=NULL));
            }
            if (func->type == FC_BUILDIN)
                continue;
            if (v && dv)
                v = Jsi_ValueDup(interp, v);
            else if (v)
                Jsi_IncrRefCount(interp, v);
            else {
                v = Jsi_ValueNew(interp);
            }
            jsi_ValueObjSet(interp, args, argkey, v, JSI_OM_DONTENUM | JSI_OM_INNERSHARED, 1);
        }
    }
    return (nrc == JSI_ERROR?nrc:rc);
}

void jsi_SetCallee(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *tocall)
{
    if (interp->hasCallee) {
        Jsi_Value *callee = Jsi_ValueNew1(interp);
        Jsi_ValueCopy(interp, callee, tocall);
        Jsi_ValueInsert(interp, args, "\1callee\1", callee, JSI_OM_DONTENUM);
        Jsi_DecrRefCount(interp, callee);
    }
}

static Jsi_RC jsi_FuncBindCall(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_FuncObj *fo = funcPtr->fobj;
    Jsi_Value *nargs = args, *fargs = fo->bindArgs;
    int i, argc = Jsi_ValueGetLength(interp, args);
    int fargc = (fargs? Jsi_ValueGetLength(interp, fargs) : 0);

    if (fargc>0) {
        Jsi_Value *nthis = Jsi_ValueArrayIndex(interp, fargs, 0);
        if (nthis && !Jsi_ValueIsNull(interp, nthis))
            _this = nthis;
        if (fargc>1) {
            nargs = Jsi_ValueNewArray(interp, NULL, 0);
            for (i=1; i<fargc; i++)
                Jsi_ValueArrayPush(interp, nargs, Jsi_ValueArrayIndex(interp, fargs, i));
            for (i=0; i<argc; i++)
                Jsi_ValueArrayPush(interp, nargs, Jsi_ValueArrayIndex(interp, args, i));
            Jsi_IncrRefCount(interp, nargs);
        }
    }
    Jsi_RC rc = Jsi_FunctionInvoke(interp, fo->bindFunc, nargs, ret, _this);
    if (Jsi_InterpGone(interp))
        return JSI_ERROR;
    if (nargs != args)
        Jsi_DecrRefCount(interp, nargs);
    return rc;
}

static Jsi_RC jsi_FunctionBindCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *tocall = _this;
    if (!Jsi_ValueIsFunction(interp, tocall) || !tocall->d.obj->d.fobj) 
        return Jsi_LogError("can not execute expression, expression is not a function");
    
    Jsi_Value *oval = jsi_MakeFuncValue(interp, jsi_FuncBindCall, NULL, ret, NULL);
    Jsi_Obj *obj = oval->d.obj;
    Jsi_FuncObj *fo = obj->d.fobj;
    Jsi_Func *fstatic = tocall->d.obj->d.fobj->func;
    Jsi_ObjDecrRefCount(interp, obj);
    fo->bindArgs = Jsi_ValueDup(interp, args);
    fo->bindFunc = tocall;
    Jsi_IncrRefCount(interp, tocall);
    if (fstatic->callback == jsi_NoOpCmd)
        obj->isNoOp = 1;
    return JSI_OK;
}

Jsi_RC Jsi_FunctionCall(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret)
{
    Jsi_Value *tocall = _this;
    if (!Jsi_ValueIsFunction(interp, tocall)) 
        return Jsi_LogError("can not execute expression, expression is not a function");

    if (!tocall->d.obj->d.fobj) {   /* empty function */
        return JSI_OK;
    }
    
    /* func to call */
    Jsi_Func *funcPtr = tocall->d.obj->d.fobj->func;
    
    /* new this */
    Jsi_Value *fthis;
    Jsi_Value *arg1 = NULL;
    if ((arg1 = Jsi_ValueArrayIndex(interp, args, 0)) && !Jsi_ValueIsUndef(interp, arg1)
        && !Jsi_ValueIsNull(interp, arg1))
        fthis = Jsi_ValueDup(interp, arg1);
    else
        fthis = Jsi_ValueDup(interp, interp->Top_object);
    Jsi_ValueToObject(interp, fthis);
    
    /* prepare args */
    Jsi_ValueArrayShift(interp, args);
    Jsi_RC res = jsi_SharedArgs(interp, args, funcPtr, 1);
    
    if (res == JSI_OK) {
        jsi_InitLocalVar(interp, args, funcPtr);
        jsi_SetCallee(interp, args, tocall);
        
        if (funcPtr->type == FC_NORMAL) {
            res = jsi_evalcode(interp->ps, funcPtr, funcPtr->opcodes, tocall->d.obj->d.fobj->scope, 
                       args, fthis, ret);
        } else {
            res = funcPtr->callback(interp, args, fthis, ret, funcPtr);
        }
        funcPtr->callCnt++;
    }
    if (res == JSI_OK && funcPtr->retType)
        res = jsi_ArgTypeCheck(interp, funcPtr->retType, *ret, "returned from", funcPtr->name, 0, funcPtr, 0);
    jsi_SharedArgs(interp, args, funcPtr, 0);
    Jsi_DecrRefCount(interp, fthis);
    return res;
}

static Jsi_RC jsi_FunctionCallCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_FunctionIsConstructor(funcPtr)) 
        return Jsi_LogError("Execute call as constructor");
    
    return Jsi_FunctionCall(interp, args, _this, ret);
}


static Jsi_RC ObjectKeysCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
   int argc = Jsi_ValueGetLength(interp, args);
   Jsi_Value *val = _this;
   
   if (argc>0)
        val = Jsi_ValueArrayIndex(interp, args, 0);

    Jsi_RC rc = Jsi_ValueGetKeys(interp, val, *ret);
    if (rc != JSI_OK)
        Jsi_LogError("can not call Keys() with non-object");
    return rc;
}

Jsi_RC jsi_ObjectToStringCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_ValueIsString(interp, _this)) {
        Jsi_ValueCopy(interp, *ret, _this);
        return JSI_OK;
    }    
    int quote = JSI_OUTPUT_QUOTE;
    Jsi_DString dStr = {};
    Jsi_ValueGetDString(interp, _this, &dStr, quote);
    Jsi_ValueMakeStringDup(interp, ret, Jsi_DSValue(&dStr));
    Jsi_DSFree(&dStr);
    return JSI_OK;
}


Jsi_RC Jsi_FunctionApply(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret)
{
    int isalloc = 0;
    Jsi_Value *tocall = _this;
    if (!Jsi_ValueIsFunction(interp, tocall)) 
        return Jsi_LogError("can not execute expression, expression is not a function");

    if (!tocall->d.obj->d.fobj) {   /* empty function */
        return JSI_OK;
    }
    
    /* func to call */
    Jsi_Func *funcPtr = tocall->d.obj->d.fobj->func;
   /* if (funcPtr == NULL || funcPtr->callback == jsi_Function_constructor) 
        return Jsi_LogError("can not use apply to itself");*/
    
    /* new this */
    Jsi_Value *fthis;
    Jsi_Value *arg1 = NULL;
    if ((arg1 = Jsi_ValueArrayIndex(interp, args, 0)) && !Jsi_ValueIsUndef(interp, arg1)
        && !Jsi_ValueIsNull(interp, arg1))
        fthis = Jsi_ValueDup(interp, arg1);
    else
        fthis = Jsi_ValueDup(interp, interp->Top_object);
    Jsi_ValueToObject(interp, fthis);
    
    /* prepare args */
    Jsi_RC res = JSI_ERROR;
    Jsi_Value *fargs = Jsi_ValueArrayIndex(interp, args, 1);
    if (fargs) {
        if (fargs->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, fargs->d.obj)) {
            Jsi_LogError("second argument to Function.prototype.apply must be an array");
            goto done;
        }
    } else {
        isalloc = 1;
        fargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewType(interp, JSI_OT_ARRAY));
        Jsi_IncrRefCount(interp, fargs);
    }
    
    res = jsi_SharedArgs(interp, fargs, funcPtr, 1);
    if (res == JSI_OK) {
        jsi_InitLocalVar(interp, fargs, funcPtr);
        jsi_SetCallee(interp, fargs, tocall);
    
        if (funcPtr->type == FC_NORMAL) {
            res = jsi_evalcode(interp->ps, funcPtr, funcPtr->opcodes, tocall->d.obj->d.fobj->scope, 
                fargs, fthis, ret);
        } else {
            res = funcPtr->callback(interp, fargs, fthis, ret, funcPtr);
        }
        funcPtr->callCnt++;
    }
    if (res == JSI_OK && funcPtr->retType)
        res = jsi_ArgTypeCheck(interp, funcPtr->retType, *ret, "returned from", funcPtr->name, 0, funcPtr, 0);
    jsi_SharedArgs(interp, fargs, funcPtr, 0);
done:
    if (isalloc)
        Jsi_DecrRefCount(interp, fargs);
    Jsi_DecrRefCount(interp, fthis);
    return res;
}


static Jsi_RC ObjectValueOfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_ValueDup2(interp, ret, _this);
    return JSI_OK;
}

Jsi_RC ObjectMergeCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *v = Jsi_ValueArrayIndex(interp, args,0);
    if (!v || v->vt != JSI_VT_OBJECT || v->d.obj->ot != JSI_OT_OBJECT ||
        _this->vt != JSI_VT_OBJECT || _this->d.obj->ot != JSI_OT_OBJECT)
        return Jsi_LogError("expected object");
    Jsi_Obj *obj = Jsi_ObjNewType(interp, JSI_OT_OBJECT);
    Jsi_ValueMakeObject(interp, ret, obj);
    Jsi_TreeEntry *tPtr;
    Jsi_TreeSearch search;
    for (tPtr = Jsi_TreeSearchFirst(_this->d.obj->tree, &search, 0, NULL);
        tPtr; tPtr = Jsi_TreeSearchNext(&search)) {
        Jsi_Value *v = (Jsi_Value *)Jsi_TreeValueGet(tPtr);
        if (v && v->f.bits.dontenum == 0)
            Jsi_ObjInsert(interp, obj, (const char *)Jsi_TreeKeyGet(tPtr), v, 0);
    }
    Jsi_TreeSearchDone(&search);
    for (tPtr = Jsi_TreeSearchFirst(v->d.obj->tree, &search, 0, NULL);
        tPtr; tPtr = Jsi_TreeSearchNext(&search)) {
        Jsi_Value *v = (Jsi_Value *)Jsi_TreeValueGet(tPtr);
        if (v && v->f.bits.dontenum == 0)
            Jsi_ObjInsert(interp, obj, (const char *)Jsi_TreeKeyGet(tPtr), v, 0);
    }
    Jsi_TreeSearchDone(&search);

    return JSI_OK;
}

#if (JSI_HAS___PROTO__==1)
Jsi_RC jsi_GetPrototypeOfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *v = Jsi_ValueArrayIndex(interp, args,0);
    if (v->vt != JSI_VT_OBJECT)
        return JSI_ERROR;
    Jsi_ValueDup2(interp, ret, v->d.obj->__proto__);
    return JSI_OK;
}

Jsi_RC jsi_SetPrototypeOfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *v = Jsi_ValueArrayIndex(interp, args,0);
    Jsi_Value *a = Jsi_ValueArrayIndex(interp, args,1);
    if (v->vt != JSI_VT_OBJECT || a->vt != JSI_VT_OBJECT)
        return JSI_ERROR;
    v->d.obj->__proto__ = Jsi_ValueDup(interp, a);
    v->d.obj->clearProto = 1;
    return JSI_OK;
}
#endif

Jsi_RC jsi_HasOwnPropertyCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *v;
    char *key = Jsi_ValueArrayIndexToStr(interp, args,0, NULL);
    v = Jsi_ValueObjLookup(interp, _this, key, 0);
    Jsi_ValueMakeBool(interp, ret, (v != NULL));
    return JSI_OK;
}

static Jsi_RC ObjectIsPrototypeOfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *proto, *sproto, *v = Jsi_ValueArrayIndex(interp, args,0);
    int bval = 0;
    if (v->vt != JSI_VT_OBJECT || _this->vt != JSI_VT_OBJECT) {
        goto retval;
    }
    proto = _this->d.obj->__proto__;
    sproto = v->d.obj->__proto__;
    if (!proto)
        goto retval;
    while (sproto) {
        if ((bval=(sproto == proto)))
            break;
        if (sproto->vt != JSI_VT_OBJECT)
            break;
        sproto = sproto->d.obj->__proto__;
    }
retval:
    Jsi_ValueMakeBool(interp, ret, bval);
    return JSI_OK;
    
}

static Jsi_RC ObjectIsCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *v1 = Jsi_ValueArrayIndex(interp, args,0);
    Jsi_Value *v2 = Jsi_ValueArrayIndex(interp, args,1);
    Jsi_ValueMakeBool(interp, ret, Jsi_ValueIsEqual(interp, v1, v2));
    return JSI_OK;
}

static Jsi_RC ObjectCreateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (interp->subOpts.noproto) 
        return Jsi_LogError("inheritance is disabled in interp");
    Jsi_Obj *obj;
    Jsi_Value *proto = Jsi_ValueArrayIndex(interp, args,0);
    Jsi_Value *props = Jsi_ValueArrayIndex(interp, args,1);

    if (proto->vt != JSI_VT_NULL && proto->vt != JSI_VT_OBJECT) 
        return Jsi_LogError("arg 1 is not a proto object or null");
    if (props && (props->vt != JSI_VT_OBJECT || props->d.obj->ot != JSI_OT_OBJECT)) 
        return Jsi_LogError("arg 2 is not a properties object");
        
    Jsi_ValueMakeObject(interp, ret, obj=Jsi_ObjNew(interp));
    if (proto->vt == JSI_VT_OBJECT) {
        obj->__proto__ = proto;
        obj->clearProto = 1;
        Jsi_IncrRefCount(interp, proto);
    }
    if (props) {
        Jsi_Obj *pobj = props->d.obj;
        Jsi_TreeEntry *tPtr;
        Jsi_TreeSearch search;
        for (tPtr = Jsi_TreeSearchFirst(pobj->tree, &search, 0, NULL);
            tPtr; tPtr = Jsi_TreeSearchNext(&search)) {
            Jsi_Value *v = (Jsi_Value *)Jsi_TreeValueGet(tPtr);
            if (v && v->f.bits.dontenum == 0)
                Jsi_ObjInsert(interp, obj, (const char *)Jsi_TreeKeyGet(tPtr), v, 0);
        }
        Jsi_TreeSearchDone(&search);
    }
    return JSI_OK;
    
}

static Jsi_RC ObjectPropertyIsEnumerableCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *v;
    int b = 0;

    char *key = Jsi_ValueArrayIndexToStr(interp, args,0, NULL);
    v = Jsi_ValueObjLookup(interp, _this, key, 0);
    b = (v && (v->f.bits.dontenum==0));

    Jsi_ValueMakeBool(interp, ret, b);
    return JSI_OK;
}

static Jsi_RC ObjectToLocaleStringCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return jsi_ObjectToStringCmd(interp, args, _this, ret, funcPtr);
}

static Jsi_RC jsi_FunctionApplyCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_FunctionIsConstructor(funcPtr)) 
        return Jsi_LogError("Execute apply as constructor");
    
    return Jsi_FunctionApply(interp, args, _this, ret);
}

Jsi_Value *jsi_ProtoObjValueNew1(Jsi_Interp *interp, const char *name) {
    Jsi_Value *v = jsi_ObjValueNew(interp);
    Jsi_IncrRefCount(interp, v);
    //v->f.bits.isglob = 1;
#ifdef JSI_MEM_DEBUG
    v->VD.label = "jsi_ProtoValueNew1";
    v->VD.label2 = name;
#endif
    if (name != NULL)
        Jsi_PrototypeDefine(interp, name, v);
    return v;
}

Jsi_Value *jsi_ProtoValueNew(Jsi_Interp *interp, const char *name, const char *parent)
{
    Jsi_Value *fproto;
    if (parent == NULL)
        parent = "Object";
    fproto = jsi_ProtoObjValueNew1(interp, name);
    Jsi_PrototypeObjSet(interp, parent, Jsi_ValueGetObj(interp, fproto));
    return fproto;
}

static Jsi_CmdSpec functionCmds[] = {
    { "Function",  jsi_Function_constructor,   0, 0,  "", .help="Function constructor (unimplemented)", .retType=(uint)JSI_TT_FUNCTION, .flags=JSI_CMD_IS_CONSTRUCTOR },
    { "apply",     jsi_FunctionApplyCmd,       1, 2,  "thisArg:null|object|function, args:array=void", .help="Call function passing args array", .retType=(uint)JSI_TT_ANY },
    { "bind",      jsi_FunctionBindCmd,        0, -1, "thisArg:object|function=null,arg,...", .help="Return function that calls bound function prepended with thisArg+arguments", .retType=(uint)JSI_TT_ANY },
    { "call",      jsi_FunctionCallCmd,        1, -1, "thisArg:null|object|function, arg1, ...", .help="Call function with args", .retType=(uint)JSI_TT_ANY },
    { NULL, 0,0,0,0, .help="Commands for accessing functions" }
};

int jsi_InitFunction(Jsi_Interp *interp, int release)
{
    if (!release)
        Jsi_CommandCreateSpecs(interp, NULL, functionCmds, interp->Function_prototype, JSI_CMDSPEC_PROTO|JSI_CMDSPEC_ISOBJ);
    return JSI_OK;
}

/* TODO: defineProperty, defineProperties, */
static Jsi_CmdSpec objectCmds[] = {
#ifndef __JSI_OMITDECL
    { "Object",         ObjectConstructor,      0, 1,  "val:object|function|null=void", .help="Object constructor", .retType=(uint)JSI_TT_OBJECT, .flags=JSI_CMD_IS_CONSTRUCTOR },
    { "create",         ObjectCreateCmd,        1, 2, "proto:null|object, properties:object=void", .help="Create a new object with prototype object and properties", .retType=(uint)JSI_TT_OBJECT },
#if (JSI_HAS___PROTO__>0)
    { "getPrototypeOf", jsi_GetPrototypeOfCmd,  1, 1, "name:object|function", .help="Return prototype of an object", .retType=(uint)JSI_TT_OBJECT|JSI_TT_FUNCTION },
#endif
    { "hasOwnProperty", jsi_HasOwnPropertyCmd,  1, 1, "name:string", .help="Returns a true if object has the specified property", .retType=(uint)JSI_TT_BOOLEAN },
    { "is",             ObjectIsCmd, 2, 2, "value1, value2", .help="Tests if two values are equal", .retType=(uint)JSI_TT_BOOLEAN },
    { "isPrototypeOf",  ObjectIsPrototypeOfCmd, 1, 1, "name", .help="Tests for an object in another object's prototype chain", .retType=(uint)JSI_TT_BOOLEAN },
    { "keys",           ObjectKeysCmd,          0, 1, "obj:object|function=void", .help="Return the keys of an object or array", .retType=(uint)JSI_TT_ARRAY },
    { "merge",          ObjectMergeCmd,         1, 1, "obj:object|function", .help="Return new object containing merged values", .retType=(uint)JSI_TT_OBJECT },
    { "propertyIsEnumerable", ObjectPropertyIsEnumerableCmd,1, 1, "name", .help="Determine if a property is enumerable", .retType=(uint)JSI_TT_BOOLEAN },
#if (JSI_HAS___PROTO__>0)
    { "setPrototypeOf", jsi_SetPrototypeOfCmd,  2, 2, "name:object, value:object", .help="Set prototype of an object" },
#endif
    { "toLocaleString", ObjectToLocaleStringCmd,0, 1, "quote:boolean=false", .help="Convert to string", .retType=(uint)JSI_TT_STRING },
    { "toString",       jsi_ObjectToStringCmd,  0, 1, "quote:boolean=false", .help="Convert to string", .retType=(uint)JSI_TT_STRING }, 
    { "valueOf",        ObjectValueOfCmd,       0, 0, "", .help="Returns primitive value", .retType=(uint)JSI_TT_ANY },
    { NULL, 0,0,0,0, .help="Commands for accessing Objects" }
#endif
};

int jsi_InitObject(Jsi_Interp *interp, int release)
{
    if (!release)
        Jsi_CommandCreateSpecs(interp, NULL, objectCmds, interp->Object_prototype, JSI_CMDSPEC_PROTO|JSI_CMDSPEC_ISOBJ);
    return JSI_OK;
}

Jsi_RC jsi_InitProto(Jsi_Interp *interp, int release)
{
    if (release) return JSI_OK;
     /* Function and Object are created together. */
    Jsi_Value *global = interp->csc;
    /* Top, the default "this" value, pointed to global, is an object */
    interp->Top_object = global;

    /* object_prototype the start of protochain */
    interp->Object_prototype = jsi_ProtoObjValueNew1(interp, "Object");
    interp->Top_object->d.obj->__proto__ = interp->Object_prototype;
        
    /* Function.prototype.prototype is a common object */
    interp->Function_prototype_prototype = jsi_ProtoObjValueNew1(interp, "Function.prototype");
    interp->Function_prototype_prototype->d.obj->__proto__ = interp->Object_prototype;
    
    /* Function.prototype.__proto__ pointed to Jsi_Obj.prototype */
    interp->Function_prototype = jsi_MakeFuncValue(interp, jsi_FunctionPrototypeConstructor, "prototype", NULL, NULL);
    //Jsi_IncrRefCount(interp, interp->Function_prototype);
    Jsi_ValueInsertFixed(interp, interp->Function_prototype, "prototype", 
                              interp->Function_prototype_prototype);
    interp->Function_prototype->d.obj->__proto__ = interp->Object_prototype;
    
    /* Jsi_Obj.__proto__ pointed to Function.prototype */
    Jsi_Value *_Object = jsi_MakeFuncValue(interp, ObjectConstructor, "prototype", NULL, NULL);
    //Jsi_IncrRefCount(interp, _Object);
    Jsi_ValueInsertFixed(interp, _Object, "prototype", interp->Object_prototype);
    _Object->d.obj->__proto__ = interp->Function_prototype;

    /* both Function.prototype,__proto__ pointed to Function.prototype */
    Jsi_Value *_Function = jsi_MakeFuncValue(interp, jsi_Function_constructor, "prototype", NULL, NULL);
    //Jsi_IncrRefCount(interp, _Function);
    Jsi_ValueInsertFixed(interp, _Function, "prototype", interp->Function_prototype);
    _Function->d.obj->__proto__ = interp->Function_prototype;

    Jsi_ValueInsert(interp, global, "Object", _Object, JSI_OM_DONTENUM);
    Jsi_ValueInsert(interp, global, "Function", _Function, JSI_OM_DONTENUM);

    //Jsi_HashSet(interp->genValueTbl, _Object, _Object);
    //Jsi_HashSet(interp->genValueTbl, _Function, _Function);
    //Jsi_HashSet(interp->genValueTbl, interp->Object_prototype , interp->Object_prototype);
    //Jsi_HashSet(interp->genValueTbl, interp->Function_prototype, interp->Function_prototype);
    Jsi_HashSet(interp->genObjTbl, interp->Function_prototype->d.obj, interp->Function_prototype->d.obj);

    interp->cleanObjs[0] = _Function->d.obj;
    interp->cleanObjs[1] = _Object->d.obj;
    interp->cleanObjs[2] = NULL;

    jsi_InitObject(interp, 0);
    jsi_InitFunction(interp, 0);
    
    jsi_InitString(interp, 0);
    jsi_InitBoolean(interp, 0);
    jsi_InitNumber(interp, 0);
    jsi_InitArray(interp, 0);
    jsi_InitRegexp(interp, 0);
#ifndef JSI_OMIT_MATH
    jsi_InitMath(interp, 0);
#endif
    jsi_InitTree(interp, 0);
    interp->protoInit = 1;
    return JSI_OK;
}

#endif
#ifndef JSI_AMALGAMATION
#include "jsiInt.h"
#endif
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <dirent.h>

#ifndef __WIN32
#include <pwd.h>
#include <unistd.h>
#else
#include <windef.h>
#endif
#include <limits.h>

#ifndef JSI_LITE_ONLY

#define _JSI_GETFP(ch,in) (ch && ch->fp ? ch->fp : (in?stdin:stdout))

//static Jsi_CmdSpec filesysCmds[];

static char* jsi_FSRealPathProc(Jsi_Interp *interp, Jsi_Value *path, char *newPath);


typedef struct FileObj {
#ifdef JSI_HAS_SIG
    jsi_Sig sig;
#endif
    Jsi_Interp *interp;
    Jsi_Channel chan;
    Jsi_Value *fname;
    char *filename;
    char *mode;
    Jsi_Obj *fobj;
    int objId;
} FileObj;

static void fileObjErase(FileObj *fo);
static Jsi_RC fileObjFree(Jsi_Interp *interp, void *data);
static bool fileObjIsTrue(void *data);
static bool fileObjEqual(void *data1, void *data2);

#ifdef __WIN32
char *get_current_dir_name() {
    static char buf[MAX_PATH] = ".";
    getcwd(buf, sizeof(buf));
    return buf;
}
#endif

static const char *jsi_TildePath(Jsi_Interp *interp, const char* path, Jsi_DString *dStr) {
    if (*path != '~')
        return path;
    const char *homedir = jsi_GetHomeDir(interp);
    if (!homedir)
        return path;
    Jsi_DSAppend(dStr, homedir, path[1] == '/' ? "" : "/", path+1, NULL);
    return Jsi_DSValue(dStr);
}

int jsi_FSScandirProc(Jsi_Interp *interp, Jsi_Value *path, Jsi_Dirent ***namelist,
   int (*filter)(const Jsi_Dirent *), int (*compar)(const Jsi_Dirent **, const Jsi_Dirent**))
{
    const char *dirname = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*dirname == '~')
        dirname = jsi_TildePath(interp, dirname, &dStr);
    int rc = scandir(dirname, namelist, filter, compar);
    Jsi_DSFree(&dStr);
    return rc;
}

static int jsi_FSCreateDirectoryProc(Jsi_Interp *interp, Jsi_Value* path) {
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    int rc;
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);

#ifdef __WIN32
    rc = mkdir(pathPtr);
#else
    rc = mkdir(pathPtr, 0666);
#endif
    Jsi_DSFree(&dStr);
    return rc;
}

static int jsi_FSRenameProc(Jsi_Interp *interp, Jsi_Value *src, Jsi_Value *dest) {
    const char *zSrc = Jsi_ValueToString(interp, src, NULL);
    const char *zDest = Jsi_ValueToString(interp, dest, NULL);
    Jsi_DString dStr = {}, eStr = {};
    if (*zSrc == '~')
        zSrc = jsi_TildePath(interp, zSrc, &dStr);
    if (*zDest == '~')
        zDest = jsi_TildePath(interp, zDest, &eStr);
    int rc = rename(zSrc, zDest);
    Jsi_DSFree(&dStr);
    Jsi_DSFree(&eStr);
    return rc;
}

static Jsi_Value * jsi_FSListVolumesProc(Jsi_Interp *interp) {return 0;}

static int jsi_FSRemoveProc(Jsi_Interp *interp, Jsi_Value* path, int flags) {
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    int rc = remove(pathPtr);
    Jsi_DSFree(&dStr);
    return rc;
}

static bool jsi_FSPathInFilesystemProc(Jsi_Interp *interp, Jsi_Value* path,void **clientDataPtr) {return 1;}

static int jsi_FSAccessProc(Jsi_Interp *interp, Jsi_Value* path, int mode) {
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    int rc = access(pathPtr, mode);
    Jsi_DSFree(&dStr);
    return rc;
}

static int jsi_FSChmodProc(Jsi_Interp *interp, Jsi_Value* path, int mode) {
#ifdef __WIN32
    return -1;
#else
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    int rc = chmod(pathPtr, mode);
    Jsi_DSFree(&dStr);
    return rc;
#endif
}

static int jsi_FSStatProc(Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf) {
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    int rc = stat(pathPtr, buf);
    Jsi_DSFree(&dStr);
    return rc;
}

static int jsi_FSLstatProc(Jsi_Interp *interp, Jsi_Value* path, Jsi_StatBuf *buf) {
#ifdef __WIN32
    return jsi_FSStatProc(interp, path, buf);
#else
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    int rc = lstat(pathPtr, buf);
    Jsi_DSFree(&dStr);
    return rc;
#endif
}
static int jsi_FSFlushProc(Jsi_Channel chan) { return fflush(_JSI_GETFP(chan,0));}
static int jsi_FSTellProc(Jsi_Channel chan) { return ftell(_JSI_GETFP(chan,1));}
static int jsi_FSEofProc(Jsi_Channel chan) { return feof(_JSI_GETFP(chan,1));}
static int jsi_FSTruncateProc(Jsi_Channel chan, unsigned int len) { return ftruncate(fileno(_JSI_GETFP(chan,1)), len);}
static int jsi_FSRewindProc(Jsi_Channel chan) { rewind(_JSI_GETFP(chan,1)); return 0;}
static int jsi_FSCloseProc(Jsi_Channel chan) { return fclose(_JSI_GETFP(chan,1));}
static int jsi_FSSeekProc(Jsi_Channel chan, Jsi_Wide offset, int mode) { return fseek(_JSI_GETFP(chan,1), offset, mode);}

static int jsi_FSWriteProc(Jsi_Channel chan, const char *buf, int size) {
    return fwrite(buf, 1, size, _JSI_GETFP(chan,0));
}

static int jsi_FSReadProc(Jsi_Channel chan, char *buf, int size) {
    return fread(buf, 1, size, _JSI_GETFP(chan,0));
}

#ifdef __WIN32
#define jsi_FSLinkProc NULL
#define jsi_FSReadlinkProc NULL
#else //__WIN32
static int jsi_FSLinkProc(Jsi_Interp *interp, Jsi_Value* path, Jsi_Value *toPath, int linkType) {
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    const char *toPtr = Jsi_ValueToString(interp, toPath, NULL);
    Jsi_DString dStr = {}, eStr = {};
    int rc;
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    if (*toPtr == '~')
        toPtr = jsi_TildePath(interp, toPtr, &eStr);
    if (linkType != 0)
        rc = link(pathPtr, toPtr);
    else
        rc = symlink(pathPtr, toPtr);
    Jsi_DSFree(&dStr);
    Jsi_DSFree(&eStr);
    return rc;
}

static int jsi_FSReadlinkProc(Jsi_Interp *interp, Jsi_Value *path, char *buf, int size) {
    const char *pathPtr = Jsi_ValueToString(interp, path, NULL);
    Jsi_DString dStr = {};
    if (*pathPtr == '~')
        pathPtr = jsi_TildePath(interp, pathPtr, &dStr);
    int rc = readlink(pathPtr, buf, size);
    Jsi_DSFree(&dStr);
    return rc;
}
#endif // __WIN32

static int jsi_FSGetcProc(Jsi_Channel chan) {
    return fgetc(_JSI_GETFP(chan,1));
}

static int jsi_FSUngetcProc(Jsi_Channel chan, int ch) {
    return ungetc(ch, _JSI_GETFP(chan,1));
}

static char * jsi_FSGetsProc(Jsi_Channel chan, char *s, int size) {
    return fgets(