Login
shell_extend.c at [f9746a7526]
Login

File th1ish/shell_extend.c artifact 4394c85f4c part of check-in f9746a7526


/**
  This file holds script bindings for libfossil/th1ish.  Build shell.c
  with TH1ISH_SHELL_EXTEND and link this file to shell.o, which will
  cause shell.c to run this file's init routine
  (th1ish_shell_extend()) when the shell is run.
*/

#include <assert.h>
#include <locale.h>
#include "th1ish_amalgamation.h"
#include "fossil-scm/fossil.h"
#include "fossil-scm/fossil-internal.h"
#include <stdlib.h>

#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h> /* F_OK and friends */
#endif


/* Only for debuggering... */
#include <stdio.h>
#define MARKER(pfexp)                                                      \
    do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
        printf pfexp; \
    } while(0)

static fsl_timer_state RunTimer = fsl_timer_state_empty_m;

static void fsl_finalizer_f_fsl_cx( cwal_engine * e, void * m ){
    if(m){
        /* MARKER(("Finalizing fsl_cx @%p\n", m)); */
        fsl_cx_finalize( (fsl_cx*)m );
    }
}
static void fsl_finalizer_f_fsl_db( cwal_engine * e, void * m ){
    if(m){
        /* MARKER(("Finalizing fsl_db @%p\n", m)); */
        fsl_db_close( (fsl_db*)m );
    }
}

static void fsl_finalizer_f_fsl_stmt( cwal_engine * e, void * m ){
    if(m){
        /* MARKER(("Finalizing fsl_stmt @%p\n", m)); */
        fsl_stmt_finalize( (fsl_stmt*)m );
    }
}

static void fsl_finalizer_f_fsl_zip( cwal_engine * e, void * m ){
    if(m){
        /* MARKER(("Finalizing fsl_zip_writer @%p\n", m)); */
        fsl_zip_finalize( (fsl_zip_writer*)m );
        fsl_free(m);
    }
}


/**
  Type IDs for type-safely mapping (void*) to cwal_native
  instances. Their types and values are irrelevant - we use only their
  addresses.
*/
static const int cwal_type_id_fsl_cx = 0;
static const int cwal_type_id_fsl_db = 0;
static const int cwal_type_id_fsl_stmt = 0;
static const int cwal_type_id_fsl_zip_writer = 0;
#define FSL_TYPEID(X) (&cwal_type_id_##X)

static int cb_toss( cwal_callback_args const * args, int code,
                    char const * fmt, ... ){
    int rc;
    va_list vargs;
    th1ish_interp * ie = th1ish_args_interp(args);
    cwal_native * nat = cwal_value_native_part(args->engine, args->self);
    cwal_value * natV = nat ? cwal_native_value(nat) : NULL;
    fsl_cx * f = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL;
    assert(ie);
    va_start(vargs,fmt);
    rc = th1ish_tossv( ie, code, fmt, vargs );
    if(f){
        /* Ensure that script-triggered errors do not unduly
           lie around, potentially propagating long after
           they are valid.
        */
        fsl_cx_err_reset(f);
    }
    va_end(vargs);
    return rc;
}

static int cb_toss_fsl( cwal_callback_args const * args,
                        fsl_cx * f ){
    int rc;
    assert(f && f->error.code);
    rc = (FSL_RC_OOM==f->error.code)
        ? CWAL_RC_OOM
        : th1ish_toss(th1ish_args_interp(args),
                      f->error.code,
                      "%s", (char const *)f->error.msg.mem );
    fsl_cx_err_reset(f);
    return rc;
}

static int cb_toss_db( cwal_callback_args const * args,
                       fsl_db * db ){
    int rc;
    assert(db && db->error.code);
    rc = (FSL_RC_OOM==db->error.code)
        ? CWAL_RC_OOM
        : th1ish_toss(th1ish_args_interp(args),
                      db->error.code,
                      "%s", (char const *)db->error.msg.mem );
    fsl_db_err_reset(db);
    return rc;
}


/*
** fsl_output_f() impl which forwards to cwal_output().
** state must be a (cwal_engine *).
*/
static int fsl_output_f_cwal_out( void * state,
                                  void const * src,
                                  fsl_size_t n ){
    cwal_engine * e = (cwal_engine *)state;
    return cwal_output( e, src, (cwal_size_t)n )
        ? FSL_RC_IO
        : 0;
}
/*
** fsl_flush_f() impl which forwards to cwal_output_flush(). state
** must be a (cwal_engine *).
*/
static int fsl_flush_f_cwal_out( void * state ){
    cwal_engine * e = (cwal_engine *)state;
    return cwal_output_flush(e);
}

static cwal_value * fsl_cx_prototype( th1ish_interp * ie );
static cwal_value * fsl_db_prototype( th1ish_interp * ie );
static cwal_value * fsl_stmt_prototype( th1ish_interp * ie );
static cwal_value * fsl_zip_prototype( th1ish_interp * ie );

/**
 ** If cx has a property named "db", it is returned, else if
 ** createIfNotExists is true then a new native fsl_db Object named
 ** "db" is inserted into cx and returned. Returns NULL on allocation
 ** error or if no such property exists and createIfNotExists is
 ** false.
 */
static cwal_value * fsl_cx_db_prop( fsl_cx * f,
                                    cwal_value * fv,
                                    th1ish_interp * ie,
                                    char createIfNotExists);

typedef struct card_visitor_F_to_object {
    cwal_engine * e;
    cwal_array * dest;
} card_visitor_F_to_object;

/**
   fsl_card_F_visitor_f() impl which converts fc to a cwal Object
   representation and appends it to
   ((card_visitor_F_to_object*)state)->dest.
*/
static int fsl_card_F_visitor_fc_to_object(fsl_card_F const * fc,
                                           void * state){
    card_visitor_F_to_object * vs = (card_visitor_F_to_object*)state;
    int rc;
    cwal_value * ov = cwal_new_object_value(vs->e);
    char const * typeLabel = NULL;
    rc = cwal_array_append(vs->dest, ov);
    if(rc){
        cwal_value_unref(ov);
        return rc;
    }
#define vset(K,KL,V) cwal_prop_set(ov, K, KL, V)
    vset("name", 4, cwal_new_string_value(vs->e, fc->name, 0));
    if(fc->priorName){
        vset("priorName", 9, cwal_new_string_value(vs->e, fc->priorName, 0));
    }
    if(fc->uuid){
        vset("uuid", 4, cwal_new_string_value(vs->e, fc->uuid, FSL_UUID_STRLEN));
    }
    switch(fc->perm){
      case FSL_FS_PERM_EXE: typeLabel = "x"; break;
      case FSL_FS_PERM_LINK: typeLabel = "l"; break;
      default: typeLabel = "f";
    }
    vset("perm", 4, cwal_new_string_value(vs->e, typeLabel, 1)
         /* good case for cwal's string interning! */);
#undef vset

    return rc;
}


/**
   Converts a fsl_deck to an Object representation, assigning *rv
   to the new object (new, with no live references).

   Must return a CWAL_RC value, not FSL_RC!
*/
static int th_deck_to_object( cwal_engine * e, fsl_deck * mf,
                              char includeBaselineFiles,
                              char includeBaselineObj,
                              cwal_value ** rv ){
    int rc = 0;
    cwal_value * ov = NULL;
    cwal_value * card = NULL;
    cwal_array * ar = NULL;
    cwal_value * av = NULL;
    cwal_size_t i;
    assert(e);
    assert(mf);
    assert(mf->uuid);
    assert(mf->rid);
    rc = fsl_deck_F_rewind(mf);
    if(rc) goto end;
    ov = cwal_new_object_value(e);
#define vset2(OBJ,K,V) cwal_prop_set(OBJ, K, fsl_strlen(K), V)
#define dset(K,V) vset2(ov,K,V)
#define cset(K,V) vset2(card,K,V)
#define vornull(VP,V) ((VP) ? (V) : cwal_value_null())
#define strval2(CSTR,LEN) vornull((CSTR), cwal_new_string_value(e, (char const *)(CSTR), (cwal_size_t)(LEN)))
#define strval(CSTR) strval2((CSTR), fsl_strlen(CSTR))
    dset("type", cwal_new_integer(e, mf->type));
    dset("rid", cwal_new_integer(e, (cwal_int_t)mf->rid));
    dset("uuid", strval2(mf->uuid, FSL_UUID_STRLEN));

    if(mf->A.src){
        card = cwal_new_object_value(e);
        dset("A", card);
        cset("name", strval(mf->A.name));
        cset("tgt", strval(mf->A.tgt));
        cset("uuid", strval2(mf->A.src, FSL_UUID_STRLEN));
    }

    if(mf->B.uuid){
        card = cwal_new_object_value(e);
        dset("B", card);
        cset("uuid", strval2(mf->B.uuid,FSL_UUID_STRLEN));
        if(includeBaselineObj && mf->B.baseline){
            cwal_value * v = NULL;
            rc = th_deck_to_object( e, mf->B.baseline,
                                    includeBaselineFiles,
                                    includeBaselineObj,
                                    &v );
            if(v){
                cset("baseline", v);
            }
            else{
                goto end;
            }
        }
    }

    if(mf->C){
        dset("C", strval(mf->C));
    }
    if(mf->D > 0){
        dset("D", cwal_new_double(e, mf->D));
    }

    if(mf->E.julian > 0){
        card = cwal_new_object_value(e);
        dset("E", card);
        cset("julian", cwal_new_double(e, mf->E.julian));
        cset("uuid", strval2(mf->E.uuid, FSL_UUID_STRLEN));
    }

    if(mf->F.list.used || (includeBaselineFiles && mf->B.uuid)){
        card_visitor_F_to_object vstate;
        cwal_size_t const reserveSize = mf->F.list.used
            + (mf->B.baseline ? mf->B.baseline->F.list.used : 0);
        assert(fsl_card_is_legal(mf->type, 'F'));
        ar = cwal_new_array(e);
        card = av = cwal_array_value(ar);
        dset("F", card);
        cwal_array_reserve(ar, reserveSize);
        vstate.e = e;
        vstate.dest = ar; 
        rc = fsl_deck_F_foreach(mf, includeBaselineFiles,
                                fsl_card_F_visitor_fc_to_object,
                                &vstate);
        if(rc) goto end;
    }/* end of F-card */

#define append(V) cwal_array_append(ar, (V))
    if(mf->J.used){
        ar = cwal_new_array(e);
        card = av = cwal_array_value(ar);
        dset("J", card);
        append(strval2("TODO",4));
        for(i = 0; i < mf->J.used; ++i ){
            /* TODO */
        }
    }

    if(mf->K){
        dset("K", strval2(mf->K, FSL_UUID_STRLEN));
    }
    if(mf->L.used){
        dset("L", vornull(mf->L.used, strval2(mf->L.mem,mf->L.used)));
    }

    if(mf->M.used){
        ar = cwal_new_array(e);
        card = av = cwal_array_value(ar);
        dset("M", card);
        for(i = 0; i < mf->M.used; ++i ){
            fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->M.list[i];
            assert(uuid);
            append(strval2(uuid,FSL_UUID_STRLEN));
        }
    }

    if(mf->P.used){
        ar = cwal_new_array(e);
        card = av = cwal_array_value(ar);
        dset("P", card);
        for(i = 0; i < mf->P.used; ++i ){
            fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->P.list[i];
            assert(uuid);
            append(strval2(uuid,FSL_UUID_STRLEN));
        }
    }

    if(mf->Q.used){
        ar = cwal_new_array(e);
        card = av = cwal_array_value(ar);
        dset("Q", card);
        append(strval2("TODO",4));
        for(i = 0; i < mf->P.used; ++i ){
            fsl_card_Q const * qc = (fsl_card_Q const *)mf->Q.list[i];
            assert(qc);
            /* TODO */
        }
    }

    if(mf->U){
        dset("U", strval(mf->U));
    }

    if(mf->W.used){
        dset("W", vornull(mf->W.used, strval2(mf->W.mem,mf->W.used)));
    }else if(0 < fsl_card_is_legal(mf->type, 'W') /* required card */){
        /* corner-case: empty wiki page */
        dset("W", strval2("",0));
    }

#undef append
#undef strval
#undef strval2
#undef vornull
#undef cset
#undef dset
#undef vset2
    end:
    if(!rc){
        if(rv) *rv = ov;
        else cwal_value_unref(ov);
    }else if(ov){
        cwal_value_unref(ov);
    }
    return rc;

}

/*
** Constructor function for fsl_cx instances.
**
** Script-side usage:
**
** var f = ThisFunc(object{...})
**
**
** The parameter object is optional. The only supported option at the
** moment is the traceSql boolean, which enables or disabled SQL
** tracing output.
**
** On success it returns (via *rv) a new cwal_native which is bound to
** a new fsl_cx instance. The fsl_cx will be initialized such that
** fsl_output() and friends will be redirected through cwal_output(),
** to allow fsl_output()-generated output to take advantage of
** th1ish's output buffering and whatnot.
*/
int cb_fsl_cx_ctor( cwal_callback_args const * args,
                     cwal_value **rv ){
    fsl_cx * f = NULL;
    cwal_value * v;
    int rc;
    fsl_cx_init_opt init = fsl_cx_init_opt_empty;
    th1ish_interp * ie = th1ish_args_interp(args);
    assert(ie);
    init.output.out = fsl_output_f_cwal_out;
    init.output.flush = fsl_flush_f_cwal_out;
    init.output.state.state = args->engine;
    if(args->argc && cwal_props_can(args->argv[0])){
        init.config.traceSql =
            cwal_value_get_bool(cwal_prop_get(args->argv[0],
                                              "traceSql", 8));
    }
    rc = fsl_cx_init( &f, &init );
    if(rc){
        return cb_toss(args, FSL_RC_ERROR,
                         "Fossil context initialization failed "
                         "with code #%d (%s).", rc,
                         fsl_rc_cstr(rc));
    }
    v = cwal_new_native_value(args->engine, f,
                              fsl_finalizer_f_fsl_cx,
                              FSL_TYPEID(fsl_cx));
    if(!v){
        fsl_cx_finalize( f );
        return CWAL_RC_OOM;
    }
    cwal_value_prototype_set( v, fsl_cx_prototype(ie) );
    fsl_cx_db_prop( f, v, ie, 1 ) /* initialize this->db */;
    *rv  = v;
    return 0;
}

/*
** Searches v and its prototype chain for a fsl_cx binding. If found,
** it is returned, else NULL is returned.
*/
static fsl_cx * cwal_value_fsl_cx_part( cwal_engine * e,
                                     cwal_value * v ){
    if(!v) return NULL;
    else {
        cwal_native * nv;
        fsl_cx * f;
        while(v){
            nv = cwal_value_get_native(v);
            f = nv
                ? (fsl_cx *)cwal_native_get( nv, FSL_TYPEID(fsl_cx) )
                : NULL;
            if(f) return f;
            else v = cwal_value_prototype_get(e, v);
        } while(v);
        return NULL;
    }
}

#if 0
/*
** Searches v and its prototype chain for a fsl_db binding. If found,
** it is returned, else NULL is returned.
*/
static fsl_db * fsl_value_db_part( th1ish_interp * ie,
                                   cwal_value * v ){
    if(!v) return NULL;
    else {
        cwal_native * nv;
        fsl_db * db;
        while(v){
            nv = cwal_value_get_native(v);
            db = nv
                ? (fsl_db *)cwal_native_get( nv, FSL_TYPEID(fsl_db) )
                : NULL;
            if(db) return db;
            else v = cwal_value_prototype_get(ie->e, v);
        } while(v);
        return NULL;
    }
}
#endif

/*
** Creates a new cwal_value (cwal_native) wrapper for the given fsl_db
** instance. If addDtor is true then a finalizer is installed for the
** instance, else it is assumed to live native-side and gets no
** destructor installed (in which case we will eventually have a problem
** when such a db is destroyed outside of the script API, unless we
** rewrite these to use weak references instead, but that might require
** one more level of struct indirection). The role of the db
** is important so that we can get the proper filename. A role of
** FSL_DB_ROLE_NONE should be used for non-fossil-core db handles.
**
** On success, returns 0 and assigns *rv to the new Db value.
**
** If 0!=db->filename.used then the new value gets a 'name' property
** set to the contents of db->filename.
**
** Maintenance reminder: this routine must return cwal_rc_t codes, not
** fsl_rc_t codes.
**
** TODO? Wrap up cwal_weak_ref of db handle, instead of the db handle
** itself? We only need this if Fossil instances will have their DB
** instances manipulated from outside of script-space.
*/
static int fsl_db_new_native( th1ish_interp * ie,
                              fsl_cx * f,
                              fsl_db * db,
                              char addDtor,
                              cwal_value **rv,
                              fsl_db_role_t role){
    cwal_native * n;
    cwal_value * nv;
    int rc = 0;
    char const * fname = NULL;
    fsl_size_t nameLen = 0;
    cwal_value * tmpV = NULL;
    assert(ie && db && rv);
    n = cwal_new_native(ie->e, db,
                        addDtor ? fsl_finalizer_f_fsl_db : NULL,
                        FSL_TYPEID(fsl_db));
    if(!n) return CWAL_RC_OOM;
    nv = cwal_native_value(n);

    /* Set up  "filename" property. It's problematic because
       of libfossil's internal DB juggling :/.
    */
    fname = fsl_cx_db_file_for_role(f, role, &nameLen);
    if(!fname){
        fname = fsl_db_filename(db, &nameLen);
    }
    if(fname){
        tmpV = cwal_new_string_value(ie->e, fname, (cwal_size_t)nameLen);
        rc = tmpV
            ? cwal_prop_set(nv, "filename", 8, tmpV)
            : CWAL_RC_OOM;
        if(rc && tmpV){
            cwal_value_unref(tmpV);
            tmpV = NULL;
            goto end;
        }
    }

    /* Set up  "name" property. */
    fname = fsl_cx_db_name_for_role(f, role, &nameLen);
    if(!fname){
        fname = fsl_db_name(db);
        nameLen = fname ? fsl_strlen(fname) : 0;
    }
    if(fname){
        tmpV =
            cwal_new_string_value(ie->e, fname, (cwal_size_t)nameLen);
        rc = tmpV
            ? cwal_prop_set(nv, "name", 4, tmpV)
            : CWAL_RC_OOM;
        if(rc && tmpV){
            cwal_value_unref(tmpV);
            tmpV = NULL;
            goto end;
        }
    }
    end:
    if(!rc){
        *rv = nv;
        cwal_value_prototype_set( nv, fsl_db_prototype(ie) );
    }else{
        /*
          Achtung: if addDtor then on error the dtor will be called
          here.
        */
        cwal_value_unref(nv);
    }
    return rc;
}

static cwal_value * fsl_cx_db_prop( fsl_cx * f,
                                    cwal_value * fv,
                                    th1ish_interp * ie,
                                    char createIfNotExists){
    cwal_value * rv;
    fsl_db * db = fsl_cx_db(f);
    /* assert(db); */
    rv = db ? cwal_prop_get(fv, "db", 2) : NULL;
    if(db && !rv && createIfNotExists){
        int rc = fsl_db_new_native(ie, f, db, 0, &rv, FSL_DB_ROLE_MAIN);
        if(rc) return NULL;
        if(rv && cwal_prop_set(fv, "db", 2, rv)){
            cwal_value_unref(rv);
            rv = NULL;
        }
    }
    return rv;
}

/**
 ** Internal helper which adds vSelf->db->repo/checkout
 ** handles. Returns 0 on success. MUST return CWAL_RC_xxx
 ** codes, NOT FSL_RC_xxx codes.
 */
static int fsl_cx_add_db_handles(fsl_cx * f, cwal_value * vSelf,
                                 th1ish_interp * ie){
    cwal_value * dbV = fsl_cx_db_prop(f, vSelf, ie, 1);
    cwal_value * d = NULL;
    fsl_db * db;
    int rc;
    if(!dbV) return FSL_RC_OOM;
    d = cwal_prop_get(dbV, "repo", 4);
    if(!d && (db = fsl_cx_db_repo(f))){
        rc = fsl_db_new_native(ie, f, db, 0, &d, FSL_DB_ROLE_REPO);
        if(rc) return rc;
        rc = cwal_prop_set(dbV, "repo", 4, d);
        if(rc){
            cwal_value_unref(d);
            return rc;
        }
    }
    d = cwal_prop_get(dbV, "checkout", 8);
    if(!d && (db = fsl_cx_db_checkout(f))){
        rc = fsl_db_new_native(ie, f, db, 0, &d, FSL_DB_ROLE_CHECKOUT);
        if(rc) return rc;
        rc = cwal_prop_set(dbV, "checkout", 8, d);
        if(rc){
            cwal_value_unref(d);
            return rc;
        }
    }
    return 0;    
}

#define THIS_F th1ish_interp * ie = th1ish_args_interp(args); \
    cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
    cwal_value * natV = nat ? cwal_native_value(nat) : NULL;                   \
    fsl_cx * f = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL;         \
    assert(ie); \
    if(!f){ return th1ish_toss(ie, FSL_RC_TYPE, \
                               "'this' is not (or is no longer) "       \
                               "a Fossil instance."); } (void)0

static int cb_fsl_repo_open( cwal_callback_args const * args,
                                 cwal_value **rv ){
    char const * dbName;
    int rc;
    THIS_F;
    dbName = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!dbName || !*dbName){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "Expecting a non-empty string argument.");
    }
    rc = fsl_repo_open( f, dbName );
    if(rc){
        rc = cb_toss_fsl(args, f);
    }else{
        rc = fsl_cx_add_db_handles(f, args->self, ie);
    }
    return rc;
}

static int cb_fsl_checkout_open_dir( cwal_callback_args const * args,
                                    cwal_value **rv ){
    char const * dbName;
    cwal_size_t nameLen = 0;
    int rc;
    THIS_F;
    dbName = args->argc
        ? cwal_value_get_cstr(args->argv[0], &nameLen)
        : NULL;
    rc = fsl_checkout_open_dir( f, dbName,
                               nameLen ? (fsl_int_t)nameLen : -1 );
    if(rc){
        rc = cb_toss_fsl(args, f);
    }else{
        rc = fsl_cx_add_db_handles(f, args->self, ie);
    }
    return rc;
}


/*
** Copies fb's state into cb. This is intended to be a temporary
** switch - it is vital that both fb and cb do not get cleaned up
** while they point to this memory, or they will double-free it.
*/
static void fsl_buffer_to_cwal_buffer( fsl_buffer const * fb,
                                       cwal_buffer * cb ){
    cb->mem = fb->mem;
    cb->capacity = fb->capacity;
    cb->used = fb->used;
}

/*
** Reverses the damage done by fsl_buffer_to_cwal_buffer().
*/
static void cwal_buffer_to_fsl_buffer( cwal_buffer const * cb,
                                       fsl_buffer * fb ){
    fb->mem  = cb->mem;
    fb->capacity = cb->capacity;
    fb->used = cb->used;
    fb->cursor = 0;
}


static int cb_fsl_cx_content_get( cwal_callback_args const * args,
                                  cwal_value **rv ){
    int rc;
    fsl_id_t rid = 0;
    char const * sym = NULL;
    fsl_buffer fbuf = fsl_buffer_empty;
    THIS_F;
    if(!args->argc) goto usage;
    else{
        cwal_value * arg = args->argv[0];
        if(cwal_value_is_integer(arg)){
            rid = (fsl_id_t)cwal_value_get_integer(arg);
            if(rid<=0) goto usage;
        }else{
            sym = cwal_value_get_cstr(arg, NULL);
            if(!sym || !*sym) goto usage;
        }
    }
    rc = (rid>0)
        ? fsl_content_get(f, rid, &fbuf)
        : fsl_content_get_sym(f, sym, &fbuf);
    if(rc){
        assert(f->error.code);
        rc = cb_toss_fsl(args, f);
    }else{
        cwal_buffer * cbuf = cwal_new_buffer(args->engine, 0);
        if(!cbuf) rc = CWAL_RC_OOM;
        else{
            *rv = cwal_buffer_value(cbuf);
            fsl_buffer_to_cwal_buffer(&fbuf, cbuf);
            fbuf = fsl_buffer_empty;
        }
    }
    fsl_buffer_clear(&fbuf);
    return rc;
    usage:
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one symbolic name (string) or "
                   "RID (positive integer) argument.");
}


/**
   Script usage:

   const deck = Fossil.loadManifest(
       version [, includeBaselineFiles
          [, includeBaselineObject]]

   The deck object's struct matches fsl_deck pretty
   closely.
*/
static int cb_fsl_cx_deck_load( cwal_callback_args const * args,
                                cwal_value **rv ){
    int rc;
    fsl_deck mf = fsl_deck_empty;
    fsl_id_t rid = 0;
    char const * sym = NULL;
    fsl_catype_t mfType = FSL_CATYPE_ANY;
    char includeBaselineFiles;
    char includeBaselineObj;
    THIS_F;
    if(!args->argc) goto usage;
    else if(cwal_value_is_number(args->argv[0])){
        rid = (fsl_id_t)cwal_value_get_integer(args->argv[0]);
        if(rid<=0) goto usage;
    }else{
        sym = cwal_value_get_cstr(args->argv[0], NULL);
        if(!sym || !*sym) goto usage;
    }
    includeBaselineFiles = (args->argc>1)
        ? cwal_value_get_bool(args->argv[1])
        : 0;
    includeBaselineObj = (args->argc>2)
        ? cwal_value_get_bool(args->argv[2])
        : 0;
    /* TODO: script mapping for fsl_catype_t values for 3nd arg.
     */
    rc = sym
        ? fsl_deck_load_sym(f, &mf, sym, mfType)
        : fsl_deck_load_rid(f, &mf, rid, mfType)
        ;
    if(rc){
        rc = cb_toss_fsl(args, f);
        goto end;
    }

    rc = th_deck_to_object(args->engine, &mf,
                           includeBaselineFiles,
                           includeBaselineObj, rv);

    end:
    fsl_deck_finalize(&mf);
    return rc;
    usage:
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one symbolic name (string) or "
                   "RID (positive integer) argument.");
}

static int cb_fsl_cx_resolve_sym( cwal_callback_args const * args,
                                  cwal_value **rv,
                                  char toUuid ){
    char * uuid = NULL;
    char const * sym;
    int rc;
    cwal_int_t argRid = 0;
    fsl_id_t rid = 0;
    THIS_F;
    sym = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if((!sym || !*sym) && args->argc){
        argRid = cwal_value_get_integer(args->argv[0]);
    }
    if(argRid<=0 && (!sym || !*sym)){
        return cb_toss(args, FSL_RC_MISUSE,
                       "Expecting a positive integer or non-empty "
                       "string argument.");
    }
    rc = toUuid
        ? fsl_sym_to_uuid( f, sym, FSL_CATYPE_ANY, &uuid, &rid )
        : fsl_sym_to_rid( f, sym, FSL_CATYPE_ANY, &rid );
    if(rc){
        rc = cb_toss_fsl(args, f);
    }else{
        if(toUuid){
            assert(uuid);
            *rv = cwal_new_zstring_value(args->engine, uuid, FSL_UUID_STRLEN)
                /* Reminder zstring is only safe here if f and args->engine
                   are configured for the same allocator.
                */;
            if(!*rv){
                fsl_free(uuid);
                rc = CWAL_RC_OOM;
            }
        }else{
            assert(!uuid);
            assert(rid>0);
            *rv = cwal_new_integer(args->engine, (cwal_int_t)rid);
            if(!*rv) rc = CWAL_RC_OOM;
        }
    }
    return rc;
}


static int cb_fsl_cx_sym2uuid( cwal_callback_args const * args,
                                   cwal_value **rv ){
    return cb_fsl_cx_resolve_sym(args, rv, 1);
}

static int cb_fsl_cx_sym2rid( cwal_callback_args const * args,
                              cwal_value **rv ){
    return cb_fsl_cx_resolve_sym(args, rv, 0);
}

static int cb_fsl_cx_adiff( cwal_callback_args const * args,
                            cwal_value **rv ){
    char const * sym;
    fsl_id_t rid1, rid2;
    cwal_value * arg;
    int diffFlags = 0;
    int rc;
    short contextLines = 3;
    short sbsWidth = 0;
    fsl_buffer c1 = fsl_buffer_empty;
    fsl_buffer c2 = fsl_buffer_empty;
    fsl_buffer delta = fsl_buffer_empty;
    THIS_F;
    if(args->argc<2){
        misuse:
        return th1ish_toss(ie, FSL_RC_MISUSE, "Expecting two artifact ID arguments.");
    }
    /* The v1 arg... */
    arg = args->argv[0];
    if(cwal_value_is_integer(arg)){
        rid1 = (fsl_id_t)cwal_value_get_integer(arg);
    }else if(!(sym = cwal_value_get_cstr(arg,NULL))){
        goto misuse;
    }else{
        rc = fsl_sym_to_rid(f, sym, FSL_CATYPE_ANY, &rid1);
        if(rc) return cb_toss_fsl(args, f);
    }

    /* The v2 arg... */
    arg = args->argv[1];
    if(cwal_value_is_integer(arg)){
        rid2 = (fsl_id_t)cwal_value_get_integer(arg);
    }else if(!(sym = cwal_value_get_cstr(arg,NULL))){
        goto misuse;
    }else{
        rc = fsl_sym_to_rid(f, sym, FSL_CATYPE_ANY, &rid2);
        if(rc) return cb_toss_fsl(args, f);
    }

    assert(rid1>0);
    assert(rid2>0);

    rc = fsl_content_get(f, rid1, &c1);
    if(!rc) rc = fsl_content_get(f, rid2, &c2);
    if(rc){
        rc = cb_toss_fsl(args, f);
        goto end;
    }
    diffFlags = FSL_DIFF_LINENO | FSL_DIFF_SIDEBYSIDE;
    if(args->argc>2)do{
            cwal_value * v;
            arg = args->argv[2];
            if(!cwal_props_can(arg)) break;
            v = cwal_prop_get(arg, "contextLines", 12);
            if(v) contextLines = (short)cwal_value_get_integer(v);
            v = cwal_prop_get(arg, "html", 4);
            if(v && cwal_value_get_bool(v)){
                diffFlags |= FSL_DIFF_HTML;
            }
            v = cwal_prop_get(arg, "inline", 6);
            if(v && cwal_value_get_bool(v)){
                diffFlags &= ~FSL_DIFF_SIDEBYSIDE;
                sbsWidth = 0;
            }
            v = cwal_prop_get(arg, "invert", 6);
            if(v && cwal_value_get_bool(v)){
                diffFlags |= FSL_DIFF_INVERT;
            }
            v = cwal_prop_get(arg, "sbsWidth", 8);
            if(v){
                sbsWidth = (short)cwal_value_get_integer(v);
            }
            v = cwal_prop_get(arg, "text", 4);
            if(v && cwal_value_get_bool(v)){
                diffFlags &= ~FSL_DIFF_HTML;
            }
    } while(0);
    /* return th1ish_toss(ie, 1, "flags=%08x", diffFlags); */


    rc = fsl_diff_text(&c1, &c2, fsl_output_f_buffer, &delta,
                       contextLines, sbsWidth, diffFlags);
    if(rc){
        if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
        else rc = th1ish_toss(ie, rc, "Error %s while creating diff.",
                              fsl_rc_cstr(rc));
    }else{
        cwal_value * v = cwal_new_buffer_value(args->engine, 0);
        cwal_buffer * bv = v ? cwal_value_buffer_part(args->engine, v) : NULL;
        if(!v){
            rc = CWAL_RC_OOM;
            goto end;
        }
        assert(!bv->mem);
        *rv = v;
        fsl_buffer_to_cwal_buffer(&delta, bv);
        delta = fsl_buffer_empty /* transfer ownership */;
    }
    end:
    fsl_buffer_clear(&c1);
    fsl_buffer_clear(&c2);
    fsl_buffer_clear(&delta);
    return rc;
}
#if 0
/* recycle this func. changing trace mode at runtime current has
   no effect.
*/
static int cb_fsl_cx_trace_sql( cwal_callback_args const * args,
                              cwal_value **rv ){
    THIS_F;
    if(args->argc){
        f->config.traceSql = cwal_value_get_bool(args->argv[0]);
    }
    *rv = cwal_new_bool(f->config.traceSql);
    return 0;
}
#endif

static void cb_fsl_clear_handles(cwal_value * parent,
                                 char const * dbName){
    cwal_engine * e = cwal_value_engine(parent);
    cwal_value * v = e ? cwal_prop_get(parent, dbName, 0) : NULL;
    assert(e);
    if(v){
        cwal_native * nat = cwal_value_native_part(e, v);
        fsl_db * db = nat ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL;
        if(db){
            cwal_native_clear(nat, 0);
            cwal_prop_unset(parent, dbName, 0);
        }
    }

}

static int cb_fsl_repo_close( cwal_callback_args const * args,
                              cwal_value **rv ){
    cwal_value * dbNs;
    int rc;
    THIS_F;
    dbNs = fsl_cx_db_prop(f, natV, ie, 0);
    if(dbNs){
        cb_fsl_clear_handles(dbNs, "repo");
    }
    rc = fsl_repo_close( f );
    return rc
        ? cb_toss(args, rc,
                    "Closing repo failed with code %d (%s).",
                    rc, fsl_rc_cstr(rc))
        : 0;
}

static int cb_fsl_checkout_close( cwal_callback_args const * args,
                                  cwal_value **rv ){
    cwal_value * dbNs;
    int rc;
    THIS_F;
    dbNs = fsl_cx_db_prop(f, natV, ie, 0);
    if(dbNs){
        cb_fsl_clear_handles(dbNs, "repo");
        cb_fsl_clear_handles(dbNs, "checkout");
    }
    rc = fsl_checkout_close( f );
    return rc
        ? cb_toss(args, rc,
                  "Closing checkout failed with code %d (%s).",
                  rc, fsl_rc_cstr(rc))
        : 0;
}

/**
   Script usage:


   const m = globMatches("*.*", "some.string")

   Or:

   const m = "*.*".globMatches(someString)

   (The second form is not installed by default!)

 */
static int cb_strglob( cwal_callback_args const * args,
                       cwal_value **rv ){
    int rc = 0;
    char const * glob =
        (args->argc>0) ? cwal_value_get_cstr(args->argv[0], NULL) : NULL;
    char const * str =
        (args->argc>1) ? cwal_value_get_cstr(args->argv[1], NULL) : NULL;
    char const * sSelf = cwal_value_get_cstr(args->self, NULL);
    if(sSelf){
        str = glob;
        glob = sSelf;
    }
    if(!glob){
        rc = cb_toss(args, FSL_RC_MISUSE,
                     "Expecting (glob,string) arguments "
                     "OR stringInstance.thisFunc(otherString) "
                     "usage.");
    }
    else if(!str){
        *rv = cwal_value_false();
    }else{
        *rv = fsl_str_glob(glob, str)
            ? cwal_value_true() : cwal_value_false();
    }
    return rc;
}

static int cb_is_uuid( cwal_callback_args const * args,
                       cwal_value **rv ){
    cwal_size_t slen = 0;
    char const * str =
        (args->argc>0) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL;
    *rv = (str && (FSL_UUID_STRLEN==slen))
        ? cwal_new_bool(fsl_is_uuid(str))
        : cwal_value_false();
    return 0;
}


static int cb_time_now( cwal_callback_args const * args,
                        cwal_value **rv ){
    char asJulian = 0;
    time_t now;
    asJulian = args->argc && cwal_value_get_bool(args->argv[0]);
    time(&now);
    *rv = asJulian
        ? cwal_new_double(args->engine,
                          fsl_unix_to_julian((fsl_time_t)now))
        : cwal_new_integer(args->engine,
                           (cwal_int_t)now)
        ;
    return *rv ? 0 : CWAL_RC_OOM;
}


static int cb_unix_to_julian( cwal_callback_args const * args,
                              cwal_value **rv ){
    cwal_int_t tm;
    if(!args->argc){
        return cb_toss(args, FSL_RC_MISUSE,
                       "Expecting one integer argument.");
    }
    tm = cwal_value_get_integer(args->argv[0]);
    *rv = cwal_new_double(args->engine,
                          fsl_unix_to_julian( (fsl_time_t)tm ));
    return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_julian_to_unix( cwal_callback_args const * args,
                              cwal_value **rv ){
    cwal_double_t tm;
    if(!args->argc){
        return cb_toss(args, FSL_RC_MISUSE,
                       "Expecting one double argument.");
    }
    tm = cwal_value_get_double(args->argv[0]);
    *rv = cwal_new_integer(args->engine,
                           fsl_julian_to_unix( (fsl_double_t)tm ));
    return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_julian_to_iso8601( cwal_callback_args const * args,
                                 cwal_value **rv ){
    cwal_double_t tm;
    char includeMs;
    char buf[24];
    if(!args->argc){
        return cb_toss(args, FSL_RC_MISUSE,
                       "Expecting one double argument.");
    }
    tm = cwal_value_get_double(args->argv[0]);
    includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0;
    if(fsl_julian_to_iso8601(tm, buf, includeMs)){
        *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19);
        return *rv ? 0 : CWAL_RC_OOM;
    }else{
        return cb_toss(args, FSL_RC_MISUSE,
                       "Invalid Julian date value.");
    }
}

static int cb_julian_to_human( cwal_callback_args const * args,
                               cwal_value **rv ){
    cwal_double_t tm;
    char includeMs;
    char buf[24];
    if(!args->argc){
        return cb_toss(args, FSL_RC_MISUSE,
                       "Expecting one double argument.");
    }
    includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0;
    tm = cwal_value_get_double(args->argv[0]);
    if(fsl_julian_to_iso8601(tm, buf, includeMs)){
        assert('T' == buf[10]);
        buf[10] = ' ';
        *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19);
        return *rv ? 0 : CWAL_RC_OOM;
    }else{
        return cb_toss(args, FSL_RC_MISUSE,
                       "Invalid Julian data value.");
    }
}

static int cb_fsl_cx_finalize( cwal_callback_args const * args,
                              cwal_value **rv ){
    THIS_F;
    cwal_native_clear( nat, 1 );
    return 0;
}

static int th1ish_fsl_openmode_to_flags(char const * openMode, int startFlags){
    int openFlags = startFlags;
    for( ; *openMode; ++openMode ){
        switch(*openMode){
          case 'w': openFlags |= FSL_OPEN_F_RW;
              break;
          case 'c': openFlags |= FSL_OPEN_F_CREATE;
              break;
          case 'v': openFlags |= FSL_OPEN_F_SCHEMA_VALIDATE;
              break;
          default:
              break;
        }
    }
    return openFlags;
}

int cb_fsl_db_ctor( cwal_callback_args const * args,
                    cwal_value **rv ){
    cwal_value * v = NULL;
    int rc;
    th1ish_interp * ie = th1ish_args_interp(args);
    char const * fn;
    char const * openMode;
    cwal_size_t fnLen = 0;
    int openFlags = 0;
    fsl_db dbA = fsl_db_empty;
    fsl_cx * f = cwal_value_fsl_cx_part(args->engine, args->self)
        /* It's okay if this is NULL. It will only be set when we
           aFossilContext.dbOpen() is called.
        */
        ;
    assert(ie);
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], &fnLen)
        : NULL;
    if(!fn){
        return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting a string (db file name) argument.");
    }
    openMode = (args->argc>0)
        ? cwal_value_get_cstr(args->argv[1], NULL)
        : NULL;
    if(!openMode){
        openFlags = SQLITE_OPEN_READWRITE |
            SQLITE_OPEN_CREATE;
    }else{
        openFlags = th1ish_fsl_openmode_to_flags( openMode, openFlags );
    }
    dbA.f = f;
    rc = fsl_db_open( &dbA, fn, openFlags );
    if(rc){
        if(dbA.error.msg.used){
            rc = cb_toss(args, FSL_RC_ERROR,
                           "Db open failed: code #%d: %s",
                           dbA.error.code,
                           (char const *)dbA.error.msg.mem);
        }else{
            rc = cb_toss(args, FSL_RC_ERROR,
                           "Db open failed "
                           "with code #%d (%s).", rc,
                           fsl_rc_cstr(rc));
        }
        fsl_db_close(&dbA);
    }
    else{
        fsl_db * dbP = fsl_db_malloc();
        if(!dbP){
            fsl_db_close(&dbA);
            rc = CWAL_RC_OOM;
        }
        else{
            void const * kludge = dbP->allocStamp;
            *dbP = dbA /* transfer ownership */;
            dbA = fsl_db_empty;
            dbP->allocStamp = kludge /* ensure that fsl_db_close() DTRT. */;
            rc = fsl_db_new_native(ie, f, dbP, 1, &v, FSL_DB_ROLE_NONE);
            if(rc){
                fsl_db_close(dbP);
                dbP = NULL;
                assert(!v);
            }
        }
    }
    if(!rc){
        assert(v);
        *rv  = v;
        return 0;
    }else{
        assert(!v);
        return rc;
    }
}

#define THIS_DB th1ish_interp * ie = th1ish_args_interp(args); \
    cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
    cwal_value * natV = nat ? cwal_native_value(nat) : NULL;                   \
    fsl_db * db = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL;         \
    assert(ie); \
    if(!db){ return th1ish_toss(ie, FSL_RC_TYPE, \
                               "'this' is not (or is no longer) "       \
                               "a Db instance."); } (void)0

#define THIS_STMT th1ish_interp * ie = th1ish_args_interp(args);          \
    cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
    cwal_value * natV = nat ? cwal_native_value(nat) : NULL;                   \
    fsl_stmt * stmt = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_stmt) ) : NULL;         \
    assert(ie); \
    if(!stmt){ return th1ish_toss(ie, FSL_RC_TYPE, \
                                  "'this' is not (or is no longer) "    \
                                  "a Stmt instance."); } (void)0

static int cb_fsl_db_finalize( cwal_callback_args const * args,
                               cwal_value **rv ){
    THIS_DB;
    cwal_native_clear( nat, 1 );
    return 0;
}

static int cb_fsl_db_filename( cwal_callback_args const * args,
                               cwal_value **rv ){
    char const * fname = NULL;
    fsl_size_t nameLen = 0;
    THIS_DB;
    fname = fsl_db_filename(db, &nameLen);
    *rv = fname
        ? cwal_new_string_value(args->engine, fname, nameLen)
        : cwal_value_null();
    return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_db_name( cwal_callback_args const * args,
                               cwal_value **rv ){
    char const * fname = NULL;
    THIS_DB;
    fname = fsl_db_name(db);
    *rv = fname
        ? cwal_new_string_value(args->engine, fname, fsl_strlen(fname))
        : cwal_value_null();
    return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_stmt_finalize( cwal_callback_args const * args,
                                 cwal_value **rv ){
    THIS_STMT;
    cwal_native_clear( nat, 1 );
    return 0;
}

static int cb_fsl_db_last_insert_id( cwal_callback_args const * args,
                                     cwal_value **rv ){
    THIS_DB;
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)sqlite3_last_insert_rowid(db->dbh));
    return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Extracts result column ndx from st and returns a "the closest
   approximation" of its type in cwal_value form by assigning it to
   *rv. Returns 0 on success. On errror *rv is not modified.
   Does not trigger a th1ish/cwal exception on error.
*/
static int th1ish_fsl_stmt_to_value( cwal_engine * e,
                                     fsl_stmt * st,
                                     int ndx,
                                     cwal_value ** rv ){
    int vtype = sqlite3_column_type(st->stmt, ndx);
    switch( vtype ){
      case SQLITE_NULL:
          *rv = cwal_value_null();
          return 0;
      case SQLITE_INTEGER:
          *rv = cwal_new_integer( e,
                                  (cwal_int_t)sqlite3_column_int64(st->stmt,ndx) );
          break;
      case SQLITE_FLOAT:
          *rv = cwal_new_double( e,
                                 (cwal_double_t)sqlite3_column_double(st->stmt, ndx) );
          break;
      case SQLITE_BLOB: {
          int slen = 0;
          void const * bl = sqlite3_column_blob( st->stmt, ndx );
          slen = bl ? sqlite3_column_bytes(st->stmt, ndx) : 0;
#if 1
          {
              cwal_buffer * buf = cwal_new_buffer(e, slen+1);
              int rc;
              if(!buf) return CWAL_RC_OOM;
              rc = cwal_buffer_append( e, buf, bl, slen );
              if(rc){
                  assert(!*rv);
                  cwal_value_unref(cwal_buffer_value(buf));
              }else{
                  *rv = cwal_buffer_value(buf);
              }
          }
#else
          /* we'll just hope it's really a string for now. We could/should
             use a buffer here. */
          *rv = cwal_new_string_value(e, slen ? (char const *)bl : NULL,
                                      (cwal_size_t)slen);
#endif
          break;
      }
      case SQLITE_TEXT: {
          int slen = 0;
          void const * str = sqlite3_column_text( st->stmt, ndx );
          slen = str ? sqlite3_column_bytes(st->stmt, ndx) : 0;
          *rv = cwal_new_string_value(e, slen ? str : NULL,
                                      (cwal_size_t) slen);
          break;
      }
      default:
          return cwal_exception_setf(e, CWAL_RC_TYPE,
                                     "Unknown db column type (%d).",
                                     vtype);
    }
    return *rv ? 0 : CWAL_RC_OOM;
}


/**
   Extracts all columns from st into a new cwal_object value.
   colNames is expected to hold the same number of entries
   as st has columns, and in the same order. The entries in
   colNames are used as the object's field keys.

   Returns NULL on error.
*/
static cwal_value * th1ish_fsl_stmt_row_to_object2( cwal_engine * e,
                                                    fsl_stmt * st,
                                                    cwal_array const * colNames ){
    int colCount;
    cwal_value * objV;
    cwal_object * obj;
    cwal_string * colName;
    int i;
    int rc;
    if( ! st ) return NULL;
    colCount = st->colCount;
    if( !colCount || (colCount>(int)cwal_array_length_get(colNames)) ) {
        return NULL;
    }
    obj = cwal_new_object(e);
    if( ! obj ) return NULL;
    objV = cwal_object_value( obj );
    for( i = 0; i < colCount; ++i ){
        cwal_value * v = NULL;
        colName = cwal_value_get_string( cwal_array_get( colNames, i ) );
        if( ! colName ) goto error;
        rc = th1ish_fsl_stmt_to_value( e, st, i, &v );
        if( rc ) goto error;
        rc = cwal_object_set_s( obj, colName, v );
        if( rc ){
            cwal_value_unref(v);
            goto error;
        }
    }
    return objV;
    error:
    cwal_value_unref( objV );
    return NULL;
}

/**
   Extracts all columns from st into a new cwal_object value,
   using st's column names as the keys.

   Returns NULL on error.
*/
static cwal_value * th1ish_fsl_stmt_row_to_object( cwal_engine * e,
                                                   fsl_stmt * st ){
    cwal_value * objV;
    cwal_object * obj;
    char const * colName;
    int i;
    int rc;
    if( ! st || !st->colCount ) return NULL;
    obj = cwal_new_object(e);
    if( ! obj ) return NULL;
    objV = cwal_object_value(obj);
    for( i = 0; i < st->colCount; ++i ){
        cwal_value * v = NULL;
        colName = sqlite3_column_name(st->stmt, i);
        if( ! colName ) goto error;
        rc = th1ish_fsl_stmt_to_value( e, st, i, &v );
        if( rc ) goto error;
        rc = cwal_object_set( obj, colName, 0, v );
        if( rc ){
            cwal_value_unref(v);
            goto error;
        }
    }
    return objV;
    error:
    cwal_value_unref( objV );
    return NULL;
}

/**
    Appends all result columns from st to ar.
*/
static int th1ish_fsl_stmt_row_to_array2( cwal_engine * e,
                                          fsl_stmt * st,
                                          cwal_array * ar)
{
    int i;
    int rc = 0;
    for( i = 0; i < st->colCount; ++i ){
        cwal_value * v = NULL;
        rc = th1ish_fsl_stmt_to_value( e, st, i, &v );
        if( rc ) goto end;
        rc = cwal_array_append( ar, v );
        if( rc ){
            cwal_value_unref(v);
            goto end;
        }
    }
    end:
    return rc;
}

/**
    Converts all column values from st into a cwal_array
    value. Returns NULL on error, else an array value.
*/
static cwal_value * th1ish_fsl_stmt_row_to_array( cwal_engine * e,
                                                  fsl_stmt * st )
{
    cwal_array * ar;
    int rc = 0;
    if( ! st || !st->colCount ) return NULL;
    ar = cwal_new_array(e);
    if( ! ar ) return NULL;
    rc = th1ish_fsl_stmt_row_to_array2( e, st, ar );
    if(rc){
        cwal_array_unref(ar);
        return NULL;
    }else{
        return cwal_array_value(ar);
    }
}

/**
    Returns a new cwal_array value containing the result column names
    of the given statement . Returns NULL on error, else an array value.
*/
static cwal_value * th1ish_fsl_stmt_col_names( cwal_engine * e,
                                               fsl_stmt * st ){
    cwal_value * aryV = NULL;
    cwal_array * ary = NULL;
    char const * colName = NULL;
    int i = 0;
    int rc = 0;
    cwal_value * newVal = NULL;
    assert(st);
    if( ! st->colCount ) return NULL;
    ary = cwal_new_array(e);
    if( ! ary ) return NULL;
    rc = cwal_array_reserve(ary, (cwal_size_t)st->colCount);
    if(rc) return NULL;
    aryV = cwal_array_value(ary);
    assert(ary);
    for( i = 0; (0==rc) && (i < st->colCount); ++i ){
        colName = sqlite3_column_name(st->stmt, i);
        if( ! colName ) rc = CWAL_RC_OOM;
        else{
            newVal = cwal_new_string_value(e, colName,
                                           fsl_strlen(colName));
            if( NULL == newVal ){
                rc = CWAL_RC_OOM;
            }
            else{
                rc = cwal_array_set( ary, i, newVal );
                if( rc ) cwal_value_unref( newVal );
            }
        }
    }
    if( 0 == rc ) return aryV;
    else{
        cwal_value_unref(aryV);
        return NULL;
    }
}

static int th1ish_fsl_setup_new_stmt(th1ish_interp * ie,
                                     cwal_value * nv,
                                     fsl_stmt * st){
    int rc;
    cwal_value * v;
    fsl_size_t sqlLen = 0;
    char const * sql;
    cwal_engine * e = ie->e;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(K) CHECKV; rc = cwal_prop_set(nv, K, fsl_strlen(K), v); \
    if(rc) goto end;
    sql = fsl_buffer_cstr2(&st->sql, &sqlLen);
    v = cwal_new_string_value(e, sql, (cwal_size_t)sqlLen);
    SET("sql");
    cwal_prop_set( nv, "sql", 3, v );
    v = cwal_new_integer(e, (cwal_int_t)st->colCount);
    SET("columnCount");
    v = cwal_new_integer(e, (cwal_int_t)st->paramCount);
    SET("parameterCount");
    if(st->colCount){
        v = th1ish_fsl_stmt_col_names(e, st);
        SET("columnNames");
    }
    cwal_value_prototype_set(nv, fsl_stmt_prototype(ie));
#undef SET
#undef CHECKV
    assert(!rc);
    end:
    return rc;

}


static int cb_fsl_db_prepare( cwal_callback_args const * args,
                              cwal_value **rv ){
    fsl_stmt st = fsl_stmt_empty;
    int rc;
    char const * sql;
    cwal_size_t sqlLen = 0;
    cwal_value * nv = NULL;
    cwal_engine * e = args->engine;
    fsl_stmt * st2 = NULL;
    THIS_DB;
    sql = args->argc
        ? cwal_value_get_cstr(args->argv[0], &sqlLen)
        : NULL;
    if(!sql || !sqlLen){
        return cwal_exception_setf(e,
                                   FSL_RC_MISUSE,
                                   "Expecting a non-empty string "
                                   "argument (SQL).");
    }
    assert(!st.stmt);
    rc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql);
    if(rc){
        rc = cb_toss_db(args, db);
        assert(!st.stmt);
    }else{
        st2 = fsl_stmt_malloc();
        nv = st2
            ? cwal_new_native_value(e,
                                    st2, fsl_finalizer_f_fsl_stmt,
                                    FSL_TYPEID(fsl_stmt))
            : NULL;
        if(!nv){
            if(st2) fsl_free(st2);
            fsl_stmt_finalize(&st);
            st2 = NULL;
            rc = CWAL_RC_OOM;
        }else{
            void const * kludge = st2->allocStamp;
            *st2 = st;
            st2->allocStamp = kludge;
            rc = th1ish_fsl_setup_new_stmt(ie, nv, st2);
            if(!rc) *rv = nv;
        }
    }
    if(rc && nv){
        cwal_value_unref(nv);
    }        
    return rc;
}

static int cb_fsl_stmt_row_to_array( cwal_callback_args const * args,
                                     cwal_value **rv ){
    int rc;
    THIS_STMT;
    if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE,
                                       "Cannot fetch row data from an "
                                       "unstepped statement.");
    *rv = th1ish_fsl_stmt_row_to_array(args->engine, stmt);
    if(*rv) rc = 0;
    else rc = cb_toss(args, CWAL_RC_ERROR,
                      "Unknown error converting statement "
                      "row to Array.");
    return rc;
}

static int cb_fsl_stmt_row_to_object( cwal_callback_args const * args,
                                     cwal_value **rv ){
    int rc;
    THIS_STMT;
    if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE,
                                       "Cannot fetch row data from "
                                       "an unstepped statement.");
    *rv = th1ish_fsl_stmt_row_to_object(args->engine, stmt);
    if(*rv) rc = 0;
    else rc = cb_toss(args, CWAL_RC_ERROR,
                      "Unknown error converting statement row "
                      "to Object.");
    return rc;
}


static int cb_fsl_stmt_reset( cwal_callback_args const * args, cwal_value **rv ){
    char resetCounter = 0;
    int rc;
    THIS_STMT;
    if(1 < args->argc) resetCounter = cwal_value_get_bool(args->argv[1]);
    rc = fsl_stmt_reset2(stmt, resetCounter);
    return rc
        ? cb_toss_db( args, stmt->db )
        : 0;
}

static int cb_fsl_db_exec_impl( cwal_callback_args const * args,
                                cwal_value **rv,
                                char isMulti ){
    int rc;
    char const * sql;
    cwal_size_t sqlLen = 0;
    int argIndex = 0;
    THIS_DB;
    again:
    sql = (args->argc>argIndex)
        ? cwal_value_get_cstr(args->argv[argIndex], &sqlLen)
        : NULL;
    if(!sql || !sqlLen){
        return (argIndex>0)
            ? 0
            : cb_toss(args,
                        FSL_RC_MISUSE,
                        "Expecting a non-empty string "
                        "argument (SQL).");
    }
    /* MARKER(("SQL:<<<%.*s>>>\n", (int)sqlLen, sql)); */
    rc = isMulti
        ? fsl_db_exec_multi( db, "%.*s", (int)sqlLen, sql )
        : fsl_db_exec( db, "%.*s", (int)sqlLen, sql )
        ;
    if(rc){
        rc = cb_toss_db( args, db );
    }else if(++argIndex < args->argc){
        goto again;
    }
    return rc;
}

static int cb_fsl_db_exec_multi( cwal_callback_args const * args,
                                 cwal_value **rv ){
    return cb_fsl_db_exec_impl( args, rv, 1 );
}

static int cb_fsl_db_exec( cwal_callback_args const * args,
                           cwal_value **rv ){
    return cb_fsl_db_exec_impl( args, rv, 0 );
}

#if 0
static int cb_fsl_stmt_col_count( cwal_callback_args const * args,
                                  cwal_value **rv ){
    THIS_STMT;
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)stmt->colCount);
    return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_stmt_param_count( cwal_callback_args const * args,
                                  cwal_value **rv ){
    THIS_STMT;
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)stmt->paramCount);
    return *rv ? 0 : CWAL_RC_OOM;
}
#endif


/**
   mode: 0==Object, >0==Array, <0==no row data (just boolean
   indicator)
*/
static int th1ish_fsl_stmt_step_impl( cwal_callback_args const * args,
                                    cwal_value **rv,
                                    int mode ){
    int rc;
    int scode;
    THIS_STMT;
    scode = fsl_stmt_step( stmt );
    switch(scode){
      case FSL_RC_STEP_DONE:
          rc = 0;
          *rv = cwal_value_undefined();
          break;
      case FSL_RC_STEP_ROW:{
          if(0==mode){
              /* Object mode */
              cwal_array const * colNames =
                  cwal_value_get_array( cwal_prop_get(args->self,
                                                      "columnNames", 11) );
              *rv = colNames
                  ? th1ish_fsl_stmt_row_to_object2(args->engine,
                                                   stmt, colNames)
                  : th1ish_fsl_stmt_row_to_object(args->engine, stmt);
          }else if(mode>0){
              /* Array mode */
              *rv = th1ish_fsl_stmt_row_to_array(args->engine, stmt);
          }else{
              /* "Normal" mode */
              *rv = cwal_value_true();
          }
          rc = *rv ? 0 : CWAL_RC_OOM;
          break;
      }
      default:
          rc = cb_toss_db(args, stmt->db);
          break;
    }
    return rc;
}

static int cb_fsl_stmt_step_array( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_fsl_stmt_step_impl( args, rv, 1 );
}

static int cb_fsl_stmt_step_object( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_fsl_stmt_step_impl( args, rv, 0 );
}

static int cb_fsl_stmt_step( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_fsl_stmt_step_impl( args, rv, -1 );
}

/**
   Tries to bind v to the given parameter column (1-based) of
   nv->stmt.  Returns 0 on success, triggers a script-side exception
   on error.
*/
static int th1ish_fsl_stmt_bind( cwal_engine * e,
                                 fsl_stmt * st,
                                 int ndx,
                                 cwal_value * v ){
    int rc;
    int const vtype = v ? cwal_value_type_id(v) : CWAL_TYPE_NULL;
    if(ndx<1) {
        return cwal_exception_setf(e, FSL_RC_RANGE,
                                   "Bind index %d is invalid: indexes are 1-based.",
                                   ndx);
    }
    else if(ndx > st->paramCount) {
        return cwal_exception_setf(e, FSL_RC_RANGE,
                                   "Bind index %d is out of range. Range=(1..%d).",
                                   ndx, st->paramCount);
    }
    /*MARKER(("Binding %s to column #%u\n", cwal_value_type_name(v), ndx));*/
    switch( vtype ){
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_UNDEF:
          rc = fsl_stmt_bind_null(st, ndx);
          break;
      case CWAL_TYPE_BOOL:
          rc = fsl_stmt_bind_int32(st, ndx, cwal_value_get_bool(v));
          break;
      case CWAL_TYPE_INTEGER:
          /* We have no way of knowing which type (32/64-bit) to bind
             here, so we'll guess. */
          rc = fsl_stmt_bind_int64(st, ndx,
                                 (fsl_int64_t)cwal_value_get_integer(v));
          break;
      case CWAL_TYPE_DOUBLE:
          rc = fsl_stmt_bind_double(st, ndx, (fsl_double_t)cwal_value_get_double(v));
          break;
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_STRING: {
          /* FIXME: bind using the fsl_stmt_bind_xxx() APIs!!!
             string/buffer binding is currently missing.
           */
          cwal_size_t slen = 0;
          char const * cstr = cwal_value_get_cstr(v, &slen);
          if(!cstr){
              /* Will only apply to empty buffers (Strings are never
                 NULL). But it's also possible that a buffer with
                 length 0 has a non-NULL memory buffer. So we cannot,
                 without further type inspection, clearly
                 differentiate between a NULL and empty BLOB
                 here. This distinction would seem to be (?) 
                 unimportant for this particular use case, so
                 fixing/improving it can wait.
              */
              rc = fsl_stmt_bind_null(st, ndx);
          }
#if 1
          else if(CWAL_TYPE_BUFFER==vtype){
              rc = fsl_stmt_bind_blob(st, ndx, cstr, (int)slen, 1);
          }
#endif
          else{
              rc = fsl_stmt_bind_text(st, ndx, cstr, (int)slen, 1);
          }
          break;
      }
      default:
          return cwal_exception_setf(e, FSL_RC_TYPE,
                                     "Unhandled data type (%s) for binding "
                                     "column %d.",
                                     cwal_value_type_name(v), ndx);
    }
    if(rc){
        fsl_size_t msgLen = 0;
        char const * msg = NULL;
        int const dbRc = fsl_db_err_get(st->db, &msg, &msgLen);
        rc = cwal_exception_setf(e, FSL_RC_DB,
                                 "Binding column %d failed with "
                                 "sqlite code #%d: %.*s", ndx,
                                 dbRc, (int)msgLen, msg);
    }
    return rc;
}

static int th1ish_fsl_stmt_bind_values_a( cwal_engine * e,
                                          fsl_stmt * st,
                                          cwal_array const * src){
    cwal_size_t const n = cwal_array_length_get(src);
    int i = 0;
    int rc = 0;
    for( ; !rc && (i < n); ++i ){
        rc = th1ish_fsl_stmt_bind( e, st, i+1,
                                   cwal_array_get(src,(cwal_size_t)i) );
    }
    return rc;
}

static int th1ish_fsl_stmt_bind_proxy( cwal_engine * e,
                                       fsl_stmt * st,
                                       int ndx,
                                       cwal_value * bind){
    int rc = 0;
    if(cwal_value_is_array(bind)){
        rc = th1ish_fsl_stmt_bind_values_a(e, st,
                                           cwal_value_get_array(bind));
    }else if(cwal_value_is_object(bind)){
        rc = cwal_exception_setf(e, FSL_RC_NYI,
                                 "TODO: binding by name");
    }else{
        rc = th1ish_fsl_stmt_bind( e, st, ndx, bind);
    }
    return rc;
}

#if 0
static int th1ish_fsl_stmt_bind_from_args( cwal_engine * e,
                                           fsl_stmt *st,
                                           cwal_callback_args const * args,
                                           uint16_t startAtArg ){
    int rc = 0;
    cwal_value * bind = (args->argc < startAtArg)
        ? args->argv[startAtArg]
        : NULL;
    if(!bind) return 0;
    if(cwal_value_is_array(bind)){
        rc = th1ish_fsl_stmt_bind_values_a(e, st,
                                           cwal_value_get_array(bind));
    }else if(cwal_value_is_object(bind)){
        MARKER(("TODO: binding by name"));
        rc = CWAL_RC_ERROR;
    }else if(!cwal_value_is_undef(bind) && (args->argc > startAtArg)){
        int i = startAtArg;
        for( ; !rc && (i < (int)args->argc); ++i ){
            rc = th1ish_fsl_stmt_bind( e, st, i, args->argv[i]);
        }
    }
    return rc;
}
#endif

static int cb_fsl_stmt_bind( cwal_callback_args const * args,
                             cwal_value **rv ){
    cwal_int_t ndx = -999;
    THIS_STMT;
    if(!args->argc){
        return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting (Index[,Value]) arguments.");
    }
    if(cwal_value_is_integer(args->argv[0])){
        ndx = cwal_value_get_integer(args->argv[0]);
        if(1>ndx){
            return cb_toss(args, FSL_RC_RANGE,
                           "SQL bind() indexes are 1-based.");
        }
    }
    return th1ish_fsl_stmt_bind_proxy(args->engine, stmt, (int)ndx,
                                      (-999==ndx)
                                      ? args->argv[0]
                                      : ((args->argc>0)
                                         ? args->argv[1]
                                         : NULL));
}

static int cb_fsl_stmt_get( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_int_t ndx;
    THIS_STMT;
    if(!args->argc || !cwal_value_is_integer(args->argv[0])){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "Expecting Index arguments.");
    }
    else if(!stmt->colCount){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "This is not a fetch-style statement.");
    }
    ndx = cwal_value_get_integer(args->argv[0]);
    if((ndx<0) || (ndx>=stmt->colCount)){
        return cb_toss(args, CWAL_RC_RANGE,
                         "Column index %d is out of range. "
                         "Valid range is (%d..%d).", ndx,
                         0, stmt->colCount-1);
    }
    rc = th1ish_fsl_stmt_to_value( args->engine, stmt,
                                   (uint16_t)ndx, rv );
    if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
        rc = cwal_exception_setf( args->engine, rc,
                                  "Get-by-index failed.");
    }
    return rc;
}

/**
   If !cb, this is a no-op, else if cb is-a Function, it is call()ed,
   if it is a String/Buffer, it is eval'd, else an exception is
   thrown.  self is the 'this' for the call(). Result is placed in
   *rv.  Returns 0 on success.
*/
static int th1ish_fsl_stmt_each_call_proxy( th1ish_interp * ie,
                                            fsl_stmt * st,
                                            cwal_value * cb,
                                            cwal_value * self,
                                            cwal_value **rv ){
    char const * cstr;
    cwal_size_t strLen = 0;
    char const useNewScope = 1;
    if(!cb) return 0;
    else if((cstr = cwal_value_get_cstr(cb,&strLen))){
        /** TODO: tokenize this only the first time. */
        /* Workaround for script source location :/ */
        int rc;
        /**
           Bug Reminder: in stack traces and assertion traces the
           script name/location info is wrong in this case (empty and
           relative to cstr, respectively) because th1ish doesn't have
           a way to map this string back to a source location at this
           point. That needs to be fixed/enabled at the th1ish level.
         */
        rc = th1ish_eval_string(ie, useNewScope, cstr, strLen, rv);
        if(CWAL_RC_RETURN==rc){
            rc = 0;
        }
        return rc;
    }else if(cwal_value_is_function(cb)){
        cwal_function * f = cwal_value_get_function(cb);
        if(useNewScope){
            return cwal_function_call(f, self, rv, 0, NULL );
        }else{
            cwal_scope * sc = cwal_scope_current_get(ie->e);
            return cwal_function_call_in_scope(sc, f,
                                               self, rv,
                                               0, NULL );
        }
    }else {
        return cwal_exception_setf(ie->e, FSL_RC_MISUSE,
                                   "Don't now how to handle callback "
                                   "of type '%s'.",
                                   cwal_value_type_name(cb));
    }
}


/**
   Script usage:

   $db.each object{
     sql: "SQL CODE", // required
     bind: X, // parameter value or array of values to bind
     mode: 0, // 0==rows as objects, else as arrays
     callback: string | function, // called for each row
   }

   Only the 'sql' property is required, and 'bind' must be set
   if the SQL contains any binding placeholders.

   In the scope of the callback, 'this' will resolve to the current
   row data, either in object or array form (depending on the 'mode'
   property). If the callback throws, that exception is propagated.
   If it returns a literal false (as opposed to another falsy value)
   then iteration stops without an error.

   In addition, the following scope-level variables are set:

   - rowNumber: 1-based number of the row (the iteration count).

   - columnNames: array of column names for the result set.

  
   Example callbacks:

   {
      $print rowNumber columnNames this
      $print this.0 + this.1 // array-mode column access
   }

   proc(){
      $print rowNumber columnNames this
      $print this.colA + this.colB // object-mode column access
   }

   Using the string form should be ever so slightly more efficient.
*/
static int cb_fsl_db_each( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    cwal_value const * sql;
    char const * csql;
    cwal_size_t sqlLen = 0;
    fsl_stmt st = fsl_stmt_empty;
    int scode;
    cwal_value * vMode;
    int mode;
    cwal_value * props;
    cwal_value * bind;
    cwal_value * callback;
    cwal_value * cbSelf = NULL;
    cwal_array * cbArray = NULL;
    cwal_value * colNamesV = 0;
    cwal_array * colNames = 0;
    cwal_int_t rowNum;
    cwal_engine * e = args->engine;
    THIS_DB;
    if(!args->argc || !cwal_value_is_object(args->argv[0])){
        return cwal_exception_setf(e, FSL_RC_MISUSE,
                                   "Expecting Object parameter.");
    }
    props = args->argv[0];
    sql = cwal_prop_get(props, "sql", 3 );
    csql = sql ? cwal_value_get_cstr(sql, &sqlLen) : NULL;
    if(!csql){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "Missing 'sql' string/buffer property.");
    }
    vMode = cwal_prop_get( props, "mode", 4 );
    mode = vMode ? cwal_value_get_integer(vMode) : 1;
    bind = cwal_prop_get( props, "bind", 4 );
    callback = cwal_prop_get( props, "callback", 8 );
    if(callback){
        if(!cwal_value_is_function(callback)
           && !cwal_value_is_string(callback)
           && !cwal_value_is_buffer(callback)){
            callback = NULL;
        }
    }
    rc = fsl_db_prepare(db, &st, "%.*s", (fsl_int_t)sqlLen, csql);
    if(rc){
        return cb_toss_db(args, db);
    }
    if(bind && !cwal_value_is_undef(bind)){
        rc = th1ish_fsl_stmt_bind_proxy( e, &st, 1, bind );
        if(rc) goto end;
    }
    if(st.colCount){
        colNamesV = th1ish_fsl_stmt_col_names(e,&st);
        if(!colNamesV){
            rc = CWAL_RC_OOM;
            goto end;
        }
        rc = th1ish_var_set( ie, 0, "columnNames", 11, colNamesV );
        if(rc){
            cwal_value_unref(colNamesV);
            colNamesV = NULL;
            goto end;
        }
        colNames = cwal_value_get_array(colNamesV);
        assert(cwal_array_length_get(colNames) == st.colCount);
    }
    rc = th1ish_var_set( ie, 0, "columnCount", 11,
                         cwal_new_integer(e, (cwal_int_t)st.colCount) );
    if(rc) goto end;
    
    /*
      Step through each row and call the given callback function/string...
    */
    for( rowNum = 1;
         FSL_RC_STEP_ROW == (scode = fsl_stmt_step( &st ));
         ++rowNum){
        if(callback && !cbSelf){
            /** Init callback info if needed */
            cbArray = (0==mode)
                ? NULL
                : cwal_new_array(e);
            cbSelf = cbArray
                ? cwal_array_value(cbArray)
                : cwal_new_object_value(e);
            if(cbSelf){
                rc = th1ish_var_set(ie, 0, "this", 4, cbSelf);
                if(rc){
                    cwal_value_unref(cbSelf);
                    goto end;
                }
            }
        }
        if(cbSelf){
            /**
               Set up this.XXX to each column's value, where
               XXX is either the column index (for array mode)
               or column name (for object mode).
            */
            cwal_value * frv = 0;
            int i = 0;
            for( ; !rc && (i < st.colCount); ++i ){
                cwal_value * cv;
                cv = NULL;
                rc = th1ish_fsl_stmt_to_value(e, &st, i, &cv);
                if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
                    rc = cwal_exception_setf(e, rc,
                                             "Conversion from db column to "
                                             "value failed with code "
                                             "%d (%s).",
                                             rc, fsl_rc_cstr(rc));
                }
                if(cbArray) rc = cwal_array_set( cbArray, i, cv);
                else{
                    cwal_value * key;
                    assert(colNames);
                    key = cwal_array_get(colNames, i);
                    assert(key);
                    rc = cwal_prop_set_v( cbSelf, key, cv);
                }
                if(rc) cwal_value_unref(cv);
            }
            if(rc) goto end;
            rc = th1ish_var_set( ie, 0, "rowNumber", 9,
                                 cwal_new_integer(e, rowNum) );
            if(rc) goto end;
            rc = th1ish_fsl_stmt_each_call_proxy(ie, &st,
                                                 callback, cbSelf,
                                                 &frv);
            if(rc) goto end;
            else if(frv == cwal_value_false()/*yes, a ptr comparison*/){
                /* If the function returns literal false, stop
                   looping without an error. */
                goto end;
            }
        }
    }
    if(FSL_RC_STEP_ERROR==scode){
        rc = cwal_exception_setf(e, FSL_RC_DB,
                                 "Stepping cursor failed.");
    }
    end:
    if(st.stmt){
        fsl_stmt_finalize( &st );
    }
    return rc;
}


cwal_value * fsl_db_prototype( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    char const * pKey = "Fossil.Db";
    proto = th1ish_prototype_get_by_name(ie, pKey);
    if(proto) return proto;
    /* cwal_value * fv; */
    assert(ie && ie->e);
    proto = cwal_new_object_value(ie->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = th1ish_prototype_store( ie, pKey, proto );
    if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    v = cwal_new_string_value(ie->e, "Db", 2);
    SET("__typename");

    FUNC2("close", cb_fsl_db_finalize);
    FUNC2("each", cb_fsl_db_each);
    FUNC2("exec", cb_fsl_db_exec);
    FUNC2("execMulti", cb_fsl_db_exec_multi);
    FUNC2("getFilename", cb_fsl_db_filename);
    FUNC2("getName", cb_fsl_db_name);
    FUNC2("open", cb_fsl_db_ctor);
    FUNC2("lastInsertId", cb_fsl_db_last_insert_id);
    FUNC2("prepare", cb_fsl_db_prepare);

#undef SET
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}


cwal_value * fsl_stmt_prototype( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    char const * pKey = "Fossil.Stmt";
    proto = th1ish_prototype_get_by_name(ie, pKey);
    if(proto) return proto;
    /* cwal_value * fv; */
    assert(ie && ie->e);
    proto = cwal_new_object_value(ie->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = th1ish_prototype_store( ie, pKey, proto );
    if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    v = cwal_new_string_value(ie->e, "Stmt", 4);
    SET("__typename");

    /* FUNC2("columnCount", cb_fsl_stmt_col_count); */
    /* FUNC2("parameterCount", cb_fsl_stmt_param_count); */
    /* TODO: FUNC2("each", cb_fsl_db_each); */
    FUNC2("bind", cb_fsl_stmt_bind);
    FUNC2("finalize", cb_fsl_stmt_finalize);
    FUNC2("get", cb_fsl_stmt_get);
    FUNC2("reset", cb_fsl_stmt_reset);
    FUNC2("rowToArray", cb_fsl_stmt_row_to_array);
    FUNC2("rowToObject", cb_fsl_stmt_row_to_object);
    FUNC2("step", cb_fsl_stmt_step);
    FUNC2("stepArray", cb_fsl_stmt_step_array);
    FUNC2("stepObject", cb_fsl_stmt_step_object);
    
#undef SET
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}

/**
   Script usage:

   const size = Fossil.file.size(filename)
*/
static int cb_fs_file_size( cwal_callback_args const * args,
                            cwal_value **rv ){
    char const * fn;
    fsl_size_t sz;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    sz = fsl_file_size(fn);
    if((fsl_size_t)-1 == sz){
        return cb_toss(args, FSL_RC_IO,
                       "Could not stat file: %s", fn);
    }
    *rv = (sz > (fsl_int64_t)CWAL_INT_T_MAX)
        ? cwal_new_double(args->engine, (cwal_double_t)sz)
        : cwal_new_integer(args->engine, (cwal_int_t)sz)
        ;
    return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Script usage:

   const tm = Fossil.file.unlink(filename)
*/
static int cb_fs_file_unlink( cwal_callback_args const * args,
                            cwal_value **rv ){
    char const * fn;
    int rc;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    rc = fsl_file_unlink( fn );
    return rc
        ? cb_toss(args, rc, "Got %s error while "
                  "trying to remove file: %s",
                  fsl_rc_cstr(rc))
        : 0;
}

static int cb_fs_file_chdir( cwal_callback_args const * args,
                             cwal_value **rv ){
    char const * fn;
    int rc;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    rc = fsl_chdir( fn, 0 );
    return rc
        ? cb_toss(args, rc, "Got %s error while "
                  "trying to remove file: %s",
                  fsl_rc_cstr(rc))
        : 0;
}

static int cb_fs_file_cwd( cwal_callback_args const * args,
                           cwal_value **rv ){
    int rc;
    enum { BufSize = 512 * 5 };
    fsl_size_t nLen = 0;
    char buf[BufSize] = {0};

    rc = fsl_getcwd(buf, BufSize, &nLen);
    if(rc) return cb_toss(args, rc, "Got %s error while "
                          "trying to getcwd()",
                          fsl_rc_cstr(rc));
    *rv = cwal_new_string_value(args->engine, buf, (cwal_size_t)nLen);
    return *rv ? 0 : CWAL_RC_OOM;
}


/**
   Script binding to fsl_stat(). Returns undefined if
   stat fails, else in object:

   
   {
     name: args->argv[0],
     mtime: unix epoch,
     ctime: unix epoch,
     size: in bytes,
     type: 'file', 'dir', 'link', or 'unknown'
   }
 */
static int cb_fs_file_stat( cwal_callback_args const * args,
                            cwal_value **rv ){
    char const * fn;
    th1ish_interp * ie = th1ish_args_interp(args);
    assert(ie);
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                           "Expecting non-empty string "
                           "argument.");
    else{
        fsl_fstat fst = fsl_fstat_empty;
        int rc;
        cwal_engine * e = args->engine;
        rc = fsl_stat( fn, &fst, 1 );
        if(rc){
            *rv = cwal_value_undefined();
            rc = 0;
        }else{
            char const * typeName;
            cwal_value * obj = th1ish_new_object(ie);
            switch(fst.type){
              case FSL_FSTAT_TYPE_DIR: typeName = "dir"; break;
              case FSL_FSTAT_TYPE_FILE: typeName = "file"; break;
              case FSL_FSTAT_TYPE_LINK: typeName = "link"; break;
              case FSL_FSTAT_TYPE_UNKNOWN:
              default:
                  typeName = "unknown";
                  break;
            }
            cwal_prop_set(obj, "type", 4, cwal_new_string_value(e, typeName,
                                                                fsl_strlen(typeName)));
            cwal_prop_set(obj, "name", 4, args->argv[0]);
            cwal_prop_set(obj, "size", 4, cwal_new_integer(e, (cwal_int_t)fst.size));
            cwal_prop_set(obj, "mtime", 5, cwal_new_integer(e, (cwal_int_t)fst.mtime));
            cwal_prop_set(obj, "ctime", 5, cwal_new_integer(e, (cwal_int_t)fst.ctime));
            *rv = obj;
            /* See how little code it is if we ignore malloc errors? */
        }
        return rc;
    }
}


/**
   Script usage:

   const tm = Fossil.file.mtime(filename)
*/
static int cb_fs_file_mtime( cwal_callback_args const * args,
                            cwal_value **rv ){
    char const * fn;
    fsl_time_t sz;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    sz = fsl_file_mtime(fn);
    if(-1 == sz){
            return cb_toss(args, FSL_RC_IO,
                           "Could not stat file: %s", fn);
    }
    *rv = (sz > (fsl_time_t)CWAL_INT_T_MAX)
        ? cwal_new_double(args->engine, (double)sz)
        : cwal_new_integer(args->engine, (cwal_int_t)sz)
        ;
    return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Script usage:

   const bool = Fossil.file.isFile(filename)
*/
static int cb_fs_file_isfile( cwal_callback_args const * args,
                              cwal_value **rv ){
    char const * fn;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    *rv = fsl_is_file(fn)
        ? cwal_value_true()
        : cwal_value_false();
    return 0;
}

/**
   Script usage:

   const bool = Fossil.file.isDir(filename)
*/
static int cb_fs_file_isdir( cwal_callback_args const * args,
                             cwal_value **rv ){
    char const * fn;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                           "Expecting non-empty string "
                           "argument.");
    *rv = (fsl_dir_check(fn) > 0)
        ? cwal_value_true()
        : cwal_value_false();
    return 0;
}

/**
   Script usage:

   const string = Fossil.file.canonicalName(filename)
*/
static int cb_fs_file_canonical( cwal_callback_args const * args,
                                 cwal_value **rv ){
    int rc;
    char const * fn;
    fsl_buffer buf = fsl_buffer_empty;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    rc = fsl_file_canonical_name( fn, &buf, 0 );
    if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
    else if(!rc){
        *rv = cwal_new_zstring_value(args->engine,
                                     (char *)buf.mem,
                                     (cwal_size_t)buf.used);
        rc = *rv ? 0 : CWAL_RC_OOM;
        if(rc){
            fsl_buffer_reserve(&buf, 0);
        }
        /*else transfer ownership of buf.mem to the new z-string*/
    }
    return rc;
}

/**
   Script binding to fsl_file_dirpart():

   var x = thisFunc(path, leaveSlash=true)
 */
static int cb_fs_file_dirpart( cwal_callback_args const * args,
                                 cwal_value **rv ){
    int rc;
    char const * fn;
    fsl_buffer buf = fsl_buffer_empty;
    cwal_size_t fnLen = 0;
    th1ish_interp * ie = th1ish_args_interp(args);
    char leaveSlash;
    assert(ie);
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], &fnLen)
        : NULL;
    if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                   "Expecting non-empty string "
                                   "argument.");
    leaveSlash = args->argc>1
        ? cwal_value_get_bool(args->argv[1])
        : 1;

    rc = fsl_file_dirpart(fn, (fsl_size_t)fnLen, &buf, leaveSlash);
    if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
    else if(!rc){
        if(buf.used){
            *rv = cwal_new_zstring_value(args->engine,
                                         (char *)buf.mem,
                                         (cwal_size_t)buf.used);
            rc = *rv ? 0 : CWAL_RC_OOM;
            if(rc){
                fsl_buffer_clear(&buf);
            }/*else transfer ownership of buf.mem to the new z-string*/
        }else{
            fsl_buffer_clear(&buf);
            *rv = cwal_value_null();
        }
    }
    return rc;
}


/**
   A fsl_output_f() impl which expects state to be a (cwal_engine*e). It
   simply forwards (state, src,n) to cwal_output().
*/
static int fsl_output_f_cwal_output( void * state, void const * src, fsl_size_t n ){
    cwal_engine * e = (cwal_engine *)state;
    return (src && n) ? cwal_output(e, src, (cwal_size_t)n) : 0;
}


/**
   Streams a file's contents directly to the script engine's output
   channel, without (unless the file is small) reading the whole file
   into a buffer first.

   Script usage:

   Fossil.file.passthrough(filename)
*/
static int cb_fs_file_passthrough( cwal_callback_args const * args,
                                   cwal_value **rv ){
    char const * fn;
    cwal_size_t len;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], &len)
        : NULL;
    if(!fn){
        return cb_toss(args, FSL_RC_MISUSE,
                       "Expecting non-empty string "
                       "argument.");
    }else if(0!=fsl_file_access(fn, 0)){
        return cb_toss(args, FSL_RC_NOT_FOUND,
                       "Cannot find file: %s", fn);
    }else{
        FILE * fi = fsl_fopen(fn, "r");
        int rc;
        if(!fi){
            rc = cb_toss(args, FSL_RC_IO,
                         "Could not open file for reading: %s",
                         fn);
        }else{
            rc = fsl_stream( fsl_input_f_FILE, fi,
                             fsl_output_f_cwal_output,
                             args->engine );
            fsl_fclose(fi);
        }
        return rc ? CWAL_RC_IO : 0;
    }
}


/*
** Combined impl for Buffer.compress() and Buffer.uncompress().  If
** doCompress is true, it is the former, else the latter.
*/
static int cb_buffer_press( cwal_callback_args const * args,
                            cwal_value **rv, char doCompress ){
    int rc;
    cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
    fsl_buffer tmp = fsl_buffer_empty;
    if(!buf){
        return cb_toss(args, CWAL_RC_TYPE,
                         "'this' is-not-a Buffer.");
    }
    cwal_buffer_to_fsl_buffer( buf, &tmp );
    if(doCompress && fsl_buffer_is_compressed(&tmp)){
        rc = 0;
    }else if(!doCompress && !fsl_buffer_is_compressed(&tmp)){
        rc = 0;
    }else{
        rc = doCompress
            ? fsl_buffer_compress( &tmp, &tmp )
            : fsl_buffer_uncompress( &tmp, &tmp );
        fsl_buffer_to_cwal_buffer( &tmp, buf );
        if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
        else if(rc){
            rc = cb_toss(args, rc,
                         "Buffer %s failed with code %d (%s).",
                         doCompress ? "compression" : "decompression",
                         rc, fsl_rc_cstr(rc));
        }
    }
    return rc;
}

/**
   Script usage:

   bufferInstance.compress()
*/
static int cb_buffer_compress( cwal_callback_args const * args,
                               cwal_value **rv ){
    return cb_buffer_press(args, rv, 1);
}

/**
   Script usage:

   bufferInstance.uncompress()
*/
static int cb_buffer_uncompress( cwal_callback_args const * args,
                               cwal_value **rv ){
    return cb_buffer_press(args, rv, 0);
}

/**
   Script usage:

   const isCompressed = bufferInstance.isCompressed()
*/
static int cb_buffer_is_compressed( cwal_callback_args const * args,
                                    cwal_value **rv ){
    char rc = 0;
    fsl_buffer fb = fsl_buffer_empty;
    cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
    if(!buf){
        return cb_toss(args, CWAL_RC_TYPE,
                         "'this' is-not-a Buffer.");
    }
    cwal_buffer_to_fsl_buffer(buf, &fb);
    rc = fsl_buffer_is_compressed(&fb);
    *rv = cwal_new_bool( rc );
    return 0;
}

/**
   Script usage:

   const sha1 = bufferInstance.sha1()
*/
static int cb_buffer_sha1_self( cwal_callback_args const * args,
                                    cwal_value **rv ){
    cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
    char * sha;
    if(!buf){
        return cb_toss(args, CWAL_RC_TYPE,
                         "'this' is-not-a Buffer.");
    }
    sha = fsl_sha1sum_cstr( (char const*)buf->mem, (int)buf->used);
    if(!sha) return CWAL_RC_OOM;
    assert(sha[0] && sha[FSL_UUID_STRLEN-1] && !sha[FSL_UUID_STRLEN]);
    *rv = cwal_new_zstring_value(args->engine, sha, FSL_UUID_STRLEN);
    if(*rv) return 0;
    else{
        fsl_free(sha);
        return CWAL_RC_OOM;
    }
}

/**
   Script usage:

   const md5 = bufferInstance.md5()
*/
static int cb_buffer_md5_self( cwal_callback_args const * args,
                                    cwal_value **rv ){
    cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
    char * hash;
    if(!buf){
        return cb_toss(args, CWAL_RC_TYPE,
                         "'this' is-not-a Buffer.");
    }
    hash = fsl_md5sum_cstr( (char const*)buf->mem, (int)buf->used);
    if(!hash) return CWAL_RC_OOM;
    assert(hash[0] && hash[FSL_MD5_STRLEN-1] && !hash[FSL_MD5_STRLEN]);
    *rv = cwal_new_zstring_value(args->engine, hash, FSL_MD5_STRLEN);
    if(*rv) return 0;
    else{
        fsl_free(hash);
        return CWAL_RC_OOM;
    }
}

/**
   Script usage:

   bool Fossil.file.access(filename [, bool mustBeADir=false)
 */
static int cb_fs_file_access( cwal_callback_args const * args,
                              cwal_value **rv ){
    char const * fn;
    char checkWrite;
    fn = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting non-empty string "
                             "argument.");
    checkWrite = (args->argc>1)
        ? cwal_value_get_bool(args->argv[1])
        : 0;

    *rv = fsl_file_access( fn, checkWrite ? W_OK : F_OK )
        /*0==OK*/
        ? cwal_value_false()
        : cwal_value_true();
    return 0;
}

static int cb_fsl_delta_create( cwal_callback_args const * args,
                                cwal_value **rv ){
    char const * s1 = NULL;
    char const * s2 = NULL;
    cwal_size_t len1, len2;
    fsl_size_t outLen = 0;
    cwal_buffer * cb;
    int rc;
    if(args->argc>1){
        s1 = cwal_value_get_cstr(args->argv[0], &len1);
        s2 = cwal_value_get_cstr(args->argv[1], &len2);
    }
    if(!s1 || !s2){
        return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting two non-empty string/buffer "
                         "arguments.");
    }
    cb = cwal_new_buffer(args->engine, len2+61);
    if(!cb) return CWAL_RC_OOM;
    assert(cb->capacity > (len2+60));
    rc = fsl_delta_create( (unsigned char const *)s1, (fsl_size_t)len1,
                           (unsigned char const *)s2, (fsl_size_t)len2,
                           cb->mem, &outLen);
    if(!rc){
        /* Resize the buffer to fit. cwal_buffer is missing
           this function as of this writing, so we proxy it
           through a fsl_buffer.
        */
        fsl_buffer rsz = fsl_buffer_empty;
        cwal_buffer_to_fsl_buffer(cb, &rsz);
        rc = fsl_buffer_resize(&rsz, outLen);
        rsz.used = outLen;
        fsl_buffer_to_cwal_buffer(&rsz, cb);
        assert(0==cb->mem[cb->used]);
    }

    if(rc){
        cwal_value_unref(cwal_buffer_value(cb));
    }else{
        *rv = cwal_buffer_value(cb);
    }
    return rc;
}

static int cb_fsl_delta_applied_len( cwal_callback_args const * args,
                                     cwal_value **rv ){
    char const * src;
    cwal_size_t srcLen;
    fsl_size_t appliedLen = 0;
    int rc;
    src = (args->argc>0)
        ? cwal_value_get_cstr(args->argv[0], &srcLen)
        : NULL;
    if(!src){
        return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting one delta string "
                         "argument.");
    }
    rc = fsl_delta_applied_size((unsigned char const *)src,
                                (fsl_size_t)srcLen, &appliedLen);
    if(rc){
        return cb_toss(args, rc,
                         "Input does not appear to be a "
                         "delta. Error #%d (%s).",
                         rc, fsl_rc_cstr(rc));
    }else{
        *rv = cwal_new_integer(args->engine,
                               (cwal_int_t)appliedLen);
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

static int cb_fsl_delta_apply( cwal_callback_args const * args,
                                cwal_value **rv ){
    char const * s1 = NULL;
    char const * s2 = NULL;
    char const * src;
    char const * delta;
    cwal_size_t len1, len2, srcLen, dLen;
    fsl_size_t appliedLen = 0;
    cwal_buffer * cb;
    int rc;
    if(args->argc>1){
        s1 = cwal_value_get_cstr(args->argv[0], &len1);
        s2 = cwal_value_get_cstr(args->argv[1], &len2);
    }
    if(!s1 || !s2){
        return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting two non-empty string/buffer "
                         "arguments.");
    }
    rc = fsl_delta_applied_size((unsigned char const *)s2,
                                (fsl_size_t)len2, &appliedLen);
    if(!rc){
        src = s1;
        srcLen = len1;
        delta = s2;
        dLen = len2;
    }else{ /* Check if the user perhaps swapped the args. */
        rc = fsl_delta_applied_size((unsigned char const *)s1,
                                    (fsl_size_t)len1, &appliedLen);
        if(rc){
            return cb_toss(args, FSL_RC_MISUSE,
                             "Expecting a delta string/buffer as one "
                             "of the first two arguments.");
        }
        src = s2;
        srcLen = len2;
        delta = s1;
        dLen = len1;
    }
    cb = cwal_new_buffer(args->engine, appliedLen+1);
    if(!cb) return CWAL_RC_OOM;
    assert(cb->capacity > appliedLen);
    rc = fsl_delta_apply( (unsigned char const *)src, (fsl_size_t)srcLen,
                           (unsigned char const *)delta, (fsl_size_t)dLen,
                           cb->mem);
    if(!rc){
        assert(0==cb->mem[appliedLen]);
        if(cb->capacity > (cb->used * 4 / 3)){
            fsl_buffer rsz = fsl_buffer_empty;
            cwal_buffer_to_fsl_buffer(cb, &rsz);
            rc = fsl_buffer_resize(&rsz, appliedLen);
            rsz.used = appliedLen;
            fsl_buffer_to_cwal_buffer(&rsz, cb);
            assert(0==cb->mem[cb->used]);
        }
    }else{
        rc = cb_toss(args, rc,
                       "Application of delta failed with "
                       "code #%d (%s).", rc,
                       fsl_rc_cstr(rc));
    }
    if(rc){
        cwal_value_unref(cwal_buffer_value(cb));
    }else{
        *rv = cwal_buffer_value(cb);
    }
    return rc;
}

static int fsl_add_file_funcs( th1ish_interp * ie, cwal_value * ns ){
    cwal_value * func;
    cwal_value * v;
    int rc;
    func = cwal_new_object_value(ie->e);
    if(!func) return CWAL_RC_OOM;

    rc = cwal_prop_set(ns, "file", 4, func);
    if(rc){
        cwal_value_unref(func);
        return rc;
    }
    
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( func, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    FUNC2("canonicalName", cb_fs_file_canonical);
    FUNC2("chdir", cb_fs_file_chdir);
    FUNC2("currentDir", cb_fs_file_cwd);
    FUNC2("dirPart", cb_fs_file_dirpart);
    FUNC2("isAccessible", cb_fs_file_access);
    FUNC2("isDir", cb_fs_file_isdir);
    FUNC2("isFile", cb_fs_file_isfile);
    FUNC2("mtime", cb_fs_file_mtime);
    FUNC2("passthrough", cb_fs_file_passthrough);
    FUNC2("size", cb_fs_file_size);
    FUNC2("stat", cb_fs_file_stat);
    FUNC2("unlink", cb_fs_file_unlink);
    
#undef SET
#undef FUNC2
#undef CHECKV
    end:

    return rc;
}

static int fsl_add_delta_funcs( th1ish_interp * ie, cwal_value * ns ){
    cwal_value * func;
    cwal_value * v;
    int rc;
    func = cwal_new_object_value(ie->e);
    if(!func) return CWAL_RC_OOM;

    rc = cwal_prop_set(ns, "delta", 5, func);
    if(rc){
        cwal_value_unref(func);
        return rc;
    }
    
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( func, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    FUNC2("appliedLength", cb_fsl_delta_applied_len);
    FUNC2("apply", cb_fsl_delta_apply);
    FUNC2("create", cb_fsl_delta_create);
    
#undef SET
#undef FUNC2
#undef CHECKV
    end:

    return rc;
}

static int fsl_add_time_funcs( th1ish_interp * ie, cwal_value * ns ){
    cwal_value * func;
    cwal_value * v;
    int rc;
    func = cwal_new_object_value(ie->e);
    if(!func) return CWAL_RC_OOM;

    rc = cwal_prop_set(ns, "time", 4, func);
    if(rc){
        cwal_value_unref(func);
        return rc;
    }
    
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( func, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    FUNC2("julianToHuman", cb_julian_to_human);
    FUNC2("julianToISO8601", cb_julian_to_iso8601);
    FUNC2("julianToUnix", cb_julian_to_unix);
    FUNC2("now", cb_time_now);
    FUNC2("unixToJulian", cb_unix_to_julian);
    
#undef SET
#undef FUNC2
#undef CHECKV
    end:

    return rc;
}


static int fsl_add_rcs( th1ish_interp * ie, cwal_value * ns ){
    cwal_value * func;
    cwal_value * v;
    int rc;
    func = cwal_new_object_value(ie->e);
    if(!func) return CWAL_RC_OOM;

    rc = cwal_prop_set(ns, "rc", 2, func);
    if(rc){
        cwal_value_unref(func);
        return rc;
    }
    
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( func, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end

#define RC(N) v = cwal_new_integer(ie->e, FSL_RC_##N); \
    SET(#N)

    RC(ACCESS);
    RC(ALREADY_EXISTS);
    RC(BREAK);
    RC(CHECKSUM_MISMATCH);
    RC(CONSISTENCY);
    RC(DB);
    RC(DELTA_INVALID_OPERATOR);
    RC(DELTA_INVALID_SEPARATOR);
    RC(DELTA_INVALID_SIZE);
    RC(DELTA_INVALID_TERMINATOR);
    RC(ERROR);
    RC(IO);
    RC(MISUSE);
    RC(NOT_A_CHECKOUT);
    RC(NOT_A_REPO);
    RC(NOT_FOUND);
    RC(NYI);
    RC(OK);
    RC(OOM);
    RC(RANGE);
    RC(REPO_MISMATCH);
    RC(REPO_NEEDS_REBUILD);
    RC(REPO_VERSION);
    RC(SIZE_MISMATCH);
    RC(STEP_DONE);
    RC(STEP_ERROR);
    RC(STEP_ROW);
    RC(TYPE);
    
#undef RC
#undef SET
#undef CHECKV
    end:

    return rc;
}


cwal_value * fsl_cx_prototype( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    char const * pKey = "Fossil.Context";
    proto = th1ish_prototype_get_by_name(ie, pKey);
    if(proto) return proto;
    /* cwal_value * fv; */
    assert(ie && ie->e);
    proto = th1ish_new_function2( ie, cb_fsl_cx_ctor );
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = th1ish_prototype_store( ie, pKey, proto );
    if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    v = cwal_new_string_value(ie->e, "FossilContext", 13);
    SET("__typename");
    
    FUNC2("artifactDiff", cb_fsl_cx_adiff);
    FUNC2("closeCheckout", cb_fsl_checkout_close);
    FUNC2("closeRepo", cb_fsl_repo_close);
    FUNC2("finalize", cb_fsl_cx_finalize);
    FUNC2("loadBlob", cb_fsl_cx_content_get);
    FUNC2("loadManifest",cb_fsl_cx_deck_load);
    FUNC2("openCheckout", cb_fsl_checkout_open_dir);
    FUNC2("openDb", cb_fsl_db_ctor);
    FUNC2("openRepo", cb_fsl_repo_open);
    FUNC2("symToRid", cb_fsl_cx_sym2rid);
    FUNC2("symToUuid", cb_fsl_cx_sym2uuid);
    /* FUNC2("traceSql", cb_fsl_cx_trace_sql); */
    
#undef SET
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}

#define THIS_ZIP th1ish_interp * ie = th1ish_args_interp(args); \
    cwal_native * nat = cwal_value_native_part(args->engine, args->self); \
    cwal_value * natV = nat ? cwal_native_value(nat) : NULL;                   \
    fsl_zip_writer * z = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_zip_writer) ) : NULL;         \
    assert(ie); \
    if(!z){ return th1ish_toss(ie, FSL_RC_TYPE, \
                               "'this' is not (or is no longer) "   \
                               "a Zip instance."); } (void)0

static int cb_fsl_zip_finalize( cwal_callback_args const * args,
                                 cwal_value **rv ){
    THIS_ZIP;
    cwal_native_clear( nat, 1 );
    return 0;
}

static int cb_fsl_zip_root_set( cwal_callback_args const * args,
                                cwal_value **rv ){
    char const * root;
    int rc;
    fsl_time_t mtime;
    THIS_ZIP;
    root = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!args->argc){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "Expecting a string argument.");
    }
    /* If root dir exists, use its timestamp, otherwise use the
       current time. Interestingly... we can easily end up with
       directories with older times newer than their files.
    */
    mtime = fsl_file_mtime(root);
    if(mtime<0) mtime = (fsl_time_t)time(NULL);
    fsl_zip_timestamp_set_unix( z, mtime );
    rc = fsl_zip_root_set( z, root );
    if(rc){
        assert(FSL_RC_OOM==rc);
        rc = CWAL_RC_OOM;
    }
    return rc;
}

/**
   zip.addBuffer(name, content)
*/
static int cb_fsl_zip_buffer_add( cwal_callback_args const * args,
                                  cwal_value **rv ){
    char const * fn;
    char const * content;
    fsl_size_t clen = 0;
    int rc;
    THIS_ZIP;
    if(args->argc>1){
        cwal_size_t clen2 = 0;
        fn = cwal_value_get_cstr(args->argv[0], NULL);
        content = cwal_value_get_cstr(args->argv[1], &clen2);
        clen = (fsl_size_t)clen2;
    }
    if(!fn || !*fn || (!content && clen)){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "Expecting (string name, string|Buffer content) arguments.");
    }
    fsl_zip_timestamp_set_unix( z, (fsl_time_t)time(NULL) );
    {
        fsl_buffer tmp = fsl_buffer_empty;
        tmp.mem = (unsigned char *)content /* evil! */;
        tmp.used = tmp.capacity = clen;
        rc = fsl_zip_file_add( z, fn, &tmp, FSL_FS_PERM_REGULAR );
        if(rc){
            assert(FSL_RC_OOM==rc);
            rc = CWAL_RC_OOM;
        }
        assert(tmp.mem == (unsigned const char *)content);
        assert(tmp.used == tmp.capacity);
        assert(tmp.used == clen);
    }
    return rc;
}

/**
   zip.addFile(fileName)
*/
static int cb_fsl_zip_file_add( cwal_callback_args const * args,
                                cwal_value **rv ){
    char const * fn;
    fsl_time_t ftime;
    int rc;
    fsl_buffer content = fsl_buffer_empty;
    THIS_ZIP;
    if(args->argc>0){
        fn = cwal_value_get_cstr(args->argv[0], NULL);
    }
    if(!fn || !*fn){
        return th1ish_toss(ie, FSL_RC_MISUSE,
                           "Expecting (string filename) argument.");
    }
    ftime = fsl_file_mtime( fn );
    if(ftime < 0){
        return th1ish_toss(ie, FSL_RC_IO,
                           "Could not stat() file: %s",
                           fn);
    }

    rc = fsl_buffer_fill_from_filename(&content, fn);
    if(rc){
        fsl_buffer_clear(&content);
        return (FSL_RC_OOM==rc)
            ? CWAL_RC_OOM
            : CWAL_RC_IO;
    }
    fsl_zip_timestamp_set_unix( z, ftime );
    rc = fsl_zip_file_add( z, fn, &content, FSL_FS_PERM_REGULAR );
    fsl_buffer_clear(&content);
    if(rc){
        assert(FSL_RC_OOM==rc);
        rc = CWAL_RC_OOM;
    }
    return rc;
}

/**
   Script usage:

   zip.finish(bool false) discards the zip contents.

   var buf = zip.finish(bool true || no args) returns ZIP as a buffer.

   zip.finish(string) saves the ZIP to a file.

   ZIP is reset in all cases, ready for creating a new archive.
*/
static int cb_fsl_zip_finish( cwal_callback_args const * args,
                              cwal_value **rv ){
    char const * fn;
    cwal_size_t nFn = 0;
    cwal_value * bV = NULL;
    cwal_buffer * cbuf = NULL;
    fsl_buffer zipBody = fsl_buffer_empty;
    THIS_ZIP;
    if(args->argc>0){
        cwal_value const * v = args->argv[0];
        if(cwal_value_is_bool(v)){
            if(!cwal_value_get_bool(v)){
                fsl_zip_finalize(z);
                return 0;
            }
            /* else fall through to the return-a-buffer case. */
        }else{
            fn = cwal_value_get_cstr(v, &nFn);
        }
    }

    fsl_zip_end_take(z, &zipBody);
    if(!zipBody.used){
        fsl_buffer_clear(&zipBody/* might be "" */);
        return th1ish_toss(th1ish_args_interp(args),
                           FSL_RC_RANGE,
                           "Cowardly refusing to write "
                           "empty ZIP buffer");
    }

    if(!fn || !nFn){
        /* Return a new buffer... */
        bV = cwal_new_buffer_value(args->engine, 0);
        if(!bV){
            fsl_buffer_clear(&zipBody);
            return CWAL_RC_OOM;
        }
        cbuf = cwal_value_buffer_part(args->engine, bV);
        assert(cbuf);              
        fsl_buffer_to_cwal_buffer( &zipBody, cbuf )
            /* This is ONLY valid if cwal and fsl both have the
               same underlying allocator, otherwise we need
               to create a copy from fsl to cwal.
            */;
        assert(!zipBody.mem);
        assert(!zipBody.used);
        assert(!zipBody.capacity);
        *rv = bV;
        return 0;
    }else{
        /* Save it to a file... */
        int rc = fsl_buffer_to_filename(&zipBody, fn);
        if(rc){
            rc = th1ish_toss(th1ish_args_interp(args),
                             rc, "Error %s writing ZIP buffer "
                             "to file: %s",
                             fsl_rc_cstr(rc), fn);
        }else{
            *rv = cwal_value_undefined();
            rc = 0;
        }
        fsl_buffer_clear(&zipBody);
        return rc;
    }
}

int cb_fsl_zip_ctor( cwal_callback_args const * args,
                     cwal_value **rv ){
    fsl_zip_writer * z = NULL;
    cwal_value * v;
    th1ish_interp * ie = th1ish_args_interp(args);
    assert(ie);

    z = fsl_malloc( sizeof(fsl_zip_writer) );
    if(!z) return CWAL_RC_OOM;
    *z = fsl_zip_writer_empty;
    v = cwal_new_native_value(args->engine, z,
                              fsl_finalizer_f_fsl_zip,
                              FSL_TYPEID(fsl_zip_writer));
    if(!v){
        fsl_finalizer_f_fsl_zip(ie->e, z);
        return CWAL_RC_OOM;
    }
    cwal_value_prototype_set( v, fsl_zip_prototype(ie) );
    *rv  = v;
    return 0;
}

cwal_value * fsl_zip_prototype( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    char const * pKey = "Fossil.Zip";
    proto = th1ish_prototype_get_by_name(ie, pKey);
    if(proto) return proto;
    assert(ie && ie->e);
    proto = th1ish_new_function2( ie, cb_fsl_zip_ctor );
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = th1ish_prototype_store( ie, pKey, proto );
    if(rc) goto end;
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( proto, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    v = cwal_new_string_value(ie->e, "Zip", 3);
    SET("__typename");

    FUNC2("addBuffer", cb_fsl_zip_buffer_add);
    FUNC2("addFile", cb_fsl_zip_file_add);
    FUNC2("setRoot", cb_fsl_zip_root_set);
    FUNC2("finish", cb_fsl_zip_finish);
    FUNC2("finalize", cb_fsl_zip_finalize);
    
#undef SET
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}


/**
    We copy fsl_lib_configurable.allocator as a base allocator.
*/
static fsl_allocator fslAllocOrig;

/**
    Proxies fslAllocOrig() and abort()s on OOM conditions.
 */
static void * fsl_realloc_f_failing(void * state, void * mem, fsl_size_t n){
  void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n);
  if(n && !rv){
    fprintf(stderr,"\nfcli: fossi1ish IS OUT OF MEMORY\n");
    fflush(stderr);
    abort();
  }
  return rv;
}

/**
   Retuns the number of milliseconds since th1ish_shell_extend()
   was run. This is our approximate script runtime.
*/
static int cb_run_timer_fetch( cwal_callback_args const * args,
                               cwal_value **rv ){
    fsl_uint64_t t = fsl_timer_fetch(&RunTimer);
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)(t/1000));
    return *rv ? 0 : CWAL_RC_OOM;
}


/**
    Replacement for fsl_lib_configurable.allocator which abort()s on OOM.
    Why? Because fossil(1) has shown how much that can simplify error
    checking in an allocates-often API.
 */
static const fsl_allocator fcli_allocator = {
fsl_realloc_f_failing,
NULL/*state*/
};

/**
   Called by shell.c to plug in the th1ish bindings.

   Maintenance reminder: must return a CWAL_RC_xxx code, not
   FSL_RC_xxx code!
 */
int th1ish_shell_extend(th1ish_interp * ie, int argc, char const * const * argv){
    static char once = 0;
    int rc = 0;
    cwal_value * top = cwal_scope_properties(ie->topScope);
    cwal_value * v;
    cwal_value * ns;
    cwal_engine * e = th1ish_interp_engine(ie);

    if(once){
        assert(!"libfossil/th1ish bindings initialized more than once!");
        return CWAL_RC_MISUSE;
    }
    once = 1;
    assert(top);

    fsl_timer_start(&RunTimer);
    fslAllocOrig = fsl_lib_configurable.allocator;
    fsl_lib_configurable.allocator = fcli_allocator
        /* This MUST be done BEFORE the fsl API allocates
           ANY memory!
        */;

    setlocale(LC_CTYPE,"") /* supposedly important for the JSON-parsing bits? */;
    ns = cwal_new_object_value(e);
    if(!ns) return CWAL_RC_OOM;
    rc = cwal_prop_set( top, "Fossil", 6, ns );
    if(rc){
        cwal_value_unref(ns);
        return rc;
    }

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( ns, NAME, fsl_strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    FUNC2("globMatches", cb_strglob);
    FUNC2("isUuid", cb_is_uuid);
    FUNC2("runTimeMs", cb_run_timer_fetch);
    
    v = fsl_cx_prototype(ie);
    SET("Context");
    v = fsl_zip_prototype(ie);
    SET("Zip");
    v = fsl_db_prototype(ie);
    SET("Db");
    cwal_prop_set( v, "Stmt", 4, fsl_stmt_prototype(ie) );

    rc = fsl_add_file_funcs( ie, ns );
    if(!rc) rc = fsl_add_delta_funcs( ie, ns );
    if(!rc) rc = fsl_add_time_funcs( ie, ns );
    if(!rc) rc = fsl_add_rcs( ie, ns );
    if(!rc){
        /* Extend the built-in Buffer prototype */
        cwal_value * bufProto = th1ish_prototype_buffer(ie);
        assert(bufProto);
#define BFUNC(NAME,FP) cwal_prop_set( bufProto, NAME, fsl_strlen(NAME), \
                       th1ish_new_function2(ie, FP))
        BFUNC("compress",cb_buffer_compress);
        BFUNC("isCompressed", cb_buffer_is_compressed);
        BFUNC("md5", cb_buffer_md5_self);
        BFUNC("sha1", cb_buffer_sha1_self);
        BFUNC("uncompress", cb_buffer_uncompress);
#undef BFUNC
    }


    if(!rc && (NULL != getenv("GATEWAY_INTERFACE"))){
        extern int th1ish_cgi_install_to_interp( th1ish_interp * ie,
                                                 cwal_value * ns )
            /* in cgiish.c */;
        cwal_value * api = cwal_prop_get(top, "api", 3);
        assert(api);
        rc = th1ish_cgi_install_to_interp(ie, api);
    }

#undef SET
#undef FUNC2
#undef CHECKV
    end:
    return rc;    
}

#undef MARKER
#undef THIS_F
#undef THIS_DB
#undef THIS_STMT
#undef THIS_ZIP
#undef FSL_TYPEID