Commit message: Release "3.0.7": Fix toPrecision bug "stack overflow #4".

FossilOrigin-Name: 6c7f0c37027d7f890b57cb38f776af39b8f81f03e60ceeb0a231a1d21e24b5de
CVE: CVE-2020-22873
CWE: CWE-120
Vulnerability description: Buffer overflow vulnerability in function NumberToPrecisionCmd in jsish before 3.0.7, allows remote attackers to execute arbitrary code.

Project: jsish

static Jsi_RC MySqlExtractParmNames(MySqlObj* jdb, const char *sql, Jsi_DString *sStr, Jsi_DString *nStr) {
    const char *ocp, *cp = sql;
    int cnt = 0;
    while (*cp) {
        if (*cp == '\"'||*cp == '\'') {
            ocp = cp;
            cp = mdbFindEndStr(cp);
            if (!cp)
                return JSI_ERROR;
            Jsi_DSAppendLen(sStr, ocp, cp-ocp+1);
        } else if ((*cp == '@' || *cp == ':' || *cp == '$' ) && (isalpha(cp[1]) || cp[1] == '_')) {
            ocp = cp;
            cp+=2;
            while (*cp && (isalnum(*cp) || *cp == '_'))
                cp++;
            if (*ocp == '$' && *cp == '(') {
                const char *ttp = NULL, *ttb = NULL, *eq = NULL;
                cp++;
                if (*cp == '[')
                    eq = cp++;
                while (*cp && (isalnum(*cp) || *cp == '_' || *cp == ':' || *cp == '.' || *cp == ']')) {
                    if (*cp == ':') {
                        if (ttp)
                            return JSI_ERROR;
                        ttp = cp;
                    }
                    if (*cp == ']') {
                        if (ttb)
                            return JSI_ERROR;
                        ttb = cp;
                        if (cp[1] != ')' && cp[1] != ':' && cp[1] != '.')
                            return JSI_ERROR;
                    }
                    cp++;
                }
                if (*cp != ')')
                    return JSI_ERROR;
                if (eq && !ttb)
                    return JSI_ERROR;
                if (ttp) {
                    Jsi_DString tStr = {};
                    Jsi_DSAppendLen(&tStr, ttp+1, (cp - ttp - 1));
                    if (!jdb->typeNameHash) mdbTypeNameHashInit(jdb);
                    int rc = (Jsi_HashEntryFind(jdb->typeNameHash, Jsi_DSValue(&tStr)) != NULL);
                    if (!rc) {
                        Jsi_DString eStr = {};
                        Jsi_HashEntry *hPtr;
                        Jsi_HashSearch search;
                        Jsi_Interp *interp = jdb->interp;
                        int n = 0;
                        for (hPtr = Jsi_HashSearchFirst(jdb->typeNameHash, &search);
                            hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
                            const char *key = (char*)Jsi_HashKeyGet(hPtr);
                            Jsi_DSAppend(&eStr, (n++?", ":""), key, NULL);
                        }
                        Jsi_LogWarn("bind type \"%s\" is not one of: %s", Jsi_DSValue(&tStr), Jsi_DSValue(&eStr));
                        Jsi_DSFree(&eStr);
                    }
                    Jsi_DSFree(&tStr);
                    if (!rc)
                        return JSI_ERROR;
                }
            } else
                cp--;
            if (cnt++)
                Jsi_DSAppendLen(nStr, " ", 1);
            Jsi_DSAppendLen(nStr, ocp, cp-ocp+1);
            Jsi_DSAppendLen(sStr, "?", 1);
        } else if (*cp == '\\' && cp[1]) {
            Jsi_DSAppendLen(sStr, cp, 2);
            cp++;
        } else {
            Jsi_DSAppendLen(sStr, cp, 1);
        }
        cp++;
    }
    return JSI_OK;
}

Project: jsish

Jsi_RC Jsi_doneSqlite(Jsi_Interp *interp)
{
    Jsi_InterpFreeData(interp, JSI_SQLITE_DB_VFS);
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

void *Jsi_DbHandle(Jsi_Interp *interp, Jsi_Db* jdb)
{
    SQLSIGASSERT(jdb,DB);
    return jdb->db;
}

Project: jsish

void Jsi_ListValueSet(Jsi_ListEntry *l, const void *value) {
    SIGASSERTV(l, LISTENTRY);
    l->value = (void*)value;
}

Project: jsish

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);
}

Project: jsish

static Jsi_RC dbEvalStepOption(DbEvalContext *p, OptionBind *obPtr, int *cntPtr, int dataIdx, int bindMax, Jsi_CDataDb *dbopts, int *erc) {
    Jsi_Db *jdb = p->jdb;
    int cnt = 0;
    while( p->zSql[0] || p->pPreStmt ) {
        Jsi_RC rc;
        cnt++;
        if( p->pPreStmt==0 ) {
            rc = dbPrepareStmt(p->jdb, p->zSql, &p->zSql, &p->pPreStmt);
            if( rc!=JSI_OK ) return rc;
        }
        if (bindMax!=0) {
            rc = dbBindOptionStmt(jdb, p->pPreStmt->pStmt, obPtr, dataIdx, bindMax, dbopts);
            if( rc!=JSI_OK ) return rc;
        }
        rc = dbEvalStepSub(p, 1, erc);
        if (rc != JSI_BREAK)
            return rc;
        *cntPtr = cnt;
    }
    
    /* Finished */
    return JSI_BREAK;
}

Project: jsish

static Jsi_RC StringSliceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int sLen, bLen;
    const char *vstr;
    ChkString(_this, funcPtr, vstr, &sLen, &bLen);

    Jsi_Value *start = Jsi_ValueArrayIndex(interp, args, skip);
    Jsi_Value *end = Jsi_ValueArrayIndex(interp, args, 1+skip);
    int istart, iend, len = Jsi_Strlen(vstr);

    if (Jsi_GetIntFromValue(interp, start, &istart)) {
        return JSI_ERROR;
    }
    if (istart < 0)
        istart = len+istart;
    if (istart < 0)
        istart = 0;
    if (istart>=len)
        istart = len-1;
    iend = len;
    if (end) {
        if (Jsi_GetIntFromValue(interp, end, &iend)) {
            return JSI_ERROR;
        }
        if (iend < 0)
            iend = len+iend;
        if (iend>=len)
            iend = len;
        if (iend<istart)
            iend = istart;
    }
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    Jsi_UtfSubstr(vstr, istart, iend, &dStr);
    Jsi_ValueFromDS(interp, &dStr, ret);
    return JSI_OK;
}

Project: jsish

static Jsi_RC SqliteImportCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
                         Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Db *jdb;
    Jsi_RC rv = JSI_OK;
    int rc;
    char *zTable;               /* Insert data into this table */
    char *zFile;                /* The file from which to extract data */
    const char *zConflict;            /* The conflict algorithm to use */
    sqlite3_stmt *pStmt;        /* A statement */
    int nCol;                   /* Number of columns in the table */
    int nByte;                  /* Number of bytes in an SQL string */
    int i, j;                   /* Loop counters */
    int nSep;                   /* Number of bytes in zSep[] */
    int nNull;                  /* Number of bytes in zNull[] */
    char *zSql;                 /* An SQL statement */
    char *zLine;                /* A single line of input from the file */
    char **azCol;               /* zLine[] broken up into columns */
    const char *onCommit;              /* How to commit changes */
    Jsi_Channel in;                   /* The input file */
    int lineno = 0;             /* Line number of input file */
    int created = 0;
    const char *zSep;
    const char *zNull;

    if (!(jdb = dbGetDbHandle(interp, _this, funcPtr))) return JSI_ERROR;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 2);
    ImportData opts = {};

    if (arg) {
        if (Jsi_OptionsProcess(interp, ImportOptions, &opts, arg, 0) < 0)
            return JSI_ERROR;
    }
    zConflict = copyConflictStrs[opts.conflict];
    
    if(opts.separator ) {
        zSep = opts.separator;
    } else {
        zSep = (opts.csv ? "," : "\t");
    }
    if(opts.nullvalue ) {
        zNull = opts.nullvalue;
    } else {
        zNull = "";
    }
    zTable = Jsi_ValueArrayIndexToStr(interp, args, 0, NULL);
    Jsi_Value *fname = Jsi_ValueArrayIndex(interp, args, 1);
    zFile = Jsi_ValueArrayIndexToStr(interp, args, 1, NULL);
    nSep = Jsi_Strlen(zSep);
    nNull = Jsi_Strlen(zNull);
    if( nSep==0 ) 
        return Jsi_LogError("Error: non-null separator required for copy");


    zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
    if (zSql==0) 
        return Jsi_LogError("Error: bad table: %s", zTable);
    
    if (opts.headers) {
        in = Jsi_Open(interp, fname, "rb");
        if( in==0 ) 
            return Jsi_LogError("Error: cannot open file: %s", zFile);
        if ((zLine = dbLocalGetline(interp, 0, in))==0 ) {
            Jsi_Close(interp, in);
            return JSI_ERROR;
        }
        Jsi_Close(interp, in);
        char *zn, *ze, *z = zLine;
        Jsi_DString cStr = {};
        int zlen = 0, icnt = 0;
        Jsi_DSAppend(&cStr, "CREATE TABLE IF NOT EXISTS '", zTable, "' (", NULL);
        while (1) {
            zn = Jsi_Strstr(z, zSep);
            if (!zn) zlen = Jsi_Strlen(z);
            else zlen = zn-z;
            if (zlen<=0) break;
            ze = z+zlen-1;
            Jsi_DSAppend(&cStr, (icnt?",":""), "'", NULL);
            icnt++;
            if (opts.csv && *z=='"' && zn>z && *ze == '"')
                Jsi_DSAppendLen(&cStr, z+1, zlen-2);
            else
                Jsi_DSAppendLen(&cStr, z, zlen);
            Jsi_DSAppend(&cStr, "'", NULL);
            if (!zn) break;
            z = zn+nSep;
        }
        Jsi_DSAppend(&cStr, ");", NULL);
        Jsi_Free(zLine);
        if (zlen<=0) {
            Jsi_DSFree(&cStr);
            Jsi_LogError("null header problem");
            return JSI_ERROR;
        }
        rc = sqlite3_exec(jdb->db, Jsi_DSValue(&cStr), 0, 0, 0);
        Jsi_DSFree(&cStr);
        if (rc) 
            return Jsi_LogError("%s", sqlite3_errmsg(jdb->db));
        created = 1;
    }
    
    nByte = Jsi_Strlen(zSql);
    rc = sqlite3_prepare(jdb->db, zSql, -1, &pStmt, 0);
        sqlite3_free(zSql);
    if( rc ) {
        Jsi_LogError("%s", sqlite3_errmsg(jdb->db));
        nCol = 0;
    } else {
        nCol = sqlite3_column_count(pStmt);
    }
    sqlite3_finalize(pStmt);
    if( nCol==0 ) {
        rc = JSI_ERROR;
        goto bail;
    }
    zSql = (char*)Jsi_Malloc( nByte + 50 + nCol*2 );
    if( zSql==0 ) {
        Jsi_LogError("Error: can't malloc()");
        rc = JSI_ERROR;
        goto bail;
    }
    sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?",
                     zConflict, zTable);
    j = Jsi_Strlen(zSql);
    for(i=1; i<nCol; i++) {
        zSql[j++] = ',';
        zSql[j++] = '?';
    }
    zSql[j++] = ')';
    zSql[j] = 0;
    rc = sqlite3_prepare(jdb->db, zSql, -1, &pStmt, 0);
    Jsi_Free(zSql);
    if( rc ) {
        Jsi_LogError("Error: %s", sqlite3_errmsg(jdb->db));
        sqlite3_finalize(pStmt);
        return JSI_ERROR;
    }
    in = Jsi_Open(interp, fname, "rb");
    if( in==0 ) {
        Jsi_LogError("Error: cannot open file: %s", zFile);
        sqlite3_finalize(pStmt);
        return JSI_ERROR;
    }
    azCol = (char**)Jsi_Malloc( sizeof(azCol[0])*(nCol+1) );
    if( azCol==0 ) {
        Jsi_LogError("Error: can't malloc()");
        Jsi_Close(interp, in);
        rc = JSI_ERROR;
        goto bail;
    }
    (void)sqlite3_exec(jdb->db, "BEGIN", 0, 0, 0);
    onCommit = "COMMIT";
    while ((zLine = dbLocalGetline(interp, 0, in))!=0 ) {
        char *z;
        i = 0;
        lineno++;
        if (opts.limit>0 && lineno > opts.limit) {
            Jsi_Free(zLine);
            break;
        }
        if (lineno == 1 && opts.headers) {
            Jsi_Free(zLine);
            continue;
        }
        if (opts.csv && Jsi_Strchr(zLine,'"')) 
        {
            char *zn, *z = zLine;
            Jsi_DString sStr = {};
            int qcnt = 0;
            i = -1;
            while (*z) if (*z++ == '"') qcnt++;
            z = zLine;
            if (qcnt%2) { /* aggregate quote spanning newlines */
                Jsi_DSAppend(&sStr, zLine, NULL);
                do {
                    lineno++;
                    Jsi_DSAppend(&sStr, "\n", NULL);
                    Jsi_Free(zLine);
                    if (((zLine = dbLocalGetline(interp, 0, in)))==0)
                        break;
                    Jsi_DSAppend(&sStr, zLine, NULL);
                    z = zLine;
                    while (*z) if (*z++ == '"') qcnt++;
                } while (qcnt%2);
                z = Jsi_DSValue(&sStr);
            }
            if (qcnt%2) {
                Jsi_DSFree(&sStr);
                Jsi_Free(zLine);
                Jsi_Close(interp, in);
                Jsi_LogError("unterminated string at line: %d", lineno);
                break;
            }
            while (z) {
                if (*z != '\"') { /* Handle un-quoted value */
                    zn = Jsi_Strstr(z, zSep);
                    azCol[++i] = z;
                    if (!zn)
                        break;
                    *zn = 0;
                    z = zn+nSep;
                    continue;
                }
                /* Handle quoted value */
                zn = ++z;
                Jsi_DString cStr = {};
                while (1) {
                    if (!zn)
                        break;
                    if (*zn != '"')
                        Jsi_DSAppendLen(&cStr, zn, 1);
                    else {
                        if (zn[1] == '"') {
                            zn++;
                            Jsi_DSAppendLen(&cStr, "\"", 1);
                        } else if (zn[1] == 0) {
                            break;
                        } else if (Jsi_Strncmp(zn+1,zSep, nSep)==0) {
                            *zn = 0;
                            zn += (nSep + 1);
                            break;
                        } else {
                            /* Invalid, comma should be right after close quote, so just eat quote. */
                            Jsi_DSAppendLen(&cStr, zn, 1);
                        }
                    }
                    zn++;
                }
                Jsi_Strcpy(z, Jsi_DSValue(&cStr));
                Jsi_DSFree(&cStr);
                azCol[++i] = z;
                z = zn;
            }
        } else {
            azCol[0] = zLine;
            for(i=0, z=zLine; *z; z++) {
                if( *z==zSep[0] && Jsi_Strncmp(z, zSep, nSep)==0 ) {
                    *z = 0;
                    i++;
                    if( i<nCol ) {
                        azCol[i] = &z[nSep];
                        z += nSep-1;
                    }
                }
            }
        }
        if( i+1!=nCol ) {
            Jsi_LogError("%s line %d: expected %d columns of data but found %d",
                 zFile, lineno, nCol, i+1);
            onCommit = "ROLLBACK";
            break;
        }
        for(i=0; i<nCol; i++) {
            /* check for null data, if so, bind as null */
            if( (nNull>0 && Jsi_Strcmp(azCol[i], zNull)==0)
                    || Jsi_Strlen(azCol[i])==0
              ) {
                sqlite3_bind_null(pStmt, i+1);
            } else {
                sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC );
            }
        }
        sqlite3_step(pStmt);
        rc = sqlite3_reset(pStmt);
        if (zLine)
            Jsi_Free(zLine);
        if( rc!=SQLITE_OK ) {
            Jsi_LogError("%s at line: %d", sqlite3_errmsg(jdb->db), lineno);
            onCommit = "ROLLBACK";
            break;
        }
    }
    Jsi_Free(azCol);
    Jsi_Close(interp, in);
    sqlite3_finalize(pStmt);
    (void)sqlite3_exec(jdb->db, onCommit, 0, 0, 0);

    if( onCommit[0] == 'C' ) {
        /* success, set result as number of lines processed */
        Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)lineno);
        rv = JSI_OK;
    } else {
        rv = JSI_ERROR;
    }
    
bail:
    if (rc != JSI_OK && created && opts.conflict == CC_ROLLBACK) {
        Jsi_DString cStr = {};
        Jsi_DSAppend(&cStr, "DROP TABLE IF EXISTS '", zTable, "';", NULL);
        (void)sqlite3_exec(jdb->db, Jsi_DSValue(&cStr), 0, 0, 0);
        Jsi_DSFree(&cStr);
    }
    return rv;
}

Project: jsish

int Jsi_ListEntryDelete(Jsi_ListEntry *l) {
    SIGASSERT(l, LISTENTRY);
    if (l->next || l->prev)
        Jsi_ListPop(l->list, l);
    Jsi_Free(l);
    return 1;
}

Project: jsish

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;
}

Project: jsish

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
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

Jsi_HashClear(Jsi_Hash *tablePtr)
{
    Jsi_HashEntry *hPtr, *nextPtr;
    size_t i;
    if (!tablePtr)
        return;
    if (tablePtr->opts.lockHashProc && (*tablePtr->opts.lockHashProc)(tablePtr, 1) != JSI_OK)
        return;
    for (i = 0; i < (size_t)tablePtr->numBuckets; i++) {
        hPtr = tablePtr->buckets[i];
        while (hPtr != NULL) {
            nextPtr = hPtr->nextPtr;
            if (tablePtr->opts.freeHashProc && hPtr->clientData)
                (tablePtr->opts.freeHashProc)(tablePtr->opts.interp, hPtr, hPtr->clientData);
            Jsi_Free(hPtr);
            hPtr = nextPtr;
            tablePtr->buckets[i] = hPtr;
        }
    }
    
    if (tablePtr->buckets != tablePtr->staticBuckets) {
        Jsi_Free(tablePtr->buckets);
    }
    if (tablePtr->opts.lockHashProc)
        (*tablePtr->opts.lockHashProc)(tablePtr, 0);
}

Project: jsish

Jsi_Tree *Jsi_TreeNew(Jsi_Interp *interp, unsigned int keyType, Jsi_TreeDeleteProc *freeProc)
{
    Jsi_Tree* treePtr = (Jsi_Tree*)Jsi_Calloc(1,sizeof(Jsi_Tree));
    SIGINIT(treePtr,TREE);
    treePtr->opts.mapType = (Jsi_Map_Type)JSI_MAP_TREE;
    treePtr->typ = (Jsi_Map_Type)JSI_MAP_TREE;
    treePtr->root = NULL;
    treePtr->opts.interp = interp;
    treePtr->numEntries = 0;
    treePtr->epoch = 0;
    treePtr->opts.keyType = (Jsi_Key_Type)keyType;
    treePtr->keyType = (Jsi_Key_Type)keyType;
    treePtr->opts.freeTreeProc = freeProc;

    switch (keyType) {
    case JSI_KEYS_STRING:   /* NULL terminated string keys. */
        treePtr->opts.compareTreeProc = StringCompare;
        treePtr->createProc = TreeStringCreate;
        break;

    case JSI_KEYS_STRINGKEY: /*  */
        treePtr->opts.compareTreeProc = StringPtrCompare;
        treePtr->createProc = StringPtrCreate;
        break;
        
    case JSI_KEYS_ONEWORD: /* 32 or 64 bit atomic keys. */
        treePtr->opts.compareTreeProc = OneWordCompare;
        treePtr->createProc = OneWordCreate;
        break;


    default:            /* Struct. */
        if (keyType < JSI_KEYS_STRUCT_MINSIZE) {
            Jsi_LogError("Jsi_TreeNew: Key size can't be %d, must be > %d", keyType, JSI_KEYS_STRUCT_MINSIZE);
            Jsi_Free(treePtr);
            return NULL;
        }
        treePtr->opts.compareTreeProc = TreeArrayCompare;
        treePtr->createProc = TreeArrayCreate;
        break;
    }
    return treePtr;
}

Project: jsish

static int TreeArrayCompare(Jsi_Tree *treePtr, const void *key1, const void *key2)
{
    return memcmp(key1, key2, treePtr->keyType);
}

Project: jsish

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);
}

Project: jsish

static Jsi_RC jsi_EvalLogErr(Jsi_Interp *interp, const char *str) { Jsi_LogMsg(interp, JSI_ERROR, "%s", str); return JSI_ERROR; }

Project: jsish

static Jsi_OpCodes *code_nneq() { JSI_NEW_CODES(0,OP_STRICTNEQ, 0); }

Project: jsish

static char* jsi_FSRealPathProc(Jsi_Interp *interp, Jsi_Value *src, char *newPath) {
    return Jsi_FileRealpath(interp, src, newPath);
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

static void jsiSetupStack(Jsi_Interp *interp)
{
    int oldsz = interp->maxStack;
    if (interp->maxStack)
        interp->maxStack += STACK_INCR_SIZE;
    else
        interp->maxStack = STACK_INIT_SIZE;
    _jsi_STACK = jsi_ValuesAlloc(interp, interp->maxStack, _jsi_STACK, oldsz);
    _jsi_THIS = jsi_ValuesAlloc(interp, interp->maxStack, _jsi_THIS, oldsz); //TODO:!!! use interp->framePtr for this.
}

Project: jsish

JSI_RC mycall(Jsi_Tree* treePtr, Jsi_TreeEntry* hPtr, void *data)
{
    printf("CALL: %s(%d) : %d\n", (char*)Jsi_TreeKeyGet(hPtr), jsi_nodeDepth(hPtr), (int)Jsi_TreeValueGet(hPtr));
    return JSI_OK;
}

Project: jsish

static Jsi_RC StringTrimCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return _StringTrimCmd(interp, args, _this, ret, funcPtr, 3);
}

Project: jsish

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 *vwait = Jsi_ValueArrayIndex(interp, args, 2);
    Jsi_RC rc = JSI_OK;
    bool wait = 0;
    if (vwait && Jsi_GetBoolFromValue(interp, vwait, &wait))
        return JSI_ERROR;

    if (wait) {
        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(sinterp);
        /* 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);
        Jsi_CleanValue(sinterp, interp, srPtr, ret);
        //Jsi_ValueCopy(interp, *ret, srPtr);
        Jsi_DecrRefCount(sinterp, 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);
    } else {
        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;
}

Project: jsish

static Jsi_TreeEntry* sibling(Jsi_TreeEntry* n) {
    Assert (n != NULL);
    Assert (n->parent != NULL);
    return (n == n->parent->left ? n->parent->right : n->parent->left);
}

Project: jsish

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;
}

Project: jsish

static Jsi_RC FilesysSeekCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    static const char *posStr[] = { "set", "cur", "end", NULL };
    enum { W_SET, W_CUR, W_END };
    UdfGet(udf, _this, funcPtr);
    Jsi_Value *vPos = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Value *vWhence = Jsi_ValueArrayIndex(interp, args, 1);
    int mode = 0, p;
    Jsi_Wide pos;
    Jsi_Number num;
    if (Jsi_ValueGetNumber(interp, vPos, &num) != JSI_OK)
        return JSI_ERROR;
    if (Jsi_ValueGetIndex(interp, vWhence, posStr, "position", 0, &p) != JSI_OK)
        return JSI_ERROR;
    switch (p) {
        case W_SET: mode = SEEK_SET; break;
        case W_CUR: mode = SEEK_CUR; break;
        case W_END: mode = SEEK_END; break;
    }
    pos = (Jsi_Wide)num;
    pos = Jsi_Seek(interp, udf->chan, pos, mode);
    Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)pos);
    return JSI_OK;
}

Project: jsish

void  Jsi_Free(void *n) { Assert(n); free(n); }

Project: jsish

static Jsi_RC StringCharCodeAtCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int sLen, bLen;
    const char *source_str;
    ChkString(_this, funcPtr, source_str, &sLen, &bLen);
    const char *v = source_str;

    Jsi_Value *ttPtr = Jsi_ValueNew1(interp);
    Jsi_ValueCopy(interp, ttPtr, _this);
    
    int pos = 0;
    Jsi_Value *vpos;
    if ((vpos = Jsi_ValueArrayIndex(interp, args, skip))) {
        jsi_ValueToOInt32(interp, vpos);
        pos = (int)vpos->d.num;
    }

#if JSI__UTF8
    int m;
    if (pos >= 0 && pos < sLen && ((m=Jsi_UtfIndexToOffset(v, pos))>=0)) {
        int32_t n;
        Jsi_UtfToUniChar(v+m, &n);
        Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)n);
    }
#else
    int slen = Jsi_Strlen(v);
    if (pos >= 0 && pos < slen) {
        Jsi_ValueMakeNumber(interp, ret, v[pos]);
    }
#endif
    else
        Jsi_ValueMakeNumber(interp, ret, Jsi_NumberNaN());
    Jsi_DecrRefCount(interp, ttPtr);
    return JSI_OK;
}

Project: jsish

Jsi_HashDelete(Jsi_Hash *tablePtr)
{
    Jsi_HashClear(tablePtr);
    Jsi_Free(tablePtr);
}

Project: jsish

static Jsi_RC NumberToStringCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    char buf[JSI_MAX_NUMBER_STRING+1];
    int radix = 10, skip = 0, argc = Jsi_ValueGetLength(interp, args);
    Jsi_Number num;
    Jsi_Value *v;
    ChkStringN(_this, funcPtr, v);
    Jsi_GetDoubleFromValue(interp, v, &num);
    if (argc>skip && (Jsi_GetIntFromValue(interp, Jsi_ValueArrayIndex(interp, args, skip), &radix) != JSI_OK
        || radix<2))
        return JSI_ERROR;
    if (argc==skip)
        return jsi_ObjectToStringCmd(interp, args, _this, ret, funcPtr);
    switch (radix) {
        case 16: snprintf(buf, sizeof(buf), "%" PRIx64, (Jsi_Wide)num); break;
        case 8: snprintf(buf, sizeof(buf), "%" PRIo64, (Jsi_Wide)num); break;
        case 10: snprintf(buf, sizeof(buf), "%" PRId64, (Jsi_Wide)num); break;
        default: return jsi_ObjectToStringCmd(interp, args, _this, ret, funcPtr);
    }
    Jsi_ValueMakeStringDup(interp, ret, buf);
    return JSI_OK;
}

Project: jsish

HashStringFind( Jsi_Hash *tablePtr, const void *key)
{
    jsi_Hash hval;
    Jsi_HashEntry *hPtr;
    size_t hindex;

    hval = HashString((char *)key);
    hindex = hval & tablePtr->mask;

    /*
     * Search all of the entries in the appropriate bucket.
     */

    for (hPtr = tablePtr->buckets[hindex]; hPtr != NULL;
            hPtr = hPtr->nextPtr) {
        if (hPtr->hval == hval) {
            const char *p1, *p2;

            for (p1 = (char*)key, p2 = hPtr->key.string; ; p1++, p2++) {
                if (*p1 != *p2) {
                    break;
                }
                if (*p1 == '\0') {
                    return hPtr;
                }
            }
        }
    }
    return NULL;
}

Project: jsish

void Jsi_TreeValueSet(Jsi_TreeEntry *hPtr, void *value)
{
    Jsi_Value *v = (Jsi_Value*)value;
#if JSI__MEMDEBUG
    SIGASSERTV(hPtr, TREEENTRY);
    if (hPtr->treePtr->flags.valuesonly)
        SIGASSERTV(v,VALUE);
#endif
    hPtr->value = v;
}

Project: jsish

const char *mysqlGetbindParamName(MysqlPrep* pStmt, int n) {
    if (n>=1 && n<=pStmt->paramCnt)
        return pStmt->paramNames[n-1];
    return NULL;
}

Project: jsish

Jsi_OptionsFree(Jsi_Interp *interp, Jsi_OptionSpec *specs, void *rec, Jsi_Wide flags /*unused*/)
{
    Jsi_OptionSpec *specPtr;
    char *record = (char*)rec;
    for (specPtr = specs; specPtr->id>=JSI_OPTION_BOOL && specPtr->id < JSI_OPTION_END && specPtr->name; specPtr++) {
        char *ptr = record + specPtr->offset;
        if (specPtr->flags&JSI_OPT_NO_CLEAR)
            continue;
        if (specPtr->custom) {
            Jsi_OptionCustom* cust = Jsi_OptionCustomBuiltin(specPtr->custom);
            if (cust->freeProc != NULL) {
                (*cust->freeProc)(interp, specPtr, (char **)ptr);
                continue;
            }
        }

        switch (specPtr->id) {
#ifndef JSI_LITE_ONLY
        case JSI_OPTION_VALUE:
        case JSI_OPTION_STRING:
        case JSI_OPTION_OBJ:
        case JSI_OPTION_REGEXP:
        case JSI_OPTION_ARRAY:
        case JSI_OPTION_FUNC:
        case JSI_OPTION_USEROBJ:
        case JSI_OPTION_VAR:
        {
            Jsi_Value **vpp = (Jsi_Value**)ptr, *vPtr = *vpp;
            if (vPtr && (!(specPtr->flags&JSI_OPT_NO_DUPVALUE)))
                Jsi_DecrRefCount(interp, vPtr);
            *vpp = 0;
            break;
        }
#endif
        case JSI_OPTION_STRKEY:
            *(char**)ptr = 0;
            break;
        case JSI_OPTION_STRBUF:
            *(char*)ptr = 0;
            break;
        case JSI_OPTION_DSTRING:
            Jsi_DSFree((Jsi_DString *)ptr);
            break;
        case JSI_OPTION_TIME_T:
            *(time_t*)ptr = 0;
            break;
        case JSI_OPTION_NUMBER:
            *(Jsi_Number*)ptr = 0;
            break;
        case JSI_OPTION_FLOAT:
            *(float*)ptr = 0;
            break;
        case JSI_OPTION_LDOUBLE:
            *(ldouble*)ptr = 0;
            break;
        case JSI_OPTION_DOUBLE:
        case JSI_OPTION_TIME_D:
            *(double*)ptr = 0;
            break;
        case JSI_OPTION_TIME_W:
            *(Jsi_Wide*)ptr = 0;
            break;
        case JSI_OPTION_INT8:  *(int8_t*)ptr = 0; break;
        case JSI_OPTION_INT16: *(int16_t*)ptr = 0; break;
        case JSI_OPTION_INT32: *(int32_t*)ptr = 0; break;
        case JSI_OPTION_INT64: *(int64_t*)ptr = 0; break;
        case JSI_OPTION_UINT8: *(uint8_t*)ptr = 0; break;
        case JSI_OPTION_UINT16:*(uint16_t*)ptr = 0; break;
        case JSI_OPTION_UINT32:*(uint32_t*)ptr = 0; break;
        case JSI_OPTION_UINT64:*(uint64_t*)ptr = 0; break;
        case JSI_OPTION_INT: *(int*)ptr = 0; break;
        case JSI_OPTION_UINT: *(uint*)ptr = 0; break;
        case JSI_OPTION_BOOL: *(bool*)ptr = 0; break;
        case JSI_OPTION_CUSTOM:
            break;
        default:
            if (specPtr->size>0)
                memset(ptr, 0, specPtr->size);
            break;
        }
    }
}

Project: jsish

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);
}

Project: jsish

static void jsi_wsOnModify(jsi_wsCmdObj *cmdPtr) {
    if (!cmdPtr->stats.httpLast) return;
    time_t now = time(NULL);
    double dt = (difftime(now, cmdPtr->lastModifyCheck));
    if (dt<0) {
        cmdPtr->lastModifyCheck = now;
        return;
    }
    uint secs = cmdPtr->modifySecs;
    if (!secs) secs = 2;
    if (dt<secs) return;
    cmdPtr->lastModifyCheck = now;
    Jsi_Interp *interp = cmdPtr->interp;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    Jsi_Value* changed = NULL;
    time_t ll = cmdPtr->lastModifyNotify;
    if (ll<cmdPtr->stats.httpLast)
        ll = cmdPtr->stats.httpLast;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->fileHash, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
        jsi_wsFile* fPtr = (jsi_wsFile*)Jsi_HashValueGet(hPtr);
        if (fPtr && fPtr->fileVal) {
            Jsi_StatBuf sb;
            int n = Jsi_Stat(interp, fPtr->fileVal, &sb);
            if (!n && sb.st_mtime > ll) {
                changed = fPtr->fileVal;
                break;
            }
        }
    }
    cmdPtr->lastModifyCheck = time(NULL);
    if (!changed) return;
    Jsi_Obj *oarg1;
    Jsi_Value *vpargs, *vargs[10];
    int n = 0;
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
    vargs[n++] = changed;
    vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
    Jsi_IncrRefCount(interp, vpargs);

    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_ValueMakeUndef(interp, &ret);
    Jsi_RC rc = Jsi_FunctionInvoke(interp, cmdPtr->onModify, vpargs, &ret, NULL);

    Jsi_DecrRefCount(interp, vpargs);
    Jsi_DecrRefCount(interp, ret);
    if (rc != JSI_OK) {
        Jsi_LogWarn("websock bad onModify eval: disabling");
        Jsi_DecrRefCount(interp, cmdPtr->onModify);
        cmdPtr->onModify = NULL;
    }
    cmdPtr->lastModifyCheck = time(NULL);
    cmdPtr->lastModifyNotify = time(NULL);
}

Project: jsish

static jsi_TryList *jsi_trylist_new(jsi_try_op_type t, jsi_ScopeChain *scope_save, Jsi_Value *curscope_save)
{
    jsi_TryList *n = (jsi_TryList *)Jsi_Calloc(1,sizeof(*n));
    
    n->type = t;
    n->curscope_save = curscope_save;
    /*Jsi_IncrRefCount(interp, curscope_save);*/
    n->scope_save = scope_save;
    
    return n;
}

Project: jsish

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;
}

Project: jsish

bool Jsi_ValueIsNull(Jsi_Interp *interp, Jsi_Value *pv)
{
    return (pv->vt == JSI_VT_NULL);
}

Project: jsish

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;
}

Project: jsish

void* Jsi_ListValueGet(Jsi_ListEntry *l) {
    SIGASSERT(l, LISTENTRY);
    return l?l->value:NULL;
}

Project: jsish

bool Jsi_NumberIsInteger(Jsi_Number n) { return (isnormal(n) ? (Jsi_Number)((Jsi_Wide)(n)) == (n) : n==0.0); }

Project: jsish

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;
}

Project: jsish

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);
}

Project: jsish

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;
}

Project: jsish

static void dbReleaseStmt(
    Jsi_Db *jdb,                  /* Database handle */
    SqlPreparedStmt *pPreStmt,      /* Prepared statement handle to release */
    int discard                     /* True to delete (not cache) the pPreStmt */
) {
    //int i;
    //Jsi_Interp *interp = jdb->interp;

    /* Free the bound string and blob parameters */
    /*for(i=0; i<pPreStmt->nParm; i++) {
        Jsi_DecrRefCount(interp, pPreStmt->apParm[i]);
    }*/
    //pPreStmt->nParm = 0;

    if( jdb->stmtCacheMax<=0 || discard ) {
        /* If the cache is turned off, deallocated the statement */
        dbPrepStmtFree(jdb, pPreStmt);
    } else {
        /* Add the prepared statement to the beginning of the cache list, then limit. */
        if (!pPreStmt->elPtr)
            pPreStmt->elPtr = Jsi_ListPushFrontNew(jdb->stmtCache, pPreStmt);
        else
            Jsi_ListPushFront(jdb->stmtCache, pPreStmt->elPtr);
        dbPrepStmtLimit(jdb);
        jdb->stmtCacheCnt = Jsi_ListSize(jdb->stmtCache);
    }
}

Project: jsish

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);
}

Project: jsish

Jsi_RC Jsi_EvalString(Jsi_Interp* interp, const char *str, int flags)
{
    int lev = interp->framePtr->level;
    return jsi_evalStrFile(interp, NULL, (char*)str, flags, lev);
}

Project: jsish

Jsi_Number Jsi_NumberInfinity(int i)
{
    Jsi_Number r = INFINITY;
    if (i < 0) r = -r;
    return r;
}

Project: jsish

void jsi_FilesysDone(Jsi_Interp *interp)
{
    Jsi_UserObjUnregister(interp, &fileobject);
    Jsi_InitZvfs(interp, 1);
    while (jsiFSList != NULL) {
        FSList *fsPtr = jsiFSList;
        jsiFSList = fsPtr->next;
        if (fsPtr)
            Jsi_Free(fsPtr);
    }
}

Project: jsish

static Jsi_OpCodes *code_eequ() { JSI_NEW_CODES(0,OP_STRICTEQU, 0); }

Project: jsish

static Jsi_RC FilesysConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_Value *that = _this;
    if (!Jsi_FunctionIsConstructor(funcPtr)) {
        Jsi_Obj *o = Jsi_ObjNew(interp);
        Jsi_PrototypeObjSet(interp, "File", o);
        Jsi_ValueMakeObject(interp, ret, o);
        that = *ret;
    }

    FileObj *fobj = (FileObj *)Jsi_Calloc(1,sizeof(*fobj));
    SIGINIT(fobj, FILEOBJ);
    if (try_open_file(interp, fobj, args) != JSI_OK) { /* Error out on open fail */
        Jsi_Free(fobj);
        return JSI_ERROR;
    }
    Jsi_Obj *nobj = (Jsi_Obj*)Jsi_ValueGetObj(interp, that);
    fobj->objId = Jsi_UserObjNew(interp, &fileobject, nobj, fobj);
    if (fobj->objId<0) {
        Jsi_Free(fobj); // TODO: finish cleanup
        return JSI_ERROR;
    }
    fobj->fobj = nobj;
    return JSI_OK;
}

Project: jsish

static Jsi_RC StringIndexOfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int sLen, bLen;
    const char *v;
    ChkString(_this, funcPtr, v, &sLen, &bLen);

    Jsi_Value *seq = Jsi_ValueArrayIndex(interp, args, skip);
    Jsi_Value *start = Jsi_ValueArrayIndex(interp, args, skip+1);

    if (!seq) {
        Jsi_ValueMakeNumber(interp,ret, -1);
        return JSI_OK;
    }

    const char *vseq = Jsi_ValueToString(interp, seq, NULL);
    int istart = 0;
    if (start) {
        if (Jsi_GetIntFromValue(interp, start, &istart)) {
            return JSI_ERROR;
        }
        if (istart < 0) istart = 0;
    }

    int r = Jsi_Strpos(v, istart, vseq, 0);
    Jsi_ValueMakeNumber(interp, ret, r);

    return JSI_OK;
}

Project: jsish

static Jsi_RC WebSocketUpdateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply to non-websock object");
    if (!cmdPtr->noUpdate)
        jsi_wsService(cmdPtr);
    return JSI_OK;
}

Project: jsish

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";
}

Project: jsish

int jsi_treeHeight(Jsi_TreeEntry* hPtr, int n)
{
    int l = -1, r = -1;
    if (hPtr->right == NULL && hPtr->right == NULL )
        return n;
    if (hPtr->left)
        l = jsi_treeHeight(hPtr->left, n+1);
    if (hPtr->right)
        r = jsi_treeHeight(hPtr->right, n+1);
    return (r > l ? r : l);
}

Project: jsish

static Jsi_RC SysTimesCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    Jsi_RC rc = JSI_OK;
    int i, n=1, argc = Jsi_ValueGetLength(interp, args);
    Jsi_Value *func = Jsi_ValueArrayIndex(interp, args, 0);
    if (Jsi_ValueIsBoolean(interp, func)) {
        bool bv;
        if (argc != 1)
            return Jsi_LogError("bool must be only arg");
        Jsi_GetBoolFromValue(interp, func, &bv);
        double now = jsi_GetTimestamp();
        if (bv)
            interp->timesStart = now;
        else {
            char buf[JSI_MAX_NUMBER_STRING];
            snprintf(buf, sizeof(buf), " (times = %.6f sec)\n", (now-interp->timesStart));
            Jsi_Puts(interp, jsi_Stderr, buf, -1);
        }
        Jsi_ValueMakeNumber(interp, ret, 0);
        return JSI_OK;
    }
    Jsi_Wide diff, start, end;
    if (!Jsi_ValueIsFunction(interp, func))
        return Jsi_LogError("arg1: expected function|bool");
    if (argc > 1 && Jsi_GetIntFromValue(interp, Jsi_ValueArrayIndex(interp, args, 1), &n) != JSI_OK)
        return JSI_ERROR;
    if (n<=0) 
        return Jsi_LogError("count not > 0: %d", n);
    struct timeval tv;
    gettimeofday(&tv, NULL);
    start = (Jsi_Wide) tv.tv_sec * 1000000 + tv.tv_usec;
    for (i=0; i<n && rc == JSI_OK; i++) {
        rc = Jsi_FunctionInvoke(interp, func, NULL, ret, NULL);
    }
    gettimeofday(&tv, NULL);
    end = (Jsi_Wide) tv.tv_sec * 1000000 + tv.tv_usec;
    diff = (end - start);
    Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)diff);
    return rc;
}

Project: jsish

bool Jsi_HashUnset(Jsi_Hash *tbl, const void *key) {
    Jsi_HashEntry *hPtr;
    hPtr = Jsi_HashEntryFind(tbl, key);
    if (!hPtr)
        return false;
    Jsi_HashEntryDelete(hPtr);
    return true;
}

Project: jsish

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);
}

Project: jsish

int jsi_nodeDepth(Jsi_TreeEntry* hPtr) {
    int d = 0;
    while (hPtr->parent != NULL) {
        d++;
        hPtr = hPtr->parent;
    }
    return d;
}

Project: jsish

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;
}

Project: jsish

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--;
}

Project: jsish

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++;
}

Project: jsish

Jsi_Value* Jsi_ValueNewObj(Jsi_Interp *interp, Jsi_Obj *o) {
    Jsi_Value *v = Jsi_ValueNew(interp);
    Jsi_ValueMakeObject(interp, &v, o);
    return v;
}

Project: jsish

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;
}

Project: jsish

int Jsi_Printf(Jsi_Interp *interp, Jsi_Channel chan, const char *fmt, ...)
{
    va_list va;
    int n;
    FILE *fp = (chan && chan->fp ? chan->fp : stdout);
    va_start(va,fmt);
    n = vfprintf(fp, fmt, va);
    va_end(va);
    return n;
}

Project: jsish

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;
    }
}

Project: jsish

static int jsi_do_throw(Jsi_Interp *interp, jsi_Pstate *ps, jsi_OpCode **ipp, jsi_TryList **tlp,
     jsi_ScopeChain **scope, Jsi_Value **currentScope, int *context_id, Jsi_Value *top, const char *nam) {
    if (Jsi_InterpGone(interp))
        return JSI_ERROR;
    jsi_TryList *trylist = *tlp;
    while (1) {
        if (trylist == NULL) {
            const char *str = (top?Jsi_ValueString(interp, top, NULL):"");
            if (str) {
                if (!Jsi_Strcmp(nam, "help"))
                    Jsi_LogError("...%s", str);
                else
                    Jsi_LogError("%s: %s", nam, str);
            }
            return JSI_ERROR;
        }
        if (trylist->type == jsi_TL_TRY) {
            int n = interp->framePtr->Sp - trylist->d.td.tsp;
            jsiPop(interp, n);
            if (*ipp >= trylist->d.td.tstart && *ipp < trylist->d.td.tend) {
                *ipp = trylist->d.td.cstart - 1;
                break;
            } else if (*ipp >= trylist->d.td.cstart && *ipp < trylist->d.td.cend) {
                trylist->d.td.last_op = jsi_LOP_THROW;
                *ipp = trylist->d.td.fstart - 1;
                break;
            } else if (*ipp >= trylist->d.td.fstart && *ipp < trylist->d.td.fend) {
                jsi_pop_try(interp, tlp);
                trylist = *tlp;
            } else Jsi_LogBug("Throw within a try, but not in its scope?");
        } else {
            jsi_restore_scope(interp, ps, trylist, scope, currentScope, context_id);
            jsi_pop_try(interp, tlp);
            trylist = *tlp;
        }
    }
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

static int jsi_FSReadProc(Jsi_Channel chan, char *buf, int size) {
    return fread(buf, 1, size, _JSI_GETFP(chan,0));
}

Project: jsish

Jsi_OptionsProcess(Jsi_Interp *interp, Jsi_OptionSpec *specs,  void *rec, Jsi_Value *args, Jsi_Wide flags)
{
    Jsi_OptionSpec *specPtr;
    int count = 0;
    char *record = (char*)rec;
    Jsi_TreeEntry *tPtr;
    Jsi_TreeSearch search;
    Jsi_Obj *to;
    bool isSafe = interp->isSafe;
    if (!Jsi_OptionsValid(interp, specs))
        return -1;

    if (interp->subOpts.compat && !(flags&JSI_OPTS_FORCE_STRICT))
        flags |=  JSI_OPTS_IGNORE_EXTRA;
    specs = jsi_GetCachedOptionSpecs(interp, specs);
    
    if (args == NULL || args->vt == JSI_VT_NULL) {
        for (specPtr = specs; specPtr->id>=JSI_OPTION_BOOL && specPtr->id < JSI_OPTION_END && specPtr->name; specPtr++) {
            specPtr->flags &= ~JSI_OPT_IS_SPECIFIED;
            if (jsi_SetOption(interp, specPtr, (char*)specPtr->name, record, NULL, flags, isSafe) != JSI_OK)
                return -1;
        }
        assert(specPtr->id == JSI_OPTION_END);
        return 0;
    }
    if (args->vt != JSI_VT_OBJECT || args->d.obj->ot != JSI_OT_OBJECT || args->d.obj->arr) {
        Jsi_LogError("expected object");
        return -1;
    }
    to = args->d.obj;
    int reqCnt = 0;
    if ((JSI_OPTS_IS_UPDATE&flags)==0 && !(JSI_OPT_PASS2&flags)) {
        for (specPtr = specs; specPtr->id>=JSI_OPTION_BOOL && specPtr->id < JSI_OPTION_END && specPtr->name; specPtr++) {
            specPtr->flags &= ~JSI_OPT_IS_SPECIFIED;
            if (specPtr->flags &  JSI_OPT_REQUIRED)
                reqCnt++;
        }
        assert(specPtr->id == JSI_OPTION_END);
    }
        
    for (tPtr = Jsi_TreeSearchFirst(to->tree, &search, 0, NULL);
        tPtr != NULL; tPtr = Jsi_TreeSearchNext(&search)) {
        
        const char *arg;
        Jsi_Value *optval;
        count++;
        arg =(char*) Jsi_TreeKeyGet(tPtr);
        optval = (Jsi_Value*)Jsi_TreeValueGet(tPtr);

        specPtr = Jsi_OptionsFind(interp, specs, arg, flags);
        if (specPtr == NULL) {
            if (flags&JSI_OPTS_IGNORE_EXTRA)
                continue;
            count = -1;
            goto done;
        }
        if (((JSI_OPT_PASS2&flags) && !(JSI_OPT_PASS2&specPtr->flags))
            || ((JSI_OPT_PASS2&specPtr->flags) && !(JSI_OPT_PASS2&flags)))
            continue;
            
        if ((JSI_OPT_READ_ONLY&specPtr->flags)) {
            Jsi_LogError("Error option is readonly: \"%.40s\"", specPtr->name);
            count = -1;
            goto done;
        }
        if ((JSI_OPTS_IS_UPDATE&flags) && (JSI_OPT_INIT_ONLY&specPtr->flags)) {
            Jsi_LogError("Error can not update option: \"%.40s\"", specPtr->name);
            count = -1;
            goto done;
        }
        if (isSafe && (JSI_OPTS_IS_UPDATE&flags) && (JSI_OPT_LOCKSAFE&specPtr->flags)) {
            Jsi_LogError("Error isSafe disallows updating option: \"%.40s\"", specPtr->name);
            count = -1;
            goto done;
        }

        if (jsi_SetOption(interp, specPtr, (char*)arg, record, optval, flags, isSafe) != JSI_OK) {
            count = -1;
            goto done;
        }
 
        specPtr->flags |= JSI_OPT_IS_SPECIFIED;
    }
    if (reqCnt) {
        for (specPtr = specs; specPtr->id>=JSI_OPTION_BOOL && specPtr->id < JSI_OPTION_END && specPtr->name; specPtr++) {
            if (specPtr->flags &  JSI_OPT_REQUIRED &&
                !(specPtr->flags&JSI_OPT_IS_SPECIFIED)) {

                Jsi_LogError("Error required field not specified: \"%.40s\"", specPtr->name);
                count = -1;
            }
        }
    }

done:
    Jsi_TreeSearchDone(&search);
    if (count<0 && !(JSI_OPTS_IS_UPDATE&flags))
        Jsi_OptionsFree(interp, specs, rec, flags);
    return count;
}

Project: jsish

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;
}

Project: jsish

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[JSI_MAX_NUMBER_STRING];
                    snprintf(fbuf, sizeof(fbuf), "Function.%s", kstr);
                    Jsi_Value *vv = Jsi_NameLookup(interp, fbuf);
                    if (vv)
                        return vv;
                }
            }
        }
    }
    return NULL;
}

Project: jsish

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;
}

Project: jsish

Jsi_RC jsi_InitJSON(Jsi_Interp *interp, int release) {
    if (release) return JSI_OK;
    Jsi_CommandCreateSpecs(interp, "JSON", jsonCmds, NULL, 0);
#ifdef TEST_JSON_INVOKE
    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_CommandInvokeJSON(interp, "Info.cmds", "[\"*\", true]", ret);
    Jsi_DString dStr = {};
    Jsi_Puts(NULL, Jsi_ValueGetDString(interp, ret, &dStr, 1), -1);
    Jsi_DSFree(&dStr);
    Jsi_DecrRefCount(interp, ret);
#endif
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

void* Jsi_Calloc(unsigned int n,unsigned int size) {
    void *v = calloc(n,size);
    Assert(v);
    return v;
}

Project: jsish

Jsi_Number Jsi_Version(void) {
    Jsi_Number d = JSI_VERSION;
    return d;
}

Project: jsish

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);
}

Project: jsish

static Jsi_RC MySqlEvalCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
                         Jsi_Value **ret, Jsi_Func *funcPtr)
{
    MySqlObj *jdb  = _mysql_getDbHandle(interp, _this, funcPtr);
    if (!jdb) return JSI_ERROR;
    int zLen, cnt = 0;
    Jsi_RC rc = JSI_OK;
    const char *zSql = Jsi_ValueArrayIndexToStr(interp, args, 0, &zLen);

    if (mysql_real_query(jdb->db, zSql, zLen))
        rc = Jsi_LogError("mysql error: %s", mysql_error(jdb->db));
    else if (jdb->enableMulti) {
        MYSQL_RES *results;
        int sr = mysql_next_result(jdb->db);
        while (sr == 0 && (results = mysql_store_result(jdb->db)))
            mysql_free_result(results);
        cnt = mysql_field_count(jdb->db);
    }
        
    if (rc == JSI_OK)
        Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)cnt);
    return rc;
}

Project: jsish

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;
}

Project: jsish

void jsi_DumpOptionSpecs(Jsi_Interp *interp, Jsi_Obj *nobj, Jsi_OptionSpec* spec)
{
    int i = 0;
    while (spec[i].id>=JSI_OPTION_BOOL && spec[i].id < JSI_OPTION_END) {
        Jsi_Obj *sobj = Jsi_ObjNewType(interp, JSI_OT_OBJECT);
        Jsi_Value *svalue = Jsi_ValueMakeObject(interp, NULL, sobj);
        jsi_DumpOptionSpec(interp, sobj, spec+i, 1);
        Jsi_ObjArrayAdd(interp, nobj, svalue);
        i++;
    }
}

Project: jsish

static void jsiSqlFuncUnixTime(sqlite3_context *context, int argc, sqlite3_value**argv) {
    Jsi_Db *jdb = (Jsi_Db*)sqlite3_user_data(context);
    SQLSIGASSERT(jdb,DB);
    Jsi_Interp *interp = jdb->interp;
    if (argc>3) {
        Jsi_LogWarn("sqlite unixtime, expected: str fmt isutc");
        return;
    }
    const char *str = NULL, *fmt = NULL;
    bool isUtc = 0;
    if (argc>=1)
        str = (char *)sqlite3_value_text(argv[0]);
    if (argc>=2)
        fmt = (char *)sqlite3_value_text(argv[1]);
    if (argc>=3)
        isUtc = (bool)sqlite3_value_int64(argv[2]);
    Jsi_Number d = 0;
    Jsi_DatetimeParse(interp, str, fmt, isUtc, &d, 0);
    sqlite3_result_double(context, (double)d);
}

Project: jsish

Jsi_TreeEntry *Jsi_TreeSearchNext(Jsi_TreeSearch *searchPtr)
{
    Jsi_TreeEntry *hPtr = NULL;
    if (searchPtr->epoch == searchPtr->treePtr->epoch)
        hPtr = searchAdd(searchPtr, NULL);
    if (!hPtr)
        Jsi_TreeSearchDone(searchPtr);
    return hPtr;
}

Project: jsish

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++;
}

Project: jsish

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;
}

Project: jsish

Jsi_Value *Jsi_ValueNew1(Jsi_Interp *interp) { return ValueNew1(interp); }

Project: jsish

static Jsi_OpCodes *code_bnot() { JSI_NEW_CODES(0,OP_BNOT, 0); }

Project: jsish

static int jsi_FSSeekProc(Jsi_Channel chan, Jsi_Wide offset, int mode) { return fseek(_JSI_GETFP(chan,1), offset, mode);}

Project: jsish

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);
}

Project: jsish

char * Jsi_GetCwd(Jsi_Interp *interp, Jsi_DString *cwdPtr) {
    char cdbuf[PATH_MAX];
    Jsi_DSInit(cwdPtr);
    if (jsiIntData.cwdFsPtr)
        Jsi_DSAppend(cwdPtr, Jsi_DSValue(&jsiIntData.pwdStr), NULL);
    else
        Jsi_DSAppend(cwdPtr, getcwd(cdbuf, sizeof(cdbuf)), NULL);
#ifdef __WIN32
    DeBackSlashify(Jsi_DSValue(cwdPtr));
#endif
    return Jsi_DSValue(cwdPtr);
}

Project: jsish

static Jsi_RC jsi_SuboptToValue(Jsi_Interp *interp, Jsi_OptionSpec* spec, Jsi_Value **outValue, Jsi_DString *outStr, void *record, Jsi_Wide flags)
{
    if (outStr)
        return JSI_ERROR;
    char *s = ((char*)record) + spec->offset;
    Jsi_OptionSpec *subspec = (Jsi_OptionSpec *)spec->data;
    if (spec == subspec) 
        return Jsi_LogError("recursive subspec not allowed");
    if (!subspec) 
        return Jsi_LogError("custom suboption spec did not set data: %s", spec->name);
    return Jsi_OptionsDump(interp, subspec, s, outValue, flags);
}

Project: jsish

Jsi_Wide Jsi_Rewind(Jsi_Interp *interp, Jsi_Channel chan) {
    if (chan->fsPtr==0 || !chan->fsPtr->rewindProc) return -1;
    return chan->fsPtr->rewindProc(chan);
}

Project: jsish

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);
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

static Jsi_RC dbIsNumArray(Jsi_Interp *interp, Jsi_Value *value, Jsi_OptionSpec* spec, void *record)
{
    if (!Jsi_ValueIsArray(interp, value)) 
        return Jsi_LogError("expected array of numbers");
    int i, argc = Jsi_ValueGetLength(interp, value);
    for (i=0; i<argc; i++) {
        Jsi_Value *v = Jsi_ValueArrayIndex(interp, value, i);
        if (!Jsi_ValueIsNumber(interp, v)) 
            return Jsi_LogError("expected array of numbers");
    }
    return JSI_OK;
}

Project: jsish

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;
    }
}

Project: jsish

static int jsi_FSPutsProc(Jsi_Channel chan, const char *s) {
    return fputs(s, _JSI_GETFP(chan,0));
}

Project: jsish

bool Jsi_NumberIsEqual(Jsi_Number n1, Jsi_Number n2)
{
    return (n1 == n2); // TODO: Do we need more than this?
}

Project: jsish

static Jsi_RC SubInterpEvalCallback(Jsi_Interp *interp, void* data)
{
    Jsi_RC rc = JSI_OK;
    Jsi_DString dStr = {};
    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;
    }
    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 (se->acdata) {
            jsi_AliasCreateCmd(interp, Jsi_DSValue(&se->func), se->acdata);
        }
        else if (se->acfunc) {
            if (JSI_OK != Jsi_FunctionInvokeJSON(interp, se->acfunc, Jsi_DSValue(&se->data), NULL))
                rc = JSI_ERROR;
            Jsi_DecrRefCount(interp, se->acfunc);
        }
        else 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 {
            const char *scp = Jsi_DSValue(&se->data);
            if (JSI_OK != Jsi_CommandInvokeJSON(interp, Jsi_DSValue(&se->func), scp, 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;
}

Project: jsish

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;
}

Project: jsish

static void MutexDone(Jsi_Mutex *mtx) { DeleteCriticalSection(&mtx->mtx); }

Project: jsish

static Jsi_RC WebSocketUnaliasCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply to non-websock object");
    int vlen, nlen;
    const char *kstr, *vstr, *nstr = Jsi_ValueArrayIndexToStr(interp, args, 0, &nlen);
    if (!nstr)
        return Jsi_LogError("arg 1: expected string");
    Jsi_Value *v, *a = cmdPtr->pathAliases;
    if (!a|| !Jsi_ValueIsObjType(interp, a, JSI_OT_OBJECT)) return JSI_OK;
    Jsi_IterObj *io = Jsi_IterObjNew(interp, NULL);
    Jsi_IterGetKeys(interp, cmdPtr->pathAliases, io, 0);
    uint i;
    for (i=0; i<io->count; i++) {
        kstr = io->keys[i];
        v = Jsi_ValueObjLookup(interp, a, kstr, 1);
        if (!v) continue;
        vstr = Jsi_ValueToString(interp, v, &vlen);
        if (!vstr) continue;
        if (nlen<=vlen) continue;
        if (Jsi_Strncmp(vstr, nstr, vlen)) continue;
        Jsi_DString dStr = {};
        Jsi_DSAppend(&dStr, "/", kstr, nstr+vlen, NULL);
        Jsi_ValueFromDS(interp, &dStr, ret);
        break;
    }
    Jsi_IterObjFree(io);
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

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);
    Jsi_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;
}

Project: jsish

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;
}

Project: jsish

Jsi_RC Jsi_HashKeysDump(Jsi_Interp *interp, Jsi_Hash *tablePtr, Jsi_Value **ret, int flags) {
    char *key;
    int n = 0;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch search;
    Jsi_Obj *nobj;
    Jsi_MapFmtKeyProc* fmtKeyProc = (Jsi_MapFmtKeyProc*)tablePtr->opts.fmtKeyProc;
    
    if (!fmtKeyProc && tablePtr->keyType == JSI_KEYS_ONEWORD && flags!=JSI_KEYS_ONEWORD )
        fmtKeyProc = jsi_hashFmtKey;
    
    if (!fmtKeyProc && tablePtr->keyType >= JSI_KEYS_STRUCT_MINSIZE) 
        return Jsi_LogError("Can not dump struct hash");
    nobj = Jsi_ObjNew(interp);
    Jsi_ValueMakeArrayObject(interp, ret, nobj);
    for (hPtr = Jsi_HashSearchFirst(tablePtr, &search);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
        key = (char*)Jsi_HashKeyGet(hPtr);
        Jsi_Value *val;
        if (fmtKeyProc) {
            val = (*fmtKeyProc)((Jsi_MapEntry*)hPtr, &tablePtr->opts, flags);
            if (!val) {
                Jsi_LogError("key format failed");
                Jsi_ValueMakeUndef(interp, ret);
                return JSI_ERROR;
            }
        } else if (tablePtr->keyType == JSI_KEYS_ONEWORD)
            val = Jsi_ValueNewNumber(interp, (Jsi_Number)(uintptr_t)key);
        else
            val = Jsi_ValueNewStringKey(interp, key);
        Jsi_ObjArraySet(interp, nobj, val, n++);
        if (fmtKeyProc && val->refCnt>1) //TODO: Hmmm. for StructKey Jsi_OptionsDump() returns refCnt=1
            Jsi_DecrRefCount(interp, val);

    }
    return JSI_OK;
}

Project: jsish

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;

}

Project: jsish

static int dbBusyHandler(void *cd, int nTries) {
    int rc;
    Jsi_Db *jdb = (Jsi_Db*)cd;
    Jsi_Value *vpargs, *items[3] = {}, *ret;
    Jsi_Interp *interp = jdb->interp;

    items[0] = Jsi_ValueNewObj(interp, jdb->fobj);
    items[1] = Jsi_ValueMakeNumber(interp, NULL, (Jsi_Number)nTries);
    vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, items, 2, 0));
    Jsi_IncrRefCount(interp, vpargs);
    ret = Jsi_ValueNew1(interp);
    rc = Jsi_FunctionInvoke(interp, jdb->onBusy, vpargs, &ret, NULL);
    if( JSI_OK!=rc ) {
        jdb->errCnt++;
        rc = 1;
    } else
        rc = dbGetIntBool(interp, ret);
    Jsi_DecrRefCount(interp, vpargs);
    Jsi_DecrRefCount(interp, ret);
    return rc;
}

Project: jsish

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);
}

Project: jsish

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;
}

Project: jsish

Jsi_RC jsi_InitString(Jsi_Interp *interp, int release)
{
    if (!release)
        interp->String_prototype = Jsi_CommandCreateSpecs(interp, "String", stringCmds, NULL, JSI_CMDSPEC_ISOBJ);
    return JSI_OK;
}

Project: jsish

static void MutexInit(Jsi_Mutex *mtx) {  InitializeCriticalSection(&mtx->mtx); }

Project: jsish

unsigned char *Jsi_ValueBlob(Jsi_Interp *interp, Jsi_Value* v, int *lenPtr)
{
    return (unsigned char*)Jsi_ValueString(interp, v, lenPtr);
}

Project: jsish

static void replace_node(Jsi_TreeEntry* oldn, Jsi_TreeEntry* newn) {
    Assert(oldn);
    Jsi_Tree* t = oldn->treePtr;
    if (oldn->parent == NULL) {
        t->root = newn;
    } else {
        if (oldn == oldn->parent->left)
            oldn->parent->left = newn;
        else
            oldn->parent->right = newn;
    }
    if (newn != NULL) {
        newn->parent = oldn->parent;
    }
}

Project: jsish

HashOneWordCreate( Jsi_Hash *tablePtr, const void *key, bool *newPtr)
{
    Jsi_HashEntry **bucketPtr;
     Jsi_HashEntry *hPtr;
    size_t hindex;

    hindex = RANDOM_INDEX(tablePtr, key);


    for (hPtr = tablePtr->buckets[hindex]; hPtr != NULL;
        hPtr = hPtr->nextPtr) {
        if (hPtr->key.oneWordValue == key) {
            if (newPtr)
                *newPtr = 0;
            return hPtr;
        }
    }

    if (newPtr)
        *newPtr = 1;
    hPtr = (Jsi_HashEntry*)Jsi_Calloc(1, sizeof(*hPtr));
    hPtr->typ = JSI_MAP_HASH;
    bucketPtr = tablePtr->buckets + hindex;
    hPtr->tablePtr = tablePtr;
    hPtr->nextPtr = *bucketPtr;
    hPtr->hval = (jsi_Hash)key;
    hPtr->clientData = 0;
    hPtr->key.oneWordValue = (void *)key; 
    *bucketPtr = hPtr;
    tablePtr->numEntries++;


    if (tablePtr->numEntries >= tablePtr->rebuildSize)
        RebuildTable(tablePtr);

    return hPtr;
}

Project: jsish

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;
    }
}

Project: jsish

static Jsi_RC Jsi_DoneMySql(Jsi_Interp *interp)
{
    if (Jsi_UserObjUnregister(interp, &mysqlobject) != JSI_OK)
        return JSI_ERROR;
    Jsi_PkgProvide(interp, "MySql", -1, NULL);
    return JSI_OK;
}

Project: jsish

static void delete_case1(Jsi_TreeEntry* n) {
    if (n->parent != NULL)
        delete_case2(n);
}

Project: jsish

static Jsi_OpCodes *code_mul() { JSI_NEW_CODES(0,OP_MUL, 0); }

Project: jsish

Jsi_RC Jsi_MutexLock(Jsi_Interp *interp, Jsi_Mutex *mtx) { if (interp) interp->lockRefCnt++; return MutexLock(interp, mtx);}

Project: jsish

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;
    }
}

Project: jsish

static Jsi_RC jsi_CArrayToValue(Jsi_Interp *interp, Jsi_OptionSpec* spec, Jsi_Value **outValue, Jsi_DString *dStr, void *record, Jsi_Wide flags)
{
    Jsi_Obj *obj = NULL;
    if (dStr)
        return JSI_ERROR;
    uchar *s = (uchar*)((char*)record) + spec->offset;
    Jsi_OptionSpec *subSpec = spec->init.OPT_CARRAY;
    int i, cnt, isize, size = spec->arrSize;
    if (!subSpec || size<=0 || (isize=subSpec->size)<=0)
        goto bail;
    isize = isize/size;
#ifndef JSI_LITE_ONLY //TODO: already in lite #ifdef
    obj = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
    cnt = 0;
    for (i=0; i<size; i++) {
        Jsi_Value *v = Jsi_ValueNew1(interp);
        cnt++;
        Jsi_RC rc = Jsi_OptionsGet(interp, subSpec, s, spec->name, &v, 0);
        Jsi_ObjArrayAdd(interp, obj, v);
        Jsi_DecrRefCount(interp, v);
        if (JSI_OK != rc)
            goto bail;
        s += isize;
    }
    Jsi_ValueMakeArrayObject(interp, outValue, obj);
#endif
    return JSI_OK;
bail:
    if (obj)
        Jsi_ObjFree(interp, obj);
    Jsi_LogError("bad config");
    return JSI_ERROR;
}

Project: jsish

static void SetupStdChan(Jsi_Chan* ch, FILE *fp, Jsi_Filesystem *fsPtr, int flags) {
    memset(ch, 0, sizeof(*ch));
    ch->fp = fp;
    ch->fsPtr = fsPtr;
    ch->flags = flags|JSI_FS_NOCLOSE;
}

Project: jsish

jsi_wsdeletePss(jsi_wsPss *pss)
{
    jsi_wsCmdObj*cmdPtr = pss->cmdPtr;
    if (pss->sig == 0)
        return;
    WSSIGASSERT(pss, PWS);
    if (pss->state == PWS_DEAD)
        return;
    if (cmdPtr && cmdPtr->debug>3)
        fprintf(stderr, "PSS DELETE: %p\n", pss);

    jsi_wsrecv_flush(cmdPtr, pss);
    if (pss->hPtr) {
        Jsi_HashValueSet(pss->hPtr, NULL);
        Jsi_HashEntryDelete(pss->hPtr);
        pss->hPtr = NULL;
    }
    Jsi_Interp *interp = cmdPtr->interp;
    if (pss->stack) {
        Jsi_StackFreeElements(interp, pss->stack, jsi_wsDelPss);
        Jsi_StackFree(pss->stack);
    }
    Jsi_DSFree(&pss->dHdrs);
    if (pss->isWebsock)
        pss->cmdPtr->stats.connectCnt--;
    Jsi_OptionsFree(cmdPtr->interp, WPSOptions, pss, 0);
    pss->state = PWS_DEAD;
    Jsi_DSFree(&pss->resultStr);
    Jsi_DSFree(&pss->paramDS);
    if (pss->lastData)
        Jsi_Free(pss->lastData);
    pss->lastData = NULL;
    if (pss->spa)
        lws_spa_destroy(pss->spa);
    Jsi_Free(pss);
}

Project: jsish

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));
}

Project: jsish

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;
}

Project: jsish

static bool jsi_wsIsSSIExt(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss, const char *ext) {
    if (cmdPtr->ssiExts) {
        Jsi_Value *mVal = Jsi_ValueObjLookup(interp, cmdPtr->ssiExts, ext, 1);
        if (mVal) {
            bool b = 0;
            if (Jsi_ValueGetBoolean(interp, mVal, &b) != JSI_OK) {
                Jsi_LogWarn("expected bool for ssiExts '%s': disabling all\n", ext);
                Jsi_DecrRefCount(interp, cmdPtr->ssiExts);
                cmdPtr->ssiExts = NULL;
            }
            return b;
        }
    }
    if (ext[0]=='s'  && (!Jsi_Strcmp(ext, "shtml")
        || !Jsi_Strcmp(ext, "scss") || !Jsi_Strcmp(ext, "sjs")))
        return 1;

    return 0;
}

Project: jsish

void jsi_DebuggerStmt(void) {
    // Called for "debugger" statement.
}

Project: jsish

int Jsi_TreeEntryDelete (Jsi_TreeEntry *entryPtr)
{
    int cnt = 0;
    Jsi_TreeEntry* n = entryPtr;
    Jsi_Tree* treePtr = n->treePtr;

    if (treePtr->flags.destroyed  || treePtr->flags.nonredblack == 1 /* || entryPtr->f.bits.deletesub */) {
        goto dodel;
    }
    /*printf("DEL(tree=%p,root=%p): (%p)%s\n", treePtr, treePtr->root, entryPtr,(char*)entryPtr->key.string);*/
    /*dumpTree(treePtr);*/
    if (treePtr->opts.lockTreeProc && (*treePtr->opts.lockTreeProc)(treePtr, 1) != JSI_OK)
        return -1;
    entryPtr->treePtr->epoch++;
    if (n->left != NULL && n->right != NULL) {
        /* swap key/values delete pred instead */
        Jsi_TreeEntry* pred = maximum_node(n->left);
        switch (treePtr->keyType) {
        case JSI_KEYS_STRINGKEY:
        case JSI_KEYS_ONEWORD: {
            void *nv = n->value;
            n->value = pred->value;
            pred->value = nv;
            nv = n->key.oneWordValue;
            n->key.oneWordValue = pred->key.oneWordValue;
            pred->key.oneWordValue = nv;
            n = pred;
            break;
        }
        case JSI_KEYS_STRING:
            SwapNodes(n,pred);
            break;
        default: { // Struct keys have the same length so we swap bytes.
            uint i;
            void *nv = n->value;
            n->value = pred->value;
            pred->value = nv;
            char ct, *cs = (char*)(n->key.string), *cd = (char*)(pred->key.string);
            for (i=0; i<treePtr->keyType; i++, cs++, cd++) {
                ct = *cd;
                *cd = *cs;
                *cs = ct;
            }
        }
                
        }
    }
    delete_one_child(n);
    cnt++;
    /*dumpTree(treePtr);*/
dodel:
    treePtr->numEntries--;
    n->treePtr = NULL;
    if (treePtr->opts.freeTreeProc && n && n->value)
        (treePtr->opts.freeTreeProc)(treePtr->opts.interp, n, n->value);
    Jsi_Free(n);
    if (treePtr->opts.lockTreeProc)
        (*treePtr->opts.lockTreeProc)(treePtr, 0);
    return cnt;
}

Project: jsish

static Jsi_OpCodes *code_assign(jsi_Pstate *p, jsi_Pline *line, int h) { JSI_NEW_CODESLN(0,OP_ASSIGN, h); }

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

static int jsi_GetListType(Jsi_MapEntry *h) {
    Jsi_HashEntry *hPtr =(Jsi_HashEntry *)h;
    return hPtr->typ;
}

Project: jsish

static Jsi_RC StringTrimRightCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    return _StringTrimCmd(interp, args, _this, ret, funcPtr, 2);
}

Project: jsish

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);
}

Project: jsish

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);
}

Project: jsish

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);
}

Project: jsish

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 && !interp->cleanup) {
        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);
}

Project: jsish

static Jsi_RC jsi_ValueToNull(Jsi_Interp *interp, Jsi_OptionSpec* spec, Jsi_Value *inValue, const char *inStr, void *record, Jsi_Wide flags)
{
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

static Jsi_RC MySqlPingCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
                              Jsi_Value **ret, Jsi_Func *funcPtr)
{
    MySqlObj *jdb;
    if (!(jdb = _mysql_getDbHandle(interp, _this, funcPtr))) return JSI_ERROR;
    int n = mysql_ping(jdb->db);
    bool noErr = 0;
    Jsi_Value *val = Jsi_ValueArrayIndex(interp, args, 0);
    if (val)
        Jsi_ValueGetBoolean(interp, val, &noErr);
    if (n && noErr==0) 
        return Jsi_LogError("ping failed: (%d) %s", n, mysql_error(jdb->db));
    Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)n);

    return JSI_OK;    
}

Project: jsish

static Jsi_RC MySqlErrorStateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
                              Jsi_Value **ret, Jsi_Func *funcPtr)
{
    MySqlObj *jdb;
    if (!(jdb = _mysql_getDbHandle(interp, _this, funcPtr))) return JSI_ERROR;
    const char *str = mysql_sqlstate(jdb->db);
    if (str)
        Jsi_ValueMakeStringDup(interp, ret, str);
    return JSI_OK;
}

Project: jsish

Jsi_Channel jsi_FSOpenProc(Jsi_Interp *interp, Jsi_Value *file, const char *modeString)
{
    return Jsi_Open(interp, file, modeString);
}

Project: jsish

static bool jsi_wsAddStdHeader(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, struct lws *wsi,    Jsi_DString *hStr) {
    uchar buffer[JSI_BUFSIZ];
    uchar *p = (unsigned char *)buffer, *end = p + sizeof(buffer);
    const char *srv = cmdPtr->server;
    if (!srv) srv = "jsiWebSocket";
    int n = 0;
    if (srv[0] && lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (uchar*)srv, Jsi_Strlen(srv), &p, end))
        return false;
    n = p - buffer;
    if (n>0) {
        Jsi_DSAppendLen(hStr, (char*)buffer, n);
        p = buffer;
    }
    return true;
}

Project: jsish

static Jsi_RC regExpFree(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    Jsi_RegExpFree((Jsi_Regex*)ptr);
    return JSI_OK;
}

Project: jsish

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);
}

Project: jsish

void dbSetupCallbacks(Jsi_Db *jdb, Jsi_Db *ojdb)
{
    if (jdb->onAuth && (!ojdb || !ojdb->onAuth) )
        sqlite3_set_authorizer(jdb->db, dbAuthCallback, jdb);
    else
        sqlite3_set_authorizer(jdb->db, 0, 0);

    if (jdb->onCommit && (!ojdb || !ojdb->onCommit) )
        sqlite3_commit_hook(jdb->db, dbCommitHandler, jdb);
    else
        sqlite3_commit_hook(jdb->db, 0, 0);

    if (jdb->onBusy && (!ojdb || !ojdb->onBusy) )
        sqlite3_busy_handler(jdb->db, dbBusyHandler, jdb);
    else
        sqlite3_busy_handler(jdb->db, 0, 0);
    
    if (jdb->onTrace && (!ojdb || !ojdb->onTrace) )
        sqlite3_trace(jdb->db, dbTraceHandler, jdb);
    else
        sqlite3_trace(jdb->db, 0, 0);

    if (jdb->onNeedCollate && (!ojdb || !ojdb->onNeedCollate) )
        sqlite3_collation_needed(jdb->db, jdb, dbCollateNeeded);
    else
        sqlite3_collation_needed(jdb->db, 0, 0);

    if (jdb->onUpdate && (!ojdb || !ojdb->onUpdate) )
        sqlite3_update_hook(jdb->db, dbUpdateHandler, jdb);
    else
        sqlite3_update_hook(jdb->db, 0, 0);

    if (jdb->onWalHook && (!ojdb || !ojdb->onWalHook) )
        sqlite3_wal_hook(jdb->db, dbWalHandler, jdb);
    else
        sqlite3_wal_hook(jdb->db, 0, 0);

    if (jdb->onRollback && (!ojdb || !ojdb->onRollback) )
        sqlite3_rollback_hook(jdb->db, dbRollbackHandler, jdb);
    else
        sqlite3_rollback_hook(jdb->db, 0, 0);

    if (jdb->onProfile && (!ojdb || !ojdb->onProfile) )
        sqlite3_profile(jdb->db, dbProfileHandler, jdb);
    else
        sqlite3_profile(jdb->db, 0, 0);

    if (jdb->onProgress && jdb->progressSteps && (!ojdb || !ojdb->onProgress || ojdb->progressSteps != jdb->progressSteps) )
        sqlite3_progress_handler(jdb->db, jdb->progressSteps, dbProgressHandler, jdb);
    else
        sqlite3_progress_handler(jdb->db, 0, 0, 0);
    
    if (!ojdb || jdb->load != ojdb->load)
        sqlite3_enable_load_extension(jdb->db, jdb->load);

    if (!ojdb || jdb->timeout != ojdb->timeout)
        sqlite3_busy_timeout( jdb->db, jdb->timeout );

/*    if (jdb->onUnlock && (!ojdb || !ojdb->onUnlock) )
        sqlite3_unlock_notify(jdb->db, dbUnlockNotify, (void*)jdb);
    else
        sqlite3_unlock_notify(jdb->db, 0, 0);
        */
}

Project: jsish

static Jsi_OpCodes *code_band() { JSI_NEW_CODES(0,OP_BAND, 0); }

Project: jsish

Jsi_OptionsCustomPrint(void* clientData, Jsi_Interp *interp, const char *name, void *rec, int offset)
{
    char *record = (char*)rec;
    Jsi_Value *valuePtr;
    valuePtr = *(Jsi_Value **)(record + offset);
    return valuePtr;
}

Project: jsish

Jsi_Db* Jsi_DbNew(const char *zFile, int inFlags /* JSI_DBI_* */)
{
    char *zErrMsg;
    int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
#ifdef SQLITE_JSI_DEFAULT_FULLMUTEX
    flags |= SQLITE_OPEN_FULLMUTEX;
#else
    flags |= SQLITE_OPEN_NOMUTEX;
#endif
    if (!zFile)
        zFile = ":memory:";
    zErrMsg = 0;
    Jsi_Db *db = (Jsi_Db*)Jsi_Calloc(1, sizeof(*db) );
    if( db==0 ) {
        JSI_DBQUERY_PRINTF( "malloc failed\n");
        return NULL;
    }
    db->sig = SQLITE_SIG_DB;
    db->stmtCacheMax = NUM_PREPARED_STMTS;
    db->optPtr = &db->queryOpts;

    if (inFlags&JSI_DBI_READONLY) {
        flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE);
        flags |= SQLITE_OPEN_READONLY;
    } else {
        flags &= ~SQLITE_OPEN_READONLY;
        flags |= SQLITE_OPEN_READWRITE;
        if (inFlags&JSI_DBI_NOCREATE) {
            flags &= ~SQLITE_OPEN_CREATE;
        }
    }
    if(inFlags&JSI_DBI_NO_MUTEX) {
        flags |= SQLITE_OPEN_NOMUTEX;
        flags &= ~SQLITE_OPEN_FULLMUTEX;
    } else {
        flags &= ~SQLITE_OPEN_NOMUTEX;
    }
    if(inFlags&JSI_DBI_FULL_MUTEX) {
        flags |= SQLITE_OPEN_FULLMUTEX;
        flags &= ~SQLITE_OPEN_NOMUTEX;
    } else {
        flags &= ~SQLITE_OPEN_FULLMUTEX;
    }
    char cpath[PATH_MAX];
    char *npath = Jsi_FileRealpathStr(NULL, zFile, cpath);
    
    if (SQLITE_OK != sqlite3_open_v2(npath, &db->db, flags, NULL)) {
        JSI_DBQUERY_PRINTF( "db open failed: %s\n", npath);
        goto bail;
    }
    //Jsi_DSFree(&translatedFilename);

    if( SQLITE_OK!=sqlite3_errcode(db->db) ) {
        zErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(db->db));
        DbClose(db->db);
        db->db = 0;
    }
    if( db->db==0 ) {
        JSI_DBQUERY_PRINTF( "Db open failed %s\n", zErrMsg);
        sqlite3_free(zErrMsg);
        goto bail;
    }
    db->stmtHash = Jsi_HashNew(NULL, JSI_KEYS_STRING, NULL);
    db->strKeyTbl = Jsi_HashNew(NULL, JSI_KEYS_STRING, NULL);
    db->stmtCache = Jsi_ListNew((Jsi_Interp*)db, 0, dbStmtFreeProc);
    return db;
    
bail:
    return NULL;
}

Project: jsish

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;
}

Project: jsish

static Jsi_OpCodes *code_efinal(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_EFINAL, 0); }

Project: jsish

static Jsi_RC WebSocketIdsCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    const char *val = Jsi_ValueArrayIndexToStr(interp, args, 0, NULL);
    Jsi_DString dStr = {"["};
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    int cnt = 0;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
        pss = (jsi_wsPss*)Jsi_HashValueGet(hPtr);
        WSSIGASSERT(pss, PWS);
        if (pss->state == PWS_DEAD) continue;
        if (val && Jsi_Strcmp(pss->key, val)) continue;
        Jsi_DSPrintf(&dStr, "%s%d", (cnt++?",":""), pss->wid);
        if (val) break;
    }
    Jsi_DSAppend(&dStr, "]", NULL);
    Jsi_RC rc = Jsi_JSONParse(interp, Jsi_DSValue(&dStr), ret, 0);
    Jsi_DSFree(&dStr);
    return rc;
}

Project: jsish

const char *jsi_ScopeStrsGet(Jsi_ScopeStrs *ss, int i)
{
    if (i < 0 || i >= ss->count)
        return NULL;
    return ss->args[i].name;
}

Project: jsish

static void delete_one_child(Jsi_TreeEntry*n)
{
    Jsi_TreeEntry *child;
    Assert(n->left == NULL || n->right == NULL);
    child = n->right == NULL ? n->left  : n->right;
#if 1
    if (node_color(n) == _JSI_TREE_BLACK) {
        set_color(n, node_color(child));
        delete_case1(n);
    }
    replace_node(n, child);
    if (n->parent == NULL && child != NULL)
        set_color(child, _JSI_TREE_BLACK);
    
#else
    replace_node(n, child);
    if (node_color(n) == _JSI_TREE_BLACK) {
        if (node_color(child) == _JSI_TREE_RED)
            child->f.bits.color = _JSI_TREE_BLACK;
        else
            delete_case1(n);
    }
#endif
}

Project: jsish

static Jsi_RC StringToUpperCaseCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int sLen, bLen;
    const char *vstr;
    ChkString(_this, funcPtr, vstr, &sLen, &bLen);

    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    jsi_utf_tocase(vstr, 1, &dStr);
    Jsi_ValueFromDS(interp, &dStr, ret);
    return JSI_OK;
}

Project: jsish

char *Jsi_FileRealpathStr(Jsi_Interp *interp, const char *path, char *newname)
{
    if (!path || !path[0]) return NULL;
    Jsi_DString dStr, eStr;
    char *npath = (char*)path, *apath;
    Jsi_DSInit(&dStr); Jsi_DSInit(&eStr);
    if (*path == '~') {
#ifndef __WIN32
        struct passwd pw, *pwp; /* TODO: could fallback to using env HOME. */
        char buf[JSI_BUFSIZ];
        int n = getpwuid_r(getuid(), &pw, buf, sizeof(buf), &pwp);
        const char *homedir = (n == 0 ? pwp->pw_dir : "");
        Jsi_DSAppend(&dStr, homedir, path[1] == '/' ? "" : "/", path+1, NULL);
#else
        const char *homedir = getenv("HOMEPATH");
        if (!homedir) homedir = "/";
        const char *homedrv = getenv("HOMEDRIVE");
        if (!homedrv) homedrv = "";
        Jsi_DSAppend(&dStr, homedrv, homedir, path[1] == '/' ? "" : "/", path+1, NULL);
#endif
        npath = Jsi_DSValue(&dStr);
    }
#ifdef __WIN32
    if (Jsi_Strncmp(path, JSI_ZVFS_DIR, sizeof(JSI_ZVFS_DIR)-1)==0 || Jsi_Strncmp(path, JSI_VFS_DIR, sizeof(JSI_VFS_DIR)-1)==0)
        apath = NULL;
    else
#endif
    apath = realpath(npath, newname);
    if (!apath) {
        if ((path[0] == '.' && path[1] == '/') || (path[0] != '/' && 
        !(path[0] == '.' && path[1] == '.') && path[1] != ':')) {
            Jsi_GetCwd(interp, &eStr);
            Jsi_DSAppend(&eStr, "/", path, NULL);
            npath = Jsi_DSValue(&eStr);
            apath = realpath(npath, newname);
            //npath = (char*)path;
        }
    }
    if (!apath) {
        if (newname)
            Jsi_Strcpy(apath=newname, npath);
        else
            apath = Jsi_Strdup(npath);
#ifndef __WIN32
        /* If path not exists on unix we try to eliminate ../ and /./ etc.*/
        NormalizeUnixPath(interp, apath);
#endif
    }
#ifdef __WIN32
    DeBackSlashify(apath);
#endif
    Jsi_DSFree(&dStr); Jsi_DSFree(&eStr);
    return apath;
}

Project: jsish

static Jsi_RC dbBindStmt(Jsi_Db *jdb, SqlPreparedStmt *prep)
{
    sqlite3_stmt *pStmt = prep->pStmt;    /* Object used to cache statement */
    Jsi_Interp *interp = jdb->interp;
    int i, btype = 0, bindArr=0, n;
    Jsi_RC rc = JSI_OK;
    Jsi_Number r;
    Jsi_Wide wv;
 
    Jsi_Value *pv = NULL, *apv = NULL;
    int nVar = sqlite3_bind_parameter_count(pStmt);
    char tname[50];
    
   /* Bind values to parameters that begin with @, $, :, or ? */
    for(i=1; i<=nVar; i++) {
        tname[0] = 0;
        int isInt = 0, isBlob = 0;
        const char *zVar = sqlite3_bind_parameter_name(pStmt, i);
        if (zVar == NULL) {
            if (!jdb->optPtr || !(apv=jdb->optPtr->values)) 
                return Jsi_LogError("? bind without values for param %d", i);
            if (!(pv =Jsi_ValueArrayIndex(interp, apv, i-1))) 
                return Jsi_LogError("array element %d missing", nVar);
        }
        else if((zVar[0]=='$' || zVar[0]==':' || zVar[0]=='@') ) {
            int zvLen = Jsi_Strlen(zVar);
            char *zcp;
            if (zVar[0] =='$' && ((zcp = (char*)Jsi_Strchr(zVar,'('))) && zVar[zvLen-1] == ')')
            {
                bindArr = 1;
                Jsi_DString vStr;
                Jsi_DSInit(&vStr);
                Jsi_DSAppendLen(&vStr, zVar+1, (zcp-zVar-1));
                int slen = Jsi_Strlen(zcp);
                const char *ttp;
                if ((ttp = Jsi_Strchr(zVar,':'))) { // Extract bind-type.
                    Jsi_DString tStr = {};
                    int tlen = Jsi_Strlen(ttp+1);
                    Jsi_DSAppendLen(&tStr, ttp+1, tlen-1);
                    if (!jdb->typeNameHash)
                        dbTypeNameHashInit(jdb);
                    Jsi_HashEntry *htPtr = Jsi_HashEntryFind(jdb->typeNameHash, Jsi_DSValue(&tStr));
                    int rc = ( htPtr != NULL);
                    if (!htPtr) {
                        Jsi_DString eStr = {};
                        Jsi_HashSearch search;
                        Jsi_Interp *interp = jdb->interp;
                        int n = 0;
                        Jsi_HashEntry *hPtr;
                        for (hPtr = Jsi_HashSearchFirst(jdb->typeNameHash, &search);
                            hPtr != NULL; hPtr = Jsi_HashSearchNext(&search)) {
                            const char *key = (char*)Jsi_HashKeyGet(hPtr);
                            Jsi_DSAppend(&eStr, (n++?", ":""), key, NULL);
                        }
                        Jsi_LogWarn("bind type \"%s\" is not one of: %s", Jsi_DSValue(&tStr), Jsi_DSValue(&eStr));
                        Jsi_DSFree(&eStr);
                    }
                    Jsi_Strcpy(tname, Jsi_DSValue(&tStr));
                    Jsi_DSFree(&tStr);
                    if (!rc)
                        return JSI_ERROR;

                    btype = (uintptr_t)Jsi_HashValueGet(htPtr);
                    Jsi_DSFree(&tStr);
                    slen -= tlen;
                }

                if (isdigit(zcp[1])) {
                    Jsi_DSAppendLen(&vStr, "[", 1);
                    Jsi_DSAppendLen(&vStr, zcp+1, slen-2);
                    Jsi_DSAppendLen(&vStr, "]", 1);
                } else {
                    if (zcp[1] != '[')
                        Jsi_DSAppendLen(&vStr, ".", 1);
                    Jsi_DSAppendLen(&vStr, zcp+1, slen-2);
                }
                pv = Jsi_NameLookup(interp, Jsi_DSValue(&vStr));
                Jsi_DSFree(&vStr);
            } else
                pv = Jsi_VarLookup(interp, &zVar[1]);
        } else 
            return Jsi_LogError("can not find bind var %s", zVar);
            
        if(!pv ) {
            if (!jdb->bindWarn) {
                Jsi_LogError("unknown bind param: %s", zVar);
                rc = JSI_ERROR;
                break;
            } else
                Jsi_LogWarn("unknown bind param: %s", zVar);
        } else {
            int match = 1, cast = (jdb->optPtr->typeCheck==dbTypeCheck_Cast);
            if (btype && !Jsi_ValueIsUndef(interp, pv)) {
                switch (btype) {
                    case JSI_OPTION_STRBUF:
                        isBlob = 1;
                    case JSI_OPTION_STRING:
                        if (cast)
                            Jsi_ValueToString(interp, pv, &n);
                        else
                            match = Jsi_ValueIsString(interp, pv);
                        break;
                    case JSI_OPTION_NUMBER:
                    case JSI_OPTION_DOUBLE:
                        if (cast)
                            Jsi_ValueToNumber(interp, pv);
                        else
                            match = Jsi_ValueIsNumber(interp, pv);
                        break;
                    case JSI_OPTION_TIME_W:
                    case JSI_OPTION_TIME_T:
                    case JSI_OPTION_INT64:
                        isInt = 1;
                        if (cast)
                            Jsi_ValueToNumber(interp, pv);
                        else
                            match = Jsi_ValueIsNumber(interp, pv);
                        break;
                    case JSI_OPTION_BOOL:
                        if (cast)
                            Jsi_ValueToBool(interp, pv);
                        else
                            match = Jsi_ValueIsNumber(interp, pv);
                        break;
                    case JSI_OPTION_TIME_D:
                        if (cast)
                            Jsi_ValueToNumber(interp, pv); //TODO: do something more for dates?
                        else
                            match = Jsi_ValueIsNumber(interp, pv);
                        break;
                    default:
                        Jsi_LogBug("Unhandled bind type: %s = %d", tname, btype);
                }
                if (cast == 0 && match == 0) {
                    int ltyp = (jdb->optPtr->typeCheck==dbTypeCheck_Error?JSI_LOG_ERROR:JSI_LOG_WARN);
                    Jsi_LogMsg(interp, ltyp, "bind param \"%s\" type is not \"%s\"", zVar, tname);
                    if (ltyp == JSI_LOG_ERROR)
                        return JSI_ERROR;
                }
            }
            bool bn, isArr;
            const char *dectyp;
            if (Jsi_ValueIsBoolean(interp, pv)) {
                Jsi_GetBoolFromValue(interp, pv, &bn);
                sqlite3_bind_int(pStmt, i, bn);
            } else if (Jsi_ValueIsNumber(interp, pv)) {
                Jsi_GetNumberFromValue(interp, pv, &r);
                wv = (Jsi_Wide)r;
                if (isInt || (jdb->forceInt && (((Jsi_Number)wv)-r)==0))
                    sqlite3_bind_int64(pStmt, i,wv);
                else
                    sqlite3_bind_double(pStmt, i,(double)r);
            } else if (Jsi_ValueIsNull(interp, pv) || (Jsi_ValueIsUndef(interp, pv) && jdb->queryOpts.mapundef)) {
                sqlite3_bind_null(pStmt, i);
            } else if (Jsi_ValueIsString(interp, pv)) {
                const char *sstr = Jsi_ValueGetStringLen(interp, pv, &n);
                if (!sstr) sstr = "";
                if (isBlob)
                    sqlite3_bind_blob(pStmt, i, (char *)sstr, n, SQLITE_TRANSIENT );
                else
                    sqlite3_bind_text(pStmt, i, (char *)sstr, n, SQLITE_TRANSIENT );
            } else if (!jdb->noJsonConv && bindArr && ((isArr=Jsi_ValueIsArray(interp, pv))
                || Jsi_ValueIsObjType(interp, pv, JSI_OT_OBJECT))
                && (((dectyp = sqlite3_column_decltype(pStmt, i))==NULL) || 
                    !Jsi_Strncasecmp(dectyp,"charjson",8))) {
                    // Limitation: on INSERT can not access decltype.
                    Jsi_DString jStr = {};
                    Jsi_ValueGetDString(interp, pv, &jStr, JSI_OUTPUT_JSON|JSI_JSON_STRICT);
                    n = Jsi_DSLength(&jStr);
                    sqlite3_bind_text(pStmt, i, Jsi_DSValue(&jStr), n, SQLITE_TRANSIENT );
                    Jsi_DSFree(&jStr);
            } else {
                if (!jdb->bindWarn) {
                    Jsi_LogError("bind param must be string/number/bool/null: %s", zVar);
                    rc = JSI_ERROR;
                    break;
                } else
                    Jsi_LogWarn("bind param must be string/number/bool/null: %s", zVar);
                sqlite3_bind_null(pStmt, i);
            }

        }
    }
    return rc;
}

Project: jsish

static void sqliteObjErase(Jsi_Db *jdb)
{
    dbDeleteCmd(jdb);
    jdb->db = NULL;
}

Project: jsish

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;
}

Project: jsish

void jsi_OBJCHK(Jsi_Obj *obj) {
    SIGASSERTV(obj,OBJ);
    assert(obj->ot <= JSI_OT__MAX);
}

Project: jsish

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;
}

Project: jsish

static Jsi_StructSpec* dbLookupSpecFromName(Jsi_StructSpec *specs, const char *name) {
    Jsi_StructSpec *specPtr = NULL;
    for (specPtr = specs; specPtr->id>=JSI_OPTION_BOOL && specPtr->id < JSI_OPTION_END; specPtr++) {
        if  (specPtr->flags&JSI_OPT_DB_IGNORE)
            continue;
        const char *cname = specPtr->name;
        if (cname[0] == name[0] && !Jsi_Strncasecmp(cname, name, -1))
            return specPtr;
    }
    return NULL;
}

Project: jsish

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;
    }
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

static void MutexUnlock(Jsi_Mutex *mtx) { pthread_mutex_unlock(&mtx->mtx); }

Project: jsish

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;
}

Project: jsish

static Jsi_OpCodes *code_push_args() { JSI_NEW_CODES(0,OP_PUSHARG, 0); }

Project: jsish

const Jsi_OptionTypedef* Jsi_OptionTypeInfo(Jsi_OptionId typ) {
    if (typ>=JSI_OPTION_BOOL && typ < JSI_OPTION_END)
        return jsi_OptTypeInfo+typ;
    return NULL;
}

Project: jsish

Jsi_ScopeStrs *jsi_ScopeGetVarlist(jsi_Pstate *ps)
{
    Jsi_Interp *interp = ps->interp;
    return jsi_ScopeStrsDup(ps, interp->scopes[interp->cur_scope]);
}

Project: jsish

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;
}

Project: jsish

bool Jsi_FunctionIsConstructor(Jsi_Func *funcPtr)
{
    return (funcPtr->f.bits.iscons);
}

Project: jsish

static Jsi_OpCodes *code_push_bool(int v) { JSI_NEW_CODES(0,OP_PUSHBOO, v); }

Project: jsish

void *Jsi_HashValueGet(Jsi_HashEntry *h)
{
    return h->clientData;
}

Project: jsish

void *Jsi_StackPop(Jsi_Stack *stack)
{
    if (stack->len == 0)
        return NULL;
    stack->len--;
    return stack->vector[stack->len];
}

Project: jsish

void *Jsi_HashKeyGet(Jsi_HashEntry *hPtr)
{
    Jsi_Hash *t = hPtr->tablePtr;
    return (t->keyType == JSI_KEYS_ONEWORD || t->keyType == JSI_KEYS_STRINGKEY ? hPtr->key.oneWordValue : hPtr->key.string);
}

Project: jsish

static Jsi_OpCodes *code_nop() { JSI_NEW_CODES(0,OP_NOP, 0); }

Project: jsish

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); 
}

Project: jsish

void* Jsi_InterpThread(Jsi_Interp *interp) { return NULL; }

Project: jsish

const char *jsi_PstateGetFilename(jsi_Pstate *ps)
{
    Jsi_Interp *interp = ps->interp;
    return interp->curFile;
}

Project: jsish

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);
}

Project: jsish

static Jsi_RC StringFromCharCodeCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (_this->vt != JSI_VT_OBJECT || _this->d.obj->ot == JSI_OT_STRING)
        return Jsi_LogError("should be called via String.fromCharCode");
    
    Jsi_DString dStr = {};
    int n, i, len, argc = Jsi_ValueGetLength(interp, args);
    for (i=0; i<argc; i++) {
        Jsi_Value *v = Jsi_ValueArrayIndex(interp, args, i);
        if (!Jsi_ValueIsNumber(interp, v) || Jsi_GetIntFromValue(interp, v, &n) != JSI_OK) {
            Jsi_DSFree(&dStr);
            return Jsi_LogError("expected int value at arg %d", i+1);
        }
        char dest[5];
        len = Jsi_UniCharToUtf((Jsi_UniChar)n, dest);
        Jsi_DSAppendLen(&dStr, dest, len);
    }
    
    Jsi_ValueMakeDStringObject(interp, ret, &dStr);
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

static void dbEvalRowInfo(
    DbEvalContext *p,               /* Evaluation context */
    int *pnCol,                     /* OUT: Number of column names */
    char ***papColName,           /* OUT: Array of column names */
    int **papColType
) {
    /* Compute column names */
    // Jsi_Interp *interp = p->jdb->interp;

    if( 0==p->apColName ) {
        sqlite3_stmt *pStmt = p->pPreStmt->pStmt;
        int i;                        /* Iterator variable */
        int nCol;                     /* Number of columns returned by pStmt */
        char **apColName = 0;      /* Array of column names */
        int *apColType = 0;
        const char *zColName;         /* Column name */
        int numRid = 0;               /* Number of times rowid seen. */

        p->nCol = nCol = sqlite3_column_count(pStmt);
        if( nCol>0 && (papColName || p->pArray) ) {
            int cnLen = sizeof(char*)*nCol, cnStart = cnLen;
            for(i=0; i<nCol && cnLen<sizeof(p->staticColNames); i++)
                cnLen += Jsi_Strlen(sqlite3_column_name(pStmt,i))+1;
            if (cnLen>=sizeof(p->staticColNames)) {
                apColName = (char**)Jsi_Calloc(nCol, sizeof(char*) );
                cnStart = 0;
            } else {
                apColName = (char**)p->staticColNames;
            }
            if (papColType) {
                if (nCol < SQL_MAX_STATIC_TYPES)
                    apColType = p->staticColTypes;
                else
                    apColType = (int*)Jsi_Calloc(nCol, sizeof(int));
            }
            for(i=0; i<nCol; i++) {
                zColName = sqlite3_column_name(pStmt,i);
                if (cnStart==0)
                    apColName[i] = Jsi_Strdup(zColName);
                else {
                    apColName[i] = p->staticColNames+cnStart;
                    Jsi_Strcpy(apColName[i], zColName);
                    cnStart += Jsi_Strlen(zColName)+1;
                }
                if (apColType)
                    apColType[i] = sqlite3_column_type(pStmt,i);
                /* Check if rowid appears first, and more than once. */
                if ((i == 0 || numRid>0) &&
                        (zColName[0] == 'r' && Jsi_Strcmp(zColName,"rowid") == 0)) {
                    numRid++;
                }
            }
            /* Change first rowid to oid. */
            if (numRid > 1) {
                if (apColName != (char**)p->staticColNames) {
                    Jsi_Free(apColName[0]);
                    apColName[0] = Jsi_Strdup("oid");
                } else {
                    Jsi_Strcpy(apColName[0], "oid");
                }
            }
            p->apColName = apColName;
            p->apColType = apColType;
        }
    }
    if( papColName ) {
        *papColName = p->apColName;
    }
    if( papColType ) {
        *papColType = p->apColType;
    }
    if( pnCol ) {
        *pnCol = p->nCol;
    }
}

Project: jsish

const Jsi_OptionTypedef* Jsi_OptionsStr2Type(const char *str, bool cName) {
    int typ;
    for (typ=JSI_OPTION_BOOL; typ < JSI_OPTION_END; typ++) {
        const char *snam = (cName?jsi_OptTypeInfo[typ].cName:jsi_OptTypeInfo[typ].idName);
        if (snam && snam[0] && !Jsi_Strcmp(str, snam))
            return jsi_OptTypeInfo+typ;
    }
    return NULL;
}

Project: jsish

static Jsi_RC dbPrepareStmt(
    Jsi_Db *jdb,                  /* Database object */
    char const *zIn,                /* SQL to compile */
    char const **pzOut,             /* OUT: Pointer to next SQL statement */
    SqlPreparedStmt **ppPreStmt     /* OUT: Object used to cache statement */
) {
    const char *zSql = zIn;         /* Pointer to first SQL statement in zIn */
    sqlite3_stmt *pStmt;            /* Prepared statement object */
    SqlPreparedStmt *pPreStmt = 0;  /* Pointer to cached statement */
   // int nSql;                       /* Length of zSql in bytes */
    //int nVar;                       /* Number of variables in statement */
    //int iParm = 0;                  /* Next free entry in apParm */
    Jsi_RC rc = JSI_OK;
    Jsi_Interp *interp = jdb->interp;
    JSI_NOTUSED(interp);

    *ppPreStmt = 0;

    /* Trim spaces from the start of zSql and calculate the remaining length. */
    while( isspace(zSql[0]) ) {
        zSql++;
    }
    //nSql = Jsi_Strlen(zSql);
    Jsi_HashEntry *entry = Jsi_HashEntryFind(jdb->stmtHash, zSql);
    if (entry && ((pPreStmt = (SqlPreparedStmt*)Jsi_HashValueGet(entry)))) {
        
        if (jdb->debug & TMODE_PREPARE)
            JSI_DBQUERY_PRINTF( "DEBUG: prepare cache-hit: %s\n", zSql);
        pStmt = pPreStmt->pStmt;
        *pzOut = &zSql[pPreStmt->nSql];

        /* When a prepared statement is found, unlink it from the
        ** cache list.  It will later be added back to the beginning
        ** of the cache list in order to implement LRU replacement.
        */
        Jsi_ListPop(jdb->stmtCache, pPreStmt->elPtr);
        jdb->stmtCacheCnt = Jsi_ListSize(jdb->stmtCache);

    }

    /* If no prepared statement was found. Compile the SQL text. Also allocate
    ** a new SqlPreparedStmt structure.  */
    if( pPreStmt==0 ) {
        int nByte;

        if( SQLITE_OK!=sqlite3_prepare_v2(jdb->db, zSql, -1, &pStmt, pzOut) )
        
            return Jsi_LogError("PREPARE: %s", sqlite3_errmsg(jdb->db));
        if( pStmt==0 ) {
            if( SQLITE_OK!=sqlite3_errcode(jdb->db) ) {
                /* A compile-time error in the statement. */
                Jsi_LogError("PREP: %s", sqlite3_errmsg(jdb->db));
                return JSI_ERROR;
            } else {
                /* The statement was a no-op.  Continue to the next statement
                ** in the SQL string.
                */
                return JSI_OK;
            }
        }

        if (jdb->debug & TMODE_PREPARE)
            JSI_DBQUERY_PRINTF( "DEBUG: prepare new: %s\n", zSql);
        assert( pPreStmt==0 );
        //nVar = sqlite3_bind_parameter_count(pStmt);
        jdb->stmtCacheCnt++;
        nByte = sizeof(SqlPreparedStmt); // + nVar*sizeof(Jsi_Obj *);
        pPreStmt = (SqlPreparedStmt*)Jsi_Calloc(1, nByte);
        pPreStmt->sig = SQLITE_SIG_STMT;

        pPreStmt->pStmt = pStmt;
        pPreStmt->nSql = (*pzOut - zSql);
        pPreStmt->zSql = sqlite3_sql(pStmt);
        bool isNew = 0;
        pPreStmt->entry = Jsi_HashEntryNew(jdb->stmtHash, zSql, &isNew);
        if (!isNew)
            JSI_DBQUERY_PRINTF( "sqlite dup stmt entry");
        Jsi_HashValueSet(pPreStmt->entry, pPreStmt);
            
        //pPreStmt->apParm = (Jsi_Value **)&pPreStmt[1];
    }
    assert( pPreStmt );
    assert( Jsi_Strlen(pPreStmt->zSql)==pPreStmt->nSql );
    assert( 0==memcmp(pPreStmt->zSql, zSql, pPreStmt->nSql) );
    *ppPreStmt = pPreStmt;
    //pPreStmt->nParm = iParm; 
    return rc;
}

Project: jsish

bool Jsi_TreeUnset(Jsi_Tree *treePtr, void *key) {
    Jsi_TreeEntry *hPtr = Jsi_TreeEntryFind(treePtr, key);
    if (!hPtr)
        return false;
    Jsi_TreeEntryDelete(hPtr);
    return true;
}

Project: jsish

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);
}

Project: jsish

static int jsi_FSTruncateProc(Jsi_Channel chan, unsigned int len) { return ftruncate(fileno(_JSI_GETFP(chan,1)), len);}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

jsi_wscallback_websock(struct lws *wsi,
      enum lws_callback_reasons reason,
      void *user, void *in, size_t len)
{
    struct lws_context *context = lws_get_context(wsi);

    jsi_wsPss *pss = NULL;
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj *)lws_context_user(context);
    if (!cmdPtr) {
        fprintf(stderr, "null ws context\n");
        return -1;
    }
    Jsi_Interp *interp = cmdPtr->interp;
    char *inPtr = (char*)in;
    int sLen, n, rc =0;
    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;

    if (cmdPtr->debug>=32) {
        switch (reason) {
            case LWS_CALLBACK_SERVER_WRITEABLE:
            case LWS_CALLBACK_CLIENT_WRITEABLE:
                break;
            default:
                fprintf(stderr, "WS CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
        }
    }

    switch (reason) {
    case LWS_CALLBACK_PROTOCOL_INIT:
        if (cmdPtr->noWebsock)
            return 1;
        break;

    case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            if (!pss)
                pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 0);
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);

            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 0);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onFilter, vpargs, &ret, NULL);
            if (rc == JSI_OK && Jsi_ValueIsFalse(interp, ret)) {
                if (cmdPtr->debug>1)
                    fprintf(stderr, "WS:KILLING CONNECTION: %p\n", user);
                killcon = 1;
            }

            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, ret);
            if (rc != JSI_OK) {
                Jsi_LogError("websock bad rcv eval");
                return 1;
            }
            if (killcon)
                return 1;
        }
        break;

    case LWS_CALLBACK_CLIENT_ESTABLISHED:
    case LWS_CALLBACK_ESTABLISHED:
        if (cmdPtr->bufferPwr2>0) {
            char nbuf[JSI_MAX_NUMBER_STRING];
            snprintf(nbuf, sizeof(nbuf), "%d", cmdPtr->bufferPwr2);
            lws_set_extension_option(wsi, "permessage-deflate", "rx_buf_size", nbuf);
            lws_set_extension_option(wsi, "permessage-deflate", "tx_buf_size", nbuf);
        }
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 0);
        if (cmdPtr->onOpen && !cmdPtr->deleted) {
            /* Pass 2 args: ws id. */
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);

            Jsi_Value *ret = Jsi_ValueNew1(interp);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onOpen, vpargs, &ret, NULL);

            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, ret);
            if (rc != JSI_OK)
                return Jsi_LogError("websock bad rcv eval");
        }
        break;

    case LWS_CALLBACK_WSI_DESTROY:
        break;

    case LWS_CALLBACK_CLOSED:
    case LWS_CALLBACK_PROTOCOL_DESTROY:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;
        if (cmdPtr->onClose || pss->onClose) {
            rc = jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1);
            if (rc != JSI_OK)
                return Jsi_LogError("websock bad rcv eval");
        }
        jsi_wsdeletePss(pss);
        if (cmdPtr->stats.connectCnt<=0 && cmdPtr->onCloseLast && !Jsi_InterpGone(interp)) {
            Jsi_RC jrc;
            Jsi_Value *retStr = Jsi_ValueNew1(interp);
            // 1 args: ws
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
            vargs[n++] = (cmdPtr->deleted?Jsi_ValueNewNull(interp):Jsi_ValueNewObj(interp, cmdPtr->fobj));
            vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            jrc = Jsi_FunctionInvoke(interp, cmdPtr->onCloseLast, vpargs, &retStr, NULL);
            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, retStr);
            if (Jsi_InterpGone(interp))
                return JSI_ERROR;
            return jrc;
        }
        break;

    case LWS_CALLBACK_CLIENT_WRITEABLE:
    case LWS_CALLBACK_SERVER_WRITEABLE: {
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss || !pss->stack) break;
        if (pss->lastData)
            Jsi_Free(pss->lastData);
        n=0;
        char *data = pss->lastData = (char*)Jsi_StackUnshift(pss->stack);
        unsigned char *p;
        if (data == NULL)
            break;
        pss->stats.msgQLen--;
        pss->state = PWS_SENT;
        p = (unsigned char *)data+LWS_PRE;
        sLen = Jsi_Strlen((char*)p);
        n = jsi_wswrite(pss, wsi, p, sLen, (pss->stats.isBinary?LWS_WRITE_BINARY:LWS_WRITE_TEXT));
        if (cmdPtr->debug>=10)
            fprintf(stderr, "WS:CLIENT WRITE(%p): %d=>%d\n", pss, sLen, n);

        if (n >= 0) {
            cmdPtr->stats.sentCnt++;
            cmdPtr->stats.sentLast = time(NULL);
            pss->stats.sentCnt++;
            pss->stats.sentLast = time(NULL);
        } else {
            lwsl_err("ERROR %d writing to socket\n", n);
            pss->state = PWS_SENDERR;
            pss->stats.sentErrCnt++;
            pss->stats.sentErrLast = time(NULL);
            cmdPtr->stats.sentErrCnt++;
            cmdPtr->stats.sentErrLast = time(NULL);
            rc = 1;
        }
        break;
    }

    case LWS_CALLBACK_CLIENT_RECEIVE:
    case LWS_CALLBACK_RECEIVE:
    {
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;

        pss->stats.recvCnt++;
        pss->stats.recvLast = time(NULL);
        cmdPtr->stats.recvCnt++;
        cmdPtr->stats.recvLast = time(NULL);

        if (cmdPtr->onRecv || pss->onRecv) {
            /* Pass 2 args: id and data. */
            int nlen = len;
            if (nlen<=0)
                return 0;
            int rblen = Jsi_DSLength(&pss->recvBuf),
                bmax = cmdPtr->recvBufMax,
                isfin = pss->stats.isFinal = lws_is_final_fragment(wsi);
            pss->stats.isBinary = lws_frame_is_binary(wsi);
            if (rblen) {
                if (bmax && rblen>bmax) {
                    fprintf(stderr, "WS: Recv exceeds recvBufMax: %d>%d\n", rblen, bmax);
                    rc = 1;
                    break;
                }
                Jsi_DSAppendLen(&pss->recvBuf, inPtr, len);
                if (!isfin) break;
                cmdPtr->recvBufCnt--;
                nlen = Jsi_DSLength(&pss->recvBuf);
                inPtr = Jsi_DSFreeDup(&pss->recvBuf);
            } else {
                if (!isfin) {
                    cmdPtr->recvBufCnt++;
                    Jsi_DSAppendLen(&pss->recvBuf, inPtr, len);
                    break;
                }
            }
            rc = jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, nlen, 0);
            if (inPtr != in)
                Jsi_Free(inPtr);
            if (rc != JSI_OK) {
                Jsi_LogError("websock bad rcv eval");
                return 1;
            }
        }
        lws_callback_on_writable_all_protocol(cmdPtr->context, lws_get_protocol(wsi));
        break;

    }
    default:
        break;
    }
    return rc;
}

Project: jsish

void *Jsi_TreeValueGet(Jsi_TreeEntry *hPtr)
{
    return hPtr->value;
}

Project: jsish

static Jsi_RC jsi_wsrecv_callback(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss,
    const char *inPtr, int nlen, bool isClose)
{
    Jsi_Value *vpargs, *vargs[10];
    Jsi_Value* func = NULL;
    if (Jsi_InterpGone(interp) || (cmdPtr->deleted && !isClose)) return JSI_ERROR;
    int n = 0;
    if (isClose)
        func = ((pss && pss->onClose)?pss->onClose:cmdPtr->onClose);
    else
        func = ((pss && pss->onRecv)?pss->onRecv:cmdPtr->onRecv);
    if (!func)
        return JSI_OK;
    vargs[n++] = (cmdPtr->deleted?Jsi_ValueNewNull(interp):Jsi_ValueNewObj(interp, cmdPtr->fobj));
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss?pss->wid:0));
    if (!isClose) {
        if (nlen<=0)
            return JSI_OK;
        vargs[n++]  = Jsi_ValueNewBlob(interp, (uchar*)inPtr, nlen);
        if ((cmdPtr->echo||(pss && pss->echo)) && inPtr)
            Jsi_LogInfo("WS-RECV: %s\n", inPtr);
    }
    vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, vargs, n, 0));
    Jsi_IncrRefCount(interp, vpargs);

    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_RC rc = Jsi_FunctionInvoke(interp, func, vpargs, &ret, NULL);
    if (rc == JSI_OK && Jsi_ValueIsUndef(interp, ret)==0 && !isClose) {
        /* TODO: should we handle callback return data??? */
    }
    Jsi_DecrRefCount(interp, vpargs);
    Jsi_DecrRefCount(interp, ret);
    return rc;
}

Project: jsish

static Jsi_RC MySqlConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    MySqlObj *jdb;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_LogTest("Creating new MySql");
        
    jdb = (MySqlObj*)Jsi_Calloc(1, sizeof(*jdb));
    SQLSIGINIT(jdb, DB);
    const char *groupname = "mysqljsi";
    jdb->_ = &mydbObjCmd;
    jdb->_->newCnt++;
    jdb->_->activeCnt++;

    jdb->maxStmts = NUM_PREPARED_STMTS;
    jdb->forceInt = 1;
    jdb->interp = interp;
    jdb->hasOpts = (arg != NULL && !Jsi_ValueIsNull(interp,arg));
    if (jdb->hasOpts && Jsi_OptionsProcess(interp, SqlOptions, jdb, arg, 0) < 0) {
        jdb->deleted = 1;
        mysqlObjFree(interp, jdb);
        return JSI_ERROR;
    }
    if (!jdb->udata) {
        jdb->udata = Jsi_ValueNewObj(interp, NULL);
        Jsi_IncrRefCount(interp, jdb->udata);
    }
    jdb->db = mysql_init(NULL);
    jdb->version = (MYSQL_VERSION_MAJOR + ((Jsi_Number)MYSQL_VERSION_MINOR/100.0) + ((Jsi_Number)MYSQL_VERSION_PATCH/10000.0));

#if (MYSQL_VERSION_ID>=32350)
    if (jdb->reconnect)
    {
      my_bool reconnect = 1;
      mysql_options(jdb->db, MYSQL_OPT_RECONNECT, &reconnect);
    }
    mysql_options(jdb->db, MYSQL_READ_DEFAULT_GROUP, groupname);
#endif

#if (MYSQL_VERSION_ID >= 40107)
    if (jdb->sslKey) {
        const char *sslcert = Jsi_ValueString(interp, jdb->sslCert, NULL),
            *sslca = Jsi_ValueString(interp, jdb->sslCA, NULL),
            *sslcapath = Jsi_ValueString(interp, jdb->sslCAPath, NULL),
            *sslcipher = Jsi_ValueString(interp, jdb->sslCipher, NULL),
            *sslkey = Jsi_ValueString(interp, jdb->sslKey, NULL);
        mysql_ssl_set(jdb->db, sslkey, sslcert, sslca, sslcapath, sslcipher);
        jdb->dbflags |= CLIENT_SSL;
    }
#endif

    if (!mdbConnect(interp, jdb)) {
        Jsi_LogError("connect failed %s", mysql_error(jdb->db));
        mysqlObjFree(interp, jdb);
        return JSI_ERROR;
    }

    if (jdb->enableMulti) {
        if (mysql_set_server_option(jdb->db, MYSQL_OPTION_MULTI_STATEMENTS_ON))
            Jsi_LogWarn("multi on failed %s", mysql_error(jdb->db));
    }
    //jdb->event = Jsi_EventNew(interp, mysqlUpdate, jdb); //TODO: events
    Jsi_Value *toacc = NULL;
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        toacc = _this;
    } else {
        Jsi_Obj *o = Jsi_ObjNew(interp);
        Jsi_PrototypeObjSet(interp, "MySql", o);
        Jsi_ValueMakeObject(interp, ret, o);
        toacc = *ret;
    }

    Jsi_Obj *fobj = Jsi_ValueGetObj(interp, toacc);
    if ((jdb->objId = Jsi_UserObjNew(interp, &mysqlobject, fobj, jdb))<0) {
        mysqlObjFree(interp, jdb);
        Jsi_ValueMakeUndef(interp, ret);
        return JSI_ERROR;
    }
    jdb->stmtHash = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL);
    jdb->userObjPtr = fobj;
    jdb->optPtr = &jdb->queryOpts;
    jdb->stmtCache = Jsi_ListNew((Jsi_Interp*)jdb, 0, mdbStmtFreeProc);
    return JSI_OK;
}

Project: jsish

static Jsi_RC jsi_evalSubscript(Jsi_Interp *interp, Jsi_Value *src, Jsi_Value *idx, jsi_OpCode *ip,  jsi_OpCode *end,
    Jsi_Value *currentScope)
{
    Jsi_RC rc = JSI_OK;
    jsiVarDeref(interp,2);
    int isnull;
    if ((isnull=Jsi_ValueIsNull(interp, src)) || Jsi_ValueIsUndef(interp, src)) {
        Jsi_LogError("invalid subscript of %s", (isnull?"null":"undefined"));
        jsiPop(interp, 1);
        return JSI_ERROR;
    }
    Jsi_String *str = jsi_ValueString(src);
    if (str && Jsi_ValueIsNumber(interp, idx)) {
        int bLen, cLen;
        char bbuf[10], *cp = Jsi_ValueString(interp, src, &bLen);
        int n = (int)idx->d.num;
        cLen = bLen;
#if JSI__UTF8
        if (str->flags&JSI_IS_UTF || !(str->flags&JSI_UTF_CHECKED)) {
            cLen = Jsi_NumUtfChars(cp, bLen);
            str->flags |= JSI_UTF_CHECKED;
            if (cLen != bLen)
                str->flags |= JSI_IS_UTF;
        }
#endif
        if (n<0 || n>=cLen) {
            Jsi_ValueMakeUndef(interp, &src);
        } else {
            if (cLen != bLen)
                Jsi_UtfGetIndex(cp, n, bbuf);
            else {
                bbuf[0] = cp[n];
                bbuf[1] = 0;
            }
            Jsi_ValueMakeStringDup(interp, &src, bbuf);
        }
        jsiPop(interp, 1);
        return rc;
    }
    Jsi_ValueToObject(interp, src);
    if (interp->hasCallee && (src->d.obj == currentScope->d.obj || (interp->framePtr->arguments && src->d.obj == interp->framePtr->arguments->d.obj))) {
        if (idx->vt == JSI_VT_STRING && Jsi_Strcmp(idx->d.s.str, "callee") == 0) {
            jsiClearStack(interp,1);
            Jsi_ValueMakeStringKey(interp, &idx, "\1callee\1");
        }
    }
    int bsc = Jsi_ValueIsObjType(interp, src, JSI_OT_NUMBER); // Previous bad subscript.
    if (bsc == 0 && interp->lastSubscriptFail && interp->lastSubscriptFail->vt != JSI_VT_UNDEF)
        Jsi_ValueReset(interp, &interp->lastSubscriptFail);

    if (src->vt != JSI_VT_UNDEF) {
        int right_val = (uintptr_t)ip->data;
        Jsi_Value res = VALINIT, 
            *resPtr = &res,
            *vp = jsi_ValueSubscript(interp, src, idx, &resPtr);
        if (!vp && bsc == 0) {
            /* eg. so we can list available commands for  "db.xx()" */
            if (idx->vt == JSI_VT_STRING)
                interp->lastSubscriptFailStr = idx->d.s.str;
            Jsi_ValueDup2(interp, &interp->lastSubscriptFail, src);
        }
        if (vp)
            Jsi_IncrRefCount(interp, vp);
        jsiClearStack(interp,2);
        if (!vp)
            Jsi_ValueMakeUndef(interp, &src);
        else {
            //printf("IDX(%p): %s\n", idx, Jsi_ValueString(interp, idx, NULL));
            if (right_val || vp->f.bits.readonly) {
                if (vp == resPtr && (res.vt == JSI_VT_OBJECT || res.vt == JSI_VT_STRING))  // TODO:*** Undo using ValueCopy. ***
                    Jsi_ValueMove(interp, src, vp);
                else
                    Jsi_ValueCopy(interp, src, vp);
            } else {
                assert(vp != resPtr);
                res.vt = JSI_VT_VARIABLE;
                res.d.lval = vp;
                Jsi_ValueCopy(interp, src, resPtr);
            }
            Jsi_DecrRefCount(interp, vp);
        }
    }
    jsiPop(interp, 1);
    return rc;
}

Project: jsish

static bool interpObjEqual(void *data1, void *data2)
{
    return (data1 == data2);
}

Project: jsish

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[JSI_MAX_NUMBER_STRING];
    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);
    }
}

Project: jsish

int Jsi_Truncate(Jsi_Interp *interp, Jsi_Channel chan, unsigned int len) {
    if (chan->fsPtr==0 || !chan->fsPtr->truncateProc) return -1;
    return chan->fsPtr->truncateProc(chan, len);
}

Project: jsish

static Jsi_RC FilesysEofCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    UdfGet(udf, _this, funcPtr);
    Jsi_ValueMakeBool(interp, ret, Jsi_Eof(interp, udf->chan));
    return JSI_OK;
}

Project: jsish

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;
}

Project: jsish

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;
}

Project: jsish

static Jsi_RC freeUserdataTbl(Jsi_Interp *interp, Jsi_HashEntry *hPtr, void *ptr) {
    if (ptr)
        jsi_UserObjDelete(interp, ptr);
    return JSI_OK;
}

Project: jsish

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);
}

Project: jsish

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);
}

Project: jsish

static void mdbOutputQuotedString(Jsi_DString *dStr, const char *z) {
    int i;
    int nSingle = 0;
    for(i=0; z[i]; i++) {
        if( z[i]=='\'' ) nSingle++;
    }
    if( nSingle==0 ) {
        Jsi_DSAppend(dStr,"'", z, "'", NULL);
    } else {
        Jsi_DSAppend(dStr,"'", NULL);
        while( *z ) {
            for(i=0; z[i] && z[i]!='\''; i++) {}
            if( i==0 ) {
                Jsi_DSAppend(dStr,"''", NULL);
                z++;
            } else if( z[i]=='\'' ) {
                Jsi_DSAppendLen(dStr,z, i);
                Jsi_DSAppend(dStr,"''", NULL);
                z += i+1;
            } else {
                Jsi_DSAppend(dStr, z, NULL);
                break;
            }
        }
        Jsi_DSAppend(dStr,"'", NULL);
    }
}

Project: jsish

static Jsi_RC StringSubstringCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    int sLen = 0, bLen;
    const char *v;
    ChkString(_this, funcPtr, v, &sLen, &bLen);

    Jsi_Value *start = Jsi_ValueArrayIndex(interp, args, skip);
    Jsi_Value *end = Jsi_ValueArrayIndex(interp, args, skip+1);
    
    Jsi_Number nstart, nend;
    if (!start || Jsi_GetNumberFromValue(interp,start, &nstart) != JSI_OK) {
        Jsi_ValueMakeStringDup(interp, ret, v);
        return JSI_OK;
    }
    int istart = (int)nstart, olen = -1;
    Jsi_DString dStr;
    Jsi_DSInit(&dStr);
    char *ostr;
    if (!end || Jsi_GetNumberFromValue(interp,end, &nend) != JSI_OK) {
        if (sLen == bLen) {
            ostr = jsi_SubstrDup(v, bLen, istart, -1, &olen);
            Jsi_ValueMakeBlob(interp, ret, (uchar*)ostr, olen);
        } else {
            Jsi_UtfSubstr(v, istart, -1, &dStr);
            Jsi_ValueFromDS(interp, &dStr, ret);
        }
        return JSI_OK;
    }
    int iend = (int)nend;
    if (iend>sLen)
        iend = sLen;
    if (iend < istart) {
        Jsi_ValueMakeStringDup(interp, ret, "");
    } else {
        if (sLen == bLen) {
            ostr = jsi_SubstrDup(v, bLen, istart, iend-istart+1, &olen);
            Jsi_ValueMakeBlob(interp, ret, (uchar*)ostr, olen);
        } else {
            Jsi_UtfSubstr(v, istart, iend-istart+1, &dStr);
            Jsi_ValueFromDS(interp, &dStr, ret);
        }
    }
    return JSI_OK;
}

Project: jsish

static Jsi_OpCodes *code_ecatch(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_ECATCH, 0); }

Project: jsish

bool Jsi_NumberIsSubnormal(Jsi_Number a) { return fpclassify(a) == FP_SUBNORMAL; }

Project: jsish

int Jsi_Link(Jsi_Interp *interp, Jsi_Value* src, Jsi_Value *dest, int typ) {
    void *data;
    Jsi_Filesystem *fsPtr = Jsi_FilesystemForPath(interp, src, &data);
    if (fsPtr == NULL || !fsPtr->linkProc) return -1;
    return fsPtr->linkProc(interp, src, dest, typ);
}

Project: jsish

void Jsi_NumberUtoA10(Jsi_UWide value, char* buf, int bsiz)
{
    snprintf(buf, bsiz, "%" PRIu64, value);
}

Project: jsish

static Jsi_OpCodes *code_throw(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_THROW, 0); }

Project: jsish

static Jsi_OpCodes *code_sfinal(jsi_Pstate *p, jsi_Pline *line) { JSI_NEW_CODESLN(0,OP_SFINAL, 0); }

Project: jsish

static void jsi_pop_try(Jsi_Interp* interp, jsi_TryList **head)
{
    interp->framePtr->tryDepth--;
    jsi_TryList *t = (*head)->next;
    Jsi_Free(*head);
    *head = t;
    interp->tryList = t;
}

Project: jsish

static Jsi_Value *jsi_hashFmtKey(Jsi_MapEntry* h, struct Jsi_MapOpts *opts, int flags)
{
    Jsi_HashEntry* hPtr = (Jsi_HashEntry*)h;
    void *key = Jsi_HashKeyGet(hPtr);
    if (opts->keyType == JSI_KEYS_ONEWORD)
        return Jsi_ValueNewNumber(opts->interp, (Jsi_Number)(intptr_t)key);
    char nbuf[JSI_MAX_NUMBER_STRING];
    snprintf(nbuf, sizeof(nbuf), "%p", key);
    return Jsi_ValueNewStringDup(opts->interp, nbuf);
}

Project: jsish

bool Jsi_FSNative(Jsi_Interp *interp, Jsi_Value *file) {
    void *data;
    Jsi_Filesystem *fsPtr = Jsi_FilesystemForPath(interp, file, &data);
    if (fsPtr && fsPtr == &jsiFilesystem)
        return 1;
    else
        return 0;
}

Project: jsish

Jsi_CmdSpec *Jsi_FunctionGetSpecs(Jsi_Func *funcPtr)
{
    return funcPtr->cmdSpec;
}

Project: jsish

static Jsi_RC sqliteObjFree(Jsi_Interp *interp, void *data)
{
    Jsi_Db *db = (Jsi_Db*)data;
    SQLSIGASSERT(db,DB);
    db->_->activeCnt--;
    sqliteObjErase(db);
    _JSI_MEMCLEAR(db);
    Jsi_Free(db);
    return JSI_OK;
}

Project: jsish

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 "";
}

Project: jsish

static void mysqlObjErase(MySqlObj *jdb)
{
    mdbDeleteCmd(jdb);
    jdb->db = NULL;
}

Project: jsish

Jsi_HashSearchNext(Jsi_HashSearch *searchPtr)
{
    Jsi_HashEntry *hPtr;
    Jsi_Hash *tablePtr = searchPtr->tablePtr;
    int locked = 0;
    
    while (searchPtr->nextEntryPtr == NULL) {
        if (searchPtr->nextIndex >= (size_t)tablePtr->numBuckets) {
            if (tablePtr->opts.lockHashProc && locked)
                (*tablePtr->opts.lockHashProc)(tablePtr, 0);
            return NULL;
        }
        if (tablePtr->opts.lockHashProc && locked == 0 && (*tablePtr->opts.lockHashProc)(tablePtr, locked++) != JSI_OK)
            return NULL;
        searchPtr->nextEntryPtr =
            tablePtr->buckets[searchPtr->nextIndex];
        searchPtr->nextIndex++;
    }
    if (tablePtr->opts.lockHashProc && locked)
        (*tablePtr->opts.lockHashProc)(tablePtr, 0);
    hPtr = searchPtr->nextEntryPtr;
    searchPtr->nextEntryPtr = hPtr->nextPtr;
    return hPtr;
}

Project: jsish

static Jsi_RC mdbPrepareAndBind(MyDbEvalContext *p)
{
    if (mdbPrepareStmt(p) != JSI_OK)
        return JSI_ERROR;
    MysqlPrep *prep = p->prep;
    MySqlObj *jdb = p->jdb;
    Jsi_Interp *interp = jdb->interp;
    Jsi_Value *pv = NULL, *apv = NULL;
    char tname[50];

    Jsi_RC rc = JSI_OK;
    int i, n, nVar = prep->numParam;

    if (nVar<=0)
        return rc;
    if (!prep->bindParam)
        prep->bindParam = (MYSQL_BIND*)Jsi_Calloc(nVar, sizeof(MYSQL_BIND));
    else
        memset(prep->bindParam, 0, (nVar * sizeof(MYSQL_BIND)));
    for(i=1; i<=nVar; i++) {
        int btype = 0;
        int isInt = 0;
        int isBlob = 0;
        const char *zVar = mysqlGetbindParamName(prep, i);
        tname[0] = 0;
        if (zVar == NULL) {
            if (!jdb->optPtr || !(apv=jdb->optPtr->values))
                return Jsi_LogError("? bind without values for param %d", i);
            if (!(pv =Jsi_ValueArrayIndex(interp, apv, i-1))) 
                return Jsi_LogError("array element %d missing", nVar);
        }
        else if ((zVar[0]!='$' && zVar[0]!=':' && zVar[0]!='@') ) 
            return Jsi_LogError("can not find bind var %s", zVar); else {
           
            int zvLen = Jsi_Strlen(zVar);
            char *zcp;
            if (zVar[0] =='$' && ((zcp = Jsi_Strchr(zVar,'('))) && zVar[zvLen-1] == ')')
            {
                Jsi_DString vStr;
                Jsi_DSInit(&vStr);
                Jsi_DSAppendLen(&vStr, zVar+1, (zcp-zVar-1));
                int slen = Jsi_Strlen(zcp);
                const char *ttp;
                if (jdb->optPtr->typeCheck!=mdbTypeCheck_None && (ttp = Jsi_Strchr(zVar,':'))) {
                    // Extract bind-type.
                    Jsi_DString tStr = {};
                    int tlen = Jsi_Strlen(ttp+1);
                    Jsi_DSAppendLen(&tStr, ttp+1, tlen-1);
                    snprintf(tname, sizeof(tname), "%s", Jsi_DSValue(&tStr));
                    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(jdb->typeNameHash, tname);
                    assert(hPtr);
                    btype = (long)Jsi_HashValueGet(hPtr);
                    Jsi_DSFree(&tStr);
                    slen -= tlen;
                }

                if (isdigit(zcp[1])) {
                    Jsi_DSAppendLen(&vStr, "[", 1);
                    Jsi_DSAppendLen(&vStr, zcp+1, slen-2);
                    Jsi_DSAppendLen(&vStr, "]", 1);
                } else {
                    if (zcp[1] != '[')
                        Jsi_DSAppendLen(&vStr, ".", 1);
                    Jsi_DSAppendLen(&vStr, zcp+1, slen-2);
                }
                pv = Jsi_NameLookup(interp, Jsi_DSValue(&vStr));
                Jsi_DSFree(&vStr);
            } else
                pv = Jsi_VarLookup(interp, &zVar[1]);
        }
        Jsi_Number r;
        SqlFieldResults *fres = prep->fieldParam+i-1;
        MYSQL_BIND *bind = prep->bindParam+i-1;
        memset(bind, 0, sizeof(*bind));
        // Now create binding.
        if(!pv ) {
            if (!jdb->bindWarn) {
                rc = Jsi_LogError("unknown bind param: %s", zVar);
                break;
            } else
                Jsi_LogWarn("unknown bind param: %s", zVar);
        } else {
            if (btype && !Jsi_ValueIsUndef(interp, pv)) {
                int done = 0, match = 1, cast = (jdb->optPtr->typeCheck==mdbTypeCheck_Cast);
                switch (btype) {
                    case MYSQL_TYPE_BLOB:
                        isBlob = 1;
                    case MYSQL_TYPE_STRING:
                        if (cast)
                            Jsi_ValueToString(interp, pv, &n);
                        else
                            match = Jsi_ValueIsString(interp, pv); 
                        break;
                    case MYSQL_TYPE_DOUBLE:
                        if (cast)
                            Jsi_ValueToNumber(interp, pv);
                        else
                            match = Jsi_ValueIsNumber(interp, pv); 
                        break;
                    case MYSQL_TYPE_LONGLONG:
                        isInt = 1;
                        if (cast)
                            Jsi_ValueToNumber(interp, pv);
                        else
                            match = Jsi_ValueIsNumber(interp, pv); 
                        break;
                    case MYSQL_TYPE_TINY:
                        if (cast)
                            Jsi_ValueToBool(interp, pv);
                        else
                            match = Jsi_ValueIsBoolean(interp, pv); 
                        break;
                    case MYSQL_TYPE_TIME:
                    case MYSQL_TYPE_DATE:
                    case MYSQL_TYPE_DATETIME:
                    case MYSQL_TYPE_TIMESTAMP:
                        if (cast)
                            Jsi_ValueToNumber(interp, pv);
                        else if (!Jsi_ValueIsNumber(interp, pv))
                            goto errout;
                        Jsi_GetNumberFromValue(interp, pv, &r);
                        bind->buffer_type = (enum enum_field_types)btype;
                        bind->buffer=&fres->buffer.timestamp;
                        bind->buffer_length = sizeof(fres->buffer.timestamp);
                        bind->length = NULL;
                        mdbJsToMyTime(r, &fres->buffer.timestamp, 1);
                        done = 1;
                        break;
                    default:
                        Jsi_LogBug("Unhandled bind type: %s = %d", tname, btype);
                }
                if (done)
                    continue;
                 if (cast == 0 && match == 0) 
errout:
                 {
                    int ltyp = (jdb->optPtr->typeCheck==mdbTypeCheck_Error?JSI_LOG_ERROR:JSI_LOG_WARN);
                    Jsi_LogMsg(interp, ltyp, "bind param \"%s\" type is not \"%s\"", zVar, tname);
                    if (ltyp == JSI_LOG_ERROR)
                        return JSI_ERROR;
                }
            }
            
            if (Jsi_ValueIsBoolean(interp, pv)) {
                bool nb;
                Jsi_GetBoolFromValue(interp, pv, &nb);
                n = nb;
                bind->buffer_type = MYSQL_TYPE_TINY;
                bind->buffer = &fres->buffer.vchar;
                bind->buffer_length = sizeof(fres->buffer.vchar);
                bind->length = &fres->len;
                //bind->is_null = &fres->isnull;
                fres->buffer.vchar = n;
                
            } else if (Jsi_ValueIsNumber(interp, pv)) {
                Jsi_Number r;
                Jsi_Wide wv;
                Jsi_GetNumberFromValue(interp, pv, &r);
                wv = (Jsi_Wide)r;
                bind->buffer_type = MYSQL_TYPE_DOUBLE;
                bind->buffer = &fres->buffer.vdouble;
                bind->buffer_length = sizeof(fres->buffer.vdouble);
                bind->length = &fres->len;
                fres->buffer.vdouble = (double)r;
                if (isInt || (jdb->forceInt && (((Jsi_Number)wv)-r)==0)) {
                    bind->buffer = &fres->buffer.vlonglong;
                    bind->buffer_type = MYSQL_TYPE_LONGLONG;
                    bind->buffer_length = sizeof(fres->buffer.vlonglong);
                    fres->buffer.vlonglong = wv;
                }
            } else if (Jsi_ValueIsNull(interp, pv) || (Jsi_ValueIsUndef(interp, pv) && jdb->optPtr->mapundef)) {
bindnull:
                bind->buffer_type = MYSQL_TYPE_NULL;
                bind->buffer = &fres->buffer.vchar;
                bind->buffer_length = sizeof(fres->buffer.vchar);
                bind->length = &fres->len;
                fres->buffer.vchar = 0;

            } else if (Jsi_ValueIsString(interp, pv)) {
                char *sstr = Jsi_ValueGetStringLen(interp, pv, &n);
                bind->buffer_type = MYSQL_TYPE_STRING;
                bind->buffer=sstr;
                bind->buffer_length = n;
                bind->length = NULL;
                if (isBlob)
                    bind->buffer_type = MYSQL_TYPE_BLOB;
            } else {
                if (!jdb->bindWarn) {
                    rc = Jsi_LogError("bind param must be string/number/bool/null: %s", zVar);
                    break;
                } else {
                    Jsi_LogWarn("bind param must be string/number/bool/null: %s", zVar);
                    goto bindnull;
                }
            }
        }
    }
    if (mysql_stmt_bind_param(prep->myStmt, prep->bindParam))
        rc = Jsi_LogError("bind failed: %s", mysql_error(jdb->db));
    return rc;
}

Project: jsish

static Jsi_RC NumberToPrecisionCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    char buf[JSI_MAX_NUMBER_STRING*2];
    int prec = 0, skip = 0;
    Jsi_Number num;
    Jsi_Value *v;
    ChkStringN(_this, funcPtr, v);
    if (Jsi_GetIntFromValue(interp, Jsi_ValueArrayIndex(interp, args, skip), &prec) != JSI_OK)
        return JSI_ERROR;
    if (prec<=0 || prec>JSI_MAX_NUMBER_STRING) return Jsi_LogError("precision must be between 1 and %d", JSI_MAX_NUMBER_STRING);
    Jsi_GetDoubleFromValue(interp, v, &num);
    snprintf(buf, sizeof(buf),"%.*" JSI_NUMFFMT, prec, num);
    if (num<0)
        prec++;
    buf[prec+1] = 0;
    if (buf[prec] == '.')
        buf[prec] = 0;
    Jsi_ValueMakeStringDup(interp, ret, buf);
    return JSI_OK;
}

Project: jsish

static Jsi_RC FilesysFlushCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    UdfGet(udf, _this, funcPtr);
    int pos = Jsi_Flush(interp, udf->chan);
    Jsi_ValueMakeNumber(interp, ret, (Jsi_Number)pos);
    return JSI_OK;
}

Project: jsish

static Jsi_RC jsi_ParentFuncToValue(Jsi_Interp *interp, Jsi_OptionSpec* spec, Jsi_Value **outValue, Jsi_DString *outStr, void *record, Jsi_Wide flags)
{
    if (outStr)
        return JSI_ERROR;
    Jsi_Value **v = (Jsi_Value **)(((char*)record) + spec->offset);
    //const char *s, *subspec = (const char *)spec->data;
    if (!*v) {
        Jsi_ValueMakeNull(interp, outValue);
        return JSI_OK;
    }
    if (!interp->parent)
        return Jsi_LogError("no parent interp");
    if (spec->data) {
        Jsi_DString dStr = {};
        Jsi_DSPrintf(&dStr, "func(%s)", (char*)spec->data);
        Jsi_ValueFromDS(interp, &dStr, outValue);
    }
    return JSI_OK;
}

Project: jsish

static void mdbDeleteCmd(MySqlObj *jdb)
{
    Jsi_Interp *interp = jdb->interp;
    if (jdb->debug & mdbTMODE_DELETE)
        JSI_DBQUERY_PRINTF( "DEBUG: delete\n");
    if (jdb->stmtCache)
        mdbFlushStmtCache(jdb);
    if (jdb->stmtHash)
        Jsi_HashDelete(jdb->stmtHash);
    //closeIncrblobChannels(jdb);
    if (jdb->db) {
        mdbClose(jdb->db);
    }
    while( jdb->pFunc ) {
        SqlFunc *pFunc = jdb->pFunc;
        jdb->pFunc = pFunc->pNext;
        Jsi_DSFree(&pFunc->dScript);
        Jsi_DecrRefCount(interp, pFunc->tocall);
        Jsi_Free((char*)pFunc);
    }
    if( jdb->zNull ) {
        Jsi_Free(jdb->zNull);
    }
    if (jdb->typeNameHash)
        Jsi_HashDelete(jdb->typeNameHash);
    Jsi_OptionsFree(interp, SqlOptions, jdb, 0);
    if (jdb->stmtCache)
        Jsi_ListDelete(jdb->stmtCache);
}

Project: jsish

static bool sqliteObjEqual(void *data1, void *data2)
{
    //SQLSIGASSERT(data1,DB);
    //SQLSIGASSERT(data2,DB);
    return (data1 == data2);
}

Project: jsish

static void delete_case3(Jsi_TreeEntry* n) {
    Jsi_TreeEntry* s  = sibling(n);
    if (node_color(n->parent) == _JSI_TREE_BLACK &&
        node_color(s) == _JSI_TREE_BLACK &&
        node_color(s->left) == _JSI_TREE_BLACK &&
        node_color(s->right) == _JSI_TREE_BLACK)
    {
        set_color(s, _JSI_TREE_RED);
        delete_case1(n->parent);
    } else
        delete_case4(n);
}

Project: jsish

static Jsi_OpCodes *code_jmp(int off) { JSI_NEW_CODES(0,OP_JMP, off); }

Project: jsish

static void jsi_VerifyFree(Jsi_Interp *interp, Jsi_OptionSpec* spec, void *ptr)
{
    Jsi_Value **v = (Jsi_Value**)ptr;
    if (v && *v)
        Jsi_DecrRefCount(interp, *v);
}

Project: jsish

static void rotate_left(Jsi_TreeEntry* n) {
    Jsi_TreeEntry* r;
    Assert(n);
    r = n->right;
    replace_node(n, r);
    n->right = r->left;
    if (r->left != NULL) {
        r->left->parent = n;
    }
    r->left = n;
    n->parent = r;
}

Project: jsish

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;
}

Project: jsish

static Jsi_RC mdbEvalInit(
    Jsi_Interp *interp,
    MyDbEvalContext *p,               /* Pointer to structure to initialize */
    MySqlObj *jdb,                  /* Database handle */
    const char* zSql,                /* Value containing SQL script */
    Jsi_DString *dStr,
    Jsi_Obj *pArray,                /* Name of Jsi array to set (*) element of */
    Jsi_Obj *pValVar                  /* Name element in array for list. */
) {
    p->dzSql = dStr;
    p->zSql = Jsi_DSAppend(p->dzSql, zSql?zSql:"", NULL);
    p->jdb = jdb;
    return JSI_OK;
}

Project: jsish

static void mdbReleaseStmt( MySqlObj *jdb, MysqlPrep *prep, int discard ) {
    if( jdb->maxStmts<=0 || discard ) {
        /* If the cache is turned off, deallocated the statement */
        mdbRelease1Stmt(jdb, prep);
    } else {
        /* Add the prepared statement to the beginning of the cache list. */
        if (!prep->elPtr)
            prep->elPtr = Jsi_ListPushFrontNew(jdb->stmtCache, prep);
        else
            Jsi_ListPushFront(jdb->stmtCache, prep->elPtr);
        mdbStmtLimit(jdb);
    }
}

Project: jsish

static void delete_case6(Jsi_TreeEntry* n) {
    Jsi_TreeEntry* s = sibling(n);
    set_color(s, node_color(n->parent));
    set_color(n->parent, _JSI_TREE_BLACK);
    if (n == n->parent->left) {
        Assert (node_color(s->right) == _JSI_TREE_RED);
        set_color(s->right, _JSI_TREE_BLACK);
        rotate_left(n->parent);
    }
    else
    {
        //Assert (node_color(s->left) == _JSI_TREE_RED);
        set_color(s->left, _JSI_TREE_BLACK);
        rotate_right(n->parent);
    }
}

Project: jsish

static Jsi_OpCodes *code_delete(int n) { JSI_NEW_CODES(0,OP_DELETE, n); }

Project: jsish

static jsi_Pstate* jsiNewParser(Jsi_Interp* interp, const char *codeStr, Jsi_Channel fp, int iseval)
{
    bool isNew;
    Jsi_HashEntry *hPtr = NULL;
    hPtr = Jsi_HashEntryNew(interp->codeTbl, (void*)codeStr, &isNew);
    if (!hPtr) return NULL;
    jsi_Pstate *ps, *topPs;

    if (isNew==0 && ((ps = (jsi_Pstate *)Jsi_HashValueGet(hPtr)))) {
        interp->codeCacheHit++;
        return ps;
    }
    ps = jsi_PstateNew(interp);
    ps->eval_flag = iseval;
    if (codeStr)
        jsi_PstateSetString(ps, codeStr);
    else
        jsi_PstateSetFile(ps, fp, 1);
        
    interp->inParse++;
    topPs = interp->parsePs;
    interp->parsePs = ps;
    yyparse(ps);
    interp->parsePs = topPs;
    interp->inParse--;
    
    if (ps->err_count) {
        Jsi_HashEntryDelete(hPtr);
        jsi_PstateFree(ps);
        return NULL;
    }
    if (isNew) {
        Jsi_HashValueSet(hPtr, ps);
        ps->hPtr = hPtr;
    }
    return ps;
}

Project: jsish

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;
}

Project: jsish

static void dbEvalFinalize(DbEvalContext *p) {
//  Jsi_Interp *interp = p->jdb->interp;

    if( p->pPreStmt ) {
        sqlite3_reset(p->pPreStmt->pStmt);
        dbReleaseStmt(p->jdb, p->pPreStmt, p->nocache);
        p->pPreStmt = 0;
    }
    if (p->dSql)
        Jsi_DSFree(p->dSql);
    dbReleaseColumnNames(p);
}

Project: jsish

static Jsi_RC DebugInfoCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (!interp->breakpointHash) {
        Jsi_ValueMakeArrayObject(interp, ret, NULL);
        return JSI_OK;
    }
    int argc = Jsi_ValueGetLength(interp, args);
    if (argc == 0)
        return Jsi_HashKeysDump(interp, interp->breakpointHash, ret, 0);
    Jsi_Value *val = Jsi_ValueArrayIndex(interp, args, 0);
    int num;
    char nbuf[JSI_MAX_NUMBER_STRING];
    if (Jsi_GetIntFromValue(interp, val, &num) != JSI_OK) 
        return Jsi_LogError("bad number");
    
    snprintf(nbuf, sizeof(nbuf), "%d", num);
    Jsi_HashEntry *hPtr = Jsi_HashEntryFind(interp->breakpointHash, nbuf);
    if (!hPtr) 
        return Jsi_LogError("unknown breakpoint");
    jsi_BreakPoint* bp = (jsi_BreakPoint*)Jsi_HashValueGet(hPtr);
    if (!bp) return JSI_ERROR;
    Jsi_DString dStr = {};
    if (bp->func)
        Jsi_DSPrintf(&dStr, "{id:%d, type:\"func\", func:\"%s\", hits:%d, enabled:%s, temporary:%s}",
         bp->id, bp->func, bp->hits, bp->enabled?"true":"false", bp->temp?"true":"false");
    else
        Jsi_DSPrintf(&dStr, "{id:%d, type:\"line\", file:\"%s\", line:%d, hits:%d, enabled:%s}",
            bp->id, bp->file?bp->file:"", bp->line, bp->hits, bp->enabled?"true":"false");
    Jsi_RC rc = Jsi_JSONParse(interp, Jsi_DSValue(&dStr), ret, 0);
    Jsi_DSFree(&dStr);
    return rc;
}

Project: jsish

static Jsi_RC FilesysTruncateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    UdfGet(udf, _this, funcPtr);
    Jsi_Value *vPos = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Number num;
    if (Jsi_ValueGetNumber(interp, vPos, &num) != JSI_OK)
        return JSI_ERROR;
    num = (Jsi_Number)Jsi_Truncate(interp, udf->chan, (unsigned int)num);
    Jsi_ValueMakeNumber(interp, ret, num);
    return JSI_OK;
}

Project: jsish

static void dbEvalSetColumn(DbEvalContext *p, int iCol, Jsi_DString *dStr) {
    Jsi_Interp *interp = p->jdb->interp;
    char nbuf[JSI_MAX_NUMBER_STRING];

    sqlite3_stmt *pStmt = p->pPreStmt->pStmt;

    switch( sqlite3_column_type(pStmt, iCol) ) {
    case SQLITE_BLOB: {
        int bytes = sqlite3_column_bytes(pStmt, iCol);
        const char *zBlob = (char*)sqlite3_column_blob(pStmt, iCol);
        if( !zBlob ) {
            return;
        }
        Jsi_DSAppendLen(dStr, zBlob, bytes);
        return;
    }
    case SQLITE_INTEGER: {
        sqlite_int64 v = sqlite3_column_int64(pStmt, iCol);
        if (v==0 || v==1) {
            const char *dectyp = sqlite3_column_decltype(pStmt, iCol);
            if (dectyp && !Jsi_Strncasecmp(dectyp,"bool", 4)) {
                Jsi_DSAppend(dStr, (v?"true":"false"), NULL);
                return;
            }
        }
#ifdef __WIN32
        snprintf(nbuf, sizeof(nbuf), "%" PRId64, (Jsi_Wide)v);
#else
        snprintf(nbuf, sizeof(nbuf), "%lld", v);
#endif
        Jsi_DSAppend(dStr, nbuf, NULL);
        return;
    }
    case SQLITE_FLOAT: {
        Jsi_NumberToString(interp, sqlite3_column_double(pStmt, iCol), nbuf, sizeof(nbuf));
        Jsi_DSAppend(dStr, nbuf, NULL);
        return;
    }
    case SQLITE_NULL: {
        return;
    }
    }
    const char *str = (char*)sqlite3_column_text(pStmt, iCol );
    if (!str)
        str = p->jdb->optPtr->nullvalue;
    Jsi_DSAppend(dStr, str?str:"", NULL);
}