cwal

Artifact [677b9fdd88]
Login

Artifact [677b9fdd88]

Artifact 677b9fdd88782ef20eade96b753d9fb8d40a759c:


/**
   Test/demo code for the cwal library.
*/
#if defined(NDEBUG)
#  undef NDEBUG
#endif
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#if defined(CWAL_AMALGAMATION_BUILD)
#  include "cwal_amalgamation.h"
#else
#  include "wh/cwal/cwal.h"
#  include "cwal_internal.h" /* don't do this at home, clients. */
#endif

#if 1
#define MARKER if(1) printf("MARKER: %s:%d:\t",__FILE__,__LINE__); if(1) printf
#else
static void noop_printf(char const * fmt, ...) {}
#define MARKER if(0) printf
#endif

struct {
    char enableTracing;
    char enableValueRecycling;
    char enableStringInterning;
    char showMetrics;
    char enableScopeHashes;
    char logScopePushPop;
} App = {
0/*enableTracing*/,
1/*enableValueRecycling*/,
1/*enableStringInterning*/,
0/*showMetrics*/,
0/*enableScopeHashes*/,
1/*logScopePushPop*/
};

/**

Tokenizes an input string on a given separator. Inputs are:

- (inp) = is a pointer to the pointer to the start of the input.

- (separator) = the separator character

- (end) = a pointer to NULL. i.e. (*end == NULL)

This function scans *inp for the given separator char or a NUL char.
Successive separators at the start of *inp are skipped. The effect is
that, when this function is called in a loop, all neighboring
separators are ignored. e.g. the string "aa.bb...cc" will tokenize to
the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the
separator is 'b'.

Returns 0 (false) if it finds no token, else non-0 (true).

Output:

- (*inp) will be set to the first character of the next token.

- (*end) will point to the one-past-the-end point of the token.

If (*inp == *end) then the end of the string has been reached
without finding a token.

Post-conditions:

- (*end == *inp) if no token is found.

- (*end > *inp) if a token is found.

It is intolerant of NULL values for (inp, end), and will assert() in
debug builds if passed NULL as either parameter.
*/
static char wh_str_toker_next_token( char const ** inp, char separator, char const ** end )
{
    char const * pos = NULL;
    assert( inp && end && *inp );
    if( *inp == *end ) return 0;
    pos = *inp;
    if( !*pos )
    {
        *end = pos;
        return 0;
    }
    for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ }
    *inp = pos;
    for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ }
    *end = pos;
    return (pos > *inp) ? 1 : 0;
}

struct wh_str_toker {
    char separator;
    char const * tokenBegin;
    char const * tokenEnd;
    char const * theEnd;
};
typedef struct wh_str_toker wh_str_toker;
#define wh_str_toker_empty_m {' ', 0, 0, 0}
const wh_str_toker wh_str_toker_empty = wh_str_toker_empty_m;

char wh_str_toker_init( wh_str_toker * t, char const * begin,
                        char const * end, char separator )
{
    if(!t || !begin || (end<=begin) ) return 0;
    else {
        t->tokenBegin = begin;
        t->theEnd = end;
        t->tokenEnd = NULL;
        t->separator = separator;
        return 1;
    }
}

char wh_str_toker_next( wh_str_toker * t, char const ** begin, char const ** end )
{
    if(!t || !begin || ! end || (t->tokenBegin >= t->theEnd)) return 0;
    else {
        t->tokenEnd = NULL;
        if( ! wh_str_toker_next_token( &t->tokenBegin, t->separator, &t->tokenEnd ) ) return 0;
        else {
            assert( t->tokenEnd > t->tokenBegin );
            *begin = t->tokenBegin;
            if(t->tokenEnd > t->theEnd){
                /* read trailing parts... */
                t->tokenEnd = t->theEnd;
            }
            *end = t->tokenEnd;
            t->tokenBegin = t->tokenEnd;
            return 1;
        }
    }
}

#define RC if(rc){                                  \
        MARKER("rc=%d=%s\n", rc, cwal_rc_cstr(rc)); \
    } assert(0==rc)

void test_toker(){
    wh_str_toker t = wh_str_toker_empty;
    char const * in = "///aA/bB/C////de/F";
    CWAL_UNUSED_VAR const int expect = 4;
    CWAL_UNUSED_VAR int count = 0;
    char const * b = NULL;
    char const * e = NULL;
    CWAL_UNUSED_VAR char rc = wh_str_toker_init( &t, in, in+strlen(in)-3, '/');
    assert(rc);
    while( wh_str_toker_next(&t, &b, &e) ){
        const int len = (int)(e-b);
        ++count;
        assert(e>b);
        MARKER("len=%d token=%.*s\n", len, len, b);
        if(4==count){
            assert(1==len && *b=='d');
        }
    }
    assert(expect == count);
}

int visitorCount = 0;
int ptr_visitor_foo( cwal_value ** V, void * S ){
#if 0
    cwal_value *v = *V;
    MARKER("Visiting value #%d @ %p\n", ++visitorCount, (void *)v);
#endif
    if(S){/*avoid unused var warning*/}
    *V = NULL;
    return CWAL_RC_OK;
}

#define dump_val(V,MSG) cwal_dump_value(__FILE__,__LINE__,(V),(MSG))

void e_ptr_table( cwal_engine * e ){
    enum { ValTableSize = 113,
           ValsToCreate = ValTableSize * 8 / 10  };
    int rc;
    uint32_t i, x;
    cwal_array * ar = NULL;
    cwal_ptr_table * vt = NULL;
    cwal_value * v;
    cwal_string * s;
    s = cwal_new_stringf( e, "%s", "hi, world!" );
    v = cwal_string_value( s );
    assert(s && (s == cwal_value_get_string(v)));
    assert(10==cwal_string_length_bytes(s));
    assert(0==cwal_compare_str_cstr(s, "hi, world!", 10));
    
    rc = cwal_ptr_table_create(e, &vt, (unsigned)ValTableSize, 0 );
    assert( 0 == rc );

    rc = cwal_ptr_table_search(e, vt, v );
    assert( 0 != rc && "Search unexpectedly succeeded." );
    rc = cwal_ptr_table_insert(e, vt, v );
    assert( 0 == rc && "Insert unexpectedly failed." );
    rc = cwal_ptr_table_search(e, vt, v );
    assert( 0 == rc && "Search unexpectedly failed." );
    rc = cwal_ptr_table_insert(e, vt, v );
    assert( CWAL_RC_ALREADY_EXISTS == rc && "Unexpected insert result." );
    rc = cwal_ptr_table_remove( e, vt, v );
    assert( 0 == rc );
    rc = cwal_ptr_table_search(e, vt, v );
    assert( CWAL_RC_NOT_FOUND == rc && "Search failed in unexpected manner." );

    MARKER("Val list loop starts here...\n");
    ar = cwal_new_array(e);
    cwal_value_ref( cwal_array_value(ar) );
    assert(ar);
    rc = cwal_scope_push( e, 0 );
    assert(0 == rc && "Scope push failed");
    for( x = 0, i = 0; i < ValsToCreate; ++i ){
        cwal_value * vv = cwal_new_integer( e, i );
        rc = cwal_ptr_table_insert( e, vt, vv );
        ++x;
        assert( 0 == rc );
        rc = cwal_ptr_table_search( e, vt, vv );
        assert( 0 == rc );
#if 0
        if(i && !(i%2)){
            rc = cwal_ptr_table_remove( e, vt, vv );
            assert( 0 == rc && "Remove failed.");
            /*cwal_value_unref( e, vv );*/
            --x;
            continue;
        }
#endif
        rc = cwal_array_append( ar, vv );
        RC;
    }
    rc = cwal_scope_pop(e);
    assert(0 == rc && "Scope pop failed");
    assert( cwal_array_length_get(ar) == x );

    cwal_ptr_table_visit( vt, ptr_visitor_foo, NULL );

    {
        unsigned int pageCount = 0;
        uint32_t a = 0, m = 0;
        cwal_ptr_page const * p = vt->pg.head;
        for( ; p; p = p->next ) ++pageCount;
        rc = cwal_ptr_table_mem_cost( vt, &a, &m );
        assert(0==rc);
        MARKER("table depth=%u with hashSize=%u, step=%u, value count=%d\n"
               "Memory (approximate!): mallocs~=%u memory~=%u\n",
               pageCount, (unsigned)vt->hashSize, (unsigned)vt->step, (unsigned)x,
               a, m);
        for( i = 0, p = vt->pg.head; p; ++i, p = p->next ){
            MARKER("Page #%u entry count=%u\n", i+1, p->entryCount);
        }
    }

    cwal_ptr_table_destroy( e, vt );
    /*cwal_string_unref( e, s );*/
    if(0){
        cwal_array * arB = arB = ar;
        v = cwal_array_value(arB);
        rc = cwal_array_append( ar, v );
        assert(0 == rc);
        /* rc = cwal_array_unref( ar ); */
        ar = arB;
    }
    cwal_array_unref(ar);
}

typedef struct NativeType NativeType;
struct NativeType {
    int x;
    int y;
};
static const NativeType NativeType_empty = {0,0};
static void finalizer_NativeType( cwal_engine * e, void * F ){
    NativeType * f = (NativeType*)F;
    MARKER("Freeing NativeType@%p x=%d y=%d\n", F, f->x, f->y);
    cwal_free(e, F);
}

cwal_native * new_native(cwal_engine * e){
    cwal_native * n;
    CWAL_UNUSED_VAR cwal_value * v;
    void * pCheck = NULL;
    NativeType * foo;
    int rc;

    foo = cwal_malloc(e, sizeof(NativeType));
    foo->x = 3; foo->y = 7;
    assert(foo);
    n = cwal_new_native( e, foo, finalizer_NativeType, &NativeType_empty );
    assert(n);
    v = cwal_native_value(n);
    assert(0 == cwal_value_refcount(v));
    assert(v);
    assert(n == cwal_value_get_native(v));
    assert( CWAL_TYPE_NATIVE == cwal_value_type_id(v) );
    assert(0 == cwal_native_get(n, ""));
    rc = cwal_native_fetch(n, &NativeType_empty, &pCheck);
    RC;
    assert(pCheck == foo);
    assert(foo == cwal_native_get(n, &NativeType_empty));
    return n;
}


static cwal_json_output_opt JsonOutOpt = cwal_json_output_opt_empty_m;

void e_json1( cwal_engine * e ){
    cwal_value * v;
    cwal_array * a;
    cwal_object * o;
    cwal_value * oV;
    int rc;
    cwal_json_output_opt opt = cwal_json_output_opt_empty;
    a = cwal_new_array( e );
    o = cwal_new_object( e );
    oV = cwal_object_value(o);
    cwal_array_append( a, cwal_object_value(o) );
    v = cwal_new_double(e, 39);
    cwal_array_append( a, v );
    cwal_prop_set( oV, "foooooo", 3, v );

    cwal_array_append(a, cwal_value_null() );
    cwal_array_append( a, cwal_new_integer(e, 100) );

    opt.addNewline = 1;
    opt.indent = 2;

    rc = cwal_json_output_FILE( cwal_array_value(a), stdout, &opt );
    RC;

#define AOUT rc = cwal_json_output_FILE( cwal_array_value(a), stdout, &opt ); RC
    
    MARKER("Sorted:\n");
    cwal_array_sort( a, cwal_compare_value_void );
    AOUT;

    /* cwal_array_set( a, 1, NULL ); */

    MARKER("Reverse-sorted:\n");
    cwal_array_sort( a, cwal_compare_value_reverse_void );
    AOUT;

    MARKER("via array_reverse():\n");
    cwal_array_reverse( a );
    AOUT;

#undef AOUT

#if 0
    opt.indent = -1;
    opt.indentSingleMemberValues = 1;
    rc = cwal_json_output_engine( e, cwal_object_value(o), &opt );
    RC;
#endif
}

void e_interned_strings( cwal_engine * e ){
    cwal_value * s1;
    cwal_value * s2;
    char const * cstr = "foo";
    cwal_size_t clen;
    clen = cwal_strlen(cstr);
    /*
      Interning introduces an interesting problem:

      We don't use cwal_value_push() for interned strings, because it
      seems silly to do so. However, this causes that the refcount
      exceeds what we will ever unref.  If we do not ref() each "new"
      value then we can get invalid refcounts when we allocate a
      string from 2 scopes, because only one of them would have the
      value in its stack.
      
      If we push the value into the scope for each allocation
      we fix that we have two problems:
      
      a) code which re-uses a string a lot (in a loop) will cause
      the scope to grow inordinately. 

      b) Fixing (a) requires adding more infrastructure (==memory) to
      the scope class. The cwal_ptr_table could be used to track "have
      we seen this", but costs us a great deal of memory and a usable
      table size is impossible to guess. We "could" add the scope pointer
      back to the value instances and push the value to the scope only
      when it differs... no, that has other problems...
    */
    s1 = cwal_new_string_value( e, cstr, clen );
    cwal_value_ref(s1)
        /* 20160111: interning won't re-use strings
           with a refcount of 0, to avoid ugly client-side bugs
           in certain usage patterns.

           20160126: not true anymore. That only delayed
           the problem.
        */;
    s2 = cwal_new_string_value( e, cstr, clen );
    cwal_value_ref(s2);
    MARKER("String interning is %s.\n",
           (s1==s2) ? "ENABLED" : "DISABLED");
    if(App.enableStringInterning){
        assert(s1==s2);
        assert(2==cwal_value_refcount(s1));
    }else{
        assert(s1!=s2);
        assert(1==cwal_value_refcount(s1));
        assert(1==cwal_value_refcount(s2));
    }
    if(s1==s2){
        /**
           allocate lots of them and make sure they don't really allocate.
        */
        int i, max = 1000;
        cwal_value * vbar = 0;
        CWAL_UNUSED_VAR cwal_value * prev = 0;
        cstr = "bar";
        clen = 3;
        MARKER("Looping over %d \"new\" strings which \"should\" "
               "be interned.\n", max);
        for( i = 0; i < max; ++i ){
            prev = vbar;
            vbar = cwal_new_string_value( e, cstr, clen );
            if(0==i){
                cwal_value_ref(vbar);
                /* see note above about interning an refcount */
            }else{
                assert(prev == vbar);
            }
        }
        assert(1==cwal_value_refcount(vbar));
        cwal_value_unref(vbar);
        MARKER("Make sure valgrind doesn't show a lot of string re-allocs here.\n");
    }
    cwal_value_unref(s1);
    cwal_value_unref(s2);

#if 0
    /* this heuristic was removed on 20160126... */
    if( cwal_engine_feature_flags(e, -1) & CWAL_FEATURE_INTERN_STRINGS ){
        /* ensure that the "never re-use temporary interned strings" heuristic
           works... */
        s1 = cwal_new_string_value( e, cstr, clen );
        /* not taking a ref, so interning is not supposed to kick in... */
        s2 = cwal_new_string_value( e, cstr, clen );
        assert(s1 && s2 && s1 != s2);
        assert(0 == cwal_value_compare(s1,s2));
        cwal_value_unref(s1);
        cwal_value_unref(s2);
    }
#endif
}

void e_random_strings( cwal_engine * e ){
    enum {
        StringCount = 500,
        MaxStrLen = 3
    };
    static const char alphanum[] =
#if 0
        "0123456789"
        " !@#$%^&*()_+|[]{}<>/?.,\\\"'~`"
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        "abcdefghijklmnopqrstuvwxyz"
#else
        /* use this with a small MaxStrLen to get lots of re-use. */
        "abc"
#endif
        ;
    unsigned const slen = (sizeof(alphanum)-sizeof(alphanum[0]/*NUL byte!*/))/sizeof(alphanum[0]);
    cwal_size_t i, x;
    CWAL_UNUSED_VAR cwal_string * s;
    uint32_t totalSize = 0;
    char buf[MaxStrLen+3] = {0,};
    srand(time(0));
    MARKER("Allocating %d strings with a maximum length of %d...\n",
           StringCount, MaxStrLen );
    for( i = 0; i < StringCount; ++i ){
        unsigned mylen = rand() % MaxStrLen + 1;
        unsigned mySize = mylen + sizeof(cwal_string) + 1 /*NUL byte!*/;
        memset( buf, 0, sizeof(buf)/sizeof(buf[0]) );
        for (x = 0; x < mylen; ++x) {
            buf[x] = alphanum[rand() % slen];
        }
        buf[x--] = 0;
        s = cwal_new_string( e, buf, x );
        assert(s);
        totalSize += mySize;
    }
    if(e->interned.pg.head){
        cwal_dump_interned_strings_table( e, 1, slen );
    }
    MARKER("APPROXIMATE total memory usage, had we allocated "
           "all these strings: %u bytes\n",
           (unsigned)totalSize);
}


void e_relaunch1( cwal_engine * e ){
    /*cwal_size_t i, x;*/
    cwal_string * s;
    cwal_value * sv;
    cwal_value * v;
    cwal_array * ar;
    cwal_object * obj;
    cwal_value * objV;
    cwal_weak_ref * wr = 0;
    int rc;
    cwal_size_t expectRC;
    s = cwal_new_stringf( e, "%s", "HI! world!" );
    sv = cwal_string_value(s);
    assert(s && (s == cwal_value_get_string(sv)));
    assert(10==cwal_string_length_bytes(s));
    assert(0==cwal_compare_str_cstr(s, "HI! world!", 10));

    expectRC = App.enableStringInterning
        ? 0 /* depends on cwal-internal magic/kludge */
        : 0;
    assert( expectRC == cwal_value_refcount( sv ) );
    cwal_value_ref( sv );
    ++expectRC;
    assert( expectRC == cwal_value_refcount( sv ) );
    
    v = cwal_new_integer(e, 0);
    assert( 0 == cwal_value_refcount(v) /* special built-in rc */ );
    rc = cwal_value_unref(v);
    assert(0 == rc);
    v = cwal_new_integer(e, 1);
    assert( 0 == cwal_value_refcount(v) );
    assert(1 == cwal_value_get_integer(v));
    /* assert(CWAL_RC_OK == cwal_value_unref(v)); */

    v = cwal_new_integer(e, 200);
    assert( 0 == cwal_value_refcount(v) );
    assert(CWAL_RC_FINALIZED == cwal_value_unref(v));
    
    v = cwal_new_double(e, 0.0);
    assert( 0 == cwal_value_refcount(v) /* special built-in value */ );
    assert(0 == cwal_value_unref(v));
    v = cwal_new_double(e, 42.24);
    assert( 0 == cwal_value_refcount(v) );
    assert(CWAL_RC_FINALIZED == cwal_value_unref(v));

    ar = cwal_new_array(e);
    assert(ar);
#define APP(V) rc = cwal_array_append( ar, (V) ); RC
    APP(sv); /* Hmmmm. should NULL be a legal array append value? */
    v = cwal_array_value( ar );
    rc = cwal_array_append( ar, v );
    RC;


    obj = cwal_new_object(e);
    objV = cwal_object_value(obj);
    assert(e->current == v->scope);
    v = cwal_object_value(obj);
    assert(v && (0==cwal_value_refcount(v)));
    /*rc = cwal_value_ref(v);
      RC;*/
    /*APP(v); assert(0 == v->scope);*/
#define SET(K,V) rc = cwal_prop_set( objV, K, cwal_strlen((K)), (V) );
    SET("feezle", cwal_array_value(ar) );
    RC;
    APP(v);
    APP(v);
    assert(2 == cwal_value_refcount(v));
    if(1){
        cwal_native * n;
        CWAL_UNUSED_VAR void * pv;
        MARKER("Adding a Native to the mix...\n");
        n = new_native(e);
        pv = cwal_native_get( n, &NativeType_empty );
        v = cwal_native_value(n);
        assert(v && pv);
        assert(0==cwal_value_refcount(v));

        wr = cwal_weak_ref_new(v);
        assert(wr);
        assert(cwal_weak_ref_value(wr)==v);

        APP(v);
        assert(1 == cwal_value_refcount(v));        
        rc = cwal_prop_set( objV, "native", 6, v );
        assert(2 == cwal_value_refcount(v));
        RC;
        APP(v);
        assert(3 == cwal_value_refcount(v));

        if(0){
            cwal_buffer * buf;
            cwal_size_t const bufSize = 300;
            MARKER("Adding a scope-local buffer of %u bytes...\n", (unsigned)bufSize);
            buf = cwal_new_buffer(e,bufSize);
            assert(buf);
            assert(bufSize == buf->capacity);
            assert(bufSize == cwal_buffer_fill( buf, '!' ));
            cwal_buffer_unref( e, buf );
        }

    }


#if 1
    {/* Try some cross-scope referencing.
      */
        /* cwal_size_t sz; */
        cwal_array *ar2;
        MARKER("Pushing subscope.\n");
        rc = cwal_scope_push(e, 0);
        RC;
        v = cwal_new_string_value(e, "subscoped", 0);
        APP(v);

        ar2 = cwal_new_array(e);
        v = cwal_array_value(ar2);
        assert(0==cwal_value_refcount(v));

        if(1){
            cwal_value * o2;
            cwal_string * estr;
            MARKER("And now for multi-scope distance...\n");
            rc = cwal_scope_push(e,0);
            RC;
            cwal_new_string(e, "alone", 5);
            rc = cwal_scope_push(e,0);
            RC;

            v = cwal_new_integer(e, 73);
            o2 = cwal_new_object_value(e);
            rc = cwal_prop_set( o2, "myInt", 5, v );
            RC;
            rc = cwal_prop_set( o2, "true", 4, cwal_value_true() );
            RC;
            
            rc = cwal_array_append(ar2, cwal_array_value(ar));
            RC;
            SET("subArray",cwal_array_value(ar2));

            SET("subSubObj",o2);
            SET("subSubObjB",o2);
            APP(o2);

            estr = cwal_new_string(e,"",0);
            rc = strcmp("",cwal_string_cstr(estr));
            assert( 0==rc );
            rc = cwal_prop_set( o2, "emptyString", 11, cwal_string_value(estr) );

            rc = cwal_scope_pop(e);
            RC;
            rc = cwal_scope_pop(e);
            RC;
            if(0){
                cwal_size_t sz;
                MARKER("Sweeping subscope. We should not see any freeing going on here...\n");
                sz = cwal_engine_sweep(e);
                MARKER("Sweep count: %u\n",(unsigned)sz);
                RC;
            }
        }

        if(1){
            cwal_size_t sz;
            cwal_new_integer(e, 32000)
                /* Just to see it get swept up. It must be a value
                   which will allocate (not builtin). */;
            MARKER("Sweeping subscope. We should see at least one clean-up here...\n");
            sz = cwal_engine_sweep(e);
            MARKER("Sweep count: %u\n",(unsigned)sz);
            assert(sz>0);
        }
        MARKER("Popping subscope.\n");
        rc = cwal_scope_pop(e);
        RC;

        MARKER("Popped subscope.\n");
        assert(cwal_weak_ref_value(wr));

        if(1){
            cwal_value const * check;
            cwal_scope * s = cwal_scope_current_get(e);
            int sweepCount = 0;
#if 1
            /* We need at least one var to see this in action... */
            rc = cwal_scope_chain_set(s, 0, "foo", 3, cwal_value_true());
            RC;
            rc = cwal_scope_chain_set(s, 0, "bar", 3, cwal_value_false());
            RC;
#endif
            MARKER("Vacuuming scope #%d.\n", (int)s->level);
            if(cwal_value_get_object(cwal_scope_properties(s))){
                /* using hashes for scope storage breaks this */
                MARKER("Vars before vacuum:\n");
                rc = cwal_json_output_FILE( cwal_scope_properties(s), stdout, 0 );
                RC;
            }
            rc = cwal_engine_vacuum(e, &sweepCount);
            RC;

            assert(!cwal_weak_ref_value(wr) && "We expect this to have been vacuumed up");

            MARKER("Vacuumed away %d value(s).\n", sweepCount);
            if(cwal_value_get_object(cwal_scope_properties(s))){
                /* using hashes for scope storage breaks this */
                MARKER("Vars after vacuum:\n");
                rc = cwal_json_output_FILE( cwal_scope_properties(s), stdout, 0 );
                RC;
            }

#  define VLIST_COUNT(WHO) check = WHO; rc = 0; while(check){++rc; check=check->right;}(void)0
            VLIST_COUNT(s->mine.headPod);
            assert(2==rc) /* one for each key. The bool values are not owned by the scope */;
            VLIST_COUNT(s->mine.headSafe) /* s->props */;
            assert(rc == 1);
            VLIST_COUNT(s->mine.headObj) /* nothing! */;
            assert(rc == 0);
            assert(cwal_value_true() == cwal_scope_search(s, 0, "foo", 3, 0));
            assert(cwal_value_false() == cwal_scope_search(s, 0, "bar", 3, 0));

#undef VLIST_COUNT
        }
        cwal_weak_ref_free(e, wr);

    }
#endif

    
#undef SET
#undef APP
#if 0
    cwal_dump_interned_strings_table( e, 1, 30  );
#endif
    MARKER("And now let us pray as the scope gets popped "
           "(or did vacuuming already clean it up?)..."
           " What will happen to our dear graph?\n");
}

static int val_visitor_counter( cwal_value * v, void * state ){
    ++*(cwal_size_t*)state;
    MARKER("Visiting kvp part #%u @%p\n",
           (unsigned) *(cwal_size_t*)state, (void*)v);
    return 0;
}

void e_object2(cwal_engine * e){
    cwal_object * o = cwal_new_object(e);
    cwal_value * ov = cwal_object_value(o);
    cwal_prop_set_v( ov, ov, ov );
    cwal_prop_set( ov, "hi", 2, ov );
#if 0
    cwal_prop_set( ov, "hi", 2, 0 );
    cwal_prop_set_v( ov, ov, 0 );
#endif
    /*cwal_value_unref(ov);*/
}

void e_object(cwal_engine * e){
    cwal_object * o = cwal_new_object(e);
    cwal_value * oV = cwal_object_value(o);
    cwal_string * key = cwal_new_string(e, "key", 3);
    cwal_value * keyV = cwal_string_value(key);
    int rc;
    cwal_size_t pCount = 0, x;
    assert(o);
    assert(!o->base.kvp);
    rc = cwal_prop_set(oV, "foo", 3, cwal_value_true());
    RC;
    ++pCount;
    assert(o->base.kvp);
    assert(o->base.kvp->value == cwal_value_true());
    rc = cwal_prop_set(oV, "bar", 3, oV);
    RC;
    ++pCount;
    assert(o->base.kvp->right && "May break when/if props get sorted");
    cwal_prop_set_v(oV, keyV, keyV);
    ++pCount;
    /* assert(o->base.kvp->key == keyV); relies on specific insertion order. */
    /* assert(o->base.kvp->value == keyV); relies on specific insertion order. */

    MARKER("Running props key visitor...\n");
    x = 0;
    rc = cwal_props_visit_keys( oV, val_visitor_counter, &x );
    RC;
    assert(pCount == x);
    MARKER("Running props value visitor (don't be alarmed by dupe pointers)...\n");
    x = 0;
    rc = cwal_props_visit_values( oV, val_visitor_counter, &x );
    RC;
    assert(pCount == x);

    
    if(1){
        /* Should fail because of the cycle. */
        rc = cwal_json_output_engine( e, oV, &JsonOutOpt );
        putchar('\n')/*assumes output dest is the same :/ */;
        assert(CWAL_RC_CYCLES_DETECTED==rc);
        MARKER("JSON output aborted because of cycles. (This was expected.)\n");
    }
}

void e_buffer_format( cwal_engine * e ){

    cwal_buffer b = cwal_buffer_empty;
    cwal_value * argv[7] = {
    NULL,NULL,NULL,NULL,NULL,
    NULL,NULL
    };
    CWAL_UNUSED_VAR int rc;
    char const * fmt;
    cwal_size_t flen;
    const cwal_size_t argc = sizeof(argv)/sizeof(argv[0]);

    assert(!cwal_buffer_value(&b));
    rc = cwal_buffer_reserve(e, &b, 100);
    assert(!rc);

#define FMT(X) ((fmt=X), flen=strlen(X))
#define DUMP(F)                                                         \
    FMT(F); \
    rc = cwal_buffer_format(e, &b, fmt, flen, argc, argv);              \
    MARKER("\n\tFormat string: [%s]\n\tBuffer contents: <<<%.*s>>>\n",\
           fmt, (int)b.used, (char const *)b.mem);                    \
    b.used = 0; \
    assert(0==rc)

    argv[0] = cwal_new_integer(e, 42);
    argv[1] = cwal_new_string_value(e, "string formatter", 16);
    argv[2] = cwal_new_string_value(e, "abcde", 5);
    argv[3] = cwal_new_double(e, 19.17);
    argv[4] = cwal_value_true();
    argv[5] = cwal_value_false();
    argv[6] = cwal_new_double(e, -13.19);
    DUMP("%1$y %1$p %1$d %1$03d %1$03X %1$04x");
    DUMP("(%1$10d) (%1$-10d)");
    DUMP("(%1$+10d) (%1$+-10d)");
    DUMP("%1$-10d");
    DUMP("%1$05d");
    DUMP("%1$0.5f");
    DUMP("%2$s");
    DUMP("%2$s and %2$s");
    DUMP("(%3$-10s) (%3$10s)");
    DUMP("(%3$10s) (%3$-10s)");
    DUMP("(%3$10.3s) (%3$-10.3s)");
    DUMP("(%3$-10.3s) (%3$10.3s)");
    DUMP("(%3$.4s) (%3$-8.4s)");
    DUMP("%4$f %4$.1f %4$.2f %4$.3f");
    DUMP("%4$+0f (%4$-10.3f) (%4$10.3f) (%4$+010.3f)");
    DUMP("%5$d %5$b %6$b %5$N %5$U");
    DUMP("%6$+f");
    DUMP("%7$+f");
#if 0
    /* must fail: */
    DUMP("%1$d %10$d 2nd formatter is invalid");
#endif
#undef FMT
#undef DUMP
    cwal_buffer_reserve(e, &b, 0);
}



void e_comparisons(cwal_engine * e){

    cwal_value * s = cwal_new_string_value(e, "hi", 2);
    cwal_value * s2 = cwal_new_string_value(e, "bi", 2);
    cwal_value * i = cwal_new_integer(e, 3);
    cwal_value * i2 = cwal_new_integer(e, 42);
    cwal_value * i0 = cwal_new_integer(e, 0);
    cwal_value * d = cwal_new_double(e, 42.24);
    cwal_value * d2 = cwal_new_double(e, 42.242);
    cwal_value * sd2 = cwal_new_string_value(e, "42.242", 6);
    cwal_value * null = cwal_value_null();
    cwal_value * undef = cwal_value_undefined();
    CWAL_UNUSED_VAR int rc;
#define CMP(L,R,COND) rc = cwal_value_compare(L,R); assert(COND)
    CMP(s,s2,(rc>0));
    CMP(null,undef,(0==rc));
    CMP(undef,null,(0==rc));
    CMP(i,i,(0==rc));
    CMP(i,i2,(0>rc));
    CMP(i2,i,(0<rc));
    CMP(d,d2,(0>rc));
    CMP(d2,d,(0<rc));
    CMP(d,i2,(0<rc));
    CMP(i2,d,(0>rc));
    CMP(i2,d2,(0>rc));
    CMP(sd2,d2,(0==rc));
    CMP(d2,sd2,(0==rc));
    CMP(sd2,d,(0!=rc));
    CMP(cwal_value_true(),cwal_value_false(),(0<rc));
    CMP(i0,cwal_value_false(),(0==rc));
    CMP(i2,cwal_value_false(),(rc>0));
#undef CMP
    assert(1 == cwal_value_get_bool(cwal_value_true()));
    assert(0 == cwal_value_get_bool(cwal_value_false()));

    {
        char const * key = "foo";
        cwal_size_t const keyLen = strlen(key);
        cwal_string * xstr = cwal_new_xstring(e, key, keyLen);
        CWAL_UNUSED_VAR cwal_string * str;
        char const * x = cwal_string_cstr(xstr);
        assert(x == key);
        assert(keyLen==cwal_string_length_bytes(xstr));
        assert(xstr->length & keyLen);
        assert(xstr->length > keyLen);
        assert(0==cwal_compare_str_cstr(xstr, key, keyLen));
        assert(0==cwal_compare_str_cstr(xstr, "foo", 3));
        assert(0!=cwal_compare_str_cstr(xstr,"foo",2));
        assert(0!=cwal_compare_str_cstr(xstr,"bar",3));
        MARKER("xstr->length=%d => %u\n", (int)xstr->length, (unsigned)cwal_string_length_bytes(xstr));
        MARKER("xstr=[%s]\n", x);
        str = cwal_new_string( e, key, keyLen );
        assert(0==cwal_value_compare(cwal_string_value(xstr),
                                     cwal_string_value(str)));
#if 0
        cwal_string_unref(xstr);
        cwal_string_unref(str);
#endif
    }
}

int my_callback( cwal_callback_args const * args, cwal_value **rv ){
    NativeType * nt;
    int rc;
    cwal_value * v = 0;
    cwal_value * funcV = cwal_function_value(args->callee);
    cwal_kvp const * kvp;
    MARKER("argc=%u\n", args->argc);
    nt = (NativeType *)args->state;
    MARKER("function state: NativeType@%p x=%d, y=%d\n", (void*)nt, nt->x, nt->y);

    
    kvp = cwal_scope_search_kvp( args->scope, 0, "myKey", 5, 0 );
    rc = kvp ? 0 : CWAL_RC_NOT_FOUND;
    RC;
    v = cwal_kvp_value(kvp);
    assert(v && (cwal_value_true()==v));

    {
        cwal_value * keyCheck = cwal_new_string_value(args->engine, "funcProp", 8);
        v = cwal_prop_get( funcV, "funcProp", 8 );
        assert(cwal_value_true()==v);
        v = cwal_prop_get( funcV, "funcProp", 8 );
        assert(cwal_value_true()==v);
    
        {
            v = cwal_prop_get_v( funcV, keyCheck );
            assert(v);
            assert(cwal_value_true()==v);
        }

        rc = cwal_prop_set_v( funcV, keyCheck, NULL );
        RC;
        v = cwal_prop_get( funcV, "funcProp", 8 );
        assert(!v);
        rc = cwal_prop_set( funcV, "funcProp", 8, NULL );
        assert(CWAL_RC_NOT_FOUND==rc);
    }

    /* Check for magic properties which were set by our
       init code... */
    {
        cwal_value * intKey = cwal_new_integer(args->engine,42);
        assert(intKey);
        cwal_value_ref( intKey );
        v = cwal_prop_get_v( funcV, intKey );
        assert(cwal_value_true()==v);
        rc = cwal_prop_set_v( funcV, intKey, NULL );
        RC;
        cwal_value_unref( intKey );
        v = cwal_prop_get_v( funcV, funcV );
        assert(!v);
    }


    {
        rc = cwal_prop_set_v( funcV, funcV, funcV );
        RC;
        v = cwal_prop_get_v( funcV, funcV );
        assert(v == funcV);
        MARKER("Woohoo: func as a key :)\n");
        rc = cwal_prop_set_v( funcV, funcV, NULL );
        RC;
        rc = cwal_prop_set_v( funcV, funcV, NULL );
        /* arguable behaviour:*/ assert(CWAL_RC_NOT_FOUND==rc);
        v = cwal_prop_get_v( funcV, funcV );
        assert(!v);
    }

    rc = cwal_props_clear( funcV );
    RC;

    *rv = cwal_new_string_value(args->engine,"function result",15);
    assert(*rv);
    return CWAL_RC_OK;
}

void e_function(cwal_engine * e){
    cwal_value * fv;
    cwal_function * f;
    cwal_scope * scope = 0;
    int rc;
    NativeType* n;
    cwal_value * v;

    rc = cwal_scope_push(e, &scope);
    RC;
    assert(0!=scope);
    {
        cwal_value * key1 = cwal_new_string_value( e, "myKey", 5);
        cwal_value_ref(key1);
        rc = cwal_var_decl_v( e, scope, key1, cwal_value_true(), 0 );
        RC;
        rc = cwal_var_decl_v( e, scope, key1, cwal_value_null(), 0 );
        assert(CWAL_RC_ALREADY_EXISTS==rc);

        v = cwal_scope_search_v(scope, 0, key1, 0);
        cwal_value_unref(key1);
        key1 = 0;
        assert(cwal_value_true()==v);
    
        n = cwal_malloc(e, sizeof(NativeType));
        assert(n);
        *n = NativeType_empty;
        n->x = -42;
        n->y = 17;
        fv = cwal_new_function_value(e, my_callback,
                                     n, finalizer_NativeType, &NativeType_empty);
        f = cwal_value_get_function(fv);
        assert(f && fv);
        rc = cwal_prop_set( fv, "padding1", 8, cwal_value_null() );
        RC;
        rc = cwal_prop_set( fv, "funcProp", 8, cwal_value_true() );
        RC;
        rc = cwal_prop_set( fv, "padding2", 8, cwal_value_null() );
        RC;
        rc = cwal_prop_set_v( fv, cwal_new_integer(e,42), cwal_value_true() );
        RC;

        cwal_value_ref(fv) /* Very important if property fv[fv] === fv!!!! */;
        rc = cwal_function_call_in_scopef(scope, f, NULL, NULL, fv, NULL);
        RC;
    }
    rc = cwal_scope_pop(e);
    RC;
}

void e_exception(cwal_engine * e){
    /* Make sure exceptions get moved down the scope stack
       as scopes are popped.
    */
    cwal_value * xv;
    CWAL_UNUSED_VAR cwal_value * xv2;
    int rc;
    cwal_weak_ref * wr;
    rc = cwal_scope_push(e, 0); RC;
    rc = cwal_scope_push(e, 0); RC;
    rc = cwal_scope_push(e, 0); RC;
    rc = cwal_exception_setf( e, CWAL_RC_TYPE, "Type exception: #%d", CWAL_RC_TYPE);
    assert(CWAL_RC_EXCEPTION==rc);
    xv = cwal_exception_get(e);
    assert(xv);
    wr = cwal_weak_ref_new(xv);
    assert(wr);
    MARKER("exception value type name = %s\n", cwal_value_type_name(xv));
    assert(cwal_value_is_exception(xv));
#define POP rc = cwal_scope_pop(e); RC; assert(xv->scope==e->current)
    POP;
    POP;
    POP;
    xv2 = cwal_exception_get(e);
    assert(xv2==xv);
    assert(cwal_weak_ref_value(wr)==xv2);
    cwal_weak_ref_free(e, wr);
}


void e_prototypes( cwal_engine * e ){
    cwal_object * o1 = cwal_new_object(e);
    cwal_object * o2 = cwal_new_object(e);
    cwal_value * ov1 = cwal_object_value(o1);
    cwal_value * ov2 = cwal_object_value(o2);
    CWAL_UNUSED_VAR cwal_value * v;
    char const * key = "protoProp";
    cwal_size_t keyLen = cwal_strlen(key);
    CWAL_UNUSED_VAR int rc;
    assert( !cwal_value_derives_from(e, ov1, ov2) );
    rc = cwal_value_prototype_set( ov1, ov2 );
    assert(!rc && "Prototype set failed.");
    assert(ov2 == cwal_value_prototype_get(e, ov1));
    assert(NULL == cwal_value_prototype_get(e, ov2));
    assert( cwal_value_derives_from(e, ov1, ov2) );

    cwal_prop_set(ov2, key, keyLen, cwal_value_true() );
    v = cwal_prop_get( ov1, key, keyLen );
    assert(v && "search through prototype failed.");
    assert(!cwal_prop_has(ov1, key, keyLen, 0));
    assert(cwal_prop_has(ov2, key, keyLen, 0));

    rc = cwal_value_prototype_set( ov2, ov2 );
    assert( CWAL_RC_MISUSE == rc );

#if 0
    /* i removed circular prototypes - too easy to
       get into loops.
    */
    rc = cwal_value_prototype_set( ov2, ov1 );
    assert(!rc && "circular prototypes failed.");
    assert(ov1 == cwal_value_prototype_get(ov2));
    assert(ov2 == cwal_value_prototype_get(ov1));
#endif    
}

void e_sweeping1( cwal_engine * e ){
    cwal_scope * s = NULL;
    cwal_size_t i, x, outer = 100, inner = 10;
    int rc = cwal_scope_push( e, &s );
    RC;
    assert(s);
    MARKER("If recycling is enabled, the following should add "
           "very few malloc()s (check valgrind stats).\n");
    for( i = 1; i <= outer; ++i ){
        for( x = 1; x <= inner; ++x ){
            CWAL_UNUSED_VAR cwal_value * v = cwal_new_integer(e, 100 * (i+x));
            assert(v);
            assert(0 == cwal_value_refcount(v));
        }
        {
            CWAL_UNUSED_VAR cwal_size_t sz;
            /*MARKER("Sweeping. Expecting %d sweep-ups...\n",inner);*/
            sz = cwal_scope_sweep( s );
            /*MARKER("Sweeping. Expecting %d, got %u sweep-ups...\n",
              inner, (unsigned)sz);*/
            assert(sz == inner)
                /* LOL: if v is one of the built-in constants this
                   assertion fails b/c 0/1 are a special integer
                   values which never gets scoped. */;
        }
    }
    rc = cwal_scope_pop(e);
    RC;

}

void e_basic_assertions( CWAL_UNUSED_VAR cwal_engine *ie ){
    cwal_int_t i = 0;
    cwal_double_t d = 0;
    CWAL_UNUSED_VAR int rc;
    rc = cwal_cstr_to_int( "342", 3, &i );
    assert(0==rc);
    assert(342==i);

    rc = cwal_cstr_to_int( "-42", 3, &i );
    assert(0==rc);
    assert(-42==i);

    /* Interesting... on my 32-bit box the double
       comparisons fail. Printf'ing the values shows
       the values are correct but the comparison
       assertions fail. This does not happen on my
       64-bit box.
    */
    rc = cwal_cstr_to_double( "1.2", 3, &d );
    /* MARKER("d=%f\n", d); */
    assert(0==rc);
    assert(1.2==d);

    rc = cwal_cstr_to_double( "1.3abcd", 3, &d );
    /* MARKER("d=%f\n", d); */
    assert(0==rc);
    assert(1.3==d);
    
    rc = cwal_cstr_to_double( "-31.3", 5, &d );
    assert(0==rc);
    assert(-31.3==d);

    rc = cwal_cstr_to_double( "+17.17 junk", 6, &d );
    assert(0==rc);
    assert(17.17==d);

    rc = cwal_cstr_to_double( "-1333", 5, &d );
    assert(0==rc);
    assert(-1333==d);

    rc = cwal_cstr_to_double( "-1333.-1", 8, &d );
    assert(CWAL_RC_TYPE==rc);

    assert(cwal_new_string_value(ie, "abc", 0) == cwal_new_string_value(ie,0,0));
}

#if 0
/* do not use - fundamentally broken interface */
void e_utf8_char_next( cwal_engine *ie ){
    char const * str = "hi\0→wÜrld";
    char const * const end = str + 12 /* careful! */;
    char const * pos = str;
    unsigned int unicode = 0;
    int len = 0, bytes = 0, utfLen = 0;
    assert(9U == cwal_strlen_utf8(pos, (end-str)));
    for( ; 0<(len = cwal_utf8_char_next(pos, end, &unicode));
         pos += len, bytes += len, ++utfLen){
        MARKER("Got len-%d character: %.*s (0x%x)\n", (int)len, (int)len, pos, unicode);
        assert(len>=1 && len<=4) /* a NUL byte has a len of 1! */;
    }
    assert(12 == bytes);
    assert(9 == utfLen);
    {
        cwal_string * s = cwal_new_string(ie, str, end-str);
        assert(s);
        assert(!cwal_string_is_ascii(s));
        cwal_string_unref(s);
    }
}
#endif

void e_unique(cwal_engine * e){

    cwal_value * u1 = cwal_new_unique(e, 0);
    cwal_value * u2 = cwal_new_unique(e, u1);
    CWAL_UNUSED_VAR int cmp = cwal_value_compare(u1,u2);
    CWAL_UNUSED_VAR char const * tname = cwal_value_type_name(u1);
    CWAL_UNUSED_VAR int rc;
    cwal_value_ref(u1);
    cwal_value_ref(u2);
    assert(0!=cmp);
    assert( cwal_value_is_unique(u1) );
    assert( 0 == cwal_compare_cstr("unique", 6, tname, cwal_strlen(tname)) );
    assert( cwal_value_compare(u2,u1)>0 ? (cmp<0) : (cmp>0) );
    assert( 0 == cwal_value_compare(u1,u1) );
    assert( 0 != cwal_value_compare(u1, cwal_value_true()) );
    assert( cwal_value_get_bool(u1) );

    assert( u1 == cwal_unique_wrapped_get(u2) );
    assert( 0 == cwal_unique_wrapped_get(u1) );
    assert( 2 == cwal_value_refcount(u1) );
    rc = cwal_unique_wrapped_set(u2, 0);
    assert(!rc);
    assert( 1 == cwal_value_refcount(u1) );
    rc = cwal_unique_wrapped_set(u2, u2);
    assert(CWAL_RC_CYCLES_DETECTED==rc);
    cwal_value_unref(u1);
    cwal_value_unref(u2);
}

void e_call( cwal_engine * e, void (*func)( cwal_engine * ), char const * descr ){
    int rc;
    cwal_scope _p1 = cwal_scope_empty;
    cwal_scope _p2 = cwal_scope_empty;
    cwal_scope * s1 = &_p1, * s2 = &_p2;
    MARKER("Pushing scope for test [%s]...\n", descr);
    /* Workaround:
       Push 2 scopes to keep the result value from poluting our global
       so that i can better watch the lifetimes.
    */
    rc = cwal_scope_push(e, &s1); RC;
    rc = cwal_scope_push(e, &s2); RC;
    func(e);
    MARKER("Popping scope [%s]...\n", descr);
    rc = cwal_scope_pop(e); RC;
    rc = cwal_scope_pop(e); RC;
    MARKER("Scope popped [%s].\n", descr);
    if(0 && e->interned.pg.head){
        cwal_dump_interned_strings_table( e, 1, 30  );
    }
}

/**
   Called via cwal_engine_init() using our customized
   cwal_engine_vtab.
 */
int e_init_engine(cwal_engine *e, cwal_engine_vtab * vtab){

    int32_t featureFlags = 0;
    MARKER("cwal_engine_vtab::hook::on_init() callback. e=%p state=%p\n",
           (void const *)e, vtab->hook.init_state);
    JsonOutOpt.addNewline = 1;
    if(1){
        /*Enable tracing.*/
        int32_t flags = CWAL_TRACE_NONE;
        flags |= CWAL_TRACE_ERROR_MASK;
        if(App.enableTracing){
            flags |=
                CWAL_TRACE_VALUE_MASK
                | CWAL_TRACE_SCOPE_MASK
                ;
            if(App.enableTracing>1){
                flags |= CWAL_TRACE_ENGINE_MASK;
                flags |= CWAL_TRACE_MEM_MASK;
                flags |= CWAL_TRACE_FYI_MASK;
            }
        }
        cwal_engine_trace_flags( e, flags );
    }

    if(App.enableScopeHashes){
        MARKER("Enable hashes as scope property storage.\n");
        featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH;
    }
    if(App.enableStringInterning){
        /* Enable auto-interning of strings:

           Costs a lot of memory but is pretty cool.
        */
        featureFlags |= CWAL_FEATURE_INTERN_STRINGS;
    }

    
    /* rc = cwal_engine_feature_flags(e,-1); */
    cwal_engine_feature_flags(e, /* rc |  */featureFlags );


#define REMAX(T,N) cwal_engine_recycle_max( e, CWAL_TYPE_ ## T, (N) )
    REMAX(UNDEF,0);
    if(App.enableValueRecycling){
        /* A close guess based on the post-20141129 model...
           List them in "priority order," highest priority last.
           Lower prio ones might get trumped by a higher prio one.
        */
        REMAX(UNIQUE,10)/* will end up being trumped by integer (32-bit)
                           or double (64-bit) */;
        REMAX(KVP,30) /* guaranteed individual recycler */;
        REMAX(WEAK_REF,5) /* guaranteed individual recycler */;
        REMAX(STRING,30) /* guaranteed individual recycler */;
        REMAX(EXCEPTION,3);
        REMAX(HASH,5) /* might also include: function, native */;
        REMAX(BUFFER,5) /* might also include: object */ ;
        REMAX(XSTRING,5/*also Z-strings, might also include doubles*/);
        REMAX(NATIVE,5)  /* might also include: function, hash */;
        REMAX(DOUBLE,20)/* might also include z-/x-strings */;
        REMAX(FUNCTION,20) /* might include: hash, native */;
        REMAX(ARRAY,20);
        REMAX(OBJECT,20) /* might include: buffer */;
        REMAX(INTEGER,40) /* might include: double */;
    }
#undef REMAX
    return 0;
}


static int my_scope_hook_push( cwal_scope * s,
                               CWAL_UNUSED_VAR void * clientState ){
    MARKER("Pushing scope level %d\n", (int)s->level);
    return 0;
}
static void my_scope_hook_pop( cwal_scope const * s,
                              CWAL_UNUSED_VAR void * clientState ){
    MARKER("Popping scope level %d\n", (int)s->level);
    if(1==s->level){
        MARKER("pop hook: the engine is shutting down.\n");
    }
}

void e_main(){
    cwal_engine_vtab vtab = cwal_engine_vtab_basic;
    cwal_engine E = cwal_engine_empty;
    cwal_engine * e =
        1 ? &E : 0;
    int rc;
    JsonOutOpt.cyclesAsStrings = 0;
    assert(cwal_output_f_FILE == vtab.outputer.output);
    assert(cwal_finalizer_f_fclose == vtab.outputer.state.finalize);
    vtab.outputer.state.data = stdout;
    vtab.tracer = cwal_engine_tracer_FILE;
    vtab.tracer.state = stdout;
    vtab.hook.on_init = e_init_engine;
    vtab.hook.init_state = e;
    if(App.logScopePushPop){
        vtab.hook.scope_push = my_scope_hook_push;
        vtab.hook.scope_pop = my_scope_hook_pop;
    }

    if(1){
        if(0) vtab.memcap.maxSingleAllocSize = 48;
        if(0) vtab.memcap.maxTotalAllocCount = 10;
        vtab.memcap.maxConcurrentMem = 1024 * 50;
    }

    rc = cwal_engine_init( &e, &vtab );
    RC;

    cwal_outputf( e, "e_main(): hi, world!\n" );

#if 0
    cwal_new_string( e, "hi, world!", 10 );
    cwal_new_string( e, "foo", 3 )
        /* for checking if interning is working. */;
#endif

    { /* weak reference sanity check... */
        cwal_value * v = cwal_new_integer(e,
                                          10000/*some value which will allocate*/);
        cwal_weak_ref * r = cwal_weak_ref_new(v);
        cwal_weak_ref * r2 = cwal_weak_ref_new(v);
        assert(r);
        assert(v == cwal_weak_ref_value(r));
        assert(r==r2);
        cwal_value_unref(v);
        assert(!cwal_weak_ref_value(r));
        cwal_weak_ref_free(e, r);
        cwal_weak_ref_free(e, r2);

        r = cwal_weak_ref_new(cwal_value_undefined());
        assert(r);
        assert(cwal_weak_ref_new(cwal_value_undefined())==r);
        cwal_weak_ref_free(e, r);
    }

#if CWAL_VOID_PTR_IS_BIG
    /*
       The double-type assertions in e_basic_assertions() are failing
       on my 32-bit box even though the values are provably correct
       (meaning that printf() shows the expected values).
    */
    if(1) e_call( e, e_basic_assertions,
                  "basic assertions");
#endif
    if(1) e_call( e, e_ptr_table,
                  "ptr_table stuff");
    if(1) e_call( e, e_interned_strings,
                  "interned strings tests..." );
    if(1) e_call( e, e_random_strings,
                  "random strings test..." );
    if(0) goto end;
    if(1) e_call( e, e_json1,
                  "cwal_json_output()..." );
    if(1) e_call( e, e_object2,
                  "Basic Object cycle of death...");
    if(1) e_call( e, e_object,
                  "Object basics...");
    if(1) e_call( e, e_relaunch1,
                  "Cycle-mania...");
    if(1) e_call( e, e_comparisons,
                  "Value comparison...");
    if(1) e_call( e, e_function,
                  "function() calls...");
    if(1) e_call( e, e_exception,
                  "exception lifetime...");
    if(1) e_call( e, e_prototypes,
                  "prototypes...");
    if(1) e_call( e, e_sweeping1,
                  "alloc/sweep loops...");
    if(1) e_call( e, e_buffer_format,
                  "cwal_buffer_format()...");
#if 0
    if(1) e_call( e, e_utf8_char_next,
                  "cwal_utf8_char_next()...");
#endif
    if(1) e_call( e, e_unique,
                  "cwal_new_unique()...");

    MARKER("Shutting down engine. All values (except maybe the "
           "exception/result values) should have been cleaned "
           "up by now-dead scopes by now.\n");

    end:
    if(e->interned.pg.head){
        cwal_dump_interned_strings_table( e, 1, 30 );
    }
    if(App.showMetrics){
        cwal_dump_allocation_metrics( e );
    }
    rc = cwal_engine_destroy( e );
    RC;
}

void show_sizeofs(){
    cwal_size_t total = 0;
    cwal_engine e = cwal_engine_empty;
    MARKER("Various library-level sizeof()s...\n");
#define C(M) MARKER(#M"=%u\n", M);
    C(CWAL_SIZE_T_BITS);
    C(CWAL_INT_T_BITS);
    C(CWAL_VOID_PTR_IS_BIG);
    
#undef C
#define SO(T) total += sizeof(T); MARKER("sizeof(%s)=%u\n", #T, (unsigned int)sizeof(T))
    SO(void*);
    SO(cwal_array);
    SO(cwal_buffer);
    SO(cwal_callback_args);
    SO(cwal_double_t);
    SO(cwal_engine);
    SO(e.metrics);
    SO(e.recycler);
    SO(e.weakr);
    SO(cwal_engine_vtab);
    SO(cwal_int_t);
    SO(cwal_kvp);
    SO(cwal_list);
    SO(cwal_memchunk_overlay);
    SO(cwal_native);
    SO(cwal_obase);
    SO(cwal_object);
    SO(cwal_ptr_page);
    SO(cwal_ptr_table);
    SO(cwal_recycler);
    SO(cwal_scope);
    SO(cwal_size_t);
    SO(cwal_state);
    SO(cwal_string);
    SO(cwal_tuple);
    SO(cwal_value);
    SO(cwal_value_vtab);
    SO(cwal_weak_ref);
    SO(e.metrics);
    SO(e.recycler);
    SO(e.memcap);
#undef SO
    MARKER("sizeof() total: %u\n", (unsigned)total );
}

#define TEST_CASE_FOLD_ASSERTIONS 0
#if TEST_CASE_FOLD_ASSERTIONS
/*
  Testing whether all tolower/toupper characters have the same
  byte-length, as that would allow an in-place case-folding algo.
  They don't :(. There are conversions which have varying sizes
  when folded :(.
*/
static int utf8_char_len(int ch){
    if(ch<=0x7f) return 1;
    else if(ch <= 0x7FF) return 2;
    else if(ch <= 0xffff) return 3;
    return 4;
}
static void test_case_fold_assertions(){
    int c = 0;
    int l = 0;
    int u = 0;
    int count = 0, mismatch = 0;
    const int max = 0x10ffff;
    for( ; c <= max; ++c ){
        l = cwal_utf8_char_tolower(c);
        u = cwal_utf8_char_toupper(c);
        if(l!=u){
            ++count;
            if(utf8_char_len(l) != utf8_char_len(u)){
                ++mismatch;
                MARKER("lower/upper size mismatch for char 0x%05x l=0x%05x (%d) and u=0x%05x (%d)!\n",
                       c, l, utf8_char_len(l), u, utf8_char_len(u));
            }
        }
    }
    MARKER("Checked %d case-folding cases for byte length equivalence, with %d mismatches.\n",
           count, mismatch);
}
#endif

int main(int argc, char const * const * argv)
{
    int i;
    char showSizes = 0;
    for( i = 1; i < argc; ++i ){
        char const * arg = argv[i];
        if(0==strcmp("-S",arg)){
            App.enableStringInterning = !App.enableStringInterning;
            continue;
        }
        else if(0==strcmp("-R",arg)){
            App.enableValueRecycling = !App.enableValueRecycling;
            continue;
        }
        else if(0==strcmp("-z",arg)){
            showSizes = 1;
            continue;
        }
        else if(0==strcmp("-tt",arg)){
            App.enableTracing = 2;
            continue;
        }
        else if(0==strcmp("-t",arg)){
            App.enableTracing = 1;
            continue;
        }
        else if(0==strcmp("-m",arg)){
            App.showMetrics = 1;
            continue;
        }
        else if(0==strcmp("-h",arg)){
            App.enableScopeHashes = 1;
            continue;
        }
    }

    /*test_toker();*/
#if TEST_CASE_FOLD_ASSERTIONS
    test_case_fold_assertions();
#else
    e_main();
#endif
    if(showSizes) show_sizeofs();
    MARKER("Done!\n");
    return 0;
}