Login
th1ish_amalgamation.c at [0ab8c75c49]
Login

File th1ish/th1ish_amalgamation.c artifact 339ce7c374 part of check-in 0ab8c75c49


#if !defined(__STDC_FORMAT_MACROS) /* required for PRIi32 and friends.*/
#  define __STDC_FORMAT_MACROS
#endif
#include "th1ish_amalgamation.h"
/* start of file ../cwal_amalgamation.c */
#if !defined(__STDC_FORMAT_MACROS) /* required for PRIi32 and friends.*/
#  define __STDC_FORMAT_MACROS
#endif
#include "string.h" /*memset()*/
#include "assert.h"
/* start of file cwal_internal.h */
#if !defined(WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED)
#define WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED 1
/**
   This file documents the internal/private API of libcwal.  It's
   strictly internal parts are only in the main implementation file,
   but the parts in this file are thought to potentially be useful in
   future sub-systems which live somewhere between the private API and
   public API (not quite public, not quite core).
*/

#if defined(__cplusplus)
extern "C" {
#endif

/**
   A callback type for use with cwal_ptr_table_visit(). The elem
   argument is the entry being visited over. The state argument is
   whatever state the caller passed to cwal_ptr_table_visit().

   If a visitor function returns non-0, iteration ceases and that
   result code is returned to the caller of cwal_ptr_table_visit().
*/
typedef int (*cwal_ptr_table_visitor_f)( cwal_value ** elem, void * state );

/** @internal
   Operation values for cwal_ptr_table_op().
*/
enum cwal_ptr_table_ops {
    CWAL_PTR_TABLE_OP_REMOVE = -1,
    CWAL_PTR_TABLE_OP_SEARCH = 0,
    CWAL_PTR_TABLE_OP_INSERT = 1
};
typedef enum cwal_ptr_table_ops cwal_ptr_table_ops;

/** @internal

  Initializes a new pointer table. *T may be NULL, in which case this
  function allocates a new cwal_ptr_table. If *T is not NULL then it is
  assumed to be a freshly-allocated, non-initialized pointer table
  and is initialized as-is.

  Returns 0 on error. On success *T is set to the ptr table (possibly
  freshly allocated) and ownership is transfered to the caller, who
  must eventually call cwal_ptr_table_destroy() to free its resources.

 */
int cwal_ptr_table_create( cwal_engine * e, cwal_ptr_table ** T, uint16_t hashSize, uint16_t step );
/** @internal

  Frees all resources owned by t. t must have been initialized by
  cwal_ptr_table_create(), and if that function allocated t, then t
  will also be free()'d, otherwise the caller must free t using the
  method which complements its allocation technique (e.g. do nothing
  for stack-allocated values).

  Returns 0 on success.
*/
int cwal_ptr_table_destroy( cwal_engine * e, cwal_ptr_table * t );
/** @internal

  Performs a search/remove/insert operation (specified by the op
  parameter) on the given ptr table. Returns 0 on success.

  The valid operations are:

  CWAL_PTR_TABLE_OP_REMOVE: remove the given key from the table.
  
  CWAL_PTR_TABLE_OP_SEARCH: return 0 if the table already has such
  an entry.

  CWAL_PTR_TABLE_OP_INSERT: insert the given key into the
  table. Returns CWAL_RC_ALREADY_EXISTS if key is already in the
  table.

*/
int cwal_ptr_table_op( cwal_engine * e, cwal_ptr_table * t, void * key, cwal_ptr_table_ops op );
/** @internal
   Equivalent to cwal_ptr_table_op(e,t,key,CWAL_PTR_TABLE_OP_SEARCH).
*/
int cwal_ptr_table_search( cwal_engine * e, cwal_ptr_table * t, cwal_value * key );
/** @internal
   Equivalent to cwal_ptr_table_op(e,t,key,CWAL_PTR_TABLE_OP_REMOVE).
*/
int cwal_ptr_table_remove( cwal_engine * e, cwal_ptr_table * t, cwal_value * key );
/** @internal
   Equivalent to cwal_ptr_table_op(e,t,key,CWAL_PTR_TABLE_OP_INSERT).
*/
int cwal_ptr_table_insert( cwal_engine * e, cwal_ptr_table * t, cwal_value * key );
/** @internal

    Calculates the amount of memory allocated for use with the given
    table.  The results are stored in *mallocs (the total number of
    memory allocation calls) and *memory (the total number of bytes
    allocated for use with t). Either of mallocs or memory may be
    NULL. This is an O(N) operation, where N is the number of pages
    currently managed by t.
 */
int cwal_ptr_table_mem_cost( cwal_ptr_table const * t, uint32_t * mallocs, uint32_t * memory );
/** @internal

    "Visits" each entry in it, calling the given callback for each one
    (NULL entries in the table are skipped - only "live" entries are
    iterated over). The callback is passed a pointer to a pointer to
    the original entry, and if that pointer is re-assigned, that change
    is written back to the table when the visitor returns. (Why? i'm not
    sure why it was done that way - it is a dangerous feature and should
    not be used.)

    Returns 0 on success. if the visitor returns non-0, that code is
    returned to the caller of this function.
*/
int cwal_ptr_table_visit( cwal_ptr_table * t, cwal_ptr_table_visitor_f f, void * state );


    
/** @internal
 
   Strings are allocated as an instances of this class with N+1
   trailing bytes, where N is the length of the string being
   allocated. To convert a cwal_string to c-string we simply increment
   the cwal_string pointer. To do the opposite we use (cstr -
   sizeof(cwal_string)). Zero-length strings are a special case
   handled by a couple of the cwal_string functions.

   The top-most 2 bits of the length field are reserved for
   flags. This is how we mark "x-strings" (external strings, held in
   memory allocated elsewhere (e.g. static globals or client-interned
   strings).
*/
struct cwal_string {
    cwal_size_t length;
};

/** @internal

    Internal state flags. Keep these at 16 bits or adjust various
    flags types accordingly! Note that some flags apply to only to
    particular types, and thus certain flags might have the same
    values to conserve bit-space (if we can reduce it to 8 bits,
    great, because that allows us to reasonably use the top 8 bits of
    the refcount for cwal_value::flags, removing the flags member to
    save a few bytes).
*/
enum CWAL_FLAGS {
/**
   Sentinel value for empty flags.
 */
  CWAL_F_NONE = 0,
  /**
     Used by cwal_value_vtab::flags to indicate that the type contains
     a cwal_obase as its first struct member and therefore qualifies
     as an "obase" (object base) sub-type. This is a casting-related
     optimization.

     "OBases" must meet these requirements:


     - It is allocated using the library-conventional single-malloc
     method of (cwal_value,concrete_type). See cwal_value_new() for
     the many examples.
                
     - Its concrete value type has a cwal_object struct as its first
     member. This, in combination with the first requirement, allows
     us (due to an interest C rule) to efficiently/safely cast
     directly between a cwal_value, its "object type," and a
     convenience base type (cwal_obase) which is used in many internal
     algorithms to consolidate code for container handling. And... we
     don't hold an extra pointer (or two) for this - it comes for free
     as a side-effect of these first 2 requirements.

     See cwal_object and cwal_array for examples of using the obase
     flag. Note that cwal_object and cwal_array actually have
     identical signatures and _could_ be packed into a single type,
     but by the time that became possible (in terms of API evolution)
     i already had the type-specific signatures in place (and now
     prefer them for the public API for type-safety reasons).
  */
  CWAL_F_ISA_OBASE = 0x01,

  /**
     An experiment in protecting client-specified container values
     (and those they contain/reference) from vacuuming.
  */
  CWAL_F_IS_VACUUM_SAFE = 0x02,

  /**
     A sanity-checking flag places on values in the GC queue.
  */
  CWAL_F_IS_GC_QUEUED = 0x08,
  
  /**
     Is used as an extra safety mechanism to avoid a double-delete if
     the refcounts-with-cycles-counting mechanism breaks. This only
     means that we convert a double free() into a leak (or an assert()
     in debug builds).

     Currently only used for cwal_obase subtypes, but could in
     principal be used as a cwal_engine/cwal_scope flag.
  */
  CWAL_F_IS_DESTRUCTING = 0x10,

  /**
     Is set on container values during the various visit() APIs to
     detect/break cycles during iteration over a container.

     Currently used for cwal_obase types.
  */
  CWAL_F_IS_VISITING = 0x20,

  /**
     A sanity-checking flag set on values in a recycle bin.
  */
  CWAL_F_IS_RECYCLED = 0x40
};

   
/** @internal
    Convenience typedef. */
typedef struct cwal_obase cwal_obase;

/** @internal
   "Base" type for types which contain other values (which means they
   are subject to cycles). An instance of this struct must be embedded
   at the FIRST MEMBER of any such class, and any type-specific
   members must come after that. See cwal_array and cwal_object for
   examples.

   Note that the interpretation of the list member is type-dependent.
   e.g. cwal_array stores (cwal_value*) whereas cwal_object uses
   (cwal_kvp*).
 */
struct cwal_obase {
    /**
       Linked list of key/value pairs held by this object.
    */
    cwal_kvp * kvp;
    /**
       The (experimental) "prototype" (analog to a parent class).
    */
    cwal_value * prototype;
    /**
       Internal flags.

       Maintenance note: due to struct padding, this can be either
       16 or 32-bits with no real change in struct size.
    */
    uint32_t flags;

    /**
       We might want/need to add a counter for recursion levels, to
       allow us to be able to recurse through an object while
       recursion is already in place. Currently we use the
       CWAL_F_IS_VISITING flag to mark objects being recursed over,
       but that only allows one level of recursion, and disallows
       constructs which need to enter looping on a given container
       more than once. Here's a non-sensical example which we cannot
       currently (directly) support:

       forEach( key in myObj ) {
         forEach( key2 in myObj ){
         }
       }

       That will currently be reported as CWAL_RC_CYCLES_DETECTED.

       We might be able to solve this by adding an "allow recursion"
       flag which will allow _one_ level of recursion. When recursion
       is encountered, disable the flag. It could be set from, e.g.,
       the forEach implementation before looping over
       myObj. Alternately, a counter, and in the container traversal
       code we check if that counter is equal to our current recursion
       depth (but we'd need to keep that value somewhere... where?).
    */
};
/** @internal
    Empty-initialized cwal_obase object. */
#define cwal_obase_empty_m { NULL/*kvp*/, NULL/*prototype*/, CWAL_F_NONE/*flags*/ }

/** @internal
   Concrete value type for Arrays.
*/
struct cwal_array {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    /**
       Holds (cwal_value*) and NULL entries ARE semantically legal.
    */
    cwal_list list;
};
#define cwal_array_empty_m { cwal_obase_empty_m, cwal_list_empty_m }
extern const cwal_array cwal_array_empty;

/** @internal
    The metadata for concrete Object values.
 */
struct cwal_object {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
};
#define cwal_object_empty_m { cwal_obase_empty_m }
extern const cwal_object cwal_object_empty;

/**
   Information for binding a native function to the
   script engine in the form of a Function value.
*/
struct cwal_function {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    cwal_state state;
    cwal_callback_f callback;
};

/**
   Empty-initialized cwal_function object.
*/
#define cwal_function_empty_m { cwal_obase_empty_m, cwal_state_empty_m, NULL }
/**
   Empty-initialized cwal_function object.
*/
extern const cwal_function cwal_function_empty;

/**
   Concrete type for generic error/exception values.
*/
struct cwal_exception {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    int code;
};
#define cwal_exception_empty_m { cwal_obase_empty_m, -1 }
extern const cwal_exception cwal_exception_empty;
/**
   A key/value pair of cwal_values. While key is a cwal_value, the
   engine requires that it be-a CWAL_TYPE_STRING (cwal_string).

   Each of these objects owns its key/value pointers, and they
   are cleaned up by cwal_kvp_clean().

   TODO: make this type a doubly-linked list for: a) so we can get rid
   of cwal_list in cwal_object and b) we can use it in place of a
   HashEntry in the planned cwal_hashtable type (pending hashing
   support in the cwal_value_vtab interface).
*/
struct cwal_kvp{
    /**
       The key. Keys are compared using cwal_value_vtab::compare().
    */
    cwal_value * key;
    /**
       Arbitrary value. Objects do not have NULL values - a literal
       NULL means to "unset" a property. cwal_value_null() can be
       used as a value, of course.
    */
    cwal_value * value;
    /** Right-hand member in a linked list. */
    cwal_kvp * right;
    /**
       We need this for intepreter-level flags like "const" and
       "hidden/non-iterable."
    */
    uint16_t flags;
};

#define cwal_kvp_empty_m {NULL,NULL,NULL,0U/*flags*/}
extern const cwal_kvp cwal_kvp_empty;

/** @internal
   Allocates a new cwal_kvp object, owned by e. Returned NULL on
   error. On success the returned value is empty-initialized.
*/
cwal_kvp * cwal_kvp_alloc(cwal_engine *e);

/** @internal
   Unrefs kvp->key and kvp->value and sets them to NULL, but does not
   free kvp. If !kvp then this is a no-op.
*/
void cwal_kvp_clean( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp );
    
/** @internal
   Calls cwal_kvp_clean(e,fromScope,kvp) and then either adds it to
   the recycle bin or frees it, depending on the value of allowRecycle
   and the capacity/size of the associated recycler list.

   Callers must treat this call as if kvp is free()d by it, whether or
   not this function actually does so.

   fromScope should be the owning scope of the container on behalf of
   whom kvp is being freed. It may be 0, but this is semantically
   legal only in a few select cases.
*/
void cwal_kvp_free( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp, char allowRecycle );

    
/**
   Typedef for cwal_value hashing functions. Must return a hash value
   for the given value.
*/
typedef cwal_hash_t (*cwal_value_hash_f)( cwal_value const * v );

/**
   Returns v's hash value, as computed by v's vtab.
   Returns 0 if !v.
*/
cwal_hash_t cwal_value_hash( cwal_value const * v );


    
/**
   Typedef for cwal_value comparison functions. Hash memcmp()
   semantics.  Ordering of mismatched types is type-dependent,
   possibly undefined. Implementations of this function are
   type-specific and require that the lhs (left-hand-side) argument be
   of their specific type (and are permitted to assert that that is
   so). When performing conversions, they should treat the LHS as the
   primary type for conversion/precision purposes. e.g. comparison of
   (int 42) and (double 42.24) might be different depending on which
   one is the LHS because of changes in precision.

   Beware that these comparisons are primarily intended for
   cwal-internal use (e.g. in the context of hashtables), and are not
   strictly required to follow the semantics of any given scripting
   environment or specificiation.

   Where (lhs,rhs) do not have any sort of natural ordering,
   implementations should return any value other than 0, implementing
   ECMAScript-like semantics if feasible.
   
   Implementations are encouraged to do cross-type comparisons where
   feasible (e.g. "123"==123 is true), and to delegate to the converse
   comparison (swapping lhs/rhs and the return value) when the logic
   for the comparison is non-trivial and already implemented for the
   other type. Strict-equality comparisons (e.g. "123"===123 is false)
   are handled at a higher level which compares type IDs and/or
   pointers before passing the values on to this function.

*/
typedef int (*cwal_value_compare_f)( cwal_value const * lhs, cwal_value const * rhs );
    
/**
   This type holds the "vtbl" for type-specific operations when
   working with cwal_value objects.

   All cwal_values of a given logical type share a pointer to a single
   library-internal instance of this class.
*/
struct cwal_value_vtab
{
    /**
       The logical JavaScript/JSON type associated with
       this object.
     */
    const cwal_type_id typeID;

    /**
       A descriptive type name intented primarily for debuggering, and
       not (necessarily) as a type's name as a client script language
       might see/name it.
     */
    char const * typeName;
    /**
       Internal flags.
    */
    uint16_t flags;

    /**
       Must free any memory owned by the second argument, which will
       be a cwal_value of the concrete type associated with this
       instance of this class, but not free the second argument (it is
       owned by the engine and may be recycled). The API shall never
       pass a NULL value to this function.

       The API guarantees that the scope member of the passed-in value
       will be set to the value's pre-cleanup owning scope, but also
       that the value is not in the scope value list at that time. The
       scope member is, however, needed for proper unreferencing of
       child values (which still live in a scope somewhere, very
       possibly the same one).
    */
    cwal_finalizer_f cleanup;

    /**
       Must return a hash value for this value. Hashes are used only as
       a quick comparison, with the compare() method being used for
       a more detailed comparison.
    */
    cwal_value_hash_f hash;

    /**
       Must perform a comparison on its values. The cwal engine guarantees that the
       left-hand argument will be of the type managed by the instance of the
       cwal_value_tab on which this is called, but the right-hand argument may
       be of an arbitrary type.

       This function checks for equivalence, not identity, and uses
       memcmp() semantics: less than 0 means that the left-hand
       argument is "less than" the right, 0 means they are equivalent,
       and 1 means the the lhs is "greater than" the right. It is
       understood, of course, that not all comparisons have meaningful
       results, and implementations should always return non-0 for
       meaningless comparisons. They are not required to return a
       specific value but should behave consistently (e.g. not
       swapping the order on every call or some such sillynesss).

       For many non-POD types, 0 can only be returned when both
       pointers have the same address (that said, the framework should
       short-circuit any such comparison itself).
    */
    cwal_value_compare_f compare;

    /**
       Called by the framework when it determines that v needs to be
       "upscoped." The framework will up v but cannot generically know
       if v contains any other values which also need to be upscoped,
       so this function has the following responsibilities:

       For any child values, this function must call
       cwal_value_rescope( v->scope->e, v->scope, child ). That, in
       turn, will trigger the recursive rescoping of any children of
       that value. Note that the rescoping problem is not subject to
       "cyclic misbehaviours" - the worst that will happen is a cycle
       gets visited multiple times, but those after the first will be
       no-ops because no re-scoping is necessary.

       The framework guarantees the following:

       Before this is called on a value, the following preconditions exist:

       - v->scope has been set to the "proper" scope by e. This
       function will never be called when v->scope is 0.

       - v->scope->e points to the active engine.

       - This is only called when/if v is actually upscoped. Future
       re-scope requests which do not require a rescope will not
       trigger a call to this function.

       If v owns any "unmanaged" child values (e.g. not kept in the
       cwal_obase base class container or as part of a cwal_array)
       then those must be rescoped by this function (the framework
       does not know about them). They likely also need to be
       made vacuum-proof (see cwal_value_make_vacuum_proof()).

       Classes which do not contain other values may set this member
       to 0. It is only called if it is not NULL. Classes which do
       hold other values _must_ implement it (properly!).

       Must return 0 on success and "truly shouldn't fail" because all
       it does it pass each child on to cwal_value_rescope(), which
       cannot fail if all arguments are in order and the internal
       state of the values seems okay (the engine does a good deal of
       extra checking). However, if it fails it should return an
       appropriate value from the cwal_rc enum.
    */
    int (*rescope_children)( cwal_value * v );


    /**
       TODOs:

       // Using JS semantics for true/value
       char (*bool_value)( cwal_value const * self );

       // To-string ops...
       unsigned char const * to_byte_array( cwal_value const * self, cwal_size_t * len );
       // which is an optimized form of ...
       int to_string( cwal_value const * self, cwal_engine * e, cwal_buffer * dest );
       // with the former being used for (Buffer, String, builtins)
       // and the latter for everything else. Either function may be 0
       // to indicate that the operation is not supported
       // (e.g. Object, Array, Hashtable, Function...).

       // The problem with adding an allocator is...
       int (*allocate)( cwal_engine *, ??? );
       // If we split it into allocation and initialization, might
       // that solve it? Or cause more problems?
       // cwal_value * (*allocate)( cwal_engine * e );
       // int (*initialize)( cwal_engine * e, cwal_value * v, ... )
       

       // Deep copy.
       int (*clone)( cwal_engine *e, cwal_value const * self, cwal_value ** tgt );

       // Property interceptors:
       int (*get)( cwal_engine *e, cwal_value const * self,
                   cwal_value const * key, cwal_value **rv );

       int (*set)( cwal_engine *e, cwal_value * self,
                   cwal_value * key, cwal_value *v );

       But for convenience the get() op also needs a variant taking a
       c-string key (otherwise the client has to create values when
       searching, more often than not)
    */
    
};

typedef struct cwal_value_vtab cwal_value_vtab;
/**
   Empty-initialized cwal_value_vtab object.
*/
#define cwal_value_vtab_empty_m {           \
        CWAL_TYPE_UNDEF/*typeID*/,         \
        ""/*typeName*/, \
        0U/*flags*/, \
        NULL/*cleanup()*/,                    \
        NULL/*hash()*/,                    \
        NULL/*compare()*/,             \
        NULL/*rescope_children()*/ \
      }
/**
   Empty-initialized cwal_value_vtab object.
*/
extern const cwal_value_vtab cwal_value_vtab_empty;

/**
   cwal_value represents an opaque Value handle within the cwal
   framework. Values are all represented by this basic type but they
   have certain polymorphic behaviours and many concrete types have
   high-level handle representations.

   @see cwal_new_VALUE()
*/
struct cwal_value {
    /** The "vtbl" of type-specific operations. All instances
        of a given logical value type share a single api instance.

        Results are undefined if this value is NULL or points to the
        incorrect vtab instance.
    */
    cwal_value_vtab const * vtab;

    /**
       The "current owner" scope of this value. Its definition is
       as follows:

       When a value is initially allocated its owner is the
       currently-active scope. If a value is referenced by a
       higher-level (older) scope, it is migrated into that scope
       (recursively for containers) so that we can keep cleanup of
       cycles working (because all values in a given graph need to be
       in the same scope for cleanup to work).
     */
    cwal_scope * scope;

    /**
       The left-hand-link for a linked list. Who exactly owns a value,
       and which values they own, are largely handled via this
       list. Each manager (e.g. scope, recycle bin, or gc queue) holds
       the head of a list and adds/removes the entries as needed.

       Note that we require two links because if we only have a single
       link then cleanup of a member in a list can lead traversal
       through that list into places it must not go (like into the
       recycler's memory).

       Design notes: in the original design each value-list manager
       had a separate list to manage its values. Switching to this
       form (where the values can act as a list) actually (perhaps
       non-intuitively) cuts both the number of overall allocations
       and memory cost, converts many of the previous operations from
       O(N) to O(1), and _also_ removes some unrecoverable error cases
       caused by OOM during manipulation of the owner's list of
       values. So it's an overall win despite the cost of having 2
       pointers per value. Just remember that it's not strictly
       Value-specific overhead... it's overhead Scopes and other Value
       owners would have if this class didn't.
    */
    cwal_value * left;

    /**
       Right-hand link of a linked list.
    */
    cwal_value * right;

    /**
       We use this to allow us to store cwal_value instances in
       multiple containers or multiple times within a single container
       (provided no cycles are introduced).

       Notes about the rc implementation:

       - Instances start out with a refcount of 0 (not 1). Adding them
       to a container will increase the refcount. Cleaning up the container
       will decrement the count. cwal_value_ref() can be used to obtain
       a reference when no container is handy.

       - cwal_value_unref_from() decrements the refcount (if it is not
       already 0) and cleans/frees the value only when the refcount is
       0 (and then it _might_ not destroy the value immediately,
       depending on which scope owns it and where (from which scope)
       its refcount goes to 0).
    */
    cwal_size_t refcount;

    /*
      Potential TODO: if we _need_ flags in this class, we can use the
      high byte (or half-byte) of refcount and restrict refcount to
      the lower bytes (possibly only 3 resp. 3.5). We need to make
      sure refcount uses a 32-bit type in that case, as opposed to
      cwal_size_t (which can be changed to uint16_t, which only gives
      us a range of up to 4k if we reserve 4 bits!). While 4k might
      initially sound like a reasonable upper limit for refcounts,
      practice has shown that value prototypes tend to accumulate lots
      and lots of references (one for each value which has it as a
      prototype), so 4kb quickly becomes a real limit.

      Class-level flags are set in the associated cwal_value_vtab
      instance. Container classes may have their own instance-specific
      flags.

      Reminder to self: interpreters are going to need flags for
      marking special values like constants/non-removable. Oh, but
      that's going to not like built-in constants. So we'll need to
      tag these at the cwal_kvp level (which costs us less, actually,
      because we have one set of flags per key/value pair, instead of
      per Value instance).
    */
};


/**
   Empty-initialized cwal_value object.
*/
#define cwal_value_empty_m { \
    &cwal_value_vtab_empty/*api*/,\
    0/*scope*/,\
    0/*left*/,\
    0/*right*/,                             \
    0/*refcount*/ \
}
/**
   Empty-initialized cwal_value object.
*/
extern const cwal_value cwal_value_empty;


struct cwal_native {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    void * native;
    void const * typeID;
    cwal_finalizer_f finalize;
    /**
       If this member is non-NULL then it is called to allow
       the native to rescope properties not visible via its property
       list.
    */
    cwal_native_rescoper_f rescope_children;
};
#define cwal_native_empty_m {\
        cwal_obase_empty_m, \
        0/*native*/,\
        0/*typeID*/,\
        0/*finalize*/, \
        0/*rescope_children*/ \
        }
extern const cwal_native cwal_native_empty;

struct cwal_hash {
    /**
       base MUST be the first member for casting reasons.
    */
    cwal_obase base;
    /**
       Array of cwal_kvp values this->hashSize entries long,
       containing this->entryCount entries.
    */
    cwal_kvp ** list;
    cwal_size_t hashSize;
    cwal_size_t entryCount;
};
#define cwal_hash_empty_m {\
    cwal_obase_empty_m,                     \
    0/*list*/,                          \
    27/*hashSize*/, \
    0/*entryCount*/                             \
    }
extern const cwal_hash cwal_hash_empty;

/** @internal

    Internal impl of the weak reference class.
*/
struct cwal_weak_ref {
    void * value;
    cwal_type_id typeID;
    cwal_size_t refcount;
    struct cwal_weak_ref * next;
};
/** @internal

    Initialized-with defaults cwal_weak_ref instance.
*/
#define cwal_weak_ref_empty_m {NULL, CWAL_TYPE_UNDEF, 0U, NULL}
/** @internal

    Initialized-with defaults cwal_weak_ref instance.
*/
extern const cwal_weak_ref cwal_weak_ref_empty;

    
/** @internal

    If v is-a obase then its obase part is returned, else NULL is
    returned.
*/
cwal_obase * cwal_value_obase( cwal_value * v );

/** @internal

    If v is-a obase then this function sets/unsets the is-visiting
    flag on its obase part and returns CWAL_RC_OK, else it returns
    CWAL_RC_TYPE.

    This should be called just prior to the start of any traversal
    into an obase's children, passing a value of 1 for isVisiting.  It
    should be called, passing 0 for isVisiting, immediately after the
    traversal returns.
*/
int cwal_value_set_visiting( cwal_value * v, char isVisiting );

/** @internal

   Internal debuggering function which MIGHT dump some useful info
   about v (not recursively) to some of the standard streams.

   Do not rely on this function from client code. 

   The File/Line params are expected to be the __FILE__/__LINE__
   macros.

   If msg is not NULL then it is included in the output (the exact
   placement, beginning or end, is unspecified).
 */
void cwal_dump_value( char const * File, int Line,
                      cwal_value const * v, char const * msg );

/** @def cwal_dump_v(V,M)

    Internal debuggering macro which calls cwal_dump_value() with the
    current file/line/function info.
*/
#if 1
#  define cwal_dump_v(V,M) cwal_dump_value(__func__,__LINE__,(V),(M))
#else
#  define cwal_dump_v(V,M) assert(1)
#endif
                                    

/**
   Searches e for an internalized string matching (zKey,nKey). If
   found, it returns 0 and sets any of the output parameters which are
   not NULL.

   On success, *out (if out is not NULL) be set to the value matching
   the key. Note that only String values can compare equal here, even
   if the key would normally compare as equivalent to a value of
   another type. e.g. 1=="1" when using cwal_value_compare(), but
   using that comparison here would not be possible unless we
   allocated a temporary string to compare against.
   
   It sets *itemIndex (if not NULL) to the index in the strings table
   for the given key, regardless of success of failure. The other
   output params are only set on success.

   *out (if not NULL) is set to the value in the table.  pageIndex (if
   *not NULL) is set to the page in which the entry was found.

   Reminder to self: we might be able to stack-allocate an x-string
   around zKey and do a cwal_value-keyed search on that. That would
   work around the (1!="1") inconsistency.
*/
int cwal_interned_search( cwal_engine * e, char const * zKey, cwal_size_t nKey,
                          cwal_value ** out, cwal_ptr_page ** pageIndex, uint16_t * itemIndex );

/**
   Equivalent to cwal_interned_search() except that it takes a
   cwal_value parameter and uses cwal_value_compare() for the hashing
   comparisons. A cwal String value inserted this way _will_ compare
*/
int cwal_interned_search_val( cwal_engine * e, cwal_value const * v,
                              cwal_value ** out, cwal_ptr_page ** pageIndex,
                              uint16_t * itemIndex );
/**
   Removes the string matching (zKey,nKey) from e's interned
   strings table. If an entry is found 0 is returned and
   *out (if not NULL) is set to the entry.

   Returns CWAL_RC_NOT_FOUND if no entry is found.
*/
int cwal_interned_remove( cwal_engine * e, cwal_value const * v, cwal_value ** out );

/**
   Inserts the given value, which must be-a String, into
   e's in interned strings list. Returns 0 on success.
   Returns CWAL_RC_ALREADY_EXISTS if the entry's string
   value is already in the table.
   
*/
int cwal_interned_insert( cwal_engine * e, cwal_value * v );

/**
   Pushes the given cwal_value into e->gc for later
   destruction. We do this to avoid prematurely stepping on a
   being-destroyed container when visiting cycles.

   If insertion of p into the gc list fails then this function frees
   it immediately. We "could" try to stuff it in the recycle bin, but
   that would only potentially delay the problem (of stepping on a
   freed value while cycling).

   This function asserts that e->gcLevel is not 0.
*/
int cwal_gc_push( cwal_engine * e, cwal_value * p );

/**
   Passes all entries in e->gc to cwal_value_recycle() for recycling
   or freeing.
*/
int cwal_gc_flush( cwal_engine * e );

/**
   If v->vtab->typeID (T) is of a recyclable type and e->recycler entry
   number cwal_recycler_index(T) has space, v is put there, otherwise
   it is cwal_free()'d. This is ONLY to be called when v->refcount
   drops to 0 (in place of calling cwal_free()), or results are
   undefined.

   If e->gcLevel is not 0 AND v is-a obase then v is placed in e->gc
   instead of being recycled so that the destruction process can
   finish to completion without getting tangled up in the recycle bin
   (been there, debugged that). That is a bit of a shame, actually,
   but the good news is that cwal_gc_flush() will try to stick it back
   in the recycle bin. Note that non-Objects do not need to be
   delayed-destroyed because they cannot contribute to cycles.
   
   Returns 0 if the value is freed immediately, 1 if it is recycled,
   and -1 if v is placed in e->gc. If insertion into e->gc is called
   for fails, v is freed immediately (and 0 is returned). (But since
   e-gc is now a linked list, insertion cannot fail except on internal
   mis-use of the GC bits.)
*/
int cwal_value_recycle( cwal_engine * e, cwal_value * v );

/**
   Tracing macros.
 */
#if CWAL_ENABLE_TRACE
#define CWAL_ETR(E) (E)->trace
#define CWAL_TR_SRC(E) CWAL_ETR(E).cFile=__FILE__; CWAL_ETR(E).cLine=__LINE__; CWAL_ETR(E).cFunc=__FUNCTION__
#define CWAL_TR_MSG(E,MSG) CWAL_ETR(E).msg = MSG; if((MSG) && *(MSG)) CWAL_ETR(E).msgLen = strlen(MSG)
#define CWAL_TR_EV(E,EV) CWAL_ETR(E).event = (EV);
/*if(!(CWAL_ETR(E).msg)) { CWAL_TR_MSG(E,#EV); }*/

#define CWAL_TR_RC(E,RC) CWAL_ETR(E).code = (RC)
#define CWAL_TR_V(E,V) CWAL_ETR(E).value = (V);
#define CWAL_TR_MEM(E,M,SZ) CWAL_ETR(E).memory = (M); CWAL_ETR(E).memorySize = (SZ)
#define CWAL_TR_S(E,S) CWAL_ETR(E).scope = (S)
#define CWAL_TR_SV(E,S,V) CWAL_TR_V(E,V); CWAL_TR_S(E,S)
#define CWAL_TR_VCS(E,V) CWAL_TR_V(E,V); CWAL_TR_S(E,(E)->current)
#define CWAL_TR3(E,EV,MSG)                                              \
    if(MSG && *MSG) { CWAL_TR_MSG(E,MSG); } \
    CWAL_TR_EV(E,EV);                                                \
    if(!(CWAL_ETR(E).scope)) {                                          \
        if(CWAL_ETR(E).value) CWAL_ETR(E).scope = CWAL_ETR(E).value->scope; \
        if(!(CWAL_ETR(E).scope)) CWAL_ETR(E).scope=(E)->current;        \
    }                                                                   \
    CWAL_TR_SRC(E); \
    cwal_trace(E)
#define CWAL_TR2(E,EV) CWAL_TR3(E,EV,"")
#else
#define CWAL_ETR(E)
#define CWAL_TR_SRC(E)
#define CWAL_TR_MSG(E,MSG)
#define CWAL_TR_EV(E,EV)
#define CWAL_TR_RC(E,RC)
#define CWAL_TR_V(E,V)
#define CWAL_TR_S(E,S)
#define CWAL_TR_MEM(E,M,SZ)
#define CWAL_TR_SV(E,S,V)
#define CWAL_TR_VCS(E,V)
#define CWAL_TR3(E,EV,MSG)
#define CWAL_TR2(E,EV)
#endif
void cwal_trace( cwal_engine * e );

typedef struct cwal_hash_entry cwal_hash_entry;

/**
   @internal
       
   cwal_value_take() "takes" a value away from its owning scope,
   transfering the scope's reference count point to the caller,
   removing the value from any list it is currently in, and settings
   its scope to NULL.

   On error non-0 is returned and ownership is not modified.

   This function works only on "managed" values (with an owning scope)
   because the API has not yet defined how to create/manage unmanged
   values from client code.

   Each allocated value, regardless of its reference count, has only
   one reference from its parent scope, and this function effectively
   steals that reference count from the owning scope, effectively
   transfering that one reference point to the caller.  This function
   must not be passed the same value instance more than once unless
   the value has been re-scoped since calling this (it will fail on
   subsequent calls).

   In all cases, if this function returns 0 the caller effectively
   receives ownership of 1 reference point for the value.

   Note that adding the "taken" value to a container will re-scope it
   as part of the process.
       
   Error conditions:

   CWAL_RC_RANGE: means that v has no owning scope.  This
   constellation is highly unlikely but "could happen" if the API ever
   evolves to allow "unscoped" values (not sure how GC could work
   without the scopes, though).
   
   @see cwal_value_unref()   
*/
int cwal_value_take( cwal_engine * e, cwal_value * v );

    
/* LICENSE

This software's source code, including accompanying documentation and
demonstration applications, are licensed under the following
conditions...

Certain files are imported from external projects and have their own
licensing terms. Namely, the JSON_parser.* files. See their files for
their official licenses, but the summary is "do what you want [with
them] but leave the license text and copyright in place."

The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/])
explicitly disclaims copyright in all jurisdictions which recognize
such a disclaimer. In such jurisdictions, this software is released
into the Public Domain.

In jurisdictions which do not recognize Public Domain property
(e.g. Germany as of 2011), this software is Copyright (c) 2011 by
Stephan G. Beal, and is released under the terms of the MIT License
(see below).

In jurisdictions which recognize Public Domain property, the user of
this software may choose to accept it either as 1) Public Domain, 2)
under the conditions of the MIT License (see below), or 3) under the
terms of dual Public Domain/MIT License conditions described here, as
they choose.

The MIT License is about as close to Public Domain as a license can
get, and is described in clear, concise terms at:

    http://en.wikipedia.org/wiki/MIT_License

The full text of the MIT License follows:

--

Copyright (c) 2011-2014 Stephan G. Beal
(http://wanderinghorse.net/home/stephan/)

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

--END OF MIT LICENSE--

For purposes of the above license, the term "Software" includes
documentation and demonstration source code which accompanies
this software. ("Accompanies" = is contained in the Software's
primary public source code repository.)

*/

#if defined(__cplusplus)
} /*extern "C"*/
#endif

#endif /* WANDERINGHORSE_NET_CWAL_INTERNAL_H_INCLUDED */
/* end of file cwal_internal.h */
/* start of file cwal.c */
#include <assert.h>
#include <stdlib.h> /* malloc()/free() */
#include <string.h>
#include <errno.h>
#include <stdarg.h>

#define CWAL_KVP_TRY_SORTING 0
#if CWAL_KVP_TRY_SORTING
#include <stdlib.h> /* qsort() */
#endif

#if 1
#include <stdio.h>
#define MARKER(pfexp)                                                \
    do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
        printf pfexp; \
    } while(0)
#else
#define MARKER(exp) if(0) printf
#endif

#if defined(__cplusplus)
extern "C" {
#endif


/** Convenience typedef.
    typedef enum cwal_e_options cwal_e_options; */
    

/*
  Public-API constant/shared objects.
*/
const cwal_array cwal_array_empty = cwal_array_empty_m;
const cwal_buffer cwal_buffer_empty = cwal_buffer_empty_m;
const cwal_callback_args cwal_callback_args_empty = cwal_callback_args_empty_m;
const cwal_callback_hook cwal_callback_hook_empty = cwal_callback_hook_empty_m;
const cwal_engine cwal_engine_empty = cwal_engine_empty_m;
const cwal_engine_tracer cwal_engine_tracer_empty = cwal_engine_tracer_empty_m;
const cwal_engine_vtab cwal_engine_vtab_empty = cwal_engine_vtab_empty_m;
const cwal_exception cwal_exception_empty = cwal_exception_empty_m;
const cwal_exception_info cwal_exception_info_empty = cwal_exception_info_empty_m;
const cwal_function cwal_function_empty = cwal_function_empty_m;
const cwal_hash cwal_hash_empty = cwal_hash_empty_m;
const cwal_kvp cwal_kvp_empty = cwal_kvp_empty_m;
const cwal_list cwal_list_empty = cwal_list_empty_m;
const cwal_native cwal_native_empty = cwal_native_empty_m;
const cwal_object cwal_object_empty = cwal_object_empty_m;
const cwal_output_buffer_state cwal_output_buffer_state_empty = {NULL/*e*/, NULL/*b*/};
const cwal_ptr_table cwal_ptr_table_empty = cwal_ptr_table_empty_m;
const cwal_recycler cwal_recycler_empty = cwal_recycler_empty_m;
const cwal_scope cwal_scope_empty = cwal_scope_empty_m;
const cwal_state cwal_state_empty = cwal_state_empty_m;
const cwal_trace_state cwal_trace_state_empty = cwal_trace_state_empty_m;
const cwal_value_vtab cwal_value_vtab_empty = cwal_value_vtab_empty_m;

#define cwal_string_empty_m {0U/*length*/}
static const cwal_string cwal_string_empty = cwal_string_empty_m;

#if 0
const cwal_var cwal_var_empty = cwal_var_empty_m;
#endif
const cwal_engine_tracer cwal_engine_tracer_FILE = {
    cwal_engine_tracer_f_FILE,
    cwal_engine_tracer_close_FILE,
    0
};
const cwal_allocator cwal_allocator_empty =
    cwal_allocator_empty_m;
const cwal_allocator cwal_allocator_std = {
    cwal_realloc_f_std,
    cwal_state_empty_m
};
const cwal_outputer cwal_outputer_empty =
    cwal_outputer_empty_m;
const cwal_outputer cwal_outputer_FILE = {
    cwal_output_f_FILE,
    cwal_output_flush_f_FILE,
    cwal_state_empty_m
};
#if 0
const cwal_outputer cwal_outputer_buffer = {
    cwal_output_f_buffer,
    NULL,
    cwal_state_empty_m
};
#endif

const cwal_engine_vtab cwal_engine_vtab_basic = {
    { /*allocator*/
        cwal_realloc_f_std,
        cwal_state_empty_m
    },
    {/* outputer */
        cwal_output_f_FILE,
        cwal_output_flush_f_FILE,
        cwal_state_empty_m
    },
    cwal_engine_tracer_empty_m,
    cwal_state_empty_m/*state*/,
    {/*hook*/
        NULL/*on_init()*/,
        NULL/*init_state*/
    },
    {/*interning*/
        cwal_cstr_internable_predicate_f_default/*is_internable()*/,
        NULL/*state*/
    }
};

/**
   CwalConsts holds some library-level constants and default values
   which have no better home.
*/
static const struct {
    /** The minimum/default hash size used by cwal_ptr_table. */
    const uint16_t MinimumHashSize;
    /** The minimum step/span used by cwal_ptr_table. */
    const uint16_t MinimumStep;
    /** Default length of some arrays on the first memory reservation
        op.
    */
    const uint16_t InitialArrayLength;
    /**
       Used as the "allocStamp" value for values which the library
       allocates, in order to differentiate them from those allocated
       via stack or being embedded in another object. Should point to
       some library-internal static/constant pointer (any one will
       do).
     */
    void const * const AllocStamp;
    /**
       Largest allowable size for cwal_size_t values in certain
       contexts (e.g. cwal_value::refcount).  If it gets this high
       then something is probably very wrong or the client is trying
       too hard to push the boundaries.
    */
    const cwal_size_t MaxSizeTCounter;
    /**
       If true then newly-create cwal_string instances will be
       auto-interned. All strings with the same bytes will be shared
       across a single cwal_string instance.
    */
    char AutoInternStrings;
    /**
       Property name used to store the message part of an Exception
       value.
    */
    char const * ExceptionMessageKey;

    /**
       Property name used to store the code part of an Exception
       value.
    */
    char const * ExceptionCodeKey;

    /**
       If >0, this sets the maximum size for interning strings. Larger
       strings will not be interned.
    */
    cwal_size_t MaxInternedStringSize;

    /**
       The maximum length of string to recycle. Should be relatively
       small unless we implement a close-fit strategy for
       recycling. Currently we can only recycle strings for use with
       the exact same size string.
    */
    cwal_size_t MaxRecycledStringLen;

    /**
       If StringPadSize is not 0...

       When strings are allocated, their sizes are padded to be evenly
       divisible by this many bytes. When recycling strings, we use
       this padding to allow the strings to be re-used for any similar
       length which is within 1 of these increments.

       Expected to be an even value, relatively small (under 32, in any
       case).

       Tests have shown 4-8 to be good values, saving anywhere from a
       few percent to 36%(!!!) of the total allocations in the th1ish
       test scripts (compared to a value of 0). Switching from 0 to 4
       gives a notable improvement on the current test scripts. 8 or
       12 don't reduce allocations all that much compared to 4, and
       _normally_ (not always) cost more total memory.
    */
    cwal_size_t StringPadSize;
} CwalConsts = {
    13 /* MinimumHashSize. Chosen almost arbitrarily, weighted towards
          small memory footprint per table. */,
    sizeof(void*) /* MinimumStep. */,
    6 /*InitialArrayLength, starting length of various arrays */,
    &cwal_engine_vtab_empty/*AllocStamp - any internal ptr will do.*/,
#if 16 == CWAL_SIZE_T_BITS
    (cwal_size_t)0xCFFF/*MaxSizeTCounter*/,
#else
    (cwal_size_t)0xCFFFFFFF/*MaxSizeTCounter*/,
#endif
    0/*AutoInternStrings*/,
    "message"/*ExceptionMessageKey*/,
    "code"/*ExceptionCodeKey*/,
    32/*MaxInternedStringSize*/,
    32/*MaxRecycledStringLen*/,
    8/*StringPadSize*/
};

const cwal_weak_ref cwal_weak_ref_empty = cwal_weak_ref_empty_m;

    /**
   Internal impl for cwal_value_unref().
*/
static int cwal_value_unref2(cwal_engine * e, cwal_value *v );
/**
   Internal impl for cwal_value_ref().
*/
static int cwal_value_ref2( cwal_engine *e, cwal_value * cv );

/**
   Does nothing. Intended as a cwal_value_vtab::cleanup handler
   for types which need not cleanup.

   TODO: re-add the zeroing of such values (lost in porting from
   cson).
*/
static void cwal_value_cleanup_noop( cwal_engine * e, void * self );
/**
   Requires that self is-a CWAL_TYPE_INTEGER cwal_value. This function
   zeros its numeric value but goes not free() self.
*/
static void cwal_value_cleanup_integer( cwal_engine * e, void * self );
/**
   Requires that self is-a CWAL_TYPE_DOUBLE cwal_value. This function
   zeros its numeric value but goes not free() self.
*/
static void cwal_value_cleanup_double( cwal_engine * e, void * self );
/**
   Requires that self is-a cwal_array. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_array( cwal_engine * e, void * self );
/**
   Requires that self is-a cwal_object. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_object( cwal_engine * e, void * self );
/**
   Requires that self is-a cwal_native. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_native( cwal_engine * e, void * self );
/**
   Requires that self is-a cwal_string. This function removes the
   string from e's intering table.
*/
static void cwal_value_cleanup_string( cwal_engine * e, void * self );

/**
   Requires that self is-a cwal_buffer. This function calls
   cwal_buffer_reserve() to clear the memory owned by the buffer.
*/
static void cwal_value_cleanup_buffer( cwal_engine * e, void * self );
/**
   Requires that self is-a cwal_function. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_function( cwal_engine * e, void * self );
/**
   Requires that self is-a cwal_exception. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_exception( cwal_engine * e, void * self );

/**
   Requires that self is-a cwal_hash. This function destroys its
   contents but goes not free() self.
*/
static void cwal_value_cleanup_hash( cwal_engine * e, void * V );
    
/*
  Hash/compare routines for cwal_value_vtab.
*/
static cwal_hash_t cwal_value_hash_null_undef( cwal_value const * v );
static cwal_hash_t cwal_value_hash_bool( cwal_value const * v );
static cwal_hash_t cwal_value_hash_int( cwal_value const * v );
static cwal_hash_t cwal_value_hash_double( cwal_value const * v );
static cwal_hash_t cwal_value_hash_string( cwal_value const * v );
static cwal_hash_t cwal_value_hash_ptr( cwal_value const * v );
static int cwal_value_cmp_bool( cwal_value const * lhs, cwal_value const * rhs );
#if 0
static int cwal_value_cmp_val_bool( cwal_value const * lhs, cwal_value const * rhs );
#endif
static int cwal_value_cmp_int( cwal_value const * lhs, cwal_value const * rhs );
static int cwal_value_cmp_double( cwal_value const * lhs, cwal_value const * rhs );
static int cwal_value_cmp_string( cwal_value const * lhs, cwal_value const * rhs );
/* static int cwal_value_cmp_type_only( cwal_value const * lhs, cwal_value const * rhs ); */
static int cwal_value_cmp_ptr_only( cwal_value const * lhs, cwal_value const * rhs );
static int cwal_value_cmp_nullundef( cwal_value const * lhs, cwal_value const * rhs );
static int cwal_value_cmp_func( cwal_value const * lhs, cwal_value const * rhs );
static int cwal_value_cmp_buffer( cwal_value const * lhs, cwal_value const * rhs );


/*
  Rescope-children routines for container types.
*/
static int cwal_rescope_children_obase( cwal_value * v );
static int cwal_rescope_children_array( cwal_value * v );
static int cwal_rescope_children_native( cwal_value * v );
static int cwal_rescope_children_hash( cwal_value * v );

/*
  Internal "API" (a.k.a. "VTab") instances. One per core type.
*/
static const cwal_value_vtab cwal_value_vtab_null =
    { CWAL_TYPE_NULL, "null",
      CWAL_F_NONE,
      cwal_value_cleanup_noop,
      cwal_value_hash_null_undef,
      cwal_value_cmp_nullundef,
      /* cwal_value_cmp_val_bool, */
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_undef =
    { CWAL_TYPE_UNDEF, "undefined",
      CWAL_F_NONE,
      cwal_value_cleanup_noop,
      cwal_value_hash_null_undef,
      cwal_value_cmp_nullundef,
      /* cwal_value_cmp_val_bool, */
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_bool =
    { CWAL_TYPE_BOOL, "bool",
      CWAL_F_NONE,
      cwal_value_cleanup_noop,
      cwal_value_hash_bool,
      cwal_value_cmp_bool,
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_integer =
    { CWAL_TYPE_INTEGER, "integer",
      CWAL_F_NONE,
      cwal_value_cleanup_integer,
      cwal_value_hash_int,
      cwal_value_cmp_int,
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_double =
    { CWAL_TYPE_DOUBLE, "double",
      CWAL_F_NONE,
      cwal_value_cleanup_double,
      cwal_value_hash_double,
      cwal_value_cmp_double,
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_string =
    { CWAL_TYPE_STRING, "string",
      CWAL_F_NONE,
      cwal_value_cleanup_string,
      cwal_value_hash_string,
      cwal_value_cmp_string,
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_array =
    { CWAL_TYPE_ARRAY, "array",
      CWAL_F_ISA_OBASE,
      cwal_value_cleanup_array,
      cwal_value_hash_ptr,
      cwal_value_cmp_ptr_only,
      cwal_rescope_children_array
    };
static const cwal_value_vtab cwal_value_vtab_object =
    { CWAL_TYPE_OBJECT, "object",
      CWAL_F_ISA_OBASE,
      cwal_value_cleanup_object,
      cwal_value_hash_ptr,
      cwal_value_cmp_ptr_only,
      cwal_rescope_children_obase
    };
static const cwal_value_vtab cwal_value_vtab_native =
    { CWAL_TYPE_NATIVE, "native",
      CWAL_F_ISA_OBASE,
      cwal_value_cleanup_native,
      cwal_value_hash_ptr,
      cwal_value_cmp_ptr_only,
      cwal_rescope_children_native
    };
static const cwal_value_vtab cwal_value_vtab_hash =
    { CWAL_TYPE_HASH, "hash",
      CWAL_F_ISA_OBASE,
      cwal_value_cleanup_hash,
      cwal_value_hash_ptr,
      cwal_value_cmp_ptr_only,
      cwal_rescope_children_hash
    };
static const cwal_value_vtab cwal_value_vtab_buffer =
    { CWAL_TYPE_BUFFER, "buffer",
      CWAL_F_NONE,
      cwal_value_cleanup_buffer,
      cwal_value_hash_ptr,
      cwal_value_cmp_buffer,
      NULL/*rescope_children()*/
    };
static const cwal_value_vtab cwal_value_vtab_function =
    { CWAL_TYPE_FUNCTION, "function",
      CWAL_F_ISA_OBASE,
      cwal_value_cleanup_function,
      cwal_value_hash_ptr,
      cwal_value_cmp_func,
      cwal_rescope_children_obase
    };
static const cwal_value_vtab cwal_value_vtab_exception =
    { CWAL_TYPE_EXCEPTION, "exception",
      CWAL_F_ISA_OBASE,
      cwal_value_cleanup_exception,
      cwal_value_hash_ptr,
      cwal_value_cmp_ptr_only,
      cwal_rescope_children_obase
    };

/**
   Internal constant cwal_values, used for copy-initialization of new
   Value instances.
*/
static const cwal_value cwal_value_undef = { &cwal_value_vtab_undef, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_null_empty = { &cwal_value_vtab_null, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_bool_empty = { &cwal_value_vtab_bool, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_integer_empty = { &cwal_value_vtab_integer, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_double_empty = { &cwal_value_vtab_double, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_string_empty = { &cwal_value_vtab_string, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_array_empty = { &cwal_value_vtab_array, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_object_empty = { &cwal_value_vtab_object, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_native_empty = { &cwal_value_vtab_native, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_hash_empty = { &cwal_value_vtab_hash, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_buffer_empty = { &cwal_value_vtab_buffer, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_function_empty = { &cwal_value_vtab_function, NULL, NULL, NULL, 0 };
static const cwal_value cwal_value_exception_empty = { &cwal_value_vtab_exception, NULL, NULL, NULL, 0 };

/*
  Internal convenience macros...
*/

/**
   Cast ((cwal_value*)V) to (T*). It assumes the caller knows WTF he
   is doing. Evaluates to 0 if !(V).

   This op relies on the fact that memory (V+1) contains a (T)
   value. i.e. it relies on the allocation mechanism used by
   cwal_value_new() and that V was allocated by that function, is a
   builtin/shared value instance, or is NULL.
*/
#define CWAL_VVPCAST(T,V) ((V) ? ((T*)((V)+1)) : 0)

/**
   CWAL_VALPART(CONCRETE)

   Cast (concrete_value_type*) CONCRETE to a
   (cwal_value*). CWAL_VALPART() relies on the fact that ALL cwal_value
   types which use it are allocated in a single memory chunk with
   (cwal_value,concrete_type), in that order. Do not use this macro
   for types which are not allocated that way.

   AFAIK, it is also legal for CONCRETE to be (cwal_obase*), provided
   that pointer was allocated as part of one of the container types
   (object, array, etc.). This relies on them having their (cwal_obase
   base) member as the first member of the struct.

   e.g., assuming an Object value:

   cwal_obase * base = CWAL_VOBASE(val);
   cwal_object * obj = cwal_value_get_object(val);
   assert( CWAL_VALPART(base) == val );
   assert( base == &obj->base );
*/

#if 1
#define CWAL_VALPART(CONCRETE) ((CONCRETE) ? ((cwal_value *)(((unsigned char *)(CONCRETE))-sizeof(cwal_value))) : 0)
#else
/* i'm not convinced that this one is standards-conformant.
   But it looks nicer.
*/
#define CWAL_VALPART(CONCRETE) ((CONCRETE) ? (((cwal_value *)(CONCRETE))-1) : 0)
#endif

/**
   CWAL_INT(V) casts CWAL_TYPE_INTEGER (cwal_value*) V to a
   (cwal_int_t*).
*/
#define CWAL_INT(V) (CWAL_VVPCAST(cwal_int_t,(V)))

/**
   Requires that V be one of the special cwal_values TRUE or FALSE.
   Evaluates to 1 if it is the TRUE value, else false.
*/
#define CWAL_BOOL(V) ((&CWAL_BUILTIN_VALS.vTrue==(V)) ? 1 : 0)

/**
   CWAL_DBL(V) casts CWAL_TYPE_DOUBLE (cwal_value*) V to a (cwal_double_t*).
*/
#define CWAL_DBL(V) CWAL_VVPCAST(cwal_double_t,(V))

/**
   For cwal_string we store flags in the cwal_string::length member,
   rather than add a flag to them (for memory reasons). We reserve the
   top two and encode the length in the remaining ones. So the maximum
   length of a given String value is 2^(CWAL_SIZE_T_BITS-2).
*/
#if 16 == CWAL_SIZE_T_BITS
    /* Max string length: 16k */
#  define CWAL_STRLEN_MASK 0x3FFFU
#  define CWAL_STR_XMASK 0x8000U
#  define CWAL_STR_ZMASK 0x4000U
#elif 32 == CWAL_SIZE_T_BITS
    /* Max string length: 1GB */
#  define CWAL_STRLEN_MASK 0x3FFFFFFFU
#  define CWAL_STR_XMASK 0x80000000U
#  define CWAL_STR_ZMASK 0x40000000U
#elif 64 == CWAL_SIZE_T_BITS
    /* Max string length: You don't have that much memory. */
#  define CWAL_STRLEN_MASK 0x3FFFFFFFFFFFFFFFU
#  define CWAL_STR_XMASK 0x8000000000000000U
#  define CWAL_STR_ZMASK 0x4000000000000000U
#endif

/**
   CWAL_STR(V) casts CWAL_TYPE_STRING (cwal_value*) V to a (cwal_string*).
*/
#define CWAL_STR(V) (((V) && (V)->vtab && (CWAL_TYPE_STRING==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_string,(V)) : 0)

/**
   Evaluates to true if S (which must be a valid (cwal_string*)) is an
   x-string, else false.
*/
#define CWAL_STR_ISX(S) (CWAL_STR_XMASK & (S)->length)
/**
   Evaluates to true if S (which must be a valid (cwal_string*)) is a
   z-string, else false.
*/
#define CWAL_STR_ISZ(S) (CWAL_STR_ZMASK & (S)->length)
/**
   Evaluates to true if S (which must be a valid (cwal_string*)) is a
   either an x-string or z-string, else false.
*/
#define CWAL_STR_ISXZ(S) (~CWAL_STRLEN_MASK & (S)->length)

/**
   Evaluates to the absolute value of S->length, in bytes, where S
   must be a non-NULL (cwal_string [const]*). This is preferred over
   direct access to S->length because we encode non-size info in the
   length field for X- and Z-strings.
*/
#define CWAL_STRLEN(S) ((cwal_size_t)((S)->length & CWAL_STRLEN_MASK))

/**
   CWAL_OBJ(V) casts CWAL_TYPE_OBJECT (cwal_value*) V to a (cwal_object*).
*/
#define CWAL_OBJ(V) (((V) && (V)->vtab && (CWAL_TYPE_OBJECT==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_object,(V)) : 0)

/**
   CWAL_ARRAY(V) casts CWAL_TYPE_ARRAY (cwal_value*) V to a (cwal_array*).
*/
#define CWAL_ARRAY(V) (((V) && (V)->vtab && (CWAL_TYPE_ARRAY==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_array,(V)) : 0)

/**
   CWAL_HASH(V) casts CWAL_TYPE_HASH (cwal_value*) V to a (cwal_hash*).
*/
#define CWAL_HASH(V) (((V) && (V)->vtab && (CWAL_TYPE_HASH==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_hash,(V)) : 0)

/**
   CWAL_OBASE(O) casts the OBase-type pointer OB to a (cwal_obase*).
   This relies on OB being an obase-compliant type, with a cwal_obase
   member being the first struct member of OB.
*/
#define CWAL_OBASE(OB) ((cwal_obase*)(OB))

/**
   Evaluates to true if ((cwal_value*) V)->vtab has the CWAL_F_ISA_OBASE
   flag, else false.
*/
#define CWAL_V_IS_OBASE(V) ((V) && (V)->vtab && (CWAL_F_ISA_OBASE & (V)->vtab->flags))

/**
   If CWAL_V_IS_OBASE(V), evaluates to V's (cwal_obase*) part, else it
   evaluates to 0. For this to work all "object base" types must have
   a cwal_obase member named 'base' as their VERY FIRST structure
   member because we rely on a C's rule/allowance that a struct can be
   cast to a pointer to its first member.
*/
#define CWAL_VOBASE(V) (CWAL_V_IS_OBASE(V) ? CWAL_VVPCAST(cwal_obase,(V)) : 0)

/**
   Casts CWAL_TYPE_NATIVE value V to (cwal_native*).
*/
#define CWAL_V2NATIVE(V) CWAL_VVPCAST(cwal_native,(V))
/**
   CWAL_BUF(V) casts CWAL_TYPE_BUFFER (cwal_value*) V to a (cwal_buffer*).
*/
#define CWAL_BUF(V) (((V) && (V)->vtab && (CWAL_TYPE_BUFFER==(V)->vtab->typeID)) ? CWAL_VVPCAST(cwal_buffer,(V)) : 0)

/**
   If (cwal_value*) V is not NULL and has a scope, this evaluates to
   its (cwal_engine*), else to 0. Note that built-in values have no
   scope and are not specific to a cwal_engine instance.
*/
#define CWAL_VENGINE(V) ((V) ? ((V)->scope ? (V)->scope->e : 0) : 0)
    
#define CWAL_OBASE_IS_VISITING(OB) (OB ? (CWAL_F_IS_VISITING&(OB)->flags) : 0)
#define CWAL_VAL_IS_VISITING(V) CWAL_OBASE_IS_VISITING(CWAL_VOBASE(V))

#define METRICS_REQ_INCR(E,TYPE) ++(E)->metrics.requested[TYPE]

#define CWAL_OBASE_IS_VACUUM_SAFE(OBASE) (CWAL_F_IS_VACUUM_SAFE & (OBASE)->flags)


/**
  Some "special" shared cwal_value instances.

  Note that they are not const because they are used as
  shared-allocation objects in non-const contexts. However, the public
  API provides no way of modifying them, and clients who modify values
  directly are subject to The Wrath of Undefined Behaviour.
*/
static struct CWAL_BUILTIN_VALS_ {
    /**
       Gets set to a true value when this struct gets initialized by
       cwal_init_empty_values(), to ensure that we only initialize
       this once.
    */
    char inited;
    /**
       Each of the memXXX entries holds the raw memory for a block of
       memory intended for (cwal_value + concrete_type). These are
       initialized by cwal_init_empty_values().
    */
    unsigned char memIntM1[sizeof(cwal_value)+sizeof(cwal_int_t)];
    unsigned char memInt0[sizeof(cwal_value)+sizeof(cwal_int_t)];
    unsigned char memInt1[sizeof(cwal_value)+sizeof(cwal_int_t)];
    unsigned char memDblM1[sizeof(cwal_value)+sizeof(cwal_double_t)];
    unsigned char memDbl0[sizeof(cwal_value)+sizeof(cwal_double_t)];
    unsigned char memDbl1[sizeof(cwal_value)+sizeof(cwal_double_t)];
    unsigned char memEmptyString[sizeof(cwal_value)+sizeof(cwal_string)+1/*NUL byte*/];    
#if 0
    /**
       i would like to store an OOM Exception here to use when
       propagating CWAL_RC_OOM, but then we'd have to add checks to
       all the property setters to ensure they don't modify this
       object (and error if they try, which might surprise clients).
    */
    unsigned char memOomException[sizeof(cwal_value)+sizeof(cwal_exception)];
#endif
    /**
       Each of the vXXX pointers points to memory held in the
       similarly-named memXXX member.
    */
    cwal_value * vIntM1;
    cwal_value * vInt0;
    cwal_value * vInt1;
    cwal_value * vDblM1;
    cwal_value * vDbl0;
    cwal_value * vDbl1;
    cwal_value * vEmptyString;
    /**
       Points to the cwal_string part of this->memEmptyString.
    */
    cwal_string * sEmptyString;
    cwal_value vTrue;
    cwal_value vFalse;
    cwal_value vNull;
    cwal_value vUndef;

    /**
       Integer values -1, 0, and 1.
     */
    struct {
        cwal_int_t mOne;
        cwal_int_t zero;
        cwal_int_t one;
    } ints;

    /**
       Double values -1.0, 0.0, and 1.0.
     */
    struct {
        cwal_double_t mOne;
        cwal_double_t zero;
        cwal_double_t one;
    } dbls;
    
    /**
       Each of the wref.wXXX entries is a shared cwal_weak_ref
       instance pointing to a similarly-named vXXX entry.  We do not
       allocate new cwal_weak_ref instances if they wrap a Value which
       itself is a built-in value. It's hard to imagine a use case for
       someone trying to weak-ref a boolean value, but the generic
       nature of the Value system makes it conceivably possible, so
       here it is...
    */
    struct {
        cwal_weak_ref wTrue;
        cwal_weak_ref wFalse;
        cwal_weak_ref wNull;
        cwal_weak_ref wUndef;
        cwal_weak_ref wStrEmpty;
        cwal_weak_ref wIntM1;
        cwal_weak_ref wInt0;
        cwal_weak_ref wInt1;
        cwal_weak_ref wDblM1;
        cwal_weak_ref wDbl0;
        cwal_weak_ref wDbl1;
    } wref;

} CWAL_BUILTIN_VALS = {
0/*inited*/,
{/*memIntM1*/ 0},
{/*memInt0*/ 0},
{/*memInt1*/ 0},
{/*memDblM1*/ 0},
{/*memDbl0*/ 0},
{/*memDbl1*/ 0},
{/*memEmptyString*/ 0},
NULL/*vIntM1*/,
NULL/*vInt0*/,
NULL/*vInt1*/,
NULL/*vDblM1*/,
NULL/*vDbl0*/,
NULL/*vDbl1*/,
NULL/*vEmptyString*/,
NULL/*sEmptyString*/,
{/*vTrue*/ &cwal_value_vtab_bool, NULL, NULL, NULL, 0 },
{/*vFalse*/ &cwal_value_vtab_bool, NULL, NULL, NULL, 0 },
{/*vNull*/ &cwal_value_vtab_null, NULL, NULL, NULL, 0 },
{/*vUndef*/ &cwal_value_vtab_undef, NULL, NULL, NULL, 0 },
{/*ints*/-1,0,1},
#if CWAL_DISABLE_FLOATING_POINT
{/*dbls*/-1,0,1},
#else
{/*dbls*/-1.0,0.0,1.0},
#endif
{/*wref*/
    cwal_weak_ref_empty_m/* wTrue */,
    cwal_weak_ref_empty_m/* wFalse */,
    cwal_weak_ref_empty_m/* wNull */,
    cwal_weak_ref_empty_m/* wUndef */,
    cwal_weak_ref_empty_m/* wStrEmpty */,
    cwal_weak_ref_empty_m/* wIntM1 */,
    cwal_weak_ref_empty_m/* wInt0 */,
    cwal_weak_ref_empty_m/* wInt1 */,
    cwal_weak_ref_empty_m/* wDblM1 */,
    cwal_weak_ref_empty_m/* wDbl0 */,
    cwal_weak_ref_empty_m/* wDbl1 */
}
};

static void cwal_init_empty_values(){
    struct CWAL_BUILTIN_VALS_ * h = &CWAL_BUILTIN_VALS;
    cwal_value * v;

    {/* Set up empty string */
        memset(h->memEmptyString, 0, sizeof(h->memEmptyString))
            /* ensure that the NUL terminator is set */;
        v = h->vEmptyString = (cwal_value*)h->memEmptyString;
        *v = cwal_value_string_empty;
        h->sEmptyString = CWAL_STR(v);
        assert(h->sEmptyString);
        assert(0==h->sEmptyString->length);
        assert(0==*cwal_string_cstr(h->sEmptyString));
    }

    /* Set up integers (-1, 0, 1) */
#define NUM(N,V) h->vInt##N = v = (cwal_value*)h->memInt##N;      \
    *v = cwal_value_integer_empty; \
    *CWAL_INT(v) = V
    NUM(M1,-1);
    NUM(0,0);
    NUM(1,1);
#undef NUM

    /* Set up doubles (-1, 0, 1) */
#if CWAL_DISABLE_FLOATING_POINT
#define dbl_m1 -1
#define dbl_0 0
#define dbl_1 1
#else
#define dbl_m1 -1.0
#define dbl_0 0.0
#define dbl_1 1.0
#endif
#define NUM(N,V) h->vDbl##N = v = (cwal_value*)h->memDbl##N;      \
    *v = cwal_value_double_empty; \
    *CWAL_DBL(v) = V;
    NUM(M1,dbl_m1);
    NUM(0,dbl_0);
    NUM(1,dbl_1);
#undef NUM
#undef dbl_m1
#undef dbl_0
#undef dbl_1

    {
        cwal_weak_ref * r;
#define REF(N,V) r = &h->wref.N; *r = cwal_weak_ref_empty; \
        r->value = V; r->typeID = (V)->vtab->typeID
        REF(wTrue,&h->vTrue);
        REF(wFalse,&h->vFalse);
        REF(wNull,&h->vNull);
        REF(wUndef,&h->vUndef);
        REF(wStrEmpty,h->vEmptyString);
        REF(wIntM1,h->vIntM1);
        REF(wInt0,h->vInt0);
        REF(wInt1,h->vInt1);
        REF(wDblM1,h->vDblM1);
        REF(wDbl0,h->vDbl0);
        REF(wDbl1,h->vDbl1);
#undef REF
    }

    h->inited = 1
        /* We do this at the end, instead of the start, to cover a
           specific threading corner case: If two (or more) threads
           end up triggering this routine concurrently, we let all of
           them modify the memory.  Because they all set it to the
           same values, we really don't care which one finishes
           first. Once any of them have set h->inited=1,
           CWAL_BUILTIN_VALS contains the memory we want. Even if a
           slower thread is still re-initializing it after a faster
           thread has returned, it is overwriting the contents with
           the same values, so writing them while the faster thread is
           (potentially) using them "should" be harmless.

           Note that this race can only potentially happen once during
           the life of the application, no matter how many
           cwal_engine's he uses.
        */;

}
#define CWAL_INIT_EMPTY_VALUES if(!CWAL_BUILTIN_VALS.inited) cwal_init_empty_values()

/**
   V_IS_BUILTIN(V) determines if the (cwal_value*) V is one of the
   special built-in/constant values. It does so by simply checking if
   its address lies within range of the stack-allocated special-value
   holders, so it's about as fast as it can be.
   

   Maintenance reminders:

   - Profiling shows that cwal_value_is_builtin() is by far the
   most-called routine in the library and accounts for about 1% of the
   total calls in my test app. That is the only reason it has become a
   macro. Client-side code never uses (never really has a need for,
   other than basic curiosity) cwal_value_is_builtin().

*/
#define V_IS_BUILTIN(V) \
    ((((void const *)(V) >= (void const *)&CWAL_BUILTIN_VALS)         \
      && ( (void const *)(V) < (void const *)(&CWAL_BUILTIN_VALS+1))  \
      ) ? 1 : 0)

    
char cwal_value_is_builtin( void const * m )
{
    return V_IS_BUILTIN(m);
}

/**
   Returns the index in cwal_engine::recycler[] for the given
   type ID, or -1 if there is no recycler for that type.
*/
static int cwal_recycler_index( cwal_type_id typeID ){
    /* Something to consider: Now that cwal_recycler::id exists we
       could take a cwal_engine param and walk its list to find the
       type. It would be O(N) instead of O(1) but would also mean this
       function no longer has to be maintained for new types. We use
       this routine very often, though, so it really should be O(1).
    */
    switch( typeID ){
      case CWAL_TYPE_INTEGER: return 0;
      case CWAL_TYPE_DOUBLE: return 1;
      case CWAL_TYPE_XSTRING:
      case CWAL_TYPE_ZSTRING:
      case CWAL_TYPE_STRING:
              /* we only handle x/z-strings via e->reList */
              return 2;
      case CWAL_TYPE_ARRAY: return 3;
      case CWAL_TYPE_OBJECT: return 4;
      case CWAL_TYPE_NATIVE: return 5;
      case CWAL_TYPE_BUFFER: return 6;
      case CWAL_TYPE_FUNCTION: return 7;
      case CWAL_TYPE_EXCEPTION: return 8;
      case CWAL_TYPE_KVP: return 9;
      case CWAL_TYPE_SCOPE: return 10;
      default:
          return -1;
    }
}



/** @internal

   Checks whether child refers to a higher scope than parent, and if
   it does then it moves child to par. If par is 0 this function
   sets *res (if not NULL) to 1 and returns.

   Returns 0 on success and the only error case is if popping child
   from its parent or pushing to its new parent fails (which "cannot
   fail" since the transition from arrays to linked lists for child
   values). It sets res (if not NULL) to one of:

   -1: means that par takes over child, adding it to its list setting
   child->scope to par.

   0: no action is necessary. Child already belongs to par.

   1: child is a built-in, belongs to par, or belongs to a scope with
   a lower scope->level (in which case that scope is its owner). In any
   such case, this function does not modify child's scope.

   Note that this does NOT change child's reference count in any case.
*/
static int cwal_value_xscope( cwal_engine * e, cwal_scope * par, cwal_value * child, int * res );

    
/**
   Internal debuggering routine which dumps out info about the value v
   (which must not be NULL). If msg is provided it is appended to the
   output.
*/
void cwal_dump_value( char const * File, int Line, cwal_value const * v,
                      char const * msg ){
    FILE * out = stdout;
    cwal_obase const * b = CWAL_VOBASE(v);
    if(File && *File && (Line>0)){
        fprintf(out, "%s():%d: ", File, Line );
    }
    fprintf(out, "%s@%p (scope=#%u@%p) refCount=%"CWAL_SIZE_T_PFMT,
            v->vtab->typeName,
            (void*)v,
            v->scope ? (unsigned)v->scope->level : 0U,
            (void*)v->scope,
            v->refcount);
    if(b){
        fprintf( out, " flags=%02x", b->flags );
    }
    if( cwal_value_is_string(v) ){
        cwal_string const * s = cwal_value_get_string(v);
        if(s && CWAL_STRLEN(s)){
            const cwal_size_t len = CWAL_STRLEN(s);
            fprintf( out, " strlen=%"CWAL_SIZE_T_PFMT, len);
            if( len <= 10 ){
                fprintf( out, " bytes=[%s]", cwal_string_cstr(s) );
            }
        }else{
            fprintf( out, " (STILL INITIALIZING?)" );
        }
    }
    else if(cwal_value_is_integer(v)){
        fprintf( out, " int=%"CWAL_INT_T_PFMT,
                 cwal_value_get_integer(v));
    }
    else if(cwal_value_is_double(v)){
        fprintf( out, " double=%"CWAL_DOUBLE_T_PFMT,
                 cwal_value_get_double(v));
    }
    else if(cwal_value_is_bool(v)){
        fprintf( out, " bool=%s",
                 cwal_value_get_double(v) ? "true" : "false");
    }
    if(msg && *msg) fprintf( out, "\t%s\n", msg );
    else fputc( '\n', out );
}

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

/**
   ACHTUNG: careful with type IDs for strings: X/Z strings use e->recycler,
   but non-X/Z-strings use e->reString!
*/
static cwal_recycler * cwal_recycler_get( cwal_engine * e, cwal_type_id t ){
    if(CWAL_TYPE_STRING == t){
        return &e->reString;
    }
    else {
        const int ndx = cwal_recycler_index(t);
        cwal_recycler * re;
        assert(ndx >= 0);
        re = &e->recycler[ndx];
        assert( re->id == t );
        return re;
    }
}

cwal_kvp * cwal_kvp_alloc(cwal_engine *e){
    const int ndx = cwal_recycler_index(CWAL_TYPE_KVP);
    cwal_kvp * kvp;
    cwal_recycler * re;
    assert(ndx>=0);
    ++e->metrics.requested[CWAL_TYPE_KVP];
    re = &e->recycler[ndx];
    if(re->list){
        kvp = (cwal_kvp*)re->list;
        re->list = kvp->right;
        kvp->right = 0;
        --re->count;
    }
    else{
        kvp = (cwal_kvp*)cwal_malloc(e, sizeof(cwal_kvp));
        if(kvp){
            ++e->metrics.allocated[CWAL_TYPE_KVP];
            e->metrics.bytes[CWAL_TYPE_KVP] += sizeof(cwal_kvp);
        }
    }
    if( kvp ) {
        *kvp = cwal_kvp_empty;
    }
    return kvp;
}

/**
   Works similarly to cwal_value_unref2() except that it first checks
   if v lives in a scope higher than p. If it does then this function
   will not destroy it but will remove a reference point (if it has
   one). Destruction will be up to v's owning scope or further
   references. If v->scope==p then this functions just like
   cwal_value_unref2().

   This must be called from ANY container-level unref operations, in
   place of calling cwal_value_unref2(), and it must be passed the
   owning scope of the container on whose behalf the value is being
   unreferenced.

   The purpose of this is to allow the following client-side
   pseudo-script to work:

   (false || {
    var a = {"1":[3,2,1]}
    a.1
    })

    That normally (without this feature) leads to an implicit return
    value which outlives its owning scope. If the return value (a.1 in
    this case) is moved to a higher scope before 'a' is cleaned up,
    this function is there to make sure that the value gets placed
    back into probationary state in the new parent scope, if
    needed. The end effect is that the above implicit return value has
    a refcount of 0 instead of 1 (without this hack the client is
    forced to add a reference), meaning it is back in probationary
    state and can be swept up.
*/
static void cwal_unref_from( cwal_scope * p, cwal_value * v );

void cwal_kvp_clean( cwal_engine * e, cwal_scope * fromScope,
                     cwal_kvp * kvp ){
    if( kvp ){
        cwal_value * key = kvp->key;
        cwal_value * value = kvp->value;
        *kvp = cwal_kvp_empty;
        if(key){
            if(fromScope) cwal_unref_from(fromScope, key);
            else cwal_value_unref2(e, key);
        }
        if(value){
            if(fromScope) cwal_unref_from(fromScope, value);
            else cwal_value_unref2(e, value);
        }
    }
}

void cwal_kvp_free( cwal_engine * e, cwal_scope * fromScope, cwal_kvp * kvp, char allowRecycle ){
    if( kvp ){
        cwal_kvp_clean(e, fromScope, kvp);
        if(allowRecycle){
            int const ndx = cwal_recycler_index(CWAL_TYPE_KVP);
            cwal_recycler * re;
            assert(ndx>=0);
            re = &e->recycler[ndx];
            if(re->count < re->maxLength){
                assert(!kvp->right);
                kvp->right = (cwal_kvp*)re->list;
                re->list = kvp;
                ++re->count;
            }else{
                cwal_free(e, kvp);
            }
        }
        else{
            cwal_free(e, kvp);
        }
    }
}

cwal_value * cwal_kvp_key( cwal_kvp const * kvp ){
    return kvp ? kvp->key : NULL;
}

cwal_value * cwal_kvp_value( cwal_kvp const * kvp ){
    return kvp ? kvp->value : NULL;
}

int cwal_kvp_flags( cwal_kvp const * kvp ){
    return kvp ? (int)kvp->flags : 0;
}


/** @internal

   Searchs for the given key in the given kvp list.

   keyLen MUST be positive, or 0 is returned. This is likely a
   corner-case bug (making an empty string an invalid property), but
   i've just now noticed it the midding of adding
   cwal_scope::mine::headSafe support and i can't change/test this
   right now.

   Returns the found item on success, NULL on error. If ndx is not
   NULL, it is set to the index (in obj->base.kvp) of the found (cwal_kvp*)
   item. *ndx is not modified if no entry is found.  If prev is not
   NULL then prev is set to the returned value's left-hand-side kvp
   from the linked list. a *prev value of NULL indicates that the
   value found is the head of the list (==obj->base.kvp). Note that *prev
   is only set if an entry is found.

   TODO: a variant of this which takes a cwal_string, as an
   optimization: interning will ensure that like strings have the same
   address, which allows us to compare much faster (but we can only
   use that as a contingency, and cannot rely on interning being on).
*/
static cwal_kvp * cwal_kvp_search( cwal_kvp * kvp, char const * key,
                                   cwal_size_t keyLen, cwal_size_t * ndx,
                                   cwal_kvp ** prev){
    if(!kvp || !key || !*key || !keyLen ) return NULL;
    else{
        cwal_size_t i;
        cwal_kvp * left;
        cwal_string const * sKey;
        int cmp;
        for( i = 0, left = 0; kvp;
             left = kvp, kvp = kvp->right, ++i ){
            assert( kvp->key );
            assert(kvp->right != kvp);
            sKey = cwal_value_get_string(kvp->key);
            if(!sKey) continue;
            else if( CWAL_STRLEN(sKey) != keyLen ) continue;
            cmp = memcmp(key,cwal_string_cstr(sKey),keyLen);
            if(0==cmp){
                if(ndx) *ndx = i;
                if(prev) *prev=left;
                return kvp;
            }
#if CWAL_KVP_TRY_SORTING
            else if(cmp<0) break;
#endif
        }
        return 0;
    }
}

/**
   Variant of cwal_kvp_search() which takes a cwal_value key.
*/
static cwal_kvp * cwal_kvp_search_v( cwal_kvp * kvp,
                                     cwal_value const * key,
                                     cwal_size_t * ndx,
                                     cwal_kvp ** prev){
    if(!kvp || !key ) return NULL;
    else{
        cwal_size_t i;
        cwal_kvp * left;
        int cmp;
        for( i = 0, left = 0; kvp;
             left = kvp, kvp = kvp->right, ++i ){
            assert(kvp->key);
            assert(kvp->right != kvp);
            cmp = (key==kvp->key)
                ? 0
                : key->vtab->compare(key, kvp->key);
            if(0==cmp){
                if(ndx) *ndx = i;
                if(prev) *prev=left;
                return kvp;
            }
#if CWAL_KVP_TRY_SORTING
            else if(cmp<0) break;
#endif
        }
        return 0;
    }
}

/**
   Functionally identical to cwal_kvp_unset_v() but takes
   its key in C-string form.
*/
static int cwal_kvp_unset( cwal_engine * e, cwal_scope * fromScope,
                           cwal_kvp **list, char const * key, cwal_size_t keyLen ) {
    if( !e || !list || !key ) return CWAL_RC_MISUSE;
    else if(!*list) return CWAL_RC_NOT_FOUND;
    else {
        cwal_size_t ndx = 0;
        cwal_kvp * left = 0;
        cwal_kvp * kvp;
        if(!keyLen) keyLen = cwal_strlen(key);
        kvp = cwal_kvp_search( *list, key, keyLen, &ndx, &left );
        if( ! kvp ) return CWAL_RC_NOT_FOUND;
        else if(left) left->right = kvp->right;
        else {
            assert(*list==kvp);
            *list = kvp->right;
        }
        kvp->right = NULL;
        cwal_kvp_free( e, fromScope, kvp, 1 );
        return 0;
    }
}

/**
   Unsets a the given key in the given kvp list. Modifies *list to
   point to the new head if key's value is the head of the list.
   fromScope should be the owning scope of the container on whose
   behalf this function is being called - see cwal_unref_from() for
   why.

   Returns 0 on successs.
*/
static int cwal_kvp_unset_v( cwal_engine * e, cwal_scope * fromScope,
                             cwal_kvp **list, cwal_value * key ) {
    if( !e || !list || !key ) return CWAL_RC_MISUSE;
    else if(!*list) return CWAL_RC_NOT_FOUND;
    else {
        cwal_size_t ndx = 0;
        cwal_kvp * left = 0;
        cwal_kvp * kvp;
        kvp = cwal_kvp_search_v( *list, key, &ndx, &left );
        if( ! kvp ) return CWAL_RC_NOT_FOUND;
        if(left) left->right = kvp->right;
        else {
            assert(*list==kvp);
            *list = kvp->right ? kvp->right : NULL;
        }
        kvp->right = NULL;
        cwal_kvp_free( e, fromScope, kvp, 1 );
        return 0;
    }
}


    
/**
   Returns the client-supplied hash table size "trimmed" to some
   "convenient" prime number (more or less arbitrarily chosen by this
   developer - feel free to change/extend this range.
*/
static uint16_t cwal_trim_hash_size( uint16_t hashSize ){
    if(hashSize < CwalConsts.MinimumHashSize) return CwalConsts.MinimumHashSize;
#define P(N) else if( hashSize <= N ) return N
    /* TODO? add more granularity here. */
    P(17); P(37); P(53); P(71);
    P(151); P(211); P(281); P(311);
    P(379); P(433); P(503); P(547);
    P(587); P(613); P(683); P(719);
    P(751); P(1033);
    P(1549); P(2153);
    else return 3163;
#undef P
}

/**
   Returns the client-supplied table step size "trimmed" to some
   "acceptable" range.
*/
static uint16_t cwal_trim_step( uint16_t step ){
   if(step < CwalConsts.MinimumStep) step = CwalConsts.MinimumStep;
   return step;
}


/**
   Allocates a new cwal_ptr_page object, owned by e. Returned NULL on
   error. On success the returned value is empty-initialized.

   The page has n entries (hash table size) and the given step size.

   Both n and step MUST currently be the same as the owning table's
   hash/step sizes, but there are plans to potentially change that,
   allowing new page sizes to grow if collision counts become too
   high.
*/
static cwal_ptr_page * cwal_ptr_page_create( cwal_engine * e, uint16_t n, uint16_t step ){
    const uint32_t asz = (sizeof(void*) * n);
    const uint32_t psz = asz + sizeof(cwal_ptr_page);
    cwal_ptr_page * p = (cwal_ptr_page*)cwal_malloc(e, psz);
    if(p){
        memset( p, 0, psz );
        p->list = (void**)(p+1);
        p->entryCount = 0;
    }
    return p;
}

/**
   Allocates a new page for the given table, using the given step
   value (if 0 then t->step is used).

   Returns CWAL_RC_OK on success. The only conceivable non-bug error
   case here is CWAL_RC_OOM.
*/
static int cwal_ptr_table_add_page( cwal_engine * e, cwal_ptr_table * t,
                                    uint16_t step ){
    cwal_ptr_page * p = cwal_ptr_page_create( e, t->hashSize,
                                              step ? step : t->step );
    if(!p) return CWAL_RC_OOM;
    else {
        cwal_ptr_page * tail = t->pg.tail;
        if(!tail){
            assert(!t->pg.head);
            t->pg.head = t->pg.tail = p;
        }else{
            assert(t->pg.head);
            assert(!tail->next);
            t->pg.tail = tail->next = p;
        }
        assert(t->pg.tail->next != t->pg.head);
        return 0;
    }
}
int cwal_ptr_table_create( cwal_engine * e, cwal_ptr_table ** T,
                           uint16_t hashSize,
                           uint16_t step){
    int rc;
    cwal_ptr_table * t;
    if(!e || !T) return CWAL_RC_MISUSE;
    if(!hashSize) hashSize = cwal_trim_hash_size(hashSize);
    step = cwal_trim_step(step);
    t = *T;
    if(t){
        *t = cwal_ptr_table_empty;
    }else {
        t = (cwal_ptr_table*)cwal_malloc(e, sizeof(cwal_ptr_table));
        if(!t) {
            rc = CWAL_RC_OOM;
            goto error;
        }
        *t = cwal_ptr_table_empty;
        t->allocStamp = CwalConsts.AllocStamp;
    }

    t->hashSize = hashSize;
    t->step = step;
    rc = cwal_ptr_table_add_page( e, t, 0 );
    if(rc) goto error;
    assert( t->pg.head );
    if(!*T) *T = t;
    return CWAL_RC_OK;

    error:
    assert(0 != rc && "You seem to have failed to set an error code!");
    if(t && (t!=*T)){
        cwal_free(e, t);
    }
    return rc;
}

int cwal_ptr_table_destroy( cwal_engine * e, cwal_ptr_table * t ){
    if(!e || !t) return CWAL_RC_MISUSE;
    else{
        void const * stamp = t->allocStamp;
        cwal_ptr_page * p = t->pg.head;
        t->pg.head = t->pg.tail = NULL;
        while( p ){
            cwal_ptr_page * n = p->next;
            cwal_free(e, p);
            p = n;
        }
        *t = cwal_ptr_table_empty;
        if( CwalConsts.AllocStamp == stamp ){
            cwal_free( e, t );
        }else{
            /* Assume t was stack allocated or is part of another
               object.*/
            t->allocStamp = stamp;
        }
        return CWAL_RC_OK;
    }
}

int cwal_ptr_table_visit( cwal_ptr_table * t, cwal_ptr_table_visitor_f f, void * state ){
    if(!t || !f) return CWAL_RC_MISUSE;
    else{
        cwal_size_t i;
        cwal_ptr_page * page = t->pg.head;
        cwal_value ** val;
        cwal_size_t seen;
        int rc = 0;
        for( ; page; page = page->next ){
            seen = 0;
            for( i = 0;
                 (0==rc)
                 && (i < t->hashSize)
                 && (seen < page->entryCount); ++i ){
                val = (cwal_value**)&page->list[i];
                if(!*val) continue;
                ++seen;
                rc = f( val, state );
                page->list[i] = *val;
            }

        }
        return rc;
    }
}


int cwal_ptr_table_mem_cost( cwal_ptr_table const * t,
                             uint32_t * mallocs,
                             uint32_t * memory ){
    enum { SV = sizeof(void*) };
    uint32_t a = 1;
    uint32_t m = sizeof(cwal_ptr_table);
    if(!t) return CWAL_RC_MISUSE;
    else{
        cwal_ptr_page const * p = t->pg.head;
        for( ; p; p = p->next ){
            ++a;
            m += sizeof(cwal_ptr_page)
                + (t->hashSize*SV);
        }
    }
    if(mallocs) *mallocs = a;
    if(memory) *memory = m;
    return CWAL_RC_OK;
}

static uint16_t cwal_ptr_table_hash( void const * key,
                                     uint16_t step,
                                     uint16_t hashSize){
#if CWAL_VOID_PTR_IS_BIG
    /* IF THE DEBUGGER LEADS YOU NEAR HERE...
       try changing ptr_int_t back to uint64_t.
    */
    typedef uint64_t ptr_int_t;
#else
    typedef uint32_t ptr_int_t;
#endif
#if 1
    const ptr_int_t k = ((ptr_int_t)key);
    const uint32_t v1 = (uint32_t)(k / step);
    const uint16_t v2 = v1 % hashSize;
    /*
      MARKER("key=%p step=%u hashSize=%u k=%lu v1=%u v2=%u\n",
            key,   step,   hashSize,   k,    v1,   v2);
    */
    return v2;
#else
    return ((ptr_int_t)key) / step % hashSize;
#endif
}


int cwal_ptr_table_op( cwal_engine * e,
                       cwal_ptr_table * t,
                       void * key,
                       cwal_ptr_table_ops op ){
    cwal_ptr_page * pPage;
    cwal_value * pRet = NULL;
    uint16_t iKey;
    int rc = 0;
    if(!e || !t || !key) return CWAL_RC_MISUSE;
    iKey = cwal_ptr_table_hash( key, t->step, t->hashSize );
    assert( iKey < t->hashSize );
    assert( t->pg.head );
    /*
      TODO?: honor page-specific hashSize values, re-calculating the
      hash value.  if the step value changes while iterating.
     */
    switch( op ){
        /* FIXME: refactor the 3 loops below into one loop at the start.
         */
      case CWAL_PTR_TABLE_OP_SEARCH:
          rc = CWAL_RC_NOT_FOUND;
          for(pPage = t->pg.head; pPage; pPage = pPage->next ){
              pRet = pPage->list[iKey];
              if(!pRet) break;
              else if(pRet == key){
                  rc = CWAL_RC_OK;
                  break;
              }
          }
          break;
      case CWAL_PTR_TABLE_OP_INSERT:{
          assert(t->pg.head);
          rc = CWAL_RC_OK;
          for(pPage = t->pg.head; pPage; pPage = pPage->next ){
              pRet = pPage->list[iKey];
#if 0
              MARKER("COMPARING STASHED %p AGAINST KEY %p\n", (void*)pRet, (void*)key);
#endif
              if(!pRet) goto insert;
              else if(pRet == key){
                  rc = CWAL_RC_ALREADY_EXISTS;
                  break;
              }
          }
#if 0
          MARKER("INSERT NO AVAILABLE SLOT CONTAINS %p. "
                 "ADDING PAGE for hash %u rc=%d=%s\n",
                 (void *)key, iKey, rc, cwal_rc_cstr(rc));
#endif
          if(rc) break;
          /* We reached the end of the tables and found
             no empty slot. Add a page and insert it there. */
          rc = cwal_ptr_table_add_page( e, t, 0 );
          if(rc) break;
          pPage = t->pg.tail;
          insert:
#if 0
          MARKER("INSERTING %p (hash=%u) in list %p\n", (void*)key, iKey, (void *)pPage);
#endif
          rc = CWAL_RC_OK;
          assert(NULL != pPage);
          pPage->list[iKey] = key;
          ++pPage->entryCount;
      } break;
      case CWAL_PTR_TABLE_OP_REMOVE:
          rc = CWAL_RC_NOT_FOUND;
          for(pPage = t->pg.head; pPage; pPage = pPage->next ){
              pRet = pPage->list[iKey];
              if(!pRet) break;
              else if(pRet == key){
                  cwal_ptr_page * prevPage = pPage;
                  assert(pPage->entryCount>0);
                  /* hijack the loop to shift all other entries
                     down... */
                  for(pPage = pPage->next; pPage; pPage = pPage->next ){
                      if(!(prevPage->list[iKey] = pPage->list[iKey])){
                          --prevPage->entryCount;
#if 0
                          /* FIXME: add this when we have a case to test it with. */
                          /* if prevPage is empty, move it towards the back.
                           */
                          if(!prevPage->entryCount){
                              MARKER("MOVING prevPage B/C IT'S EMPTY.\n");
                              assert(prevPage == t->pages.list[i-1]);
                              t->pages.list[i-1] = pPage;
                              t->pages.list[i] = prevPage;
                          }
#endif
                          break;
                      }
                      prevPage = pPage;
                  }
                  pPage = prevPage;
                  pPage->list[iKey] = NULL;
                  if(prevPage == pPage) --pPage->entryCount;
                  rc = CWAL_RC_OK;
#if 0
                  MARKER("REMOVING ENTRY %p for hash %u FROM PAGE #%u\n", (void*)pRet, iKey, x+1);
#endif
                  break;
              }
          }
          break;
    }
    
    return rc;
}

int cwal_ptr_table_search( cwal_engine * e,
                           cwal_ptr_table * t,
                           cwal_value * key ){
    return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_SEARCH );
}
int cwal_ptr_table_insert( cwal_engine * e,
                           cwal_ptr_table * t,
                           cwal_value * key ){
    return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_INSERT );
}
int cwal_ptr_table_remove( cwal_engine * e,
                           cwal_ptr_table * t,
                           cwal_value * key ){
    return cwal_ptr_table_op( e, t, key, CWAL_PTR_TABLE_OP_REMOVE );
}

/**
   Adds an entry in e->weakp for p, initializing e->weakp
   if needed.
*/
static int cwal_weak_annotate( cwal_engine * e, void * p ){
    int rc;
    cwal_ptr_table * pt = &e->weakp;
    if(!pt->pg.head){
        cwal_size_t const hashSize = 31
            /* We don't expect many weak refs, so we'll try
               a small hash size. */;
        rc = cwal_ptr_table_create(e, &pt, hashSize, sizeof(void *) );
        if(rc) return rc;
        assert(pt->pg.head);
    }
    rc = cwal_ptr_table_op( e, pt, p, CWAL_PTR_TABLE_OP_INSERT );
    /* MARKER(("Annotating %s weak ptr @%p insert rc=%d\n",cwal_value_type_id_name(tid),p,rc)); */
    switch(rc){
      case 0:
      case CWAL_RC_ALREADY_EXISTS:
          return 0;
      default:
          return rc;
    }
}

/**
   If p is found in e->weakr[tid] then its (shared) cwal_weak_ref is
   returned, otherwise 0 is returned.
*/
static cwal_weak_ref * cwal_weak_ref_search( cwal_engine * e, void *p, cwal_type_id tid ){
    cwal_weak_ref * r;
    assert(e && p && (tid>=CWAL_TYPE_UNDEF && tid<CWAL_TYPE_end));
    r = e->weakr[tid];
    for( ; r && (r->value != p); r = r->next ){}
    return r;
}

/**
   Removes the entry for p added by cwal_weak_annotate() and
   unsets the value field of each cwal_weak_ref in
   e->weakr[t] where value==p.
*/
static int cwal_weak_unregister( cwal_engine * e, void * p,
                                  cwal_type_id t ){
    assert(p);
    assert(t>=CWAL_TYPE_UNDEF && t<CWAL_TYPE_end);
    if(!e->weakp.pg.head) return 0;
    else{
        
        int const rc = cwal_ptr_table_op( e, &e->weakp, p,
                                          CWAL_PTR_TABLE_OP_REMOVE );
        switch(rc){
            case 0:{
                /* MARKER(("Invalidating %s weak ptr to %p\n",cwal_value_type_id_name(t),p)); */
                cwal_weak_ref * r = cwal_weak_ref_search( e, p, t );
                if(r) r->value = NULL
                    /* because we share instances for any given (p,t)
                       combination, there can be no more matches in the
                       list. */
                    ;
                return 0;
            }
            case CWAL_RC_NOT_FOUND:
                return 0;
            default:
                return rc;
        }
    }
}

char cwal_is_weak_referenced( cwal_engine * e, void * p ){
    return (e && p)
        ? (0==cwal_ptr_table_op( e, &e->weakp, p,
                            CWAL_PTR_TABLE_OP_SEARCH ))
        : 0;
}

/**
   Makes wr the head of the cwal_weak_ref chain starting at
   e->weakr[wr->typeID]. Returns 0 on success, and the only non-bug
   error cases are CWAL_RC_OOM.
*/
static int cwal_weak_ref_insert( cwal_engine * e, cwal_weak_ref * wr ){
    int rc;
    assert(wr && (wr->typeID>=CWAL_TYPE_UNDEF && wr->typeID<CWAL_TYPE_end));
    rc = cwal_weak_annotate(e, wr->value);
    if(rc) return rc;
    else{
        cwal_weak_ref * list;
        assert(!wr->next);
        list = e->weakr[wr->typeID];
        wr->next = list;
        e->weakr[wr->typeID] = wr;
        return 0;
    }
}

/**
   Removes wr from e->weakr[wr->typeID] but does not free wr.
*/
static void cwal_weak_ref_remove( cwal_engine * e, cwal_weak_ref * wr ){
    cwal_weak_ref * list;
    cwal_weak_ref * prev = NULL;
    assert(wr && (wr->typeID>=CWAL_TYPE_UNDEF && wr->typeID<CWAL_TYPE_end));
    list = e->weakr[wr->typeID];
    for( ; list; prev = list, list = list->next ){
        if(wr != list) continue;
        else if(prev) prev->next = wr->next;
        else e->weakr[wr->typeID] = wr->next;
        wr->next = NULL;
        break;
    }
    assert(wr == list);
}

/**
   Zeroes out r and either adds r to e->reWeak (if there is space) or
   frees it (if no recycling space is available).

   Preconditions:

   - neither argument may be 0.
   - r->next must be 0.

   Postconditions: r must be treated as if this function freed it
   (because semantically it does).

*/
static void cwal_weak_ref_free2( cwal_engine * e, cwal_weak_ref * r ){
    assert(e && r && !r->next);
    assert(!V_IS_BUILTIN(r));
    if(e->reWeak.count < e->reWeak.maxLength){
        r->next = (cwal_weak_ref*)e->reWeak.list;
        e->reWeak.list = r;
        ++e->reWeak.count;
    }
    else {
        *r = cwal_weak_ref_empty;
        cwal_free( e, r );
    }
}

void cwal_weak_ref_free( cwal_engine * e, cwal_weak_ref * r ){
    assert(e && r);
    if(!e || !r || V_IS_BUILTIN(r)) return;
    else if(0==r->refcount || 0==--r->refcount){
        cwal_weak_ref_remove( e, r );
        cwal_weak_ref_free2(e, r);
    }         
}

cwal_value * cwal_weak_ref_value( cwal_weak_ref * r ){
    if(!r||!r->value) return NULL;
    else switch(r->typeID){
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_UNDEF:
          assert(r->value)
              /* Because of how we allocate/share these, this will
                 always be true if r was validly allocated. */;
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_HASH:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_NATIVE:
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_STRING:
          return (cwal_value*)r->value;
      case CWAL_TYPE_KVP:
      case CWAL_TYPE_SCOPE:
      case CWAL_TYPE_WEAK_REF:
          return NULL;
      case CWAL_TYPE_XSTRING:
      case CWAL_TYPE_ZSTRING:
          assert(!"Not possible");
      default:
          assert(!"Unhandled type!");
          return NULL;          
    }
}


/**
   Allocates a new cwal_weak_ref. If possible, it takes the head of
   e->reWeak.list, else is cwal_malloc()s it.  The returned value must
   eventually be passed to cwal_weak_ref_free() or
   cwal_weak_ref_free2(), depending on its state at cleanup time.

   If cwal_weak_ref_search(e,ptr,tid) returns a value then this
   function returns the same one.

   It is up to the caller to increment the return'd object's refcount.
*/
static cwal_weak_ref * cwal_weak_ref_alloc( cwal_engine * e, void * ptr,
                                               cwal_type_id tid ){
    cwal_weak_ref * r;

    ++e->metrics.requested[CWAL_TYPE_WEAK_REF];
    r = cwal_weak_ref_search(e, ptr, tid);
    if(!r){
        r = (cwal_weak_ref *)e->reWeak.list;
        if(r){
            assert(e->reWeak.count);
            e->reWeak.list = r->next;
            r->next = NULL;
            --e->reWeak.count;
            assert(!r->refcount);
        }else{
            r = cwal_malloc(e, sizeof(cwal_weak_ref));
            if(r){
                ++e->metrics.allocated[CWAL_TYPE_WEAK_REF];
                e->metrics.bytes[CWAL_TYPE_WEAK_REF] += sizeof(cwal_weak_ref);
            }
        }
        if(r){
            *r = cwal_weak_ref_empty;
            r->value = ptr;
            r->typeID = tid;
        }
        if(cwal_weak_ref_insert(e, r)){
            cwal_weak_ref_free2(e, r);
            r = NULL;
        }
    }
    return r;
}

cwal_weak_ref * cwal_weak_ref_new( cwal_value * v ){
    cwal_engine * e = v ? ((v && v->scope) ? v->scope->e : NULL) : NULL;
    if(!e && !V_IS_BUILTIN(v)) return NULL;
    else{
        cwal_type_id const tid = v->vtab->typeID;
        cwal_weak_ref * r = NULL;
        switch(tid){
          case CWAL_TYPE_UNDEF:
              r = &CWAL_BUILTIN_VALS.wref.wUndef;
              break;
          case CWAL_TYPE_NULL:
              r = &CWAL_BUILTIN_VALS.wref.wNull;
              break;
          case CWAL_TYPE_BOOL:
              r = (&CWAL_BUILTIN_VALS.vTrue == v)
                  ? &CWAL_BUILTIN_VALS.wref.wTrue
                  : &CWAL_BUILTIN_VALS.wref.wFalse;
              break;
          case CWAL_TYPE_STRING:
              if(CWAL_BUILTIN_VALS.vEmptyString==v) r = &CWAL_BUILTIN_VALS.wref.wStrEmpty;
              break;
          case CWAL_TYPE_INTEGER:
              if(V_IS_BUILTIN(v)){
                  if(CWAL_BUILTIN_VALS.vIntM1==v) r = &CWAL_BUILTIN_VALS.wref.wIntM1;
                  else if(CWAL_BUILTIN_VALS.vInt0==v) r = &CWAL_BUILTIN_VALS.wref.wInt0;
                  else if(CWAL_BUILTIN_VALS.vInt1==v) r = &CWAL_BUILTIN_VALS.wref.wInt1;
              }
              break;
          case CWAL_TYPE_DOUBLE:
              if(V_IS_BUILTIN(v)){
                  if(CWAL_BUILTIN_VALS.vDblM1==v) r = &CWAL_BUILTIN_VALS.wref.wDblM1;
                  else if(CWAL_BUILTIN_VALS.vDbl0==v) r = &CWAL_BUILTIN_VALS.wref.wDbl0;
                  else if(CWAL_BUILTIN_VALS.vDbl1==v) r = &CWAL_BUILTIN_VALS.wref.wDbl1;
              }
              break;
          default:
              break;
        }
        if(!r){
            r = cwal_weak_ref_alloc(e, v, v->vtab->typeID);
            if(r){
                assert(r->value == v);
                assert(r->typeID == v->vtab->typeID);
                ++r->refcount;
            }
        }
        return r;
    }
}

cwal_weak_ref * cwal_weak_ref_custom_new( cwal_engine * e, void * p ){
    if(!e || V_IS_BUILTIN(p)) return NULL;
    else{
        cwal_weak_ref * r = cwal_weak_ref_alloc(e, p, CWAL_TYPE_WEAK_REF);
        if(r){
            assert(r->value == p);
            assert(r->typeID == CWAL_TYPE_WEAK_REF);
             ++r->refcount;
        }
        return r;
    }
}

void * cwal_weak_ref_custom_ptr( cwal_weak_ref * r ){
    return (r && (CWAL_TYPE_WEAK_REF==r->typeID))
        ? r->value
        : NULL;
}

void * cwal_weak_ref_custom_check( cwal_engine * e, void * p ){
    if(!e || !p) return NULL;
    else{
        cwal_weak_ref * r = cwal_weak_ref_search(e, p, CWAL_TYPE_WEAK_REF);
        if(r){
            assert(r->value==p);
        }
        return r ? r->value : NULL;
    }
}

char cwal_weak_ref_custom_invalidate( cwal_engine * e, void * p ){
    if(!e || !p) return 0;
    else{
        cwal_weak_ref * r = cwal_weak_ref_search(e, p, CWAL_TYPE_WEAK_REF);
        if(r) r->value = NULL;
        return r ? 1 : 0;
    }
}


static int cwal_engine_init_interning(cwal_engine * e){
    cwal_ptr_table * t = &e->interned;
    /* notes to self regarding table size...

    The following gives me a really good fill rate for
    the low page (over 90%) for 500 random strings:
    
    entries= 3*1024/(2*sizeof(void*))
    
    hashSize= trim_hash(entriesPerPage*33/50)
    
    But lots of tables (9 on average, it seems)
    
    */
    uint16_t const entriesPerPage =
#if 0
        /* testing a leak of interned strings. */
        3
#elif 0
        1024 * 4 / sizeof(void*)
#elif 0
        281
#elif 1
        379
#else
        /*cwal_trim_hash_size(200)*/
        /* not bad 240 */
        /* 281 seems to give a good size for 500-string tests. */
        512 * 5 / (sizeof(void*)) /* also not too bad, about 2kb/page, low page count. */
        /* One test: with a table size of 3163 we got
           over 90% in the first hit and 99% in the
           second hit with 478 strings.
           
           With 433 entries/page we got about 58%
           in the 1st hit, 85 in the 2nd hit.

           After much experimentation, a starting page size of about
           550 seems to provide a good basis here. Increasing the size
           by 8 (later: 8 what?) doesn't give us many fewer tables and
           does cost a lot more memory.
        */
#endif
        ;
    uint16_t const hashSize =
        cwal_trim_hash_size(entriesPerPage);
    uint16_t const stepSize = 0 /* not actually used by this particular table */;
    /*MARKER("INTERNED STRING TABLE hashSize=%u stepSize=%u\n", hashSize, stepSize);*/
    return cwal_ptr_table_create(e, &t, hashSize, stepSize );
}



static cwal_hash_t cwal_hash_cstr( char const * zKey, cwal_size_t nKey ){
#if 0
    /* FNV-xx. These both well for our typical inputs. */
#if CWAL_SIZE_T_BITS < 64
    cwal_hash_t prime = 16777619U;
    cwal_hash_t offset = 2166136261U;
#else
    cwal_hash_t prime = 1099511628211U;
    cwal_hash_t offset = 14695981039346656037U;
#endif
    cwal_size_t i;
    cwal_hash_t hash = offset;
    for( i = 0; i < nKey; ++i ){
#if 0
        /* FNV-1 */
        hash = hash * prime;
        hash = hash ^ zKey[i];
#else
        /* FNV-1a */
        /* Works a tick better than FNV-1 in my tests */
        hash = hash ^ zKey[i];
        hash = hash * prime;
#endif
    }
    return hash;
#else
    if(0 && (1==nKey)){
        return *zKey;
    }else if(0){
        /* This one isn't that bad for our purposes... */
        cwal_hash_t hash = 0 /*2166136261U*/;
        cwal_size_t i;
        for(i=0; i<nKey; ++i){
            hash = ((hash<<3) ^ hash) - (zKey[i]*117);
        }
        return hash;
    }else{
#if 0
        /* FVN-xx. These both well for our typical inputs. */
#if CWAL_SIZE_T_BITS < 64
        cwal_hash_t prime = 16777619U;
        cwal_hash_t offset = 2166136261U;
#else
        cwal_hash_t prime = 1099511628211U;
        cwal_hash_t offset = 14695981039346656037U;
#endif
        cwal_size_t i;
        cwal_hash_t hash = offset;
        for( i = 0; i < nKey; ++i ){
#if 0
            /* FNV-1 */
            hash = hash * prime;
            hash = hash ^ zKey[i];
#else
            /* FNV-1a */
            /* Works a tick better than FNV-1 in my tests */
            hash = hash ^ zKey[i];
            hash = hash * prime;
#endif
        }
        return hash;
#elif 0
        /* just a tiny tick faster than option #3. */
        /* this one (taken from th1) provides fairly unpredictable results
           in my tests, with +/-2 tables on any given run.
        */
        cwal_hash_t hash = 0;
        cwal_size_t i;
        for(i=0; i<nKey; i++){
            hash = (hash<<3) ^ hash ^ zKey[i];
        }
        return hash;
#elif 0
        /* slow compared to options 1 and 3, even without the shift. */
        /* http://home.comcast.net/~bretm/hash/6.html */
        static const cwal_hash_t shift = 14 /* 0..31

14, 8 seem to work well here.

<8 is especially poor
                                            */;
        cwal_hash_t hash = 2166136261U /* this is only an unsigned 32-bit const in C90? */;
        cwal_size_t i = 0;
        for( ; (i<nKey); ++zKey, ++i ){
            hash = (hash * 16777619) ^ (unsigned char)*zKey;
        }
        if( ! shift ) return hash;
        else {
            cwal_hash_t mask =  (cwal_hash_t)((1U << shift) - 1U);
            return (hash ^ (hash >> shift)) & mask;
        }
#elif 1
        /* This one seems to perform (hash-wise) pretty well, by just a tick,
           based on simple tests with the string interning table.
           It's just a tiny tick slower than option #1.

           This hash in combination with a table size of 547 performs
           quite well for my pseudo-scientific random-strings tests.
        */
        /* "djb2" algo code taken from: http://www.cse.yorku.ca/~oz/hash.html */
        static const cwal_hash_t seed = 5381;
        char const * vstr = (char const *)zKey;
        cwal_hash_t hash = seed;
        cwal_size_t i = 0;
        for( ; i<nKey; ++vstr, ++i )
        {
            if(0) hash = ((cwal_hash_t)(hash << 5) + hash) + *vstr;
            else hash = hash * 33 + *vstr;
        }
        return hash ? hash : seed;
#elif 0
        /* "Modified Bernstein" algo, taken from:
           http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx

           Seems to lead to marginally more collisions than the others.
        */
        unsigned char const * p = (unsigned char const *)zKey;
        cwal_hash_t h = 0;
        cwal_size_t i = 0;
        for( ; i < nKey; ++i )
        {
            h = 33 * h ^ p[i];
        }
        return h;
#else
        assert(!"Pick a hash algo!");
#endif    
    }

#endif
/* endless hash [algo] experimentation. */
}

#undef CWAL_HASH_ADLER32

int cwal_interned_search( cwal_engine * e,
                          char const * zKey,
                          cwal_size_t nKey,
                          cwal_value ** out,
                          cwal_ptr_page ** pageIndex,
                          uint16_t * itemIndex ){
    cwal_ptr_page * pPage;
    cwal_value * pRet = NULL;
    uint16_t iKey = 0;
    cwal_ptr_table *t;
    if(!e || !zKey) return CWAL_RC_MISUSE;
    t = &e->interned;
    if( !t->pg.head ) return CWAL_RC_NOT_FOUND;
    iKey = (uint16_t)(cwal_hash_cstr( zKey, nKey ) % t->hashSize);
    if(itemIndex) *itemIndex = iKey;
    for(pPage = t->pg.head; pPage; pPage = pPage->next ){
        pRet = (cwal_value*)pPage->list[iKey];
        if(!pRet) break /* end of the table list */;
        else if(pRet){
            cwal_string const * sRet = CWAL_STR(pRet);
            assert(sRet);
            if(sRet &&
               (CWAL_STRLEN(sRet) == nKey)
               && (0 == memcmp( cwal_string_cstr(sRet),
                                zKey, nKey ))){
                if(out) *out = pRet;
                if(pageIndex) *pageIndex = pPage;
                return CWAL_RC_OK;
            }
        }
    }
    return CWAL_RC_NOT_FOUND;
}

int cwal_interned_search_val( cwal_engine * e,
                              cwal_value const * v,
                              cwal_value ** out,
                              cwal_ptr_page ** pageIndex,
                              uint16_t * itemIndex ){
    cwal_ptr_page * pPage = NULL;
    cwal_value * pRet = NULL;
    uint16_t iKey = 0;
    cwal_ptr_table *t;
    if(!e || !v) return CWAL_RC_MISUSE;
    t = &e->interned;
    if( !t->pg.head ) return CWAL_RC_NOT_FOUND;
    iKey = (uint16_t)(v->vtab->hash(v) % t->hashSize);
    if(itemIndex) *itemIndex = iKey;
    for(pPage = t->pg.head; pPage; pPage = pPage->next ){
        pRet = (cwal_value*)pPage->list[iKey];
        if(!pRet) break /* end of the table list for this hash */;
        else if(pRet){
            if((v==pRet)
               || ((v->vtab == pRet->vtab /* enforce strict type comparisons */)
                   && (0 == v->vtab->compare(v, pRet)))){
                if(out) *out = pRet;
                if(pageIndex) *pageIndex = pPage;
                return CWAL_RC_OK;
            }
        }
    }
    return CWAL_RC_NOT_FOUND;
}

    
int cwal_interned_insert( cwal_engine * e, cwal_value * v ){
    cwal_ptr_page * pPage = NULL;
    cwal_ptr_page * pageIndex = NULL;
    cwal_value * pRet = NULL;
    uint16_t iKey = 0;
    int rc;
    cwal_ptr_table * t;
    if(!(CWAL_FEATURE_INTERN_STRINGS & e->flags)) return CWAL_RC_UNSUPPORTED;
    else if(!e || !v) return CWAL_RC_MISUSE;
    t = &e->interned;
    if(!t->pg.head){
        rc = cwal_engine_init_interning( e );
        if(rc) return rc;
        assert(t->pg.head);
    }

    rc = cwal_interned_search_val( e, v, &pRet, &pageIndex, &iKey );
    if( pRet ) {
        assert(pageIndex);
        assert( (pRet == v) && "i'm fairly sure this holds with the current code.");
        return CWAL_RC_ALREADY_EXISTS;
    }
    assert(!pageIndex);
    
    /**
         Search just failed, so we need to add an entry.  Check if one
         of our existing pages has a slot...
    */
    for(pPage = t->pg.head; pPage; pPage = pPage->next ){
        pRet = pPage->list[iKey];
        if(!pRet) goto insert;
    }
    if(!pPage){
        /* We reached the end of the table and found no empty slot
           (maybe had a collision). Add a page and insert the value
           there. */
        rc = cwal_ptr_table_add_page( e, t, 0 );
        if(rc) return rc;
        pPage = t->pg.tail;
    }
    insert:
    rc = CWAL_RC_OK;
    assert(NULL != pPage);
    assert(!pPage->list[iKey]);
    pPage->list[iKey] = v;
    CWAL_TR_VCS(e,v);
    CWAL_TR2(e,CWAL_TRACE_VALUE_INTERNED);
    ++pPage->entryCount;
#if 0
    /* Obligatory kludge to avoid sweeping these. i don't like this,
       but it does actually save a tick of memory, compared to not
       having it, in my tests.  Ideally we would wait until the 2nd
       time it's requested before forcing a phantom ref on it, but
       aggressive sweeping can defeat that.
    */
    cwal_value_ref2(e, v);
#endif
    return rc;
}

int cwal_interned_remove( cwal_engine * e,
                          cwal_value const * v,
                          cwal_value ** out ){
    cwal_ptr_page * pPage = 0;
    cwal_value * pRet = NULL;
    cwal_ptr_table * t;
    uint16_t iKey = 0;
    cwal_ptr_page * pageIndex = NULL;
    int rc;
    if(!e || !v) return CWAL_RC_MISUSE;
    t = &e->interned;
    if(!t->pg.head) return CWAL_RC_NOT_FOUND;

    rc = cwal_interned_search_val( e, v, &pRet, &pageIndex, &iKey );
    if( !pRet ) {
        assert( CWAL_RC_NOT_FOUND == rc );
        return rc;
    }
    else if(rc){
        assert(!"Cannot happen");
        return rc;
    }
    if( out ) *out = pRet;
    CWAL_TR_VCS(e,pRet);
    CWAL_TR2(e,CWAL_TRACE_VALUE_UNINTERNED);
    assert( pageIndex );
    for( pPage = pageIndex; pPage; pPage = pPage->next ){
        /* Remove the entry. If any higher-level pages contain that
           key, move it into this page (up through to the top-most
           page which has a matching key).
        */
        pPage->list[iKey] = pPage->next
            ? pPage->next->list[iKey]
            : 0;
        if( !pPage->list[iKey] ) {
            --pPage->entryCount;
            break;
        }
    }
    assert( pPage && (0 == pPage->list[iKey]) );
    return CWAL_RC_OK;
}

cwal_obase * cwal_value_obase( cwal_value * v ){
    return CWAL_VOBASE(v);
}

static void cwal_obase_set_visiting( cwal_obase * b, char isVisiting ){
    assert(b);
    if(isVisiting) b->flags |= CWAL_F_IS_VISITING;
    else b->flags &= ~CWAL_F_IS_VISITING;
    return;
}

int cwal_value_set_visiting( cwal_value * v, char isVisiting ){
    cwal_obase * b = CWAL_VOBASE(v);
    return !b
        ? CWAL_RC_TYPE
        : (cwal_obase_set_visiting( b, isVisiting ), CWAL_RC_OK);
}

void cwal_value_cleanup_noop( cwal_engine * e, void * v ){
}

void cwal_value_cleanup_integer( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value *)self;
    *CWAL_INT(v) = 0;
}
void cwal_value_cleanup_double( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value *)self;
    *CWAL_DBL(v) = 0.0;
}
        
cwal_type_id cwal_value_type_id( cwal_value const * v ){
    return (v && v->vtab) ? v->vtab->typeID : CWAL_TYPE_UNDEF;
}

cwal_value_type_name_proxy_f cwal_engine_type_name_proxy( cwal_engine * e,
                                                          cwal_value_type_name_proxy_f f ){
    cwal_value_type_name_proxy_f rc = e ? e->type_name_proxy : 0;
    if(e) e->type_name_proxy = f;
    return rc;
}

char const * cwal_value_type_name2( cwal_value const * v,
                                    cwal_size_t * len){
    if(!v || !v->vtab) return NULL;
    else{
        cwal_engine const * e = v->scope ? v->scope->e : NULL;
        char const * rc = NULL;
        if(e && e->type_name_proxy){
            rc = e->type_name_proxy(v, len);
        }
        if(!rc) rc = v->vtab->typeName;
        if(len) *len = cwal_strlen(rc);
        return rc;
    }
}

char const * cwal_value_type_name( cwal_value const * v ){
    return v ? cwal_value_type_name2(v, NULL) : NULL;
}

char const * cwal_value_type_id_name( cwal_type_id id ){
    cwal_value_vtab const * t = 0;
    switch(id){
      case CWAL_TYPE_BOOL: t = &cwal_value_vtab_bool; break;
      case CWAL_TYPE_UNDEF: t = &cwal_value_vtab_undef; break;
      case CWAL_TYPE_NULL: t = &cwal_value_vtab_null; break;
      case CWAL_TYPE_STRING: t = &cwal_value_vtab_string; break;
      case CWAL_TYPE_INTEGER: t = &cwal_value_vtab_integer; break;
      case CWAL_TYPE_DOUBLE: t = &cwal_value_vtab_double; break;
      case CWAL_TYPE_ARRAY: t = &cwal_value_vtab_array; break;
      case CWAL_TYPE_OBJECT: t = &cwal_value_vtab_object; break;
      case CWAL_TYPE_NATIVE: t = &cwal_value_vtab_native; break;
      case CWAL_TYPE_BUFFER: t = &cwal_value_vtab_buffer; break;
      case CWAL_TYPE_FUNCTION: t = &cwal_value_vtab_function; break;
      case CWAL_TYPE_EXCEPTION: t = &cwal_value_vtab_exception; break;
      case CWAL_TYPE_HASH: t = &cwal_value_vtab_hash; break;
      case CWAL_TYPE_SCOPE: return "cwal_scope";
      case CWAL_TYPE_KVP: return "cwal_kvp";
      case CWAL_TYPE_WEAK_REF: return "cwal_weak_ref";
      case CWAL_TYPE_XSTRING: return "x-string";
      case CWAL_TYPE_ZSTRING: return "z-string";
      default: break;
    }
    return t ? t->typeName : NULL;
}

char cwal_value_is_undef( cwal_value const * v ){
    return ( !v || !v->vtab || (v->vtab==&cwal_value_vtab_undef))
        ? 1 : 0;
}

char cwal_value_can_have_properties( cwal_value const * v ){
    return CWAL_V_IS_OBASE(v);
}
    
#define ISA(T,TID) char cwal_value_is_##T( cwal_value const * v ) {       \
        /*return (v && v->vtab) ? cwal_value_is_a(v,CWAL_TYPE_##TID) : 0;*/ \
        return (v && (v->vtab == &cwal_value_vtab_##T)) ? 1 : 0; \
    } static const char bogusPlaceHolderForEmacsIndention##TID = CWAL_TYPE_##TID

ISA(null,NULL);
ISA(bool,BOOL);
ISA(integer,INTEGER);
ISA(double,DOUBLE);
ISA(string,STRING);
ISA(array,ARRAY);
ISA(object,OBJECT);
ISA(native,NATIVE);
ISA(buffer,BUFFER);
ISA(function,FUNCTION);
ISA(exception,EXCEPTION);
#undef ISA

char cwal_value_is_number( cwal_value const * v ){
    if(!v) return 0;
    else switch(v->vtab->typeID){
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_BOOL:
          return 1;
      default:
          return 0;
    }
}


void cwal_finalizer_f_fclose( cwal_engine * e, void * m ){
    if(m && (m != stdout) && (m != stderr) && (m != stdin)){
        fclose( (FILE*)m );
    }
}

void * cwal_realloc_f_std( void * state, void * m, cwal_size_t n ){
    if( 0 == n ){
        free( m );
        return NULL;
    }else if( !m ){
        return malloc( n );
    }else{
        return realloc( m, n );
    }
}

int cwal_output_f_FILE( void * state, void const * src, cwal_size_t n ){
    if( !src || !n ) return 0;
    return (1 == fwrite( src, n, 1, state ? (FILE*)state : stdout ))
        ? CWAL_RC_OK
        : CWAL_RC_IO;
}

int cwal_output_flush_f_FILE( void * f ){
    return fflush(f ? (FILE*)f : stdout)
        ? CWAL_RC_IO
        : 0
        ;
}

int cwal_output_f_buffer( void * state, void const * src, cwal_size_t n ){
    cwal_output_buffer_state * ob = (cwal_output_buffer_state*) state;
#if 1
    return cwal_buffer_append(ob->e, ob->b, src, n);
#else
    int rc = 0;
    cwal_output_buffer_state * ob = (cwal_output_buffer_state *)state_;
    cwal_size_t sz = ob->b->used+n+1;
    if(sz > ob->b->capacity){
        /* TODO? expand by some factor */
        /* sz = sz * 13 / 10; */
        rc = cwal_buffer_reserve(ob->e, ob->b, sz);
    }
    if(!rc){
        rc = cwal_buffer_append( ob->e, ob->b, n );
    }
    return rc;
#endif
}

void cwal_output_buffer_finalizer( cwal_engine * e, void * m ){
    cwal_output_buffer_state * ob = (cwal_output_buffer_state *)m;
    cwal_buffer_reserve(ob->e, ob->b, 0);
    ob->e = NULL;
    *ob = cwal_output_buffer_state_empty;
}

    
void * cwal_malloc( cwal_engine * e, cwal_size_t n ){
    if(!e || !n) return 0;
    else if( n > CwalConsts.MaxSizeTCounter ) return NULL;
    else{
        void * rc;
        rc = e->vtab->allocator.realloc( e->vtab->allocator.state.data, NULL, n );
        CWAL_TR_MEM(e,rc,n);
        CWAL_TR2(e,CWAL_TRACE_MEM_MALLOC);
        return rc;
    }
}
        
void cwal_free( cwal_engine * e, void * m ){
    if(e && m){
        CWAL_TR_MEM(e,m,0);
        CWAL_TR2(e,CWAL_TRACE_MEM_FREE);
        e->vtab->allocator.realloc( e->vtab->allocator.state.data, m, 0 );
    }
}

void * cwal_realloc( cwal_engine * e, void * m, cwal_size_t n ){
    if( 0 == n ){
        cwal_free( e, m );
        return NULL;
    }else if( !m ){
        return cwal_malloc( e, n );
    }else{
        CWAL_TR_MEM(e,m,n);
        CWAL_TR2(e,CWAL_TRACE_MEM_REALLOC);
        return ( n > CwalConsts.MaxSizeTCounter )
            ? NULL
            : e->vtab->allocator.realloc( e->vtab->allocator.state.data, m, n );
    }
}

static cwal_size_t cwal_scope_sweep_r0( cwal_scope * s ){
    cwal_engine * e = s->e;
    cwal_value * v;
    cwal_value * n;
    cwal_size_t rc = 0;
    cwal_value * r0 = s->mine.r0;
    assert(e);
    CWAL_TR_MSG(e,"s->mine.r0:");
    CWAL_TR_SV(e,s,s->mine.r0);
    CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK);
#if 1
    s->mine.r0 = NULL;
    for(v = r0; v; v = n ){
        n = v->right;
        if(v->scope!=s) {
            dump_val(v,"Check for scope mismatch");
            assert(!v->left);
            if(v->right) dump_val(v->right,"v->right");
        }
        assert(v->scope==s);
        v->left = v->right = 0;
        if(n){
            assert(n->scope==s);
            n->left = 0;
        }
        assert(e->exception != v);
        assert(e->propagating != v);
#if 0
        if(e->exception == v){
            e->exception = 0;
        }
#endif
        cwal_value_unref2(e,v);
        ++rc;
    }
#else
    while( (v = s->mine.r0) ){
        CWAL_TR_SV(e,s,v);
        CWAL_TR_MSG(e,"Scope is about to unref r0 value");
        CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK);
        assert(!V_IS_BUILTIN(v));
        assert(0==v->refcount);
        if(v->scope!=s) {
            dump_val(v,"Check for scope mismatch");
            if(v->left) dump_val(v->left,"v->left");
            if(v->right) dump_val(v->right,"v->right");
        }
        assert(v->scope==s);
#if 0
        if(e->exception == v){
            e->exception = 0;
        }
#endif
        if(v->right) assert(v->right->scope==s);
        cwal_value_unref2(e, v);
        ++rc;
    }
#endif
    return rc;
}

static int cwal_engine_sweep_impl( cwal_engine * e, cwal_scope * s ){
    return (int)cwal_scope_sweep_r0(s);
}


cwal_size_t cwal_engine_sweep2( cwal_engine * e, char allScopes ){
    if(!e) return 0;
    else if(!allScopes){
        return cwal_engine_sweep_impl(e, e->current);
    }
    else {
        cwal_scope * s = e->current;
        cwal_size_t rc = 0;
        for( ; s; s = s->parent ){
            rc += cwal_engine_sweep_impl(e, s);
        }
        return rc;
    }
}

cwal_size_t cwal_engine_sweep( cwal_engine * e ){
    return e
        ? cwal_engine_sweep_impl(e, e->current)
        : 0;
}

cwal_size_t cwal_scope_sweep( cwal_scope * s ){
    return (s && s->e)
        ? cwal_engine_sweep_impl( s->e, s )
        : 0;
}


static void cwal_scope_to_r0( cwal_scope * s, cwal_value * v ){
    cwal_engine * e = s->e;
    cwal_value * list;
    assert( s && v && e );
    assert(0==v->refcount);
    assert(!v->scope);
    assert(!V_IS_BUILTIN(v));
    list = s->mine.r0;
    for( ; list && list->left; list = list->left ){
        assert(!"i don't _think_ this is possible "
               "without corruption.");
    }
    if(list){
        list->left = v;
        v->right = list;
    }
    v->scope = s;
    s->mine.r0 = v;
}

/**
   Removes v from its owning scope and places it in s->mine.r0,
   putting it back in the probationary state.  v MUST have a refcount
   of 0 and V_IS_BUILTIN(v) MUST be false. s MUST be the scope to
   reprobate v to.
*/
static void cwal_value_reprobate( cwal_scope * s, cwal_value * v){
    int rc;
    assert(v);
    assert(!v->refcount);
    assert(!V_IS_BUILTIN(v));
    assert(s);
    assert(s->e);
    /* dump_val(v,"Re-probating value"); */
    rc = cwal_value_take(s->e, v);
    assert(!rc);
    cwal_scope_to_r0(s, v);
    /* dump_val(v,"Re-probated value"); */
}

void cwal_unref_from( cwal_scope * p, cwal_value * v ){
    assert(p && v);
    if(V_IS_BUILTIN(v)) return;
    /* why does this fail sometimes? assert(v->scope); */
    if(v->scope && (v->scope->level < p->level)){
        /*
           Leave v in v->scope and let its scope clean it up.  That
           resolves a corner case in client code where they must add
           an "artificial" ref to certain return values to ensure that
           they live past their scope.
        */
#if 0
        MARKER("v level=%u, ref-from level=%u\n",
          (unsigned)v->scope->level, (unsigned)p->level);
#endif
        /* assert(v->refcount); interned strings break this */
        if(v->refcount){
            /*MARKER("v level=%u, vSelf level=%u\n",
              (unsigned)v->scope->level, (unsigned)p->level);*/
            /* dump_val(v,"Pseudo-unreffing value"); */
            if(0==--v->refcount){
                cwal_value_reprobate(v->scope, v);
            }
        }
        else{
            /* assert(!"?Can this happen? Maybe on first-time interned strings?"); */
            /* A recursive sweep could trigger this. */
            cwal_value_unref2(p->e, v);
        }
    }
    else cwal_value_unref2(p->e, v);
}

/**
   "Snips" v from its left/right neighbors.  If v->scope and v is the
   head of one of v->scope->mine's ownership lists then the list is
   adjusted to point to v->left (if set) or v->right.

   Returns the right-hand neighbor of v, or 0 if it has no neighbor.
*/
static cwal_value * cwal_value_snip( cwal_value * v );

/**
   calls cwal_value_snip() to snip v from any chain and moves v to the
   head of one of s->mine's members (depending on a couple of
   factors). Does not modify refcount. If v->scope==s then it returns
   without any side-effects.
*/
static void cwal_scope_insert( cwal_scope * s, cwal_value * v );

/**
   Internal helper to move a refcount==0 value from the r0 list to one
   of the "longer-lived" lists. Returns 0 on success.

   v->refcount must be 1 (not 0) when this is called.
*/
static int cwal_scope_from_r0( cwal_value * v ){
    cwal_scope * s = v->scope;
    assert(1==v->refcount);
    if(!s->mine.r0) return CWAL_RC_NOT_FOUND;
    else if(1!=v->refcount) return CWAL_RC_RANGE
        /* Only to ensure that caller ++'s it before calling this, so
           that the cwal_scope_insert() call below can DTRT. */
        ;
    cwal_value_snip(v);
    cwal_scope_insert( s, v );
    assert(v->scope == s);
    return 0;
}

/**
   Returns the number of values in s->mine's various lists. An O(N)
   operation, N being the number returned (not incidentally).
*/
static cwal_size_t cwal_scope_value_count( cwal_scope const * s ){
    cwal_size_t n = 0;
    cwal_value const * v;
#  define VCOUNT(WHO) v = WHO; while(v){++n; v=v->right;} (void)0
    VCOUNT(s->mine.headPod);
    VCOUNT(s->mine.headObj);
    VCOUNT(s->mine.headSafe);
    VCOUNT(s->mine.r0);
#undef VCOUNT
    return n;
}

/**
   This function frees the internal state of s but does not free s.
*/
int cwal_scope_clean( cwal_engine * e, cwal_scope * s ){
    int rc;
    cwal_value * v;
    char iInitedGc = 0;
    if(!e->gcInitiator) {
        iInitedGc = 1;
        e->gcInitiator = s;
        CWAL_TR_S(e,s);
        CWAL_TR3(e,CWAL_TRACE_MESSAGE,"Initializing gc capture.");
    }

    CWAL_TR_S(e,s);
    CWAL_TR3(e,CWAL_TRACE_SCOPE_CLEAN_START,"Scope cleanup starting.");
    /* prohibits vacuum: assert(e->current != s); */
    /* Special treatment of e->exception and e->propagating. */
    {
        cwal_value * vPropagate;
        int phase = 1;
        propagate_next:
        vPropagate = (1==phase ? e->exception : e->propagating);
        if(vPropagate && vPropagate->scope == s){
            cwal_scope * parent = (s==e->current /* vacuum op */) ? s->parent : e->current;
            CWAL_TR_SV(e,s,vPropagate);
            CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Relocating vPropagate...");
            if(!parent){
                if(1==phase) cwal_exception_set(e, 0);
                else cwal_propagating_set(e, 0);
                assert((1==phase) ? 0==e->exception : 0==e->propagating);
            }
            else {
                rc = cwal_value_xscope( e, parent, vPropagate, NULL );
                assert(!rc);
                assert(vPropagate && (vPropagate->scope!=s));
                CWAL_TR_SV(e,vPropagate->scope,vPropagate);
                CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,"Moved EXCEPTION/PROPAGATING to next scope up.");
            }
        }
        if(2==++phase) goto propagate_next;
    }

#if 0
    s->props = 0
        /*
          remember that s->props can get moved into a higher scope.
        */;
#else
    if(s->props){
        cwal_value * pv = cwal_object_value(s->props);
        s->props = 0;
        assert(pv);
        assert((pv->refcount>0) && "Who stole my properties ref?");
        /*assert(1==pv->refcount
          && "Unexpected reference on s->props.");*/
        cwal_value_unhand( pv )
            /* experimental! If that breaks, go back to original impl:
               cwal_unref_from( s, pv ) */
            ;
    }
#endif

    cwal_scope_sweep_r0( s );

    /**
       Reminder: we HAVE to clean up the containers first to ensure
       that cleanup of PODs during container cleanup does not step
       on dead POD refs we already cleaned. We could get around this
       ordering if we included PODs in the gc queue, but we do not
       need to, so we don't.

       Algorith: keep reducing each value's refcount by 1 until it's
       dead. This weeds out cycles one step at a time.

       Notes:

       For PODs we REALLY want to unref them only once here, BUT
       internalized strings screw that up for us (but are too cool to
       outright give up, yet i also don't want to special-case them).

       The destruction order is not QUITE what i want (i would prefer
       reverse-allocation order, but we cannot guaranty that ordering
       once objects move between scopes, anyway). What we're doing
       here is unref'ing the head (last-added) item. If that item
       still has references (it was not destroyed) then we proceed to
       unref subsequent items in the list until one is destroyed.
       Value destruction modifies the list we are traversing, forcing
       a re-start of the traversal if any item is actually finalized
       by the unref. As values are cleaned up they remove themselves
       from s->mine, so we can simply walk the list until it's
       empty. For "normal use" the destruction order will be the
       referse of allocation, but once references are held that
       doesn't...  well, hold.

       Remember that this ONLY works because of our scoping rules:

       - All values in a scope when it is cleaned up must not (cannot)
       reference values in lower (newer) scopes because performing
       such a reference transfers the being-referenced value
       (recursively for containers) into the referencing value's
       owning scope.
    */
    while((v = s->mine.headObj
           ? s->mine.headObj
           : (s->mine.headSafe
              ? s->mine.headSafe
              : s->mine.headPod)
           )){
        CWAL_TR_SV(e,s,v);
        CWAL_TR_MSG(e,"Scope is about to unref value");
        CWAL_TR2(e,CWAL_TRACE_SCOPE_MASK);
        assert(!V_IS_BUILTIN(v));
        assert(v->scope==s);
        if(v->right) assert(v->right->scope==s);
        while(v){
            cwal_value * n = v->right;
            assert(n != v);
            assert(!n || n->refcount>0);
            assert(!n || n->scope == s);
            if( CWAL_RC_HAS_REFERENCES == cwal_value_unref2(e, v) ) {
                /*
                  It still has references. Let's try again.

                  This is part of the reason the gc queue is so
                  important/helpful. Consider what happens when we
                  clean up a Prototype value (which may have hundreds
                  or thousands of references to it).  We may clean it
                  up before some of the objects which reference it
                  because of this "repeat if it survives" behaviour.
                */
                assert(v->refcount>0);
                v = n;
                assert((!n || s == n->scope) && "unexpected. Quite.");
                continue;
                /* break; */
            }
#if 1
            else if(n && n->scope){
                assert((s == n->scope) && "This is still true in a vacuum, right?");
                /* n is still in THIS list, else n is in the gc
                   queue and we need to re-start traversal.

                   The destruction of v can only affect n if v is a
                   container. a POD cannot form a reference to anyone
                   else, so if we're here then we know that either:

                   - v was a POD before unref'ing it.

                   - OR v was a container which did not (even
                   indirectly) reference n. Had it cleaned up n,
                   n->scope would be 0.
                */
                v = n;
                continue;
            }
#endif
            else{
                /*
                  Need to restart traversal due to either:

                  - v being finalized (which modifies this list).

                  - n being in the gc queue or recycle bin (having
                  been put there when we unref'd v, which held the
                  only reference(s) (possibly indirectly) to n).
                  We detect this case by checking whether n has a scope.
                  If it has no scope, it's in the gc queue. Because
                  PODs don't gc-queue (they don't have to because they
                  cannot reference anything) all this funkiness only
                  applies to containers.
                  
                */
                break;
            }
        }
    }
    assert(0 == s->mine.headPod);
    assert(0 == s->mine.headSafe);
    assert(0 == s->mine.r0);
    CWAL_TR3(e,CWAL_TRACE_SCOPE_CLEAN_END,"Scope cleanup finished.");
    /*MARKER("ALLOCS LIST SIZES: compound=%u simple=%u\n", s->mine.compound.count, s->mine.simple.count );*/

    if(iInitedGc){
        assert(s == e->gcInitiator);
        if(s == e->gcInitiator) {
            e->gcInitiator = 0;
            cwal_gc_flush( e );
        }
    }
    return CWAL_RC_OK;
}

static int cwal_scope_free( cwal_engine * e, cwal_scope * s, char allowRecycle ){
    void const * stamp;
    assert( e && s );
    stamp =  s->allocStamp;
    cwal_scope_clean(e, s);
    *s = cwal_scope_empty;
    if(CwalConsts.AllocStamp == stamp){
        /* This API allocated the scope - recycle or free it. */
        cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_SCOPE);
        assert(re);
        if( allowRecycle && (re->count < re->maxLength) ){
            s->parent = (cwal_scope*)re->list;
            re->list = s;
            ++re->count;
        }
        else cwal_free(e, s);
    }else{
        /* it was allocated elsewhere */
        s->allocStamp = stamp;
    }
    return CWAL_RC_OK;
}

int cwal_exception_info_clear( cwal_engine * e, cwal_exception_info * err ){
    if(!e || !err) return CWAL_RC_MISUSE;
    else{
        int rc = CWAL_RC_OK;
        if( err->zMsg ) cwal_free( e, err->zMsg );
        if( err->value ) rc = cwal_value_unref2( e, err->value );
        if(CWAL_RC_DESTRUCTION_RUNNING == rc) {
            assert( 0 && "i don't _think_ this can happen." );
            rc = 0;
        }
        assert(0 == rc);
        *err = cwal_exception_info_empty;
        return CWAL_RC_OK;
    }
}

/**
   Passes all values in the given linked value list to cwal_free().
   The values MUST have been properly cleaned up via the
   cwal_unref() mechanism.
*/       
static void cwal_value_list_free( cwal_engine * e, cwal_value * list){
    cwal_value * v, * next;
    for( v = list; v; v = next ){
        assert(0==v->scope);
        assert(0==v->refcount);
        next = v->right;
        cwal_free(e, v);
    }
}
    

/**
   If s->finalize is not NULL then s->finalize(e, s->state) is called
   (if finalize is not 0) and s's state is cleared, else this is a
   no-op.
*/
static void cwal_state_cleanup( cwal_engine * e, cwal_state * s );

static int cwal_engine_destroy_impl( cwal_engine * e, cwal_engine_vtab * vtab ){
    if(!e || !vtab) return CWAL_RC_MISUSE;
    else{
        void const * stamp = e->allocStamp;
        cwal_state_cleanup( e, &e->client );
        if(!e->vtab ) e->vtab = vtab /* only happens during on-init errors. */ ;
        e->gcInitiator = 0;
        CWAL_TR2(e,CWAL_TRACE_ENGINE_SHUTDOWN_START);

        /*
          Maintenance reminder: if we ever have an Values to clean up,
          they need to be cleaned up first (right after e->client).
        */

        e->reWeak.maxLength = 0;

        e->exception = 0 /* its scope will clean it up */;
        e->propagating = 0 /* its scope will clean it up */;
        e->prototypes = 0 /* its scope will clean it up */;
        while( e->current ){
            cwal_scope_pop(e);
        }
       
        {/* Cleanup recyclers (AFTER scopes have been popped)... */
            cwal_size_t i;
            cwal_kvp * kvp, * next;
            cwal_recycler * re;
            cwal_scope * s, * snext;
            /* This "should" be a loop, but our use of mixed types
               screws that up.
            */
#define RE(T) re = &e->recycler[ cwal_recycler_index(T) ]
#define UNCYCLE(T) RE(T); cwal_value_list_free(e, (cwal_value*)re->list); \
            re->list = 0; re->count = 0
            UNCYCLE(CWAL_TYPE_INTEGER);
            UNCYCLE(CWAL_TYPE_DOUBLE);
            UNCYCLE(CWAL_TYPE_STRING /* actually x/z-strings! */);
            UNCYCLE(CWAL_TYPE_OBJECT);
            UNCYCLE(CWAL_TYPE_ARRAY);
            UNCYCLE(CWAL_TYPE_NATIVE);
            UNCYCLE(CWAL_TYPE_BUFFER);
            UNCYCLE(CWAL_TYPE_FUNCTION);
            UNCYCLE(CWAL_TYPE_EXCEPTION);
            /* KVP and SCOPE are handled below... */
#undef UNCYCLE
            cwal_value_list_free(e, e->reString.list);
            e->reString.list = 0;
            e->reString.count = 0;
            
            RE(CWAL_TYPE_KVP);
            kvp = (cwal_kvp*) re->list;
            re->list = 0;
            for( ; kvp; kvp = next ){
                next = kvp->right;
                assert(!kvp->key);
                assert(!kvp->value);
                kvp->right = NULL;
                cwal_kvp_free( e, NULL, kvp, 0 );
                --re->count;
            }
            assert(0==re->count);
            
            RE(CWAL_TYPE_SCOPE);
            s = (cwal_scope*) re->list;
            re->list = 0;
            for( ; s; s = snext ){
                snext = s->parent;
                s->parent = 0;
                cwal_free( e, s );
                --re->count;
            }
            assert(0==re->count);
#undef RE

            { /* Clean up weak ref recycler... */
                cwal_weak_ref * wr;
                cwal_weak_ref * wnext;
                re = &e->reWeak;
                wr = (cwal_weak_ref*) re->list;
                re->list = 0;
                for( ; wr; wr = wnext ){
                    wnext = wr->next;
                    assert(!wr->value);
                    wr->next = NULL;
                    cwal_free(e, wr);
                    --re->count;
                }
                assert(0==re->count);
            }
            
            /* sanity-check to make sure we didn't leave any
               new additions out of the e-recycler cleanup...

               This has saved me grief twice now.
            */
            for( i = 0; i < (sizeof(e->recycler)/sizeof(e->recycler[0])); ++i ){
                re = &e->recycler[i];
                assert(re->id>0);
                assert(0==re->list);
                assert(0==re->count);
            }
        }        

        { /* Clean up any dangling cwal_weak_refs if the client
             failed to do so... */
            cwal_size_t i = 0;
            for( i = 0; i < sizeof(e->weakr)/sizeof(e->weakr[0]); ++i ){
                cwal_weak_ref * list = e->weakr[i];
                e->weakr[i] = NULL;
                while( list ){
                    cwal_weak_ref * next = list->next;
                    list->next = NULL;
                    cwal_weak_ref_free2(e, list);
                    list = next;
                }
            }
        }
        cwal_ptr_table_destroy(e, &e->weakp);
        cwal_ptr_table_destroy( e, &e->interned );
        cwal_buffer_reserve(e, &e->buffer, 0);
        cwal_buffer_reserve(e, &e->sortBuf, 0);
        cwal_gc_flush( e );

        { /* Clean up e->reList... */
            int i = 0;
            const int n = sizeof(e->reList.lists)/sizeof(e->reList.lists[0]);
            for( ; i <= n; ++i ){
                cwal_list * li = &e->reList.lists[i];
                if(li->list) cwal_list_reserve(e, li, 0);
            }
            memset(&e->reList.lists, 0, sizeof(e->reList.lists));
        }

        if(vtab->tracer.close){
            vtab->tracer.close( vtab->tracer.state );
            vtab->tracer.state = 0;
        }
        if(vtab->outputer.state.finalize){
            vtab->outputer.state.finalize( e, vtab->outputer.state.data );
            vtab->outputer.state.data = 0;
        }

        CWAL_TR2(e,CWAL_TRACE_ENGINE_SHUTDOWN_END);
        *e = cwal_engine_empty;

        if( stamp == CwalConsts.AllocStamp ){
            vtab->allocator.realloc( vtab->state.data, e, 0 );
        }else{
            /* client allocated it or it was part of another object. */
            e->allocStamp = stamp;
        }

        if(vtab->state.finalize){
            vtab->state.finalize( NULL, vtab->state.data );
        }

        /**
           TODO: call vtab->shutdown() once that's added to the
           vtab interface.
        */
        
        return CWAL_RC_OK;
    }
}
int cwal_engine_destroy( cwal_engine * e ){
    assert( NULL != e->vtab );
    return e ? cwal_engine_destroy_impl(e, e->vtab) : CWAL_RC_MISUSE;
}

int cwal_engine_init( cwal_engine ** E, cwal_engine_vtab * vtab ){
    unsigned const sz = sizeof(cwal_engine);
    cwal_engine * e;
    int rc;
    if(!E || !vtab){
        return CWAL_RC_MISUSE;
    }
    CWAL_INIT_EMPTY_VALUES;
    e = *E;
    if(!e){
        e = vtab->allocator.realloc( vtab->allocator.state.data, NULL, sz );
        if(!e){
            return CWAL_RC_OOM;
        }
    }
    *e = cwal_engine_empty;
    if(!*E){
        e->allocStamp = CwalConsts.AllocStamp
            /* we use this later to recognize that we allocated (and
               need to free()) e. */;
        *E = e;
    }

    memset(&e->reList.lists, 0, sizeof(e->reList.lists));
    { /* A sanity check */
        cwal_recycler * re = &e->recycler[sizeof(e->recycler)
                                          /sizeof(e->recycler[0])-1];
        assert( (re->maxLength > 0) &&
                "Steve forgot to properly initialize a new "
                "cwal_engine::recycler element again." );
        assert( !re->count);
    }
    e->vtab = vtab;
    if(CwalConsts.AutoInternStrings){
        e->flags |= CWAL_FEATURE_INTERN_STRINGS;
    }

    /* Tag e->recycler[*].id, just for sanity checking.
       The order MUST coincide with cwal_recycler_index().
       i do recognize the irony that by adding this sanity
       checking i am actually increasing the possiblity of
       error by adding yet another place where this list of
       types (and its exact ordering) must be maintained :/.
    */
    rc = 0;
#define CY(T) e->recycler[rc++].id = T
    CY(CWAL_TYPE_INTEGER);
    CY(CWAL_TYPE_DOUBLE);
    CY(CWAL_TYPE_STRING);
    CY(CWAL_TYPE_ARRAY);
    CY(CWAL_TYPE_OBJECT);
    CY(CWAL_TYPE_NATIVE);
    CY(CWAL_TYPE_BUFFER);
    CY(CWAL_TYPE_FUNCTION);
    CY(CWAL_TYPE_EXCEPTION);
    CY(CWAL_TYPE_KVP);
    CY(CWAL_TYPE_SCOPE);
#undef CY
    rc = 0;


#if CWAL_ENABLE_TRACE
    e->trace.e = e;
#endif
    {
        cwal_scope * sc = &e->topScope;
        rc = cwal_scope_push( e, &sc );
        if(rc) goto end;
        assert(sc->e == e);
    }
#if 0
    /* defer interned strings init until needed. */
    if(CWAL_FEATURE_INTERN_STRINGS & e->flags) {
        rc = cwal_engine_init_interning(e);
        if(rc) goto end;
    }
#endif

    e->prototypes = cwal_new_array(e);
    if(!e->prototypes){
        rc = CWAL_RC_OOM;
    }else{
        rc = cwal_array_reserve(e->prototypes, 16);
        if(!rc){
            cwal_value_ref2(e, CWAL_VALPART(e->prototypes))
                /* So that it cannot get sweep()'d up. */
                ;
            cwal_value_make_vacuum_proof(CWAL_VALPART(e->prototypes),1);
            if(vtab->hook.on_init){
                rc = vtab->hook.on_init( e, vtab );
            }
        }
    }
    end:
    return rc;
}


/*static int cwal_list_visit_FreeFunction( void * S, void * E ){
    cwal_engine * e = (cwal_engine *)E;
    cwal_function_info * f = (cwal_function_info *)S;
    cwal_state_cleanup( e, &f->state );
    cwal_string_unref( e, f->name );
    cwal_free( e, S );
    return CWAL_RC_OK;
}
*/

void cwal_state_cleanup( cwal_engine * e, cwal_state * s ){
    if( s ){
        if( s->finalize ) s->finalize( e, s->data );
        *s = cwal_state_empty;
    }
}

int cwal_engine_client_state_set( cwal_engine * e,
                                  void * state, void const * typeId,
                                  cwal_finalizer_f dtor){
    if(!e || !state) return CWAL_RC_MISUSE;
    else if(e->client.data) return CWAL_RC_ACCESS;
    else{
        e->client.data = state;
        e->client.typeID = typeId;
        e->client.finalize = dtor;
        return 0;
    }
}

void * cwal_engine_client_state_get( cwal_engine * e, void const * typeId ){
    return (e && (typeId == e->client.typeID))
        ? e->client.data
        : 0;
}

    
int cwal_output( cwal_engine * e, void const * src, cwal_size_t n ){
    return (e && src && n)
        ? (e->vtab->outputer.output
           ? e->vtab->outputer.output( e->vtab->outputer.state.data, src, n )
           : CWAL_RC_OK)
        : CWAL_RC_MISUSE;
}

int cwal_output_flush( cwal_engine * e ){
    return (e && e->vtab)
        ? (e->vtab->outputer.flush
           ? e->vtab->outputer.flush( e->vtab->outputer.state.data )
           : CWAL_RC_OK)
        : CWAL_RC_MISUSE;
}

static long cwal_printfv_appender_cwal_output( void * S, char const * s, long n )
{
    cwal_engine * e = (cwal_engine *)S;
    int rc = cwal_output( e, s, (cwal_size_t)n );
    return rc ? -1 : 0;
}

int cwal_outputfv( cwal_engine * e, char const * fmt, va_list args ){
    if(!e || !fmt) return CWAL_RC_MISUSE;
    else{
        long const prc = cwal_printfv( cwal_printfv_appender_cwal_output, e, fmt, args );
        return prc>=0 ? 0 : CWAL_RC_IO;
    }
}

    
int cwal_outputf( cwal_engine * e, char const * fmt, ... ){
    if(!e || !fmt) return CWAL_RC_MISUSE;
    else{
        int rc;
        va_list args;
        va_start(args,fmt);
        rc = cwal_outputfv( e, fmt, args );
        va_end(args);
        return rc;
    }
}

typedef struct BufferAppender {
    cwal_engine * e;
    cwal_buffer * b;
    int rc;
} BufferAppender;

static long cwal_printfv_appender_buffer( void * arg, char const * data, long n )
{
    BufferAppender * ba = (BufferAppender*)arg;
    cwal_buffer * sb = ba->b;
    if( !sb || (n<0) ) return -1;
    else if( ! n ) return 0;
    else
    {
        long rc;
        size_t npos = sb->used + n;
        if( npos >= sb->capacity )
        {
            const size_t asz = npos ? ((3 * npos / 2) + 1) : 32;
            if( asz < npos ) {
                ba->rc = CWAL_RC_RANGE;
                return -1; /* overflow */
            }
            else
            {
                rc = cwal_buffer_reserve( ba->e, sb, asz );
                if(rc) {
                    ba->rc = CWAL_RC_OOM;
                    return -1;
                }
            }
        }
        rc = 0;
        for( ; rc < n; ++rc, ++sb->used )
        {
            sb->mem[sb->used] = data[rc];
        }
        sb->mem[sb->used] = 0;
        return rc;
    }
}

int cwal_buffer_append( cwal_engine * e,
                        cwal_buffer * b,
                        void const * data,
                        cwal_size_t len ){
    BufferAppender ba;
    long rc;
    cwal_size_t sz;
    ba.b = b;
    ba.e = e;
    ba.rc = 0;
    if(!b || !data) return CWAL_RC_MISUSE;
    sz = b->used + len + 1/*NUL*/;
    rc = cwal_buffer_reserve( e, b, sz );
    if(rc) return rc;
    rc = cwal_printfv_appender_buffer(&ba, (char const *)data, (long)len );
    if( !ba.rc && (rc>=0) ) {
        b->mem[b->used] = 0;
        return 0;
    }
    else return ba.rc ? ba.rc : CWAL_RC_OOM;
}

int cwal_buffer_printfv( cwal_engine * e, cwal_buffer * b, char const * fmt, va_list args){
    if(!e || !b || !fmt) return CWAL_RC_MISUSE;
    else{
        BufferAppender ba;
        ba.b = b;
        ba.e = e;
        ba.rc = 0;
        cwal_printfv( cwal_printfv_appender_buffer, &ba, fmt, args );
        return ba.rc;
    }
}
int cwal_buffer_printf( cwal_engine * e, cwal_buffer * b, char const * fmt, ... ){
    if(!e || !b || !fmt) return CWAL_RC_MISUSE;
    else{
        int rc;
        va_list args;
        va_start(args,fmt);
        rc = cwal_buffer_printfv( e, b, fmt, args );
        va_end(args);
        return rc;
    }
}

static int cwal_scope_alloc( cwal_engine * e, cwal_scope ** S ){
    cwal_scope * s;
    assert( S && "Invalid NULL ptr.");
    s = *S;
    ++e->metrics.requested[CWAL_TYPE_SCOPE];
    if(!s){
        cwal_recycler * re = cwal_recycler_get(e, CWAL_TYPE_SCOPE);
        assert(re);
        if(re->count){
            s = (cwal_scope*)re->list;
            assert(s);
            if(s->parent){
                re->list = s->parent;
                s->parent = 0;
            }
            else re->list = 0;
            --re->count;
        }
        else{
            s = (cwal_scope *)cwal_malloc( e, sizeof(cwal_scope) );
            if(s){
                ++e->metrics.allocated[CWAL_TYPE_SCOPE];
                e->metrics.bytes[CWAL_TYPE_SCOPE] += sizeof(cwal_scope);
            }
        }
    }
    if(s){
        *s = cwal_scope_empty;
        s->e = e;
        if(*S != s) {
            s->allocStamp = CwalConsts.AllocStamp
                /* we use this later to know whether or not we need to
                   free() s. */;
            /* Potential TODO: use e as the allocStamp for any
               resources e allocates. */
            *S = s;
        }
    }
    return s ? CWAL_RC_OK : CWAL_RC_OOM;
}


int cwal_scope_push( cwal_engine * e, cwal_scope ** S ){
    if(!e) return CWAL_RC_MISUSE;
    else{
        cwal_scope * s = S ? *S : NULL;
        int rc;
        enum { Incr = 10 };
        rc = cwal_scope_alloc(e, &s);
        if(rc){
            assert(NULL == s);
            return rc;
        }else{
            assert(NULL != s);
            s->level = e->current ? (1 + e->current->level) : 1;
            s->parent = e->current;
            if(!e->top){
                assert(NULL == e->current);
                e->top = s;
            }
            e->current = s;
            if(S) *S = s;
            return CWAL_RC_OK;
        }
    }
}

int cwal_scope_pop( cwal_engine * e ){
    if(!e) return CWAL_RC_MISUSE;
    else if( 0 == e->current ) return CWAL_RC_RANGE;
    else{
        int rc;
        cwal_scope * p;
        cwal_scope * s = e->current;
        assert(s->e == e);
        p = s->parent;
        assert(p || (e->top==s));
        e->current = p;
        if(e->top==s) e->top = 0;
        rc = cwal_scope_free( e, s, 1 );
        assert(!rc && "Freeing scope failed?")
            /* we have no recovery strategy for this case.  But i
               "don't think it can fail" since the transition
               from arrays to linked lists for value ownership
               tracking.
            */;
        return rc;
    }
}

cwal_value * cwal_value_snip( cwal_value * v ){
    cwal_scope * p = v->scope;
    cwal_value * l = v->left;
    cwal_value * r = v->right;
    v->scope = 0;
    v->right = v->left = 0;
    assert(r != v);
    assert(l != v);
    if(l) l->right = r;
    if(r) r->left = l;
    if(p){
        /* Adjust value lifetime lists if v is the head of one of them.
           If it is not the head, then removal from its linked list
           is sufficient.
        */
        l = l ? l : r;
        if(l) while(l->left) l=l->left;
        if(p->mine.headObj==v){
            p->mine.headObj = l;
            CWAL_TR_SV(p->e,p,l);
            CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headObj.");
            if(p->mine.headObj) assert(0==p->mine.headObj->left);
        }else if(p->mine.headPod==v){
            p->mine.headPod = l;
            CWAL_TR_SV(p->e,p,l);
            CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headPod.");
            if(p->mine.headPod) assert(0==p->mine.headPod->left);
        }else if(p->mine.headSafe==v){
            p->mine.headSafe = l;
            CWAL_TR_SV(p->e,p,l);
            CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->headSafe.");
            if(p->mine.headSafe) assert(0==p->mine.headSafe->left);
        }else if(p->mine.r0==v){
            p->mine.r0 = l;
            CWAL_TR_SV(p->e,p,l);
            CWAL_TR3(p->e,CWAL_TRACE_SCOPE_MASK,"Scope replaced mine->r0.");
            if(p->mine.r0) assert(0==p->mine.r0->left);
        }
    }
    return r;
}

/**
    Inserts v so that v->right is now l, adjusting v and l as necessary.
    v->right and v->left must be 0 before calling this (it assert()s so).
*/
static void cwal_value_insert_before( cwal_value * l, cwal_value * v ){
    assert(0 == v->right);
    assert(0 == v->left);
    assert((l != v) && "Unexpected duplicate items for value list. "
           "Possibly caused by an unwarranted unref.");
    /* if(l != v){ */
        if( l->left ){
            l->left->right = v;
        }
        l->left = v;
        v->right = l;
    /* } */
}

cwal_value * cwal_string_from_recycler( cwal_engine * e, cwal_size_t len ){
    cwal_value * li = (cwal_value *)e->reString.list;
    cwal_string * s;
    cwal_value * prev = 0;
    cwal_size_t slen;
    cwal_size_t paddedLen;
    for( ; li; prev = li, li = li->right ){
        s = CWAL_STR(li) /*cwal_value_get_string(li)*/;
        assert(s);
        assert(0 < CWAL_STRLEN(s));
        assert(!CWAL_STR_ISXZ(s));
        slen = CWAL_STRLEN(s);
        if(!CwalConsts.StringPadSize){
            if(len!=slen) continue;
            /* Else fall through */
        }else if(len!=slen){
            /**
               If s's "padded length" is large enough for the request,
               but not "too large" (within 1 increment of
               CwalConsts.StringPadSize) then we will re-use it.
            */
            cwal_size_t const mod = (slen % CwalConsts.StringPadSize);
            paddedLen = mod
                ? (slen + (CwalConsts.StringPadSize - mod))
                : slen;
            if(paddedLen < len) continue;
            else if(paddedLen > len){
                if((paddedLen - CwalConsts.StringPadSize) > len) continue;
            }
        }
        if(prev){
            prev->right = li->right;
        }
        else {
            assert(e->reString.list == li);
            e->reString.list = li->right;
        }
        li->right = 0;
        --e->reString.count;
        if(CwalConsts.StringPadSize){
            s->length = CWAL_STRLEN_MASK & len;
        }
        /* MARKER("Pulling string of len %u from recycle bin.\n", (unsigned)len); */
        CWAL_TR_V(e,li);
        CWAL_TR3(e,CWAL_TRACE_MEM_FROM_RECYCLER,
                 "Pulled string from recycle bin.");
        return li;
    }
    return NULL;
}

    
static char cwal_string_recycle( cwal_engine * e, cwal_value * v ){
    cwal_string * s = cwal_value_get_string(v);
    cwal_size_t const slen = CWAL_STRLEN(s);
    char const * freeMsg;
    cwal_value * li;
    assert(s);
    if( slen > CwalConsts.MaxRecycledStringLen ){
        freeMsg = "String too long to recycle - freeing.";
        goto freeit;
    }
    else if(s->length & ~CWAL_STRLEN_MASK){
        freeMsg = "Cannot recycle x/z-strings.";
        goto freeit;
    }
    else if( 0 == e->reString.maxLength ){
        freeMsg = "String recycling disabled - freeing.";
        goto freeit;
    }
#if 0
    if(CwalConsts.StringPadSize){
        /**
           Re-set s->length to its padded length, to help ensure that
           pulling from the recycle doesn't suffer from an off-by-one.

           Nope... causes an off-by-one instead.
        */
        s->length = CWAL_STRLEN_MASK & (slen + (CwalConsts.StringPadSize - (slen % CwalConsts.StringPadSize)));
    }
#endif
    li = (cwal_value *)e->reString.list;
    if(e->reString.count>=e->reString.maxLength){
        /*
          Remove the oldest entries from the list.
          They have not been recycled in a while,
          and are not likely needed any more.

          To avoid unduly high N on the O(N) linked list traversal,
          when trimming the list we trim some percentage of it, as
          opposed to only the last (oldest) element. Otherwise this
          algo is remarkably slow on large recycle lists or when
          rapidly freeing many strings.

          TODO?: sort the strings by size(?) in order to optimize the
          from-recycle-bin size lookup?
        */
#if 0
        cwal_value * x = li;
        cwal_value * prev = 0;
        for( ;x && x->right; prev = x, x = x->right ){}
        if(prev) prev->right = 0;
        MARKER("Trimming last entry.\n");
        if(x == li) e->reString.list = li = 0;
        CWAL_TR_V(e,x);
        CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER,
                 "Popping stale string from recycler.");
        cwal_free(e,x);
        --e->reString.count;
#else
        cwal_size_t keep = e->reString.maxLength * 3 / 4;
        cwal_size_t i;
        cwal_value * x = li;
        cwal_value * prev = 0;
        cwal_value * cut = 0;
        for( i = 0; x;
             prev = x, x = x->right, ++i ){
            if(i==keep){
                assert(!x->left);
                cut = x;
                break;
            }
        }
        if(prev) prev->right = 0;
        else e->reString.list = 0;

#if 0
        MARKER("Trimming list to %u of %u entries cut=%c.\n",
               (unsigned)i, (unsigned) e->reString.maxLength, cut?'y':'n');
#endif

        if(cut){
#if 0
            e->reString.count = i;
            CWAL_TR_V(e,x);
            CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER,
                     "Popping stale string(s) from recycler.");
            cwal_value_list_free(e, cut);
#else
            /* We "could" just use cwal_value_list_free(),
               but i want more tracing info. */
            for( x = cut; x ; x = cut ){
                cut = x->right;
                x->right = 0;
                --e->reString.count;
                CWAL_TR_V(e,x);
                CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER,
                         "Popping stale string from recycler.");
                cwal_free(e,x);
            }
#endif
        }
        assert(e->reString.count < e->reString.maxLength);
#endif
    }

    /* MARKER("String to recyler...\n"); */
    assert(!v->right);
    assert(!v->scope);
    assert(!v->refcount);
    assert(CWAL_TYPE_STRING==v->vtab->typeID);
    li = (cwal_value*)e->reString.list;
    assert(!li || (CWAL_TYPE_STRING==li->vtab->typeID));
    v->right = li;
    e->reString.list = v;
    ++e->reString.count;
    CWAL_TR_V(e,v);
    CWAL_TR3(e, CWAL_TRACE_MEM_TO_RECYCLER,
             "Placed string in recycling bin.");
    return 1;
    freeit:
#if !CWAL_ENABLE_TRACE
    if(0){ assert(freeMsg && "Avoid unused var warning in non-trace builds."); }
#endif
    CWAL_TR_V(e,v);
    CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER, freeMsg);
    cwal_free( e, v );
    return 0;
    
}

int cwal_value_recycle( cwal_engine * e, cwal_value * v ){
    int ndx;
    cwal_recycler * re;    
    cwal_size_t max;
    cwal_obase * base;
#if CWAL_ENABLE_TRACE
    char const * freeMsg = "==> CANNOT RECYCLE. FREEING.";
#  define MSG(X) freeMsg = X
#else
#  define MSG(X)
#endif
    assert(e && v && v->vtab);
    assert(0==v->refcount);
    /* assert(CWAL_TYPE_XSTRING != v->vtab->typeID); */
    /* assert(CWAL_TYPE_ZSTRING != v->vtab->typeID); */
    if(CWAL_TYPE_STRING == v->vtab->typeID){
        if(!CWAL_STR_ISXZ(CWAL_STR(v))){
            return cwal_string_recycle( e, v );
        }
        /* else fall through */
    }
    else if(e->gcInitiator && CWAL_V_IS_OBASE(v)){
        /**
           This means a scope cleanup is running and deallocation must
           be delayed until the cleanup is finished. Move the value
           into the GC queue. We only need this for types which can
           participate in cycles, as it's easy to step on a destroyed
           value via a cycle during value destruction.
         */
        CWAL_TR_V(e,v);
        CWAL_TR3(e,CWAL_TRACE_MEM_TO_GC_QUEUE,
                 "Redirecting value to gc queue");
        ndx = cwal_gc_push( e, v );
        assert(0==ndx && "cwal_gc_push() can (now) only fail if !e->gcInitiator.");
        return (0==ndx)
            ? -1
            : 0;
    }
    assert( 0 == v->refcount );
    assert( 0 == v->right );
    assert( 0 == v->scope );
    ndx = cwal_recycler_index( v->vtab->typeID );
    if( ndx < 0 ) {
        /* Non-recylable type */
        MSG("==> Unrecyclable type. FREEING.");
        goto freeit;
    }
    re = &e->recycler[ndx];
    max = re->maxLength;
    assert(re->id == v->vtab->typeID);
    if(!max) {
        /* recycling disabled */
        MSG("==> Recyling disabled for this type. FREEING.");
        goto freeit;
    }
    else if( re->count >= max ){
        /* bin is full */
        MSG("==> Recyling bin for this type is full. FREEING.");
        goto freeit;
    }

    if( (base = CWAL_VOBASE(v)) ){
        base->flags |= CWAL_F_IS_RECYCLED;
    }
    if(re->list){
        cwal_value_insert_before( re->list, v );
    }
    ++re->count;
    re->list = v;
    if(re->count > 1){
        assert(((cwal_value*)re->list)->right);
    }
    CWAL_TR_V(e,v);
    CWAL_TR3(e, CWAL_TRACE_MEM_TO_RECYCLER,
             "Placed in recycling bin.");
    return 1;
    freeit:
    CWAL_TR_V(e,v);
    CWAL_TR3(e,CWAL_TRACE_MEM_TO_RECYCLER,freeMsg);
    *v = cwal_value_undef;
    cwal_free(e, v);
    return 0;
#undef MSG
}


int cwal_gc_push( cwal_engine * e, cwal_value * v ){
    cwal_obase * base;
    assert( e->gcInitiator );
    assert(0 == v->scope);
    assert(0 == v->right);
    if(!e->gcInitiator) return CWAL_RC_MISUSE;
    if(e->gcList){
        cwal_value_insert_before( e->gcList, v );
    }
    if( (base = CWAL_VOBASE(v)) ) base->flags |= CWAL_F_IS_GC_QUEUED;
    e->gcList = v;
    assert(0==e->gcList->left);
    return CWAL_RC_OK;
}

int cwal_gc_flush( cwal_engine * e ){
    int rc = 0;
    cwal_value * v;
    cwal_value * n;
    cwal_obase * base;
    assert( 0 == e->gcInitiator
            && "Otherwise we might have a loop b/t this and cwal_value_recycle()");
    for( v = e->gcList; v; v = n ){
        n = v->right;
        cwal_value_snip(v);
        if( (base = CWAL_VOBASE(v)) ) base->flags &= ~CWAL_F_IS_GC_QUEUED;
        rc = cwal_value_recycle(e, v);
        assert(-1!=rc && "Impossible loop!");
    }
    e->gcList = 0;
    return rc;
}

static void cwal_scope_insert( cwal_scope * s, cwal_value * v ){
    /* cwal_scope * p = v->scope; */
    cwal_value * list;
    cwal_value ** listpp = 0;
    cwal_obase const * b = 0;
    /* if(s == p) return; */
    assert(s && v);
    assert(!V_IS_BUILTIN(v));
    if(0==v->refcount){
        listpp = &s->mine.r0;
    }else if((b = CWAL_VOBASE(v))){
        listpp = CWAL_OBASE_IS_VACUUM_SAFE(b)
            ? &s->mine.headSafe
            : &s->mine.headObj;
    }else {
        listpp = &s->mine.headPod;
    }
    list = *listpp;
#if 1
    if(list == v){
        assert(v->scope == s);
        assert(!"Can this happen?");                    
        return;
    }
#endif
    cwal_value_snip( v );
    assert(0==v->right);
    assert(0==v->left);
    if(list) {
        v->right = list;
        assert(0==list->left);
        list->left = v;
    }
    *listpp = v;
    v->scope = s;
    assert(0==v->left);
    CWAL_TR_SV(s->e,s,v);
    CWAL_TR3(s->e,CWAL_TRACE_VALUE_SCOPED,"Value moved to higher/older scope.");
}


int cwal_value_take( cwal_engine * e, cwal_value * v ){
    cwal_scope * s = v ? v->scope : 0;
    if(!e || !v) return CWAL_RC_MISUSE;
    else if( V_IS_BUILTIN( v ) ) return CWAL_RC_OK;
    else if(!s) return CWAL_RC_RANGE;
    else{
        cwal_value_snip( v );
        CWAL_TR_SV(e,s,v);
        CWAL_TR3(e,CWAL_TRACE_VALUE_UNSCOPED,"Removed from parent scope.");
        assert(!v->scope);
        assert(0==v->right);
        assert(0==v->left);
        assert(s->mine.headObj != v);
        assert(s->mine.headPod != v);
        assert(s->mine.headSafe != v);
        assert(s->mine.r0 != v);
        return CWAL_RC_OK;
    }
}

int cwal_value_unref(cwal_value *v ){
    if(!v) return CWAL_RC_MISUSE;
    else if( V_IS_BUILTIN( v ) ) return CWAL_RC_OK;
    else if(!v->scope) return CWAL_RC_MISUSE;
#if 0
    cwal_unref_from(v->scope->e->current, v);
    return 0; /* breaks existing conventions */
#else
    else return cwal_value_unref2(v->scope->e, v);
#endif
}

int cwal_value_unref2(cwal_engine * e, cwal_value *v ){
    assert( e );
    if(NULL == e || NULL == v) return CWAL_RC_MISUSE;
    CWAL_TR_SV(e,v->scope,v);
    if(V_IS_BUILTIN(v)) return 0;
    else {
        cwal_obase * b;
        b = CWAL_VOBASE(v);
        CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"Unref'ing");
        /* undo_interning_refcount_kludge: */
        if(!v->refcount || !--v->refcount){
            int rc;
            cwal_scope * vScope = v->scope;
            CWAL_TR_V(e,v);

            if(b){
                if(CWAL_F_IS_GC_QUEUED & b->flags){
                    assert(!v->scope);
                    CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE,
                             "DESTRUCTION OF A GC-QUEUED OBJECT: SKIPPING");
                    /* Possible again since starting refcount at 0:
                       assert(!"This is no longer possible."); */
                    return CWAL_RC_DESTRUCTION_RUNNING;
                }
#if 1
                else if( CWAL_F_IS_RECYCLED & b->flags ) {
                    assert(!v->scope);
                    CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE,
                             "DESTRUCTION OF A RECYCLED OBJECT: SKIPPING");
                    /* Possible again since starting refcount at 0:
                       assert(!"This is no longer possible."); */
                    return CWAL_RC_DESTRUCTION_RUNNING;
                }
#endif
#if 1
                else if( CWAL_F_IS_DESTRUCTING & b->flags ) {
                    CWAL_TR3(e,CWAL_TRACE_VALUE_CYCLE,
                             "ENTERING DESTRUCTION A 2ND TIME (or more): SKIPPING");
                    /* Possible again since starting refcount at 0:
                       assert(!"This is no longer possible."); */
                    return CWAL_RC_DESTRUCTION_RUNNING;
                }
            }
#endif
            CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_START,
                     "Starting finalization...");

#if 1
            if(e->exception == v){
                e->exception = 0;
            }
            if(e->propagating == v){
                e->propagating = 0;
            }
#endif

            if(v->scope){
                rc = cwal_value_take(e, v)
                    /*ignoring rc! take() cannot fail any more under these
                      conditions.*/;
                assert((0 == rc) && "This op can no longer fail if v is valid.");
                assert(0 == v->scope );
            }
            /* The left/right assertions help ensure we're not now traversing
               through the recycle list.
            */
            if(v->left || v->right){
                dump_val(v,"Item from recycler???");
                if(v->left) dump_val(v->left,"v->left");
                if(v->right) dump_val(v->right,"v->right");
                assert(!"Trying to clean up item from recycler(?)!");
                abort();
            }

            if(b) b->flags |= CWAL_F_IS_DESTRUCTING;
            assert(0 == v->right);
            assert(0 == v->left);

            cwal_weak_unregister( e, v, v->vtab->typeID );
            v->scope = vScope
                /* Workaround to help support the (relatively new)
                   behaviour that containers will not destroy values
                   they reference which live in higher (older)
                   scopes. Instead they unref almost as normal, except
                   that an unref down to 0 will move the value to
                   scope->mine.r0 in that value's owning scope. This
                   is necessary for several script-side constructs
                   where a property of a scope-level object is being
                   returned from that scope and would normally (before
                   this change) be destroyed via the parent object's
                   scope.
                */;
            v->vtab->cleanup(e, v);
            v->scope = 0 /* END KLUDGE! */;
            if(b) b->flags &= ~CWAL_F_IS_DESTRUCTING;
            CWAL_TR_V(e,v);
            CWAL_TR3(e,CWAL_TRACE_VALUE_CLEAN_END,
                     "Cleanup complete. Sending to recycler...");
            assert(0 == v->refcount);
            cwal_value_recycle(e, v);
            return CWAL_RC_FINALIZED;
        }
#if 0
        /* Breaks horribly, as is usual with any refcount fiddling.
           One day i'll learn.
        */
        else if(1==v->refcount && CWAL_TYPE_STRING==v->vtab->typeID
                && (CWAL_FEATURE_INTERN_STRINGS & e->flags)
                && 0==cwal_interned_search_val(e, v, 0, 0, 0)){
            MARKER(("Doing string refcount cleanup kludge: %s\n",
                    cwal_value_get_cstr(v, 0)));
            cwal_interned_remove(e, v, 0);
            goto undo_interning_refcount_kludge;
            assert(!"Cannot reach here again.");
            abort() /* does not return */;
            return CWAL_RC_FATAL;
        }
#endif
        else{
            CWAL_TR_V(e,v);
            CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,
                     "It continues to live");
            return CWAL_RC_HAS_REFERENCES;
        }
    }
}

void cwal_value_unhand( cwal_value * v ){
    if(!v || !v->refcount/*will also get builtins*/) return;
    else if(0==--v->refcount){
        assert(v->scope);
        cwal_value_reprobate(v->scope, v);
    }else{
        assert(!V_IS_BUILTIN(v));
    }
}

/**
   Increments cv's reference count by 1. Asserts that
   !V_IS_BUILTIN(v) and (NULL!=cv). Code which might be
   dealing with those value should call the public-API-equivalent of
   this, cwal_value_ref().
*/
static void cwal_refcount_incr( cwal_engine *e, cwal_value * cv )
{
    assert( NULL != cv );
    assert(!V_IS_BUILTIN(cv));
    assert(cv->scope);
    if(1 == ++cv->refcount){
        int const rc = cwal_scope_from_r0(cv);
        /* If this is failing then internal
           preconditions/assumptions have not been met. */
        assert(0==rc);
        if(!rc){/*avoid unused var warning*/}
    }else if(0==cv->refcount){
        /* Overflow! */
        cwal_exception_setf(e, CWAL_RC_RANGE,
                            "Refcount overflow in value of type %s",
                            cwal_value_type_name(cv));
        assert(!"Refcount overflow! Undefined behaviour!");
        abort() /* does not return */;
    }
#if 0
    /* Is this sane? Needed? up-scope cv to
       e->current now if cv is i a lower scope? */
    if(cv->scope->level < e->current->level){
        cwal_value_xscope( e, e->current, cv, NULL );
    }
#endif
    CWAL_TR_V(e,cv);
    CWAL_TR3(e,CWAL_TRACE_VALUE_REFCOUNT,"++Refcount");
}

int cwal_value_ref2( cwal_engine *e, cwal_value * cv ){
    if( NULL == cv ) return CWAL_RC_MISUSE;
    else if( V_IS_BUILTIN(cv) ) return CWAL_RC_OK;
    else return CwalConsts.MaxSizeTCounter <= cv->refcount
        ? CWAL_RC_RANGE
        : (cwal_refcount_incr( e, cv ),0);
}
    
int cwal_value_ref( cwal_value * cv ){
    if( NULL == cv ) return CWAL_RC_MISUSE;
    else if( V_IS_BUILTIN(cv) ) return CWAL_RC_OK;
    else if( !cv->scope ){
        assert(!"It is no longer be possible to "
               "have no owning scope here.");
        return CWAL_RC_MISUSE;
    }
    else {
        assert( cv->scope->e );
        cwal_refcount_incr( cv->scope->e, cv );
        return 0;
    }
}

cwal_size_t cwal_value_refcount( cwal_value const * v ){
    return v ? v->refcount : 0;
}


int cwal_scope_current( cwal_engine * e, cwal_scope ** s ){
    if(!e || !s) return CWAL_RC_MISUSE;
    else if(!e->current) return CWAL_RC_RANGE;
    else{
        *s = e->current;
        return CWAL_RC_OK;
    }
}

cwal_scope * cwal_scope_current_get( cwal_engine * e ){
    return e ? e->current : 0;
}


/**
   Allocates a new value of the specified type. The value is pushed
   onto e->current, effectively transfering ownership to that scope.

   extra is a number of extra bytes to allocate after the "concrete
   type part" of the allocation. It is only valid for type
   CWAL_TYPE_STRING, and must be the length of the string to allocate
   (NOT included the terminating NUL byte - this function adds that
   byte itself).

   The returned value->vtab member will be set appropriately and.  Use
   the internal CWAL_VVPCAST() family of macros to convert the
   cwal_values to their corresponding native representation.

   Returns NULL on allocation error or if adding the new value
   to s fails.

   @see cwal_new_array()
   @see cwal_new_object()
   @see cwal_new_string()
   @see cwal_new_integer()
   @see cwal_new_double()
   @see cwal_new_function()
   @see cwal_new_native()
   @see cwal_new_buffer()
   @see cwal_new_hash()
   @see cwal_value_unref()
*/
static cwal_value * cwal_value_new(cwal_engine * e,
                                   cwal_scope * s,
                                   cwal_type_id t, cwal_size_t extra){
    enum { vsz = sizeof(cwal_value) };
    const cwal_size_t sz = vsz + extra /* base amount of memory to allocate */;
    cwal_size_t tx = 0 /* number of extra bytes to allocate for the
                          concrete value type */;
    cwal_value def = cwal_value_undef;
    cwal_value * v = NULL;
    switch(t)
    {
      case CWAL_TYPE_DOUBLE:
          assert( 0 == extra );
          def = cwal_value_double_empty;
          tx = sizeof(cwal_double_t);
          break;
      case CWAL_TYPE_INTEGER:
          assert( 0 == extra );
          def = cwal_value_integer_empty;
          /* We might want to consider using double for integers on
             these platforms, which would allow us to support
             larger-precision integers in the public interface by
             hiding the doubles behind them. With that we could
             effectively declare cwal_int_t as guarantying, say 53 or
             48 bits of precision. But would break if we add
             disable-doubles support (which i would like to eventually
             add as an option).
          */
          tx = sizeof(cwal_int_t);
          break;
      case CWAL_TYPE_STRING:{
          assert( (extra > 0) && "empty strings are handled elsewhere" );
          def = cwal_value_string_empty;
          tx = sizeof(cwal_string) + 1 /*NUL byte*/;
          if(CwalConsts.StringPadSize){
              int const pad = extra % CwalConsts.StringPadSize;
              if(pad) tx += CwalConsts.StringPadSize - pad;
          }
          break;
      }
      case CWAL_TYPE_XSTRING:
      case CWAL_TYPE_ZSTRING:
          assert( !extra && "x/z-string length is handled elsewhere" );
          def = cwal_value_string_empty;
          tx = sizeof(cwal_string) + sizeof(unsigned char **)
              /* x/z-strings are stored like
                 (cwa_value+cwal_string+(cstring-ptr)), and we stuff
                 the external string pointer in the cstring-ptr part.
              */;
          break;
      case CWAL_TYPE_ARRAY:
          assert( 0 == extra );
          def = cwal_value_array_empty;
          tx = sizeof(cwal_array);
          break;
      case CWAL_TYPE_OBJECT:
          assert( 0 == extra );
          def = cwal_value_object_empty;
          tx = sizeof(cwal_object);
          break;
      case CWAL_TYPE_FUNCTION:
          assert( 0 == extra );
          def = cwal_value_function_empty;
          tx = sizeof(cwal_function);
          break;
      case CWAL_TYPE_NATIVE:
          assert( 0 == extra );
          def = cwal_value_native_empty;
          tx = sizeof(cwal_native);
          break;
      case CWAL_TYPE_EXCEPTION:
          assert( 0 == extra );
          def = cwal_value_exception_empty;
          tx = sizeof(cwal_exception);
          break;
      case CWAL_TYPE_BUFFER:
          assert( 0 == extra );
          def = cwal_value_buffer_empty;
          tx = sizeof(cwal_buffer);
          break;
      case CWAL_TYPE_HASH:
          assert( 0 != extra );
          def = cwal_value_hash_empty;
          tx = sizeof(cwal_hash);
          break;
      default:
          assert(0 && "Unhandled type in cwal_value_new()!");
          /* FIXME: set e error state here. */
          return NULL;
    }
    assert( def.vtab->typeID != CWAL_TYPE_UNDEF );
    /* See if one of the recycle bins can serve the request... */
    if(CWAL_TYPE_STRING == t){
        v = cwal_string_from_recycler( e, extra );
    }
    else{
       int const recycleListIndex =
           cwal_recycler_index( t /*def.vtab->typeID (wrong for x/z-strings) */ );
       /* MARKER(("BIN #%d(%s)\n", recycleListIndex, def.vtab->typeName)); */
       if(0<=recycleListIndex){
            cwal_recycler * re = &e->recycler[recycleListIndex];
            if(re->count){
                /* Recycle (take) the first entry from the list. */
                cwal_obase * base;
            /* MARKER(("BIN #%d(%s) LENGTH=%u\n", recycleListIndex, cwal_value_type_id_name(t), (unsigned)re->count)); */

                v = (cwal_value*)re->list;
                assert(NULL != v);
                re->list = cwal_value_snip( v );
                --re->count;
                CWAL_TR_V(e,v);
                CWAL_TR3(e,CWAL_TRACE_MEM_FROM_RECYCLER,
                         "RECYCLED FROM BIN.");
                if( (base = CWAL_VOBASE(v)) ){
                    base->flags &= ~CWAL_F_IS_RECYCLED;
                }
                assert(v->vtab->typeID == re->id);
                if(def.vtab->typeID != CWAL_TYPE_STRING){
                    /* not true for x/z-strings */
                    assert(v->vtab->typeID == def.vtab->typeID);
                }
            }
        }
    }
    ++e->metrics.requested[t];
    if(!v){
        v = (cwal_value *)cwal_malloc(e, sz+tx);
        if(v){
            ++e->metrics.allocated[t];
            e->metrics.bytes[t] += sz+tx;
        }
    }
    if( v ) {
        int rc = 0;
        *v = def;
        if(tx || extra){
            memset(v+1, 0, tx + extra);
        }
        assert(0 == v->scope);
        assert(0 == v->refcount);

        CWAL_TR_V(e, v);
        CWAL_TR_S(e, s);
        CWAL_TR2(e, CWAL_TRACE_VALUE_CREATED);
        if(s) {
            int check = 0;
            rc = cwal_value_xscope( e, s, v, &check )
                /* reminder: xscope "cannot fail" in this case. */
                ;
            assert(!rc);
            assert(0 == v->refcount);
            assert(s == v->scope);
            assert(-1==check);
        }
        if(rc){
            /* Reminder to self: since the port to linked lists for
               values, this scenario essentially cannot happen for a
               new value.
            */
            v->vtab->cleanup( e, v );
            cwal_free(e, v);
            v = NULL;
        }
        else if(e->prototypes
                /* Will only be false once, while creating
                   e->prototypes! This also means e->prototypes will
                   not get the built-in prototype for arrays unless we
                   special-case that, but clients do not have access
                   to e->prototypes, anyway, so that shouldn't be a
                   problem/limitation.
                */){
            cwal_obase const * base = CWAL_VOBASE(v);
            if(base){
                cwal_value * proto = cwal_prototype_base_get(e, t);
                if(proto){
                    /*MARKER(("Setting %s prototype from base: @%p\n",
                      cwal_value_type_id_name(t), (void const*)proto));*/
                    cwal_value_prototype_set(v, proto);
                    assert(proto == cwal_value_prototype_get(e, v));
                }
            }
        }
#if !defined(NDEBUG)
        { /* Sanity-check that the CWAL_VOBASE() and CWAL_VALPART()
             can perform round-trip conversions. If not, much of cwal
             is broken!
          */
            cwal_obase const * baseCheck = CWAL_VOBASE(v);
            if(baseCheck){
                /*MARKER("v=@%p, baseCheck=@%p\n", (void const *)v, (void const *)baseCheck);*/
                assert( CWAL_VALPART(baseCheck) == v );
            }
        }
#endif
    }
    return v;
}

cwal_value * cwal_new_bool( char v ){
    return v ? &CWAL_BUILTIN_VALS.vTrue : &CWAL_BUILTIN_VALS.vFalse;
}

cwal_value * cwal_value_true(){
    return &CWAL_BUILTIN_VALS.vTrue;
}

cwal_value * cwal_value_false(){
    return &CWAL_BUILTIN_VALS.vFalse;
}

cwal_value * cwal_value_null(){
    return &CWAL_BUILTIN_VALS.vNull;
}

cwal_value * cwal_value_undefined(){
    return &CWAL_BUILTIN_VALS.vUndef;
}

cwal_value * cwal_new_integer( cwal_engine * e, cwal_int_t v ){
    if(!e) return NULL;
    else switch(v){
      case -1: METRICS_REQ_INCR(e,CWAL_TYPE_INTEGER); return CWAL_BUILTIN_VALS.vIntM1;
      case 0: METRICS_REQ_INCR(e,CWAL_TYPE_INTEGER); return CWAL_BUILTIN_VALS.vInt0;
      case 1: METRICS_REQ_INCR(e,CWAL_TYPE_INTEGER); return CWAL_BUILTIN_VALS.vInt1;
      default: {
          cwal_value * c = cwal_value_new(e, e->current, CWAL_TYPE_INTEGER,0);
          if( c ){
              *CWAL_INT(c) = v;
          }
          return c;
      }
    }
}

cwal_value * cwal_new_double( cwal_engine * e, cwal_double_t v )
{
    if( CWAL_BUILTIN_VALS.dbls.zero == v ){
        METRICS_REQ_INCR(e,CWAL_TYPE_DOUBLE);
        return CWAL_BUILTIN_VALS.vDbl0;
    }
    else if( CWAL_BUILTIN_VALS.dbls.one == v ){
        METRICS_REQ_INCR(e,CWAL_TYPE_DOUBLE);
        return CWAL_BUILTIN_VALS.vDbl1;
    }
    else if( CWAL_BUILTIN_VALS.dbls.mOne == v ){
        METRICS_REQ_INCR(e,CWAL_TYPE_DOUBLE);
        return CWAL_BUILTIN_VALS.vDblM1;
    }else{
        cwal_value * c = cwal_value_new(e, e->current, CWAL_TYPE_DOUBLE, 0);
        if( c ){
            *CWAL_DBL(c) = v;
        }
        return c;
    }
}


cwal_value * cwal_new_number( cwal_engine * e, cwal_double_t v ){
    return ((v > (cwal_double_t)CWAL_INT_T_MAX)
            || (v < (cwal_double_t)CWAL_INT_T_MIN))
        ? cwal_new_double(e, v)
        : cwal_new_integer(e, (cwal_int_t)v);
}

cwal_value * cwal_new_array_value(cwal_engine *e){
    cwal_value * v = (e && e->current)
        ? cwal_value_new(e, e->current, CWAL_TYPE_ARRAY,0)
        : 0;
    if( NULL != v ){
        cwal_array * ar = CWAL_ARRAY(v);
        cwal_value * proto = ar->base.prototype;
        *ar = cwal_array_empty;
        ar->base.prototype = proto;
    }
    return v;
}

cwal_array * cwal_new_array(cwal_engine *e){
    return cwal_value_get_array(cwal_new_array_value(e));
}

cwal_value * cwal_new_object_value(cwal_engine *e){
    cwal_value * v = (e && e->current)
        ? cwal_value_new(e, e->current, CWAL_TYPE_OBJECT,0)
        : 0;
    if( NULL != v )
    {
        cwal_object * ar = CWAL_OBJ(v);
        cwal_value * proto = ar->base.prototype;
        *ar = cwal_object_empty;
        ar->base.prototype = proto;
    }
    return v;
}

cwal_object * cwal_new_object(cwal_engine *e){
    return cwal_value_get_object(cwal_new_object_value(e));
}


/**
   Cleanup routine for the cwal_obase part of classes which use that.
   Cleans up the contents of b->kvp and sets b->kvp to NULL.  Also
   unrefs/NULLs b->prototype if unrefProto is true.
*/
static void cwal_cleanup_obase( cwal_engine * e, cwal_obase * b, char unrefProto ){
    cwal_value const * bv = CWAL_VALPART(b);
    while(b->kvp){
        cwal_kvp * kvp = b->kvp;
        cwal_kvp * next = 0;
        cwal_value * proto = b->prototype;
        assert(proto != bv);
        b->kvp = 0;
        /* In theory the is-visiting flag is not needed here because
           b->kvp=0 means we cannot possibly traverse the property
           list as a side-effect of this cleanup. Well, we cannot
           travse THIS property list. We can only hope that cleanup
           does not then add properties to b->kvp, but that's why we
           while(b->kvp).
        */
        /* b->flags |= CWAL_F_IS_VISITING; */
        for( ; kvp; kvp = next ){
            next = kvp->right;
            kvp->right = 0;
            /* if(kvp->key==bv) kvp->key = 0; */
            /* if(kvp->value==bv) kvp->value = 0; */
            cwal_kvp_free( e, bv->scope, kvp, 1 );
        }
        if( unrefProto && proto ){
            b->prototype = NULL;
            cwal_unref_from( bv->scope, proto );
        }
        /* b->flags &= ~CWAL_F_IS_VISITING; */
    }
}
    
static void cwal_value_cleanup_array_impl( cwal_engine * e, void * self,
                                           char freeList, char freeProps,
                                           char freeProto ){
    cwal_value * vSelf = (cwal_value *)self;
    cwal_array * ar = cwal_value_get_array(vSelf);
    assert(NULL!=ar);
    cwal_value_set_visiting( vSelf, 1 );
    if( ar->list.count ){
        cwal_value * v;
        cwal_size_t i = 0, x = ar->list.count -1;
        for( ; i < ar->list.count; ++i, --x ){
            v = (cwal_value*)ar->list.list[x];
            if(v){
                ar->list.list[x] = NULL;
                cwal_unref_from( vSelf->scope, v );
            }
        }
        ar->list.count = 0;
    }
    if(freeList && ar->list.list){
        const int listCount =
            sizeof(e->reList.lists)/sizeof(e->reList.lists[0]);
        if(e->reList.cursor < (listCount-1)){
            /* Move the list memory into the recycler. */
            e->reList.lists[++e->reList.cursor] = ar->list;
            ar->list = cwal_list_empty;
            /* MARKER(("Giving back array list memory to slot #%d of %d\n", e->reList.cursor, listCount)); */
        }else{
            cwal_list_reserve(e, &ar->list, 0);
        }
        /**ar = cwal_array_empty;*/
    }
    cwal_value_set_visiting( vSelf, 0 );
    if(freeProps) {
        cwal_cleanup_obase( e, &ar->base, 0 );
    }
    if(freeProto && ar->base.prototype){
        assert(vSelf->scope && "We can't have a prototype and yet have no scope.");
        /* dump_val(vSelf,"Unref'ing this one's prototype"); */
        /* dump_val(ar->base.prototype,"Unref'ing prototype"); */
        cwal_unref_from(vSelf->scope, ar->base.prototype);
        ar->base.prototype = 0;
    }
}


/**
   cwal_value_vtab::cleanup() impl for Array values. Cleans up
   self-owned memory, but does not free self.
*/
void cwal_value_cleanup_array( cwal_engine * e, void * self ){
    cwal_value_cleanup_array_impl( e, self, 1, 1, 1 );
}

void cwal_array_clear( cwal_array * ar, char freeList, char freeProps ){
    cwal_scope * s = ar ? CWAL_VALPART(ar)->scope : 0;
    cwal_engine * e = s ? s->e : 0;
    if(e){
        cwal_value_cleanup_array_impl( e, CWAL_VALPART(ar),
                                       freeList, freeProps, 0 );
    }
}
    
void cwal_value_cleanup_object( cwal_engine * e, void * self ){
    cwal_value * vSelf = (cwal_value *)self;
    cwal_object * ar = cwal_value_get_object(vSelf);
    assert(vSelf && ar);
    cwal_cleanup_obase( e, &ar->base, 1 );
    *ar = cwal_object_empty;
}


void cwal_value_cleanup_function( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value*)self;
    cwal_function * f = CWAL_VVPCAST(cwal_function,v);
    assert(v && f);
    if(f->state.finalize){
        f->state.finalize( e, f->state.data );
    }
    cwal_cleanup_obase( e, &f->base, 1 );
    *f = cwal_function_empty;
}

int cwal_value_fetch_function( cwal_value const * val, cwal_function ** x){
    if( ! val ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_function(val) ) return CWAL_RC_TYPE;
    else{
        if(x) *x = CWAL_VVPCAST(cwal_function,val);
        return 0;
    }
}
    

cwal_value * cwal_new_function_value(cwal_engine * e,
                                     cwal_callback_f callback,
                                     void * state,
                                     cwal_finalizer_f stateDtor,
                                     void const * stateTypeID ){
    if(!e || !callback) return NULL;
    else{
        cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_FUNCTION, 0);
        if( NULL != v ) {
            cwal_function * f = CWAL_VVPCAST(cwal_function,v);
            cwal_value * proto = f->base.prototype;
            *f = cwal_function_empty;
            f->base.prototype = proto;
            f->state.data = state;
            f->state.finalize = stateDtor;
            f->state.typeID = stateTypeID;
            f->callback = callback;
        }
        return v;
    }
}

cwal_function * cwal_new_function(cwal_engine * e,
                                  cwal_callback_f callback,
                                  void * state,
                                  cwal_finalizer_f stateDtor,
                                  void const * stateTypeID ){
    cwal_value * v = cwal_new_function_value(e, callback, state,
                                             stateDtor, stateTypeID);
    return v ? cwal_value_get_function(v) : NULL;
}

int cwal_function_unref(cwal_function *fv){
    cwal_value * v = CWAL_VALPART(fv);
    if(!v){
        assert(!fv);
        return CWAL_RC_MISUSE;
    }
    assert(v->scope);
    assert(v->scope->e);
    return cwal_value_unref2( v->scope->e, v );
}

cwal_engine * cwal_scope_engine(cwal_scope const * s){
    return s ? s->e : NULL;
}


int cwal_function_call_array( cwal_scope * s,
                              cwal_function * f,
                              cwal_value * self,
                              cwal_value ** rv,
                              cwal_array * args){
   cwal_value ** argv = args->list.count ? (cwal_value **)args->list.list : 0;
   int const argc = (int)args->list.count;
   return s
       ? cwal_function_call_in_scope( s, f, self, rv, argc, argv )
       : cwal_function_call( f, self, rv, argc, argv );
}

int cwal_function_call_in_scope( cwal_scope * s,
                                 cwal_function * f,
                                 cwal_value * self,
                                 cwal_value ** _rv,
                                 uint16_t argc,
                                 cwal_value * const * argv){
    if(!s ||!s->e || !f) return CWAL_RC_MISUSE;
    else {
        cwal_engine * e = s->e /* typing saver */;
        cwal_scope * old = e->current /* previous scope */;
        cwal_scope * check = 0 /* sanity check */;
        int rc = 0;
        cwal_callback_args args = cwal_callback_args_empty /* callback state */;
        cwal_value * rv = 0 /* result value for f() */;
        cwal_value * fv = CWAL_VALPART(f);
        cwal_callback_hook hook = e->cbHook /* why (again) do we take/need a copy? */;
        char const fWasVacuumProof = (f->base.flags & CWAL_F_IS_VACUUM_SAFE) ? 1 : 0;
        char const fHadRef = (fv->refcount>0) ? 1 : 0;
        /* uint32_t const oldScopeFlags = s->flags; */
        assert(fv->scope);
        args.engine = e;
        args.scope = s;
        args.self = self;
        args.callee = f;
        args.state = f->state.data;
        args.stateTypeID = f->state.typeID;
        args.argc = argc;
        args.argv = argv;
        /* s->flags |= CWAL_F_IS_CALL_SCOPE; */

        /*
          We set the vacuum-proofing flag and an artificial
          reference on f to practively cover this hypothetical
          case:

          function(){...}()

          where the anonymous function triggers a recursive sweep or
          vacuum.

          Reminder to self:

          (proc(){...})()

          in th1ish, that can theoretically kill that proc before the
          call op can be applied if auto-sweeping is running at an
          interval of 1!
        */
        if(!fWasVacuumProof) f->base.flags |= CWAL_F_IS_VACUUM_SAFE;
        if(!fHadRef){
            /* We can't just fiddle with the refcount here:
               we have to make sure fv is removed from
               fv->scope->mine.r0 so it's sweep-safe. */
            cwal_refcount_incr(e, fv);
        }
        if(hook.pre){
            rc = hook.pre(&args, hook.state);
        }
        if(!rc){
            rc = f->callback( &args, &rv );
            if(hook.post){
                /* ALWAYS call post() if pre() succeeds. */
                int const rc2 = hook.post(&args, hook.state,
                                          rc, rc ? NULL : rv);
                if(rc2 && !rc) rc = rc2;
            }
        }
        /* assert(fv->refcount>0 && "Someone took my ref!"); */
        if(!fWasVacuumProof) f->base.flags &= ~CWAL_F_IS_VACUUM_SAFE;
        if(!fHadRef){
            --fv->refcount /* our artificial ref, though possibly no
                              longer the only one! */;
            if(!fv->refcount){
                cwal_value_reprobate( fv->scope, fv);
            }
        }
        /* s->flags = oldScopeFlags; */
        check = e->current;
        if(old != check){
            /* i've never seen this happen - it's intended as a
               sanity check for higher-level implementors. */
            assert(!"The callback implementor or the engine "
                   "around it violated scope creation rules.");
            MARKER(("WARNING: callback created scopes without popping them "
                   "(or popped too many!)."
                    " Cleaning them up now!\n"));
            while(e->current && (e->current!=old)){
                cwal_scope_pop(e);
            }
            if(e->current!=old){
                assert(!"Serious scope mis-management during callback.");
                rc = CWAL_RC_FATAL;
                old = 0 /* assume it was lost along the way */;
            }
        }
        e->current = old;
        assert(e->current);
        if(!rc){
            if(!rv) rv = cwal_value_undefined();
            if(_rv) *_rv = rv;
            /* Reminder: don't automatically unref it on error.
               That would be bad in certain cases. Worst-case now
               is that we leave rv orphaned in the current scope.
            */
        }
        return rc;
    }
}

void * cwal_args_state( cwal_callback_args const * args,
                        void const * stateTypeID ){
    return (args && (args->stateTypeID==stateTypeID))
        ? args->state
        : NULL;
}

void * cwal_function_state_get( cwal_function * f,
                                void const * stateTypeID ){
    return (f && (f->state.typeID==stateTypeID))
        ? f->state.data
        : NULL;

}

void * cwal_args_callee_state( cwal_callback_args const * args,
                               void const * stateTypeID ){
    return (args && args->callee)
        ? cwal_function_state_get(args->callee, stateTypeID)
        : NULL;

}

int cwal_function_call_in_scopef( cwal_scope * s,
                                  cwal_function * f,
                                  cwal_value * self,
                                  cwal_value ** rv, ... ){
    if(!s || !s->e || !f) return CWAL_RC_MISUSE;
    else {
        int rc = CWAL_RC_OK;
        cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS+1] = {0,};
        cwal_value * v;
        uint16_t argc = 0;
        va_list args;
        memset( argv, 0, sizeof(argv)/sizeof(argv[0]) );
        va_start(args, rv);
        while( (v=va_arg(args,cwal_value*)) ){
            if(argc>CWAL_OPT_MAX_FUNC_CALL_ARGS){
                rc = CWAL_RC_RANGE;
                break;
            }
            else argv[argc++] = v;
        }
        va_end(args);
        if(CWAL_RC_OK==rc){
            argv[argc] = 0;
            rc = cwal_function_call_in_scope( s, f, self, rv, argc, argv );
        }
        return rc;
    }
}

int cwal_function_call( cwal_function * f,
                        cwal_value * self,
                        cwal_value ** rv,
                        uint16_t argc,
                        cwal_value * const * argv ){
    cwal_value * fv = f ? cwal_function_value(f) : NULL;
    cwal_engine * e = (fv && fv->scope) ? fv->scope->e : NULL;
    if(!e) return CWAL_RC_MISUSE;
    else{
        int rc, rc2;
        cwal_scope _sub = cwal_scope_empty;
        cwal_scope * s = &_sub;
        cwal_scope * oldS = e->current;
        cwal_value * v = 0;
        rc = cwal_scope_push(e, &s);
        if(rc) return rc;
        rc = cwal_function_call_in_scope( s, f, self, &v, argc, argv );
        if(!rc && rv && v) {
            cwal_value_rescope(oldS, v);
            *rv = v;
        }
        rc2 = cwal_scope_pop(e);
        return rc ? rc : rc2;
    }
}

int cwal_function_callv( cwal_function * f, cwal_value * self,
                         cwal_value ** rv, va_list args ){
    cwal_value * fv = f ? cwal_function_value(f) : NULL;
    cwal_engine * e = (fv && fv->scope) ? fv->scope->e : NULL;
    if(!e) return CWAL_RC_MISUSE;
    else {
        cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS+1] = {0,};
        cwal_value * v;
        uint16_t argc = 0;
        int rc = 0;
        memset( argv, 0, sizeof(argv) );
        while( (v=va_arg(args,cwal_value*)) ){
            if(argc>CWAL_OPT_MAX_FUNC_CALL_ARGS){
                rc = CWAL_RC_RANGE;
                break;
            }
            else argv[argc++] = v;
        }
        if(rc) return rc;
        else{
            argv[argc] = 0;
            return cwal_function_call( f, self, rv, argc, argv );
        }
    }
}

    
int cwal_function_callf( cwal_function * f,
                         cwal_value * self,
                         cwal_value ** rv,
                         ... ){
    int rc = 0;
    va_list args;
    va_start(args, rv);
    rc = cwal_function_callv( f, self, rv, args );
    va_end(args);
    return rc;
}

cwal_function * cwal_value_get_function( cwal_value const * v ) {
    cwal_function * ar = NULL;
    cwal_value_fetch_function( v, &ar );
    return ar;
}

cwal_value * cwal_function_value(cwal_function const * s){
    return CWAL_VALPART(s);
}

cwal_value * cwal_new_buffer_value(cwal_engine *e, cwal_size_t startingSize){
    cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_BUFFER,0);
    if( NULL != v )
    {
        cwal_buffer * b = CWAL_BUF(v);
        assert(NULL != b);
        *b = cwal_buffer_empty;
        if(startingSize &&
           cwal_buffer_reserve(e, b, startingSize)){
            cwal_value_unref2(e, v);
            v = NULL;
        }
    }
    return v;
}

int cwal_buffer_unref(cwal_engine *e, cwal_buffer *v){
    return (e&&v)
        ? cwal_value_unref2( e, cwal_buffer_value(v) )
        : CWAL_RC_MISUSE;
}

int cwal_value_fetch_buffer( cwal_value const * val, cwal_buffer ** x){
    if( ! val ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_buffer(val) ) return CWAL_RC_TYPE;
    else{
        if(x) *x = CWAL_VVPCAST(cwal_buffer,val);
        return 0;
    }
}
    
cwal_buffer * cwal_value_get_buffer( cwal_value const * v ) {
    cwal_buffer * ar = NULL;
    cwal_value_fetch_buffer( v, &ar );
    return ar;
}

cwal_buffer * cwal_new_buffer(cwal_engine *e, cwal_size_t startingSize){
    return cwal_value_get_buffer(cwal_new_buffer_value(e, startingSize));
}

cwal_value * cwal_buffer_value(cwal_buffer const * s){
    if(!s) return 0;
    else{
        cwal_value * v = CWAL_VALPART(s);
        return (v && v->vtab && (CWAL_TYPE_BUFFER==v->vtab->typeID))
            /* trying to protect against misuse of stack-allocated
               buffers there. */
            ? v : 0;
    }
}

cwal_string * cwal_buffer_to_zstring(cwal_engine * e, cwal_buffer * b){
    if(!e || !e->current || !b) return 0;
    else{
        cwal_string * s = cwal_new_zstring(e, b->used ? (char *)b->mem : 0,
                                           b->used);
        if(s){
            assert(e->metrics.bytes[CWAL_TYPE_BUFFER] >= b->used);
            e->metrics.bytes[CWAL_TYPE_BUFFER] -= b->used
                /* Z-string metrics were already updated */;
            *b = cwal_buffer_empty;
        }
        return s;
    }
}

    
/**
   cwal_value_vtab::destroy_value() impl for Buffer
   values. Cleans up self-owned memory, but does not
   free self.
*/
void cwal_value_cleanup_buffer( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value*)self;
    cwal_buffer_reserve(e, CWAL_BUF(v), 0 );
}


void cwal_value_cleanup_exception( cwal_engine * e, void * self ){
    cwal_value * v = (cwal_value*)self;
    cwal_exception * f = CWAL_VVPCAST(cwal_exception,v);
    cwal_cleanup_obase(e, &f->base, 1);
    *f = cwal_exception_empty;
}

cwal_value * cwal_new_exception_value( cwal_engine * e, int code, cwal_value * msg ){
    cwal_value * v = e
        ? cwal_value_new(e, e->current, CWAL_TYPE_EXCEPTION, 0 )
        : NULL;
    if(v){
        cwal_value * proto;
        cwal_exception * r;
        static int codeKeyLen = 0;
        static int msgKeyLen = 0;
        int rc;
        if(!codeKeyLen){
            codeKeyLen = cwal_strlen(CwalConsts.ExceptionCodeKey);
            msgKeyLen = cwal_strlen(CwalConsts.ExceptionMessageKey);
        }
        /*
          Reminder:

          i would prefer to have a cwal_exception::message member, but
          lifetime of it gets problematic. One solution would be to
          move the xscope() operation into cwal_value_vtab, so that we
          can generically xscope Exception values without having to
          know that they have a free-floating member (not in a
          cwal_obase::kvp list).

          (That feature has since been added, by the way.)
        */
        r = cwal_value_get_exception(v);
        assert(r);
        proto = r->base.prototype;
        *r = cwal_exception_empty;
        r->base.prototype = proto;
        r->code = code;
        rc = cwal_prop_set(v, CwalConsts.ExceptionCodeKey,
                           codeKeyLen,
                           cwal_new_integer(e, (cwal_int_t)code));
        if(!rc && msg) rc = cwal_prop_set(v,
                                          CwalConsts.ExceptionMessageKey,
                                          msgKeyLen,
                                          msg);
        if(0!=rc){
            cwal_value_unref2(e, v);
            v = 0;
        }
    }
    return v;
}

cwal_exception * cwal_new_exceptionfv(cwal_engine * e, int code, char const * fmt, va_list args ){
    if(!e) return 0;
    else if(!fmt || !*fmt) return cwal_new_exception(e,code, NULL);
    else{
        cwal_string * s = cwal_new_stringfv( e, fmt, args);
        cwal_exception * x;
        if(!s) return NULL;
        x = cwal_new_exception(e, code, cwal_string_value(s));
        if(!x) cwal_string_unref(s);
        return x;
    }
}

cwal_exception * cwal_new_exceptionf(cwal_engine * e, int code, char const * fmt, ...){
    if(!e) return 0;
    else if(!fmt || !*fmt) return cwal_new_exception(e,code, NULL);
    else{
        cwal_exception * x;
        va_list args;
        va_start(args,fmt);
        x = cwal_new_exceptionfv(e, code, fmt, args);
        va_end(args);
        return x;
    }
}


    
int cwal_exception_unref(cwal_engine *e, cwal_exception *v){
    return (e&&v)
        ? cwal_value_unref2( e, cwal_exception_value(v) )
        : CWAL_RC_MISUSE;
}

    
int cwal_value_fetch_exception( cwal_value const * val, cwal_exception ** x){
    if( ! val ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_exception(val) ) return CWAL_RC_TYPE;
    else{
        if(x) *x = CWAL_VVPCAST(cwal_exception,val);
        return 0;
    }
}

cwal_exception * cwal_value_get_exception( cwal_value const * v ){
    cwal_exception * r = 0;
    cwal_value_fetch_exception( v, &r );
    return r;
}
    
cwal_exception * cwal_new_exception( cwal_engine * e, int code, cwal_value * msg ){
    cwal_value * v = cwal_new_exception_value(e, code, msg);
    return v ? cwal_value_get_exception(v) : NULL;
}
    
cwal_value * cwal_exception_value(cwal_exception const * s){
    return s
        ? CWAL_VALPART(s)
        : NULL;
}

int cwal_exception_code_get( cwal_exception const * r ){
    return r ? r->code : cwal_exception_empty.code;
}
int cwal_exception_code_set( cwal_exception * r, int code ){
    return r
        ? (r->code=code, 0)
        : CWAL_RC_MISUSE;
}

cwal_value * cwal_exception_message_get( cwal_exception const * r ){
    if(!r || !r->base.kvp) return NULL;
    else{
        cwal_kvp * kvp = cwal_kvp_search( r->base.kvp,
                                          CwalConsts.ExceptionMessageKey,
                                          cwal_strlen(CwalConsts.ExceptionMessageKey),
                                          NULL, NULL );
        return kvp ? kvp->value : NULL;
    }
}

int cwal_exception_message_set( cwal_engine * e, cwal_exception * r, cwal_value * msg ){
    if(!e || !r) return CWAL_RC_MISUSE;
    else return cwal_prop_set( cwal_exception_value(r),
                               CwalConsts.ExceptionMessageKey,
                               cwal_strlen(CwalConsts.ExceptionMessageKey),
                               msg );
}
    
    
/**
   Fetches v's string value as a non-const string.

   cwal_strings are supposed to be immutable, but this form provides
   access to the immutable bits, which are v->length bytes long. A
   length-0 string is returned as NULL from here, as opposed to
   "". (This is a side-effect of the string allocation mechanism.)
   Returns NULL if !v or if v is the internal empty-string singleton.
*/
static char * cwal_string_str(cwal_string *v){
    /*
      See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
    */
    assert(v &&
           !CWAL_STR_ISX(v)/* not allowed for x-strings */);
    return CWAL_STR_ISZ(v)
        ? (char *)*((unsigned char **)(v+1))
        : (CWAL_STRLEN(v)
           ? (char *)((unsigned char *)( v+1 ))
           : NULL
           );
}

char const * cwal_string_cstr(cwal_string const *v){
#if 1
    return (NULL == v)
        ? NULL
        : (CWAL_STR_ISXZ(v)
           ? (char const *) *((unsigned char const **)(v+1))
           : (char const *) ((unsigned char const *)(v+1)))
        ;
#else
    /*
      See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a
    */
    return (NULL == v)
        ? NULL
        : (CWAL_STRLEN(v)
           ? (CWAL_STR_ISXZ(v)
              ? (char const *) *((unsigned char const **)(v+1))
              : (char const *) ((unsigned char const *)(v+1)))
           : "");
#endif
}


void cwal_value_cleanup_string( cwal_engine * e, void * V ){
    cwal_value * v = (cwal_value*)V;
    cwal_string * s = cwal_value_get_string(v);
    assert(s);
    assert(CWAL_STRLEN(s) && "Empty string cannot be cleaned up - it doesn't refcount.");
    if(CWAL_STR_ISZ(s)){
        unsigned char ** pos = (unsigned char **)(s+1);
        char * cs = cwal_string_str(s);
        assert(cs == (char *)*pos);
        *pos = NULL;
        if(e->flags & CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP){
            memset(cs, 0, CWAL_STRLEN(s));
        }
        cwal_free(e, cs);
    }else if(CWAL_STR_ISX(s)){
        unsigned char const ** pos = (unsigned char const **)(s+1);
#ifndef NDEBUG
        char const * cs = cwal_string_cstr(s);
        assert(cs == (char *)*pos);
#endif
        *pos = NULL;
        /* Nothing to free - the bytes are external */
    }else{/* Is a normal string, not an X/Y-string */
        cwal_interned_remove( e, v, 0 );
        if(e->flags & CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP){
            char * cs = cwal_string_str(s);
            memset(cs, 0, CWAL_STRLEN(s));
        }
    }
}

int cwal_string_unref(cwal_string * s){
    cwal_value * v = cwal_string_value(s);
    return v
        ? cwal_value_unref2( cwal_value_engine(v), v )
        : CWAL_RC_MISUSE;
}

cwal_size_t cwal_string_length_bytes( cwal_string const * str ){
    return (!str || !str->length)
        ? 0U
        : CWAL_STRLEN(str);
}

cwal_value * cwal_new_string_value(cwal_engine * e, char const * str, cwal_size_t len){
    return cwal_string_value( cwal_new_string(e, str, len) );
}

char cwal_cstr_internable_predicate_f_default( void * state, char const * str, cwal_size_t len ){

    return !CwalConsts.MaxInternedStringSize
        || (len <= CwalConsts.MaxInternedStringSize);
}

cwal_string * cwal_new_string(cwal_engine * e, char const * str, cwal_size_t len){
    if(!e) return NULL;
    else if( !str || !*str ){
        METRICS_REQ_INCR(e,CWAL_TYPE_STRING);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }else{
        cwal_value * c = 0;
        cwal_string * s = 0;
        if(!len) {
            len = cwal_strlen(str);
            if(!len){
                METRICS_REQ_INCR(e,CWAL_TYPE_STRING);
                return CWAL_BUILTIN_VALS.sEmptyString;
            }
        }
        if(CWAL_FEATURE_INTERN_STRINGS & e->flags){
            cwal_interned_search( e, str, len, &c, 0, 0 );
        }
        if(c){ /* Got an interned string... */
            s = cwal_value_get_string(c);
            assert(0 != s);
            METRICS_REQ_INCR(e,CWAL_TYPE_STRING);
            CWAL_TR_V(e,c);
            CWAL_TR3(e,CWAL_TRACE_VALUE_INTERNED, "RE-USING INTERNED STRING");
            /*
              Reminders to self:

              Now that we start at refcount==0 for new values, we might not
              need to add a refcount here. Hmmm.

              s1 ==> new_string("foo"); // refcount==0
              s2 ==> new_string("foo"); // refcount==1
              s3 ==> new_string("foo"); // refcount==2

              obj.set(s1,s1)
              obj.clear_props();

              That would destroy all 3 references, which, i think, is
              the proper behaviour. If we don't incr the refcount, the
              string stays forever in a scope->mine.r0 somewhere and
              will get freed by a sweep.

              If we increase the refcount we risk only orphaning one
              (previously) shared instance with a high refcount but no
              real references. It will get cleaned up at scope-pop,
              but potentially never swept up.

              If we don't increase the ref we have this problem:

              "hi"
              { "hi" }

              would clean up both instances when the scope pop... NO,
              OMG, it WON'T because the string is rooted in the first
              instance's scope!

              i'll probably regret this later, but for now let's NOT
              increment them and see what happens.

              i can imagine a case like:

              funcCall( "hi", // refcount==0
                {
                  {
                    var x = "hi" // refcount==1 b/c of assignment
                  }
                  // refcount reduced to 0
                  / No, it's not (again) b/c "hi" is now in the other scope.
                });

              Now, if those two elements were part of, e.g., an
              argument list, the first instance of "hi" would be
              cleaned up after evaluation of the inner-most scope. No.
              No, it doesn't because "hi" lives in the scope of the
              func arg list evaluation and containers (now) never
              clean up such values - they "re-probate" them back into
              scope->mine.r0 in the higher scope (where the value
              lives).

              If a sweep happened in the argument evaluation scope
              during that time then blammo, "hi" would be nuked, but
              doing that would be generically a big no-no, i think.

              ...later...

              If we do NOT ref() here then we end up with interned
              strings possibly getting sweep()'d up or otherwise
              cleaned up too soon. The worst that happens if we ref is
              that we almost guaranty that the value will get stranded
              in its owning scope because its refcount will always be
              twice (or nearly that) what it should be. So let's try
              this: if the value has a refcount, don't ref it again,
              otherwise ref it only to move it out of the scope's r0
              list. We might orphan it still, but it will, at most,
              have a refcount of only 1 higher than it "should" be.

              For some reason, this problem has not yet bitten me in
              the th1ish code. If it ever does, we may need to always
              increase the refcount for interned strings EXCEPT for
              the first instance.

              Hypothetical problem case (th1ish):

              var ar = array[]
              someFunc("hi", ar.0="hi", ar.0=undefined)

              Okay, it's kinda arbitrary, but in this case "hi" would
              be cleaned up when that 3rd argument is processed unless
              it's moved back to probationary status.

              Non-arbitrary case:

              "hi"
              scope {
                var obj.hi = true
                undefined
              }

              "hi" will hypothetically be cleaned up by obj, except
              that it's cleanup explicitly does cwal_unref_from() to
              avoid that. There might be similar cases lying in wait
              for us which do cwal_value_unref() instead.
            */
#if 0
            /*
              20140407: So far, so good, without this (in th1ish).
              My tests show no notable difference in behaviour with
              or without an extra ref. Because...

              20140509: we never actually hit this because
              th1ish aggressively sweeps them up.
            */
            if(!c->refcount){
                /*MARKER(("interned-string refcount kludge: %.*s\n",
                  (int)CWAL_STRLEN(s), cwal_string_cstr(s)));*/
                cwal_refcount_incr(e,c);
            }
#endif

#if 0
            cwal_value_xscope( e, e->current, c, 0 )
                /* We theoretically don't need this, as
                   c must live either in e->current or an
                   older scope at this point.
                */;
#else
            assert(c->scope->level <= e->current->level);
#endif
        }
        else{ /* Create new string... */
            /*MARKER("len=%"CWAL_SIZE_T_PFMT"\n",len);*/
            c = cwal_value_new(e, e->current, CWAL_TYPE_STRING, len);
            if( c ){
                char * dest = NULL;
                s = CWAL_STR(c);
                *s = cwal_string_empty;
                assert( NULL != s );
                s->length = CWAL_STRLEN_MASK & len;
                assert(CWAL_STRLEN(s) == len);
                dest = cwal_string_str(s)
                    /* maintenance note: this is the only place in the
                       library where _writing_ to a normal
                       (non-X/Z-string) cwal_string is allowed.
                    */;
                assert( (NULL != dest)
                        && "Empty string should have been caught earlier!");
                memcpy( dest, str, len );
                dest[len] = 0;
                if( (CWAL_FEATURE_INTERN_STRINGS & e->flags)
                    && (e->vtab->interning.is_internable
                        && e->vtab->interning.is_internable( e->vtab->interning.state, str, len )
                        )
                    ){
                    cwal_interned_insert( e, c )
                        /* This insertion effectively controls whether
                           or not interning of strings is on. If it
                           fails, the string is effectively not
                           interned, but otherwise no harm is
                           done. Allocation of a new interning table
                           could fail, but that's about the only
                           conceivable error condition here (and we
                           can ignore it by not interning).
                        */;
                    /* MARKER(("INTERNING rc=%d: %.*s\n", rc, (int)len, str)); */
                }
            }
        }
        return s;
    }
}

cwal_string * cwal_new_xstring(cwal_engine * e, char const * str, cwal_size_t len){
    if( !str || !*str ){
        METRICS_REQ_INCR(e,CWAL_TYPE_XSTRING);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }else{
        cwal_value * c = NULL;
        cwal_string * s = NULL;
        if(!len) {
            len = cwal_strlen(str);
            assert(len && "Length-0 strings are handled above.");
        }
        c = cwal_value_new(e, e->current, CWAL_TYPE_XSTRING, 0);
        if( c ){
            unsigned char  const ** dest;
            s = CWAL_STR(c);
            assert( NULL != s );
            *s = cwal_string_empty;
            s->length = CWAL_STR_XMASK | len;
            assert(s->length > len);
            assert(CWAL_STRLEN(s) == len);
            assert(CWAL_STR_ISX(s));
            assert(CWAL_STR_ISXZ(s));
            dest = (unsigned char const **)(s+1);
            *dest = (unsigned char const *)str;
        }
        return s;
    }
}

cwal_value * cwal_new_xstring_value(cwal_engine * e, char const * str, cwal_size_t len){
    cwal_string * s = cwal_new_xstring(e, str, len);
    return s ? cwal_string_value(s) : NULL;
}

cwal_string * cwal_new_zstring(cwal_engine * e, char * str, cwal_size_t len){
    if(!e) return NULL;
    else if(!str){
        METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }else if(!len && !*str){
        /* Special case to avoid leaking empty strings */
        METRICS_REQ_INCR(e,CWAL_TYPE_ZSTRING);
        cwal_free(e, str);
        return CWAL_BUILTIN_VALS.sEmptyString;
    }else{
        cwal_value * c = NULL;
        cwal_string * s = NULL;
        if(!len) len = cwal_strlen(str);
        assert(len>0);
        c = cwal_value_new(e, e->current, CWAL_TYPE_ZSTRING, 0);
        if( c ){
            unsigned char  ** dest;
            s = CWAL_STR(c);
            assert( NULL != s );
            *s = cwal_string_empty;
            s->length = CWAL_STR_ZMASK | len;
            e->metrics.bytes[CWAL_TYPE_ZSTRING] += len +1
                    /* we're going to assume a NUL byte for
                       metrics purposes, because there essentially
                       always is one for z-strings. */;
            assert(s->length > len);
            assert(CWAL_STRLEN(s) == len);
            assert(CWAL_STR_ISZ(s));
            assert(CWAL_STR_ISXZ(s));
            dest = (unsigned char **)(s+1);
            *dest = (unsigned char *)str;
        }else{
            /* See the API docs for why we do this. */
            cwal_free( e, str );
        }
        return s;
    }
}

cwal_value * cwal_new_zstring_value(cwal_engine * e, char * str, cwal_size_t len){
    cwal_string * s = cwal_new_zstring(e, str, len);
    return s ? cwal_string_value(s) : NULL;
}


int cwal_buffer_reset( cwal_buffer * b ){
    if(!b) return CWAL_RC_MISUSE;
    else{
        if(b->capacity){
            assert(b->mem);
            b->mem[0] = 0;
        }
        b->used = 0;
        return 0;
    }
}

int cwal_buffer_resize( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ){
  if( !buf ) return CWAL_RC_MISUSE;
  else if(n && (buf->capacity == n+1)){
    buf->used = n;
    buf->mem[n] = 0;
    return 0;
  }else{
      unsigned char * x = (unsigned char *)cwal_realloc( e, buf->mem,
                                                         n+1/*NUL*/ );
      if( ! x ) return CWAL_RC_OOM;
      if(n > buf->capacity){
          /* zero-fill new parts */
          memset( x + buf->capacity, 0, n - buf->capacity +1/*NUL*/ );
      }
      buf->capacity = n + 1 /*NUL*/;
      buf->used = n;
      buf->mem = x;
      buf->mem[buf->used] = 0;
      return 0;
  }
}


cwal_string * cwal_new_stringfv(cwal_engine * e, char const * fmt, va_list args ){
    if(!e || !fmt) return 0;
    else if(!*fmt) return cwal_new_string(e,"",0);
    else{
        int rc;
        /*
          = cwal_buffer_reserve(e, &e->buffer, 32);
        if(rc) return NULL;
        cwal_buffer_reset(&e->buffer);
        */
#if 0
        cwal_buffer buf = cwal_buffer_empty;
        cwal_string * sv;
        rc = cwal_buffer_printfv(e, &buf, fmt, args);
        sv = rc
            ? NULL
            : cwal_new_string(e,
                    buf.used ? (char const*)(buf.mem) : NULL,
                    buf.used);
        cwal_buffer_reserve(e, &buf, 0);
        return sv;
#else
        cwal_size_t const oldUsed = e->buffer.used;
        cwal_size_t slen;
        rc = cwal_buffer_printfv(e, &e->buffer, fmt, args);
        slen = e->buffer.used - oldUsed;
        e->buffer.used = oldUsed;
        return rc
            ? NULL
            : cwal_new_string(e,
                    slen ? (char const*)(e->buffer.mem+oldUsed) : NULL,
                    slen);
            ;
#endif
    }
}

cwal_string * cwal_new_stringf(cwal_engine * e, char const * fmt, ...){
    if(!e || !fmt) return 0;
    else if(!*fmt) return cwal_new_string(e,"",0);
    else{
        cwal_string * str;
        va_list args;
        va_start(args,fmt);
        str = cwal_new_stringfv(e, fmt, args);
        va_end(args);
        return str;
    }
}


cwal_value * cwal_string_value(cwal_string const * s){
    return s
        ? (CWAL_STRLEN(s)
           ? CWAL_VALPART(s)
           : CWAL_BUILTIN_VALS.vEmptyString)
        : NULL;
}

cwal_engine * cwal_value_engine( cwal_value const * v ){
    return (v && v->scope)
        ? v->scope->e
        : 0;
}

cwal_scope * cwal_value_scope( cwal_value const * v ){
    return v ? v->scope : NULL;
}

cwal_value * cwal_string_concat( cwal_string const * s1, cwal_string const * s2 ){
    if(!s1 || !s2) return NULL;
    else {
        cwal_size_t newLen;
        int rc;
        cwal_engine * e = cwal_value_engine(cwal_string_value(s1));
        assert(e);
        newLen = CWAL_STRLEN(s1) + CWAL_STRLEN(s2) + 1/*NUL byte*/;
        rc = cwal_buffer_reserve( e, &e->buffer, newLen );
        if(rc) return NULL;
        e->buffer.used = 0;
        rc = cwal_buffer_append( e, &e->buffer, cwal_string_cstr(s1), CWAL_STRLEN(s1) );
        if(rc) return NULL;
        rc = cwal_buffer_append( e, &e->buffer, cwal_string_cstr(s2), CWAL_STRLEN(s2) );
        if(rc) return NULL;
        e->buffer.mem[e->buffer.used] = 0;
        return cwal_new_string_value( e, (char const *)e->buffer.mem, e->buffer.used );
    }
}
    

int cwal_value_fetch_bool( cwal_value const * val, char * v )
{
    /**
       FIXME: move the to-bool operation into cwal_value_vtab, like we
       do in the C++ API.
     */
    if( ! val || !val->vtab ) return CWAL_RC_MISUSE;
    else
    {
        int rc = 0;
        char b = NULL != CWAL_VOBASE(val);
        if(!b) switch( val->vtab->typeID ){
          case CWAL_TYPE_BUFFER:
              b = 1;
              break;
          case CWAL_TYPE_STRING: {
              char const * str = cwal_string_cstr(cwal_value_get_string(val));
              b = (str && *str) ? 1 : 0;
              break;
          }
          case CWAL_TYPE_UNDEF:
          case CWAL_TYPE_NULL:
              break;
          case CWAL_TYPE_BOOL:
              b = CWAL_BOOL(val);
              break;
          case CWAL_TYPE_INTEGER: {
              cwal_int_t i = 0;
              cwal_value_fetch_integer( val, &i );
              b = i ? 1 : 0;
              break;
          }
          case CWAL_TYPE_DOUBLE: {
              cwal_double_t d = 0.0;
              cwal_value_fetch_double( val, &d );
              b = (0.0==d) ? 0 : 1;
              break;
          }
          default:
              rc = CWAL_RC_TYPE;
              break;
        }
        if( !rc && v ) *v = b;
        return rc;
    }
}

char cwal_value_get_bool( cwal_value const * val )
{
    char i = 0;
    cwal_value_fetch_bool( val, &i );
    return i;
}

int cwal_value_fetch_integer( cwal_value const * val, cwal_int_t * v )
{
    if( ! val || !val->vtab ) return CWAL_RC_MISUSE;
    else {
        cwal_int_t i = 0;
        int rc = 0;
        switch(val->vtab->typeID){
          case CWAL_TYPE_UNDEF: 
          case CWAL_TYPE_NULL:
              i = 0;
              break;
          case CWAL_TYPE_BOOL:{
              char b = 0;
              cwal_value_fetch_bool( val, &b );
              i = b;
              break;
          }
          case CWAL_TYPE_INTEGER: {
              i = *CWAL_INT(val);
              break;
          }
          case CWAL_TYPE_DOUBLE:{
              cwal_double_t d = 0.0;
              cwal_value_fetch_double( val, &d );
              i = (cwal_int_t)d;
              break;
          }
          case CWAL_TYPE_STRING:
              rc = cwal_string_to_int( cwal_value_get_string(val),
                                       &i );
              break;
          default:
              rc = CWAL_RC_TYPE;
              break;
        }
        if(!rc && v) *v = i;
        return rc;
    }
}

cwal_int_t cwal_value_get_integer( cwal_value const * val )
{
    cwal_int_t i = 0;
    cwal_value_fetch_integer( val, &i );
    return i;
}

int cwal_value_fetch_double( cwal_value const * val, cwal_double_t * v )
{
    if( ! val || !val->vtab ) return CWAL_RC_MISUSE;
    else
    {
        cwal_double_t d = 0.0;
        int rc = 0;
        switch(val->vtab->typeID)
        {
          case CWAL_TYPE_UNDEF: 
          case CWAL_TYPE_NULL:
              d = 0;
              break;
          case CWAL_TYPE_BOOL: {
              char b = 0;
              cwal_value_fetch_bool( val, &b );
              d = b ? 1.0 : 0.0;
              break;
          }
          case CWAL_TYPE_INTEGER: {
              cwal_int_t i = 0;
              cwal_value_fetch_integer( val, &i );
              d = i;
              break;
          }
          case CWAL_TYPE_DOUBLE:
              d = *CWAL_DBL(val);
              break;
          case CWAL_TYPE_STRING:
              rc = cwal_string_to_double( cwal_value_get_string(val),
                                          &d );
              break;
          default:
              rc = CWAL_RC_TYPE;
              break;
        }
        if(!rc && v) *v = d;
        return rc;
    }
}

cwal_double_t cwal_value_get_double( cwal_value const * val )
{
    cwal_double_t i = 0.0;
    cwal_value_fetch_double( val, &i );
    return i;
}

int cwal_value_fetch_string( cwal_value const * val, cwal_string ** dest )
{
    if( ! val || ! dest ) return CWAL_RC_MISUSE;
    else if( ! cwal_value_is_string(val) ) return CWAL_RC_TYPE;
    else
    {
        if( dest ) *dest = CWAL_STR(val);
        return CWAL_RC_OK;
    }
}

cwal_string * cwal_value_get_string( cwal_value const * val )
{
    cwal_string * rc = NULL;
    cwal_value_fetch_string( val, &rc );
    return rc;
}

char const * cwal_value_get_cstr( cwal_value const * val, cwal_size_t * len )
{
    switch(val ? val->vtab->typeID : 0){
      case CWAL_TYPE_STRING:{
          cwal_string const * s = cwal_value_get_string(val);
          if(len) *len = CWAL_STRLEN(s);
          return cwal_string_cstr(s);
      }
      case CWAL_TYPE_BUFFER:{
          cwal_buffer const * b = cwal_value_get_buffer(val);
          if(len) *len = b->used;
          return (char const *)b->mem;
      }
      default:
          return NULL;
    }
}

int cwal_value_fetch_array( cwal_value const * val, cwal_array ** ar)
{
    if( ! val ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_array(val) ) return CWAL_RC_TYPE;
    else
    {
        if(ar) *ar = CWAL_ARRAY(val);
        return 0;
    }
}

cwal_array * cwal_value_get_array( cwal_value const * v )
{
    cwal_array * ar = NULL;
    cwal_value_fetch_array( v, &ar );
    return ar;
}

cwal_value * cwal_array_value(cwal_array const * s)
{
    return s
        ? CWAL_VALPART(s)
        : NULL;
}

int cwal_array_unref(cwal_array *x)
{
    cwal_value * v = CWAL_VALPART(x);
    return (v && v->scope)
        ? cwal_value_unref2(v->scope->e, v)
        : CWAL_RC_MISUSE;
}

int cwal_array_value_fetch( cwal_array const * ar, cwal_size_t pos, cwal_value ** v )
{
    if( !ar) return CWAL_RC_MISUSE;
    if( pos >= ar->list.count ) return CWAL_RC_RANGE;
    else
    {
        if(v) *v = (cwal_value*)ar->list.list[pos];
        return 0;
    }
}

cwal_value * cwal_array_get( cwal_array const * ar, cwal_size_t pos )
{
    cwal_value *v = NULL;
    cwal_array_value_fetch(ar, pos, &v);
    return v;
}

cwal_value * cwal_array_take( cwal_array * ar, cwal_size_t pos )
{
    cwal_value *v = NULL;
    cwal_array_value_fetch(ar, pos, &v);
    if(v){
        if(V_IS_BUILTIN(v)){
            cwal_array_set(ar, pos, NULL);
        }else{
            /* TRULY UGLY HACK with the refcount mangling */
            ++v->refcount;
            cwal_array_set(ar, pos, NULL);
            assert( (v->refcount > 0) && "Should still have our reference!" );
            if(0==--v->refcount){
                cwal_value_reprobate(v->scope /* v->scope->engine->current? */,
                                     v);
            }
        }
    }
    return v;
}


int cwal_array_length_fetch( cwal_array const * ar, cwal_size_t * v )
{
    if( ! ar || !v ) return CWAL_RC_MISUSE;
    else
    {
        if(v) *v = ar->list.count;
        return 0;
    }
}

cwal_size_t cwal_array_length_get( cwal_array const * ar )
{
    cwal_size_t i = 0;
    cwal_array_length_fetch(ar, &i);
    return i;
}

int cwal_array_length_set( cwal_array * ar, cwal_size_t newSize ){
    cwal_size_t i;
    if(!ar) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS;
    else if(ar->list.count == newSize) return 0;
    if( newSize < ar->list.count ){
        int rc = 0;
        for( i = newSize; !rc && (i < ar->list.count); ++i ){
            rc = cwal_array_set( ar, i, NULL );
        }
        ar->list.count = newSize;
        return rc;
    }
    else { /* grow */
        int rc = cwal_array_reserve( ar, newSize );
        if(!rc){
            ar->list.count = newSize;
        }
        return rc;
    }
}

/**
   Internal helper macro for array-centric functions.
*/
#define SETUP_ARRAY_ARGS \
    cwal_scope * s = ar ? CWAL_VALPART(ar)->scope : 0; \
        cwal_engine * e = s ? s->e : 0; \
        if(!s || !e) return CWAL_RC_MISUSE

int cwal_array_reserve( cwal_array * ar, cwal_size_t size )
{
    SETUP_ARRAY_ARGS;
    if( ! ar ) return CWAL_RC_MISUSE;
    else if( size <= ar->list.alloced )
    {
        /* We don't want to introduce a can of worms by trying to
           handle the cleanup from here.
        */
        return 0;
    }
    else
    {
#if 0
        return (ar->list.alloced > cwal_list_reserve( e, &ar->list, size ))
            ? CWAL_RC_OOM
            : 0
            ;
#else
        /* For reasons i cannot explain, this impl of
           cwal_array_reserve() causes a subtle change in string
           interning behaviour, triggering an (arguable) assertion
           which did not trigger before.
        */
        cwal_size_t const oldLen = ar->list.alloced;
        cwal_size_t rrc;
        assert(e->metrics.bytes[CWAL_TYPE_ARRAY] > (oldLen * sizeof(void*)));
        rrc = cwal_list_reserve( e, &ar->list, size );
        if(rrc < size) return CWAL_RC_OOM;
        else{
            assert(rrc > oldLen);
            e->metrics.bytes[CWAL_TYPE_ARRAY] -= oldLen * sizeof(void*);
            e->metrics.bytes[CWAL_TYPE_ARRAY] += rrc * sizeof(void*);
            return 0;
        }
#endif
    }
}


/** @internal

   cwal_list_visitor_f which expects V to be a (cwal_value*) and
   and VParent to be its (cwal_value*) scoping parent.
   This makes sure that (sub)child are properly up-scoped
   if needed. Returns 0 on success.
*/
static int cwal_xscope_visitor_children_array( void * V, void * VParent ){
    cwal_value * par = (cwal_value*)VParent;
    cwal_value * child = (cwal_value*)V;
    assert(par && par->scope);
    return cwal_value_xscope( par->scope->e, par->scope, child, 0 );
}

int cwal_rescope_children_obase( cwal_value * v ){
    cwal_obase * b = CWAL_VOBASE(v);
    cwal_kvp * kvp = b->kvp;
    cwal_value * proto = b->prototype;
    int rc = CWAL_RC_OK;
    assert(b);
    assert(!(CWAL_F_IS_VISITING & b->flags));
#if 1
    if(proto && (proto->scope->level > v->scope->level)){
        /* dump_val(proto,"Rescoping prototype"); */
        cwal_value_xscope( v->scope->e, v->scope, proto, 0 );
    }
#else
    for( ; proto; proto = CWAL_VOBASE(proto)->prototype){
        if(proto->scope->level > v->scope->level){
            /* dump_val(proto,"Rescoping prototype"); */
            cwal_value_xscope( v->scope->e, v->scope, proto, 0 );
        }
    }
#endif
    assert(v->scope);
    for( ; kvp && (0==rc); kvp = kvp->right ){
        if(kvp->key){
            rc = cwal_value_xscope( v->scope->e, v->scope, kvp->key, 0 );
        }
        if((0==rc) && kvp->value){
            rc = cwal_value_xscope( v->scope->e, v->scope, kvp->value, 0 );
        }
    }
    return rc;
}

int cwal_rescope_children_native( cwal_value * v ){
    int rc;
    cwal_native * n = cwal_value_get_native(v);
    assert(v->scope);
    assert(n);
    rc = cwal_rescope_children_obase(v);
    if(!rc && n->rescope_children){
        rc = n->rescope_children( v->scope, v, n );
    }
    return rc;
}

int cwal_rescope_children_array( cwal_value * v ){
    int rc;
    cwal_array * ar = cwal_value_get_array(v);
    assert(ar);
    assert(!(CWAL_F_IS_VISITING & ar->base.flags));
    rc = cwal_rescope_children_obase( v );
    if(rc) return rc;
    rc = cwal_list_visit( &ar->list,
                          -1, cwal_xscope_visitor_children_array, v );
    return rc;
}

int cwal_value_rescope( cwal_scope * s, cwal_value * v ){
    return (!s || !s->e || !v)
        ? ((v && V_IS_BUILTIN(v)) ? 0 : CWAL_RC_MISUSE)
        : cwal_value_xscope( s->e, s, v, NULL );
}

/**
   Transfers child to the given scope if child is in a lower-level scope.

   Returns 0 on success and theoretically has no error cases except
   bad arguments or as side-effects of serious internal errors
   elsewhere. If res is not NULL, it will be set to one of these values:

   -1=child was moved to a higher-level scope (with a lower
   scope->level value).

   0=child was kept where it is.

   1=child was... hmm... damn, i should have written the docs as i wrote
   the code :/.
*/
static int cwal_value_xscope( cwal_engine * e, cwal_scope * par,
                              cwal_value * child, int * res ){
    cwal_obase * chb;
    int RC = res ? *res : 0;
    if(!res) res = &RC/*simplifies some code below*/;
    if(!par) {
        *res = 1;
        return 0;/*par = e->current;*/
    }
    assert( e && par && child );
    if( V_IS_BUILTIN(child) ) {
        *res = 1;
        return CWAL_RC_OK;
    }
    else if(child->scope == par) {
        *res = 0;
        return CWAL_RC_OK;
    }
    chb = CWAL_VOBASE(child);
    if( chb && ( CWAL_F_IS_VISITING & chb->flags ) ){
        *res = 0;
        return 0
            /* Normally we would return CWAL_RC_CYCLES_DETECTED,
               but for this special case we need to return 0
               to keep list iteration from aborting. */;
    }
    else{
        int rc = CWAL_RC_OK;
        if( child->scope ){
            CWAL_TR_V(e,child);
            if( child->scope->level <= par->level ){
                CWAL_TR3(e,CWAL_TRACE_MESSAGE,
                         "Keeping value in its current scope.");
                *res = 1;
                return 0;
            }
            else{
                CWAL_TR3(e,CWAL_TRACE_MESSAGE,
                     "Migrating value to older scope.");
                rc = cwal_value_take( e, child );
                if(rc){
                    CWAL_TR_SV(e,child->scope,child);
                    CWAL_TR3(e,CWAL_TRACE_ERROR,
                             "POPPING FROM ITS PARENT SCOPE IS NO "
                             "LONGER SUPPOSED TO BE ABLE TO FAIL "
                             "IN THESE CONDITIONS.");
                    assert(!"Pop child from its scope failed.");
                    return rc;
                }
            }
        }
        assert(!child->scope);
        *res = -1;
        cwal_scope_insert( par, child );
        if(child->vtab->rescope_children){
            /* For containers we now, for the sake of cross-scope
               cycles, probably need to ensure that any
               sub-(sub...)children are up-scoped. Or maybe we could
               do it before (or during?) destruction. This seems like
               the most logical place for it, but makes up-scoping a
               fairly expensive operation (which destruction already
               is).
             */
            rc = child->vtab->rescope_children(child);
            if(0!=rc){
                /* Reminder: now that values are tracked in linked
                   lists, xscope can only fail if some assertion
                   fails. There is no longer the potential for an OOM
                   case.
                */
                MARKER(("xscope returned %d/%s\n", rc, cwal_rc_cstr(rc)));
                assert(!"NO RECOVERY STRATEGY HERE!");
                return CWAL_RC_CANNOT_HAPPEN;
            }
        }
        return rc;
    }
}

int cwal_value_upscope( cwal_value * v ){
    cwal_engine * e = CWAL_VENGINE(v);
    assert(!"don't use cwal_value_upscope(). Use cwal_value_rescope() instead.");
    if(!e || !v->scope) return CWAL_RC_MISUSE;
    else {
        if(!v->scope->parent){
            return CWAL_RC_RANGE;
        }else {
            int rc, dir = 0;
            rc = cwal_value_xscope( e, v->scope->parent, v, &dir);
            assert(-1==dir);
            return rc;
        }
    }    
}


cwal_value * cwal_propagating_get( cwal_engine * e ){
  return e->propagating;
}

cwal_value * cwal_propagating_set( cwal_engine * e, cwal_value * v ){
  if(v != e->propagating){
    if(v) cwal_value_ref(v);
    if(e->propagating){
      cwal_value_unhand(e->propagating);
    }
    e->propagating = v;
  }
  return v;
}

cwal_value * cwal_propagating_take( cwal_engine * e ){
  cwal_value * rv = e->propagating;
  cwal_propagating_set(e, 0);
  return rv;
}

    
int cwal_exception_set( cwal_engine * e, cwal_value * v ){
    if(!e || !e->current) return CWAL_RC_MISUSE;
    else if(v && (v == e->exception)) /* This is ok. */ return CWAL_RC_EXCEPTION;
    else if(!v) {
#if 0
        if(e->exception
           && e->exception->scope==e->current
           && 1==e->exception->refcount){
            /* Should we really be doing this? Assuming the scope
            reference is the only ref? Previous attempts to do this
            have bitten me in the buttox, but i "think" i can get away
            with it for exceptions.

            Note that this routine does NOT add a ref to v, but instead
            of taking back our own ref we're stealing that of the owning
            scope.

            Nonono... this could open up cases where an exception is
            moved around and set/unset as the exception state multiple
            times, and we'd unref it too many times. So we'll only do
            this "optimization" to when the scopes match and
            1==refcount, which we can fairly safely assume is the
            scope-held ref unless the client has done something silly,
            like... set exception state, move it into an object owned
            by the same scope, unref the exception (refcount==1), set
            the object as the exception state... blammo. Okay, turn
            this off but leave the commentary as yet another reminder
            of why we don't just generically unref values.
            */
            cwal_value_unref2(e, e->exception);
        }
#endif
        /* cwal_value * x = e->exception; */
        if(e->exception) cwal_value_unhand(e->exception);
        e->exception = 0 /* its scope owns it */;
        /* cwal_value_unref(x); */
        return 0;
    }
    else{
        cwal_value_ref(v);
        if(e->exception) cwal_value_unhand(e->exception);
        e->exception = v;
        /* cwal_value_ref(v); */
        return CWAL_RC_EXCEPTION;
    }
}

int cwal_exception_setfv(cwal_engine * e, int code, char const * fmt, va_list args){
    if(!e) return CWAL_RC_MISUSE;
    else{
        int rc;
        cwal_exception * x;
        x = (fmt && *fmt)
            ? cwal_new_exceptionfv(e, code, fmt, args)
            : cwal_new_exception(e, code, NULL);
        if(!x) return CWAL_RC_OOM;
        rc = cwal_exception_set( e, cwal_exception_value(x) );
        if(CWAL_RC_EXCEPTION != rc) cwal_exception_unref(e, x);
        assert(0!=rc);
        return rc;
    }
}
int cwal_exception_setf(cwal_engine * e, int code, char const * fmt, ...){
    if(!e || !fmt) return 0;
    else{
        int rc;
        va_list args;
        va_start(args,fmt);
        rc = cwal_exception_setfv(e, code, fmt, args);
        va_end(args);
        return rc;
    }
}

    
cwal_value * cwal_exception_get( cwal_engine * e ){
    return e ? e->exception : 0;
}

static void cwal_array_steal_list( cwal_engine * e, cwal_array * ar ){
#if 0
    /*Enable this section to effectively disable array->list recycling
      for memory cost/savings comparisons. */
    return;
#else
    if(ar->list.list) return;
    else if(e->reList.cursor>=0){
        ar->list = e->reList.lists[e->reList.cursor];
        e->reList.lists[e->reList.cursor] = cwal_list_empty;
        /* MARKER(("Re-using array list memory (%"CWAL_SIZE_T_PFMT" slots) from slot #%d\n", ar->list.alloced, e->reList.cursor)); */
        --e->reList.cursor;
    }
#endif
}
    
int cwal_array_set( cwal_array * ar, cwal_size_t ndx, cwal_value * v )
{
    SETUP_ARRAY_ARGS;
    if( !ar ) return CWAL_RC_MISUSE;
    else if( (ndx+1) > CwalConsts.MaxSizeTCounter) /* overflow */return CWAL_RC_RANGE;
    else{
        cwal_size_t len;

        if(!ar->list.list) cwal_array_steal_list(e, ar);
        len = cwal_list_reserve( e, &ar->list,
                                 ar->list.count
                                 ? ndx+1
                                 : CwalConsts.InitialArrayLength );
        if( len <= ndx ) return CWAL_RC_OOM;
        else{
            int rc;
            cwal_value * arV = cwal_array_value( ar );
            cwal_value * old = (cwal_value*)ar->list.list[ndx];
            assert( arV && (CWAL_TYPE_ARRAY==arV->vtab->typeID) );
            if( old ){
                if(old == v) return 0;
            }
            if(v){
                rc = cwal_value_xscope( e, arV->scope, v, 0 );
                if(rc){
                    assert(!"WARNING: xscope failed! "
                           "THIS IS SUPPOSED TO BE IMPOSSIBLE :(\n");
                    return rc;
                }
                cwal_value_ref2( e, v );
            }
            if(old) cwal_value_unref2(e, old);
            ar->list.list[ndx] = v;
            if( ndx >= ar->list.count ){
                ar->list.count = ndx+1;
            }
            return 0;
        }
    }
}

int cwal_array_append( cwal_array * ar, cwal_value * v ){
    SETUP_ARRAY_ARGS;
    if( !ar ) return CWAL_RC_MISUSE;
    else if( (ar->list.count+1) < ar->list.count ) return CWAL_RC_RANGE;
    else{
        if(!ar->list.list) cwal_array_steal_list(e, ar);
        if( !ar->list.alloced
            || (ar->list.count == ar->list.alloced-1)){
            unsigned const int n = ar->list.count
                ? ar->list.alloced * 2
                : CwalConsts.InitialArrayLength;
            if( n > cwal_list_reserve( e, &ar->list, n ) ){
                return CWAL_RC_OOM;
            }
        }
        return cwal_array_set( ar, ar->list.count, v );
    }
}

int cwal_array_prepend( cwal_array * ar, cwal_value * v ){
    SETUP_ARRAY_ARGS;
    if( !ar || !v ) return CWAL_RC_MISUSE;
    else{
        int rc;
        cwal_value ** vlist;
        if(!ar->list.list) cwal_array_steal_list(e, ar);
        if( !ar->list.alloced
            || (ar->list.count == ar->list.alloced-1)){
            unsigned const int n = ar->list.count
                ? ar->list.alloced * 2
                : CwalConsts.InitialArrayLength;
            if( n > cwal_list_reserve( e, &ar->list, n ) ){
                return CWAL_RC_OOM;
            }
        }
        if(ar->list.count){
            unsigned char * mem =
                (unsigned char *)ar->list.list;
            memmove( mem+sizeof(cwal_value*), mem,
                     sizeof(cwal_value*)*ar->list.count);
        }
        vlist = (cwal_value **)ar->list.list;
        vlist[0] = NULL;
        ++ar->list.count;
        rc = cwal_array_set( ar, 0, v );
        /* A recovery here on error would be messy, but the
           set() can only fail on allocation error and we've
           already done the allocation.
        */
        assert(!rc);
        assert(v == *((cwal_value**)ar->list.list));
        return rc;
    }
}

int cwal_array_shift( cwal_array * ar, cwal_value **rv ){
    SETUP_ARRAY_ARGS;
    if( !ar ) return CWAL_RC_MISUSE;
    else{
        cwal_value ** vlist;
        unsigned char * mem;
        cwal_value * v;
        if(!ar->list.count) return CWAL_RC_RANGE;
        vlist = (cwal_value **)ar->list.list;
        v = vlist[0];
        if(rv) *rv = v;
        mem = (unsigned char *)ar->list.list;
        memmove( mem, mem+sizeof(cwal_value*),
                 sizeof(cwal_value*)*(ar->list.count-1));
        vlist[--ar->list.count] = NULL;
        if(v){
            /*
              We need to do some non-kosher tinkering of the refcount to
              ensure that we neither nuke the value we're returning nor
              strand it with too many refs...
            */
            if(rv && (1==v->refcount)){/* This is our ref */
                --v->refcount;
                cwal_value_reprobate(v->scope, v);
            }else if(v->refcount){
                cwal_unref_from(s, v);
            }else{
                assert(V_IS_BUILTIN(v));
            }
        }
        return 0;
    }
}

int cwal_array_index_of( cwal_array const * ar, cwal_value const * v,
                         cwal_size_t * index ){
    if(!ar) return CWAL_RC_MISUSE;
    else if(!ar->list.count) return CWAL_RC_NOT_FOUND;
    else{
        cwal_size_t i;
        for( i = 0; i < ar->list.count; ++i ){
            cwal_value const * rhs = (cwal_value const *)ar->list.list[i];
            if(!v && (rhs==v)) return 0;
            else if(v && ((v==rhs) || (0 == cwal_value_compare(v, rhs)))){
                if(index) *index = i;
                return 0;
            }
        }
        return CWAL_RC_NOT_FOUND;
    }
}


int cwal_array_copy_range( cwal_array * ar, cwal_size_t offset,
                           cwal_size_t count,
                           cwal_array **dest ){
    SETUP_ARRAY_ARGS;
    if( !ar || !dest || (ar==*dest) ) return CWAL_RC_MISUSE;
    else{
        int rc = 0;
        cwal_size_t i;
        cwal_array * tgt = *dest ? *dest : cwal_new_array(e);
        cwal_value ** vlist;
        vlist = (cwal_value **)ar->list.list;
        if(offset<ar->list.count){
            cwal_size_t to;
            if(!count) count = ar->list.count - offset;
            to = offset + count;
            if(to > ar->list.count) to = ar->list.count;
            rc = cwal_array_reserve( tgt, count );
            for( i = offset;
                 !rc && (i<to);
                 ++i ){
                cwal_value * v = vlist[i];
                rc = cwal_array_append( tgt, v );
            }
        }
        if(!rc) *dest = tgt;
        else if(rc && (*dest != tgt)) cwal_array_unref(tgt);
        return rc;
    }
}

int cwal_value_fetch_object( cwal_value const * val, cwal_object ** ar){
    if( ! val ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_object(val) ) return CWAL_RC_TYPE;
    else{
        if(ar) *ar = CWAL_OBJ(val);
        return 0;
    }
}

cwal_object * cwal_value_get_object( cwal_value const * v ) {
    cwal_object * ar = NULL;
    cwal_value_fetch_object( v, &ar );
    return ar;
}

cwal_value * cwal_object_value(cwal_object const * s){
    return s
        ? CWAL_VALPART(s)
        : NULL;
}

int cwal_object_unref(cwal_object *x){
    cwal_value * v = CWAL_VALPART(x);
    return (v && v->scope)
        ? cwal_value_unref2(v->scope->e, v)
        : CWAL_RC_MISUSE;
}

/**
   Searches base->kvp for the given property, recurisively up the
   base->prototype chain if the key is not found.
*/
static cwal_value * cwal_obase_search( cwal_obase const * base,
                                       char searchProto,
                                       char const * key,
                                       cwal_size_t keyLen, cwal_size_t * ndx,
                                       cwal_kvp ** prev){
    if(!base || !key || !*key || (CWAL_F_IS_VISITING & base->flags)) return NULL;
    else {
        cwal_kvp * kvp;
        cwal_value * rc = NULL;
        /* cwal_obase const * origBase = base; */
        if(!keyLen) keyLen = cwal_strlen(key);
        /* cwal_obase_set_visiting(base, 1); */
        while(base){
            kvp = cwal_kvp_search( base->kvp, key, keyLen, ndx, prev );
            if(kvp) {
                rc = kvp->value;
                break;
            }
            else base = searchProto ? CWAL_VOBASE(base->prototype) : 0;
        }
        /* cwal_obase_set_visiting(origBase, 0); */
        return rc;
    }
}

/**
   Counterpart of cwal_kvp_search_v(). Searches for key in base->kvp.
   If not found and searchProto is true, it searches base->prototype
   (recursively). If it encounters cycles while traversing the
   prototype is returned NULL.
*/
static cwal_value * cwal_obase_search_v( cwal_obase const * base,
                                         char searchProto,
                                         cwal_value const * key,
                                         cwal_size_t * ndx,
                                         cwal_kvp ** prev){
    if(!base || !key || (CWAL_F_IS_VISITING & base->flags)) return NULL;
    else {
        cwal_kvp * kvp;
        cwal_value * rc = NULL;
        /* cwal_obase const * origBase = base; */
        /* cwal_obase_set_visiting(base, 1); */
        while(base){
            kvp = cwal_kvp_search_v( base->kvp, key, ndx, prev );
            if(kvp) {
                rc = kvp->value;
                break;
            }
            else {
                base = searchProto ? CWAL_VOBASE(base->prototype) : 0;
            }
        }
        /* cwal_obase_set_visiting(origBase, 0); */
        return rc;
    }
}


cwal_value * cwal_object_get( cwal_object const * obj, char const * key, cwal_size_t keyLen ) {
    if(!obj || !key || !*key) return NULL;
    else{
        return cwal_obase_search( &obj->base, 1, key, keyLen, NULL, NULL );
    }
}

cwal_value * cwal_object_get_s( cwal_object const * obj,
                                cwal_string const *key ) {
    if(!obj || !key) return NULL;
    else {
        return cwal_obase_search_v( &obj->base, 1, cwal_string_value(key),
                                    NULL, NULL );
    }
}

int cwal_object_unset( cwal_engine * e, cwal_object * obj,
                       char const * key, cwal_size_t keyLen ) {
    return (!e || !obj || !key)
        ? CWAL_RC_MISUSE
        : cwal_kvp_unset( e, CWAL_VALPART(obj)->scope,
                          &obj->base.kvp, key, keyLen )
        ;
}


char cwal_prop_has( cwal_value const * v, char searchPrototype,
                    char const * key, cwal_size_t keyLen ) {
    cwal_obase * base = CWAL_VOBASE(v);
    if(!base || !key || !*key) return 0;
    else{
        return (searchPrototype
                ? (void const *)cwal_obase_search(base, 1, key, keyLen, NULL, NULL )
                : (void const *)cwal_kvp_search(base->kvp, key, keyLen, NULL, NULL)
            ) ? 1 : 0;
    }
}
char cwal_prop_has_s( cwal_value const * v, char searchPrototype,
                      cwal_string const *key ){
    if(!v || !key || !key->length) return 0;
    else return cwal_prop_has( v, searchPrototype,
                               cwal_string_cstr(key), CWAL_STRLEN(key) );
}

char cwal_prop_has_v( cwal_value const * v, char searchPrototype,
                      cwal_value const * key ){
    cwal_obase const * base = CWAL_VOBASE(v);
    return (!base || !key)
        ? 0
        : ((searchPrototype
            ? (void const *)cwal_obase_search_v(base, 1, key, NULL, NULL )
            : (void const *)cwal_kvp_search_v(base->kvp, key, NULL, NULL)
            ) ? 1 : 0)
        ;
}

cwal_value * cwal_prop_get( cwal_value const * v,
                            char const * key, cwal_size_t keyLen ) {
    cwal_obase const * base = CWAL_VOBASE(v);
    if(!base || !key || !*key) return NULL;
    else{
        return cwal_obase_search( base, 1,
                                  key, keyLen, NULL, NULL );
    }
}

cwal_value * cwal_prop_get_v( cwal_value const * c,
                              cwal_value const * key ) {
    cwal_obase const * base = CWAL_VOBASE(c);
    if(!base || !key) return NULL;
    else{
        return cwal_obase_search_v( base, 1, key,
                                    NULL, NULL );
    }
}


cwal_value * cwal_prop_get_s( cwal_value const * v,
                              cwal_string const *key ) {
    cwal_obase const * base = CWAL_VOBASE(v);
    if(!base || !key || !key->length) return NULL;
    else{
        return cwal_obase_search( base, 1, cwal_string_cstr(key),
                                  CWAL_STRLEN(key), NULL, NULL );
    }
}

cwal_kvp * cwal_prop_get_kvp( cwal_value * c, char const * key,
                              cwal_size_t keyLen, char searchProtos,
                              cwal_value ** foundIn ){
    cwal_obase * b = CWAL_VOBASE(c);
    if(!key || !*key || !b) return 0;
    else{
        cwal_kvp * rc = 0;
        if(!keyLen) keyLen = cwal_strlen(key);
        do{
            rc = cwal_kvp_search(b->kvp, key, keyLen,
                                 0, 0);
            if(!rc){
                if(searchProtos
                   && (c = b->prototype)
                   && (b = CWAL_VOBASE(c))){
                    continue;
                }
            }else if(foundIn){
                *foundIn = c;
            }
        }while(0);
        return rc;
    }
}

cwal_kvp * cwal_prop_get_kvp_v( cwal_value * c, cwal_value const * key,
                                char searchProtos,
                                cwal_value ** foundIn ){
    cwal_kvp * rc = 0;
    cwal_obase * b = CWAL_VOBASE(c);
    if(!key || !b) return 0;
    else{
        do{
            rc = cwal_kvp_search_v(b->kvp, key, 0, 0);
            if(!rc){
                if(searchProtos
                   && (c = b->prototype)
                   && (b = CWAL_VOBASE(c))){
                    continue;
                }
            }else if(foundIn){
                *foundIn = c;
            }
        }while(0);
        return rc;
    }
}



int cwal_prop_unset( cwal_value * c,
                     char const * key, cwal_size_t keyLen ) {
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = b ? CWAL_VENGINE(c) : NULL;
    return (e && b)
        ? cwal_kvp_unset( e, c->scope/*?e->current?*/,
                          &b->kvp, key, keyLen )
        : CWAL_RC_MISUSE;
}

int cwal_prop_unset_v( cwal_value * c, cwal_value * key ) {
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = b ? CWAL_VENGINE(c) : NULL;
    return (e && b)
        ? cwal_kvp_unset_v( e, c->scope/*?e->current?*/,
                            &b->kvp, key )
        : CWAL_RC_MISUSE;
}


/**
   Adds an entry or updates an existing one in the given kvp list.
   listOwner must be the value which owns/manages list. key is the key
   to search for and value is the value to set it to.

   On success *list is modified to point to the new kvp, and that kvp->right
   will point to the old value of *list.

   Returns 0 on success, CWAL_RC_MISUSE if any arguments are NULL, and
   CWAL_RC_OOM on allocation error.

*/
static int cwal_kvp_set_v( cwal_engine * e, cwal_value * listOwner, cwal_kvp ** list,
                           cwal_value * key, cwal_value * v, uint16_t flags )
{
    if( !e || !key || !list ||!listOwner || !v ) return CWAL_RC_MISUSE;
    else {
        int rc;
        /*char const * cKey;*/
        cwal_kvp * kvp = 0;
        cwal_kvp * left = 0;
        char i = 0;
        char iAlloccedKvp = 0;
        kvp =
            cwal_kvp_search_v( *list, key, NULL, &left )
            ;
        if( !kvp ){
            if(!v){
                return CWAL_RC_NOT_FOUND /* 0? */;
            }
            kvp = cwal_kvp_alloc( e );
            if( !kvp ) return CWAL_RC_OOM;
            iAlloccedKvp = 1;
            kvp->flags = (CWAL_VAR_F_PRESERVE==flags)
                ? CWAL_VAR_F_NONE : flags;
        }
        else if(CWAL_VAR_F_CONST & kvp->flags){
            if(kvp->value != v) return CWAL_RC_ACCESS;
            else {
                /**
                   Special case: ignore assignment to self.

                   This is triggered in th1ish on an
                   assignment-to-self operation, e.g.:

                   x += 1

                   where x has a '+' operator which returns x.
                */
                return 0;
            }
        }
        if(CWAL_VAR_F_PRESERVE!=flags) kvp->flags = flags;

        /* Reminder: we have to increase the refcount of the key in
           even if it's the same pointer as a key we have or the same
           as v. Remember that interning guarantees that we'll
           eventually see the same key instances.
        */
        i = 0;
        for( ; i < 2; ++i ){ /* 0==key, 1==value */
            cwal_value * vv = 0==i ? key : v;
            rc = cwal_value_xscope( e, listOwner->scope, vv, 0 );
            if(rc){
                if(iAlloccedKvp){
                    cwal_kvp_free( e, listOwner->scope, kvp, 1 );
                }
                goto xferFailed;
            }
            /* The ref/unref order is important in case key==kvp->key or...
               in case v==kvp->value, key, or listOwner.
            */
            if(0==i) { /* KEY part... */
                cwal_value_ref2( e, vv );
                if(kvp->key) cwal_unref_from( listOwner->scope,
                                              kvp->key );
                kvp->key = vv;
            }else{ /* VALUE part... */
                cwal_value_ref2( e, vv );
                if(kvp->value) cwal_unref_from( listOwner->scope,
                                                kvp->value );
                kvp->value = vv;
                break;
            }
        }
        assert(1 == i);
        assert(kvp->key != 0);
        assert(kvp->value != 0);
        if(!left){
            /* This is a new entry or head of an empty list. */
#if 0
            /* Make kvp the tail of the list */
            if(!*list){
                assert(!kvp->right);
                *list = kvp;
            }
            else{
                cwal_kvp * head = *list;
                while( head->right ){ head = head->right; }
                head->right = kvp;
            }
#elif !CWAL_KVP_TRY_SORTING
            /* Make kvp the head of the list */
            if(*list != kvp){
                assert(!kvp->right);
                kvp->right = *list;
                *list = kvp;
            }
#else /* CWAL_KVP_TRY_SORTING */
            /* Insert it in sorted order... */
            if(!*list){
                assert(!kvp->right);
                *list = kvp;
            }
            else if(*list != kvp){
                cwal_kvp * pos = *list;
                cwal_kvp * prev = 0;
                int cmp = 0;
                while( 1 ){
                    cmp = cwal_value_compare( kvp->key, pos->key );
                    if(0==cmp){
                        /*dump_val(kvp->key,"kvp->key");*/
                        /*dump_val(pos->key,"pos->key");*/
                        /* assert(kvp->key != pos->key); */
                    }
                    else if(cmp>0){
                        kvp->right = pos;
                        if(prev){
                            prev->right = kvp;
                        }else{
                            *list = kvp;
                        }
                        break;
                    }
                    prev = pos;
                    pos = pos->right;
                    if(!pos){
                        prev->right = kvp;
                        break;
                    }
                }
            }
#endif
        } /*else it's already in the list. */

        return CWAL_RC_OK;
        xferFailed:
        MARKER(("VERY unexpected: xscope rc=%d/%s\n", rc, cwal_rc_cstr(rc)));
        assert(!"xscope cannot fail in this context any more!");
        return rc;
    }

}

int cwal_kvp_set( cwal_engine * e, cwal_value * listOwner, cwal_kvp ** list,
                  char const * key, cwal_size_t keyLen, cwal_value * v,
                  uint16_t flags ) {
    if( !list || !key ) return CWAL_RC_MISUSE;
    /*FIXME: re-factor this back in:*/
    /*else if( NULL == v ) return cwal_object_unset( e, obj, key );*/
    else {
        cwal_string * cs;
        cs = cwal_new_string(e, key, keyLen);
        if(!cs) return CWAL_RC_OOM;
        else {
            int const rc = cwal_kvp_set_v( e, listOwner, list,
                                           cwal_string_value(cs), v, flags );
#if 0
            /* We need to leave cs to its parent scope just in case
               it's interened :/.
            */
            if(rc) cwal_value_unref2(e, cwal_string_value(cs))
                /* If kvp_set_v ever fails and cs is an interned
                   string, this is going to bite me in the butt.
                */
                ;
#endif
            return rc;
        }
    }
}


int cwal_object_set_s( cwal_object * obj, cwal_string * key, cwal_value * v )
{
    return cwal_prop_set_s( cwal_object_value(obj), key, v );
}

int cwal_object_set( cwal_object * obj, char const * key, cwal_size_t keyLen,
                     cwal_value * v ) {
    return cwal_prop_set( cwal_object_value(obj), key, keyLen, v );
}

cwal_value * cwal_object_take( cwal_object * obj, char const * key ){
    return cwal_prop_take( cwal_object_value(obj), key );
}

/**
   qsort(3) comparison func for a C-style (not cwal-style) array of
   (cwal_kvp const *).
 */
static int cwal_props_cmp(void const *l, void const *r){
    cwal_kvp const * lhs = *((cwal_kvp const **)l);
    cwal_kvp const * rhs = *((cwal_kvp const **)r);
    return cwal_value_compare( lhs ? lhs->key : NULL, rhs ? rhs->key : NULL );
}

int cwal_props_sort( cwal_value * c ){
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = CWAL_VENGINE(c);
    cwal_buffer * buf = e ? &e->sortBuf : NULL;
    assert(c ? (!!e || V_IS_BUILTIN(c)) : 1);
    if( !e || !b ) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_ACCESS;
    else if(!b->kvp || !b->kvp->right /* only 1 entry */) return 0;
    else {
        int rc;
        cwal_size_t count = 0, i;
        cwal_kvp * kvp = b->kvp;
        cwal_kvp ** kvlist;
        for( ; kvp; kvp = kvp->right){
            ++count /* we should probably store this in obase */;
        }
        assert(count>1);
        rc = cwal_buffer_reserve( e, buf, sizeof(cwal_kvp*) * (count+1/*see below*/));
        if(rc) return rc;
        b->flags |= CWAL_F_IS_VISITING
            /* Necessary in case any comparison operators traverse children.
               No built-in ones currently do, but... */;
        kvlist = (cwal_kvp **)buf->mem;
        kvp = b->kvp;
        for( i = 0 ; kvp; kvp = kvp->right) kvlist[i++] = kvp;
        kvlist[i] = NULL /* simplifies post-processing */;
        qsort( buf->mem, count, sizeof(cwal_kvp*), cwal_props_cmp );
        for( i = 0; i < count; ++i ){
            kvp = kvlist[i];
            kvp->right = kvlist[i+1 /* this is why we added a NULL entry */];
        }
        b->kvp = kvlist[0];
        b->flags &= ~CWAL_F_IS_VISITING;
        return 0;
    }
}


int cwal_prop_set_v( cwal_value * c, cwal_value * key, cwal_value * v ) {
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = CWAL_VENGINE(c);
    assert(v ? (!!e || V_IS_BUILTIN(v)) : 1);
    if( !e || !b || !key ) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_ACCESS;
    else if( NULL == v ) {
        return cwal_prop_unset_v( c, key );
    }
    else {
        return cwal_kvp_set_v( e, c, &b->kvp, key, v, CWAL_VAR_F_PRESERVE );
    }
}


int cwal_prop_set_s( cwal_value * c, cwal_string * key, cwal_value * v )
{
    return cwal_prop_set_v( c, cwal_string_value(key), v );
}

int cwal_prop_set( cwal_value * c,
                   char const * key, cwal_size_t keyLen, cwal_value * v ) {
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = b ? CWAL_VENGINE(c) : 0;
    if( !e || !b || !key ) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_ACCESS;
    else if( NULL == v ) return cwal_prop_unset( c, key, keyLen );
    else {
        cwal_value * vkey;
        if(!keyLen) {
            keyLen = cwal_strlen(key);
            assert(keyLen);
            if(!keyLen) return CWAL_RC_CANNOT_HAPPEN;
        }
        vkey = cwal_new_string_value(e, key, keyLen);
        if(!vkey) return CWAL_RC_OOM;
        else {
            int const rc = cwal_prop_set_v(c, vkey, v);
            if(rc) cwal_value_unref2(e, vkey);
            return rc;
        }
    }
}

cwal_value * cwal_prop_take( cwal_value * c, char const * key ){
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = CWAL_VENGINE(c);
    assert(c ? !!e : 1);
    if( !e || !b || !e || !key || !*key || !b->kvp) return NULL;
    else {
        /* FIXME: this is 90% identical to cwal_kvp_unset(),
           only with different refcount handling.
           Consolidate them.

           FIXME #2: instead of keeping our reference,
           drop the reference and reprobate the value
           if needed.
        */
        cwal_kvp * left = 0;
        cwal_value * rc = NULL;
        cwal_kvp * kvp = cwal_kvp_search( b->kvp, key, 0, NULL, &left );
        if( ! kvp ) return NULL;
        rc = kvp->value;
        if(left){
            left->right = kvp->right;
        }else{
            assert(b->kvp == kvp);
            b->kvp = kvp->right /* ? kvp->right : NULL */;
        }
        kvp->right = NULL;
        assert( rc );
        cwal_value_unref2(e, kvp->key);
        kvp->key = 0;
        kvp->value = 0;
        cwal_kvp_free( e, NULL, kvp, 1 );
        if(!V_IS_BUILTIN(rc)){
            assert( (rc->refcount > 0) && "Should still have our reference!" );
            if((0==--rc->refcount) && !V_IS_BUILTIN(rc)){
                cwal_value_reprobate(rc->scope /* e->current? */,
                                     rc);
            }
        }
        return rc;
    }
}

cwal_value * cwal_prop_take_v( cwal_value * c, cwal_value * key,
                               cwal_value ** takeKeyAsWell ){
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = CWAL_VENGINE(c);
    assert(c ? !!e : 1);
    if( !e || !b || !e || !key || !b->kvp) return NULL;
    else {
        cwal_kvp * left = 0;
        cwal_value * rc = 0;
        cwal_kvp * kvp = cwal_kvp_search_v( b->kvp, key, NULL, &left );
        if( ! kvp ) return NULL;
        rc = kvp->value;
        if(left){
            left->right = kvp->right;
        }else{
            assert(b->kvp == kvp);
            b->kvp = kvp->right /* ? kvp->right : NULL */;
        }
        kvp->right = NULL;
        assert( rc );
        if(takeKeyAsWell){
            *takeKeyAsWell = kvp->key;
        }else{
            cwal_value_unref2(e, kvp->key);
        }
        kvp->key = 0;
        kvp->value = 0;
        cwal_kvp_free( e, NULL, kvp, 1 );
        if(takeKeyAsWell && !V_IS_BUILTIN(*takeKeyAsWell)){
            cwal_value * vk = *takeKeyAsWell;
            assert( (vk->refcount > 0) && "Should still have our reference!" );
            if(0==--vk->refcount){
                cwal_value_reprobate(vk->scope /* vk->scope->engine->current? */, vk);
            }
        }
        if(!V_IS_BUILTIN(rc)){
            assert( (rc->refcount > 0) && "Should still have our reference!" );
            if(0==--rc->refcount){
                cwal_value_reprobate(rc->scope /* rc->scope->engine->current? */, rc);
            }
        }
        return rc;
    }

}

/**
   "Visits" one member of a key/value pair. If mode is 0 then the key
   is visited, and 1 means to visit the value. Any other value is
   illegal.

   Returns the result of func( pairPair, fState ).
*/
static int cwal_obase_visit_impl( cwal_kvp *kvp, char mode,
                                  cwal_value_visitor_f func, void * fState  ){
    cwal_value * v = 0;
    switch( mode ){
      case 0:
          v = kvp->key;
          break;
      case 1:
          v = kvp->value;
          if(!v) return 0;
          break;
      default:
          assert(!"Invalid visit mode.");
          return CWAL_RC_CANNOT_HAPPEN;
    }
    assert(0 != v);
    return func( v, fState );
    
}

char cwal_props_can( cwal_value const * c ){
    return (c && CWAL_VOBASE(c)) ? 1 : 0;
}

int cwal_props_clear( cwal_value * c ){
    cwal_obase * b = CWAL_VOBASE(c);
    cwal_engine * e = CWAL_VENGINE(c);
    /* dump_val(c,"props_clear"); */
    if(!e || !c) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    /**
       We need a mechanism for clearing properties stored in other
       places in higher-level types, e.g. we might need to add a
       public API which simply calls c->vtab->cleanup(c). For arrays,
       however, that would end badly.
    */
    else {
        cwal_cleanup_obase( e, b, 0 );
        return CWAL_RC_OK;
    }
}


static int cwal_kvp_visitor_props_copy( cwal_kvp const * kvp,
                                        void * state ){
#if 0
    return cwal_prop_set_v( (cwal_value *)state,
                            kvp->key, kvp->value );
#else
    /* We have to keep the property flags intact... */
    cwal_value * dest = (cwal_value*)state;
    cwal_obase * b = CWAL_VOBASE(dest);
    cwal_engine * e = CWAL_VENGINE(dest);
    if( !e || !b ) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_ACCESS;
    else{
        return cwal_kvp_set_v( e, dest, &b->kvp,
                               kvp->key, kvp->value, kvp->flags );
    }
#endif
}

int cwal_props_copy( cwal_value * src, cwal_value * dest ){
    if(!src || !dest) return CWAL_RC_MISUSE;
    else if(!cwal_props_can(src) || !cwal_props_can(dest)) return CWAL_RC_TYPE;
    else return cwal_props_visit_kvp( src, cwal_kvp_visitor_props_copy, dest );
}


#if 0
int cwal_value_clear_mutable_state( cwal_value * v ){
    cwal_engine * e = CWAL_VENGINE(v);
    assert(e);
    if(!e || !v) return CWAL_RC_MISUSE;
    else if(V_IS_BUILTIN(v)) return 0;
    switch(v->vtab->typeID){
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_STRING:
          return 0;
      case CWAL_TYPE_ARRAY:
          cwal_value_cleanup_array_impl( e, v, 1, 1, 0 );
          return 0;
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_NATIVE:
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_EXCEPTION:{
          cwal_obase * b = CWAL_VOBASE(v);
          cwal_value * proto = b ? b->prototype : NULL;
          assert(b);
          b->prototype = 0/* kludge */;
          v->vtab->cleanup( e, v );
          b->prototype = proto;
          return 0;
      }
      default:
          assert(!"unhandled type");
          return CWAL_RC_CANNOT_HAPPEN;
    }

}
#endif
    
int cwal_props_visit_values( cwal_value * c, cwal_value_visitor_f f, void * state ){
    cwal_obase * b = CWAL_VOBASE(c);
    if(!c || !f) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if( CWAL_F_IS_VISITING & b->flags ) return CWAL_RC_CYCLES_DETECTED;
    else {
        int rc = 0;
        cwal_kvp * kvp = b->kvp;
        cwal_value_set_visiting( c, 1 );
        for( ; kvp && (0==rc); kvp = kvp->right ){
            rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                ? 0
                : cwal_obase_visit_impl( kvp, 1, f, state );
        }
        cwal_value_set_visiting( c, 0 );
        return rc;
    }
}

int cwal_props_visit_keys( cwal_value * c, cwal_value_visitor_f f, void * state ){
    cwal_obase * b = CWAL_VOBASE(c);
    if(!c || !f) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    /* Reminder: we don't need (i think) to set CWAL_F_IS_VISITING
       for this because the keys cannot (yet!) contribute to cycles.
    */
    /*else if(CWAL_F_IS_VISITING & o->base.flags) return CWAL_RC_CYCLES_DETECTED;*/
    else{
        int rc = 0;
        cwal_kvp * kvp = b->kvp;
        /*o->base.flags |= CWAL_F_IS_VISITING;*/
        for( ; kvp && (0==rc); kvp = kvp->right ){
            rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                ? 0
                : cwal_obase_visit_impl( kvp, 0, f, state );
        }
        /*o->base.flags &= ~CWAL_F_IS_VISITING;*/
        return rc;
    }
}

char cwal_value_may_iterate( cwal_value const * c ){
    if(!c) return 0;
    else {
        cwal_obase const * b = CWAL_OBASE(c);
        return b
            ? ((CWAL_F_IS_VISITING & b->flags) ? 0 : 1)
            : 0;
    }
}

int cwal_props_visit_kvp( cwal_value * c, cwal_kvp_visitor_f f, void * state ){
    cwal_obase * b = CWAL_VOBASE(c);
    if(!c || !f) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_CYCLES_DETECTED;
    else {
        cwal_kvp * kvp;
        int rc = CWAL_RC_OK;
        cwal_value_set_visiting( c, 1 );
        for( kvp = b->kvp;
             kvp && (0==rc);
             kvp = kvp->right ){
            rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                ? 0
                : f( kvp, state );
        }
        cwal_value_set_visiting( c, 0 );
        return rc;
    }
}

int cwal_array_visit( cwal_array * o, cwal_value_visitor_f f, void * state ){
    if(!o || !f) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & o->base.flags) return CWAL_RC_CYCLES_DETECTED;
    else {
        cwal_size_t i;
        cwal_value * v;
        cwal_value * vSelf = cwal_array_value(o);
        int rc = CWAL_RC_OK;
        cwal_list * li = &o->list;
        cwal_value_set_visiting( vSelf, 1 );
        for( i = 0;
             (CWAL_RC_OK==rc)
             && (i < li->count);
             ++i ){
            v = (cwal_value*)li->list[i];
            if(v) rc = f( v, state );
        }
        cwal_value_set_visiting( vSelf, 0 );
        return rc;
    }
}

int cwal_array_visit2( cwal_array * o, cwal_array_visitor_f f, void * state ){
    if(!o || !f) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & o->base.flags) return CWAL_RC_CYCLES_DETECTED;
    else{
        cwal_size_t i;
        cwal_value * v;
        cwal_value * vSelf = cwal_array_value(o);
        int rc = CWAL_RC_OK;
        cwal_list * li = &o->list;
        cwal_value_set_visiting( vSelf, 1 );
        for( i = 0;
             (CWAL_RC_OK==rc)
             && (i < li->count);
             ++i ){
            v = (cwal_value*)li->list[i];
            if(v){
                rc = f( o, v, i, state );
            }
        }
        cwal_value_set_visiting( vSelf, 0 );
        return rc;
    }
}

#if 0
int cwal_array_swap( cwal_array * ar, cwal_size_t left, cwal_size_t right ){
    cwal_value * l = cwal_array_get(ar, left);
    cwal_value * r = cwal_array_get(ar, right);
    int rc;
    if(l) cwal_value_ref(l);
    rc = cwal_array_set(ar, left, r);
    if(!rc) {
        rc = cwal_array_set(ar, right, l);
        if(!rc) cwal_value_unref(l);
    }
    return rc;
}
#endif

/**
   qsort implementation adapted from:

   http://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Quicksort#Iterative_Quicksort

   Based on one of the C# implementations.
   
   li is the cwal_list to sort. left is the starting left-most index,
   right is the final index to sort (not the one-past-the-end!).  cmp
   is the comparison function to use and the state parameter is passed
   as-is to cmp().

   FIXME: we need a mechanism for propagating cwal-level exceptions
   set by the comparison function.
*/
static int cwal_value_list_sort_impl( cwal_list * li,
                                      cwal_size_t left,
                                      cwal_size_t right,
                                      cwal_value_stateful_compare_f cmp,
                                      void * state ){
    cwal_size_t const lhold = left;
    cwal_size_t const rhold = right;
    cwal_size_t pivot = (right - left) / 2;
    int rc = 0;
    void * swapTmp;
    assert(right>=left);
#define CV (cwal_value*)
#define LIST_SWAP(LI,L,R) \
    swapTmp = (LI)->list[L];           \
    (LI)->list[L] = (LI)->list[R];     \
    (LI)->list[R] = swapTmp;

    if(!pivot){
        if(right==left+1){
            /* Special-case for two-entry list :/ */
            rc = cmp( CV li->list[left],
                      CV li->list[right], state );
            if(rc>0){
                LIST_SWAP(li,left,right);
            }
        }
        return rc;
    }

    LIST_SWAP(li,pivot,left);
    pivot = left;
    ++left;
    while( right >= left ){
        int const cmpLeft = cmp( CV li->list[left], CV li->list[pivot], state );
        int const cmpRight = cmp( CV li->list[right], CV li->list[pivot], state );
#undef CV
        if((cmpLeft>=0) && (cmpRight<0)){
            LIST_SWAP(li,left, right);
        }else{
            if(cmpLeft>=0) --right;
            else{
                if(cmpRight<0) ++left;
                else {
                    --right;
                    ++left;
                }
            }
        }
    }
    LIST_SWAP(li,pivot,right);
    pivot = right;
    /*
      TODO use goto instead of recursion.
    */
    if(pivot>lhold){
        cwal_value_list_sort_impl(li, lhold, pivot, cmp, state);
    }
    if(rhold>(pivot+1)){
        cwal_value_list_sort_impl(li, pivot+1, rhold, cmp, state);
    }
    return rc;
#undef LIST_SWAP
}

int cwal_array_sort_stateful( cwal_array * ar,
                              cwal_value_stateful_compare_f cmp,
                              void * state ){
    if(!ar || !cmp) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS;
    else if(ar->list.count<2) return 0;
    else {
        cwal_value_list_sort_impl( &ar->list, 0, ar->list.count-1, cmp, state );
        return 0;
    }
}

/**
   Internal state for cwal_array_sort_func() and
   cwal_array_sort_func_cb().
*/
struct ArrayFuncState {
    cwal_function * f;
    cwal_value * self;
};
typedef struct ArrayFuncState ArrayFuncState;

static int cwal_array_sort_func_cb( cwal_value * lhs, cwal_value * rhs, void * state ){
    cwal_value * argv[2];
    ArrayFuncState * st = (ArrayFuncState *)state;
    cwal_value * rv = 0;
    int rc;
    argv[0] = lhs ? lhs : cwal_value_undefined();
    argv[1] = rhs ? rhs : cwal_value_undefined();
    rc = cwal_function_call(st->f, st->self, &rv, 2, argv);
    if(!rc && rv) return cwal_value_get_integer(rv);
    else return rc;
}

int cwal_array_sort_func( cwal_array * ar, cwal_value * self, cwal_function * cmp ){
    if(!ar || !cmp) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS;
    else if(ar->list.count<2) return 0;
    else {
        ArrayFuncState st;
        st.f = cmp;
        st.self = self ? self : cwal_function_value(cmp);
        return cwal_array_sort_stateful( ar, cwal_array_sort_func_cb, &st );
    }
}


int cwal_array_sort( cwal_array * ar, int(*cmp)(void const *, void const *) ){
    if(!ar || !cmp) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_ACCESS;
    else if(!ar->list.count) return 0;
    else {
        qsort( ar->list.list, ar->list.count, sizeof(void*), cmp );
        return 0;
    }
}

int cwal_array_reverse( cwal_array * ar ){
    if(!ar) return CWAL_RC_MISUSE;
    else if(CWAL_F_IS_VISITING & ar->base.flags) return CWAL_RC_MISUSE;
    else if(ar->list.count < 2) return 0;
    else{
        cwal_size_t b = 0, e = ar->list.count-1;
        void ** li = ar->list.list;
        void * tmp;
        for( ; b<e; ++b, --e ){
            tmp = li[b];
            li[b] = li[e];
            li[e] = tmp;
        }
        return 0;
    }
}

int cwal_compare_value_void( void const * lhs, void const * rhs ){
    return cwal_value_compare( *((cwal_value const**)lhs),
                               *((cwal_value const**)rhs) );
}
int cwal_compare_value_reverse_void( void const * lhs, void const * rhs ){
    int const rc = cwal_value_compare( *((cwal_value const**)lhs),
                                       *((cwal_value const**)rhs) );
    return rc ? -rc : 0;
}

#define TRY_CLONE_SHARING 1
/**
   If v is-a Object or Array then this function returns a deep
   clone, otherwise it returns v. In either case, the refcount
   of the returned value is increased by 1 by this call.
*/
static int cwal_value_clone_ref( cwal_engine * e, cwal_value * v, cwal_value ** dest ){
    if( V_IS_BUILTIN( v ) ) {
        *dest = v;
        return 0;
    } else{
        cwal_value * vrc = 0;
        int rc = CWAL_RC_OK;
#if TRY_CLONE_SHARING
        assert( v && dest );
        if( CWAL_VOBASE(v) ) rc = cwal_value_clone2( v, &vrc );
        else vrc = v;
#else
        rc = cwal_value_clone2(v, &vrc);
#endif
        if(0==rc){
            *dest = vrc;
        }
        return rc;
    }
}
    
static int cwal_value_clone_array( cwal_engine * e, cwal_value * orig, cwal_value ** pDest ){
    unsigned int i = 0;
    cwal_array * asrc = cwal_value_get_array( orig );
    unsigned int alen = cwal_array_length_get( asrc );
    cwal_array * destA = NULL;
    cwal_value * destV = NULL;
    int rc = 0;
    if(CWAL_F_IS_VISITING & asrc->base.flags) return CWAL_RC_CYCLES_DETECTED;
    asrc->base.flags |= CWAL_F_IS_VISITING;
#define RETURN(RC) rc=(RC); goto end

    assert( orig && asrc );
    destA = cwal_new_array( e );
    if(!destA){
        RETURN(CWAL_RC_OOM);
    }
    destV = CWAL_VALPART(destA);
                
    assert( destA );
    if( 0 != cwal_array_reserve( destA, alen ) ){
        cwal_value_unref2( e, destV );
        RETURN(CWAL_RC_OOM);
    }
    for( ; i < alen; ++i ){
        cwal_value * ch = cwal_array_get( asrc, i );
        if( NULL != ch ){
            cwal_value * cl = 0;
            int rc = cwal_value_clone_ref( e, ch, &cl );
            if( NULL == cl ){
                cwal_value_unref2( e, destV );
                assert(0 != rc);
                RETURN(rc);
            }
            else if( CWAL_RC_OK != (rc=cwal_array_set( destA, i, cl )) )
            {
                cwal_value_unref2( e, cl );
                cwal_value_unref2( e, destV );
                RETURN(rc);
            }
        }
    }
    end:
    if(rc){
        if(destA) cwal_array_unref(destA);
    }else{
        *pDest = destV;
    }
    asrc->base.flags &= ~CWAL_F_IS_VISITING;
    return rc;
#undef RETURN
}

static int cwal_value_clone_object( cwal_engine * e, cwal_value * orig, cwal_value ** pDest ){
    cwal_object * src = cwal_value_get_object( orig );
    cwal_object * dest = NULL;
    cwal_value * destV = NULL;
    cwal_kvp const * kvp = NULL;
    int rc = 0;
    assert( orig && src && pDest );
    if(CWAL_F_IS_VISITING & src->base.flags) return CWAL_RC_CYCLES_DETECTED;
    src->base.flags |= CWAL_F_IS_VISITING;
#define RETURN(RC) rc = (RC); goto end

    dest = cwal_new_object(e);
    if( NULL == dest ) { RETURN(CWAL_RC_OOM); }
    destV = CWAL_VALPART(dest);
    for( kvp = src->base.kvp; kvp; kvp = kvp->right ){
        cwal_value * key = 0;
        cwal_value * val = 0;
        assert( kvp->key && (kvp->key->refcount>0) );
        rc = cwal_value_clone_ref(e, kvp->key, &key);
        if(key) rc = cwal_value_clone_ref(e, kvp->value, &val);
        if(!rc) {
            assert(key);
            assert(val);
            rc = cwal_kvp_set_v( e, destV, &dest->base.kvp,
                                 key, val, CWAL_VAR_F_PRESERVE );
        }
        if(rc) {
            cwal_value_unref2(e, key);
            if(key) cwal_value_unref2(e, key);
            if(val) cwal_value_unref2(e, val);
            break;
        }
    }
    end:
    src->base.flags &= ~CWAL_F_IS_VISITING;
    if(!rc){
        if( dest ){
            assert(0 == rc);
            *pDest = destV;
        }
    }else if(destV){
        cwal_value_unref2(e, destV);
    }
    return rc;
#undef RETURN
}

int cwal_value_clone2( cwal_value * orig, cwal_value ** dest ){
    cwal_engine * e = CWAL_VENGINE(orig);
    int rc = CWAL_RC_OK;
    cwal_value * vrc = 0;
    cwal_scope * scopeCheck = e ? e->current : NULL;
    char setProto = 0;
    if(!e || !orig || !dest) return CWAL_RC_MISUSE;
    switch( orig->vtab->typeID ){
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_BOOL:
          vrc = orig;
          break;
#if TRY_CLONE_SHARING
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_STRING:
          vrc = orig;
          break;
#else
      case CWAL_TYPE_INTEGER:
          vrc = cwal_new_integer(e, cwal_value_get_integer(orig));
          if(!vrc) rc = CWAL_RC_OOM;
          break;
      case CWAL_TYPE_DOUBLE:
          vrc = cwal_new_double(e, cwal_value_get_double(orig));
          if(!vrc) rc = CWAL_RC_OOM;
          break;
      case CWAL_TYPE_STRING:{
          cwal_string const * str = cwal_value_get_string(orig);
          vrc = cwal_new_string_value(e, cwal_string_cstr(str),
                                      cwal_string_length_bytes(str));
          if(!vrc) rc = CWAL_RC_OOM;
          break;
      }
#endif
      case CWAL_TYPE_ARRAY:
          setProto = 1;
          rc = cwal_value_clone_array( e, orig, &vrc );
          break;
      case CWAL_TYPE_OBJECT:
          setProto = 1;
          rc = cwal_value_clone_object( e, orig, &vrc );
          break;
          /**
             Missing:

             EXCEPTION: todo

             BUFFER: todo

             NATIVE: cannot do these because we cannot know how to
             deal with the finalizers (we can't just copy the
             finalizer and the native state pointer and expect it to
             work). We _could_ check if they have implemented a clone
             operator (member function) and call that, but in doing so
             we might recursively call back into the function which
             led here.
           */
      case CWAL_TYPE_BUFFER:{
          cwal_buffer * borig = cwal_value_get_buffer(orig);
          cwal_buffer * b = cwal_new_buffer(e, borig->used+1);
          if(!b) rc = CWAL_RC_OOM;
          else{
              cwal_buffer_append(e, b, borig->mem, borig->used);
              assert(b->used == borig->used);
              b->mem[b->used] = 0;
              vrc = cwal_buffer_value(b);
          }
          break;
      }
      default:
          rc = CWAL_RC_TYPE;
          break;
    }
    if(vrc){
        assert(0 == rc);
        assert((scopeCheck==vrc->scope) || V_IS_BUILTIN(vrc) ||
               ((e->flags & CWAL_FEATURE_INTERN_STRINGS)
                && CWAL_STR(vrc))
               );
        *dest = vrc;
        if(setProto){
            cwal_value_prototype_set( vrc,
                                      cwal_value_prototype_get( e, orig ) );
        }
    }else if(!rc){
        assert( (NULL == vrc) && "vrc is NULL but rc says OK." );
        rc = CWAL_RC_OOM;
    }
    return rc;
}

cwal_value * cwal_value_clone( cwal_value * orig ){
    cwal_value * rc = 0;
    cwal_value_clone2( orig, &rc );
    return rc;
}


int cwal_int_to_cstr( cwal_int_t iVal, char * dest, cwal_size_t * nDest ){
  enum { BufLen = 30 };
  /* int isNegative = 0; */
  char zBuf[BufLen];
  char *z = zBuf;
  cwal_size_t zLen;
  if( !dest || !nDest ) return CWAL_RC_MISUSE;
#if 1
  zLen = sprintf(z, "%"CWAL_INT_T_PFMT, iVal);
  assert(zLen>0);
  if( *nDest < zLen ) return CWAL_RC_RANGE;
  *nDest = (cwal_size_t)zLen;
  memcpy( dest, z, zLen + 1/*NUL byte*/ );
  return 0;
#else
  /*Implementation taken from th1 sources. Breaks on INT_MIN,
    resulting in garbage. */
  if( iVal<0 ){
    isNegative = 1;
    iVal = iVal * -1;
  }
  *(--z) = '\0';
  *(--z) = (char)(48+(iVal%10));
  while( (iVal = (iVal/10))>0 ){
    *(--z) = (char)(48+(iVal%10));
    assert(z>zBuf);
  }
  if( isNegative ){
    *(--z) = '-';
  }
  zLen = zBuf+BufLen-z - 1/*NUL byte*/;
  if( *nDest <= zLen ) return CWAL_RC_RANGE;
  else{
      *nDest = zLen;
      memcpy( dest, z, zLen + 1/*NUL byte*/ );
      MARKER("int %"CWAL_INT_T_PFMT"==>string: <<<%s>>>\n", iVal, dest );
      return CWAL_RC_OK;
  }
#endif   
}


int cwal_double_to_cstr( cwal_double_t fVal, char * dest, cwal_size_t * nDest ){
  /*Implementation taken from th1 sources.*/
  enum { BufLen = 128 };
  cwal_int_t i;               /* Iterator variable */
  cwal_double_t v = fVal;     /* Input value */
  char zBuf[BufLen];   /* Output buffer */
  char *z = zBuf;      /* Output cursor */
  cwal_int_t iDot = 0;        /* Digit after which to place decimal point */
  cwal_int_t iExp = 0;        /* Exponent (NN in eNN) */
  cwal_size_t zLen;
  /* Precision: */
#define INSIGNIFICANT 0.000000000001
#define ROUNDER       0.0000000000005
  cwal_double_t insignificant = INSIGNIFICANT;

  /* If the real value is negative, write a '-' character to the
   * output and transform v to the corresponding positive number.
   */ 
  if( v<0.0 ){
    *z++ = '-';
    v *= -1.0;
  }

  /* Normalize v to a value between 1.0 and 10.0. Integer 
   * variable iExp is set to the exponent. i.e the original
   * value is (v * 10^iExp) (or the negative thereof).
   */ 
  if( v>0.0 ){
    while( (v+ROUNDER)>=10.0 ) { iExp++; v *= 0.1; }
    while( (v+ROUNDER)<1.0 )   { iExp--; v *= 10.0; }
  }
  v += ROUNDER;

  /* For a small (<12) positive exponent, move the decimal point
   * instead of using the "eXX" notation.
   */
  if( iExp>0 && iExp<12 ){
    iDot = iExp;
    iExp = 0;
  }

  /* For a small (>-4) negative exponent, write leading zeroes
   * instead of using the "eXX" notation.
   */
  if( iExp<0 && iExp>-4 ){
    *z++ = '0';
    *z++ = '.';
    for(i=0; i>(iExp+1); i--){
      *z++ = '0';
    }
    iDot = -1;
    iExp = 0;
  }

  /* Output the digits in real value v. The value of iDot determines
   * where (if at all) the decimal point is placed.
   */
  for(i=0; i<=(iDot+1) || v>=insignificant; i++){
    *z++ = (char)(48 + (int)v);
    v = (v - ((double)(int)v)) * 10.0;
    insignificant *= 10.0;
    if( iDot==i ){
      *z++ = '.';
    }
  }

  /* If the exponent is not zero, add the "eXX" notation to the
   * end of the string.
   */
  if( iExp!=0 ){
    char zExp[50];   /* String representation of iExp */
    cwal_size_t zExpLen = sizeof(zExp)/sizeof(zExp[0]);
    *z++ = 'e';
    cwal_int_to_cstr( iExp, zExp, &zExpLen );
    for(i=0; zExp[i]; i++){
      *z++ = zExp[i];
    }
  }

  *(z++) = '\0';
  zLen = z - zBuf - 1/*NUL byte*/;
  if( *nDest <= zLen ){
    return CWAL_RC_RANGE;
  }else{
    *nDest = zLen;
    memcpy( dest, zBuf, zLen + 1/*NUL byte*/ );
    return CWAL_RC_OK;
  }
#undef INSIGNIFICANT
#undef ROUNDER
}

int cwal_cstr_to_int( char const * cstr, cwal_size_t slen,
                      cwal_int_t * dest ){
    cwal_int_t v = 0;
    cwal_int_t oflow = 0;
    char const * p = cstr+slen-1;
    cwal_int_t mult = 0;
    if(!cstr || !slen || !*cstr) return CWAL_RC_MISUSE;
    for( ; p >= cstr;
         --p, oflow = v ){
        if( (*p<'0') || (*p>'9') ) {
            if(cstr == p){
                if('-'==*p){
                    v = -v;
                    break;
                }
                else if('+'==*p){
                    break;
                }
            }
            return CWAL_RC_TYPE;
        }
        v += mult
            ? (mult*(*p-'0'))
            : (*p-'0');
        if(v < oflow) return CWAL_RC_RANGE;
        mult = mult ? (mult*10) : 10;
    }
    if(dest) *dest = v;
    return 0;
}

int cwal_string_to_int( cwal_string const * s, cwal_int_t * dest ){
    return s
        ? cwal_cstr_to_int( cwal_string_cstr(s), CWAL_STRLEN(s), dest )
        : CWAL_RC_MISUSE;
}

int cwal_cstr_to_double( char const * cstr, cwal_size_t slen,
                         cwal_double_t * dest ){
    int rc;
    char const * p = cstr+slen-1;
    cwal_int_t rhs = 0;
    cwal_int_t lhs = 0;
    cwal_double_t rmult = 0.0;
    if(!cstr || !slen || !*cstr) return CWAL_RC_MISUSE;
    for( ; (p > cstr) && ('.' != *p); --p ){
        rmult = rmult ? (10*rmult) : 10;
    }
    if(p==cstr){
        /* Maybe it's an integer */
        if('.'==*p) return CWAL_RC_TYPE /* .1234 */;
        else {
            /* Try it as an integer */
            rc = cwal_cstr_to_int( p, slen, &lhs );
            if(!rc && dest) *dest = lhs;
            return rc;
        }
    }
    
    assert('.' == *p);
    rc  = cwal_cstr_to_int( p+1, cstr+slen-p-1, &rhs );
    if(rc) return rc;
    else if((p>cstr) && (rhs<0)) return CWAL_RC_TYPE /* 123.-456 */;

    rc = cwal_cstr_to_int( cstr, p - cstr, &lhs );
    if(!rc && dest){
        if(lhs>=0){
            *dest = (cwal_double_t)lhs
                + (rmult
                   ? (((cwal_double_t)rhs) / rmult)
                   : 0);
        }else{
            *dest = (cwal_double_t)lhs
                - (rmult
                   ? (((cwal_double_t)rhs) / rmult)
                   : 0);
        }

    }
    return rc;
}

int cwal_string_to_double( cwal_string const * s, cwal_double_t * dest ){
    return s
        ? cwal_cstr_to_double( cwal_string_cstr(s), CWAL_STRLEN(s), dest )
        : CWAL_RC_MISUSE;
}

void cwal_hash_clear( cwal_hash * h, char freeProps ){
    cwal_scope * sc = h ? CWAL_VALPART(h)->scope : 0;
    cwal_engine * e = sc ? sc->e : 0;
    if(!e) return;
    else{
        cwal_size_t i;
        cwal_kvp * kvp;
        cwal_kvp * next;
        if(freeProps){
            cwal_cleanup_obase( e, &h->base, 0 );
        }
        /* MARKER("Freeing hash @%p\n", (void const *)h); */
        for( i = 0; h->entryCount && (i < h->hashSize); ++i ){
            kvp = h->list[i];
            if(kvp){
                next = h->list[i] = NULL;
                for( ; kvp; kvp = next ){
                    next = kvp->right;
                    kvp->right = NULL;
                    cwal_kvp_free(e, sc, kvp, 1);
                    --h->entryCount;
                }
            }
        }
        assert(0==h->entryCount);
        memset(h->list, 0, h->hashSize*sizeof(cwal_value*));
    }
}


void cwal_value_cleanup_hash( cwal_engine * e, void * V ){
    cwal_value * vSelf = (cwal_value *)V;
    cwal_hash * h = CWAL_HASH(vSelf);
    /* MARKER("Freeing hash @%p\n", (void const *)h); */
    cwal_hash_clear( h, 1 );
    *h = cwal_hash_empty;
}

static int cwal_rescope_children_hash( cwal_value * v ){
    cwal_hash * h = CWAL_HASH(v);
    cwal_scope * sc = v->scope;
    cwal_kvp * kvp;
    cwal_size_t i;
    int rc = 0;
    assert(sc && h && v);
    rc |= cwal_rescope_children_obase(v);
    if(rc){
        assert(!"This should not be possible.");
    }
    /* cwal_dump_v(nv,"Re-scoping hashtable children..."); */
    for( i = 0; !rc && (i < h->hashSize); ++i ){
        kvp = h->list[i];
        if(kvp){
            cwal_kvp * next = NULL;
            for( ; kvp; kvp = next){
                next = kvp->right;
                assert(kvp->key);
                assert(kvp->value);
                rc = cwal_value_xscope(sc->e, sc, kvp->key, 0);
                if(!rc) rc = cwal_value_xscope(sc->e, sc, kvp->value, 0);
                /* cwal_dump_v(kvp->key,"Re-scoped key"); */
                /* cwal_dump_v(kvp->value,"Re-scoped value"); */
            }
        }
    }
    return rc;
}


cwal_value * cwal_new_hash_value( cwal_engine * e, cwal_size_t hashSize){
    if(!e || !hashSize) return NULL;
    else {
        cwal_size_t const tableSize = (hashSize*sizeof(cwal_kvp*));
        cwal_value * v = cwal_value_new(e, e->current,
                                        CWAL_TYPE_HASH, tableSize);
        cwal_hash * h;
        cwal_value * proto;
        if(!v) return NULL;
        assert(v->scope);
        h = CWAL_HASH(v);
        proto = h->base.prototype;
        *h = cwal_hash_empty;
        h->base.prototype = proto;
        h->hashSize = hashSize;
        h->list = (cwal_kvp**)((unsigned char *)(h+1));
        memset( h->list, 0, tableSize );
        return v;
    }
}

char cwal_value_is_hash( cwal_value const * v ){
    return NULL != CWAL_HASH(v);
}

cwal_hash * cwal_value_get_hash( cwal_value * v ){
    return CWAL_HASH(v);
}

cwal_hash * cwal_new_hash( cwal_engine * e, cwal_size_t hashSize ){
    cwal_value * v = cwal_new_hash_value(e, hashSize);
    return v ? CWAL_HASH(v) : NULL;
}

cwal_value * cwal_hash_value( cwal_hash * h ){
    return CWAL_VALPART(h);
}

/** @internal

    Internal impl for cwal_hash_search().  If tableIndex is not NULL
    then *tableIndex is assigned to the hash table index for
    the given key, even on search failure.

    If a match is found then its kvp entry is returned and if left is
    not NULL then *left will point to the kvp immediately to the left
    of the returned kvp (required for re-linking).

    This function is intollerant of NULLs for (h,key).

*/
static cwal_kvp * cwal_hash_search_impl( cwal_hash * h,
                                         cwal_value * key,
                                         cwal_hash_t * tableIndex,
                                         cwal_kvp ** left ){
    cwal_hash_t const ndx = cwal_value_hash( key ) % h->hashSize
        /* Reminder to self: the hash code is the reason we cannot add
           a strict comparison toggle - such comparisons would only be
           made if hash codes collided. e.g. the hashes for "1" and 1
           and 1.0 will be different, even though the values are
           equivalent using the default comparison ops.
        */
        ;
    cwal_kvp * kvp = h->list[ndx];
    cwal_type_id const kType = cwal_value_type_id(key);
    if(tableIndex) *tableIndex = ndx;
    for( ; kvp; (left?(*left=kvp):NULL), kvp = kvp->right ){
        assert(kvp->key);
        if(kvp->key==key) return kvp;
        else if(kType != cwal_value_type_id(kvp->key)){
            continue;
        }
        else if(0==cwal_value_compare(key, kvp->key)){
            return kvp;
        }
    }
    return NULL;
}

cwal_value * cwal_hash_search( cwal_hash * h, char const * key,
                               cwal_size_t keyLen ){
    cwal_hash_t ndx;
    cwal_kvp * kvp;
    cwal_string * sk;
    if(!h || !key) return NULL;
    if(!keyLen && *key) keyLen = cwal_strlen(key);
    ndx = cwal_hash_cstr( key, keyLen ) % h->hashSize;
    kvp = h->list[ndx];
    for( ; kvp; kvp = kvp->right ){
        assert(kvp->key);
        if(CWAL_TYPE_STRING != cwal_value_type_id(kvp->key)) continue;
        sk = cwal_value_get_string(kvp->key);
        if(keyLen != CWAL_STRLEN(sk)) continue;
        else if(0==cwal_compare_str_cstr(sk, key, keyLen)){
            return kvp->value;
        }
    }
    return NULL;
}
    
cwal_value * cwal_hash_search_v( cwal_hash * h, cwal_value * key ){
    cwal_kvp * kvp = (h && key)
        ? cwal_hash_search_impl(h, key, NULL, NULL)
        : NULL;
    return kvp ? kvp->value : NULL;
}

int cwal_hash_insert_v( cwal_hash * h, cwal_value * key, cwal_value * v,
                        char allowOverwrite ){
    cwal_size_t ndx = 0;
    cwal_kvp * left = 0;
    cwal_kvp * kvp;
    cwal_value * hv = CWAL_VALPART(h);
    if(!hv || !key || !v) return CWAL_RC_MISUSE;
    else if(CWAL_VAL_IS_VISITING(hv)) return CWAL_RC_ACCESS;
    kvp = cwal_hash_search_impl( h, key, &ndx, &left );
    if(kvp){
        if(!allowOverwrite) return CWAL_RC_ALREADY_EXISTS;
        if(kvp->key != key){
            cwal_value * old = kvp->key;
            kvp->key = key;
            cwal_value_xscope(hv->scope->e, hv->scope, key, 0);
            cwal_value_ref(key);
            cwal_value_unref(old);
        }
        if(kvp->value != v){
            cwal_value * old = kvp->value;
            kvp->value = v;
            cwal_value_xscope(hv->scope->e, hv->scope, v, 0);
            cwal_value_ref(v);
            cwal_value_unref(old);
        }
    }else{
        cwal_engine * e = CWAL_VALPART(h)->scope->e;
        kvp = cwal_kvp_alloc(e);
        if(!kvp) return CWAL_RC_OOM;
        assert(!kvp->right);
        if(left){
            kvp->right = left->right;
            left->right = kvp;
        }else{
            h->list[ndx] = kvp;
        }
        kvp->key = key;
        kvp->value = v;
        cwal_value_xscope(hv->scope->e, hv->scope, key, 0);
        cwal_value_xscope(hv->scope->e, hv->scope, v, 0);
        cwal_value_ref(key);
        cwal_value_ref(v);
        ++h->entryCount;
    }
    return 0;
}


int cwal_hash_insert( cwal_hash * h, char const * key, cwal_size_t keyLen,
                      cwal_value * v, char allowOverwrite ){
    cwal_value * check;
    if(!h || !key || !v) return CWAL_RC_MISUSE;
    else if(CWAL_VAL_IS_VISITING(CWAL_VALPART(h))) return CWAL_RC_ACCESS;
    else if(!keyLen && *key) keyLen = cwal_strlen(key);
    check = cwal_hash_search(h, key, keyLen);
    if(check && !allowOverwrite) return CWAL_RC_ALREADY_EXISTS;
    else {
        cwal_value * hv = CWAL_VALPART(h);
        check = cwal_new_string_value(CWAL_VENGINE(hv),
                                      key, keyLen);
        if(!check) return CWAL_RC_OOM;
        else {
            int rc = cwal_hash_insert_v( h, check, v, allowOverwrite );
            if(rc) cwal_value_unref(check);
            return rc;
        }
    }
}

int cwal_hash_remove( cwal_hash * h, cwal_value * key ){
    if(!h || !key) return CWAL_RC_MISUSE;
    else if(CWAL_VAL_IS_VISITING(CWAL_VALPART(h))) return CWAL_RC_ACCESS;
    else{
        cwal_size_t ndx = 0;
        cwal_kvp * left = 0;
        cwal_kvp * kvp = cwal_hash_search_impl( h, key, &ndx, &left );
        cwal_value * vSelf = CWAL_VALPART(h);
        cwal_engine * e = vSelf->scope->e;
        cwal_scope * sc = vSelf->scope;
        if(!kvp) return CWAL_RC_NOT_FOUND;
        else{
            if(left){
                left->right = kvp->right;
            }else{
                h->list[ndx] = kvp->right;
            }
            kvp->right = NULL;
            --h->entryCount;
            cwal_kvp_free(e, sc, kvp, 1);
            return 0;
        }
    }
}

cwal_size_t cwal_hash_entry_count(cwal_hash *h){
    return h ? h->entryCount : 0;
}

int cwal_hash_visit_kvp( cwal_hash * h, cwal_kvp_visitor_f f, void * state ){
    cwal_value * hv = (h && f) ? CWAL_VALPART(h) : NULL;
    cwal_obase * b = CWAL_VOBASE(hv);
    if(!hv || !f) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_CYCLES_DETECTED;
    else {
        cwal_kvp * kvp;
        cwal_size_t i;
        int rc = CWAL_RC_OK;
        cwal_value_set_visiting( hv, 1 );
        for( i = 0; !rc && (i < h->hashSize); ++i ){
            kvp = (cwal_kvp*)h->list[i];
            if(!kvp) continue;
            else{
                cwal_kvp * next = 0;
                for( ; !rc && kvp; kvp = next ){
                    next = kvp->right;
                    rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                        ? 0
                        : f( kvp, state );
                }
            }
        }
        cwal_value_set_visiting( hv, 0 );
        return rc;
    }
}
/**
   mode: 1==visit the keys, 0==the values.
*/
static int cwal_hash_visit_value_impl( cwal_hash * h, char mode,
                                       cwal_value_visitor_f f, void * state ){
    cwal_value * hv = (h && f) ? CWAL_VALPART(h) : NULL;
    cwal_obase * b = CWAL_VOBASE(hv);
    if(!hv || !f) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if(CWAL_F_IS_VISITING & b->flags) return CWAL_RC_CYCLES_DETECTED;
    else {
        cwal_kvp * kvp;
        cwal_size_t i;
        int rc = CWAL_RC_OK;
        rc = cwal_value_set_visiting( hv, 1 );
        assert(0==rc);
        for( i = 0; !rc && (i < h->hashSize); ++i ){
            kvp = (cwal_kvp*)h->list[i];
            if(!kvp) continue;
            else{
                cwal_kvp * next = 0;
                for( ; !rc && kvp; kvp = next ){
                    next = kvp->right;
                    rc = (CWAL_VAR_F_HIDDEN & kvp->flags)
                        ? 0
                        : f( mode ? kvp->value : kvp->key,
                             state );
                }
            }
        }
        rc = cwal_value_set_visiting( hv, 0 );
        assert(rc==0);
        return rc;
    }
}

int cwal_hash_visit_keys( cwal_hash * h, cwal_value_visitor_f f,
                          void * state ){
    return (!h || !f)
        ? CWAL_RC_MISUSE
        : cwal_hash_visit_value_impl(h, 0, f, state);
}

int cwal_hash_visit_values( cwal_hash * h, cwal_value_visitor_f f,
                            void * state ){
    return (!h || !f)
        ? CWAL_RC_MISUSE
        : cwal_hash_visit_value_impl(h, 1, f, state);
}

    
void cwal_value_cleanup_native( cwal_engine * e, void * V ){
    cwal_value * v = (cwal_value *)V;
    cwal_native * n = v ? CWAL_V2NATIVE(v) : 0;
    assert(v && n);
    if(n->finalize){
        n->finalize( e, n->native );
        n->finalize = NULL;
        n->native = NULL;
    }
    cwal_cleanup_obase( e, &n->base, 1 )
        /* Do this first in case any properties use the
           native data. No... do it last in case
           any of the properties are used in the finalizer.
           i broke linenoiseish's auto-save-at-finalize
           when i swapped this. */;
    *n = cwal_native_empty;
}

cwal_value * cwal_new_native_value( cwal_engine * e, void * N,
                                    cwal_finalizer_f dtor,
                                    void const * typeID ){
    if(!e || !N || !typeID) return NULL;
    else{
        cwal_value * v = cwal_value_new(e, e->current, CWAL_TYPE_NATIVE, 0);
        if( NULL != v ){
            cwal_native * n = CWAL_V2NATIVE(v);
            cwal_value * proto = n->base.prototype;
#if 0
            cwal_weak_annotate(e, N);
#endif
            *n = cwal_native_empty;
            n->base.prototype = proto;
            n->native = N;
            n->finalize = dtor;
            n->typeID = typeID;
        }
        return v;
    }
}

int cwal_native_set_rescoper( cwal_native * nv,
                              cwal_native_rescoper_f rescoper){
    if(!nv) return CWAL_RC_MISUSE;
    else {
        nv->rescope_children = rescoper;
        return 0;
    }
}

int cwal_value_fetch_native( cwal_value const * val, cwal_native ** ar){
    if( ! val ) return CWAL_RC_MISUSE;
    else if( CWAL_TYPE_NATIVE != val->vtab->typeID ) return CWAL_RC_TYPE;
    else{
        if(ar) *ar = CWAL_V2NATIVE(val);
        return 0;
    }
}

int cwal_native_fetch( cwal_native const * n,
                       void const * typeID, void ** dest){
    if( !n) return CWAL_RC_MISUSE;
    else if(!typeID || n->typeID==typeID){
        if(dest) *dest = n->native;
        return 0;
    }else return CWAL_RC_TYPE;
}

void * cwal_native_get( cwal_native const * n, void const * typeID){
    void * x = NULL;
    cwal_native_fetch( n, typeID, &x );
    return x;
}

void cwal_native_clear( cwal_native * n, char callFinalizer ){
    if(n && n->native){
        if(callFinalizer && n->finalize){
            cwal_value * nv = CWAL_VALPART(n);
            assert(nv->scope && nv->scope->e);
            n->finalize( nv->scope->e, n->native );
        }            
        n->native = NULL;
        n->finalize = NULL;
        n->typeID = NULL;
    }
}

cwal_value * cwal_native_value( cwal_native const * n ){
    return n ? CWAL_VALPART(n) : 0;
}

cwal_native * cwal_new_native( cwal_engine * e, void * n,
                               cwal_finalizer_f dtor,
                               void const * typeID ){
    cwal_value * v = cwal_new_native_value( e, n, dtor, typeID );
    return v ? CWAL_V2NATIVE(v) : 0;
}

cwal_native * cwal_value_get_native( cwal_value const * v ) {
    cwal_native * ar = NULL;
    cwal_value_fetch_native( v, &ar );
    return ar;
}


cwal_size_t cwal_engine_recycle_max_get( cwal_engine * e, cwal_type_id type ){
    if(!e) return 0;
    else switch(type){
      case CWAL_TYPE_STRING:
          return e->reString.maxLength;
      case CWAL_TYPE_WEAK_REF:
          return e->reWeak.maxLength;
      default:{
          int const ndx = cwal_recycler_index( type );
          return (ndx<0)
              ? 0
              : e->recycler[ndx].maxLength;
      }
    }
}

int cwal_engine_recycle_max( cwal_engine * e, cwal_type_id typeID,
                             cwal_size_t max ){
    if(!e) return CWAL_RC_MISUSE;
    else if( CWAL_TYPE_UNDEF == typeID ){
        int rc = 0;
#define DO(T) rc = cwal_engine_recycle_max(e, CWAL_TYPE_##T, max ); if(rc) return rc
        DO(ARRAY);
        DO(BUFFER);
        DO(DOUBLE);
        DO(EXCEPTION);
        DO(FUNCTION);
        DO(INTEGER);
        DO(KVP);
        DO(NATIVE);
        DO(OBJECT);
        DO(SCOPE);
        DO(STRING);
        DO(WEAK_REF);
#undef DO
        return rc;
    }
    else {
        cwal_recycler * li;
        void * mem;
        switch(typeID){
          case CWAL_TYPE_STRING:
              li = &e->reString;
              break;
          case CWAL_TYPE_WEAK_REF:
              li = &e->reWeak;
              break;
          default:{
            int const ndx = cwal_recycler_index( typeID );
            if( ndx < 0 ) return CWAL_RC_TYPE;
            li = &e->recycler[ndx];
          }
        }
        li->maxLength = max;
        while( li->count > max ){
            /* If we're over our limit, give them back... */
            assert(li->list);
            switch(typeID){
              case CWAL_TYPE_WEAK_REF:{
                  cwal_weak_ref * r = (cwal_weak_ref *)li->list;
                  mem = r;
                  li->list = r->next;
                  r->next = NULL;
                  break;
              }
              case CWAL_TYPE_KVP:{
                  cwal_kvp * kvp = (cwal_kvp *)li->list;
                  mem = kvp;
                  li->list = kvp->right;
                  kvp->right = 0;
                  break;
              }
              default:
                  mem = (cwal_value *)li->list;
                  li->list = cwal_value_snip((cwal_value *)li->list);
                  break;
            }
            --li->count;
            cwal_free( e, mem );
        }
        return CWAL_RC_OK;
    }
}

/**
   Ensures that s->props is initialized. Returns 0 on success, non-0
   on error (which is serious and must not be ignored). After a successful
   call it "cannot fail."
*/
static int cwal_scope_init_props(cwal_scope *s){
    if(s->props) return 0;
    assert( s->e );
    s->props = cwal_new_object(s->e);
    if(s->props){
        cwal_value * pv;
        pv = cwal_object_value(s->props);
        cwal_value_ref2(s->e, pv)
            /* we do this so that sweep() cannot wipe it out.
               Yes, i've actually seen this object get swept
               up before. */
            ;
#if 0
        cwal_value_make_vacuum_proof(pv, 1)
            /* hypothetically not necessary, since s->props is the
               value which vacuuming revolves around, but for the
               sake of sanity, let's do this. */;
#endif
#if 0
        /* Vacuuming breaks this. */
        assert((pv->scope->level == s->level) && "Is this a sane assumption?");
#else
        if(pv->scope->level > s->level){
            cwal_value_xscope(s->e, s, pv, NULL);
        }
#endif
#if 1
        if(s->props->base.prototype){
            /**
               If we don't do this then cwal_scope_search_v()
               and friends will resolve prototype members installed
               for the default prototype for props->proto's type by
               the client. It might be interesting to install our own
               prototype for these objects, but i'm not sure what we
               might do with it.

               i.e. if we don't do this then (using th1ish as an example):

               scope {
                 get(xyz) // resolves to Object.get() inherited method
               }
            */
            cwal_value_prototype_set(pv, NULL);
        }
#endif
    }
    return s->props
        ? 0
        : CWAL_RC_OOM;
}

/**
   Temporary internal macro used to set up the engine/scope
   arguments for the cwal_var_xxx() family of functions.
 */
#define SETUP_VAR_E_S if(!e) e=s?s->e:0; if(!s)s=e?e->current:0; \
    if(!s && !e) return CWAL_RC_MISUSE


static int cwal_var_set_v_impl( cwal_engine * e, cwal_scope * s,
                                cwal_value * key, cwal_value * v,
                                char searchParents,
                                uint16_t flags ){
    if(!e || !key) return CWAL_RC_MISUSE;
    SETUP_VAR_E_S;
    else{
        /* We need to take parent scopes into account when assigning a
           variable. This causes duplicate lookup of the var via
           cwal_kvp_set_v() (which also has to search).  FIXME: add a
           setter which accounts for this stuff in one go. We need an
           internal cwal_var_get_kvp() which gives us the cwal_kvp
           associated with the var.
        */
        cwal_scope * foundIn = 0;
        int rc = 0;
        cwal_value * got = 0;
        if(!v) searchParents = 0 /* arguable! */;
        rc = cwal_var_get_v( e, s, key, &got, searchParents, &foundIn );
        if(CWAL_RC_NOT_FOUND==rc){
            assert(!foundIn);
            if(!v) return rc;
        }
        else if(!rc){
            assert(foundIn && foundIn->props);
        }
        else if(rc) return rc;
        else if(searchParents && s->parent
#if 0
           && !(CWAL_VAR_F_CONST & flags/*KLUDGE to avoid a loop!*/)
#endif
           ){
            rc = cwal_var_get_v( e, s, key, &got, searchParents, &foundIn );
            if((CWAL_RC_NOT_FOUND==rc) && !v) {
                return 0;
            }
            else if(rc) return rc;
            else {
                assert(foundIn && foundIn->props);
            }
        }

        if(!foundIn){
            foundIn = s;
            if(!s->props){
                rc = cwal_scope_init_props(s);
                if(rc) return rc;
            }
        }
        assert(foundIn && foundIn->props);
#if 0
        MARKER("Setting [opaque key type] in scope#%"CWAL_SIZE_T_PFMT" via scope#%"CWAL_SIZE_T_PFMT".\n",
               foundIn->level, s->level);
#endif
        return v
            ? cwal_kvp_set_v( e, cwal_object_value(foundIn->props),
                              &foundIn->props->base.kvp,
                              key, v, flags )
            : cwal_kvp_unset_v( e, foundIn, &foundIn->props->base.kvp, key )
            ;
    }
}

int cwal_var_set_v( cwal_engine * e, cwal_scope * s,
                    cwal_value * key, cwal_value * v ){
    return cwal_var_set_v_impl(e, s, key, v, 1, CWAL_VAR_F_PRESERVE);
}

    
int cwal_var_set_s( cwal_engine * e, cwal_scope * s, cwal_string * key, cwal_value * v ){
    return cwal_var_set_v_impl(e, s, cwal_string_value(key), v, 1, CWAL_VAR_F_PRESERVE);
}

int cwal_var_set( cwal_engine * e, cwal_scope * s, char const * key,
                  cwal_size_t keyLen, cwal_value * v ){
    if(!key) return CWAL_RC_MISUSE;
    SETUP_VAR_E_S;
    else{
        cwal_string * sKey;
        cwal_value * sv;
        int rc;
        sKey = cwal_new_string(e, key, keyLen);
        if(!s) return CWAL_RC_OOM;
        sv = cwal_string_value(sKey);
        rc = cwal_var_set_v_impl( e, s, sv, v, 1, CWAL_VAR_F_PRESERVE );
        /* Note that i "would very much like" to generically unref sKey
           now, but interning of values messes with that.
        */
        if(rc
#if 0
           || (/*special-case hack b/c we know there cannot be
                 any other references in this case. i will likely
                 regret this at some point. */
               2==sv->refcount /*1==s, 1==s->props*/)
           /* and indeed i did regret it. */
#endif
           )
        {
            cwal_value_unref2( e, sv );
        }
        return rc;
    }
}

cwal_scope * cwal_scope_parent( cwal_scope * s ){
    return s ? s->parent : 0;
}

cwal_scope * cwal_scope_top( cwal_scope * s ){
    for( ; s && s->parent; s = s->parent ){}
    return s;
}

cwal_value * cwal_scope_properties( cwal_scope * s ){
    if(!s) return NULL;
    else if(!s->props) cwal_scope_init_props(s);
    return CWAL_VALPART(s->props);
}

cwal_value * cwal_scope_search_v( cwal_scope * s,
                                        int upToDepth,
                                        cwal_value const * key,
                                        cwal_scope ** foundIn ){
    if(!s || !key) return NULL;
    else if(!s->props && !upToDepth) return NULL;
    else {
        cwal_scope * os;
        cwal_value * v = 0;
        if( upToDepth<0 ){
            assert(s->level);
            upToDepth = (int)(s->level-1);
        }
        for( os = s;
             os && (upToDepth>=0);
             --upToDepth ){
            v = os->props
                ? cwal_prop_get_v( cwal_object_value(os->props), key )
                : NULL;
            if(v){
                if(foundIn) *foundIn = os;
                break;
            }
            else os = os->parent;
        }
        return v;
    }
}


cwal_kvp * cwal_scope_search_kvp_v( cwal_scope * s,
                                    int upToDepth,
                                    cwal_value const * key,
                                    cwal_scope ** foundIn ){
    if(!s || !key) return NULL;
    else if(!s->props && !upToDepth) return NULL;
    else {
        cwal_scope * os;
        cwal_kvp * kvp = 0;
        if( upToDepth<0 ){
            assert(s->level);
            upToDepth = (int)(s->level-1);
        }
        for( os = s;
             os && (upToDepth>=0);
             --upToDepth ){
            kvp = os->props
                ? cwal_prop_get_kvp_v( CWAL_VALPART(os->props),
                                       key, 0, 0 )
                : NULL;
            if(kvp){
                if(foundIn) *foundIn = os;
                break;
            }
            else os = os->parent;
        }
        return kvp;
    }
}

cwal_kvp * cwal_scope_search_kvp( cwal_scope * s,
                                  int upToDepth,
                                  char const * key,
                                  cwal_size_t keyLen,
                                  cwal_scope ** foundIn ){
    if(!s || !key) return NULL;
    else if(!s->props && !upToDepth) return NULL;
    else {
        cwal_scope * os;
        cwal_kvp * kvp = 0;
        if(!keyLen && *key) keyLen = cwal_strlen(key);
        if( upToDepth<0 ){
            assert(s->level);
            upToDepth = (int)(s->level-1);
        }
        for( os = s;
             os && (upToDepth>=0);
             --upToDepth ){
            kvp = os->props
                ? cwal_prop_get_kvp( CWAL_VALPART(os->props),
                                     key, keyLen, 0, 0 )
                : NULL;
            if(kvp){
                if(foundIn) *foundIn = os;
                break;
            }
            else os = os->parent;
        }
        return kvp;
    }

}


cwal_value * cwal_scope_search( cwal_scope * s, int upToDepth,
                                      char const * key,
                                      cwal_size_t keyLen,
                                      cwal_scope ** foundIn ){
    if(!s || !key ) return NULL;
    else if(!s->props && !upToDepth) return NULL;
    else{
        cwal_scope * os;
        cwal_value * v = 0;
        cwal_scope * _foundIn = 0;
        if(!foundIn) foundIn = &_foundIn;
        if( upToDepth<0 ){
            assert(s->level);
            upToDepth = (int)(s->level - 1);
        }
        for( os = s;
             os && (upToDepth>=0);
             --upToDepth ){
            v = os->props
                ? cwal_prop_get( cwal_object_value(os->props),
                                 key, keyLen )
                : NULL;
            if(v){
                *foundIn = os;
                break;
            }
            else os = os->parent;
        }
        return v;
    }
}

int cwal_scope_chain_set_v( cwal_scope * s, int upToDepth,
                            cwal_value * k, cwal_value * v ){
    if(!s || !k) return CWAL_RC_MISUSE;
    else {
        int rc;
        /* cwal_value * oldV = 0; */
        cwal_scope * os = NULL;
        /* oldV =  */cwal_scope_search_v( s, upToDepth, k, &os );
        if(/*!oldV ||*/ !os){
            os = s;
        }
        if(!os->props){
            rc = cwal_scope_init_props( os );
            if(rc) return rc;
            assert(os->props);
        }
        rc = cwal_prop_set_v( cwal_object_value(os->props), k, v );
        return rc;
    }
}

int cwal_scope_chain_set( cwal_scope * s, int upToDepth,
                          char const * k, cwal_size_t keyLen,
                          cwal_value * v ){
    if(!s || !k) return CWAL_RC_MISUSE;
    else {
        int rc;
        /* cwal_value * oldV = 0; */
        cwal_scope * os = NULL;
        /* oldV =  */cwal_scope_search( s, upToDepth, k, keyLen, &os );
        if(/*!oldV ||*/ !os) os = s;
        if(!os->props){
            rc = cwal_scope_init_props( os );
            if(rc) return rc;
            assert(os->props);
        }
        rc = cwal_prop_set( cwal_object_value(os->props), k, keyLen, v );
        return rc;
    }
}


int cwal_var_decl_v( cwal_engine * e, cwal_scope * s,
                     cwal_value * key, cwal_value * v,
                     uint16_t flags ){
    if(!e || !key) return CWAL_RC_MISUSE;
    SETUP_VAR_E_S;
    else{
        int const rc = cwal_scope_init_props(s);
        return rc
            ? rc
            : (cwal_kvp_search_v(s->props->base.kvp, key, 0, 0)
               ? CWAL_RC_ALREADY_EXISTS
               : cwal_var_set_v_impl( e, s, key,
                                      v ? v : cwal_value_undefined(),
                                      0, flags));
    }
}

int cwal_prop_flags_v( cwal_value *c, cwal_value * key ){
    cwal_scope * s = c ? c->scope : 0;
    cwal_engine * e = s ? s->e : 0;
    cwal_obase * ob = CWAL_VOBASE(c);
    if(!e || !key || !ob) return -1;
    else if(!ob->kvp) return -2;
    else{
        cwal_kvp * kvp = cwal_kvp_search_v(ob->kvp, key, 0, 0);
        return kvp ? (int)kvp->flags : -3;
    }
}
    
int cwal_var_decl_s( cwal_engine * e, cwal_scope * s,
                     cwal_string * key, cwal_value * v,
                     uint16_t flags ){
    return cwal_var_decl_v( e, s, cwal_string_value(key), v, flags );
}

int cwal_var_decl( cwal_engine * e, cwal_scope * s, char const * key,
                   cwal_size_t keyLen, cwal_value * v, uint16_t flags ){
    if(!e || !key || !*key) return CWAL_RC_MISUSE;
    SETUP_VAR_E_S;
    else {
        int rc;
        if(!keyLen) keyLen = cwal_strlen(key);
        rc = cwal_scope_init_props(s);
        if(rc) return rc;
        else if( cwal_kvp_search(s->props->base.kvp, key, keyLen, 0, 0) ) {
            return CWAL_RC_ALREADY_EXISTS;
        }
        else{
            cwal_value * skey = cwal_new_string_value(e, key, keyLen);
            if(!skey) return CWAL_RC_OOM;
            rc = cwal_var_set_v_impl( e, s, skey,
                                      v ? v : cwal_value_undefined(),
                                      0, flags);
            if(rc) cwal_value_unref(skey);
            return rc;
        }
    }
}

int cwal_var_unset_v( cwal_engine * e, cwal_scope * s, cwal_value * key ){
    return cwal_var_set_v_impl( e, s, key, 0, 0, 0 );
}

int cwal_var_unset_s( cwal_engine * e, cwal_scope * s, cwal_string * key ){
    return cwal_var_unset_v( e, s, cwal_string_value(key) );
}

int cwal_var_unset( cwal_engine * e, cwal_scope * s, char const * key, cwal_size_t keyLen ){
    return cwal_var_set( e, s, key, keyLen, 0 );
}


int cwal_var_get( cwal_engine * e, cwal_scope * s, char const * key, cwal_size_t keyLen,
                  cwal_value ** v, char searchParents, cwal_scope ** containingScope ){
    if(!e || !key) return CWAL_RC_MISUSE;
    SETUP_VAR_E_S;
    else if(!s->props && (!searchParents || !s->parent)) return CWAL_RC_NOT_FOUND;
    else{
        cwal_scope * foundIn = 0;
        cwal_kvp * kvp;
        if(!keyLen) keyLen = cwal_strlen(key);
        kvp = (s->props && s->props->base.kvp)
            ? cwal_kvp_search( s->props->base.kvp, key, keyLen, NULL, NULL )
            : 0;
        if(kvp) foundIn = s;
        else if(searchParents && s->parent){
            cwal_scope * p;
            int rc = 0;
            for( p = s->parent; p && (0==rc); p = p->parent ){
                if(!p->props || !p->props->base.kvp) continue;
                kvp = cwal_kvp_search( p->props->base.kvp, key, keyLen, 0, 0 );
                if(kvp){
                    foundIn = p;
                    break;
                }
            }
            if(rc) return rc;
        }
        if(!kvp) return CWAL_RC_NOT_FOUND;
        else{
            assert(foundIn);
            if(v) *v = kvp->value;
#if 0
            /* This breaks w/ s1 func calls when a param names match
               a deeper scope's var. */
            /* nonono - assignment over the parent value would not be seen here.
               We need a mechanism for linking vars like TH1 has.

               Or we can at least apply this hack for CONST vars, which we "know"
               cannot be overwritten...
            */
            if((s != foundIn) && (CWAL_VAR_F_CONST & kvp->flags)){
                CWAL_TR_S(e,s);
                CWAL_TR3(e,CWAL_TRACE_SCOPE_MASK,
                         "Setting local var alias for CONST value found in parent.");
                /* Add an alias in this scope for future reference */
                cwal_var_set_s_impl( e, s, cwal_value_get_string(kvp->key),
                                     kvp->value, 0, kvp->flags )
                    /* If it fails, no big deal for this case. */;
            }
#endif
            if(containingScope) *containingScope = foundIn;
            return 0;
        }
    }
}

int cwal_var_get_v( cwal_engine * e, cwal_scope * s, cwal_value * key,
                    cwal_value ** v, char searchParents, cwal_scope ** containingScope ){
    SETUP_VAR_E_S;
    else if(!key) return CWAL_RC_MISUSE;
    else if(!s->props && (!searchParents || !s->parent)) return CWAL_RC_NOT_FOUND;
    else{
        cwal_scope * foundIn = 0;
        cwal_kvp * kvp;
        kvp = (s->props && s->props->base.kvp)
            ? cwal_kvp_search_v( s->props->base.kvp, key, NULL, NULL )
            : 0;
        if(kvp) foundIn = s;
        else if(searchParents && s->parent){
            cwal_scope * p;
            int rc = 0;
            for( p = s->parent; p && (0==rc); p = p->parent ){
                if(!p->props || !p->props->base.kvp) continue;
                kvp = cwal_kvp_search_v( p->props->base.kvp, key, 0, 0 );
                if(kvp){
                    foundIn = p;
                    break;
                }
            }
            if(rc) return rc;
        }
        if(!kvp) return CWAL_RC_NOT_FOUND;
        else{
            assert(foundIn);
            if(v) *v = kvp->value;
            if(containingScope) *containingScope = foundIn;
            return 0;
        }
    }
}


int cwal_var_get_s( cwal_engine * e, cwal_scope * s, cwal_string * key, cwal_value ** v, char searchParents,
                    cwal_scope ** containingScope ){
    return cwal_var_get_v( e, s, cwal_string_value(key),
                           v, searchParents, containingScope );
}

    
#undef SETUP_VAR_E_S
    

cwal_hash_t cwal_value_hash_null_undef( cwal_value const * v ){
    return (cwal_hash_t)
        ((CWAL_TYPE_NULL==v->vtab->typeID)
         ? -1 : -2);
}
cwal_hash_t cwal_value_hash_bool( cwal_value const * v ){
    return (cwal_hash_t) (CWAL_BOOL(v) ? -4 : -3);
}
cwal_hash_t cwal_value_hash_int( cwal_value const * v ){
    return (cwal_hash_t) *CWAL_INT(v);
}
cwal_hash_t cwal_value_hash_double( cwal_value const * v ){
    return (cwal_hash_t) *CWAL_DBL(v);
}
cwal_hash_t cwal_value_hash_string( cwal_value const * v ){
    cwal_string const * s = CWAL_STR(v);
    assert(s);
    return cwal_hash_cstr( cwal_string_cstr(s), CWAL_STRLEN(s) );
}

cwal_hash_t cwal_value_hash_ptr( cwal_value const * v ){
#if CWAL_VOID_PTR_IS_BIG
    /* IF THE DEBUGGER LEADS YOU NEAR HERE...
       try changing ptr_int_t back to uint64_t.
    */
    typedef uint64_t ptr_int_t;
#else
    typedef uint32_t ptr_int_t;
#endif
    return (cwal_hash_t)((ptr_int_t)v);
}

cwal_hash_t cwal_value_hash( cwal_value const * v ){
    return (v && v->vtab && v->vtab->hash)
        ? v->vtab->hash(v)
        : 0;
}

int cwal_value_compare( cwal_value const * lhs, cwal_value const * rhs ){
    if(lhs == rhs) return 0;
    else if(!lhs) return -1;
    else if(!rhs) return 1;
    else return lhs->vtab->compare( lhs, rhs );
}

#define COMPARE_TYPE_IDS(L,R) ((L)->vtab->typeID - (R)->vtab->typeID)
/* (((L)->vtab->typeID < (R)->vtab->typeID) ? -1 : 1) */

int cwal_value_cmp_ptr_only( cwal_value const * lhs, cwal_value const * rhs ){
    if(lhs==rhs) return 0;
    else if(lhs->vtab->typeID==rhs->vtab->typeID) return
        ((void const*)lhs < (void const *)rhs)
        ? -1 : 1 /*
                   Why do this? Because it at least provides
                   consistent ordering for two different instances
                   within a given value's lifetime. i can't think of
                   any saner alternative :/.
                 */
        ;
    else return COMPARE_TYPE_IDS(lhs,rhs);
}

int cwal_value_cmp_func( cwal_value const * lhs, cwal_value const * rhs ){
    if(lhs==rhs) return 0;
    else if(lhs->vtab->typeID!=rhs->vtab->typeID) return COMPARE_TYPE_IDS(lhs,rhs);
    else {
        cwal_function const * l  = cwal_value_get_function(lhs);
        cwal_function const * r  = cwal_value_get_function(rhs);
        assert(l);
        if(!l) return r ? -1 : 0;
        else if(!r) return 1;
        else if((l->callback == r->callback) && (l->state.data==r->state.data)) return 0;
        else return
            ((void const*)lhs < (void const *)rhs)
            ? -1 : 1 /* see comments in cwal_value_cmp_ptr_only() */;
    }
}

int cwal_value_cmp_buffer( cwal_value const * lhs, cwal_value const * rhs ){
    if(lhs==rhs) return 0;
    else if(lhs->vtab->typeID!=rhs->vtab->typeID) return COMPARE_TYPE_IDS(lhs,rhs);
    else {
        cwal_buffer const * l  = cwal_value_get_buffer(lhs);
        cwal_buffer const * r  = cwal_value_get_buffer(rhs);
        assert(l);
        if(!l) return r ? -1 : 0;
        else if(!r) return 1;
        else if(l->used == r->used){
            return l->used ? memcmp(l->mem, r->mem, l->used) : 0;
        }else{
            cwal_size_t const min = (l->used < r->used) ? l->used : r->used;
            int const cmp = min
                ? memcmp(l->mem, r->mem, min)
                : ((l->used==min) ? -1 : 1);
            return cmp ? cmp : ((l->used==min) ? -1 : 1);
        }
    }
}
    
int cwal_compare_cstr( char const * s1, cwal_size_t len1,
                       char const * s2, cwal_size_t len2 ){
    if(!len1) return len2 ? -1 : 0;
    else if(!len2) return 1;
    else if(!s1) return s2 ? -1 : 0;
    else if(!s2) return 1;
    else {
        cwal_size_t const max = (len1<len2) ? len1 : len2;
        int const rc = memcmp( s1, s2, max );
        /* When the first max bytes match, the shorter string compares less
           than the longer one. */
        return (0!=rc)
            ? rc
            : (len1==len2 ? 0 :
               (len1<len2 ? -1 : 1));
    }
}

int cwal_compare_str_cstr( cwal_string const * s1,
                           char const * s2, cwal_size_t len2 ){
    return cwal_compare_cstr(s1? cwal_string_cstr(s1) : 0,
                             s1 ? CWAL_STRLEN(s1) : 0,
                             s2, len2);
}


#if 0
int cwal_value_cmp_type_only( cwal_value const * lhs, cwal_value const * rhs ){
    return(lhs->vtab->typeID==rhs->vtab->typeID)
        ? 0
        : COMPARE_TYPE_IDS(lhs,rhs);
}
#endif

int cwal_value_cmp_nullundef( cwal_value const * lhs, cwal_value const * rhs ){
    char const isNull = (CWAL_TYPE_NULL==lhs->vtab->typeID) ? 1 : 0;
    assert(isNull || CWAL_TYPE_UNDEF==lhs->vtab->typeID);
    switch( rhs->vtab->typeID ){
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_UNDEF:
          return 0;
      default:
          return cwal_value_get_bool(rhs) ? -1 : 0;
    }
}

int cwal_value_cmp_string( cwal_value const * lhs, cwal_value const * rhs ){
    cwal_string const * s = CWAL_STR(lhs);
    assert(s && "lhs is not-a string");
    switch(rhs->vtab->typeID){
#if 0
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_NATIVE:
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_EXCEPTION:
          /* Seems to be how JS does it. */
          return CWAL_STRLEN(s) ? -1 : 1;
#endif
      case CWAL_TYPE_STRING:{
          cwal_string const * r = CWAL_STR(rhs);
          return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s),
                                    cwal_string_cstr(r), CWAL_STRLEN(r) );
      }
      case CWAL_TYPE_BOOL: /* BOOL here is sooo arguable. */
          return CWAL_STRLEN(s)
              ? (cwal_value_get_bool(rhs) ? 0 : 1)
              : (cwal_value_get_bool(rhs) ? -1 : 0);
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:{
          /* FIXME: for number-string conversions it would make
             more sense to do it the other way around: convert
             the string to a number, then compare. That way we
             have no problems with trailing 0's and whatnot.
          */
          enum { BufSize = 100 };
          char buf[BufSize] = {0,};
          cwal_size_t sz = BufSize;
          switch(rhs->vtab->typeID){
            case CWAL_TYPE_BOOL:
                /* FIXME? Use "true" and "false" instead of 0 and 1? */
            case CWAL_TYPE_INTEGER:
                cwal_int_to_cstr( cwal_value_get_integer(rhs), buf, &sz);
                break;
            case CWAL_TYPE_DOUBLE:
                cwal_double_to_cstr( cwal_value_get_double(rhs), buf, &sz);
                break;
            default:
                assert(!"CANNOT HAPPEN");
                break;
          }
          return cwal_compare_cstr( cwal_string_cstr(s), CWAL_STRLEN(s),
                                    buf, sz );
      }
      default:
          return COMPARE_TYPE_IDS(lhs,rhs);
    }
}

int cwal_value_cmp_bool( cwal_value const * lhs, cwal_value const * rhs ){
    assert(CWAL_TYPE_BOOL==lhs->vtab->typeID);
    switch( rhs->vtab->typeID ){
      case CWAL_TYPE_BOOL:
          /* reminder: all booleans point either to the same value
             (for true) or NULL (for false). */
          return (lhs==rhs)
              ? 0
              : (CWAL_BOOL(lhs) ? 1 : -1);
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:{
          char const l = CWAL_BOOL(lhs);
          char const r = cwal_value_get_bool(rhs);
          return l==r
              ? 0 : (l<r ? -1 : 1);
      }
      case CWAL_TYPE_STRING:
          return -(rhs->vtab->compare(rhs, lhs));
#if 0
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:
          return -1;
#endif
      default:
          return COMPARE_TYPE_IDS(lhs,rhs);
    };
}

#if 0
int cwal_value_cmp_val_bool( cwal_value const * lhs, cwal_value const * rhs ){
    cwal_value const * bl = cwal_value_get_bool(lhs)
        ? cwal_value_true()
        : cwal_value_false();
    return cwal_value_cmp_bool(bl, rhs);
}
#endif

int cwal_value_cmp_int( cwal_value const * lhs, cwal_value const * rhs ){
    assert(CWAL_TYPE_INTEGER==lhs->vtab->typeID);
    switch( rhs->vtab->typeID ){
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:{
          cwal_int_t const l = *CWAL_INT(lhs);
          cwal_int_t const r = cwal_value_get_integer(rhs);
          return l==r
              ? 0 : (l<r ? -1 : 1);
      }
      case CWAL_TYPE_STRING:
          return -(rhs->vtab->compare(rhs, lhs));
#if 0
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:{
          cwal_int_t const l = *CWAL_INT(lhs);
          return (l==0) ? 0 : (l<0 ? -1 : 1)
              /* in xemacs's font that looks like
                 one's instead of ell's.
              */
              ;
      }
#endif
      default:
          return COMPARE_TYPE_IDS(lhs,rhs);
    };
}

int cwal_value_cmp_double( cwal_value const * lhs, cwal_value const * rhs ){
    assert(CWAL_TYPE_DOUBLE==lhs->vtab->typeID);
    switch( rhs->vtab->typeID ){
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:{
          cwal_double_t const l = *CWAL_DBL(lhs);
          cwal_double_t const r = cwal_value_get_double(rhs);
          return l==r
              ? 0 : (l<r ? -1 : 1);
      }
      case CWAL_TYPE_STRING:
          return -(rhs->vtab->compare(rhs, lhs));
#if 0
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:{
          cwal_double_t const l = *CWAL_DBL(lhs);
          return (l==0.0) ? 0 : (l<0.0 ? -1 : 1);
      }
#endif
      default:
          return COMPARE_TYPE_IDS(lhs,rhs);
    };
}
    
int cwal_buffer_fill_from( cwal_engine * e, cwal_buffer * dest, cwal_input_f src, void * state )
{
    int rc;
    enum { BufSize = 512 * 8 };
    char rbuf[BufSize];
    cwal_size_t total = 0;
    cwal_size_t rlen = 0;
    if( ! dest || ! src ) return CWAL_RC_MISUSE;
    dest->used = 0;
    while(1)
    {
        rlen = BufSize;
        rc = src( state, rbuf, &rlen );
        if( rc ) break;
        total += rlen;
        if(total<rlen){
            /* Overflow! */
            rc = CWAL_RC_RANGE;
            break;
        }
        if( dest->capacity < (total+1) )
        {
            rc = cwal_buffer_reserve( e, dest,
                                      total + ((rlen<BufSize) ? 1 : BufSize)
                                      /* Probably unnecessarily clever :/ */
                                      );
            if( 0 != rc ) break;
        }
        memcpy( dest->mem + dest->used, rbuf, rlen );
        dest->used += rlen;
        if( rlen < BufSize ) break;
    }
    if( !rc && dest->used )
    {
        assert( dest->used < dest->capacity );
        dest->mem[dest->used] = 0;
    }
    return rc;
}

int cwal_input_FILE( void * state, void * dest, cwal_size_t * n ){
    FILE * f = (FILE*) state;
    if( ! state || ! n || !dest ) return CWAL_RC_MISUSE;
    else if( !*n ) return CWAL_RC_RANGE;
    *n = (cwal_size_t)fread( dest, 1, *n, f );
    return !*n
        ? (feof(f) ? 0 : CWAL_RC_IO)
        : 0;
}

int cwal_buffer_fill_from_FILE( cwal_engine * e, cwal_buffer * dest, FILE * src ){
    if(!e || !dest || !src) return CWAL_RC_MISUSE;
    else{
        long pos = ftell(src);
        int rc = (pos>=0) ? fseek(src, 0, SEEK_END) : -1;
        long epos = rc ? 0 : ftell(src);
        /* Ignore any tell/fseek-related errors */
        if(!rc){
            rc = fseek(src, pos, SEEK_SET);
            if(!rc){
                assert(epos>=pos);
                if(epos>pos){
                    rc = cwal_buffer_reserve(e, dest,
                                             dest->used +
                                             (cwal_size_t)(epos-pos) + 1);
                    if(rc) return rc;
                }
            }
        }
        return cwal_buffer_fill_from( e, dest, cwal_input_FILE, src );
    }
}


int cwal_buffer_fill_from_filename( cwal_engine * e, cwal_buffer * dest, char const * filename ){
    if(!e || !dest || !filename || !*filename) return CWAL_RC_MISUSE;
    else{
        int rc;
        FILE * src = (('-'==*filename)&&!*(filename+1)) ? stdin : fopen(filename,"rb");
        if(!src) return CWAL_RC_IO;
        rc = (stdin==src)
                ? cwal_buffer_fill_from( e, dest, cwal_input_FILE, src )
                : cwal_buffer_fill_from_FILE( e, dest, src) /* to get pre-allocating behaviour */
                ;
        if(stdin!=src) fclose(src);
        return rc;
    }
}


int cwal_buffer_clear( cwal_engine * e, cwal_buffer * b ){
    return cwal_buffer_reserve(e, b, 0);
}

int cwal_buffer_reserve( cwal_engine * e, cwal_buffer * buf, cwal_size_t n ){
    if( !e || !buf ) return CWAL_RC_MISUSE;
    else if( 0 == n ){
        cwal_free(e, buf->mem);
        *buf = cwal_buffer_empty;
        return 0;
    }
    else if( buf->capacity >= n ){
        return 0;
    }
    else{
        unsigned char * x = (unsigned char *)cwal_realloc( e, buf->mem, n );
        if( ! x ) return CWAL_RC_OOM;
        assert(e->metrics.bytes[CWAL_TYPE_BUFFER] >= buf->capacity);
        e->metrics.bytes[CWAL_TYPE_BUFFER] -= buf->capacity;
        e->metrics.bytes[CWAL_TYPE_BUFFER] += n;
        memset( x + buf->used, 0, n - buf->used );
        buf->mem = x;
        buf->capacity = n;
        return 0;
    }
}

cwal_size_t cwal_buffer_fill( cwal_buffer * buf, unsigned char c ){
    if( !buf || !buf->capacity || !buf->mem ) return 0;
    else{
        memset( buf->mem, c, buf->capacity );
        return buf->capacity;
    }
}

void cwal_dump_allocation_metrics( cwal_engine * e ){
    int i;
    cwal_size_t totalRequests = 0;
    cwal_size_t totalAlloced = 0;
    cwal_size_t totalSizeof = 0;
    cwal_size_t grandTotal = 0;
    struct {
        char const * note;
        int size;
    } sizes[] = {
    {/*CWAL_TYPE_UNDEF*/ 0, 0},
    {/*CWAL_TYPE_NULL*/ 0, 0},
    {/*CWAL_TYPE_BOOL*/ 0, 0},
    {/*CWAL_TYPE_INTEGER*/ 0, sizeof(cwal_value)+sizeof(cwal_int_t)},
    {/*CWAL_TYPE_DOUBLE*/ 0, sizeof(cwal_value)+sizeof(cwal_double_t)},
    {/*CWAL_TYPE_STRING*/ "Incl. string bytes", sizeof(cwal_value)+sizeof(cwal_string)},
    {/*CWAL_TYPE_ARRAY*/ "Incl. peak list memory", sizeof(cwal_value)+sizeof(cwal_array)},
    {/*CWAL_TYPE_OBJECT*/ 0, sizeof(cwal_value)+sizeof(cwal_object)},
    {/*CWAL_TYPE_FUNCTION*/ 0, sizeof(cwal_value)+sizeof(cwal_function)},
    {/*CWAL_TYPE_EXCEPTION*/ 0, sizeof(cwal_value)+sizeof(cwal_exception)},
    {/*CWAL_TYPE_NATIVE*/ 0, sizeof(cwal_value)+sizeof(cwal_native)},
    {/*CWAL_TYPE_BUFFER*/ "[1] Incl. peak buffered memory and non-Value buffers", sizeof(cwal_value)+sizeof(cwal_buffer)},
    {/*CWAL_TYPE_HASH*/ "Incl. table memory", sizeof(cwal_value)+sizeof(cwal_hash)},
    {/*CWAL_TYPE_SCOPE*/ "[2]", sizeof(cwal_scope)},
    {/*CWAL_TYPE_KVP*/ 0, sizeof(cwal_kvp)},
    {/*CWAL_TYPE_WEAK_REF*/ 0, sizeof(cwal_weak_ref)},
    {/*CWAL_TYPE_XSTRING*/ "Not incl. external string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)},
    {/*CWAL_TYPE_ZSTRING*/ "Incl. string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)},
    {0,0}
    };
    assert(e);

        
    cwal_outputf(e, "%-20s %-18s "
                 "Actually allocated count * Value-type sizeof() "
                 "==> total bytes from allocator\n\n",
                 "ValueTypeId/Name",
                 "CreationRequests"
                 );

    for( i = CWAL_TYPE_UNDEF; i < CWAL_TYPE_end; ++i ){
        if(!e->metrics.requested[i]) continue;

        switch(i){
          case CWAL_TYPE_SCOPE:
              /* The stack-allocatedness of these skews the metrics.
                 Don't count them for grand total statistics. */
              totalRequests += e->metrics.allocated[i];
              break;
          default:
              totalRequests += e->metrics.requested[i];
              break;
        }
        totalAlloced += e->metrics.allocated[i];
        totalSizeof += sizes[i].size * e->metrics.allocated[i];
        grandTotal += e->metrics.bytes[i];
        cwal_outputf(e, "%-3d%-17s "
                     "%-19"CWAL_SIZE_T_PFMT
                     "%-7"CWAL_SIZE_T_PFMT
                     "(%06.2f%%) " /* reminder to self: the 06 applies to the _whole_ number! */
                     " * %d "
                     "==> %-8"CWAL_SIZE_T_PFMT" %s"
                     "\n",
                     i, cwal_value_type_id_name((cwal_type_id)i),
                     (cwal_size_t)e->metrics.requested[i],
                     (cwal_size_t)e->metrics.allocated[i],
                     (double)e->metrics.allocated[i]/e->metrics.requested[i]*100.0,
                     sizes[i].size,
                     (cwal_size_t)e->metrics.bytes[i],
                     sizes[i].note ? sizes[i].note : ""
                     );
    }

    if(totalRequests){
        cwal_outputf(e,
                     "\nTotals:              "
                     "%-19"CWAL_SIZE_T_PFMT
                     "%-7"CWAL_SIZE_T_PFMT
                     "(%06.2f%%)"
                     "[1]    ==> %-8"CWAL_SIZE_T_PFMT
                     "\n",
                     (cwal_size_t)totalRequests,
                     (cwal_size_t)totalAlloced,
                     (double)totalAlloced/totalRequests*100.0,
                     (cwal_size_t)grandTotal);
        cwal_outputf(e,"\n[1] = "
                     "%% applies to allocation counts, not sizes. "
                     "The total count does not account for "
                     "buffer memory (re)allocations, but the total size "
                     "does.\n");
        cwal_outputf(e, "\n[2] = Scopes are almost always stack allocated "
                     "and skew the statistics, so only allocated scopes "
                     "are counted for totals purposes.\n");
    }


    { /* cwal_ptr_table memory... */
        int x;
        for(x = 0; x < 2; ++x ){ /* 0==string interning table, 1==weak ref table */
            char const * label = 0;
            cwal_ptr_table const * ptbl = 0;
            switch(x){
              case 0: label = "String interning tables:"; ptbl = &e->interned; break;
              case 1: label = "Weak ref tables:"; ptbl = &e->weakp; break;
            }
            assert(label && ptbl);
            if(ptbl->pg.head){
                unsigned i = 0;
                cwal_ptr_page const * h = ptbl->pg.head;
                unsigned const pgSize = sizeof(cwal_ptr_page) +
                    (ptbl->hashSize * sizeof(void*));
                for( ; h ; h = h->next, ++i );
                grandTotal += i * pgSize;
                cwal_outputf(e, "\n%-25s %u page(s) of size %u (%u bytes) ==> %u bytes\n",
                             label,
                             i, (unsigned)ptbl->hashSize, pgSize,
                             i * pgSize);
            }
        }
    }

    if(e->buffer.capacity){
        cwal_outputf(e,"\nGeneral-purpose buffer capacity: %u bytes\n", (unsigned)e->buffer.capacity);
        grandTotal += e->buffer.capacity;
    }
    if(e->sortBuf.capacity){
        cwal_outputf(e,"\nProperty-sorting buffer capacity: %u bytes\n", (unsigned)e->sortBuf.capacity);
        grandTotal += e->sortBuf.capacity;
    }

    cwal_outputf(e,"\ncwal_engine instance ");
    if(e->allocStamp){
        unsigned int const sz = (unsigned)sizeof(cwal_engine);
        cwal_outputf(e,"is malloced: sizeof=%u\n", sz);
        grandTotal += sz;
    }else{
        cwal_outputf(e,"appears to have been stack-allocated (not by cwal_engine_init()).\n");
    }
    if(grandTotal){
        cwal_outputf(e, "\nTotal bytes allocated for metrics-tracked resources: %"CWAL_SIZE_T_PFMT"\n",
                     (cwal_size_t)grandTotal);
    }

}

void cwal_dump_interned_strings_table( cwal_engine * e,
                                       char showEntries,
                                       cwal_size_t includeStrings ){
    enum { BufSize = 1024 };
    char buf[BufSize] = {0,};
    cwal_ptr_table * t = &e->interned;
    uint16_t i, x;
    int rc;
    cwal_output_f f = e->vtab->outputer.output;
    void * outState = e->vtab->outputer.state.data;
    uint32_t total = 0;
    unsigned int pageSize = sizeof(cwal_ptr_page)+(t->hashSize*sizeof(void*));
    unsigned int pageCount = 0;
    cwal_ptr_page const * p;
    assert( NULL != f );
    assert(0==buf[5]);
    assert(e && f);
    for( p = t->pg.head; p; p = p->next ) ++pageCount;
        
    rc = sprintf( buf, "Interned value table for engine@%p: ", (void*)e);
#define RC (cwal_size_t)rc
    f( outState, buf, RC );
    rc = sprintf( buf, " Page size=%"PRIu16" (%u bytes) "
                  "count=%u (%u bytes)\n",
                  t->hashSize, (unsigned)pageSize,
                  (unsigned)pageCount,
                  (unsigned)(pageSize * pageCount));
    f( outState, buf, RC );
    for( i = 0, p = t->pg.head; p; p = p->next, ++i ){
        uint16_t seen = 0;
        rc = sprintf( buf, "  Page #%"PRIu16": entry count=%"PRIu16"\n",
                      i+1, p->entryCount );
        f( outState, buf, RC );
        if(!showEntries){
            total += p->entryCount;
            continue;
        }
        for( x = 0;
             (x < t->hashSize); /*&& (seen<p->entryCount);*/
             ++x ){
            cwal_value * v = (cwal_value *)p->list[x];
            if(!v) continue;
            ++seen;
            rc = sprintf( buf, "    #%"PRIu16":\t %s"
                          "@%p [scope=%d] "
                          "refcount=%"CWAL_SIZE_T_PFMT,
                          x, v->vtab->typeName,
                          (void const *)v, (int)v->scope->level,
                          (cwal_size_t)v->refcount);
            f( outState, buf, RC );
            if(includeStrings){
                switch(v->vtab->typeID){
                  case CWAL_TYPE_STRING:{
                      cwal_string const * s = cwal_value_get_string(v);
                      rc = sprintf(buf, " len=%"CWAL_SIZE_T_PFMT" bytes=[", CWAL_STRLEN(s));
                      f( outState, buf, RC );
                      if(includeStrings >= CWAL_STRLEN(s)){
                          f(outState, cwal_string_cstr(s), CWAL_STRLEN(s));
                      }else{
                          f(outState, cwal_string_cstr(s), includeStrings);
                          f(outState, ">>>", 3);
                      }
                      f( outState, "]", 1 );
                      break;
                  }
                  case CWAL_TYPE_INTEGER:
                      rc = sprintf(buf, " value=%"CWAL_INT_T_PFMT,
                                   cwal_value_get_integer(v));
                      f( outState, buf, RC );
                      break;
                  case CWAL_TYPE_DOUBLE:
                      rc = sprintf(buf, " value=%"CWAL_DOUBLE_T_PFMT,
                                   cwal_value_get_double(v));
                      f( outState, buf, RC );
                      break;
                  default:
                      break;
                      
                }
            }
            f( outState, "\n", 1 );
        }
        assert( (seen == p->entryCount) && "Mis-management of p->entryCount detected." );
        if(seen){
            total += seen;
            rc = sprintf( buf, "  End Page #%"PRIu16" (%"PRIu16" entry(ies))\n",
                          i+1, p->entryCount );
            f( outState, buf, RC );
        }
    }
    rc = sprintf(buf, "  Total entry count=%"PRIu32"\n", total);
    f(outState, buf, RC );
#undef RC

}

void cwal_engine_tracer_close_FILE( void * filePtr ){
    FILE * f = (FILE*)filePtr;
    if(f && (f!=stdout) && (f!=stderr) && (f!=stdin)){
        fclose(f);
    }
}

#if CWAL_ENABLE_TRACE
static char const * cwal_tr_cstr( cwal_trace_flags ev ){
    switch(ev){
#define CASE(X) case CWAL_TRACE_##X: return #X
        CASE(NONE);
        CASE(ALL);

        CASE(GROUP_MASK);
        
        CASE(MEM_MASK);
        CASE(MEM_MALLOC);
        CASE(MEM_REALLOC);
        CASE(MEM_FREE);
        CASE(MEM_TO_RECYCLER);
        CASE(MEM_FROM_RECYCLER);
        CASE(MEM_TO_GC_QUEUE);

        CASE(VALUE_MASK);
        CASE(VALUE_CREATED);
        CASE(VALUE_SCOPED);
        CASE(VALUE_UNSCOPED);
        CASE(VALUE_CLEAN_START);
        CASE(VALUE_CLEAN_END);
        CASE(VALUE_CYCLE);
        CASE(VALUE_INTERNED);
        CASE(VALUE_UNINTERNED);
        CASE(VALUE_VISIT_START);
        CASE(VALUE_VISIT_END);
        CASE(VALUE_REFCOUNT);

        CASE(SCOPE_MASK);
        CASE(SCOPE_PUSHED);
        CASE(SCOPE_CLEAN_START);
        CASE(SCOPE_CLEAN_END);
        CASE(SCOPE_SWEEP_START);
        CASE(SCOPE_SWEEP_END);

        CASE(ENGINE_MASK);
        CASE(ENGINE_STARTUP);
        CASE(ENGINE_SHUTDOWN_START);
        CASE(ENGINE_SHUTDOWN_END);

        CASE(FYI_MASK);
        CASE(MESSAGE);
        CASE(ERROR_MASK);
        CASE(ERROR);

    };
    assert(!"MISSING cwal_tr_cstr() ENTRY!");
    return NULL;
#undef CASE
}
#endif
/*CWAL_ENABLE_TRACE*/

void cwal_engine_tracer_f_FILE( void * filePtr,
                                cwal_trace_state const * ev ){
#if CWAL_ENABLE_TRACE
    FILE * f = (FILE*)filePtr;
    if(!f) f = stdout;
    fprintf(f, "%s\tengine@%p scope#%"CWAL_SIZE_T_PFMT"@%p",
            cwal_tr_cstr(ev->event),
            (void const *)ev->e,
            ev->scope ? ev->scope->level : 0,
            (void const *)ev->scope );
    fprintf(f, "\t%s():%d",
            ev->cFunc, ev->cLine );

    if(ev->msg && *ev->msg) fprintf( f, "\n\t%s", ev->msg );
    fputc( '\n', f );

    if(ev->value){
        cwal_obase const * b = CWAL_VOBASE(ev->value);
        cwal_value const * v = ev->value;
        
        fprintf(f, "\t%s@%p", v->vtab->typeName, (void*)v);
        if(v->scope){
            fprintf(f, "(->scope#%"CWAL_SIZE_T_PFMT"@%p)",
                    v->scope->level, (void*)v->scope);
        }
        fprintf(f, " refCount=%"CWAL_SIZE_T_PFMT, v->refcount);
        if(b){
            fprintf( f, " flags=%02x", b->flags );
#if 0
            fprintf( f, " childCount=%"CWAL_SIZE_T_PFMT,
                     b->list.count );
#endif
        }
        if( cwal_value_is_string(v) ){
            const cwal_size_t truncLen = 16;
            cwal_string const * s = cwal_value_get_string(v);
            if(s && CWAL_STRLEN(s)){
                fprintf( f, " strlen=%"CWAL_SIZE_T_PFMT, CWAL_STRLEN(s) );
                if( CWAL_STRLEN(s) <= truncLen ){
                    fprintf( f, " bytes=[%s]", cwal_string_cstr(s) );
                }else{
                    fprintf( f, " bytes=[%.*s...] (truncated)", (int)truncLen, cwal_string_cstr(s) );
                    /*fprintf( f, " bytes=[%s]", cwal_string_cstr(s) );*/
                }
            }else{
                fprintf( f, " (STILL INITIALIZING!)" );
            }
        }else if(cwal_value_is_integer(v)){
            fprintf( f, " value=[%"CWAL_INT_T_PFMT"]",cwal_value_get_integer(v));
        }else if(cwal_value_is_double(v)){
            fprintf( f, " value=[%"CWAL_DOUBLE_T_PFMT"]", cwal_value_get_double(v));
        }else if(cwal_value_is_bool(v)){
            fprintf( f, " value=[%s]", cwal_value_get_bool(v) ? "true" : "false" );
        }
        fputc( '\n', f );
    }
    if(ev->memory||ev->memorySize){
        fputc('\t',f);
        if( ev->memory) fprintf(f, "memory=%p ", ev->memory);
        if( ev->memorySize) fprintf(f, "(%"CWAL_SIZE_T_PFMT" bytes)",
                                    ev->memorySize);
        fputc('\n',f);
    }

    fflush(f);
#endif
}

uint32_t cwal_engine_feature_flags( cwal_engine * e, int32_t mask ){
    if(!e) return -1;
    else{
        uint32_t const rc = e->flags & CWAL_FEATURE_MASK;
        if(mask>0){
            e->flags &= ~CWAL_FEATURE_MASK;
            e->flags |= CWAL_FEATURE_MASK & mask;
        }
        /** If auto-interning was disabled, free up the
            table memory.
        */
        if((CWAL_FEATURE_INTERN_STRINGS & rc)
           && !(CWAL_FEATURE_INTERN_STRINGS & e->flags)){
            cwal_ptr_table_destroy( e, &e->interned );
        }
        return rc;
    }
}

int32_t cwal_engine_trace_flags( cwal_engine * e, int32_t mask ){
#if !CWAL_ENABLE_TRACE
    return -1;
#else
    if(!e) return -1;
    else if(-1==mask) return e->trace.mask;
    else {
        int32_t const rc = e->trace.mask;
        e->trace.mask = mask;
        return rc;
    }
#endif
}
void cwal_trace( cwal_engine * e ){
#if CWAL_ENABLE_TRACE
    int32_t const m = e->trace.mask;
    /*MARKER("TRACE()? ev=%08x mask=%08x\n", e->trace.event, e->trace.mask);*/
    
    if(!
       ((m & (CWAL_TRACE_GROUP_MASK&e->trace.event))
        /*&& (e->trace.mask & (~CWAL_TRACE_GROUP_MASK&e->trace.event))*/
        )){
        goto end;
    }
    else if(!e->vtab->tracer.trace) goto end;
    else {
        if( e->trace.msg && !e->trace.msgLen && *e->trace.msg ){
            e->trace.msgLen = cwal_strlen(e->trace.msg);
        }
        e->vtab->tracer.trace( e->vtab->tracer.state, &e->trace );
        /* fall through */
    }
    end:
    e->trace = cwal_trace_state_empty;
    e->trace.e = e;
    e->trace.mask = m;
    assert(0==e->trace.msg);
    assert(0==e->trace.msgLen);
#endif
}



char const * cwal_rc_cstr(int rc)
{
    if(0 == rc) return "OK";
    switch(rc){
#define C(N) case N: return #N
        C(CWAL_RC_OK);
        C(CWAL_RC_ERROR);
        C(CWAL_RC_OOM);
        C(CWAL_RC_FATAL);

        C(CWAL_RC_CONTINUE);
        C(CWAL_RC_BREAK);
        C(CWAL_RC_RETURN);
        C(CWAL_RC_EXIT);
        C(CWAL_RC_EXCEPTION);
        C(CWAL_RC_ASSERT);

        C(CWAL_RC_MISUSE);
        C(CWAL_RC_NOT_FOUND);
        C(CWAL_RC_ALREADY_EXISTS);
        C(CWAL_RC_RANGE);
        C(CWAL_RC_TYPE);
        C(CWAL_RC_UNSUPPORTED);
        C(CWAL_RC_ACCESS);
        
        C(CWAL_RC_CYCLES_DETECTED);
        C(CWAL_RC_DESTRUCTION_RUNNING);
        C(CWAL_RC_FINALIZED);
        C(CWAL_RC_HAS_REFERENCES);
        C(CWAL_RC_INTERRUPTED);
        C(CWAL_RC_CANCELLED);
        C(CWAL_RC_IO);
        C(CWAL_RC_CANNOT_HAPPEN);

        C(CWAL_RC_JSON_INVALID_CHAR);
        C(CWAL_RC_JSON_INVALID_KEYWORD);
        C(CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE);
        C(CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE);
        C(CWAL_RC_JSON_INVALID_NUMBER);
        C(CWAL_RC_JSON_NESTING_DEPTH_REACHED);
        C(CWAL_RC_JSON_UNBALANCED_COLLECTION);
        C(CWAL_RC_JSON_EXPECTED_KEY);
        C(CWAL_RC_JSON_EXPECTED_COLON);
        
        C(CWAL_SCR_CANNOT_CONSUME);
        C(CWAL_SCR_INVALID_OP);
        C(CWAL_SCR_UNKNOWN_IDENTIFIER);
        C(CWAL_SCR_CALL_OF_NON_FUNCTION);
        C(CWAL_SCR_MISMATCHED_BRACE);
        C(CWAL_SCR_MISSING_SEPARATOR);
        C(CWAL_SCR_UNEXPECTED_TOKEN);
        C(CWAL_SCR_UNEXPECTED_EOF);
        C(CWAL_SCR_DIV_BY_ZERO);
        C(CWAL_SCR_SYNTAX);
        C(CWAL_SCR_EOF);
        C(CWAL_SCR_TOO_MANY_ARGUMENTS);
        C(CWAL_SCR_EXPECTING_IDENTIFIER);
      default: return "Unknown result code";
    }
#undef C
}

int cwal_prototype_base_set( cwal_engine * e, cwal_type_id t, cwal_value * proto ){
    if(!e || !e->prototypes) return CWAL_RC_MISUSE;
    else{
        assert((t>=0) && (t<CWAL_TYPE_end));
        switch(t){
          case CWAL_TYPE_XSTRING:
          case CWAL_TYPE_ZSTRING:
              t = CWAL_TYPE_STRING;
          default:
              break;
        }
        return cwal_array_set(e->prototypes, (cwal_size_t)t, proto );
    }
}

cwal_value * cwal_prototype_base_get( cwal_engine * e, cwal_type_id t ){
    if(!e || !e->prototypes) return NULL;
    else{
        assert((t>=0) && (t<CWAL_TYPE_end));
        switch(t){
          case CWAL_TYPE_XSTRING:
          case CWAL_TYPE_ZSTRING:
              t = CWAL_TYPE_STRING;
          default:
              break;
        }
        return cwal_array_get(e->prototypes, (cwal_size_t)t);
    }
}


int cwal_value_prototype_set( cwal_value * v, cwal_value * prototype ){
    cwal_obase * child = CWAL_VOBASE(v);
    cwal_obase * chPro = child ? CWAL_VOBASE(prototype) : NULL;
    cwal_obase * pBase;
    cwal_value * pCheck;
    if((prototype && !chPro) || !child || (child == chPro)){
        return CWAL_RC_MISUSE;
    }
#if 1
    if( prototype == child->prototype ){
        return 0;
    }
#endif
    /* Ensure that v is nowhere in prototype's chain. */
    for( pCheck = prototype; pCheck ;
         pBase = CWAL_VOBASE(pCheck),
             pCheck = (pBase?pBase->prototype:NULL)){
        if(pCheck == v) return CWAL_RC_CYCLES_DETECTED;
    }
    if(prototype){
        cwal_value_ref2( v->scope->e, prototype );
        cwal_value_rescope( v->scope, prototype );
    }
    if(child->prototype){
        cwal_unref_from( v->scope/* ??? */, child->prototype );
    }
    child->prototype = prototype;
    return 0;
}

cwal_value * cwal_value_prototype_get(cwal_engine * e, cwal_value const * v){
    if(!v) return NULL;
    else{
        cwal_obase const * b = CWAL_VOBASE(v);
        if(b) return b->prototype;
        else{
            if(!e && v->scope) e = v->scope->e;
            return cwal_prototype_base_get(e, v->vtab->typeID);
        }
    }
}

char cwal_value_derives_from( cwal_engine * e,
                              cwal_value const * v,
                              cwal_value const * p ){
    if(!e || !v || !p) return 0;
    else if(v==p) return 1;
    else {
        cwal_type_id const tid = v->vtab->typeID;
        while(v){
            cwal_obase const * base = CWAL_VOBASE(v);
            cwal_value const * theP =
                base ? base->prototype : cwal_prototype_base_get(e, tid);
            if(p==theP) return 1;
            else v = theP;
        }
        return 0;
    }
}


cwal_buffer * cwal_value_buffer_part( cwal_engine * e,
                                      cwal_value * v ){
    do{
        cwal_buffer * f = cwal_value_get_buffer(v);
        if(f) return f;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_native * cwal_value_native_part( cwal_engine * e,
                                        cwal_value * v ){
    do{
        cwal_native * n = cwal_value_get_native(v);
        if(n) return n;
        v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_function * cwal_value_function_part( cwal_engine * e,
                                            cwal_value * v ){
    do{
        cwal_function * f = cwal_value_get_function(v);
        if(f) return f;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_array * cwal_value_array_part( cwal_engine * e,
                                      cwal_value * v ){
    do{
        if(cwal_value_is_array(v)) return cwal_value_get_array(v);
        v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_value * cwal_value_exception_part( cwal_engine * e,
                                          cwal_value * v ){
    do{
        cwal_exception * f = cwal_value_get_exception(v);
        if(f) return v;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_hash * cwal_value_hash_part( cwal_engine * e,
                                    cwal_value * v ){
    do{
        cwal_hash * f = cwal_value_get_hash(v);
        if(f) return f;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_string * cwal_value_string_part( cwal_engine * e,
                                      cwal_value * v ){
    do{
        cwal_string * f = cwal_value_get_string(v);
        if(f) return f;
        else v = cwal_value_prototype_get(e, v);
    }while(v);
    return NULL;
}

cwal_value * cwal_container_part( cwal_engine * e, cwal_value * v ){
    while(v){
        if(cwal_props_can(v)) return v;
        v = cwal_value_prototype_get(e, v);
    }
    return 0;
}

cwal_size_t cwal_list_reserve( cwal_engine * e, cwal_list * self, cwal_size_t n )
{
    if( !self ) return 0;
    else if(0 == n)
    {
        if(0 == self->alloced) return 0;
        cwal_free(e, self->list);
        self->list = NULL;
        self->alloced = self->count = 0;
        return 0;
    }
    else if( self->alloced >= n )
    {
        return self->alloced;
    }
    else
    {
        size_t const sz = sizeof(void*) * n;
        void* * m = (void**)cwal_realloc( e, self->list, sz );
        if( ! m ) return self->alloced;

        memset( m + self->alloced, 0, (sizeof(void*)*(n-self->alloced)));
        self->alloced = n;
        self->list = m;
        return n;
    }
}

int cwal_list_append( cwal_engine * e, cwal_list * self, void* cp )
{
    if( !e || !self || !cp ) return CWAL_RC_MISUSE;
    else if( self->alloced > cwal_list_reserve(e, self, self->count+1) )
    {
        return CWAL_RC_OOM;
    }
    else
    {
        self->list[self->count++] = cp;

        if(self->count< self->alloced-1) self->list[self->count]=0;

        return 0;
    }
}

int cwal_list_visit( cwal_list * self, char order,
                        cwal_list_visitor_f visitor, void * visitorState )
{
    int rc = CWAL_RC_OK;
    if( self && self->count && visitor )
    {
        cwal_size_t i = 0;
        cwal_size_t pos = (order<0) ? self->count-1 : 0;
        int step = (order<0) ? -1 : 1;
        for( rc = 0; (i < self->count) && (0 == rc); ++i, pos+=step )
        {

            void* obj = self->list[pos];



            if(obj) rc = visitor( obj, visitorState );


            if( obj != self->list[pos] ){
                --i;
                if(order>=0) pos -= step;
            }


        }
    }
    return rc;
}


int cwal_list_visit_p( cwal_list * self, char order,
                          char shiftIfNulled,
                          cwal_list_visitor_f visitor, void * visitorState )
{
    int rc = CWAL_RC_OK;
    if( self && self->count && visitor )
    {
        int i = 0;
        int pos = (order<0) ? self->count-1 : 0;
        int step = (order<0) ? -1 : 1;
        for( rc = 0; (i < (int)self->count) && (0 == rc); ++i, pos+=step )
        {
            void* obj = self->list[pos];
            if(obj) {
                assert((order<0) && "TEST THAT THIS WORKS WITH IN-ORDER!");
                rc = visitor( &self->list[pos], visitorState );
                if( shiftIfNulled && !self->list[pos]){
                    int x = pos;
                    int const to = self->count-pos;

                    assert( to < (int) self->alloced );
                    for( ; x < to; ++x ) self->list[x] = self->list[x+1];
                    if( x < (int)self->alloced ) self->list[x] = 0;
                    --i;
                    --self->count;
                    if(order>=0) pos -= step;
                }
            }
        }
    }
    return rc;
}


cwal_value * cwal_value_from_arg(cwal_engine * e,
                                 char const *arg){
    char * end = NULL;
    cwal_size_t sLen;
    if(!e) return NULL;
    else if(!arg) return cwal_value_null();
    else if(('0'>*arg) || ('9'<*arg)){
        goto do_string;
    }
    else{ /* try numbers... */
        /* TODO: use cwal_cstr_to_int/double() here. */
        long const val = strtol(arg, &end, 10);
        if(!*end){
            return cwal_new_integer( e, (cwal_int_t)val);
        }
        else if( '.' != *end ) {
            goto do_string;
        }
        else {
            double const val = strtod(arg, &end);
            if(!*end){
                return cwal_new_double(e, val);
            }
        }
    }
    do_string:
    sLen = cwal_strlen(arg);
    if(4==sLen){
        if(0==strcmp("true",arg)) return cwal_value_true();
        if(0==strcmp("null",arg)) return cwal_value_null();
    }
    else if(5==sLen && 0==strcmp("false",arg)) return cwal_value_false();

    return cwal_new_string_value(e, arg, sLen);
}


int cwal_parse_argv_flags( cwal_engine * e,
                           int argc, char const * const * argv,
                           cwal_value ** tgt ){
    cwal_value * o = NULL;
    cwal_value * flags = NULL;
    cwal_array * nonFlags = NULL;
    int rc = 0;
    int i = 0;
    if(!e || !tgt) return CWAL_RC_MISUSE;
    else if(*tgt && !cwal_props_can(*tgt)) return CWAL_RC_MISUSE;

    flags = cwal_new_object_value(e);
    if(!flags) return CWAL_RC_OOM;
    nonFlags = cwal_new_array(e);
    if(!nonFlags) {
        cwal_value_unref(flags);
        return CWAL_RC_OOM;
    }
   
    o = *tgt ? *tgt : cwal_new_object_value(e);
    if(!o){
        cwal_value_unref(flags);
        cwal_value_unref(cwal_array_value(nonFlags));
        return CWAL_RC_OOM;
    }
    rc = cwal_prop_set(o, "flags", 5, flags);
    if(rc){
        cwal_value_unref(flags);
        goto end;
    }
    rc = cwal_prop_set(o, "nonFlags", 8, cwal_array_value(nonFlags));
    if(rc){
        cwal_value_unref(cwal_array_value(nonFlags));
        goto end;
    }
    for( i = 0; i < argc; ++i ){
        char const * arg = argv[i];
        char const * key = arg;
        char const * pos;
        cwal_value * k = NULL;
        cwal_value * v = NULL;
        if('-' != *arg){
            v = cwal_new_string_value(e, arg, cwal_strlen(arg));
            if(!v){
                rc = CWAL_RC_OOM;
                break;
            }
            rc = cwal_array_append(nonFlags, v);
            if(rc){
                cwal_value_unref(v);
                break;
            }
            continue;
        }
        while('-'==*key) ++key;
        if(!*key) continue;
        pos = key;
        while( *pos && ('=' != *pos)) ++pos;
        k = cwal_new_string_value(e, key, pos-key);
        if(!k){
            rc = CWAL_RC_OOM;
            break;
        }
        if(!*pos){ /** --key */
            v = cwal_value_true();
        }else{ /** --key=...*/
            assert('=' == *pos);
            ++pos /*skip '='*/;
            v = cwal_value_from_arg(e, pos);
        }
        if(0 != (rc=cwal_prop_set_v(flags, k, v))){
            cwal_value_unref(k);
            cwal_value_unref(v);
            break;
        }
    }
    end:
    if(o != *tgt){
        if(rc) cwal_value_unref(o);
        else *tgt = o;
    }
    return rc;
}

char cwal_strtok( char const ** inp, char separator,
                    char const ** end ){
    char const * pos = NULL;
    assert( inp && end && *inp );
    if( ! inp || !end ) return 0;
    else 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;
}

int cwal_prop_fetch_sub( cwal_value * obj, cwal_value ** tgt, char const * path, char sep )
{
    if( ! obj || !path ) return CWAL_RC_MISUSE;
    else if( !*path || !sep ) return CWAL_RC_RANGE;
    else{
        char const * beg = path;
        char const * end = NULL;
        int rc;
        unsigned int i, len;
        unsigned int tokenCount = 0;
        cwal_value * cv = NULL;
        cwal_value * curObj = obj;
        enum { BufSize = 128 };
        char buf[BufSize];
        memset( buf, 0, BufSize );

        while( cwal_strtok( &beg, sep, &end ) ){
            if( beg == end ) break;
            else{
                ++tokenCount;
                beg = end;
                end = NULL;
            }
        }
        if( 0 == tokenCount ) return CWAL_RC_RANGE;
        beg = path;
        end = NULL;
        for( i = 0; i < tokenCount; ++i, beg=end, end=NULL ){
            rc = cwal_strtok( &beg, sep, &end );
            assert( 1 == rc );
            assert( beg != end );
            assert( end > beg );
            len = end - beg;
            if( len > (BufSize-1) ) return CWAL_RC_RANGE;
            /* memset( buf, 0, len + 1 ); */
            memcpy( buf, beg, len );
            buf[len] = 0;
            cv = cwal_prop_get( curObj, buf, len );
            if( NULL == cv ) return CWAL_RC_NOT_FOUND;
            else if( i == (tokenCount-1) ){
                if(tgt) *tgt = cv;
                return 0;
            }
            else if( cwal_props_can(cv) ){
                curObj = cv;
            }
            /* TODO: arrays. Requires numeric parsing for the index. */
            else{
                return CWAL_RC_NOT_FOUND;
            }
        }
        assert( i == tokenCount );
        return CWAL_RC_NOT_FOUND;
    }
}

int cwal_prop_fetch_sub2( cwal_value * obj, cwal_value ** tgt, char const * path )
{
    if( ! obj || !path ) return CWAL_RC_MISUSE;
    else if( !*path || !*(1+path) ) return CWAL_RC_RANGE;
    else return cwal_prop_fetch_sub(obj, tgt, path+1, *path);
}


cwal_value * cwal_prop_get_sub( cwal_value * obj, char const * path, char sep )
{
    cwal_value * v = NULL;
    cwal_prop_fetch_sub( obj, &v, path, sep );
    return v;
}

cwal_value * cwal_prop_get_sub2( cwal_value * obj, char const * path )
{
    cwal_value * v = NULL;
    cwal_prop_fetch_sub2( obj, &v, path );
    return v;
}

int cwal_callback_hook_set(cwal_engine * e, cwal_callback_hook const * h ){
    if(!e) return CWAL_RC_MISUSE;
    e->cbHook = h ? *h : cwal_callback_hook_empty;
    return 0;
}

cwal_size_t cwal_strlen( char const * str ){
    return str ? (cwal_size_t)strlen(str) : 0U;
}

char cwal_value_is_vacuum_proof( cwal_value const * v ){
    cwal_obase const * b = CWAL_VOBASE(v);
    return b
        ? ((b->flags & CWAL_F_IS_VACUUM_SAFE) ? 1 : 0)
        : V_IS_BUILTIN(v);
}

int cwal_value_make_vacuum_proof( cwal_value * v, char yes ){
    cwal_obase * b = CWAL_VOBASE(v);
    if(!v) return CWAL_RC_MISUSE;
    else if(!b) return CWAL_RC_TYPE;
    else if(yes && (b->flags & CWAL_F_IS_VACUUM_SAFE)) return 0;
    else if(!yes && !(b->flags & CWAL_F_IS_VACUUM_SAFE)) return 0;
    else{
        cwal_scope * s = v->scope;
        assert(s);
        cwal_value_snip(v);
        b->flags |= CWAL_F_IS_VACUUM_SAFE;
        cwal_scope_insert(s, v);
        return 0;
    }
}

int cwal_engine_vacuum( cwal_engine * e, int * sweepCount ){
    cwal_scope * s = e ? e->current : 0;
    int rc = 0;
    cwal_scope s2 = cwal_scope_empty;
    cwal_size_t origCount = 0;
#define SHOWCOUNTS 0
#if SHOWCOUNTS
    cwal_value * check = 0;
#endif
    if(!e || !s) return CWAL_RC_MISUSE;
    assert(s->level>0);
    assert(s->parent || (1==s->level/*top scope*/));
    if(!s->props && !s->mine.headSafe){
        /* s has no safe properties, so we clean up everything... */
        if(sweepCount){
            *sweepCount = (int)cwal_scope_value_count(s);
        }
        cwal_scope_clean(e, s);
        return 0;
    }

#  define VLIST_COUNT(WHO) check = WHO; rc = 0; while(check){++rc; check=check->right;}(void)0
#  define VCOUNT(WHO) VLIST_COUNT(WHO); cwal_outputf(e, #WHO" count: %d\n", rc); rc = 0
    if(sweepCount){
        /* Count starting number of Values in the scope ... */
        origCount = cwal_scope_value_count(s);
    }
#if SHOWCOUNTS
    VCOUNT(s->mine.headPod);
    VCOUNT(s->mine.headObj);
    VCOUNT(s->mine.headSafe);
    VCOUNT(s->mine.r0);
#endif

    s2.level = s->level - 1;
    s2.parent = s->parent;
    s->parent = &s2;
    s2.e = e;
    if(s->props){
        cwal_value * pv = cwal_object_value(s->props);
        cwal_value_rescope( &s2, pv );
        assert(pv->scope == &s2);
        s2.props = s->props;
        s->props = 0 /* transfer ref point */;
    }
    while(s->mine.headSafe){
#ifndef NDEBUG
        cwal_value const * vcheck = s->mine.headSafe;
#endif
        cwal_value_rescope( &s2, s->mine.headSafe );
        assert(s->mine.headSafe != vcheck);
    }
    /* Clean up, fake a lower/newer scope level,
       and move the vars back... */
    cwal_scope_clean( e, s );

#if SHOWCOUNTS
    VCOUNT(s->mine.headPod);
    VCOUNT(s->mine.headObj);
    VCOUNT(s->mine.headSafe);
    VCOUNT(s->mine.r0);

    VCOUNT(s2.mine.headPod);
    VCOUNT(s2.mine.headObj);
    VCOUNT(s2.mine.headSafe);
    VCOUNT(s2.mine.r0);
#endif

    /* Make s2 be s's child */
    s->parent = s2.parent;
    s2.parent = s;
    s2.level = s->level + 1;
    /* Move properties and vacuum-proof values back to s */
    if(s2.props){
        cwal_value * pv = cwal_object_value(s2.props);
        cwal_value_rescope( s, pv );
        assert(pv->scope == s);
        s->props = s2.props;
        s2.props = 0 /* transfer ref point */;
    }
    while(s2.mine.headSafe){
#ifndef NDEBUG
        cwal_value const * vcheck = s2.mine.headSafe;
#endif
        cwal_value_rescope( s, s2.mine.headSafe );
        assert(s2.mine.headSafe != vcheck);
    }

    if(e->propagating && e->propagating->scope==&s2){
        cwal_value_rescope(s, e->propagating);
    }
    if(e->exception && e->exception->scope==&s2){
        cwal_value_rescope(s, e->exception);
    }
    assert(0==s2.mine.r0);
    assert(0==s2.mine.headPod);
    assert(0==s2.mine.headObj);
    assert(0==s2.mine.headSafe);
    assert(0==s2.props);
    if(sweepCount){
        cwal_size_t newCount = cwal_scope_value_count(s);
#if SHOWCOUNTS
        VCOUNT(s->mine.headPod);
        VCOUNT(s->mine.headObj);
        VCOUNT(s->mine.headSafe);
        VCOUNT(s->mine.r0);

        VCOUNT(s2.mine.headPod);
        VCOUNT(s2.mine.headObj);
        VCOUNT(s2.mine.headSafe);
        VCOUNT(s2.mine.r0);
        MARKER(("origCount=%d, newCount=%d\n", (int)origCount, (int)newCount));
#endif
        /*
          Having more items than before is a sign that we imported
          more than we should have.
        */
        assert(newCount <= origCount);
        *sweepCount = (int)origCount - (int)newCount;
    }
    cwal_scope_clean(e, &s2) /* not strictly necessary - s2 must
                                be empty by now or our universe's
                                physics are all wrong. */;
#undef SHOWCOUNTS
#undef VCOUNT
#undef VLIST_COUNT
    return rc;
}


#undef MARKER
#undef cwal_string_empty_m
#undef cwal_obase_empty_m
#undef cwal_array_empty_m
#undef cwal_kvp_empty_m
#undef cwal_object_empty_m
#undef cwal_value_vtab_empty_m
#undef cwal_value_empty_m
#undef CWAL_VVPCAST
#undef CWAL_VALPART
#undef CWAL_INT
#undef CWAL_DBL
#undef CWAL_BOOL
#undef CWAL_STR
#undef CWAL_OBASE
#undef CWAL_VOBASE
#undef CWAL_OBJ
#undef CWAL_ARRAY
#undef CWAL_HASH
#undef CWAL_V2NATIVE
#undef dump_val
#undef V_IS_BUILTIN
#undef CWAL_VENGINE
#undef SETUP_ARRAY_ARGS
#undef CWAL_STRLEN
#undef CWAL_STRLEN_MASK
#undef CWAL_STR_ISX
#undef CWAL_STR_ISZ
#undef CWAL_STR_ISXZ
#undef CWAL_STR_XMASK
#undef CWAL_STR_ZMASK
#undef CWAL_INIT_EMPTY_VALUES
#undef CWAL_KVP_TRY_SORTING
#undef TRY_CLONE_SHARING
#undef COMPARE_TYPE_IDS
#undef CWAL_OBASE_IS_VISITING
#undef CWAL_VAL_IS_VISITING
#undef METRICS_REQ_INCR
#undef CWAL_OBASE_IS_VACUUM_SAFE


#if defined(__cplusplus)
} //extern "C" LOL - the C++-style comments do not upset C89 here.
#endif
/* end of file cwal.c */
/* start of file cwal_format.c */
/**
   This file contains the impl for cwal_buffer_format() and friends.
*/
#include <string.h> /* memcpy() */
#include <stdlib.h> /* strtol() */
#include <assert.h>

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


/**
   Internal type IDs for cwal_format_info instances.
   
   Maintenance reminder: these MUST be aligned with
   the CWAL_FORMAT_INFO array.
*/
enum cwal_format_t {
CWAL_FMT_UNKNOWN = 0,
CWAL_FMT_BOOL,
CWAL_FMT_NULL,
CWAL_FMT_UNDEF,
CWAL_FMT_VOIDP,
CWAL_FMT_INT_DEC,
CWAL_FMT_INT_HEXLC,
CWAL_FMT_INT_HEXUC,
CWAL_FMT_INT_OCTAL,
CWAL_FMT_DOUBLE,
CWAL_FMT_STRING,
CWAL_FMT_STRING_SQL,
CWAL_FMT_CHAR,
CWAL_FMT_TYPENAME,
CWAL_FMT_BLOB,
CWAL_FMT_JSON,
CWAL_FMT_MAX
};
typedef enum cwal_format_t cwal_format_t;

enum cwal_format_flags {
CWAL_FMT_F_NONE = 0,
CWAL_FMT_F_SIGNED = 0x01,
CWAL_FMT_F_ALT_FORM = 0x02,
CWAL_FMT_F_SQL_QUOTES_DOUBLE = 0x01
};
typedef enum cwal_format_flags cwal_format_flags;

typedef struct cwal_format_info cwal_format_info;
/**
   Callback for concrete cwal_buffer_format() handlers.  fi holds the
   formatting details, v the value. Implementations must interpret v
   for formatting purposes, following the settings in fi (insofar as
   possible for the given type). On error the handler must return
   non-0 and may set fi->errMsg to a string describing the problem.
   Handlers are allowed to silently ignore small misuses like invalid
   width/precision values.
*/
typedef int (*cwal_format_part_f)( cwal_engine * e, cwal_buffer * tgt,
                                   cwal_format_info * fi, cwal_value const * v );
/**
   Holds information related to the handling of a format specifier in
   cwal_buffer_format().
*/
struct cwal_format_info {
    /** The format type for which this handler is intended. */
    cwal_format_t type;
    /** Type-specific flags, from cwal_format_flags. */
    int flags;
    /**
       Precision part of the format string.
    */
    int precision;
    /**
       Width part of the format string.
    */
    int width;
    /**
       Ordinal position in the args processing, 1-based.
    */
    int position;
    /**
       Can be set to propagate an error message in certai
       contexts.
    */
    char const * errMsg;
    /**
       Padding char. TODO: change to int and output it as a UTF8
       char. TODO: split into left/right padding. Currently we
       hard-code space as right-side padding.
    */
    char pad;
    /**
       Callback handler for this formatter.
    */
    cwal_format_part_f f;
};

static int cwal_format_part_int( cwal_engine * e, cwal_buffer * tgt,
                                 cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_double( cwal_engine * e, cwal_buffer * tgt,
                                    cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_string( cwal_engine * e, cwal_buffer * tgt,
                                    cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_sql( cwal_engine * e, cwal_buffer * tgt,
                                 cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_char( cwal_engine * e, cwal_buffer * tgt,
                                  cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_basics( cwal_engine * e, cwal_buffer * tgt,
                                    cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_blob( cwal_engine * e, cwal_buffer * tgt,
                                  cwal_format_info * fi, cwal_value const * v );
static int cwal_format_part_json( cwal_engine * e, cwal_buffer * tgt,
                                  cwal_format_info * fi, cwal_value const * v );

static const cwal_format_info CWAL_FORMAT_INFO[CWAL_FMT_MAX+1] = {
/*
  Maintenance reminder: these MUST be in the same order the entries
  are defined in cwal_format_t.
*/
/* {type,flags,precision,width,position,errMsg,pad,f} */
{CWAL_FMT_UNKNOWN, 0, 0, 0, 0,NULL,' ', NULL},
#define FMT(T,CB) {CWAL_FMT_##T, 0, 0, 0, 0,NULL,' ', cwal_format_part_ ## CB}
FMT(BOOL,basics),
FMT(NULL,basics),
FMT(UNDEF,basics),
FMT(VOIDP,basics),
FMT(INT_DEC,int),
FMT(INT_HEXLC,int),
FMT(INT_HEXUC,int),
FMT(INT_OCTAL,int),
FMT(DOUBLE,double),
FMT(STRING,string),
FMT(STRING_SQL,sql),
FMT(CHAR,char),
FMT(TYPENAME,basics),
FMT(BLOB,blob),
FMT(JSON,json),
#undef FMT
{CWAL_FMT_MAX, 0, 0, 0, 0,NULL,' ', NULL},
};

static const cwal_format_info cwal_format_info_empty = {
CWAL_FMT_UNKNOWN,
0/*flags*/,
0/*precision*/,
0/*width*/,
0/*position*/,
NULL/*errMsg*/,
' '/*pad*/,
NULL/*f*/
};

int cwal_format_part_basics( cwal_engine * e, cwal_buffer * tgt,
                             cwal_format_info * fi, cwal_value const * v ){
    switch(fi->type){
      case CWAL_FMT_BOOL:{
          char const b = cwal_value_get_bool(v);
          return cwal_buffer_append( e, tgt, b ? "true" : "false", b ? 4 : 5);
      }
      case CWAL_FMT_UNDEF:
          return cwal_buffer_append( e, tgt, "undefined", 9);
      case CWAL_FMT_VOIDP:
          return cwal_buffer_printf( e, tgt, "%s@%p",
                                     cwal_value_type_name(v),
                                     (void const*)v);
      case CWAL_FMT_TYPENAME:
          return cwal_buffer_printf(e, tgt, "%s", cwal_value_type_name(v));
      case CWAL_FMT_NULL:
      default:
          /* This can happen for %N when the value is-not NULL. */
          return cwal_buffer_append( e, tgt, "null", 4);
    }
}


int cwal_format_part_int( cwal_engine * e,
                          cwal_buffer * tgt,
                          cwal_format_info * fi,
                          cwal_value const * v ){
    enum { BufSize = 100 };
    char fmtBuf[BufSize+1] = {'%',0};
    char * b = fmtBuf + 1;
    int rc;
    cwal_int_t const i = cwal_value_get_integer(v);
    if((i>0) && (CWAL_FMT_F_SIGNED & fi->flags)){
        *(b++) = '+';
    }
    if(0 != fi->width){
        if('0'==fi->pad) *(b++) = '0';
        b += sprintf(b, "%d", fi->width);
    }
    if(0 != fi->precision){
        b += sprintf(b, ".%d", fi->precision);
    }
    switch(fi->type){
      case CWAL_FMT_INT_DEC:
          *(b++) = 'd';
          break;
      case CWAL_FMT_INT_HEXLC:
          *(b++) = 'x';
          break;
      case CWAL_FMT_INT_HEXUC:
          *(b++) = 'X';
          break;
      case CWAL_FMT_INT_OCTAL:
          *(b++) = 'o';
          break;
      default:
          assert(!"CANNOT HAPPEN");
          break;
    }
    *b = 0;
    rc = cwal_buffer_printf(e, tgt, fmtBuf, i );
    return rc;
}

int cwal_format_part_double( cwal_engine * e,
                             cwal_buffer * tgt,
                             cwal_format_info * fi,
                             cwal_value const * v ){

    enum { BufSize = 120 };
    static unsigned int fmtLen = sizeof(CWAL_DOUBLE_T_PFMT)-1;
    char fmtBuf[BufSize+1] = {'%',0};
    cwal_size_t fmtBufLen = BufSize;
    char * b = fmtBuf + 1;
    int rc = 0;
    cwal_double_t const d = cwal_value_get_double(v);
    if((d>0) && (CWAL_FMT_F_SIGNED & fi->flags)){
        *(b++) = '+';
    }
    if(fi->width){
        if('0'==fi->pad){
            *(b++) = '0';
            --fmtBufLen;
        }
        cwal_int_to_cstr( fi->width, b, &fmtBufLen );
        b += fmtBufLen;
    }
    if(fi->precision){
        fmtBufLen = BufSize - (b - fmtBuf);
        *(b++) = '.';
        cwal_int_to_cstr( fi->precision, b, &fmtBufLen);
        b += fmtBufLen;
    }
    assert(b < (fmtBuf+BufSize+fmtLen));
    memcpy(b, CWAL_DOUBLE_T_PFMT, fmtLen);
    b += fmtLen;
    *b = 0;
    if(!rc){
        rc = cwal_buffer_printf(e, tgt, fmtBuf, d );
        if(!rc && tgt->used && !fi->precision){
            /* Trim trailing zeroes when no precision is specified... */
            b = (char *)(tgt->mem + tgt->used - 1);
            for( ; ('0'==*b) && tgt->used; --b, --tgt->used){}
            if('.'==*b) ++tgt->used /* leave one trailing 0 if we just
                                       stripped the last one. */;
        }
    }
    return rc;
}

int cwal_format_part_string( cwal_engine * e,
                             cwal_buffer * tgt,
                             cwal_format_info * fi,
                             cwal_value const * v ){
    int rc = 0;
    cwal_size_t slen = 0;
    cwal_size_t ulen;
    char const * cstr = cwal_value_get_cstr(v, &slen);
    int width = fi->width;
    if(!cstr){
#if 0
        cwal_type_id const tid = cwal_value_type_id(v);
        switch(tid){
       /* Reconsider these and whether we need to support the
          width/precision values in these cases. */
          case CWAL_TYPE_BUFFER:
              /* completely empty buffer */
              return 0;
          case CWAL_TYPE_HASH:
          case CWAL_TYPE_FUNCTION:
          case CWAL_TYPE_NATIVE:
              return cwal_buffer_printf(e, tgt, "%s@%p",
                                        cwal_value_type_name(v),
                                        (void const*)v);
          case CWAL_TYPE_BOOL:
              return cwal_buffer_printf(e, tgt, "%s",
                                        cwal_value_get_bool(v)
                                        ? "true" : "false");
          case CWAL_TYPE_INTEGER:
              return cwal_buffer_printf(e, tgt, "%"CWAL_INT_T_PFMT,
                                        (cwal_int_t)cwal_value_get_integer(v));
          case CWAL_TYPE_DOUBLE:
              return cwal_buffer_printf(e, tgt, "%"CWAL_DOUBLE_T_PFMT,
                                        (cwal_int_t)cwal_value_get_double(v));
          default:
              cstr = "(nil)";
              ulen = slen = 5;
        }
#else
        cstr = "(nil)";
        ulen = slen = 5;
#endif
    }
    else{
        ulen = cwal_cstr_length_utf8( (unsigned char const *)cstr, slen );
    }
    if((fi->precision>0) && (fi->precision<(int)ulen)){
        int i = 0;
        unsigned char const * ustr = (unsigned char const *)cstr;
        unsigned char const * uend = (unsigned char const *)cstr + slen;
        unsigned char const * upos = ustr;
        for( ; i < fi->precision; ++i, ustr = upos ){
            cwal_utf8_read_char( ustr, uend, &upos );
            assert(upos > ustr);
        }
        ulen = (cwal_size_t)fi->precision;
        slen = upos - (unsigned char const *)cstr;
        /* slen = (cwal_size_t)fi->precision; */
    }
    if(width>0 && ((int)ulen < width)){ /* right alignment */
        int i = (int)ulen - width;
        for( ; !rc && (i < 0); ++i){
            rc = cwal_buffer_append(e, tgt, &fi->pad, 1);
        }
    }
    if(!rc){
        rc = cwal_buffer_append(e, tgt, cstr, slen);
        if(!rc && (width<0)){ /* right padding */
            int i = (int)ulen - -width;
            for( ; !rc && (i < 0); ++i){
                rc = cwal_buffer_append(e, tgt, &fi->pad, 1);
            }
        }
    }
    return rc;
}

int cwal_format_part_sql( cwal_engine * e,
                          cwal_buffer * tgt,
                          cwal_format_info * fi,
                          cwal_value const * v ){
    int rc = 0;
    cwal_size_t slen = 0;
    unsigned int i, n;
    cwal_size_t j;
    int ch, isnull;
    int needQuote;
    char const q = ((CWAL_FMT_F_SQL_QUOTES_DOUBLE & fi->flags)?'"':'\'');   /* Quote character */
    char const * escarg = cwal_value_get_cstr(v, &slen);
    char * bufpt = NULL;
    cwal_size_t const oldUsed = e->buffer.used;
    isnull = escarg==0;
    if( isnull ) {
        escarg = (CWAL_FMT_F_ALT_FORM & fi->flags) ? "NULL" : "(NULL)";
    }
    for(i=n=0; (i<slen) && ((ch=escarg[i])!=0); ++i){
        if( ch==q ) ++n;
    }
    needQuote = !isnull && (CWAL_FMT_F_ALT_FORM & fi->flags);
    n += i + 1 + needQuote*2;
    /* FIXME: use a static buffer here instead of malloc()! Shame on you! */
    rc = cwal_buffer_reserve( e, &e->buffer, oldUsed + n );
    if(rc) return rc;
    bufpt = (char *)e->buffer.mem + oldUsed;
    if( ! bufpt ) return CWAL_RC_OOM;
    j = 0;
    if( needQuote ) bufpt[j++] = q;
    for(i=0; (ch=escarg[i])!=0; i++){
        bufpt[j++] = ch;
        if( ch==q ) bufpt[j++] = ch;
    }
    if( needQuote ) bufpt[j++] = q;
    bufpt[j] = 0;
    rc = cwal_buffer_append(e, tgt, bufpt, j);
    e->buffer.used = oldUsed;
    e->buffer.mem[e->buffer.used] = 0;
    return 0;
}


int cwal_format_part_blob( cwal_engine * e,
                           cwal_buffer * tgt,
                           cwal_format_info * fi,
                           cwal_value const * v ){
    int rc = 0;
    cwal_size_t slen = 0;
    char const * cstr = cwal_value_get_cstr(v, &slen);
    int width = fi->width;
    if(!cstr) return 0;
    else if((fi->precision>0) && (fi->precision<(int)slen)){
        slen = (cwal_size_t)fi->precision;
    }
    if(width>0 && ((int)slen < width)){ /* right alignment */
        int i = (int)slen - width;
        for( ; !rc && (i < 0); ++i){
            rc = cwal_buffer_append(e, tgt, &fi->pad, 1);
        }
    }
    if(!rc){
        rc = cwal_buffer_reserve(e, tgt, tgt->used + (slen*2) + 1);
        if(!rc){
            char const * hchar = "0123456789ABCDEF";
            cwal_size_t i;
            char hex[2] = {0,0};
            for( i = 0; !rc && (i < slen); ++i ){
                hex[0] = hchar[(cstr[i] & 0xF0)>>4];
                hex[1] = hchar[cstr[i] & 0x0F];
                rc = cwal_buffer_append(e, tgt, hex, 2);
            }
            if(!rc && (width<0)){ /* right padding */
                int i = (int)slen - -width;
                for( ; !rc && (i < 0); ++i){
                    rc = cwal_buffer_append(e, tgt, &fi->pad, 1);
                }
            }
        }
    }
    return rc;
}

int cwal_format_part_json( cwal_engine * e,
                           cwal_buffer * tgt,
                           cwal_format_info * fi,
                           cwal_value const * v ){
    cwal_json_output_opt outOpt = cwal_json_output_opt_empty;
    outOpt.indent = (char)(fi->width & 0xFF);
    return cwal_json_output_buffer(e, (cwal_value *)/*EVIL!*/v,
                                   tgt, &outOpt);
}


int cwal_format_part_char( cwal_engine * e,
                           cwal_buffer * tgt,
                           cwal_format_info * fi,
                           cwal_value const * v ){
    unsigned char intBuf[10] = {0,0,0,0,0,0,0,0,0,0};
    int rc = 0;
    cwal_size_t slen = 0;
    char const * cstr = cwal_value_get_cstr(v, &slen);
    cwal_int_t i;
    if(!cstr){
        i = cwal_value_get_integer(v);
        if(i<0){
            fi->errMsg = "Invalid (negative) character value.";
            return CWAL_RC_RANGE;
        }
        slen = (cwal_size_t)cwal_utf8_char_to_cstr( (unsigned int)i, intBuf,
                                                    sizeof(intBuf)/sizeof(intBuf[0]));
        if(slen>4){
            fi->errMsg = "Invalid UTF character value.";
            return CWAL_RC_RANGE;
        }
        cstr = (char const *)intBuf;
    }
    if(!slen){
        fi->errMsg = "Invalid (empty) character.";
        return CWAL_RC_RANGE;
    }else{
        unsigned char const * begin = (unsigned char const *)cstr;
        unsigned char const * eof = begin + slen;
        unsigned char const * tail = begin;
        int width = !fi->width ? 0 : ((fi->width<0) ? -fi->width : fi->width);
        cwal_size_t chLen;
        cwal_int_t n;
        cwal_utf8_read_char( begin, eof, &tail );
        if(tail == begin){
            fi->errMsg = "Could not decode UTF character.";
            return CWAL_RC_RANGE;
        }
        chLen =  tail - begin;
        n = (fi->precision>0) ? fi->precision : 1;
        /* MARKER(("n=%d, width=%d, precision=%d, \n",(int)n, fi->width, fi->precision)); */
        if((fi->width>0) && (width > n)){ /* left-pad */
            for( ; !rc && (width > n); --width){
                rc = cwal_buffer_append( e, tgt, &fi->pad, 1 );
            }
        }
        for( i = 0; !rc && (i < n); ++i ){
            rc = cwal_buffer_append( e, tgt, begin, chLen );
        }
        if(!rc && (fi->width<0) && (width > n)){ /* right-pad */
            for( ; !rc && (width > n); --width){
                rc = cwal_buffer_append( e, tgt, &fi->pad, 1 );
            }
        }
        return rc;
    }
}
/**
   Parse the range [b,*e) as a java.lang.String.format()-style
   format string:

   %N$[flags][width][.precision][type]
*/
static int cwal_format_parse_info( char const * b, char const ** e,
                                   cwal_format_info * fi,
                                   int * index ){
    char const * p = b;
    char const * x;
    char isMinus = 0;
    *fi = cwal_format_info_empty;
    for( ; (p < *e) && ('$' != *p); ++p ){
        if(('0'>*p) && ('9'<*p)){
            fi->errMsg = "Invalid argument index character after %.";
            return CWAL_RC_RANGE;
        }
    }
    /* MARKER(("fmt= b=[%s] p=[%s] e=[%c]\n",b, p, **e)); */
    if((p==b) || (p==*e)) {
        fi->errMsg = "Invalid format token.";
        return CWAL_RC_RANGE;
    }
    else if('$'!=*p) {
        /* MARKER(("fmt=[%s] [%s]\n",b, p)); */
        fi->errMsg = "Missing '$' after %N.";
        return CWAL_RC_RANGE;
    }
    *index = (int)strtol( b, (char **)&p, 10 );
    if(*index < 1){
        fi->errMsg = "Argument index must be greater than 0.";
        return CWAL_RC_RANGE;
    }
    *index = *index - 1 /* make it zero-based */;
    ++p /* skip '$' */;
    /* MARKER(("p=[%s]\n",p)); */
    if(*e == p){
        no_type:
        fi->errMsg = "No type specifier given in format token.";
        return CWAL_RC_TYPE;
    }
    /* Check for flag. */
    switch(*p){
      case '+':
          fi->flags |= CWAL_FMT_F_SIGNED;
          ++p;
          break;
      default: break;
    }
    /* Read [[-]width] ... */
    if('-'==*p){
        isMinus = 1;
        ++p;
    }
    x = p;
    if('0' == *x){ /* Make it 0-padded */
        fi->pad = '0';
        ++x;
        ++p;
    }
    for( ; (x<*e) && ('1'<=*x) && ('9'>=*x); ++x ){}
    /* MARKER(("x=[%s]\n",x)); */
    if(x==*e){
        fi->errMsg = "Unexpected end of width specifier.";
        return CWAL_RC_RANGE;
    }
    else if(x!=p){
        fi->width = (int)strtol( p, (char **)&p, 10 );
        if(p==*e) goto no_type;
        if(isMinus) fi->width = -fi->width;
        /* MARKER(("p=[%s], width=%d\n",p, fi->width)); */
    }
    /* Read [.precision] ... */
    /* MARKER(("p=[%s]\n",p)); */
    if('.'==*p){
        ++p;
        x = p;
        for( ; (x < *e) && ('0'<=*x) && ('9'>=*x); ++x ){}
        if(x==*e){
            fi->errMsg = "Unexpected end of precision specifier.";
            return CWAL_RC_RANGE;
        }
        else if(x==p){
            fi->errMsg = "Missing digits after '.'.";
            return CWAL_RC_RANGE;
        }
        else{
            fi->precision = (int)strtol( p, (char**)&p, 10 );
            /* MARKER(("p=[%s], precision=%d\n",p, fi->precision)); */
            if(p==*e) goto no_type;
        }
    }
    switch( *p ){
      case 'b': fi->type = CWAL_FMT_BOOL;
          ++p;
          break;
      case 'B': fi->type = CWAL_FMT_BLOB;
          ++p;
          break;
      case 'c': fi->type = CWAL_FMT_CHAR;
          ++p;
          break;
      case 'd': fi->type = CWAL_FMT_INT_DEC;
          ++p;
          break;
      case 'f': fi->type = CWAL_FMT_DOUBLE;
          ++p;
          break;
      case 'J': fi->type = CWAL_FMT_JSON;
          ++p;
          break;
      case 'N': fi->type = CWAL_FMT_NULL;
          ++p;
          break;
      case 'o': fi->type = CWAL_FMT_INT_OCTAL;
          ++p;
          break;
      case 'p': fi->type = CWAL_FMT_VOIDP;
          ++p;
          break;
      case 'q': fi->type = CWAL_FMT_STRING_SQL;
          ++p;
          break;
      case 'Q': fi->type = CWAL_FMT_STRING_SQL;
          fi->flags |= CWAL_FMT_F_ALT_FORM;
          ++p;
          break;
      case 's': fi->type = CWAL_FMT_STRING;
          ++p;
          break;
      case 'y': fi->type = CWAL_FMT_TYPENAME;
          ++p;
          break;
      case 'U': fi->type = CWAL_FMT_UNDEF;
          ++p;
          break;
      case 'x': fi->type = CWAL_FMT_INT_HEXLC;
          ++p;
          break;
      case 'X': fi->type = CWAL_FMT_INT_HEXUC;
          ++p;
          break;
      default:
          fi->errMsg = "Unknown format type specifier.";
          return CWAL_RC_RANGE;
    }
    switch(fi->type){
      case CWAL_FMT_INT_DEC:
      case CWAL_FMT_INT_HEXLC:
      case CWAL_FMT_INT_HEXUC:
          if(fi->precision){
              fi->errMsg = "Integer conversion does not support precision.";
              return CWAL_RC_MISUSE;
          }
      case CWAL_FMT_CHAR:
          if(fi->precision<0){
              fi->errMsg = "Character precision (repetition) may not be negative.";
              return CWAL_RC_MISUSE;
          }
      default:
          break;
    }
    *e = p;
    fi->f = CWAL_FORMAT_INFO[fi->type].f;
    return 0;
}

int cwal_buffer_format( cwal_engine * e, cwal_buffer * tgt,
                        char const * fmt, cwal_size_t fmtLen,
                        uint16_t argc, cwal_value * const * const argv){
    int rc = 0;
    char const * fpos = fmt;
    char const * fend = fpos + fmtLen;
    char const * tstart = fmt;
    cwal_format_info fi = cwal_format_info_empty;
    int index;
    int gotFmt = 0;
    cwal_size_t oldUsed;
    int ordPos = 0;
    if(!e || !tgt || !fmt ) return CWAL_RC_MISUSE;
    else if(!fmtLen) return 0;
    oldUsed = tgt->used;
    rc = cwal_buffer_reserve(e, tgt, oldUsed + fmtLen);
    if(rc) return rc;
    for( ; !rc && (fpos < fend); ){
        index = -1;
        fi = cwal_format_info_empty;
        switch( *fpos ){
          case '%':{
              ++fpos;
              ++gotFmt;
              if(fpos==fend){
                  fi.errMsg = "Unexpected end of input after '%'.";
                  rc = CWAL_RC_RANGE;
                  goto end;
              }
              else if('%'==*fpos){
                  rc = cwal_buffer_append( e, tgt, tstart, fpos - tstart);
                  tstart = ++fpos;
                  continue;
              }
              else{
                  char const * pend = fend;
                  fi.position = ++ordPos;
                  rc = cwal_format_parse_info( fpos, &pend, &fi, &index );
                  tstart = fpos = pend;
                  if(!rc){
                      assert(index>=0);
                      if(index>=(int)argc){
                          fi.errMsg = "Argument index out of range.";
                          rc = CWAL_RC_RANGE;
                          goto end;
                      }
                      else if(!argv[index]){
                          fi.f = CWAL_FORMAT_INFO[CWAL_FMT_NULL].f;
                      }
                      if(!fi.f){
                          fi.errMsg =
                              "Missing formatting function for "
                              "type specifier.";
                          rc = CWAL_RC_ERROR;
                          goto end;
                      }
                      rc = fi.f( e, tgt, &fi, argv[index] );
                      if(!rc) continue;
                  }
                  break;
              }
          }/* case '%' */
          default:
              ++fpos;
              break;
        }/*switch(*fpos)*/
        if(!rc){
            rc = cwal_buffer_append( e, tgt, tstart, fpos - tstart);
            tstart = fpos;
        }
        else break;
    }
    end:
    if(rc) switch(rc){
      case CWAL_RC_OOM:
        /* Pass through errors which might be introduced indirectly
           via client code if we expand this support to include custom
           formatters or callbacks. Note that if other errors pass
           through here we'll end up "overwriting" them as a formatting
           error.
        */
      case CWAL_RC_ASSERT:
      case CWAL_RC_EXIT:
      case CWAL_RC_FATAL:
      case CWAL_RC_EXCEPTION:
          break;
      default:{
          int errPos = (int)(fpos - fmt);
          int pedanticRc;
          tgt->used = oldUsed;
          pedanticRc = cwal_buffer_printf( e, tgt,
                                           "Formatting failed with "
                                           "code %d (%s) at format string "
                                           "byte offset %d.",
                                           rc, cwal_rc_cstr(rc), errPos - 1);
          if(!pedanticRc && fi.errMsg){
              cwal_buffer_printf(e, tgt, " Format token #%d%s%s", gotFmt,
                                 fi.errMsg ? ": " : ".",
                                 fi.errMsg ? fi.errMsg : "");
          }
      }
    }
    return rc;
}

#undef MARKER
/* end of file cwal_format.c */
/* start of file JSON_parser/JSON_parser.h */
/* See JSON_parser.c for copyright information and licensing. */

#ifndef JSON_PARSER_H
#define JSON_PARSER_H

/* JSON_parser.h */


#include <stddef.h>

/* Windows DLL stuff */
#ifdef JSON_PARSER_DLL
#   ifdef _MSC_VER
#	    ifdef JSON_PARSER_DLL_EXPORTS
#		    define JSON_PARSER_DLL_API __declspec(dllexport)
#	    else
#		    define JSON_PARSER_DLL_API __declspec(dllimport)
#       endif
#   else
#	    define JSON_PARSER_DLL_API 
#   endif
#else
#	define JSON_PARSER_DLL_API 
#endif

/* Determine the integer type use to parse non-floating point numbers */
#if __STDC_VERSION__ >= 199901L || HAVE_LONG_LONG == 1
typedef long long JSON_int_t;
#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld"
#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld"
#else 
typedef long JSON_int_t;
#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld"
#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld"
#endif


#ifdef __cplusplus
extern "C" {
#endif 

typedef enum 
{
    JSON_E_NONE = 0,
    JSON_E_INVALID_CHAR,
    JSON_E_INVALID_KEYWORD,
    JSON_E_INVALID_ESCAPE_SEQUENCE,
    JSON_E_INVALID_UNICODE_SEQUENCE,
    JSON_E_INVALID_NUMBER,
    JSON_E_NESTING_DEPTH_REACHED,
    JSON_E_UNBALANCED_COLLECTION,
    JSON_E_EXPECTED_KEY,
    JSON_E_EXPECTED_COLON,
    JSON_E_OUT_OF_MEMORY
} JSON_error;

typedef enum 
{
    JSON_T_NONE = 0,
    JSON_T_ARRAY_BEGIN,
    JSON_T_ARRAY_END,
    JSON_T_OBJECT_BEGIN,
    JSON_T_OBJECT_END,
    JSON_T_INTEGER,
    JSON_T_FLOAT,
    JSON_T_NULL,
    JSON_T_TRUE,
    JSON_T_FALSE,
    JSON_T_STRING,
    JSON_T_KEY,
    JSON_T_MAX
} JSON_type;

typedef struct JSON_value_struct {
    union {
        JSON_int_t integer_value;
        
        double float_value;
        
        struct {
            const char* value;
            size_t length;
        } str;
    } vu;
} JSON_value;

typedef struct JSON_parser_struct* JSON_parser;

/*! \brief JSON parser callback 

    \param ctx The pointer passed to new_JSON_parser.
    \param type An element of JSON_type but not JSON_T_NONE.    
    \param value A representation of the parsed value. This parameter is NULL for
        JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END,
        JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned
        as zero-terminated C strings.

    \return Non-zero if parsing should continue, else zero.
*/    
typedef int (*JSON_parser_callback)(void* ctx, int type, const struct JSON_value_struct* value);


/**
   A typedef for allocator functions semantically compatible with malloc().
*/
typedef void* (*JSON_malloc_t)(size_t n);
/**
   A typedef for deallocator functions semantically compatible with free().
*/
typedef void (*JSON_free_t)(void* mem);

/*! \brief The structure used to configure a JSON parser object 
*/
typedef struct {
    /** Pointer to a callback, called when the parser has something to tell
        the user. This parameter may be NULL. In this case the input is
        merely checked for validity.
    */
    JSON_parser_callback    callback;
    /**
       Callback context - client-specified data to pass to the
       callback function. This parameter may be NULL.
    */
    void*                   callback_ctx;
    /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting.
        If negative, the parser can parse arbitrary levels of JSON, otherwise
        the depth is the limit.
    */
    int                     depth;
    /**
       To allow C style comments in JSON, set to non-zero.
    */
    int                     allow_comments;
    /**
       To decode floating point numbers manually set this parameter to
       non-zero.
    */
    int                     handle_floats_manually;
    /**
       The memory allocation routine, which must be semantically
       compatible with malloc(3). If set to NULL, malloc(3) is used.

       If this is set to a non-NULL value then the 'free' member MUST be
       set to the proper deallocation counterpart for this function.
       Failure to do so results in undefined behaviour at deallocation
       time.
    */
    JSON_malloc_t       malloc;
    /**
       The memory deallocation routine, which must be semantically
       compatible with free(3). If set to NULL, free(3) is used.

       If this is set to a non-NULL value then the 'alloc' member MUST be
       set to the proper allocation counterpart for this function.
       Failure to do so results in undefined behaviour at deallocation
       time.
    */
    JSON_free_t         free;
} JSON_config;

/*! \brief Initializes the JSON parser configuration structure to default values.

    The default configuration is
    - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c)
    - no parsing, just checking for JSON syntax
    - no comments
    - Uses realloc() for memory de/allocation.

    \param config. Used to configure the parser.
*/
JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config);

/*! \brief Create a JSON parser object 

    \param config. Used to configure the parser. Set to NULL to use
        the default configuration. See init_JSON_config.  Its contents are
        copied by this function, so it need not outlive the returned
        object.
    
    \return The parser object, which is owned by the caller and must eventually
    be freed by calling delete_JSON_parser().
*/
JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config);

/*! \brief Destroy a previously created JSON parser object. */
JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc);

/*! \brief Parse a character.

    \return Non-zero, if all characters passed to this function are part of are valid JSON.
*/
JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char);

/*! \brief Finalize parsing.

    Call this method once after all input characters have been consumed.
    
    \return Non-zero, if all parsed characters are valid JSON, zero otherwise.
*/
JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc);

/*! \brief Determine if a given string is valid JSON white space 

    \return Non-zero if the string is valid, zero otherwise.
*/
JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s);

/*! \brief Gets the last error that occurred during the use of JSON_parser.

    \return A value from the JSON_error enum.
*/
JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc);

/*! \brief Re-sets the parser to prepare it for another parse run.

    \return True (non-zero) on success, 0 on error (e.g. !jc).
*/
JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc);


#ifdef __cplusplus
}
#endif 
    

#endif /* JSON_PARSER_H */
/* end of file JSON_parser/JSON_parser.h */
/* start of file cwal_json.c */
#include <assert.h>
#include <string.h> /* for a single sprintf() need :/ */
#include <ctype.h> /* toupper(), tolower() */

#if defined(__cplusplus)
extern "C" {
#endif

const cwal_json_output_opt cwal_json_output_opt_empty =
    cwal_json_output_opt_empty_m;


/**
   Returns true if v is of a type which has a direct JSON representation,
   else false.
*/
static char cwal_json_can_output( cwal_value const * v );
    
/**
   Escapes the first len bytes of the given string as JSON and sends
   it to the given output function (which will be called often - once
   for each logical character). The output is also surrounded by
   double-quotes.

   A NULL str will be escaped as an empty string, though we should
   arguably export it as "null" (without quotes). We do this because
   in JavaScript (typeof null === "object"), and by outputing null
   here we would effectively change the data type from string to
   object.
*/
static int cwal_str_to_json( char const * str, unsigned int len,
                             char escapeFwdSlash,
                             cwal_output_f f, void * state )
{
    if( NULL == f ) return CWAL_RC_MISUSE;
    else if( !str || !*str || (0 == len) )
    { /* special case for 0-length strings. */
        return f( state, "\"\"", 2 );
    }
    else
    {
        unsigned char const * pos = (unsigned char const *)str;
        unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL);
        unsigned char const * next = NULL;
        int ch;
        unsigned char clen = 0;
        char escChar[3] = {'\\',0,0};
        enum { UBLen = 8 };
        char ubuf[UBLen];
        int rc = 0;
        rc = f(state, "\"", 1 );
        for( ; (pos < end) && (0 == rc); pos += clen )
        {
            ch = cwal_utf8_read_char(pos, end, &next);
            if( 0 == ch ) break;
            assert( next > pos );
            clen = next - pos;
            assert( clen );
            if( 1 == clen )
            { /* ASCII */
                assert( *pos == ch );
                escChar[1] = 0;
                switch(ch)
                {
                  case '\t': escChar[1] = 't'; break;
                  case '\r': escChar[1] = 'r'; break;
                  case '\n': escChar[1] = 'n'; break;
                  case '\f': escChar[1] = 'f'; break;
                  case '\b': escChar[1] = 'b'; break;
                  case '/':
      /*
        Regarding escaping of forward-slashes. See the main exchange below...

        --------------
        From: Douglas Crockford <douglas@crockford.com>
        To: Stephan Beal <sgbeal@googlemail.com>
        Subject: Re: Is escaping of forward slashes required?

        It is allowed, not required. It is allowed so that JSON can be safely
        embedded in HTML, which can freak out when seeing strings containing
        "</". JSON tolerates "<\/" for this reason.

        On 4/8/2011 2:09 PM, Stephan Beal wrote:
        > Hello, Jsonites,
        >
        > i'm a bit confused on a small grammatic detail of JSON:
        >
        > if i'm reading the grammar chart on http://www.json.org/ correctly,
        > forward slashes (/) are supposed to be escaped in JSON. However, the
        > JSON class provided with my browsers (Chrome and FF, both of which i
        > assume are fairly standards/RFC-compliant) do not escape such characters.
        >
        > Is backslash-escaping forward slashes required? If so, what is the
        > justification for it? (i ask because i find it unnecessary and hard to
        > look at.)
        --------------
      */
                      if( escapeFwdSlash ) escChar[1] = '/';
                      break;
                  case '\\': escChar[1] = '\\'; break;
                  case '"': escChar[1] = '"'; break;
                  default: break;
                }
                if( escChar[1])
                {
                    rc = f(state, escChar, 2);
                }
                else
                {
                    rc = f(state, (char const *)pos, clen);
                }
                continue;
            }
            else
            { /* UTF: transform it to \uXXXX */
                memset(ubuf,0,UBLen);
                rc = sprintf(ubuf, "\\u%04x",ch);
                if( rc != 6 )
                {
                    rc = CWAL_RC_RANGE;
                    break;
                }
                rc = f( state, ubuf, 6 );
                continue;
            }
        }
        if( 0 == rc ) rc = f(state, "\"", 1 );
        return rc;
    }
}


static int cwal_json_output_null( cwal_output_f f, void * state ){
    return f(state, "null", 4);
}

static int cwal_json_output_bool( cwal_value const * src, cwal_output_f f, void * state )
{
    char const v = cwal_value_get_bool(src);
    return f(state, v ? "true" : "false", v ? 4 : 5);
}

static int cwal_json_output_integer( cwal_value const * src, cwal_output_f f, void * state )
{
    if( !f ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_integer(src) ) return CWAL_RC_TYPE;
    else {
        char b[100];
        cwal_size_t bLen = sizeof(b)/sizeof(b[0]);
        int rc;
        memset( b, 0, bLen );
        rc = cwal_int_to_cstr( cwal_value_get_integer(src), b, &bLen );
        return rc ? rc : f( state, b, bLen );
    }
}

static int cwal_json_output_double( cwal_value const * src, cwal_output_f f, void * state )
{
    if( !f ) return CWAL_RC_MISUSE;
    else if( !cwal_value_is_double(src) ) return CWAL_RC_TYPE;
    else
    {
        enum { BufLen = 128 /* this must be relatively large or huge
                               doubles can cause us to overrun here,
                               resulting in stack-smashing errors.
                            */};
        char b[BufLen];
        cwal_size_t bLen = BufLen;
        int rc;
        memset( b, 0, BufLen );
        rc = cwal_double_to_cstr( cwal_value_get_double(src), b, &bLen );
        if( rc ) return rc;
        else if(0) {
            /* Strip trailing zeroes before passing it on... */
            char * pos = b + bLen - 1;
            for( ; ('0' == *pos) && bLen && (*(pos-1) != '.');
                 --pos, --bLen ){
                *pos = 0;
            }
            assert(bLen && *pos);
            return f( state, b, bLen );
        }
        else{
            return f( state, b, bLen );
        }
        return 0;
    }
}

static int cwal_json_output_string( cwal_value const * src, char escapeFwdSlash, cwal_output_f f, void * state )
{
    char const * cstr;
    cwal_size_t strLen = 0;
    if( !f ) return CWAL_RC_MISUSE;
    else if(!cwal_value_is_string(src) && !cwal_value_is_buffer(src)){
        return CWAL_RC_TYPE;
    }else{
        cstr = cwal_value_get_cstr(src,&strLen);
        return cstr
            ? cwal_str_to_json(cstr, strLen, escapeFwdSlash, f, state)
            : /*only applies to buffers:*/
            cwal_json_output_null( f, state );
    }
}


/**
   Outputs indention spacing to f().

   blanks: (0)=no indentation, (-N)=-N TABs per/level, (+N)=n spaces/level

   depth is the current depth of the output tree, and determines how much
   indentation to generate.

   If blanks is 0 this is a no-op. Returns non-0 on error, and the
   error code will always come from f().
*/
static int cwal_json_output_indent( cwal_output_f f, void * state,
                                    int blanks, unsigned int depth )
{
    if( 0 == blanks ) return 0;
    else
    {
#if 0
        /* FIXME: stuff the indention into the buffer and make a
           single call to f(). We need a dyanmic buffer for that to
           keep it across calls, but we have no place to store it
           here. That said... we could keep two static buffers
           pre-filled with some amount of padding (one for spaces and
           one for tabs) and output them.
        */
        enum { BufLen = 200 };
        char buf[BufLen];
#endif
        int i;
        int x;
        char const ch = (blanks<0) ? '\t' : ' ';
        int rc = f(state, "\n", 1 );
        if(blanks<0) blanks = -blanks;
        for( i = 0; (i < (int)depth) && (0 == rc); ++i ){
            for( x = 0; (x < blanks) && (0 == rc); ++x ){
                rc = f(state, &ch, 1);
            }
        }
        return rc;
    }
}

static int cwal_json_output_array( cwal_value * src, cwal_output_f f, void * state,
                                   cwal_json_output_opt const * fmt, unsigned int level );
#if 0
static int cwal_json_output_object( cwal_value * src, cwal_output_f f, void * state,
                                    cwal_json_output_opt const * fmt, unsigned int level );
#endif

/**
   Outputs a JSON Object from the base->kvp list.
*/
static int cwal_json_output_obase( cwal_value * base, cwal_output_f f, void * state,
                                   cwal_json_output_opt const * fmt, unsigned int level );


static int cwal_json_cycle_string( cwal_value * cycled, cwal_output_f f, void * state,
                                   cwal_json_output_opt const * fmt ){
    enum {BufSize = 64};
    char buf[BufSize];
    cwal_size_t nLen = 0;
    char const * tname = cwal_value_type_name2(cycled, &nLen);
    int slen;
    assert(tname);
    assert(nLen);
    slen = sprintf(buf, "<CYCLE:%.*s@%p>", (int)(nLen>30U ? 30U : nLen), tname, (void const *)cycled);
    assert(slen>0);
    return cwal_str_to_json( buf, (unsigned)slen, fmt->escapeForwardSlashes, f, state);


}

/**
   Main cwal_json_output() implementation. Dispatches to a different impl depending
   on src->vtab->typeID.

   Returns 0 on success.
*/
static int cwal_json_output_impl( cwal_value * src, cwal_output_f f, void * state,
                                  cwal_json_output_opt const * fmt, unsigned int level )
{
    int rc;
    cwal_type_id const tid = cwal_value_type_id( src );
#if 0
    cwal_obase * base = cwal_value_obase(src);
    if( base ) {
        if( CWAL_F_IS_VISITING & base->flags ){
            if(fmt->cyclesAsStrings){
                
            }else{
                return CWAL_RC_CYCLES_DETECTED;
            }
        }
        cwal_value_set_visiting(src, 1);
    }
#endif
    switch( tid ){
      case CWAL_TYPE_UNDEF:
#if 0
          rc = f( state, "undefined", 9);
          break;
#endif
          /* Fall through to null */
      case CWAL_TYPE_NULL:
          rc = cwal_json_output_null(f, state);
          break;
      case CWAL_TYPE_BOOL:
          rc = cwal_json_output_bool(src, f, state);
          break;
      case CWAL_TYPE_INTEGER:
          rc = cwal_json_output_integer(src, f, state);
          break;
      case CWAL_TYPE_DOUBLE:
          rc = cwal_json_output_double(src, f, state);
          break;
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_STRING:
          rc = cwal_json_output_string(src, fmt->escapeForwardSlashes, f, state);
          break;
      case CWAL_TYPE_ARRAY:
          rc = cwal_json_output_array( src, f, state, fmt, level );
          break;
      /* case CWAL_TYPE_FUNCTION: */
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_OBJECT:{
          rc = cwal_json_output_obase( src, f, state, fmt, level );
          break;
      }
      default:
          rc = CWAL_RC_TYPE;
          break;
    }
#if 0
    if( base ) cwal_value_set_visiting(src, 0);
#endif
    return rc;
}



static int cwal_json_output_array( cwal_value * src, cwal_output_f f, void * state,
                                   cwal_json_output_opt const * fmt, unsigned int level )
{
    if( !src || !f || !fmt ) return CWAL_RC_MISUSE;
    else if( ! cwal_value_is_array(src) ) return CWAL_RC_TYPE;
    else if( level > fmt->maxDepth ) return CWAL_RC_RANGE;
    else
    {
        int rc;
        unsigned int i;
        cwal_value * v;
        char doIndent = fmt->indent ? 1 : 0;
        cwal_list const * li;
        cwal_array const * ar = cwal_value_get_array(src);

        if( CWAL_F_IS_VISITING & ar->base.flags ){
            if(fmt->cyclesAsStrings){
                return cwal_json_cycle_string( src, f, state, fmt);
            }else{
                return CWAL_RC_CYCLES_DETECTED;
            }
        }
        cwal_value_set_visiting( src, 1 );
        li = &ar->list;
        assert( NULL != li );
        if( 0 == li->count )
        {
            rc = f(state, "[]", 2 );
            goto end;
        }
        else if( (1 == li->count) && !fmt->indentSingleMemberValues ) doIndent = 0;
        rc = f(state, "[", 1);
        ++level;
        if( doIndent ){
            rc = cwal_json_output_indent( f, state, fmt->indent, level );
        }
        for( i = 0; (i < li->count) && (0 == rc); ++i ){
            v = (cwal_value *) li->list[i];
            rc = (v && cwal_json_can_output(v))
                ? cwal_json_output_impl( v, f, state, fmt, level )
                : cwal_json_output_null( f, state );
            if( 0 == rc ){
                if(i < (li->count-1)){
                    rc = f(state, ",", 1);
                    if( 0 == rc ){
                        rc = doIndent
                            ? cwal_json_output_indent( f, state, fmt->indent, level )
                            : (fmt->addSpaceAfterComma
                               ? f( state, " ", 1 )
                               : 0);
                    }
                }
            }
        }
        end:
        --level;
        if( doIndent && (0 == rc) ){
            rc = cwal_json_output_indent( f, state, fmt->indent, level );
        }
        cwal_value_set_visiting( src, 0 );
        return (0 == rc)
            ? f(state, "]", 1)
            : rc;
    }
}

char cwal_json_can_output( cwal_value const * v ){
    switch(cwal_value_type_id(v)){
      case CWAL_TYPE_ARRAY:
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_EXCEPTION:
      case CWAL_TYPE_STRING:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_NULL:
          return 1;
      default:
          return 0;
    }

}

int cwal_json_output_obase( cwal_value * self, cwal_output_f f, void * state,
                            cwal_json_output_opt const * fmt, unsigned int level )
{
    if( !self || !f || !fmt ) return CWAL_RC_MISUSE;
    else if( level > fmt->maxDepth ) return CWAL_RC_RANGE;
    else
    {
        int rc = 0;
        unsigned int i;
        cwal_kvp const * kvp;
        cwal_string const * sKey;
        cwal_value * val;
        char doIndent = fmt->indent ? 1 : 0;
        cwal_obase * base = cwal_value_obase(self);
        int outputCount = 0 /* keep track of where we need a comma */;
        if(!base) return CWAL_RC_MISUSE;
        assert( (NULL != fmt));
        kvp = base->kvp;
        if( 0 == kvp ) return f(state, "{}", 2 );

        if( CWAL_F_IS_VISITING & base->flags ){
            if(fmt->cyclesAsStrings){
                return cwal_json_cycle_string( self, f, state, fmt);
            }else{
                return CWAL_RC_CYCLES_DETECTED;
            }
        }
        cwal_value_set_visiting(self, 1);
       
        if( !kvp->right && !fmt->indentSingleMemberValues ) doIndent = 0;
        rc = f(state, "{", 1);
        ++level;
        if( doIndent ){
            rc = cwal_json_output_indent( f, state, fmt->indent, level );
        }
        for( i = 0; (0==rc) && kvp; kvp = kvp->right, ++i ){
            char const * cKey;
            cwal_value * key = cwal_kvp_key( kvp );
            sKey = kvp ? cwal_value_get_string(key) : 0;
            if(!sKey){
                    /*assert(sKey && "FIXME: cannot handle non-string keys.");*/
                switch(cwal_value_type_id(kvp->key)){
                  case CWAL_TYPE_INTEGER:
                    break;
                  default: continue;
                }
            }
            val = cwal_kvp_value(kvp);
            if(!cwal_json_can_output(val)) continue;
            if(outputCount){
                /* Output comma between us and our left-side neighbor */
                rc = f(state, ",", 1);
                if( 0 == rc ){
                    rc = doIndent
                        ? cwal_json_output_indent( f, state, fmt->indent, level )
                        : (fmt->addSpaceAfterComma
                           ? f( state, " ", 1 )
                           : 0);
                }
                if(rc) break;
            }

            if(sKey){
                cKey = cwal_string_cstr(sKey);
                rc = cwal_str_to_json(cKey,
                                      cwal_string_length_bytes(sKey),
                                      fmt->escapeForwardSlashes, f, state);
            }else{
                rc = f(state, "\"", 1);
                if(!rc) rc = cwal_json_output_impl(key, f, state, 0, level);
                if(!rc) rc = f(state, "\"", 1);
            }
            if(0 == rc) rc = fmt->addSpaceAfterColon
                ? f(state, ": ", 2 )
                : f(state, ":", 1 )
                ;
            if(0 == rc) {
                rc = ( val )
                    ? cwal_json_output_impl( val, f, state, fmt, level )
                    : cwal_json_output_null( f, state );
                ++outputCount;
            }
        }
        --level;
        if( doIndent && (0 == rc) ){
            rc = cwal_json_output_indent( f, state, fmt->indent, level );
        }
        cwal_value_set_visiting(self, 0);
        return (0 == rc)
            ? f(state, "}", 1)
            : rc;
    }
}



#if 0
static int cwal_json_output_object( cwal_value * src, cwal_output_f f, void * state,
                                    cwal_json_output_opt const * fmt, unsigned int level )
{
    if( !src || !f || !fmt ) return CWAL_RC_MISUSE;
    else if( ! cwal_value_is_object(src) ) return CWAL_RC_TYPE;
    {
        return cwal_json_output_obase( src, f, state, fmt, level );
    }
}
#endif

int cwal_json_output( cwal_value * src, cwal_output_f f,
                      void * state, cwal_json_output_opt const * fmt )
{
    int rc;
    if(! fmt ) fmt = &cwal_json_output_opt_empty;
    rc = cwal_json_output_impl(src, f, state, fmt, 0 );
    if( (0 == rc) && fmt->addNewline )
    {
        rc = f(state, "\n", 1);
    }
    return rc;
}

int cwal_json_output_FILE( cwal_value * src, FILE * dest,
                           cwal_json_output_opt const * fmt ){
    static cwal_json_output_opt sopt = cwal_json_output_opt_empty_m;
    int rc = 0;
    if(!sopt.addNewline){
        sopt.addNewline = 1;
    }
    if(!fmt) fmt = &sopt;
    rc = cwal_json_output( src, cwal_output_f_FILE, dest, fmt );
    if( 0 == rc ) fflush( dest );
    return rc;
}

int cwal_json_output_filename( cwal_value * src,
                               char const * fn,
                               cwal_json_output_opt const * fmt )
{
    if( !src || !fn || !*fn ) return CWAL_RC_MISUSE;
    else {
        FILE * f = (*fn && !fn[1] && ('-'==*fn))
            ? stdout
            : fopen( fn, "wb" );
        if( !f ) return CWAL_RC_IO;
        else {
            int const rc = cwal_json_output_FILE( src, f, fmt );
            if(stdout != f) fclose(f);
            return rc;
        }
    }
}

int cwal_json_output_buffer( cwal_engine * e, cwal_value * src,
                             cwal_buffer * dest,
                             cwal_json_output_opt const * fmt ){

    static cwal_json_output_opt const outOpt = cwal_json_output_opt_empty_m;
    cwal_output_buffer_state job = cwal_output_buffer_state_empty;
    if(!e || !src || !dest) return CWAL_RC_MISUSE;
    else if(!fmt) fmt = &outOpt;
    job.e = e;
    job.b = dest;
    return cwal_json_output( src, cwal_output_f_buffer, &job, fmt );
}


int cwal_json_output_engine( cwal_engine * e, cwal_value * src,
                             cwal_json_output_opt const * fmt ){
    return (src && e && e->vtab && e->vtab->outputer.output)
        ? cwal_json_output( src, e->vtab->outputer.output, e->vtab->outputer.state.data, fmt )
        : CWAL_RC_MISUSE;
}

struct cwal_json_parser{
    JSON_parser p;
    cwal_engine * e;
    cwal_value * root;
    cwal_value * node;
    cwal_string * ckey;
    int errNo;
    char const * errMsg;
    cwal_list stack;
};
typedef struct cwal_json_parser cwal_json_parser;
static const cwal_json_parser cwal_json_parser_empty = {
NULL/*e*/,
NULL/*p*/,
NULL/*root*/,
NULL/*node*/,
NULL/*ckey*/,
0/*errNo*/,
NULL/*errMsg*/,
cwal_list_empty_m/*stack*/
};

/**
   Converts a JSON_error code to one of the cwal_rc values.
*/
static int cwal_json_err_to_rc( JSON_error jrc )
{
    switch(jrc)
    {
      case JSON_E_NONE: return 0;
      case JSON_E_INVALID_CHAR: return CWAL_RC_JSON_INVALID_CHAR;
      case JSON_E_INVALID_KEYWORD: return CWAL_RC_JSON_INVALID_KEYWORD;
      case JSON_E_INVALID_ESCAPE_SEQUENCE: return CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE;
      case JSON_E_INVALID_UNICODE_SEQUENCE: return CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE;
      case JSON_E_INVALID_NUMBER: return CWAL_RC_JSON_INVALID_NUMBER;
      case JSON_E_NESTING_DEPTH_REACHED: return CWAL_RC_JSON_NESTING_DEPTH_REACHED;
      case JSON_E_UNBALANCED_COLLECTION: return CWAL_RC_JSON_UNBALANCED_COLLECTION;
      case JSON_E_EXPECTED_KEY: return CWAL_RC_JSON_EXPECTED_KEY;
      case JSON_E_EXPECTED_COLON: return CWAL_RC_JSON_EXPECTED_COLON;
      case JSON_E_OUT_OF_MEMORY: return CWAL_RC_OOM;
      default:
          return CWAL_RC_ERROR;
    }
}

/** @internal

   Cleans up all contents of p but does not free p.

   To properly take over ownership of the parser's root node on a
   successful parse:

   - Copy p->root's pointer and set p->root to NULL.
   - Eventually free up p->root with cwal_value_free().
   
   If you do not set p->root to NULL, p->root will be freed along with
   any other items inserted into it (or under it) during the parsing
   process.
*/
static void cwal_json_parser_clean( cwal_json_parser * p ){
    if(p->p) delete_JSON_parser(p->p);
    if(p->ckey) cwal_value_unref(cwal_string_value(p->ckey));
    cwal_list_reserve(p->e, &p->stack, 0);
    if(p->root) cwal_value_unref( p->root );
    *p = cwal_json_parser_empty;
}


/**
   Empty-initialized cwal_json_parse_info object.
*/
const cwal_json_parse_info cwal_json_parse_info_empty =
    cwal_json_parse_info_empty_m;

/** @internal

   If p->node is-a Object then value is inserted into the object
   using p->key. In any other case cwal_rc.InternalError is returned.

   Returns cwal_rc.AllocError if an allocation fails.

   Returns 0 on success. On error, parsing must be ceased immediately.
   
   Ownership of val is ALWAYS TRANSFERED to this function. If this
   function fails, val will be cleaned up and destroyed. (This
   simplifies error handling in the core parser.)
*/
static int cwal_json_parser_set_key( cwal_json_parser * p, cwal_value * val )
{
    assert( p && val );

    if( p->ckey && cwal_value_is_object(p->node) ){
        int rc;
        cwal_object * obj = cwal_value_get_object(p->node);
        rc = cwal_object_set_s( obj, p->ckey, val );
        if(rc){
            cwal_value_unref(val);
            cwal_value_unref(cwal_string_value(p->ckey));
        }
        p->ckey = NULL /* required to avoid mis-cleanup */;
        return rc;
    }
    else{
        if(val) cwal_value_unref(val);
        return p->errNo = CWAL_RC_ERROR;
    }
}

/** @internal

    Pushes val into the current object/array parent node, depending on the
    internal state of the parser.

    Ownership of val is always transfered to this function, regardless of
    success or failure.

    Returns 0 on success. On error, parsing must be ceased immediately.
*/
static int cwal_json_parser_push_value( cwal_json_parser * p, cwal_value * val ){
    if( p->ckey ){
        /* we're in Object mode */
        assert( cwal_value_is_object( p->node ) );
        return cwal_json_parser_set_key( p, val );
    }
    else if( cwal_value_is_array( p->node ) ){
        /* we're in Array mode */
        int rc;
        cwal_array * ar = cwal_value_get_array( p->node );
        rc = cwal_array_append( ar, val );
        if(rc) cwal_value_unref(val);
        return rc;
    }
    else
    { /* WTF? */
        assert( 0 && "Internal error in cwal_parser code" );
        return p->errNo = CWAL_RC_ERROR;
    }
}


/**
   Callback for JSON_parser API. Reminder: it returns 0 (meaning false)
   on error!
*/
static int cwal_json_parse_callback( void * cx, int type,
                                     JSON_value const * value ){
    cwal_json_parser * p = (cwal_json_parser *)cx;
    int rc = 0;
    cwal_value * v = NULL;
#define CHECKV if( !v ) { rc = CWAL_RC_OOM; break; } else
    switch(type) {
      case JSON_T_ARRAY_BEGIN:
      case JSON_T_OBJECT_BEGIN: {
          cwal_value * obja = (JSON_T_ARRAY_BEGIN == type)
              ? cwal_new_array_value(p->e)
              : cwal_new_object_value(p->e);
          if( ! obja ){
              p->errNo = CWAL_RC_OOM;
              break;
          }
          if( ! p->root ){
              p->root = p->node = obja;
              rc = cwal_list_append( p->e, &p->stack, obja );
              if( 0 != rc ){
                  /* work around a (potential) corner case in the cleanup code. */
                  p->root = NULL;
              }
          }
          else{
              rc = cwal_list_append( p->e, &p->stack, obja );
              if(rc) cwal_value_unref( obja );
              else{
                  rc = cwal_json_parser_push_value( p, obja );
                  if( 0 == rc ) p->node = obja;
              }
          }
          break;
      }
      case JSON_T_ARRAY_END:
      case JSON_T_OBJECT_END: {
          if(!p->stack.count){
              rc = CWAL_RC_RANGE;
              break;
          }
          /* Reminder: do not use cwal_array_pop_back( &p->stack )
             because that will clean up the object, and we don't want
             that.  We just want to forget this reference
             to it. The object is either the root or was pushed into
             an object/array in the parse tree (and is owned by that
             object/array).
          */
          --p->stack.count;
          assert( p->node == p->stack.list[p->stack.count] );
          p->stack.list[p->stack.count] = NULL;
          if( p->stack.count ){
              p->node = (cwal_value *)p->stack.list[p->stack.count-1];
          }
          else p->node = p->root;
          break;
      }
      case JSON_T_INTEGER: {
          v = cwal_new_integer(p->e, value->vu.integer_value);
          CHECKV {
              rc = cwal_json_parser_push_value( p, v );
              break;
          }
      }
      case JSON_T_FLOAT: {
          v = cwal_new_double(p->e, value->vu.float_value);
          CHECKV {
              rc =  cwal_json_parser_push_value( p, v );
              break;
          }
      }
      case JSON_T_NULL: {
          rc = cwal_json_parser_push_value( p, cwal_value_null() );
          break;
      }
      case JSON_T_TRUE: {
          rc = cwal_json_parser_push_value( p, cwal_value_true() );
          break;
      }
      case JSON_T_FALSE: {
          rc = cwal_json_parser_push_value( p, cwal_value_false() );
          break;
      }
      case JSON_T_KEY: {
          assert(!p->ckey);
          p->ckey = cwal_new_string( p->e,
                                     value->vu.str.length
                                     ? value->vu.str.value
                                     : NULL,
                                     (cwal_size_t)value->vu.str.length );
          if( ! p->ckey ){
              rc = CWAL_RC_OOM;
              break;
          }
          break;
      }
      case JSON_T_STRING: {
          v = cwal_new_string_value( p->e,
                                     value->vu.str.length
                                     ? value->vu.str.value
                                     : NULL,
                                     (cwal_size_t)value->vu.str.length );
          CHECKV {
              rc = cwal_json_parser_push_value( p, v );
              break;
          }
      }
      default:
          assert(0);
          rc = CWAL_RC_ERROR;
          break;
    }
#undef CHECKV
    return ((p->errNo = rc)) ? 0 : 1;
}

int cwal_json_parse( cwal_engine * e, cwal_input_f src,
                     void * state, cwal_value ** tgt,
                     cwal_json_parse_info * pInfo){
    unsigned char ch[2] = {0,0};
    int rc = 0;
    cwal_size_t len = 1;
    cwal_json_parse_info info = pInfo ? *pInfo : cwal_json_parse_info_empty;
    cwal_json_parser p = cwal_json_parser_empty;
    JSON_config jopt = {0};
    if( !e || !tgt || !src ) return CWAL_RC_MISUSE;
    init_JSON_config( &jopt );
    jopt.allow_comments = 0;
    jopt.depth = 30;
    jopt.callback_ctx = &p;
    jopt.handle_floats_manually = 0;
    jopt.callback = cwal_json_parse_callback;
    p.p = new_JSON_parser(&jopt);
    if( !p.p ) return CWAL_RC_OOM;
    p.e = e;
    do
    { /* FIXME: buffer the input in multi-kb chunks. */
        len = 1;
        ch[0] = 0;
        rc = src( state, ch, &len );
        if( 0 != rc ) break;
        else if( !len /* EOF */ ) break;
        ++info.length;
        if('\n' == ch[0]){
            ++info.line;
            info.col = 0;
        }
        if( ! JSON_parser_char(p.p, ch[0]) ){
            rc = cwal_json_err_to_rc( JSON_parser_get_last_error(p.p) );
            if(0==rc) rc = p.errNo ? p.errNo : CWAL_RC_ERROR;
            info.errorCode = rc;
            break;
        }
        if( '\n' != ch[0]) ++info.col;
    } while(1);
    if(pInfo) *pInfo = info;
    if( 0 != rc ){
        cwal_json_parser_clean(&p);
        return rc;
    }
    if( ! JSON_parser_done(p.p) ){
        rc = cwal_json_err_to_rc( JSON_parser_get_last_error(p.p) );
        cwal_json_parser_clean(&p);
        if(0==rc) rc = p.errNo ? p.errNo : CWAL_RC_ERROR;
    }
    else{
        cwal_value * root = p.root;
        p.root = NULL;
        cwal_json_parser_clean(&p);
        if( root ) *tgt = root;
        else{ /* this can happen on empty input. */
            rc = CWAL_RC_ERROR;
        }
    }
    return rc;
}


int cwal_json_parse_FILE( cwal_engine * e, FILE * src, cwal_value ** tgt,
                         cwal_json_parse_info * pInfo ){
    return cwal_json_parse( e, cwal_input_FILE, src, tgt, pInfo );
}

int cwal_json_parse_filename( cwal_engine * e, char const * src,
                              cwal_value ** tgt,
                              cwal_json_parse_info * pInfo ){
    if( !src || !tgt ) return CWAL_RC_MISUSE;
    else{
        FILE * f = fopen(src, "r");
        if( !f ) return CWAL_RC_IO;
        else{
            int const rc = cwal_json_parse_FILE( e, f, tgt, pInfo );
            fclose(f);
            return rc;
        }
    }
}

/** Internal type to hold state for a JSON input string.
 */
typedef struct cwal_input_StringSource_
{
    /** Start of input string. */
    char const * str;
    /** Current iteration position. Must initially be == str. */
    char const * pos;
    /** Logical EOF, one-past-the-end of str. */
    char const * end;
}  cwal_input_StringSource_t;

/**
   A cwal_input_f() implementation which requires the state argument
   to be a properly populated (cwal_input_StringSource_t*).
*/
static int cwal_input_StringSource( void * state, void * dest, cwal_size_t * n )
{
    if( !state || !n || !dest ) return CWAL_RC_MISUSE;
    else if( !*n ) return 0 /* ignore this */;
    else
    {
        cwal_size_t i;
        cwal_input_StringSource_t * ss = (cwal_input_StringSource_t*) state;
        unsigned char * tgt = (unsigned char *)dest;
        for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt )
        {
            *tgt = *ss->pos;
        }
        *n = i;
        return 0;
    }
}

int cwal_json_parse_cstr( cwal_engine * e, char const * src,
                          cwal_size_t len, cwal_value ** tgt,
                          cwal_json_parse_info * pInfo ){
    if( ! tgt || !src ) return CWAL_RC_MISUSE;
    else if( !*src || (len<2/*2==len of {} and []*/) ) return CWAL_RC_RANGE;
    else{
        cwal_input_StringSource_t ss;
        ss.str = ss.pos = src;
        ss.end = src + len;
        return cwal_json_parse( e, cwal_input_StringSource, &ss, tgt, pInfo );
    }
}

#if defined(__cplusplus)
} /*extern "C"*/
#endif
/* end of file cwal_json.c */
/* start of file cwal_printf.c */
/************************************************************************
The printf-like implementation in this file is based on the one found
in the sqlite3 distribution is in the Public Domain.

This copy was forked for use with the clob API in Feb 2008 by Stephan
Beal (http://wanderinghorse.net/home/stephan/) and modified to send
its output to arbitrary targets via a callback mechanism. Also
refactored the %X specifier handlers a bit to make adding/removing
specific handlers easier.

All code in this file is released into the Public Domain.

The printf implementation (cwal_printfv()) is pretty easy to extend
(e.g. adding or removing %-specifiers for cwal_printfv()) if you're
willing to poke around a bit and see how the specifiers are declared
and dispatched. For an example, grep for 'etSTRING' and follow it
through the process of declaration to implementation.

See below for several WHPRINTF_OMIT_xxx macros which can be set to
remove certain features/extensions.
************************************************************************/

#include <assert.h>
#include <stdio.h> /* FILE */
#include <string.h> /* strlen() */
#include <stdlib.h> /* free/malloc() */
#include <ctype.h>
#include <stdint.h>

typedef long double LONGDOUBLE_TYPE;


/*
   If WHPRINTF_OMIT_FLOATING_POINT is defined to a true value, then
   floating point conversions are disabled.
*/
#ifndef WHPRINTF_OMIT_FLOATING_POINT
#  define WHPRINTF_OMIT_FLOATING_POINT 0
#endif

/*
   If WHPRINTF_OMIT_SIZE is defined to a true value, then
   the %n specifier is disabled.
*/
#ifndef WHPRINTF_OMIT_SIZE
#  define WHPRINTF_OMIT_SIZE 0
#endif

/*
   If WHPRINTF_OMIT_SQL is defined to a true value, then
   the %q and %Q specifiers are disabled.
*/
#ifndef WHPRINTF_OMIT_SQL
#  define WHPRINTF_OMIT_SQL 0
#endif

/*
   If WHPRINTF_OMIT_HTML is defined to a true value then the %h (HTML
   escape), %t (URL escape), and %T (URL unescape) specifiers are
   disabled.
*/
#ifndef WHPRINTF_OMIT_HTML
#  define WHPRINTF_OMIT_HTML 0
#endif

/*
Most C compilers handle variable-sized arrays, so we enable
that by default. Some (e.g. tcc) do not, so we provide a way
to disable it: set WHPRINTF_HAVE_VARARRAY to 0

One approach would be to look at:

  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)

but some compilers support variable-sized arrays even when not
explicitly running in c99 mode.
*/
#if !defined(WHPRINTF_HAVE_VARARRAY)
#  if defined(__TINYC__)
#    define WHPRINTF_HAVE_VARARRAY 0
#  else
#    if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#        define WHPRINTF_HAVE_VARARRAY 1 /*use 1 in C99 mode */
#    else
#        define WHPRINTF_HAVE_VARARRAY 0
#    endif
#  endif
#endif

/**
WHPRINTF_CHARARRAY is a helper to allocate variable-sized arrays.
This exists mainly so this code can compile with the tcc compiler.
*/
#if WHPRINTF_HAVE_VARARRAY
#  define WHPRINTF_CHARARRAY(V,N) char V[N+1]; memset(V,0,N+1);
#  define WHPRINTF_CHARARRAY_FREE(V)
#else
#  define WHPRINTF_CHARARRAY(V,N) char * V = (char *)malloc(N); memset(V,0,N);
#  define WHPRINTF_CHARARRAY_FREE(V) free(V)
#endif

/*
   Conversion types fall into various categories as defined by the
   following enumeration.
*/
enum PrintfCategory {etRADIX = 1, /* Integer types.  %d, %x, %o, and so forth */
		     etFLOAT = 2, /* Floating point.  %f */
		     etEXP = 3, /* Exponentional notation. %e and %E */
		     etGENERIC = 4, /* Floating or exponential, depending on exponent. %g */
		     etSIZE = 5, /* Return number of characters processed so far. %n */
		     etSTRING = 6, /* Strings. %s */
		     etDYNSTRING = 7, /* Dynamically allocated strings. %z */
		     etPERCENT = 8, /* Percent symbol. %% */
		     etCHARX = 9, /* Characters. %c */
/* The rest are extensions, not normally found in printf() */
		     etCHARLIT = 10, /* Literal characters.  %' */
#if !WHPRINTF_OMIT_SQL
		     etSQLESCAPE = 11, /* Strings with '\'' doubled.  %q */
		     etSQLESCAPE2 = 12, /* Strings with '\'' doubled and enclosed in '',
                          NULL pointers replaced by SQL NULL.  %Q */
		     etSQLESCAPE3 = 16, /* %w -> Strings with '\"' doubled */
#endif /* !WHPRINTF_OMIT_SQL */
		     etPOINTER = 15, /* The %p conversion */
		     etORDINAL = 17, /* %r -> 1st, 2nd, 3rd, 4th, etc.  English only */
#if ! WHPRINTF_OMIT_HTML
                     etHTML = 18, /* %h -> basic HTML escaping. */
                     etURLENCODE = 19, /* %t -> URL encoding. */
                     etURLDECODE = 20, /* %T -> URL decoding. */
#endif
		     etPLACEHOLDER = 100
};

/*
   An "etByte" is an 8-bit unsigned value.
*/
typedef unsigned char etByte;

/*
   Each builtin conversion character (ex: the 'd' in "%d") is described
   by an instance of the following structure
*/
typedef struct et_info {   /* Information about each format field */
  char fmttype;            /* The format field code letter */
  etByte base;             /* The base for radix conversion */
  etByte flags;            /* One or more of FLAG_ constants below */
  etByte type;             /* Conversion paradigm */
  etByte charset;          /* Offset into aDigits[] of the digits string */
  etByte prefix;           /* Offset into aPrefix[] of the prefix string */
} et_info;

/*
   Allowed values for et_info.flags
*/
enum et_info_flags { FLAG_SIGNED = 1,    /* True if the value to convert is signed */
		     FLAG_EXTENDED = 2,  /* True if for internal/extended use only. */
		     FLAG_STRING = 4     /* Allow infinity precision */
};

/*
  Historically, the following table was searched linearly, so the most
  common conversions were kept at the front.

  Change 2008 Oct 31 by Stephan Beal: we reserve an array or ordered
  entries for all chars in the range [32..126]. Format character
  checks can now be done in constant time by addressing that array
  directly.  This takes more static memory, but reduces the time and
  per-call overhead costs of cwal_printfv().
*/
static const char aDigits[] = "0123456789ABCDEF0123456789abcdef";
static const char aPrefix[] = "-x0\000X0";
static const et_info fmtinfo[] = {
/**
   If WHPRINTF_FMTINFO_FIXED is 1 then we use the original
   implementation: a linear list of entries. Search time is linear. If
   WHPRINTF_FMTINFO_FIXED is 0 then we use a fixed-size array which
   we index directly using the format char as the key.
*/
#define WHPRINTF_FMTINFO_FIXED 0
#if WHPRINTF_FMTINFO_FIXED
  {  'd', 10, FLAG_SIGNED, etRADIX,      0,  0 },
  {  's',  0, FLAG_STRING, etSTRING,     0,  0 },
  {  'g',  0, FLAG_SIGNED, etGENERIC,    30, 0 },
  {  'z',  0, FLAG_STRING, etDYNSTRING,  0,  0 },
  {  'c',  0, 0, etCHARX,      0,  0 },
  {  'o',  8, 0, etRADIX,      0,  2 },
  {  'u', 10, 0, etRADIX,      0,  0 },
  {  'x', 16, 0, etRADIX,      16, 1 },
  {  'X', 16, 0, etRADIX,      0,  4 },
  {  'i', 10, FLAG_SIGNED, etRADIX,      0,  0 },
#if !WHPRINTF_OMIT_FLOATING_POINT
  {  'f',  0, FLAG_SIGNED, etFLOAT,      0,  0 },
  {  'e',  0, FLAG_SIGNED, etEXP,        30, 0 },
  {  'E',  0, FLAG_SIGNED, etEXP,        14, 0 },
  {  'G',  0, FLAG_SIGNED, etGENERIC,    14, 0 },
#endif /* !WHPRINTF_OMIT_FLOATING_POINT */
  {  '%',  0, 0, etPERCENT,    0,  0 },
  {  'p', 16, 0, etPOINTER,    0,  1 },
  {  'r', 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL,    0,  0 },
#if ! WHPRINTF_OMIT_SQL
  {  'q',  0, FLAG_STRING, etSQLESCAPE,  0,  0 },
  {  'Q',  0, FLAG_STRING, etSQLESCAPE2, 0,  0 },
  {  'w',  0, FLAG_STRING, etSQLESCAPE3, 0,  0 },
#endif /* !WHPRINTF_OMIT_SQL */
#if ! WHPRINTF_OMIT_HTML
  {  'h',  0, FLAG_STRING, etHTML, 0, 0 },
  {  't',  0, FLAG_STRING, etURLENCODE, 0, 0 },
  {  'T',  0, FLAG_STRING, etURLDECODE, 0, 0 },
#endif /* !WHPRINTF_OMIT_HTML */
#if !WHPRINTF_OMIT_SIZE
  {  'n',  0, 0, etSIZE,       0,  0 },
#endif
#else /* WHPRINTF_FMTINFO_FIXED */
  /*
    These entries MUST stay in ASCII order, sorted
    on their fmttype member!
  */
  {' '/*32*/, 0, 0, 0, 0, 0 },
  {'!'/*33*/, 0, 0, 0, 0, 0 },
  {'"'/*34*/, 0, 0, 0, 0, 0 },
  {'#'/*35*/, 0, 0, 0, 0, 0 },
  {'$'/*36*/, 0, 0, 0, 0, 0 },
  {'%'/*37*/, 0, 0, etPERCENT, 0, 0 },
  {'&'/*38*/, 0, 0, 0, 0, 0 },
  {'\''/*39*/, 0, 0, 0, 0, 0 },
  {'('/*40*/, 0, 0, 0, 0, 0 },
  {')'/*41*/, 0, 0, 0, 0, 0 },
  {'*'/*42*/, 0, 0, 0, 0, 0 },
  {'+'/*43*/, 0, 0, 0, 0, 0 },
  {','/*44*/, 0, 0, 0, 0, 0 },
  {'-'/*45*/, 0, 0, 0, 0, 0 },
  {'.'/*46*/, 0, 0, 0, 0, 0 },
  {'/'/*47*/, 0, 0, 0, 0, 0 },
  {'0'/*48*/, 0, 0, 0, 0, 0 },
  {'1'/*49*/, 0, 0, 0, 0, 0 },
  {'2'/*50*/, 0, 0, 0, 0, 0 },
  {'3'/*51*/, 0, 0, 0, 0, 0 },
  {'4'/*52*/, 0, 0, 0, 0, 0 },
  {'5'/*53*/, 0, 0, 0, 0, 0 },
  {'6'/*54*/, 0, 0, 0, 0, 0 },
  {'7'/*55*/, 0, 0, 0, 0, 0 },
  {'8'/*56*/, 0, 0, 0, 0, 0 },
  {'9'/*57*/, 0, 0, 0, 0, 0 },
  {':'/*58*/, 0, 0, 0, 0, 0 },
  {';'/*59*/, 0, 0, 0, 0, 0 },
  {'<'/*60*/, 0, 0, 0, 0, 0 },
  {'='/*61*/, 0, 0, 0, 0, 0 },
  {'>'/*62*/, 0, 0, 0, 0, 0 },
  {'?'/*63*/, 0, 0, 0, 0, 0 },
  {'@'/*64*/, 0, 0, 0, 0, 0 },
  {'A'/*65*/, 0, 0, 0, 0, 0 },
  {'B'/*66*/, 0, 0, 0, 0, 0 },
  {'C'/*67*/, 0, 0, 0, 0, 0 },
  {'D'/*68*/, 0, 0, 0, 0, 0 },
  {'E'/*69*/, 0, FLAG_SIGNED, etEXP, 14, 0 },
  {'F'/*70*/, 0, 0, 0, 0, 0 },
  {'G'/*71*/, 0, FLAG_SIGNED, etGENERIC, 14, 0 },
  {'H'/*72*/, 0, 0, 0, 0, 0 },
  {'I'/*73*/, 0, 0, 0, 0, 0 },
  {'J'/*74*/, 0, 0, 0, 0, 0 },
  {'K'/*75*/, 0, 0, 0, 0, 0 },
  {'L'/*76*/, 0, 0, 0, 0, 0 },
  {'M'/*77*/, 0, 0, 0, 0, 0 },
  {'N'/*78*/, 0, 0, 0, 0, 0 },
  {'O'/*79*/, 0, 0, 0, 0, 0 },
  {'P'/*80*/, 0, 0, 0, 0, 0 },
#if WHPRINTF_OMIT_SQL
  {'Q'/*81*/, 0, 0, 0, 0, 0 },
#else
  {'Q'/*81*/, 0, FLAG_STRING, etSQLESCAPE2, 0, 0 },
#endif
  {'R'/*82*/, 0, 0, 0, 0, 0 },
  {'S'/*83*/, 0, 0, 0, 0, 0 },
  {'T'/*84*/,  0, FLAG_STRING, etURLDECODE, 0, 0 },
  {'U'/*85*/, 0, 0, 0, 0, 0 },
  {'V'/*86*/, 0, 0, 0, 0, 0 },
  {'W'/*87*/, 0, 0, 0, 0, 0 },
  {'X'/*88*/, 16, 0, etRADIX,      0,  4 },
  {'Y'/*89*/, 0, 0, 0, 0, 0 },
  {'Z'/*90*/, 0, 0, 0, 0, 0 },
  {'['/*91*/, 0, 0, 0, 0, 0 },
  {'\\'/*92*/, 0, 0, 0, 0, 0 },
  {']'/*93*/, 0, 0, 0, 0, 0 },
  {'^'/*94*/, 0, 0, 0, 0, 0 },
  {'_'/*95*/, 0, 0, 0, 0, 0 },
  {'`'/*96*/, 0, 0, 0, 0, 0 },
  {'a'/*97*/, 0, 0, 0, 0, 0 },
  {'b'/*98*/, 0, 0, 0, 0, 0 },
  {'c'/*99*/, 0, 0, etCHARX,      0,  0 },
  {'d'/*100*/, 10, FLAG_SIGNED, etRADIX,      0,  0 },
  {'e'/*101*/, 0, FLAG_SIGNED, etEXP,        30, 0 },
  {'f'/*102*/, 0, FLAG_SIGNED, etFLOAT,      0,  0},
  {'g'/*103*/, 0, FLAG_SIGNED, etGENERIC,    30, 0 },
  {'h'/*104*/, 0, FLAG_STRING, etHTML, 0, 0 },
  {'i'/*105*/, 10, FLAG_SIGNED, etRADIX,      0,  0},
  {'j'/*106*/, 0, 0, 0, 0, 0 },
  {'k'/*107*/, 0, 0, 0, 0, 0 },
  {'l'/*108*/, 0, 0, 0, 0, 0 },
  {'m'/*109*/, 0, 0, 0, 0, 0 },
  {'n'/*110*/, 0, 0, etSIZE, 0, 0 },
  {'o'/*111*/, 8, 0, etRADIX,      0,  2 },
  {'p'/*112*/, 16, 0, etPOINTER, 0, 1 },
#if WHPRINTF_OMIT_SQL
  {'q'/*113*/, 0, 0, 0, 0, 0 },
#else
  {'q'/*113*/, 0, FLAG_STRING, etSQLESCAPE,  0, 0 },
#endif
  {'r'/*114*/, 10, (FLAG_EXTENDED|FLAG_SIGNED), etORDINAL,    0,  0},
  {'s'/*115*/, 0, FLAG_STRING, etSTRING,     0,  0 },
  {'t'/*116*/,  0, FLAG_STRING, etURLENCODE, 0, 0 },
  {'u'/*117*/, 10, 0, etRADIX,      0,  0 },
  {'v'/*118*/, 0, 0, 0, 0, 0 },
#if WHPRINTF_OMIT_SQL
  {'w'/*119*/, 0, 0, 0, 0, 0 },
#else
  {'w'/*119*/, 0, FLAG_STRING, etSQLESCAPE3, 0, 0 },
#endif
  {'x'/*120*/, 16, 0, etRADIX,      16, 1  },
  {'y'/*121*/, 0, 0, 0, 0, 0 },
  {'z'/*122*/, 0, FLAG_STRING, etDYNSTRING,  0,  0},
  {'{'/*123*/, 0, 0, 0, 0, 0 },
  {'|'/*124*/, 0, 0, 0, 0, 0 },
  {'}'/*125*/, 0, 0, 0, 0, 0 },
  {'~'/*126*/, 0, 0, 0, 0, 0 },
#endif /* WHPRINTF_FMTINFO_FIXED */
};
#define etNINFO  (sizeof(fmtinfo)/sizeof(fmtinfo[0]))

#if ! WHPRINTF_OMIT_FLOATING_POINT
/*
   "*val" is a double such that 0.1 <= *val < 10.0
   Return the ascii code for the leading digit of *val, then
   multiply "*val" by 10.0 to renormalize.
**
   Example:
       input:     *val = 3.14159
       output:    *val = 1.4159    function return = '3'
**
   The counter *cnt is incremented each time.  After counter exceeds
   16 (the number of significant digits in a 64-bit float) '0' is
   always returned.
*/
static int et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){
  int digit;
  LONGDOUBLE_TYPE d;
  if( (*cnt)++ >= 16 ) return '0';
  digit = (int)*val;
  d = digit;
  digit += '0';
  *val = (*val - d)*10.0;
  return digit;
}
#endif /* !WHPRINTF_OMIT_FLOATING_POINT */

/*
   On machines with a small(?) stack size, you can redefine the
   WHPRINTF_BUF_SIZE to be less than 350.  But beware - for smaller
   values some %f conversions may go into an infinite loop.
*/
#ifndef WHPRINTF_BUF_SIZE
#  define WHPRINTF_BUF_SIZE 350  /* Size of the output buffer for numeric conversions */
#endif

#if ! defined(__STDC__) && !defined(__TINYC__)
#ifdef WHPRINTF_INT64_TYPE
  typedef WHPRINTF_INT64_TYPE int64_t;
  typedef unsigned WHPRINTF_INT64_TYPE uint64_t;
#elif defined(_MSC_VER) || defined(__BORLANDC__)
  typedef __int64 int64_t;
  typedef unsigned __int64 uint64_t;
#else
  typedef long long int int64_t;
  typedef unsigned long long int uint64_t;
#endif
#endif

#if 0
/   Not yet used. */
enum PrintfArgTypes {
TypeInt = 0,
TypeIntP = 1,
TypeFloat = 2,
TypeFloatP = 3,
TypeCString = 4
};
#endif


#if 0
/   Not yet used. */
typedef struct cwal_printf_spec_handler_def
{
	char letter; /   e.g. %s */
	int xtype; /* reference to the etXXXX values, or fmtinfo[*].type. */
	int ntype; /* reference to PrintfArgTypes enum. */
} spec_handler;
#endif

/**
   cwal_printf_spec_handler is an almost-generic interface for farming
   work out of cwal_printfv()'s code into external functions.  It doesn't
   actually save much (if any) overall code, but it makes the cwal_printfv()
   code more manageable.


   REQUIREMENTS of implementations:

   - Expects an implementation-specific vargp pointer.
   cwal_printfv() passes a pointer to the converted value of
   an entry from the format va_list. If it passes a type
   other than the expected one, undefined results.

   - If it calls pf then it must return the return value
   from that function.

   - If it calls pf it must do: pf( pfArg, D, N ), where D is
   the data to export and N is the number of bytes to export.
   It may call pf() an arbitrary number of times

   - If pf() successfully is called, the return value must be the
   accumulated totals of its return value(s), plus (possibly, but
   unlikely) an imnplementation-specific amount.

   - If it does not call pf() then it must return 0 (success)
   or a negative number (an error) or do all of the export
   processing itself and return the number of bytes exported.


   SIGNIFICANT LIMITATIONS:

   - Has no way of iterating over the format string,
   so handling precisions and such here can't work too
   well.
*/
typedef long (*cwal_printf_spec_handler)( cwal_printf_appender pf,
                                          void * pfArg,
                                          unsigned int pfLen,
                                          void * vargp );


/**
  cwal_printf_spec_handler for etSTRING types. It assumes that varg is a
  null-terminated (char [const] *)
*/
static long spech_string( cwal_printf_appender pf,
			  void * pfArg,
                          unsigned int pfLen,
			  void * varg )
{
	char const * ch = (char const *) varg;
	return ch ? pf( pfArg, ch, pfLen ) : 0;
}

/**
  cwal_printf_spec_handler for etDYNSTRING types.  It assumes that varg
  is a non-const (char *). It behaves identically to spec_string() and
  then calls free() on that (char *).
*/
static long spech_dynstring( cwal_printf_appender pf,
			     void * pfArg,
                             unsigned int pfLen,
			     void * varg )
{
    long ret = spech_string( pf, pfArg, pfLen, varg );
    free( (char *) varg );
    return ret;
}

#if !WHPRINTF_OMIT_HTML
static long spech_string_to_html( cwal_printf_appender pf,
                                  void * pfArg,
                                  unsigned int pfLen,
                                  void * varg )
{
    char const * ch = (char const *) varg;
    unsigned int i;
    long ret = 0;
    if( ! ch ) return 0;
    ret = 0;
    for( i = 0; (i<pfLen) && *ch; ++ch, ++i )
    {
        switch( *ch )
        {
          case '<': ret += pf( pfArg, "&lt;", 4 );
              break;
          case '&': ret += pf( pfArg, "&amp;", 5 );
              break;
          default:
              ret += pf( pfArg, ch, 1 );
              break;
        };
    }
    return ret;
}

static int httpurl_needs_escape( int c )
{
    /*
      Definition of "safe" and "unsafe" chars
      was taken from:

      http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4029/
    */
    return ( (c >= 32 && c <=47)
             || ( c>=58 && c<=64)
             || ( c>=91 && c<=96)
             || ( c>=123 && c<=126)
             || ( c<32 || c>=127)
             );
}

/**
   The handler for the etURLENCODE specifier.

   It expects varg to be a string value, which it will preceed to
   encode using an URL encoding algothrim (certain characters are
   converted to %XX, where XX is their hex value) and passes the
   encoded string to pf(). It returns the total length of the output
   string.
 */
static long spech_urlencode( cwal_printf_appender pf,
                             void * pfArg,
                             unsigned int pfLen,
                             void * varg )
{
    char const * str = (char const *) varg;
    long ret = 0;
    char ch = 0;
    char const * hex = "0123456789ABCDEF";
#define xbufsz 10
    char xbuf[xbufsz];
    int slen = 0;
    if( ! str ) return 0;
    memset( xbuf, 0, xbufsz );
    ch = *str;
#define xbufsz 10
    slen = 0;
    for( ; ch; ch = *(++str) )
    {
        if( ! httpurl_needs_escape( ch ) )
        {
            ret += pf( pfArg, str, 1 );
            continue;
        }
        else {
            xbuf[0] = '%';
            xbuf[1] = hex[((ch>>4)&0xf)];
            xbuf[2] = hex[(ch&0xf)];
            xbuf[3] = 0;
            slen = 3;
            ret += pf( pfArg, xbuf, slen );
        }
    }
#undef xbufsz
    return ret;
}

/* 
   hexchar_to_int():

   For 'a'-'f', 'A'-'F' and '0'-'9', returns the appropriate decimal
   number.  For any other character it returns -1.
    */
static int hexchar_to_int( int ch )
{
    if( (ch>='a' && ch<='f') ) return ch-'a'+10;
    else if( (ch>='A' && ch<='F') ) return ch-'A'+10;
    else if( (ch>='0' && ch<='9') ) return ch-'0';
    return -1;
}

/**
   The handler for the etURLDECODE specifier.

   It expects varg to be a ([const] char *), possibly encoded
   with URL encoding. It decodes the string using a URL decode
   algorithm and passes the decoded string to
   pf(). It returns the total length of the output string.
   If the input string contains malformed %XX codes then this
   function will return prematurely.
 */
static long spech_urldecode( cwal_printf_appender pf,
                             void * pfArg,
                             unsigned int pfLen,
                             void * varg )
{
    char const * str = (char const *) varg;
    long ret = 0;
    int ch = 0;
    int ch2 = 0;
    char xbuf[4];
    int decoded;
    if( ! str ) return 0;
    ch = *str;
    while( ch )
    {
        if( ch == '%' )
        {
            ch = *(++str);
            ch2 = *(++str);
            if( isxdigit(ch) &&
                isxdigit(ch2) )
            {
                decoded = (hexchar_to_int( ch ) * 16)
                    + hexchar_to_int( ch2 );
                xbuf[0] = (char)decoded;
                xbuf[1] = 0;
                ret += pf( pfArg, xbuf, 1 );
                ch = *(++str);
                continue;
            }
            else
            {
                xbuf[0] = '%';
                xbuf[1] = ch;
                xbuf[2] = ch2;
                xbuf[3] = 0;
                ret += pf( pfArg, xbuf, 3 );
                ch = *(++str);
                continue;
            }
        }
        else if( ch == '+' )
        {
            xbuf[0] = ' ';
            xbuf[1] = 0;
            ret += pf( pfArg, xbuf, 1 );
            ch = *(++str);
            continue;
        }
        xbuf[0] = ch;
        xbuf[1] = 0;
        ret += pf( pfArg, xbuf, 1 );
        ch = *(++str);
    }
    return ret;
}

#endif /* !WHPRINTF_OMIT_HTML */


#if !WHPRINTF_OMIT_SQL
/**
   Quotes the (char *) varg as an SQL string 'should'
   be quoted. The exact type of the conversion
   is specified by xtype, which must be one of
   etSQLESCAPE, etSQLESCAPE2, or etSQLESCAPE3.

   Search this file for those constants to find
   the associated documentation.
*/
static long spech_sqlstring_main( int xtype,
                                  cwal_printf_appender pf,
                                  void * pfArg,
                                  unsigned int pfLen,
                                  void * varg )
{
    unsigned int i, n;
    int j, ch, isnull;
    int needQuote;
    char q = ((xtype==etSQLESCAPE3)?'"':'\'');   /* Quote character */
    char const * escarg = (char const *) varg;
	char * bufpt = NULL;
    long ret;
    isnull = escarg==0;
    if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)");
    for(i=n=0; (i<pfLen) && ((ch=escarg[i])!=0); ++i){
        if( ch==q ) ++n;
    }
    needQuote = !isnull && xtype==etSQLESCAPE2;
    n += i + 1 + needQuote*2;
    /* FIXME: use a static buffer here instead of malloc()! Shame on you! */
    bufpt = (char *)malloc( n );
    if( ! bufpt ) return -1;
    j = 0;
    if( needQuote ) bufpt[j++] = q;
    for(i=0; (ch=escarg[i])!=0; i++){
        bufpt[j++] = ch;
        if( ch==q ) bufpt[j++] = ch;
    }
    if( needQuote ) bufpt[j++] = q;
    bufpt[j] = 0;
    ret = pf( pfArg, bufpt, j );
    free( bufpt );
	return ret;
}

static long spech_sqlstring1( cwal_printf_appender pf,
                              void * pfArg,
                              unsigned int pfLen,
                              void * varg )
{
	return spech_sqlstring_main( etSQLESCAPE, pf, pfArg, pfLen, varg );
}

static long spech_sqlstring2( cwal_printf_appender pf,
                              void * pfArg,
                              unsigned int pfLen,
                              void * varg )
{
	return spech_sqlstring_main( etSQLESCAPE2, pf, pfArg, pfLen, varg );
}

static long spech_sqlstring3( cwal_printf_appender pf,
                              void * pfArg,
                              unsigned int pfLen,
                              void * varg )
{
	return spech_sqlstring_main( etSQLESCAPE3, pf, pfArg, pfLen, varg );
}

#endif /* !WHPRINTF_OMIT_SQL */

				      

/*
   The root printf program.  All variations call this core.  It
   implements most of the common printf behaviours plus (optionally)
   some extended ones.

   INPUTS:

     pfAppend : The is a cwal_printf_appender function which is responsible
     for accumulating the output. If pfAppend returns a negative integer
     then processing stops immediately.

     pfAppendArg : is ignored by this function but passed as the first
     argument to pfAppend. pfAppend will presumably use it as a data
     store for accumulating its string.

     fmt : This is the format string, as in the usual printf().

     ap : This is a pointer to a list of arguments.  Same as in
     vprintf() and friends.

   OUTPUTS:

   The return value is the total number of characters sent to the
   function "func".  Returns -1 on a error.

   Note that the order in which automatic variables are declared below
   seems to make a big difference in determining how fast this beast
   will run.

   Much of this code dates back to the early 1980's, supposedly.

   Known change history (most historic info has been lost):

   10 Feb 2008 by Stephan Beal: refactored to remove the 'useExtended'
   flag (which is now always on). Added the cwal_printf_appender typedef to
   make this function generic enough to drop into other source trees
   without much work.

   31 Oct 2008 by Stephan Beal: refactored the et_info lookup to be
   constant-time instead of linear.
*/
long cwal_printfv(
  cwal_printf_appender pfAppend,          /* Accumulate results here */
  void * pfAppendArg,                /* Passed as first arg to pfAppend. */
  const char *fmt,                   /* Format string */
  va_list ap                         /* arguments */
){
    /**
       HISTORIC NOTE (author and year unknown):

       Note that the order in which automatic variables are declared below
       seems to make a big difference in determining how fast this beast
       will run.
    */

#if WHPRINTF_FMTINFO_FIXED
  const int useExtended = 1; /* Allow extended %-conversions */
#endif
  long outCount = 0;          /* accumulated output count */
  int pfrc = 0;              /* result from calling pfAppend */
  int c;                     /* Next character in the format string */
  char *bufpt = 0;           /* Pointer to the conversion buffer */
  int precision;             /* Precision of the current field */
  int length;                /* Length of the field */
  int idx;                   /* A general purpose loop counter */
  int width;                 /* Width of the current field */
  etByte flag_leftjustify;   /* True if "-" flag is present */
  etByte flag_plussign;      /* True if "+" flag is present */
  etByte flag_blanksign;     /* True if " " flag is present */
  etByte flag_alternateform; /* True if "#" flag is present */
  etByte flag_altform2;      /* True if "!" flag is present */
  etByte flag_zeropad;       /* True if field width constant starts with zero */
  etByte flag_long;          /* True if "l" flag is present */
  etByte flag_longlong;      /* True if the "ll" flag is present */
  etByte done;               /* Loop termination flag */
  uint64_t longvalue;   /* Value for integer types */
  LONGDOUBLE_TYPE realvalue; /* Value for real types */
  const et_info *infop = 0;      /* Pointer to the appropriate info structure */
  char buf[WHPRINTF_BUF_SIZE];       /* Conversion buffer */
  char prefix;               /* Prefix character.  "+" or "-" or " " or '\0'. */
  etByte xtype = 0;              /* Conversion paradigm */
  char * zExtra = 0;              /* Extra memory used for etTCLESCAPE conversions */
#if ! WHPRINTF_OMIT_FLOATING_POINT
  int  exp, e2;              /* exponent of real numbers */
  double rounder;            /* Used for rounding floating point values */
  etByte flag_dp;            /* True if decimal point should be shown */
  etByte flag_rtz;           /* True if trailing zeros should be removed */
  etByte flag_exp;           /* True to force display of the exponent */
  int nsd;                   /* Number of significant digits returned */
#endif


  /* WHPRINTF_RETURN, WHPRINTF_CHECKERR, and WHPRINTF_SPACES
     are internal helpers.
  */
#define WHPRINTF_RETURN if( zExtra ) free(zExtra); return outCount;
#if WHPRINTF_HAVE_VARARRAY
  /*
    This impl possibly mallocs
  */
#define WHPRINTF_CHECKERR(FREEME) if( pfrc<0 ) { WHPRINTF_CHARARRAY_FREE(FREEME); WHPRINTF_RETURN; } else outCount += pfrc
#define WHPRINTF_SPACES(N) \
if(1){				       \
    WHPRINTF_CHARARRAY(zSpaces,N);		      \
    memset( zSpaces,' ',N);			      \
    pfrc = pfAppend(pfAppendArg, zSpaces, N);	      \
    WHPRINTF_CHECKERR(zSpaces);			      \
    WHPRINTF_CHARARRAY_FREE(zSpaces);		      \
}(void)0
#else
  /* But this one is subject to potential wrong output
     on "unusual" inputs.
     FIXME: turn this into a loop on N,
     with a much buffer size, so we can do away
     with the limit.

     reminder to self: libfossil's fork uses a similar, arguably
     better, approach, which falls back to allocating.
  */
#define WHPRINTF_CHECKERR(FREEME) if( pfrc<0 ) { WHPRINTF_CHARARRAY_FREE(FREEME); WHPRINTF_RETURN; } else outCount += pfrc
#define WHPRINTF_SPACES(N) \
if(1){				       \
    enum { BufSz = 128 }; \
    char zSpaces[BufSz]; \
    int n = ((N)>=BufSz) ? BufSz : (int)N;    \
    memset( zSpaces,' ',n);			      \
    pfrc = pfAppend(pfAppendArg, zSpaces, n);       \
    WHPRINTF_CHECKERR(0);                           \
}(void)0
#endif

  length = 0;
  bufpt = 0;
  for(; (c=(*fmt))!=0; ++fmt){
    if( c!='%' ){
      int amt;
      bufpt = (char *)fmt;
      amt = 1;
      while( (c=(*++fmt))!='%' && c!=0 ) amt++;
      pfrc = pfAppend( pfAppendArg, bufpt, amt);
      WHPRINTF_CHECKERR(0);
      if( c==0 ) break;
    }
    if( (c=(*++fmt))==0 ){
      pfrc = pfAppend( pfAppendArg, "%", 1);
      WHPRINTF_CHECKERR(0);
      break;
    }
    /* Find out what flags are present */
    flag_leftjustify = flag_plussign = flag_blanksign = 
     flag_alternateform = flag_altform2 = flag_zeropad = 0;
    done = 0;
    do{
      switch( c ){
        case '-':   flag_leftjustify = 1;     break;
        case '+':   flag_plussign = 1;        break;
        case ' ':   flag_blanksign = 1;       break;
        case '#':   flag_alternateform = 1;   break;
        case '!':   flag_altform2 = 1;        break;
        case '0':   flag_zeropad = 1;         break;
        default:    done = 1;                 break;
      }
    }while( !done && (c=(*++fmt))!=0 );
    /* Get the field width */
    width = 0;
    if( c=='*' ){
      width = va_arg(ap,int);
      if( width<0 ){
        flag_leftjustify = 1;
        width = -width;
      }
      c = *++fmt;
    }else{
      while( c>='0' && c<='9' ){
        width = width*10 + c - '0';
        c = *++fmt;
      }
    }
    if( width > WHPRINTF_BUF_SIZE-10 ){
      width = WHPRINTF_BUF_SIZE-10;
    }
    /* Get the precision */
    if( c=='.' ){
      precision = 0;
      c = *++fmt;
      if( c=='*' ){
        precision = va_arg(ap,int);
        if( precision<0 ) precision = -precision;
        c = *++fmt;
      }else{
        while( c>='0' && c<='9' ){
          precision = precision*10 + c - '0';
          c = *++fmt;
        }
      }
    }else{
      precision = -1;
    }
    /* Get the conversion type modifier */
    if( c=='l' ){
      flag_long = 1;
      c = *++fmt;
      if( c=='l' ){
        flag_longlong = 1;
        c = *++fmt;
      }else{
        flag_longlong = 0;
      }
    }else{
      flag_long = flag_longlong = 0;
    }
    /* Fetch the info entry for the field */
    infop = 0;
#if WHPRINTF_FMTINFO_FIXED
    for(idx=0; idx<etNINFO; idx++){
      if( c==fmtinfo[idx].fmttype ){
        infop = &fmtinfo[idx];
        if( useExtended || (infop->flags & FLAG_EXTENDED)==0 ){
          xtype = infop->type;
        }else{
	    WHPRINTF_RETURN;
        }
        break;
      }
    }
#else
#define FMTNDX(N) (N - fmtinfo[0].fmttype)
#define FMTINFO(N) (fmtinfo[ FMTNDX(N) ])
    infop = ((c>=(fmtinfo[0].fmttype)) && (c<fmtinfo[etNINFO-1].fmttype))
	? &FMTINFO(c)
	: 0;
    /*fprintf(stderr,"char '%c'/%d @ %d,  type=%c/%d\n",c,c,FMTNDX(c),infop->fmttype,infop->type);*/
    if( infop ) xtype = infop->type;
#undef FMTINFO
#undef FMTNDX
#endif /* WHPRINTF_FMTINFO_FIXED */
    zExtra = 0;
    if( (!infop) || (!infop->type) ){
	WHPRINTF_RETURN;
    }


    /* Limit the precision to prevent overflowing buf[] during conversion */
    if( precision>WHPRINTF_BUF_SIZE-40 && (infop->flags & FLAG_STRING)==0 ){
      precision = WHPRINTF_BUF_SIZE-40;
    }

    /*
       At this point, variables are initialized as follows:
    **
         flag_alternateform          TRUE if a '#' is present.
         flag_altform2               TRUE if a '!' is present.
         flag_plussign               TRUE if a '+' is present.
         flag_leftjustify            TRUE if a '-' is present or if the
                                     field width was negative.
         flag_zeropad                TRUE if the width began with 0.
         flag_long                   TRUE if the letter 'l' (ell) prefixed
                                     the conversion character.
         flag_longlong               TRUE if the letter 'll' (ell ell) prefixed
                                     the conversion character.
         flag_blanksign              TRUE if a ' ' is present.
         width                       The specified field width.  This is
                                     always non-negative.  Zero is the default.
         precision                   The specified precision.  The default
                                     is -1.
         xtype                       The class of the conversion.
         infop                       Pointer to the appropriate info struct.
    */
    switch( xtype ){
      case etPOINTER:
        flag_longlong = sizeof(char*)==sizeof(int64_t);
        flag_long = sizeof(char*)==sizeof(long int);
        /* Fall through into the next case */
      case etORDINAL:
      case etRADIX:
        if( infop->flags & FLAG_SIGNED ){
          int64_t v;
          if( flag_longlong )   v = va_arg(ap,int64_t);
          else if( flag_long )  v = va_arg(ap,long int);
          else                  v = va_arg(ap,int);
          if( v<0 ){
            longvalue = -v;
            prefix = '-';
          }else{
            longvalue = v;
            if( flag_plussign )        prefix = '+';
            else if( flag_blanksign )  prefix = ' ';
            else                       prefix = 0;
          }
        }else{
          if( flag_longlong )   longvalue = va_arg(ap,uint64_t);
          else if( flag_long )  longvalue = va_arg(ap,unsigned long int);
          else                  longvalue = va_arg(ap,unsigned int);
          prefix = 0;
        }
        if( longvalue==0 ) flag_alternateform = 0;
        if( flag_zeropad && precision<width-(prefix!=0) ){
          precision = width-(prefix!=0);
        }
        bufpt = &buf[WHPRINTF_BUF_SIZE-1];
        if( xtype==etORDINAL ){
	    /** i sure would like to shake the hand of whoever figured this out: */
          static const char zOrd[] = "thstndrd";
          int x = longvalue % 10;
          if( x>=4 || (longvalue/10)%10==1 ){
            x = 0;
          }
          buf[WHPRINTF_BUF_SIZE-3] = zOrd[x*2];
          buf[WHPRINTF_BUF_SIZE-2] = zOrd[x*2+1];
          bufpt -= 2;
        }
        {
          const char *cset;
          int base;
          cset = &aDigits[infop->charset];
          base = infop->base;
          do{                                           /* Convert to ascii */
            *(--bufpt) = cset[longvalue%base];
            longvalue = longvalue/base;
          }while( longvalue>0 );
        }
        length = &buf[WHPRINTF_BUF_SIZE-1]-bufpt;
        for(idx=precision-length; idx>0; idx--){
          *(--bufpt) = '0';                             /* Zero pad */
        }
        if( prefix ) *(--bufpt) = prefix;               /* Add sign */
        if( flag_alternateform && infop->prefix ){      /* Add "0" or "0x" */
          const char *pre;
          char x;
          pre = &aPrefix[infop->prefix];
          if( *bufpt!=pre[0] ){
            for(; (x=(*pre))!=0; pre++) *(--bufpt) = x;
          }
        }
        length = &buf[WHPRINTF_BUF_SIZE-1]-bufpt;
        break;
      case etFLOAT:
      case etEXP:
      case etGENERIC:
        realvalue = va_arg(ap,double);
#if ! WHPRINTF_OMIT_FLOATING_POINT
        if( precision<0 ) precision = 6;         /* Set default precision */
        if( precision>WHPRINTF_BUF_SIZE/2-10 ) precision = WHPRINTF_BUF_SIZE/2-10;
        if( realvalue<0.0 ){
          realvalue = -realvalue;
          prefix = '-';
        }else{
          if( flag_plussign )          prefix = '+';
          else if( flag_blanksign )    prefix = ' ';
          else                         prefix = 0;
        }
        if( xtype==etGENERIC && precision>0 ) precision--;
#if 0
        /* Rounding works like BSD when the constant 0.4999 is used.  Wierd! */
        for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1);
#else
        /* It makes more sense to use 0.5 */
        for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){}
#endif
        if( xtype==etFLOAT ) realvalue += rounder;
        /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
        exp = 0;
#if 1
	if( (realvalue)!=(realvalue) ){
	    /* from sqlite3: #define sqlite3_isnan(X)  ((X)!=(X)) */
	    /* This weird array thing is to avoid constness violations
	       when assinging, e.g. "NaN" to bufpt.
	    */
	    static char NaN[4] = {'N','a','N','\0'};
	    bufpt = NaN;
          length = 3;
          break;
        }
#endif
        if( realvalue>0.0 ){
          while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; }
          while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
          while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
          while( realvalue<1e-8 && exp>=-350 ){ realvalue *= 1e8; exp-=8; }
          while( realvalue<1.0 && exp>=-350 ){ realvalue *= 10.0; exp--; }
          if( exp>350 || exp<-350 ){
            if( prefix=='-' ){
		static char Inf[5] = {'-','I','n','f','\0'};
		bufpt = Inf;
            }else if( prefix=='+' ){
		static char Inf[5] = {'+','I','n','f','\0'};
		bufpt = Inf;
            }else{
		static char Inf[4] = {'I','n','f','\0'};
		bufpt = Inf;
            }
            length = strlen(bufpt);
            break;
          }
        }
        bufpt = buf;
        /*
           If the field type is etGENERIC, then convert to either etEXP
           or etFLOAT, as appropriate.
        */
        flag_exp = xtype==etEXP;
        if( xtype!=etFLOAT ){
          realvalue += rounder;
          if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
        }
        if( xtype==etGENERIC ){
          flag_rtz = !flag_alternateform;
          if( exp<-4 || exp>precision ){
            xtype = etEXP;
          }else{
            precision = precision - exp;
            xtype = etFLOAT;
          }
        }else{
          flag_rtz = 0;
        }
        if( xtype==etEXP ){
          e2 = 0;
        }else{
          e2 = exp;
        }
        nsd = 0;
        flag_dp = (precision>0) | flag_alternateform | flag_altform2;
        /* The sign in front of the number */
        if( prefix ){
          *(bufpt++) = prefix;
        }
        /* Digits prior to the decimal point */
        if( e2<0 ){
          *(bufpt++) = '0';
        }else{
          for(; e2>=0; e2--){
            *(bufpt++) = et_getdigit(&realvalue,&nsd);
          }
        }
        /* The decimal point */
        if( flag_dp ){
          *(bufpt++) = '.';
        }
        /* "0" digits after the decimal point but before the first
           significant digit of the number */
        for(e2++; e2<0 && precision>0; precision--, e2++){
          *(bufpt++) = '0';
        }
        /* Significant digits after the decimal point */
        while( (precision--)>0 ){
          *(bufpt++) = et_getdigit(&realvalue,&nsd);
        }
        /* Remove trailing zeros and the "." if no digits follow the "." */
        if( flag_rtz && flag_dp ){
          while( bufpt[-1]=='0' ) *(--bufpt) = 0;
          /* assert( bufpt>buf ); */
          if( bufpt[-1]=='.' ){
            if( flag_altform2 ){
              *(bufpt++) = '0';
            }else{
              *(--bufpt) = 0;
            }
          }
        }
        /* Add the "eNNN" suffix */
        if( flag_exp || (xtype==etEXP && exp) ){
          *(bufpt++) = aDigits[infop->charset];
          if( exp<0 ){
            *(bufpt++) = '-'; exp = -exp;
          }else{
            *(bufpt++) = '+';
          }
          if( exp>=100 ){
            *(bufpt++) = (exp/100)+'0';                /* 100's digit */
            exp %= 100;
          }
          *(bufpt++) = exp/10+'0';                     /* 10's digit */
          *(bufpt++) = exp%10+'0';                     /* 1's digit */
        }
        *bufpt = 0;

        /* The converted number is in buf[] and zero terminated. Output it.
           Note that the number is in the usual order, not reversed as with
           integer conversions. */
        length = bufpt-buf;
        bufpt = buf;

        /* Special case:  Add leading zeros if the flag_zeropad flag is
           set and we are not left justified */
        if( flag_zeropad && !flag_leftjustify && length < width){
          int i;
          int nPad = width - length;
          for(i=width; i>=nPad; i--){
            bufpt[i] = bufpt[i-nPad];
          }
          i = prefix!=0;
          while( nPad-- ) bufpt[i++] = '0';
          length = width;
        }
#endif /* !WHPRINTF_OMIT_FLOATING_POINT */
        break;
#if !WHPRINTF_OMIT_SIZE
      case etSIZE:
        *(va_arg(ap,int*)) = outCount;
        length = width = 0;
        break;
#endif
      case etPERCENT:
        buf[0] = '%';
        bufpt = buf;
        length = 1;
        break;
      case etCHARLIT:
      case etCHARX:
        c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt);
        if( precision>=0 ){
          for(idx=1; idx<precision; idx++) buf[idx] = c;
          length = precision;
        }else{
          length =1;
        }
        bufpt = buf;
        break;
      case etSTRING: {
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
          if( precision>=0 && precision<length ) length = precision;
          break;
      }
      case etDYNSTRING: {
          /* etDYNSTRING needs to be handled separately because it
             free()s its argument (which isn't available outside this
             block). This means, though, that %-#z does not work.
          */
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
          pfrc = spech_dynstring( pfAppend, pfAppendArg,
                                  (precision<length) ? precision : length,
                                  bufpt );
          WHPRINTF_CHECKERR(0);
          length = 0;
          break;
      }
#if ! WHPRINTF_OMIT_HTML
      case etHTML:{
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
          pfrc = spech_string_to_html( pfAppend, pfAppendArg,
                                       (precision<length) ? precision : length,
                                       bufpt );
          WHPRINTF_CHECKERR(0);
          length = 0;
          break;
      }
      case etURLENCODE:{
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
          pfrc = spech_urlencode( pfAppend, pfAppendArg,
                                  (precision<length) ? precision : length,
                                  bufpt );
          WHPRINTF_CHECKERR(0);
          length = 0;
          break;
      }
      case etURLDECODE:{
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
          pfrc = spech_urldecode( pfAppend, pfAppendArg,
                                  (precision<length) ? precision : length,
                                  bufpt );
          WHPRINTF_CHECKERR(0);
          length = 0;
          break;
      }
#endif /* WHPRINTF_OMIT_HTML */
#if ! WHPRINTF_OMIT_SQL
      case etSQLESCAPE:
      case etSQLESCAPE2:
      case etSQLESCAPE3: {
          cwal_printf_spec_handler spf =
              (xtype==etSQLESCAPE)
              ? spech_sqlstring1
              : ((xtype==etSQLESCAPE2)
                 ? spech_sqlstring2
                 : spech_sqlstring3
                 );
          bufpt = va_arg(ap,char*);
          length = bufpt ? strlen(bufpt) : 0;
          pfrc = spf( pfAppend, pfAppendArg,
                      (precision<length) ? precision : length,
                      bufpt );
          WHPRINTF_CHECKERR(0);
          length = 0;
      }
#endif /* !WHPRINTF_OMIT_SQL */
    }/* End switch over the format type */
    /*
       The text of the conversion is pointed to by "bufpt" and is
       "length" characters long.  The field width is "width".  Do
       the output.
    */
    if( !flag_leftjustify ){
      int nspace;
      nspace = width-length;
      if( nspace>0 ){
	      WHPRINTF_SPACES(nspace);
      }
    }
    if( length>0 ){
      pfrc = pfAppend( pfAppendArg, bufpt, length);
      WHPRINTF_CHECKERR(0);
    }
    if( flag_leftjustify ){
      int nspace;
      nspace = width-length;
      if( nspace>0 ){
	      WHPRINTF_SPACES(nspace);
      }
    }
    if( zExtra ){
      free(zExtra);
      zExtra = 0;
    }
  }/* End for loop over the format string */
  WHPRINTF_RETURN;
} /* End of function */


#undef WHPRINTF_SPACES
#undef WHPRINTF_CHECKERR
#undef WHPRINTF_RETURN
#undef WHPRINTF_OMIT_FLOATING_POINT
#undef WHPRINTF_OMIT_SIZE
#undef WHPRINTF_OMIT_SQL
#undef WHPRINTF_BUF_SIZE
#undef WHPRINTF_OMIT_HTML

long cwal_printf(cwal_printf_appender pfAppend,          /* Accumulate results here */
	    void * pfAppendArg,                /* Passed as first arg to pfAppend. */
	    const char *fmt,                   /* Format string */
	    ... )
{
	va_list vargs;
        long ret;
	va_start( vargs, fmt );
	ret = cwal_printfv( pfAppend, pfAppendArg, fmt, vargs );
	va_end(vargs);
	return ret;
}


long cwal_printf_FILE_appender( void * a, char const * s, long n )
{
	FILE * fp = (FILE *)a;
	if( ! fp ) return -1;
        else
        {
            long ret = fwrite( s, sizeof(char), n, fp );
            return (ret >= 0) ? ret : -2;
        }
}

long cwal_printfv_FILE( FILE * fp, char const * fmt, va_list vargs ){
	return cwal_printfv( cwal_printf_FILE_appender, fp, fmt, vargs );
}

long cwal_printf_FILE( FILE * fp, char const * fmt, ... )
{
	va_list vargs;
    int ret;
	va_start( vargs, fmt );
	ret = cwal_printfv( cwal_printf_FILE_appender, fp, fmt, vargs );
	va_end(vargs);
	return ret;
}

/**
   Internal implementation details for cwal_printfv_appender_stringbuf.
*/
typedef struct cwal_printfv_stringbuf
{
    /** dynamically allocated buffer */
    char * buffer;
    /** bytes allocated to buffer */
    size_t alloced;
    /** Current position within buffer. */
    size_t pos;
} cwal_printfv_stringbuf;
static const cwal_printfv_stringbuf cwal_printfv_stringbuf_init = { 0, 0, 0 };

/**
   A cwal_printfv_appender implementation which requires arg to be a
   (cwal_printfv_stringbuf*). It appends n bytes of data to the
   cwal_printfv_stringbuf object's buffer, reallocating it as
   needed. Returns less than 0 on error, else the number of bytes
   appended to the buffer. The buffer will always be null terminated.
*/
static long cwal_printfv_appender_stringbuf( void * arg, char const * data, long n )
{
    cwal_printfv_stringbuf * sb = (cwal_printfv_stringbuf*)arg;
    if( ! sb || (n<0) ) return -1;
    else if( ! n ) return 0;
    else
    {
        long rc;
        size_t npos = sb->pos + n;
        if( npos >= sb->alloced )
        {
            const size_t asz = (3 * npos / 2) + 1;
            if( asz < npos ) return -1; /* overflow */
            else
            {
                char * buf = (char *)realloc( sb->buffer, asz );
                if( ! buf ) return -1;
                memset( buf + sb->pos, 0, (npos + 1 - sb->pos) ); /* the +1 adds our NUL for us*/
                sb->buffer = buf;
                sb->alloced = asz;
            }
        }
        rc = 0;
        for( ; rc < n; ++rc, ++sb->pos )
        {
            sb->buffer[sb->pos] = data[rc];
        }
        return rc;
    }
}


char * cwal_printfv_cstr( char const * fmt, va_list vargs )
{
    if( ! fmt ) return 0;
    else
    {
        cwal_printfv_stringbuf sb = cwal_printfv_stringbuf_init;
        long rc = cwal_printfv( cwal_printfv_appender_stringbuf, &sb, fmt, vargs );
        if( rc <= 0 )
        {
            free( sb.buffer );
            sb.buffer = 0;
        }
        return sb.buffer;
    }
}

char * cwal_printf_cstr( char const * fmt, ... )
{
    va_list vargs;
    char * ret;
    va_start( vargs, fmt );
    ret = cwal_printfv_cstr( fmt, vargs );
    va_end( vargs );
    return ret;
}
/* end of file cwal_printf.c */
/* start of file cwal_utf.c */
#include <assert.h>
#include <string.h> /* for a single sprintf() need :/ */
#include <ctype.h> /* toupper(), tolower() */

/**
   Parts of the UTF code was originally taken from sqlite3's
   public-domain source code (http://sqlite.org), modified only
   slightly for use here. This code generates some "possible data
   loss" warnings on MSC, but if this code is good enough for sqlite3
   then it's damned well good enough for me, so we disable that
   warning for MSC builds.
*/
#ifdef _MSC_VER
#  if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
#    define PUSHED_MSC_WARNING
#    pragma warning( push )
#    pragma warning(disable:4244) /* complaining about data loss due
                                     to integer precision in the
                                     sqlite3 utf decoding routines */
#  endif
#endif

/*
** This lookup table is used to help decode the first byte of
** a multi-byte UTF8 character.
*/
static const unsigned char cwal_utfTrans1[] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
  0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
  0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00
};

int cwal_utf8_read_char(
  const unsigned char *zIn,       /* First byte of UTF-8 character */
  const unsigned char *zTerm,     /* Pretend this byte is 0x00 */
  const unsigned char **pzNext    /* Write first byte past UTF-8 char here */
){
  int c;
  c = *(zIn++);
  if( c>=0xc0 ){
    c = cwal_utfTrans1[c-0xc0];
    while( zIn!=zTerm && (*zIn & 0xc0)==0x80 )
      c = (c<<6) + (0x3f & *(zIn++));
    if( c<0x80
        || (c&0xFFFFF800)==0xD800
        || (c&0xFFFFFFFE)==0xFFFE ) c = 0xFFFD;
  }
  *pzNext = zIn;
  return c;
}

cwal_size_t cwal_cstr_length_utf8( unsigned char const * str, cwal_size_t len ){
    if( ! str || !len ) return 0;
    else{
        char unsigned const * pos = str;
        char unsigned const * end = pos + len;
        cwal_size_t rc = 0;
        for( ; (pos < end) && cwal_utf8_read_char(pos, end, &pos);
             ++rc )
        {}
        return rc;
    }
}


cwal_size_t cwal_string_length_utf8( cwal_string const * str ){
    return str
        ? cwal_cstr_length_utf8( (char unsigned const *)cwal_string_cstr(str),
                                 cwal_string_length_bytes(str) )
        : 0;
}

int cwal_utf8_char_to_cstr(unsigned int c, unsigned char *output, cwal_size_t length){
    /*
      Stolen from the internet, adapted from several variations which
      all _seem_ to have derived from librdf.
    */
    cwal_size_t size=0;

    /* check for illegal code positions:
     * U+D800 to U+DFFF (UTF-16 surrogates)
     * U+FFFE and U+FFFF
     */
    if((c > 0xD7FF && c < 0xE000)
       || c == 0xFFFE || c == 0xFFFF) return -1;
    
    /* Unicode 3.2 only defines U+0000 to U+10FFFF and UTF-8 encodings of it */
    if(c > 0x10ffff) return -1;
    
    if(!c) size = 0;
    else if (c < 0x00000080) size = 1;
    else if (c < 0x00000800) size = 2;
    else if (c < 0x00010000) size = 3;
    else size = 4;
    if(!output) return size;
    else if(size > length) return -1;
    else switch(size) {
      case 0:
          output[0] = 0;
          return 0;
      case 4:
          output[3] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0x10000;
          /* fall through */
      case 3:
          output[2] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0x800;
          /* fall through */
      case 2:
          output[1] = 0x80 | (c & 0x3F);
          c = c >> 6;
          c |= 0xc0; 
          /* fall through */
      case 1:
          output[0] = (unsigned char)c;
          /* keep falling... */
      default:
          return size;
    }
}


int cwal_utf8_char_tolower( int ch ){
    /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt */
  switch(ch){
    case 0x0041: return 0x0061;
    case 0x0042: return 0x0062;
    case 0x0043: return 0x0063;
    case 0x0044: return 0x0064;
    case 0x0045: return 0x0065;
    case 0x0046: return 0x0066;
    case 0x0047: return 0x0067;
    case 0x0048: return 0x0068;
    case 0x0049: return 0x0069;
    case 0x004A: return 0x006A;
    case 0x004B: return 0x006B;
    case 0x004C: return 0x006C;
    case 0x004D: return 0x006D;
    case 0x004E: return 0x006E;
    case 0x004F: return 0x006F;
    case 0x0050: return 0x0070;
    case 0x0051: return 0x0071;
    case 0x0052: return 0x0072;
    case 0x0053: return 0x0073;
    case 0x0054: return 0x0074;
    case 0x0055: return 0x0075;
    case 0x0056: return 0x0076;
    case 0x0057: return 0x0077;
    case 0x0058: return 0x0078;
    case 0x0059: return 0x0079;
    case 0x005A: return 0x007A;
    case 0x00C0: return 0x00E0;
    case 0x00C1: return 0x00E1;
    case 0x00C2: return 0x00E2;
    case 0x00C3: return 0x00E3;
    case 0x00C4: return 0x00E4;
    case 0x00C5: return 0x00E5;
    case 0x00C6: return 0x00E6;
    case 0x00C7: return 0x00E7;
    case 0x00C8: return 0x00E8;
    case 0x00C9: return 0x00E9;
    case 0x00CA: return 0x00EA;
    case 0x00CB: return 0x00EB;
    case 0x00CC: return 0x00EC;
    case 0x00CD: return 0x00ED;
    case 0x00CE: return 0x00EE;
    case 0x00CF: return 0x00EF;
    case 0x00D0: return 0x00F0;
    case 0x00D1: return 0x00F1;
    case 0x00D2: return 0x00F2;
    case 0x00D3: return 0x00F3;
    case 0x00D4: return 0x00F4;
    case 0x00D5: return 0x00F5;
    case 0x00D6: return 0x00F6;
    case 0x00D8: return 0x00F8;
    case 0x00D9: return 0x00F9;
    case 0x00DA: return 0x00FA;
    case 0x00DB: return 0x00FB;
    case 0x00DC: return 0x00FC;
    case 0x00DD: return 0x00FD;
    case 0x00DE: return 0x00FE;
    case 0x0100: return 0x0101;
    case 0x0102: return 0x0103;
    case 0x0104: return 0x0105;
    case 0x0106: return 0x0107;
    case 0x0108: return 0x0109;
    case 0x010A: return 0x010B;
    case 0x010C: return 0x010D;
    case 0x010E: return 0x010F;
    case 0x0110: return 0x0111;
    case 0x0112: return 0x0113;
    case 0x0114: return 0x0115;
    case 0x0116: return 0x0117;
    case 0x0118: return 0x0119;
    case 0x011A: return 0x011B;
    case 0x011C: return 0x011D;
    case 0x011E: return 0x011F;
    case 0x0120: return 0x0121;
    case 0x0122: return 0x0123;
    case 0x0124: return 0x0125;
    case 0x0126: return 0x0127;
    case 0x0128: return 0x0129;
    case 0x012A: return 0x012B;
    case 0x012C: return 0x012D;
    case 0x012E: return 0x012F;
    case 0x0130: return 0x0069;
    case 0x0132: return 0x0133;
    case 0x0134: return 0x0135;
    case 0x0136: return 0x0137;
    case 0x0139: return 0x013A;
    case 0x013B: return 0x013C;
    case 0x013D: return 0x013E;
    case 0x013F: return 0x0140;
    case 0x0141: return 0x0142;
    case 0x0143: return 0x0144;
    case 0x0145: return 0x0146;
    case 0x0147: return 0x0148;
    case 0x014A: return 0x014B;
    case 0x014C: return 0x014D;
    case 0x014E: return 0x014F;
    case 0x0150: return 0x0151;
    case 0x0152: return 0x0153;
    case 0x0154: return 0x0155;
    case 0x0156: return 0x0157;
    case 0x0158: return 0x0159;
    case 0x015A: return 0x015B;
    case 0x015C: return 0x015D;
    case 0x015E: return 0x015F;
    case 0x0160: return 0x0161;
    case 0x0162: return 0x0163;
    case 0x0164: return 0x0165;
    case 0x0166: return 0x0167;
    case 0x0168: return 0x0169;
    case 0x016A: return 0x016B;
    case 0x016C: return 0x016D;
    case 0x016E: return 0x016F;
    case 0x0170: return 0x0171;
    case 0x0172: return 0x0173;
    case 0x0174: return 0x0175;
    case 0x0176: return 0x0177;
    case 0x0178: return 0x00FF;
    case 0x0179: return 0x017A;
    case 0x017B: return 0x017C;
    case 0x017D: return 0x017E;
    case 0x0181: return 0x0253;
    case 0x0182: return 0x0183;
    case 0x0184: return 0x0185;
    case 0x0186: return 0x0254;
    case 0x0187: return 0x0188;
    case 0x0189: return 0x0256;
    case 0x018A: return 0x0257;
    case 0x018B: return 0x018C;
    case 0x018E: return 0x01DD;
    case 0x018F: return 0x0259;
    case 0x0190: return 0x025B;
    case 0x0191: return 0x0192;
    case 0x0193: return 0x0260;
    case 0x0194: return 0x0263;
    case 0x0196: return 0x0269;
    case 0x0197: return 0x0268;
    case 0x0198: return 0x0199;
    case 0x019C: return 0x026F;
    case 0x019D: return 0x0272;
    case 0x019F: return 0x0275;
    case 0x01A0: return 0x01A1;
    case 0x01A2: return 0x01A3;
    case 0x01A4: return 0x01A5;
    case 0x01A6: return 0x0280;
    case 0x01A7: return 0x01A8;
    case 0x01A9: return 0x0283;
    case 0x01AC: return 0x01AD;
    case 0x01AE: return 0x0288;
    case 0x01AF: return 0x01B0;
    case 0x01B1: return 0x028A;
    case 0x01B2: return 0x028B;
    case 0x01B3: return 0x01B4;
    case 0x01B5: return 0x01B6;
    case 0x01B7: return 0x0292;
    case 0x01B8: return 0x01B9;
    case 0x01BC: return 0x01BD;
    case 0x01C4: return 0x01C6;
    case 0x01C5: return 0x01C6;
    case 0x01C7: return 0x01C9;
    case 0x01C8: return 0x01C9;
    case 0x01CA: return 0x01CC;
    case 0x01CB: return 0x01CC;
    case 0x01CD: return 0x01CE;
    case 0x01CF: return 0x01D0;
    case 0x01D1: return 0x01D2;
    case 0x01D3: return 0x01D4;
    case 0x01D5: return 0x01D6;
    case 0x01D7: return 0x01D8;
    case 0x01D9: return 0x01DA;
    case 0x01DB: return 0x01DC;
    case 0x01DE: return 0x01DF;
    case 0x01E0: return 0x01E1;
    case 0x01E2: return 0x01E3;
    case 0x01E4: return 0x01E5;
    case 0x01E6: return 0x01E7;
    case 0x01E8: return 0x01E9;
    case 0x01EA: return 0x01EB;
    case 0x01EC: return 0x01ED;
    case 0x01EE: return 0x01EF;
    case 0x01F1: return 0x01F3;
    case 0x01F2: return 0x01F3;
    case 0x01F4: return 0x01F5;
    case 0x01F6: return 0x0195;
    case 0x01F7: return 0x01BF;
    case 0x01F8: return 0x01F9;
    case 0x01FA: return 0x01FB;
    case 0x01FC: return 0x01FD;
    case 0x01FE: return 0x01FF;
    case 0x0200: return 0x0201;
    case 0x0202: return 0x0203;
    case 0x0204: return 0x0205;
    case 0x0206: return 0x0207;
    case 0x0208: return 0x0209;
    case 0x020A: return 0x020B;
    case 0x020C: return 0x020D;
    case 0x020E: return 0x020F;
    case 0x0210: return 0x0211;
    case 0x0212: return 0x0213;
    case 0x0214: return 0x0215;
    case 0x0216: return 0x0217;
    case 0x0218: return 0x0219;
    case 0x021A: return 0x021B;
    case 0x021C: return 0x021D;
    case 0x021E: return 0x021F;
    case 0x0220: return 0x019E;
    case 0x0222: return 0x0223;
    case 0x0224: return 0x0225;
    case 0x0226: return 0x0227;
    case 0x0228: return 0x0229;
    case 0x022A: return 0x022B;
    case 0x022C: return 0x022D;
    case 0x022E: return 0x022F;
    case 0x0230: return 0x0231;
    case 0x0232: return 0x0233;
    case 0x023A: return 0x2C65;
    case 0x023B: return 0x023C;
    case 0x023D: return 0x019A;
    case 0x023E: return 0x2C66;
    case 0x0241: return 0x0242;
    case 0x0243: return 0x0180;
    case 0x0244: return 0x0289;
    case 0x0245: return 0x028C;
    case 0x0246: return 0x0247;
    case 0x0248: return 0x0249;
    case 0x024A: return 0x024B;
    case 0x024C: return 0x024D;
    case 0x024E: return 0x024F;
    case 0x0370: return 0x0371;
    case 0x0372: return 0x0373;
    case 0x0376: return 0x0377;
    case 0x0386: return 0x03AC;
    case 0x0388: return 0x03AD;
    case 0x0389: return 0x03AE;
    case 0x038A: return 0x03AF;
    case 0x038C: return 0x03CC;
    case 0x038E: return 0x03CD;
    case 0x038F: return 0x03CE;
    case 0x0391: return 0x03B1;
    case 0x0392: return 0x03B2;
    case 0x0393: return 0x03B3;
    case 0x0394: return 0x03B4;
    case 0x0395: return 0x03B5;
    case 0x0396: return 0x03B6;
    case 0x0397: return 0x03B7;
    case 0x0398: return 0x03B8;
    case 0x0399: return 0x03B9;
    case 0x039A: return 0x03BA;
    case 0x039B: return 0x03BB;
    case 0x039C: return 0x03BC;
    case 0x039D: return 0x03BD;
    case 0x039E: return 0x03BE;
    case 0x039F: return 0x03BF;
    case 0x03A0: return 0x03C0;
    case 0x03A1: return 0x03C1;
    case 0x03A3: return 0x03C3;
    case 0x03A4: return 0x03C4;
    case 0x03A5: return 0x03C5;
    case 0x03A6: return 0x03C6;
    case 0x03A7: return 0x03C7;
    case 0x03A8: return 0x03C8;
    case 0x03A9: return 0x03C9;
    case 0x03AA: return 0x03CA;
    case 0x03AB: return 0x03CB;
    case 0x03CF: return 0x03D7;
    case 0x03D8: return 0x03D9;
    case 0x03DA: return 0x03DB;
    case 0x03DC: return 0x03DD;
    case 0x03DE: return 0x03DF;
    case 0x03E0: return 0x03E1;
    case 0x03E2: return 0x03E3;
    case 0x03E4: return 0x03E5;
    case 0x03E6: return 0x03E7;
    case 0x03E8: return 0x03E9;
    case 0x03EA: return 0x03EB;
    case 0x03EC: return 0x03ED;
    case 0x03EE: return 0x03EF;
    case 0x03F4: return 0x03B8;
    case 0x03F7: return 0x03F8;
    case 0x03F9: return 0x03F2;
    case 0x03FA: return 0x03FB;
    case 0x03FD: return 0x037B;
    case 0x03FE: return 0x037C;
    case 0x03FF: return 0x037D;
    case 0x0400: return 0x0450;
    case 0x0401: return 0x0451;
    case 0x0402: return 0x0452;
    case 0x0403: return 0x0453;
    case 0x0404: return 0x0454;
    case 0x0405: return 0x0455;
    case 0x0406: return 0x0456;
    case 0x0407: return 0x0457;
    case 0x0408: return 0x0458;
    case 0x0409: return 0x0459;
    case 0x040A: return 0x045A;
    case 0x040B: return 0x045B;
    case 0x040C: return 0x045C;
    case 0x040D: return 0x045D;
    case 0x040E: return 0x045E;
    case 0x040F: return 0x045F;
    case 0x0410: return 0x0430;
    case 0x0411: return 0x0431;
    case 0x0412: return 0x0432;
    case 0x0413: return 0x0433;
    case 0x0414: return 0x0434;
    case 0x0415: return 0x0435;
    case 0x0416: return 0x0436;
    case 0x0417: return 0x0437;
    case 0x0418: return 0x0438;
    case 0x0419: return 0x0439;
    case 0x041A: return 0x043A;
    case 0x041B: return 0x043B;
    case 0x041C: return 0x043C;
    case 0x041D: return 0x043D;
    case 0x041E: return 0x043E;
    case 0x041F: return 0x043F;
    case 0x0420: return 0x0440;
    case 0x0421: return 0x0441;
    case 0x0422: return 0x0442;
    case 0x0423: return 0x0443;
    case 0x0424: return 0x0444;
    case 0x0425: return 0x0445;
    case 0x0426: return 0x0446;
    case 0x0427: return 0x0447;
    case 0x0428: return 0x0448;
    case 0x0429: return 0x0449;
    case 0x042A: return 0x044A;
    case 0x042B: return 0x044B;
    case 0x042C: return 0x044C;
    case 0x042D: return 0x044D;
    case 0x042E: return 0x044E;
    case 0x042F: return 0x044F;
    case 0x0460: return 0x0461;
    case 0x0462: return 0x0463;
    case 0x0464: return 0x0465;
    case 0x0466: return 0x0467;
    case 0x0468: return 0x0469;
    case 0x046A: return 0x046B;
    case 0x046C: return 0x046D;
    case 0x046E: return 0x046F;
    case 0x0470: return 0x0471;
    case 0x0472: return 0x0473;
    case 0x0474: return 0x0475;
    case 0x0476: return 0x0477;
    case 0x0478: return 0x0479;
    case 0x047A: return 0x047B;
    case 0x047C: return 0x047D;
    case 0x047E: return 0x047F;
    case 0x0480: return 0x0481;
    case 0x048A: return 0x048B;
    case 0x048C: return 0x048D;
    case 0x048E: return 0x048F;
    case 0x0490: return 0x0491;
    case 0x0492: return 0x0493;
    case 0x0494: return 0x0495;
    case 0x0496: return 0x0497;
    case 0x0498: return 0x0499;
    case 0x049A: return 0x049B;
    case 0x049C: return 0x049D;
    case 0x049E: return 0x049F;
    case 0x04A0: return 0x04A1;
    case 0x04A2: return 0x04A3;
    case 0x04A4: return 0x04A5;
    case 0x04A6: return 0x04A7;
    case 0x04A8: return 0x04A9;
    case 0x04AA: return 0x04AB;
    case 0x04AC: return 0x04AD;
    case 0x04AE: return 0x04AF;
    case 0x04B0: return 0x04B1;
    case 0x04B2: return 0x04B3;
    case 0x04B4: return 0x04B5;
    case 0x04B6: return 0x04B7;
    case 0x04B8: return 0x04B9;
    case 0x04BA: return 0x04BB;
    case 0x04BC: return 0x04BD;
    case 0x04BE: return 0x04BF;
    case 0x04C0: return 0x04CF;
    case 0x04C1: return 0x04C2;
    case 0x04C3: return 0x04C4;
    case 0x04C5: return 0x04C6;
    case 0x04C7: return 0x04C8;
    case 0x04C9: return 0x04CA;
    case 0x04CB: return 0x04CC;
    case 0x04CD: return 0x04CE;
    case 0x04D0: return 0x04D1;
    case 0x04D2: return 0x04D3;
    case 0x04D4: return 0x04D5;
    case 0x04D6: return 0x04D7;
    case 0x04D8: return 0x04D9;
    case 0x04DA: return 0x04DB;
    case 0x04DC: return 0x04DD;
    case 0x04DE: return 0x04DF;
    case 0x04E0: return 0x04E1;
    case 0x04E2: return 0x04E3;
    case 0x04E4: return 0x04E5;
    case 0x04E6: return 0x04E7;
    case 0x04E8: return 0x04E9;
    case 0x04EA: return 0x04EB;
    case 0x04EC: return 0x04ED;
    case 0x04EE: return 0x04EF;
    case 0x04F0: return 0x04F1;
    case 0x04F2: return 0x04F3;
    case 0x04F4: return 0x04F5;
    case 0x04F6: return 0x04F7;
    case 0x04F8: return 0x04F9;
    case 0x04FA: return 0x04FB;
    case 0x04FC: return 0x04FD;
    case 0x04FE: return 0x04FF;
    case 0x0500: return 0x0501;
    case 0x0502: return 0x0503;
    case 0x0504: return 0x0505;
    case 0x0506: return 0x0507;
    case 0x0508: return 0x0509;
    case 0x050A: return 0x050B;
    case 0x050C: return 0x050D;
    case 0x050E: return 0x050F;
    case 0x0510: return 0x0511;
    case 0x0512: return 0x0513;
    case 0x0514: return 0x0515;
    case 0x0516: return 0x0517;
    case 0x0518: return 0x0519;
    case 0x051A: return 0x051B;
    case 0x051C: return 0x051D;
    case 0x051E: return 0x051F;
    case 0x0520: return 0x0521;
    case 0x0522: return 0x0523;
    case 0x0524: return 0x0525;
    case 0x0526: return 0x0527;
    case 0x0531: return 0x0561;
    case 0x0532: return 0x0562;
    case 0x0533: return 0x0563;
    case 0x0534: return 0x0564;
    case 0x0535: return 0x0565;
    case 0x0536: return 0x0566;
    case 0x0537: return 0x0567;
    case 0x0538: return 0x0568;
    case 0x0539: return 0x0569;
    case 0x053A: return 0x056A;
    case 0x053B: return 0x056B;
    case 0x053C: return 0x056C;
    case 0x053D: return 0x056D;
    case 0x053E: return 0x056E;
    case 0x053F: return 0x056F;
    case 0x0540: return 0x0570;
    case 0x0541: return 0x0571;
    case 0x0542: return 0x0572;
    case 0x0543: return 0x0573;
    case 0x0544: return 0x0574;
    case 0x0545: return 0x0575;
    case 0x0546: return 0x0576;
    case 0x0547: return 0x0577;
    case 0x0548: return 0x0578;
    case 0x0549: return 0x0579;
    case 0x054A: return 0x057A;
    case 0x054B: return 0x057B;
    case 0x054C: return 0x057C;
    case 0x054D: return 0x057D;
    case 0x054E: return 0x057E;
    case 0x054F: return 0x057F;
    case 0x0550: return 0x0580;
    case 0x0551: return 0x0581;
    case 0x0552: return 0x0582;
    case 0x0553: return 0x0583;
    case 0x0554: return 0x0584;
    case 0x0555: return 0x0585;
    case 0x0556: return 0x0586;
    case 0x10A0: return 0x2D00;
    case 0x10A1: return 0x2D01;
    case 0x10A2: return 0x2D02;
    case 0x10A3: return 0x2D03;
    case 0x10A4: return 0x2D04;
    case 0x10A5: return 0x2D05;
    case 0x10A6: return 0x2D06;
    case 0x10A7: return 0x2D07;
    case 0x10A8: return 0x2D08;
    case 0x10A9: return 0x2D09;
    case 0x10AA: return 0x2D0A;
    case 0x10AB: return 0x2D0B;
    case 0x10AC: return 0x2D0C;
    case 0x10AD: return 0x2D0D;
    case 0x10AE: return 0x2D0E;
    case 0x10AF: return 0x2D0F;
    case 0x10B0: return 0x2D10;
    case 0x10B1: return 0x2D11;
    case 0x10B2: return 0x2D12;
    case 0x10B3: return 0x2D13;
    case 0x10B4: return 0x2D14;
    case 0x10B5: return 0x2D15;
    case 0x10B6: return 0x2D16;
    case 0x10B7: return 0x2D17;
    case 0x10B8: return 0x2D18;
    case 0x10B9: return 0x2D19;
    case 0x10BA: return 0x2D1A;
    case 0x10BB: return 0x2D1B;
    case 0x10BC: return 0x2D1C;
    case 0x10BD: return 0x2D1D;
    case 0x10BE: return 0x2D1E;
    case 0x10BF: return 0x2D1F;
    case 0x10C0: return 0x2D20;
    case 0x10C1: return 0x2D21;
    case 0x10C2: return 0x2D22;
    case 0x10C3: return 0x2D23;
    case 0x10C4: return 0x2D24;
    case 0x10C5: return 0x2D25;
    case 0x10C7: return 0x2D27;
    case 0x10CD: return 0x2D2D;
    case 0x1E00: return 0x1E01;
    case 0x1E02: return 0x1E03;
    case 0x1E04: return 0x1E05;
    case 0x1E06: return 0x1E07;
    case 0x1E08: return 0x1E09;
    case 0x1E0A: return 0x1E0B;
    case 0x1E0C: return 0x1E0D;
    case 0x1E0E: return 0x1E0F;
    case 0x1E10: return 0x1E11;
    case 0x1E12: return 0x1E13;
    case 0x1E14: return 0x1E15;
    case 0x1E16: return 0x1E17;
    case 0x1E18: return 0x1E19;
    case 0x1E1A: return 0x1E1B;
    case 0x1E1C: return 0x1E1D;
    case 0x1E1E: return 0x1E1F;
    case 0x1E20: return 0x1E21;
    case 0x1E22: return 0x1E23;
    case 0x1E24: return 0x1E25;
    case 0x1E26: return 0x1E27;
    case 0x1E28: return 0x1E29;
    case 0x1E2A: return 0x1E2B;
    case 0x1E2C: return 0x1E2D;
    case 0x1E2E: return 0x1E2F;
    case 0x1E30: return 0x1E31;
    case 0x1E32: return 0x1E33;
    case 0x1E34: return 0x1E35;
    case 0x1E36: return 0x1E37;
    case 0x1E38: return 0x1E39;
    case 0x1E3A: return 0x1E3B;
    case 0x1E3C: return 0x1E3D;
    case 0x1E3E: return 0x1E3F;
    case 0x1E40: return 0x1E41;
    case 0x1E42: return 0x1E43;
    case 0x1E44: return 0x1E45;
    case 0x1E46: return 0x1E47;
    case 0x1E48: return 0x1E49;
    case 0x1E4A: return 0x1E4B;
    case 0x1E4C: return 0x1E4D;
    case 0x1E4E: return 0x1E4F;
    case 0x1E50: return 0x1E51;
    case 0x1E52: return 0x1E53;
    case 0x1E54: return 0x1E55;
    case 0x1E56: return 0x1E57;
    case 0x1E58: return 0x1E59;
    case 0x1E5A: return 0x1E5B;
    case 0x1E5C: return 0x1E5D;
    case 0x1E5E: return 0x1E5F;
    case 0x1E60: return 0x1E61;
    case 0x1E62: return 0x1E63;
    case 0x1E64: return 0x1E65;
    case 0x1E66: return 0x1E67;
    case 0x1E68: return 0x1E69;
    case 0x1E6A: return 0x1E6B;
    case 0x1E6C: return 0x1E6D;
    case 0x1E6E: return 0x1E6F;
    case 0x1E70: return 0x1E71;
    case 0x1E72: return 0x1E73;
    case 0x1E74: return 0x1E75;
    case 0x1E76: return 0x1E77;
    case 0x1E78: return 0x1E79;
    case 0x1E7A: return 0x1E7B;
    case 0x1E7C: return 0x1E7D;
    case 0x1E7E: return 0x1E7F;
    case 0x1E80: return 0x1E81;
    case 0x1E82: return 0x1E83;
    case 0x1E84: return 0x1E85;
    case 0x1E86: return 0x1E87;
    case 0x1E88: return 0x1E89;
    case 0x1E8A: return 0x1E8B;
    case 0x1E8C: return 0x1E8D;
    case 0x1E8E: return 0x1E8F;
    case 0x1E90: return 0x1E91;
    case 0x1E92: return 0x1E93;
    case 0x1E94: return 0x1E95;
    case 0x1E9E: return 0x00DF;
    case 0x1EA0: return 0x1EA1;
    case 0x1EA2: return 0x1EA3;
    case 0x1EA4: return 0x1EA5;
    case 0x1EA6: return 0x1EA7;
    case 0x1EA8: return 0x1EA9;
    case 0x1EAA: return 0x1EAB;
    case 0x1EAC: return 0x1EAD;
    case 0x1EAE: return 0x1EAF;
    case 0x1EB0: return 0x1EB1;
    case 0x1EB2: return 0x1EB3;
    case 0x1EB4: return 0x1EB5;
    case 0x1EB6: return 0x1EB7;
    case 0x1EB8: return 0x1EB9;
    case 0x1EBA: return 0x1EBB;
    case 0x1EBC: return 0x1EBD;
    case 0x1EBE: return 0x1EBF;
    case 0x1EC0: return 0x1EC1;
    case 0x1EC2: return 0x1EC3;
    case 0x1EC4: return 0x1EC5;
    case 0x1EC6: return 0x1EC7;
    case 0x1EC8: return 0x1EC9;
    case 0x1ECA: return 0x1ECB;
    case 0x1ECC: return 0x1ECD;
    case 0x1ECE: return 0x1ECF;
    case 0x1ED0: return 0x1ED1;
    case 0x1ED2: return 0x1ED3;
    case 0x1ED4: return 0x1ED5;
    case 0x1ED6: return 0x1ED7;
    case 0x1ED8: return 0x1ED9;
    case 0x1EDA: return 0x1EDB;
    case 0x1EDC: return 0x1EDD;
    case 0x1EDE: return 0x1EDF;
    case 0x1EE0: return 0x1EE1;
    case 0x1EE2: return 0x1EE3;
    case 0x1EE4: return 0x1EE5;
    case 0x1EE6: return 0x1EE7;
    case 0x1EE8: return 0x1EE9;
    case 0x1EEA: return 0x1EEB;
    case 0x1EEC: return 0x1EED;
    case 0x1EEE: return 0x1EEF;
    case 0x1EF0: return 0x1EF1;
    case 0x1EF2: return 0x1EF3;
    case 0x1EF4: return 0x1EF5;
    case 0x1EF6: return 0x1EF7;
    case 0x1EF8: return 0x1EF9;
    case 0x1EFA: return 0x1EFB;
    case 0x1EFC: return 0x1EFD;
    case 0x1EFE: return 0x1EFF;
    case 0x1F08: return 0x1F00;
    case 0x1F09: return 0x1F01;
    case 0x1F0A: return 0x1F02;
    case 0x1F0B: return 0x1F03;
    case 0x1F0C: return 0x1F04;
    case 0x1F0D: return 0x1F05;
    case 0x1F0E: return 0x1F06;
    case 0x1F0F: return 0x1F07;
    case 0x1F18: return 0x1F10;
    case 0x1F19: return 0x1F11;
    case 0x1F1A: return 0x1F12;
    case 0x1F1B: return 0x1F13;
    case 0x1F1C: return 0x1F14;
    case 0x1F1D: return 0x1F15;
    case 0x1F28: return 0x1F20;
    case 0x1F29: return 0x1F21;
    case 0x1F2A: return 0x1F22;
    case 0x1F2B: return 0x1F23;
    case 0x1F2C: return 0x1F24;
    case 0x1F2D: return 0x1F25;
    case 0x1F2E: return 0x1F26;
    case 0x1F2F: return 0x1F27;
    case 0x1F38: return 0x1F30;
    case 0x1F39: return 0x1F31;
    case 0x1F3A: return 0x1F32;
    case 0x1F3B: return 0x1F33;
    case 0x1F3C: return 0x1F34;
    case 0x1F3D: return 0x1F35;
    case 0x1F3E: return 0x1F36;
    case 0x1F3F: return 0x1F37;
    case 0x1F48: return 0x1F40;
    case 0x1F49: return 0x1F41;
    case 0x1F4A: return 0x1F42;
    case 0x1F4B: return 0x1F43;
    case 0x1F4C: return 0x1F44;
    case 0x1F4D: return 0x1F45;
    case 0x1F59: return 0x1F51;
    case 0x1F5B: return 0x1F53;
    case 0x1F5D: return 0x1F55;
    case 0x1F5F: return 0x1F57;
    case 0x1F68: return 0x1F60;
    case 0x1F69: return 0x1F61;
    case 0x1F6A: return 0x1F62;
    case 0x1F6B: return 0x1F63;
    case 0x1F6C: return 0x1F64;
    case 0x1F6D: return 0x1F65;
    case 0x1F6E: return 0x1F66;
    case 0x1F6F: return 0x1F67;
    case 0x1F88: return 0x1F80;
    case 0x1F89: return 0x1F81;
    case 0x1F8A: return 0x1F82;
    case 0x1F8B: return 0x1F83;
    case 0x1F8C: return 0x1F84;
    case 0x1F8D: return 0x1F85;
    case 0x1F8E: return 0x1F86;
    case 0x1F8F: return 0x1F87;
    case 0x1F98: return 0x1F90;
    case 0x1F99: return 0x1F91;
    case 0x1F9A: return 0x1F92;
    case 0x1F9B: return 0x1F93;
    case 0x1F9C: return 0x1F94;
    case 0x1F9D: return 0x1F95;
    case 0x1F9E: return 0x1F96;
    case 0x1F9F: return 0x1F97;
    case 0x1FA8: return 0x1FA0;
    case 0x1FA9: return 0x1FA1;
    case 0x1FAA: return 0x1FA2;
    case 0x1FAB: return 0x1FA3;
    case 0x1FAC: return 0x1FA4;
    case 0x1FAD: return 0x1FA5;
    case 0x1FAE: return 0x1FA6;
    case 0x1FAF: return 0x1FA7;
    case 0x1FB8: return 0x1FB0;
    case 0x1FB9: return 0x1FB1;
    case 0x1FBA: return 0x1F70;
    case 0x1FBB: return 0x1F71;
    case 0x1FBC: return 0x1FB3;
    case 0x1FC8: return 0x1F72;
    case 0x1FC9: return 0x1F73;
    case 0x1FCA: return 0x1F74;
    case 0x1FCB: return 0x1F75;
    case 0x1FCC: return 0x1FC3;
    case 0x1FD8: return 0x1FD0;
    case 0x1FD9: return 0x1FD1;
    case 0x1FDA: return 0x1F76;
    case 0x1FDB: return 0x1F77;
    case 0x1FE8: return 0x1FE0;
    case 0x1FE9: return 0x1FE1;
    case 0x1FEA: return 0x1F7A;
    case 0x1FEB: return 0x1F7B;
    case 0x1FEC: return 0x1FE5;
    case 0x1FF8: return 0x1F78;
    case 0x1FF9: return 0x1F79;
    case 0x1FFA: return 0x1F7C;
    case 0x1FFB: return 0x1F7D;
    case 0x1FFC: return 0x1FF3;
    case 0x2126: return 0x03C9;
    case 0x212A: return 0x006B;
    case 0x212B: return 0x00E5;
    case 0x2132: return 0x214E;
    case 0x2160: return 0x2170;
    case 0x2161: return 0x2171;
    case 0x2162: return 0x2172;
    case 0x2163: return 0x2173;
    case 0x2164: return 0x2174;
    case 0x2165: return 0x2175;
    case 0x2166: return 0x2176;
    case 0x2167: return 0x2177;
    case 0x2168: return 0x2178;
    case 0x2169: return 0x2179;
    case 0x216A: return 0x217A;
    case 0x216B: return 0x217B;
    case 0x216C: return 0x217C;
    case 0x216D: return 0x217D;
    case 0x216E: return 0x217E;
    case 0x216F: return 0x217F;
    case 0x2183: return 0x2184;
    case 0x24B6: return 0x24D0;
    case 0x24B7: return 0x24D1;
    case 0x24B8: return 0x24D2;
    case 0x24B9: return 0x24D3;
    case 0x24BA: return 0x24D4;
    case 0x24BB: return 0x24D5;
    case 0x24BC: return 0x24D6;
    case 0x24BD: return 0x24D7;
    case 0x24BE: return 0x24D8;
    case 0x24BF: return 0x24D9;
    case 0x24C0: return 0x24DA;
    case 0x24C1: return 0x24DB;
    case 0x24C2: return 0x24DC;
    case 0x24C3: return 0x24DD;
    case 0x24C4: return 0x24DE;
    case 0x24C5: return 0x24DF;
    case 0x24C6: return 0x24E0;
    case 0x24C7: return 0x24E1;
    case 0x24C8: return 0x24E2;
    case 0x24C9: return 0x24E3;
    case 0x24CA: return 0x24E4;
    case 0x24CB: return 0x24E5;
    case 0x24CC: return 0x24E6;
    case 0x24CD: return 0x24E7;
    case 0x24CE: return 0x24E8;
    case 0x24CF: return 0x24E9;
    case 0x2C00: return 0x2C30;
    case 0x2C01: return 0x2C31;
    case 0x2C02: return 0x2C32;
    case 0x2C03: return 0x2C33;
    case 0x2C04: return 0x2C34;
    case 0x2C05: return 0x2C35;
    case 0x2C06: return 0x2C36;
    case 0x2C07: return 0x2C37;
    case 0x2C08: return 0x2C38;
    case 0x2C09: return 0x2C39;
    case 0x2C0A: return 0x2C3A;
    case 0x2C0B: return 0x2C3B;
    case 0x2C0C: return 0x2C3C;
    case 0x2C0D: return 0x2C3D;
    case 0x2C0E: return 0x2C3E;
    case 0x2C0F: return 0x2C3F;
    case 0x2C10: return 0x2C40;
    case 0x2C11: return 0x2C41;
    case 0x2C12: return 0x2C42;
    case 0x2C13: return 0x2C43;
    case 0x2C14: return 0x2C44;
    case 0x2C15: return 0x2C45;
    case 0x2C16: return 0x2C46;
    case 0x2C17: return 0x2C47;
    case 0x2C18: return 0x2C48;
    case 0x2C19: return 0x2C49;
    case 0x2C1A: return 0x2C4A;
    case 0x2C1B: return 0x2C4B;
    case 0x2C1C: return 0x2C4C;
    case 0x2C1D: return 0x2C4D;
    case 0x2C1E: return 0x2C4E;
    case 0x2C1F: return 0x2C4F;
    case 0x2C20: return 0x2C50;
    case 0x2C21: return 0x2C51;
    case 0x2C22: return 0x2C52;
    case 0x2C23: return 0x2C53;
    case 0x2C24: return 0x2C54;
    case 0x2C25: return 0x2C55;
    case 0x2C26: return 0x2C56;
    case 0x2C27: return 0x2C57;
    case 0x2C28: return 0x2C58;
    case 0x2C29: return 0x2C59;
    case 0x2C2A: return 0x2C5A;
    case 0x2C2B: return 0x2C5B;
    case 0x2C2C: return 0x2C5C;
    case 0x2C2D: return 0x2C5D;
    case 0x2C2E: return 0x2C5E;
    case 0x2C60: return 0x2C61;
    case 0x2C62: return 0x026B;
    case 0x2C63: return 0x1D7D;
    case 0x2C64: return 0x027D;
    case 0x2C67: return 0x2C68;
    case 0x2C69: return 0x2C6A;
    case 0x2C6B: return 0x2C6C;
    case 0x2C6D: return 0x0251;
    case 0x2C6E: return 0x0271;
    case 0x2C6F: return 0x0250;
    case 0x2C70: return 0x0252;
    case 0x2C72: return 0x2C73;
    case 0x2C75: return 0x2C76;
    case 0x2C7E: return 0x023F;
    case 0x2C7F: return 0x0240;
    case 0x2C80: return 0x2C81;
    case 0x2C82: return 0x2C83;
    case 0x2C84: return 0x2C85;
    case 0x2C86: return 0x2C87;
    case 0x2C88: return 0x2C89;
    case 0x2C8A: return 0x2C8B;
    case 0x2C8C: return 0x2C8D;
    case 0x2C8E: return 0x2C8F;
    case 0x2C90: return 0x2C91;
    case 0x2C92: return 0x2C93;
    case 0x2C94: return 0x2C95;
    case 0x2C96: return 0x2C97;
    case 0x2C98: return 0x2C99;
    case 0x2C9A: return 0x2C9B;
    case 0x2C9C: return 0x2C9D;
    case 0x2C9E: return 0x2C9F;
    case 0x2CA0: return 0x2CA1;
    case 0x2CA2: return 0x2CA3;
    case 0x2CA4: return 0x2CA5;
    case 0x2CA6: return 0x2CA7;
    case 0x2CA8: return 0x2CA9;
    case 0x2CAA: return 0x2CAB;
    case 0x2CAC: return 0x2CAD;
    case 0x2CAE: return 0x2CAF;
    case 0x2CB0: return 0x2CB1;
    case 0x2CB2: return 0x2CB3;
    case 0x2CB4: return 0x2CB5;
    case 0x2CB6: return 0x2CB7;
    case 0x2CB8: return 0x2CB9;
    case 0x2CBA: return 0x2CBB;
    case 0x2CBC: return 0x2CBD;
    case 0x2CBE: return 0x2CBF;
    case 0x2CC0: return 0x2CC1;
    case 0x2CC2: return 0x2CC3;
    case 0x2CC4: return 0x2CC5;
    case 0x2CC6: return 0x2CC7;
    case 0x2CC8: return 0x2CC9;
    case 0x2CCA: return 0x2CCB;
    case 0x2CCC: return 0x2CCD;
    case 0x2CCE: return 0x2CCF;
    case 0x2CD0: return 0x2CD1;
    case 0x2CD2: return 0x2CD3;
    case 0x2CD4: return 0x2CD5;
    case 0x2CD6: return 0x2CD7;
    case 0x2CD8: return 0x2CD9;
    case 0x2CDA: return 0x2CDB;
    case 0x2CDC: return 0x2CDD;
    case 0x2CDE: return 0x2CDF;
    case 0x2CE0: return 0x2CE1;
    case 0x2CE2: return 0x2CE3;
    case 0x2CEB: return 0x2CEC;
    case 0x2CED: return 0x2CEE;
    case 0x2CF2: return 0x2CF3;
    case 0xA640: return 0xA641;
    case 0xA642: return 0xA643;
    case 0xA644: return 0xA645;
    case 0xA646: return 0xA647;
    case 0xA648: return 0xA649;
    case 0xA64A: return 0xA64B;
    case 0xA64C: return 0xA64D;
    case 0xA64E: return 0xA64F;
    case 0xA650: return 0xA651;
    case 0xA652: return 0xA653;
    case 0xA654: return 0xA655;
    case 0xA656: return 0xA657;
    case 0xA658: return 0xA659;
    case 0xA65A: return 0xA65B;
    case 0xA65C: return 0xA65D;
    case 0xA65E: return 0xA65F;
    case 0xA660: return 0xA661;
    case 0xA662: return 0xA663;
    case 0xA664: return 0xA665;
    case 0xA666: return 0xA667;
    case 0xA668: return 0xA669;
    case 0xA66A: return 0xA66B;
    case 0xA66C: return 0xA66D;
    case 0xA680: return 0xA681;
    case 0xA682: return 0xA683;
    case 0xA684: return 0xA685;
    case 0xA686: return 0xA687;
    case 0xA688: return 0xA689;
    case 0xA68A: return 0xA68B;
    case 0xA68C: return 0xA68D;
    case 0xA68E: return 0xA68F;
    case 0xA690: return 0xA691;
    case 0xA692: return 0xA693;
    case 0xA694: return 0xA695;
    case 0xA696: return 0xA697;
    case 0xA722: return 0xA723;
    case 0xA724: return 0xA725;
    case 0xA726: return 0xA727;
    case 0xA728: return 0xA729;
    case 0xA72A: return 0xA72B;
    case 0xA72C: return 0xA72D;
    case 0xA72E: return 0xA72F;
    case 0xA732: return 0xA733;
    case 0xA734: return 0xA735;
    case 0xA736: return 0xA737;
    case 0xA738: return 0xA739;
    case 0xA73A: return 0xA73B;
    case 0xA73C: return 0xA73D;
    case 0xA73E: return 0xA73F;
    case 0xA740: return 0xA741;
    case 0xA742: return 0xA743;
    case 0xA744: return 0xA745;
    case 0xA746: return 0xA747;
    case 0xA748: return 0xA749;
    case 0xA74A: return 0xA74B;
    case 0xA74C: return 0xA74D;
    case 0xA74E: return 0xA74F;
    case 0xA750: return 0xA751;
    case 0xA752: return 0xA753;
    case 0xA754: return 0xA755;
    case 0xA756: return 0xA757;
    case 0xA758: return 0xA759;
    case 0xA75A: return 0xA75B;
    case 0xA75C: return 0xA75D;
    case 0xA75E: return 0xA75F;
    case 0xA760: return 0xA761;
    case 0xA762: return 0xA763;
    case 0xA764: return 0xA765;
    case 0xA766: return 0xA767;
    case 0xA768: return 0xA769;
    case 0xA76A: return 0xA76B;
    case 0xA76C: return 0xA76D;
    case 0xA76E: return 0xA76F;
    case 0xA779: return 0xA77A;
    case 0xA77B: return 0xA77C;
    case 0xA77D: return 0x1D79;
    case 0xA77E: return 0xA77F;
    case 0xA780: return 0xA781;
    case 0xA782: return 0xA783;
    case 0xA784: return 0xA785;
    case 0xA786: return 0xA787;
    case 0xA78B: return 0xA78C;
    case 0xA78D: return 0x0265;
    case 0xA790: return 0xA791;
    case 0xA792: return 0xA793;
    case 0xA7A0: return 0xA7A1;
    case 0xA7A2: return 0xA7A3;
    case 0xA7A4: return 0xA7A5;
    case 0xA7A6: return 0xA7A7;
    case 0xA7A8: return 0xA7A9;
    case 0xA7AA: return 0x0266;
    case 0xFF21: return 0xFF41;
    case 0xFF22: return 0xFF42;
    case 0xFF23: return 0xFF43;
    case 0xFF24: return 0xFF44;
    case 0xFF25: return 0xFF45;
    case 0xFF26: return 0xFF46;
    case 0xFF27: return 0xFF47;
    case 0xFF28: return 0xFF48;
    case 0xFF29: return 0xFF49;
    case 0xFF2A: return 0xFF4A;
    case 0xFF2B: return 0xFF4B;
    case 0xFF2C: return 0xFF4C;
    case 0xFF2D: return 0xFF4D;
    case 0xFF2E: return 0xFF4E;
    case 0xFF2F: return 0xFF4F;
    case 0xFF30: return 0xFF50;
    case 0xFF31: return 0xFF51;
    case 0xFF32: return 0xFF52;
    case 0xFF33: return 0xFF53;
    case 0xFF34: return 0xFF54;
    case 0xFF35: return 0xFF55;
    case 0xFF36: return 0xFF56;
    case 0xFF37: return 0xFF57;
    case 0xFF38: return 0xFF58;
    case 0xFF39: return 0xFF59;
    case 0xFF3A: return 0xFF5A;
    case 0x10400: return 0x10428;
    case 0x10401: return 0x10429;
    case 0x10402: return 0x1042A;
    case 0x10403: return 0x1042B;
    case 0x10404: return 0x1042C;
    case 0x10405: return 0x1042D;
    case 0x10406: return 0x1042E;
    case 0x10407: return 0x1042F;
    case 0x10408: return 0x10430;
    case 0x10409: return 0x10431;
    case 0x1040A: return 0x10432;
    case 0x1040B: return 0x10433;
    case 0x1040C: return 0x10434;
    case 0x1040D: return 0x10435;
    case 0x1040E: return 0x10436;
    case 0x1040F: return 0x10437;
    case 0x10410: return 0x10438;
    case 0x10411: return 0x10439;
    case 0x10412: return 0x1043A;
    case 0x10413: return 0x1043B;
    case 0x10414: return 0x1043C;
    case 0x10415: return 0x1043D;
    case 0x10416: return 0x1043E;
    case 0x10417: return 0x1043F;
    case 0x10418: return 0x10440;
    case 0x10419: return 0x10441;
    case 0x1041A: return 0x10442;
    case 0x1041B: return 0x10443;
    case 0x1041C: return 0x10444;
    case 0x1041D: return 0x10445;
    case 0x1041E: return 0x10446;
    case 0x1041F: return 0x10447;
    case 0x10420: return 0x10448;
    case 0x10421: return 0x10449;
    case 0x10422: return 0x1044A;
    case 0x10423: return 0x1044B;
    case 0x10424: return 0x1044C;
    case 0x10425: return 0x1044D;
    case 0x10426: return 0x1044E;
    case 0x10427: return 0x1044F;
    default: return ch;
  }
}

int cwal_utf8_char_toupper( int ch ){
    /* Imported from ftp://ftp.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt */
    switch(ch){
      case 0x0061: return 0x0041;
      case 0x0062: return 0x0042;
      case 0x0063: return 0x0043;
      case 0x0064: return 0x0044;
      case 0x0065: return 0x0045;
      case 0x0066: return 0x0046;
      case 0x0067: return 0x0047;
      case 0x0068: return 0x0048;
      case 0x0069: return 0x0049;
      case 0x006A: return 0x004A;
      case 0x006B: return 0x004B;
      case 0x006C: return 0x004C;
      case 0x006D: return 0x004D;
      case 0x006E: return 0x004E;
      case 0x006F: return 0x004F;
      case 0x0070: return 0x0050;
      case 0x0071: return 0x0051;
      case 0x0072: return 0x0052;
      case 0x0073: return 0x0053;
      case 0x0074: return 0x0054;
      case 0x0075: return 0x0055;
      case 0x0076: return 0x0056;
      case 0x0077: return 0x0057;
      case 0x0078: return 0x0058;
      case 0x0079: return 0x0059;
      case 0x007A: return 0x005A;
      case 0x00B5: return 0x039C;
      case 0x00E0: return 0x00C0;
      case 0x00E1: return 0x00C1;
      case 0x00E2: return 0x00C2;
      case 0x00E3: return 0x00C3;
      case 0x00E4: return 0x00C4;
      case 0x00E5: return 0x00C5;
      case 0x00E6: return 0x00C6;
      case 0x00E7: return 0x00C7;
      case 0x00E8: return 0x00C8;
      case 0x00E9: return 0x00C9;
      case 0x00EA: return 0x00CA;
      case 0x00EB: return 0x00CB;
      case 0x00EC: return 0x00CC;
      case 0x00ED: return 0x00CD;
      case 0x00EE: return 0x00CE;
      case 0x00EF: return 0x00CF;
      case 0x00F0: return 0x00D0;
      case 0x00F1: return 0x00D1;
      case 0x00F2: return 0x00D2;
      case 0x00F3: return 0x00D3;
      case 0x00F4: return 0x00D4;
      case 0x00F5: return 0x00D5;
      case 0x00F6: return 0x00D6;
      case 0x00F8: return 0x00D8;
      case 0x00F9: return 0x00D9;
      case 0x00FA: return 0x00DA;
      case 0x00FB: return 0x00DB;
      case 0x00FC: return 0x00DC;
      case 0x00FD: return 0x00DD;
      case 0x00FE: return 0x00DE;
      case 0x00FF: return 0x0178;
      case 0x0101: return 0x0100;
      case 0x0103: return 0x0102;
      case 0x0105: return 0x0104;
      case 0x0107: return 0x0106;
      case 0x0109: return 0x0108;
      case 0x010B: return 0x010A;
      case 0x010D: return 0x010C;
      case 0x010F: return 0x010E;
      case 0x0111: return 0x0110;
      case 0x0113: return 0x0112;
      case 0x0115: return 0x0114;
      case 0x0117: return 0x0116;
      case 0x0119: return 0x0118;
      case 0x011B: return 0x011A;
      case 0x011D: return 0x011C;
      case 0x011F: return 0x011E;
      case 0x0121: return 0x0120;
      case 0x0123: return 0x0122;
      case 0x0125: return 0x0124;
      case 0x0127: return 0x0126;
      case 0x0129: return 0x0128;
      case 0x012B: return 0x012A;
      case 0x012D: return 0x012C;
      case 0x012F: return 0x012E;
      case 0x0131: return 0x0049;
      case 0x0133: return 0x0132;
      case 0x0135: return 0x0134;
      case 0x0137: return 0x0136;
      case 0x013A: return 0x0139;
      case 0x013C: return 0x013B;
      case 0x013E: return 0x013D;
      case 0x0140: return 0x013F;
      case 0x0142: return 0x0141;
      case 0x0144: return 0x0143;
      case 0x0146: return 0x0145;
      case 0x0148: return 0x0147;
      case 0x014B: return 0x014A;
      case 0x014D: return 0x014C;
      case 0x014F: return 0x014E;
      case 0x0151: return 0x0150;
      case 0x0153: return 0x0152;
      case 0x0155: return 0x0154;
      case 0x0157: return 0x0156;
      case 0x0159: return 0x0158;
      case 0x015B: return 0x015A;
      case 0x015D: return 0x015C;
      case 0x015F: return 0x015E;
      case 0x0161: return 0x0160;
      case 0x0163: return 0x0162;
      case 0x0165: return 0x0164;
      case 0x0167: return 0x0166;
      case 0x0169: return 0x0168;
      case 0x016B: return 0x016A;
      case 0x016D: return 0x016C;
      case 0x016F: return 0x016E;
      case 0x0171: return 0x0170;
      case 0x0173: return 0x0172;
      case 0x0175: return 0x0174;
      case 0x0177: return 0x0176;
      case 0x017A: return 0x0179;
      case 0x017C: return 0x017B;
      case 0x017E: return 0x017D;
      case 0x017F: return 0x0053;
      case 0x0180: return 0x0243;
      case 0x0183: return 0x0182;
      case 0x0185: return 0x0184;
      case 0x0188: return 0x0187;
      case 0x018C: return 0x018B;
      case 0x0192: return 0x0191;
      case 0x0195: return 0x01F6;
      case 0x0199: return 0x0198;
      case 0x019A: return 0x023D;
      case 0x019E: return 0x0220;
      case 0x01A1: return 0x01A0;
      case 0x01A3: return 0x01A2;
      case 0x01A5: return 0x01A4;
      case 0x01A8: return 0x01A7;
      case 0x01AD: return 0x01AC;
      case 0x01B0: return 0x01AF;
      case 0x01B4: return 0x01B3;
      case 0x01B6: return 0x01B5;
      case 0x01B9: return 0x01B8;
      case 0x01BD: return 0x01BC;
      case 0x01BF: return 0x01F7;
      case 0x01C5: return 0x01C4;
      case 0x01C6: return 0x01C4;
      case 0x01C8: return 0x01C7;
      case 0x01C9: return 0x01C7;
      case 0x01CB: return 0x01CA;
      case 0x01CC: return 0x01CA;
      case 0x01CE: return 0x01CD;
      case 0x01D0: return 0x01CF;
      case 0x01D2: return 0x01D1;
      case 0x01D4: return 0x01D3;
      case 0x01D6: return 0x01D5;
      case 0x01D8: return 0x01D7;
      case 0x01DA: return 0x01D9;
      case 0x01DC: return 0x01DB;
      case 0x01DD: return 0x018E;
      case 0x01DF: return 0x01DE;
      case 0x01E1: return 0x01E0;
      case 0x01E3: return 0x01E2;
      case 0x01E5: return 0x01E4;
      case 0x01E7: return 0x01E6;
      case 0x01E9: return 0x01E8;
      case 0x01EB: return 0x01EA;
      case 0x01ED: return 0x01EC;
      case 0x01EF: return 0x01EE;
      case 0x01F2: return 0x01F1;
      case 0x01F3: return 0x01F1;
      case 0x01F5: return 0x01F4;
      case 0x01F9: return 0x01F8;
      case 0x01FB: return 0x01FA;
      case 0x01FD: return 0x01FC;
      case 0x01FF: return 0x01FE;
      case 0x0201: return 0x0200;
      case 0x0203: return 0x0202;
      case 0x0205: return 0x0204;
      case 0x0207: return 0x0206;
      case 0x0209: return 0x0208;
      case 0x020B: return 0x020A;
      case 0x020D: return 0x020C;
      case 0x020F: return 0x020E;
      case 0x0211: return 0x0210;
      case 0x0213: return 0x0212;
      case 0x0215: return 0x0214;
      case 0x0217: return 0x0216;
      case 0x0219: return 0x0218;
      case 0x021B: return 0x021A;
      case 0x021D: return 0x021C;
      case 0x021F: return 0x021E;
      case 0x0223: return 0x0222;
      case 0x0225: return 0x0224;
      case 0x0227: return 0x0226;
      case 0x0229: return 0x0228;
      case 0x022B: return 0x022A;
      case 0x022D: return 0x022C;
      case 0x022F: return 0x022E;
      case 0x0231: return 0x0230;
      case 0x0233: return 0x0232;
      case 0x023C: return 0x023B;
      case 0x023F: return 0x2C7E;
      case 0x0240: return 0x2C7F;
      case 0x0242: return 0x0241;
      case 0x0247: return 0x0246;
      case 0x0249: return 0x0248;
      case 0x024B: return 0x024A;
      case 0x024D: return 0x024C;
      case 0x024F: return 0x024E;
      case 0x0250: return 0x2C6F;
      case 0x0251: return 0x2C6D;
      case 0x0252: return 0x2C70;
      case 0x0253: return 0x0181;
      case 0x0254: return 0x0186;
      case 0x0256: return 0x0189;
      case 0x0257: return 0x018A;
      case 0x0259: return 0x018F;
      case 0x025B: return 0x0190;
      case 0x0260: return 0x0193;
      case 0x0263: return 0x0194;
      case 0x0265: return 0xA78D;
      case 0x0266: return 0xA7AA;
      case 0x0268: return 0x0197;
      case 0x0269: return 0x0196;
      case 0x026B: return 0x2C62;
      case 0x026F: return 0x019C;
      case 0x0271: return 0x2C6E;
      case 0x0272: return 0x019D;
      case 0x0275: return 0x019F;
      case 0x027D: return 0x2C64;
      case 0x0280: return 0x01A6;
      case 0x0283: return 0x01A9;
      case 0x0288: return 0x01AE;
      case 0x0289: return 0x0244;
      case 0x028A: return 0x01B1;
      case 0x028B: return 0x01B2;
      case 0x028C: return 0x0245;
      case 0x0292: return 0x01B7;
      case 0x0345: return 0x0399;
      case 0x0371: return 0x0370;
      case 0x0373: return 0x0372;
      case 0x0377: return 0x0376;
      case 0x037B: return 0x03FD;
      case 0x037C: return 0x03FE;
      case 0x037D: return 0x03FF;
      case 0x03AC: return 0x0386;
      case 0x03AD: return 0x0388;
      case 0x03AE: return 0x0389;
      case 0x03AF: return 0x038A;
      case 0x03B1: return 0x0391;
      case 0x03B2: return 0x0392;
      case 0x03B3: return 0x0393;
      case 0x03B4: return 0x0394;
      case 0x03B5: return 0x0395;
      case 0x03B6: return 0x0396;
      case 0x03B7: return 0x0397;
      case 0x03B8: return 0x0398;
      case 0x03B9: return 0x0399;
      case 0x03BA: return 0x039A;
      case 0x03BB: return 0x039B;
      case 0x03BC: return 0x039C;
      case 0x03BD: return 0x039D;
      case 0x03BE: return 0x039E;
      case 0x03BF: return 0x039F;
      case 0x03C0: return 0x03A0;
      case 0x03C1: return 0x03A1;
      case 0x03C2: return 0x03A3;
      case 0x03C3: return 0x03A3;
      case 0x03C4: return 0x03A4;
      case 0x03C5: return 0x03A5;
      case 0x03C6: return 0x03A6;
      case 0x03C7: return 0x03A7;
      case 0x03C8: return 0x03A8;
      case 0x03C9: return 0x03A9;
      case 0x03CA: return 0x03AA;
      case 0x03CB: return 0x03AB;
      case 0x03CC: return 0x038C;
      case 0x03CD: return 0x038E;
      case 0x03CE: return 0x038F;
      case 0x03D0: return 0x0392;
      case 0x03D1: return 0x0398;
      case 0x03D5: return 0x03A6;
      case 0x03D6: return 0x03A0;
      case 0x03D7: return 0x03CF;
      case 0x03D9: return 0x03D8;
      case 0x03DB: return 0x03DA;
      case 0x03DD: return 0x03DC;
      case 0x03DF: return 0x03DE;
      case 0x03E1: return 0x03E0;
      case 0x03E3: return 0x03E2;
      case 0x03E5: return 0x03E4;
      case 0x03E7: return 0x03E6;
      case 0x03E9: return 0x03E8;
      case 0x03EB: return 0x03EA;
      case 0x03ED: return 0x03EC;
      case 0x03EF: return 0x03EE;
      case 0x03F0: return 0x039A;
      case 0x03F1: return 0x03A1;
      case 0x03F2: return 0x03F9;
      case 0x03F5: return 0x0395;
      case 0x03F8: return 0x03F7;
      case 0x03FB: return 0x03FA;
      case 0x0430: return 0x0410;
      case 0x0431: return 0x0411;
      case 0x0432: return 0x0412;
      case 0x0433: return 0x0413;
      case 0x0434: return 0x0414;
      case 0x0435: return 0x0415;
      case 0x0436: return 0x0416;
      case 0x0437: return 0x0417;
      case 0x0438: return 0x0418;
      case 0x0439: return 0x0419;
      case 0x043A: return 0x041A;
      case 0x043B: return 0x041B;
      case 0x043C: return 0x041C;
      case 0x043D: return 0x041D;
      case 0x043E: return 0x041E;
      case 0x043F: return 0x041F;
      case 0x0440: return 0x0420;
      case 0x0441: return 0x0421;
      case 0x0442: return 0x0422;
      case 0x0443: return 0x0423;
      case 0x0444: return 0x0424;
      case 0x0445: return 0x0425;
      case 0x0446: return 0x0426;
      case 0x0447: return 0x0427;
      case 0x0448: return 0x0428;
      case 0x0449: return 0x0429;
      case 0x044A: return 0x042A;
      case 0x044B: return 0x042B;
      case 0x044C: return 0x042C;
      case 0x044D: return 0x042D;
      case 0x044E: return 0x042E;
      case 0x044F: return 0x042F;
      case 0x0450: return 0x0400;
      case 0x0451: return 0x0401;
      case 0x0452: return 0x0402;
      case 0x0453: return 0x0403;
      case 0x0454: return 0x0404;
      case 0x0455: return 0x0405;
      case 0x0456: return 0x0406;
      case 0x0457: return 0x0407;
      case 0x0458: return 0x0408;
      case 0x0459: return 0x0409;
      case 0x045A: return 0x040A;
      case 0x045B: return 0x040B;
      case 0x045C: return 0x040C;
      case 0x045D: return 0x040D;
      case 0x045E: return 0x040E;
      case 0x045F: return 0x040F;
      case 0x0461: return 0x0460;
      case 0x0463: return 0x0462;
      case 0x0465: return 0x0464;
      case 0x0467: return 0x0466;
      case 0x0469: return 0x0468;
      case 0x046B: return 0x046A;
      case 0x046D: return 0x046C;
      case 0x046F: return 0x046E;
      case 0x0471: return 0x0470;
      case 0x0473: return 0x0472;
      case 0x0475: return 0x0474;
      case 0x0477: return 0x0476;
      case 0x0479: return 0x0478;
      case 0x047B: return 0x047A;
      case 0x047D: return 0x047C;
      case 0x047F: return 0x047E;
      case 0x0481: return 0x0480;
      case 0x048B: return 0x048A;
      case 0x048D: return 0x048C;
      case 0x048F: return 0x048E;
      case 0x0491: return 0x0490;
      case 0x0493: return 0x0492;
      case 0x0495: return 0x0494;
      case 0x0497: return 0x0496;
      case 0x0499: return 0x0498;
      case 0x049B: return 0x049A;
      case 0x049D: return 0x049C;
      case 0x049F: return 0x049E;
      case 0x04A1: return 0x04A0;
      case 0x04A3: return 0x04A2;
      case 0x04A5: return 0x04A4;
      case 0x04A7: return 0x04A6;
      case 0x04A9: return 0x04A8;
      case 0x04AB: return 0x04AA;
      case 0x04AD: return 0x04AC;
      case 0x04AF: return 0x04AE;
      case 0x04B1: return 0x04B0;
      case 0x04B3: return 0x04B2;
      case 0x04B5: return 0x04B4;
      case 0x04B7: return 0x04B6;
      case 0x04B9: return 0x04B8;
      case 0x04BB: return 0x04BA;
      case 0x04BD: return 0x04BC;
      case 0x04BF: return 0x04BE;
      case 0x04C2: return 0x04C1;
      case 0x04C4: return 0x04C3;
      case 0x04C6: return 0x04C5;
      case 0x04C8: return 0x04C7;
      case 0x04CA: return 0x04C9;
      case 0x04CC: return 0x04CB;
      case 0x04CE: return 0x04CD;
      case 0x04CF: return 0x04C0;
      case 0x04D1: return 0x04D0;
      case 0x04D3: return 0x04D2;
      case 0x04D5: return 0x04D4;
      case 0x04D7: return 0x04D6;
      case 0x04D9: return 0x04D8;
      case 0x04DB: return 0x04DA;
      case 0x04DD: return 0x04DC;
      case 0x04DF: return 0x04DE;
      case 0x04E1: return 0x04E0;
      case 0x04E3: return 0x04E2;
      case 0x04E5: return 0x04E4;
      case 0x04E7: return 0x04E6;
      case 0x04E9: return 0x04E8;
      case 0x04EB: return 0x04EA;
      case 0x04ED: return 0x04EC;
      case 0x04EF: return 0x04EE;
      case 0x04F1: return 0x04F0;
      case 0x04F3: return 0x04F2;
      case 0x04F5: return 0x04F4;
      case 0x04F7: return 0x04F6;
      case 0x04F9: return 0x04F8;
      case 0x04FB: return 0x04FA;
      case 0x04FD: return 0x04FC;
      case 0x04FF: return 0x04FE;
      case 0x0501: return 0x0500;
      case 0x0503: return 0x0502;
      case 0x0505: return 0x0504;
      case 0x0507: return 0x0506;
      case 0x0509: return 0x0508;
      case 0x050B: return 0x050A;
      case 0x050D: return 0x050C;
      case 0x050F: return 0x050E;
      case 0x0511: return 0x0510;
      case 0x0513: return 0x0512;
      case 0x0515: return 0x0514;
      case 0x0517: return 0x0516;
      case 0x0519: return 0x0518;
      case 0x051B: return 0x051A;
      case 0x051D: return 0x051C;
      case 0x051F: return 0x051E;
      case 0x0521: return 0x0520;
      case 0x0523: return 0x0522;
      case 0x0525: return 0x0524;
      case 0x0527: return 0x0526;
      case 0x0561: return 0x0531;
      case 0x0562: return 0x0532;
      case 0x0563: return 0x0533;
      case 0x0564: return 0x0534;
      case 0x0565: return 0x0535;
      case 0x0566: return 0x0536;
      case 0x0567: return 0x0537;
      case 0x0568: return 0x0538;
      case 0x0569: return 0x0539;
      case 0x056A: return 0x053A;
      case 0x056B: return 0x053B;
      case 0x056C: return 0x053C;
      case 0x056D: return 0x053D;
      case 0x056E: return 0x053E;
      case 0x056F: return 0x053F;
      case 0x0570: return 0x0540;
      case 0x0571: return 0x0541;
      case 0x0572: return 0x0542;
      case 0x0573: return 0x0543;
      case 0x0574: return 0x0544;
      case 0x0575: return 0x0545;
      case 0x0576: return 0x0546;
      case 0x0577: return 0x0547;
      case 0x0578: return 0x0548;
      case 0x0579: return 0x0549;
      case 0x057A: return 0x054A;
      case 0x057B: return 0x054B;
      case 0x057C: return 0x054C;
      case 0x057D: return 0x054D;
      case 0x057E: return 0x054E;
      case 0x057F: return 0x054F;
      case 0x0580: return 0x0550;
      case 0x0581: return 0x0551;
      case 0x0582: return 0x0552;
      case 0x0583: return 0x0553;
      case 0x0584: return 0x0554;
      case 0x0585: return 0x0555;
      case 0x0586: return 0x0556;
      case 0x1D79: return 0xA77D;
      case 0x1D7D: return 0x2C63;
      case 0x1E01: return 0x1E00;
      case 0x1E03: return 0x1E02;
      case 0x1E05: return 0x1E04;
      case 0x1E07: return 0x1E06;
      case 0x1E09: return 0x1E08;
      case 0x1E0B: return 0x1E0A;
      case 0x1E0D: return 0x1E0C;
      case 0x1E0F: return 0x1E0E;
      case 0x1E11: return 0x1E10;
      case 0x1E13: return 0x1E12;
      case 0x1E15: return 0x1E14;
      case 0x1E17: return 0x1E16;
      case 0x1E19: return 0x1E18;
      case 0x1E1B: return 0x1E1A;
      case 0x1E1D: return 0x1E1C;
      case 0x1E1F: return 0x1E1E;
      case 0x1E21: return 0x1E20;
      case 0x1E23: return 0x1E22;
      case 0x1E25: return 0x1E24;
      case 0x1E27: return 0x1E26;
      case 0x1E29: return 0x1E28;
      case 0x1E2B: return 0x1E2A;
      case 0x1E2D: return 0x1E2C;
      case 0x1E2F: return 0x1E2E;
      case 0x1E31: return 0x1E30;
      case 0x1E33: return 0x1E32;
      case 0x1E35: return 0x1E34;
      case 0x1E37: return 0x1E36;
      case 0x1E39: return 0x1E38;
      case 0x1E3B: return 0x1E3A;
      case 0x1E3D: return 0x1E3C;
      case 0x1E3F: return 0x1E3E;
      case 0x1E41: return 0x1E40;
      case 0x1E43: return 0x1E42;
      case 0x1E45: return 0x1E44;
      case 0x1E47: return 0x1E46;
      case 0x1E49: return 0x1E48;
      case 0x1E4B: return 0x1E4A;
      case 0x1E4D: return 0x1E4C;
      case 0x1E4F: return 0x1E4E;
      case 0x1E51: return 0x1E50;
      case 0x1E53: return 0x1E52;
      case 0x1E55: return 0x1E54;
      case 0x1E57: return 0x1E56;
      case 0x1E59: return 0x1E58;
      case 0x1E5B: return 0x1E5A;
      case 0x1E5D: return 0x1E5C;
      case 0x1E5F: return 0x1E5E;
      case 0x1E61: return 0x1E60;
      case 0x1E63: return 0x1E62;
      case 0x1E65: return 0x1E64;
      case 0x1E67: return 0x1E66;
      case 0x1E69: return 0x1E68;
      case 0x1E6B: return 0x1E6A;
      case 0x1E6D: return 0x1E6C;
      case 0x1E6F: return 0x1E6E;
      case 0x1E71: return 0x1E70;
      case 0x1E73: return 0x1E72;
      case 0x1E75: return 0x1E74;
      case 0x1E77: return 0x1E76;
      case 0x1E79: return 0x1E78;
      case 0x1E7B: return 0x1E7A;
      case 0x1E7D: return 0x1E7C;
      case 0x1E7F: return 0x1E7E;
      case 0x1E81: return 0x1E80;
      case 0x1E83: return 0x1E82;
      case 0x1E85: return 0x1E84;
      case 0x1E87: return 0x1E86;
      case 0x1E89: return 0x1E88;
      case 0x1E8B: return 0x1E8A;
      case 0x1E8D: return 0x1E8C;
      case 0x1E8F: return 0x1E8E;
      case 0x1E91: return 0x1E90;
      case 0x1E93: return 0x1E92;
      case 0x1E95: return 0x1E94;
      case 0x1E9B: return 0x1E60;
      case 0x1EA1: return 0x1EA0;
      case 0x1EA3: return 0x1EA2;
      case 0x1EA5: return 0x1EA4;
      case 0x1EA7: return 0x1EA6;
      case 0x1EA9: return 0x1EA8;
      case 0x1EAB: return 0x1EAA;
      case 0x1EAD: return 0x1EAC;
      case 0x1EAF: return 0x1EAE;
      case 0x1EB1: return 0x1EB0;
      case 0x1EB3: return 0x1EB2;
      case 0x1EB5: return 0x1EB4;
      case 0x1EB7: return 0x1EB6;
      case 0x1EB9: return 0x1EB8;
      case 0x1EBB: return 0x1EBA;
      case 0x1EBD: return 0x1EBC;
      case 0x1EBF: return 0x1EBE;
      case 0x1EC1: return 0x1EC0;
      case 0x1EC3: return 0x1EC2;
      case 0x1EC5: return 0x1EC4;
      case 0x1EC7: return 0x1EC6;
      case 0x1EC9: return 0x1EC8;
      case 0x1ECB: return 0x1ECA;
      case 0x1ECD: return 0x1ECC;
      case 0x1ECF: return 0x1ECE;
      case 0x1ED1: return 0x1ED0;
      case 0x1ED3: return 0x1ED2;
      case 0x1ED5: return 0x1ED4;
      case 0x1ED7: return 0x1ED6;
      case 0x1ED9: return 0x1ED8;
      case 0x1EDB: return 0x1EDA;
      case 0x1EDD: return 0x1EDC;
      case 0x1EDF: return 0x1EDE;
      case 0x1EE1: return 0x1EE0;
      case 0x1EE3: return 0x1EE2;
      case 0x1EE5: return 0x1EE4;
      case 0x1EE7: return 0x1EE6;
      case 0x1EE9: return 0x1EE8;
      case 0x1EEB: return 0x1EEA;
      case 0x1EED: return 0x1EEC;
      case 0x1EEF: return 0x1EEE;
      case 0x1EF1: return 0x1EF0;
      case 0x1EF3: return 0x1EF2;
      case 0x1EF5: return 0x1EF4;
      case 0x1EF7: return 0x1EF6;
      case 0x1EF9: return 0x1EF8;
      case 0x1EFB: return 0x1EFA;
      case 0x1EFD: return 0x1EFC;
      case 0x1EFF: return 0x1EFE;
      case 0x1F00: return 0x1F08;
      case 0x1F01: return 0x1F09;
      case 0x1F02: return 0x1F0A;
      case 0x1F03: return 0x1F0B;
      case 0x1F04: return 0x1F0C;
      case 0x1F05: return 0x1F0D;
      case 0x1F06: return 0x1F0E;
      case 0x1F07: return 0x1F0F;
      case 0x1F10: return 0x1F18;
      case 0x1F11: return 0x1F19;
      case 0x1F12: return 0x1F1A;
      case 0x1F13: return 0x1F1B;
      case 0x1F14: return 0x1F1C;
      case 0x1F15: return 0x1F1D;
      case 0x1F20: return 0x1F28;
      case 0x1F21: return 0x1F29;
      case 0x1F22: return 0x1F2A;
      case 0x1F23: return 0x1F2B;
      case 0x1F24: return 0x1F2C;
      case 0x1F25: return 0x1F2D;
      case 0x1F26: return 0x1F2E;
      case 0x1F27: return 0x1F2F;
      case 0x1F30: return 0x1F38;
      case 0x1F31: return 0x1F39;
      case 0x1F32: return 0x1F3A;
      case 0x1F33: return 0x1F3B;
      case 0x1F34: return 0x1F3C;
      case 0x1F35: return 0x1F3D;
      case 0x1F36: return 0x1F3E;
      case 0x1F37: return 0x1F3F;
      case 0x1F40: return 0x1F48;
      case 0x1F41: return 0x1F49;
      case 0x1F42: return 0x1F4A;
      case 0x1F43: return 0x1F4B;
      case 0x1F44: return 0x1F4C;
      case 0x1F45: return 0x1F4D;
      case 0x1F51: return 0x1F59;
      case 0x1F53: return 0x1F5B;
      case 0x1F55: return 0x1F5D;
      case 0x1F57: return 0x1F5F;
      case 0x1F60: return 0x1F68;
      case 0x1F61: return 0x1F69;
      case 0x1F62: return 0x1F6A;
      case 0x1F63: return 0x1F6B;
      case 0x1F64: return 0x1F6C;
      case 0x1F65: return 0x1F6D;
      case 0x1F66: return 0x1F6E;
      case 0x1F67: return 0x1F6F;
      case 0x1F70: return 0x1FBA;
      case 0x1F71: return 0x1FBB;
      case 0x1F72: return 0x1FC8;
      case 0x1F73: return 0x1FC9;
      case 0x1F74: return 0x1FCA;
      case 0x1F75: return 0x1FCB;
      case 0x1F76: return 0x1FDA;
      case 0x1F77: return 0x1FDB;
      case 0x1F78: return 0x1FF8;
      case 0x1F79: return 0x1FF9;
      case 0x1F7A: return 0x1FEA;
      case 0x1F7B: return 0x1FEB;
      case 0x1F7C: return 0x1FFA;
      case 0x1F7D: return 0x1FFB;
      case 0x1F80: return 0x1F88;
      case 0x1F81: return 0x1F89;
      case 0x1F82: return 0x1F8A;
      case 0x1F83: return 0x1F8B;
      case 0x1F84: return 0x1F8C;
      case 0x1F85: return 0x1F8D;
      case 0x1F86: return 0x1F8E;
      case 0x1F87: return 0x1F8F;
      case 0x1F90: return 0x1F98;
      case 0x1F91: return 0x1F99;
      case 0x1F92: return 0x1F9A;
      case 0x1F93: return 0x1F9B;
      case 0x1F94: return 0x1F9C;
      case 0x1F95: return 0x1F9D;
      case 0x1F96: return 0x1F9E;
      case 0x1F97: return 0x1F9F;
      case 0x1FA0: return 0x1FA8;
      case 0x1FA1: return 0x1FA9;
      case 0x1FA2: return 0x1FAA;
      case 0x1FA3: return 0x1FAB;
      case 0x1FA4: return 0x1FAC;
      case 0x1FA5: return 0x1FAD;
      case 0x1FA6: return 0x1FAE;
      case 0x1FA7: return 0x1FAF;
      case 0x1FB0: return 0x1FB8;
      case 0x1FB1: return 0x1FB9;
      case 0x1FB3: return 0x1FBC;
      case 0x1FBE: return 0x0399;
      case 0x1FC3: return 0x1FCC;
      case 0x1FD0: return 0x1FD8;
      case 0x1FD1: return 0x1FD9;
      case 0x1FE0: return 0x1FE8;
      case 0x1FE1: return 0x1FE9;
      case 0x1FE5: return 0x1FEC;
      case 0x1FF3: return 0x1FFC;
      case 0x214E: return 0x2132;
      case 0x2170: return 0x2160;
      case 0x2171: return 0x2161;
      case 0x2172: return 0x2162;
      case 0x2173: return 0x2163;
      case 0x2174: return 0x2164;
      case 0x2175: return 0x2165;
      case 0x2176: return 0x2166;
      case 0x2177: return 0x2167;
      case 0x2178: return 0x2168;
      case 0x2179: return 0x2169;
      case 0x217A: return 0x216A;
      case 0x217B: return 0x216B;
      case 0x217C: return 0x216C;
      case 0x217D: return 0x216D;
      case 0x217E: return 0x216E;
      case 0x217F: return 0x216F;
      case 0x2184: return 0x2183;
      case 0x24D0: return 0x24B6;
      case 0x24D1: return 0x24B7;
      case 0x24D2: return 0x24B8;
      case 0x24D3: return 0x24B9;
      case 0x24D4: return 0x24BA;
      case 0x24D5: return 0x24BB;
      case 0x24D6: return 0x24BC;
      case 0x24D7: return 0x24BD;
      case 0x24D8: return 0x24BE;
      case 0x24D9: return 0x24BF;
      case 0x24DA: return 0x24C0;
      case 0x24DB: return 0x24C1;
      case 0x24DC: return 0x24C2;
      case 0x24DD: return 0x24C3;
      case 0x24DE: return 0x24C4;
      case 0x24DF: return 0x24C5;
      case 0x24E0: return 0x24C6;
      case 0x24E1: return 0x24C7;
      case 0x24E2: return 0x24C8;
      case 0x24E3: return 0x24C9;
      case 0x24E4: return 0x24CA;
      case 0x24E5: return 0x24CB;
      case 0x24E6: return 0x24CC;
      case 0x24E7: return 0x24CD;
      case 0x24E8: return 0x24CE;
      case 0x24E9: return 0x24CF;
      case 0x2C30: return 0x2C00;
      case 0x2C31: return 0x2C01;
      case 0x2C32: return 0x2C02;
      case 0x2C33: return 0x2C03;
      case 0x2C34: return 0x2C04;
      case 0x2C35: return 0x2C05;
      case 0x2C36: return 0x2C06;
      case 0x2C37: return 0x2C07;
      case 0x2C38: return 0x2C08;
      case 0x2C39: return 0x2C09;
      case 0x2C3A: return 0x2C0A;
      case 0x2C3B: return 0x2C0B;
      case 0x2C3C: return 0x2C0C;
      case 0x2C3D: return 0x2C0D;
      case 0x2C3E: return 0x2C0E;
      case 0x2C3F: return 0x2C0F;
      case 0x2C40: return 0x2C10;
      case 0x2C41: return 0x2C11;
      case 0x2C42: return 0x2C12;
      case 0x2C43: return 0x2C13;
      case 0x2C44: return 0x2C14;
      case 0x2C45: return 0x2C15;
      case 0x2C46: return 0x2C16;
      case 0x2C47: return 0x2C17;
      case 0x2C48: return 0x2C18;
      case 0x2C49: return 0x2C19;
      case 0x2C4A: return 0x2C1A;
      case 0x2C4B: return 0x2C1B;
      case 0x2C4C: return 0x2C1C;
      case 0x2C4D: return 0x2C1D;
      case 0x2C4E: return 0x2C1E;
      case 0x2C4F: return 0x2C1F;
      case 0x2C50: return 0x2C20;
      case 0x2C51: return 0x2C21;
      case 0x2C52: return 0x2C22;
      case 0x2C53: return 0x2C23;
      case 0x2C54: return 0x2C24;
      case 0x2C55: return 0x2C25;
      case 0x2C56: return 0x2C26;
      case 0x2C57: return 0x2C27;
      case 0x2C58: return 0x2C28;
      case 0x2C59: return 0x2C29;
      case 0x2C5A: return 0x2C2A;
      case 0x2C5B: return 0x2C2B;
      case 0x2C5C: return 0x2C2C;
      case 0x2C5D: return 0x2C2D;
      case 0x2C5E: return 0x2C2E;
      case 0x2C61: return 0x2C60;
      case 0x2C65: return 0x023A;
      case 0x2C66: return 0x023E;
      case 0x2C68: return 0x2C67;
      case 0x2C6A: return 0x2C69;
      case 0x2C6C: return 0x2C6B;
      case 0x2C73: return 0x2C72;
      case 0x2C76: return 0x2C75;
      case 0x2C81: return 0x2C80;
      case 0x2C83: return 0x2C82;
      case 0x2C85: return 0x2C84;
      case 0x2C87: return 0x2C86;
      case 0x2C89: return 0x2C88;
      case 0x2C8B: return 0x2C8A;
      case 0x2C8D: return 0x2C8C;
      case 0x2C8F: return 0x2C8E;
      case 0x2C91: return 0x2C90;
      case 0x2C93: return 0x2C92;
      case 0x2C95: return 0x2C94;
      case 0x2C97: return 0x2C96;
      case 0x2C99: return 0x2C98;
      case 0x2C9B: return 0x2C9A;
      case 0x2C9D: return 0x2C9C;
      case 0x2C9F: return 0x2C9E;
      case 0x2CA1: return 0x2CA0;
      case 0x2CA3: return 0x2CA2;
      case 0x2CA5: return 0x2CA4;
      case 0x2CA7: return 0x2CA6;
      case 0x2CA9: return 0x2CA8;
      case 0x2CAB: return 0x2CAA;
      case 0x2CAD: return 0x2CAC;
      case 0x2CAF: return 0x2CAE;
      case 0x2CB1: return 0x2CB0;
      case 0x2CB3: return 0x2CB2;
      case 0x2CB5: return 0x2CB4;
      case 0x2CB7: return 0x2CB6;
      case 0x2CB9: return 0x2CB8;
      case 0x2CBB: return 0x2CBA;
      case 0x2CBD: return 0x2CBC;
      case 0x2CBF: return 0x2CBE;
      case 0x2CC1: return 0x2CC0;
      case 0x2CC3: return 0x2CC2;
      case 0x2CC5: return 0x2CC4;
      case 0x2CC7: return 0x2CC6;
      case 0x2CC9: return 0x2CC8;
      case 0x2CCB: return 0x2CCA;
      case 0x2CCD: return 0x2CCC;
      case 0x2CCF: return 0x2CCE;
      case 0x2CD1: return 0x2CD0;
      case 0x2CD3: return 0x2CD2;
      case 0x2CD5: return 0x2CD4;
      case 0x2CD7: return 0x2CD6;
      case 0x2CD9: return 0x2CD8;
      case 0x2CDB: return 0x2CDA;
      case 0x2CDD: return 0x2CDC;
      case 0x2CDF: return 0x2CDE;
      case 0x2CE1: return 0x2CE0;
      case 0x2CE3: return 0x2CE2;
      case 0x2CEC: return 0x2CEB;
      case 0x2CEE: return 0x2CED;
      case 0x2CF3: return 0x2CF2;
      case 0x2D00: return 0x10A0;
      case 0x2D01: return 0x10A1;
      case 0x2D02: return 0x10A2;
      case 0x2D03: return 0x10A3;
      case 0x2D04: return 0x10A4;
      case 0x2D05: return 0x10A5;
      case 0x2D06: return 0x10A6;
      case 0x2D07: return 0x10A7;
      case 0x2D08: return 0x10A8;
      case 0x2D09: return 0x10A9;
      case 0x2D0A: return 0x10AA;
      case 0x2D0B: return 0x10AB;
      case 0x2D0C: return 0x10AC;
      case 0x2D0D: return 0x10AD;
      case 0x2D0E: return 0x10AE;
      case 0x2D0F: return 0x10AF;
      case 0x2D10: return 0x10B0;
      case 0x2D11: return 0x10B1;
      case 0x2D12: return 0x10B2;
      case 0x2D13: return 0x10B3;
      case 0x2D14: return 0x10B4;
      case 0x2D15: return 0x10B5;
      case 0x2D16: return 0x10B6;
      case 0x2D17: return 0x10B7;
      case 0x2D18: return 0x10B8;
      case 0x2D19: return 0x10B9;
      case 0x2D1A: return 0x10BA;
      case 0x2D1B: return 0x10BB;
      case 0x2D1C: return 0x10BC;
      case 0x2D1D: return 0x10BD;
      case 0x2D1E: return 0x10BE;
      case 0x2D1F: return 0x10BF;
      case 0x2D20: return 0x10C0;
      case 0x2D21: return 0x10C1;
      case 0x2D22: return 0x10C2;
      case 0x2D23: return 0x10C3;
      case 0x2D24: return 0x10C4;
      case 0x2D25: return 0x10C5;
      case 0x2D27: return 0x10C7;
      case 0x2D2D: return 0x10CD;
      case 0xA641: return 0xA640;
      case 0xA643: return 0xA642;
      case 0xA645: return 0xA644;
      case 0xA647: return 0xA646;
      case 0xA649: return 0xA648;
      case 0xA64B: return 0xA64A;
      case 0xA64D: return 0xA64C;
      case 0xA64F: return 0xA64E;
      case 0xA651: return 0xA650;
      case 0xA653: return 0xA652;
      case 0xA655: return 0xA654;
      case 0xA657: return 0xA656;
      case 0xA659: return 0xA658;
      case 0xA65B: return 0xA65A;
      case 0xA65D: return 0xA65C;
      case 0xA65F: return 0xA65E;
      case 0xA661: return 0xA660;
      case 0xA663: return 0xA662;
      case 0xA665: return 0xA664;
      case 0xA667: return 0xA666;
      case 0xA669: return 0xA668;
      case 0xA66B: return 0xA66A;
      case 0xA66D: return 0xA66C;
      case 0xA681: return 0xA680;
      case 0xA683: return 0xA682;
      case 0xA685: return 0xA684;
      case 0xA687: return 0xA686;
      case 0xA689: return 0xA688;
      case 0xA68B: return 0xA68A;
      case 0xA68D: return 0xA68C;
      case 0xA68F: return 0xA68E;
      case 0xA691: return 0xA690;
      case 0xA693: return 0xA692;
      case 0xA695: return 0xA694;
      case 0xA697: return 0xA696;
      case 0xA723: return 0xA722;
      case 0xA725: return 0xA724;
      case 0xA727: return 0xA726;
      case 0xA729: return 0xA728;
      case 0xA72B: return 0xA72A;
      case 0xA72D: return 0xA72C;
      case 0xA72F: return 0xA72E;
      case 0xA733: return 0xA732;
      case 0xA735: return 0xA734;
      case 0xA737: return 0xA736;
      case 0xA739: return 0xA738;
      case 0xA73B: return 0xA73A;
      case 0xA73D: return 0xA73C;
      case 0xA73F: return 0xA73E;
      case 0xA741: return 0xA740;
      case 0xA743: return 0xA742;
      case 0xA745: return 0xA744;
      case 0xA747: return 0xA746;
      case 0xA749: return 0xA748;
      case 0xA74B: return 0xA74A;
      case 0xA74D: return 0xA74C;
      case 0xA74F: return 0xA74E;
      case 0xA751: return 0xA750;
      case 0xA753: return 0xA752;
      case 0xA755: return 0xA754;
      case 0xA757: return 0xA756;
      case 0xA759: return 0xA758;
      case 0xA75B: return 0xA75A;
      case 0xA75D: return 0xA75C;
      case 0xA75F: return 0xA75E;
      case 0xA761: return 0xA760;
      case 0xA763: return 0xA762;
      case 0xA765: return 0xA764;
      case 0xA767: return 0xA766;
      case 0xA769: return 0xA768;
      case 0xA76B: return 0xA76A;
      case 0xA76D: return 0xA76C;
      case 0xA76F: return 0xA76E;
      case 0xA77A: return 0xA779;
      case 0xA77C: return 0xA77B;
      case 0xA77F: return 0xA77E;
      case 0xA781: return 0xA780;
      case 0xA783: return 0xA782;
      case 0xA785: return 0xA784;
      case 0xA787: return 0xA786;
      case 0xA78C: return 0xA78B;
      case 0xA791: return 0xA790;
      case 0xA793: return 0xA792;
      case 0xA7A1: return 0xA7A0;
      case 0xA7A3: return 0xA7A2;
      case 0xA7A5: return 0xA7A4;
      case 0xA7A7: return 0xA7A6;
      case 0xA7A9: return 0xA7A8;
      case 0xFF41: return 0xFF21;
      case 0xFF42: return 0xFF22;
      case 0xFF43: return 0xFF23;
      case 0xFF44: return 0xFF24;
      case 0xFF45: return 0xFF25;
      case 0xFF46: return 0xFF26;
      case 0xFF47: return 0xFF27;
      case 0xFF48: return 0xFF28;
      case 0xFF49: return 0xFF29;
      case 0xFF4A: return 0xFF2A;
      case 0xFF4B: return 0xFF2B;
      case 0xFF4C: return 0xFF2C;
      case 0xFF4D: return 0xFF2D;
      case 0xFF4E: return 0xFF2E;
      case 0xFF4F: return 0xFF2F;
      case 0xFF50: return 0xFF30;
      case 0xFF51: return 0xFF31;
      case 0xFF52: return 0xFF32;
      case 0xFF53: return 0xFF33;
      case 0xFF54: return 0xFF34;
      case 0xFF55: return 0xFF35;
      case 0xFF56: return 0xFF36;
      case 0xFF57: return 0xFF37;
      case 0xFF58: return 0xFF38;
      case 0xFF59: return 0xFF39;
      case 0xFF5A: return 0xFF3A;
      case 0x10428: return 0x10400;
      case 0x10429: return 0x10401;
      case 0x1042A: return 0x10402;
      case 0x1042B: return 0x10403;
      case 0x1042C: return 0x10404;
      case 0x1042D: return 0x10405;
      case 0x1042E: return 0x10406;
      case 0x1042F: return 0x10407;
      case 0x10430: return 0x10408;
      case 0x10431: return 0x10409;
      case 0x10432: return 0x1040A;
      case 0x10433: return 0x1040B;
      case 0x10434: return 0x1040C;
      case 0x10435: return 0x1040D;
      case 0x10436: return 0x1040E;
      case 0x10437: return 0x1040F;
      case 0x10438: return 0x10410;
      case 0x10439: return 0x10411;
      case 0x1043A: return 0x10412;
      case 0x1043B: return 0x10413;
      case 0x1043C: return 0x10414;
      case 0x1043D: return 0x10415;
      case 0x1043E: return 0x10416;
      case 0x1043F: return 0x10417;
      case 0x10440: return 0x10418;
      case 0x10441: return 0x10419;
      case 0x10442: return 0x1041A;
      case 0x10443: return 0x1041B;
      case 0x10444: return 0x1041C;
      case 0x10445: return 0x1041D;
      case 0x10446: return 0x1041E;
      case 0x10447: return 0x1041F;
      case 0x10448: return 0x10420;
      case 0x10449: return 0x10421;
      case 0x1044A: return 0x10422;
      case 0x1044B: return 0x10423;
      case 0x1044C: return 0x10424;
      case 0x1044D: return 0x10425;
      case 0x1044E: return 0x10426;
      case 0x1044F: return 0x10427;
      default: return ch;
    }
}


#ifdef PUSHED_MSC_WARNING
#  pragma warning( pop )
#  undef PUSHED_MSC_WARNING
#endif
/* end of file cwal_utf.c */
/* start of file JSON_parser/JSON_parser.c */
/*
Copyright (c) 2005 JSON.org

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

The Software shall be used for Good, not Evil.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

/*
    Callbacks, comments, Unicode handling by Jean Gressmann (jean@0x42.de), 2007-2010.
    
    
    Changelog:
        2010-11-25
            Support for custom memory allocation (sgbeal@googlemail.com).
                        
        2010-05-07
            Added error handling for memory allocation failure (sgbeal@googlemail.com). 
            Added diagnosis errors for invalid JSON.
            
        2010-03-25
            Fixed buffer overrun in grow_parse_buffer & cleaned up code.
            
        2009-10-19
            Replaced long double in JSON_value_struct with double after reports 
            of strtold being broken on some platforms (charles@transmissionbt.com).
            
        2009-05-17 
            Incorporated benrudiak@googlemail.com fix for UTF16 decoding.
            
        2009-05-14 
            Fixed float parsing bug related to a locale being set that didn't
            use '.' as decimal point character (charles@transmissionbt.com).
            
        2008-10-14 
            Renamed states.IN to states.IT to avoid name clash which IN macro
            defined in windef.h (alexey.pelykh@gmail.com)
            
        2008-07-19 
            Removed some duplicate code & debugging variable (charles@transmissionbt.com)
        
        2008-05-28 
            Made JSON_value structure ansi C compliant. This bug was report by 
            trisk@acm.jhu.edu
        
        2008-05-20 
            Fixed bug reported by charles@transmissionbt.com where the switching 
            from static to dynamic parse buffer did not copy the static parse 
            buffer's content.
*/



#include <assert.h>
#include <ctype.h>
#include <float.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>


#ifdef _MSC_VER
#   if _MSC_VER >= 1400 /* Visual Studio 2005 and up */
#      pragma warning(disable:4996) /* unsecure sscanf */
#      pragma warning(disable:4127) /* conditional expression is constant */
#   endif
#endif


#define true  1
#define false 0
#define __   -1     /* the universal error code */

/* values chosen so that the object size is approx equal to one page (4K) */
#ifndef JSON_PARSER_STACK_SIZE
#   define JSON_PARSER_STACK_SIZE 128
#endif

#ifndef JSON_PARSER_PARSE_BUFFER_SIZE
#   define JSON_PARSER_PARSE_BUFFER_SIZE 3500
#endif

typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason);

#ifdef JSON_PARSER_DEBUG_MALLOC
#   define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason)
#else
#   define JSON_parser_malloc(func, bytes, reason) func(bytes)
#endif

typedef unsigned short UTF16;

struct JSON_parser_struct {
    JSON_parser_callback callback;
    void* ctx;
    signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error;
    char decimal_point;
    UTF16 utf16_high_surrogate;
    int current_char;
    int depth;
    int top;
    int stack_capacity;
    signed char* stack;
    char* parse_buffer;
    size_t parse_buffer_capacity;
    size_t parse_buffer_count;
    signed char static_stack[JSON_PARSER_STACK_SIZE];
    char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE];
    JSON_malloc_t malloc;
    JSON_free_t free;
};

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

/*
    Characters are mapped into these character classes. This allows for
    a significant reduction in the size of the state transition table.
*/



enum classes {
    C_SPACE,  /* space */
    C_WHITE,  /* other whitespace */
    C_LCURB,  /* {  */
    C_RCURB,  /* } */
    C_LSQRB,  /* [ */
    C_RSQRB,  /* ] */
    C_COLON,  /* : */
    C_COMMA,  /* , */
    C_QUOTE,  /* " */
    C_BACKS,  /* \ */
    C_SLASH,  /* / */
    C_PLUS,   /* + */
    C_MINUS,  /* - */
    C_POINT,  /* . */
    C_ZERO ,  /* 0 */
    C_DIGIT,  /* 123456789 */
    C_LOW_A,  /* a */
    C_LOW_B,  /* b */
    C_LOW_C,  /* c */
    C_LOW_D,  /* d */
    C_LOW_E,  /* e */
    C_LOW_F,  /* f */
    C_LOW_L,  /* l */
    C_LOW_N,  /* n */
    C_LOW_R,  /* r */
    C_LOW_S,  /* s */
    C_LOW_T,  /* t */
    C_LOW_U,  /* u */
    C_ABCDF,  /* ABCDF */
    C_E,      /* E */
    C_ETC,    /* everything else */
    C_STAR,   /* * */   
    NR_CLASSES
};

static const signed char ascii_class[128] = {
/*
    This array maps the 128 ASCII characters into character classes.
    The remaining Unicode characters should be mapped to C_ETC.
    Non-whitespace control characters are errors.
*/
    __,      __,      __,      __,      __,      __,      __,      __,
    __,      C_WHITE, C_WHITE, __,      __,      C_WHITE, __,      __,
    __,      __,      __,      __,      __,      __,      __,      __,
    __,      __,      __,      __,      __,      __,      __,      __,

    C_SPACE, C_ETC,   C_QUOTE, C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
    C_ETC,   C_ETC,   C_STAR,   C_PLUS,  C_COMMA, C_MINUS, C_POINT, C_SLASH,
    C_ZERO,  C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT,
    C_DIGIT, C_DIGIT, C_COLON, C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,

    C_ETC,   C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E,     C_ABCDF, C_ETC,
    C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
    C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_ETC,
    C_ETC,   C_ETC,   C_ETC,   C_LSQRB, C_BACKS, C_RSQRB, C_ETC,   C_ETC,

    C_ETC,   C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC,
    C_ETC,   C_ETC,   C_ETC,   C_ETC,   C_LOW_L, C_ETC,   C_LOW_N, C_ETC,
    C_ETC,   C_ETC,   C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC,   C_ETC,
    C_ETC,   C_ETC,   C_ETC,   C_LCURB, C_ETC,   C_RCURB, C_ETC,   C_ETC
};


/*
    The state codes.
*/
enum states {
    GO,  /* start    */
    OK,  /* ok       */
    OB,  /* object   */
    KE,  /* key      */
    CO,  /* colon    */
    VA,  /* value    */
    AR,  /* array    */
    ST,  /* string   */
    ES,  /* escape   */
    U1,  /* u1       */
    U2,  /* u2       */
    U3,  /* u3       */
    U4,  /* u4       */
    MI,  /* minus    */
    ZE,  /* zero     */
    IT,  /* integer  */
    FR,  /* fraction */
    E1,  /* e        */
    E2,  /* ex       */
    E3,  /* exp      */
    T1,  /* tr       */
    T2,  /* tru      */
    T3,  /* true     */
    F1,  /* fa       */
    F2,  /* fal      */
    F3,  /* fals     */
    F4,  /* false    */
    N1,  /* nu       */
    N2,  /* nul      */
    N3,  /* null     */
    C1,  /* /        */
    C2,  /* / *     */
    C3,  /* *        */
    FX,  /* *.* *eE* */
    D1,  /* second UTF-16 character decoding started by \ */
    D2,  /* second UTF-16 character proceeded by u */
    NR_STATES
};

enum actions
{
    CB = -10, /* comment begin */
    CE = -11, /* comment end */
    FA = -12, /* false */
    TR = -13, /* false */
    NU = -14, /* null */
    DE = -15, /* double detected by exponent e E */
    DF = -16, /* double detected by fraction . */
    SB = -17, /* string begin */
    MX = -18, /* integer detected by minus */
    ZX = -19, /* integer detected by zero */
    IX = -20, /* integer detected by 1-9 */
    EX = -21, /* next char is escaped */
    UC = -22  /* Unicode character read */
};


static const signed char state_transition_table[NR_STATES][NR_CLASSES] = {
/*
    The state transition table takes the current state and the current symbol,
    and returns either a new state or an action. An action is represented as a
    negative number. A JSON text is accepted if at the end of the text the
    state is OK and if the mode is MODE_DONE.

                 white                                      1-9                                   ABCDF  etc
             space |  {  }  [  ]  :  ,  "  \  /  +  -  .  0  |  a  b  c  d  e  f  l  n  r  s  t  u  |  E  |  * */
/*start  GO*/ {GO,GO,-6,__,-5,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*ok     OK*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*object OB*/ {OB,OB,__,-9,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*key    KE*/ {KE,KE,__,__,__,__,__,__,SB,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*colon  CO*/ {CO,CO,__,__,__,__,-2,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*value  VA*/ {VA,VA,-6,__,-5,__,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__},
/*array  AR*/ {AR,AR,-6,__,-5,-7,__,__,SB,__,CB,__,MX,__,ZX,IX,__,__,__,__,__,FA,__,NU,__,__,TR,__,__,__,__,__},
/*string ST*/ {ST,__,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST},
/*escape ES*/ {__,__,__,__,__,__,__,__,ST,ST,ST,__,__,__,__,__,__,ST,__,__,__,ST,__,ST,ST,__,ST,U1,__,__,__,__},
/*u1     U1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U2,U2,U2,U2,U2,U2,U2,U2,__,__,__,__,__,__,U2,U2,__,__},
/*u2     U2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U3,U3,U3,U3,U3,U3,U3,U3,__,__,__,__,__,__,U3,U3,__,__},
/*u3     U3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,U4,U4,U4,U4,U4,U4,U4,U4,__,__,__,__,__,__,U4,U4,__,__},
/*u4     U4*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,UC,UC,UC,UC,UC,UC,UC,UC,__,__,__,__,__,__,UC,UC,__,__},
/*minus  MI*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,ZE,IT,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*zero   ZE*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*int    IT*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,DF,IT,IT,__,__,__,__,DE,__,__,__,__,__,__,__,__,DE,__,__},
/*frac   FR*/ {OK,OK,__,-8,__,-7,__,-3,__,__,CB,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__},
/*e      E1*/ {__,__,__,__,__,__,__,__,__,__,__,E2,E2,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*ex     E2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*exp    E3*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,E3,E3,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*tr     T1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T2,__,__,__,__,__,__,__},
/*tru    T2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,T3,__,__,__,__},
/*true   T3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__},
/*fa     F1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*fal    F2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F3,__,__,__,__,__,__,__,__,__},
/*fals   F3*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,F4,__,__,__,__,__,__},
/*false  F4*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__,__,__},
/*nu     N1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N2,__,__,__,__},
/*nul    N2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,N3,__,__,__,__,__,__,__,__,__},
/*null   N3*/ {__,__,__,__,__,__,__,__,__,__,CB,__,__,__,__,__,__,__,__,__,__,__,OK,__,__,__,__,__,__,__,__,__},
/*/      C1*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,C2},
/*/*     C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3},
/**      C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3},
/*_.     FX*/ {OK,OK,__,-8,__,-7,__,-3,__,__,__,__,__,__,FR,FR,__,__,__,__,E1,__,__,__,__,__,__,__,__,E1,__,__},
/*\      D1*/ {__,__,__,__,__,__,__,__,__,D2,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__},
/*\      D2*/ {__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,__,U1,__,__,__,__},
};


/*
    These modes can be pushed on the stack.
*/
enum modes {
    MODE_ARRAY = 1, 
    MODE_DONE = 2,  
    MODE_KEY = 3,   
    MODE_OBJECT = 4
};

static void set_error(JSON_parser jc)
{
    switch (jc->state) {
        case GO:
            switch (jc->current_char) {
            case '{': case '}': case '[': case ']': 
                jc->error = JSON_E_UNBALANCED_COLLECTION;
                break;
            default:
                jc->error = JSON_E_INVALID_CHAR;
                break;    
            }
            break;
        case OB:
            jc->error = JSON_E_EXPECTED_KEY;
            break;
        case AR:
            jc->error = JSON_E_UNBALANCED_COLLECTION;
            break;
        case CO:
            jc->error = JSON_E_EXPECTED_COLON;
            break;
        case KE:
            jc->error = JSON_E_EXPECTED_KEY;
            break;
        /* \uXXXX\uYYYY */
        case U1: case U2: case U3: case U4: case D1: case D2:
            jc->error = JSON_E_INVALID_UNICODE_SEQUENCE;
            break;
        /* true, false, null */
        case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3:
            jc->error = JSON_E_INVALID_KEYWORD;
            break;
        /* minus, integer, fraction, exponent */
        case MI: case ZE: case IT: case FR: case E1: case E2: case E3:
            jc->error = JSON_E_INVALID_NUMBER;
            break;
        default:
            jc->error = JSON_E_INVALID_CHAR;
            break;
    }
}

static int
push(JSON_parser jc, int mode)
{
/*
    Push a mode onto the stack. Return false if there is overflow.
*/
    assert(jc->top <= jc->stack_capacity);
    
    if (jc->depth < 0) {
        if (jc->top == jc->stack_capacity) {
            const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]);
            const size_t new_capacity = jc->stack_capacity * 2;
            const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]);
            void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack");
            if (!mem) {
                jc->error = JSON_E_OUT_OF_MEMORY;
                return false;
            }
            jc->stack_capacity = (int)new_capacity;
            memcpy(mem, jc->stack, bytes_to_copy);
            if (jc->stack != &jc->static_stack[0]) {
                jc->free(jc->stack);
            }
            jc->stack = (signed char*)mem;
        }
    } else {
        if (jc->top == jc->depth) {
            jc->error = JSON_E_NESTING_DEPTH_REACHED;
            return false;
        }
    }
    jc->stack[++jc->top] = (signed char)mode;
    return true;
}


static int
pop(JSON_parser jc, int mode)
{
/*
    Pop the stack, assuring that the current mode matches the expectation.
    Return false if there is underflow or if the modes mismatch.
*/
    if (jc->top < 0 || jc->stack[jc->top] != mode) {
        return false;
    }
    jc->top -= 1;
    return true;
}


#define parse_buffer_clear(jc) \
    do {\
        jc->parse_buffer_count = 0;\
        jc->parse_buffer[0] = 0;\
    } while (0)
    
#define parse_buffer_pop_back_char(jc)\
    do {\
        assert(jc->parse_buffer_count >= 1);\
        --jc->parse_buffer_count;\
        jc->parse_buffer[jc->parse_buffer_count] = 0;\
    } while (0)    



void delete_JSON_parser(JSON_parser jc)
{
    if (jc) {
        if (jc->stack != &jc->static_stack[0]) {
            jc->free((void*)jc->stack);
        }
        if (jc->parse_buffer != &jc->static_parse_buffer[0]) {
            jc->free((void*)jc->parse_buffer);
        }
        jc->free((void*)jc);
     }   
}

int JSON_parser_reset(JSON_parser jc)
{
    if (NULL == jc) {
        return false;
    }
    
    jc->state = GO;
    jc->top = -1;

    /* parser has been used previously? */
    if (NULL == jc->parse_buffer) {
    
        /* Do we want non-bound stack? */
        if (jc->depth > 0) {
            jc->stack_capacity = jc->depth;
            if (jc->depth <= (int)COUNTOF(jc->static_stack)) {
                jc->stack = &jc->static_stack[0];
            } else {
                const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]);
                jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack");
                if (jc->stack == NULL) {
                    return false;
                }
            }
        } else {
            jc->stack_capacity = (int)COUNTOF(jc->static_stack);
            jc->depth = -1;
            jc->stack = &jc->static_stack[0];
        }
        
        /* set up the parse buffer */
        jc->parse_buffer = &jc->static_parse_buffer[0];
        jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer);
    }
    
    /* set parser to start */
    push(jc, MODE_DONE);
    parse_buffer_clear(jc);
    
    return true;
}

JSON_parser
new_JSON_parser(JSON_config const * config)
{
/*
    new_JSON_parser starts the checking process by constructing a JSON_parser
    object. It takes a depth parameter that restricts the level of maximum
    nesting.

    To continue the process, call JSON_parser_char for each character in the
    JSON text, and then call JSON_parser_done to obtain the final result.
    These functions are fully reentrant.
*/

    int use_std_malloc = false;
    JSON_config default_config;
    JSON_parser jc;
    JSON_malloc_t alloc;
    
    /* set to default configuration if none was provided */
    if (NULL == config) {
        /* initialize configuration */
        init_JSON_config(&default_config);
        config = &default_config;
    }
    
    /* use std malloc if either the allocator or deallocator function isn't set */
    use_std_malloc = NULL == config->malloc || NULL == config->free;
    
    alloc = use_std_malloc ? malloc : config->malloc;
    
    jc = JSON_parser_malloc(alloc, sizeof(*jc), "parser");    
    
    if (NULL == jc) {
        return NULL;
    }
    
    /* configure the parser */
    memset(jc, 0, sizeof(*jc));
    jc->malloc = alloc;
    jc->free = use_std_malloc ? free : config->free;
    jc->callback = config->callback;
    jc->ctx = config->callback_ctx;
    jc->allow_comments = (signed char)(config->allow_comments != 0);
    jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0);
    jc->decimal_point = *localeconv()->decimal_point;
    /* We need to be able to push at least one object */
    jc->depth = config->depth == 0 ? 1 : config->depth;
    
    /* reset the parser */
    if (!JSON_parser_reset(jc)) {
        jc->free(jc);
        return NULL;
    }
    
    return jc;
}

static int parse_buffer_grow(JSON_parser jc)
{
    const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]);
    const size_t new_capacity = jc->parse_buffer_capacity * 2;
    const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]);
    void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer");
    
    if (mem == NULL) {
        jc->error = JSON_E_OUT_OF_MEMORY;
        return false;
    }
    
    assert(new_capacity > 0);
    memcpy(mem, jc->parse_buffer, bytes_to_copy);
    
    if (jc->parse_buffer != &jc->static_parse_buffer[0]) {
        jc->free(jc->parse_buffer);
    }
    
    jc->parse_buffer = (char*)mem;
    jc->parse_buffer_capacity = new_capacity;
    
    return true;
}

static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars)
{
    while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) {
        if (!parse_buffer_grow(jc)) {
            assert(jc->error == JSON_E_OUT_OF_MEMORY);
            return false;
        }
    }
    
    return true;
}

#define parse_buffer_has_space_for(jc, count) \
    (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity)

#define parse_buffer_push_back_char(jc, c)\
    do {\
        assert(parse_buffer_has_space_for(jc, 1)); \
        jc->parse_buffer[jc->parse_buffer_count++] = c;\
        jc->parse_buffer[jc->parse_buffer_count]   = 0;\
    } while (0)

#define assert_is_non_container_type(jc) \
    assert( \
        jc->type == JSON_T_NULL || \
        jc->type == JSON_T_FALSE || \
        jc->type == JSON_T_TRUE || \
        jc->type == JSON_T_FLOAT || \
        jc->type == JSON_T_INTEGER || \
        jc->type == JSON_T_STRING)
    

static int parse_parse_buffer(JSON_parser jc)
{
    if (jc->callback) {
        JSON_value value, *arg = NULL;
        
        if (jc->type != JSON_T_NONE) {
            assert_is_non_container_type(jc);
        
            switch(jc->type) {
                case JSON_T_FLOAT:
                    arg = &value;
                    if (jc->handle_floats_manually) {
                        value.vu.str.value = jc->parse_buffer;
                        value.vu.str.length = jc->parse_buffer_count;
                    } else { 
                        /* not checking with end pointer b/c there may be trailing ws */
                        value.vu.float_value = strtod(jc->parse_buffer, NULL);
                    }
                    break;
                case JSON_T_INTEGER:
                    arg = &value;
                    sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value);
                    break;
                case JSON_T_STRING:
                    arg = &value;
                    value.vu.str.value = jc->parse_buffer;
                    value.vu.str.length = jc->parse_buffer_count;
                    break;
            }
            
            if (!(*jc->callback)(jc->ctx, jc->type, arg)) {
                return false;
            }
        }
    }
    
    parse_buffer_clear(jc);
    
    return true;
}

#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800)
#define IS_LOW_SURROGATE(uc)  (((uc) & 0xFC00) == 0xDC00)
#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000)
static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 };

static int decode_unicode_char(JSON_parser jc)
{
    int i;
    unsigned uc = 0;
    char* p;
    int trail_bytes;
    
    assert(jc->parse_buffer_count >= 6);
    
    p = &jc->parse_buffer[jc->parse_buffer_count - 4];
    
    for (i = 12; i >= 0; i -= 4, ++p) {
        unsigned x = *p;
        
        if (x >= 'a') {
            x -= ('a' - 10);
        } else if (x >= 'A') {
            x -= ('A' - 10);
        } else {
            x &= ~0x30u;
        }
        
        assert(x < 16);
        
        uc |= x << i;
    }
    
    /* clear UTF-16 char from buffer */
    jc->parse_buffer_count -= 6;
    jc->parse_buffer[jc->parse_buffer_count] = 0;
    
    /* attempt decoding ... */
    if (jc->utf16_high_surrogate) {
        if (IS_LOW_SURROGATE(uc)) {
            uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc);
            trail_bytes = 3;
            jc->utf16_high_surrogate = 0;
        } else {
            /* high surrogate without a following low surrogate */
            return false;
        }
    } else {
        if (uc < 0x80) {
            trail_bytes = 0;
        } else if (uc < 0x800) {
            trail_bytes = 1;
        } else if (IS_HIGH_SURROGATE(uc)) {
            /* save the high surrogate and wait for the low surrogate */
            jc->utf16_high_surrogate = (UTF16)uc;
            return true;
        } else if (IS_LOW_SURROGATE(uc)) {
            /* low surrogate without a preceding high surrogate */
            return false;
        } else {
            trail_bytes = 2;
        }
    }
    
    jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]);
    
    for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) {
        jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80);
    }

    jc->parse_buffer[jc->parse_buffer_count] = 0;
    
    return true;
}

static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char)
{
    assert(parse_buffer_has_space_for(jc, 1));
    
    jc->escaped = 0;
    /* remove the backslash */
    parse_buffer_pop_back_char(jc);
    switch(next_char) {
        case 'b':
            parse_buffer_push_back_char(jc, '\b');
            break;
        case 'f':
            parse_buffer_push_back_char(jc, '\f');
            break;
        case 'n':
            parse_buffer_push_back_char(jc, '\n');
            break;
        case 'r':
            parse_buffer_push_back_char(jc, '\r');
            break;
        case 't':
            parse_buffer_push_back_char(jc, '\t');
            break;
        case '"':
            parse_buffer_push_back_char(jc, '"');
            break;
        case '\\':
            parse_buffer_push_back_char(jc, '\\');
            break;
        case '/':
            parse_buffer_push_back_char(jc, '/');
            break;
        case 'u':
            parse_buffer_push_back_char(jc, '\\');
            parse_buffer_push_back_char(jc, 'u');
            break;
        default:
            return false;
    }

    return true;
}

static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class)
{
    if (!parse_buffer_reserve_for(jc, 1)) {
        assert(JSON_E_OUT_OF_MEMORY == jc->error);
        return false;
    }
    
    if (jc->escaped) {
        if (!add_escaped_char_to_parse_buffer(jc, next_char)) {
            jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE;
            return false; 
        }
    } else if (!jc->comment) {
        if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) {
            parse_buffer_push_back_char(jc, (char)next_char);
        }
    }
    
    return true;
}

#define assert_type_isnt_string_null_or_bool(jc) \
    assert(jc->type != JSON_T_FALSE); \
    assert(jc->type != JSON_T_TRUE); \
    assert(jc->type != JSON_T_NULL); \
    assert(jc->type != JSON_T_STRING)


int
JSON_parser_char(JSON_parser jc, int next_char)
{
/*
    After calling new_JSON_parser, call this function for each character (or
    partial character) in your JSON text. It can accept UTF-8, UTF-16, or
    UTF-32. It returns true if things are looking ok so far. If it rejects the
    text, it returns false.
*/
    int next_class, next_state;

/*
    Store the current char for error handling
*/    
    jc->current_char = next_char;
    
/*
    Determine the character's class.
*/
    if (next_char < 0) {
        jc->error = JSON_E_INVALID_CHAR;
        return false;
    }
    if (next_char >= 128) {
        next_class = C_ETC;
    } else {
        next_class = ascii_class[next_char];
        if (next_class <= __) {
            set_error(jc);
            return false;
        }
    }
    
    if (!add_char_to_parse_buffer(jc, next_char, next_class)) {
        return false;
    }
    
/*
    Get the next state from the state transition table.
*/
    next_state = state_transition_table[jc->state][next_class];
    if (next_state >= 0) {
/*
    Change the state.
*/
        jc->state = (signed char)next_state;
    } else {
/*
    Or perform one of the actions.
*/
        switch (next_state) {
/* Unicode character */        
        case UC:
            if(!decode_unicode_char(jc)) {
                jc->error = JSON_E_INVALID_UNICODE_SEQUENCE;
                return false;
            }
            /* check if we need to read a second UTF-16 char */
            if (jc->utf16_high_surrogate) {
                jc->state = D1;
            } else {
                jc->state = ST;
            }
            break;
/* escaped char */
        case EX:
            jc->escaped = 1;
            jc->state = ES;
            break;
/* integer detected by minus */
        case MX:
            jc->type = JSON_T_INTEGER;
            jc->state = MI;
            break;  
/* integer detected by zero */            
        case ZX:
            jc->type = JSON_T_INTEGER;
            jc->state = ZE;
            break;  
/* integer detected by 1-9 */            
        case IX:
            jc->type = JSON_T_INTEGER;
            jc->state = IT;
            break;  
            
/* floating point number detected by exponent*/
        case DE:
            assert_type_isnt_string_null_or_bool(jc);
            jc->type = JSON_T_FLOAT;
            jc->state = E1;
            break;   
        
/* floating point number detected by fraction */
        case DF:
            assert_type_isnt_string_null_or_bool(jc);
            if (!jc->handle_floats_manually) {
/*
    Some versions of strtod (which underlies sscanf) don't support converting 
    C-locale formated floating point values.
*/           
                assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.');
                jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point;
            }            
            jc->type = JSON_T_FLOAT;
            jc->state = FX;
            break;   
/* string begin " */
        case SB:
            parse_buffer_clear(jc);
            assert(jc->type == JSON_T_NONE);
            jc->type = JSON_T_STRING;
            jc->state = ST;
            break;        
        
/* n */
        case NU:
            assert(jc->type == JSON_T_NONE);
            jc->type = JSON_T_NULL;
            jc->state = N1;
            break;        
/* f */
        case FA:
            assert(jc->type == JSON_T_NONE);
            jc->type = JSON_T_FALSE;
            jc->state = F1;
            break;        
/* t */
        case TR:
            assert(jc->type == JSON_T_NONE);
            jc->type = JSON_T_TRUE;
            jc->state = T1;
            break;        
        
/* closing comment */
        case CE:
            jc->comment = 0;
            assert(jc->parse_buffer_count == 0);
            assert(jc->type == JSON_T_NONE);
            jc->state = jc->before_comment_state;
            break;        
        
/* opening comment  */
        case CB:
            if (!jc->allow_comments) {
                return false;
            }
            parse_buffer_pop_back_char(jc);
            if (!parse_parse_buffer(jc)) {
                return false;
            }
            assert(jc->parse_buffer_count == 0);
            assert(jc->type != JSON_T_STRING);
            switch (jc->stack[jc->top]) {
            case MODE_ARRAY:
            case MODE_OBJECT:   
                switch(jc->state) {
                case VA:
                case AR:
                    jc->before_comment_state = jc->state;
                    break;
                default:
                    jc->before_comment_state = OK;
                    break;
                }
                break;
            default:
                jc->before_comment_state = jc->state;
                break;
            }
            jc->type = JSON_T_NONE;
            jc->state = C1;
            jc->comment = 1;
            break;
/* empty } */
        case -9:        
            parse_buffer_clear(jc);
            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) {
                return false;
            }
            if (!pop(jc, MODE_KEY)) {
                return false;
            }
            jc->state = OK;
            break;

/* } */ case -8:
            parse_buffer_pop_back_char(jc);
            if (!parse_parse_buffer(jc)) {
                return false;
            }
            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) {
                return false;
            }
            if (!pop(jc, MODE_OBJECT)) {
                jc->error = JSON_E_UNBALANCED_COLLECTION;
                return false;
            }
            jc->type = JSON_T_NONE;
            jc->state = OK;
            break;

/* ] */ case -7:
            parse_buffer_pop_back_char(jc);
            if (!parse_parse_buffer(jc)) {
                return false;
            }
            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) {
                return false;
            }
            if (!pop(jc, MODE_ARRAY)) {
                jc->error = JSON_E_UNBALANCED_COLLECTION;
                return false;
            }
            
            jc->type = JSON_T_NONE;
            jc->state = OK;
            break;

/* { */ case -6:
            parse_buffer_pop_back_char(jc);
            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) {
                return false;
            }
            if (!push(jc, MODE_KEY)) {
                return false;
            }
            assert(jc->type == JSON_T_NONE);
            jc->state = OB;
            break;

/* [ */ case -5:
            parse_buffer_pop_back_char(jc);
            if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) {
                return false;
            }
            if (!push(jc, MODE_ARRAY)) {
                return false;
            }
            assert(jc->type == JSON_T_NONE);
            jc->state = AR;
            break;

/* string end " */ case -4:
            parse_buffer_pop_back_char(jc);
            switch (jc->stack[jc->top]) {
            case MODE_KEY:
                assert(jc->type == JSON_T_STRING);
                jc->type = JSON_T_NONE;
                jc->state = CO;
                
                if (jc->callback) {
                    JSON_value value;
                    value.vu.str.value = jc->parse_buffer;
                    value.vu.str.length = jc->parse_buffer_count;
                    if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) {
                        return false;
                    }
                }
                parse_buffer_clear(jc);
                break;
            case MODE_ARRAY:
            case MODE_OBJECT:
                assert(jc->type == JSON_T_STRING);
                if (!parse_parse_buffer(jc)) {
                    return false;
                }
                jc->type = JSON_T_NONE;
                jc->state = OK;
                break;
            default:
                return false;
            }
            break;

/* , */ case -3:
            parse_buffer_pop_back_char(jc);
            if (!parse_parse_buffer(jc)) {
                return false;
            }
            switch (jc->stack[jc->top]) {
            case MODE_OBJECT:
/*
    A comma causes a flip from object mode to key mode.
*/
                if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) {
                    return false;
                }
                assert(jc->type != JSON_T_STRING);
                jc->type = JSON_T_NONE;
                jc->state = KE;
                break;
            case MODE_ARRAY:
                assert(jc->type != JSON_T_STRING);
                jc->type = JSON_T_NONE;
                jc->state = VA;
                break;
            default:
                return false;
            }
            break;

/* : */ case -2:
/*
    A colon causes a flip from key mode to object mode.
*/
            parse_buffer_pop_back_char(jc);
            if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) {
                return false;
            }
            assert(jc->type == JSON_T_NONE);
            jc->state = VA;
            break;
/*
    Bad action.
*/
        default:
            set_error(jc);
            return false;
        }
    }
    return true;
}

int
JSON_parser_done(JSON_parser jc)
{
    if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE))
    {
        return true;
    }

    jc->error = JSON_E_UNBALANCED_COLLECTION;
    return false;
}


int JSON_parser_is_legal_white_space_string(const char* s)
{
    int c, char_class;
    
    if (s == NULL) {
        return false;
    }
    
    for (; *s; ++s) {   
        c = *s;
        
        if (c < 0 || c >= 128) {
            return false;
        }
        
        char_class = ascii_class[c];
        
        if (char_class != C_SPACE && char_class != C_WHITE) {
            return false;
        }
    }
    
    return true;
}

int JSON_parser_get_last_error(JSON_parser jc)
{
    return jc->error;
}


void init_JSON_config(JSON_config* config)
{
    if (config) {
        memset(config, 0, sizeof(*config));
        
        config->depth = JSON_PARSER_STACK_SIZE - 1;
        config->malloc = malloc;
        config->free = free;
    }
}
/* end of file JSON_parser/JSON_parser.c */
/* end of file ../cwal_amalgamation.c */
/* start of file th1ish_io.c */
#include <assert.h>
#include <memory.h> /* strlen() */

#if 1
#include <stdio.h>
#define MARKER(pfexp)                                                \
    do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
        printf pfexp; \
    } while(0)
#else
#define MARKER(pfexp) if(0) printf pfexp
#endif
#define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args); \
    assert(ie)

int th1ish_io_cb_flush( cwal_callback_args const * args, cwal_value **rv ){
    int rc = cwal_output_flush(args->engine);
    ARGS_IE;
    if(rc) rc = th1ish_toss(ie, rc, "Flushing output failed.");
    /* Weird: if i output from here, my error messages caught
       via handling linenoise input are flushed immediately,
       else they are not.
    */
    /* MARKER(("Flushed %d\n",rc)); */
    return rc;
}
/**
   Script usage:

   $output args...

   each arg is output with no separator and no newline at the end.

   If args->callee.separator is set then that value is output between
   each item. If args->callee.eol is set then that value is output
   after all arguments.

*/
int th1ish_io_cb_output( cwal_callback_args const * args, cwal_value **rv ){
    uint16_t i;
    int rc = 0;
    static cwal_json_output_opt JsonOutOpt = cwal_json_output_opt_empty_m;
    cwal_value * vsep = cwal_prop_get(cwal_function_value(args->callee),
                                      "separator", 9);
    cwal_value * veol = cwal_prop_get(cwal_function_value(args->callee),
                                      "eol", 3);
    th1ish_interp * ie = (th1ish_interp *)args->state;
    cwal_engine * e = args->engine;
    cwal_buffer * buf = &ie->buffer;
    cwal_size_t sepLen = 0;
    cwal_size_t eolLen = 0;
    char const * eolStr = 0;
    buf->used = 0;
    if(vsep){
        rc = th1ish_value_to_buffer( ie, buf, vsep );
        sepLen = buf->used;
        if(rc) goto end;
    }
    if(veol){
        /*rc = cwal_buffer_append( ie->e, buf, "\0", 1 );
          if(rc) goto end;*/
        rc = th1ish_value_to_buffer( ie, buf, veol );
        if(rc) goto end;
        eolStr = (char const *)(buf->mem + sepLen + 1);
        eolLen = buf->used - sepLen - 1;
        if(rc) goto end;
        
    }
    /* dump_val(args->self, "'this' for print()"); */
    /* MARKER(("th1ish_f_print() called with %"PRIu16" arg(s).\n", args->argc)); */
    for(i = 0; !rc && (i < args->argc); ++i ){
        cwal_value * v = args->argv[i];
        if(i && sepLen){
            rc = cwal_output(e, buf->mem, sepLen);
        }
        if(rc) break;
        switch(cwal_value_type_id(v)){
          case CWAL_TYPE_INTEGER:
          case CWAL_TYPE_DOUBLE:
          case CWAL_TYPE_BOOL:
          case CWAL_TYPE_OBJECT:
          case CWAL_TYPE_ARRAY:
          case CWAL_TYPE_NULL:
          case CWAL_TYPE_EXCEPTION:
              rc = cwal_json_output_engine( e, v, &JsonOutOpt );
              break;
          case CWAL_TYPE_UNDEF:
              rc = cwal_output(e, "undefined", 9);
              break;
          case CWAL_TYPE_STRING:{
              cwal_string const * str = cwal_value_get_string(v);
              char const * cstr = cwal_string_cstr(str);
              cwal_size_t const slen = cwal_string_length_bytes(str);
              rc = slen ? cwal_output(e, cstr, slen) : 0;
              break;
          }
          case CWAL_TYPE_BUFFER:{
              cwal_buffer const * vb = cwal_value_get_buffer(v);
              rc = vb->used ? cwal_output(e, vb->mem, vb->used) : 0;
              break;
          }
          case CWAL_TYPE_FUNCTION:
          case CWAL_TYPE_NATIVE:
              rc = cwal_outputf(e, "%s@%p",
                                cwal_value_type_name(v),
                                (void const*)v);
              break;
          default:
              break;
        }
    }
    end:
    if(!rc && eolLen){
        rc = cwal_output(e, eolStr, eolLen);
    }
    if(rc){
        rc = cwal_exception_setf(args->engine, rc, "Output error #%d (%s).",
                                 rc, cwal_rc_cstr(rc));
    }
    /*cwal_result_set(args->engine, cwal_value_undefined());*/
    /* cwal_output_flush(args->engine); */
    *rv = NULL;
    return rc;
}


int th1ish_setup_io( th1ish_interp * ie, cwal_value * ns ){
    int rc;
    cwal_value * v;
    cwal_value * outer;
    if(!cwal_props_can(ns)) return CWAL_RC_MISUSE;

    outer = th1ish_new_object(ie);
    rc = cwal_prop_set( ns, "io", 2, outer );
    if(rc) goto end;

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

    FUNC2("flush", th1ish_io_cb_flush);
    FUNC2("output", th1ish_io_cb_output);
    FUNC2("slurpFile", th1ish_f_slurp_file);
    
#undef FUNC2
#undef CHECKV
    end:
    return rc;
}

#undef MARKER
#undef ARGS_IE
/* end of file th1ish_io.c */
/* start of file th1ish_mod.c */
#include <assert.h>
#include <memory.h> /* strlen() */

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

/** @def TH1ISH_HAVE_DLOPEN

    If set to true, use dlopen() and friends. Requires
    linking to -ldl on most platforms.

    Only one of TH1ISH_HAVE_DLOPEN and TH1ISH_HAVE_LTDLOPEN may be
    true.
*/
/** @def TH1ISH_HAVE_LTDLOPEN

    If set to true, use lt_dlopen() and friends. Requires
    linking to -lltdl on most platforms.

    Only one of TH1ISH_HAVE_DLOPEN and TH1ISH_HAVE_LTDLOPEN may be
    true.
*/

/*
  #define TH1ISH_HAVE_DLOPEN 1
  #define TH1ISH_HAVE_LTDLOPEN 0
*/

#if !defined(TH1ISH_HAVE_DLOPEN)
#define TH1ISH_HAVE_DLOPEN 0
#endif

#if !defined(TH1ISH_HAVE_LTDLOPEN)
#define TH1ISH_HAVE_LTDLOPEN 0
#endif

#if !defined(TH1ISH_ENABLE_MODULES)
#  define TH1ISH_ENABLE_MODULES (TH1ISH_HAVE_LTDLOPEN || TH1ISH_HAVE_DLOPEN)
#endif

/**
   For access() resp. _access().

   FIXME: move the platform determination somewhere more global.
*/
#if defined(_WIN32)
#  define SMELLS_LIKE_UNIX 0
#else
#  define SMELLS_LIKE_UNIX 1
#endif

#if SMELLS_LIKE_UNIX
#  include <unistd.h> /* access() */
#else
#  include  <io.h>
#endif

#if TH1ISH_HAVE_LTDLOPEN
#  include <ltdl.h>
typedef lt_dlhandle dl_handle_t;
#elif TH1ISH_HAVE_DLOPEN
typedef void * dl_handle_t;
#  include <dlfcn.h> /* this actually has a different name on some platforms! */
#elif !TH1ISH_ENABLE_MODULES
typedef int dl_handle_t;
#else
#  error "We have no dlopen() impl for this configuration."
#endif

#if TH1ISH_ENABLE_MODULES
static void dl_init(){
    static char once  = 0;
    if(!once){
        once = 1;
#  if TH1ISH_HAVE_LTDLOPEN
        lt_dlinit();
        lt_dlopen( 0 );
#  elif TH1ISH_HAVE_DLOPEN
        dlopen( 0, RTLD_NOW | RTLD_GLOBAL );
#  endif
    }
}
#endif

#if TH1ISH_ENABLE_MODULES
static dl_handle_t dl_open( char const * fname, char const **errMsg ){
    dl_handle_t soh;
#if TH1ISH_HAVE_LTDLOPEN
    soh = lt_dlopen( fname );
#elif TH1ISH_HAVE_DLOPEN
    soh = dlopen( fname, RTLD_NOW | RTLD_GLOBAL );
#endif
    if(!soh && errMsg){
#if TH1ISH_HAVE_LTDLOPEN
        *errMsg = lt_dlerror();
#elif TH1ISH_HAVE_DLOPEN
        *errMsg = dlerror();
#endif
    }
    return soh;
}
#endif

#if TH1ISH_ENABLE_MODULES
static th1ish_loadable_module const * dl_sym( dl_handle_t soh, char const * mname ){
    dl_handle_t sym =
#if TH1ISH_HAVE_LTDLOPEN
        lt_dlsym( soh, mname )
#elif TH1ISH_HAVE_DLOPEN
        dlsym( soh, mname )
#else
        NULL
#endif
        ;
    return sym ? *((th1ish_loadable_module const **)sym) : NULL;
}
#endif

#if TH1ISH_ENABLE_MODULES
static void dl_close( dl_handle_t soh ){
    /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */
#if TH1ISH_HAVE_LTDLOPEN
    lt_dlclose( soh );
#elif TH1ISH_HAVE_DLOPEN
    dlclose( soh );
#endif
}
#endif

void th1ish_modules_close( th1ish_interp * ie ){
    if(ie){
#if !TH1ISH_ENABLE_MODULES
        assert(!ie->modules.list);
#else
        if(ie->modules.list){
            /* dlcose() does not seem to care whether they are closed
               in reverse-opened order or not, and it leaks all of
               them on my box. lt_dlclose() leaks even more!
            */
#if 0
            int i;
            assert(ie->modules.count);
            i = ((int)ie->modules.count) - 1;
            for( ; i >= 0; --i ){
                void * soh = ie->modules.list[i];
                assert(soh);
                dl_close(soh);
            }
#else
            /*
              Closing DLLs is NOT generically safe because we may
              stomp resources used elsewhere. It can't be done 100%
              reliably at this level, i am fully convinced. Let the OS
              clean them up.
            */
#endif
            cwal_list_reserve( ie->e, &ie->modules, 0 );
            assert(!ie->modules.list);
            assert(!ie->modules.count);
            assert(!ie->modules.alloced);
        }
#endif
    }
}

int th1ish_module_load( th1ish_interp * ie,
                        char const * fname,
                        char const * symName,
                        cwal_value * injectPoint,
                        char const ** errMsg ){
#if !TH1ISH_ENABLE_MODULES
    if(errMsg) *errMsg =
        "No dlopen() equivalent is installed for this build configuration.";
    return CWAL_RC_UNSUPPORTED;
#else
#if !TH1ISH_HAVE_LTDLOPEN && !TH1ISH_HAVE_DLOPEN
#   error "We have no dlopen() and friends impl for this configuration."
#endif
    if(!ie || !fname || !*fname || !injectPoint) return CWAL_RC_MISUSE;
    else if(!cwal_props_can(injectPoint)) return CWAL_RC_TYPE;
    else {
        static char once = 0;
        dl_handle_t soh;
        if(!once){
            once = 1;
            dl_init();
        }
        soh = dl_open( fname, errMsg );
        if(!soh) return CWAL_RC_ERROR;
        else {
            enum { MaxLen = 60 };
            char buf[MaxLen] = {0};
            cwal_size_t const slen = symName ? strlen(symName) : 0;
            th1ish_loadable_module const * mod;
            if(slen > (MaxLen-20)) return CWAL_RC_RANGE;
            if(!slen){
                memcpy(buf, "th1ish_module", 13);
                buf[14] = 0;
            }else{
                sprintf(buf,"th1ish_module_%s", symName);
            }
            mod = dl_sym( soh, buf );
            /* MARKER(("module=%s ==> %p\n",buf, (void const *)mod)); */
            if(mod){
                cwal_size_t i = 0;
                char found = 0;
                for( ; i < ie->modules.count; ++i ){
                    if(soh == ie->modules.list[i]){
                        found = 1;
                        break;
                    }
                }
                if(!found){
                    int const rc = cwal_list_append(ie->e,
                                                    &ie->modules, soh);
                    if(rc){
                        dl_close(soh);
                        return rc;
                    }
                }
            }
            return mod
                ? mod->init( ie, injectPoint )
                : CWAL_RC_NOT_FOUND;
        }
    }
#endif
}

int th1ish_f_module_load( cwal_callback_args const * args,
                          cwal_value **rv ){
    th1ish_interp * ie = (th1ish_interp *)args->state;
#if !TH1ISH_ENABLE_MODULES
    assert(ie);
    return th1ish_toss(ie, CWAL_RC_UNSUPPORTED,
                       "Module loading is not supported "
                       "in this build configuration.");
#else
    int rc;
    char const * fn;
    char const * sym = NULL;
    cwal_value * injectPoint;
    char const * errMsg = NULL;
    assert(ie);
    if(args->argc < 2){
        goto misuse;
    }
    fn = cwal_string_cstr(cwal_value_get_string(args->argv[0]));
    sym = fn ? cwal_string_cstr(cwal_value_get_string(args->argv[1])) : NULL;
    if(args->argc>2){
        injectPoint = args->argv[2];
    }else{
        injectPoint = args->argv[1];
        sym = NULL;
    }
    if(!injectPoint || !cwal_props_can(injectPoint)){
        goto misuse;
    }
    
    rc = th1ish_module_load(ie, fn, sym, injectPoint, &errMsg);
    /* MARKER(("load_module(%s, %s) rc=%d\n", fn, sym, rc)); */
    if(rc){
        if(errMsg){
            rc = th1ish_toss(ie, rc, "Opening DLL failed with code "
                             "%d (%s): %s", rc, cwal_rc_cstr(rc), errMsg);
        }else{
            rc = th1ish_toss(ie, rc, "Loading module failed with code "
                             "%d (%s).", rc, cwal_rc_cstr(rc));
        }
    }else{
        *rv = injectPoint;
    }
    return rc;
    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE,
                       "Expecting a (String,String,Object) "
                       "or (String,Object) arguments.");
#endif
}


char th1ish_file_is_accessible( char const * fn, char checkWrite ){
#ifdef _WIN32
#  define CHECKACCESS _access
#  define CHECKRIGHTS (checkWrite ? 2 : 4)
    /*
      2==writeable
      4==readable
      6==r/w
    */
#elif SMELLS_LIKE_UNIX
#  define CHECKACCESS access
#  define CHECKRIGHTS (checkWrite ? W_OK : R_OK)
#else
# error "Don't know how to check file readability in this configuration!"
#endif
    return (0 == CHECKACCESS( fn, CHECKRIGHTS ));
#undef CHECKACCESS
#undef CHECKRIGHTS

}

int th1ish_f_file_accessible( cwal_callback_args const * args, cwal_value **rv ){
    th1ish_interp * ie = (th1ish_interp *)args->state;
    assert(ie);
    if(!args->argc || !cwal_value_is_string(args->argv[0])){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a string argument.");
    }
    else{
        char const * fn = cwal_string_cstr(cwal_value_get_string(args->argv[0]));
        char const checkWrite = (args->argc>1)
            ? cwal_value_get_bool(args->argv[1])
            : 0;
        assert(fn);
        *rv = th1ish_file_is_accessible(fn, checkWrite)
            ? cwal_value_true()
            : cwal_value_false()
            ;
        return 0;
    }
}

#undef MARKER
#undef SMELLS_LIKE_UNIX
/* end of file th1ish_mod.c */
/* start of file th1ish_num.c */
#include <assert.h>
#include <memory.h> /* strlen() */

#include <stdio.h>
#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf


int th1ish_number_cb_parseInt( cwal_callback_args const * args, cwal_value **rv ){
    if(!args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "parseInt requires one argument.");
    }else{
        cwal_value * v = args->argv[0];
        switch(cwal_value_type_id(v)){
          case CWAL_TYPE_INTEGER:
              *rv = v;
              return 0;
          case CWAL_TYPE_DOUBLE:
          case CWAL_TYPE_BOOL:
              *rv = cwal_new_integer( args->engine, cwal_value_get_integer(v) );
              return *rv ? 0 : CWAL_RC_OOM;
          case CWAL_TYPE_STRING:{
              cwal_int_t i = 0;
              int rc = cwal_string_to_int( cwal_value_get_string(v), &i );
              if(rc){
                  *rv = cwal_value_undefined();
                  rc = 0;
              }else{
                  *rv = cwal_new_integer(args->engine, i);
              }
              return rc;
          }
          default:
              return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                         "parseInt does not know how to handle "
                                         "arguments of type '%s'.",
                                         cwal_value_type_name(v));
        }
    }
}

int th1ish_number_cb_parseDouble( cwal_callback_args const * args, cwal_value **rv ){
    if(!args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "parseDouble requires one argument.");
    }else{
        cwal_value * v = args->argv[0];
        switch(cwal_value_type_id(v)){
          case CWAL_TYPE_DOUBLE:
              *rv = v;
              return 0;
          case CWAL_TYPE_INTEGER:
          case CWAL_TYPE_BOOL:
              *rv = cwal_new_double( args->engine, cwal_value_get_integer(v) );
              return *rv ? 0 : CWAL_RC_OOM;
          case CWAL_TYPE_STRING:{
              cwal_double_t d = 0;
              int rc = cwal_string_to_double( cwal_value_get_string(v), &d );
              if(rc){
                  *rv = cwal_value_undefined();
                  rc = 0;
              }else{
                  *rv = cwal_new_double(args->engine, d);
              }
              return rc;
          }
          default:
              return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                         "parseDouble does not know how to handle "
                                         "arguments of type '%s'.",
                                         cwal_value_type_name(v));
        }
    }
}


int th1ish_setup_number( th1ish_interp * ie, cwal_value * ns ){
    int rc;
    cwal_value * v;
    cwal_value * outer;
    if(!cwal_props_can(ns)) return CWAL_RC_MISUSE;

    outer = th1ish_new_object(ie);
    rc = cwal_prop_set( ns, "numeric", 7, outer );
    if(rc) goto end;

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

    /* FUNC2("output", th1ish_io_cb_output); */

#define SET(KEY) rc = cwal_prop_set( outer, KEY, strlen(KEY), v ); \
    assert(!rc); if(rc) goto end

    FUNC2("parseInt", th1ish_number_cb_parseInt);
    FUNC2("parseDouble", th1ish_number_cb_parseDouble);
    
    v = cwal_new_integer( ie->e, CWAL_INT_T_MIN );
    CHECKV;
    SET("INT_MIN");
    v = cwal_new_integer( ie->e, CWAL_INT_T_MAX );
    CHECKV;
    SET("INT_MAX");

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


#undef MARKER
/* end of file th1ish_num.c */
/* start of file th1ish_protos.c */
#include <assert.h>
#include <memory.h> /* strlen() */
#include <ctype.h> /* tolower() and friends */
#include <math.h> /* floor() and friends */

/*
  This file includes the code related to the various value prototypes
  which th1ish sets up.
*/

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

#define PROTOTYPE_NAME_BUF(K) \
    enum { BufLen = 64 }; \
    char buf[BufLen]; \
    int const rc = sprintf(buf, "prototype.%s", K); \
    assert(rc<BufLen)

int th1ish_prototype_store( th1ish_interp * ie,
                            char const * typeName,
                            cwal_value * proto ){
    PROTOTYPE_NAME_BUF(typeName);
    return th1ish_stash_set( ie, buf, proto );
}

cwal_value * th1ish_prototype_for_type_id( th1ish_interp * ie,
                                           cwal_type_id id ){
    assert(!"Still used?");
    return cwal_prototype_base_get( ie->e, id );
}

cwal_value * th1ish_prototype_get( th1ish_interp * ie,
                                   cwal_value const * v ){
    return cwal_value_prototype_get(ie->e, v);
}

cwal_value * th1ish_prototype_get_by_name( th1ish_interp * ie,
                                           char const * typeName ){
    PROTOTYPE_NAME_BUF(typeName);
    return th1ish_stash_get( ie, buf );
}
#undef PROTOTYPE_NAME_BUF



static int th1ish_cb_value_may_iterate( cwal_callback_args const * args,
                                        cwal_value **rv ){
    *rv = cwal_value_may_iterate(args->self)
        ? cwal_value_true()
        : cwal_value_false();
    return 0;
}

#define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args); \
    assert(ie)

static int th1ish_cb_prop_has_own( cwal_callback_args const * args,
                                   cwal_value **rv ){
    ARGS_IE;
    if(!args->argc)
        return th1ish_toss(ie,
                           CWAL_RC_MISUSE,
                           "Expecting a property key.");
    else {
        *rv = cwal_new_bool(cwal_prop_has_v(args->self, 0,
                                            args->argv[0]));
        return 0;
    }
}


static int th1ish_cb_prop_clear( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    ARGS_IE;
    rc = cwal_props_clear( args->self );
    if(rc) rc = th1ish_toss(ie, rc,
                            "Cannot clear properties for this value.");
    return rc;
}

static int th1ish_value_visit_append_to_array( cwal_value * v,
                                               void * state ){
    return cwal_array_append((cwal_array *)state, v);
}

/**
   Script usage: var keys = [obj.propertyKeys]

   Returns an array containing all key/value pairs for obj.
*/
static int th1ish_cb_prop_keys( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_array * ar;
    ARGS_IE;
    if(!cwal_props_can(args->self)){
        return th1ish_toss(ie, CWAL_RC_TYPE,
                           "This value (of type %s) cannot hold properties.",
                           cwal_value_type_name(args->self));
    }
    ar = th1ish_new_array(ie);
    if(!ar) return CWAL_RC_OOM;
    rc = cwal_props_visit_keys( args->self,
                                th1ish_value_visit_append_to_array, ar );
    if(!rc) *rv = cwal_array_value(ar);
    else {
        cwal_array_unref(ar);
    }
    return rc;
}

/**
   Script usage: obj.set(KEY,VAL)

   If obj is-a Array and KEY is-a Integer then the key is interpreted
   as an array index. Sets *rv to the VAL argument. If KEY is an
   integer and obj is-a Array then this function treats the key as an
   array index.

   Or:

   obj.set OBJ2

   to copy all keys from OBJ2 to obj.
*/
static int th1ish_cb_prop_set( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    ARGS_IE;
    if(1 == args->argc){
        /**
           [obj.set OBJ2] copies all properties of OBJ2.           
        */
        if(!cwal_props_can(args->argv[0])){
            rc = CWAL_RC_TYPE;
            goto misuse;
        }else{
            rc = cwal_props_copy( args->argv[0], args->self );
            if(!rc) *rv = cwal_value_undefined();
            return rc;
        }
    }
    else if(2 == args->argc){
        assert(args->self);
        rc = th1ish_set_v( ie, NULL, args->self,
                         args->argv[0], args->argv[1] );
        if(!rc) *rv = args->argv[1];
        return rc;
    }
    /* Else fall through */
    misuse:
    assert(rc);
    return th1ish_toss(ie, rc,
                       "'set' requires (INDEX, VALUE) "
                       "or (OBJECT) arguments.");
}

/**
   Script usage: obj.unset(KEY [, ... KEY])

   If obj is-a Array and KEY is-a Integer then the key is interpreted
   as an array index. Sets *rv to true on success.
*/
static int th1ish_cb_prop_unset( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    uint16_t i = 0;
    int b = -1;
    ARGS_IE;
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "'unset' requires (INDEX|KEY,...) arguments.");
    }
    for( ; !rc && (i < args->argc); ++i ){
        rc = th1ish_set_v( ie, NULL, args->self, args->argv[i], NULL );
        if(CWAL_RC_NOT_FOUND==rc){
            rc = 0;
            b = 0;
        }
        else if(!rc && (-1==b)) b = 1;
    }
    if(!rc) *rv = cwal_new_bool( b>0 );
    return rc;
}


/**
   Script usage: [obj.get KEY]

   Returns the given key for the given obj, or undefined if not
   found. If KEY is an integer and obj is-a Array then this function
   treats the key as an array index. Sets *rv to the fetch value if found,
   else sets it to the undefined value.

   Potential TODO:

   obj.get [list of keys]

   returns [list of values]
*/
int th1ish_cb_prop_get( cwal_callback_args const * args,
                        cwal_value **rv ){
    int rc;
    cwal_value * v = 0;
    ARGS_IE;
    assert(ie);
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "'get' requires (INDEX|KEY) argument.");
    }
    rc = th1ish_get_v(ie, args->self, args->argv[0], &v);
    if(!rc) *rv = v ? v : cwal_value_undefined();
    return rc;
}



/**
   Internal state for Object.eachProperty().
*/
struct ObjectEach {
    cwal_engine * e;
    cwal_value * self;
    cwal_function * callback;
};
typedef struct ObjectEach ObjectEach;

/**
   Script usage:  [obj.eachProperty Function]

   the given function is called once per property, passed the key and
   value. *rv will be set to undefined. Propagates any exception
   thrown from the callback.
*/
static int th1ish_kvp_visitor_prop_each( cwal_kvp const * kvp,
                                         void * state_ ){
    cwal_value * av[2] = {NULL,NULL} /* (key, value) arguments for callback */;
    ObjectEach * state = (ObjectEach*)state_;
    cwal_value * rv = NULL;
    int rc;
    av[0] = cwal_kvp_key(kvp);
    av[1] = cwal_kvp_value(kvp);
    assert(av[0] && av[1]);
    assert(state->e);
    assert(state->callback);
    assert(state->self);
    cwal_engine_sweep(state->e);
#if 0
    cwal_dump_v(av[0],"visiting key");
    cwal_dump_v(av[1],"visiting value");
#endif
    assert(state->callback && state->self);
    rc = cwal_function_call(state->callback, state->self, &rv,
                            2, av );
    if(!rc && rv && cwal_value_is_bool(rv)
       && !cwal_value_get_bool(rv)){
        rc = CWAL_RC_BREAK;
    }
    return rc;
}

static int th1ish_cb_prop_each( cwal_callback_args const * args, cwal_value **rv ){
    cwal_function * f;
    ARGS_IE;
    f = args->argc
        ? cwal_value_function_part(args->engine, args->argv[0])
        /* cwal_value_get_function(args->argv[0]) */
        : NULL;
    /*
      Example of breakage:

const R = api.cgi.request
const rc = object{
  ENV: R.ENV,
  GET: R.GET,
  POST: R.POST,
  COOKIES: R.COOKIES
}
$rc.sortProperties
const sorter = proc(k,v){
    //print("Sorting",k)
    const ce = argv.callee
    assert 'function' === typename ce
    if('object' === typename v){
        v.sortProperties()
        // all of these:
        //v.eachProperty( ce )
        //v.eachProperty.call( v, ce )
        //[v.eachProperty ce]
        //$v.eachProperty ce
    }
}
$rc.eachProperty sorter 

    OMG... argv.callee resolving to eachProperty b/c we don't overwrite it here
    and bypass th1ish's calling bits...
    */

    if(!f){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "'eachProperty' expects a Function argument, "
                           "but got a %s.",
                           args->argc
                           ? cwal_value_type_name(args->argv[0])
                           : "NULL");
    }else{
        ObjectEach state;
        int rc;
        state.e = args->engine;
        state.callback = f;
        state.self = args->self /*better option???*/;
        rc = cwal_props_visit_kvp( args->self,
                                   th1ish_kvp_visitor_prop_each,
                                   &state );
        if(CWAL_RC_BREAK==rc) rc = 0;
        if(!rc) *rv = cwal_value_undefined();
        return rc;
    }
}

static int th1ish_cb_props_sort( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    ARGS_IE;
    rc = cwal_props_sort(args->self);
    if(rc){
        switch(rc){
          case CWAL_RC_CYCLES_DETECTED:
              rc = th1ish_toss(ie, rc, "Cannot sort properties "
                               "while object is being iterated over.");
              break;
        }
    }else{
        *rv = args->self;
    }
    return rc;
}

cwal_value * th1ish_prototype_object( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get( ie->e, CWAL_TYPE_OBJECT );
    if(proto) return proto;
    proto = cwal_new_object_value(ie->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    assert(!cwal_value_prototype_get(ie->e, proto));
    rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_OBJECT, proto );
    if(rc) goto end;
    th1ish_prototype_store(ie, "Object", proto);
    assert(proto == cwal_prototype_base_get(ie->e, CWAL_TYPE_OBJECT));

    /**
       TODOs:

       - toJSON()
       - toString()

       ...
    */
    
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("clearProperties", th1ish_cb_prop_clear);
    FUNC2("compare", th1ish_f_value_compare);
    FUNC2("eachProperty", th1ish_cb_prop_each);
    FUNC2("get", th1ish_cb_prop_get);
    FUNC2("hasOwnProperty", th1ish_cb_prop_has_own);
    FUNC2("mayIterate", th1ish_cb_value_may_iterate);
    FUNC2("propertyKeys", th1ish_cb_prop_keys);
    FUNC2("set", th1ish_cb_prop_set);
    FUNC2("sortProperties", th1ish_cb_props_sort);
    FUNC2("toJSONString", th1ish_f_this_to_json_token);
    FUNC2("toString", th1ish_f_value_to_string);
    FUNC2("unset", th1ish_cb_prop_unset);
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}

cwal_value * th1ish_new_object(th1ish_interp * ie){
    cwal_value * rc;
    cwal_value * ap = th1ish_prototype_object( ie )
        /* Must be called once to initialize the automatic prototype
           injection. */;
    if(!ap){
        th1ish_toss(ie, CWAL_RC_ERROR,
                    "Object prototype initialization failed.");
        return NULL;
    }
    rc = cwal_new_object_value(ie->e);
    if(!rc) return NULL;
    assert(cwal_value_prototype_get(ie->e, rc)==ap);
    return rc;
}


/**
   Temporary helper for Array member functions to set up their
   arguments.
*/
#define THIS_ARRAY \
    cwal_array * self = 0;                          \
    ARGS_IE; \
    self = cwal_value_array_part(ie->e, args->self); \
    if(!self){ \
        return th1ish_toss( ie, CWAL_RC_TYPE, \
                            "'this' is-not-an Array." );        \
    } (void)0

static int th1ish_cb_array_clear( cwal_callback_args const * args, cwal_value **rv ){
    char clearProps;
    THIS_ARRAY;
    clearProps = (args->argc>0)
        ? cwal_value_get_bool(args->argv[0])
        : 0;
    cwal_array_clear(self, 0, clearProps);
    return 0;
}


static int th1ish_cb_array_isempty( cwal_callback_args const * args, cwal_value **rv ){
    THIS_ARRAY;
    *rv = cwal_new_bool( cwal_array_length_get(self) ? 0 : 1 );
    return 0;        
}


static int th1ish_cb_array_length( cwal_callback_args const * args, cwal_value **rv ){
    THIS_ARRAY;
    if(args->argc){
        /* SET length */
        cwal_value * const index = args->argv[0];
        cwal_int_t len = cwal_value_get_integer(index);
        int rc;
        if((len<0) || !cwal_value_is_number(index)){
            return th1ish_toss(ie, CWAL_RC_MISUSE,
                               "length argument must be "
                               "a non-negative integer.");
        }
        rc = cwal_array_length_set( self, (cwal_size_t)len );
        if(rc){
            return th1ish_toss(ie, CWAL_RC_MISUSE,
                               "Setting array length to %"CWAL_INT_T_PFMT
                               " failed with code %d (%s).",
                               len, rc, cwal_rc_cstr(rc));
        }
    }
    *rv = cwal_new_integer( args->engine,
                            (cwal_int_t)cwal_array_length_get(self) );
    /* dump_val(*rv,"array.length()"); */
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_array_reserve( cwal_callback_args const * args, cwal_value **rv ){
    static char const * usage = "Expecting a non-negative integer argument.";
    THIS_ARRAY;
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE, "%s", usage );
    }
    else{
        cwal_value * const index = args->argv[0];
        cwal_int_t len = cwal_value_get_integer(index);
        int rc;
        if((len<0) || !cwal_value_is_number(index)){
            return th1ish_toss(ie, CWAL_RC_MISUSE, "%s", usage);
        }
        rc = cwal_array_reserve( self, (cwal_size_t)len );
        if(rc){
            return th1ish_toss(ie, rc,
                               "cwal_array_reserve() failed!");
        }
        *rv = args->self;
    }
    return 0;
}


static int th1ish_cb_array_push( cwal_callback_args const * args, cwal_value **rv ){
    THIS_ARRAY;

    if(!args->argc){
        *rv = cwal_value_undefined();
        return 0;
    }
    else {
        int rc;
        cwal_size_t i = 0;
        for( i = 0; i < args->argc; ++i ){
            rc = cwal_array_append( self, args->argv[i] );
            if(!rc) *rv = args->argv[i];
            else break;
        }
        if(rc){
            return th1ish_toss(ie, CWAL_RC_MISUSE,
                               "Appending to array failed with "
                               "code %d (%s).",
                               rc, cwal_rc_cstr(rc));
        }
    }
    return 0;
}

static int th1ish_cb_array_pop( cwal_callback_args const * args, cwal_value **rv ){
    cwal_size_t aLen;
    THIS_ARRAY;
    aLen = cwal_array_length_get(self);
    if(!aLen){
        *rv = cwal_value_undefined();
    }else{
        --aLen;
        *rv = cwal_array_take(self, aLen);
        cwal_array_length_set(self, aLen);
    }
    return 0;
}


static int th1ish_cb_array_slice( cwal_callback_args const * args, cwal_value **rv ){
    cwal_int_t offset = 0, count = 0;
    cwal_array * acp = NULL;
    int rc;
    THIS_ARRAY;
    if(args->argc>0){
        offset = cwal_value_get_integer(args->argv[0]);
        if(args->argc>1){
            count = cwal_value_get_integer(args->argv[1]);
        }
    }
    if((offset<0) || (count<0)){
        return th1ish_toss(ie, CWAL_RC_RANGE,
                           "Slice offset and count must both "
                           "be positive numbers.");
    }
    rc = cwal_array_copy_range( self, (cwal_size_t)offset,
                                (cwal_size_t)count, &acp );
    if(!rc){
        assert(acp);
        *rv = cwal_array_value(acp);
    }
    return rc;
}


static int th1ish_cb_array_unshift( cwal_callback_args const * args, cwal_value **rv ){
    THIS_ARRAY;

    if(!args->argc){
        *rv = cwal_value_undefined();
        return 0;
    }
    else {
        int rc;
        cwal_size_t i = 0;
        if(args->argc>1){
            rc = cwal_array_reserve(self, args->argc
                                    + cwal_array_length_get(self));
            if(rc) return rc;
        }
        for( i = args->argc-1; i < args->argc; --i ){
            rc = cwal_array_prepend( self, args->argv[i] );
            if(!rc) *rv = args->argv[i];
            else break;
        }
        if(rc){
            return th1ish_toss(ie, CWAL_RC_MISUSE,
                               "Prepending to array failed with "
                               "code %d (%s).",
                               rc, cwal_rc_cstr(rc));
        }
    }
    return 0;
}

static int th1ish_cb_array_shift( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    THIS_ARRAY;
    rc = cwal_array_shift( self, rv );
    if(CWAL_RC_RANGE==rc){
        rc = 0;
        *rv = cwal_value_undefined();
    }else if(!*rv){
        *rv = cwal_value_undefined();
    }
    return rc;
}


static int th1ish_cb_array_sort( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    THIS_ARRAY;
    if(!args->argc || !cwal_value_is_function(args->argv[0])){
        rc = cwal_array_sort(self, cwal_compare_value_void);
    }else{
        cwal_function * cmp = cwal_value_get_function(args->argv[0]);
        rc = cwal_array_sort_func( self, args->self, cmp);
    }
    if(!rc){
        *rv = args->self;
    }
    return rc;
}

static int th1ish_cb_array_reverse( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    THIS_ARRAY;
    rc = cwal_array_reverse(self);
    if(!rc) *rv = args->self;
    return rc;
}

static int th1ish_cb_array_each( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    cwal_function * f;
    cwal_size_t i = 0;
    cwal_size_t alen;
    cwal_value * vnull = cwal_value_undefined();
    cwal_value * av[2] = {NULL,NULL} /* Value, Index arguments for callback */;
    cwal_value * ndx;
    cwal_value * cbRv = NULL;
    THIS_ARRAY;
    f = args->argc ? cwal_value_get_function(args->argv[0]) : NULL;
    if(!f){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "'eachIndex' expects a Function argument, "
                           "but got a %s.",
                           args->argc
                           ? cwal_value_type_name(args->argv[0])
                           : "NULL");
    }
    alen = cwal_array_length_get(self);
    for( i = 0; !rc && i < alen; ++i ){
        cwal_engine_sweep(args->engine);
        av[0] = cwal_array_get( self, i );
        if(!av[0]) av[0] = vnull;
        ndx = cwal_new_integer( args->engine, (cwal_int_t)i );
        if(!ndx){
            rc = CWAL_RC_OOM;
            break;
        }
        cwal_value_ref(ndx);
        av[1] = ndx;
        rc = cwal_function_call( f, args->self,
                                 &cbRv, 2, av );
        av[0] = av[1] = NULL;
        /* Reminder ndx will always end up in the argv array,
           which will clean it up before the call's scope returns.
           No, it won't because the new argv is in a lower scope
           (do it doesn't own ndx).
        */
        cwal_value_unref(ndx)
            /* If we unref without explicitly ref()'ing we will likely
               pull the arguments out from the underlying argv array
               if a ref is held to is. If we don't ref it we don't
               know if it can be cleaned up safely. So we ref/unref it
               manually to force cleanup _unless_ someone else got
               a reference.
            */;
        if(!rc && rv && cwal_value_is_bool(cbRv)
           && !cwal_value_get_bool(cbRv)){
            break;
        }
    }
    if(!rc) *rv = args->self;
    return rc;
}

/**
   Internal helper which looks for an integer argument at
   args->argv[atIndex] and checks if it is negative.  Returns non-0
   and triggers an exception in args->engine on error. Assigns the
   fetched integer (if any) to *rv, even if it is negative (which also
   results in a CWAL_RC_RANGE exception).
*/
static int th1ish_array_get_index( cwal_callback_args const * args,
                                   uint16_t atIndex,
                                   cwal_int_t * rv ){
    ARGS_IE;
    if(args->argc<atIndex){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Too few arguments.");
    }
    *rv = cwal_value_get_integer(args->argv[atIndex]);
    return (*rv<0)
        ? th1ish_toss(ie, CWAL_RC_RANGE,
                      "Array indexes may not be be negative.")
        : 0;
}

int th1ish_cb_array_get_index( cwal_callback_args const * args,
                               cwal_value **rv ){
    int rc;
    cwal_int_t i = 0;
    THIS_ARRAY;
    assert(ie);
    rc = th1ish_array_get_index(args, 0, &i);
    if(rc) return rc;
    else{
        cwal_value * v;
        assert(i>=0);
        v = cwal_array_get(self, (cwal_size_t)i);
        *rv = v ? v : cwal_value_undefined();
        return 0;
    }
}

int th1ish_cb_array_set_index( cwal_callback_args const * args,
                               cwal_value **rv ){
    int rc;
    cwal_int_t i;
    THIS_ARRAY;
    rc = th1ish_array_get_index(args, 0, &i);
    if(rc) return rc;
    else{
        cwal_value * v = (args->argc>1) ? args->argv[1] : NULL;
        assert(i>=0);
        rc = cwal_array_set(self, (cwal_size_t)i, v);
        if(!rc) *rv = v ? v : cwal_value_undefined();
        return rc;
    }
}

static int th1ish_cb_array_join( cwal_callback_args const * args, cwal_value **rv ){
    char const * joiner = NULL;
    cwal_size_t jLen = 0;
    cwal_size_t i, aLen, oldUsed;
    int rc = 0;
    cwal_buffer * buf;
    THIS_ARRAY;
    buf = &ie->buffer;
    oldUsed = buf->used;
    if(!args->argc){
        misuse:
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting string argument.");
    }
    joiner = cwal_value_get_cstr(args->argv[0], &jLen);
    if(!joiner) goto misuse;
    aLen = cwal_array_length_get(self);
    for( i = 0; !rc && i < aLen; ++i ){
        cwal_value * v = cwal_array_get(self, i);
        if(i>0){
            rc = cwal_buffer_append(args->engine, buf, joiner, jLen);
        }
        if(v && !rc){
            rc = th1ish_value_to_buffer(ie, buf, v);
        }
    }
    if(!rc){
        cwal_size_t const ln = buf->used - oldUsed;
        assert(oldUsed <= buf->used);
        *rv = cwal_new_string_value(args->engine,
                                    ln ? (char const *)(buf->mem+oldUsed) : NULL,
                                    ln);
        if(!*rv) rc = CWAL_RC_OOM;
    }
    buf->used = oldUsed;
    return rc;
}

static int th1ish_cb_array_index_of( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_size_t ndx = 0;
    cwal_int_t irc = 0;
    THIS_ARRAY;

    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE, "indexOf() requires an argument.");
    }
    rc = cwal_array_index_of(self, args->argv[0], &ndx);
    if(CWAL_RC_NOT_FOUND==rc){
        rc = 0;
        irc = -1;
    }else if(!rc){
        irc = (cwal_int_t)ndx;
    }else{
        assert(!"Should not be possible to fail like this with valid args.");
    }
    if(!rc){
        *rv = cwal_new_integer(args->engine, irc);
        rc = *rv ? 0 : CWAL_RC_OOM;
    }
    return rc;
}

cwal_value * th1ish_prototype_array( th1ish_interp * ie ){
    int rc;
    cwal_value * proto;
    cwal_value * v;
    assert(ie && ie->e);

    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_ARRAY);
    if(proto) return proto;

    proto = cwal_new_object_value(ie->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_ARRAY, proto);
    if(rc) goto end;
    th1ish_prototype_store(ie, "Array", proto);
    assert(proto == cwal_prototype_base_get(ie->e, CWAL_TYPE_ARRAY));
    assert(cwal_value_prototype_get(ie->e,proto) == th1ish_prototype_object(ie));

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC(NAME,FP) \
    v = th1ish_new_function2( ie, FP );   \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end
    
    FUNC("clear", th1ish_cb_array_clear);
    FUNC("eachIndex", th1ish_cb_array_each);
    FUNC("getIndex", th1ish_cb_array_get_index);
    FUNC("indexOf", th1ish_cb_array_index_of);
    FUNC("isEmpty",th1ish_cb_array_isempty);
    FUNC("join",th1ish_cb_array_join);
    FUNC("length",th1ish_cb_array_length);
    FUNC("pop", th1ish_cb_array_pop);
    FUNC("push", th1ish_cb_array_push);
    FUNC("reserve", th1ish_cb_array_reserve);
    FUNC("reverse", th1ish_cb_array_reverse);
    FUNC("setIndex", th1ish_cb_array_set_index);
    FUNC("shift", th1ish_cb_array_shift);
    FUNC("slice", th1ish_cb_array_slice);
    FUNC("sort", th1ish_cb_array_sort);
    FUNC("toString", th1ish_f_value_to_string);
    FUNC("unshift", th1ish_cb_array_unshift);

#undef FUNC
#undef CHECKV
    
    end:
    return rc ? 0 : proto;
}

cwal_array * th1ish_new_array(th1ish_interp * ie){
    cwal_value * rc;
    cwal_value * ap = th1ish_prototype_array( ie );
    if(!ap){
        th1ish_toss(ie, CWAL_RC_ERROR,
                    "Array prototype initialization failed.");
        return NULL;
    }
    rc = cwal_new_array_value(ie->e);
    if(!rc) return NULL;
    else {
        assert(ap == cwal_value_prototype_get(ie->e,rc));
        return cwal_value_get_array(rc);
    }
}

cwal_value * th1ish_new_function2( th1ish_interp * ie,
                                   cwal_callback_f callback){
    return th1ish_new_function(ie, callback, ie, NULL, &th1ish_interp_empty);
}

cwal_value * th1ish_new_function( th1ish_interp * ie,
                                  cwal_callback_f callback,
                                  void * state,
                                  cwal_finalizer_f stateDtor,
                                  void const * stateTypeID){
    cwal_value * rc;
    cwal_value * proto = th1ish_prototype_function( ie );
    if(!proto){
        th1ish_toss(ie, CWAL_RC_ERROR,
                    "Function prototype initialization failed.");
        return NULL;
    }
    rc = cwal_new_function_value(ie->e, callback, state, stateDtor, stateTypeID);
    if(!rc) return NULL;
    else {
        assert(cwal_value_prototype_get(ie->e,rc) == proto);
        return rc;
    }
}

/**
   Internal property key for storing "imported symbols" in a Function
   instance.

   ACHTUNG: duplicated in th1ish.c!!!
*/
#define FUNC_SYM_PROP "$imported$"
#define FUNC_SYM_PROP_LEN (sizeof(FUNC_SYM_PROP)-1)

#define THIS_FUNCTION                                  \
    cwal_function * self = 0;                          \
    ARGS_IE; \
    self = cwal_value_function_part(ie->e, args->self); \
    if(!self){ \
        return th1ish_toss( ie, CWAL_RC_TYPE, \
                            "'this' is-not-a Function." );        \
    } (void)0

/**
   Implements Function.importSymbols().
*/
int th1ish_cb_func_import_symbols( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    cwal_value * props /* where we store the symbols */;
    cwal_value * ce = args->self;
    uint16_t i;
    THIS_FUNCTION;
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting at least one symbol name to import.");
    }
    props = cwal_prop_get(ce, FUNC_SYM_PROP, FUNC_SYM_PROP_LEN);
    if(!props){
        props = cwal_new_object_value(ie->e);
        if(!props) return CWAL_RC_OOM;
        rc = cwal_prop_set(ce, FUNC_SYM_PROP, FUNC_SYM_PROP_LEN, props);
        if(rc){
            cwal_value_unref(props);
            return rc;
        }
    }else{
        cwal_props_clear( props );
    }
    for( i = 0; !rc && (i < args->argc); ++i ){
        cwal_value * key;
        cwal_value * v;
        /* cwal_array * ary; */
        key = args->argv[i];
#if 0
        /* TODO: treat as a list of symbols. */
        if((ary = cwal_value_array_part(key))){
            continue;
        }
#endif
        if(cwal_props_can(key)){
            rc = cwal_props_copy( key, props );
            continue;
        }
        v = th1ish_var_get_v(ie, -1, key);
        if(!v){
            cwal_string const * str = cwal_value_get_string(key);
            char const * cstr = str ? cwal_string_cstr(str) : NULL;
            return th1ish_toss(ie, CWAL_RC_NOT_FOUND,
                               "Could not resolve symbol '%.*s' "
                               "in the current scope stack.",
                               str ? (int)cwal_string_length_bytes(str) : 0,
                               cstr ? cstr : "<non-string symbol>"
                               /* Reminder: %.*s is necessary for the
                                  case of non-NUL-terminated
                                  x-strings. */
                               );
        }
        rc = cwal_prop_set_v( props, key, v );
    }
    if(!rc){
        /* *rv = cwal_function_value(args->callee); */
        *rv =
            /*args->self*/
            cwal_function_value(self);
    }
    return rc;
}

/**
   Script usage:

   [Function.apply thisObject array[arg1...argN]]
*/
static int th1ish_cb_func_apply( cwal_callback_args const * args, cwal_value **rv ){
    /* FIXME: consolidate with th1ish_cb_func_call(). 90% the same code. */
    int rc;
    cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */;
    cwal_array * theList /* args->argv[1], the list of arguments to apply() */;
    cwal_value * origArgv = 0 /* scope.argv, set by args collection process */;
    cwal_array * origArgvA = 0;
    cwal_scope * sc;
    uint16_t argc = 0;
    cwal_value * argv[CWAL_OPT_MAX_FUNC_CALL_ARGS] = {0};
    THIS_FUNCTION;
    assert(ie);
    memset( argv, 0, sizeof(argv));
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "'apply' expects (OBJ[, ARRAY]) argument(s).");
    }
    origArgvA = th1ish_new_array(ie);
    if(!origArgvA) return CWAL_RC_OOM;
    rc = cwal_array_reserve(origArgvA, args->argc-1);
    if(rc) return rc;
    origArgv = cwal_array_value(origArgvA);
    fwdSelf = args->argv[0];
    if(!th1ish_props_can(ie, fwdSelf)){
        return th1ish_toss(ie, CWAL_RC_TYPE,
                           "Invalid first argument to 'apply' - "
                           "expecting an Object or at least "
                           "something with a prototype.");
    }
    theList = (args->argc>1)
        ? cwal_value_array_part(ie->e, args->argv[1])
        : NULL;
    if((args->argc>1) && !theList){
        return th1ish_toss(ie, CWAL_RC_TYPE,
                           "Second argument to 'apply' must be an array.");
    }

    assert(origArgvA);
    /* assert(0 == cwal_array_length_get(origArgvA)); */

    if(theList){
        /**
           Copy the arguments into argv and origArgv...
        */
        cwal_size_t const alen = cwal_array_length_get(theList);
        /* th1ish_dump_val(args->argv[1],"theList"); */
        if(alen>CWAL_OPT_MAX_FUNC_CALL_ARGS){
            return th1ish_toss(ie, CWAL_RC_TYPE,
                               "Too many arguments in parameter list. "
                               "Limit is %d.",
                               CWAL_OPT_MAX_FUNC_CALL_ARGS);
        }
        for(; argc < alen; ++argc ){
            argv[argc] = cwal_array_get(theList,argc);
            /* dump_val(argv[argc],"argv[argc]"); */
            if(!argv[argc]) argv[argc] = cwal_value_undefined();
            cwal_array_set( origArgvA, argc, argv[argc] );
        }
    }
    else {
        theList = th1ish_new_array(ie);
    }
    sc = cwal_scope_current_get(ie->e);
#define RC if(rc) return rc
    /* rc = cwal_array_length_set( origArgvA, argc ) */
        /* Make sure we don't bleed old arguments
           into the function. Been there, debugged there.
        */

    /* We have to duplicate some of the bits done in
       th1ish_eval_func_collect_call() or else our up-coming
       call will use the value that function set when it
       called THIS function...
    */
    rc = cwal_scope_chain_set( sc, 0, "this", 4, fwdSelf );
    RC;
    rc = cwal_prop_set( origArgv, "callee", 6,
                        cwal_function_value(self)
                        /*argv->self?*/
                        );
    RC;
    rc = cwal_scope_chain_set( sc, 0, "argv", 4, origArgv );
    RC;

    rc = cwal_function_call_in_scope( sc, self, fwdSelf, rv,
                                      argc, argc ? argv : NULL );
#if 0
    /* Reminder: unsetting callee causes valgrind warnings. Not sure
       why but it is highly unexpected.
    */
    cwal_prop_set( origArgv, "callee", 6, NULL );
    cwal_scope_chain_set( sc, 0, "this", 4, NULL );
    /* This breaks if caller returns argv. */
    cwal_scope_chain_set( sc, 0, "argv", 4, NULL );
    if(argc) cwal_array_length_set( origArgvA, 0 )
        /* to avoid holding on to temporaries or to strings held by higher
           scopes.
        */
        ;
#endif
#undef RC
    return rc;
}

/**
   Script usage:

   [Function.call thisObject arg1...argN]
*/
static int th1ish_cb_func_call( cwal_callback_args const * args, cwal_value **rv ){
    /* FIXME: consolidate with th1ish_cb_func_apply(). 90% the same code. */
    int rc;
    cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */;
    cwal_value * origArgv = 0 /* scope.argv, set by args collection process */;
    cwal_array * origArgvA = 0;
    cwal_scope * sc;
    uint16_t i;
    THIS_FUNCTION;
    assert(ie);
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting (OBJ[, params...]) argument(s).");
    }
    origArgvA = th1ish_new_array(ie);
    if(!origArgvA) return CWAL_RC_OOM;
    rc = cwal_array_reserve(origArgvA, args->argc-1);
    if(rc) return rc;
    origArgv = cwal_array_value(origArgvA);
    assert(origArgv && "Expecting origArgv to be set from the func args processing process.");
    fwdSelf = args->argv[0];
    if(!th1ish_props_can(ie, fwdSelf)){
        return th1ish_toss(ie, CWAL_RC_TYPE,
                           "Invalid first argument - "
                           "expecting an Object.");
    }
    /* assert(0 == cwal_array_length_get(origArgvA)); */
    for( i = 1; i < args->argc; ++i ){
        /**
           Copy the arguments into argv and origArgv...
        */
        rc = cwal_array_set(origArgvA, i-1, args->argv[i]);
        if(rc) return rc;
    }
    sc = cwal_scope_current_get(ie->e);
    cwal_array_length_set( origArgvA, args->argc-1 )
        /* Make sure we don't bleed old arguments
           into the function. Been there, debugged there.
        */
        ;

#define RC if(rc) return rc
    /* We have to duplicate some of the bits done in
       th1ish_eval_func_collect_call() or else our up-coming
       call will use the value that function set when it
       called THIS function...
    */
    rc = cwal_scope_chain_set( sc, 0, "this", 4, fwdSelf );
    RC;
    rc = cwal_prop_set( origArgv, "callee", 6,
                        cwal_function_value(self)
                        /*argv->self?*/
                        );
    RC;
    rc = cwal_scope_chain_set( sc, 0, "argv", 4, origArgv );
    RC;

    rc = cwal_function_call_in_scope( sc, self, fwdSelf, rv,
                                      args->argc-1, args->argv+1 );
#if 0
    cwal_prop_set( origArgv, "callee", 6, NULL );

    cwal_scope_chain_set( sc, 0, "this", 4, NULL );
    if(1<args->argc) cwal_array_length_set( origArgvA, 0 )
        /* to avoid holding on to temporaries or values held by higher
           scopes.
        */
        ;
#endif
#undef RC
    return rc;
}

/* Defined in th1ish.c for type visibility reasons. */
int th1ish_f_source_info( cwal_callback_args const * args, cwal_value **rv );

cwal_value * th1ish_prototype_function( th1ish_interp * ie ){
    int rc;
    cwal_value * proto;
    cwal_value * v;
    assert(ie && ie->e);
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_FUNCTION);
    if(proto) return proto;
    th1ish_prototype_object(ie)
        /* Workaround for a timing issue in the creation of
           functions before Object's prototype is registered.
        */
        ;

    proto = cwal_new_object_value(ie->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_FUNCTION, proto);
    if(rc) goto end;
    th1ish_prototype_store(ie, "Function", proto);
    assert(proto == cwal_prototype_base_get(ie->e, CWAL_TYPE_FUNCTION));
    assert(cwal_value_prototype_get(ie->e,proto) == th1ish_prototype_object(ie));

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP );   \
    CHECKV; \
    if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v );  \
    assert(!rc);                                                 \
    if(rc) goto end
    /**
       Side-note: Function.apply's prototype is Function, so we
       can do this bit of sillyness:

       [anyFunc.apply.apply.apply.apply foo [bar]]

       it won't have the same effect as [func.apply...], of course (it
       will apply() to apply()).
    */
    FUNC2("apply", th1ish_cb_func_apply);
    FUNC2("call", th1ish_cb_func_call);
    FUNC2("importSymbols", th1ish_cb_func_import_symbols);
    FUNC2("functionInfo", th1ish_f_source_info);
#undef FUNC2

    assert(!rc);
    v = cwal_new_integer(ie->e, (cwal_int_t)CWAL_OPT_MAX_FUNC_CALL_ARGS);
    CHECKV;
    rc = cwal_prop_set( proto, "maxCallArgs", 11, v );
#undef CHECKV
    
    end:
    if(rc){
        proto = 0 /* let ie->stash deal with it */;
    }
    return rc ? 0 : proto;
}

/* Implemented in th1ish.c */
int th1ish_strace_copy( th1ish_interp * ie, cwal_value * ex );


static int th1ish_cb_exception_isa( cwal_callback_args const * args,
                                    cwal_value **rv ){
    ARGS_IE;
    *rv = args->argc
        ? (cwal_value_exception_part(ie->e,args->argv[0])
           ? cwal_value_true()
           : cwal_value_false()
           )
        : cwal_value_false();
    return 0;
}

static int th1ish_cb_exception_code_string( cwal_callback_args const * args,
                                            cwal_value **rv ){
    int rc = 0;
    cwal_value * v;
    ARGS_IE;
    v = cwal_prop_get(args->self, "code", 4);
    if(v){
        cwal_int_t const code = cwal_value_get_integer(v);
        char const * str = cwal_rc_cstr(code);
        *rv = cwal_new_xstring_value(args->engine, str, cwal_strlen(str));
        if(!*rv) rc = CWAL_RC_OOM;
    }else{
        *rv = cwal_value_undefined();
    }
    return rc;
}


/**
   The Exception class constructor.

   Script-side usage:

   var ex = api.Exception("Message")

   var ex = api.Exception(1234, "Message")

   If stack tracing is enabled, it captures the line/column info from
   the construction point
*/
static int th1ish_cb_exception_ctor( cwal_callback_args const * args,
                                     cwal_value **rv ){
    int rc = 0;
    int code = CWAL_RC_EXCEPTION;
    cwal_value * exv = NULL;
    cwal_value * arg0 = NULL;
    cwal_value * arg1 = NULL;
    ARGS_IE;
    if(args->argc>0){
        arg0 = args->argv[0];
        if(args->argc>1) arg1 = args->argv[1];
        if(arg1){
            code = (int)cwal_value_get_integer(arg0);
        }else{
            if(cwal_value_is_integer(arg0)){
                code = (int)cwal_value_get_integer(arg0);
            }else{
                arg1 = arg0;
            }
        }
    }
    exv = th1ish_new_exception(ie, code, arg1);
    if(!exv) return CWAL_RC_OOM;
    else if(!rc) *rv = exv;
    else cwal_value_unref(exv);
    return rc;
}


cwal_value * th1ish_prototype_exception( th1ish_interp * ie ){
    int rc;
    cwal_value * proto;
    cwal_value * v;
    assert(ie && ie->e);
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_EXCEPTION);
    if(proto) return proto;

    proto = th1ish_new_function2(ie, th1ish_cb_exception_ctor );
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_EXCEPTION, proto);
    if(rc) goto end;
    th1ish_prototype_store(ie, "Exception", proto);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP );        \
    CHECKV; \
    if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v );  \
    assert(!rc);                                                 \
    if(rc) goto end

    FUNC2("isA",th1ish_cb_exception_isa);
    FUNC2("codeString", th1ish_cb_exception_code_string);

#undef FUNC2
#undef CHECKV
    
    end:
    if(rc){
        proto = 0 /* let ie->stash deal with it */;
    }
    return rc ? 0 : proto;
}

cwal_value * th1ish_new_exception(th1ish_interp * ie, int code,
                                  cwal_value * msg ){
    cwal_value * exv;
    cwal_value * proto = th1ish_prototype_exception( ie );
    /* th1ish_stack_entry se = th1ish_stack_entry_empty; */
    int rc;
    if(!proto){
        th1ish_toss(ie, CWAL_RC_ERROR,
                    "Exception prototype initialization failed.");
        return NULL;
    }
    exv = cwal_new_exception_value(ie->e, code, msg);
    if(!exv) return NULL;
    assert(proto == cwal_value_prototype_get(ie->e,exv))
        /* because of prototype injection done at binding-init time */;
    rc = cwal_prop_set( exv, "code", 4,
                        cwal_new_integer(ie->e, code));
#if 0
    /* it turns out the stack traces are less confusing if we don't pollute
       them here and instead let the upstream throw mechanism do it.
    */
    if(!rc && th1ish_stacktrace_current(ie, &se)){
        /*
          if stack trace is enabled, we glean the source info from
          it. For reasons i don't entirely understand, line is always
          1 and column always 0 here, and scriptName is always
          empty. So we'll elide these for the time being.
        */
#if 0
        if(se.scriptNameLen){
            cwal_engine * e = ie->e;
            rc = cwal_prop_set( exv, "line", 4,
                                cwal_new_integer(e, (cwal_int_t)se.line));
            if(!rc) rc = cwal_prop_set( exv, "column", 6,
                                        cwal_new_integer(e, (cwal_int_t)se.col));
            if(!rc && se.scriptName){
                char const * s = (char const *)(se.scriptNameLen
                                                ? se.scriptName
                                                : NULL);
                rc = cwal_prop_set( exv, "script", 6,
                                    cwal_new_string_value(e,
                                                          s,
                                                          se.scriptNameLen)
                                    );
            }
        }
#endif
        if(!rc) rc = th1ish_strace_copy(ie, exv);
    }
#endif
    if(rc){
        cwal_value_unref(exv);
        exv = NULL;
    }
    return exv;
}

#define THIS_DOUBLE                             \
    cwal_double_t self; \
    ARGS_IE; \
    assert(ie); \
    if(!cwal_value_is_number(args->self)){                      \
        return th1ish_toss( ie, CWAL_RC_TYPE, \
                            "'this' is-not-a Number." );        \
    }\
    self = cwal_value_get_double(args->self)

#define THIS_INT                             \
    cwal_int_t self; \
    ARGS_IE; \
    assert(ie); \
    if(!cwal_value_is_number(args->self)){                      \
        return th1ish_toss( ie, CWAL_RC_TYPE,                   \
                            "'this' is-not-a Number." );        \
    }\
    self = cwal_value_get_integer(args->self)

static int th1ish_cb_int_to_dbl( cwal_callback_args const * args, cwal_value **rv ){
    THIS_INT;
    *rv = cwal_value_is_double(args->self)
        ? args->self
        : cwal_new_double( args->engine, (cwal_int_t)self )
        ;
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_int_to_utf8_char( cwal_callback_args const * args, cwal_value **rv ){
    unsigned char buf[5] = {0,0,0,0,0};
    int sz;
    THIS_INT;
    sz = cwal_utf8_char_to_cstr( (unsigned int)self, buf, 5U );
    if(sz<0){
        return th1ish_toss(ie, CWAL_RC_RANGE,
                           "Integer value %"CWAL_INT_T_PFMT" is not "
                           "a valid UTF8 character.", self);
    }
    *rv = cwal_new_string_value(args->engine,
                                (char const *)buf,
                                (cwal_size_t)sz);
    return *rv ? 0 : CWAL_RC_OOM;
}


static int th1ish_cb_dbl_to_int( cwal_callback_args const * args, cwal_value **rv ){
    THIS_DOUBLE;
    *rv = cwal_value_is_integer(args->self)
        ? args->self
        : cwal_new_integer( args->engine, (cwal_int_t)self )
        ;
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_num_to_string( cwal_callback_args const * args, cwal_value **rv ){
    enum { BufLen = 100 };
    char buf[BufLen] = {0};
    cwal_size_t bLen = (cwal_size_t)BufLen;
    int rc;
    THIS_DOUBLE;
    if(cwal_value_is_double(args->self)){
        rc = cwal_double_to_cstr( self, buf, &bLen );
    }else{
        rc = cwal_int_to_cstr( (cwal_int_t)self, buf, &bLen );
    }
    if(rc){
        return th1ish_toss(ie, rc,
                           "Conversion from number to string failed.");
    }
    *rv = cwal_new_string_value(args->engine, bLen ? buf : NULL,
                                bLen);
    return *rv ? 0 : CWAL_RC_OOM;
}


static int th1ish_cb_dbl_floor( cwal_callback_args const * args, cwal_value **rv ){
    THIS_DOUBLE;
    *rv = cwal_new_double( args->engine, floor((double)self));
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_dbl_ceil( cwal_callback_args const * args, cwal_value **rv ){
    THIS_DOUBLE;
    *rv = cwal_new_double( args->engine, ceil((double)self));
    return *rv ? 0 : CWAL_RC_OOM;
}

#if 0
/* Requires C99 :/
 */
static int th1ish_cb_dbl_round( cwal_callback_args const * args, cwal_value **rv ){
    THIS_DOUBLE;
    *rv = cwal_new_double( args->engine, round((double)self));
    return *rv ? 0 : CWAL_RC_OOM;
}
#endif

static int th1ish_cb_dbl_format( cwal_callback_args const * args, cwal_value **rv ){
    int left = 0, right = 0;
    enum { BufLen = 100 };
    char buf[BufLen] = {0};
    THIS_DOUBLE;
    if(args->argc>0){
        left = (int)cwal_value_get_integer(args->argv[0]);
        if(args->argc>1) right = (int)cwal_value_get_integer(args->argv[1]);
        else{
            right = left;
            left = 0;
        }
            

    }
    sprintf( buf, "%%%d.%d"CWAL_DOUBLE_T_PFMT, left, right );
    *rv = cwal_string_value( cwal_new_stringf( ie->e, buf, self ) );
    return *rv ? 0 : CWAL_RC_OOM;
}


/*
   TODO: add th1ish_prototype_number() and make it the prototype
   of th1ish_prototype_double/integer().
*/

cwal_value * th1ish_prototype_double( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_DOUBLE);
    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 = cwal_prototype_base_set(ie->e, CWAL_TYPE_DOUBLE, proto);
    if(rc) goto end;
#if 0
    cwal_value_prototype_set(proto, NULL)
        /* So that (1.0 instanceof object{}.prototype) fails. */;
#endif
    /* TODO: add Number prototype. */
    th1ish_prototype_store(ie, "Double", proto);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP );          \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end

    /* FUNC2("round", th1ish_cb_dbl_round); */
    FUNC2("ceil", th1ish_cb_dbl_ceil);
    FUNC2("compare", th1ish_f_value_compare);
    FUNC2("floor", th1ish_cb_dbl_floor);
    FUNC2("format", th1ish_cb_dbl_format);
    FUNC2("toDouble", th1ish_cb_int_to_dbl);
    FUNC2("toInt", th1ish_cb_dbl_to_int);
    FUNC2("toJSONString", th1ish_f_this_to_json_token);
    FUNC2("toString", th1ish_cb_num_to_string);

#undef FUNC2
#undef CHECKV
    end:
    return rc ? NULL : proto;
}

cwal_value * th1ish_prototype_integer( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_INTEGER);
    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 = cwal_prototype_base_set(ie->e, CWAL_TYPE_INTEGER, proto);
    if(rc) goto end;
#if 0
    cwal_value_prototype_set(proto, NULL)
        /* So that (1 instanceof object{}.prototype) fails. */;
#endif
    /* TODO: add Number prototype. */
    th1ish_prototype_store(ie, "Integer", proto);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("toDouble", th1ish_cb_int_to_dbl);
    FUNC2("toInt", th1ish_cb_dbl_to_int);
    FUNC2("toJSONString", th1ish_f_this_to_json_token);
    FUNC2("toString", th1ish_cb_num_to_string);
    FUNC2("toUtf8Char", th1ish_cb_int_to_utf8_char);
    FUNC2("compare", th1ish_f_value_compare);
    
#undef FUNC2
#undef CHECKV
    end:
    return rc ? NULL : proto;
}


#define THIS_HASH \
    cwal_hash * h; \
    ARGS_IE; \
    h = cwal_value_hash_part(ie->e, args->self);                        \
    if(!h) return cwal_exception_setf(args->engine, CWAL_RC_TYPE,     \
                                      "'this' is not a hash table.")


int th1ish_cb_hash_insert( cwal_callback_args const * args, cwal_value **rv ){
    THIS_HASH;
    if(2!=args->argc) return th1ish_toss(ie,
                                         CWAL_RC_MISUSE,
                                         "insert() requires (KEY,VALUE) "
                                         "arguments.");
    else{
        int rc = 0;
        rc = cwal_hash_insert_v( h, args->argv[0],
                                 args->argv[1], 1 );
        if(!rc) *rv = args->argv[1];
        else switch(rc){
          case CWAL_RC_ACCESS:
              rc = th1ish_toss(ie, rc, "May not modify a hash while it "
                               "is being iterated over.");
              break;
          default:
              break;
        }
        return rc;
    }
}

int th1ish_cb_hash_get( cwal_callback_args const * args, cwal_value **rv ){
    cwal_value * v;
    THIS_HASH;
    if(1!=args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "get() requires (KEY) argument.");
    }
    v = cwal_hash_search_v( h, args->argv[0] );
    *rv = v ? v : cwal_value_undefined();
    return 0;
}

int th1ish_cb_hash_has( cwal_callback_args const * args, cwal_value **rv ){
    THIS_HASH;
    if(!args->argc) return th1ish_toss(ie, CWAL_RC_MISUSE,
                                       "has() expects 1 argument.");
    else {
        *rv = cwal_hash_search_v( h, args->argv[0] )
            ? cwal_value_true()
            : cwal_value_false();
        return 0;
    }
}

static int th1ish_cb_hash_clear( cwal_callback_args const * args, cwal_value **rv ){
    char clearProps;
    THIS_HASH;
    clearProps = (args->argc>0)
        ? cwal_value_get_bool(args->argv[0])
        : 0;
    cwal_hash_clear(h, clearProps);
    return 0;
}


int th1ish_cb_hash_remove( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    THIS_HASH;
    if(1!=args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "remove() requires (KEY) argument.");
    }
    rc = cwal_hash_remove( h, args->argv[0] );
    if(!rc) *rv = cwal_value_true();
    else switch(rc){
      case CWAL_RC_ACCESS:
          rc = th1ish_toss(ie, rc, "May not modify a hash while it "
                           "is being iterated over.");
          break;
      case CWAL_RC_NOT_FOUND:
          *rv = cwal_value_false();
          rc = 0;
          break;
      default:
          break;
    }
    return rc;
}

int th1ish_cb_hash_size( cwal_callback_args const * args, cwal_value **rv ){
    THIS_HASH;
    *rv = cwal_new_integer(args->engine, (cwal_int_t)h->hashSize);
    return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Returns the "next higher" prime number starting at (n+1), where "next"
   is really the next one in a list of rather arbitrary prime numbers.
   If n is larger than some value which we internally (and almost
   arbitrarily) define in this code, a value smaller than n will be
   returned.
*/
static cwal_hash_t cwal_hash_next_prime( cwal_hash_t n ){
    /**
       This list was more or less arbitrarily chosen by starting at
       some relatively small prime and roughly doubling it for each
       increment, then filling out some numbers in the middle.
    */
#define N(P) if(n < P) return P
    N(11);
    else N(19);
    else N(27);
    else N(59);
    else N(71);
    else N(109);
    else N(137);
    else N(167);
    else N(199);
    else N(239);
    else N(373);
    else N(457);
    else N(613);
    else N(983);
    else N(1319);
    else N(2617);
    else N(5801);
    else N(7919);
    else N(9973);
    else N(20011);
#if CWAL_INT_T_BITS > 16
    else N(49999);
    else N(100003);
    else return 199999;
#else
    else return 49999;
#endif    
#undef N
}

cwal_value * th1ish_prototype_hash( th1ish_interp * ie );
int th1ish_new_hash( th1ish_interp * ie, cwal_int_t hashSize,
                     /* char strictKeyTypes,  */cwal_value ** rv ){
    cwal_hash * h;
    cwal_value * hv;
    cwal_value * proto = th1ish_prototype_hash( ie );
    if(!proto){
        return th1ish_toss(ie, CWAL_RC_ERROR,
                           "Hash table prototype initialization failed.");
    }
    if(hashSize<=0) hashSize = 17;
    else if(hashSize>7919){
        hashSize = 7919;
    }
    else {
        hashSize = cwal_hash_next_prime(hashSize);
    }
    h = cwal_new_hash(ie->e, hashSize/* , strictKeyTypes */);
    hv = h
        ? cwal_hash_value(h)
        : NULL;
    if(!hv){
        assert(!h);
        return CWAL_RC_OOM;
    }
    else {
        assert(proto == cwal_value_prototype_get(ie->e,hv));
        *rv = hv;
        return 0;
    }
}


/**
   Internal impl of th1ish_cp_hash_keys/values(). mode==0 means
   keys, anything else means values.
*/
static int th1ish_cb_hash_kv( cwal_callback_args const * args,
                              char mode,
                              cwal_value **rv ){
    int rc;
    cwal_array * ar;
    THIS_HASH;
    ar = th1ish_new_array(ie);
    if(!ar) return CWAL_RC_OOM;
    if(!mode){
        rc = cwal_hash_visit_keys( h,
                                   th1ish_value_visit_append_to_array,
                                   ar );
    }else{
        rc = cwal_hash_visit_values( h,
                                     th1ish_value_visit_append_to_array,
                                     ar );
    }
    if(!rc) *rv = cwal_array_value(ar);
    else {
        cwal_array_unref(ar);
    }
    return rc;
}

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

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

static int th1ish_cb_hash_entry_count( cwal_callback_args const * args,
                                       cwal_value **rv ){
    THIS_HASH;
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)cwal_hash_entry_count(h));
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_hash_new( cwal_callback_args const * args, cwal_value **rv ){
    cwal_int_t hsz = 0;
    ARGS_IE;
    hsz = (args->argc>0)
        ? cwal_value_get_integer(args->argv[0])
        : 0;
#if 0
    if(args->argc>1) strictKeyTypes = cwal_value_get_bool(args->argv[1]);
#endif
    return th1ish_new_hash(ie, hsz/* , strictKeyTypes */, rv );
}

/**
   Script usage:  [obj.eachEntry FunctionValue]

   the given function is called once per property, passed the key and
   value. *rv will be set to undefined.
*/
static int th1ish_cb_hash_each_entry( cwal_callback_args const * args, cwal_value **rv ){
    cwal_function * f;
    THIS_HASH;
    f = args->argc ? cwal_value_get_function(args->argv[0]) : NULL;
    if(!f){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "'eachEntry' expects a Function argument, "
                           "but got a %s.",
                           args->argc
                           ? cwal_value_type_name(args->argv[0])
                           : "NULL");
    }else{
        cwal_kvp * kvp;
        ObjectEach state;
        int rc = 0;
        cwal_size_t i;
        char doBreak = 0;
        state.e = args->engine;
        state.callback = f;
        state.self = args->self /*better option???*/;
        cwal_value_set_visiting(args->self, 1);
        /* rc = th1ish_inject_callback_argv(args, f); */
        for( i = 0; !doBreak && !rc && (i < h->hashSize); ++i ){
            kvp = h->list[i];
            if(kvp){
                cwal_kvp * next = 0;
                for( ; !rc && kvp; kvp = next ){
                    next = kvp->right;
#if 0
                    cwal_dump_v(kvp->key,"about to visit key");
                    cwal_dump_v(kvp->value,"about to visit value");
#endif
                    rc = th1ish_kvp_visitor_prop_each(kvp, &state);
                    if(CWAL_RC_BREAK==rc){
                        rc = 0;
                        doBreak = 1;
                        break;
                    }
                }
            }
        }
        cwal_value_set_visiting(args->self, 0);
        if(!rc) *rv = cwal_value_undefined();
        return rc;
    }
}

cwal_value * th1ish_prototype_hash( th1ish_interp * ie ){
    int rc;
    cwal_value * proto;
    cwal_value * v;
    assert(ie && ie->e);
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_HASH);
    if(proto) return proto;
    proto = th1ish_new_function2(ie, th1ish_cb_hash_new );
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_HASH, proto);
    if(rc) goto end;
    th1ish_prototype_store(ie, "Hash", proto);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP ); \
    CHECKV; \
    if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v );  \
    assert(!rc);                                                 \
    if(rc) goto end

    FUNC2("clearEntries", th1ish_cb_hash_clear);
    FUNC2("containsEntry", th1ish_cb_hash_has);
    FUNC2("eachEntry", th1ish_cb_hash_each_entry);
    FUNC2("entryKeys", th1ish_cb_hash_keys);
    FUNC2("entryValues", th1ish_cb_hash_values);
    FUNC2("hashSize", th1ish_cb_hash_size);
    FUNC2("insert", th1ish_cb_hash_insert);
    FUNC2("entryCount", th1ish_cb_hash_entry_count);
    FUNC2("remove", th1ish_cb_hash_remove);
    FUNC2("search", th1ish_cb_hash_get);
#undef FUNC2
#undef CHECKV
    
    end:
    if(rc){
        proto = 0 /* let ie->gc deal with it */;
    }
    return rc ? 0 : proto;
}


int th1ish_setup_hash( th1ish_interp * ie, cwal_value * ns ){

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

    FUNC2("Hash", th1ish_cb_hash_new);

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


struct th1ish_pf {
    th1ish_interp * ie;
    cwal_value * self;
    cwal_buffer buf;
    /* char (*predicate)( char const * fn ); */
};

static const th1ish_pf th1ish_pf_empty = {
NULL/*ie*/,
NULL/*self*/,
cwal_buffer_empty_m/*buf*/
};

cwal_array * th1ish_pf_exts(th1ish_pf *pf){
    cwal_value * ar = cwal_prop_get(pf->self,"suffix", 6);
    if(!ar || !cwal_value_is_array(ar)){
        ar = cwal_new_array_value(pf->ie->e);
        if(ar && (0!=cwal_prop_set(pf->self, "suffix",6, ar))){
            cwal_value_unref(ar);
            ar = NULL;
        }
    }
    return cwal_value_get_array(ar);
}

cwal_array * th1ish_pf_dirs(th1ish_pf *pf){
    cwal_value * ar = cwal_prop_get(pf->self,"prefix", 6);
    if(!ar || !cwal_value_is_array(ar)){
        ar = cwal_new_array_value(pf->ie->e);
        if(ar && (0!=cwal_prop_set(pf->self, "prefix", 6, ar))){
            cwal_value_unref(ar);
            ar = NULL;
        }
    }
    return cwal_value_get_array(ar);
}

static void th1ish_pf_finalizer(cwal_engine * e, void * m){
    th1ish_pf * pf = (th1ish_pf*)m;
    cwal_buffer_reserve(e, &pf->buf, 0);
    cwal_free( e, m );
}

int th1ish_pf_dirs_set( th1ish_pf * pf, cwal_array * ar ){
    if(!pf || !pf->self || !ar) return CWAL_RC_MISUSE;
    else{
        return cwal_prop_set(pf->self, "prefix", 6, cwal_array_value(ar));
    }
}

int th1ish_pf_exts_set( th1ish_pf * pf, cwal_array * ar ){
    if(!pf || !pf->self || !ar) return CWAL_RC_MISUSE;
    else{
        return cwal_prop_set(pf->self, "suffix", 6, cwal_array_value(ar));
    }
}

int th1ish_pf_dir_add_v( th1ish_pf * pf, cwal_value * v ){
    if(!pf || !pf->self || !v) return CWAL_RC_MISUSE;
    else{
        cwal_array * ar = th1ish_pf_dirs(pf);
        return ar
            ? cwal_array_append(ar, v)
            : CWAL_RC_OOM;
    }
}

int th1ish_pf_dir_add( th1ish_pf * pf, char const * dir, cwal_size_t dirLen){
    if(!pf || !pf->self || !dir) return CWAL_RC_MISUSE;
    else{
        int rc;
        cwal_value * v = cwal_new_string_value(pf->ie->e,
                                               dirLen ? dir : NULL,
                                               dirLen);
        if(!v) rc = CWAL_RC_OOM;
        else {
            rc = th1ish_pf_dir_add_v( pf, v);
            if(rc) cwal_value_unref(v);
        }
        return rc;
    }
}

int th1ish_pf_ext_add_v( th1ish_pf * pf, cwal_value * v ){
    if(!pf || !pf->self || !v) return CWAL_RC_MISUSE;
    else{
        cwal_array * ar = th1ish_pf_exts(pf);
        return ar
            ? cwal_array_append(ar, v)
            : CWAL_RC_OOM;
    }
}

int th1ish_pf_ext_add( th1ish_pf * pf, char const * dir, cwal_size_t dirLen){
    if(!pf || !pf->self || !dir) return CWAL_RC_MISUSE;
    else{
        int rc;
        cwal_value * v = cwal_new_string_value(pf->ie->e,
                                               dirLen ? dir : NULL,
                                               dirLen);
        if(!v) rc = CWAL_RC_OOM;
        else {
            rc = th1ish_pf_ext_add_v( pf, v);
            if(rc) cwal_value_unref(v);
        }
        return rc;
    }
}

/**
   If v is-a PathFinder or derives from it, this function returns the
   th1ish_pf part of v or one of its prototypes.
*/
static th1ish_pf * th1ish_value_pf_part(cwal_value *v){
    cwal_native * n;
    th1ish_pf * pf = NULL;
    while(v){
        n = cwal_value_get_native(v);
        if(n){
            pf = (th1ish_pf *)cwal_native_get(n, &th1ish_pf_empty);
            if(pf) break;
        }
        v = cwal_value_prototype_get(NULL,v);
    }
    return pf;
}

#define THIS_PF \
    th1ish_pf * pf = th1ish_value_pf_part(args->self); \
    th1ish_interp * ie = pf ? pf->ie : NULL;                                 \
    if(!pf) return \
        cwal_exception_setf(args->engine, CWAL_RC_TYPE, \
                            "'this' is not a PathFinder instance."); \
    else assert(ie)

static int th1ish_cb_pf_add( cwal_callback_args const * args, cwal_value **rv,
                             cwal_array * dest ){
    THIS_PF;
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting string arguments.");
    }
    else if(!dest) return CWAL_RC_OOM;
    else {
        cwal_size_t i = 0;
        int rc = 0;
        for( ; !rc && (i < args->argc); ++i ){
            rc = cwal_array_append( dest, args->argv[i]);
        }
        if(!rc) *rv = args->self;
        return rc;
    }
}


static int th1ish_cb_pf_dir_add( cwal_callback_args const * args, cwal_value **rv ){
    THIS_PF;
    return th1ish_cb_pf_add( args, rv, th1ish_pf_dirs(pf) );
}

static int th1ish_cb_pf_ext_add( cwal_callback_args const * args, cwal_value **rv ){
    THIS_PF;
    return th1ish_cb_pf_add( args, rv, th1ish_pf_exts(pf) );
}


static void th1ish_pf_separator( th1ish_pf * pf, char const ** sep, cwal_size_t * sepLen ){
    cwal_value const * vs = cwal_prop_get(pf->self,"separator",9);
    char const * rc = vs ? cwal_value_get_cstr(vs, sepLen) : NULL;
    assert(sep && sepLen);
    if(!rc){
        if(vs){ /* Non-string separator value. FIXME: we _could_ use a
                   non-string with just a little more work but there's
                   currently no use case for it. If PF is abstracted
                   to do other types of searches (where the user
                   supplies a predicate function we call for each path
                   combination we try) then it will make sense to
                   allow non-string separators. For now we're only
                   dealing with file paths, which means strings.
                 */
            *sep = "";
            *sepLen = 0;
        }
        else{
#ifdef _WIN32
            rc = "\\";
            *sepLen = 1;
#else
            rc = "/";
            *sepLen = 1;
#endif
        }
    }
    *sep = rc;
}

char const * th1ish_pf_search( th1ish_pf * pf, char const * base,
                               cwal_size_t baseLen, cwal_size_t * rcLen ){
    char const * pathSep = NULL;
    cwal_size_t sepLen;
    cwal_buffer * buf;
    cwal_engine * e;
    cwal_size_t d, x, nD, nX, resetLen = 0;
    cwal_array * ad;
    cwal_array * ax;
    int rc = 0;
    if(!pf || !base) return NULL;
    else if(!*base || !baseLen) return NULL;
    th1ish_pf_separator(pf, &pathSep, &sepLen);
    assert(pathSep);
    buf = &pf->buf;
    e = pf->ie->e;
    ad = th1ish_pf_dirs(pf);
    ax = th1ish_pf_exts(pf);
    nD = cwal_array_length_get(ad);
    nX = cwal_array_length_get(ax);
    buf->used = 0;
    for( d = 0; !rc && (d < nD); ++d ){
        cwal_value * vD = cwal_array_get(ad, d);
        buf->used = 0;
        if(vD){
            cwal_size_t const used = buf->used;
            rc = th1ish_value_to_buffer(pf->ie, buf, vD);
            if(rc) break;
            if(sepLen && (used != buf->used)){
                /* Only append separator if vD is non-empty. */
                rc = cwal_buffer_append(e, buf, pathSep, sepLen);
                if(rc) break;
            }
        }
        rc = cwal_buffer_append(e, buf, base, baseLen);
        if(rc) break;
        if(th1ish_file_is_accessible( (char const *)buf->mem, 0 )) goto gotone;
        resetLen = buf->used;
        for( x = 0; !rc && (x < nX); ++x ){
            cwal_value * vX = cwal_array_get(ax, x);
            if(vX){
                buf->used = resetLen;
                rc = th1ish_value_to_buffer(pf->ie, buf, vX);
                if(rc) break;
            }
            assert(buf->used < buf->capacity);
            buf->mem[buf->used] = 0;
            if(th1ish_file_is_accessible( (char const *)buf->mem, 0 )){
                goto gotone;
            }
        }
    }
    return NULL;
    gotone:
    if(rcLen) *rcLen = buf->used;
    return (char const *)buf->mem;
}

static int th1ish_cb_pf_search( cwal_callback_args const * args, cwal_value **rv ){
    cwal_size_t baseLen = 0;
    char const * base;
    char const * rc;
    cwal_size_t rcLen = 0;
    THIS_PF;
    if(!args->argc) goto misuse;
    base = cwal_value_get_cstr(args->argv[0], &baseLen);
    if(!base || !baseLen) goto misuse;
    rc = th1ish_pf_search( pf, base, baseLen, &rcLen );
    if(!rc) *rv = cwal_value_undefined();
    else {
        *rv = cwal_new_string_value(args->engine, rc, rcLen);
    }
    return *rv ? 0 : CWAL_RC_OOM;
    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE, "Expecting a non-empty string argument.");
}

int th1ish_cb_pf_new( cwal_callback_args const * args, cwal_value **rv ){
    if(th1ish_value_pf_part(args->self)){
        /* A PF instance call()ed via its prototype Function. */
        return th1ish_cb_pf_search(args, rv);
    }else{
        /* Constructor... */
        int rc = 0;
        th1ish_pf * pf;
        ARGS_IE;
        pf = th1ish_pf_new(ie);
        if(!pf) return CWAL_RC_OOM;
        *rv = pf->self;
        if(args->argc){
            cwal_array * ar = cwal_value_get_array(args->argv[0]);
            if(ar){
                rc = th1ish_pf_dirs_set(pf, ar);
                ar = (!rc && (args->argc > 1))
                    ? cwal_value_get_array(args->argv[1])
                    : NULL;
                if(ar) th1ish_pf_exts_set(pf, ar);
            }
        }
        return rc;
    }
}

cwal_value * th1ish_prototype_pf(th1ish_interp *ie){
    int rc = 0;
    cwal_value * v;
    cwal_value * proto;
    char const * pKey = "class.PathFinder";
    proto = th1ish_prototype_get_by_name(ie, pKey);
    if(proto) return proto;
    assert(ie && ie->e);
    proto = cwal_new_function_value(ie->e, th1ish_cb_pf_new,
                                    ie, NULL, &th1ish_interp_empty);
    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, 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,
                              "PathFinder", 10);
    SET("__typename");
    FUNC2("addDir", th1ish_cb_pf_dir_add);
    FUNC2("addExt", th1ish_cb_pf_ext_add);
    FUNC2("search", th1ish_cb_pf_search);

#undef SET
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;

}


th1ish_pf * th1ish_pf_new(th1ish_interp *ie){
    th1ish_pf * pf;
    cwal_value * vSelf;
    if(!ie || !ie->e) return NULL;
    pf = (th1ish_pf*)cwal_malloc(ie->e, sizeof(th1ish_pf));
    if(!pf) return NULL;
    *pf = th1ish_pf_empty;
    vSelf = cwal_new_native_value(ie->e, pf, th1ish_pf_finalizer,
                                  &th1ish_pf_empty);
    if(!vSelf){
        cwal_free(ie->e, pf);
        pf = NULL;
    }else{
        pf->self = vSelf;
        pf->ie = ie;
        assert(cwal_props_can(vSelf));
#if 0
        th1ish_pf_dirs(pf);
        th1ish_pf_exts(pf);
        assert(cwal_prop_get(vSelf,"suffix",6));
        assert(cwal_prop_get(vSelf,"prefix",6));
#endif
        cwal_value_prototype_set( vSelf, th1ish_prototype_pf(ie) );
    }
    return pf;
}

int th1ish_install_pf( th1ish_interp * ie, cwal_value * ns ){
    return cwal_props_can(ns)
        ?  cwal_prop_set(ns, "PathFinder", 10, th1ish_prototype_pf(ie))
        : CWAL_RC_MISUSE;
}


#undef THIS_ARRAY
#undef THIS_STRING
#undef THIS_DOUBLE
#undef THIS_INT
#undef THIS_HASH
#undef THIS_FUNCTION
#undef THIS_PF
#undef ARGS_IE
#undef FUNC_SYM_PROP
#undef FUNC_SYM_PROP_LEN
#undef PROTOTYPE_NAME_BUF
#undef MARKER
/* end of file th1ish_protos.c */
/* start of file th1ish_str.c */

#include <assert.h>
#include <memory.h> /* strlen() */
#include <ctype.h> /* tolower() and friends */
#include <errno.h>

#if 1
#endif

/*
  This file includes the script-side string-related functions.  They
  are encapsulated script-side under (api.string...).
*/

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

#define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args)



#define THIS_STRING \
    ARGS_IE; \
    cwal_string * self = cwal_value_string_part(args->engine, args->self); \
    if(!self && args->argc) self = cwal_value_string_part(args->engine, args->argv[0]); \
    if(!self){ \
        return th1ish_toss( ie, CWAL_RC_TYPE, \
                            "Expecting a string 'this' or first argument." );        \
    } (void)0

static int th1ish_cb_str_length( cwal_callback_args const * args, cwal_value **rv ){
    THIS_STRING;
    *rv = cwal_new_integer( args->engine,
                            (cwal_int_t)cwal_string_length_bytes(self) );
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_str_length_utf8( cwal_callback_args const * args, cwal_value **rv ){
    THIS_STRING;
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t) cwal_string_length_utf8(self) );
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_str_char_at( cwal_callback_args const * args, cwal_value **rv ){
    cwal_int_t pos;
    THIS_STRING;
    pos = (args->argc>0)
        ? (cwal_value_is_number(args->argv[0])
           ? cwal_value_get_integer(args->argv[0])
           : -1)
        : -1;
    if(pos<0){
        return th1ish_toss( ie, CWAL_RC_MISUSE,
                            "charAt expects an integer argument with a value of 0 or more.");
    }else{
        unsigned char const * cstr =
            (unsigned char const *)cwal_string_cstr(self);
        cwal_size_t const slen = cwal_string_length_bytes(self);
        if(pos >= (cwal_int_t)slen){
            *rv = cwal_value_undefined();
        }else{
            unsigned char const * at = cstr;
            unsigned char const * end = cstr + slen;
            cwal_int_t i = 0;
            for( ; i < pos; ++i ){
                cwal_utf8_read_char( at, end, &at );
            }
            assert(at!=end);
            cstr = at;
            i = cwal_utf8_read_char( cstr, end, &at );
            assert(at>cstr);
            if((args->argc>1) && cwal_value_get_bool(args->argv[1])){
                *rv = cwal_new_integer(args->engine, i);
            }
            else {
                *rv = cwal_new_string_value( args->engine,
                                             (char const *)cstr,
                                             at - cstr);
            }
        }
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

static int th1ish_cb_str_byte_at( cwal_callback_args const * args, cwal_value **rv ){
    cwal_int_t pos;
    THIS_STRING;
    pos = (args->argc>0)
        ? (cwal_value_is_number(args->argv[0])
           ? cwal_value_get_integer(args->argv[0])
           : -1)
        : -1;
    if(pos<0){
        return th1ish_toss( ie, CWAL_RC_MISUSE,
                            "byteAt expects an integer argument with a value of 0 or more.");
    }else{
        unsigned char const * cstr =
            (unsigned char const *)cwal_string_cstr(self);
        cwal_size_t const slen = cwal_string_length_bytes(self);
        if(pos >= (cwal_int_t)slen){
            *rv = cwal_value_undefined();
        }else{
            *rv = cwal_new_integer(args->engine, cstr[pos]);
        }
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

static int th1ish_cb_str_toupperlower( cwal_callback_args const * args,
                                       cwal_value **rv,
                                       char doUpper ){
    THIS_STRING;

    {
        int rc = 0;
        unsigned char const * cs = (unsigned char const *)cwal_string_cstr(self);
        cwal_size_t const len = cwal_string_length_bytes(self);
        unsigned char const * csEnd = cs+len;
        unsigned char * out;
        int ch;
        th1ish_interp * ie = (th1ish_interp *)args->state;
        cwal_buffer buf = cwal_buffer_empty;
        if(!len){
            *rv = cwal_string_value(self);
            return 0;
        }
        rc = cwal_buffer_reserve( ie->e, &buf, len + 5 );
        if(rc) return rc;
        out = buf.mem;
        for( ; cs < csEnd; ){
            unsigned char const * pos = cs;
            ch = cwal_utf8_read_char( cs, csEnd, &cs );
            ch = doUpper
                ? cwal_utf8_char_toupper(ch)
                : cwal_utf8_char_tolower(ch);
            cwal_utf8_char_to_cstr( (unsigned int)ch, out, 5
                                    /*(buf.capacity - (out-buf.mem))*/);
            out += cs-pos;
        }
        assert(out >= buf.mem);
        buf.used = out - buf.mem;
        buf.mem[buf.used] = 0;
        *rv = cwal_new_string_value(ie->e,
                                    buf.used ? (char const *)buf.mem : NULL,
                                    buf.used);
        if(!*rv) rc = CWAL_RC_OOM;
        cwal_buffer_reserve(ie->e, &buf, 0);
        return rc;
    }
}

static int th1ish_cb_str_tolower( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_cb_str_toupperlower( args, rv, 0 );
}
static int th1ish_cb_str_toupper( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_cb_str_toupperlower( args, rv, 1 );
}

static int th1ish_cb_str_split( cwal_callback_args const * args,
                                cwal_value **rv ){
    cwal_size_t sepLen = 0;
    unsigned char const * sep;
    THIS_STRING;
    sep = args->argc
        ? (unsigned char const *)cwal_value_get_cstr(args->argv[0], &sepLen)
        : NULL;
    if(!sep){
        return cwal_exception_setf( args->engine, CWAL_RC_MISUSE,
                                    "Expecting a string argument.");
    }else{
        int rc = 0;
        /* cwal_int_t count = 0; */
        /* cwal_int_t limit; */
        cwal_array * ar = NULL;
        unsigned char const * pos =
            (unsigned char const *)cwal_string_cstr(self);
        cwal_size_t const slen = cwal_string_length_bytes(self);
        unsigned char const * eof = pos + slen;
        unsigned char const * start = pos;
        cwal_value * v;
        /*TODO?: limit = (args->argc>1)
            ? cwal_value_get_integer(args->argv[1])
            : 0
            ;*/
        ar = cwal_new_array(args->engine);
        if(!ar) return CWAL_RC_OOM;
        else if(!slen || (sepLen>slen)){
            rc = cwal_array_append(ar, args->self);
        }
        else while( 1 ){
            if( (pos>=eof) || (0==memcmp(sep, pos, sepLen)) ){
                cwal_size_t sz;
                char last = (pos>=eof);
                if(pos>eof) pos=eof;
                sz = pos-start;
                v = sz
                    ? cwal_new_string_value(args->engine,
                                            (char const *)start,
                                            sz)
                    : NULL;
                if(!v && sz){
                    rc = CWAL_RC_OOM;
                    goto end;
                }
                else rc = cwal_array_append(ar, v);
                if(rc){
                    cwal_value_unref(v);
                    goto end;
                }
                if(last) goto end;
                /* ++count; */
                pos += sepLen;
                start = pos;
            }else{
                cwal_utf8_read_char( pos, eof, &pos );
            }
        }
        end:
        if(rc && ar){
            cwal_array_unref(ar);
        }else if(!rc){
            *rv = cwal_array_value(ar);
            if(!*rv) rc = CWAL_RC_OOM;
        }
        return rc;
    }
}

/**

*/
static int th1ish_cb_str_substr( cwal_callback_args const * args, cwal_value **rv ){
    if(!args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "Expecting 1 or 2 integer arguments.");
    }else{
        cwal_int_t offset, len;
        cwal_int_t sLen;
        char const * cstr;
        THIS_STRING;
        offset = cwal_value_get_integer(args->argv[0]);
        len = (args->argc>1)
            ? cwal_value_get_integer(args->argv[1])
            : 0;
        if(len<0){
            return cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                                       "Length argument may not be negative.");
        }
        sLen = (cwal_int_t)cwal_string_length_bytes(self);
        cstr = cwal_string_cstr(self);
        if(offset<0){
            offset = sLen + offset;
            if(offset < 0) offset = 0;
        }
        if(offset>=sLen){
            *rv = cwal_new_string_value(args->engine, NULL, 0);
            return 0;
        }
        else if((offset+len) > sLen){
            len = sLen - offset;
            /* assert(len < sLen); */
            if(len > sLen) len = sLen;
        }
        {
            /* Calculate the range/length based on the UTF8
               length. */
            unsigned char const * at = (unsigned char const *)cstr;
            unsigned char const * eof = at + sLen;
            unsigned char const * start;
            unsigned char const * end;
            cwal_int_t i = 0;
            for( ; i < offset; ++i ){
                cwal_utf8_read_char( at, eof, &at );
            }
            start = at;
            if(len){
                end = start;
                for( i = 0; (end<eof) && (i<len); ++i ){
                    cwal_utf8_read_char( end, eof, &end );
                }
            }else{
                end = eof;
            }
            assert(end >= start);
            len = end - start;
            *rv = cwal_new_string_value(args->engine,
                                        len ? (char const *)start : NULL,
                                        len);
        }
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

/**
   Script usage:

   var x = "haystack".indexOf("needle" [, startOffset=0])

   if(undefined===x) { ...no match found... }
   else { x === index of "needle" in "haystack" }

   If the startOffset is negative, it is counted as the number of
   characters from the end of the haystack, but does not change the
   search direction (because counting utf8 lengths backwards sounds
   really hard ;).
*/
static int th1ish_cb_str_indexof( cwal_callback_args const * args, cwal_value **rv ){
    if(!args->argc){
        misuse:
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "Expecting 1 string argument.");
    }else{
        char const * arg;
        cwal_size_t aLen = 0;
        THIS_STRING;
        arg = cwal_value_get_cstr(args->argv[0], &aLen);
        if(!arg) goto misuse;
        else if(!aLen){
            *rv = cwal_new_integer(args->engine, -1);
            return 0;
        }
        else{
            /* Calculate the range/length based on the UTF8
               length. */
            cwal_size_t sLen = cwal_string_length_bytes(self);
            unsigned char const * myStr =
                (unsigned char const *)cwal_string_cstr(self);
            unsigned char const * eof = myStr + sLen;
            cwal_int_t i = 0;
            cwal_int_t offset;
            if(aLen>sLen){
                *rv = cwal_value_undefined();
                return 0;
            }
            offset = (args->argc>1)
                ? cwal_value_get_integer(args->argv[1])
                : 0;
            if(offset<0){
                cwal_size_t const ulen = cwal_string_length_utf8(self);
                if(ulen < ((cwal_size_t)-offset)){
                    offset = 0;
                }else{
                    offset = ulen + offset;
                }
                assert(offset >= 0);
            }
            while(1){
                unsigned char const * start = myStr;
                if((start==eof) || ((cwal_int_t)aLen > (eof-start))) break;
                cwal_utf8_read_char( start, eof, &myStr );
                if(i<offset){
                    ++i;
                    continue;
                }
                assert(myStr <= eof);
                /*MARKER(("tick i=%d [%.*s]\n",
                  (int)i, (int)(myStr-start),(char const*)start));*/
                if(0==memcmp(start, arg, aLen)){
                    *rv = cwal_new_integer(args->engine, i);
                    return *rv ? 0 : CWAL_RC_OOM;
                }
                else ++i;
            }
            *rv = cwal_new_integer(args->engine, -1);
            return 0;
        }
    }
}

static int th1ish_cb_str_concat( cwal_callback_args const * args, cwal_value **rv ){
    if(!args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "'concat' requires at lest one argument.");
    }else{
        int rc = 0;
        cwal_size_t i = 0, argc = args->argc;
        cwal_buffer buf = cwal_buffer_empty;
        th1ish_interp * ie = (th1ish_interp *)args->state;
        if(cwal_value_is_string(args->self)){
            rc = th1ish_value_to_buffer( ie, &buf, args->self );
        }
        for( ; !rc && (i < argc); ++i ){
            rc = th1ish_value_to_buffer( ie, &buf, args->argv[i] );
        }
        if(!rc){
            *rv = cwal_new_string_value(ie->e,
                                        buf.used ? (char const *)buf.mem : NULL,
                                        buf.used);
            if(!*rv) rc = CWAL_RC_OOM;
        }
        cwal_buffer_reserve( ie->e, &buf, 0 );
        return rc;
    }
}


/**
   Impl of (STRING+VALUE) operator.
*/
static int th1ish_cb_str_op_concat( cwal_callback_args const * args, cwal_value **rv ){
    if(!args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "'concat' requires at lest one argument.");
    }else{
        int rc = 0;
        cwal_size_t i = 0, argc = args->argc;
        cwal_buffer buf = cwal_buffer_empty;
        th1ish_interp * ie = (th1ish_interp *)args->state;
        char checkForSelf = 0;
        if(cwal_value_is_string(args->self)){
            checkForSelf = 1;
            rc = th1ish_value_to_buffer( ie, &buf, args->self );
        }
        for( ; !rc && (i < argc); ++i ){
            if((0==i) && checkForSelf && (args->argv[i]==args->self)){
                checkForSelf = 0;
                continue;
            }
            rc = th1ish_value_to_buffer( ie, &buf, args->argv[i] );
        }
        if(!rc){
            *rv = cwal_new_string_value(ie->e,
                                        buf.used ? (char const *)buf.mem : NULL,
                                        buf.used);
            if(!*rv) rc = CWAL_RC_OOM;
        }
        cwal_buffer_reserve( ie->e, &buf, 0 );
        return rc;
    }
}

static int th1ish_cb_str_unescape_c( cwal_callback_args const * args,
                                     cwal_value **rv ){
    if(!args->argc && !cwal_value_is_string(args->self)){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "'' requires at lest one argument.");
    }else{
        int rc = 0;
        unsigned char const * begin = 0;
        cwal_size_t sLen = 0;
        cwal_string * sv;
        cwal_value * theV;
        th1ish_interp * ie = (th1ish_interp *)args->state;
        cwal_buffer * buf = &ie->buffer;
        cwal_size_t const oldUsed = buf->used;
        if(cwal_value_is_string(args->self)){
            theV = args->self;
        }else{
            theV = args->argv[0];
        }
        sv = cwal_value_get_string(theV);
        if(!sv){
            return th1ish_toss(ie, CWAL_RC_TYPE,
                               "Expecting a STRING as 'this' or first argument.");
        }
        begin = (unsigned char const *)cwal_string_cstr(sv);
        sLen = cwal_string_length_bytes(sv);
        if(!sLen){
            *rv = cwal_string_value(sv);
            return 0;
        }
        rc = cwal_st_unescape_string(args->engine, begin, begin + sLen,
                                     buf );
        assert(buf->used >= oldUsed);
        if(rc){
            return th1ish_toss(ie, rc, "Unescaping failed!?");
        }
        if((sLen == (buf->used-oldUsed))
           && (0==cwal_compare_cstr((char const*)buf->mem, buf->used,
                                    (char const*)begin, sLen))){
            /* Same byte sequence - re-use it. */
            *rv = theV;
        }else{
            *rv = cwal_new_string_value( args->engine,
                                         (buf->used!=oldUsed)
                                         ? ((char const *)buf->mem+oldUsed)
                                         : NULL,
                                         buf->used-oldUsed );
        }
        buf->used = oldUsed;
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

/**
   Impl for trim/trimLeft/trimRight(). mode determines which:

   <0 == left
   0 == both
   >0 == right
*/
static int th1ish_cb_str_trim_impl( cwal_callback_args const * args,
                                    cwal_value **rv,
                                    int mode ){
    THIS_STRING;
    {
        int rc = 0;
        unsigned char const * cs = (unsigned char const *)cwal_string_cstr(self);
        cwal_int_t const len = (cwal_int_t)cwal_string_length_bytes(self);
        unsigned char const * end = cs + len;
        if(!len){
            *rv = args->self;
            return rc;
        }
        if(mode <= 0) for( ; *cs && isspace((int)*cs); ++cs){}
        if(mode>=0){
            for( --end; (end>cs) && isspace((int)*end); --end){}
            ++end;
        }
        *rv = ((end-cs) == len)
            ? args->self
            : cwal_new_string_value(args->engine,
                                    (char const *)cs, end-cs)
            ;
        return *rv ? 0 : CWAL_RC_OOM;
    }
}
static int th1ish_cb_str_trim_left( cwal_callback_args const * args,
                                    cwal_value **rv ){
    return th1ish_cb_str_trim_impl( args, rv, -1 );
}
static int th1ish_cb_str_trim_right( cwal_callback_args const * args,
                                    cwal_value **rv ){
    return th1ish_cb_str_trim_impl( args, rv, 1 );
}

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

/**
   Internal helper for Buffer.appendf() and String.applyFormat()
*/
static int th1ish_helper_appendf( th1ish_interp * ie, cwal_buffer * buf,
                                  char const * fmt,
                                  cwal_size_t fmtLen,
                                  uint16_t argc,
                                  cwal_value * const * argv ){
    cwal_size_t oldUsed = buf->used;
    int rc = cwal_buffer_format( ie->e, buf, fmt, fmtLen, argc, argv);
    if(rc && (CWAL_RC_OOM != rc)){
        if(buf->used > oldUsed){
            /* Error string is embedded in the buffer... */
            rc = th1ish_toss(ie, rc, "%s",
                             (char const *)(buf->mem + oldUsed));
            buf->used = oldUsed;
        }else{
            rc = th1ish_toss(ie, rc, "String formatting failed with code: %s",
                             cwal_rc_cstr(rc));
        }
    }
    return rc;
}

static int th1ish_cb_str_apply_format( cwal_callback_args const * args, cwal_value **rv ){
    char const * fmt;
    cwal_size_t fmtLen;
    cwal_size_t oldUsed;
    cwal_buffer * buf;
    int rc;
    THIS_STRING;
    fmt = cwal_string_cstr(self);
    fmtLen = cwal_string_length_bytes(self);
    buf = &ie->buffer;
    oldUsed = buf->used;
    rc = th1ish_helper_appendf( ie, buf, fmt, fmtLen, args->argc, args->argv);
    if(!rc){
        cwal_size_t const n = buf->used - oldUsed;
        *rv = cwal_new_string_value(args->engine,
                                    n ? ((char const *)buf->mem+oldUsed) : NULL,
                                    n);
        rc = *rv ? 0 : CWAL_RC_OOM;
    }
    buf->used = oldUsed;
    return rc;
}

static int th1ish_cb_str_to_string( cwal_callback_args const * args, cwal_value **rv ){
    cwal_string * str;
    ARGS_IE;
    assert(ie);
    str = cwal_value_string_part( args->engine, args->self );
    if(!str){
        return th1ish_toss(ie, CWAL_RC_TYPE,
                           "FIXME: check for a different toString() impl in this case.");
    }else{
        *rv = cwal_string_value(str);
        return 0;
    }
}


cwal_value * th1ish_prototype_string( th1ish_interp * ie ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_STRING);
    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 = cwal_prototype_base_set(ie->e, CWAL_TYPE_STRING, proto);
    if(rc) goto end;
#if 0
    cwal_value_prototype_set(proto, NULL)
        /* So that ("" instanceof object{}.prototype) fails. */;
#endif
    th1ish_prototype_store(ie, "String", proto);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("applyFormat", th1ish_cb_str_apply_format);
    FUNC2("byteAt", th1ish_cb_str_byte_at);
    FUNC2("charAt", th1ish_cb_str_char_at);
    FUNC2("concat", th1ish_cb_str_concat);
    FUNC2("indexOf", th1ish_cb_str_indexof);
    FUNC2("lengthBytes", th1ish_cb_str_length);
    FUNC2("lengthUtf8", th1ish_cb_str_length_utf8);
    FUNC2("length", th1ish_cb_str_length_utf8);
    FUNC2("operator+", th1ish_cb_str_op_concat);
    FUNC2("split", th1ish_cb_str_split);
    FUNC2("substr", th1ish_cb_str_substr);
    FUNC2("toLower", th1ish_cb_str_tolower);
    FUNC2("toString", th1ish_cb_str_to_string);
    FUNC2("toUpper", th1ish_cb_str_toupper);
    FUNC2("trimLeft", th1ish_cb_str_trim_left);
    FUNC2("trimRight", th1ish_cb_str_trim_right);
    FUNC2("trim", th1ish_cb_str_trim_both);
    FUNC2("unescape", th1ish_cb_str_unescape_c);
    FUNC2("compare", th1ish_f_value_compare);
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}

#define THIS_BUFFER                             \
    ARGS_IE; \
    cwal_buffer * self = cwal_value_get_buffer(args->self);     \
    assert(ie); \
    if(!self){ \
        return cwal_exception_setf( args->engine, CWAL_RC_TYPE, \
                                    "'this' is-not-a Buffer." ); \
    } (void)0

/**
   Callback handler for buffer.length() (if isCapacity is false) and
   buffer.capacity (if isCapacity is true).
*/
static int th1ish_cb_buffer_length_uc( cwal_callback_args const * args,
                                       cwal_value **rv,
                                       char isCapacity){
    THIS_BUFFER;
    if(args->argc>0){
        /* SET length */
        cwal_value * const index = args->argv[0];
        cwal_int_t len = cwal_value_get_integer(index);
        int rc;
        if((len<0) || !cwal_value_is_number(index)){
            return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                       "length argument must be "
                                       "non-negative integer.");
        }
        if(isCapacity || ((cwal_size_t)len > self->capacity)){
            rc = cwal_buffer_reserve(args->engine, self, (cwal_size_t)len );
            if(rc){
                return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                           "Setting buffer length to %"CWAL_INT_T_PFMT
                                           " failed with code %d (%s).",
                                           len, rc, cwal_rc_cstr(rc));
            }
        }
        self->used = (cwal_size_t)len;
        assert(self->used <= self->capacity);
        *rv = args->self;
    }else{
        *rv = cwal_new_integer( args->engine,
                                (cwal_int_t)
                                (isCapacity ? self->capacity : self->used)
                                );
    }
    return *rv ? 0 : CWAL_RC_OOM;
}


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

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

static int th1ish_cb_buffer_append( cwal_callback_args const * args, cwal_value **rv ){
    uint16_t i;
    int rc = 0;
    THIS_BUFFER;
    for( i = 0; !rc && (i < args->argc); ++i ){
        rc = th1ish_value_to_buffer(ie, self, args->argv[i]);
    }
    return rc;
}

static int th1ish_cb_buffer_appendf( cwal_callback_args const * args, cwal_value **rv ){
    uint16_t i;
    int rc = 0;
    enum { AC = CWAL_OPT_MAX_FUNC_CALL_ARGS };
    cwal_value * argv[AC];
    cwal_size_t fmtlen;
    char const * fmt;
    cwal_size_t oldUsed;
    THIS_BUFFER;
    fmt = (args->argc>0)
        ? cwal_value_get_cstr(args->argv[0], &fmtlen)
        : NULL;
    if((!args->argc) || !fmt){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting (String,...) arguments.");
    }
    oldUsed = self->used;
    memset(argv, 0, sizeof(argv));
    for( i = 1; i < args->argc; ++i ){
        argv[i-1] = args->argv[i];
    }
    rc = th1ish_helper_appendf(ie, self, fmt, fmtlen, args->argc-1, argv);
    if(!rc){
        cwal_size_t const newLen = self->used - oldUsed;
        *rv = cwal_new_integer(args->engine, (int)(newLen));
        rc = *rv ? 0 : CWAL_RC_OOM;
    }
    return rc;
}

static int th1ish_cb_buffer_reset( cwal_callback_args const * args, cwal_value **rv ){
    THIS_BUFFER;
    self->used = 0;
    memset(self->mem, 0, self->capacity);
    *rv = args->self;
    return 0;
}

static int th1ish_cb_buffer_resize( cwal_callback_args const * args, cwal_value **rv ){
    cwal_int_t n;
    THIS_BUFFER;
    n = args->argc>0
        ? cwal_value_get_integer(args->argv[0])
        : -1;
    if(n<0){
        return cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                                   "Expecting an integer value 0 or larger.");
    }else{
        int const rc = cwal_buffer_resize(args->engine, self, (cwal_size_t)n );
        if(!rc) *rv = args->self;
        return rc;
    }
}

static int th1ish_cb_buffer_to_string( cwal_callback_args const * args, cwal_value **rv ){
    char const * begin = 0;
    char const * end = 0;
    THIS_BUFFER;
    if(!self->mem){
        *rv = cwal_value_null();
        return 0;
    }
    if(args->argc){
        cwal_int_t off, len;
        if(!cwal_value_is_number(args->argv[0])) goto misuse;
        off = cwal_value_get_integer(args->argv[0]);
        if(args->argc>1){
            if(!cwal_value_is_number(args->argv[1])) goto misuse;
            len = cwal_value_get_integer(args->argv[1]);
        }else{
            len = off;
            off = 0;
        }
        if(off<0 || len<0) goto misuse;
        begin = (char const *)self->mem + off;
        end = begin + len;
        if(end>((char const *)self->mem+self->used)){
            end = (char const *)self->mem + self->used;
        }
    }
    else {
        begin = (char const *)self->mem;
        end = begin + self->used;
    }
    *rv = cwal_new_string_value(args->engine,
                                (end-begin) ? begin : NULL,
                                end - begin );
    return *rv ? 0 : CWAL_RC_OOM;
    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE,
                       "Buffer.toString() arguments must 0 or "
                       "positive integers.");
}


static cwal_int_t th1ish_get_byte(cwal_value const *v){
    char const * cstr = cwal_value_get_cstr(v, NULL);
    return cstr ? *cstr : cwal_value_get_integer(v);
}

int th1ish_cb_buffer_byte_at( cwal_callback_args const * args, cwal_value **rv ){
    THIS_BUFFER;
    if(!args->argc || !cwal_value_is_number(args->argv[0])){
        return cwal_exception_setf( args->engine, CWAL_RC_MISUSE,
                                    "Expecting one integer argument.");
    }
    else {
        cwal_value * const index = args->argv[0];
        cwal_int_t pos = cwal_value_get_integer(index);
        if(pos<0){
            return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                       "Byte position argument must be "
                                       "non-negative integer.");
        }
        if(args->argc<2){
            *rv = (pos>=(cwal_int_t)self->used)
                ? cwal_value_undefined()
                : cwal_new_integer(args->engine, self->mem[pos])
                ;
            return *rv ? 0 : CWAL_RC_OOM;
        }else{
            int const rc = cwal_buffer_reserve( args->engine,
                                                self,
                                                (cwal_size_t)pos+1);
            if(rc) return rc;
            self->mem[pos] = 0xFF & th1ish_get_byte(args->argv[1]);
            *rv = cwal_value_true();
            if(self->used <= (cwal_size_t)pos) self->used = (cwal_size_t)pos+1;
            return 0;
        }

    }
}

int th1ish_cb_buffer_file_read( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    char const * fname;
    cwal_buffer * newBuf = NULL;
    cwal_buffer * self = cwal_value_get_buffer(args->self); 
    ARGS_IE;
    assert(ie);
    fname = args->argc
        ? cwal_string_cstr(cwal_value_get_string(args->argv[0]))
        : NULL;
    if(!fname){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a filename argument.");
    }
    if(!self || (cwal_buffer_value(self) == th1ish_prototype_buffer(ie))){
        newBuf = cwal_new_buffer(args->engine, 0);
        if(!newBuf) return CWAL_RC_OOM;
    }
    rc = cwal_buffer_fill_from_filename( args->engine, newBuf ? newBuf : self, fname );
    if(rc){
        if(newBuf) cwal_value_unref(cwal_buffer_value(newBuf));
        assert(CWAL_RC_EXCEPTION!=rc);
        rc = th1ish_toss(ie, rc,
                         "Reading file [%s] failed.",
                         fname);
    }
    else {
        *rv = newBuf ? cwal_buffer_value(newBuf) : args->self;
    }
    return rc;
}

int th1ish_cb_buffer_file_write( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    char const * fname;
    cwal_size_t nameLen = 0;
    THIS_BUFFER;
    fname = args->argc
        ? cwal_value_get_cstr(args->argv[0], &nameLen)
        : NULL;
    if(!fname){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a filename argument.");
    }
    else{
        FILE * f;
        char append = (args->argc>1)
            ? cwal_value_get_bool(args->argv[1])
            : 0;
        f = fopen( fname, append ? "a" : "w" );
        if(!f){
            if(errno){
                rc = th1ish_toss(ie, CWAL_RC_IO,
                                 "Could not open file [%.*s] "
                                 "in %s mode. "
                                 "errno=%d: %s",
                                 (int)nameLen, fname,
                                 append ? "append" : "truncate/write",
                                 errno, strerror(errno));
            }else{
                rc = th1ish_toss(ie, CWAL_RC_IO,
                                 "Could not open file [%.*s] "
                                 "in %s mode.",
                                 (int)nameLen, fname,
                                 append ? "append" : "truncate/write");
            }
        }else{
            if(1 != fwrite(self->mem, self->used, 1, f)){
                rc = th1ish_toss(ie, CWAL_RC_IO,
                                 "Error writing %"CWAL_SIZE_T_PFMT" byte(s) "
                                 "to file [%.*s].",
                                 self->used, (int)nameLen, fname);
            }
            fclose(f);
        }
        return rc;
    }
}

/**
    Script usage:

    buffer.fill( Byte [offset=0 [,length=0]])

    Byte may be an integer or string (in which case the first byte
    is used, even if it is a NUL byte). By default it fills the whole
    buffer with the byte, but the offset and length may be specified.
    It _will_not_ expand the buffer. If the offset/length are out of range
    this is a no-op.
*/
static int th1ish_cb_buffer_fill( cwal_callback_args const * args, cwal_value **rv ){
    cwal_int_t byte;
    THIS_BUFFER;
    if(!args->argc){
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Expecting a byte value argument.");
    }
    byte = 0xFF & th1ish_get_byte(args->argv[0]);
    if(1 == args->argc){
        cwal_buffer_fill(self, (unsigned char)byte);
        *rv = cwal_new_integer(args->engine, (cwal_int_t)self->capacity);
        return 0;
    }else{
        cwal_int_t offset;
        cwal_int_t len;
        offset = cwal_value_get_integer(args->argv[1]);
        len = (args->argc>2)
            ? cwal_value_get_integer(args->argv[2])
            : 0;
        if((offset<0) || (len<0)){
            return th1ish_toss(ie, CWAL_RC_MISUSE,
                "Neither offset nor length may be negative.");
        }
        if(0==len){
            len = ((cwal_int_t)self->capacity <= offset)
                ? 0
                : ((cwal_int_t)self->capacity - offset);
        }
        if( (len>0) && ((cwal_size_t)offset < self->capacity) ) {
            if((size_t)(offset+len) > self->capacity) len = (cwal_int_t)self->capacity - offset;
            /*MARKER(("Filling %d byte(s) at offset %d to 0x%02x\n", (int)len, (int)offset, (int)byte));*/
            memset( self->mem+offset, byte, (size_t)len );
        }
        *rv = cwal_new_integer( args->engine, len );
        return *rv ? 0 : CWAL_RC_OOM;
    }

}

#undef THIS_BUFFER

static int th1ish_cb_buffer_new( cwal_callback_args const * args, cwal_value **rv );

cwal_value * th1ish_prototype_buffer( th1ish_interp * ie ){
    int rc;
    cwal_value * proto;
    cwal_value * v;

    assert(ie && ie->e);
    proto = cwal_prototype_base_get(ie->e, CWAL_TYPE_BUFFER);
    if(proto) return proto;

    proto = th1ish_new_function2(ie, th1ish_cb_buffer_new);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(ie->e, CWAL_TYPE_BUFFER, proto);
    if(rc) goto end;
    th1ish_prototype_store(ie, "Buffer", proto);
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define FUNC2(NAME,FP)                              \
    v = th1ish_new_function2( ie, FP );        \
    CHECKV; \
    rc = cwal_value_prototype_set(v, proto); \
    assert(!rc); \
    if(!rc) rc = cwal_prop_set( proto, NAME, strlen(NAME), v );  \
    assert(!rc);                                                 \
    if(rc) goto end
    
    FUNC2("append", th1ish_cb_buffer_append);
    FUNC2("appendf", th1ish_cb_buffer_appendf);
    FUNC2("byteAt", th1ish_cb_buffer_byte_at);
    FUNC2("capacity", th1ish_cb_buffer_length_c);
    FUNC2("fill", th1ish_cb_buffer_fill);
    FUNC2("length", th1ish_cb_buffer_length_u);
    FUNC2("readFile", th1ish_cb_buffer_file_read);
    FUNC2("reserve", th1ish_cb_buffer_length_c);
    FUNC2("reset", th1ish_cb_buffer_reset);
    FUNC2("resize", th1ish_cb_buffer_resize);
    FUNC2("toString", th1ish_cb_buffer_to_string);
    FUNC2("appendJSON", th1ish_f_this_to_json_token);
    FUNC2("writeToFile", th1ish_cb_buffer_file_write);
    
#undef FUNC2
#undef CHECKV
    
    end:
    if(rc){
        proto = 0 /* let ie->gc deal with it */;
    }
    return rc ? 0 : proto;
}

cwal_value * th1ish_new_buffer(th1ish_interp * ie, cwal_size_t initialSize){
    cwal_buffer * buf;
    cwal_value * bp = th1ish_prototype_buffer( ie );
    if(!bp){
        cwal_exception_setf(ie->e, CWAL_RC_ERROR,
                            "Buffer prototype initialization failed.");
        return NULL;
    }
    buf = cwal_new_buffer(ie->e, initialSize);
    assert(cwal_value_derives_from(ie->e, cwal_buffer_value(buf), bp));
    return buf
        ? cwal_buffer_value(buf)
        : NULL;
}

int th1ish_cb_buffer_new( cwal_callback_args const * args, cwal_value **rv ){
    th1ish_interp * ie = (th1ish_interp *)args->state;
    cwal_int_t len;
    assert(ie);
    if(cwal_value_buffer_part(ie->e, args->self)){
        /* assert(!"This doesn't seem to be possible any more."); */
        return th1ish_toss(ie, CWAL_RC_MISUSE,
                           "Do not call a buffer as a function.");
    }
    len = args->argc
        ? cwal_value_get_integer(args->argv[0])
        : 0;
    if(len<0) len = 0;
    *rv = th1ish_new_buffer(ie, (cwal_size_t) len);
    return *rv ? 0 : CWAL_RC_OOM;
}

static int th1ish_cb_str_length_bytes( cwal_callback_args const * args, cwal_value **rv ){
    if(1 != args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "'strlen' requires a string argument.");
    }else{
        cwal_string * s = cwal_value_get_string(args->argv[0]);
        if(!s){
            return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                       "'strlen' requires a string argument.");
        }
        *rv = cwal_new_integer(args->engine,
                               (cwal_int_t) cwal_string_length_bytes(s) );
        return 0;
    }
}

int th1ish_value_to_buffer( th1ish_interp *ie,
                            cwal_buffer * buf,
                            cwal_value * arg ){
    int rc = 0;
    cwal_engine * e = ie->e;
    switch(cwal_value_type_id(arg)){
      case CWAL_TYPE_STRING:{
          cwal_string const * s = cwal_value_get_string(arg);
          rc = cwal_buffer_append( e, buf, cwal_string_cstr(s),
                                   cwal_string_length_bytes(s) );
          break;
      }
      case CWAL_TYPE_BOOL: {
          char const b = cwal_value_get_bool(arg);
          rc = cwal_buffer_append( e, buf,
                                   b ? "true" : "false",
                                   b ? 4 : 5);
          break;
      }
      case CWAL_TYPE_UNDEF:
          rc = cwal_buffer_append( e, buf, "undefined", 9);
          break;
      case CWAL_TYPE_NULL:
          rc = cwal_buffer_append( e, buf, "null", 4);
          break;
      case CWAL_TYPE_INTEGER:
          rc = cwal_buffer_printf( e, buf, "%"CWAL_INT_T_PFMT,
                                   cwal_value_get_integer(arg));
          break;
      case CWAL_TYPE_DOUBLE:
          rc = cwal_buffer_printf( e, buf, "%"CWAL_DOUBLE_T_PFMT,
                                   cwal_value_get_double(arg));
          if(!rc){
              /* Trim trailing zeroes... */
              unsigned char * pos = buf->mem + buf->used - 1;
              while('0' == *pos) {
                  *pos = 0;
                  --pos;
                  --buf->used;
              }
          }
          break;
      case CWAL_TYPE_BUFFER:{
          cwal_buffer const * vb = cwal_value_get_buffer(arg);
          assert(vb);
          if(vb->used){
              rc = cwal_buffer_reserve( e, buf, buf->used + vb->used + 1 )
                  /* Preallocation required in case buf===vb */;
              if(!rc){
                  if(vb==buf){
                      memmove(buf->mem + buf->used , vb->mem, vb->used);
                      buf->used *= 2;
                      buf->mem[buf->used] = 0;
                      break;
                  }else{
                      rc = cwal_buffer_append( e, buf, vb->mem, vb->used );
                  }
              }
          }
          break;
      }
      case CWAL_TYPE_OBJECT:
      case CWAL_TYPE_ARRAY:{
          cwal_output_buffer_state job = cwal_output_buffer_state_empty;
          job.e = ie->e;
          job.b = buf;
          rc = cwal_json_output( arg, cwal_output_f_buffer, &job, NULL );
          break;
      }          
      case CWAL_TYPE_HASH:
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_NATIVE:
          rc = cwal_buffer_printf(e, buf, "%s@%p",
                                  cwal_value_type_name(arg),
                                  (void const*)arg);
          break;
      default:
          rc = cwal_exception_setf( e, CWAL_RC_TYPE,
                                    "Don't know how to to-string arguments "
                                    "of type '%s'.",
                                    cwal_value_type_name(arg));
          break;
    }
    return rc;
}

static char th1ish_value_try_toString(th1ish_interp *ie,
                                      cwal_value * arg,
                                      cwal_value **rv,
                                      int * rc ){
    cwal_value * fv = 0;
    cwal_function * f = 0;
    /* cwal_value * origRv; */
    char result = 0;
    /* start: */
    /* origRv = *rv; */
    *rc = th1ish_get( ie, arg, "toString", 8, &fv );
    if(*rc) return *rc;
    f = fv ? cwal_value_get_function(fv) : NULL;
    if(!f){
        *rc = 0;
        return result;
    }
    *rc = cwal_function_call(f, arg, rv, 0, NULL);
    result = 1;
#if 0
    if(!*rc /* rc is not an error */
       && (origRv!=*rv) /* original *rv was modified by toString() call */
       && !cwal_value_is_string(*rv) /* toString() returned a non-string! */
       ){
        /* So try again on the toString()'s result...
           i can conceive of "artificial" cases which
           could trigger an infinite loop here.
        */
        arg = *rv;
        goto start;
    }
#endif
    return result;
}


int th1ish_value_to_string( th1ish_interp *ie, cwal_value * arg,
                            cwal_value **rv ){
    int rc = 0;
    cwal_value * v = 0;
    cwal_buffer buf = cwal_buffer_empty;
    cwal_engine * e = ie->e;
    switch(cwal_value_type_id(arg)){
      case CWAL_TYPE_STRING:
          v = arg;
          break;
      default:
          if(!th1ish_value_try_toString(ie, arg, &v, &rc)){
              rc = th1ish_value_to_buffer(ie, &buf, arg);
          }else if(!rc){
              /* arg->toString() exists and that call succeeded... */
              rc = th1ish_value_to_buffer(ie, &buf,
                                          v ? v : cwal_value_undefined());
              v = 0;
          }
          break;
    }
    if(!rc && !v){
        v = cwal_new_string_value(e,
                                  buf.used ? (char const *)buf.mem : NULL,
                                  buf.used);
        if(!v) rc = CWAL_RC_OOM;
    }
    if(!rc){
        assert(v);
        *rv = v;
        assert(cwal_value_type_id(*rv) == CWAL_TYPE_STRING);
    }
    cwal_buffer_reserve(ie->e, &buf, 0);
    return rc;
}

int th1ish_f_value_to_string( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_buffer buf = cwal_buffer_empty;
    ARGS_IE;
    assert(ie);
    rc = th1ish_value_to_buffer(ie, &buf, args->self);
    if(!rc){
        *rv = cwal_new_zstring_value(args->engine,
                                     buf.used ? (char *)buf.mem : NULL,
                                     buf.used);
        if(!*rv) rc = CWAL_RC_OOM;
        else buf = cwal_buffer_empty/*transfer ownership*/;
    }
    cwal_buffer_reserve(args->engine, &buf, 0);
    return rc;
}



int th1ish_cb_str_tostring( cwal_callback_args const * args, cwal_value **rv ){
    if(1 != args->argc){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "'toString' requires one argument.");
    }else{
        int rc = 0;
        cwal_value * v = 0;
        th1ish_interp * ie = (th1ish_interp *)args->state;
        rc = th1ish_value_to_string( ie, args->argv[0], &v );
        if(!rc){
            assert(v);
            *rv = v;
            assert(cwal_value_type_id(*rv) == CWAL_TYPE_STRING);
        }
        return rc;
    }
}

/**

*/
int th1ish_setup_strings( th1ish_interp * ie, cwal_value * ns ){
    int rc;
    cwal_value * v;
    cwal_value * outer;
    if(!cwal_props_can(ns)) return CWAL_RC_MISUSE;

    outer = th1ish_new_object(ie);
    rc = cwal_prop_set( ns, "string", 6, outer );
    if(rc) goto end;

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( outer, NAME, strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)
    
    FUNC2("concat", th1ish_cb_str_concat);
    FUNC2("length", th1ish_cb_str_length_bytes);
    FUNC2("lengthUtf8", th1ish_cb_str_length_utf8);
    FUNC2("toJSONString", th1ish_f_this_to_json_token);
    FUNC2("toLower", th1ish_cb_str_tolower);
    FUNC2("toString", th1ish_cb_str_tostring);
    FUNC2("toUpper", th1ish_cb_str_toupper);

    /* FUNC2("Buffer", th1ish_cb_buffer_new); */
    /* Also add Buffer to the api namespace ... */
#if 0
    v = th1ish_new_function2( ie, th1ish_cb_buffer_new );
#else
    v = th1ish_prototype_buffer(ie);
#endif
    CHECKV;
    rc = cwal_prop_set( ns, "Buffer", 6, v );
    if(rc) goto end;

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


#undef MARKER
#undef ARGS_IE
/* end of file th1ish_str.c */
/* start of file cwal_cish_ttypes.h */
/* Automatically generated. Edit at your own risk.  License: Public Domain. */
#ifndef WANDERINGHORSE_NET_CWAL_TOKER_TYPES_H_INCLUDED
#define WANDERINGHORSE_NET_CWAL_TOKER_TYPES_H_INCLUDED 1
#ifdef __cplusplus
extern "C" {
#endif
enum cwal_cish_token_types {
     TT_UNDEF= -1, /* Ideally a token's initial type. */
     TT_EOF= 0, /* NULL char or end of source */
     TT_Bell= 7, /* \\a */
     TT_Backspace= 8, /* \\b */
     TT_HTab= 9, /* \\t */
     TT_NL= 10, /* \\n */
     TT_VTab= 11, /* \\v */
     TT_FormFeed= 12, /* \\f */
     TT_CR= 13, /* \\r */
     TT_ChSpace= 32,
     TT_ChExcl= 33,
     TT_ChQuote= 34,
     TT_ChHash= 35,
     TT_ChDollar= 36,
     TT_ChPercent= 37,
     TT_ChAmp= 38,
     TT_ChApos= 39,
     TT_ChParenOpen= 40,
     TT_ChParenClose= 41,
     TT_ChAsterisk= 42,
     TT_ChPlus= 43,
     TT_ChComma= 44,
     TT_ChMinus= 45,
     TT_ChPeriod= 46,
     TT_ChFSlash= 47,
     TT_Ch0= 48,
     TT_Ch1= 49,
     TT_Ch2= 50,
     TT_Ch3= 51,
     TT_Ch4= 52,
     TT_Ch5= 53,
     TT_Ch6= 54,
     TT_Ch7= 55,
     TT_Ch8= 56,
     TT_Ch9= 57,
     TT_ChColon= 58,
     TT_ChSemicolon= 59,
     TT_ChLt= 60,
     TT_ChEq= 61,
     TT_ChGt= 62,
     TT_ChQuestion= 63,
     TT_ChAt= 64,
     TT_ChA= 65,
     TT_ChB= 66,
     TT_ChC= 67,
     TT_ChD= 68,
     TT_ChE= 69,
     TT_ChF= 70,
     TT_ChN= 78,
     TT_ChO= 79,
     TT_ChP= 80,
     TT_ChQ= 81,
     TT_ChR= 82,
     TT_ChS= 83,
     TT_ChT= 84,
     TT_ChU= 85,
     TT_ChV= 86,
     TT_ChW= 87,
     TT_ChX= 88,
     TT_ChY= 89,
     TT_ChZ= 90,
     TT_ChBraceOpen= 91,
     TT_ChBSlash= 92,
     TT_ChBraceClose= 93, /* it is curious that ] is not directly after [ in the ASCII chart */
     TT_ChCaret= 94,
     TT_ChUnderscore= 95,
     TT_ChBacktick= 96,
     TT_Cha= 97,
     TT_Chb= 98,
     TT_Chc= 99,
     TT_Chd= 100,
     TT_Che= 101,
     TT_Chf= 102,
     TT_Chg= 103,
     TT_Chh= 104,
     TT_Chi= 105,
     TT_Chj= 106,
     TT_Chk= 107,
     TT_Chl= 108,
     TT_Chm= 109,
     TT_Chn= 110,
     TT_Cho= 111,
     TT_Chp= 112,
     TT_Chq= 113,
     TT_Chr= 114,
     TT_Chs= 115,
     TT_Cht= 116,
     TT_Chu= 117,
     TT_Chv= 118,
     TT_Chw= 119,
     TT_Chx= 120,
     TT_Chy= 121,
     TT_Chz= 122,
     TT_ChScopeOpen= 123,
     TT_ChPipe= 124,
     TT_ChScopeClose= 125,
     TT_ChTilde= 126,
     TT_Literal= 3000, /* some literal constant type: int, double, string */
     TT_LiteralIntDec= 3001, /* a decimal int number */
     TT_LiteralDouble= 3002, /* a double number */
     TT_LiteralStringDQ= 3003, /* a double-quoted string */
     TT_LiteralStringSQ= 3004, /* a single-quoted string */
     TT_LiteralChar= 3005,
     TT_LiteralIntHex= 3006, /* a hexadecimal number */
     TT_LiteralIntOctal= 3007, /* an octal number */
     TT_Whitespace= 3030, /* Runs of whitespace. */
     TT_CppComment= 3031,
     TT_CComment= 3032,
     TT_EOL= 3033, /* intended for use in generically replacing TT_NL and TT_CR. */
     TT_CRNL= 3034, /* Windows-style newline. */
     TT_Blank= 3035, /* Runs of blanks, not including TT_NL/TT_CR. */
     TT_CommentHash= 3036, /* hash-style comment */
     TT_LiteralObjOpen= 3100, /* {{ */
     TT_LiteralObjClose= 3101, /* }} */
     TT_LiteralArrayOpen= TT_ChBraceOpen,
     TT_LiteralArrayClose= TT_ChBraceClose,
     TT_OpMinusEq= 4000, /* -= */
     TT_OpDecr= 4001, /* -- */
     TT_OpPlusEq= 4002, /* += */
     TT_OpIncr= 4003, /* ++ */
     TT_OpLogNot= TT_ChExcl, /* ! */
     TT_OpLogAnd= 4004, /* && */
     TT_OpLogOr= 4005, /* || */
     TT_OpShiftLeft= 4006, /* << */
     TT_OpShiftRight= 4007, /* >> */
     TT_OpMultEq= 4008, /* *= */
     TT_OpDivEq= 4009, /* /= */
     TT_OpShiftLeftEq= 4010, /* <<= */
     TT_OpShiftRightEq= 4011, /* >>= */
     TT_OpTildeEq= 4012, /* ~= */
     TT_OpAndEq= 4013, /* &= */
     TT_OpOrEq= 4014, /* |= */
     TT_OpModEq= 4015, /* %= */
     TT_OpHeredocStart= 4016, /* <<< */
     TT_OpEqHeredocStart= 4017, /* =<<< */
     TT_OpXorEq= 4018, /* =^ */
     TT_OpArrow= 4019, /* -> */
     TT_OpNamespace= 4020, /* :: */
     TT_OpMultiply= TT_ChAsterisk,
     TT_OpDivide= TT_ChFSlash,
     TT_OpModulo= TT_ChPercent,
     TT_OpAdd= TT_ChPlus,
     TT_OpSubtract= TT_ChMinus,
     TT_OpAssign= TT_ChEq, /* = */
     TT_CmpEq= 4100, /* == */
     TT_CmpGe= 4101, /* >= */
     TT_CmpGt= TT_ChGt, /* > */
     TT_CmpLe= 4102, /* <= */
     TT_CmpLt= TT_ChLt, /* < */
     TT_CmpNe= 4103, /* != */
     TT_CmpEqStrict= 4104, /* === */
     TT_CmpNeStrict= 4105, /* !== */
     TT_Identifier= 5000, /* function name, keyword, typename or var name */
     TT_IdFuncName= 5001, /* name of a script-callable function */
     TT_IdNamespaceName= 5002, /* name of a namespace or scope */
     TT_IdVarName= 5003, /* name of a variable */
     TT_IdOstream= 5004, /* name of a built-in ostream, e.g. cout or cerr */
     TT_IdIstream= 5005, /* name of a built-in istream, e.g. cin */
     TT_IdEllipsis= 5006, /* '...' TODO: re-implement this (was removed during C++-to-C port) */
     TT_Keyword= 5100,
     TT_KeywordCase= 5101,
     TT_KeywordChar= 5102,
     TT_KeywordDo= 5103,
     TT_KeywordDouble= 5104,
     TT_KeywordElse= 5105,
     TT_KeywordBreak= 5106, /* break */
     TT_KeywordExit= 5107, /* exit */
     TT_KeywordFor= 5108,
     TT_KeywordIf= 5109,
     TT_KeywordInt= 5110,
     TT_KeywordNamespace= 5111,
     TT_KeywordContinue= 5112, /* continue */
     TT_KeywordReturn= 5113, /* return */
     TT_KeywordString= 5114,
     TT_KeywordSwitch= 5115,
     TT_KeywordVariant= 5116,
     TT_KeywordWhile= 5117,
     TT_KeywordCatch= 5118,
     TT_KeywordThrow= 5119,
     TT_KeywordTry= 5120,
     TT_KeywordAssert= 5121,
     TT_KeywordCast= 5122,
     TT_KeywordConst= 5123,
     TT_KeywordUndef= 5124,
     TT_KeywordIsA= 5125,
     TT_KeywordForEach= 5126, /* foreach */
     TT_KeywordIn= 5127, /* in */
     TT_KeywordTypeof= 5128, /* typeof */
     TT_KeywordTypename= 5129, /* typename */
     TT_KeywordRefcount= 5130, /* Internal ref-count of values. */
     TT_KeywordUnref= 5131, /* "unref" keyword */
     TT_KeywordUnset= 5132, /* Unsets a var declared in the current scope. */
     TT_KeywordSweep= 5133,
     TT_KeywordClosure= 5134,
     TT_KeywordEval= 5135, /* eval */
     TT_KeywordScope= 5136, /* scope */
     TT_KeywordSet= 5137, /* set */
     TT_KeywordGet= 5138, /* get */
     TT_KeywordNull= 5139, /* null */
     TT_KeywordTrue= 5140, /* true */
     TT_KeywordFalse= 5141, /* false */
     TT_KeywordFunction= 5142, /* "function", "proc", etc. */
     TT_KeywordArray= 5143, /* "array" */
     TT_KeywordObject= 5144, /* "object" */
     TT_KeywordUse= 5145, /* use */
     TT_KeywordPrototype= 5146, /* prototype */
     TT_KeywordNameof= 5147, /* nameof */
     TT_Keyword__FILE= 5148, /* __FILE */
     TT_Keyword__LINE= 5149, /* __LINE */
     TT_Keyword__COLUMN= 5150, /* __COLUMN */
     TT_KeywordFatal= 5151, /* fatal */
     TT_Keyword__BREAKPOINT= 5152, /* __BREAKPOINT */
     TT_Keyword__SRC= 5153, /* __SRC */
     TT_Keyword__SCOPE= 5154, /* __SCOPE */
     TT_KeywordToss= 5155, /* toss */
     TT_KeywordInstanceOf= 5156, /* instanceof */
     TT_KeywordVacuum= 5157,
     TT_KeywordVarDecl= 5200, /* var */
     TT_KeywordVarDeclConst= 5201, /* const */
     TT_ExpandoEntry= 5300, /* @array expansion entries */
     TT_ElemOpen= 7000,
     TT_ElemClose= 7001,
     TT_ElemName= 7002,
     TT_ElemValue= 7003,
     TT_AttrName= 7004,
     TT_AttrValue= 7005,
     TT_HeredocBody= 7100, /* Contents of a heredoc */
     TT_Undef= 9000, /*  Used by new Toekins */
     TT_TokErr= 9001, /*  Used to tag toekinization errors */
     TT_VEND= 9002, /*  Used as a list terminator for ... funcs. */
    cwal_cish_token_types_AUTOMATION_KLUDGE
}; /*enum cwal_cish_token_types*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* WANDERINGHORSE_NET_CWAL_TOKER_TYPES_H_INCLUDED */
/* end of file cwal_cish_ttypes.h */
/* start of file cwal_t10n.c */
#include <assert.h>
#include <stdlib.h> /* strtol() and friends. */
#include <string.h> /* memcpy() */
#if 1
#include <stdio.h>
#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf
#else
#define MARKER if(0) printf
#endif
#if defined(__cplusplus)
extern "C" {
#endif

const cwal_simple_tokenizer cwal_simple_tokenizer_empty = cwal_simple_tokenizer_empty_m;
const cwal_simple_token cwal_simple_token_empty = cwal_simple_token_empty_m;
const cwal_st_src cwal_st_src_empty = cwal_st_src_empty_m;

char cwal_is_whitespace( int ch ){
    switch(ch){
      case ' ':
      case '\n':
      case '\r':
      case '\t':
      case '\v':
      case '\f':
          return 1;
      default:
          return 0;
    }
}
char cwal_is_blank( int ch ){
    switch(ch){
      case ' ':
      case '\t':
      case '\v':
      case '\f':
          return 1;
      default:
          return 0;
    }
}

char cwal_is_digit( int ch ){
    return '0'<=ch && '9'>=ch;
}

char cwal_is_xdigit( int ch ){
    return ('a'<=ch && 'f'>=ch)
        ? 1
        : (('A'<=ch && 'F'>=ch)
           ? 1
           :cwal_is_digit(ch));
}

char cwal_is_alpha( int ch ){
    return ('a'<=ch && 'z'>=ch)
        ? 1
        : ('A'<=ch && 'Z'>=ch);
}
char cwal_is_alnum( int ch ){
    return cwal_is_alpha(ch) ? 1 : cwal_is_digit(ch);
}

char cwal_is_identifier_char( int ch, char isStart ) {
    switch(ch){
      case '_':
      /* case '@': */
          return 1;
      default:
          return isStart
              ? cwal_is_alpha(ch)
              : cwal_is_alnum(ch);
    }
}


cwal_simple_token * cwal_st_skip_ws( cwal_simple_token * t ){
    for( ; t &&
             (TT_Blank==t->ttype || TT_Whitespace==t->ttype || cwal_is_whitespace(t->ttype));
         t = t->right ){
    }
    return t;
}

cwal_simple_token * cwal_st_skip_blank( cwal_simple_token * t ){
    for( ; t && (TT_Blank==t->ttype || cwal_is_blank(t->ttype)); t = t->right ){
    }
    return t;
}

char cwal_st_is_noise( cwal_simple_token const * t ){
    switch(t->ttype){
      case ' ':
      case '\t':
      case '\v':
      case '\f':
      /*??? case TT_Whitespace:*/
      case TT_Blank:
      case TT_CComment:
      case TT_CppComment:
          return 1;
      default:
          return 0;
    }
}

cwal_simple_token * cwal_st_skip_noise( cwal_simple_token * t ){
    for( ; t && !cwal_st_eof(t); ){
        if( cwal_st_is_noise(t) ) t = t->right;
        else break;
    }
    return t;
}


char cwal_st_eof( cwal_simple_token const * t ){
    return !t || (TT_EOF==t->ttype);
}


int cwal_cish_next_token( cwal_simple_tokenizer * t ){
    int rc = 0;
    char const * curpos = t->current.end
        ? t->current.end
        : t->begin;

    t->errMsg = 0;
#define BUMP(X) curpos+=(X);
#define RETURN_ERR(RC,MSG) t->ttype = TT_TokErr; t->errMsg = MSG; return (RC)
#define NEXT_LINE (void)0
    t->ttype = TT_UNDEF;
    t->current.begin = curpos;
    /* FIXME: convert this to switch(*curpos){}, and move
       only those which handle long ranges and such to
       if/else.
    */
    switch(*curpos){
      case 0:
          t->ttype = TT_EOF;
          /*This is a necessary exception to the bump-on-consume
            rule.*/
          break;
      case '\r':
          if('\n'==*(curpos+1)){
              t->ttype = TT_CRNL;
              BUMP(2);
              NEXT_LINE;
          }
          else{
              t->ttype = TT_CR;
              BUMP(1);
          }
          break;
      case '\n':
          t->ttype = TT_NL;
          BUMP(1);
          NEXT_LINE;
          break;
      case ' ':
      case '\t':
      case '\v':
      case '\f':
          t->ttype = *curpos /*TT_Blank*/;
          BUMP(1);
#if 1
          /* TODO once i figure out if we should handle
             EOL and EOL/blank runs consistently.
          */
          while( *curpos && cwal_is_blank(*curpos) ){
              t->ttype = TT_Blank;
              BUMP(1);
          }
#endif
          break;
      case ':': /* colon or namespace */
          if( ':' == *(curpos+1) ){
              t->ttype = TT_OpNamespace;
              BUMP(2);
          }else{
              t->ttype = TT_ChColon;
              BUMP(1);
          }
          break;
      case '~': /* ~ or ~= */
          t->ttype = *curpos;
          BUMP(1);
#if 0
          if('=' == *curpos){
              t->ttype = TT_OpInverseEq;
              BUMP(1);
          }
#endif
          break;
      case '/': /* numeric division or C-style comment block */
          t->ttype = *curpos;
          BUMP(1);
          if('=' == *curpos){
              t->ttype = TT_OpDivEq;
              BUMP(1);
          }
          else if( '*' == *curpos) /* C-style comment block */{
              t->ttype = TT_CComment;
              BUMP(1);
              do{
                  while( *curpos && ('*' != *curpos) ){
                      if('\n'==*curpos){
                          NEXT_LINE;
                      }
                      BUMP(1);
                  }
                  if( ! *curpos ) break;
                  BUMP(1); /* skip '*' */
              } while( *curpos && ('/' != *curpos));
              if( *curpos != '/' ){
                  RETURN_ERR(CWAL_RC_RANGE,"End of C++-style comment not found."); 
              }
              BUMP(1); /* get that last slash */
          }
          else if( '/' == *curpos ) /* C++-style comment line */{
              BUMP(1);
              t->ttype = TT_CppComment;
              while( *curpos && ('\n' != *curpos) ){
                  BUMP(1);
              }
          }
          break;
      case '"':
      case '\'': /* read string literal */{
          char const quote = *curpos;
          t->ttype = ('"' == *curpos)
              ? TT_LiteralStringDQ
              : TT_LiteralStringSQ;
          BUMP(1)/*leading quote*/;
          while(*curpos && (*curpos != quote)){
              if( (*curpos == '\\') ){
                  /* consider next char to be escaped, but keep escape char */
                  BUMP(1);
                  if(*curpos == 0){
                      RETURN_ERR(CWAL_RC_RANGE,
                                 "Unexpected EOF while toekinizing backslash-escaped char in string literal.");
                  }
                  BUMP(1);
                  continue;
              }
              else if('\n'==*curpos){
                  BUMP(1);
                  NEXT_LINE;
              }
              else {
                  BUMP(1);
              }
          }
          if(*curpos != quote){
              RETURN_ERR(CWAL_RC_RANGE,
                         "Unexpected end of string literal.");
          }
          BUMP(1)/*trailing quote*/;
          break;
      } /* end literal string */
      case '&': /* & or && */
          t->ttype = TT_ChAmp;
          BUMP(1);
          if( '&' == *curpos ){
              t->ttype = TT_OpLogAnd;
              BUMP(1);
          }
          else if( '=' == *curpos ){
              t->ttype = TT_OpAndEq;
              BUMP(1);
          }
          break;
      case '|': /* | or || */
          t->ttype = TT_ChPipe;
          BUMP(1);
          if( '|' == *curpos ){
              t->ttype = TT_OpLogOr;
              BUMP(1);
          }
          else if( '=' == *curpos ){
              t->ttype = TT_OpOrEq;
              BUMP(1);
          }
          break;
      case '+': /* + or ++ or += */{
          t->ttype = TT_ChPlus;
          BUMP(1);
          switch(*curpos){
            case '+': /* ++ */
                t->ttype = TT_OpIncr;
                BUMP(1);
                break;
            case '=': /* += */
                t->ttype = TT_OpPlusEq;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '*': /* multiply operator */
          t->ttype = TT_ChAsterisk;
          BUMP(1);
          if('=' == *curpos){
              t->ttype = TT_OpMultEq;
              BUMP(1);
          }
          break;
          
      case '-': /* - or -- or -= */{
          t->ttype = TT_ChMinus;
          BUMP(1);
          switch( *curpos ){
            case '-': /* -- */
                t->ttype = TT_OpDecr;
                BUMP(1);
                break;
            case '=':
                t->ttype = TT_OpMinusEq;
                BUMP(1);
                break;
            case '>':
                t->ttype = TT_OpArrow;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '<': /* LT or << or <= or <<= or <<< */{
          t->ttype = TT_CmpLt;
          BUMP(1);
          switch( *curpos ){
            case '<': /* tok= << */ {
                t->ttype = TT_OpShiftLeft;
                BUMP(1);
                switch(*curpos){
                  case '=': /* <<= */
                      t->ttype = TT_OpShiftLeftEq;
                      BUMP(1);
                      break;
#if 0
                  case '<': /* <<< */
                      t->ttype = TT_OpHeredocStart;
                      BUMP(1);
                      break;
#endif
                  default: break;
                }
                break;
            }
            case '=': /* tok= <= */
                t->ttype = TT_CmpLe;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '>': /* GT or >> or >= */{
          t->ttype = TT_CmpGt;
          BUMP(1);
          switch(*curpos){
            case '>': /* tok= >> */
                t->ttype = TT_OpShiftRight;
                BUMP(1);
                if( '=' == *curpos ){/* >>= */
                    t->ttype = TT_OpShiftRightEq;
                    BUMP(1);
                }
                break;
            case '=': /* tok= >= */
                t->ttype = TT_CmpGe;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '=': /* = or == or === */
          t->ttype = TT_ChEq;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_CmpEq;
              BUMP(1);
              if( '=' == *curpos ){
                  t->ttype = TT_CmpEqStrict;
                  BUMP(1);
              }
          }
          break;
      case '%': /* % or %= */
          t->ttype = *curpos;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_OpModEq;
              BUMP(1);
          }
          break;
      case '!': /* ! or != or !== */
          t->ttype = TT_ChExcl;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_CmpNe;
              BUMP(1);
              if( '=' == *curpos ){
                  t->ttype = TT_CmpNeStrict;
                  BUMP(1);
              }
          }
          break;
      case '{': /* { or {{ */
          t->ttype = TT_ChScopeOpen;
          BUMP(1);
#if 0
          if('{' == *curpos){
              t->ttype = TT_LiteralObjOpen;
              BUMP(1);
          }
#endif
          break;
      case '}': /* } or }} */
          t->ttype = TT_ChScopeClose;
          BUMP(1);
#if 0
          if('}' == *curpos){
              t->ttype = TT_LiteralObjClose;
              BUMP(1);
          }
#endif
          break;
      case '0': /* hex or octal */
          BUMP(1);
          if( (*curpos == 'x') || (*curpos == 'X') ){
              /** hex digit. Convert to decimal to save pain later. */
              /* TODO: reimplement to use strtol(). */
              BUMP(1);
              while( *curpos && cwal_is_xdigit(*curpos) ){
                  BUMP(1);
              }
              t->ttype = TT_LiteralIntHex;
              if( *curpos && (
                              cwal_is_alnum(*curpos)
                              || (*curpos == '_'))
                  ){
                  BUMP(1); /* make sure it shows up in the error string. */
                  RETURN_ERR(CWAL_RC_RANGE,
                             "Malformed hexidecimal int constant.");
              }
          }
          else if((*curpos>='0') && (*curpos<='7')){
              /* try octal... */
              BUMP(1);
              while( *curpos && (*curpos>='0') && (*curpos<='7') ){
                  BUMP(1);
              }
              t->ttype = TT_LiteralIntOctal;
              if( *curpos && (
                              cwal_is_alnum(*curpos)
                              || (*curpos == '_'))
                  ){
                  BUMP(1); /* make sure it shows up in the error string. */
                  RETURN_ERR(CWAL_RC_RANGE,
                             "Malformed octal int constant.");
              }
          }
          else if( *curpos && (
                               cwal_is_alnum(*curpos)
                               || (*curpos == '_'))
                   )
          { /* reject 12334x where x is alphanum or _ */
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_RC_RANGE,
                         "Malformed numeric constant starts "
                         "with '0' but is neither octal nor hex..");
          }
          else if('.'==*curpos){
              BUMP(1);
              while( cwal_is_digit(*curpos) ){
                  BUMP(1);
              }
              if('.'==*(curpos-1)){
                  RETURN_ERR(CWAL_SCR_UNEXPECTED_TOKEN,
                             "Mis-terminated floating point value.");
              }
              t->ttype = TT_LiteralDouble;
          }
          else {
              t->ttype = TT_LiteralIntDec;
              /* A literal 0. This is okay. */
          }
          break;
      case '1': case '2': case '3':
      case '4': case '5': case '6':
      case '7': case '8': case '9': /* integer or double literal. */
          while( *curpos && cwal_is_digit(*curpos) ){
              /* integer or first part of a double. */
              BUMP(1);
          }
          if( ('.' == *curpos) && (cwal_is_digit(*(curpos+1))) ){
              /* double number */
              t->ttype = TT_LiteralDouble;
              BUMP(1);
              while( *curpos && cwal_is_digit(*curpos) ){
                  BUMP(1);
              }
          }
          else {
              t->ttype = TT_LiteralIntDec;
          }
          if( *curpos &&
              (cwal_is_alnum(*curpos)
               || (*curpos == '_'))) {
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_RC_RANGE,
                         "Malformed numeric constant.");
          }
          break;
    }/* end of switch(*curpos) */

    /**
       TODO: UTF8 multibyte. The necessary code is in cwal_json.c.
    */
    
    if(0==rc
       && (curpos == t->current.begin)
       && (TT_EOF != t->ttype) ){
        /* keep trying... */
        if( cwal_is_identifier_char(*curpos,1) ) /* identifier string */{
            BUMP(1);
            while( cwal_is_identifier_char(*curpos,0) ){
                BUMP(1);
            }
            t->ttype = TT_Identifier;
        }
        else if( (*curpos >= 32) && (*curpos < 127) ) {
            t->ttype = *curpos;
            BUMP(1);
        }
        else {
            assert(curpos == t->current.begin);
            assert(TT_UNDEF==t->ttype);
        }
    }

    t->current.end = curpos;
    if(TT_UNDEF == t->ttype){
        t->ttype = TT_TokErr;
        t->errMsg = "Don't know how to tokenize this.";
        rc = CWAL_RC_ERROR;
    }
    
#undef BUMP
#undef RETURN_ERR
#undef NEXT_LINE

    return rc;
}

cwal_simple_token * cwal_st_alloc( cwal_engine * e, cwal_simple_token * left ){
    cwal_simple_token * t = (cwal_simple_token*)cwal_malloc(e,sizeof(cwal_simple_token));
    if(t) *t = cwal_simple_token_empty;
    if(left){
        assert(0==left->right);
        cwal_st_insert_after( left, t );
    }
    return t;
}

void cwal_st_free( cwal_engine * e, cwal_simple_token * t ){
    assert(0 == t->right);
    /*??? if(t->value) cwal_value_unref(t->value);*/
    *t = cwal_simple_token_empty;
    cwal_free( e, t );
}

void cwal_st_snip( cwal_simple_token * t ){
    t->right = 0;
}

void cwal_st_insert_after( cwal_simple_token * after, cwal_simple_token * t ){
    cwal_st_snip( t );
    if( after->right ){
        t->right = after->right;
    }
    after->right = t;
}

cwal_size_t cwal_st_count( cwal_simple_token const * t ){
    cwal_size_t i = 0;
    for( ; t; t = t->right ){
        ++i;
    }
    return i;
}
    
void cwal_st_free_chain( cwal_engine * e, cwal_simple_token * head ){
    if(!e || !head) return;
    else {
        cwal_simple_token * t, * next;
        for( t = head; t; t = next ){
            next = t->right;
            t->right = 0;
            cwal_st_free( e, t );
        }
    }
}


char cwal_st_predicate_ttype( cwal_simple_token const * t, void * state ){
    return t->ttype == *(int*)state;
}

void cwal_st_snip_matching( cwal_simple_token ** head,
                                      cwal_simple_token ** snippedHead,
                                      cwal_st_predicate_f pred,
                                      void * predState ){
    cwal_simple_token * tail = 0;
    cwal_simple_token * next;
    cwal_simple_token * t = *head;
    cwal_simple_token * snipped = 0;
    for( ; t && TT_EOF!=t->ttype; t = next ){
        next = t->right;
        if( pred( t, predState ) ){
            cwal_st_snip(t);
            if(!snipped) snipped = t;
            if(tail) cwal_st_insert_after( tail, t );
            tail = t;
            if(*head == t) *head = next;
        }
    }
    *snippedHead = snipped;
}
void cwal_st_snip_ttype( cwal_simple_token ** head,
                         cwal_simple_token ** snippedHead,
                         int ttype ){
    cwal_st_snip_matching( head, snippedHead,
                           cwal_st_predicate_ttype,
                           &ttype );
}


void cwal_st_snip_junk( cwal_engine * e, cwal_simple_token ** head ){
        cwal_simple_token * cut = 0;
#define SNIP(T) \
        cwal_st_snip_ttype( head, &cut, T);     \
        if(cut) {cwal_st_free_chain(e,cut); cut = 0; }        \
        if(!*head) return

        SNIP(TT_CComment);
        SNIP(TT_CppComment);
        /*SNIP(TT_Blank);*/

#undef SNIP
}


char cwal_st_isa( cwal_simple_token const * t, int type ){
    return t
        ? ((t->ttype == type) ? 1 : 0)
        : ((type==TT_EOF) ? 1 : 0);
}

cwal_size_t cwal_st_tag_functions( cwal_simple_token * head ){
    cwal_size_t count = 0;
    cwal_simple_token * n;
    for( ; head; head = n ){
        n = cwal_st_skip_noise(head->right);
        if( !cwal_st_isa(head,TT_Identifier) ) continue;
        if( cwal_st_isa(n,TT_ChParenOpen) ){
            head->ttype = TT_IdFuncName;
            head = head->right /* skip this one */;
        }
    }
    return count;
}


char cwal_st_is_literal( cwal_simple_token const * tok ){
    switch( tok->ttype ){
      case TT_LiteralIntDec:
      case TT_LiteralIntOctal:
      case TT_LiteralIntHex:
      case TT_LiteralStringSQ:
      case TT_LiteralStringDQ:
      case TT_LiteralDouble:
          return 1;
      default:
          return 0;
    };
}

cwal_simple_token * cwal_st_find_matching_brace( cwal_simple_token * t ) {
    if( ! t ) return 0;
    else{
        int matcher = 0;
        int bcount = 0;
        int const brace = t->ttype;
        switch(brace)
        {
          case TT_ChParenOpen:
              matcher = TT_ChParenClose;
              break;
          case TT_ChBraceOpen:
              matcher = TT_ChBraceClose;
              break;
          case TT_ChScopeOpen:
              matcher = TT_ChScopeClose;
              break;
          case TT_ChQuestion:
              matcher = TT_ChColon;
              break;
          case TT_ChLt:
              matcher = TT_ChGt;
              break;
          default:
              break;
        };
        if( ! matcher ) return NULL;
        while( t ){
            if( brace == t->ttype ) ++bcount;
            else if( matcher == t->ttype ) --bcount;
            if( 0 == bcount ) {
                /* found a match */
                return t;
            }
            t = t->right;
        }
        return NULL;
    }
}

#if 0
int cwal_st_process_heredoc( cwal_engine * e,
                             cwal_simple_token * t,
                             cwal_simple_token ** next,
                             char createValues,
                             cwal_buffer * escapeBuf ){
    if((t->ttype != TT_OpHeredocStart)
       && (t->ttype != TT_OpEqHeredocStart)) return CWAL_RC_TYPE;
    else {
    }
}
#endif

static int cwal_hexbyte( int ch ){
    if(ch>='0' && ch<='9') return ch - '0';
    else if(ch>='a' && ch<='f') return ch - 'a' + 10;
    else if(ch>='A' && ch<='F') return ch - 'A' + 10;
    else return -1;
}

static unsigned char cwal_read_hex_bytes(unsigned char const *zPos,
                                        unsigned int * rv ){
    unsigned int rc = 0;
    int ch1, ch2;
    unsigned char n = 0;
    assert(zPos && rv);
    while(n<8){
        ch1 = *zPos ? cwal_hexbyte(*zPos++) : -1;
        ch2 = (ch1>=0) ? cwal_hexbyte(*zPos++) : -1;
        if(ch2<0) break;
        rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2);
        n += 2;
    }
    *rv = rc;
    return n;
}
    
int cwal_st_unescape_string( cwal_engine * e,
                             unsigned char const * begin,
                             unsigned char const * end,
                             cwal_buffer * dest ){
    cwal_size_t sz;
    unsigned char const * p = begin;
    unsigned char * out;
    int check;
    cwal_size_t oldUsed;
    unsigned char uCount;
    unsigned int uChar;
    if(!e || !begin || !end || !dest) return CWAL_RC_MISUSE;
    else if(end<begin) return CWAL_RC_RANGE;
    oldUsed = dest->used;
    sz = end - begin + 1 /*NUL*/;
    check = cwal_buffer_reserve( e, dest, sz + oldUsed );
    if(check) return check;
    out = dest->mem + oldUsed;
    for( ; p < end; ++p ){
        switch(*p){
          case '\\':
              switch(*++p){
                case 't': *(out++) = '\t'; break;
                case 'n': *(out++) = '\n'; break;
                case 'u':
                    uCount = cwal_read_hex_bytes(p+1, &uChar);
                    if(!uCount) return CWAL_RC_RANGE;
                    assert(0 == (uCount % 2));
                    check = cwal_utf8_char_to_cstr(uChar, out, uCount);
                    if(check<0) return CWAL_RC_RANGE
                        /* Invalid UTF */
                        ;
                    out += check;
                    p += uCount;
                    break;
                default:
                    *(out++) = *p;
                    break;
              }
              break;
          default:
              *(out++) = *p;
              break;
        }
    }
    assert( (out - dest->mem + oldUsed) >= 0 );
    *out = 0;
    dest->used = out - dest->mem;
    return 0;
}
    
    
int cwal_st_from_tokenizer( cwal_engine * e,
                            cwal_simple_tokenizer * t,
                            cwal_simple_token ** tgt,
                            char createValues,
                            cwal_buffer * escapeBuf ){
    cwal_simple_token * rc = 0;
    cwal_simple_token * left = *tgt;
    rc = cwal_st_alloc(e, left);
    if(!rc) return CWAL_RC_OOM;
    else {
        rc->ttype = t->ttype;
        rc->src = t->current;
        *tgt = rc;
        return 0;
    }
}


int cwal_simple_tokenizer_init( cwal_simple_tokenizer * t, char const * src, cwal_size_t len ){
    if(!t||!src) return CWAL_RC_MISUSE;
    else{
        if(!len) len = strlen(src);
        t->begin = src;
        t->end = src+len;
        t->errMsg = t->current.begin = t->current.end = 0;
        t->ttype = TT_UNDEF;
        return 0;
    }
}

cwal_simple_token * cwal_st_skip( cwal_simple_token * t ){
    char cont = 1;
    for( ; cont && t; ){
        switch(t->ttype){
          case ' ':
          case '\t':
          case '\v':
          case '\f':
          case TT_Blank:
          case TT_Whitespace:
          case TT_CR:
          case TT_NL:
          case TT_CRNL:
          case TT_EOL:
          case TT_CComment:
          case TT_CppComment:
              t = t->right;
              continue;
          default:
              cont = 0;
              break;
        }
    }
    return t;
}

#if 0
static int cwal_st_count_lines( char const * src, cwal_size_t srcLen,
                                char const * pos,
                                int *line,
                                int *col ){
    int l = 1, c = 0;
    if((pos<src) || (pos>(src+srcLen))) return CWAL_RC_RANGE;
    for( ; (pos < (src+srcLen)) && *pos; ++pos ){
        if('\n'==*pos){
            ++l;
            c = 0;
        }
        else {
            ++c;
        }
    }
    if(line) *line = l;
    if(col) *col = c;
    return 0;
}
#endif

int cwal_st_toss_val( cwal_engine * e, cwal_simple_token * t,
                      int code, cwal_value * v){
    int rc = 0;
    cwal_exception * x;
    char iAllocedEx = 0;
    x = v ? cwal_value_get_exception(v) : 0;
    if(!x){
        x = cwal_new_exception( e, code, v );
        if(!x) return CWAL_RC_OOM;
        iAllocedEx = 1;
        v = cwal_exception_value(x);
    }
#if 0
    if( !cwal_prop_get( v, "line", 4 ) ){
        rc = cwal_prop_set( v, "line", 4,
                            cwal_new_integer(e, t->src.line) );
        if(!rc) rc = cwal_prop_set( v, "column", 6,
                                    cwal_new_integer(e, t->src.column) );
    }
#elif 0
    rc = cwal_prop_set( v, "column", 6,
                        cwal_new_integer(e, (cwal_int_t)t->src.column) );
    if(!rc) rc = cwal_prop_set( v, "line", 4,
                                cwal_new_integer(e, (cwal_int_t)t->src.line) );
    if(!rc) rc = cwal_prop_set( v, "code", 4,
                                cwal_new_integer(e, (cwal_int_t)code ) );

#endif
    if(!rc) rc = cwal_exception_set( e, v );
    if(rc && (CWAL_RC_EXCEPTION!=rc) && iAllocedEx){
        cwal_value_unref( cwal_exception_value(x) );
    }
    return rc ? rc : CWAL_RC_EXCEPTION;
}

    
int cwal_st_tossv( cwal_engine * e, cwal_simple_token * t,
                  int code, char const * fmt, va_list args ){
    int rc;
#if 1
    rc = cwal_exception_setfv( e, code, fmt, args );
#else
    cwal_value * xV;
    cwal_exception * x;
    rc = cwal_exception_setfv( e, code, fmt, args );
    if(t && (CWAL_RC_EXCEPTION==rc)){
        xV = cwal_exception_get(e);
        assert(xV);
        x = cwal_value_get_exception(xV);
        assert(x);
        rc = cwal_prop_set( xV, "column", 6,
                            cwal_new_integer(e, t->src.column) );
        if(!rc) rc = cwal_prop_set( xV, "line", 4,
                                    cwal_new_integer(e, t->src.line) );
        if(!rc) rc = cwal_prop_set( xV, "code", 4,
                                    cwal_new_integer(e, (cwal_int_t)code) );
        if(!rc) rc = CWAL_RC_EXCEPTION;
    }
#endif
    return rc;
}

int cwal_st_toss( cwal_engine * e, cwal_simple_token * t,
                  int code, char const * fmt, ... ){
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = cwal_st_tossv( e, t, code, fmt, args );
    va_end(args);
    return rc;
}
    
#if defined(__cplusplus)
} /*extern "C"*/
#endif
    
#undef MARKER
/* end of file cwal_t10n.c */
/* start of file th1ish_hwtime.h */
/*
** 2008 May 27
**
** The author disclaims copyright to this source code.  In place of
** a legal notice, here is a blessing:
**
**    May you do good and not evil.
**    May you find forgiveness for yourself and forgive others.
**    May you share freely, never taking more than you give.
**
******************************************************************************
**
** This file contains inline asm code for retrieving "high-performance"
** counters for x86 class CPUs.
*/
#ifndef _MY_HWTIME_H_INCLUDED_
#define _MY_HWTIME_H_INCLUDED_

/*
** The following routine only works on pentium-class (or newer) processors.
** It uses the RDTSC opcode to read the cycle count value out of the
** processor and returns that value.  This can be used for high-res
** profiling.
*/
#if (defined(__GNUC__) || defined(_MSC_VER)) && \
      (defined(i386) || defined(__i386__) || defined(_M_IX86))

  #if defined(__GNUC__)

  __inline__ uint64_t sqlite3Hwtime(void){
     unsigned int lo, hi;
     __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi));
     return (uint64_t)hi << 32 | lo;
  }

  #elif defined(_MSC_VER)

  __declspec(naked) __inline __uint64 __cdecl sqlite3Hwtime(void){
     __asm {
        rdtsc
        ret       ; return value at EDX:EAX
     }
  }
  #else
    /** dummy/no-op impl. */
    uint64_t sqlite3Hwtime(void){ return ((uint64_t)0); }


  #endif

#elif (defined(__GNUC__) && defined(__x86_64__))

  __inline__ uint64_t sqlite3Hwtime(void){
      unsigned long val;
      __asm__ __volatile__ ("rdtsc" : "=A" (val));
      return val;
  }
 
#elif (defined(__GNUC__) && defined(__ppc__))

  __inline__ uint64_t sqlite3Hwtime(void){
      unsigned long long retval;
      unsigned long junk;
      __asm__ __volatile__ ("\n\
          1:      mftbu   %1\n\
                  mftb    %L0\n\
                  mftbu   %0\n\
                  cmpw    %0,%1\n\
                  bne     1b"
                  : "=r" (retval), "=r" (junk));
      return retval;
  }

#else

#if 0
#error Need implementation of sqlite3Hwtime() for your platform.
#endif

  /*
  ** To compile without implementing sqlite3Hwtime() for your platform,
  ** you can remove the above #error and use the following
  ** stub function.  You will lose timing support for many
  ** of the debugging and testing utilities, but it should at
  ** least compile and run.
  */
  uint64_t sqlite3Hwtime(void){ return ((uint64_t)0); }

#endif

#endif /* !defined(_MY_HWTIME_H_INCLUDED_) */
/* end of file th1ish_hwtime.h */
/* start of file th1ish.c */
/**
   Main implementation of the th1ish scripting engine.
*/
#if defined(NDEBUG)
/* force assert() to always work */
#  undef NDEBUG
#endif

#if defined(_WIN32)
#define TH1ISH_ENABLE_USLEEP 0
#define TH1ISH_UNIX 0
#else
#define TH1ISH_ENABLE_USLEEP 1
#define TH1ISH_UNIX 1
#endif

#if TH1ISH_ENABLE_USLEEP
#  if ! defined(_XOPEN_SOURCE)
   /** on Linux, required for usleep(). */
#    define _XOPEN_SOURCE 500
#    include <errno.h>
#  endif
#  ifndef _XOPEN_SOURCE_EXTENDED
#    define _XOPEN_SOURCE_EXTENDED
#  endif
#  ifndef _BSD_SOURCE
#    define _BSD_SOURCE
#  endif
#endif

#if 0 && TH1ISH_ENABLE_USLEEP
/* i simply cannot get usleep() to appear on my system */
extern int usleep(useconds_t usec);
#endif


#if !defined(_WIN32)
#include <unistd.h> /* sleep(), usleep() */
#endif

#include <assert.h>
#include <time.h> /* sleep() */
#include <string.h>
#include <stdlib.h> /* strtol() and friends, getenv(). */
#include <time.h> /* time() */
#include <ctype.h> /* isspace() */


#if 0
#endif

#if 1
#include <stdio.h>
#define MARKER(pfexp)                                                \
    do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); \
        printf pfexp; \
    } while(0)
#else
#define MARKER(exp) if(0) printf
#endif

typedef int (*th1ish_eval_f)(th1ish_interp *, cwal_simple_token *,
                             cwal_simple_token **, cwal_value ** );

enum th1ish_interp_flags {
/**
     Used by th1ish_eval_dot_deref() to communicate that the next '.' 
     should not be consumed by the higher-level parsers because
     th1ish_eval_dot_deref() is busy expanding an object.property
     chain.
*/
TH1ISH_F_EVAL_NO_DOT = 0x0001,

/**
   A flag which means that encountering another +=, -=, *=, /=, or %=
   is an error (they may not be chained).
*/
TH1ISH_F_EVAL_NO_ASSIGN = 0x0002,

/**
   If this th1ish_interp flag is set then conventionalFuncCallSyntax()
   is enabled.
*/
TH1ISH_F_CONVENTIONAL_CALL_OP = 0x0004,

/**
   If this th1ish_interp flag is set then for/while loops get a new
   scope for each iteration. Costs memory and performance (about 15%
   in my simple tests) but enables var declarations inside such loops.
   
*/
TH1ISH_F_LOOPS_EXTRA_SCOPES = 0x0008,

/**
   If this th1ish_interp flag is set then assertion checks which pass
   are output via cwal_output().
*/
TH1ISH_F_TRACE_ASSERTIONS = 0x0100,

/**
   Stack tracing is only done if this th1ish_interp flag is set.

   @see th1ish_enable_stacktrace()
*/
TH1ISH_F_TRACE_STACK = 0x0200,



/**

   If this interpreter flag is set, the 'proc' handling will cache
   token changes for parameter lists and bodies. Strangely, though,
   this costs us more total overall mallocs/memory than not caching in
   my tests.  Presumably this is because the caching empties out the
   recycler, causing more mallocs? Maybe caching only becomes useful
   for often-called functions?

   Wider tests show that this normally saves a number of allocs and
   maybe a few bytes of memory, but normally costs a tick more memory.
   In some tests it notably cuts the number of allocs (about 1/3rd)
   but increases both the peak and total memory usage.

   It does run a tiny tick faster with caching, though.
*/
TH1ISH_F_CACHE_PROCS = 0x0400,

TH1ISH_F_LOG_AUTO_SWEEP = 0x0800,

/**
   The default values for th1ish_interp::flags.
*/
TH1ISH_F_INTERP_DEFAULTS =
    TH1ISH_F_TRACE_STACK
    | TH1ISH_F_LOG_AUTO_SWEEP
    /* | TH1ISH_F_CACHE_PROCS */
    /* | TH1ISH_F_CONVENTIONAL_CALL_OP */
};

const th1ish_stack_entry th1ish_stack_entry_empty = th1ish_stack_entry_empty_m;
const th1ish_interp th1ish_interp_empty = th1ish_interp_empty_m;
const th1ish_tmplish_opt th1ish_tmplish_opt_empty = th1ish_tmplish_opt_empty_m;
/**
   In some experimental cases, strings longer than this value will be
   created via cwal_new_xstring() instead of cwal_new_string().  This
   actually ends up costing us more if string interning is enabled
   because xstrings don't currently participate in string interning.
*/
static cwal_size_t const th1ish_xstr_threshold =
    sizeof(char const *) + 32
    ;


/* Internal helper. */
#define IE_SKIP_MODE (ie->skipLevel)

/**
   Internal property key for storing "imported symbols" in a Function
   instance.

   ACHTUNG: duplicated in th1ish_protos.c!
*/
#define FUNC_SYM_PROP "$imported$"
#define FUNC_SYM_PROP_LEN (sizeof(FUNC_SYM_PROP)-1)
#define ARGS_IE th1ish_interp * ie = th1ish_args_interp(args)

/**
   Holds state for Function instances, pointing back to their original
   source code and declared argument names.
*/
struct th1ish_proc_state {
    th1ish_interp * ie;
    /** Origin token for the parameter list. */
    cwal_simple_token tParams;
    /** Origin token for the function body. */
    cwal_simple_token tBody;
    /**
       We cache the argument names list here.
     */
    cwal_array * argNames;
    /*
       cParams and cBody are only used when proc token caching is on,
       but tests show that these pointers don't cost us all that much
       when it's off, so caching is a runtime option as opposed to a
       macro-controlled option.
    */
    /**
       Compiled form of tParams.
    */
    cwal_simple_token * cParams;
    /**
       Compiled form of tBody.
    */
    cwal_simple_token * cBody;
    /**
       Workaround for cleanup of cParams and cBody during recursive
       calls when caching is disabled.
    */
    unsigned short recurseCount;
};
typedef struct th1ish_proc_state th1ish_proc_state;
static const th1ish_proc_state th1ish_proc_state_empty = {
NULL, cwal_simple_token_empty_m, cwal_simple_token_empty_m,
NULL, NULL, NULL, 0U
};

/**
   cwal_callback_hook_pre_f() impl which installs the following
   vars in argv->scope:

   this
   argv
   argv.callee

   and symbols imported via Function.importSymbols()

   It does not set up named parameters - those are set up before
   the Function is call()ed, and this callback is triggered
   from cwal_function_call_in_scope().

   The state parameter must be a (th1ish_interp*), though we currently
   ignore it.

   Reminders to self:

   - We could _potentially_ (not certain) set a flag at a function call
   which tells the scope not to resolve variables in higher scopes. That
   would break resolution of global vars too, though, and clients don't
   generically know where "global" vars are really stored (which scope).

*/
static int th1ish_callback_hook_pre(cwal_callback_args const * argv,
                                    void * state){
#if 1 /* See comments below. Management summary: this optimization is
         potentially dangerous vis-a-vis vacuuming and may need to be
         removed.

         This block also means native funcs do not have direct access
         to imported symbols. Having need of them "should" be a corner
         case for a native function (as suggested by us not needing
         them so far).
      */
    th1ish_proc_state * ps = argv->callee
        ? (th1ish_proc_state *)cwal_args_callee_state(argv, &th1ish_proc_state_empty)
        : NULL;
    if(!ps){
        /* MARKER(("Not a script function. Not injecting argv/this.\n")); */

        /*
          FIXME: since the addition of vacuuming, we have a potential problem
          here. Because we do not add "argv" for this case, we can in fact
          have values from script space with no reference, and pass those
          on to native code. If that code then calls back into script
          space (e.g. using th1ish_eval_string()), it could trigger a vacuum
          which nukes such temporaries.

          There are several options:

          - We add a client-specific flag (somewhere) in argv->engine
          which which we can check in the autosweep/vacuum bits and
          not vacuum in that case. We "could" use
          th1ish_interp::vacuumGuard, but that would only works on
          functions bound with an interpreter instance as its state
          (which many do not do because they need their own state).

          - Oh, that's not true at all: the 'state' param of this
          function is our interpreter, so we have access to
          interp->vacuumGuard. That leaves a corner case, where a
          script calls into a native function early on, which then
          runs most of the remaining script code from native
          space. That would leave us unable to vacuum (only to sweep).

          - Maybe we add argv and friends only if the vacuum guard is
          not set? Then again, we don't currently (20140508) set it
          anywhere.

          - We push another scope here, set a flag in e which tells us
          we've done so, check that flag in the post-call hook, and
          pop it if needed. That protects the func call scope against
          a vacuum by adding a scope. Relatively ugly, and it would
          still break if i can prove that a scope-recursive variant of
          vacuum can work (that would be Holy Grail Algo Version 2.0,
          and would basically eliminate all leak possibilities).

          - We go ahead and treat native funcs and script functions
          the same, always injecting "argv", "this", and named params
          into the local scope. We need to do that anyway, in case the
          client calls back into the interp. Hmm. That's the easiest
          solution, anyway. But it's an inefficient waste for the vast
          majority of cases, which doesn't sit well with me.

          Man, disabling this block costs us another 500b bytes RAM in
          the smaller unit test scripts :(. Which is weird because the
          cwal-level metrics report no difference in allocations. The
          number of mallocs doesn't go up all that much if recycling's
          on, though.
        */
        return 0;
    }
    else
#endif
    if(cwal_scope_search(argv->scope, 0, "argv", 4, NULL)){
        /* argv is already set. This is (we hope) a callback which itself
           passed on a call via cwal_function_call_in_scope(). Do we have
           a better mechanism for avoiding a duplicate init in that case?
           What if the initial callback passed some value other than its
           args->self to cwal_...call()? The second callback would have
           a different native 'this' than the script code. Hmm.
        */
        return 0;
    }else{
        int rc;
        cwal_array * ar;
        cwal_value * av;
        cwal_value * calleeV;
        cwal_engine * e = argv->engine;
        cwal_scope * s = argv->scope;
        uint16_t i;
        /* MARKER(("PRE-FUNC HOOK!\n")); */
        calleeV = cwal_function_value(argv->callee);
        assert(calleeV);
        rc = cwal_scope_chain_set( s, 0, "this", 4, argv->self)
            /* Should we use var_decl instead and make it const?  So
               far no need, and i have a vague memory of that approach
               causing grief when that topic came up before.
            */;
        if(rc) return rc;
        ar = cwal_new_array(e);
        if(!ar) return CWAL_RC_OOM;
        av = cwal_array_value(ar);
        rc = cwal_scope_chain_set( s, 0, "argv", 4, av);
        if(rc){
            cwal_value_unref(av);
            return rc;
        }
        rc = cwal_prop_set(av, "callee", 6, calleeV);
        if(rc){
            cwal_value_unref(calleeV);
            return rc /* ar is now owned by s */;
        }else{
            rc = cwal_array_reserve(ar, argv->argc);
            if(rc) return rc /* ar is now owned by s */;
        }
        for(i = 0; !rc && (i < argv->argc); ++i ){
            rc = cwal_array_set(ar, i, argv->argv[i]);
            assert(!rc && "'cannot fail' because of reservation");
        }
        if(!rc){
            /* Import "closured" properties (Function.importSymbols())
               into the current scope.

               Reminder to self: i really don't like storing these in
               a script-visible space, but now that vacuuming has been
               added, they need to be so until/unless i can find a
               mechanism to protect such values from a vacuum.

               Reminder to self:

               we could add another cwal_ptr_table which records that
               Values in it are "unvacuumable," and we check that
               table when vacuuming (we set a flag which tells the
               scope it's vacuuming, and does the check in
               cwal_scope_clean()). Something like that. Maybe even
               add cwal_scope::mine::headSafe, which is a place which
               "safe" (unsweep/vacuumable) values should go. Safe vars
               would need to be marked as such by the client, and any
               safe vars would be treated like named vars for purpose
               of a vacuum. If we limit safifying to Objects
               (cwal_obase) we could set a flag there (we have a few
               bits left over), rather than using a ptr_table. Note
               that values referenced by a Safe value need not be
               Safe, but would inherently be moved around like named
               vars via vacuum (because a Safe var refs them, they
               will get "saved"). Maybe we call them Lifeguard Values.

               That currently sounds quite reasonable. A client could
               "safify" some top-level container where he stores his
               internal (not-script-visible) values, and anything in
               that container would survive a vacuum by virtue of
               cwal_scope::mine::headSafe.
            */
            cwal_value * imported = cwal_prop_get( calleeV, FUNC_SYM_PROP,
                                                   FUNC_SYM_PROP_LEN );
            if(imported){
                cwal_value * scProp =
                    cwal_scope_properties(s);
                assert(scProp && "Properties have already been set "
                       "at this point, so we cannot get NULL here.");
                rc = cwal_props_copy( imported, scProp );
            }
        }
        return rc;
    }
}

static int th1ish_callback_hook_post(cwal_callback_args const * argv,
                                     void * state,
                                     int fRc, cwal_value * rv){
    return 0;
}
    
static const cwal_callback_hook cwal_callback_hook_th1ish = {
    NULL /*state*/,
    th1ish_callback_hook_pre,
    th1ish_callback_hook_post
};


/**
   Pushes ent entry into the stack trace stack (ie->stack) and sets
   ent->scriptPos to srcPos. We delay evaluation of ent's other members
   until an exception is thrown to avoid unduly slowing down execution
   while we count line/column positions.

   ent is expected to be a stack-allocated instance and its memory
   MUST survive until the matching call to th1ish_strace_pop() or
   all sorts of bad stuff will happen.

   The only error conditions are if !ie or !ent, but we're hoping
   you're not going to do that.
*/
static void th1ish_strace_push_pos( th1ish_interp * ie,
                                    char const * srcPos,
                                    th1ish_stack_entry * ent );

/**
   Moves ie->stack.tail to ie->stack.reuse and decrements
   ie->stack.count. Asserts that there is a stack entry.

   Returns non-0 on error, and the only error condition is that
   ie->stack.count is 0. It might assert in that case, anyway, so
   ignore the result code.
*/
static int th1ish_strace_pop( th1ish_interp * ie );

char th1ish_stacktrace_current( th1ish_interp * ie, th1ish_stack_entry * dest ){
    if(!ie || !ie->stack.tail) return 0;
    else{
        if(dest) *dest = *ie->stack.tail;
        return 1;
    }
}

/* typedef int (*th1ish_op_f)( th1ish_interp *, cwal_value * lhs, cwal_value * rhs ); */

/**
   Moves ie->returnVal up a scope, if possible.
*/
static void th1ish_result_up( th1ish_interp * ie ){
    if(ie && ie->returnVal){
        cwal_scope * p =
            cwal_scope_parent( cwal_scope_current_get(ie->e) );
        if(p){
            cwal_value_rescope( p, ie->returnVal );
        }
    }
}

/**
   Evaluates to true if RC==CWAL_RC_EXIT or RC==CWAL_RC_FATAL, else
   false.
*/
#define RC_IS_EXIT(RC) ((CWAL_RC_EXIT==(RC))||(CWAL_RC_FATAL==(RC)))

/**
   If RC_IS_EXIT(RC) then this macro advances *(NEXT) to the TT_EOF
   token of (TOK)'s chain, else it has no side effects.
*/
#define th1ish_eval_check_exit(IE,RC,TOK,NEXT) if(RC_IS_EXIT(RC)){      \
        for( ; (NEXT) && (TOK) && (TT_EOF!=(TOK)->ttype); (TOK) = (TOK)->right ){} \
        *(NEXT) = (TOK);\
    } (void)0

enum th1ish_rc {
TH1ISH_RC_EOC = TT_VEND + 1,
TH1ISH_RC_TOSS
/* TH1ISH_RC_EOEXP */
};
typedef enum th1ish_rc th1ish_rc;


#if 0
/* no longer needed */
static int th1ish_possibly_bubble(th1ish_interp * ie, int rc,
                                  cwal_scope * to, cwal_value * v ){
    if(!v) return 0;
    else switch(rc){
      case TH1ISH_RC_TOSS:
      case CWAL_RC_RETURN:
      case CWAL_RC_BREAK:
          /* cwal_value_rescope( to, v ); */
          /* fall through */
      default:
          return rc;
    }
}
#endif

/**
   Sets the current "return value" (triggered by the CWAL_RC_RETURN
   evaluation result). The return value needs some special handling to
   ensure that it survives longer than its originating scope.
*/
static int th1ish_result_set( th1ish_interp * ie, cwal_value * v ){
    if(v == ie->returnVal) return 0;
    else {
        ie->returnVal = v;
        if(v){
#if 0
            cwal_value_ref(v) /* shouldn't be needed!  And yet it is! 
                                 It's evil and orphans values in an
                                 unsweepable manner.*/;

#endif
            th1ish_result_up( ie );
        }
        return 0;
    }
}

#if 0
/*
  To try out someday:

  http://eli.thegreenplace.net/2012/08/02/parsing-expressions-by-precedence-climbing/
*/

typedef struct th1ish_op th1ish_op;
struct th1ish_op{
    /* token type */
    int ttype;
    /* Precedence */
    int prec;
    /* -1==left, +1===right */
    char assoc;
};
static th1ish_op th1ish_ops[] = {
/* More or less adapted from:

http://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence

*/
{TT_ChParenOpen,-10,0}, /*{TT_ChParenClose,-10,0},*/
{TT_OpIncr,-10,-1} /* postfix */,
{TT_OpDecr,-10,-1} /* postfix */,   
/* Missing: . [] */

{TT_ChExcl,0,1}, /* Logical NOT */
{TT_ChTilde,0,1}, /* bitwise NOT */
{TT_ChPlus,0,1}, /* Unary + */
{TT_ChMinus,0,1}, /* Unary - */
{TT_OpIncr,0,1} /* ++prefix */,
{TT_OpDecr,0,1} /* --prefix */,   
    
{TT_OpMultiply,5,-1},
{TT_OpDivide,5,-1},
{TT_OpModulo,5,-1},

{TT_ChPlus,10,-1}, /* Binary + */
{TT_ChMinus,10,-1}, /* Binary - */

/* Bitshift operators */
{TT_OpShiftLeft,15,-1},
{TT_OpShiftRight,15,-1},
    
/* Comparison ops */
{TT_CmpLt,20,-1},
{TT_CmpLe,20,-1},
{TT_CmpGt,20,-1},
{TT_CmpGe,20,-1},

{TT_CmpEq,30,-1},
{TT_CmpNe,30,-1},
{TT_CmpEqStrict,30,-1},
{TT_CmpNeStrict,30,-1},

/* Bitwise ops */
{TT_ChAmp,40,-1},
{TT_ChCaret,40,-1},
{TT_ChPipe,40,-1},

/* Logical ops */
{TT_OpLogAnd,50,-1},
{TT_OpLogOr,50,-1},

/* Ternary IF */
{TT_ChQuestion,60,1},

/* Assignments */
{TT_OpAssign,70,1},
{TT_OpPlusEq,70,1},
{TT_OpMinusEq,70,1},
{TT_OpMultEq,70,1},
{TT_OpDivEq,70,1},
{TT_OpModEq,70,1},
{TT_OpOrEq,70,1},
{TT_OpAndEq,70,1},
{TT_OpShiftLeftEq,70,1},
{TT_OpShiftRightEq,70,1},


{TT_ChComma,100,-1},
    
#if 0
/* Token id collisions with Unary +/- */
{TT_OpAdd,10,0}, /* Binary + */
{TT_OpSubtract,10,-1}, /* Binary - */
#endif

{0,0,0}
};

typedef struct th1ish_expr_state th1ish_expr_state;
struct th1ish_expr_state {
    int opcode;
    int skipLevel;
    cwal_value * rv;
};
const struct th1ish_expr_state th1ish_expr_state_empty = {0,0,0};

typedef int (*th1ish_expr_f)( th1ish_interp * ie, th1ish_expr_state * st );
typedef struct th1ish_expr th1ish_expr;
struct th1ish_expr {
    th1ish_op const * op;
    th1ish_expr * parent;
    th1ish_expr * left;
    th1ish_expr * right;
};


th1ish_op const * th1ish_op_by_type( int ttype ){
    int i = 0;
    for(; i < sizeof(th1ish_ops)/sizeof(th1ish_ops[0]); ++i ){
        th1ish_op const * b = &th1ish_ops[i];
        if(b->ttype==ttype) return b;
    }
    return NULL;
}

/* End th1ish_op bits. */
#endif

/**
   Internal help macro which evaluates to the next
   non-space/blank/junk token to the right of (cwal_simple_token*) T,
   or 0 if !T.
*/
#define TNEXT(T) ((T) ? cwal_st_skip((T)->right) : 0)

/**
   Returns the first non-blank/space/comment token starting in the
   chain t, moving to the right.  Returns 0 if the input contains only
   "blank" tokens. Returns t if t is-not-a skipable token type.
*/
static cwal_simple_token * th1ish_skip( th1ish_interp *ie,
                                        cwal_simple_token * t ){
    char cont = 1;
    while( t && cont ){
        switch(t->ttype){
          case '\t':
          case ' ':
          case TT_Blank:
          /* case TT_CommentHash: */
          case TT_CComment:
          case TT_CppComment:
              t = t->right;
              continue;
          case TT_EOL:
#if 0
/* 20130706: had to disable this because it causes so very broken
   behaviour in perfectly valid if() conditions. Have not yet
   determined which expression evaluator(s) is/are the culprits.

   _Might_ be related to $func..., which does its own EOL handling and
   would be indirectly affected by this.
*/
              if(ie->skipNLLevel > 0){
                  t = t->right;
                  continue;
              }
#endif
              /*else fall through*/
          default:
              cont = 0;
              break;
        }
    }
    return t;
}

void th1ish_dump_val2( cwal_value * v, char const * msg, char const * func, int line ){
    cwal_scope const * sc = cwal_value_scope(v);
    MARKER(("%s:%d: %s%stype=%s@%p[scope=#%d@%p] refcount=%d: ", func, line,
            msg ? msg : "", msg?": ":"",
            cwal_value_type_name(v),
            (void const *)v,
            (int)(sc ? sc->level : 0),
            (void const *)sc,
            (int)cwal_value_refcount(v)));
    switch( cwal_value_type_id(v) ){
      case CWAL_TYPE_FUNCTION:
      case CWAL_TYPE_BUFFER:
      case CWAL_TYPE_NATIVE:
          fprintf(stdout, "%s@%p\n",
                  cwal_value_type_name(v), (void const*)v);
          break;
      default:
          cwal_json_output_FILE( v, stdout, 0 );
          break;
    }
}
/*
  static cwal_json_output_opt JsonOutOpt = cwal_json_output_opt_empty_m;
*/

#if 0
static void dump_json( cwal_value * v ){
    assert(v);
    cwal_json_output_FILE( v, stdout, 0 );
}
#endif


void dump_token2( cwal_simple_token const * t, char const * msg, int fileLine ){
    if(!t) return;
    printf("%s:%d: %s%sToken type [%d] len=%u string=<<<%.*s>>>",
           __FILE__, fileLine,
           msg?msg:"", msg?": ":"",
           t->ttype,
           (unsigned)(t->src.end - t->src.begin),
           (int)(t->src.end - t->src.begin),
           t->src.begin
           );
#if 0
    if(t->value){
        printf(" JSON value: ");
        dump_json(t->value);
    }
    else
#endif
        putchar('\n');
}
#define dump_token(TOK,MSG) dump_token2((TOK), (MSG), __LINE__)


static struct {
    int stAllocs;
    int stFrees;
    int stRequests;
}
    Th1ishMetrics = {
    0/*stAllocs*/,
    0/*stFrees*/
    };

/**
   Tries to allocate a cwal_simple_token from the given recycler.
   li->list must contain (if anything) a chain of
   (cwal_simple_token*). Returns NULL if there is nothing to recycle,
   otherwise it removes the first entry from li->list and updates
   the list accordingly.
*/
static cwal_simple_token * th1ish_st_from_recycler( cwal_recycler * li ){
    if(!li || !li->count) return NULL;
    else {
        cwal_simple_token * t = (cwal_simple_token *) li->list;
        assert(t);
        li->list = t->right;
        t->right = 0;
        --li->count;
        /* MARKER(("Pulled token@%p from recycler.\n", (void const *)t)); */
        return t;
    }
}

/**
   If ie has recycling enabled and there is space left
   in the recycle bin then st is moved to ie's recycler,
   else st and all tokens to st->right are freed.
*/
static int th1ish_st_to_recycler( th1ish_interp * ie,
                                  cwal_simple_token * st ){
    if(!ie || !ie->e) return CWAL_RC_MISUSE;
    else {
        cwal_recycler * li = &ie->recycler.tokens;
        if(li->count >= li->maxLength) {
            ++Th1ishMetrics.stFrees;
            cwal_free( ie->e, st );
            return CWAL_RC_RANGE;
        }
        else {
            cwal_simple_token * h = li->count
                ? (cwal_simple_token *) li->list
                : 0;
            /* dump_token(st,"Adding to recycler"); */
            cwal_st_snip(st);
            if(h){
                st->right = h;
            }
            st->ttype = 0;
            st->src = cwal_st_src_empty;
            li->list = st;
            ++li->count;
            return 0;
        }
    }
}

/**
   Allocates a new cwal_simple_token instance, from the recycling
   bin if possible. Returns NULL if !ie or on OOM.
*/
static cwal_simple_token * th1ish_st_alloc( th1ish_interp * ie ){
    cwal_simple_token * rc = NULL;
    if(ie){
        rc = th1ish_st_from_recycler(&ie->recycler.tokens);
        if(!rc){
            rc = (cwal_simple_token *)cwal_malloc( ie->e, sizeof(cwal_simple_token) );
            ++Th1ishMetrics.stAllocs;
        }
        if(rc) *rc = cwal_simple_token_empty;
        ++Th1ishMetrics.stRequests;
    }
#if 0
    if(rc){
        MARKER(("token allocation counters: total "
               "requests: %d, actually allocated=%d, freed=%d, "
               "in recycle bin=%"CWAL_SIZE_T_PFMT"\n",
               Th1ishMetrics.stRequests,
               Th1ishMetrics.stAllocs,
               Th1ishMetrics.stFrees,
               ie->recycler.tokens.count
                ));
    }
#endif
    return rc;
}

/**
   Frees all memory held by ie->recycler.
*/
static void th1ish_recycler_clean( th1ish_interp * ie ){
    if(ie && ie->e){
        cwal_recycler * re = &ie->recycler.tokens;
        cwal_simple_token * tli = (cwal_simple_token *)re->list;
        cwal_simple_token * next;
        for( ; tli; tli = next ){
            /* dump_token( tli, "Cleaning up recycler"); */
            next = tli->right;
            tli->right = 0;
            cwal_free( ie->e, tli );
        }
        re->list = 0;
        re->count = 0;
    }
}

void th1ish_st_free_chain( th1ish_interp * ie, cwal_simple_token * st,
                           char allowRecycle ){
    assert(ie && ie->e);
    if(!st) return;
    else{
        cwal_simple_token * n;
        for( ; st; st = n ) {
            n = st->right;
            st->right = 0;
            if(allowRecycle){
                th1ish_st_to_recycler( ie, st );
            }else{
                cwal_st_free( ie->e, st );
            }
        }
    }
}

/* Implemented in th1ish_mod.c */
void th1ish_modules_close( th1ish_interp * ie );

static void th1ish_script_finalize2(th1ish_script *scr);

th1ish_interp * th1ish_interp_alloc( cwal_engine * e ){
    th1ish_interp * rc = (th1ish_interp*)cwal_malloc(e, sizeof(th1ish_interp));
    if(rc){
        *rc = th1ish_interp_empty;
        rc->allocStamp = e;
        rc->e = e /* need this for dealloc */;
    }
    return rc;
}

void th1ish_interp_finalize( th1ish_interp * ie ){
    if(!ie) return;
    else if(!ie->e){
        assert(!ie->recycler.tokens.count);
        assert(!ie->recycler.tokens.list);
        assert(!ie->buffer.mem);
    }
    else{
        cwal_engine * e = ie->e;
        void const * allocStamp = ie->allocStamp;
        cwal_buffer_reserve(e, &ie->buffer, 0);

        if(ie->stash){
            cwal_value_unref(ie->stash);
            ie->stash = 0;
        }

        while(ie->ob.count){
            th1ish_ob_pop(ie);
        }
        cwal_list_reserve(e, &ie->ob, 0);

        ie->recycler.tokens.maxLength = 0
            /* so that engine-time cleanup does not leak entries to
               the recycle list. */
            ;
        th1ish_recycler_clean( ie )
            /* cannot call after the engine is cleaned up */
            ;
        assert(0 == ie->recycler.tokens.count);
        /*
          Reminder: we have a chicken/egg scenario here:

          Destroying the engine will free up any resources left in
          the global scope, so we have to leave ie's memory intact
          until the engine is destroyed. Case in point:
          th1ish_proc_state_finalize() may free a cached token chain
          during cleanup and it needs both ie and ie->e.

          Now we have another potential ordering problem...

          th1ish_scripts need to be cleaned up by e, but it is
          theoretically possible that a finalizing value during e's
          cleanup might trigger script code living in one of
          e->scripts. We have to clean up the scripts before cleaning
          up e because we simply have no other choice (we need e
          in order to clean them up).

          Two non-mutually-exclusive workarounds:

          a) It is, as of now, illegal to trigger script code from
          value finalizers.

          b) Pop all scopes from e before cleaning it to ensure
          destruction of all values. We set the exception state to 0
          to keep it from bubbling up the scope stack. Why didn't i
          think of this before?

          We still cannot destroy e before ie because ie might have
          come from e's allocator.
        */
        cwal_exception_set( e, NULL );
        while( !cwal_scope_pop(e) ){
            /* see notes above. */
        }
        while( ie->scripts.head ) {
#if 1
            th1ish_script * sc = ie->scripts.head;
            ie->scripts.head = sc->chain;
            sc->chain = 0;
            th1ish_script_finalize2( sc );
#else
            th1ish_script_finalize( ie->scripts.head );
#endif
        }
        ie->stack.head = NULL;
        ie->stack.count = 0;
        th1ish_modules_close(ie);
        assert(0 == ie->recycler.tokens.count);
        assert(!ie->buffer.mem);
        assert(ie->e == e);
        *ie = th1ish_interp_empty;
        if(e == allocStamp){
            cwal_free( e, ie );
            ie = 0;
        }else{
            ie->allocStamp = allocStamp;
        }
        cwal_engine_destroy(e);
    }
}

cwal_engine * th1ish_interp_engine( th1ish_interp * ie ){
    return ie ? ie->e : NULL;
}


int th1ish_stash_set( th1ish_interp * ie, char const * key, cwal_value * v ){
    int rc;
    if(!ie || !key || !v || !ie->topScope) return CWAL_RC_MISUSE;
    else if(!ie->stash){
        ie->stash = cwal_new_hash_value(ie->e, 27);
        if(!ie->stash) return CWAL_RC_OOM;
        assert(ie->topScope);
        cwal_value_rescope(ie->topScope, ie->stash);
        cwal_value_ref(ie->stash)
            /* Make sure it doesn't get swept up! */
            ;
        rc = cwal_value_make_vacuum_proof(ie->stash, 1)
            /* And not vacuumed, either. */
            ;
        assert(0==rc);

#if 1
        cwal_scope_chain_set( ie->topScope, 0, "$th1ish_stash$", 14, ie->stash )
            /* Only as an experiment with cwal_engine_vacuum(),
               to keep it from nuking the stash. */
            ;
#endif
    }
    rc = cwal_hash_insert( cwal_value_get_hash(ie->stash),
                           key, 0, v, 1 );
    return rc;
}

cwal_value * th1ish_stash_get( th1ish_interp * ie, char const * key ){
    if(!ie || !key || !*key) return NULL;
    else return ie->stash
        ? cwal_hash_search( cwal_value_get_hash(ie->stash),
                            key, strlen(key) )
        : 0;
}

/**
   cwal_value_type_name_proxy_f() implementation which checks v for a
   property named __typename.
*/
static char const * th1ish_type_name_proxy( cwal_value const * v, cwal_size_t * len ){
    cwal_value const * tn = cwal_prop_get(v, "__typename", 10);
    return tn
        ? cwal_value_get_cstr(tn, len)
        : NULL;
}


int th1ish_interp_init( th1ish_interp * ie, cwal_engine * e ){
    int rc;
    if(!ie || !e) return CWAL_RC_MISUSE;
    assert(!ie->e || (ie->e == e));
    {
        void const * allocStamp = ie->allocStamp;
        int const oldFlags = ie->flags;
        *ie = th1ish_interp_empty;
        ie->allocStamp = allocStamp;
        ie->flags = oldFlags ? oldFlags : TH1ISH_F_INTERP_DEFAULTS;
    }
    ie->e = e;
    rc = cwal_buffer_reserve(e, &ie->buffer, 512);
    if(rc) goto end;
    ie->topScope = cwal_scope_current_get(e);
    if(!ie->topScope){
        rc = CWAL_RC_RANGE;
        goto end;
    }
    cwal_engine_type_name_proxy( e, th1ish_type_name_proxy );
    ie->recycler.tokens.maxLength =
#if 1
        (1024*(CWAL_SIZE_T_BITS/2)) / sizeof(cwal_simple_token)
#elif 0
        100
#else
        0
#endif            
        ;
#if 0
    /* Remind me again why i wanted this? */
    {
        cwal_value * gp = th1ish_global_props(ie);
        if(!gp) rc = CWAL_RC_OOM;
        else {
            assert(!cwal_value_prototype_get(gp));
            rc = cwal_value_prototype_set(gp,
                                          th1ish_prototype_object(ie));
            assert(0==rc);
        }
    }
#endif

    /* The following we do so that the default prototypes get set
       up.
    */
    {
        cwal_value * v;
#define DO(T) v = th1ish_prototype_##T(ie); \
        if(!v) { rc = CWAL_RC_OOM; goto end; } (void)0
        DO(object);
        DO(function);
        DO(integer);
        DO(double);
        DO(array);
        DO(hash);
        DO(buffer);
        DO(exception);
        DO(string);
#undef DO
    }
    assert(!rc);
    {
        cwal_callback_hook hook = cwal_callback_hook_th1ish;
        hook.state = ie;
        rc = cwal_callback_hook_set(e, &hook);
        assert(!rc);
    }
    end:
    return rc;
}

int th1ish_interp_init2( th1ish_interp ** ie, cwal_engine * e ){
    if(!ie || !e) return CWAL_RC_MISUSE;
    else {
        int rc;
        th1ish_interp * i = *ie;
        if(!i){
            i = th1ish_interp_alloc(e);
            if(!i) return CWAL_RC_OOM;
            assert(e == i->allocStamp);
            *ie = i;
        }
        rc = th1ish_interp_init( i, e );
        return rc;
    }
}

/**
   If pos is in the range [src,end] then this function calculates the
   line (1-based) and column (0-based) of pos within [src,end] and
   sets line/col to those values. If pos is out of range CWAL_RC_RANGE
   is returned and this function has no side-effects. Returns 0 on
   success.
*/
static int th1ish_count_lines( char const * src, char const * end_,
                               char const * pos_,
                               unsigned int *line, unsigned int *col ){
    unsigned int ln = 1, c = 0;
    unsigned char const * x = (unsigned char const *)src;
    unsigned char const * pos = (unsigned char const *)pos_;
    unsigned char const * end = (unsigned char const *)end_;
    unsigned char const * tail = end;
    int ch;
    if((pos<x) || (pos>end)) {
        /* This happens when processing sub-eval'd strings :/ */
        /* MARKER(("BAD count_lines RANGE!\n")); */
        return CWAL_RC_RANGE;
    }
    else for( ; (x < pos) && *x; ++x ){
        if('\n'==*x){
            ++ln;
            c = 0;
        }
        else {
            ch = cwal_utf8_read_char( x, end, &tail );
            if(ch>0){
                assert(tail>x);
                ++c;
                x = tail - 1/* account for loop incr */;
            }
        }
    }
    if(line) *line = ln;
    if(col) *col = c;
    /* MARKER(("Updated line/col: %u, %u\n", ln, c )); */
    return 0;
}

static th1ish_script * th1ish_script_for_pos( th1ish_interp * ie, char const * pos_ ){
    th1ish_script * sc = ie->scripts.current;
    unsigned char const * pos = (unsigned char const *)pos_;
    if(sc && (pos >= sc->code)
       && (pos < (sc->code+sc->codeLength))) {
        return sc;
    }
    sc = ie->scripts.head;
    for( ; sc; sc = sc->chain ){
        if((pos >= sc->code)
           && (pos < (sc->code+sc->codeLength))) {
            return sc;
        }
    }
    return NULL;
}

th1ish_script * th1ish_script_by_name( th1ish_interp const * ie,
                                       char const * name ){
    th1ish_script * sc;
    cwal_size_t nLen;
    if(!ie || !ie->scripts.head || !name) return NULL;
    nLen = strlen(name);
#if 0
    sc = ie->scripts.current;
    if(sc && (sc->nameLen==nLen)
       && (0==cwal_compare_cstr(name, nLen,
                                sc->name, sc->nameLen))){
        return ie->scripts.current;
    }
#endif
    sc = ie->scripts.head;
    for( ; sc; sc = sc->chain ){
        if( (sc->nameLength==nLen)
            && (0==cwal_compare_cstr(name, nLen,
                                     (char const *)sc->name,
                                     sc->nameLength))){
            return sc;
        }
    }
    return NULL;
}


/**
   Internal helper to store ie->src in tgt (bitwise copy),
   re-set ie->src to point to [newBegin, newEnd), and ie->srcName
   to newScriptName. Keep the tgt and oldName values to pass to
   th1ish_src_restore().
 */
static void th1ish_src_save( th1ish_interp * ie, cwal_st_src * tgt,
                             char const ** oldName,
                             char const * newScriptName,
                             char const * newBegin, char const * newEnd
                             ){
    *tgt = ie->src;
    ie->src.begin = newBegin;
    ie->src.end = newEnd;
    if( oldName ) *oldName = ie->srcName;
    if(newScriptName){
        ie->srcName = newScriptName;
    }
}

/**
   Expected to be passed the src and srcName arguments stored by
   th1ish_src_save().
*/
static void th1ish_src_restore( th1ish_interp * ie, cwal_st_src const * src,
                                char const * srcName ){
    ie->src = *src;
    if(srcName){
        ie->srcName = srcName;
    }
}

static int th1ish_line_col( th1ish_interp * ie, char const * pos,
                            unsigned int * line, unsigned int * col ){
    int rc = CWAL_RC_RANGE;
    char const * begin = 0;
    char const * end = 0;
#if 1
    /* Would like to get rid of th1ish_interp::src but
       th1ish_eval_filename(), or one of the routines it uses, still
       relies on it.
    */
    if( (pos >= ie->src.begin) && (pos < ie->src.end)) {
        begin = ie->src.begin;
        end = ie->src.end;
    }
    else
#endif
    {
        th1ish_script * sc = th1ish_script_for_pos(ie, pos);
        if( !sc ) return CWAL_RC_RANGE;
        begin = (char const *)sc->code;
        end = (char const *)sc->code + sc->codeLength;
    }
    rc = th1ish_count_lines( begin, end, pos, line, col );
    return rc;
}

static int th1ish_token_line_col( th1ish_interp * ie, cwal_simple_token const * t,
                                  unsigned int * line, unsigned int * col ){
    return th1ish_line_col( ie, t->src.begin, line, col );
}


/**
   Returns true if ch is a legal char for "identifier" strings
   (e.g. var/function names). Pass 1 as isStart for the first call and
   0 for subsequent calls for the same string. If isStart is true it
   only allows alpha and underscore characters, otherwise is also
   allows numbers.

   To support unicode identifers, this function accepts any character
   with a value >0x7f (127d) as an identifier character. Any character
   value <=0 is not an identifier character.
*/
static char th1ish_is_id_char( int ch, char isStart ) {
    if(ch<=0) return 0;
    else if(ch>0x7f) return 1 /* TODO: filter to specific ranges? Or
                                 at least remove any unicode
                                 whitespace/blanks (are there such
                                 things?). */;
    else switch(ch){
      case '_':
      /* case '@': */
          return 1;
      default:
          return isStart
              ? cwal_is_alpha(ch)
              : cwal_is_alnum(ch);
    }
}

void th1ish_read_identifier( char const * zPos,
                             char const * zMaxPos,
                             char const ** zIdEnd ){
    unsigned char const * start = (unsigned char const *) zPos;
    unsigned char const * pos = start;
    unsigned char const * end = (unsigned char const *) zMaxPos;
    unsigned char const * endChar = pos;
    int ch;
    assert(zMaxPos>zPos);
    for( ; pos < end; ){
        ch = cwal_utf8_read_char( pos, end, &endChar );
        if((endChar == pos) || !th1ish_is_id_char(ch, (pos==start) ? 1 : 0)) break;
        pos = endChar;
    }
    *zIdEnd = zPos + (pos - start);
}

/**
   Tries to parse the next token from t, which must have been
   initalized with cwal_simple_tokenizer_init(). On success returns 0
   and updates t's state to reflect the location and type of the
   curren token. On error returns non-0 and t->errMsg "might"
   contain a description of the problem.
*/
static int th1ish_next_token( cwal_simple_tokenizer * t ){
    int rc = 0;
    char const * curpos = t->current.end
        ? t->current.end
        : t->begin;

    t->errMsg = 0;
#if 0
    /* i want something _like_ this, but it needs to behave properly
       with multi-byte tokens. e.g. hitting EOF after a '+', while
       checking for '++'.
    */
#define CHECKEND if(curpos>=t->end) { t->type = TT_EOF; return 0; }
#else
#define CHECKEND (void)0
#endif
#define BUMP(X) curpos+=(X); CHECKEND
#define RETURN_ERR(RC,MSG) t->ttype = TT_TokErr; t->errMsg = MSG; return (RC)
#define NEXT_LINE (void)0

    if((curpos == t->begin) && (('#'==*curpos) && ('!'==*(curpos+1)))){
        /* Workaround: strip shebang line from start of scripts */
        while(*(++curpos) && ('\n' != *curpos)) {}
        ++curpos;
    }
    if( curpos >= t->end ) {
        t->ttype = TT_EOF;
        t->current.begin = t->current.end = t->end;
        return 0;
    }
    

    t->ttype = TT_UNDEF;
    t->current.begin = curpos;
    
    switch(*curpos){
      case 0:
          t->ttype = TT_EOF;
          /*This is a necessary exception to the bump-on-consume
            rule.*/
          break;
      case '\\': /* Treat \<NEWLINE> as a continuation of a line */
          if(('\n'==*(curpos+1)) || (('\r'==*(curpos+1)) && ('\n'==*(curpos+2)))){
              t->ttype = TT_Whitespace;
              BUMP(('\r'==*(curpos+1)) ? 3 : 2);
              NEXT_LINE;
          }
          break;
      case '\r':
          if('\n'==*(curpos+1)){
              t->ttype = TT_CRNL;
              BUMP(2);
              NEXT_LINE;
          }
          else{
              t->ttype = TT_CR;
              BUMP(1);
          }
          break;
      case '\n':
          t->ttype = TT_NL;
          BUMP(1);
          NEXT_LINE;
          break;
      case ' ':
      case '\t':
      case '\v':
      case '\f':
          t->ttype = *curpos /*TT_Blank*/;
          BUMP(1);
#if 1
          /* TODO once i figure out if we should handle
             EOL and EOL/blank runs consistently.
          */
          while( *curpos && cwal_is_blank(*curpos) ){
              t->ttype = TT_Blank;
              BUMP(1);
          }
#endif
          break;
      case ':': /* colon or namespace */
          if( ':' == *(curpos+1) ){
              t->ttype = TT_OpNamespace;
              BUMP(2);
          }else{
              t->ttype = TT_ChColon;
              BUMP(1);
          }
          break;
      case '~': /* ~ or ~= */
          t->ttype = *curpos;
          BUMP(1);
#if 1
          if('=' == *curpos){
              t->ttype = TT_OpTildeEq;
              BUMP(1);
          }
#endif
          break;
#if 0
          /**
             #-style comments disabled because they break this use case:

             var str {item #1}

             b/c the brace bits use the tokenizer to find the closing
             brace. (PS: they don't anymore because that route is littered
             with corner cases like this one!)
           */
      case '#': /* comment until EOL */
          t->ttype = TT_CommentHash;
          while( *curpos && ('\n' != *curpos) ){
              BUMP(1);
          }
          break;
#endif
      case '/': /* numeric division or C-style comment block */
          t->ttype = *curpos;
          BUMP(1);
          if('=' == *curpos){
              t->ttype = TT_OpDivEq;
              BUMP(1);
          }
          else if( '*' == *curpos) /* C-style comment block */{
              t->ttype = TT_CComment;
              BUMP(1);
              do{
                  while( *curpos && ('*' != *curpos) ){
                      if('\n'==*curpos){
                          NEXT_LINE;
                      }
                      BUMP(1);
                  }
                  if( ! *curpos ) break;
                  BUMP(1); /* skip '*' */
              } while( *curpos && ('/' != *curpos));
              if( *curpos != '/' ){
                  RETURN_ERR(CWAL_RC_RANGE,"End of C-style comment not found."); 
              }
              BUMP(1); /* get that last slash */
          }
          else if( '/' == *curpos ) /* C++-style comment line */{
              BUMP(1);
              t->ttype = TT_CppComment;
              while( *curpos && ('\n' != *curpos) ){
                  BUMP(1);
              }
          }
          break;
      case '"':
      case '\'': /* read string literal */{
          char const quote = *curpos;
          t->ttype = ('"' == *curpos)
              ? TT_LiteralStringDQ
              : TT_LiteralStringSQ;
          BUMP(1)/*leading quote*/;
          while(*curpos && (*curpos != quote)){
              if( (*curpos == '\\') ){
                  /* consider next char to be escaped, but keep escape char */
                  BUMP(1);
                  if(*curpos == 0){
                      RETURN_ERR(CWAL_RC_RANGE,
                                 "Unexpected EOF while tokenizing "
                                 "backslash-escaped char in string literal.");
                  }
                  BUMP(1);
                  continue;
              }
              else if('\n'==*curpos){
                  BUMP(1);
                  NEXT_LINE;
              }
              else {
                  BUMP(1);
              }
          }
          if(*curpos != quote){
              RETURN_ERR(CWAL_RC_RANGE,
                         "Unexpected end of string literal.");
          }
          BUMP(1)/*trailing quote*/;
          break;
      } /* end literal string */
      case '&': /* & or && */
          t->ttype = TT_ChAmp;
          BUMP(1);
          if( '&' == *curpos ){
              t->ttype = TT_OpLogAnd;
              BUMP(1);
          }
          else if( '=' == *curpos ){
              t->ttype = TT_OpAndEq;
              BUMP(1);
          }
          break;
      case '|': /* | or || */
          t->ttype = TT_ChPipe;
          BUMP(1);
          if( '|' == *curpos ){
              t->ttype = TT_OpLogOr;
              BUMP(1);
          }
          else if( '=' == *curpos ){
              t->ttype = TT_OpOrEq;
              BUMP(1);
          }
          break;
      case '^': /* ^ or ^= */
          t->ttype = TT_ChCaret;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_OpXorEq;
              BUMP(1);
          }
          break;
      case '+': /* + or ++ or += */{
          t->ttype = TT_ChPlus;
          BUMP(1);
          switch(*curpos){
            case '+': /* ++ */
                t->ttype = TT_OpIncr;
                BUMP(1);
                break;
            case '=': /* += */
                t->ttype = TT_OpPlusEq;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '*': /* multiply operator */
          t->ttype = TT_ChAsterisk;
          BUMP(1);
          if('=' == *curpos){
              t->ttype = TT_OpMultEq;
              BUMP(1);
          }
          else if('/' == *curpos){
              t->ttype = TT_UNDEF;
              RETURN_ERR(CWAL_RC_RANGE,
                         "Comment closer (*/) not inside a comment.");
          }
          break;
          
      case '-': /* - or -- or -= */{
          t->ttype = TT_ChMinus;
          BUMP(1);
          switch( *curpos ){
            case '-': /* -- */
                t->ttype = TT_OpDecr;
                BUMP(1);
                break;
            case '=':
                t->ttype = TT_OpMinusEq;
                BUMP(1);
                break;
            case '>':
                t->ttype = TT_OpArrow;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '<': /* LT or << or <= or <<= or <<< */{
          t->ttype = TT_CmpLt;
          BUMP(1);
          switch( *curpos ){
            case '<': /* tok= << */ {
                t->ttype = TT_OpShiftLeft;
                BUMP(1);
                switch(*curpos){
                  case '=': /* <<= */
                      t->ttype = TT_OpShiftLeftEq;
                      BUMP(1);
                      break;
#if 1
                  case '<': /* <<< */
                      t->ttype = TT_OpHeredocStart;
                      BUMP(1);
                      break;
#endif
                  default: break;
                }
                break;
            }
            case '=': /* tok= <= */
                t->ttype = TT_CmpLe;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '>': /* GT or >> or >= */{
          t->ttype = TT_CmpGt;
          BUMP(1);
          switch(*curpos){
            case '>': /* tok= >> */
                t->ttype = TT_OpShiftRight;
                BUMP(1);
                if( '=' == *curpos ){/* >>= */
                    t->ttype = TT_OpShiftRightEq;
                    BUMP(1);
                }
                break;
            case '=': /* tok= >= */
                t->ttype = TT_CmpGe;
                BUMP(1);
                break;
            default: break;
          }
          break;
      }
      case '=': /* = or == or === */
          t->ttype = TT_ChEq;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_CmpEq;
              BUMP(1);
              if( '=' == *curpos ){
                  t->ttype = TT_CmpEqStrict;
                  BUMP(1);
              }
          }
          break;
      case '%': /* % or %= */
          t->ttype = *curpos;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_OpModEq;
              BUMP(1);
          }
          break;
      case '!': /* ! or != or !== */
          t->ttype = TT_ChExcl;
          BUMP(1);
          if( '=' == *curpos ){
              t->ttype = TT_CmpNe;
              BUMP(1);
              if( '=' == *curpos ){
                  t->ttype = TT_CmpNeStrict;
                  BUMP(1);
              }
          }
          break;
      case '{': /* { or {{ */
          t->ttype = TT_ChScopeOpen;
          BUMP(1);
#if 0
          if('{' == *curpos){
              t->ttype = TT_LiteralObjOpen;
              BUMP(1);
          }
#endif
          break;
      case '}': /* } or }} */
          t->ttype = TT_ChScopeClose;
          BUMP(1);
#if 0
          if('}' == *curpos){
              t->ttype = TT_LiteralObjClose;
              BUMP(1);
          }
#endif
          break;
      case '0': /* hex or octal */
          BUMP(1);
          if( (*curpos == 'x') || (*curpos == 'X') ){
              /** hex digit. Convert to decimal to save pain later. */
              /* TODO: reimplement to use strtol(). */
              BUMP(1);
              while( *curpos && cwal_is_xdigit(*curpos) ){
                  BUMP(1);
              }
              t->ttype = TT_LiteralIntHex;
              if( *curpos && (
                              cwal_is_alnum(*curpos)
                              || (*curpos == '_'))
                  ){
                  BUMP(1); /* make sure it shows up in the error string. */
                  RETURN_ERR(CWAL_RC_RANGE,
                             "Malformed hexidecimal int constant.");
              }
          }
          else if((*curpos>='0') && (*curpos<='7')){
              /* try octal... */
              BUMP(1);
              while( *curpos && (*curpos>='0') && (*curpos<='7') ){
                  BUMP(1);
              }
              t->ttype = TT_LiteralIntOctal;
              if( *curpos && (
                              cwal_is_alnum(*curpos)
                              || (*curpos == '_'))
                  ){
                  BUMP(1); /* make sure it shows up in the error string. */
                  RETURN_ERR(CWAL_RC_RANGE,
                             "Malformed octal int constant.");
              }
          }
          else if( *curpos && (
                               cwal_is_alnum(*curpos)
                               || (*curpos == '_'))
                   )
          { /* reject 12334x where x is alphanum or _ */
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_RC_RANGE,
                         "Malformed numeric constant starts "
                         "with '0' but is neither octal nor hex.");
          }
          else if('.'==*curpos){
              BUMP(1);
              while( cwal_is_digit(*curpos) ){
                  BUMP(1);
              }
              if('.'==*(curpos-1)){
#if 0
                  
#else
                  RETURN_ERR(CWAL_SCR_UNEXPECTED_TOKEN,
                             "Mis-terminated floating point value.");
#endif
              }
              t->ttype = TT_LiteralDouble;
          }
          else {
              t->ttype = TT_LiteralIntDec;
              /* A literal 0. This is okay. */
          }
          break;
      case '1': case '2': case '3':
      case '4': case '5': case '6':
      case '7': case '8': case '9': /* integer or double literal. */
          while( *curpos && cwal_is_digit(*curpos) ){
              /* integer or first part of a double. */
              BUMP(1);
          }
          if( ('.' == *curpos) && (cwal_is_digit(*(curpos+1))) ){
              /* double number */
              t->ttype = TT_LiteralDouble;
              BUMP(1);
              while( *curpos && cwal_is_digit(*curpos) ){
                  BUMP(1);
              }
          }
          else {
              t->ttype = TT_LiteralIntDec;
          }
          if( *curpos &&
              (cwal_is_alnum(*curpos)
               || (*curpos == '_'))) {
              BUMP(1); /* make sure it shows up in the error string. */
              RETURN_ERR(CWAL_RC_RANGE,
                         "Malformed numeric constant.");
          }
          break;
    }/* end of switch(*curpos) */

    if(0==rc
       && (curpos == t->current.begin)
       && (TT_EOF != t->ttype) ){
        /* keep trying... */
        if( th1ish_is_id_char(*curpos,1) ) /* identifier string */{
#if 1
            th1ish_read_identifier( curpos, t->end, &curpos );
#else
            BUMP(1);
            while( cwal_is_identifier_char(*curpos,0) ){
                BUMP(1);
            }
#endif
            t->ttype = TT_Identifier;
        }
        else if( (*curpos >= 32) && (*curpos < 127) ) {
            t->ttype = *curpos;
            BUMP(1);
        }
        else {
            assert(curpos == t->current.begin);
            assert(TT_UNDEF==t->ttype);
        }
    }

    if(TT_UNDEF == t->ttype){
        unsigned char const cCh = (unsigned char)*curpos;
        if( cCh > 0x7F )
            /* _hope_ for a UTF8 identifier string! */{
            th1ish_read_identifier( curpos, t->end, &curpos );
            t->ttype = TT_Identifier;
        }else{
            t->ttype = TT_TokErr;
            /* MARKER(("byte=%02x\n", (unsigned char)*curpos)); */
            t->errMsg = "Don't know how to tokenize this.";
            rc = CWAL_RC_ERROR;
        }
    }
    t->current.end = curpos;
#undef CHECKEND
#undef BUMP
#undef RETURN_ERR
#undef NEXT_LINE

    return rc;
}


static void dump_tokenizer2( cwal_simple_tokenizer const * t,
                             char const * msg, int srcLine ){
    cwal_size_t const plen = t->current.end - t->current.begin;
    unsigned int line = 0, col = 0;
    th1ish_count_lines( t->begin, t->end, t->current.begin, &line, &col );
    printf("%s:%d: %s%sTokenizer: Token type %d line=%u "
           "col(@end)=%u length=%"CWAL_SIZE_T_PFMT", string=<<<%.*s>>>\n",
           __FILE__, srcLine, 
           msg?msg:"", msg?": ":"",
           t->ttype, line, col, plen, (int)plen,
           t->current.begin );
}

#define dump_tokenizer( T, MSG ) dump_tokenizer2( (T), (MSG), __LINE__ )

/**
   Creates a value for a token. This basically just takes the t->src
   and makes a string from it, but has different handling for certain
   token types.

   On success *rv contains the new string.

   Returns 0 on success, CWAL_RC_OOM on OOM, else it should not fail.
*/
static int th1ish_st_create_value( th1ish_interp * ie,
                                   cwal_simple_token * t,
                                   cwal_value ** rv ){
    int rc = CWAL_RC_TYPE;
    cwal_value * v = NULL;
    cwal_engine * e = ie->e;
    /* FIXME: improve the string-to-numeric conversions. We're largely
       relying on the tokenizer having done a good job and the caller
       checking result codes.
    */
#define RC rc = v ? CWAL_RC_OK : CWAL_RC_OOM
    switch(t->ttype){
      case TT_LiteralIntDec:
          v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin, NULL, 10));
          RC;
          break;
      case TT_LiteralIntOctal:
          assert( (t->src.end-t->src.begin) > 2 /*"0" prefix */);
          v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+1/*0*/, NULL, 8));
          /* t->ttype = TT_LiteralIntDec; */
          RC;
          break;
      case TT_LiteralIntHex:
          assert( (t->src.end-t->src.begin) > 2 /*"0x" prefix */);
          v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+2/*0x*/, NULL, 16));
          /* t->ttype = TT_LiteralIntDec; */
          RC;
          break;
      case TT_LiteralDouble:
          v = cwal_new_double(e, (cwal_double_t)strtod(t->src.begin, NULL));
          RC;
          break;
      case TT_LiteralStringSQ:
      case TT_LiteralStringDQ:{
#if 0
          /* This variant does no unescaping of the string's content.
             Valgrind comparisons show that this _normally_ saves us just
             a few bytes per script (<200) and could also inconvenience
             print()-style routines a great deal :/. Re-using ie->buffer
             for escaping makes all the difference in the number of real
             allocations we make.
          */
          cwal_size_t const len = t->src.end - t->src.begin - 2;
          assert((t->src.end - t->src.begin)>=2 /* for the quotes */);
          v = (0 && (len>=th1ish_xstr_threshold))
              ? cwal_new_xstring_value(e, len ? (t->src.begin+1) : 0, len)
              : cwal_new_string_value(e, len ? (t->src.begin+1) : 0, len)


              /* Reminder: if we pass (t->src.begin,0) then it uses
                 strlen() to determine the length. That bit me.
              */
              ;
#else
          cwal_buffer * escapeBuf = &ie->buffer;
          cwal_size_t const oldUsed = escapeBuf->used;
          int check;
          assert((t->src.end - t->src.begin)>=2 /* for the quotes */);
          check = cwal_st_unescape_string(e,
                                          (unsigned char const *)t->src.begin + 1 /*quote*/,
                                          (unsigned char const *)t->src.end - 1 /*quote*/,
                                          escapeBuf );
          if(check) return check;
          assert(escapeBuf->used >= oldUsed);

          /*MARKER("STRING: [%.*s]\n", (int)(escapeBuf->used - oldUsed),
            (char const *)escapeBuf->mem+oldUsed);*/
          assert(0 == escapeBuf->mem[escapeBuf->used]);
          v = cwal_new_string_value(e,
                                    (escapeBuf->used-oldUsed)
                                    ? (char const *)(escapeBuf->mem+oldUsed)
                                    : NULL,
                                    escapeBuf->used - oldUsed );
          escapeBuf->used = oldUsed;
#endif
          /* th1ish_dump_val(v,"string literal"); */
          RC;
          break;
      }
      default:{
          if((long)(t->src.end - t->src.begin) >= 0){
              cwal_size_t len = t->src.end - t->src.begin;
              /* MARKER("STRING: [%.*s]\n", (int)(t->src.end - t->src.begin), t->src.begin); */
              /* MARKER("STRING: len=%ld\n", (long) (t->src.end - t->src.begin) ); */
              /* Remember that tokenization post-processing changed t->src to
                 point to the trimmed string and t->right to be the element after the
                 closing brace.
              */
              v = (0 && (len>=th1ish_xstr_threshold))
                  ? cwal_new_xstring_value(e, len ? t->src.begin : 0, len)
                  : cwal_new_string_value(e, len ? t->src.begin : 0, len)
                  /* Reminder: if we pass (t->src.begin,0) then it uses
                     strlen() to determine the length. That bit me.
                  */
                  ;
              /* th1ish_dump_val(v,"scope string"); */
              RC;
          }else if(TT_ChScopeOpen == t->ttype){
              /* Negative lengths can happen on {} and heredocs */
              v = cwal_new_string_value(e, "", 0);
              rc = 0;
          }else{
              MARKER(("STRING: ttype=%d len=%ld\n", t->ttype, (long) (t->src.end - t->src.begin) ));
              assert(!"This should not be able to happen.");
              rc = CWAL_RC_ASSERT;
          }
          break;
      }
    }
    if(!rc) {
        assert(v);
        *rv = v;
    }else{
        assert(!v);
    }
    return rc;
#undef RC
}/* th1ish_st_create_value() */

char const * th1ish_interp_script_name( th1ish_interp * ie, cwal_size_t * len ){
     if(!ie) return NULL;
     else {
         th1ish_script * sc = ie->scripts.current;
        if(!sc){
            if(len) *len = ie->srcName ? strlen(ie->srcName) : 0;
            return ie->srcName;
        }
        else {
            if(len) *len = sc->nameLength;
            return (char const *)sc->name;
        }
    }
}    

/**
   For the given source code position, this function tries to find the
   name of the script in which that position is found. On success
   returns the name and if len is not NULL then *len is set to the
   length of the name (it "should" be NUL-terminated, though).
*/
static char const *
th1ish_script_name_for_pos( th1ish_interp * ie,
                            char const * srcPos,
                            cwal_size_t * len ){
    if(!ie) return NULL;
    else {
        th1ish_script * sc = th1ish_script_for_pos(ie, srcPos);
        if(sc){
            if(len) *len = sc->nameLength;
            return (char const *)sc->name;
        }
        else if((srcPos>=ie->src.begin)
                && (srcPos<ie->src.end)){
            if(len) *len = ie->srcName ? strlen(ie->srcName) : 0;
            return ie->srcName;
        }
        return NULL;
    }
}

/**
   Sets the following properties of tgt:

   line, column: the position of srcPos within its source script.

   script: the name of the current script (determined by examining
   srcPos against the current list of scripts).

   code: if code is non-0 then this property is added, else it is not.

   Returns 0 on success.
*/
static int th1ish_set_script_props(th1ish_interp * ie,
                                   int code,
                                   char const * srcPos,
                                   cwal_value * tgt ){
    int rc;
    unsigned int line = 0;
    unsigned int column = 0;
    cwal_size_t nameLen = 0;
    char const * name =
        th1ish_script_name_for_pos(ie, srcPos, &nameLen);
    th1ish_line_col( ie, srcPos, &line, &column );
    if(line>0){
        /* we start counting at line==1, so line 0 means we don't
           know where we are. It is arguable to output the line/col
           in that case. It's ugly, but is probably technically more
           correct, to keep them in.
        */
        rc = cwal_prop_set( tgt, "column", 6,
                            cwal_new_integer(ie->e, (cwal_int_t)column) );
        if(!rc) rc = cwal_prop_set( tgt, "line", 4,
                                    cwal_new_integer(ie->e, (cwal_int_t)line) );
    }
    if(!rc && code) rc = cwal_prop_set( tgt, "code", 4,
                                        cwal_new_integer(ie->e, (cwal_int_t)code) );
    if(!rc && name && *name){
        rc = cwal_prop_set(tgt, "script", 6,
                           cwal_new_string_value(ie->e, name, nameLen));
    }
    /* th1ish_dump_val(tgt,"th1ish_set_script_props"); */
    return rc;
}


/**
    Populates ent->scriptName, ent->line, and ent->col based on the
    value of ent->scriptPos.
*/
static void th1ish_strace_populate_src_info( th1ish_interp * ie,
                                               th1ish_stack_entry *ent ){
    if(ent->scriptPos){
        unsigned int line = 0;
        unsigned int column = 0;
        ent->scriptName = (unsigned char const *)
            th1ish_script_name_for_pos(ie, (char const *)ent->scriptPos,
                                       &ent->scriptNameLen);
        th1ish_line_col( ie, (char const *)ent->scriptPos, &line, &column );
        ent->line = (cwal_size_t)line;
        ent->col = (cwal_size_t)column;
    }
}


/**
   @internal

   Copies the line/column/script location infromation from the
   current stack entry to the given container value.

   Returns 0 on success, if no stack entries are available,
   or if ex's exception code is CWAL_RC_OOM (in which case it
   does not copy the stack trace).

   Reminder to self: someday client code will throw an exception with
   the same value as CWAL_RC_OOM and we will end up debugging it to
   this code.
*/
int th1ish_strace_copy( th1ish_interp * ie, cwal_value * ex ){
    if(!ie->stack.head || !ie->stack.count) return 0;
    else if(!(TH1ISH_F_TRACE_STACK & ie->flags)) return 0;
    else {
        int rc = 0;
        cwal_array * cp;
        th1ish_stack_entry * ent = ie->stack.tail;
        cwal_exception const * eX = cwal_value_get_exception(ex);
        if(ex && (CWAL_RC_OOM==cwal_exception_code_get(eX))) return 0;
        assert(ent);
        cp = th1ish_new_array(ie);
        if(!cp) return CWAL_RC_OOM;
        rc = cwal_array_reserve(cp, ie->stack.count);
        for( ; !rc && ent; ent = ent->up ){
            cwal_value * c = th1ish_new_object(ie);
            if(!c){
                rc = CWAL_RC_OOM;
                break;
            }
            if(!ent->scriptName){
                th1ish_strace_populate_src_info(ie, ent);
            }
            rc = cwal_prop_set( c, "line", 4,
                                cwal_new_integer(ie->e, (cwal_int_t)ent->line));
            if(!rc) rc = cwal_prop_set( c, "column", 6,
                                        cwal_new_integer(ie->e, (cwal_int_t)ent->col));
            if(!rc && ent->scriptName){
                rc = cwal_prop_set(c, "script", 6,
                                   cwal_new_string_value(ie->e,
                                                         (char const *)ent->scriptName,
                                                         ent->scriptNameLen));
            }
            if(rc){
                cwal_value_unref(c);
                break;
            }
            if(!rc) rc = cwal_array_append( cp, c );
        }
        if(!rc) rc = cwal_prop_set(ex, "callStack", 9,
                                   cwal_array_value(cp));
        if(rc) cwal_array_unref(cp)
            /* Reminder: only safe because there's no way cp can
               have been injected in ex at this point */;
        /* th1ish_dump_val(ex,"callStack?"); */

        return rc;
    }
}

int th1ish_toss_val( th1ish_interp * ie, int code, cwal_value * msg ){
    int rc;
    cwal_value * ex = msg
        ? cwal_value_exception_part(ie->e, msg)
        : NULL;
    if(!ex){
        ex = th1ish_new_exception( ie, code, msg );
        if(!ex) return CWAL_RC_OOM;
    }
    rc = cwal_exception_set(ie->e, ex);
    if(CWAL_RC_EXCEPTION!=rc){
        if(msg != ex) cwal_value_unref(ex);
    }else{
        rc = th1ish_strace_copy( ie, ex );
        if(!rc) rc = CWAL_RC_EXCEPTION;
    }
    return rc;
}


int th1ish_tossv( th1ish_interp * ie, int code,
                 char const * fmt, va_list args ){
    int rc;
    cwal_value * exp = th1ish_prototype_exception(ie)/*initialize prototype injection*/;
    rc = cwal_exception_setfv(ie->e, code, fmt, args);
    if(CWAL_RC_EXCEPTION==rc){
        cwal_value * ex = cwal_exception_get(ie->e);
        assert(ex);
        assert(cwal_value_prototype_get(ie->e, ex)==exp);
        rc = th1ish_strace_copy( ie, ex );
        if(!rc) rc = CWAL_RC_EXCEPTION;
    }
    return rc;

}

int th1ish_toss( th1ish_interp * ie, int code,
                 char const * fmt, ... ){
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = th1ish_tossv(ie, code, fmt, args);
    va_end(args);
    return rc;
}

int th1ish_toss_token_val( th1ish_interp * ie, cwal_simple_token const * t,
                           int code, cwal_value * v ){
    int rc = 0;
    cwal_value * xRethrown = v
        ? cwal_value_exception_part(ie->e, v)
        : NULL;
    cwal_value * xV = NULL;
    /*
      Dilemma:

      var x = catch{throw 42}
      throw x

      x has a stacktrace (or not), and we're about to overwrite it. If
      we keep its existing line/col info, we report the original throw
      point, not the new one. Likewise, the opposite if we do not
      overwrite it. At this level the engine cannot really
      differentiate between:

      throw "!!!"
      vs
      throw api.Exception(42,"!!!")

      except possibly based on xV->code (which "should" be
      CWAL_RC_EXCEPTION for the throw case but could also be a
      client-side value which equals CWAL_RC_EXCEPTION).

      Various approach seem equally (in)correct here. :/
    */
    if(xRethrown){
        if(!cwal_prop_get(xRethrown,"line",4)){
            /* New exception, not yet populated. */
            xV = xRethrown;
            rc = th1ish_set_script_props( ie,
                                          cwal_exception_code_get(cwal_value_get_exception(xRethrown)),
                                          t->src.begin, xRethrown );
            if(!rc) rc = th1ish_strace_copy( ie, xV );
        }else{
#if 1
            xV = xRethrown;
#else
            /*
               Re-throwing a thrown/caught exception.  Wrap that
               exception as the message of a new exception. We copy
               the current stack trace ONLY if xRethrown doesn't have
               one of its own, because we otherwise get duplicates of
               most of the callStack info across xV and xRethrown.

               Bug: we "lose" type info this way by giving the client
               a type other than the one he just threw. We can't just
               re-assign xV's prototype because a client-defined
               Exception type might have native-level resources we
               don't know about, and the prototype adjustment would be
               a lie in that case.
            */
            xV = th1ish_new_exception( ie, code, v );
            if(!xV) return CWAL_RC_OOM;
            rc = th1ish_set_script_props( ie, code, t->src.begin, xV );
#endif
            if(!rc
               && (TH1ISH_F_TRACE_STACK & ie->flags)
               && !cwal_prop_get(xRethrown,"callStack", 9)
               ){
                rc = th1ish_strace_copy( ie, xV );
            }
        }
    }else{
        xV = th1ish_new_exception( ie, code, v );
        if(!xV) return CWAL_RC_OOM;
        rc = th1ish_set_script_props( ie, code, t->src.begin, xV );
        if(!rc) rc = th1ish_strace_copy( ie, xV );
    }

    if(!rc){
        assert(xV);
        rc = cwal_exception_set( ie->e, xV );
    }
    if(CWAL_RC_EXCEPTION!=rc){
        if(xV != xRethrown) cwal_value_unref(xV);
    }
    assert(0 != rc);
    return rc;
}

int th1ish_toss_posv( th1ish_interp * ie, char const * codePos,
                      int code, char const * fmt, va_list args ){
    int rc;
    rc = th1ish_tossv( ie, code, fmt, args );
    if(CWAL_RC_EXCEPTION==rc){
        cwal_value * xV;
        xV = cwal_exception_get(ie->e);
        assert(xV);
        rc = th1ish_set_script_props( ie, code, codePos, xV );
        if(!rc) rc = CWAL_RC_EXCEPTION;
        /* th1ish_dump_val(xV,"stack trace?"); */
    }
    return rc;
}


int th1ish_toss_pos( th1ish_interp * ie, char const * codePos,
                       int code, char const * fmt, ... ){
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = th1ish_toss_posv( ie, codePos, code, fmt, args );
    va_end(args);
    return rc;
}


int th1ish_toss_token( th1ish_interp * ie, cwal_simple_token const * t,
                       int code, char const * fmt, ... ){
    int rc;
    va_list args;
    va_start(args,fmt);
    rc = th1ish_toss_posv( ie, t->src.begin,
                           code, fmt, args );
    va_end(args);
    return rc;
}


/**
    Intended to be called when th1ish_next_token() fails, passing it
    the tokenizer and the code from the failed tokenization. This
    function tosses an exception with that information and returns
    CWAL_RC_EXCEPTION on success. It never returns 0.
*/
static int th1ish_toss_t10n(th1ish_interp * ie,
                              cwal_simple_tokenizer const * cst,
                              int code){
    return th1ish_toss_pos(ie, cst->current.begin,
                            code, "Tokenizer returned code %d (%s) %s%s",
                            code, cwal_rc_cstr(code),
                            cst->errMsg ? ": " :"",
                            cst->errMsg ? cst->errMsg : "");
}

/**
   Requires st to be a TT_ChScopeOpen, TT_ChBraceOpen, or
   TT_ChParenOpen token. It "slurps" the content part (between the
   braces), trimming whitespace from beginning and ending. Sets
   out->begin and out->end to reflect the trimmed range (which may be
   empty, i.e. end==begin).

   Returns 0 on success. On error it may set ie->e's exception state.
*/
static int th1ish_slurp_braces( th1ish_interp *ie,
                                cwal_simple_tokenizer * st,
                                cwal_st_src * out ){
    int const opener = st->ttype;
    int closer;
    int rc = 0;
    int level = 1;
    cwal_st_src origSrc;
    char const * pos;
    switch(opener){
      case TT_ChParenOpen: closer = TT_ChParenClose; break;
      case TT_ChBraceOpen: closer = TT_ChBraceClose; break;
      case TT_ChScopeOpen: closer = TT_ChScopeClose; break;
      default:
          return CWAL_RC_TYPE;
    }
    /*
      TODO?

      Tokenize this ourselves instead of going through st. The main
      advantage is we avoid a lot of tokenization. The end effect is
      the same, but the former is less expensive (many fewer function
      calls), but memory usage is the same either way.
    */
    /* dump_tokenizer(st, "slurping..."); */
    assert(st->end > st->current.begin);
    while((st->end > ++st->current.begin)
          && cwal_is_whitespace(*st->current.begin)){
        /* Skip leading whitespaces */
    }
    if(st->end == st->current.begin){
        return CWAL_RC_RANGE;
    }
    st->current.end = st->current.begin;
    origSrc = st->current;
    /**
       If we use the tokenizer to parse {strings}, then...

       This has up-sides and down sides. It traverses string literals,
       so that any '}' in the string is not seen by the slurping
       code. But it breaks a desired use case:

       var x {item #3}

       the #-style comment will hide the closing brace if we use the
       tokenizer here.

       It also breaks simpler things like: { ... 1a ... }
    */

    /**
       Alternate slurp strategy: traverse the raw bytes. This cannot protect
       us from a '}' inside a string unless we add the string-skipping
       code here.
    */
    pos = origSrc.begin;
    for( ; pos < st->end ; ++pos){
        /**
           FIXME: by slurping this way, instead of going through
           the tokenizer, we ignore any string literals and such
           which might "protect" what we will interpret here as
           a closing '}'. OTOH, slurping this way allows us to defer
           some forms of tokenization errors until this source string
           is tokenized. It also allows us to enable #-style comments
           in the tokenizer without breaking things here.
        */
        if(!*pos){
            cwal_simple_token tok = cwal_simple_token_empty;
            tok.ttype = opener;
            tok.src = origSrc;
            rc = th1ish_toss_token( ie, &tok, CWAL_RC_RANGE,
                                    "Unexpected EOF while slurping "
                                    "{SCOPE} block.");
            break;
        }
        else if(opener == *pos){
            ++level;
        }
        else if(closer == *pos){
            assert(level>0);
            if(!--level){
                char const * end = pos /*st->current.end-1*/ /*back to closing brace*/;
                /*dump_tokenizer( st, "At slurp closing");*/
                while((--end>origSrc.begin) && cwal_is_whitespace(*end)){}
                ++end;
                st->current.end = pos+1;
                /*dump_tokenizer( st, "Again at slurp closing");*/
                out->begin = origSrc.begin;
                assert( end >= origSrc.begin );
                out->end = end;
                /* dump_tokenizer( st, "Again at slurp closing"); */
                rc = 0;
                /* MARKER("slurped <<%.*s>>\n", (int)(out->end - out->begin), out->begin); */
                break;
            }
        }
    }
    return rc;

}

/**
   Post-processor for certain token types. ie is the interpreter, t is
   the current token. target is where the results are written.  If the
   token needs postprocessing then target's state will differ from t's
   when this function returns successfully.

   t->current will be continually modified by the post-processing to
   point to the current end of the tokenization process. After
   completion (on success), tokenization of the script may continue at
   t->current's location.

   On success target->ttype is set to the type of the original
   t->ttype value, but target->current's contents will reflect a
   different source range, a superset of t and some arbitrary number
   of tokens after t.

   Returns 0 on success.
*/
static int th1ish_tokenize_post( th1ish_interp * ie, cwal_simple_tokenizer * t,
                                 cwal_simple_tokenizer * target ){
    int rc = 0;
    int const openerType = t ? t->ttype : 0;
    if(!target) target = t;
    else if(target != t) *target = *t;
    switch(openerType){
      case TT_ChParenOpen:
      case TT_ChBraceOpen:
      case TT_ChScopeOpen: {
          rc = th1ish_slurp_braces( ie, t, &target->current );
          /* dump_tokenizer( t, "After slurp-braces()"); */
          if(!rc) {
              target->ttype = openerType;
#if 0
              MARKER("Slurped up a () or {}: <<<");
              fwrite( target->current.begin,
                      target->current.end - target->current.begin,
                      1, stdout );
              puts(">>>");
              /* MARKER("Char after slurp=%d (%c)\n", *t->current.end, *t->current.end); */
              dump_tokenizer(target, "After slurping");
#endif
          }
          break;
      }
      default: break;
    }
    return rc;

}

/**
   Post-processor for TT_OpHeredocStart tokens. ie is the interpreter, tr is
   the current token state. tgt is where the results are written.  If the
   token needs postprocessing then target's state will differ from t's
   when this function returns successfully.

   On success tgt->ttype is set to TT_ChScopeOpen and its contents
   behave more or less like a {...} block. That is, its start/end pointers
   reflect the content between the heredoc identifier begin/end markers.
   
   On success tgt->current will reflect a different source range than
   tr->current, a superset of t and some arbitrary number of tokens
   after tr. tr->current is adjusted so that after this returns successfully,
   th1ish_next_token(tr) will start tokenizing immediately after the
   heredoc's closing identifer.

   Returns 0 on success.
*/
static int th1ish_tokenize_heredoc( th1ish_interp * ie, cwal_simple_tokenizer * tr,
                                    cwal_simple_tokenizer * tgt ){
    int rc = 0;
    char const * docBegin;
    char const * docEnd;
    char const * idBegin;
    char const * idEnd;
    char const * theEnd = NULL;
    cwal_size_t idLen;
    int modeFlag = 0;
    /* char const * idEnd; */
    assert(tr && tgt);
    assert(TT_OpHeredocStart==tr->ttype);
    *tgt = *tr;
#if 0
    idBegin = tgt->current.begin + 3;
    if(isspace((int)*idBegin)){
        do{
            ++idBegin;
        }
        while(isspace((int)*idBegin));
        tgt->current.end = idBegin;
    }
#endif
    rc = th1ish_next_token(tgt);
    if(rc) goto tok_err;

    if(TT_ChColon==tgt->ttype){
        modeFlag = tgt->ttype;
        rc = th1ish_next_token(tgt);
        if(rc) goto tok_err;
    }


    switch(tgt->ttype){
      case TT_Identifier:
      case TT_LiteralStringDQ:
      case TT_LiteralStringSQ:
      /* doesn't work case TT_ChScopeOpen: */
          break;
      default:
          return th1ish_toss_pos(ie, tgt->current.begin,
                                 CWAL_SCR_SYNTAX,
                                 "Expecting identifier or quoted string "
                                 "at start of HEREDOC.");
    }
    idBegin = tgt->current.begin;
    idEnd = tgt->current.end;
    idLen = idEnd - idBegin;
    docBegin = idEnd;
    switch(modeFlag){
      case TT_ChColon:
          if('\n'==*docBegin || isspace((int)*docBegin)) ++docBegin;
          break;
      default:
          if('\n'==*docBegin) ++docBegin;
          else while(isspace((int)*docBegin)) ++docBegin;
    }
    rc = CWAL_SCR_SYNTAX;
    for( docEnd = docBegin; docEnd < tgt->end; ++docEnd ){
        if(*docEnd != *idBegin) continue;
        else if(0 == memcmp( docEnd, idBegin, idLen)){
#if 1
            char const * back = docEnd-1;
            theEnd = docEnd + idLen;
            switch(modeFlag){
              case TT_ChColon:
                  if('\n'==*back || isspace((int)*back)) --docEnd;
                  break;
              default:
                  if('\n'==*docBegin) --docBegin;
                  else for( ; isspace((int)*back); --back) --docEnd;
            }
            rc = 0;
            break;
#else
            char const check = *(docEnd + idLen);
            /* We can't reliably check to see if the trailing identifier
               has something after it because (A) we sometimes see
               trailing bits from outer (), {}, [] constructs and (B)
               X-strings needn't be NUL terminated.
           */
            if(!check
               || (';'==check) || (','==check)
               || isspace((int)check)){
                char const * back = docEnd-1;
                theEnd = docEnd + idLen;
                if('\n'==*back) --docEnd;
                else for( ; (' '==*back) || ('\t'==*back); --back) --docEnd;
                rc = 0;
                break;
            }else{
                docEnd += idLen;
            }
#endif
        }
    }
    if(rc){
        rc = th1ish_toss_pos(ie, docBegin,
                             rc, "Did not find end of HEREDOC "
                             "starting at '%.*s'.",
                             (int)idLen, idBegin);
    }else{
        tr->current.end = theEnd;
        tgt->ttype = TT_ChScopeOpen;
        tgt->current.begin = docBegin;
        tgt->current.end = docEnd;
#if 0
        MARKER(("HEREDOC<%.*s> body: %.*s\n",
                (int)(idEnd-idBegin), idBegin,
                (int)(docEnd-docBegin), docBegin));
#endif
    }
    return rc;
    tok_err:
    {
        return th1ish_toss_t10n(ie, tgt, rc);
    }
}

/**
   th1ish_eval_atom() and th1ish_eval_0..5() are named so as a tip of
   the hat to Herbert Schildt. (And because i relied very much on his
   sample code for the overall structure.)

   Atoms include:

   - Literal values (strings, numbers)
   - Identifiers
   - Keywords
   - Ternay if expressions
   - [tcl-style-func-calls]
*/
static int th1ish_eval_atom(th1ish_interp * ie, cwal_simple_token * tok,
                            cwal_simple_token ** next,
                            cwal_value ** rv );
/**
   Evaluator for var decls, var assignments.
*/
static int th1ish_eval_0(th1ish_interp * ie, cwal_simple_token * tok,
                         cwal_simple_token ** next,
                         cwal_value ** rv );
/**
   Process relational ops.
*/
static int th1ish_eval_1(th1ish_interp * ie, cwal_simple_token * tok,
                         cwal_simple_token ** next,
                         cwal_value ** rv );
/**
   Process binary add/subtract and assignment
*/
static int th1ish_eval_2(th1ish_interp * ie, cwal_simple_token * tok,
                         cwal_simple_token ** next,
                         cwal_value ** rv );
/**
   Process multiply/divide/modulo.
*/
static int th1ish_eval_3(th1ish_interp * ie, cwal_simple_token * tok,
                         cwal_simple_token ** next,
                         cwal_value ** rv );
/**
   Unary +/-/~, potentially prefix ++/-- (NOT IMPLEMENTED).
*/
static int th1ish_eval_4(th1ish_interp * ie, cwal_simple_token * tok,
                         cwal_simple_token ** next,
                         cwal_value ** rv );
/**
   Interprets (lists,of,expressions), a single atom, Object.Property
   access, or $functionCallsWithDollarPrefix. ([func calls in braces]
   are handled as atoms).
*/
static int th1ish_eval_5(th1ish_interp * ie, cwal_simple_token * tok,
                         cwal_simple_token ** next,
                         cwal_value ** rv );

/**
   Evalutator for for keyword tokens.
*/
static int th1ish_eval_keywords( th1ish_interp * ie,
                                 cwal_simple_token * t,
                                 cwal_simple_token ** next,
                                 cwal_value ** rv );


static int th1ish_eval_ternary_if(th1ish_interp * ie,
                                  cwal_value * lhs,
                                  cwal_simple_token * t,
                                  cwal_simple_token ** next,
                                  cwal_value ** rv );

/**
   The entry-point for evaluating a single expression. It handles some
   common token cases like newlines, semicolons, and EOF, falling back
   to th1ish_eval_0().
*/
static int th1ish_eval_entry( th1ish_interp * ie, cwal_simple_token * t,
                              cwal_simple_token ** next,
                              cwal_value ** rv );

/**
   Evaluates t and subsequent expressions as arguments to be passed to
   the given function by iteratively using th1ish_eval_0().

   On success func is call()ed with selfObj as the "this" context (or
   the function itself as the context is selfObj is NULL). The selfObj
   value will be available the args->self passed to the
   cwal_callback_f() implementation wrapped by func.

   This processes calls for two different forms:

   $func arg0 ... argN

   func(args...)

   ignoreEOL must be false for the first form and true for the second.
   
   Returns 0 on success.
*/
static int th1ish_eval_func_collect_call(th1ish_interp * ie, cwal_simple_token * t,
                                         cwal_simple_token ** next,
                                         cwal_value ** rv,
                                         cwal_value * selfObj,
                                         cwal_function * func,
                                         char ignoreEOL);

/**
   Expects t to be a TT_ChScopeOpen, and evaluates its code via
   th1ish_eval_string(ie, addScope, ...code..., rv).  Returns 0 on
   success. If addScope is greater than 0 and the eval returns
   CWAL_RC_RETURN, then the RETURN code is translated to 0. FIXME:
   don't do that translation here, or add a flag for it.
*/
static int th1ish_eval_scope_string(th1ish_interp * ie,
                                    char addScope,
                                    cwal_simple_token * t,
                                    cwal_simple_token ** next,
                                    cwal_value ** rv );


/**
   Typedef for "evaluator" callbacks. These are the recursive-descent
   parsers for an interpreter.

   ie is the interpreter instance. tok is the current token which we
   wish to evaluate it. If the callback does not know how to handle
   tok it must return CWAL_SCR_CANNOT_CONSUME and not modify any of its
   arguments.

   On success *next must be assigned to the token after the
   last-consumed token and *rv must be set to the result value (it may
   be set to NULL).

   On error non-0 must be returned. Some parsers (looping/recursive
   ones) may have already set *rv and *next by the time they fail, and
   that is ok (it can help narrow down the position of the error).

   Upon returning if *next==tok and the function returns 0 then
   EOF should be assumed. All non-EOF parsers must consume input
   if they return success.
   
   It is illegal to pass a NULL for any argument.

   Implementations must keep in mind that "filler" tokens often litter
   the input chain (comments, whitespace, extra newlines) and they may
   need to account for those.
*/
typedef int (*th1ish_evaluator_f)( th1ish_interp * ie,
                                   cwal_simple_token * tok,
                                   cwal_simple_token ** next,
                                   cwal_value ** rv );
typedef struct th1ish_keyword th1ish_keyword;
struct th1ish_keyword {
    int ttype;
    char const * word;
    unsigned char wlen;
    th1ish_evaluator_f parser;
};

#define KW(W) static int th1ish_keyword_##W( th1ish_interp * ie, cwal_simple_token * t, cwal_simple_token ** next, cwal_value ** rv )
KW(array);
KW(assert);
KW(catch);
KW(continue);
KW(do_while);
KW(eval);
KW(for);
KW(get);
KW(if);
KW(nameof);
KW(object);
KW(proc);
KW(refcount);
KW(return);
KW(set);
KW(sweep);
KW(throw);
KW(typename);
KW(var);
KW(while);
KW(builtin_value);
KW(FILE_LINE);
KW(SCOPE);
KW(SRC);
#if 0
KW(closure);
KW(unref);
#endif
#undef KW

static int th1ish_keyword_breakpoint( th1ish_interp * ie, cwal_simple_token * t,
                                      cwal_simple_token ** next,
                                      cwal_value ** rv ){
    /**
       The sole purpose of this function is to act as way to drop
       debugger breakpoints into a script, such that one can
       relatively quickly jump straight to a particular part of the
       script. That said, because th1ish is so recursive, "relative"
       might not actually be anywhere close.
    */
    unsigned int line = 0;
    unsigned int col = 0;
    cwal_size_t nameLen = 0;
    char const * script =
        th1ish_script_name_for_pos(ie, t->src.begin, &nameLen );
    th1ish_line_col(ie, t->src.begin, &line, &col);
    if(!script){/*unused var warning*/}
    /* Place your debugging point around here and you'll have the
       script location information populated. This makes it easier
       to figure out which instance of a breakpoint you're running
       at.
    */
    *next = t->right;
    return 0;
}

static th1ish_keyword th1ish_keywords[] = {
/* Keep these ASCII-sorted by name - we rely on that
   in other places as a search optimization.
*/
/* { TOKEN_TYPE, NAME, NAME_LEN, th1ish_evaluator_f } */
{TT_Keyword__BREAKPOINT, "__BREAKPOINT",12, th1ish_keyword_breakpoint},
{TT_Keyword__COLUMN,  "__COLUMN",8, th1ish_keyword_FILE_LINE },
{TT_Keyword__FILE,    "__FILE",6, th1ish_keyword_FILE_LINE },
{TT_Keyword__LINE,    "__LINE",6, th1ish_keyword_FILE_LINE },
{TT_Keyword__SCOPE,   "__SCOPE",7, th1ish_keyword_SCOPE},
{TT_Keyword__SRC,     "__SRC",5, th1ish_keyword_SRC },
{TT_KeywordSweep,     "__sweep",7, th1ish_keyword_sweep},
{TT_KeywordVacuum,    "__vacuum",8, th1ish_keyword_sweep},
{TT_KeywordArray,     "array", 5, th1ish_keyword_array},
{TT_KeywordAssert,    "assert",6, th1ish_keyword_assert},
{TT_KeywordBreak,     "break",5, th1ish_keyword_return/*yes, return*/},
{TT_KeywordCatch,     "catch",5, th1ish_keyword_catch},
{TT_KeywordVarDeclConst, "const", 5, th1ish_keyword_var},
{TT_KeywordContinue,  "continue",8, th1ish_keyword_continue},
{TT_KeywordDo,        "do",2, th1ish_keyword_do_while},
{TT_KeywordElse,      "else", 4, 0 /* handled internally by 'if' */},
{TT_KeywordEval,      "eval",4, th1ish_keyword_eval},
{TT_KeywordExit,      "exit",4, th1ish_keyword_return},
{TT_KeywordFalse,     "false", 5, th1ish_keyword_builtin_value},
{TT_KeywordFatal,     "fatal",5, th1ish_keyword_return},
{TT_KeywordFor,       "for",3, th1ish_keyword_for},
{TT_KeywordFunction,  "function", 8, th1ish_keyword_proc},
{TT_KeywordGet,       "get",3, th1ish_keyword_get},
{TT_KeywordIf,        "if", 2, th1ish_keyword_if},
{TT_KeywordNameof,    "nameof", 6, th1ish_keyword_nameof},
{TT_KeywordNull,      "null", 4, th1ish_keyword_builtin_value},
{TT_KeywordObject,    "object", 6, th1ish_keyword_object},
{TT_KeywordFunction,  "proc",4, th1ish_keyword_proc},
{TT_KeywordPrototype, "prototype", 9, 0},
{TT_KeywordRefcount,  "refcount", 8, th1ish_keyword_refcount},
{TT_KeywordReturn,    "return",6, th1ish_keyword_return},
{TT_KeywordSet,       "set",3, th1ish_keyword_set},
{TT_KeywordScope,     "scope",5, th1ish_keyword_eval},
{TT_KeywordThrow,     "throw",5, th1ish_keyword_throw},
{TT_KeywordToss,      "toss",4, th1ish_keyword_return},
{TT_KeywordTrue,      "true", 4, th1ish_keyword_builtin_value},
{TT_KeywordTypename,  "typename",8, th1ish_keyword_typename},
{TT_KeywordUndef,     "undefined", 9, th1ish_keyword_builtin_value},
{TT_KeywordUnset,     "unset", 5, th1ish_keyword_set},
{TT_KeywordVarDecl,   "var", 3, th1ish_keyword_var},
{TT_KeywordWhile,     "while",5, th1ish_keyword_while},
#if 0
{TT_KeywordClosure,   "closure",7, 0},
{TT_KeywordElse,    "else",4, 0/*actually handled internally*/},
{TT_KeywordForEach, "foreach",6, 0},
{TT_KeywordIn,      "in",2, 0},
{TT_KeywordUnref,  "unref", 5, 0},
#endif
{0,0,0}
};

/**
   An internal helper for th1ish_chain_postprocess() to work
   around this script-side problem:

   var a array[array [1,2,3]]
   a.0.1

   That looks like (IDENTIFIER,DOT,DOUBLE) to the tokenizer.  This
   function "undoes" that misunderstanding.
   
   Expects t->ttype to be TT_ChPeriod. If the following token is a
   TT_LiteralDouble then this code replaces that token with
   (INTEGER,DOT,INTEGER) tokens. It does this iteratively, so
   IDENT.DOUBLE.DOUBLE will also be treated properly, provided there
   are no junk tokens between the dots which confuse us here.

   (*next) is set to the the final token this function processes,
   _not_ the one-after-the-end like all of the rest of the API,
   because the one caller of this function requires it to be that
   way. The caller must continue processing at (*next)->right.

   Returns 0 on success, non-0 only on allocation error.
*/
static int th1ish_chain_fix_dot_double( th1ish_interp * ie,
                                        cwal_simple_token * t,
                                        cwal_simple_token **next){
    int rc = 0;
    cwal_simple_token * dbl;
    cwal_simple_token * i1;
    cwal_simple_token * i2;
    cwal_simple_token * dot;
    char const * pos;
    start:
    assert(TT_ChPeriod==t->ttype);
    dbl = TNEXT(t);
    if(!dbl || (TT_LiteralDouble!=dbl->ttype)){
        *next = t;
        return 0;
    }
    /* Split the double into INTEGER DOT INTEGER */
    /* Create first INTEGER token */
    i1 = th1ish_st_alloc( ie );
    dot = i1 ? th1ish_st_alloc( ie ) : 0;
    i2 = dot ? dbl /*th1ish_st_alloc( ie )*/ : 0;
    if(!dot){
        if(i1) th1ish_st_free_chain(ie, i1, 1);
        if(dot) th1ish_st_free_chain(ie, dot, 1);
        assert(!i2);
        rc = CWAL_RC_OOM;
        goto end;
    }

    t->right = i1;
    i1->right = dot;
    dot->right = i2;

    *next = i2 /*not dbl->right due to 'continue' behaviour in our caller */;

    /* The first integer token... */
    i1->ttype = i2->ttype = TT_LiteralIntDec;
    pos = i1->src.begin = dbl->src.begin;
    for( ; '.' != *pos; ++pos ){}
    assert('.'==*pos);
    i1->src.end = pos;

    /* The DOT token */
    dot->ttype = TT_ChPeriod;
    dot->src.begin = pos;
    dot->src.end = pos + 1;
    
    /* The second integer token... */
    i2->src.begin = pos+1;
    assert(i2->src.end > i2->src.begin);
    /* dump_token(i1,"i1"); dump_token(dot,"dot"); dump_token(i2,"i2"); */
    t = i2->right ? cwal_st_skip(i2->right) : 0;
    if(t && (TT_ChPeriod==t->ttype)){
        goto start;
    }
    end:
    return rc;
}

/**
   Returns true if t is a TT_ChPeriod or TT_OpArrow, else false.
*/
static char th1ish_token_is_dot_op(cwal_simple_token const * t){
    switch(t ? t->ttype : 0){
      case TT_ChPeriod:
      case TT_OpArrow:
          return 1;
      default:
          return 0;
    }
}


/**
   Like th1ish_token_is_dot_op(), but cwal_st_skip()s
   over t first.
*/
static char th1ish_dot_is_pending( cwal_simple_token * t ){
    return th1ish_token_is_dot_op(cwal_st_skip(t));
}

/**
   If t, or the next "non-junk" token after t (on the same line)
   is a ++ or -- operator then that operator's token type value
   is returned, otherwise 0 is returned. t may be 0.

   Used for detecting postfix ++/-- operators in certain places.
*/
static int th1ish_pending_incr_op( cwal_simple_token * t ){
    t = th1ish_skip(0, t);
    switch( t ? t->ttype : 0 ){
      case TT_OpIncr:
      case TT_OpDecr:
          assert(t->ttype>0);
          return t->ttype;
      default: return 0;
    }
}



/**
   Walks to the right through the given token chain and does some
   post-processing:

   - It converts Identifier tokens which contain
   keywords to Keyword tokens.

   - Tweaks "prototype" tokens to be of type TT_KeywordPrototype.
   
   Returns 0 on success.
*/
static int th1ish_chain_postprocess( th1ish_interp * ie,
                                     cwal_simple_token * t ){
    int rc = 0;
    th1ish_keyword * k;
    cwal_size_t klen;
    cwal_simple_token * prev = 0;
    t = cwal_st_skip(t);
    for( ; !rc && t; t = TNEXT(t) ){
        klen = t->src.end - t->src.begin;
        if(!klen) continue;
        switch(t->ttype){
          case TT_Whitespace:
          case TT_EOF:
          case TT_EOL:
              /* Do not set (prev=t) so that
                 the (x.y) check(s) below can work.
              */
              continue;
          case TT_ChPeriod:{
              cwal_simple_token * x = t;
              rc = th1ish_chain_fix_dot_double(ie, t, &x);
              /* dump_token(t,"t after fix-dot-double"); */
              if(t==x) break;
              else continue;
          }
          case TT_LiteralDouble:
              if(prev && (TT_ChPeriod==prev->ttype)){
                  assert(!"FIXME: dot followed by float is "
                         "not recognized how it needs to be.");
                  /* fall through */
              }
          default: break;
        }
        if(TT_Identifier != t->ttype){
            prev = t;
            continue;
        }
        if((8==klen) && (0==cwal_compare_cstr("inherits", klen,
                                              t->src.begin, klen))){
            /* We treat inherits as an operator, not keyword
               handler. */
            t->ttype = TT_KeywordInstanceOf;
            prev = t;
            continue;
        }
        for( k = &th1ish_keywords[0]; k->word; ++k ){
            if(k->wlen != klen) continue;
            else {
                int const cmp =
                    /* memcmp(t->src.begin, k->word, klen) */
                    cwal_compare_cstr(k->word, klen,
                                      t->src.begin, klen);
                    ;
                if(cmp>0) break
                    /* Relies on th1ish_keywords being ASCII
                       sorted by k->word. */
                    ;
                else if(0==cmp){
#if 1
                    if((k->ttype==TT_KeywordPrototype)
                       && !th1ish_token_is_dot_op(prev)){
                        /* Not-strictly-necessary workaround: allow
                           'prototype' as an identifier outside the
                           context of obj.prototype resolution (where we
                           must treat it specially).
                        */
                        t->ttype = TT_Identifier;
                    }
                    else
#endif
                        t->ttype = k->ttype;
                    assert(1 || prev);
                    break;
                }
            }
        }
        prev = t;
    }
    return rc;
}

static th1ish_keyword const * th1ish_st_keyword_get_for_type( int ttype ){
    th1ish_keyword const * k;
    for( k = &th1ish_keywords[0]; k->word; ++k ){
        if(k->ttype == ttype) return k;
    }
    return 0;
}

static th1ish_keyword const * th1ish_st_keyword_get( cwal_simple_token const * t ){
    return t ? th1ish_st_keyword_get_for_type( t->ttype ) : NULL;
}


#if 0
static cwal_function * th1ish_check_function( cwal_value * v ){
    return cwal_value_get_function(v);
}
#endif

/**
   NULLs out IE->currentThis and IE->currentIdentKey. This is needed
   at the start of most top-level eval_N() routines to ensure that the
   dot-deref operation results do not polute down-stream processing.
*/
#define th1ish_reset_this(IE) (IE)->currentThis = (IE)->currentIdentKey = 0
#define th1ish_this_save(IE) cwal_value * _oldThis = (IE)->currentThis; \
    cwal_value * _oldIdentKey = (IE)->currentIdentKey
#define th1ish_this_restore(IE) (IE)->currentThis = _oldThis; (IE)->currentIdentKey = _oldIdentKey

char th1ish_props_can( th1ish_interp * ie, cwal_value const * v ){
    while(v){
        if(cwal_props_can(v)) return 1;
        v = th1ish_prototype_get(ie, v);
    }
    return 0;
}



cwal_value * th1ish_global_props(th1ish_interp * ie){
    return ie ? cwal_scope_properties(ie->topScope) : NULL;
}

cwal_scope * th1ish_global_scope(th1ish_interp * ie){
    return ie ? ie->topScope : NULL;
}

int th1ish_set_v( th1ish_interp * ie, char const * srcPos,
                cwal_value * self, cwal_value * key, cwal_value * v ){
    int rc;
    char const *errMsg = 0;
    if(self){
        if(th1ish_props_can(ie,self)){
            cwal_array * ar;
            if(cwal_value_is_integer(key)
               && (ar=cwal_value_array_part(ie->e,self))){
                /*
                  Reminder: th1ish_value_array_part() ends up setting
                  entries in arrays used as prototypes. e.g.:

                  var a = array[], b = array[]
                  b.3 = 'hi'
                  assert b.3 !== a.3 // would fail

                  but that is arguably expected.
                */
                cwal_int_t const i = cwal_value_get_integer(key);
                if(i<0){
                    rc = CWAL_RC_RANGE;
                    errMsg = "Array indexes may not be negative.";
                }else{
                    /*MARKER("Setting array index #%u\n",(unsigned)i);
                      th1ish_dump_val(v,"array entry");*/
                    rc = cwal_array_set(ar, (cwal_size_t)i, v);
                    if(rc) errMsg = "cwal_array_set() failed.";
                }
            }
            else if(!cwal_props_can(self)){
                goto non_container;
            }
            else if(0==cwal_compare_str_cstr( cwal_value_get_string(key),
                                              "prototype", 9 )){
                /* i don't like this "prototype" bit at all, but it's a
                   necessary workaround... */
                rc = cwal_value_prototype_set( self, v );
                if(rc) errMsg = "cwal_value_prototype_set() failed";
            }
            else if(cwal_props_can(self)){
                rc = cwal_prop_set_v( self, key, v );
                if(CWAL_RC_TYPE==rc) errMsg = "Invalid target type for assignment.";
#if 0
                else if(!v && (CWAL_RC_NOT_FOUND==rc)) rc = 0 /* for sanity's sake */;
#endif
            }
            else {
                assert(!"Unhandled case");
            }
        }
        else{
            goto non_container;
        }
    }else{
#if 0
        th1ish_dump_val(key,"KEY Setting scope var");
        th1ish_dump_val(v,"VALUE Setting scope var");
        th1ish_dump_val(self,"SELF Setting scope var");
#endif

        /**
           FIXME: disable a SET on a scope variable which is
           undeclared in all scopes, throw an error in that case.
           Requires a search+set (and set has to do its own search,
           again) or a flag to one of the cwal-level setters which
           tells it to fail if the var cannot be found.
        */
        rc = th1ish_var_set_v( ie,
                               v ? -1 : 0 /* don't allow 'unset'
                                             across scopes*/,
                               key, v );
        switch(rc){
          case 0:
          case CWAL_RC_EXCEPTION:
              break;
          case CWAL_RC_ACCESS:
              errMsg = "Cannot assign to a const variable.";
              break;
          default:
              errMsg = "th1ish_var_set_v() failed.";
              break;
        }
    }
    if(errMsg && (CWAL_RC_EXCEPTION!=rc)){
        rc = srcPos
            ? th1ish_toss_pos(ie, srcPos, rc, "%s", errMsg )
            : th1ish_toss(ie, rc, "%s", errMsg );
    }
    return rc;
    non_container:
    {
        cwal_size_t tlen = 0;
        char const * tn = cwal_value_type_name2(self, &tlen);
        return th1ish_toss_pos(ie, srcPos, CWAL_RC_TYPE,
                               "Cannot set properties on "
                               "non-container type '%.*s'.",
                               (int)tlen, tn);
    }
}

int th1ish_set( th1ish_interp * ie, char const * srcPos,
                cwal_value * self,
                char const * key, cwal_size_t keyLen,
                cwal_value * v ){
    int rc;
    char const *errMsg = 0;
    if(!key) return CWAL_RC_MISUSE;
    else if(!keyLen && *key) keyLen = strlen(key);
    if(self){
        if(th1ish_props_can(ie,self)){
            if(!cwal_props_can(self)){
                goto non_container;
            }
            else if((9==keyLen) && 0==memcmp( key, "prototype", 9 )){
                /* i don't like this "prototype" bit at all, but it's a
                   necessary workaround... */
                rc = cwal_value_prototype_set( self, v );
                if(rc) errMsg = "cwal_value_prototype_set() failed.";
            }
            else{
                rc = cwal_prop_set( self, key, keyLen, v );
                if(CWAL_RC_TYPE==rc) errMsg = "Invalid target type for assignment.";
#if 0
                else if(!v && (CWAL_RC_NOT_FOUND==rc)) rc = 0 /* for sanity's sake */;
#endif
            }
        }
        else{
            goto non_container;
        }
    }else{
        /**
           FIXME: disable a SET on a scope variable which is
           undeclared in all scopes, throw an error in that case.
           Requires a search+set (and set has to do its own search,
           again) or a flag to one of the cwal-level setters which
           tells it to fail if the var cannot be found.
        */
        rc = th1ish_var_set( ie,
                             v ? -1 : 0 /* don't allow 'unset'
                                             across scopes*/,
                             key, keyLen, v );
        if(rc && (CWAL_RC_EXCEPTION!=rc)){
            errMsg = "th1ish_var_set_v() failed.";
        }
    }
    if(errMsg && (CWAL_RC_EXCEPTION!=rc)){
        rc = srcPos
            ? th1ish_toss_pos(ie, srcPos, rc, "%s", errMsg )
            : th1ish_toss(ie, rc, "%s", errMsg );
    }
    return rc;
    non_container:
    {
        cwal_size_t tlen = 0;
        char const * tn = cwal_value_type_name2(self, &tlen);
        return th1ish_toss_pos(ie, srcPos, CWAL_RC_TYPE,
                               "Cannot set properties on "
                               "non-container type '%s'.",
                               (int)tlen, tn);
    }
}

/**
   Internal th1ish_get_v() impl. Recursively looks up
   self's protototype chain until it finds the given
   property key.

   Reminder to self: this is what fixed the problem that
   in some callbacks, this.xyx was always resolving to
   undefined.
*/
static cwal_value * th1ish_get_v_proxy( th1ish_interp * ie,
                                        cwal_value * self,
                                        cwal_value * key ){

    cwal_value * v = NULL;
    assert(ie && self && key);
#if 0
    while(!v && self){
        if(cwal_props_can(self)) v = cwal_prop_get_v(self, key);
        if(!v) self = th1ish_prototype_get(ie, self);
    }
#else
    for(; !v && self; self = th1ish_prototype_get(ie, self) ){
        if(cwal_props_can(self)) v = cwal_prop_get_v(self, key);
    }
#endif
    return v;
}

/**
   C-string equivalent of th1ish_get_v_proxy().
*/
static cwal_value * th1ish_get_proxy( th1ish_interp * ie,
                                      cwal_value * self,
                                      char const * key,
                                      cwal_size_t keyLen ){
    cwal_value * v = NULL;
    assert(ie && self && key);
    while(!v && self){
        if(cwal_props_can(self)) v = cwal_prop_get(self, key, keyLen);
        if(!v) self = th1ish_prototype_get(ie, self);
    }
    return v;
}

int th1ish_get_v( th1ish_interp * ie, cwal_value * self,
                cwal_value * key, cwal_value ** rv ){
    int rc;
    char const *errMsg = 0;
    if(self){
        cwal_array * ar;
        if(cwal_value_is_integer(key)
           && (ar=cwal_value_array_part(ie->e,self))){
            cwal_int_t const i = cwal_value_get_integer(key);
            if(i<0){
                rc = CWAL_RC_RANGE;
                errMsg = "Array indexes may not be negative.";
            }else{
                *rv = cwal_array_get(ar, (cwal_size_t)i);
                rc = 0;
            }
        }
        /* i don't like this "prototype" bit at all, but it's a
           necessary workaround... */
        else if(0==cwal_compare_str_cstr( cwal_value_get_string(key),
                                          "prototype", 9 )){
            *rv = th1ish_prototype_get( ie, self );
            rc = 0;
        }
        else {
            *rv = th1ish_get_v_proxy( ie, self, key );
            rc = 0;
#if 0
            if(!*rv && ie->currentThis && (self != ie->currentThis)){
                th1ish_dump_val(key,"trying implicit lookup in 'this'");
                th1ish_get_v(ie, ie->currentThis, key, rv);
                if(*rv){
                    th1ish_dump_val(*rv,"via implicit lookup in 'this'");
                }
            }
#endif
        }
    }else{
#if 0
        if(ie->currentThis){
            /* An attempt an implicit lookup in 'this' */
            th1ish_get_v(ie, ie->currentThis, key, rv);
            if(!*rv) *rv = th1ish_var_get_v( ie, -1, key );
            else{
                th1ish_dump_val(*rv,"via implicit lookup in 'this'");
            }
        }else{
            *rv = th1ish_var_get_v( ie, -1, key );
        }
#else
        *rv = th1ish_var_get_v( ie, -1, key );
#endif
        rc = 0;
    }
    if(errMsg && (CWAL_RC_EXCEPTION!=rc)){
        assert(rc);
        rc = th1ish_toss(ie, rc, "%s", errMsg );
    }
    return rc;
}

int th1ish_get( th1ish_interp * ie, cwal_value * self,
                char const * key, cwal_size_t keyLen,
                cwal_value ** rv ){
    int rc = 0;
    if(!key) return CWAL_RC_MISUSE;
    else if(!keyLen && *key) keyLen = strlen(key);
    if((keyLen==9) && (0==memcmp( key, "prototype", 9 ))){
        *rv = th1ish_prototype_get( ie, self );
        rc = 0;
    }
    else if(self){
        *rv = th1ish_get_proxy( ie, self, key, keyLen );
        rc = 0;
    }else{
        *rv = th1ish_var_get( ie, -1, key, keyLen );
        rc = 0;
    }
    return rc;
}


/**
   Adds or subtracts lhs and rhs, depending on the value
   of 'add' (true==addition, false==subtraction).

   Returns 0 on success. Result is written to *rv.

   srcPos is the source code position of the operation,
   for error reporting purposes. It may be NULL, but
   don't complain if there is no proper error location
   info.
*/
int th1ish_values_addsub( th1ish_interp * ie,
                          char const * srcPos,
                          cwal_value * lhs, char add,
                          cwal_value * rhs,
                          cwal_value **rv );
/**
   Multiplies, divides, or modulos lhs and rhs, depending on the value
   of mode:

   mode: <0==divide, >0==multiply, 0==modulo

   codePos should point to the script code position being evaluated,
   or NULL if evaluating outside of script evaluation.

   Returns 0 on success. Result is written to *rv.
*/
static int th1ish_values_multdivmod( th1ish_interp * ie,
                                     char const *codePos,
                                     cwal_value * lhs,
                                     int mode,
                                     cwal_value * rhs,
                                     cwal_value **rv );

/**
   Implements these assignment operators:

   =, +=, -=, /=, *=, %=

   Returns 0 on success.
*/
static int th1ish_eval_assignment(th1ish_interp * ie,
                                  cwal_value * self,
                                  cwal_value * key,
                                  cwal_simple_token * t,
                                  cwal_simple_token ** next,
                                  cwal_value ** rv ){
    int rc;
    cwal_value * vL = 0;
    cwal_value * vR = 0;
    char const * srcPos = t->src.begin;
    int const op = t->ttype;
    /* cwal_value * nextThis = 0; */
    /* cwal_value * nextKey = 0; */
    /* cwal_value * prevThis = 0; */
    /* cwal_value * prevKey = 0; */
    /* start: */
    switch(op){
      case TT_OpAssign:
          break;
      case TT_OpShiftLeftEq:
      case TT_OpShiftRightEq:
      case TT_OpOrEq:
      case TT_OpXorEq:
      case TT_OpAndEq:
      case TT_OpPlusEq:
      case TT_OpMinusEq:
      case TT_OpMultEq:
      case TT_OpDivEq:
      case TT_OpModEq:
          if(TH1ISH_F_EVAL_NO_ASSIGN & ie->flags){
              return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                       "The '%.*s' assignment operator "
                                       "may not be chained.",
                                       (int)(t->src.end-t->src.begin),
                                       t->src.begin);
          }
          /* ie->flags |= TH1ISH_F_EVAL_NO_ASSIGN; */
          break;
      default:
          assert(!"cannot happen");
          return CWAL_SCR_CANNOT_CONSUME;
    }
    *next = t = TNEXT(t)
        /*
          Reminders to self:

          When we encounter an operator we treat following newlines
          as blanks, skipping over them.
        */;
    rc = th1ish_eval_0(ie, t, next, &vR)
        /* Reminder: we use eval_0 instead of eval_1 so that
           assignment chaining is processed recursively and so that we
           can better report errors when non-'=' assignment ops are
           chained. An iterative approach (like i'd prefer) does not
           mix well with ie->currentThis and friends out of the
           box.
        */
        ;
    if(rc || IE_SKIP_MODE) {
        goto end;
    }
    /* nextThis = ie->currentThis; */
    /* nextKey = ie->currentIdentKey; */
    assert(vR);

    switch(op){
      case TT_OpAssign:
          break;
#define GET \
        rc = th1ish_get_v(ie, self, key, &vL); \
        if(!vL) rc = th1ish_toss_pos(ie, srcPos, CWAL_RC_TYPE, \
                                     "Invalid left-hand side (type %s) " \
                                     "for '%.*s' assignment.",\
                                     cwal_value_type_name(self), 2, srcPos)
#define DOOP(MODE) \
        GET; \
        else if(!rc) rc = th1ish_values_addsub(ie, srcPos, vL, (MODE), vR, &vR)

      case TT_OpPlusEq:
          DOOP(1);
          break;
      case TT_OpMinusEq:
          DOOP(0);
          break;
#undef DOOP
#define DOOP(MODE)                           \
        GET; \
        else if(!rc) rc = th1ish_values_multdivmod(ie, srcPos, vL, (MODE), vR, &vR)
      case TT_OpMultEq:
          DOOP(1);
          break;
      case TT_OpDivEq:
          DOOP(-1);
          break;
      case TT_OpModEq:
          DOOP(0);
          break;
#undef DOOP
      /* FIXME: we're bypassing operator overloading here */
      case TT_OpShiftRightEq:
      case TT_OpShiftLeftEq:
      case TT_OpOrEq:
      case TT_OpXorEq:
      case TT_OpAndEq:{
          GET;
          else if(!rc){
              cwal_int_t const iL = cwal_value_get_integer(vL);
              cwal_int_t const iR = cwal_value_get_integer(vR);
              cwal_int_t v = 0;
              switch(op){
                case TT_OpShiftRightEq: v = iL>>iR; break;
                case TT_OpShiftLeftEq: v = iL<<iR; break;
                case TT_OpOrEq: v = iL|iR; break;
                case TT_OpXorEq: v = iL^iR; break;
                case TT_OpAndEq: v = iL&iR; break;
                default:
                    assert(!"not possible");
              }
              vR = cwal_new_integer(ie->e, v);
              if(!vR) rc = CWAL_RC_OOM;
              break;
          }
          break;
      }
#undef GET
      default:
          assert(!"cannot happen");
          rc = CWAL_SCR_CANNOT_CONSUME;
          goto end;
    }
    if(!rc){
        rc = th1ish_set_v( ie, t->src.begin,
                         self, key, vR );
        if(!rc) *rv = vR;
    }
    end:
    ie->flags &= ~TH1ISH_F_EVAL_NO_ASSIGN;
    return rc;
}

/**
   Returns non-0 (true) if t is a TT_Identifier token followed
   immediately (blanks do not count) by one of the assignment token
   types, else 0 (false).
*/
static char th1ish_is_assignment_to_ident(th1ish_interp * ie, cwal_simple_token * t){
    if(!t || (TT_Identifier!=t->ttype)) return 0;
    t = th1ish_skip(ie, t->right);
    switch(t ? t->ttype : 0){
      case TT_OpShiftLeftEq:
      case TT_OpShiftRightEq:
      case TT_OpOrEq:
      case TT_OpXorEq:
      case TT_OpAndEq:
      case TT_OpPlusEq:
      case TT_OpMinusEq:
      case TT_OpMultEq:
      case TT_OpDivEq:
      case TT_OpModEq:
      case TT_OpAssign:
          return 1;
      default:
          return 0;
    }
}

int th1ish_eval_0(th1ish_interp * ie, cwal_simple_token * t,
                  cwal_simple_token ** next, cwal_value ** rv ){
    int rc;
    char isIdentAssign;
    int const oldIdentPolicy = ie->identPolicy;
    int identPolicy;
    t = 1
        ? cwal_st_skip(t)
        : th1ish_skip(ie, t) /* prefered but currently breaks stuff */
        ;
    isIdentAssign = th1ish_is_assignment_to_ident(ie, t);
    identPolicy = isIdentAssign
        ? 1 /* Fetch this up-coming identifier in its string form */
        : oldIdentPolicy;
    *next = t;
    th1ish_reset_this(ie);
    ie->identPolicy = identPolicy;
    ++ie->sweepGuard;
    rc = th1ish_eval_1(ie, t, next, rv);
    th1ish_eval_check_exit(ie,rc, t, next);
    ie->identPolicy = oldIdentPolicy;
    if(rc) goto end;
    /*
      Check for assignments:
      
      identifier = ...
      obj.prop = ...
    */
    t = th1ish_skip(ie,*next);
    switch(t ? t->ttype : 0){
      case TT_OpShiftLeftEq:
      case TT_OpShiftRightEq:
      case TT_OpOrEq:
      case TT_OpXorEq:
      case TT_OpAndEq:
      case TT_OpPlusEq:
      case TT_OpMinusEq:
      case TT_OpMultEq:
      case TT_OpDivEq:
      case TT_OpModEq:
      case TT_OpAssign:
          *next = t;
          if(ie->currentIdentKey){
              /* assert(ie->currentThis); */
              /* th1ish_dump_val(ie->currentThis,"assignment LHS ie->currentThis"); */
              /* th1ish_dump_val(ie->currentIdentKey,"assignment LHS ie->currentIdentKey"); */
              rc = th1ish_eval_assignment(ie, ie->currentThis,
                                          ie->currentIdentKey,
                                          t, next, rv );
          }else if(cwal_value_is_string(*rv)
                   /*
                     We're cheating there for simplicity. This
                     allows the undocumented (mis?)feature:

                      var a = 7
                      'a' = 3
                      assert 3 === a
                   */){
              /* th1ish_dump_val(*rv,"assignment LHS string"); */
              rc = th1ish_eval_assignment(ie, NULL, *rv,
                                          t, next, rv );
          }else{
              rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE,
                                     "Invalid left-hand side for '%.*s' assignment.", \
                                     (t->src.end-t->src.begin), t->src.begin);
          }
      default:
          break;
    }
    
    end:
    --ie->sweepGuard;
    return rc;
}

int th1ish_eval_1(th1ish_interp * ie, cwal_simple_token * t,
                  cwal_simple_token ** next, cwal_value ** rv ){
    cwal_simple_token * n;
    cwal_value * vL = 0;
    cwal_value * vR = 0;
    int op = 0;
    int rc;
    int bL = 0;
    int bR = -1;
    switch(t ? t->ttype : 0){
      case TT_EOF:
          return 0/*CWAL_SCR_EOF*/;
      default:
          break;
    }
    rc = th1ish_eval_2(ie, t, next, rv);
    th1ish_eval_check_exit(ie,rc, t, next);
    if(rc || cwal_st_eof(*next)) return rc;
    assert((*next != t) || ie->expando.ar);
    the_beginning:
    vL = *rv;
    n = *next;
    /* Logical ops, comparisons, and bit-shifts. */
    switch(n->ttype){
      case TT_OpLogOr:
      case TT_OpLogAnd:
          bL = cwal_value_get_bool(vL);
          /* Fall through */
      case TT_CmpLt:
      case TT_CmpLe:
      case TT_CmpGt:
      case TT_CmpGe:
      case TT_CmpEq:
      case TT_CmpEqStrict:
      case TT_CmpNe:
      case TT_CmpNeStrict:{
          char doSkip = 0;
          /* get RHS */
          op = n->ttype;
          /* If we got a true LHS with an OR op,
             or a false LHS with an AND op, turn
             on skip-mode to implement short-circuit
             logic.
          */
          if(IE_SKIP_MODE ||
             (bL && (op==TT_OpLogOr))
             ||
             (!bL && (op==TT_OpLogAnd))){
                 bR = 0;
                 doSkip = 1;
                 ++ie->skipLevel;
          }
#if 1
          rc = th1ish_eval_2( ie, TNEXT(n), &n, rv );
#else
          switch(op){
            case TT_CmpLt:
            case TT_CmpLe:
            case TT_CmpGt:
            case TT_CmpGe:
                rc = th1ish_eval_1( ie, TNEXT(n), &n, rv );
                break;
            default:
                rc = th1ish_eval_2( ie, TNEXT(n), &n, rv )
                    /* th1ish_eval_1( ie, TNEXT(n), &n, rv ) */
                    /**
                       Experimentally using eval_1() so that:
                       (1==0 || 2<3) evaluates properly.

                       Hmm. But that breaks:

                       7 === (1==3 || 7)
                    */
              ;
              break;
          }
#endif
          *next = n;
          if(doSkip){
              --ie->skipLevel;
          }
          if(rc) return rc;
          vR = *rv;
          /*if( cwal_st_eof(n) ) return 0;*/
          break /* deal with the op down below */;
      }
      default:
          /* there was no comparison */
          return 0;
    }
    if(rc) return rc;
    else if(IE_SKIP_MODE) goto tail_check;
    switch(op){
      case TT_OpLogAnd:
      case TT_OpLogOr:{
          bL = cwal_value_get_bool(vL);
          if(-1==bR) bR = cwal_value_get_bool(vR);
          if(TT_OpLogOr == op){
              if(bL) *rv = vL;
#if 0
              else if(bR) *rv = vR;
              else *rv = cwal_new_bool(0);
#endif
              else *rv = vR;
              /*th1ish_dump_val(vL,"Logical || LHS");
                th1ish_dump_val(vR,"Logical || RHS");
              */
          }else{
              *rv = cwal_new_bool(bL && bR);
          }
          goto tail_check;
          break;
      }
          /**
             The strict === and !== operators must not invoke the
             value comparison if the types are not identical.
          */
      case TT_CmpEqStrict:
      case TT_CmpNeStrict:
          if(vL == vR){
              rc = -3;
          }
          else if(cwal_value_type_id(vL) != cwal_value_type_id(vR)){
              rc = -2;
          }
          break;
      default: break;
    }
    if(-3 == rc){ /* a===a, a!==a */
        rc = (TT_CmpEqStrict==op) ? 1 : 0/*invert for !==*/;
        /*fall through*/
    }
    if(-2 == rc){ /* x===y or x!==y of differing types */
        rc = (TT_CmpEqStrict==op) ? 0 : 1/*invert for !==*/;
    }
    else {
        rc = cwal_value_compare(vL, vR);
#if 0
        MARKER("compare rc=%d\n", rc);
        th1ish_dump_val(vL,"LHS");
        th1ish_dump_val(vR,"RHS");
#endif
        switch(op){
          case TT_CmpLt: rc = rc < 0; break;
          case TT_CmpLe: rc = rc <= 0; break;
          case TT_CmpGt: rc = rc > 0; break;
          case TT_CmpGe: rc = rc >= 0; break;
          case TT_CmpEqStrict: rc = 0==rc; break;
          case TT_CmpEq:
              /*
                Reminder: we now have the problem that:

                  assert null != undefined
                  assert !(null == undefined)

                Which is somewhat non-intuitive/non-JavaScripty.

                Not sure how to best handle that without special-casing
                null/undefined.
              */
              rc = 0==rc;
              break;
          case TT_CmpNeStrict: rc = 0!=rc; break;
          case TT_CmpNe: rc = 0!=rc; break;
          default:
              assert(!"CANNOT HAPPEN");
              return CWAL_RC_CANNOT_HAPPEN;
            }
    }
    *rv =
        /*(rc<0) ? vL : ((rc>0) ? vR : vL)*/
        cwal_new_bool( rc ? 1 : 0 )
        /* reminder: does not alloc (cannot fail) */
        ;
    rc = 0;
    tail_check:
    if(!rc){
        n = th1ish_skip(ie,*next);
        switch(n ? n->ttype : 0){
          case TT_CmpLt:
          case TT_CmpLe:
          case TT_CmpGt:
          case TT_CmpGe:
          case TT_CmpEqStrict:
          case TT_CmpEq:
          case TT_CmpNeStrict:
          case TT_CmpNe:
          case TT_OpLogAnd:
          case TT_OpLogOr:
              /* MARKER("Continuing &&/||...\n"); */
              vL = *rv;
              t = *next = n;
              goto the_beginning;
          default: break;
        }
    }
    return rc;
}

/**
   Binary/unary operator overloading handling. Checks lhs to see
   if it has a Function property named opName (which is
   opLen bytes long). If so, it calls that function
   like:

   [f.apply lhs [lhs,rhs]] // SEE BELOW!!!

   If lhs has no such function it then checks rhs in same way IF
   tryRhsOp is true.

   If it finds a function, it calls that function and sets *rc to its
   return value, then returns true (non-0).

   On any error (running the function or fetching the operator) then
   *theRc is set to the error value (and this function may return
   either true or false in that case).

   Note that if the lhs does not contain the operator and rhs does
   then the call is made slightly differently:

   [f.call rhs lhs rhs]

   If rhs is NULL then it is treated like a nullary function (unary
   operator):

   [f.call lhs]

   i.e. the argument order is the same but the 'this' is different.
*/
static char th1ish_operator_proxy( th1ish_interp * ie,
                                   char const * srcPos,
                                   char const * opName,
                                   cwal_size_t opLen,
                                   cwal_value * lhs,
                                   cwal_value * rhs,
                                   char tryRhsOp,
                                   cwal_value **rv,
                                   int * theRc ){
    cwal_value * fCheck = lhs;
    cwal_value * fv = 0;
    int rc;
    assert(opLen);
    opCheck:
    /* Check for member function operator*, operator%, or
       operator/.
    */
    rc = th1ish_get( ie, fCheck, opName, opLen, &fv );
    if(rc){
        if((CWAL_RC_NOT_FOUND!=rc) || (tryRhsOp && (fCheck==rhs))){
            *theRc = rc;
            return 0;
        }
        /*else fall through and through and try with rhs...*/
    }
    else if(fv && cwal_value_is_function(fv)){
        th1ish_stack_entry sta = th1ish_stack_entry_empty;
        cwal_value * argv[2] = {0,0};
        argv[0] = lhs;
        argv[1] = rhs;
        if(srcPos) th1ish_strace_push_pos( ie, srcPos, &sta );
        *theRc = cwal_function_call( cwal_value_get_function(fv),
                                     fCheck, rv, rhs ? 2 : 0, argv );
        if(srcPos) th1ish_strace_pop( ie );
        return 1;
    }
    if(rhs && tryRhsOp && (lhs==fCheck) && (lhs!=rhs)){
        fCheck = rhs;
        goto opCheck;
    }
    return 0;
}

int th1ish_values_addsub( th1ish_interp * ie,
                          char const * srcPos,
                          cwal_value * lhs,
                          char doAdd,
                          cwal_value * rhs,
                          cwal_value **rv ){
    switch(cwal_value_type_id(lhs)){
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:
          break;
      default:{
          static cwal_size_t const opLen = 9;
          char const * opStr = doAdd ? "operator+" : "operator-";
          int rc = 0;
          /* Check for operator overloads... */
          if(th1ish_operator_proxy( ie, srcPos, opStr, opLen,
                                    lhs, rhs,
                                    0 /* addition is not (necessarily) commutative
                                         for abitrary types (only numbers) */,
                                    rv, &rc )
             || rc) return rc;
          /* else fall through... */
          break;
      }
    }
    {
        const cwal_double_t iL = cwal_value_get_double( lhs );
        if(doAdd && (iL==0.0)) *rv = rhs;
        else {
            cwal_double_t const iR = cwal_value_get_double( rhs );
            if(doAdd && (iR==0.0)) *rv = lhs;
            else{
                const cwal_double_t res = doAdd ? (iL+iR) : (iL-iR);
                *rv = cwal_value_is_double(lhs)
                    ? cwal_new_double(ie->e, res )
                    : cwal_new_integer(ie->e, (cwal_int_t)res )
                    ;
            }
        }
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

int th1ish_values_multdivmod( th1ish_interp * ie,
                              char const *codePos,
                              cwal_value * lhs,
                              int mode,
                              cwal_value * rhs,
                              cwal_value **rv ){
    int rc = 0;
    char buf[10] = {'o','p','e','r','a','t','o','r','X',0};
    buf[8] = (mode<0) ? '/' : ((mode>0) ? '*' : '%');
    switch(cwal_value_type_id(lhs)){
      case CWAL_TYPE_UNDEF:
      case CWAL_TYPE_NULL:
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_INTEGER:
      case CWAL_TYPE_DOUBLE:
      case CWAL_TYPE_STRING:
          break;
      default:
          /* Check for operator overloads... */
          if(th1ish_operator_proxy(ie, codePos, buf, 9, lhs, rhs,
                                   (mode>0) /* only multiplication is
                                               commutative here */,
                                   rv, &rc)
             || rc) return rc;
          /* else fall through... */
    }
    if(mode<0){ /* Division */
        cwal_double_t const iR = cwal_value_get_double( rhs );
        if(0.0==iR){
            char const * msg = "Divide by zero.";
            return codePos
                ? th1ish_toss_pos(ie, codePos, CWAL_SCR_DIV_BY_ZERO, msg)
                : th1ish_toss(ie, CWAL_SCR_DIV_BY_ZERO, msg);
        }else{
            cwal_double_t const iL = cwal_value_get_double( lhs );
            /* Optimizations:
               if 0==lhs, return lhs
               if 1==rhs, return lhs.
            */
            if((0.0==iL) && cwal_value_is_number(lhs)) *rv = lhs;
            else if(1.0==iR) *rv = lhs /* optimization */;
            else {
                cwal_double_t const res = iL / iR;
                *rv = cwal_value_is_double(lhs)
                    ? cwal_new_double(ie->e, res )
                    : cwal_new_integer(ie->e, (cwal_int_t)res)
                    ;
            }
        }
    }else{
        if(0==mode){
            /* Modulo */
            cwal_int_t const iR = cwal_value_get_integer( rhs );
            if(0==iR){
                char const * msg = "Modulo by zero.";
                return codePos
                    ? th1ish_toss_pos(ie, codePos, CWAL_SCR_DIV_BY_ZERO, msg)
                    : th1ish_toss(ie, CWAL_SCR_DIV_BY_ZERO, msg);
            }else{
                cwal_int_t const iL = cwal_value_get_integer( lhs );
                /* Optimization: if 1==lhs && 1!=rhs, return lhs.
                   If the rhs is 1 it is already optimized
                   b/c cwal doesn't allocate for value (-1,0,1).
                */
                *rv = ((1==iL) && (iR!=1))
                    ? lhs
                    : cwal_new_integer(ie->e, iL % iR);
            }
        }else{
            /* Multiplication */
            cwal_double_t const iL = cwal_value_get_double( lhs );
            cwal_double_t const iR = cwal_value_get_double( rhs );
            /* Optimizations:
               if 1.0==lhs, return rhs, and vice versa.
               if either is 0.0, return that value.
            */
            if(1.0==iR) *rv = lhs;
            else if((0.0==iL) && cwal_value_is_number(lhs)) *rv = lhs;
            else if(1.0==iL) *rv = rhs;
            else if((0.0==iR) && cwal_value_is_number(rhs)) *rv = rhs;
            else{
                cwal_double_t const res = iL * iR;
                *rv = cwal_value_is_double(lhs)
                    ? cwal_new_double(ie->e, res )
                    : cwal_new_integer(ie->e, (cwal_int_t)res)
                    ;
            }
        }
    }
    return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Evaluator for binary addition, subtraction, and bitshift operators.
*/
static int th1ish_eval_binary_addsub_shift(th1ish_interp * ie,
                                           cwal_value * vL,
                                           cwal_simple_token * t,
                                           cwal_simple_token ** next,
                                           cwal_value ** rv ){
    int rc;
    cwal_value * vR = 0;
    int ttype;
    char const * srcPos = t->src.begin;
    start:
    ttype = t->ttype;
    switch(ttype){
      case TT_OpShiftRight:
      case TT_OpShiftLeft:
      case TT_OpAdd:
      case TT_OpSubtract:
          break;
      default:
          assert(!"CANNOT HAPPEN");
          return CWAL_SCR_CANNOT_CONSUME;
    }
    *next = t = TNEXT(t)
        /*
          Reminders to self:

          When we encounter an operator we treat following newlines
          as blanks, skipping over them.
        */;
    rc = th1ish_eval_3(ie, t, next, &vR);
    if(rc) return rc;
    if(!IE_SKIP_MODE){
        switch(ttype){
          case TT_OpShiftRight:
          case TT_OpShiftLeft:{
              char const * opStr = (TT_OpShiftLeft==ttype)
                  ? "operator<<" : "operator>>";
              if(!th1ish_operator_proxy(ie, srcPos,
                                        opStr, 10 /*opStr len*/,
                                        vL, vR,
                                        0, rv, &rc) && !rc){
                  cwal_int_t const iL = cwal_value_get_integer(vL);
                  cwal_int_t const iR = cwal_value_get_integer(vR);
                  cwal_int_t const v = (TT_OpShiftLeft==ttype)
                      ? (iL<<iR) : (iL>>iR);
                  *rv = cwal_new_integer(ie->e, v);
                  rc = *rv ? 0 : CWAL_RC_OOM;
              }
              break;
          }
          case TT_OpAdd:
          case TT_OpSubtract:
              rc = th1ish_values_addsub(ie, srcPos,
                                        vL, (TT_OpAdd==ttype), vR, rv);
              break;
          default:
              break;
        }
    }
    /* dump_token(t,"Checking for trailing + - << >>"); */
    if(!rc){
        t = *next = th1ish_skip(ie,*next);
        switch(t ? t->ttype : 0){
          case TT_OpShiftRight:
          case TT_OpShiftLeft:
          case TT_OpAdd:
          case TT_OpSubtract:
              vL = *rv;
              goto start;
              break;
          default:
              break;
        }
    }
    return rc;
}

/**
   Evaluator for binary multiplication, division, and modulus.
*/
static int th1ish_eval_binary_multdivmod(th1ish_interp * ie,
                                         cwal_value * vL,
                                         cwal_simple_token * t,
                                         cwal_simple_token ** next,
                                         cwal_value ** rv );

int th1ish_eval_2(th1ish_interp * ie, cwal_simple_token * t,
                  cwal_simple_token ** next, cwal_value ** rv ){
    int rc;
    cwal_value * vL = 0;
    cwal_simple_token * n = 0;
    th1ish_reset_this(ie);
    rc = th1ish_eval_3(ie, t, next, rv);
    th1ish_eval_check_exit(ie,rc, t, next);
    if(rc || *next==t) return rc;
    vL = *rv ? *rv : cwal_value_undefined();
    n = *next = th1ish_skip(ie,*next);
    switch(n ? n->ttype : 0){
      case TT_OpShiftLeft:
      case TT_OpShiftRight:
      case TT_OpAdd:
      case TT_OpSubtract:
          rc = th1ish_eval_binary_addsub_shift( ie, vL, n, next, rv );
          break;
      default:
          break;
    }
    return rc;
}


static int th1ish_eval_binary_multdivmod(th1ish_interp * ie,
                                         cwal_value * vL,
                                         cwal_simple_token * t,
                                         cwal_simple_token ** next,
                                         cwal_value ** rv ){
    int rc;
    cwal_value * vR = 0;
    int ttype;
    start:
    ttype = t->ttype;
    switch(ttype){
      case TT_OpMultiply:
      case TT_OpDivide:
      case TT_OpModulo:
          break;
      default:
          assert(!"CANNOT HAPPEN");
          return CWAL_SCR_CANNOT_CONSUME;
    }
    *next = t = TNEXT(t)
        /* Reminder: we treat newlines in this run as blanks */
        ;
    rc = th1ish_eval_4(ie, t, next, &vR);
    if(rc) return rc;
    else if(!IE_SKIP_MODE){
        rc = th1ish_values_multdivmod(ie, t->src.begin, vL,
                                      ((TT_OpModulo==ttype)
                                       ? 0
                                       : ((TT_OpMultiply==ttype)
                                          ? 1
                                          : -1)),
                                      vR, rv);
        if(rc) return rc;
    }
    else if(!*rv) *rv = cwal_value_undefined();
    t = th1ish_skip(ie,*next);
    if(!rc) switch(t ? t->ttype : 0){
      case TT_OpDivide:
      case TT_OpModulo:
      case TT_OpMultiply:
          /*rc = th1ish_eval_binary_multdivmod( ie, *rv, t, next, rv );*/
          vL = *rv;
          goto start;
          break;
      default:
          break;
    }
    return rc;
}

/**
   Implements operators (|, &, <<, >>).

   vL is the left hand side value, t is the operator.

   Result is saved in *rv. *next will be set to the next token to
   evaluate.

   Returns 0 on success.
*/
static int th1ish_eval_binary_and_or(th1ish_interp * ie,
                                     cwal_value * vL,
                                     cwal_simple_token * t,
                                     cwal_simple_token ** next,
                                     cwal_value ** rv ){
    int rc;
    cwal_value * vR = 0;
    cwal_int_t iL, iR, res;
    int ttype;
    start:
    ttype = t->ttype;
    switch(ttype){
      case TT_ChCaret:
      case TT_ChPipe:
      case TT_ChAmp:
          break;
      default:
          assert(!"CANNOT HAPPEN");
          return CWAL_SCR_CANNOT_CONSUME;
    }
    *next = t = TNEXT(t)
        /*
          Reminders to self:
          
          When we encounter an operator we treat following newlines
          as blanks, skipping over them.
        */;
    rc = th1ish_eval_3(ie, t, next, &vR);
    if(rc) {
        return rc;
    }
    if(!IE_SKIP_MODE){
        assert(vR);
        iL = cwal_value_get_integer( vL );
        iR = cwal_value_get_integer( vR );
        switch(ttype){
          case TT_ChCaret: res = iL^iR; break;
          case TT_ChPipe: res = iL|iR; break;
          case TT_ChAmp: res = iL&iR; break;
          default:
              assert(!"cannot happen");
        }
        *rv = cwal_new_integer(ie->e, (cwal_int_t)res );
        if(!*rv) rc = CWAL_RC_OOM;
    }
    /* dump_token(t,"Checking for trailing | or &"); */
    if(!rc){
        t = *next = th1ish_skip(ie,*next);
        switch(t ? t->ttype : 0){
          case TT_ChCaret:
          case TT_ChPipe:
          case TT_ChAmp:
              vL = *rv;
              goto start;
              break;
#if 0
          case TT_ChQuestion:
              rc = th1ish_eval_ternary_if( ie, *rv, t, next, rv );
              break;
#endif
          default:
              break;
        }
    }
    return rc;
}


int th1ish_eval_3(th1ish_interp * ie, cwal_simple_token * t,
                  cwal_simple_token ** next, cwal_value ** rv ){
    int rc;
    cwal_value * vL = 0;
    cwal_simple_token * n = 0;
    th1ish_reset_this(ie);
    rc = th1ish_eval_4(ie, t, next, rv);
    th1ish_eval_check_exit(ie,rc, t, next);
    if(rc) return rc;
    vL = *rv ? *rv : cwal_value_undefined();
    n = *next; /* cwal_st_skip(*next); */
    switch(n ? n->ttype : 0){
      case TT_ChCaret:
      case TT_ChPipe:
      case TT_ChAmp:
          rc = th1ish_eval_binary_and_or( ie, vL, n, next, rv );
          break;
      case TT_OpDivide:
      case TT_OpModulo:
      case TT_OpMultiply:
          rc = th1ish_eval_binary_multdivmod( ie, vL, n, next, rv );
          break;
      default:
          break;
    }
#if 1
    /*
      Reminder to self: we have to catch the '?' as a "tail check", instead
      of having it somewhere up the eval chain, because if we do not then:

      [print true ? 1 : 2]

      gets 2 arguments: (true, 1)
    */
    if(!rc){
        t = cwal_st_skip(*next);
        switch(t ? t->ttype : 0){
          case TT_ChQuestion:
              rc = th1ish_eval_ternary_if( ie, *rv, t, next, rv );
              break;
          default:
              break;
        }
    }
#endif
    return rc;
}

int th1ish_eval_4(th1ish_interp * ie, cwal_simple_token * t,
                  cwal_simple_token ** next, cwal_value ** rv ){

    enum { OP_UMINUS = -1 /* - */, OP_UPLUS = 1 /* + */,
           /* ACHTUNG: -1, +1 are magic values below */
           OP_BNEG /* ~ */,
           OP_INCR /* ++ */,
           OP_DECR /* -- */
    };
    int rc;
    char const * opString = NULL;
    cwal_size_t opStrLen = 0;
    int const op = t->ttype;
    /* check for unary +/- or logical NOT */
    int prefixOp = 0;
    unsigned doNot = 0 /* "There is no try." */;
    assert(!ie->pendingPrefixOp && "Should get set to 0 by this point");
    if(ie->pendingPrefixOp){
        return th1ish_toss_token( ie, t, CWAL_RC_RANGE,
                                  "Should not have hit this while "
                                  "a prefix op is pending.");
    }
    switch(op){
      case TT_ChTilde:
          prefixOp = OP_BNEG;
          t = *next = t->right;
          opString = "operator~";
          opStrLen = 9;
          break;
      case TT_OpAdd:
          prefixOp = OP_UPLUS;
          t = *next = t->right;
          opString = "operator+unary";
          opStrLen = 14;
          break;
      case TT_OpSubtract:
          prefixOp = OP_UMINUS;
          opString = "operator-unary";
          opStrLen = 14;
          t = *next = t->right;
          break;
#if 1
      case TT_OpIncr:
          prefixOp = OP_INCR;
          ie->pendingPrefixOp = op;
          /* opString = "operator++prefix"; */
          /* opStrLen = 16; */
          t = *next = t->right;
          break;
      case TT_OpDecr:
          prefixOp = OP_DECR;
          ie->pendingPrefixOp = op;
          /* opString = "operator--prefix"; */
          /* opStrLen = 16; */
          t = *next = t->right;
          break;
#endif
      case TT_ChExcl:{
          /* Process a series of '!' to avoid a downstream
             parsing error if we expect recursion to handle
             the second one (it doesn't).
          */
          cwal_simple_token * mv = t;
          for( ; mv && (TT_ChExcl==mv->ttype);
               mv = th1ish_skip(ie,mv->right)){
              ++doNot;
          }
          if(cwal_st_eof(mv)){
              return th1ish_toss_token(ie, t, CWAL_SCR_UNEXPECTED_EOF,
                                       "Unexpected EOF after '!'");
          }
          t = mv;
          *next = t;
          break;
      }
      default: break;
    }

    /* for prefix ++/--, we need to know if the next
       part is an Object.Property pair or an identifier.
       We cannot know that here without switching parse modes
       and inspecting it. What we "could" do, however...
       is set a flag which tells eval5 that a prefix op is
       pending, and let them DTRT there. They need a proxy
       op func to call. Something like:

       int doPrefixOp( interp, opType, theThis, theIdentifier, rv )
     */
    th1ish_reset_this(ie);
    rc = th1ish_eval_5(ie, t, next, rv);
    if(prefixOp){
        ie->pendingPrefixOp = 0
            /* Kind-of-a-bug: i should not have to set this to 0 here.
               i'd like for a downstream bit to catch if it was not
               reset via eval_5.
            */;
    }
    th1ish_eval_check_exit(ie,rc, t, next);
    if(rc) return rc;
    else if(prefixOp && !IE_SKIP_MODE){
        /* Apply unary +/-/~ */
        char overridden = 0;
        cwal_type_id const rvTid = cwal_value_type_id(*rv);
        switch(rvTid){
            /* Do not allow overloading of this op on these types: */
          case CWAL_TYPE_UNDEF:
          case CWAL_TYPE_NULL:
          case CWAL_TYPE_BOOL:
          case CWAL_TYPE_INTEGER:
          case CWAL_TYPE_DOUBLE:
          case CWAL_TYPE_STRING:
              break;
          default:
              /* Check for overloaded operator. */
              if(!opString) break;
              assert(opString && opStrLen);
              overridden =
                  th1ish_operator_proxy( ie, t->src.begin,
                                         opString, opStrLen,
                                         *rv, NULL, 0, rv, &rc);
              /* if(overridden) ie->pendingPrefixOp = 0; */
              break;
        }
        while(!overridden && !rc){
            /* Apply prefix ++/-- */
#if 0
            if(ie->pendingPrefixOp){
                /* ie->pendingPrefixOp is set to 0 by the types which handle it. */
                rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, "Unhandled prefix operator.");
                break;
            }
#endif
            switch(prefixOp){
              case OP_BNEG:
                  *rv = cwal_new_integer(ie->e,
                                         (cwal_int_t)~cwal_value_get_integer( *rv ) );
                  break;
              case OP_UPLUS:
              case OP_UMINUS:{
                  /* Apply numeric unary +/- */
                  /*
                    Careful:
                    
                    +"1.2"
                    
                    Results in 0 if we don't make an extra check for doubles
                    because cwal_value_get_integer() doesn't like "1.2".
                    
                    But i'm not sure how to fix that without special-casing
                    it.
                  */
                  cwal_double_t v = cwal_value_get_double( *rv );
                  if( cwal_value_is_double(*rv)
                      || (v && 
                          /* has a non-0 remainder */
                          (cwal_int_t)((v-(cwal_int_t)v)*10000001))
                      ){
                      /* Seems to be a real double or double-like string. */
                      *rv = cwal_new_double( ie->e, v * prefixOp );
                  }else{
                      /* Let's assume it's an integer */
                      *rv = cwal_new_integer( ie->e, (cwal_int_t)v * prefixOp );
                  }
                  break;
              }
              case OP_INCR:
              case OP_DECR:
                  /* handled elsewhere */
                  assert(!ie->pendingPrefixOp && "Expected this to be cleared by now.");
                  break;
              default:{
                  assert(!"Not possible");
                  return CWAL_RC_FATAL;
              }
            }
            if(!*rv) rc = CWAL_RC_OOM;
            break;
        } /* (no longer) pending prefix ops */
        assert(!ie->pendingPrefixOp);
    }
    else if(doNot){
        /* Workaround for a parsing error with "!!",
           since the logical ! is handled at a level
           which we won't wrap around to on the
           RHS token.
        */
        char b = cwal_value_get_bool(*rv);
        for( ; doNot> 0; --doNot ) b = !b;
        if(!IE_SKIP_MODE){
            *rv = cwal_new_bool( b );
        }
    }
    return rc;
}

/**
   Evaluates t, which must be a (...) group. Each entry in the list
   gets added to rvList. On input *rvLen must be the maximum length of
   the rvList array. On output it will be the number of items populated
   in rvList. If the list is too short an error is triggered.

   To only capture the last value in the list, pass a NULL rvLen
   and a non-NULL rvList with room for one element. On success,
   rvList[0] will contain the last evaluated result value.
   
   Returns 0 on success.
   
   Advances *next to the token in the chain following the closing ')'
   (or where an error occurred during parsing of a contained
   expression).
*/
static int th1ish_eval_list( th1ish_interp * ie, cwal_simple_token * t,
                             cwal_simple_token ** next,
                             uint16_t * rvLen,
                             cwal_value ** rvList );

/**
   Handles obj.prop... dereferencing (iteratively over any number of
   dots).

   Each left-hand-side of a dot must resolve to a container type. Each
   right-hand side may be an arbitrary expression which is a key in
   the container on the left. The resulting value may be of an
   arbitrary type.

   On success it sets ie->currentThis to the "this" object from the
   right-most dot dereference, ie->currentIdentKey to the key, and
   updates rv to the referenced value. e.g. in (obj.foo.bar),
   "this"===foo, the key is "bar", and the rv will be whatever is in
   that property.

   It behaves slightly differently than scope lookup: a missing
   property is returned as the undefined value, as opposed to throwing
   an unknown identifier exception. If an undefined value is followed
   by another dot then it will throw (because it cannot dereference
   the undefined value).

   Examples:

   OK: obj.foo.bar
   Eqivalent: obj.("foo").bar
   Eqivalent: obj.(1,2,3,"foo").bar
   Eqivalent: obj.([proc f {} {return "foo"}]).bar
   
   Exception: obj.someUndefinedThing.bar

   The property "prototype" requires special handling, and effectively
   resolves to the result of cwal_value_prototype_get(ie->currentThis).
*/
static int th1ish_eval_dot_deref( th1ish_interp * ie,
                                  cwal_value * self,
                                  cwal_simple_token * t,
                                  cwal_simple_token ** next,
                                  cwal_value ** rv );

int th1ish_eval_5(th1ish_interp * ie, cwal_simple_token * t,
                  cwal_simple_token ** next, cwal_value ** rv ){

    int rc = 0;
    cwal_simple_token * origin = t;
    char doDollar = 0;
    char const skipDot = (TH1ISH_F_EVAL_NO_DOT & ie->flags);
    if(cwal_st_eof(t)) return CWAL_SCR_EOF;
    ie->flags &= ~TH1ISH_F_EVAL_NO_DOT
        /* Required so that this embedded call will work:

        var a array [1,2,3]
        set a.([a.length]-1) 'TheEnd' // here

        Maybe we really need a counter instead of a flag?
        */
        ;
#define TH1ISH_ENABLE_EXPANDO 0
#if TH1ISH_ENABLE_EXPANDO
    /*
      @expando support:

      expand @ARRAY "inline"
    */
    next_expando:
    if(ie->expando.ar){
        cwal_size_t n, p;
        assert(ie->expando.next);
        assert(ie->expando.atPos);
        n = cwal_array_length_get(ie->expando.ar);
        p = ie->expando.pos++;
        if(p<n){
            *rv = IE_SKIP_MODE
                ? cwal_value_undefined()
                : cwal_array_get(ie->expando.ar, p);
            if(!*rv) *rv = cwal_value_undefined();
            if(ie->expando.pos==n){
                t = *next = ie->expando.returnTo;
                ie->expando.next = NULL;
                ie->expando.returnTo = NULL;
                ie->expando.atPos = NULL;
                ie->expando.ar = NULL;
                ie->expando.pos = 0;
                /* cwal_dump_v(*rv,"Re-set expando"); */
                /* dump_token(t,"Next token after expando"); */
            }
            else {
                /**
                  Fudge the jump-back value so we end up here again.
                */
                *next = ie->expando.atPos;
            }
            /* cwal_dump_v(*rv, "Expando"); */
            return 0;
        }
        else {
            t = *next = th1ish_skip(ie,ie->expando.returnTo);
            ie->expando.next = NULL;
            ie->expando.returnTo = NULL;
            ie->expando.atPos = NULL;
            ie->expando.ar = NULL;
            ie->expando.pos = 0;
            /* dump_token(t, "Re-setting for empty expando"); */
            /* cwal_dump_v(*rv, "rv"); */
            assert(t);
            rc = 0;
            switch(t ? t->ttype : 0){
                /**
                   Problem: empty list at the end of an expression
                   will evaluate to [] instead of "nothing", and our
                   engine has no way of dealing with "nothing" except
                   for EOF. We need a way of dealing with/enabling
                   this.
                */
              case TT_EOF:
                  /* Avoids a "cannot consume" error but evaluates
                     as an empty array :(. */
                  return CWAL_SCR_EOF;
              default:
                  break;
            }
            /* fall through ... */
            /**
               NASTY BUG:

               [@array] inside a sub-parse will end evaluation of the whole
               token chain because ie->expando.returnTo is point to (@array)->right,
               which is the EOF of that sub-parse. This does not affect
               [foo @array] because that is handled in
            */
        }
    }

    if(TT_ChAt==t->ttype){
        /**
           Treat @ARRAY as an "inlined list"...
        */
        cwal_value * av = NULL;
        cwal_array * ar;
        if(ie->expando.atPos && (ie->expando.atPos!=t)){
            return th1ish_toss_token(ie, t, CWAL_RC_MISUSE,
                                     "Nested @-expansion is not supported (or possible?).");
        }
        if(!ie->expando.next){
            cwal_simple_token * atPos = t;
            t = *next = TNEXT(t);
            rc = th1ish_eval_0(ie, t, next, &av);
            if(rc) return rc;
            if(!av || !(ar=th1ish_value_array_part(ie, av))){
                return th1ish_toss_token(ie, t, CWAL_RC_TYPE,
                                         "Expecting Array after '@'.");
            }else{
                ie->expando.atPos = atPos;
                ie->expando.next = *next;
                ie->expando.returnTo = *next;
                ie->expando.ar = ar;
            }
            *rv = av;
        }
        goto next_expando;
    }
    else
#endif
/* TH1ISH_ENABLE_EXPANDO */
    if(TT_ChDollar==t->ttype){
        *next = t = th1ish_skip(ie,t->right)
            /* We really want TNEXT(t) here, but
               that breaks with:

                 $foo // no args
                 blah

               It calls it as [foo blah ...]
            */
            ;
        /* dump_token(t,"Got dollar here"); */
        th1ish_reset_this(ie);
        doDollar = 1;
    }
    switch(t ? t->ttype : 0){
      case TT_ChSemicolon:
          rc = 0;
          *next = t->right;
          /* Leave *rv as is */
          break;
      case TT_ChParenOpen:{
          cwal_value * argv = 0;
          rc = th1ish_eval_list(ie, t, next, NULL, &argv );
          if(rc) return rc;
          /* assert(argv); */
          *rv = argv ? argv : cwal_value_undefined()
              /* Are we just hiding a bug here? */;
        break;
      }
      default:
          rc = th1ish_eval_atom(ie, t, next, rv);
          th1ish_eval_check_exit(ie,rc, t, next);
          break;
    }
    if(!skipDot && !rc){
        /* Check for '.' dereference... */
        t = cwal_st_skip(*next);
        if(th1ish_token_is_dot_op(t)){
            cwal_value * self = *rv;
            /*breaks here: th1ish_this_save(ie); but why?
              b/c assignment to OBJ.PROP expects the 'this'
              value fetched here.
            */
            /* th1ish_dump_val(self,"Running dot deref on this object"); */
            rc = th1ish_eval_dot_deref( ie, self, t, next, rv );
            th1ish_eval_check_exit(ie,rc, t, next);
            /* th1ish_this_restore(ie); */
            /* th1ish_dump_val(ie->currentThis,"ie->currentThis after dot-deref"); */
            /* dump_token(*next,"next token after dot-deref"); */
            if(!rc){
                /* assert(ie->currentThis); */
            }
        }
    }
    /* if(doDollar) dump_token(*next,"After eval-1, before $ deref"); */
    if(!rc && doDollar){ /* Do $func ... calls. */
        /*
          $functionCall

          i don't like this all that much, but it's better than having
          to wrap all calls in [...].
        */
        cwal_function * func = 0;
        if(IE_SKIP_MODE || (func = cwal_value_get_function(*rv))){
            t = th1ish_skip(ie,*next);
            /* th1ish_dump_val(ie->currentThis,"ie->currentThis at $call() point"); */
            /* dump_token(t,"collect+calling from this token"); */
            rc =
                /* For proc'd functions this tokenizes the func params declared
                   at the proc point if argc==0! 
                 */
                th1ish_eval_func_collect_call( ie, t, next, rv,
                                               ie->currentThis,
                                               func, 0 )
                ;
            th1ish_eval_check_exit(ie,rc, t, next);
            th1ish_reset_this( ie );
        }
        else{
            rc = th1ish_toss_token(ie, origin, CWAL_SCR_SYNTAX,
                                   "Expecting function after '$'.");
        }
    }
    while(!rc && (ie->flags & TH1ISH_F_CONVENTIONAL_CALL_OP)){
        /* Check for func() calls, possibly chained. */
        cwal_function * func = 0;
        if(IE_SKIP_MODE || (func = cwal_value_get_function(*rv))){
            cwal_value * xrv = cwal_value_undefined();
#if 0
            if(IE_SKIP_MODE) *rv = cwal_value_undefined()
                /* Workaround for an uninitialized stack-living
                   (*rv) somewhere up the call stack :/. Thanks
                   to valgrind for noticing.
                */;
#endif
            t = th1ish_skip(ie,*next);
            while(cwal_st_isa(t, TT_ChParenOpen)){
                if(!IE_SKIP_MODE && !func){ /* only true on 2nd+ chained call */
                    func = cwal_value_get_function(*rv);
                    if(!func){
                        break;
                        /*
                          Alternately, we could fall through into the
                          func call so it will fail with "cannot call
                          a non-function", but that breaks on some
                          weird (but legal) expressions:

                          $print 1 2; print(3,4)
                          ($print 1 2) print(3,4)

                          The 2nd line looks like the start of a
                          chained call, but print(3,4) evals to
                          undefined.
                        */
                    }
                }
                rc =
                    th1ish_eval_func_collect_call( ie, t, next, &xrv,
                                                   ie->currentThis,
                                                   func, 1 )
                    ;
                func = 0;
                th1ish_eval_check_exit(ie,rc, t, next);
                th1ish_reset_this( ie );
                if(rc) return rc;
                *rv = xrv;
                /* Check for chained()()() calls. */
                t = cwal_st_skip(*next) /* th1ish_skip(ie, *next)? */;
            }
        }
        break;
    }


#if 0
    /*
      Reminder to self: we have to catch the '?' as a "tail check", instead
      of having it somewhere up the eval chain, because if we do not then:

      [print true ? 1 : 2]

      gets 2 arguments: (true, 1)
    */
    if(!rc){
        t = th1ish_skip(ie,*next);
        switch(t ? t->ttype : 0){
          case TT_ChQuestion:
              rc = th1ish_eval_ternary_if( ie, *rv, t, next, rv );
              break;
          default:
              break;
        }
    }
#endif    

    if(!rc){ /* check for (X instanceof Y)... */
        t = cwal_st_skip(*next);
        if(t && (TT_KeywordInstanceOf==t->ttype)){
            cwal_value * vL = *rv;
            th1ish_reset_this(ie);
            t = *next = TNEXT(*next);
            rc = th1ish_eval_5(ie, t, next, rv);
            th1ish_eval_check_exit(ie,rc, t, next);
            if(!rc){
                *rv = cwal_value_derives_from( ie->e, vL, *rv )
                    ? cwal_value_true()
                    : cwal_value_false()
                    ;
            }
        }
    }
    


    
#if 0
    if(rc && (CWAL_RC_EXCEPTION!=rc)){
        rc = th1ish_toss_token(ie, origin, rc,
                               "Non-Exception error #%d (%s)",
                               rc, cwal_rc_cstr(rc));
    }
#endif
    return rc;
}

/**
   A lame attempt to try to determine if the token t represents part
   of a chain after "OBJECT." needs to be eval()'d or can be used as
   an identifier. Returns true (non-0) if it thinks the next part
   (starting at t) should be eval()'d, else false (in which case it
   thinks it is an identifier or literal which can be used as-is).  It
   is not terribly smart, and can probably be easily confused.
*/
static char th1ish_set_might_need_eval( th1ish_interp *ie, cwal_simple_token * t );

int th1ish_eval_dot_deref( th1ish_interp * ie,
                           cwal_value * self,
                           cwal_simple_token * t,
                           cwal_simple_token ** next,
                           cwal_value ** rv ){
    int rc = 0;
    cwal_value * v = 0;
    int const pendingPrefixOp = ie->pendingPrefixOp;
    switch(t->ttype){
      case TT_ChPeriod:
      case TT_OpArrow:
          break;
      default:
          assert(!"CANNOT HAPPEN");
          return CWAL_SCR_CANNOT_CONSUME;
    }
    start:
    ie->pendingPrefixOp = 0;
    if(!IE_SKIP_MODE && !th1ish_props_can(ie, self)){
        /* th1ish_dump_val(v,"Invalid 'self' for dot deref"); */
        /* th1ish_dump_val(self,"Invalid 'self' for dot deref"); */
        rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE,
                               "Invalid left-hand type (%s) for "
                               "the dot operator.",
                               cwal_value_type_name(self));
        goto end;
    }
    ie->currentThis = self;
    /* th1ish_dump_val(ie->currentThis,"ie->currentThis"); */
    t = *next = TNEXT(t);
#if 0
    {
        int const oldIdentPolicy = ie->identPolicy;
        v = cwal_value_undefined();
        if(!th1ish_set_might_need_eval(ie, t)){
            ie->identPolicy = 1;
        }
        rc = th1ish_eval_5( ie, t, next, &v );
        ie->identPolicy = oldIdentPolicy;
        th1ish_eval_check_exit(ie,rc, t, next);
        if(rc) goto end;
    }
#else
    if(TT_KeywordPrototype==t->ttype){
        /*
          We need special handling for the word prototype so that we
          are not forced to set it as a property on all objects. While
          that approach works, it muddles up any JSON output and
          looping, in that the prototype will appear there.

          We likewise need to special-case prototype in
          setter/assignment.
        */
        rc = th1ish_st_create_value(ie, t, &ie->currentIdentKey);
        if(rc) goto end;
        v = th1ish_prototype_get(ie, self);
        *next = t->right;
        assert(ie->currentIdentKey || IE_SKIP_MODE);
        goto post;
    }
    else if(/*don't like this special handling!*/
            cwal_st_isa(t,TT_Identifier) ||
            th1ish_st_keyword_get(t)){
        rc = th1ish_st_create_value(ie, t, &v);
        if(rc) goto end;
        assert(v);
        t = *next = (*next)->right;
        /* t = *next = th1ish_skip(ie,(*next)->right); */
    }
    else {
        cwal_value * xrv = self;
        th1ish_this_save(ie);
        /* ++ie->identPolicy */
            /* We want unknown identifiers to evaluate to
               their string value for this case.
            */;
        ie->flags |= TH1ISH_F_EVAL_NO_DOT;
        rc = th1ish_eval_5( ie, t, next, &xrv );
        th1ish_this_restore(ie);
        /* assert(!(TH1ISH_F_EVAL_NO_DOT & ie->flags)); */
        th1ish_eval_check_exit(ie,rc, t, next);

        /* --ie->identPolicy; */
        /* th1ish_dump_val(self,"self"); */
        /* th1ish_dump_val(xrv,"xrv"); */
        if(!rc && (TH1ISH_F_EVAL_NO_DOT & ie->flags)){
            rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "Mis-terminated dot operator.");
        }
        if(rc) goto end;
        v = xrv;
    }
#endif
    /* th1ish_dump_val(self,"Dot-deref self"); */
    /* th1ish_dump_val(v,"Dot-deref key"); */
    ie->currentIdentKey = v;
    rc = th1ish_get_v( ie, self, v, &v );
    if(rc) goto end;
    post:
    /* th1ish_dump_val(v,"Dot-deref val"); */
    if(!v) v = *rv = cwal_value_undefined();
    else {
        *rv = v;
        ie->currentThis = self
            /* Ensure it wasn't overwritten by a sub-parse */
            ;
        /* assert(self); */
    }
    check_tail:
    if(!rc){
        t = th1ish_skip(ie,*next)
            /* We need this instead of cwal_st_skip() so that this doesn't
                throw:

                const x = someObj.method
                (x && ...) // here, might look like a func call
            */
        ;
        if(th1ish_token_is_dot_op(t)){
            *next = t;
            self = *rv;
            goto start;
        }
        /*
          When conventionalFuncCalling() is enabled
          we have to account for that here to enable
          them to be chained.
        */
        else if((ie->flags & TH1ISH_F_CONVENTIONAL_CALL_OP)
           && cwal_st_isa(t,TT_ChParenOpen)){
            /* Check for func() calls. */
            cwal_function * func = 0;
            if(IE_SKIP_MODE || (func = cwal_value_get_function(*rv))){
                /* cwal_value * oldRv = *rv; */
                rc =
                    th1ish_eval_func_collect_call( ie, t, next, rv,
                                                   ie->currentThis,
                                                   func, 1 )
                    ;
                th1ish_eval_check_exit(ie,rc, t, next);
                ie->currentThis = *rv /* Very important for proper
                                         'this' in chaining. */;
                goto check_tail;
            }else{
                rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                           "'(' following a value of type '%s' is not "
                           "allowed when conventional call() syntax "
                           "is enabled.",
                           cwal_value_type_name(*rv));
            }
        }
    }
    end:
    if(rc){
        ie->currentThis = NULL;
    }else{
        /* Check for pre-/post-fix ops.

           TODO: move this into its own function.
        */
        int postOp = 0;
        switch(pendingPrefixOp){
          case 0: break;
          case TT_OpDecr:
          case TT_OpIncr:{
              cwal_value * vinc;
              cwal_value * prop = 0;
              char const doAdd = (TT_OpIncr==pendingPrefixOp) ? 1 : 0;
              if(IE_SKIP_MODE) break;
              assert(ie->currentThis);
              assert(ie->currentIdentKey);
              /* th1ish_dump_val(ie->currentThis,"prefix op obj"); */
              /* th1ish_dump_val(ie->currentIdentKey,"prefix op key"); */
              prop = cwal_prop_get_v(ie->currentThis, ie->currentIdentKey);
              if(!prop){
                  rc = th1ish_toss_token(ie, t, CWAL_RC_NOT_FOUND,
                                         "Cowardly refusing to prefix %s "
                                         "a non-existing property.",
                                         doAdd ? "++" : "--");
                  break;
                  /* prop = cwal_new_integer(ie->e, 0); */
              }
              vinc = cwal_new_integer(ie->e, 1);
              assert(vinc);
              rc = th1ish_values_addsub( ie, t->src.begin, prop,
                                         doAdd, vinc, rv);
              if(!rc){
                  assert(*rv);
                  rc = cwal_prop_set_v(ie->currentThis, ie->currentIdentKey, *rv);
              }
              break;
          }
          default:
              assert(!"Unknown prefix op");
              return CWAL_RC_FATAL;
        }
        /* Handle postfix op... */
        if(!rc
           && !pendingPrefixOp /* Remove this to allow ++x.y--? Why? Why not? */
           && (postOp = th1ish_pending_incr_op(*next))
           ){
            t = cwal_st_skip(*next);
            assert(postOp == t->ttype);
            switch(postOp){
              case TT_OpIncr:
              case TT_OpDecr:{
                  cwal_value * vinc;
                  cwal_value * prop = 0;
                  cwal_value * origProp = 0;
                  cwal_value * takenIdent = 0;
                  char const doAdd = (TT_OpDecr==postOp) ? 0 : 1;
                  *next = t->right;
                  if(IE_SKIP_MODE) break;
                  vinc = cwal_new_integer(ie->e, 1);
                  /* Lifetime problem: when re reassign the value, the original
                     can be destroyed, so we have to fiddle with the lifetimes
                     a bit...

                     We take() the prop and (it turns out) key. We will put the key right
                     back for the reassignment.
                  */
                  assert(vinc);
                  assert(ie->currentThis);
                  assert(ie->currentIdentKey);
                  prop = cwal_prop_take_v( ie->currentThis, ie->currentIdentKey,
                                           &takenIdent);
                  /* th1ish_dump_val(ie->currentThis,"prefix op obj"); */
                  /* th1ish_dump_val(ie->currentIdentKey,"prefix op key"); */
                  if(!prop){
                      assert(!takenIdent);
                      /* prop = cwal_new_integer(ie->e, 0); */
                      rc = th1ish_toss_token(ie, t, CWAL_RC_NOT_FOUND,
                                             "Cowardly refusing to postfix %s "
                                             "a non-existing property.",
                                             doAdd ? "++" : "--");
                      break;
                  }else{
                      assert(prop);
                      assert(takenIdent);
                      if(takenIdent != ie->currentIdentKey){
                          /* MARKER(("Freeing old identifier.\n")); */
                          cwal_value_unref(ie->currentIdentKey) /* is this really safe here? */;
                          ie->currentIdentKey = takenIdent;
                          takenIdent = 0;
                      }else{
                          /* MARKER(("Same identifier.\n")); */
                      }
                  }
                  origProp = prop;
                  rc = th1ish_values_addsub( ie, t->src.begin, prop,
                                             doAdd, vinc, &prop);
                  if(!rc){
                      assert(*rv);
                      assert(origProp != prop);
                      rc = cwal_prop_set_v(ie->currentThis, ie->currentIdentKey, prop);
                      *rv = origProp;
                  }
                  break;
              }
              default:
                  MARKER(("Unknown postfix op: %d\n", postOp));
                  assert(!"Unknown postfix op");
                  return CWAL_SCR_CANNOT_CONSUME;
            }
        }/* postfix op */
    }/* pre/postfix check */
    return rc;
}



static int th1ish_eval_literal_number(th1ish_interp * ie, cwal_simple_token * t,
                                      cwal_simple_token ** next,
                                      cwal_value ** rv ){
    int rc = 0;
    cwal_value * v = 0;
    cwal_engine * e = ie->e;
#define RC rc = v ? CWAL_RC_OK : CWAL_RC_OOM
    switch(t->ttype){
      case TT_LiteralIntDec:
          if(!IE_SKIP_MODE){
              v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin, NULL, 10));
              RC;
          }
          break;
      case TT_LiteralIntOctal:
          if(!IE_SKIP_MODE){
              v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+1/*0*/, NULL, 8));
              t->ttype = TT_LiteralIntDec;
              RC;
          }
          break;
      case TT_LiteralIntHex:
          if(!IE_SKIP_MODE){
              v = cwal_new_integer(e, (cwal_int_t)strtol(t->src.begin+2/*0x*/, NULL, 16));
              t->ttype = TT_LiteralIntDec;
              RC;
          }
          break;
      case TT_LiteralDouble:
          if(!IE_SKIP_MODE){
              v = cwal_new_double(e, (cwal_double_t)strtod(t->src.begin, NULL));
              RC;
          }
          break;
      default:
          rc = CWAL_SCR_CANNOT_CONSUME;
          break;
    }
    *rv = v ? v : cwal_value_undefined();
    if(!rc) {
        *next = t->right;
    }
#undef RC
    return rc;
}

static int th1ish_eval_literal_string(th1ish_interp * ie, cwal_simple_token * t,
                                      cwal_simple_token ** next,
                                      cwal_value ** rv ){
    int rc = CWAL_SCR_CANNOT_CONSUME;
    switch( t->ttype ){
      case TT_LiteralStringDQ:
      case TT_LiteralStringSQ:
      case TT_ChBraceOpen:
      case TT_ChParenOpen:
      case TT_ChScopeOpen:{
          *next = t->right;
          rc = IE_SKIP_MODE
                  ? 0
                  : th1ish_st_create_value( ie, t, rv );
          if(!rc){
              assert(*rv || IE_SKIP_MODE);
          }
          break;
      }
      default:
          break;
    }
    return rc;
}

int th1ish_eval_identifier(th1ish_interp * ie, cwal_simple_token * t,
                           cwal_simple_token ** next,
                           cwal_value ** rv ){
    if(TT_Identifier!=t->ttype) return CWAL_SCR_CANNOT_CONSUME;
    else if(IE_SKIP_MODE){
        *next = t->right;
        return 0;
    } else {
        cwal_value * v = 0;
        cwal_value * iden = 0;
        int pendingPrefixOp = ie->pendingPrefixOp;
        int rc = th1ish_st_create_value( ie, t, &iden );
        if(rc) return rc;
        ie->pendingPrefixOp = 0;
        *next = t->right;
        /* untested: if(IE_SKIP_MODE) return 0; */
        if(IE_SKIP_MODE) return 0 /* seems to work */;
        else if(1 == ie->identPolicy /*eval identifiers as strings*/){
            v = iden;
        }else{
            cwal_scope * s = cwal_scope_current_get(ie->e);
            cwal_scope * foundIn = 0;
            cwal_kvp * kvp;
            char doAdd = (TT_OpIncr==pendingPrefixOp);
            int checkForOp = 0;
            assert(iden);
            kvp = cwal_scope_search_kvp_v( s, -1, iden, &foundIn );
            v = kvp ? cwal_kvp_value(kvp) : 0;
            if(!v && (pendingPrefixOp
                      || (checkForOp=th1ish_pending_incr_op(*next)))
               ){
                if(checkForOp) doAdd = (TT_OpIncr==checkForOp);
                rc = th1ish_toss_token(ie, t, CWAL_RC_MISUSE,
                                       "Cowardly refusing to %s %s "
                                       "a non-existing property.",
                                       pendingPrefixOp ? "prefix" : "postfix",
                                       doAdd ? "++" : "--");
            }else if(v){
                int postOp = 0;
                switch(pendingPrefixOp){ /* Apply any pending prefix operator */
                  case 0: break;
                  case TT_OpDecr:
                  case TT_OpIncr:{
                      cwal_value * vinc;
                      if(th1ish_dot_is_pending(*next)){
                          ie->pendingPrefixOp = pendingPrefixOp;
                          break /* let dot-deref deal with it. */;
                      }
                      /* Else increment the variable... */
                      vinc = cwal_new_integer(ie->e, 1);
                      assert(vinc /* cannot fail for value 1 */);
                      rc = th1ish_values_addsub( ie, t->src.begin, v,
                                                 doAdd, vinc, rv);
                      if(!rc){
                          assert(*rv);
                          rc = cwal_scope_chain_set_v(foundIn, 0, iden, *rv);
                          if(CWAL_RC_ACCESS==rc){
                              rc = th1ish_toss_token(ie, t, rc,
                                                     "Prefix ++/-- cannot be applied "
                                                     "to a const variable.");
                          }else{
                              v = *rv;
                          }
                      }
                      break;
                  }
                  default:
                      MARKER(("Unknown prefix op: %d\n", pendingPrefixOp));
                      assert(!"Unknown prefix op");
                      return CWAL_SCR_CANNOT_CONSUME;
                }/* prefix op */
                /* Handle postfix ++/-- */
                if(!pendingPrefixOp && (postOp = th1ish_pending_incr_op(*next))){
                    t = cwal_st_skip(*next);
                    assert(postOp == t->ttype);
                    switch(postOp){
                      case TT_OpIncr:
                      case TT_OpDecr:{
                          cwal_value * vinc;
                          cwal_value * taken;
                          cwal_value * takenIdent = 0;
                          cwal_value * scopeProps = cwal_scope_properties(foundIn);
                          char const doAdd = (TT_OpIncr==postOp) ? 1 : 0;
                          assert(scopeProps);
                          /* Lifetime problem: when re reassign the value, the original
                             can be destroyed, so we have to fiddle with the lifetimes
                             a bit...

                             We take() v and (it turns out) iden. We will put iden right
                             back for the reassignment.

                             Reminder: we currently have no way
                             removing kvp from scopeProps without
                             another search. The internal API could
                             give us this, but would require leaking
                             some abstractions which should not be
                             leaked.

                          */
                          if(CWAL_VAR_F_CONST & cwal_kvp_flags(kvp)){
                              rc = th1ish_toss_token(ie, t, CWAL_RC_ACCESS,
                                                     "Posfix ++/-- cannot be applied "
                                                     "to a const variable.");
                              break;
                          }
                          *next = t->right;
                          taken = cwal_prop_take_v( scopeProps, iden, &takenIdent);
                          assert(taken == v);
                          assert(iden);
                          /* only valid if string interning is on: assert(takenIdent == iden); */
                          if(takenIdent != iden){
                              cwal_value_unref(ie->currentIdentKey);
                              ie->currentIdentKey = takenIdent;
                              /* MARKER(("Freeing old identifier.\n")); */
                          }else{
                              /* MARKER(("Same identifier.\n")); */
                          }
                          vinc = cwal_new_integer(ie->e, 1);
                          assert(vinc /* cannot fail for value 1 */);
                          rc = th1ish_values_addsub( ie, t->src.begin, v,
                                                     doAdd, vinc, &vinc);
                          if(!rc){
                              rc = cwal_scope_chain_set_v(foundIn, 0, iden, vinc);
                          }
                          break;
                      }
                      default:
                          MARKER(("Unknown postfix op: %d\n", postOp));
                          assert(!"Unknown postfix op");
                          return CWAL_SCR_CANNOT_CONSUME;
                    }
                }/* postfix op */
            }else{
                if(-1==ie->identPolicy){
                    /* eval UNKNOWN identifiers as their strings.
                      We do this mainly for short-circuiting, IIRC.
                    */
                    v = iden;
                }
                else {
                    cwal_string * id = cwal_value_get_string(iden);
                    /* dump_token(t,"unknown identifier"); */
                    rc = th1ish_toss_token(ie, t, CWAL_RC_NOT_FOUND,
                                           "Unknown identifier '%.*s'",
                                           cwal_string_length_bytes(id),
                                           cwal_string_cstr(id));
                }
            }
        }
        if(!rc) *rv = v;
        return rc;
    }
}

int th1ish_eval_list( th1ish_interp * ie, cwal_simple_token * t,
                      cwal_simple_token ** next, uint16_t * rvLen,
                      cwal_value ** rvList ){
    /* Read list of (expr1 [,...exprN]). */
    int rc = 0;
    char const * code = t->src.begin;
    cwal_size_t codeLen = t->src.end - t->src.begin;
    uint16_t const argc = rvLen ? *rvLen : (rvList ? 1 : 0);
    uint16_t count = 0;
    cwal_value * vv = 0;
    int const oldIdentLevel = ie->identPolicy;
    cwal_simple_token * head = 0;
    cwal_simple_token * origin = t;
#if 1
    /* Maybe let the baser-most evaluators do this, to catch
       syntax errors with more precision?
    */
    if(!codeLen || IE_SKIP_MODE) {
        if(rvLen) *rvLen = 0;
        if(rvList) rvList[0] = cwal_value_undefined();
        *next = t->right;
        return 0;
    }
#endif
    assert(argc);
    if(TT_ChParenOpen != t->ttype) return CWAL_SCR_CANNOT_CONSUME;
    if(!argc) return CWAL_RC_MISUSE;
    rc = codeLen
        ? th1ish_tokenize_all( ie, code, codeLen, &head )
        : 0;
    if(rc) return rc;
    else if(!head){
        *rvList = cwal_value_undefined();
        return 0;
    }
    /*
      Loop through the following expressions and collect
      their their values into rvList.
    */
    ie->identPolicy = 0
        /*
          We need to temporarily move out of "identPolicy" mode
          so that things like this can work:
          
          obj.subobj.([func ...])
        */
        ;
    vv = rvList[0];
    if(!vv) vv = rvList[0] = cwal_value_undefined();
    *next = t = cwal_st_skip(head);
    /* dump_token(*next, "token following '('"); */
    for( ; t && (TT_EOF!=t->ttype); t = cwal_st_skip(*next)){
        char doBreak = 1;
        assert(t);
        if(argc>1 && count==argc){
            rc = CWAL_SCR_TOO_MANY_ARGUMENTS;
            goto end;
        }
        /* dump_token(t,"t"); */
        rc = th1ish_eval_0(ie, t, next, &vv);
        th1ish_eval_check_exit(ie,rc, t, next);
        if(rc/*  || *next==t */){
            goto end;
        }
        assert(*next);
        assert((*next != t) || ie->expando.next);
        *next = cwal_st_skip(*next);
        /* dump_token(*next,"*next after eval-0"); */
        if(argc>1){
            rvList[count] = vv;
            *rvLen = ++count;
        }
        else rvList[0] = vv;
        /* th1ish_dump_val(vv,"List part"); */
        if( cwal_st_eof(*next) ){
            /* dump_token((*next)->right, "Closing (list). Next token"); */
            break;
        }
        switch((*next)->ttype){
          case TT_ChParenClose:
#if 1
              /* dump_token(*next, "')' in parens."); */
              doBreak = 1;
              break;
#endif
          case TT_ChComma:
              /* Inconsistency: (space separated lists) not working here. */
          case TT_ChSpace:
          case TT_HTab:
          case TT_Whitespace:
          case TT_CR:
          case TT_EOL:
              *next = TNEXT(*next);
              doBreak = 0;
              break;
          default:
              /* dump_token(*next,"Breaking out of (group)."); */
              /* cwal_dump_v(vv,"RC of (group)"); */
              assert(!rc);
              break;
        }
        if(doBreak){
            break;
            /*return cwal_st_toss(ie->e, *next, CWAL_SCR_SYNTAX, "Missing comma.");*/
        }
        /* dump_token(*next,"next token"); */
    }
    /* dump_token(*next,"end token"); */
    end:
    ie->identPolicy = oldIdentLevel;
    if(head){
        th1ish_st_free_chain(ie, head, 1);
    }
#if 0
    /* i would like to do this, but it nukes any temporaries
       the up-stream might be about to get via *rv. */
    if(!rc){
        cwal_scope_sweep(cwal_scope_current_get(ie->e));
    }
#endif
    *next = rc ? origin : th1ish_skip(ie,origin->right)
        /* Remember that *next likely lives in the head chain,
           so we need to set it whether we succeed or not.
        */
        ;
    /* dump_token(*next, "token following ')'"); */
    return rc;
}


int th1ish_eval_keywords( th1ish_interp * ie,
                          cwal_simple_token * t,
                          cwal_simple_token ** next,
                          cwal_value ** rv ){
    th1ish_keyword const * kw = th1ish_st_keyword_get(t);
    if(!kw || !kw->parser) return CWAL_SCR_CANNOT_CONSUME;
    else if(ie->identPolicy>0){
        int rc = th1ish_st_create_value(ie, t, rv);
        if(rc) return rc;
        *next = t->right;
        return 0;
    }
    else {
        int rc = 0;
        rc = kw->parser(ie, t, next, rv);
        /* th1ish_dump_val(*rv, "Post-keyword rv"); */
        return rc;
    }
}

/**
   Returns true if t is NULL or t->ttype represents
   an "end of expression" or EOF token.
*/
static char th1ish_st_eox( cwal_simple_token const * t );


int th1ish_keyword_refcount( th1ish_interp * ie,
                             cwal_simple_token * t,
                             cwal_simple_token ** next,
                             cwal_value ** rv ){
    int rc;
    assert(TT_KeywordRefcount==t->ttype);
    *next = t = th1ish_skip(ie,t->right) /*
                                 Note that this one does not skip
                                 EOLs like some keywords do.
                               */;
    if(th1ish_st_eox(t)){
        rc = th1ish_toss_token(ie, t, CWAL_SCR_UNEXPECTED_EOF,
                               "Expecting expression after 'refcount'.");
    }else{
        *rv = cwal_value_undefined() /* required for skip mode(?) and empty "return;". */;
        rc = th1ish_eval_0( ie, t, next, rv );
        if(!rc){
            *rv = cwal_new_integer( ie->e,
                                    (cwal_int_t)cwal_value_refcount(*rv) );
        }
    }
    return rc;
}


void th1ish_exit_value_set( th1ish_interp * ie,
                            cwal_value * v ){
    /**
       Unlike a return value, we can stuff an exit result
       directly into the top scope and not have to deal
       with bubbling it up the call stack...
    */
    assert(ie->topScope);
    if(v){
        cwal_value_rescope( ie->topScope, v );
    }
    ie->exitValue = v;
    /* we _might_ want to consider adding a reference
       to make sure a subsequent sweep does not
       wipe it out.

       Na.


       That also means that we don't own a ref to unref()
       if this is called multiple times, so we just
       overwrite ie->exitValue. In theory this function
       won't be called multiple times except possibly
       during/after a fork()?
    */
    if(0 && v && !cwal_value_refcount(v)) cwal_value_ref(v);
}

int th1ish_keyword_return( th1ish_interp * ie,
                           cwal_simple_token * t,
                           cwal_simple_token ** next,
                           cwal_value ** rv ){
    int rc;
    int const op = t->ttype;
    switch(op){
      case TT_KeywordReturn:
      case TT_KeywordExit:
      case TT_KeywordFatal:
      case TT_KeywordBreak:
          break;
      case TT_KeywordToss:
          if(!ie->catchLevel){
              return th1ish_toss_token(ie, t, CWAL_RC_MISUSE,
                                       "Cannot 'toss' outside of a local 'catch' "
                                       "or across function call boundaries.");
          }
          break;
      default:
          assert(!"CANNOT HAPPEN");
          return CWAL_RC_CANNOT_HAPPEN;
    }
    *next = t = th1ish_skip(ie,t->right)
        /*
          Note that these do not skip EOLs like some keywords do.
        */;
    *rv = cwal_value_undefined() /* required for skip mode(?) and empty "return;". */;
    if(th1ish_st_eox(t)){
        rc = 0;
    }else{
        rc = th1ish_eval_entry( ie, t, next, rv )
            /* Reminder using _entry() instead of _0()
               ONLY to work around a semicolon reporting
               as a cannot-consume error :/
            */;
    }
    
    if(!rc && !IE_SKIP_MODE) switch(op){
      case TT_KeywordBreak:
      case TT_KeywordReturn:
      case TT_KeywordToss:
          th1ish_result_set( ie, *rv )
              /* We assume we're in our own scope and will be popped
                 momentarily, and this forces the value to survive an
                 additional scope.
              */
              ;
          switch(op){
            case TT_KeywordBreak: rc = CWAL_RC_BREAK; break;
            case TT_KeywordReturn: rc = CWAL_RC_RETURN; break;
            case TT_KeywordToss: rc = TH1ISH_RC_TOSS; break;
          }
          break;
      case TT_KeywordFatal:
          th1ish_toss_token_val( ie, t, CWAL_RC_FATAL, *rv );
          /* fall through */
      case TT_KeywordExit:
          th1ish_exit_value_set(ie, *rv);
          /* th1ish_dump_val(ie->exitValue,"EXIT VALUE"); */
          rc = (TT_KeywordExit==op) ? CWAL_RC_EXIT : CWAL_RC_FATAL;
          break;
      default:
          assert(!"CANNOT HAPPEN");
          break;
    }
    return rc;
}

int th1ish_keyword_continue( th1ish_interp * ie,
                             cwal_simple_token * t,
                             cwal_simple_token ** next,
                             cwal_value ** rv ){
    int rc = 0;
    switch(t ? t->ttype : 0){
      case 0:
          rc = CWAL_SCR_UNEXPECTED_EOF;
          break;
      case TT_KeywordContinue:
          *rv = cwal_value_undefined();
          *next = t->right;
          rc = CWAL_RC_CONTINUE;
          break;
      default:
          rc = CWAL_SCR_CANNOT_CONSUME;
          break;
    }
    return IE_SKIP_MODE ? 0 : rc;
}


int th1ish_keyword_typename( th1ish_interp * ie,
                             cwal_simple_token * t,
                             cwal_simple_token ** next,
                             cwal_value ** rv ){
    int rc;
    assert(TT_KeywordTypename==t->ttype);
    *next = t = TNEXT(t);
    if(!IE_SKIP_MODE && (TT_Identifier==t->ttype)){
        cwal_value * v = 0;
        cwal_value * key = 0;
        /* For Identifiers, check if it is defined, and return
           'undefined' if it is not.
        */
        rc = th1ish_st_create_value(ie,t, &key);
        if(rc) return rc;
        /** Check if the identifier can be resolved. If not,
            return "undefined" as the typename.
        */
        v = cwal_scope_search_v( cwal_scope_current_get(ie->e), -1,
                                       key, NULL );
        if(!v){
            /* TODO: recycle this string somewhere. Set aside an
               object for that purpose in ie, e.g. ie->stash.  If
               string interning is on, that's not needed.
            */
            v = cwal_new_string_value(ie->e,
                                      cwal_value_type_name(cwal_value_undefined()),
                                      0);
            if(!v) return CWAL_RC_OOM;
            *rv = v;
            *next = th1ish_skip(ie,t->right);
            return 0;
        }
        /* else fall through and evaluate it. */
    }
    rc = th1ish_eval_1( ie, t, next, rv )
        /*
          Reminder: we should arguably use eval_0
          so that we get assignments, but that leads to an inconsistency
          with most other operators. Using eval_1 requires a parens
          around:

          typename (a = 3)
        */
        ;
    if(rc || IE_SKIP_MODE) return rc;
    else {
        cwal_size_t tlen = 0;
        char const * tn = NULL;
        if(*rv) tn = cwal_value_type_name2(*rv, &tlen);
        if(!tn){
            tn = cwal_value_type_id_name(CWAL_TYPE_UNDEF);
            tlen = strlen(tn);
        }
        *rv = cwal_new_string_value(ie->e, tn, tlen);
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

int th1ish_keyword_nameof( th1ish_interp * ie,
                           cwal_simple_token * t,
                           cwal_simple_token ** next,
                           cwal_value ** rv ){
    int rc;
    cwal_simple_token * idTok;
    assert(TT_KeywordNameof==t->ttype);
    *next = t = TNEXT(t);
    if(IE_SKIP_MODE){
        *next = TNEXT(*next);
        return 0;
    }
    if(TT_Identifier!=t->ttype){
        return th1ish_toss_token(ie, t, CWAL_RC_MISUSE,
                                 "nameof requires an identifer argument.");
    }
    idTok = t;
    t = *next = th1ish_skip(ie,t->right);
#if 0
    switch(t ? t->ttype : 0){
      case TT_ChPeriod:
      case TT_OpArrow:
          /**
             TODO: if the following looks like it might be OBJ.KEY
             then use eval_0(t) to evaluate it, then look at
             ie->currentIdentKey.

             However, this precludes:

             nameof (foo).bar

             and similar.
          */
      default:
          break;
    }
#endif
    rc = th1ish_st_create_value(ie, idTok, rv);
    if(rc) return rc;
    *next = th1ish_skip(ie,idTok->right);
    return 0;
}

th1ish_interp * th1ish_args_interp( cwal_callback_args const * args ){
    return cwal_args_state(args, &th1ish_interp_empty);
}

/**
   Continually parses all tokens in t until EOF or a ']'. Each
   expression evaluated is appended the given cwal_array. If count is
   not NULL then on return it will hold the number of values appended
   to the array.

   Returns 0 on success.

   It is intended that this be used to parse token chains derived from
   '[' tokens (which, during tokenization, get special handling).
*/
static int th1ish_collect_into_array(th1ish_interp * ie,
                                     cwal_simple_token * t,
                                     cwal_simple_token ** next,
                                     cwal_array * dest,
                                     cwal_size_t * count ){
    int rc;
    cwal_size_t argc = 0;
    cwal_simple_token * origin = t;
    t = cwal_st_skip(t);
    if(cwal_st_eof(t)){
        /*rc = CWAL_SCR_EOF;
          goto end;*/
        rc = 0;
        goto end;
    }
    /* dump_token(t,"array parsing"); */
    *next = t;
    rc = 0;
    for( ; !cwal_st_eof(t); t = TNEXT(*next) ){
        cwal_value * v = 0;
        char doBreak = 0;
        /* dump_token(t,"list parsing"); */
        *next = t;
        switch(t->ttype){
          case TT_ChComma:
              rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                     "Too many commas in list.");
              doBreak = 1;
              continue;
          case TT_ChScopeClose:
          case TT_ChBraceClose:
              /*happens in empty [].
                assert(!"We were not really expecting this.");*/
              *next = t->right;
              doBreak = 1;
              break;
          case TT_EOF:
              /* We allow EOF because these chains are processed as
                 sub-parses. */
              *next = t;
              doBreak = 1;
              break;
          case TT_EOL:
          case TT_NL:
          case TT_CRNL:
              assert(!"Cannot happen due to TNEXT()");
              continue;
          default:
              break;
        }
        if(rc) goto end;
        else if(doBreak) break;
        /* dump_token(t,"Evaling list element"); */
        rc = th1ish_eval_1( ie, t, next, &v );
        if(rc) goto end;
        /* th1ish_dump_val(v, "list entry"); */
        rc = cwal_array_append( dest, v ? v : cwal_value_undefined() );
        if(rc) goto end /* do not throw: this is likely an OOM */;
        ++argc;
    }
    end:
    if(!rc){
        *next = origin->right;
    }
    if(count) *count = argc;
    return rc;
}

int th1ish_keyword_array(th1ish_interp * ie, cwal_simple_token * t,
                         cwal_simple_token ** next, cwal_value ** rv ){

    int rc = 0;
    cwal_array * ar;
    cwal_simple_token * argChain = 0;
    cwal_simple_token * bogoNext = 0;
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * pushed = &pushedScope_;
    assert(TT_KeywordArray==t->ttype);
    t = TNEXT(t);
    switch(t ? t->ttype : 0){
      case TT_ChBraceOpen:
      case TT_ChScopeOpen:
          break;
      default:
          return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "Expecting '[' or '{' after array keyword.");
    }
    if(IE_SKIP_MODE){
        *rv = cwal_value_undefined();
        *next = t->right;
        return 0;
    }
    ar = th1ish_new_array(ie);
    if(!ar) return CWAL_RC_OOM;
    
    /**
       Reminder: we cannot sweep() after this because of:

       (1+8) && array[1,2,3]

       That would sweep up the LHS (the integer 9, which has already
       been evaluated but is not anywhere with a reference held do it.

       But we _can_ safely add a new scope, because insertion of the
       new temporaries into the array will upscope them to the array's
       owning scope.
    */
    rc = cwal_scope_push(ie->e, &pushed);
    if(rc) {
        pushed = 0;
        goto end;
    }
    rc = th1ish_tokenize_all( ie, t->src.begin,
                              t->src.end - t->src.begin,
                              &argChain );
    if(rc){
        assert(!argChain);
        goto end;
    }

    *next = t->right;
    rc = th1ish_collect_into_array( ie, argChain, &bogoNext, ar, NULL );
    end:
    if(argChain){
        th1ish_st_free_chain( ie, argChain, 1 );
    }
    if(pushed){
        cwal_scope_pop(ie->e);
    }
    if(!rc){
        *rv = cwal_array_value(ar);
    }
    else {
        cwal_array_unref(ar);
    }
    return rc;
}

/**
   Continually parses all key/value pairs in t until EOF or a '}'. Each
   expression evaluated is appended the given cwal_array. If count is
   not NULL then on return it will hold the number of values set in
   the destination object.

   Returns 0 on success.

   It is intended that this be used to parse token chains derived from
   '{' tokens (which, during tokenization, get special handling).

   Syntax: KEY: VALUE [, KEY: VALUE...]
*/
static int th1ish_inline_object(th1ish_interp * ie,
                                cwal_simple_token * t,
                                cwal_simple_token ** next,
                                cwal_value * dest,
                                cwal_size_t * count ){
    int rc;
    cwal_size_t argc = 0;
    cwal_simple_token * origin = t;
    t = cwal_st_skip(t);
    if(cwal_st_eof(t)){
        /*rc = CWAL_SCR_EOF;
          goto end;*/
        rc = 0;
        goto end;
    }
    /* dump_token(t,"array parsing"); */
    *next = t;
    rc = 0;
    for( ; !cwal_st_eof(t); t = TNEXT(*next) ){
        cwal_value * key = 0;
        cwal_value * v = 0;
        char doBreak = 0;
        /* dump_token(t,"list parsing"); */
        *next = t;
        switch(t->ttype){
          case TT_ChComma:
              rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                     "Stray comma in object literal.");
              doBreak = 1;
              break;
          case TT_ChScopeClose:
              /*happens on empty objects:
                assert(!"We were not really expecting this.");*/
              *next = t->right;
              doBreak = 1;
              break;
          case TT_EOF:
              /* We allow EOF because these chains are processed as
                 sub-parses. */
              *next = t;
              doBreak = 1;
              break;
          case TT_EOL:
          case TT_NL:
          case TT_CRNL:
              assert(!"Cannot happen due to TNEXT()");
              continue;
          default:
              break;
        }
        if(rc) goto end;
        else if(doBreak) break;
        /* dump_token(t,"Evaling list element"); */

        /* For ease of use, keys may be entered as identifiers... */
#if 0
        if(cwal_st_isa(t,TT_Identifier)){
            th1ish_st_create_value(ie, t, &key);
            *next = t->right;
        }else {
            rc = th1ish_eval_5( ie, t, next, &key );
            if(rc) goto end;
        }
#else
        {
            int const oldIdentPolicy = ie->identPolicy;
            ie->identPolicy = 1;
            rc = th1ish_eval_5( ie, t, next, &key );
            ie->identPolicy = oldIdentPolicy;
            if(rc) goto end;
        }
#endif
        assert(key);
        t = *next;
        *next = cwal_st_skip(*next);
        if(!cwal_st_isa(*next,TT_ChColon)){
            rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "Expecting ':' after key.");
            goto end;
        }
        *next = TNEXT(*next) /* skip colon */;
        if(cwal_st_isa(*next,TT_EOF)){
            rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "Unexpected EOF in object literal.");
        }
        else {
            t = *next;
            rc = th1ish_eval_0( ie, t, next, &v );
        }
        if(rc) goto end;
        rc = cwal_prop_set_v( dest, key, v ? v : cwal_value_undefined() );
        if(rc) goto end /* do not throw: this is likely an OOM */;
        ++argc;
    }
    end:
    if(!rc){
        *next = origin->right;
    }
    if(count) *count = argc;
    return rc;
}
int th1ish_keyword_object(th1ish_interp * ie, cwal_simple_token * t,
                          cwal_simple_token ** next, cwal_value ** rv ){

    int rc = 0;
    cwal_value * obj;
    cwal_simple_token * argChain = 0;
    cwal_simple_token * bogoNext = 0;
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * pushed = &pushedScope_;
    assert(TT_KeywordObject==t->ttype);
    t = TNEXT(t);
    if(!cwal_st_isa(t,TT_ChScopeOpen)){
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "Expecting '{' after object keyword.");
    }
    if(IE_SKIP_MODE){
        *rv = cwal_value_undefined();
        *next = t->right;
        return 0;
    }
    obj = th1ish_new_object(ie);
    if(!obj) return CWAL_RC_OOM;
    
    /**
       Reminder: we cannot sweep() after this because of:

       (1+8) && array[1,2,3]

       That would sweep up the LHS (the integer 9, which has already
       been evaluated but is not anywhere with a reference held do it.

       But we _can_ safely add a new scope, because insertion of any
       new temporaries into the object will upscope them to the
       object's owning scope (as well as give them a reference).
    */
    rc = cwal_scope_push(ie->e, &pushed);
    if(rc){
        pushed = 0;
        goto end;
    }

    rc = th1ish_tokenize_all( ie, t->src.begin,
                              t->src.end - t->src.begin,
                              &argChain );
    if(rc){
        assert(!argChain);
        goto end;
    }

    *next = t->right;
    rc = th1ish_inline_object( ie, argChain, &bogoNext, obj, NULL );
    end:
    if(argChain){
        th1ish_st_free_chain( ie, argChain, 1 );
    }
    if(pushed){
        cwal_scope_pop(ie->e);
    }
    if(!rc){
        *rv = obj;
    }
    else {
        cwal_value_unref(obj);
    }
    return rc;
}



/**
   Script usage:

   assert expression

   If expression is false then we trigger a cwal-side exception with a
   code of CWAL_RC_ASSERT, containing the text of the expression
   token(s) from the expression.

   Reminder: due to how some token types are post-processed, e.g. comments
   are elided and {strings} are trimmed, the output messages might
   contain this which exist in the sources but might not be what the user
   expects to see. e.g.:

     (assert {one} === "two")

   The string part of that assertion, for purposes of creating the error mesage,
   is:

     one} === "two"

*/
int th1ish_keyword_assert(th1ish_interp * ie, cwal_simple_token * t,
                          cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    assert(TT_KeywordAssert==t->ttype);
    t = th1ish_skip(ie,t->right);
    *next = t;
    rc = th1ish_eval_0( ie, t, next, rv );
    if(rc || IE_SKIP_MODE) return rc;
    else if(!cwal_value_get_bool(*rv)){
        char const * b = t->src.begin;
        char const * e = (*next)->src.begin
            /* LOL:
            <SCRIPTCODE>
            assert false
            // comments
            // comments
            [print "abc"]
            </SCRIPTCODE>

            Will, because the comments are skipped at
            tokenization, lead 'e' to be the string
            starting at 'print "abc"'

            i.e. the assertion error text contains the following
            no-open (non-)tokens.

            Workaround is to end the assertion with a semicolon.
            */
            ;
        assert(e>=b);
        /* dump_token(*next,"Next???"); */
        return th1ish_toss_token(ie, t, CWAL_RC_ASSERT,
                                 "Assertion failed: %.*s",
                                 e-b, b);
    }
    else if(TH1ISH_F_TRACE_ASSERTIONS & ie->flags){
        char const * b = t->src.begin;
        char const * e = (*next)->src.begin;
        cwal_size_t nameLen = 0;
        char const * name =
            (char const *)
            th1ish_script_name_for_pos(ie, t->src.begin, &nameLen);
        unsigned int line = 0;
        unsigned int col = 0;
        if(!name){
            name = "<unknown>";
            nameLen = 9;
        }
        th1ish_token_line_col( ie, t, &line, &col );
        cwal_outputf( ie->e, "Assertion OK @[%.*s]:%u:%u: ",
                      (int)nameLen, name,
                      line, col);
        assert( e >= b );
        cwal_output( ie->e, b, e - b);
        if(*(e-1) != '\n'){
            cwal_output( ie->e, "\n", 1);
        }
        return 0;
    }
    else return 0;
}

static cwal_value * th1ish_token_to_string(th1ish_interp * ie, cwal_simple_token * t ){
    char const * b = t->src.begin;
    char const * e = t->src.end;
    cwal_size_t const len = e - b;
    cwal_value * code;
    code = cwal_new_string_value( ie->e, len ? b : 0, len );
    assert(code);
    return code;
}

static char th1ish_set_might_need_eval( th1ish_interp * ie, cwal_simple_token * t ){
    t = th1ish_skip(ie,t);
    if(TT_Identifier!=t->ttype) return 1;
    else if(th1ish_st_keyword_get(t)) return 1;
    else {
        t = TNEXT(t);
        switch(t ? t->ttype : 0){
          case TT_ChPeriod:
          case TT_OpArrow:
              return 1;
          default:
              return 0;
        }
    }
}

/**
   Handles the 'set' and 'unset' keywords:

   set IDENTIFIER [[=] VALUE, [IDENTIFIER ...]

   unset IDENTIFIER [, IDENTIFIER...]

   where IDENTIFIER may be a normal identifier, a string, a number, or
   a series of OBJECT.PROPERTY dereferences:

   set myObj.subObj.foo "bar"
*/
int th1ish_keyword_set(th1ish_interp * ie, cwal_simple_token * t,
                       cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_value * obj;
    cwal_value * setv = 0;
    cwal_value * key = 0;
    char const isUnset = t ? (t->ttype == TT_KeywordUnset) : 0;
    /* cwal_value * idString = 0; */
    int const oldIdentPolicy = ie->identPolicy;
    char const * callPoint = t->src.begin;
    th1ish_this_save(ie);
    assert( (TT_KeywordSet==t->ttype) || (TT_KeywordUnset==t->ttype) );
    
    start:
    key = obj = setv = 0;
    t = *next = TNEXT(t);
    th1ish_reset_this(ie);
    /*
      Reminder: i don't like this special case of identifiers here but
      we don't have much of a choice if we want to support both (set
      foo.bar 7) and (set a 3). If we disable identifier expansion
      temporarily that solves the latter case but causes the former to
      fail because the dot-deref operator does the work of finding the
      proper container/key pair for that case.
    */
    if(!th1ish_set_might_need_eval(ie, t)){
        /* dump_token(t,"Setting ie->identPolicy = 1"); */
        ie->identPolicy = 1;
    }
    /* dump_token(t,"evaling SET KEY token"); */
    rc = th1ish_eval_1( ie, t, next, &key );
    ie->identPolicy = oldIdentPolicy;
    
    if(rc) goto end;
    if(ie->currentThis){
        obj = ie->currentThis;
        /* th1ish_dump_val(obj,"SET ie->currentThis"); */
        if(ie->currentIdentKey){
            key = ie->currentIdentKey;
            /* th1ish_dump_val(obj,"SET ie->currentIdentKey"); */
        }
    }
    /* th1ish_dump_val(key,"invalid key?"); */

    if(!IE_SKIP_MODE){
        if(!(key && cwal_value_is_string(key)) && !obj ){
            /* th1ish_dump_val(key,"invalid key?"); */
            rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE,
                                   "'set' expect (OBJECT.PROP ?VALUE?) "
                                   "or (KEY ?VALUE?) arguments.");
            goto end;
        }
    }
    t = *next = th1ish_skip(ie,*next);

    if(obj){
        assert(key);
    }

    if(t && (TT_OpAssign == t->ttype)){
        /* Skip over optional '='. '=' enables newline-skipping. */
        t = *next = TNEXT(t);
    }

    if(isUnset){
        setv = NULL;
    }
    else {
        setv = cwal_value_undefined();
        switch(t ? t->ttype : 0){
          case 0:
              break;
          case TT_EOL:
          /* case TT_ChComma: */
          case TH1ISH_RC_EOC:
              /* Naive attempt at discovering whether or not we have an
                 expression to parse. */
              *next = t->right;
              break;
          default:
              /* dump_token(t,"Evaling VAL part"); */
              rc = th1ish_eval_1( ie, t, next, &setv );
              t = *next = th1ish_skip(ie,*next);
              break;
        }
    }
    if(rc) goto end;
    else if(IE_SKIP_MODE) {
        goto commaCheck;
    }

    assert(isUnset ? !setv : !!setv);

    assert(key);
    rc = th1ish_set_v( ie, callPoint, obj, key, setv );
    if(isUnset) {
        if(obj && (CWAL_RC_NOT_FOUND==rc)){
            rc = 0;
            setv = cwal_value_false();
        }
        else if(!rc) setv = cwal_value_true();
    }
    if(!rc) *rv = setv;
    else if((CWAL_RC_OOM!=rc) && (CWAL_RC_EXCEPTION!=rc)){
        rc = th1ish_toss_token(ie, t, rc,
                               "'%s' failed with code %d (%s).",
                               (isUnset ? "unset" : "set"),
                               rc, cwal_rc_cstr(rc));
        goto end;
    }
    commaCheck:
    if(!rc){
        t = th1ish_skip(ie,*next)
            /* Consider using cwal_st_skip(*next) here */
            ;
        switch(t ? t->ttype : 0){
          case TT_OpAssign:{
              char const * msg = isUnset
                  ? "'=' is not legal in 'unset'."
                  : "'set' cannot chain assignments "
                    "(the '=' operator can!)."
                  ;
              rc = th1ish_toss_token(ie, t, CWAL_RC_MISUSE, msg);
              break;
          }
          case TT_ChComma:
              goto start;
          default:
              break;
        }
    }
    end:
    th1ish_this_restore(ie);
    return rc;
}

int th1ish_keyword_get(th1ish_interp * ie, cwal_simple_token * t,
                       cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_value * idv = cwal_value_undefined();
    cwal_simple_token * idt;
    assert(TT_KeywordGet==t->ttype);
    /* FIXME: support obj.prop properly */
    idt = *next = TNEXT(t);
    rc = th1ish_eval_1( ie, idt, next, &idv );
    if(rc) {
        goto end;
    }
    assert(idv);
    if(!cwal_props_can(idv)){
        idv = th1ish_var_get_v( ie, -1, idv );
        *rv = idv ? idv : cwal_value_undefined();
    }
    else {
        cwal_value * obj = idv;
        idv = cwal_value_undefined();
        t = *next;
        rc = th1ish_eval_1( ie, t, next, &idv );
        if(rc) goto end;
        assert(idv);
        /* if(obj) th1ish_dump_val(obj,"Object"); */
        /* th1ish_dump_val(idv,"Property name"); */
        if(cwal_value_is_array(obj) && cwal_value_is_integer(idv)){
            cwal_int_t i = cwal_value_get_integer(idv);
            if(i>=0){
                idv = cwal_array_get(cwal_value_get_array(obj), (cwal_size_t)i);
            }
            obj = 0;
        }
        else{
            idv = obj ? cwal_prop_get_v( obj, idv ) : 0;
        }
        *rv = idv ? idv : cwal_value_undefined();
        /* th1ish_dump_val(*rv,"GET result"); */
    }
    end:
    return rc;
}

int th1ish_keyword_var(th1ish_interp * ie, cwal_simple_token * t,
                       cwal_simple_token ** next,
                       cwal_value ** rv ){
    if((TT_KeywordVarDecl != t->ttype)
       && (TT_KeywordVarDeclConst != t->ttype)){
        return CWAL_SCR_CANNOT_CONSUME;
    }else{
        int rc;
        cwal_value * v = 0;
        cwal_string * key = NULL;
        cwal_value * keyV = 0;
        int const op = t->ttype;
        cwal_simple_token * idt;
        uint16_t flags =
            (TT_KeywordVarDeclConst==op)
            ? CWAL_VAR_F_CONST
            : CWAL_VAR_F_NONE;
        start:
        *next = t = idt = TNEXT(t);
        if(!cwal_st_isa(t,TT_Identifier)){
            return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                     "Expecting identifier in var declaration.");
        }
        rc = th1ish_st_create_value(ie, t, &keyV);
        if(rc) return rc;
        if(!IE_SKIP_MODE){
            key = cwal_value_get_string(keyV);
            if(!key) {
                return th1ish_toss_token(ie, t, CWAL_RC_ERROR,
                                         "Identifier token is missing its string part.");
            }
        }
        *next = t = th1ish_skip(ie,t->right);
        if(t && (TT_OpAssign==t->ttype)){/* var x = ... */
            *next = t = TNEXT(t);
        }
        switch(t ? t->ttype : 0){
          case 0:
              v = 0;
              break;
          case TT_ChComma:
              v = 0;
              break;
          case TT_EOL:
          case TH1ISH_RC_EOC:
              /* Naive attempt at discovering whether or not we have an
                 expression to parse. This will likely break at some point.
              */
              *next = t->right;
              v = 0;
              break;
          default:
              rc = th1ish_eval_0( ie, t, next, &v );
              if(rc) return rc;
              *rv = v;
              break;
        }

        if(!IE_SKIP_MODE){
            if(!v && (CWAL_VAR_F_CONST & flags)){
                return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                         "Const declaration requires a value.");
            }
            /* th1ish_dump_val(cwal_string_value(key), "VAR key"); */
            /* th1ish_dump_val(v, "VAR value"); */
            rc = cwal_var_decl_s(ie->e, 0, key,
                                 v ? v : cwal_value_undefined(),
                                 flags );
            if(rc){
                return th1ish_toss_token(ie, idt, rc,
                                         "Var decl '%.*s' failed with code %d (%s).",
                                         (int)cwal_string_length_bytes(key),
                                         cwal_string_cstr(key),
                                         rc, cwal_rc_cstr(rc) );
            }
        }
        /* Now goto the top if we find a comma... */
        t = th1ish_skip(ie,*next)
            /* might want to consider using cwal_st_skip(*next) for
               this. */;
        if(t && (TT_ChComma == t->ttype)) goto start;
        return rc;
    }
}


/**
   Evaluator for builtin constant values. We do this as keywords, instead
   of as vars/constants, purely as a speed optimization.
*/
int th1ish_keyword_builtin_value(th1ish_interp * ie, cwal_simple_token * t,
                                 cwal_simple_token ** next, cwal_value ** rv ){

    cwal_value * v =0;
    switch(t ? t->ttype : 0){
      case TT_KeywordTrue:
          v = cwal_value_true();
          break;
      case TT_KeywordFalse:
          v = cwal_value_false();
          break;
      case TT_KeywordNull:
          v = cwal_value_null();
          break;
      case TT_KeywordUndef:
          v = cwal_value_undefined();
          break;
      default:
          return CWAL_SCR_CANNOT_CONSUME;
    }
    if(v){
        *next = t->right;
        *rv = v;
    }
    return 0;
}

/**
   cwal_finalizer_f() impl for th1ish_proc_state.
*/
static void th1ish_proc_state_finalize( cwal_engine * e, void * v ){
    if(v){
        /* MARKER("Freeing th1ish_proc_state@%p\n", v); */
        th1ish_proc_state * s = (th1ish_proc_state*)v;
        char const recycling = (0 != s->ie->recycler.tokens.maxLength)
            /*
              If recycling is on here we can leak entries because
              cwal_engine cleanup can/will trigger this call.
              The catch is that th1ish_interp_finalize() has to
              use the engine to free the memory, but its recycle
              list would normally be modified by this function
              during engine destruction. After engine destruction
              it is not legal to use it for cwal_free().
              Chicken, egg.
            */;
        if(s->cParams){
            /* MARKER("Freeing th1ish_proc_state@%p PARAMS\n", v); */
            th1ish_st_free_chain( s->ie, s->cParams, recycling );
            s->cParams = 0;
        }
        if(s->cBody){
            /* MARKER("Freeing th1ish_proc_state@%p BODY\n", v); */
            th1ish_st_free_chain( s->ie, s->cBody, recycling );
            s->cBody = 0;
        }
        cwal_free( e, v );
    }
}

/**
   Parses:  identifier [=] [value][, repeat]

   into var decls in the current scope. t must point to the first
   token in the param list chain. The _name_ of each parameter is
   appended to dest if dest is not NULL.
*/
static int th1ish_func_params_grok_chain( th1ish_interp * ie,
                                          cwal_simple_token * t,
                                          cwal_array * dest ){
    int rc;
    cwal_simple_token * _next = 0;
    cwal_simple_token ** next = &_next;
    cwal_value * v = 0;
    cwal_value * key;

    start:
    v = cwal_value_undefined();
    *next = t = cwal_st_skip(t);
    switch(t ? t->ttype : TT_EOF){
      case TT_EOF:
          return 0/*??CWAL_SCR_EOF*/;
      case TT_Identifier:
          break;
      case TT_ChParenClose:
      case TT_ChScopeClose:
      case TT_ChBraceClose/*???*/:
          /** Allow a list closer only because their
              appearance is a consequence of how we sub-parse
              them. Note that we are not 100% correct here - we
              might be closed by a stray closer of a different
              type.  FIXME: add the opener/close type to the
              params and only allow the proper one to close the
              list.
          */
          *next = t->right;
          return 0;
      default:
        /* dump_token(t,"groking args list"); */
        rc = th1ish_toss_token(ie, t, CWAL_SCR_EXPECTING_IDENTIFIER,
                               "Expecting identifier.");
        return rc;
    }
    key = th1ish_token_to_string( ie, t );
    if(!key) {
        rc = th1ish_toss_token(ie, t, CWAL_RC_ERROR,
                               "Internal error (or OOM) while allocating "
                               "value for token");
        goto end;
    }
    *next = t = th1ish_skip(ie,t->right);
    if(t && (TT_OpAssign==t->ttype)){/* x = ... */
        *next = t = TNEXT(t);
    }
    switch(t ? t->ttype : 0){
      case 0:
          v = 0;
          break;
      case TT_ChComma:
          v = 0;
          break;
      case TT_EOL:
      case TH1ISH_RC_EOC:
          /* Naive attempt at discovering whether or not we have an
             expression to parse. This will likely break at some point.
          */
          *next = t->right;
          v = 0;
#if 0
          /* Achtung: this might not be safe. It's an experiment. */
          cwal_engine_sweep(ie->e);
#endif
          break;
      default:
          rc = th1ish_eval_0( ie, t, next, &v );
          if(rc) goto end;
          t = th1ish_skip(ie,*next);
          break;
    }
    if(!IE_SKIP_MODE){
        if(!v) v = cwal_value_undefined();
        rc =
            cwal_scope_chain_set_v( cwal_scope_current_get(ie->e), 0,
                                    key, v );
            /*cwal_var_decl_v(ie->e, 0, key, v ? v : cwal_value_undefined(), flags )*/
        ;
        if(!rc && dest){
            rc = cwal_array_append( dest, key );
        }
        if(rc) {
            rc = th1ish_toss_token(ie, t, rc, "Var decl failed.");
            goto end;
        }
    }

    /* Now goto the top if we find a comma... */
    t = th1ish_skip(ie,*next);
    if(t && (TT_ChComma == t->ttype)) {
        t = TNEXT(t);
        goto start;
    }

    
    end:
    return rc;

}

#if 0
static int th1ish_func_params_grok( th1ish_interp * ie, cwal_simple_token * t,
                                    cwal_array * dest ){
    int rc;
    cwal_simple_token * head = 0;
    rc = th1ish_tokenize_all( ie, t->src.begin, t->src.end-t->src.begin, &head);
    if(!rc) = th1ish_func_params_grok_chain( ie, head, dest );
    if(head) th1ish_st_free_chain( ie, head, 1 );
    return rc;
}
#endif

/**
   A cwal_callback_f() impl which gets installed by the 'proc'
   keyword. Requires that args->state be a (th1ish_proc_state*).
*/
static int th1ish_cwal_f_callback( cwal_callback_args const * args, cwal_value **rv ){
    int rc = 0;
    th1ish_proc_state * ps =
        (th1ish_proc_state *)cwal_args_state(args, &th1ish_proc_state_empty);
    cwal_array * argsA = ps->argNames;
    cwal_size_t aLen;
    th1ish_interp * ie = ps->ie;
    cwal_value * callRv = 0;
    cwal_size_t i;
    cwal_scope * scope = cwal_scope_current_get(ie->e);
    cwal_simple_token * cParams = 0;
    cwal_simple_token * cBody = 0;
    cwal_simple_token * bogoNext = 0;
#if 0
    unsigned tBodyLen = ps->tBody.src.end - ps->tBody.src.begin;
    unsigned tParamLen = ps->tParams.src.end - ps->tParams.src.begin;
    MARKER(("call() w/ %u args\n", (unsigned) args->argc));
    MARKER(("proc params: %.*s\n", (int)tParamLen, ps->tParams.src.begin));
    MARKER(("proc body: %.*s\n", tBodyLen, ps->tBody.src.begin));
#endif    

    ++ps->recurseCount;
    cParams = ps->cParams;
    cBody = ps->cBody;

    if(!cParams){
        /* Tokenize parameters (not arguments)... */
        int srcLen = (int)(ps->tParams.src.end - ps->tParams.src.begin);
        assert(srcLen>=0);
        /*valgrind check: */ assert(srcLen || *ps->tParams.src.begin);
        rc = th1ish_tokenize_all( ie, ps->tParams.src.begin,
                                  (cwal_size_t)srcLen,
                                  &cParams);
        if(rc) goto end;
        /* Reminder to self:
           ALWAYS set ps->cParams and ps->cBody so that recursive calls
           do not re-tokenize. We will do the TH1ISH_F_CACHE_PROCS
           check at the end.
        */
        if(cParams){
            assert(1==ps->recurseCount);
        }
        ps->cParams = cParams;
    }
    if(!argsA){
        /* Cache parameter names */
        argsA = cwal_new_array(ie->e);
        if(!argsA){
            rc = CWAL_RC_OOM;
            goto end;
        }
        /* Now hold a reference somewhere with a useful
           lifetime: */
        cwal_prop_set_v( cwal_function_value(args->callee),
                         cwal_value_null(),
                         cwal_array_value( argsA ) );
        /* th1ish_dump_val(cwal_array_value(argsA),"ARGS?"); */
    }

#if 0
    /**
       Required in order to keep the 'this' from being overridden with
       a Function in some contexts. ??? (Next time explain better,
       please!)

       In any case, we need "this" (as it were).

       Reminder: set "this" before parsing parameters so that we can do:

       proc(a,b=this){...}
    */
    rc = cwal_scope_chain_set(cwal_scope_current_get(args->engine), 0,
                              "this", 4, args->self);
    if(rc) goto end;
#endif    

    
    /*
      Set var names in the current scope...

      We unfortunately have to grok this each time, instead of caching
      once, because we otherwise don't have access to the declared
      default values (unless we cache them). We cache the param name
      list only on the first call, though.
    */
    rc = th1ish_func_params_grok_chain( ie, cParams,
                                        ps->argNames ? NULL : argsA );
    if(rc) goto end;
    if(!ps->argNames) ps->argNames = argsA;
    
    /* th1ish_dump_val(cwal_array_value(argsA), "parameter list"); */

    /* Set local variables for each named parameter... */
    aLen = cwal_array_length_get( argsA );
    for( i = 0;
         (i < args->argc) /*
                             reminder: if we cache the default values
                             we need to move this second part into the
                             chain_set_v() call.

                             reminder: we cannot safely cache default
                             parameters if they live in a higher scope
                             which gets destroyed between calls.
                          */;
         ++i ){
        cwal_value * key = (i < aLen) ? cwal_array_get( argsA, i ) : NULL;
        if(key){
            rc = cwal_scope_chain_set_v( scope, 0, key, args->argv[i] );
            if(rc) goto end;
        }
        /* th1ish_dump_val(args->argv[i],"args->argv[i]"); */
    }

    if(!cBody){
        /* Tokenize the code body */
        cwal_size_t const codeLen = ps->tBody.src.end - ps->tBody.src.begin;
        if(codeLen){
            rc = th1ish_tokenize_all( ie, ps->tBody.src.begin, codeLen, &cBody);
            if(CWAL_SCR_EOF==rc){
                rc = 0;
                assert(!cBody);
            }
            /* MARKER("Caching code body. rc=%s\n",cwal_rc_cstr(rc)); */
            /* dump_token(cBody,"cBody"); */
            if(rc) goto end;
            if(cBody){
                assert(1==ps->recurseCount);
            }
            ps->cBody = cBody;
        }
            
    }
    
    /* Evaluate the body... */
    /* MARKER("Function body: <<<%.*s>>>\n", (int)codeLen, code ); */
    /* th1ish_dump_val(cwal_object_value(cwal_scope_current_get(ie->e)->props), "Param props"); */

    callRv = cwal_value_undefined();

    /* dump_token(cBody,"cBody"); */
    rc = cBody
        ? th1ish_eval_chain( ie, cBody, &bogoNext, &callRv )
        : 0;
    if(CWAL_RC_RETURN==rc){
        /* dump_token(cBody, "function caught RETURN"); */
        /* cwal_exception_set( ie->e, 0 ); */
        /* th1ish_dump_val( callRv, "proc callback got RETURN"); */
#if 1
        if(cwal_value_scope(callRv)==scope){
            /*
              This ref should not be necessary!  OTOH, i haven't yet
              seen it triggered, so this appears to be dead code.
            */
            if(cwal_value_refcount(callRv)){
                th1ish_dump_val(callRv,"Adding painful reference");
                assert(!"Never seen this triggered.");
                cwal_value_ref(callRv);
            }
            assert(cwal_scope_parent(scope));
            cwal_value_rescope( cwal_scope_parent(scope), callRv );
        }
#endif
        /* cwal_value_rescope( callingScope, callRv ); */
        /* th1ish_result_set(ie, callRv); */
        assert( scope != cwal_value_scope(callRv) );
        *rv = callRv;
        rc = 0;
    }
    end:
#if 0
    if(rc && (CWAL_RC_EXCEPTION!=rc)){
        rc = th1ish_toss_token(ie, cBody ? cBody : cParams /* not exactly correct */,
                               rc,
                               "Function had non-Exception error #%d (%s).",
                               rc, cwal_rc_cstr(rc));
    }
#endif

    --ps->recurseCount;
    if(!ps->recurseCount && !(TH1ISH_F_CACHE_PROCS & ie->flags)){
        if(cParams){
            th1ish_st_free_chain( ie, cParams, 1 );
            ps->cParams = NULL;
        }
        if(cBody){
            th1ish_st_free_chain( ie, cBody, 1 );
            ps->cBody = NULL;
        }
    }
    return rc;
}

/**
   cwal_callback_f() which ONLY works on script-side functions. Trying
   to call it with a different 'this' context cause an exception.

   Maintenance reminder: not static (used in th1ish_protos.c) but not
   public.
*/
int th1ish_f_source_info( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_value * props;
    cwal_function * fSelf = cwal_value_get_function(args->self);
    th1ish_proc_state * ps = fSelf
        ? (th1ish_proc_state *)cwal_function_state_get(fSelf, &th1ish_proc_state_empty)
        : NULL;
    props = cwal_new_object_value(args->engine);
    if(!props) return CWAL_RC_OOM;
    if(!ps){ /* Non-script function */
        cwal_engine * e = args->engine;
#define STRF(X) cwal_string_value( cwal_new_stringf X )
        rc = cwal_prop_set(props, "args->callee", 13, STRF((e,"@%p",(void*)args->callee)));
        if(!rc) rc = cwal_prop_set(props, "args->state", 11, STRF((e,"@%p",(void*)args->state)));
        if(!rc) rc = cwal_prop_set(props, "args->self", 11, STRF((e,"@%p",(void*)args->self)));
        /* Other info requires internal access to cwal_function */
#undef STRF
    }else{ /* Script function */
        rc = th1ish_set_script_props(ps->ie, 0, ps->tParams.src.begin, props);
        if(!rc){
            cwal_string * rcS;
            char const * pBegin = ps->tParams.src.begin;
            char const * pEnd = ps->tParams.src.end;
            char const * bBegin = ps->tBody.src.begin;
            char const * bEnd = ps->tBody.src.end;
            /*
              We have to fudge a bit on the reconstruction of the source code because
              we do not know (without expensive analysis) if the proc body is
              a {string} or HEREDOC.
            */
            assert(pEnd>=pBegin);
            assert(bEnd>=bBegin);
            rcS = cwal_new_stringf(args->engine, "proc(%.*s)<<<:'!pr0c%p!' %.*s '!pr0c%p!'",
                                   (int)(pEnd - pBegin), pBegin, (void const *)ps,
                                   (int)(bEnd - bBegin), bBegin, (void const *)ps);
            if(!rcS){
                rc = CWAL_RC_OOM;
                goto end;
            }
            rc = cwal_prop_set(props, "sourceCode", 10, cwal_string_value(rcS));
            if(!rc) rc = cwal_prop_set(props,"isCached", 8,
                                       (ps->cBody || ps->cParams)
                                       ? cwal_value_true()
                                       : cwal_value_false());
        }
    }
    end:
    if(rc){
        cwal_value_unref(props);
    }else{
        *rv = props;
    }
    return rc;
}



int th1ish_keyword_proc(th1ish_interp * ie, cwal_simple_token * t,
                        cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * tParams = 0;
    cwal_simple_token * tBody = 0;
    /**
       Big FIXME revealed by doing this in an interactive
       linenoiseish shell:

       g.x = proc(){...}

       When we run that proc later, its tokens are possibly pointing
       to bytes which got nuked (and not cached) after that line was
       evaluated. It can be worked around (somewhat) at the client
       level, but really needs to be fixed here by storing a copy of
       the token bytes whether we like it or not.

       To trigger this easily:

       th1ish linenoiseish.th1ish

       Enter:

       prompt > g.x = proc(a,b){print(a,b), return true}

       Then run g.x a few times:

       prompt > g.x(g.x,g.x.functionInfo().sourceCode)
       function@215A4D0 proc(a,b)<<<:'!pr0c2155340!' print(a,b), return true '!pr0c2155340!'
       bool true
       prompt > g.x(g.x,g.x.functionInfo().sourceCode)
       EXCEPTION #2013: Expecting identifier.
       exception {"script": "eval", "line": 1, "column": 11, "callStack": [{"script": "eval", "column": 4, "line": 1}], "message": "Expecting identifier.", "code": 2013}

       The problem is the gone-missing tokens. Token caching hides
       this problem somewhat, apparently. Proc caching doesn't help
       because the cache is not compiled until the firs time the
       function is run (at which point the tokens have been pulled out
       from under the cached references).

       TODO? Add a refcount to token instances, and have the proc
       keyword ref all proc-part tokens?
    */
    assert(TT_KeywordFunction == t->ttype);
    t = *next = TNEXT(t);
    switch(t ? t->ttype : 0){
      case TT_ChParenOpen:
      case TT_ChScopeOpen:
          break;
      default:
          return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "'proc' expects {ARGS} {BODY} arguments");
    }
    tParams = t;
    t = *next = TNEXT(t);
    if(!t || (TT_ChScopeOpen != t->ttype)){
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "'proc' missing {BODY} argument");
    }
    tBody = t;
    *next = t->right;
    if(IE_SKIP_MODE){
        *rv = cwal_value_undefined();
        return 0;
    }


    {
        cwal_value * fv;
        th1ish_proc_state * ps = cwal_malloc(ie->e, sizeof(th1ish_proc_state));
        if(!ps){
            rc = CWAL_RC_OOM;
            goto end;
        }
        memset(ps, 0, sizeof(th1ish_proc_state));
        ps->ie = ie;
        ps->tParams = *tParams;
        ps->tBody = *tBody;
        ps->tParams.right = ps->tBody.right = NULL;
        ps->recurseCount = 0;
        fv = th1ish_new_function(ie, th1ish_cwal_f_callback,
                                 ps, th1ish_proc_state_finalize,
                                 &th1ish_proc_state_empty);
        if(!fv){
            cwal_free( ie->e, ps );
            rc = CWAL_RC_OOM;
            goto end;
        }
        *rv = fv;
    }
    
    end:
    return rc;
}

/**
   Script usage:

   if {condition} {body}

   if {condition} {body} else {body}

   if {condition} {body} else if {condition} {body} ...

*/
int th1ish_keyword_if(th1ish_interp * ie, cwal_simple_token * t,
                         cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * tCond;
    cwal_simple_token * tBody;
    cwal_value * xrv = 0;
    char cond = 0;
    char hasTrued = 0;
    char scoped = 0;
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = &pushedScope_;
    if(IE_SKIP_MODE){
        hasTrued = 1;
    }
    startIf:
    assert(TT_KeywordIf == t->ttype);
    t = *next = TNEXT(t);
    switch(t ? t->ttype : 0){
      case TT_ChParenOpen:
      case TT_ChScopeOpen:
          break;
      default:
          return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "'if' expects {CONDITION} {BODY} arguments");
    }
    tCond = t;
    t = *next = TNEXT(t);
    if(!t || (TT_ChScopeOpen != t->ttype)){
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "'if' missing {BODY} argument");
    }
    tBody = t;

    if(!scoped && !IE_SKIP_MODE){
        rc = cwal_scope_push(ie->e, &subScope);
        if(rc) return rc;
        scoped = 1;
    }

    if(!IE_SKIP_MODE && !hasTrued){
        cwal_size_t const lv = cwal_scope_current_get(ie->e)->level;
        assert(lv == pushedScope_.level);
        assert(cwal_scope_current_get(ie->e) == subScope);
        ++ie->skipNLLevel;
        rc = th1ish_eval_scope_string( ie, 0, tCond, next, &xrv );
        --ie->skipNLLevel;
        assert(cwal_scope_current_get(ie->e) == subScope);
        assert(cwal_scope_current_get(ie->e)->level == lv);
        assert(lv == pushedScope_.level);
        if(rc) goto end;
        cond = cwal_value_get_bool(xrv);
        if(cond) hasTrued = 1;
    }
    else cond = 0;
    *next = t->right;

    startBody:
    if(cond){
        rc = th1ish_eval_scope_string( ie, 0, tBody, next, &xrv );
        if(rc) goto end;
    }
    *next = t->right;
    /** Check for else/if else ... */
    t = TNEXT(t);
    if(!cwal_st_isa(t,TT_KeywordElse)) goto end;
    *next = t;
    t = TNEXT(t);
    switch(t ? t->ttype : 0){
      case TT_KeywordIf:
          goto startIf;
      case TT_ChScopeOpen:
          tBody = t;
          *next = t->right;
          cond = !hasTrued;
          goto startBody;
          break;
      default:
          rc = th1ish_toss_token( ie, t, CWAL_SCR_SYNTAX,
                                  "'else' expects 'if' or {CODE}.");
          break;
    }
    end:
    if(!rc){
        *rv = cwal_new_bool( hasTrued );
    }
    if(scoped){
        assert(pushedScope_.level);
        cwal_scope_pop(ie->e);
        assert(!pushedScope_.level);
    }
    return rc;
}

int th1ish_keyword_SCOPE(th1ish_interp * ie, cwal_simple_token * t,
                         cwal_simple_token ** next, cwal_value ** rv ){
    *next = t->right;
    *rv = cwal_scope_properties(cwal_scope_current_get(ie->e));
    return *rv ? 0 : CWAL_RC_OOM;
}

int th1ish_keyword_FILE_LINE(th1ish_interp * ie, cwal_simple_token * t,
                             cwal_simple_token ** next, cwal_value ** rv ){

    int rc;
    switch(t->ttype){
      case TT_Keyword__FILE:{
          cwal_size_t nLen = 0;
          char const * n =
              th1ish_script_name_for_pos(ie, t->src.begin, &nLen);
          *rv = nLen
              ? cwal_new_string_value(ie->e, n, nLen)
              : cwal_value_undefined();
          rc = 0;
          break;
      }
      case TT_Keyword__COLUMN:
      case TT_Keyword__LINE:{
          unsigned int line = 0;
          unsigned int col = 0;
          rc = th1ish_line_col(ie, t->src.begin, &line, &col);
          *rv = rc
              ? cwal_value_undefined()
              : cwal_new_integer(ie->e,
                                 (cwal_int_t)((TT_Keyword__LINE==t->ttype)
                                              ? line : col));
          rc = 0;
          break;
      }
      default:
          assert(!"Cannot happen");
          rc = CWAL_SCR_CANNOT_CONSUME;
          break;
    }
    if(!rc){
        rc = *rv ? 0 : CWAL_RC_OOM;
        if(!rc) *next = t->right;
    }

    return rc;
}

int th1ish_keyword_SRC(th1ish_interp * ie, cwal_simple_token * t,
                       cwal_simple_token ** next, cwal_value ** rv ){
    unsigned int line = 0, col = 0;
    char const * scrName =
        th1ish_script_name_for_pos(ie, t->src.begin, 0);
    *next = t->right;
    th1ish_line_col( ie, t->src.begin, &line, &col );
    *rv = cwal_string_value(cwal_new_stringf(ie->e,"%.*s:%u:%u",
                                             scrName
                                             ? (int)strlen((char const *)scrName)
                                             : 9,
                                             scrName
                                             ? scrName
                                             : NULL,
                                             line, col));
    return *rv ? 0 : CWAL_RC_OOM;
}



/**
   Script usage:

   while {condition} {body}

*/
int th1ish_keyword_while(th1ish_interp * ie, cwal_simple_token * t,
                         cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * tCond;
    cwal_simple_token * tBody;
    cwal_simple_token * cCond = 0;
    cwal_simple_token * cBody = 0;
    cwal_simple_token * bogoNext = 0;
    cwal_value * xrv = 0;
    /* cwal_scope * callingScope = cwal_scope_current_get(ie->e); */
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = &pushedScope_;
    th1ish_eval_f const evalFunc =
        (TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags)
        ? th1ish_eval_chain_push_scope
        : th1ish_eval_chain
        /**
           Function to use for parsing the body.  The runtime
           difference is measurable when doing any significant
           looping, but we need the push-scope variant if we want to
           support in-loop var decls.

           In a script comprised only of looping, with an almost-empty
           loop body, i'm seeing roughly a 15% speed difference, which
           argues greatly in favour of punting on in-loop-body vars.

           Interestingly, the memory cost is the same as long as
           recycling is on and has enough capacity.
        */;
    assert(TT_KeywordWhile == t->ttype);
    t = *next = TNEXT(t);
    switch(t ? t->ttype : 0){
      case TT_ChParenOpen:
      case TT_ChScopeOpen:
          break;
      default:
          return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "'while' expects {CONDITION} {BODY} arguments");
    }
    tCond = t;

    t = *next = TNEXT(t);
    if(!t || (TT_ChScopeOpen != t->ttype)){
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "'while' missing {BODY} argument");
    }
    tBody = t;
    *next = t->right;

    *rv = cwal_value_undefined();
    if(IE_SKIP_MODE){
        return 0;
    }

    rc = cwal_scope_push(ie->e, &subScope);
    if(rc) return rc;

    rc = th1ish_tokenize_all( ie, tCond->src.begin,
                              tCond->src.end - tCond->src.begin,
                              &cCond );
    if(rc) goto end;
    while(1){
        xrv = cwal_value_undefined();
        ++ie->skipNLLevel;
        rc = th1ish_eval_chain( ie, cCond, &bogoNext, &xrv );
        --ie->skipNLLevel;
        if(rc) goto end;
        else if(!cwal_value_get_bool(xrv)) break;
        else {
            /* dump_token(cCond,"WHILE condition true"); */
            if(!cBody){
                /* Tokenize the code body */
                cwal_size_t const codeLen = tBody->src.end - tBody->src.begin;
                if(codeLen){
                    rc = th1ish_tokenize_all( ie, tBody->src.begin,
                                              tBody->src.end - tBody->src.begin,
                                              &cBody );
                    if(rc) goto end;
                }
            }
            rc = cBody ? evalFunc( ie, cBody, &bogoNext, &xrv ) : 0;
            switch(rc){
              case 0: break;
              case CWAL_RC_BREAK:
                  goto end;
              case CWAL_RC_CONTINUE:
                  rc = 0;
                  break;
              default:
                  goto end;
            }
            if(!(TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags)){
                cwal_engine_sweep( ie->e );
            }
        }
    }
   
    end:
    if( cCond ) th1ish_st_free_chain( ie, cCond, 1 );
    if( cBody ) th1ish_st_free_chain( ie, cBody, 1 );
    switch( rc ){
      case CWAL_RC_BREAK:
          assert(ie->returnVal);
          *rv = ie->returnVal;
          ie->returnVal = NULL;
          /* cwal_value_rescope(callingScope, *rv); */
          /* th1ish_dump_val(*rv, "BREAK value?"); */
          rc = 0;
          break;
      case CWAL_RC_RETURN:
          /* th1ish_dump_val(ie->returnVal, "WHILE passing a RETURN by: ie->returnVal"); */
          /* th1ish_result_up( ie ); */
          break;
      default:
          break;
    }
    assert(pushedScope_.level);
    cwal_scope_pop(ie->e);
    assert(!pushedScope_.level);
    return rc;
}

/**
   Script usage:

   do {body} [while] {condition}

*/
int th1ish_keyword_do_while(th1ish_interp * ie, cwal_simple_token * t,
                         cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * tCond;
    cwal_simple_token * tBody;
    cwal_simple_token * cCond = 0;
    cwal_simple_token * cBody = 0;
    cwal_simple_token * bogoNext = 0;
    cwal_value * xrv = 0;
    /* cwal_scope * callingScope = cwal_scope_current_get(ie->e); */
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = &pushedScope_;
    th1ish_eval_f const evalFunc =
        (TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags)
        ? th1ish_eval_chain_push_scope
        : th1ish_eval_chain;
    assert(TT_KeywordDo == t->ttype);
    t = *next = TNEXT(t);
    switch(t ? t->ttype : 0){
      case TT_ChParenOpen:
      case TT_ChScopeOpen:
          break;
      default:
          return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                   "'do' expects {BODY} while {CONDITION} arguments");
    }
    tBody = t;

    t = *next = TNEXT(t);
    if(t && (TT_KeywordWhile == t->ttype)){
        t = *next = TNEXT(t);
    }
    if(!t || (TT_ChScopeOpen != t->ttype)){
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "'do' missing {CONDITION} argument");
    }
    tCond = t;
    *next = t->right;

    *rv = cwal_value_undefined();
    if(IE_SKIP_MODE){
        return 0;
    }

    rc = cwal_scope_push(ie->e, &subScope);
    if(rc) return rc;

    do{
        xrv = cwal_value_undefined();
        if(!cBody){
            /* Tokenize {BODY} */
            cwal_size_t const codeLen = tBody->src.end - tBody->src.begin;
            if(codeLen){
                rc = th1ish_tokenize_all( ie, tBody->src.begin,
                                          tBody->src.end - tBody->src.begin,
                                          &cBody );
                if(rc) goto end;
            }
        }
        rc = cBody ? evalFunc( ie, cBody, &bogoNext, &xrv ) : 0;
        switch(rc){
          case 0: break;
          case CWAL_RC_BREAK:
              goto end;
          case CWAL_RC_CONTINUE:
              rc = 0;
              break;
          default:
              goto end;
        }

        if(!cCond){
            /* Tokenize {CONDITION} */
            rc = th1ish_tokenize_all( ie, tCond->src.begin,
                                      tCond->src.end - tCond->src.begin,
                                      &cCond );
            if(rc) goto end;
        }

        xrv = cwal_value_undefined();
        ++ie->skipNLLevel;
        rc = th1ish_eval_chain( ie, cCond, &bogoNext, &xrv );
        --ie->skipNLLevel;
        if(rc) goto end;
        else if(!cwal_value_get_bool(xrv)) break;
        else{
            if(!(TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags)){
                cwal_engine_sweep( ie->e );
            }
            continue;
        }
    }while(1);
   
    end:
    if( cCond ) th1ish_st_free_chain( ie, cCond, 1 );
    if( cBody ) th1ish_st_free_chain( ie, cBody, 1 );
    switch( rc ){
      case CWAL_RC_BREAK:
          assert(ie->returnVal);
          *rv = ie->returnVal;
          ie->returnVal = NULL;
          /* cwal_value_rescope(callingScope, *rv); */
          /* th1ish_dump_val(*rv, "BREAK value?"); */
          rc = 0;
          break;
      case CWAL_RC_RETURN:
          /* th1ish_dump_val(ie->returnVal, "WHILE passing a RETURN by: ie->returnVal"); */
          /* th1ish_result_up( ie ); */
          break;
      default:
          break;
    }
    assert(pushedScope_.level);
    cwal_scope_pop(ie->e);
    assert(!pushedScope_.level);
    return rc;
}


/**
   Script usage:

   for {pre} {condition} {post} {body}

*/
int th1ish_keyword_for(th1ish_interp * ie, cwal_simple_token * t,
                         cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * tPre = 0;
    cwal_simple_token * tCond = 0;
    cwal_simple_token * tPost = 0;
    cwal_simple_token * tBody = 0;
    cwal_simple_token * cCond = 0;
    cwal_simple_token * cBody = 0;
    cwal_simple_token * cPost = 0;
    cwal_simple_token * bogoNext = 0;
    cwal_value * xrv = 0;
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = &pushedScope_;
    /* cwal_scope * callingScope = cwal_scope_current_get(ie->e); */
    char const * usage = "'for' expects {PRE} {CONDITION} {POST} {BODY} arguments";
    th1ish_eval_f const evalFunc =
        (TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags)
        ? th1ish_eval_chain_push_scope
        : th1ish_eval_chain
        /**
           Function to use for parsing the body.  The runtime
           difference is measurable when doing any significant
           looping, but we need the push-scope variant if we want to
           support in-loop var decls.

           In a script comprised only of looping, with an almost-empty
           loop body, i'm seeing roughly a 15% speed difference, which
           argues greatly in favour of punting on in-loop-body vars.
        */
        ;
    assert(TT_KeywordFor == t->ttype);

#define PART(TOK) \
    t = *next = TNEXT(t); \
    if(!t || (TT_ChScopeOpen != t->ttype)){ \
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX, usage); \
    } \
    TOK = t

    /* Collect {tPre} {tCond} {tPost} {tBody} */
    PART(tPre);
    PART(tCond);
    PART(tPost);
    PART(tBody);
#undef PART

    *next = t->right;
    *rv = cwal_value_undefined();
    if(IE_SKIP_MODE){
        return 0;
    }

    rc = cwal_scope_push(ie->e, &subScope);
    if(rc) return rc;

    ++ie->skipNLLevel;
    rc = th1ish_eval_scope_string( ie, 0, tPre, &bogoNext, &xrv );
    --ie->skipNLLevel;
    if(rc) goto end;
    {
        /* Tokenize the condition part */
        cwal_size_t const codeLen = tCond->src.end - tCond->src.begin;
        if(codeLen){
            rc = th1ish_tokenize_all( ie, tCond->src.begin,
                                      codeLen, &cCond);
            if(rc) goto end;
        }
    }
    if(cCond) while(1){
        /* cwal_scope * pushy = &pushed2; */
        /* pushed2 = cwal_scope_empty; */
        /*i would like to push an additional scope
          to solve the problem that var decls in loops
          break on the 2nd iteration, but doing so
          corrupts memory and i haven't the energy to
          track down why :/.
        */

        if(!cBody){
            /* Tokenize the code body */
            cwal_size_t const codeLen = tBody->src.end - tBody->src.begin;
            if(codeLen){
                rc = th1ish_tokenize_all( ie, tBody->src.begin,
                                          codeLen, &cBody);
                if(rc) goto end;
            }
            /* MARKER("Caching code body. rc=%s\n",cwal_rc_cstr(rc)); */
            /* dump_token(cBody,"cBody"); */
        }

        if(!cPost){
            cwal_size_t const codeLen = tPost->src.end - tPost->src.begin;
            if(codeLen){
                rc = th1ish_tokenize_all( ie, tPost->src.begin,
                                          tPost->src.end - tPost->src.begin,
                                          &cPost );
                if(rc) goto end;
            }
        }

        /* rc = cwal_scope_push(ie->e, NULL) */
        /* We do this so that var decls in a loop body
           do not error out on their second time around.
        */;
        assert(!rc);
        
        xrv = cwal_value_undefined();
        ++ie->skipNLLevel;
        rc = th1ish_eval_chain( ie, cCond, &bogoNext, &xrv );
        --ie->skipNLLevel;
        if(rc) goto end;
        else if(!cwal_value_get_bool(xrv)){
            break;
        }
        else {
            xrv = cwal_value_undefined();
            rc = cBody ? evalFunc(ie, cBody, &bogoNext, &xrv ) : 0;
            if( CWAL_RC_CONTINUE == rc ){
                rc = 0;
            }
            else if(rc) goto end;
            xrv = cwal_value_undefined();
            ++ie->skipNLLevel;
            rc = cPost
                ? evalFunc( ie, cPost, &bogoNext, &xrv )
                : 0;
            --ie->skipNLLevel;
        }
        if(rc) goto end;
        if(!(TH1ISH_F_LOOPS_EXTRA_SCOPES & ie->flags)){
            cwal_engine_sweep( ie->e );
        }
    }
   
    end:
    if( cPost ) th1ish_st_free_chain( ie, cPost, 1 );
    if( cCond ) th1ish_st_free_chain( ie, cCond, 1 );
    if( cBody ) th1ish_st_free_chain( ie, cBody, 1 );
    switch( rc ){
      case CWAL_RC_BREAK:
          assert(ie->returnVal);
          *rv = ie->returnVal;
          ie->returnVal = NULL;
          /* cwal_value_rescope(callingScope, *rv); */
          /* th1ish_dump_val(*rv, "BREAK value?"); */
          rc = 0;
          break;
      case CWAL_RC_RETURN:
          /* th1ish_dump_val(ie->returnVal, "FOR passing a RETURN by: ie->returnVal"); */
          /* th1ish_result_up( ie ); */
          break;
      default:
          break;
    }
    assert(pushedScope_.level);
    cwal_scope_pop(ie->e);
    assert(!pushedScope_.level);
    return rc;
}


int th1ish_eval_scope_string(th1ish_interp * ie,
                             char addScope,
                             cwal_simple_token * t,
                             cwal_simple_token ** next,
                             cwal_value ** rv ){
    int rc;
    cwal_size_t codeLen = 0;
    char const * code = 0;
    cwal_buffer escBuf = cwal_buffer_empty;
    th1ish_this_save(ie);
    switch(t ? t->ttype : TT_EOF){
      case TT_EOF: return 0 /* CWAL_SCR_EOF */;
      case TT_ChParenOpen:
      case TT_ChBraceOpen:
      case TT_ChScopeOpen:
          *next = t->right;
          if(IE_SKIP_MODE) return 0;
          code = t->src.begin;
          codeLen = t->src.end - t->src.begin;
          break;
      case TT_LiteralStringSQ:
      case TT_LiteralStringDQ:
          *next = t->right;
          if(IE_SKIP_MODE) return 0;
          /*
            Reminder: because we have to unescape the string, we are
            forced to allocate an unescape buffer. We cannot used
            ie->buffer because the code being evaluated might call
            back to here and it would overwrite the buffer,
            invalidating the script bytes pointed to by the tokens
            higher-up executions of this routine are doing.
           */
          assert((t->src.end - t->src.begin)>=2);
          codeLen = t->src.end - t->src.begin;
          rc = cwal_buffer_reserve(ie->e, &escBuf, codeLen+1/*NUL*/ );
          if(rc) goto end;
          rc = cwal_st_unescape_string(ie->e,
                                       (unsigned char const *)t->src.begin + 1 /*quote*/,
                                       (unsigned char const *)t->src.end - 1 /*quote*/,
                                       &escBuf );
          if(rc) goto end;
          code = (char const *)escBuf.mem;
          codeLen = escBuf.used;
          *next = t->right;
          break;
      default:
          rc = CWAL_SCR_CANNOT_CONSUME;
          goto end;
    }
    if(!codeLen){
        *rv = cwal_value_undefined();
        rc = 0;
        goto end;
    }
#if 0
    MARKER("%sing code: <<<%.*s>>>\n",
           addScope ? "EVAL" : "SCOPE", (int)codeLen, code );
#endif
    if(codeLen){
        rc = th1ish_eval_string( ie, addScope, code, codeLen, rv );
    }
    else {
        *rv = cwal_value_undefined();
    }
    switch(rc){
      case CWAL_SCR_EOF:
          rc = 0;
          break;
      case CWAL_RC_RETURN:
          if(addScope>0){
              /* *rv is up-scoped if necessary in th1ish_eval_string() */
              rc = 0;
          }
      default:
          break;
    }
    end:
    th1ish_this_restore(ie);
    if(escBuf.mem){
        cwal_buffer_reserve( ie->e, &escBuf, 0 );
    }
    return rc;
}

/**
   Script usage:

   eval {CODE}
   scope {CODE}

   Tokenizes and evals the string and *rv is set. to the
   result. 'eval' is evaluated in the current scope, where 'scope'
   uses a new scope for evaluation and honors the CWAL_RC_RETURN
   value as "success", and sets rv to that result value.
*/
int th1ish_keyword_eval(th1ish_interp * ie, cwal_simple_token * t,
                        cwal_simple_token ** next, cwal_value ** rv ){
    int rc = 0;
    char const doScope = (TT_KeywordScope==t->ttype);
    int const ttype = t->ttype;
    int const oldSweepGuard = ie->sweepGuard;
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * pushed = doScope ? &pushedScope_ : 0;
    th1ish_this_save(ie);
    assert((TT_KeywordEval==ttype) || (TT_KeywordScope==ttype));
    *next = t = TNEXT(t);
    if(doScope) ie->sweepGuard = 0;
    switch(t ? t->ttype : 0){
        /*
          This is the wrong way to do this. We "should"
          keep evaluating the chain and, based on the result,
          return either that RV or (if the result is a string)
          eval() it. That leads to an ambiguity, though:
          
              eval $funcref

          where $funcref returns some string.

          The current impl means we can't do:

             set code {...}
             eval code
             # or: eval $code

          However, the proper way of doing it has the
          drawback that evaling the script-side string representation
          loses the line/column numbers (resetting them, since they
          are inside a new string value). The current impl allows us
          to reference the original string sources, keeping error
          reporting intact.
        */
      case TT_LiteralStringDQ:
      case TT_LiteralStringSQ:
      case TT_ChScopeOpen:{
          rc = th1ish_eval_scope_string( ie, doScope ? 1 : 0,
                                         t, next, rv )
              /* Reminder to self: to disable the "return swallowing" for
                 'scope', pass -1 as the 2nd parameter. */
              ;
          break;
      }
      default: {
          cwal_scope * callingScope = doScope
              ? cwal_scope_current_get(ie->e)
              : 0;
          cwal_value * xrv = 0;
          if(doScope){
              assert(callingScope);
              rc = cwal_scope_push( ie->e, &pushed );
              if(rc) goto end;
              assert(pushedScope_.level);
          }
          /* dump_token(t, "Evaluating for EVAL/SCOPE"); */
          rc = th1ish_eval_0(ie, t, next, &xrv);
          /* th1ish_dump_val(xrv,"EVAL result"); */
          if(!rc){
              cwal_size_t codeLen = 0;
              char const * code = 0;
#if 1
              code = cwal_value_get_cstr(xrv, &codeLen);
              /* This isn't quite right - we don't really want to now
                 re-eval all string contents, but OTOH we want (eval
                 refToCodeString) to work
              */
#else
              if(cwal_value_is_string(xrv)){
                  cwal_string * str = cwal_value_get_string(xrv);
                  code = cwal_string_cstr(str);
                  codeLen = cwal_string_length_bytes(str);
              }
#endif
              if(codeLen){
                  cwal_st_src origSrc = cwal_st_src_empty;
                  char const * origName = 0;
                  char const * newName =
                      th1ish_script_name_for_pos(ie, code, 0);
                  /*MARKER(("Evaluating String result value for EVAL/SCOPE: %.*s\n",
                    (int)codeLen, code));*/
                  if(!newName) newName = doScope ? "scope" : "eval";
                  th1ish_src_save(ie, &origSrc, &origName, newName,
                                  code, code + codeLen);
                  rc = th1ish_eval_string( ie, 0, code, codeLen, &xrv );
                  th1ish_src_restore( ie, &origSrc, origName );
                  /* th1ish_reset_this(ie); */
              }

              if(!rc && rv && xrv) *rv = xrv;
          }
          if(pushed){
              if(!rc && xrv){
                  cwal_value_rescope( callingScope, xrv );
              }
              cwal_scope_pop( ie->e );
              assert(!pushedScope_.level);
          }
          /* th1ish_dump_val(*rv, "after eval_1"); */
          break;
      }
    }

    end:
#if 0
    if(doScope){
        /* Workaround for a really weird bug.
           20140507 - unit tests run without this.
        */
        th1ish_reset_this(ie);
    }
#endif
    ie->sweepGuard = oldSweepGuard;
    th1ish_this_restore(ie);
#if 0
    /*
      Currently nukes *rv when *rv is a temporary and we are
      running in the global scope because *rv value cannot
      be upscoped.
    */
    if(!rc && !doScope){
        /* Clean up temps created during EVAL. */
        cwal_engine_sweep(ie->e);
    }
#endif
    return rc;
}

/**
   Script usage:

   catch {CODE}

   The code is evaluated in a new scope and *rv is set to one of
   the following:

   - The undefined value if the code ran to completion without
   one of the following conditions.

   - A value is returned via the 'return' keyword.

   - An exception is triggered, either during tokenization, evaluation
   of the code, or via the 'throw' keyword. There is currently no way
   for client code to differentiate between those cases.

*/
int th1ish_keyword_catch( th1ish_interp * ie,
                          cwal_simple_token * t,
                          cwal_simple_token ** next,
                          cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * open;
    cwal_value * vexc = 0;
    cwal_size_t codeLen;
    /* th1ish_stack_entry stack = th1ish_stack_entry_empty; */
    assert(TT_KeywordCatch == t->ttype);
    t = TNEXT(t);
    if(!t || (TT_ChScopeOpen != t->ttype)){
        return th1ish_toss_token( ie, t, CWAL_SCR_SYNTAX,
                                  "Expecting {...} after 'catch'.");
    }
    open = t;
    codeLen = t->src.end - t->src.begin;
    if(!codeLen){
        *next = t->right;
        *rv = cwal_value_undefined();
        return 0;
    }
    /* th1ish_dump_val(t->value, "String to eval"); */
    ++ie->catchLevel;
    /* th1ish_strace_push_pos(ie, (unsigned char const *)t->src.begin, &stack); */
    rc = th1ish_eval_string( ie, 1, t->src.begin, codeLen, rv );
    /* th1ish_strace_pop(ie); */
    --ie->catchLevel;
    /* th1ish_dump_val(*rv, "eval result"); */
    /* MARKER("rc=%d %s\n",rc, cwal_rc_cstr(rc)); */
    *next = t->right;
    switch(rc){
      case 0:
          *rv = cwal_value_undefined();
          break;
#if 0
      /* TODO: phase this out */
      case CWAL_RC_RETURN:
          rc = 0;
          break;
#endif
      case TH1ISH_RC_TOSS:
          /* Was up-scoped by eval-string already */
          *rv = ie->returnVal;
          /* th1ish_dump_val(*rv,"CATCH caught TOSS"); */
          ie->returnVal = 0;
          rc = 0;
          break;
      case CWAL_RC_EXCEPTION:
          vexc = cwal_exception_get(ie->e);
          assert(vexc);
          cwal_exception_set(ie->e, NULL);
          *rv = vexc;
          rc = 0;
          break;
      default:
          break;
    }
    if(!rc) *next = open->right;
    return rc;
}

int th1ish_keyword_sweep( th1ish_interp * ie,
                          cwal_simple_token * t,
                          cwal_simple_token ** next,
                          cwal_value ** rv ){
    assert(TT_KeywordSweep == t->ttype || TT_KeywordVacuum==t->ttype);
    *next = t->right;
    *rv = cwal_value_undefined();
    if(TT_KeywordVacuum==t->ttype){
        int sw = 0;
        int const rc = cwal_engine_vacuum(ie->e, &sw);
        if(!rc) *rv = sw ? cwal_value_true() : cwal_value_false();
        MARKER(("vacuum count=%d\n", sw));
        return rc;
    }else{
        cwal_size_t const sw = cwal_engine_sweep( ie->e );
        *rv = sw ? cwal_value_true() : cwal_value_false();
        /* MARKER(("sweep count=%d\n", (int)sw)); */
        return 0;
    /* We don't return sw because it seems kinda silly to allocate for
       this operation. */
    }
}

int th1ish_keyword_throw( th1ish_interp * ie,
                          cwal_simple_token * t,
                          cwal_simple_token ** next,
                          cwal_value ** rv ){
    int rc;
    cwal_simple_token * origin = t;
    assert(TT_KeywordThrow == t->ttype);
    *next = t = th1ish_skip(ie,t->right)
        /* Reminder: not skipping newlines so that a simple "throw" eventually
           becomes possible.
        */;
    /* dump_token(t,"Throwing this"); */
    *rv = cwal_value_undefined() /* required for skip mode. */;
    /* cwal_exception_set(ie->e, NULL); */
    rc = th1ish_st_eox(t) ? 0 : th1ish_eval_0( ie, t, next, rv );
    switch(rc){
      case 0: break;
      case CWAL_SCR_EOF:
          rc = 0;
          break;
      default:
          /* MARKER("THROW EVAL RC=%s\n", cwal_rc_cstr(rc)); */
          break;
    }
    if(!rc && !IE_SKIP_MODE){
        rc = th1ish_toss_token_val( ie, origin,
                                    CWAL_RC_EXCEPTION, *rv );
        assert(CWAL_RC_EXCEPTION==rc);
        *rv = cwal_value_undefined();
    }
    return rc;
}


static int th1ish_eval_func_collect_call(th1ish_interp * ie, cwal_simple_token * t,
                                         cwal_simple_token ** next,
                                         cwal_value ** rv,
                                         cwal_value * selfObj,
                                         cwal_function * func,
                                         char ignoreEOL){
    /* This function is way too long. */
    int rc = 0;
    enum { MaxArgs = CWAL_OPT_MAX_FUNC_CALL_ARGS };
    cwal_value * fv = func ? cwal_function_value(func) : NULL;
    cwal_value * argv[MaxArgs];
    uint16_t argc = 0;
    cwal_simple_token * pHead = 0;
    cwal_simple_token * origT = t;
    cwal_simple_token const * errReportPos = t;
    cwal_simple_token const * callPoint = t;
    cwal_scope * callingScope = cwal_scope_current_get(ie->e);
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = 0;

    memset( argv, 0, sizeof(argv) )
        /* Reminder: MUST even be done when IE_SKIP_MODE
           or else we end up sending uninitialized memory
           to eval_0 below when in skip mode.
        */
        ;
    if(!IE_SKIP_MODE){
        if(!fv){
            return th1ish_toss_token(ie, callPoint,
                                     CWAL_RC_TYPE,
                                     "Cannot call() non-function.");
        }
        subScope = &pushedScope_;
        rc = cwal_scope_push(ie->e, &subScope);
        if(rc) return rc;
        assert(pushedScope_.level);
    }else{
        assert(!fv);
    }
    t = th1ish_skip(ie,t);
    if(cwal_st_eof(t)){
        /*rc = CWAL_SCR_EOF;
          goto end;*/
        goto call;
    }
    else if((TH1ISH_F_CONVENTIONAL_CALL_OP & ie->flags)
            && (TT_ChParenOpen==t->ttype)){
        cwal_size_t const codeLen = t->src.end - t->src.begin;
        if(!codeLen){
            *next = t->right;
            goto call;
        }
        rc = th1ish_tokenize_all(ie, t->src.begin,
                                 codeLen, &pHead);
        if(rc) goto end;
        t = *next = pHead;
    }else{
        *next = t->right;
    }
    rc = 0;
    for( ; !cwal_st_eof(t); t = th1ish_skip(ie,*next) ){
        char doBreak = 0;
        if(argc == MaxArgs) {
            rc = th1ish_toss_token(ie, t, CWAL_RC_RANGE,
                                   "Too many arguments passed "
                                   "to function call.");
            goto end;
        }
        /* dump_token(t,"args parsing"); */
        *next = th1ish_skip(ie,t->right);
        switch(t->ttype){
            /*
              If we allow commas in func arg lists then we open up a
              syntax ambiguity:

               ($func foo, bar)

               Could be 1 or 2 expressions in the (group).
            */
          case TT_ChBraceClose:
              if(pHead){
                  rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                         "Unexpected ']' in call arguments.");
                  doBreak = 1;
                  break;
              }
          case TT_EOF:
              /* return CWAL_SCR_EOF; */
              /* Fall through... */
          case TH1ISH_RC_EOC:
              /* *next = t->right; */
              *next = t;
              doBreak = 1;
              break;
          case TT_ChComma:
              if(!argc || !pHead){
#if 1
                  *next = t;
                  doBreak = 1;
#else
                  /*
                    Leads to an error in this construct:

                    var y = 7
                    var o = object {
                      x: $proc(){
                        this.y = y
                      }.importSymbols nameof y, // <== here
                      z: 17
                    }
                   */
                  rc = th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                         "Unexpected ',' in call arguments.");
                  doBreak = 1;
#endif
                  break;
              }else{
                  /* FIXME: add check for multiple commas. */
                  *next = t->right;
                  continue;
              }
          case TT_CRNL:
          case TT_NL:
              assert(!"Missing translation of CRNL/NL to EOL somewhere.");
          case TT_EOL:
              /* MARKER("EOL in func args\n"); */
              if(/*(ie->skipNLLevel<=0) &&*/ !ignoreEOL){
                  /* dump_token(t, "Ending arg collection for newline"); */
                  *next = t;
                  doBreak = 1;
              }
              break;
#if 1
          /*
            @inlined-arrays are now done via eval_5() but we repeat it
            here for the time being because this handles _empty_
            arrays the way they should be instead of they way the
            parser will handle them in some contexts (namely that they
            evaluate to an empty array, instead of "nothing" _when_
            they evaluate at the end of an expression (because there
            is no next part to "overwrite" the array result value
            with). We need a mechanism/flag/heuristic for telling
            certain evaluators that their expression should be
            considered as "skipped", and its return value discarded.
          */
          case TT_ChAt:{
              /**
                 Treat @ARRAY as an "inlined list", copying all
                 entries from the array into the argument list.
              */
              cwal_value * av = NULL;
              cwal_array * ar;
              t = *next = TNEXT(t);
              errReportPos = t;
              rc = th1ish_eval_atom(ie, t, next, &av);
              if(rc){
                  /* errReportPos = *next; */
                  goto end;
              }
              else if(IE_SKIP_MODE) break;
              /* if(av) cwal_dump_v(av,"@-> array"); */
              else if(!av ||
                      /*!(ar=th1ish_value_array_part(ie, av))*/
                      !(ar=cwal_value_get_array(av)) 
                      ){
                  rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE,
                                         "Expecting Array after '@'.");
                  goto end;
              }else{
                  cwal_size_t const alen = cwal_array_length_get(ar);
                  cwal_size_t i;
                  errReportPos = t;
                  if((alen + argc) > MaxArgs){
                      rc = th1ish_toss_token(ie, t, CWAL_RC_RANGE,
                                             "'@' would expand to too "
                                             "many arguments. Limit is %d.\n",
                                             MaxArgs);
                      goto end;
                  }
                  for( i = 0; i < alen; ++i ){
                      cwal_value * v = cwal_array_get(ar, i);
                      argv[argc++] = v ? v : cwal_value_undefined();
                  }
                  continue;
              }

          }
#endif
          default:
              break;
        }
        if(rc) goto end;
        else if(doBreak) break;
        /* dump_token(t,"Evaling func call arg"); */
        errReportPos = t;
        rc = th1ish_eval_0( ie, t, next, &argv[argc] );
        if(rc){
            /* errReportPos = *next; */
            /*if(CWAL_SCR_EOF==rc) rc = 0;
              else*/
            goto end;
        }
        ++argc;
    }
    call:
    assert(argc<=MaxArgs);
    assert(0==rc);

    if(!selfObj) selfObj = fv;

    if(IE_SKIP_MODE) goto end;
    else {
        th1ish_stack_entry sta = th1ish_stack_entry_empty;
        cwal_value * xrv = 0;
        int const catchLevel = ie->catchLevel;
        assert(callPoint && callPoint->src.begin);
        th1ish_strace_push_pos(ie, callPoint->src.begin, &sta)
            /* This is actually 1 token to the right of where
               we want to be, especially in the case of an
               anonymous function called directly:

               $proc {}{...}

               reports the end of the final brace as the trace
               position.
            */;
        ie->catchLevel = 0;
#if 0
        /* This has stopped working since moving the
           argv/this/callee/importSymbols bits into
           cwal_callback_hooks, and i cannot quite explain why.  We
           _still_ need to create the subscope to have a sane (namely
           very brief) lifetime for all the call arg parameters.
           But calling in subScope is falling on:

           var ar = array[1,2,3]
           $ar.eachIndex proc(v,i){...}

           with a non-exception CWAL_RC_MISUSE. Have not yet
           debugged why that happens, though.
        */
        rc = cwal_function_call_in_scope( subScope, func, selfObj,
                                          &xrv, argc, argc ? argv : NULL );
#else
        /**
           It turns out that we need to override argv.callee in
           downstream callback code sometimes, and that is unduly
           difficult to do properly (without horribly side-effects) if
           we run the callback in the same some as our argv is already
           defined.
         */
        rc = cwal_function_call( func, selfObj,
                                 &xrv, argc, argc ? argv : NULL );
#endif
        ie->catchLevel = catchLevel;
        if(RC_IS_EXIT(rc)){
            /* th1ish_dump_val(ie->exitValue,"PASSING ON EXIT"); */
            /* assert(ie->exitValue); fork() breaks this.*/
            if(ie->exitValue){
                *rv = xrv = ie->exitValue;
            }
        }
        th1ish_strace_pop(ie);
#if 0
        assert(CWAL_RC_RETURN!=rc);
#else
        if(CWAL_RC_RETURN==rc){
            /* if an import()ed script return()s,
               we want to accept its return value.
            */
            rc = 0;
        }
#endif

        if(!rc){
            if(xrv && rv){
                *rv = xrv;
                /* th1ish_dump_val(xrv,"Rescoping return value"); */
                cwal_value_rescope( callingScope, xrv );
            }
            else if(rv) *rv = cwal_value_undefined();
        }
    }
    errReportPos = callPoint;
    end:
    switch(rc){
      case 0: break;
      case CWAL_SCR_EOF:
          /* rc = 0; */
          break;
      case CWAL_RC_EXCEPTION:
      case CWAL_RC_EXIT:
      case CWAL_RC_FATAL:{
          /* If we find an exception with no line/column info
             (i.e. native functions) then set the line/column
             properties of the exception to the location of the last
             known-OK token.
          */
          cwal_value * exv = cwal_exception_get(ie->e);
          if(exv && cwal_value_is_exception(exv)){
              cwal_exception * ex = cwal_value_get_exception(exv);
              cwal_value const * enriched = cwal_prop_get(exv, "line", 4);
              if(!enriched){ /* Enrich the exception with call location info. */
                  rc = th1ish_set_script_props( ie, cwal_exception_code_get(ex),
                                                errReportPos->src.begin, exv );
                  if(!rc) rc = CWAL_RC_EXCEPTION;
              }
          }/* potential TODO: create an exception here? So that the
              caller knows where a native function EXIT'd from? (Fatal
              and Exception are already supposed to have exception
              state.)
           */
          break;
      }
      default:{
          /* !cwal_exception_get(ie->e) reminder: we cannot currently safely
             unset the cwal exception state, so it is (almost) always set
             after one exception has been thrown.

             Later: i don't think that's true anymore.
          */
          rc = th1ish_toss_token(ie, callPoint, rc,
                                 "Function triggered non-exception error "
                                 "#%d (%s).",
                                 rc, cwal_rc_cstr(rc));
          break;
      }
    }
    if(pHead){
        t = origT;
        *next = t->right;
        th1ish_st_free_chain(ie, pHead, 1);
    }
    if(subScope){
        assert(pushedScope_.level);
        cwal_scope_pop(ie->e);
        assert(!pushedScope_.level);
    }
    return rc;
}

int th1ish_eval_func_call_raw(th1ish_interp * ie, cwal_simple_token * t,
                              cwal_simple_token ** next,
                              cwal_value ** rv,
                              char ignoreEOL){
    int rc;
    cwal_value * fv = 0;
    cwal_function * func = 0;
    cwal_value * selfObj = 0;
    assert(!IE_SKIP_MODE);
    t = cwal_st_skip(t);
    if(cwal_st_eof(t)){
        *next = t;
        rc = 0 /*CWAL_SCR_EOF*/;
        goto end;
    }
    *next = t;
    fv = cwal_value_undefined();
    /* dump_token(t,"Initial eval inside [CALL]"); */
    /* ???th1ish_reset_this(ie); */
    rc = th1ish_eval_5( ie, t, next, &fv )
        /*
          Reminder: must be eval_5 to avoid this corner case:

          [foo.func -1 2 3]

          incorrectly evals as:

          [(foo.func - 1) 3 4]

          (and similar cases) if we start at eval_4 or lower.


          Bug:

          $array [
            'cpdoish',
            'linenoiseish'
            ].eachIndex proc(v,i){
                catch{[api.loadModule v api]} &&
                    print("Warning: module not loaded:",v)
            }

         Works but wrapping it in [] instead does not. Conventional
         call() syntax works as well.
        */
        ;
    assert(!IE_SKIP_MODE && "Entered (but did not leave) skip mode mid-call" );
    /* dump_token(*next,"After eval inside [CALL]"); */
    /* th1ish_dump_val(fv,"eval rv"); */
    if(rc) goto end;
    /* dump_token(t,"function?"); */
    t = *next = ignoreEOL ? cwal_st_skip(*next) : th1ish_skip(ie,*next);
    if(!fv || !(func = cwal_value_function_part(ie->e,fv))){
        /* th1ish_dump_val(fv,"Not a function"); */
        rc = th1ish_toss_token(ie, t, CWAL_RC_TYPE,
                               "Function call expects a function as its first argument.");
        goto end;
    }
    /* *next = t = th1ish_skip(ie,t->right); */
    *next = t = th1ish_skip(ie,t);
    /* dump_token(t,"First argument token"); */
    selfObj = ie->currentThis;
    /* th1ish_dump_val(selfObj,"selfObj for call_raw()"); */
    rc = th1ish_eval_func_collect_call( ie, t, next, rv,
                                        selfObj ? selfObj : fv,
                                        func, ignoreEOL );
    th1ish_reset_this(ie);
    end:
    return rc;
}

/**
   Evalutes [func ...args...] constructs.
*/
static int th1ish_eval_func_call(th1ish_interp * ie, cwal_simple_token * t,
                                 cwal_simple_token ** next,
                                 cwal_value ** rv ){
    int rc;
    cwal_simple_token * head = 0;
    cwal_simple_token * myt = 0;
    assert(TT_ChBraceOpen==t->ttype);
    if(IE_SKIP_MODE){
        *next = t->right;
        return 0;
    }
    /* MARKER("Function call body: %u bytes, %.*s\n", t->src.end-t->src.begin, t->src.end-t->src.begin, t->src.begin); */
    rc = th1ish_tokenize_all( ie, t->src.begin, t->src.end - t->src.begin, &head );
    if(rc){
        assert(!head);
        return rc;
    }
    assert(head);
    if(0){
        myt = head;
        while(myt){
            dump_token(myt,"func call body token");
            myt = myt->right;
        }
    }
    myt = cwal_st_skip(head);
    rc = th1ish_eval_func_call_raw( ie, myt, &myt, rv, 1 );
    *next = t->right;
    if(head){
        th1ish_st_free_chain( ie, head, 1 );
    }
    return rc;
}

/**
   Evaluates [func call] blocks. Excepts t to be a "post-processed"
   TT_ChBraceOpen token, with [t->src.begin,t->src.end) pointing to
   the whole contents of the [...] block, stripped of leading and
   trailing whitespace.
*/
static int th1ish_eval_brace_block(th1ish_interp * ie, cwal_simple_token * t,
                                   cwal_simple_token ** next,
                                   cwal_value ** rv ){
    if(TT_ChBraceOpen != t->ttype) return CWAL_SCR_CANNOT_CONSUME;
    else if(IE_SKIP_MODE) {
        *next = TNEXT(t)
            /* Remember that tokenization sets t->right to the token
               after the closing ']' token.
            */
            ;
        return 0;
    }
    else{
        return th1ish_eval_func_call( ie, t, next, rv );
    }
}

int th1ish_eval_ternary_if(th1ish_interp * ie,
                           cwal_value * lhs,
                           cwal_simple_token * t,
                           cwal_simple_token ** next,
                           cwal_value ** rv ){
    int rc = 0;
    cwal_simple_token * close;
    assert(TT_ChQuestion == t->ttype);
    close = cwal_st_find_matching_brace( t );
    if(!close){
        return th1ish_toss_token(ie, t, CWAL_SCR_SYNTAX,
                                 "Missing ':' part of ternary IF expression.");
    }
    /* th1ish_dump_val(lhs, "ternary LHS"); */
    if(!IE_SKIP_MODE && cwal_value_get_bool(lhs)){
        cwal_value * ignored = 0;
        t = *next = TNEXT(t);
        rc = th1ish_eval_0( ie, t, next, rv );
        if(rc) return rc;
        /* th1ish_dump_val(*rv, "Ternary TRUE part"); */

        /* Skip RHS... */
        t = *next = TNEXT(close);
        /* MARKER("Skipping the closing ':' part.\n"); */
        ++ie->skipLevel;
        rc = th1ish_eval_0( ie, t, next, &ignored );
        --ie->skipLevel;
        /* th1ish_dump_val(ignored, "Ternary FALSE part"); */
        /* MARKER("Done skipping. Were there side-effects?\n"); */
    }else {
        /* Skip to ':' part... */
        /* MARKER("Skipping to closing ':' part.\n"); */
        t = TNEXT(close);
        rc = th1ish_eval_0( ie, t, next, rv );
        /* th1ish_dump_val(*rv, "Ternary FALSE part"); */
    }
    return rc;
}


int th1ish_eval_atom(th1ish_interp * ie, cwal_simple_token * t,
                     cwal_simple_token ** next,
                     cwal_value ** rv ){
    int rc;
    switch(t->ttype){
      case TT_Identifier:
          rc = th1ish_eval_identifier(ie, t, next, rv );
          th1ish_eval_check_exit(ie,rc, t, next);
          break;
      case TT_ChBraceOpen:
          rc = th1ish_eval_brace_block(ie, t, next, rv );
          th1ish_eval_check_exit(ie,rc, t, next);
          break;
      case TT_LiteralStringSQ:
      case TT_LiteralStringDQ:
      case TT_ChScopeOpen:
          rc = th1ish_eval_literal_string(ie, t, next, rv );
          th1ish_eval_check_exit(ie,rc, t, next);
          break;
      case TT_LiteralDouble:
      case TT_LiteralIntDec:
      case TT_LiteralIntHex:
      case TT_LiteralIntOctal:
          rc = th1ish_eval_literal_number(ie, t, next, rv );
          th1ish_eval_check_exit(ie,rc, t, next);
          break;
#if 0
      case TT_ChParenOpen:{
          cwal_value * argv = 0;
          rc = th1ish_eval_list(ie, t, next, NULL, &argv );
          /* assert(argv); */
          th1ish_eval_check_exit(ie,rc, t, next);
          *rv = argv ? argv : cwal_value_undefined()
              /* Are we just hiding a bug here? */;
        break;
      }
#endif
          break;
      default:
          rc = th1ish_eval_keywords(ie, t, next, rv );
          th1ish_eval_check_exit(ie,rc, t, next);
          break;
    }
    /*MARKER("Ending eval_atom() w/ rc=%d (%s)\n",rc, cwal_rc_cstr(rc));*/
    return rc;
}



char th1ish_st_eox( cwal_simple_token const * t ){
    if(!t) return 1;
    else switch(t->ttype){
      case TT_NL:
      case TT_CR:
      case TT_CRNL:
      case TT_EOL:
      case TT_ChSemicolon:
      case TT_EOF:
      case TT_ChComma:
          return 1;
      default:
          return 0;
    }
}

void th1ish_autosweep_config(th1ish_interp * ie, int sweepInterval,
                             int vacuumInterval, char logSweeps ){
    ie->sweepInterval = (sweepInterval>=0)
        ? sweepInterval
        : th1ish_interp_empty.sweepInterval ;
    ie->vacuumInterval = (vacuumInterval>=0)
        ? vacuumInterval
        : th1ish_interp_empty.sweepInterval;
    if(logSweeps) ie->flags |= TH1ISH_F_LOG_AUTO_SWEEP;
    else ie->flags &= ~TH1ISH_F_LOG_AUTO_SWEEP;
}

static int th1ish_maybe_sweepvac( th1ish_interp * ie, cwal_simple_token * t ){
    if(ie->sweepGuard>0 || ie->sweepInterval<=0) return 0;
    else if(++ie->sweepTick != ie->sweepInterval) return 0;
    else{
        int sw = 0;
        int useBroom = 1 /* 1 == sweep,
                            0 == vacuum,
                            -1 == sweep2 */;
        ++ie->sweepTotal;

        /* See if we can/should use sweep2 or vacuum... */
        if(ie->vacuumInterval>0
           && !ie->vacuumGuard
#if 0
           /* Fixed! But as a reminder to future me's... */
           && (ie->e->current->level>1)
           /* Vacuuming the global scope currently breaks (triggers
              seemingly valid assertions in cwal or causing downstream
              errors due to memory corruption). Very possibly (not yet
              proven) caused by th1ish infrastructure which is not
              accessible via script space.
           */
#endif
           && (0 == (ie->sweepTotal % ie->vacuumInterval))
           ){
            useBroom = 0;
        }
#if 0
        /*
          Recursive sweep currently nukes things, though not yet
          100% sure what. Possible culprits: propagating values,
          exceptions, ... ?

          Functions are (now) protected from sweep/vaccuum for the
          life of their call(), so an r-sweep should not nuke an
          anonymous function being called inline.
        */
        else if(
                 ie->sweepRInterval>0
                 && (ie->e->current->level>1)
                 /* no sense in recursively sweeping the top scope */
                 && (0 == (ie->sweepTotal % ie->sweepRInterval))
                 ){
            useBroom = -1;
        }
#endif
        switch(useBroom){
          case 1:
              sw = (int)cwal_engine_sweep(ie->e);
              break;
          case -1:
              sw = (int)cwal_engine_sweep2(ie->e, 1);
              break;
          default:{
              int rc;
              assert(0==useBroom);
              rc = cwal_engine_vacuum(ie->e, &sw);
              if(rc) return rc /* very serious problem */;
          }
        }
        if((TH1ISH_F_LOG_AUTO_SWEEP & ie->flags) && sw){
            /* When we use cwal_outputf() here we break
               the [api.ob] unit tests :/.
            */
            unsigned int line = 1, col = 0;
            cwal_size_t nameLen = 0;
            char const * verb;
            char const * scriptName =
                th1ish_script_name_for_pos( ie, t->src.begin,
                                            &nameLen );
            th1ish_token_line_col( ie, t, &line, &col );
            switch(useBroom){
              case 1: verb = "swept"; break;
              case -1: verb = "swept2"; break;
              default:
                  assert(0==useBroom);
                  verb = "vacuumed";
            }
            cwal_outputf(ie->e,"th1ish auto-sweep: @[%.*s]:%u:%u: "
                         "scope #%d: Auto-sweep #%d "
                         "%s up %d value(s).",
                         (int)nameLen, scriptName, line, col,
                         (int)ie->e->current->level, ie->sweepTotal,
                         verb, sw);
            if(0==(ie->vacuumTotalValues + ie->sweepTotalValues)){
                cwal_outputf(ie->e," sweepInterval=%d, vacuumInterval=%d,",
                             ie->sweepInterval, ie->vacuumInterval);
            }
            if(useBroom) ie->sweepTotalValues += sw;
            else ie->vacuumTotalValues += sw;
            cwal_outputf(ie->e," totals: s=%"CWAL_SIZE_T_PFMT
                         ", v=%"CWAL_SIZE_T_PFMT"\n",
                         (cwal_size_t)ie->sweepTotalValues,
                         (cwal_size_t)ie->vacuumTotalValues);
        }
        ie->sweepTick = 0;
        return 0;
    }
}

static int th1ish_eval_entry_impl( th1ish_interp * ie, cwal_simple_token * t,
                                   cwal_simple_token ** next,
                                   cwal_value ** rv ){
    int rc;
    assert(ie);
    assert(t);
    assert(next);
    assert(rv);
    /* dump_token(t, "eval_entry()"); */
    ie->exitValue = 0;
    th1ish_reset_this(ie);
    t = cwal_st_skip(t);
    if(cwal_st_eof(t)){
        *next = NULL;
        return CWAL_SCR_EOF;
    }

    switch(t->ttype){
#if 1
      case TT_EOL:
      case TT_ChSemicolon:
      case TT_NL:
      case TT_CR:
      case TT_CRNL:
          assert(!"This block is apparenlty never triggered.");
#endif
      case TH1ISH_RC_EOC:
          /* fall through */
      case TT_ChComma:
          *next = t->right;
          return 0;
      default:{
          rc = th1ish_maybe_sweepvac(ie, t);
          if(!rc) rc = th1ish_eval_0(ie, t, next, rv);
          /* dump_token(*next, "eval_0 says next is"); */
          /* th1ish_dump_val(*rv, "eval_0 result"); */
          return rc;
      }
    }
}

int th1ish_eval_entry( th1ish_interp * ie, cwal_simple_token * t,
                       cwal_simple_token ** next,
                       cwal_value ** rv ){
    if(!ie || !ie->e || !t || !next || !rv) return CWAL_RC_MISUSE;
    else{
        int rc;
        th1ish_this_save(ie);
        rc = th1ish_eval_entry_impl(ie, t, next, rv);
        th1ish_this_restore(ie);
        return rc;
    }
}

int th1ish_eval_chain( th1ish_interp * ie,
                       cwal_simple_token * head,
                       cwal_simple_token ** next,
                       cwal_value **rv ){
    cwal_simple_token * _next = head;
    cwal_simple_token * t = head;
    cwal_value * _rv = 0;
    int rc = 0;
    if(!ie || !head) return CWAL_RC_MISUSE;
    if(!next) next = &_next;
    if(!rv) rv = &_rv;

#if 0
    /*
       Reminder: we cannot(?) do this reliably for some
       conceivable use cases, e.g.:

       $func scope {...} scope {...}
       
       :/

       And, aside from that, it breaks plenty of the
       test scripts.
    */
    cwal_engine_sweep( ie->e );
#endif
    for( t = head; t && 0==rc ; t = *next ){
        *next = t->right;
#if 0
        dump_token(t,"eval()'ing token");
        if(*rv) th1ish_dump_val(*rv,"rv before next token");
#endif
        if(cwal_st_eof(t)) break;
        else if(cwal_is_whitespace(t->ttype)) continue;
        else{
            *next = t;
            rc = th1ish_eval_entry( ie, t, next, rv );
            if(RC_IS_EXIT(rc)){
                if(ie->exitValue){
                    *rv = ie->exitValue;
                    /* ie->exitValue = 0; */
                }
                break;
            }
            if(rc) break;
            else if(*next==t && !ie->expando.ar){
                assert(!"Internal parser returned 0 but did not consume.");
                rc = CWAL_SCR_CANNOT_CONSUME;
                break;
            }
        }
#if 0
        if(*rv){
            th1ish_dump_val(*rv, "th1ish_eval_entry() result value");
        }
#endif
    }
#if 0
    if(rv && *rv){
        th1ish_dump_val(*rv, "Final th1ish_eval_chain() result value");
        dump_token( *next, "Next token" );
    }
#endif
    if(CWAL_SCR_EOF==rc) {
        rc = CWAL_RC_OK;
    }
    else switch(rc){
      case 0: break;
      case CWAL_RC_CONTINUE:
      case CWAL_RC_EXCEPTION:
      case CWAL_RC_EXIT:
      case CWAL_RC_FATAL:
          break;
      case TH1ISH_RC_TOSS:
      case CWAL_RC_BREAK:
      case CWAL_RC_RETURN:
          /* th1ish_dump_val( *rv, "Allowing a RETURN or BREAK to pass"); */
          if(ie->returnVal){
              *rv = ie->returnVal;
              th1ish_result_up( ie )
                  /* (possibly) necessary to keep its lifetime
                     intact */
                  ;
          }
          break;
      default:
          if(!cwal_exception_get(ie->e)){
              t = *next;
              /* MARKER("eval-entry rc=%s\n", cwal_rc_cstr(rc)); */
              /* th1ish_dump_val(*rv,"eval-entry rv"); */
              /* dump_token(t,"Setting generic exception."); */
              rc = th1ish_toss_token(ie, t, rc,
                                     "Caught non-exception error #%d (%s)",
                                     rc, cwal_rc_cstr(rc) );

          }
    }
    /* th1ish_dump_val(*rv, "eval_chain() rv"); */
    return rc;
}

int th1ish_eval_chain_push_scope( th1ish_interp * ie,
                                  cwal_simple_token * head,
                                  cwal_simple_token ** next,
                                  cwal_value **rv ){
    int rc;
    cwal_scope * sc = cwal_scope_current_get(ie->e);
    cwal_value * myRv = 0;
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = &pushedScope_;
    int const oldSweepGuard = ie->sweepGuard;
    rc = cwal_scope_push(ie->e, &subScope);
    if(rc) return rc;
    assert(pushedScope_.level);
    ie->sweepGuard = 0;
    rc = th1ish_eval_chain( ie, head, next, &myRv );
    ie->sweepGuard = oldSweepGuard;
    if(rv){
        *rv = myRv;
        cwal_value_rescope( sc, myRv );
    }
    cwal_scope_pop(ie->e);
    assert(!pushedScope_.level);
    return rc;
}

int th1ish_tokenize_all( th1ish_interp * ie, char const * src,
                         cwal_size_t slen, cwal_simple_token ** dest ){
    cwal_simple_tokenizer t = cwal_simple_tokenizer_empty;
    cwal_simple_tokenizer tgt = cwal_simple_tokenizer_empty;
    cwal_simple_token * head = 0;
    cwal_simple_token * tail = 0;
    cwal_simple_token * theTok = 0;
    /*cwal_simple_token * tail = 0;*/
    int rc = 0;
    if(!ie || !src) return CWAL_RC_MISUSE;
    else if(!slen) slen = cwal_strlen(src);
    if(!slen || !*src) return CWAL_SCR_EOF;
    /*escBuf = &ie->buffer;*/
    rc = cwal_simple_tokenizer_init(&t, src, slen);
    while(0==rc){
        char doBreak = 0;
        rc = th1ish_next_token(&t);
        if(rc) {
            rc = th1ish_toss_t10n(ie, &t, rc);
            break;
        }
        tgt = t;
        switch(t.ttype){
          case TT_TokErr:
              assert(!"Cannot happen!");
              MARKER(("Error while tokenizing. start of token=[%c] message=%s\n",
                      *t.current.begin, t.errMsg));
              dump_tokenizer(&t,"TT_TokErr");
              rc = CWAL_RC_ERROR;
              doBreak = 1;
              break;
          case TT_UNDEF:
              assert(!"Cannot happen?");
              MARKER(("Unknown token type around byte [%c]\n", *t.current.begin));
              dump_tokenizer(&t,"Unknown token");
              rc = CWAL_RC_ERROR;
              doBreak = 1;
              break;
          case TT_EOF:
              rc = CWAL_SCR_EOF;
              break;
#if 1
          /* case TT_CommentHash: */
          case TT_CComment:
          case TT_CppComment:
              /* MARKER("Skipping comments.\n"); */
              continue;
              break;
#endif
#if 1 /* XXXXXXX
         disabling this breaks the following:

         assert "undefined" == typename 3 * 9 % 7

         evaluates to 0.

         FIXME: find the culprits and make them space-friendly.
       */
          case TT_ChSpace:
          case TT_Blank:
          /* case TT_Whitespace: */
              continue;
#endif
#if 1
          case TT_ChSemicolon:
              tgt.ttype = TH1ISH_RC_EOC;
              break;
          case TT_NL:
          case TT_CR:
          case TT_CRNL:
              tgt.ttype = TT_EOL;
              break;
#endif
          case TT_ChParenOpen:
          case TT_ChBraceOpen:
          case TT_ChScopeOpen:
              /* MARKER("Running tokenize postprocessing...\n"); */
              rc = th1ish_tokenize_post( ie, &t, &tgt );
              /*dump_tokenizer( &tgt, "Post-processed tgt token");*/
              break;
          case TT_OpHeredocStart:
              rc = th1ish_tokenize_heredoc(ie, &t, &tgt);
              break;
          default:{
              if(TT_EOF == tgt.ttype){
                  /* MARKER("EOF reached.\n"); */
                  rc = CWAL_SCR_EOF;
                  break;
              }
          }
        }
        if(doBreak) break;
        /* dump_tokenizer( &tgt, "tokenize_all() got token" ); */
        theTok = th1ish_st_alloc( ie );
        if(!theTok) {
            rc = CWAL_RC_OOM;
            break;
        }
        theTok->src = tgt.current;
        theTok->ttype = tgt.ttype;
        if(!tail){
            assert(!head);
            head = tail = theTok;
        }else{
            assert(!tail->right);
            tail->right = theTok;
            tail = theTok;
            assert(!tail->right);
        }
        if(TH1ISH_RC_EOC == rc){
            rc = 0;
            break;
        }
        else if(CWAL_SCR_EOF == rc/*our virtual EOF*/) {
            rc = 0;
            break;
        }
#if 0
        /* i would prefer to tag keywords here, as opposed to at the end,
           but the obj.prototype kludge in th1ish_chain_postprocess() breaks
           this.
        */
        else if(TT_Identifier == theTok->ttype){
            th1ish_chain_postprocess(ie,theTok);
        }
#endif
        /* dump_tokenizer( &tgt, "Got token" ); */
    }
    /*end:*/
#if 0
    if(head){
        MARKER("Grabbed %u byte(s): [%*.s]\n",
               tail->src.begin - head->src.begin,
               tail->src.begin - head->src.begin,
               head->src.begin /* why is this not what i expect? */
               );
    }
#endif
    if(rc){
        cwal_st_free_chain( ie->e, head );
    }
    else{
        if(dest){
            if(head) th1ish_chain_postprocess( ie, head );
            *dest = head;
        }else{
            cwal_st_free_chain( ie->e, head );
        }
    }
    return rc;
}


int th1ish_eval_string( th1ish_interp * ie,
                        char pushScope,
                        char const * src,
                        cwal_size_t slen, cwal_value **rv ){
    int rc;
    cwal_simple_token * head = 0;
    cwal_value * xrv = 0;
    cwal_scope * callingScope = cwal_scope_current_get(ie->e);
    cwal_scope pushedScope_ = cwal_scope_empty;
    cwal_scope * subScope = &pushedScope_;
    if(!src || !*src || !slen){
        if(rv) *rv = cwal_value_undefined();
        return 0;
    }
    /* cwal_scope * sc = cwal_scope_current_get(ie->e); */
    rc = th1ish_tokenize_all( ie, src, slen, &head );
    if(rc) {
        if(CWAL_SCR_EOF==rc){
            rc = 0;
            if(rv) *rv = cwal_value_undefined();
        }
        return rc;
    }
    if(pushScope){
        rc = cwal_scope_push(ie->e, &subScope);
        if(rc) return rc;
    }
    xrv = NULL;
    assert(!pushScope || pushedScope_.level);    
    rc = th1ish_eval_chain( ie, head, 0, &xrv );
    assert(!pushScope || pushedScope_.level);
    if(RC_IS_EXIT(rc) && rv && xrv){
        *rv = xrv = ie->exitValue;
    }
    else if((!rc||(CWAL_RC_RETURN==rc)||(TH1ISH_RC_TOSS==rc))
            && rv && xrv){
        if(pushScope){
            /**
               An interesting case:
            
               1 && scope {
                 var dest array []
                 set dest.foo object {}
                 // crashes w/o explicit return.
                 // scope's implicit return value = dest.foo
               }

               The problem now is that dest will be cleaned up at the
               end of the scope, but we have no reference to
               dest.PROP, which we need pass up the scope chain
               (because dest.PROP is our implicit return value). So
               when the scope ends it passes back an invalid value
               (cleaned up during scope pop), which we then
               (illegally) reference as the right-hand side of the &&
               expression.

               Unfortunately, this also applies to:

               [proc {} { var a = 3; return a }]

               the var holds a ref to a, which we cannot distinguish
               from a ref held by a container. A potential fix might
               be in the cwal core: when a container is cleaning up,
               if its value lives in a higher scope, don't really
               destroy it, but move it back into probationary state
               (in the higher-level scope) if its refcount goes
               back to 0.

               ... after making that change to cwal, we now only
               have to re-scope the value.

               However, we still have this problem for the prototype
               special property but only on implicit returns:

               false || scope {
                 var a array[]
                 set a.prototype
               }

               An explicit return does not have that problem. :/

               Fixed in the mean time, but the above comments are
               enlightening.
            */
            /* th1ish_dump_val(xrv,"Up-scoping"); */
            /* if(cwal_value_scope(xrv) == subScope) */
            cwal_value_rescope( callingScope, xrv );
        }
        *rv = xrv;
    }
    assert(!pushScope || pushedScope_.level);    
    th1ish_st_free_chain( ie, head, 1 );
    if(pushScope){
        assert(pushedScope_.level);
        cwal_scope_pop(ie->e);
        assert(!pushedScope_.level);
    }
    /* th1ish_dump_val( *rv, "eval_string()=>rv" ); */
    return rc;
}

const th1ish_script th1ish_script_empty = th1ish_script_empty_m;

#if 0
static th1ish_script * th1ish_script_alloc(th1ish_interp *ie){
    th1ish_script * rc = cwal_malloc(ie->e, sizeof(th1ish_script));
    if(rc){
        *rc = th1ish_script_empty;
        rc->allocStamp = &th1ish_script_empty;
        rc->ie = ie;
    }
    return rc;
}
#endif

/**
   Internal impl of th1ish_script_finalize().  This one does NOT
   detach scr from scr->ie's script list.
*/
static void th1ish_script_finalize2(th1ish_script *scr){
    if(scr && scr->ie){
        th1ish_interp *ie = scr->ie;
        cwal_buffer scrBuf = scr->mem /* see comments below */;
        unsigned char const * bufMem = scr->mem.mem;
        /* Reminder: if this API allocated scr then ((void*)scr) lives
           inside bufMem, else the client allocated the th1ish_script
           part of the construct (possibly on the stack).
        */

        /**
           Because the token compiler can inject new tokens into
           our chain (which lives in bufMem), we must jump through
           some hoops to clean it up...
        */
#define ADDR(X) ((unsigned char const *)(X))
#define IS_MINE(T) ((ADDR(T)>=bufMem) && (ADDR(T)<(bufMem+scr->mem.capacity)))
        cwal_simple_token * t = scr->head;
        cwal_simple_token * toFree = 0;
        for( ; t;  ){
            if(IS_MINE(t)){
                t = t->right;
            }
            else {
                cwal_simple_token * n = t->right;
                t->right = toFree;
                toFree = t;
                t = n;
            }
        }
#undef IS_MINE
#undef ADDR
        if(toFree){
            th1ish_st_free_chain(ie, toFree, 1);
        }
        assert(!scr->chain);
        /* Some unsightly memory-fiddling here: i want the freeing of
           scr->mem.mem to go through cwal_buffer_reserve() for
           metrics-counting purposes. scr might be allocated inside
           scr->mem, so clearing its state may wipe it out. After
           we have freed scr->mem, any access to scr is invalid if
           it was allocated in scr->mem. So...
         */
        *scr = th1ish_script_empty;
        cwal_buffer_reserve(ie->e, &scrBuf, 0);
    }
}


void th1ish_script_finalize(th1ish_script *scr){
    if(scr && scr->ie){
        th1ish_script * s = scr->ie->scripts.head;
        th1ish_script * prev = 0;
        for( ; s && (scr != s); prev = s, s = s->chain ){
        }
        if(s == scr){
            if(prev){
                prev->chain = s->chain;
                s->chain = 0;
            }else{
                assert( s == scr->ie->scripts.head );
                scr->ie->scripts.head = s->chain;
                s->chain = 0;
            }
        }
        th1ish_script_finalize2(scr);
    }
}

/**
  Internal impl of th1ish_script_compile() and friends which takes a
  cwal_buffer as source and expands/re-uses it. This allow us to avoid
  a temporary extra copy from th1ish_script_compile_filename() and
  potential future work-alikes.

  On success ownership of buf->mem is transfered to (*rv)->mem and
  buf's state is re-set.  *rv may or may not be allocated as part of
  that buffer, as documented for th1ish_script_compile().

  On error non-0 is returned and ownership of buf->mem is not
  transfered (but it may have been expanded and appended to by this
  operation before it failed).
*/
int th1ish_script_compile_buffer(th1ish_interp *ie, char const * name,
                                 cwal_buffer * buf, th1ish_script ** rv){
    /*
       Achtung, code duplication: this is mostly a clone of
       th1ish_tokenize_all().
    */
    cwal_simple_tokenizer t;
    cwal_simple_tokenizer tgt /* "interim" tokenizer which may be modified by
                                 post-processing. */;
    cwal_simple_token * head = 0 /* Head of the token chain */;
    cwal_simple_token * tail = 0 /* Current tail of the chain */;
    cwal_simple_token * theTok = 0 /* Current token */;
    cwal_simple_token * tokenArray = 0 /* Array of tokens tCount items long */;
    int rc = 0;
    char mode = 0 /* controls the loop described below */;
    cwal_size_t tCount = 0 /* Number of tokens to allocate */;
    cwal_size_t tIndex = 0 /* Current index in tokenArray */;
    th1ish_script * script = 0;
    unsigned char const * codePos = 0;
    unsigned char const * namePos = 0;
    cwal_size_t const nameLen = name ? strlen(name) : 0;
    unsigned char const * src = buf ? buf->mem : NULL;
    cwal_size_t const slen = buf ? buf->used : 0;
    cwal_st_src oldSrc;
    if(!ie || !src || !rv) return CWAL_RC_MISUSE;
    /* else if(!*src || !slen) return CWAL_SCR_EOF; */
    else if(*rv && (*rv)->ie) return CWAL_RC_MISUSE;
    oldSrc = ie->src;
    ie->src.begin = (char const *)src;
    ie->src.end = ie->src.begin + slen;
    /**
       Tokenize the input twice. The first time (0==mode) we simply
       count the tokens (and incidentally confirm the basic
       tokenization). The second time (1==mode) we allocate a single
       block to store a copy of the source, the name, and all tokens
       we'll need. We need to tokenize again at that point so that the
       created tokens point to the our copy of the sources. The reason
       for this approach is to reduce compilation of a script to a
       single allocation. We lose the ability to modify the token
       chain later, though.
    */
    start:
    t = tgt = cwal_simple_tokenizer_empty;
    assert(mode==0 || mode==1);
    rc = cwal_simple_tokenizer_init(&t, (char const *)src, slen);
    while(0==rc){
        char doBreak = 0;
        rc = th1ish_next_token(&t);
        if(rc) {
            assert(0==mode);
            rc = th1ish_toss_t10n(ie, &t, rc);
            break;
        }
        tgt = t;
        switch(t.ttype){
          case TT_TokErr:
              assert(!"Cannot happen! Caught above.");
              /*MARKER(("Error while tokenizing. start of token=[%c] message=%s\n",
               *t.current.begin, t.errMsg));*/
              rc = CWAL_RC_ERROR;
              doBreak = 1;
              break;
          case TT_UNDEF:
              assert(!"Cannot happen?");
              MARKER(("Unknown token type around byte [%c]\n", *t.current.begin));
              dump_tokenizer(&t,"Unknown token");
              rc = CWAL_RC_ERROR;
              doBreak = 1;
              break;
          case TT_EOF:
              rc = CWAL_SCR_EOF;
              break;
#if 1
          /* case TT_CommentHash: */
          case TT_CComment:
          case TT_CppComment:
              continue;
              break;
#endif
#if 1 /* XXXXXXX
         disabling this breaks the following:

         assert "undefined" == typename 3 * 9 % 7

         evaluates to 0.

         FIXME: find the culprits and make them space-friendly.
       */
          case TT_ChSpace:
          case TT_Blank:
          /* case TT_Whitespace: */
              continue;
#endif
          case TT_ChSemicolon:
              tgt.ttype = TH1ISH_RC_EOC;
              break;
          case TT_NL:
          case TT_CR:
          case TT_CRNL:
              tgt.ttype = TT_EOL;
              break;
          case TT_ChParenOpen:
          case TT_ChBraceOpen:
          case TT_ChScopeOpen:
              /* MARKER("Running tokenize postprocessing...\n"); */
              rc = th1ish_tokenize_post( ie, &t, &tgt );
              /*dump_tokenizer( &tgt, "Post-processed tgt token");*/
              if(rc) doBreak = 1;
              break;
          case TT_OpHeredocStart:
              rc = th1ish_tokenize_heredoc(ie, &t, &tgt);
              if(rc) doBreak = 1;
              break;
          default:
              break;
        }
        if(doBreak) break;
        else if(0==mode){
            ++tCount;
            if(CWAL_SCR_EOF != rc) continue;
            else{
                /*
                  Reserve enough space in buf for:

                  src + NUL
                  + name + NUL if name was provided
                  + 1 th1ish_script instance if !*rv
                  + (array of tCount tokens)
                */
                cwal_size_t const sz =
                    + slen + 1/*NUL*/
                    + nameLen + 1/*NUL*/
                    + (*rv ? 0 : sizeof(th1ish_script))
                    + (sizeof(cwal_simple_token) * tCount)
                    ;
                assert((buf->used==slen) && "Someone modified our buffer?");
                rc = cwal_buffer_reserve(ie->e, buf, sz );
                /* Reminder: the src pointer (buf->mem) is invalid as of here
                   via (potential) realloc. */
                if(rc) break;
                /*
                  Append the (name, Script, array) to buf, then start
                  the second round of tokenization.
                */
                mode = 1;
                codePos = buf->mem;
                src = codePos /* we need the tokens "allocated" on the
                                 second run to point to this copy, not
                                 the original source (reallocation
                                 might have invalidated the
                                 address)! */;
                buf->mem[buf->used++] = 0;
                if(nameLen){
                    namePos = buf->mem + buf->used;
                    memcpy( buf->mem + buf->used, name, nameLen );
                    buf->used += nameLen;
                    buf->mem[buf->used++] = 0;
                }
                if(!*rv){
                    /* Client provided no target script object,
                       so we allocate one from buf->mem. */
                    script = (th1ish_script*)(buf->mem+buf->used);
                    buf->used += sizeof(th1ish_script);
                }
                tokenArray = (cwal_simple_token*)(buf->mem+buf->used);
                buf->used += (sizeof(cwal_simple_token)*tCount);
                assert(buf->used <= buf->capacity);
                goto start;
            }
        }
        /* Now create the token and append it to the chain... */
        /* dump_tokenizer( &tgt, "tokenize_all() got token" ); */
        assert(tIndex<tCount);
        theTok = tokenArray + tIndex++;
        *theTok = cwal_simple_token_empty;
        theTok->src = tgt.current;
        theTok->ttype = tgt.ttype;
        if(!tail){
            assert(!head);
            head = tail = theTok;
        }else{
            assert(!tail->right);
            tail->right = theTok;
            tail = theTok;
            assert(!tail->right);
        }
        if(CWAL_SCR_EOF == rc/*our virtual EOF*/) {
            rc = 0;
            break;
        }
        /* dump_tokenizer( &tgt, "Got token" ); */
    }
    if(!rc){
        assert(1==mode);
        assert(tCount==tIndex);
        if(script){
            assert(!*rv);
            *rv = script;
        }
        else{
            assert(*rv);
            script = *rv;
        }
        *script = th1ish_script_empty;
        script->ie = ie;
        if(head) th1ish_chain_postprocess( ie, head )
            /* Reminder: i would prefer to do this during tokenization
               but special-casing in th1ish_chain_postprocess() breaks
               if i do that. Bad, bad special case(s).
            */;
        script->mem = *buf /* take ownership of this memory */;
        script->code = codePos;
        script->codeLength = slen;
        script->head = script->cursor = head;
        script->nameLength = nameLen;
        if(namePos){
            script->name = namePos;
        }
        *buf = cwal_buffer_empty;
    }
    ie->src = oldSrc;
    return rc;
}

int th1ish_script_compile(th1ish_interp *ie, char const * name,
                          unsigned char const * src,
                          cwal_size_t slen, th1ish_script ** rv){
    if(!ie || !src) return CWAL_RC_MISUSE;
    else {
        int rc;
        cwal_buffer buf = cwal_buffer_empty;
        rc = cwal_buffer_append( ie->e, &buf, src, slen );
        if(!rc){
            assert(0==buf.mem[buf.used]);
            rc = th1ish_script_compile_buffer( ie, name, &buf, rv );
            if(rc){
                cwal_buffer_reserve(ie->e, &buf, 0 );
            }else{
                assert(!buf.mem);
                assert(!buf.used);
                assert(!buf.capacity);
            }
        }
        return rc;
    }
}


int th1ish_script_compile_filename(th1ish_interp *ie, char const * filename,
                                   th1ish_script ** rv){
    if(!ie || !filename || !*filename ) return CWAL_RC_MISUSE;
    else {
        cwal_buffer buf = cwal_buffer_empty;
        int rc = cwal_buffer_fill_from_filename( ie->e, &buf, filename );
        if(!rc){
            rc = th1ish_script_compile_buffer( ie, filename, &buf, rv );
            if(rc){
                cwal_buffer_reserve(ie->e, &buf, 0 );
            }else{
                assert(!buf.mem);
                assert(!buf.used);
                assert(!buf.capacity);
            }
        }
        return rc;
    }
}

int th1ish_script_add( th1ish_interp * ie, th1ish_script * sc ){
    if(!ie || !sc || (ie!=sc->ie) || sc->chain) return CWAL_RC_MISUSE;
    sc->chain = ie->scripts.head;
    ie->scripts.head = sc;
    return 0;
}

int th1ish_script_run(th1ish_script *sc, char pushScope, cwal_value **rv){
    if(!sc || !sc->ie) return CWAL_RC_MISUSE;
    else {
        int rc;
        th1ish_script * prevScript = sc->ie->scripts.current;
        int const oldSweepGuard = sc->ie->sweepGuard;
        sc->ie->scripts.current = sc;
        sc->cursor = sc->head;
        sc->ie->sweepGuard = 0;
        rc = rv
            ? th1ish_eval_chain( sc->ie, sc->cursor, &sc->cursor, rv )
            : th1ish_eval_chain_push_scope( sc->ie, sc->cursor, &sc->cursor, rv );
        sc->ie->sweepGuard = oldSweepGuard;
        sc->ie->scripts.current = prevScript;
        return rc;
    }
}

int th1ish_token_recycler_size_set( th1ish_interp * ie, cwal_size_t maxSize ){
    if(!ie) return CWAL_RC_MISUSE;
    else {
        cwal_recycler * r = &ie->recycler.tokens;
        cwal_simple_token * t = 0;
        while( r->count > maxSize ){
            t = (cwal_simple_token *)r->list;
            assert(t);
            r->list = t->right;
            t->right = 0;
            th1ish_st_free_chain(ie, t, 0);
            --r->count;
        }
        r->maxLength = maxSize;
        return 0;
    }

}

int th1ish_var_decl( th1ish_interp * ie, char const * key, cwal_size_t keyLen,
                     cwal_value * v, uint16_t flags ){
    return ((ie && ie->e) || !key)
        ? cwal_var_decl( ie->e, 0, key, keyLen, v, flags )
        : CWAL_RC_MISUSE;
}


int th1ish_var_decl_v( th1ish_interp * ie, cwal_value * key,
                       cwal_value * v, uint16_t flags ){
    return ((ie && ie->e) || !key)
        ? cwal_var_decl_v( ie->e, 0, key, v, flags )
        : CWAL_RC_MISUSE;
}


int th1ish_var_set( th1ish_interp * ie, int scopeDepth,
                    char const * key, cwal_size_t keyLen,
                    cwal_value * v ){
    return (!ie || !ie->e || !key)
        ? CWAL_RC_MISUSE
        : cwal_scope_chain_set( cwal_scope_current_get(ie->e),
                                scopeDepth, key, keyLen, v );
}

int th1ish_var_set_v( th1ish_interp * ie, int scopeDepth,
                      cwal_value * key, cwal_value * v ){
    return (!ie || !ie->e || !key)
        ? CWAL_RC_MISUSE
        : cwal_scope_chain_set_v( cwal_scope_current_get(ie->e),
                                  scopeDepth, key, v );
}

cwal_value * th1ish_var_get( th1ish_interp * ie, int scopeDepth,
                             char const * key, cwal_size_t keyLen ){
    return (!ie || !key)
        ? NULL
        : cwal_scope_search( cwal_scope_current_get(ie->e),
                                   scopeDepth, key, keyLen, NULL );
}

cwal_value * th1ish_var_get_v( th1ish_interp * ie, int scopeDepth,
                               cwal_value * key ){
    return (!ie || !key)
        ? NULL
        : cwal_scope_search_v( cwal_scope_current_get(ie->e),
                                     scopeDepth, key, NULL );
}


int th1ish_eval_filename( th1ish_interp * ie, char pushScope,
                          char const * filename,
                          cwal_value **rv ){
    int rc;
    cwal_buffer buf = cwal_buffer_empty;
    cwal_st_src origSrc = cwal_st_src_empty;
    char const * oldName = 0;
    enum { BufStartSize = 1024 * 4 };
    rc = cwal_buffer_reserve( ie->e, &buf, (cwal_size_t)BufStartSize );
    if(rc) goto end;
    rc = cwal_buffer_fill_from_filename( ie->e, &buf, filename );
    if(rc) goto end;
    /* Fudge ie->src so that error reporting will work. This assumes,
       however... that the erroring code is not failing while calling
       back into tokens from a higher-level chain (if any). */

#if 1
    th1ish_src_save( ie, &origSrc, &oldName, filename,
                     (char const *)buf.mem,
                     (char const *)buf.mem + buf.used);
    rc = th1ish_eval_string( ie, pushScope, (char const *)buf.mem,
                             buf.used, rv );
    th1ish_src_restore(ie, &origSrc, oldName);
#else
    origSrc = ie->src;
    oldName = ie->srcName;
    ie->src.begin = (char const *)buf.mem;
    ie->src.end = (char const *)buf.mem + buf.used;
    ie->srcName = filename;
    rc = th1ish_eval_string( ie, pushScope, (char const *)buf.mem,
                             buf.used, rv );
    ie->srcName = oldName;
    ie->src = origSrc;
#endif

    end:
    cwal_buffer_reserve(ie->e, &buf, 0);
    return rc;
}

void th1ish_trace_assertions( th1ish_interp * ie, char enable ){
    if(ie){
        if(enable) ie->flags |= TH1ISH_F_TRACE_ASSERTIONS;
        else ie->flags &= ~TH1ISH_F_TRACE_ASSERTIONS;
    }
}

void th1ish_enable_proc_token_caching( th1ish_interp * ie, char enable ){
    if(ie){
        if(enable) ie->flags |= TH1ISH_F_CACHE_PROCS;
        else ie->flags &= ~TH1ISH_F_CACHE_PROCS;
    }
}

void th1ish_enable_conventional_call_op( th1ish_interp * ie,
                                         char enable ){
    if(ie){
        if(enable) ie->flags |= TH1ISH_F_CONVENTIONAL_CALL_OP;
        else ie->flags &= ~TH1ISH_F_CONVENTIONAL_CALL_OP;
    }
}

void th1ish_enable_extra_loop_scopes( th1ish_interp * ie, char enable ){
    if(ie){
        if(enable) ie->flags |= TH1ISH_F_LOOPS_EXTRA_SCOPES;
        else ie->flags &= ~TH1ISH_F_LOOPS_EXTRA_SCOPES;
    }
}

void th1ish_enable_autosweep_logging( th1ish_interp * ie, char enable ){
    if(ie){
        if(enable) ie->flags |= TH1ISH_F_LOG_AUTO_SWEEP;
        else ie->flags &= ~TH1ISH_F_LOG_AUTO_SWEEP;
    }
}

void th1ish_enable_string_interning( th1ish_interp * ie, char enable ){
    if(ie && ie->e){
        int rc = cwal_engine_feature_flags(ie->e,-1);
        if(enable) rc |= CWAL_FEATURE_INTERN_STRINGS;
        else rc &= ~CWAL_FEATURE_INTERN_STRINGS;
        cwal_engine_feature_flags(ie->e, rc );
    }
}

void th1ish_enable_stacktrace( th1ish_interp * ie, char enable ){
    if(ie){
        if(enable) ie->flags |= TH1ISH_F_TRACE_STACK;
        else ie->flags &= ~TH1ISH_F_TRACE_STACK;
    }
}

void th1ish_strace_push_pos( th1ish_interp * ie,
                             char const * pos,
                             th1ish_stack_entry * ent ){
    assert(ie && ie->e);
    ent->scriptPos = pos;
    if(ie->stack.tail){
        ie->stack.tail->down = ent;
        ent->up = ie->stack.tail;
        ie->stack.tail = ent;
    }else{
        assert(!ie->stack.head);
        ie->stack.head = ie->stack.tail = ent;
    }
    ++ie->stack.count;
}

int th1ish_strace_pop( th1ish_interp * ie ){
    assert(ie && ie->e);
    assert(ie->stack.count);
    assert(ie->stack.tail);
    if(!ie->stack.count) return CWAL_RC_RANGE;
    else {
        th1ish_stack_entry * x = ie->stack.tail;
        assert(!x->down);
        if(x->up){
            assert(x->up->down == x);
            ie->stack.tail = x->up;
            x->up->down = NULL;
            x->up = NULL;
        }else{
            ie->stack.head = ie->stack.tail = NULL;
        }
        --ie->stack.count;
    }
    return 0;
}

int th1ish_install_argv( th1ish_interp *ie, char const * varName,
                         int argc, char const * const * argv ){
    int rc = 0;
    cwal_value * av = NULL;
    int i = 0;
    cwal_array * all;
    av = cwal_new_array_value(ie->e);
    if(!av) return CWAL_RC_OOM;
    all = cwal_value_get_array(av);
    if(argc>0){
        rc = cwal_array_reserve(all, (cwal_size_t)argc);
        if(rc) goto end;
    }
    for( i = 0; !rc && (i < argc); ++i ){
        cwal_value * v = cwal_value_from_arg(ie->e, argv[i]);
        if(!v){
            rc = CWAL_RC_OOM;
            goto end;
        }
        rc = cwal_array_append(all, v);
    }
    if(rc) goto end;
    for( i = 0; argc>0; ++i, --argc){
        if(0==strcmp("--",argv[i])){
            --argc;
            ++i;
            break;
        }
    }
    rc = cwal_parse_argv_flags( ie->e, argc, argv+i, &av );
    if(!rc){
        rc = th1ish_var_set(ie, 0,
                            (varName&&*varName) ? varName : "ARGV",
                            0, av);
    }
    end:
    if(rc && av) cwal_value_unref(av);
    return rc;        
}
struct Th1ishObBuffer {
    th1ish_interp * ie;
    cwal_buffer buf;
};
typedef struct Th1ishObBuffer Th1ishObBuffer;

static
void th1ish_Th1ishObBuffer_finalize( cwal_engine * e, void * m ){
    Th1ishObBuffer * st = (Th1ishObBuffer *)m;
    cwal_buffer_reserve(e, &st->buf, 0);
    st->ie = NULL;
    cwal_free(e, m);
}

static
int th1ish_Th1ishObBuffer_output_f( void * state, void const * src, cwal_size_t n ){
    Th1ishObBuffer * ob = (Th1ishObBuffer*) state;
    return cwal_buffer_append(ob->ie->e, &ob->buf, src, n);
}

static int th1ish_ob_push_outputer( th1ish_interp * ie, cwal_outputer const * out ){
    if(!ie || !out) return CWAL_RC_MISUSE;
    else{
        int rc;
        cwal_outputer * co =
            (cwal_outputer *)cwal_malloc(ie->e,
                                         sizeof(cwal_outputer));
        if(!co){
            rc = CWAL_RC_OOM;
            goto end;
        }
        rc = cwal_list_append( ie->e, &ie->ob, co );
        end:
        if(rc){
            if(co) cwal_free(ie->e, co);
        }else{
            *co = ie->e->vtab->outputer;
            ie->e->vtab->outputer = *out;
        }
        return rc;
    }
}

#if 0
/**
   This is how we "could" integrate OB's flush() with cwal_output(),
   but doing so causes functions which flush (e.g. the default print()
   impl) to Do The Wrong Thing when we do that. So api.io.flush()
   becomes a no-op while buffering is on.
 */
int th1ish_ob_flush( th1ish_interp * ie );
static int th1ish_Th1ishObBuffer_flush(void * ob){
    return th1ish_ob_flush( ((Th1ishObBuffer*)ob)->ie );
}
#endif

cwal_size_t th1ish_ob_level( th1ish_interp * ie ){
    return ie ? ie->ob.count : 0;
}

int th1ish_ob_push( th1ish_interp * ie ){
    int rc;
    cwal_outputer out = cwal_outputer_empty;
    Th1ishObBuffer * ob;
    assert(ie);
    ob = (Th1ishObBuffer *)cwal_malloc(ie->e, sizeof(Th1ishObBuffer));
    if(!ob) return CWAL_RC_OOM;
    ob->ie = ie;
    ob->buf = cwal_buffer_empty;
    out.state.data = ob;
    out.state.finalize = th1ish_Th1ishObBuffer_finalize;
    out.output = th1ish_Th1ishObBuffer_output_f;
    /* out.flush = th1ish_Th1ishObBuffer_flush; */
    rc = th1ish_ob_push_outputer( ie, &out );
    if(rc){
        cwal_free( ie->e, ob );
    }
    return rc;   
}

int th1ish_ob_pop( th1ish_interp * ie ){
    if(!ie) return CWAL_RC_MISUSE;
    else if(!ie->ob.count) return CWAL_RC_RANGE;
    else{
        cwal_size_t const i = ie->ob.count-1;
        cwal_outputer * ob = (cwal_outputer *)ie->ob.list[i];
        cwal_outputer * io = &ie->e->vtab->outputer;
        if(io->state.finalize){
            io->state.finalize(ie->e, io->state.data);
        }
        *io = *ob;
        cwal_free(ie->e, ob);
        --ie->ob.count;
        return 0;
    }
}

int th1ish_ob_get( th1ish_interp * ie, cwal_buffer ** tgt ){
    if(!ie || !tgt) return CWAL_RC_MISUSE;
    else if(!ie->ob.count) return CWAL_RC_RANGE;
    else{
        cwal_outputer * io = &ie->e->vtab->outputer;
        Th1ishObBuffer * ob;
        assert(io->state.data);
        ob = (Th1ishObBuffer*)io->state.data;
        *tgt = &ob->buf;
        return 0;
    }
}

int th1ish_ob_take( th1ish_interp * ie, cwal_buffer * tgt ){
    if(!ie || !tgt) return CWAL_RC_MISUSE;
    else if(!ie->ob.count) return CWAL_RC_RANGE;
    else{
        cwal_outputer * io = &ie->e->vtab->outputer;
        Th1ishObBuffer * ob;
        assert(io->state.data);
        ob = (Th1ishObBuffer*)io->state.data;
        if(tgt != &ob->buf) *tgt = ob->buf;
        ob->buf = cwal_buffer_empty;
        return 0;
    }
}

int th1ish_ob_clear( th1ish_interp * ie, char releaseBufferMem ){
    if(!ie) return CWAL_RC_MISUSE;
    else if(!ie->ob.count) return CWAL_RC_RANGE;
    else{
        cwal_outputer * io = &ie->e->vtab->outputer;
        Th1ishObBuffer * ob;
        assert(io->state.data);
        ob = (Th1ishObBuffer*)io->state.data;
        if(releaseBufferMem) cwal_buffer_reserve(ie->e, &ob->buf, 0);
        else ob->buf.used = 0;
        return 0;
    }
}

int th1ish_ob_flush( th1ish_interp * ie ){
    if(!ie) return CWAL_RC_MISUSE;
    else if(!ie->ob.count) return CWAL_RC_RANGE;
    else{
        int rc = 0;
        cwal_size_t const i = ie->ob.count-1;
        cwal_outputer * to = (cwal_outputer *)ie->ob.list[i];
        cwal_outputer * io = &ie->e->vtab->outputer;
        Th1ishObBuffer * ob;
        assert(io->state.data);
        assert(to != io);
        ob = (Th1ishObBuffer*)io->state.data;
        if(ob->buf.used){
            rc = to->output( to->state.data, ob->buf.mem,
                             ob->buf.used );
        }
        ob->buf.used = 0;
        return rc;
    }
}

int th1ish_f_ob_pop( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_int_t take = 0;
    th1ish_interp * ie = th1ish_args_interp(args);
    assert(ie);
    if(args->argc) take = cwal_value_get_integer(args->argv[0]);
    if(!take) rc = th1ish_ob_pop( ie );
    else{
        rc = (take<0)
            ? th1ish_f_ob_take_string(args,rv)
            : th1ish_f_ob_take_buffer(args,rv);
        if(!rc) rc = th1ish_ob_pop(ie);
    }
    if(rc && (CWAL_RC_OOM!=rc) && (CWAL_RC_EXCEPTION!=rc)){
        rc = th1ish_toss(ie, rc, "Error #%d (%s) popping buffer.",
                         rc, cwal_rc_cstr(rc));
    }
    return rc;
    
}    

int th1ish_f_ob_push( cwal_callback_args const * args, cwal_value **rv ){
    th1ish_interp * ie = th1ish_args_interp(args);
    int rc;
    assert(ie);
    rc = th1ish_ob_push( ie );
    if(rc){
        return cwal_exception_setf(args->engine, rc,
                                  "Push failed.");
    }else{
        *rv = args->self;
        return 0;
    }
}    

int th1ish_f_ob_level( cwal_callback_args const * args, cwal_value **rv ){
    th1ish_interp * ie = th1ish_args_interp(args);
    assert(ie);
    *rv = cwal_new_integer(args->engine, (cwal_int_t)ie->ob.count);
    return *rv ? 0 : CWAL_RC_OOM;
}    

int th1ish_f_ob_get( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    th1ish_interp * ie = th1ish_args_interp(args);
    cwal_buffer * buf = NULL;
    assert(ie);
    rc = th1ish_ob_get(ie, &buf);
    if(rc) return rc;
    else{
        assert(buf);
    }
    *rv = cwal_new_string_value(args->engine,
                                buf->used ? (char const *)buf->mem : NULL,
                                buf->used);
    return *rv ? 0 : CWAL_RC_OOM;
}    

int th1ish_f_ob_take_string( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    th1ish_interp * ie = th1ish_args_interp(args);
    cwal_buffer buf = cwal_buffer_empty;
    assert(ie);
    rc = th1ish_ob_take(ie, &buf);
    if(rc) return rc;
#if 0
    *rv = cwal_new_string_value(args->engine,
                                buf.used ? (char const *)buf.mem : NULL,
                                buf.used);
    rc = *rv ? 0 : CWAL_RC_OOM;
#else
    { /* Finally get to use a z-string... */
        cwal_string * s = cwal_buffer_to_zstring(args->engine, &buf);
        if(!s) rc = CWAL_RC_OOM;
        else{
            *rv = cwal_string_value(s);
            /* we just took over ownership of buf.mem */;
            assert(!buf.mem);
            assert(!buf.used);
            assert(!buf.capacity);
        }
    }
#endif
    cwal_buffer_reserve(args->engine, &buf, 0);
    return rc;
}    

int th1ish_f_ob_take_buffer( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    th1ish_interp * ie = th1ish_args_interp(args);
    cwal_buffer * buf;
    cwal_value * bv;
    assert(ie);
    bv = cwal_new_buffer_value(args->engine, 0);
    if(!bv) return CWAL_RC_OOM;
    buf = cwal_value_get_buffer(bv);
    rc = th1ish_ob_take(ie, buf);
    if(rc){
        cwal_value_unref(bv);
        return rc;
    }
    *rv = bv;
    return 0;
}    

int th1ish_f_ob_clear( cwal_callback_args const * args, cwal_value **rv ){
    th1ish_interp * ie = (th1ish_interp *)args->state;
    int rc;
    assert(ie);
    rc = th1ish_ob_clear( ie, 0 );
    if(rc){
        return cwal_exception_setf(args->engine, rc,
                                  "Clear failed.");
    }else{
        *rv = args->self;
        return 0;
    }
}

int th1ish_f_ob_flush( cwal_callback_args const * args, cwal_value **rv ){
    th1ish_interp * ie = (th1ish_interp *)args->state;
    int rc;
    assert(ie);
    rc = th1ish_ob_flush( ie );
    if(rc){
        return cwal_exception_setf(args->engine, rc,
                                  "Flush failed.");
    }else{
        *rv = args->self;
        return 0;
    }
}    

int th1ish_install_ob( th1ish_interp * ie, cwal_value * tgt ){
    if(!ie || !tgt) return CWAL_RC_MISUSE;
    else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;
    else{
        int rc;
        cwal_value * v;
#define CHECKV if(!v)return CWAL_RC_OOM
#define FUNC(NAME,FP)                            \
        v = th1ish_new_function2( ie, FP );    \
        CHECKV;                                              \
        rc = cwal_prop_set( tgt, NAME, strlen(NAME), v );    \
        if(rc) goto end;

        FUNC("clear", th1ish_f_ob_clear);
        FUNC("flush", th1ish_f_ob_flush);
        FUNC("getString", th1ish_f_ob_get);
        FUNC("pop", th1ish_f_ob_pop);
        FUNC("push", th1ish_f_ob_push);
        FUNC("level", th1ish_f_ob_level);
        FUNC("takeBuffer", th1ish_f_ob_take_buffer);
        FUNC("takeString", th1ish_f_ob_take_string);
#undef FUNC
#undef CHECKV
        end:
        if(rc && v) cwal_value_unref(v);
        return rc;
    }
}

int th1ish_install_ob_2( th1ish_interp * ie, cwal_value * tgt,
                         char const * name ){
    if(!ie || !tgt || !name || !*name) return CWAL_RC_MISUSE;
    else {
        int rc;
        cwal_value * ob = cwal_new_object_value(ie->e);
        if(!ob) return CWAL_RC_OOM;
        rc = th1ish_install_ob(ie, ob);
        if(!rc) rc = cwal_prop_set(tgt, name, strlen(name), ob);
        return rc;
    }
}

int th1ish_f_slurp_file( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_value * fnv;
    cwal_buffer buf = cwal_buffer_empty;
    char returnBuffer;
    ARGS_IE;
    if(!args->argc) goto misuse;
    fnv = args->argv[0];
    if(!cwal_value_is_string(fnv)) goto misuse;
    returnBuffer = (args->argc>1)
        ? cwal_value_get_bool(args->argv[1])
        : 0;
    rc = cwal_buffer_fill_from_filename( args->engine, &buf,
                                         cwal_string_cstr(cwal_value_get_string(fnv)));
    if(rc){
        return th1ish_toss(ie, rc, "Error %s loading file: %s",
                           cwal_rc_cstr(rc), cwal_value_get_cstr(fnv,NULL));
    }
    if(returnBuffer){
        *rv = cwal_new_buffer_value(args->engine, 0);
        if(*rv){
            cwal_buffer * b = cwal_value_get_buffer(*rv);
            assert(b);
            *b = buf;
            buf = cwal_buffer_empty /* transfer buf.mem ownership to b */;
        }else{
            cwal_buffer_reserve(args->engine, &buf, 0);
        }
    }else{
#if 1 /* Use a Z-string... */
        /* MARKER(("Buffer used=%u, capacity=%u\n",(unsigned)buf.used, (unsigned)buf.capacity)); */
        if((buf.capacity > 512/*arbitrary*/)
           && (buf.capacity > (buf.used * 10 / 8 /* ==80% */))){
            /* Shrink the buffer a bit */
            void * m;
            m = cwal_realloc(args->engine, buf.mem, buf.used+1);
            if(m){
                buf.mem = m;
                buf.mem[buf.used] = 0;
                buf.capacity = buf.used+1;
                /* MARKER(("Shrunk buffer used=%u, capacity=%u\n",(unsigned)buf.used, (unsigned)buf.capacity)); */
            }
            /* else ignore - keep the memory we have */
        }
        *rv = cwal_string_value(cwal_buffer_to_zstring( args->engine, &buf ));
        cwal_buffer_reserve(args->engine, &buf, 0);
#else /* Use a normal string */
        *rv = cwal_new_string_value( args->engine, (char const *)buf.mem, buf.used );
        cwal_buffer_reserve(args->engine, &buf, 0);
#endif
    }
    if(!*rv) rc = CWAL_RC_OOM;
    return rc;

    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE,
                       "Expecting a single string "
                       "(filename) argument.");
}

static int th1ish_f_json_parse_impl( cwal_callback_args const * args,
                                     cwal_value **rv, char isFilename ){
    int rc;
    cwal_value * root = NULL;
    cwal_size_t slen = 0;
    cwal_json_parse_info pInfo = cwal_json_parse_info_empty;
    th1ish_interp * ie = th1ish_args_interp(args);
    char const * cstr;
    cwal_value * arg = args->argc ? args->argv[0] : args->self;
    assert(ie);
    cstr = cwal_value_get_cstr(arg, &slen);
    if(!cstr){
        cwal_buffer * b = cwal_value_buffer_part(ie->e, arg);
        if(b){
            cstr = (char const *)b->mem;
            slen = b->used;
        }
        if(!cstr){
            return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                       "Expecting a %s as argument or 'this'.",
                                       isFilename
                                       ? "filename" : "JSON (string|Buffer)");
        }
    }
    rc = isFilename
        ? cwal_json_parse_filename( args->engine, cstr, &root, &pInfo )
        : cwal_json_parse_cstr( args->engine, cstr, slen, &root, &pInfo );
    if(rc){
        if(pInfo.errorCode){
            return cwal_exception_setf(args->engine, rc,
                                       "Parsing JSON failed at byte "
                                       "offset %"CWAL_SIZE_T_PFMT
                                       ", line %"CWAL_SIZE_T_PFMT
                                       ", column %"CWAL_SIZE_T_PFMT
                                       " with code %d (%s).",
                                       pInfo.length, pInfo.line,
                                       pInfo.col, rc, cwal_rc_cstr(rc));
        }else{
            return cwal_exception_setf(args->engine, rc,
                                       "Parsing JSON failed with code %d (%s).",
                                       rc, cwal_rc_cstr(rc));
        }
    }
    assert(root);
    *rv = root;
    return 0;
}
int th1ish_f_json_parse_string( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_f_json_parse_impl(args, rv, 0);
}

int th1ish_f_json_parse_file( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_f_json_parse_impl(args, rv, 1);
}

static int th1ish_f_to_json_token_impl( cwal_callback_args const * args,
                                  cwal_value **rv,
                                  char useSelf
                                  /*0=use args->argv[0], else args->self*/
                           ){
    if(!args->argc && !useSelf){
        misuse:
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "'toJSONString' requires one argument.");
    }else{
        int rc = 0;
        cwal_value * v = NULL;
        cwal_value * vIndent;
        cwal_json_output_opt outOpt = cwal_json_output_opt_empty;
        cwal_buffer buf = cwal_buffer_empty;
        cwal_buffer * jb = NULL;
        cwal_size_t oldUsed;
        char selfBuf = 0;
        int indentIndex = -1;
        th1ish_interp * ie = th1ish_args_interp(args);
        assert(ie);
        if(useSelf){
            jb = cwal_value_buffer_part(ie->e, args->self);
            /* Change semantics when this===Buffer */
            v = jb
                ? (args->argc ? args->argv[0] : NULL)
                : args->self;
            indentIndex = jb ? 1 : 0;
        }
        else{
            assert(args->argc);
            v = args->argv[0];
            indentIndex = 1;
        }
        if(!v) goto misuse;
        assert(indentIndex >= 0);
        vIndent = (indentIndex >= (int)args->argc)
            ? NULL
            : args->argv[indentIndex];
        selfBuf = jb ? 1 : 0;
        if(!selfBuf) jb = &buf;
        oldUsed = jb->used;
        outOpt.indent = vIndent ? cwal_value_get_integer(vIndent) : 0;
        rc = cwal_json_output_buffer( args->engine, v,
                                      jb, &outOpt );
        if(!rc){
            v = selfBuf
                /* ? cwal_new_integer(args->engine,
                    (cwal_int_t)(jb->used - oldUsed))*/
                ? args->self
                /* TODO? Use a z-string and transfer the buffer memory? */
                : cwal_new_string_value( args->engine,
                                         jb->used
                                         ? ((char const *)jb->mem + oldUsed)
                                         : NULL,
                                         jb->used - oldUsed );
            if(!v) rc = CWAL_RC_OOM;
            else *rv = v;
        }
        if(buf.mem) cwal_buffer_reserve( args->engine, &buf, 0 );
        return rc;
    }
}

int th1ish_f_this_to_json_token( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_f_to_json_token_impl( args, rv, 1 );
}

int th1ish_f_arg_to_json_token( cwal_callback_args const * args, cwal_value **rv ){
    return th1ish_f_to_json_token_impl( args, rv, 0 );
}


int th1ish_f_import_script( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    cwal_value * fnv;
    th1ish_script * sc = 0;
    ARGS_IE;
    char fromCache = 0;
    char const * fname;
    assert(ie);
    if(1!=args->argc) goto misuse;
    fnv = args->argv[0];
    if(!cwal_value_is_string(fnv)) goto misuse;
    fname = cwal_string_cstr(cwal_value_get_string(fnv));
    /**
       See if ie already has a script with this name...

       TODO?: add a boolean parameter to disable cache lookup?
       Or to disable re-evaluation of cached results?
    */
    sc = th1ish_script_by_name(ie, fname);
    if(sc) fromCache = 1;
    else {
        rc = th1ish_script_compile_filename( ie, fname, &sc );
        if(rc){
            return th1ish_toss(ie, rc,
                               "Error %s reading script file: %s",
                               cwal_rc_cstr(rc), fname);
        }
        rc = th1ish_script_add( ie, sc );
        if(rc){
            th1ish_script_finalize(sc);
            return rc;
        }
    }
    rc = th1ish_script_run( sc, 0, rv );
    if(CWAL_RC_RETURN==rc) rc = 0
                               /* necessary workaround :/ */;
    else if(rc && !fromCache){
        /* This is highly arguable! The script may have partially run
           and modified higher-scope bits. We'll find out when it
           crashes during cleanup of resources housed here!
        */
        th1ish_script_finalize(sc);
    }
    return rc;

    misuse:
    return th1ish_toss(ie, CWAL_RC_MISUSE,
                       "Expecting a single string "
                       "(filename) argument.");
}

int th1ish_f_value_compare( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    if(!args->argc || (args->argc>2)){
        return cwal_exception_setf( args->engine, CWAL_RC_MISUSE,
                                    "Expecting 1 or 2 arguments.");
    }
    rc = (args->argc > 1)
        ? cwal_value_compare(args->argv[0], args->argv[1])
        : cwal_value_compare(args->self, args->argv[0])
        ;
    /* Minor optimization:
       
       Normalize rc to one of (-1, 0, 1) because we just happen to
       know that cwal_new_integer() does not allocate for those 3
       values. cwal_value_compare() does not guaranty any specific
       values, only general-purpose comaparator semantics.
    */
    if(rc<0) rc = -1;
    else if(rc>0) rc = 1;
    *rv = cwal_new_integer(args->engine, (cwal_int_t)rc);
    return *rv ? 0 : CWAL_RC_OOM;
}

int th1ish_f_clone_value( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    ARGS_IE;
    assert(ie);
    if(1 != args->argc){
        rc = th1ish_toss(ie, CWAL_RC_MISUSE, "clone requires a single argument.");
    }else{
        cwal_value * xv = NULL;
        rc = cwal_value_clone2( args->argv[0], &xv );
        if(rc){
            assert(!xv);
            rc = th1ish_toss(ie, rc, "Clone failed with code %d (%s).",
                             rc, cwal_rc_cstr(rc));
        }else{
            *rv = xv;
        }
    }
    return rc;
}

int th1ish_f_time( cwal_callback_args const * args, cwal_value **rv ){
    time_t const tm = time(NULL);
    cwal_int_t const it = (cwal_int_t)tm;
    if(tm != (time_t)it){ /* too large for our int type... */
        *rv = cwal_new_double(args->engine, (double)tm);
    }else{
        *rv = cwal_new_integer(args->engine, it);
    }
    return *rv ? 0 : CWAL_RC_OOM;
}

int th1ish_f_hwtime( cwal_callback_args const * args, cwal_value **rv ){
    uint64_t const t = sqlite3Hwtime();
    *rv = (t > (uint64_t)CWAL_INT_T_MAX)
        ? cwal_new_double(args->engine, (cwal_double_t)t)
        : cwal_new_integer(args->engine, (cwal_int_t)t)
        ;
    return *rv ? 0 : CWAL_RC_OOM;
}


int th1ish_f_getenv( cwal_callback_args const * args, cwal_value **rv ){
    char const * cstr = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!cstr){
        return cwal_exception_setf( args->engine, CWAL_RC_MISUSE,
                                    "Expecting a string argument.");
    }else{
        char const * v = getenv(cstr);
        *rv = v
            ? cwal_new_string_value(args->engine, v, 0)
            : cwal_value_undefined();
        return *rv ? 0 : CWAL_RC_OOM;
    }
}

int th1ish_f_rand_int( cwal_callback_args const * args, cwal_value **rv ){
    static cwal_size_t seed = 0;
    cwal_int_t v;
    if( !seed ){
        seed = (cwal_size_t)(((unsigned long)args->self) * 37);
        srand( seed );
    }
    v = (cwal_int_t)(rand() % CWAL_INT_T_MAX);
    *rv = cwal_new_integer( args->engine, v);
    return *rv ? 0 : CWAL_RC_OOM;
}

/* Implemented in in strftime.c */
cwal_size_t th1ish_strftime(char *s, cwal_size_t maxsize,
                            const char *format, const struct tm *timeptr);

int th1ish_f_strftime( cwal_callback_args const * args, cwal_value **rv ){
    time_t timt = -1;
    enum {BufSize = 512};
    char buf[BufSize];
    struct tm * tim;
    cwal_size_t sf;
    char isLocal;
    char const * fmt = args->argc
        ? cwal_value_get_cstr(args->argv[0], NULL)
        : NULL;
    if(!fmt){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "Expecting a format string.");
    }
    if(args->argc>1){
        timt = (time_t)cwal_value_get_double(args->argv[1]);
    }
    isLocal = (args->argc>2)
        ? cwal_value_get_bool(args->argv[2])
        : 0;
    if(timt < 0){
        timt = time(NULL);
    }
    tim = isLocal ? localtime(&timt) : gmtime(&timt);
    memset(buf, 0, BufSize);
    sf = th1ish_strftime( buf, (cwal_size_t)BufSize, fmt, tim );
    if(!sf && *buf){
        return
            cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                                "strftime() failed. Possibly some "
                                "value is too long or some buffer "
                                "too short.");
    }
    assert(!buf[sf]);
    *rv = cwal_new_string_value(args->engine, buf, sf);
    return *rv ? 0 : CWAL_RC_OOM;    
}

int th1ish_install_json( th1ish_interp * ie, cwal_value * ns ){
    cwal_value * mod = NULL;
    char const * modKey = "json";
    cwal_size_t const keyLen = 4;
    cwal_value * v;
    int rc;
    if(!ie || !ns) return CWAL_RC_MISUSE;
    else if(!cwal_props_can(ns)) return CWAL_RC_TYPE;
    else if( cwal_prop_get(ns, modKey, keyLen) ) return 0;

    mod = cwal_new_object_value(ie->e);
    if(!mod) return CWAL_RC_OOM;
    rc =  cwal_prop_set(ns, modKey, keyLen, mod);
    if(rc){
        cwal_value_unref(mod);
        return rc;
    }
#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                               \
    CHECKV;                                                    \
    rc = cwal_prop_set( mod, NAME, strlen(NAME), v );        \
    if(rc) goto end
#define FUNC2(NAME,FP)                        \
    v = th1ish_new_function2( ie, FP ); \
    SET(NAME)

    FUNC2("parse", th1ish_f_json_parse_string);
    FUNC2("parseFile", th1ish_f_json_parse_file);

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


/* Defined in th1ish_str.c */
int th1ish_setup_strings( th1ish_interp * ie, cwal_value * ns );
/* Defined in th1ish_io.c */
int th1ish_setup_io( th1ish_interp * ie, cwal_value * ns );
/* Defined in th1ish_number.c */
int th1ish_setup_number( th1ish_interp * ie, cwal_value * ns );
/* Defined in th1ish_protos.c */
int th1ish_setup_hash( th1ish_interp * ie, cwal_value * ns );

/**
   install api.rc Object to hold name-to-int and int-to-name mappings
   for CWAL_RC_xxx values.
*/
static int th1ish_install_api_rcs(th1ish_interp *ie, cwal_value * api ){
    int rc;
    cwal_value * v;
    cwal_value * sv;
    cwal_value * rcs = cwal_new_object_value(ie->e);
#define SET(RC,KEY) v = cwal_new_integer(ie->e, RC); \
    sv = v ? cwal_new_string_value(ie->e, KEY, strlen(KEY)) : 0; \
    if(!sv) { rc = CWAL_RC_OOM; goto end; } \
    rc = cwal_prop_set_v(rcs, v, sv );          \
    if(rc) goto end; \
    rc = cwal_prop_set_v(rcs, sv, v ); \
    if(rc) goto end;


    SET(CWAL_RC_OK,"OK");
    SET(CWAL_RC_ERROR, "ERROR");
    SET(CWAL_RC_OOM, "OOM");
    SET(CWAL_RC_FATAL, "FATAL");

    SET(CWAL_RC_CONTINUE, "CONTINUE");
    SET(CWAL_RC_BREAK, "BREAK");
    SET(CWAL_RC_RETURN, "RETURN");
    SET(CWAL_RC_EXIT, "EXIT");
    SET(CWAL_RC_EXCEPTION, "EXCEPTION");
    SET(CWAL_RC_ASSERT, "ASSERT");

    SET(CWAL_RC_MISUSE, "MISUSE");
    SET(CWAL_RC_NOT_FOUND, "NOT_FOUND");
    SET(CWAL_RC_ALREADY_EXISTS, "ALREADY_EXISTS");
    SET(CWAL_RC_RANGE, "RANGE");
    SET(CWAL_RC_TYPE, "TYPE");
    SET(CWAL_RC_UNSUPPORTED, "UNSUPPORTED");
    SET(CWAL_RC_ACCESS, "ACCESS");
        
    SET(CWAL_RC_CYCLES_DETECTED, "CYCLES_DETECTED");
    SET(CWAL_RC_DESTRUCTION_RUNNING, "DESTRUCTION_RUNNING");
    SET(CWAL_RC_FINALIZED, "FINALIZED");
    SET(CWAL_RC_HAS_REFERENCES, "HAS_REFERENCES");
    SET(CWAL_RC_INTERRUPTED, "INTERRUPTED");
    SET(CWAL_RC_CANCELLED, "CANCELLED");
    SET(CWAL_RC_IO, "IO");
    SET(CWAL_RC_CANNOT_HAPPEN, "CANNOT_HAPPEN");

    SET(CWAL_RC_JSON_INVALID_CHAR, "JSON_INVALID_CHAR");
    SET(CWAL_RC_JSON_INVALID_KEYWORD, "JSON_INVALID_KEYWORD");
    SET(CWAL_RC_JSON_INVALID_ESCAPE_SEQUENCE, "JSON_INVALID_ESCAPE_SEQUENCE");
    SET(CWAL_RC_JSON_INVALID_UNICODE_SEQUENCE, "JSON_INVALID_UNICODE_SEQUENCE");
    SET(CWAL_RC_JSON_INVALID_NUMBER, "JSON_INVALID_NUMBER");
    SET(CWAL_RC_JSON_NESTING_DEPTH_REACHED, "JSON_NESTING_DEPTH_REACHED");
    SET(CWAL_RC_JSON_UNBALANCED_COLLECTION, "JSON_UNBALANCED_COLLECTION");
    SET(CWAL_RC_JSON_EXPECTED_KEY, "JSON_EXPECTED_KEY");
    SET(CWAL_RC_JSON_EXPECTED_COLON, "JSON_EXPECTED_COLON");
        
    SET(CWAL_SCR_CANNOT_CONSUME, "SCR_CANNOT_CONSUME");
    SET(CWAL_SCR_INVALID_OP, "SCR_INVALID_OP");
    SET(CWAL_SCR_UNKNOWN_IDENTIFIER, "SCR_UNKNOWN_IDENTIFIER");
    SET(CWAL_SCR_CALL_OF_NON_FUNCTION, "SCR_CALL_OF_NON_FUNCTION");
    SET(CWAL_SCR_MISMATCHED_BRACE, "SCR_MISMATCHED_BRACE");
    SET(CWAL_SCR_MISSING_SEPARATOR, "SCR_MISSING_SEPARATOR");
    SET(CWAL_SCR_UNEXPECTED_TOKEN, "SCR_UNEXPECTED_TOKEN");
    SET(CWAL_SCR_UNEXPECTED_EOF, "SCR_UNEXPECTED_EOF");
    SET(CWAL_SCR_DIV_BY_ZERO, "SCR_DIV_BY_ZERO");
    SET(CWAL_SCR_SYNTAX, "SCR_SYNTAX");
    SET(CWAL_SCR_EOF, "SCR_EOF");
    SET(CWAL_SCR_TOO_MANY_ARGUMENTS, "SCR_TOO_MANY_ARGUMENTS");
    SET(CWAL_SCR_EXPECTING_IDENTIFIER, "SCR_EXPECTING_IDENTIFIER");

#undef SET
    cwal_props_sort(rcs);

    end:
    if(!rc){
        rc = cwal_prop_set(api, "rc", 2, rcs );
    }
    if(rc){
        cwal_value_unref(rcs);
    }
    return rc;
}

int th1ish_install_api( th1ish_interp * ie ){
    int rc;
    cwal_value * vApi;
    cwal_value * v;
    cwal_value * protoList;
    /**
       Install base prototypes.
    */
#define CHECKV if(!v) { rc = CWAL_RC_OOM; goto end; } else (void)0
#define RC if(rc) goto end
#define ADDV(NAME) CHECKV; \
    rc = cwal_prop_set(protoList, NAME, strlen(NAME), v)

    vApi = th1ish_new_object(ie);
    if(!vApi) { rc = CWAL_RC_OOM; goto end; }
#if 1
    rc = cwal_var_decl( ie->e, cwal_scope_current_get(ie->e),
                        "api", 3, vApi, CWAL_VAR_F_CONST );
#else
    rc = cwal_scope_chain_set( cwal_scope_current_get(ie->e), 0,
                               "api", 3, vApi );
#endif
    RC;

    v = protoList = th1ish_new_object(ie);
    CHECKV;
    rc = cwal_prop_set(vApi, "prototypes", 10, protoList);
    if(rc){
        cwal_value_unref(protoList);
        protoList = 0;
        goto end;
    }

    if(0){
        rc = th1ish_install_api_rcs(ie, vApi);
        if(rc) goto end;
    }
    
    v = th1ish_prototype_array(ie);
    CHECKV; ADDV("Array");
    v = th1ish_prototype_object(ie);
    CHECKV; ADDV("Object");
    v = th1ish_prototype_function(ie);
    CHECKV; ADDV("Function");
    v = th1ish_prototype_hash(ie);
    CHECKV; ADDV("Hash");
    v = th1ish_prototype_integer(ie);
    CHECKV; ADDV("Integer");
    v = th1ish_prototype_double(ie);
    CHECKV; ADDV("Double");
    v = th1ish_prototype_buffer(ie);
    CHECKV; ADDV("Buffer");

    v = th1ish_prototype_exception(ie);
    CHECKV; ADDV("Exception");
    rc = cwal_prop_set(vApi, "Exception", 9, v);

    v = th1ish_prototype_string(ie);
    CHECKV; ADDV("String");
    v = th1ish_prototype_pf(ie);
    CHECKV; ADDV("PathFinder");
 
    rc = th1ish_setup_strings( ie, vApi );
    RC;

    rc = th1ish_setup_io( ie, vApi );
    RC;

    rc = th1ish_setup_number( ie, vApi );
    RC;

    rc = th1ish_setup_hash( ie, vApi );
    RC;

    rc = th1ish_install_pf( ie, vApi );
    RC;

    /* 'ob' installs directly into the namespace we
       give it, so add an extra namespace for it.
    */
    rc = th1ish_install_ob_2(ie, vApi, "ob");
    RC;

    rc = th1ish_install_json( ie, vApi );
    RC;

    end:
#undef ADDV
#undef CHECKV
#undef RC
    return rc;    
}

int th1ish_f_basic_compile_check( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    th1ish_interp * ie = th1ish_args_interp(args);
    cwal_size_t strLen = 0;
    char const * str = args->argc
        ? cwal_value_get_cstr(args->argv[0], &strLen)
        : 0;
    assert(ie);
    if(!str) return th1ish_toss(ie, CWAL_RC_MISUSE,
                                "Expecting a string argument.");
    if(!*str) {
        *rv = cwal_value_false();
        return 0;
    }
    rc = th1ish_basic_compile_check( ie, str, (cwal_int_t)strLen, 0 );
    if(CWAL_RC_EXCEPTION==rc){
        /* Clear it. */
        cwal_exception_set(ie->e, 0);
    }
    *rv = rc ? cwal_value_false() : cwal_value_true();
    return 0;
}

int th1ish_basic_compile_check( th1ish_interp * ie,
                                char const * code,
                                cwal_int_t codeLen,
                                cwal_simple_token ** out ){
    int rc;
    cwal_simple_token * head = 0;
    cwal_simple_token * t = 0;
    cwal_simple_token * next = 0;
    cwal_size_t n;
    int const oldSkipLevel = ie ? ie->skipLevel : 0;
    if(!code || !codeLen) return CWAL_RC_RANGE;
    assert(!IE_SKIP_MODE);
    n = (codeLen>=0) ? (cwal_size_t)codeLen : (cwal_size_t)strlen(code);

    rc = th1ish_tokenize_all( ie, code, n, &head );
    if(rc) return rc;
    t = head;
    /* ++ie->skipLevel; */
    ie->skipLevel = 1;
    rc = th1ish_eval_chain(ie, t, &next, 0);
    assert(1==ie->skipLevel);
    for( ; !rc && (t != next) && !cwal_st_eof(next);
         t = next ){
        rc = th1ish_eval_chain(ie, t, &next, 0);
        assert(1==ie->skipLevel);
    }
    assert(1==ie->skipLevel);
    /* --ie->skipLevel; */
    ie->skipLevel = oldSkipLevel;
    if(!rc && out){
        *out = head;
    }else{
        th1ish_st_free_chain(ie, head, 1);
    }
    return rc;
}

/**
   Internal impl of sleep()/mssleep(). delayMult must be:

       sleep(): 1000*1000
       mssleep(): 1000
       usleep(): 1
*/
static int th1ish_f_sleep_impl(cwal_callback_args const * args, cwal_value ** rv,
                               unsigned int delayMult){
#if !TH1ISH_UNIX
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                              "sleep() routines not implemented in this build.");
#else
    if(!args->argc || !cwal_value_is_number(args->argv[0])){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "Expecting an integer argument.");
    }else{
        int rc = -1;
        cwal_int_t const t = cwal_value_get_integer(args->argv[0]);
        if(0<=t){
            rc = usleep( t * delayMult );
        }
        *rv = cwal_new_integer(args->engine, rc ? errno : rc);
        return *rv ? 0 : CWAL_RC_OOM;
    }
#endif
}

int th1ish_f_sleep(cwal_callback_args const * args, cwal_value ** rv){
#if !TH1ISH_UNIX
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                              "sleep() routines not implemented in this build.");
#else
    if(!args->argc || !cwal_value_is_number(args->argv[0])){
        return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                   "Expecting an integer argument.");
    }else{
        int rc = -1;
        cwal_int_t const t = cwal_value_get_integer(args->argv[0]);
        if(0<=t){
            rc = sleep( t );
        }
        *rv = cwal_new_integer(args->engine, rc);
        return *rv ? 0 : CWAL_RC_OOM;
    }
#endif
}

int th1ish_f_mssleep(cwal_callback_args const * args, cwal_value ** rv){
    return th1ish_f_sleep_impl( args, rv, 1000 );
}

int th1ish_tmplish_to_code( cwal_engine *e, cwal_buffer const * src,
                            cwal_buffer * dest,
                            th1ish_tmplish_opt const * opt){
    char const * pos;
    char const * end;
    char const * origin;
    char const * tagOpenScript = "<?";
    int nOpenScript = 2;
    char const * tagCloseScript = "?>";
    int nCloseScript = 2;
    char const * tagOpenValue = "<%";
    int nOpenValue = 2;
    char const * tagCloseValue = "%>";
    int nCloseValue = 2;
    static char const * outAlias = "➤➤" /* "ᐊᐊ" */ /*"ᗛᗛ"*/ /* local var alias for OUT */;
    char const * hDocPrefix;
    static char const * hDocId = "ᐊᐊ" /*heredoc ID used by output and eval blocks */;
    char const * errMsg = 0;
    char const * openStart = 0;
    char const * outsymLong = "TMPLISHOUT";
    int rc = 0;
    char hIsOpen = 0;
    cwal_buffer hTag = cwal_buffer_empty;
    if(!e || !src || !dest) return CWAL_RC_MISUSE;
    if(!opt) opt = &th1ish_tmplish_opt_empty;
    else{
        /* Set up custom options... */
        if(opt->outputSymbolInternal) outAlias = opt->outputSymbolInternal;
        if(opt->outputSymbolPublic) outsymLong = opt->outputSymbolPublic;
        if(opt->heredocId) hDocId = opt->heredocId;
        if(opt->tagCodeOpen && opt->tagCodeClose){
            tagOpenScript = opt->tagCodeOpen;
            nOpenScript = cwal_strlen(tagOpenScript);
            tagCloseScript = opt->tagCodeClose;
            nCloseScript = cwal_strlen(tagCloseScript);
        }
        if(opt->tagValueOpen && opt->tagValueClose){
            tagOpenValue = opt->tagValueOpen;
            nOpenValue = cwal_strlen(tagOpenValue);
            tagCloseValue = opt->tagValueClose;
            nCloseValue = cwal_strlen(tagCloseValue);
        }
        if(nOpenValue<1 /* all tags/ids need a length of at least 1 */
           || nCloseValue<1
           || nOpenScript<1
           || nCloseScript<1
           || cwal_strlen(outAlias)<1
           || cwal_strlen(outsymLong)<1
           || cwal_strlen(hDocId)<1
           ){
            return CWAL_RC_RANGE;
        }else if(/* ensure that no two open/close tags are the same. */
                 0==strcmp(tagOpenValue,tagCloseValue)
                 || 0==strcmp(tagOpenValue,tagOpenScript)
                 || 0==strcmp(tagOpenValue,tagCloseScript)
                 || 0==strcmp(tagOpenScript,tagCloseScript)
                 || 0==strcmp(tagOpenScript,tagCloseValue)
                 ){
            return CWAL_RC_RANGE;
        }
        /* MARKER(("tagOpenValue=%.*s, tagCloseValue=%.*s, "
                "tagOpenScript=%.*s, tagCloseScript=%.*s\n",
                nOpenValue, tagOpenValue, nCloseValue, tagCloseValue,
                nOpenScript, tagOpenScript, nCloseScript, tagCloseScript));*/
    }
    origin = pos = (char const *)src->mem;
    end = pos + src->used;
    rc = cwal_buffer_printf(e, &hTag, "$%s <<<:", outAlias);
    hDocPrefix = (char const *)hTag.mem;
    /* rc = cwal_buffer_printf(e, &hTag, "ĎŐĊ"); */
    if(!rc) rc = cwal_buffer_reserve(e, dest, src->capacity)
                /* likely not enough, but we cannot guess the approximate
                   final size without further inspection. */;
    while(!rc){
        rc = cwal_buffer_append( e, dest, "(", 1);
        if(!rc
           && !opt->outputSymbolPublic
           && !(opt->flags & TH1ISH_TMPLISH_ELIDE_TMPLISHOUT)){
            /* Set up local vars, if not done already */
            rc = cwal_buffer_printf( e, dest,
                                     "(({undefined}===typename %s) && var %s),\n"
                                     "((undefined === %s) "
                                       "&& assert(api && api.io && api.io.output)"
                                       "&& (%s=api.io.output)),\n",
                                     outsymLong,
                                     outsymLong,
                                     outsymLong, outsymLong);
        }
        if(rc) break;
#if 1
        rc = cwal_buffer_printf( e, dest,
                                 "((({undefined}===typename %s) "
                                 "&& (var %s)),"
                                 " (%s = %s)"
                                 ")",
                                 outAlias,
                                 outAlias,
                                 outAlias, outsymLong);
#else
        rc = cwal_buffer_printf( e, dest,
                                 "((({undefined}===typename %s) "
                                 "&& (var %s)),"
                                 " ((undefined===%s) && (%s = %s))"
                                 ")",
                                 outAlias,
                                 outAlias,
                                 outAlias, outAlias, outsymLong);
#endif
        if(!rc) cwal_buffer_append( e, dest, ");\n", 3);
        break;
    }
    if(rc) goto end;
    if(1){
        /*
          Scan the first bytes and ignore any leading spaces before an opening
          <? or <%. If the first non-space character is not one of those patterns,
          retain the leading space.
        */
        char const * x = pos;
        for( ; *x && ('\n'==*x || isspace(*x)); ++x) continue;
        if(*x && (0==memcmp(x, tagOpenScript, nOpenScript)
                  || 0==memcmp(x, tagOpenValue, nOpenValue))){
            origin = pos = x;
        }
    }


    for( ; !rc && (pos < end); ++pos ){
        if(dest->capacity <= dest->used-2){
            rc = cwal_buffer_reserve(e, dest, dest->capacity * 3 / 2 );
        }
        if(((end-pos) < nCloseScript)
           && ((end-pos) < nCloseValue)) {
            rc = cwal_buffer_append(e, dest, pos, end - pos);
            break;
        }
        else if(((end-pos) > nOpenScript) /* <? */
           && (0==memcmp(pos, tagOpenScript, nOpenScript))){
            openStart = pos;
            pos += nOpenScript;
            if(hIsOpen){
                rc = cwal_buffer_printf(e, dest, " %s;", hDocId);
                hIsOpen = 0;
            }
            for( ; !rc && (pos < end); ++pos ){
                if(pos > (end-nCloseScript)){
                    /* Allow <? tags to end the document unclosed,
                       to simplify generation of docs which do not
                       output any whitespace (e.g. a trailing
                       newline).  PHP does this (for the same
                       reason, AFAIK).
                    */
                    rc = cwal_buffer_append(e, dest, pos, (end-pos));
                    break;
                }
                else if(0==memcmp(pos, tagCloseScript, nCloseScript)){
                    pos += nCloseScript -1 /*outer loop will re-add one*/;
                    rc = cwal_buffer_printf(e, dest, "\n%s%s ",
                                            hDocPrefix, hDocId);
                    hIsOpen = 1;
                    break;
                }
                else {
                    rc = cwal_buffer_append(e, dest, pos, 1);
                }
            }
            continue;
        }
        else if(((end-pos) > nOpenValue) /* <% */
                && 0==memcmp(pos, tagOpenValue, nOpenValue)){
            openStart = pos;
            pos += nOpenValue;
            if(hIsOpen){
                rc = cwal_buffer_printf(e, dest, " %s; [%s eval<<<%s ",
                                        hDocId, outAlias, hDocId);
                hIsOpen = 0;
            }else if(openStart == origin
                     /*workaround for <% at starting pos. */){
                rc = cwal_buffer_printf(e, dest, "[%s eval<<<%s ",
                                        outAlias, hDocId);
                hIsOpen = 1;
            }
            for( ; !rc && (pos < end); ++pos ){
                if(pos > (end-nCloseValue)){
                    rc = CWAL_RC_RANGE;
                    errMsg = "Missing closing tag.";
                    pos = openStart;
                    goto syntax_err;
                }
                else if(0==memcmp(pos, tagCloseValue, nCloseValue)){
                    pos += nCloseValue-1 /*b/c outer loop will add one*/;
                    rc = cwal_buffer_printf(e, dest, " %s] %s%s ",
                                            hDocId, hDocPrefix, hDocId);
                    hIsOpen = 1;
                    break;
                }else{
                    rc = cwal_buffer_append(e, dest, pos, 1);
                }
            }
        }else{
            /* Pipe the rest through as-is. TODO: optimize this
               by scouting for the next '<' character before
               pushing the output.*/
            if(!hIsOpen){
                rc = cwal_buffer_printf(e, dest, "\n%s%s ",
                                        hDocPrefix, hDocId);
                hIsOpen = 1;
            }
            if(!rc) rc = cwal_buffer_append(e, dest, pos, 1);
        }
    }
    end:
    if(!rc){
        if(hIsOpen){
            rc = cwal_buffer_printf(e, dest, "\n%s;\n", hDocId);
        }
        if(!rc) rc = cwal_buffer_printf(e, dest,
                                        "true /*result value*/;\n");
    }
    cwal_buffer_reserve(e, &hTag, 0);
    return rc;
    syntax_err:
    {
        unsigned int line = 1;
        unsigned int col = 0;
        assert(errMsg);
        assert(0 != rc);
        th1ish_count_lines( (char const *)src->mem,
                            (char const *)src->mem+src->used,
                            pos, &line, &col);
        return cwal_exception_setf(e, rc,
                                   "tmplish error at line %u, col %u: %s",
                                   line, col, errMsg);
    }
}

int th1ish_f_tmplish_to_code( cwal_callback_args const * args, cwal_value ** rv ){
    int rc;
    char const * src;
    cwal_buffer srcB = cwal_buffer_empty;
    cwal_buffer xbuf = cwal_buffer_empty;
    cwal_engine * e = args->engine;
    th1ish_tmplish_opt topt = th1ish_tmplish_opt_empty;
    cwal_value * optObj = args->argc>0
        ? args->argv[1]
        : 0;
    src = args->argc
        ? cwal_value_get_cstr(args->argv[0], &srcB.used)
        : 0;
    if(!src){
        return cwal_exception_setf(e, CWAL_RC_MISUSE,
                                   "Expecting a string/buffer argument.");
    }else if(!srcB.used){
        *rv = args->argv[0];
        return 0;
    }

    if( optObj && cwal_props_can(optObj)){
        cwal_value const * v;
        char const * vKey;
#define CHECK(SPROP,CPROP) \
        if((v = cwal_prop_get(optObj, SPROP, 0))    \
           && (vKey=cwal_value_get_cstr(v,0))) \
            topt.CPROP = vKey
        CHECK("codeOpen", tagCodeOpen);
        CHECK("codeClose", tagCodeClose);
        CHECK("valueOpen", tagValueOpen);
        CHECK("valueClose", tagValueClose);
#undef CHECK
    }

    if(0){ /* just testing, but this makes for a much shorter header! */
        topt.outputSymbolPublic = "api.io.output";
        /* topt.outputSymbolInternal = "print"; */
        /* topt.heredocId = "'~_~'"; */
    }else if(cwal_scope_search(args->scope, -1, "TMPLISHOUT", 10, 0)){
        /* Kind of a cheap (and not always valid) optimization */
        topt.flags |= TH1ISH_TMPLISH_ELIDE_TMPLISHOUT;
    }

    srcB.mem = (unsigned char *)src;
    /* Note that our misuse of srcB here, pointing it to foreign memory,
       is only legal because it is used only in const contexts. */
    rc = th1ish_tmplish_to_code(e, &srcB, &xbuf, &topt);
    if(!rc){
        cwal_buffer * rb = cwal_new_buffer(e, 0);
        if(!rb) rc = CWAL_RC_OOM;
        else{
            *rb = xbuf /* transfer memory */;
            xbuf = cwal_buffer_empty;
            *rv = cwal_buffer_value(rb);
        }
    }else{
        switch(rc){
          case CWAL_RC_RANGE:
              rc = cwal_exception_setf(args->engine, rc,
                                       "Invalid tag configuration(?). "
                                       "See the th1ish_tmplish_to_code() API "
                                       "docs.");
              break;
          default:
              break;
        }
    }
    cwal_buffer_clear(e, &xbuf);
    return rc;
}


#undef TNEXT
#undef IE_SKIP_MODE
#undef dump_token
#undef dump_tokenizer
#undef th1ish_dump_val
#undef TH1ISH_PROC_CACHE_TOKENS
#undef th1ish_reset_this
#undef FUNC_SYM_PROP
#undef FUNC_SYM_PROP_LEN
#undef ARGS_IE
#undef th1ish_eval_check_exit
#undef RC_IS_EXIT
#undef MARKER
#undef TH1ISH_ENABLE_USLEEP
#undef th1ish_this_save
#undef th1ish_this_restore

/* end of file th1ish.c */
/* start of file strftime.c */
/*******************************************************************************
 *  The Elm Mail System  -  $Revision: 1.3 $   $State: Exp $
 *
 * Public-domain relatively quick-and-dirty implemenation of
 * ANSI library routine for System V Unix systems.
 *
 * Arnold Robbins
 *
 *******************************************************************************
 * Bug reports, patches, comments, suggestions should be sent to:
 * (Note: this routine is provided as is, without support for those sites that
 *    do not have strftime in their library)
 *
 *    Syd Weinstein, Elm Coordinator
 *    elm@DSI.COM            dsinc!elm
 *
 *******************************************************************************
 * $Log: strftime.c,v $
 * Revision 1.3  1993/10/09  19:38:51  smace
 * Update to elm 2.4 pl23 release version
 *
 * Revision 5.8  1993/08/23  02:46:51  syd
 * Test ANSI_C, not __STDC__ (which is not set on e.g. AIX).
 * From: decwrl!uunet.UU.NET!fin!chip (Chip Salzenberg)
 *
 * Revision 5.7  1993/08/03  19:28:39  syd
 * Elm tries to replace the system toupper() and tolower() on current
 * BSD systems, which is unnecessary.  Even worse, the replacements
 * collide during linking with routines in isctype.o.  This patch adds
 * a Configure test to determine whether replacements are really needed
 * (BROKE_CTYPE definition).  The <ctype.h> header file is now included
 * globally through hdrs/defs.h and the BROKE_CTYPE patchup is handled
 * there.  Inclusion of <ctype.h> was removed from *all* the individual
 * files, and the toupper() and tolower() routines in lib/opt_utils.c
 * were dropped.
 * From: chip@chinacat.unicom.com (Chip Rosenthal)
 *
 * Revision 5.6  1993/08/03  19:20:31  syd
 * Implement new timezone handling.  New file lib/get_tz.c with new timezone
 * routines.  Added new TZMINS_USE_xxxxxx and TZNAME_USE_xxxxxx configuration
 * definitions.  Obsoleted TZNAME, ALTCHECK, and TZ_MINUTESWEST configuration
 * definitions.  Updated Configure.  Modified lib/getarpdate.c and
 * lib/strftime.c to use new timezone routines.
 * From: chip@chinacat.unicom.com (Chip Rosenthal)
 *
 * Revision 5.5  1993/06/10  03:17:45  syd
 * Change from TZNAME_MISSING to TZNAME
 * From: Syd via request from Dan Blanchard
 *
 * Revision 5.4  1993/05/08  19:56:45  syd
 * update to newer version
 * From: Syd
 *
 * Revision 5.3  1993/04/21  01:42:23  syd
 * avoid name conflicts on min and max
 *
 * Revision 5.2  1993/04/16  04:29:34  syd
 * attempt to bsdize a bit strftime
 * From: many via syd
 *
 * Revision 5.1  1993/01/27  18:52:15  syd
 * Initial checkin of contributed public domain routine.
 * This routine is provided as is and not covered by Elm Copyright.
 ******************************************************************************/

/*
 * strftime.c
 *
 * Public-domain relatively quick-and-dirty implementation of
 * ANSI library routine for System V Unix systems.
 *
 * It's written in old-style C for maximal portability.
 * However, since I'm used to prototypes, I've included them too.
 *
 * If you want stuff in the System V ascftime routine, add the SYSV_EXT define.
 * For extensions from SunOS, add SUNOS_EXT.
 * For stuff needed to implement the P1003.2 date command, add POSIX2_DATE.
 * For complete POSIX semantics, add POSIX_SEMANTICS.
 *
 * The code for %c, %x, and %X is my best guess as to what's "appropriate".
 * This version ignores LOCALE information.
 * It also doesn't worry about multi-byte characters.
 * So there.
 *
 * This file is also shipped with GAWK (GNU Awk), gawk specific bits of
 * code are included if GAWK is defined.
 *
 * Arnold Robbins
 * January, February, March, 1991
 * Updated March, April 1992
 * Updated May, 1993
 *
 * Fixes from ado@elsie.nci.nih.gov
 * February 1991, May 1992
 * Fixes from Tor Lillqvist tor@tik.vtt.fi
 * May, 1993
 *
 * Fast-forward to July 2013...
 *
 * stephan@wanderinghorse.net copied these sources from:
 *
 * ftp://ftp-archive.freebsd.org/pub/FreeBSD-Archive/old-releases/i386/1.0-RELEASE/ports/elm/lib/strftime.c
 *
 * And made the following changes:
 *
 * Removed ancient non-ANSI decls. Added some headers to get it to
 * compile for me. Renamed functions to fit into my project. Replaced
 * hard tabs with 4 spaces. Added #undefs for all file-private #defines
 * to safe-ify inclusion from/with other files.
 * 
*/

#include <time.h>
/* #include <sys/time.h> */
#include <string.h> /* strchr() and friends */
#include <stdio.h> /* sprintf() */
#include <ctype.h> /* toupper(), islower() */


#define HAVE_GET_TZ_NAME 0
#if HAVE_GET_TZ_NAME
extern char *get_tz_name();
#endif

#ifndef BSD
extern void tzset (void);
#endif
static int weeknumber (const struct tm *timeptr, int firstweekday);

/* defaults: season to taste */
#define SYSV_EXT    1    /* stuff in System V ascftime routine */
#define SUNOS_EXT    1    /* stuff in SunOS strftime routine */
#define POSIX2_DATE    1    /* stuff in Posix 1003.2 date command */
#define VMS_EXT        1    /* include %v for VMS date format */

#if 0
#define POSIX_SEMANTICS    1    /* call tzset() if TZ changes */
#endif

#ifdef POSIX_SEMANTICS
#include <stdlib.h> /* malloc() and friends */
#endif

#if defined(POSIX2_DATE)
#if ! defined(SYSV_EXT)
#define SYSV_EXT    1
#endif
#if ! defined(SUNOS_EXT)
#define SUNOS_EXT    1
#endif
#endif

#if defined(POSIX2_DATE)
static int iso8601wknum(const struct tm *timeptr);
#endif

#undef strchr    /* avoid AIX weirdness */


#ifdef __GNUC__
#define inline    __inline__
#else
#define inline    /**/
#endif

#define range(low, item, hi)    maximum(low, minimum(item, hi))

/* minimum --- return minimum of two numbers */

static inline int
minimum(int a, int b)
{
    return (a < b ? a : b);
}

/* maximum --- return maximum of two numbers */

static inline int
maximum(int a, int b)
{
    return (a > b ? a : b);
}

/* strftime --- produce formatted time */

cwal_size_t
th1ish_strftime(char *s, cwal_size_t maxsize, const char *format, const struct tm *timeptr)
{
    char *endp = s + maxsize;
    char *start = s;
    char tbuf[100];
    int i;
    static short first = 1;
#ifdef POSIX_SEMANTICS
    static char *savetz = NULL;
    static int savetzlen = 0;
    char *tz;
#endif /* POSIX_SEMANTICS */

    /* various tables, useful in North America */
    static char *days_a[] = {
        "Sun", "Mon", "Tue", "Wed",
        "Thu", "Fri", "Sat",
    };
    static char *days_l[] = {
        "Sunday", "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday",
    };
    static char *months_a[] = {
        "Jan", "Feb", "Mar", "Apr", "May", "Jun",
        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
    };
    static char *months_l[] = {
        "January", "February", "March", "April",
        "May", "June", "July", "August", "September",
        "October", "November", "December",
    };
    static char *ampm[] = { "AM", "PM", };

    if (s == NULL || format == NULL || timeptr == NULL || maxsize == 0)
        return 0;

    if (strchr(format, '%') == NULL && strlen(format) + 1 >= maxsize)
        return 0;

#ifndef POSIX_SEMANTICS
    if (first) {
        tzset();
        first = 0;
    }
#else    /* POSIX_SEMANTICS */
    tz = getenv("TZ");
    if (first) {
        if (tz != NULL) {
            int tzlen = strlen(tz);

            savetz = (char *) malloc(tzlen + 1);
            if (savetz != NULL) {
                savetzlen = tzlen + 1;
                strcpy(savetz, tz);
            }
        }
        tzset();
        first = 0;
    }
    /* if we have a saved TZ, and it is different, recapture and reset */
    if (tz && savetz && (tz[0] != savetz[0] || strcmp(tz, savetz) != 0)) {
        i = strlen(tz) + 1;
        if (i > savetzlen) {
            savetz = (char *) realloc(savetz, i);
            if (savetz) {
                savetzlen = i;
                strcpy(savetz, tz);
            }
        } else
            strcpy(savetz, tz);
        tzset();
    }
#endif    /* POSIX_SEMANTICS */

    for (; *format && s < endp - 1; format++) {
        tbuf[0] = '\0';
        if (*format != '%') {
            *s++ = *format;
            continue;
        }
    again:
        switch (*++format) {
        case '\0':
            *s++ = '%';
            goto out;

        case '%':
            *s++ = '%';
            continue;

        case 'a':    /* abbreviated weekday name */
            if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, days_a[timeptr->tm_wday]);
            break;

        case 'A':    /* full weekday name */
            if (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, days_l[timeptr->tm_wday]);
            break;

#ifdef SYSV_EXT
        case 'h':    /* abbreviated month name */
#endif
        case 'b':    /* abbreviated month name */
            if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, months_a[timeptr->tm_mon]);
            break;

        case 'B':    /* full month name */
            if (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)
                strcpy(tbuf, "?");
            else
                strcpy(tbuf, months_l[timeptr->tm_mon]);
            break;

        case 'c':    /* appropriate date and time representation */
            sprintf(tbuf, "%s %s %2d %02d:%02d:%02d %d",
                days_a[range(0, timeptr->tm_wday, 6)],
                months_a[range(0, timeptr->tm_mon, 11)],
                range(1, timeptr->tm_mday, 31),
                range(0, timeptr->tm_hour, 23),
                range(0, timeptr->tm_min, 59),
                range(0, timeptr->tm_sec, 61),
                timeptr->tm_year + 1900);
            break;

        case 'd':    /* day of the month, 01 - 31 */
            i = range(1, timeptr->tm_mday, 31);
            sprintf(tbuf, "%02d", i);
            break;

        case 'H':    /* hour, 24-hour clock, 00 - 23 */
            i = range(0, timeptr->tm_hour, 23);
            sprintf(tbuf, "%02d", i);
            break;

        case 'I':    /* hour, 12-hour clock, 01 - 12 */
            i = range(0, timeptr->tm_hour, 23);
            if (i == 0)
                i = 12;
            else if (i > 12)
                i -= 12;
            sprintf(tbuf, "%02d", i);
            break;

        case 'j':    /* day of the year, 001 - 366 */
            sprintf(tbuf, "%03d", timeptr->tm_yday + 1);
            break;

        case 'm':    /* month, 01 - 12 */
            i = range(0, timeptr->tm_mon, 11);
            sprintf(tbuf, "%02d", i + 1);
            break;

        case 'M':    /* minute, 00 - 59 */
            i = range(0, timeptr->tm_min, 59);
            sprintf(tbuf, "%02d", i);
            break;

        case 'p':    /* am or pm based on 12-hour clock */
            i = range(0, timeptr->tm_hour, 23);
            if (i < 12)
                strcpy(tbuf, ampm[0]);
            else
                strcpy(tbuf, ampm[1]);
            break;

        case 'S':    /* second, 00 - 61 */
            i = range(0, timeptr->tm_sec, 61);
            sprintf(tbuf, "%02d", i);
            break;

        case 'U':    /* week of year, Sunday is first day of week */
            sprintf(tbuf, "%d", weeknumber(timeptr, 0));
            break;

        case 'w':    /* weekday, Sunday == 0, 0 - 6 */
            i = range(0, timeptr->tm_wday, 6);
            sprintf(tbuf, "%d", i);
            break;

        case 'W':    /* week of year, Monday is first day of week */
            sprintf(tbuf, "%d", weeknumber(timeptr, 1));
            break;

        case 'x':    /* appropriate date representation */
            sprintf(tbuf, "%s %s %2d %d",
                days_a[range(0, timeptr->tm_wday, 6)],
                months_a[range(0, timeptr->tm_mon, 11)],
                range(1, timeptr->tm_mday, 31),
                timeptr->tm_year + 1900);
            break;

        case 'X':    /* appropriate time representation */
            sprintf(tbuf, "%02d:%02d:%02d",
                range(0, timeptr->tm_hour, 23),
                range(0, timeptr->tm_min, 59),
                range(0, timeptr->tm_sec, 61));
            break;

        case 'y':    /* year without a century, 00 - 99 */
            i = timeptr->tm_year % 100;
            sprintf(tbuf, "%d", i);
            break;

        case 'Y':    /* year with century */
            sprintf(tbuf, "%d", 1900 + timeptr->tm_year);
            break;

#if HAVE_GET_TZ_NAME
          case 'Z':    /* time zone name or abbrevation */
            strcpy(tbuf, get_tz_name(timeptr));
            break;
#endif
            
#ifdef SYSV_EXT
        case 'n':    /* same as \n */
            tbuf[0] = '\n';
            tbuf[1] = '\0';
            break;

        case 't':    /* same as \t */
            tbuf[0] = '\t';
            tbuf[1] = '\0';
            break;

        case 'D':    /* date as %m/%d/%y */
            th1ish_strftime(tbuf, sizeof tbuf, "%m/%d/%y", timeptr);
            break;

        case 'e':    /* day of month, blank padded */
            sprintf(tbuf, "%2d", range(1, timeptr->tm_mday, 31));
            break;

        case 'r':    /* time as %I:%M:%S %p */
            th1ish_strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
            break;

        case 'R':    /* time as %H:%M */
            th1ish_strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
            break;

        case 'T':    /* time as %H:%M:%S */
            th1ish_strftime(tbuf, sizeof tbuf, "%H:%M:%S", timeptr);
            break;
#endif

#ifdef SUNOS_EXT
        case 'k':    /* hour, 24-hour clock, blank pad */
            sprintf(tbuf, "%2d", range(0, timeptr->tm_hour, 23));
            break;

        case 'l':    /* hour, 12-hour clock, 1 - 12, blank pad */
            i = range(0, timeptr->tm_hour, 23);
            if (i == 0)
                i = 12;
            else if (i > 12)
                i -= 12;
            sprintf(tbuf, "%2d", i);
            break;
#endif


#ifdef VMS_EXT
        case 'v':    /* date as dd-bbb-YYYY */
            sprintf(tbuf, "%2d-%3.3s-%4d",
                range(1, timeptr->tm_mday, 31),
                months_a[range(0, timeptr->tm_mon, 11)],
                timeptr->tm_year + 1900);
            for (i = 3; i < 6; i++)
                if (islower((int)tbuf[i]))
                    tbuf[i] = toupper((int)tbuf[i]);
            break;
#endif


#ifdef POSIX2_DATE
        case 'C':
            sprintf(tbuf, "%02d", (timeptr->tm_year + 1900) / 100);
            break;


        case 'E':
        case 'O':
            /* POSIX locale extensions, ignored for now */
            goto again;

        case 'V':    /* week of year according ISO 8601 */
#if defined(GAWK) && defined(VMS_EXT)
        {
            extern int do_lint;
            extern void warning();
            static int warned = 0;

            if (! warned && do_lint) {
                warned = 1;
                warning(
    "conversion %%V added in P1003.2/11.3; for VMS style date, use %%v");
            }
        }
#endif
            sprintf(tbuf, "%d", iso8601wknum(timeptr));
            break;

        case 'u':
        /* ISO 8601: Weekday as a decimal number [1 (Monday) - 7] */
            sprintf(tbuf, "%d", timeptr->tm_wday == 0 ? 7 :
                    timeptr->tm_wday);
            break;
#endif    /* POSIX2_DATE */
        default:
            tbuf[0] = '%';
            tbuf[1] = *format;
            tbuf[2] = '\0';
            break;
        }
        i = strlen(tbuf);
        if (i){
            if (s + i < endp - 1) {
                strcpy(s, tbuf);
                s += i;
            } else return 0;
            /* reminder: above IF originally had ambiguous else
               placement (no braces).  This placement _appears_ to be
               correct.*/
        }
    }
out:
    if (s < endp && *format == '\0') {
        *s = '\0';
        return (s - start);
    } else
        return 0;
}

#ifdef POSIX2_DATE
/* iso8601wknum --- compute week number according to ISO 8601 */

static int
iso8601wknum(const struct tm *timeptr)
{
    /*
     * From 1003.2 D11.3:
     *    If the week (Monday to Sunday) containing January 1
     *    has four or more days in the new year, then it is week 1;
     *    otherwise it is week 53 of the previous year, and the
     *    next week is week 1.
     *
     * ADR: This means if Jan 1 was Monday through Thursday,
     *    it was week 1, otherwise week 53.
     */

    int simple_wknum, jan1day, diff, ret;

    /* get week number, Monday as first day of the week */
    simple_wknum = weeknumber(timeptr, 1) + 1;

    /*
     * With thanks and tip of the hatlo to tml@tik.vtt.fi
     *
     * What day of the week does January 1 fall on?
     * We know that
     *    (timeptr->tm_yday - jan1.tm_yday) MOD 7 ==
     *        (timeptr->tm_wday - jan1.tm_wday) MOD 7
     * and that
     *     jan1.tm_yday == 0
     * and that
     *     timeptr->tm_wday MOD 7 == timeptr->tm_wday
     * from which it follows that. . .
      */
    jan1day = timeptr->tm_wday - (timeptr->tm_yday % 7);
    if (jan1day < 0)
        jan1day += 7;

    /*
     * If Jan 1 was a Monday through Thursday, it was in
     * week 1.  Otherwise it was last year's week 53, which is
     * this year's week 0.
     */
    if (jan1day >= 1 && jan1day <= 4)
        diff = 0;
    else
        diff = 1;
    ret = simple_wknum - diff;
    if (ret == 0)    /* we're in the first week of the year */
        ret = 53;
    return ret;
}
#endif

/* weeknumber --- figure how many weeks into the year */

/* With thanks and tip of the hatlo to ado@elsie.nci.nih.gov */

static int
weeknumber(const struct tm *timeptr, int firstweekday)
{
    if (firstweekday == 0)
        return (timeptr->tm_yday + 7 - timeptr->tm_wday) / 7;
    else
        return (timeptr->tm_yday + 7 -
            (timeptr->tm_wday ? (timeptr->tm_wday - 1) : 6)) / 7;
}

#undef SYSV_EXT
#undef SUNOS_EXT
#undef POSIX2_DATE
#undef VMS_EXT
#undef POSIX_SEMANTICS
#undef adddecl
#undef inline
#undef range
#undef maximum
#undef minimum
#undef HAVE_GET_TZ_NAME
#undef GAWK
/* end of file strftime.c */