#if !defined(__STDC_FORMAT_MACROS) /* required for PRIi32 and friends.*/
#  define __STDC_FORMAT_MACROS
#endif
#include "s2_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;

/**
   If CWAL_INT_DOUBLE_SAME_SIZE is true then we can pack integers
   and doubles into the same recycling bins.
*/
#define CWAL_INT_DOUBLE_SAME_SIZE (sizeof(cwal_double_t)==sizeof(cwal_int_t))

#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*/,
#if 0
    32/*MaxRecycledStringLen*/,
#elif 16 == CWAL_SIZE_T_BITS
    32/*MaxRecycledStringLen*/,
#elif 1
    64/*MaxRecycledStringLen*/,
#elif 32==CWAL_SIZE_T_BITS || 16==CWAL_SIZE_T_BITS
    2*CWAL_SIZE_T_BITS/*MaxRecycledStringLen*/,
    /* In basic tests using s2, 64 gives overall better results (tiny
       bit of peak, marginal number of mallocs saved), than 32 or 48.
    */
#else
    CWAL_SIZE_T_BITS/*MaxRecycledStringLen*/,
#endif
    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
#if 0
    /* Portability: stick with 30-bit max for the sake of platforms
       where unsigned long is too small for the 64-bit bitmask. */
#  define CWAL_STRLEN_MASK 0x3FFFFFFFU
#  define CWAL_STR_XMASK 0x80000000U
#  define CWAL_STR_ZMASK 0x40000000U
#else
    /* 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
#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_size_t)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 (void*) 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 CWAL_INT_DOUBLE_SAME_SIZE
              ? 0 /* we can re-use the integer bin here */
              : 1;
      case CWAL_TYPE_XSTRING:
      case CWAL_TYPE_ZSTRING:
      case CWAL_TYPE_STRING:
              /* we only handle x/z-strings via e->reList,
                 and STRING via e->reString.  */
              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=%u",
            v->vtab->typeName,
            (void*)v,
            v->scope ? (unsigned)v->scope->level : 0U,
            (void*)v->scope,
            (unsigned)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=%u", (unsigned)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) and the value will be reprobated if it reaches 0. 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;
}

int cwal_kvp_flags_set( cwal_kvp * kvp, int flags ){
    int const rc = kvp->flags;
    kvp->flags = (uint16_t)(0xFFFF & flags);
    return rc;
}


/** @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 ) return NULL;
    else{
        cwal_size_t i;
        cwal_kvp * left;
        char const * cKey;
        cwal_size_t kLen;
        int cmp;
        for( i = 0, left = 0; kvp;
             left = kvp, kvp = kvp->right, ++i ){
            assert( kvp->key );
            assert(kvp->right != kvp);
            cKey = cwal_value_get_cstr(kvp->key, &kLen);
            if(!cKey || kLen != keyLen || *cKey != *key
               /* reminder: whether *key is legal when keyLen==0
                  depends on the source of key! */
               ) continue;
            else if(0 == (cmp = memcmp(key,cKey,keyLen))){
                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;
    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... */
                  pPage->list[iKey] = NULL;
                  if(!pPage->next){
                      --pPage->entryCount;
                  }else{
                      /* Potential TODO: move empty pages
                         to the back of the list. */
                      for( pPage = pPage->next; pPage; 
                           prevPage = pPage, pPage = pPage->next ){
                          if(!(prevPage->list[iKey] = pPage->list[iKey])){
                              --prevPage->entryCount;
                              break;
                          }
                          else if(!pPage->next){
                              pPage->list[iKey] = 0;
                              --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 = 151
            /* We don't expect many weak refs, so we'll try
               a relatively 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.maxLength>0
       && e->reWeak.count < e->reWeak.maxLength){
       *r = cwal_weak_ref_empty;
        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 /* mediocre */
#elif 1
        379 /* reasonable */
#elif 0
        503
#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 1
    /*
      FNV-xx. These both well for our typical inputs.  Marginally more
      collisions when using the 32-bit seeds on 64-bit arch.
    */
#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;
/* end FNV-1/1a */
#else
/* several alternate implementations... */
    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 0
        /* 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.

           Longer-term tests show it not to perform as well as FNV
           for s2's unit 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 ){
#if 1
    /* Only disable this block when chasing bugs. */
    cwal_value * v = (cwal_value *)self;
    *CWAL_INT(v) = 0;
#endif
}
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

    if(e->prototypes && (s == CWAL_VALPART(e->prototypes)->scope)){
        /* cwal_value_unhand(CWAL_VALPART(e->prototypes)); */
        e->prototypes = 0;
    }

    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);
        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 special-case recyclers... */
            /* Clean up e->reList... */
            int i = 0;
            int n = (int)(sizeof(e->reList.lists)/
                          sizeof(e->reList.lists[0]));
            e->reList.cursor = -1;
            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));

            /* Clean up e->reBuf... */
            n = (int)(sizeof(e->reBuf.buffers)/
                      sizeof(e->reBuf.buffers[0]));
            e->reBuf.cursor = -1;
            assert(!e->current);
            for( i = 0; i <= n; ++i ){
                cwal_buffer * buf = &e->reBuf.buffers[i];
                if(buf->mem) cwal_buffer_reserve(e, buf, 0);
            }
            memset(&e->reBuf.buffers, 0, sizeof(e->reBuf.buffers));
        }

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

/**
   Lazily allocatesa and initializes e->prototypes, if it is not 0,
   then returns it. Returns 0 only on OOM errors during initial
   allocation/intialization.
*/
static cwal_array * cwal_engine_prototypes(cwal_engine * e){
    if(!e->prototypes && (e->prototypes = cwal_new_array(e)) ){
        if(cwal_array_reserve(e->prototypes, (cwal_size_t)CWAL_TYPE_end-1)){
            cwal_value_unref(CWAL_VALPART(e->prototypes));
            e->prototypes = 0;
        }else{
            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);
        }
    }
    return e->prototypes;
}

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

    /* In the off chance that cwal_engine::reList/reBuf initializers
       are not completely explicitly initialized... */
    memset(&e->reList.lists, 0, sizeof(e->reList.lists));
    memset(&e->reBuf.buffers, 0, sizeof(e->reBuf.buffers));
    { /* 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

    if(! cwal_engine_prototypes(e) ){
        rc = CWAL_RC_OOM;
    }else 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;
#if 0
    /* Not true with mixed-type bins */
    assert(re->id == v->vtab->typeID);
#endif
    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;
                }
                *v = def /* needed for types which share a recycling pool */;
#if 0
                /* Not true for types pooled together */
                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);
                }
#endif
            }
        }
    }
    ++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 ?
        (args->list.count ? (cwal_value **)args->list.list : 0)
        : 0;
    int const argc = argv ? (int)args->list.count : 0;
    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.

               [much later...] it's (almost) NEVER allowed/safe to
               unref a value we have not explicitely referenced
               (exception: or just created and does not yet have a
               ref).
            */
        }
        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 ){
    /* deprecated - this is a less efficient functional dupe
       of cwal_args_state(). */
    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 * cwal_buffer_to_zstring_value(cwal_engine * e, cwal_buffer * b){
    return cwal_string_value(cwal_buffer_to_zstring(e,b));
}
    
/**
   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{
            cwal_value_ref(v);
            cwal_array_set(ar, pos, NULL);
            cwal_value_unhand(v);
            assert(v->scope && "Already dead?");
        }
    }
    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 V_IS_BUILTIN(v) ? 0 : 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;
}

/**
   Internal helper for recycling buffer memory. dest must be a new,
   clean buffer with no memory (that might get assert()ed).  If the
   recyling list has an entry which can serve at least forAllocSize
   bytes, that entry's memory is transfered into dest. If no entry is
   capable of holding it, dest->mem will still be 0 after
   returning. There are no error conditions except for precondition
   violations (assertions).

*/
static void cwal_buffer_steal_mem( cwal_engine * e, cwal_buffer * dest,
                                   cwal_size_t forAllocSize){
#if 0
    /*Enable this section to effectively disable buffer->mem recycling
      for memory cost/savings comparisons. */
    return;
#else
    assert(!dest->mem);
    assert(!dest->used);
    if(dest->mem || e->reBuf.cursor<0) return;
    else{
        int i = e->reBuf.cursor;
        /* Potential TODO: find the closest-(but >=)-fit entry */
        for( ; i < (int)(sizeof(e->reBuf.buffers)/sizeof(e->reBuf.buffers[0]));
             ++i ){
            cwal_buffer * br = &e->reBuf.buffers[i];
            if(br->mem
               /* Try an approximate fit... */
               && br->capacity>=forAllocSize
#if 1
               /* This heuristic is very basic, of course. */
               && (br->capacity <= 64 * CWAL_SIZE_T_BITS /* 1k, 2k, 4k */
                   || br->capacity<= 2 * forAllocSize
                   )
#endif
               ){
                *dest = *br;
                /* MARKER(("Re-using buffer memory (%"CWAL_SIZE_T_PFMT") from slot #%d\n", br->capacity, e->reBuf.cursor)); */
                if(e->reBuf.cursor != i){
                    /* Move the final buffer in the list to this slot,
                       so that our list is always contiguous. */
                    *br = e->reBuf.buffers[e->reBuf.cursor];
                    e->reBuf.buffers[e->reBuf.cursor] = cwal_buffer_empty;
                }else{
                    *br = cwal_buffer_empty;
                }
                --e->reBuf.cursor;
                break;
            }
        }
    }
#endif
}


/**
   Internal helper for recycling array list memory. ar must be a new,
   clean array with no memory (that might get assert()ed).  If the
   recyling list has an entry then that entry's memory is transfered
   into ar. If no entry is capable of holding it, ar is left
   unmolested. There are no error conditions except for precondition
   violations (assertions).
*/
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)); */
        /* memset(ar->list.list, 0, sizeof(void*)*ar->list.alloced); */
        --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,
		(ndx<CwalConsts.InitialArrayLength)
		? CwalConsts.InitialArrayLength
		: ndx+1 );
        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 1
            if(old) cwal_value_unref2(e, old);
#else
            if(old) cwal_unref_from(s, old);
#endif

            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){
#if 0
            /*
              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));
            }
#else
            if(rv) cwal_value_unhand(v);
            else cwal_value_unref(v);
#endif
        }
        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 && ((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
           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.
            */

/*
  Experiment - i don't think (much later) we need unref-from except
  during cleanup. In the s2 unit test suite, this makes no difference
  whatsoever.
*/
#define UNSET_FROM_LIST_SCOPE 0
            if(0==i) { /* KEY part... */
                cwal_value_ref2( e, vv );
                if(kvp->key){
#if UNSET_FROM_LIST_SCOPE
                    /* "original" (known working) impl */
                    cwal_unref_from( listOwner->scope,
                                     kvp->key );
#else
                    cwal_value_unref2( e, kvp->key );
#endif
                }
                kvp->key = vv;
            }else{ /* VALUE part... */
                cwal_value_ref2( e, vv );
                if(kvp->value){
#if UNSET_FROM_LIST_SCOPE
                    cwal_unref_from( listOwner->scope,
                                     kvp->value );
#else
                    cwal_value_unref2( e, kvp->value );
#endif
                }
                kvp->value = vv;
                break;
            }
        }
#undef UNSET_FROM_LIST_SCOPE
        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_with_flags( cwal_value * c, cwal_value * key, cwal_value * v,
                                uint16_t flags ) {
    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, flags );
    }
}


int cwal_prop_set_v( cwal_value * c, cwal_value * key, cwal_value * v ) {
    return cwal_prop_set_v_with_flags( c, 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_with_flags( cwal_value * c,
                              char const * key, cwal_size_t keyLen, cwal_value * v,
                              uint16_t flags ) {
    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_with_flags(c, vkey, v, flags);
            if(rc) cwal_value_unref2(e, vkey);
            return rc;
        }
    }
}


int cwal_prop_set( cwal_value * c,
                   char const * key, cwal_size_t keyLen, cwal_value * v ) {
    return cwal_prop_set_with_flags( c, key, keyLen, v, CWAL_VAR_F_PRESERVE );
}

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
    
char cwal_props_has_any( cwal_value const * c ){
    cwal_obase const * b = CWAL_VOBASE(c);
    return b && b->kvp ? 1 : 0;
}

cwal_size_t cwal_props_count( cwal_value const * c ){
    cwal_obase const * b = CWAL_VOBASE(c);
    if(!b) return 0;
    else{
        cwal_size_t rc = 0;
        cwal_kvp const * kvp = b->kvp;
        for( ; kvp ; ++rc, kvp = kvp->right ){}
        return rc;
    }
}


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_VOBASE(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 );
        assert(CWAL_F_IS_VISITING & b->flags);
        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 );
        assert(!(CWAL_F_IS_VISITING & b->flags));
        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. Doh, no we don't: clients can
   simply check cwal_exception_get().
*/
static int cwal_value_list_sort_impl( cwal_engine * e,
                                      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 ? rc : (e->exception ? CWAL_RC_EXCEPTION: 0);
    }

    LIST_SWAP(li,pivot,left);
    pivot = left;
    ++left;
    while( !e->exception && 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 );
        if(e->exception) return CWAL_RC_EXCEPTION;
#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;
                }
            }
        }
    }
    if(!e->exception){
        LIST_SWAP(li,pivot,right);
        pivot = right;
        /*
          TODO use goto instead of recursion.
        */
        if(pivot>lhold){
            rc = cwal_value_list_sort_impl(e, li, lhold, pivot, cmp, state);
        }
        if(rhold>(pivot+1)){
            rc = cwal_value_list_sort_impl(e, li, pivot+1, rhold, cmp, state);
        }
    }
    return rc ? rc : (e->exception ? CWAL_RC_EXCEPTION: 0);
#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_engine * e;
        assert(CWAL_VALPART(ar)->scope);
        e = CWAL_VALPART(ar)->scope->e;
        cwal_value_list_sort_impl( e, &ar->list, 0, ar->list.count-1, cmp, state );
        return e->exception ? CWAL_RC_EXCEPTION : 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)
      /* Portability problem with 64-bit CWAL_SIZE_T_BITS on
         32-bit platforms: format specifier not portable. */
      ;
  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_hash_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_hash_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;
}

cwal_size_t cwal_hash_size( cwal_hash * h ){
    return h ? h->hashSize : 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;
        n->rescope_children = 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(XSTRING/* also Z-strings*/);
        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_VALPART(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_DOUBLE:{
          cwal_int_t const l = *CWAL_INT(lhs);
          cwal_double_t const r = cwal_value_get_double(rhs);
          return l==r ? 0 : (l<r ? -1 : 1);
      }
      case CWAL_TYPE_BOOL:
      case CWAL_TYPE_INTEGER:{
          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:{
          cwal_double_t const l = *CWAL_DBL(lhs);
          cwal_int_t const r = cwal_value_get_integer(rhs);
          return l==r ? 0 : (l<r ? -1 : 1);
      }
      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_fill_from_filename2( cwal_engine * e, cwal_buffer * dest, char const * filename,
                                     cwal_size_t nameLen){
    enum { BufSize = 2048 };
    if(!e || !dest || !filename || !*filename) return CWAL_RC_MISUSE;
    else if(!nameLen || nameLen>=(cwal_size_t)BufSize) return CWAL_RC_RANGE;
    else{
        int rc;
        FILE * src;
        char nameBuf[BufSize] = {0};
        memcpy( nameBuf, filename, (size_t)nameLen );
        nameBuf[nameLen] = 0;
        src = (1==nameLen && '-'==nameBuf[0] && !nameBuf[1])
            ? stdin : fopen(nameBuf,"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 ){
        if(buf->mem){
            /* Recycle or free it. */
            if(e->current /* i.e. not during shutdown */){
                if(e->reBuf.cursor <
                   (int)(sizeof(e->reBuf.buffers)/sizeof(e->reBuf.buffers[0]))-1
                   ){
                    /* Move the buffer memory into the recycler. */
                    /*
                      TODO: add options to set a peak total usage here,
                      and/or a per-buffer limit, over which we won't
                      recycle.
                    */
                    assert(e->reBuf.cursor>=-1);
                    buf->used = 0;
                    /* could argubly cwal_buffer_reset(buf) and/or memset buf->mem */
                    e->reBuf.buffers[++e->reBuf.cursor] = *buf;
                    *buf = cwal_buffer_empty;
                    /* MARKER(("Giving back buffer memory to slot #%d of %d\n", e->reBuf.cursor, listCount)); */
                    return 0;
                }
                /* else fall through */
            }
            cwal_free(e, buf->mem);
        }
        *buf = cwal_buffer_empty;
        return 0;
    }
    else if( buf->capacity >= n ){
        return 0;
    }
    else if(!buf->mem &&
            (cwal_buffer_steal_mem(e, buf, n), buf->mem)){
        assert(buf->capacity >= n);
        assert(!buf->used);
        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_engine_adjust_client_mem( cwal_engine * e, cwal_int_t amount ){
    assert(e);
    if(!amount) return;
    else if(amount<0){
        cwal_size_t const x = (cwal_size_t)(-amount);
        if(x >= e->metrics.clientMemCurrent) e->metrics.clientMemCurrent = 0;
        else e->metrics.clientMemCurrent -= x;
    }else{
        e->metrics.clientMemCurrent += amount;
        e->metrics.clientMemTotal += amount;
    }
}

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*/
    CWAL_INT_DOUBLE_SAME_SIZE
    ? "Shares recycling bin with integers in this build."
    : 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*/ "Not including (opaque/unknown) native memory", 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*/ "Key/value pairs (obj. properties/hash entries)", sizeof(cwal_kvp)},
    {/*CWAL_TYPE_WEAK_REF*/ 0, sizeof(cwal_weak_ref)},
    {/*CWAL_TYPE_XSTRING*/ "[3] Not incl. external string bytes", sizeof(cwal_value)+sizeof(cwal_string)+sizeof(char **)},
    {/*CWAL_TYPE_ZSTRING*/ "[3] 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.bytes[i] && !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],
                     e->metrics.requested[i]
                     ? (double)e->metrics.allocated[i]/e->metrics.requested[i]*100.0
                     : 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 alloc count does not account for "
                     "cwal_buffer::mem (re)allocations, but the total size "
                     "does. A request/allocation count of 0 and memory >0 "
                     "means that only non-Value buffers allocated memory.\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_outputf(e, "\n[3] = X-/Z-strings use the same recycling pool, "
                     "so it is possible to see an allocation count of 0 for "
                     "one or the other type.\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->metrics.clientMemTotal){
        cwal_outputf(e,"\nClient-reported memory: currently %u of %u total reported bytes\n",
                     (unsigned)e->metrics.clientMemCurrent,
                     (unsigned)e->metrics.clientMemTotal);
        grandTotal += e->metrics.clientMemTotal;
    }
    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=%u",
                          x, v->vtab->typeName,
                          (void const *)v, (int)v->scope->level,
                          (unsigned)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=%u bytes=[", (unsigned)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) return CWAL_RC_MISUSE;
    else if(!cwal_engine_prototypes(e)) return CWAL_RC_OOM;
    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){
        return CWAL_RC_TYPE;
    }else if(!child || (child == chPro)){
        return CWAL_RC_MISUSE;
    }
#if 1
    if( prototype == child->prototype ){
        return 0;
    }
#endif
    if(prototype){
        /* 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;
        }
        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 0
    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);
    }
#else
    cwal_scope_clean(e, &s2) /* move specially-propagating values */;
#endif
    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;
    }
#if 0
    cwal_scope_clean(e, &s2) /* not strictly necessary - s2 must
                                be empty by now or our universe's
                                physics are all wrong. */;
#endif
#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( 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 ) {
        if(CWAL_FMT_F_ALT_FORM & fi->flags){
            escarg = "NULL";
            slen = 4;
        }else{
            escarg = "(NULL)";
            slen = 6;
        }
    }
    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;
const cwal_json_parse_info cwal_json_parse_info_empty =
    cwal_json_parse_info_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:
          if(!fmt->functionsAsObjects){
              rc = CWAL_RC_TYPE;
              break;
          }
      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+1 < li->count){
                    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){
            if( doIndent ){
                rc = cwal_json_output_indent( f, state, fmt->indent, --level );
            }
            if(!rc) rc = f(state, "]", 1);
        }
        end:
        cwal_value_set_visiting( src, 0 );
        return 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:
                  case CWAL_TYPE_DOUBLE:
                    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 rc ? rc : f(state, "}", 1);
    }
}



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

#if CWAL_ENABLE_JSON_PARSER
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*/
};
#endif
/* CWAL_ENABLE_JSON_PARSER */

#if CWAL_ENABLE_JSON_PARSER
/**
   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;
    }
}
#endif
/* CWAL_ENABLE_JSON_PARSER */

#if CWAL_ENABLE_JSON_PARSER
/** @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;
}
#endif
/* CWAL_ENABLE_JSON_PARSER */


#if CWAL_ENABLE_JSON_PARSER
/** @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;
    }
}
#endif
/* CWAL_ENABLE_JSON_PARSER */

#if CWAL_ENABLE_JSON_PARSER
/** @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;
    }
}
#endif
/* CWAL_ENABLE_JSON_PARSER */

#if CWAL_ENABLE_JSON_PARSER
/**
   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;
}
#endif
/* CWAL_ENABLE_JSON_PARSER */

int cwal_json_parse( cwal_engine * e, cwal_input_f src,
                     void * state, cwal_value ** tgt,
                     cwal_json_parse_info * pInfo){
#if CWAL_ENABLE_JSON_PARSER
    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;
#else
    return CWAL_RC_UNSUPPORTED;
#endif
/* CWAL_ENABLE_JSON_PARSER */
}


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 CWAL_ENABLE_JSON_PARSER
    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;
        }
    }
#else
    return CWAL_RC_UNSUPPORTED;
#endif
/* CWAL_ENABLE_JSON_PARSER */
}

#if CWAL_ENABLE_JSON_PARSER
/** 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;
    }
}
#endif
/* CWAL_ENABLE_JSON_PARSER */


int cwal_json_parse_cstr( cwal_engine * e, char const * src,
                          cwal_size_t len, cwal_value ** tgt,
                          cwal_json_parse_info * pInfo ){
#if CWAL_ENABLE_JSON_PARSER
    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 );
    }
#else
    return CWAL_RC_UNSUPPORTED;
#endif
/* CWAL_ENABLE_JSON_PARSER */
}

#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( char const * str, cwal_size_t len ){
    if( !str || !len ) return 0;
    else{
        char unsigned const * x = (char unsigned const *)str;
        char unsigned const * end = x + len;
        cwal_size_t rc = 0;
        /* profiling shows that cwal_utf8_read_char() is, by leaps and
           bounds, the most oft-called func in the whole s2 constellation.
           We need a faster multi-byte char skipping routine.
        */
#if 1
        /* Derived from:
           http://www.daemonology.net/blog/2008-06-05-faster-utf8-strlen.html
        */
        for( ; x < end; ++x, ++rc ){
            if(*x>127U){
                switch(0xF0 & *x) {
                  case 0xF0: /* length 4 */
                      x += 3;
                      break;
                  case 0xE0: /* length 3 */
                      x+= 2;
                      break;
                  default: /* length 2 */
                      x += 1;
                      break;
                }
            }
        }
#else
        for( ; (pos < end) && cwal_utf8_read_char(pos, end, &pos);
             ++rc )
        {}
#endif
        return rc;
    }
}


cwal_size_t cwal_string_length_utf8( cwal_string const * str ){
    return str
        ? cwal_cstr_length_utf8( 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 s2.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

#include <assert.h>
#include <stdlib.h>
#include <string.h> /* memcmp() */
#include <stdio.h> /* FILE, fprintf() */

#if defined(S2_OS_UNIX)
#define S2_USE_SIGNALS 1
#endif

#if S2_USE_SIGNALS
#include <signal.h> /* sigaction(), if our feature macros are set right */
#endif

#if 1
#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp
#else
#define MARKER(pfexp) (void)0
#endif

const s2_stoken s2_stoken_empty = s2_stoken_empty_m;
#if 0
const s2_op s2_op_empty = s2_op_empty_m;
#endif
const s2_engine s2_engine_empty = s2_engine_empty_m;
const s2_stoken_stack s2_stoken_stack_empty = s2_stoken_stack_empty_m;
const s2_estack s2_estack_empty = s2_estack_empty_m;
const s2_error s2_error_empty = s2_error_empty_m;
const s2_sweep_guard s2_sweep_guard_empty = s2_sweep_guard_empty_m;
const s2_strace_entry s2_strace_entry_empty = s2_strace_entry_empty_m;
const s2_scope s2_scope_empty = s2_scope_empty_m;
#if S2_USE_SIGNALS
static s2_engine * s2Interruptable = 0;
#endif

s2_engine * s2_engine_alloc( cwal_engine * e ){
  s2_engine * rc = (s2_engine*)cwal_malloc(e, sizeof(s2_engine));
  if(rc){
    *rc = s2_engine_empty;
    rc->allocStamp = e;
    rc->e = e /* need this for dealloc */;
  }
  return rc;
}

void s2_estack_clear( s2_engine * se, s2_estack * st, char allowRecycle ){
  s2_stoken_stack_clear( se, &st->vals, allowRecycle );
  s2_stoken_stack_clear( se, &st->ops, allowRecycle );
}

void s2_estack_swap( s2_estack * lhs, s2_estack * rhs ){
  s2_estack const tmp = *lhs;
  *lhs = *rhs;
  *rhs = tmp;
}

void s2_engine_stack_swap( s2_engine * se, s2_estack * st ){
  s2_estack const tmp = se->st;
  se->st = *st;
  *st = tmp;
}

void s2_engine_reset_stack( s2_engine * se ){
  s2_estack_clear(se, &se->st, 1);
}


void s2_engine_free_recycled_funcs( s2_engine * se ) /* in s2_eval.c */;
void s2_modules_close( s2_engine * ) /* in s2_mod.c */;

void s2_engine_finalize( s2_engine * se ){
  cwal_engine * e;
  void const * allocStamp;
  if(!se) return;
#if S2_USE_SIGNALS
  if(s2Interruptable==se) s2Interruptable = 0;
#endif
  allocStamp = se->allocStamp;
  e = se->e;
  if(!e){
    assert(!se->st.vals.top);
    assert(!se->recycler.stok.top);
    assert(!se->st.ops.top);
    assert(!se->buffer.mem);
    assert(!se->err.msg.mem);
    assert(!se->stash);
    assert(!se->dotOpLhs);
    *se = s2_engine_empty;
  }else{
    /* We need to ensure that se->e is "empty" before we free se, in
       case any values it owns are laying around.
    */
    if(se->stash){
      cwal_value_unref( se->stash );
      se->stash = 0;
    }
    if(se->funcStash){
      cwal_value_unref( cwal_hash_value(se->funcStash) );
      se->funcStash = 0;
    }
    cwal_exception_set( e, 0 );
    cwal_propagating_set( e, 0 );
    while(se->ob.count) s2_ob_pop(se);
    cwal_list_reserve(se->e, &se->ob, 0);

    while( se->currentScope ){
      /* reminder: se's top scope is one level down
         from cwal's top scope. */
      s2_scope_pop(se);
    }
    s2_modules_close(se)
      /* must come after all scopes are gone */
      ;
#if 0
    assert(se->e->current /* cwal's top scope */);
    while( !cwal_scope_pop(se->e) ){}
#else
    assert(!se->e->current);
#endif

    /* Reminder: the following cleanup is only legal
       as long as we don't use/deref any Values... */
    s2_engine_free_recycled_funcs(se);
    s2_engine_reset_stack(se);
    s2_stoken_stack_clear( se, &se->recycler.stok, 0 );
    cwal_buffer_clear(e, &se->buffer);
    s2_error_clear(se, &se->err);
    
    *se = s2_engine_empty;
    if(allocStamp==e) cwal_free(e, se);
    cwal_engine_destroy( e );
  }
}


/* In s2_eval.c */
int s2_callback_hook_pre(cwal_callback_args const * argv, void * state);
/* In s2_eval.c */
int s2_callback_hook_post(cwal_callback_args const * argv, void * state, int fRc, cwal_value * rv);
/* cwal_callback_f() pre- and post-call() hooks. This is where we install
   'argv' and 'this'.
*/
static const cwal_callback_hook cwal_callback_hook_s2 = {
    NULL /*state*/,
    s2_callback_hook_pre,
    s2_callback_hook_post
};

int s2_engine_init( s2_engine * se, cwal_engine * e ){
  void const * allocStamp;
  int rc = 0;
  if(!se || !e) return CWAL_RC_MISUSE;
  allocStamp = se->allocStamp;
  assert(!se->e || (se->e == e));
  *se = s2_engine_empty;
  se->allocStamp = allocStamp;
  se->e = e;

  {
    cwal_callback_hook hook = cwal_callback_hook_s2;
    hook.state = se;
    cwal_callback_hook_set(e, &hook);
  }

  /* Reminder to self: right about here it would be really cool
     to be able to pop cwal's top scope and use s2_engine::topScope
     in its place, but e's always been partially initialized, and doing
     so would destroy those values unless we do some really non-kosher
     things with the scope pointers. It would work, but be butt ugly
     and shameful. Instead we'll just push another scope level
     and live with it.

     That said... s2sh doesn't create any values before this
     is called, so let's try it...
  */
  assert(se->e->current);
  assert(se->e->current==&se->e->topScope);
#if 1
  if( (rc = cwal_scope_pop(se->e)) ) return rc;
  assert(!se->e->current);
  if( (rc = s2_scope_push(se, &se->topScope) ) ) return rc;
  assert(se->e->current);
  assert(se->e->current==&se->topScope.scope);
  assert(se->e==se->topScope.scope.e);
#else
  if( (rc = s2_scope_push(se, &se->topScope) ) ) return rc;
#endif

  if( (rc = cwal_engine_client_state_set( e, se,
                                          &s2_engine_empty, 0 )) ){
    /*
      Required ^^^^ by the s2 function callback hook mechanism.
    */
    return rc;
  }
  assert(!rc && "Can only fail if client state was already set!");


#if 0
  /*
    Delay stash alloc until needed.  Non-s2sh apps might not install
    the script APIs which use the the stash. Unlikely, but possible.
  */
  se->stash = cwal_new_hash_value(e, 31);
  if(se->stash) cwal_value_ref( se->stash );
  else rc = CWAL_RC_OOM;
#endif
  return rc;
}

void s2_engine_sweep( s2_engine * se ){
  assert(se && se->e);
  /*MARKER(("SWEEP RUN #%d? sweepTick=%d, s-guard=%d, v-guard=%d\n",
          se->sweepTotal, se->sweepTick,
          se->sguard->sweep, se->sguard->vacuum));*/
  if(se->sguard->sweep>0 || se->sweepInterval<=0) return;
  else if(++se->sweepTick != se->sweepInterval) return;
  else{
    int useBroom = 1 /* 1 == sweep,
                        0 == vacuum,
                        -1 == sweep2 */;
    int valsSwept = 0;
    ++se->sweepTotal;
    se->sweepTick = 0;
    /* See if we can/should use sweep2 or vacuum... */
    if(se->vacuumInterval>0
       && !se->sguard->vacuum
       && (0 == (se->sweepTotal % se->vacuumInterval))
       ){
      useBroom = 0;
    }
    /* MARKER(("SWEEP RUN #%d mode=%d\n", se->sweepTotal, useBroom)); */
    switch(useBroom){
      case 1:
        valsSwept = (int)cwal_engine_sweep(se->e);
        break;
      case -1:
        assert(!"NO!");
        valsSwept = (int)cwal_engine_sweep2(se->e, 1);
        break;
      default:{
#ifndef NDEBUG
        int rc;
        assert(0==useBroom);
        rc = cwal_engine_vacuum(se->e, se->flags.traceSweeps
                                ? &valsSwept : 0
                                /* b/c this reporting costs */);
        assert(!rc);
#else
        cwal_engine_vacuum(se->e, se->flags.traceSweeps
                           ? &valsSwept : 0);
#endif
        break;
      }
    }
#if 1
    if(se->flags.traceSweeps && valsSwept){
      MARKER(("Swept up %d value(s) in %s mode\n", valsSwept,
              (useBroom==1) ? "sweep" : (0==useBroom?"vacuum":0)
              ));
    }
#endif
    return;
  }
}

void s2_engine_vacuum( s2_engine * se ){
  assert(se && se->e);
  if(!se->sguard->sweep){
    int vacCount = 0;
    cwal_engine_vacuum(se->e, &vacCount);
    assert(vacCount >= 0);
  }
}


void s2_stoken_stack_push( s2_stoken_stack * ts, s2_stoken * t ){
  assert(ts);
  assert(t);
  assert(!t->next);
  assert(t != ts->top);
  t->next = ts->top;
  ts->top = t;
  ++ts->size;
}

s2_stoken * s2_stoken_stack_pop( s2_stoken_stack * ts ){
  s2_stoken * t = 0;
  assert(ts);
  if(ts->size>0){
    t = ts->top;
    assert(t);
    ts->top = t->next;
    t->next = 0;
    --ts->size;
  }
  return t;
}

void s2_stoken_stack_clear( s2_engine * se, s2_stoken_stack * st, char allowRecycle ){
  s2_stoken * t;
  while( (t = s2_stoken_stack_pop(st)) ){
    s2_stoken_free(se, t, allowRecycle);
  }
}

static void s2_engine_trace_stack(s2_engine * se, s2_stoken const * t, char isPush){
  int const ttype = t->ttype;
  s2_op const * op = s2_ttype_op(ttype);
  assert(se->flags.traceStack);
  cwal_outputf(se->e, "s2_engine::traceStack: after %s ",
               isPush ? "push" : "pop");
  if(op){
    cwal_outputf(se->e, "op (%s) ", op->sym);
  }else{
    cwal_outputf(se->e, "token %s (typename=%s) ",
                 s2_ttype_cstr(ttype),
                 t->value ? cwal_value_type_name(t->value) : "<NULL>");
  }
  cwal_outputf(se->e, "tokens=%d, ops=%d\n",
               se->st.vals.size, se->st.ops.size);

}

#define s2__ttrace(isPush, TOK) if(se->flags.traceStack) s2_engine_trace_stack(se, TOK, isPush)

void s2_engine_push( s2_engine * se, s2_stoken * t ){
  s2_stoken_stack_push( s2_stoken_op(t) ? &se->st.ops : &se->st.vals, t );
  s2__ttrace(1, t);
}

void s2_engine_push_token( s2_engine * se, s2_stoken * t ){
  s2_stoken_stack_push( &se->st.vals, t );
  s2__ttrace(1, t);
}

void s2_engine_push_op( s2_engine * se, s2_stoken * t ){
  s2_stoken_stack_push( &se->st.ops, t );
  s2__ttrace(1, t);
}

s2_stoken * s2_engine_push_ttype( s2_engine * se, int i ){
  s2_stoken * t = s2_stoken_alloc2( se, i, 0 );
  if(t) s2_engine_push( se, t );
  return t;
}

s2_stoken * s2_engine_push_val( s2_engine * se, cwal_value * v ){
  if(!se || !v) return 0;
  else{
    s2_stoken * t = s2_stoken_alloc2( se, S2_T_Value, v );
    if(t) s2_engine_push_token( se, t );
    return t;
  }
}

s2_stoken * s2_engine_push_tv( s2_engine * se, int ttype, cwal_value * v ){
  s2_stoken * t = s2_stoken_alloc2( se, ttype, v );
  if(t) s2_engine_push_token( se, t );
  return t;
}


s2_stoken * s2_engine_push_int( s2_engine * se, cwal_int_t i ){
  s2_stoken * rc = 0;
  cwal_value * v = cwal_new_integer(se->e, i);
  if(!v) return 0;
  else{
    if(!(rc = s2_engine_push_val(se, v))){
      cwal_value_unref(v);
    }
  }
  return rc;
}


s2_stoken * s2_engine_peek_token( s2_engine * se ){
  return se->st.vals.top;
}

cwal_value * s2_engine_peek_value( s2_engine * se ){
  return se->st.vals.top ? se->st.vals.top->value : 0;
}

s2_stoken * s2_engine_peek_op( s2_engine * se ){
  return se->st.ops.top;
}

static s2_stoken * s2_engine_pop_token_impl( s2_engine * se, 
                                             s2_stoken_stack * ts,
                                             char returnItem ){
  s2_stoken * rc = s2_stoken_stack_pop(ts);
  if(rc){
    s2__ttrace(0, rc);
    if(!returnItem){
      s2_stoken_free( se, rc, 1 );
      rc = 0;
    }
  }
  return rc;
}

#undef s2__ttrace

s2_stoken * s2_engine_pop_token( s2_engine * se, char returnItem ){
  return s2_engine_pop_token_impl(se, &se->st.vals, returnItem);
}

s2_stoken * s2_engine_pop_op( s2_engine * se, char returnItem ){
  return s2_engine_pop_token_impl(se, &se->st.ops, returnItem);
}

cwal_value * s2_engine_pop_value( s2_engine * se ){
  cwal_value * v = 0;
  s2_stoken * t = s2_engine_pop_token(se, 1);
  if(t){
    v = t->value;
    t->value = 0;
    s2_stoken_free(se, t, 1);
  }
  return v;
}

#if 0
void s2_engine_stack_replace( s2_engine * se, s2_estack const * src,
                              s2_estack * priorStacks ){
  if(priorStacks) *priorStacks = se->st;
  else s2_engine_reset_stack(se);
  se->st = src ? *src : s2_estack_empty;
}
#endif

s2_stoken * s2_stoken_alloc( s2_engine * se ){
  s2_stoken * s;
  assert(se);
  assert(se->e);
  ++se->metrics.tokenRequests;
  s = s2_stoken_stack_pop(&se->recycler.stok);
  if(!s){
    s = (s2_stoken*)cwal_malloc(se->e, sizeof(s2_stoken));
    if(s){
      ++se->metrics.tokenAllocs;
      cwal_engine_adjust_client_mem(se->e,
                                    (cwal_int_t)sizeof(s2_stoken));
      assert(se->e->metrics.clientMemCurrent>0);
    }
  }
  if(s){
    *s = s2_stoken_empty;
    if(++se->metrics.liveTokenCount > se->metrics.peakLiveTokenCount){
      se->metrics.peakLiveTokenCount = se->metrics.liveTokenCount;
    }
  }
  return s;
}

s2_stoken * s2_stoken_alloc2( s2_engine * se, int type, cwal_value * v ){
  s2_stoken * t = s2_stoken_alloc(se);
  if(t){
    t->ttype = type;
    t->value = v;
  }
  return t;
}

void s2_stoken_free( s2_engine * se, s2_stoken * t, char allowRecycle ){
  assert(se);
  assert(se->e);
  assert(t);
  assert(!t->next);
  --se->metrics.liveTokenCount;
  if(allowRecycle && (se->recycler.stok.size < se->recycler.maxSTokens)){
    s2_stoken_stack_push( &se->recycler.stok, t );
  }else{
    *t = s2_stoken_empty;
    assert(se->e->metrics.clientMemCurrent>0);
    cwal_engine_adjust_client_mem(se->e, -((cwal_int_t)sizeof(s2_stoken)));
    cwal_free( se->e, t );
  }
}

static int s2_process_op_impl( s2_engine * se, s2_op const * op,
                               char popOpStack ){
  int rc = 0;
  s2_stoken_stack * st = &se->st.vals;
  int const oldStackSize = st->size;
  int popArgCount = 0;
  cwal_value * rv = 0;
  s2_stoken * topOp = 0;
  assert(op);
  se->opErrPos = 0;
  if(se->flags.traceStack){
    MARKER(("running operator %s (#%d) arity=%d assoc=%d prec=%d\n",
            op->sym, op->id, op->arity, op->assoc, op->prec));
  }
  /**
    pop op (if needed) first so that we can guaranty operators that
    they are not the top op on the stack when they are called. Useful
    for '=', which will want to know if the LHS is a dot operator or
    not.
  */
  if(popOpStack){
    topOp = s2_engine_pop_op(se, rv ? 1 : 0);
  }

  if(op->arity>=0){
#if 1
    assert((st->size >= op->arity)
           || (op->assoc>0 && op->arity==1 /* unary +, -, ~ */));
#endif
    if(st->size < op->arity){
      rc = s2_engine_err_set(se, CWAL_RC_RANGE,
                             "Not enough operands on the stack.");
    }else{
#if 0
      /* In principal we could move the skip-level checking out of the
         individual ops and into here, but we need more info: some ops
         set &rv, some don't, and we don't have that info here without
         calling the op, and that info changes how we handle the end
         of the process.
      */
      if(se->skipLevel>0){
        int i = 0;
        for( ; i < op->arity; ++i ){
          s2_engine_pop_token(se, 1);
        }
        MARKER(("Popped %d arg(s) for %d-ary op '%s'\n",i, op->arity, op->sym ));
      }else{
        rc = op->call(op, se, op->arity, &rv);
      }
#else
      rc = op->call(op, se, op->arity, &rv);
#endif
      popArgCount = op->arity;
    }
  }else{
    /* Variadic operator ... */
    s2_stoken * t = se->st.vals.top;
    int i = 0;
    char doBreak = 0;
    /* Count how the arguments by looking for
       a S2_T_MarkVariadicStart token. */
    for( ; t && !doBreak && (i <st->size); t = t->next ){
      switch(t->ttype){
        case S2_T_MarkVariadicStart:
          doBreak = 1;
          break;
        default:
          ++i;
          break;
      }
    }
    assert(doBreak);
    if(!doBreak){
      rc = s2_engine_err_set(se, CWAL_RC_MISUSE,
                             "Missing S2_T_MarkVariadicStart!");
    }else{
      /* MARKER(("variadic argc=%d\n", i)); */
      rc = op->call(op, se, i, &rv);
      assert( st->size == (oldStackSize - i) );
      if(1){
        s2_stoken * variadicCheck = s2_engine_peek_token(se);
        assert(variadicCheck);
        assert(S2_T_MarkVariadicStart == variadicCheck->ttype);
      }
      s2_engine_pop_token( se, 0 ) /* S2_T_MarkVariadicStart */;
      popArgCount = i + 1 /* variadic marker */;
    }
  }
  if(!rc){
    assert( st->size == (oldStackSize - popArgCount) );
    if(st->size != (oldStackSize - popArgCount)){
      rc = s2_engine_err_set(se, CWAL_RC_MISUSE,
                             "Unexpected stack size after "
                             "running operator '%s'\n",
                             op->sym);
      rc = CWAL_RC_MISUSE;
    }else if(rv){
      /* Push the result value... */
      s2_stoken * tResult = topOp ? topOp/*recycle it*/ : 0;
      topOp = 0;
      if(!tResult){
        tResult = s2_stoken_alloc(se);
        if(!tResult) rc = CWAL_RC_OOM;
      }else{
        *tResult = s2_stoken_empty;
      }
      if(tResult){
        tResult->ttype = rv ? S2_T_Value : S2_T_Undefined;
        tResult->value = rv ? rv : cwal_value_undefined();
        s2_engine_push_token(se, tResult);
      }
    }
  }
  if(topOp){
    if(rc) se->opErrPos = topOp->srcPos;
    s2_stoken_free(se, topOp, 1);
  }
  return rc;
}


int s2_process_op( s2_engine * se, s2_op const * op ){
  return s2_process_op_impl(se, op, 0);
}

int s2_process_top( s2_engine * se ){
  s2_stoken_stack * so = &se->st.ops;
  s2_op const * op = s2_stoken_op(so->top);
  char const * srcPos = so->top ? so->top->srcPos : 0;
  int rc;
  if(!op){
    rc = s2_engine_err_set(se, CWAL_RC_TYPE,
                           "Token type %s (#%d) is not an operator.",
                           so->top ? s2_ttype_cstr(so->top->ttype) : "<NULL>",
                           so->top ? so->top->ttype : S2_T_INVALID);
  }else if(op
           && op->arity>0
           && se->st.vals.size<op->arity){
    rc = s2_engine_err_set(se, CWAL_RC_RANGE,
                             "Value stack does not have enough values for "
                             "operator '%s'.", op->sym);
  }else{
    rc = s2_process_op_impl( se, op, 1 );
    if(rc && srcPos && !se->opErrPos) se->opErrPos = srcPos;
  }
  return rc;
}

int s2_process_op_type( s2_engine * se, int ttype ){
  s2_op const * op = s2_ttype_op(ttype);
  return op
    ? s2_process_op(se, op)
    : CWAL_RC_TYPE;
}

int s2_error_setv( s2_engine * se, s2_error * err, int code, char const * fmt, va_list args){
  int rc = 0;
  err->msg.used = 0;
  err->code = code;
  if(CWAL_RC_OOM != (err->code = code)){
    if(!fmt || !*fmt) cwal_buffer_reset(&err->msg);
    else rc = cwal_buffer_printfv(se->e, &err->msg, fmt, args);
  }else if(err->msg.capacity){
    /* make sure it's nul-terminated :/ */
    err->msg.mem[0] = 0;
  }
#if 0
  /* appears unnecessary at this point */
  if(!rc && se->currentScript){
    s2_ptoker const * script = se->currentScript;
    int line = 0, col = 0;
    cwal_size_t scriptNameLen = 0;
    char const * scriptName = s2_ptoker_name_first(script, &scriptNameLen);
    char const * errPos = s2_ptoker_err_pos_first(script);
    if(!errPos){
      errPos = script->token.begin
        ? script->token.begin
        : script->pbToken.begin;
    }
    s2_ptoker_count_lines( script, errPos, &line, &col);
    if(line>0){
      se->err.line = line;
      se->err.col = col;
    }
    if(scriptNameLen){
      cwal_buffer_reset( &se->err.script );
      rc = cwal_buffer_append( se->e, &se->err.script,
                               scriptName, scriptNameLen );
    }
  }
#endif
  return rc ? rc : code;
}

int s2_error_set( s2_engine * se, s2_error * err, int code, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = s2_error_setv(se, err, code, fmt, args);
  va_end(args);
  return rc;
}

void s2_error_reset( s2_error * err ){
  cwal_buffer_reset(&err->msg);
  cwal_buffer_reset(&err->script);
  err->code = err->line = err->col = 0;
}

void s2_error_clear( s2_engine * se, s2_error * err ){
  cwal_buffer_clear(se->e, &err->msg);
  cwal_buffer_clear(se->e, &err->script);
  *err = s2_error_empty;
}

int s2_error_get( s2_error const * err, char const ** msg, cwal_size_t * msgLen ){
  if(err->code){
    if(msg) *msg = err->msg.used ? (char const *)err->msg.mem : 0;
    if(msgLen) *msgLen = err->msg.used;
  }
  return err->code;
}


/* in s2_eval.c */
int s2_strace_generate( s2_engine * se, cwal_value ** rv );

int s2_add_script_props2( s2_engine * se,
                           cwal_value * ex,
                           char const * scriptName,
                           int line, int col){
  int rc = 0;
  if(scriptName && *scriptName){
    rc = cwal_prop_set(ex, "script", 6,
                       cwal_new_string_value(se->e,scriptName,0));
  }
  if(!rc && line>0){
    rc = cwal_prop_set(ex, "line", 4,
                       cwal_new_integer(se->e, line));
    if(!rc) rc = cwal_prop_set(ex, "column", 6,
                               cwal_new_integer(se->e, col));
  }
#if 1
  /* MARKER(("strace count=%u\n", se->strace.count)); */
  if(!rc && se->strace.count){
    cwal_value * stackTrace = 0;
    rc = s2_strace_generate(se, &stackTrace);
    if(!rc && stackTrace){
      /* s2_dump_val(stackTrace, "stackTrace"); */
      if((rc = cwal_prop_set(ex, "stackTrace", 10, stackTrace))){
        cwal_value_unref(stackTrace);
      }
    }
  }
#endif

  return rc;
}

int s2_add_script_props( s2_engine * se, cwal_value * ex, s2_ptoker const * script ){
  if(ex && !script) script = se->currentScript;
  if(!ex
     || !script
     || !cwal_props_can(ex)
     || cwal_prop_has(ex, 0, "line", 4)
     ) return 0;
  else{
    int line = 0, col = 0;
    cwal_size_t scriptNameLen = 0;
    char const * scriptName = s2_ptoker_name_first(script, &scriptNameLen);
    char const * errPos = s2_ptoker_err_pos_first(script);
    if(!errPos){
      errPos = script->token.begin
        ? script->token.begin
        : script->pbToken.begin;
    }
    s2_ptoker_count_lines( script, errPos, &line, &col);
    if(scriptName || (line>0)){
      return s2_add_script_props2(se, ex, scriptName, line, col);
    }else{
      return 0;
    }
  }
}

int s2_exception_add_script_props( s2_engine * se, s2_ptoker const * script ){
  cwal_value * ex = cwal_exception_get(se->e);
  return ex ? s2_add_script_props(se, ex, script) : 0;
}

cwal_value * s2_error_exception( s2_engine * se,
                                 s2_error * err,
                                 char const * scriptName,
                                 int line, int col ){
  int rc = 0;
  cwal_value * msg;
  cwal_value * ex;
  if(!err) err = &se->err;
  if(line<=0){
    line = err->line;
    col = err->col;
  }
  if(err->msg.used){
    msg = cwal_string_value(cwal_buffer_to_zstring(se->e, &err->msg));
  }else{
    msg = cwal_string_value(cwal_new_stringf(se->e, "Error #%d (%s).",
                                             err->code,
                                             s2_rc_cstr(err->code)));
  }
  ex = msg
    ? cwal_new_exception_value(se->e, err->code, msg)
    : 0;
  if(!ex){
    cwal_value_unref(msg);
    rc = CWAL_RC_OOM;
  }else{
    if(!scriptName && err->script.used) scriptName = (char const *)err->script.mem;
    rc = s2_add_script_props2(se, ex, scriptName, line, col);
    if(rc) {
      cwal_value_unref(ex);
      ex = 0;
    }
  }
  s2_error_reset(err);
  assert(!err->msg.mem);
  return ex;
}

int s2_throw_err( s2_engine * se, s2_error * err,
                 char const * script,
                 int line, int col ){
  cwal_value * ex = s2_error_exception(se, err, script, line, col);
  if(!ex) return CWAL_RC_OOM;
  else{
    int rc;
    /* s2_dump_val(ex, "exception"); */
    rc = cwal_exception_set(se->e, ex);
    assert(CWAL_RC_EXCEPTION==rc);
    assert(1==cwal_value_refcount(ex));
    return rc;
  }
}

int s2_throw_value( s2_engine * se, s2_ptoker const * pr, int code, cwal_value *v ){
  int rc = 0, rc2 = 0;
  cwal_value * ex;
  assert(v);
  if(!pr) pr = se->currentScript;
  /* pr = pr ? s2_ptoker_top_parent( pr ) : 0; */
  ex = cwal_value_is_exception(v)
    ? v
    : cwal_new_exception_value(se->e, code ? code : CWAL_RC_EXCEPTION, v);
  if(!ex) rc2 = CWAL_RC_OOM;
  else{
    rc = cwal_exception_set(se->e, ex);
    rc2 = s2_add_script_props(se, ex, pr);
  }
  return rc2 ? rc2 : rc;
}


int s2_throw( s2_engine * se, int code, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = s2_error_setv( se, &se->err, code, fmt, args);
  va_end(args);
  return (rc==code) ? s2_throw_err(se, &se->err, 0, 0, 0) : rc;
}

int s2_cb_throw( cwal_callback_args const * args, int code, char const * fmt, ... ){
  int rc;
  va_list vargs;
  va_start(vargs,fmt);
  rc = cwal_exception_setfv(args->engine, code, fmt, vargs);
  va_end(vargs);
  return rc;
}


int s2_engine_err_has( s2_engine const * se ){
  return se->flags.interrupted
    ? se->flags.interrupted
    : (se->err.code
       ? se->err.code
       : (cwal_exception_get(se->e)
          ? CWAL_RC_EXCEPTION : 0))
    ;      
}

int s2_engine_err_setv( s2_engine * se, int code, char const * fmt, va_list vargs ){
  return s2_error_setv(se, &se->err, code, fmt, vargs);
}

int s2_engine_err_set( s2_engine * se, int code, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = s2_error_setv(se, &se->err, code, fmt, args);
  va_end(args);
  return rc;
}

void s2_engine_err_reset( s2_engine * se ){
  se->flags.interrupted = 0;
  s2_error_reset(&se->err);
}

void s2_engine_err_clear( s2_engine * se ){
  se->flags.interrupted = 0;
  s2_error_clear(se, &se->err);
}

int s2_engine_err_get( s2_engine const * se, char const ** msg, cwal_size_t * msgLen ){
  if(se->flags.interrupted && se->flags.interrupted!=se->err.code){
    if(msg) *msg = 0;
    if(msgLen) *msgLen = 0;
    return se->flags.interrupted;
  }else{
    return s2_error_get( &se->err, msg, msgLen );
  }
}



void s2_dump_value( cwal_value * v, char const * msg, char const * func, int line ){
  cwal_scope const * sc = cwal_value_scope(v);
  static cwal_json_output_opt jopt = cwal_json_output_opt_empty_m;
  static int once = 0;
  FILE * out = stdout;
  if(v){
    assert((sc || cwal_value_is_builtin(v))
           && "Seems like we've cleaned up too early.");
  }

  if(!once){
    once = 1;
    jopt.cyclesAsStrings = 1;
    jopt.functionsAsObjects = 0;
    jopt.indent = 2;
    jopt.addNewline = 1;
    jopt.indentSingleMemberValues = 0;
  }
  if(func && line>0){
    fprintf(out,"%s():%d: ", func, line);
  }
  fprintf(out,"%s%s%s@%p[scope=#%d@%p ref#=%d] "
          "==> ",
          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( v ? cwal_value_type_id(v) : -1 ){
    case -1:
      fwrite("<NULL>\n", 7, 1, out);
      break;
    case CWAL_TYPE_FUNCTION:
    case CWAL_TYPE_BUFFER:
    case CWAL_TYPE_NATIVE:
    case CWAL_TYPE_HASH:
      fprintf(out, "%s@%p\n",
              cwal_value_type_name(v), (void const*)v);
      break;
    case CWAL_TYPE_UNDEF:
      fwrite("undefined\n", 10, 1, out);
      break;
    default:
      cwal_json_output_FILE( v, out, &jopt );
      break;
  }
}

void s2_fatal( int code, char const * fmt, ... ){
  va_list args;
  cwal_printf_FILE(stderr, "FATAL ERROR: code=%d (%s)\n",
                   code, s2_rc_cstr(code));
  if(fmt && *fmt){
    va_start(args,fmt);
    cwal_printfv_FILE(stderr, fmt, args);
    va_end(args);
    fwrite("\n", 1, 1, stderr);
  }
  exit(EXIT_FAILURE);
}

int s2_var_decl_v( s2_engine * se, cwal_value * key,
                   cwal_value * v, uint16_t flags ){
  return cwal_var_decl_v(se->e, 0, key, v, flags);
}

int s2_var_decl( s2_engine * se, char const * key, cwal_size_t keyLen,
                 cwal_value * v, uint16_t flags ){
  return cwal_var_decl(se->e, 0, key, keyLen, v, flags);
}

int s2_stash_set( s2_engine * se, char const * key, cwal_value * v ){
    if(!key || !v) return CWAL_RC_MISUSE;
    else if(!se->stash){
      cwal_scope * topScope;
      /* assert(se->topScope.scope.parent); */
      topScope = &se->topScope.scope;
      /* = cwal_scope_top(cwal_scope_current_get(se->e)); */
      /* assert(topScope); */
      se->stash = cwal_new_hash_value(se->e, 31);
      if(!se->stash) return CWAL_RC_OOM;
      else{
        cwal_value_rescope(topScope, se->stash);
        cwal_value_ref(se->stash)
          /* Make sure it doesn't get swept up! */
          ;
        cwal_value_make_vacuum_proof(se->stash, 1)
          /* And not vacuumed, either. */
          ;
      }
    }
    return cwal_hash_insert( cwal_value_get_hash(se->stash),
                             key, 0, v, 1 );
}

cwal_value * s2_stash_get( s2_engine * se, char const * key ){
    if(!se || !key || !*key) return NULL;
    else return se->stash
        ? cwal_hash_search( cwal_value_get_hash(se->stash),
                            key, 0 )
        : 0;
}

/* cwal_value * s2_new_function(s2_engine * se, cwal_callback_f  */

cwal_value * s2_new_function2( s2_engine *se, cwal_callback_f callback){
  return cwal_new_function_value(se->e, callback, se, NULL, &s2_engine_empty);
}

s2_engine * s2_args_state( cwal_callback_args const * args ){
  return cwal_args_state( args, &s2_engine_empty );
}

/**
   Internal s2_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 * s2_get_v_proxy( s2_engine * se,
                                    cwal_value * self,
                                    cwal_value * key ){

  cwal_value * v = NULL;
  assert(se && self && key);
#if 0
  /* Why does this not work? */
  self = cwal_container_part(se->e, self);
  /* if(!cwal_props_can(self)) self = cwal_value_prototype_get(se->e, self); */
  v = self ? cwal_prop_get_v(self, key) : 0;
#elif 0
  /* Nor this? */
  if(!cwal_props_can(self)) self = cwal_value_prototype_get(se->e, self);
  v = self ? cwal_prop_get_v(self, key) : 0;
#elif 1
  while(!v && self){
    v = cwal_prop_get_v(self, key);
    self = v ? 0 : cwal_value_prototype_get(se->e, self);
    /* s2_dump_val(self,key); */
  }
#else
  for(; !v && self; self = cwal_value_prototype_get(se->e, self) ){
    if(cwal_props_can(self)) v = cwal_prop_get_v(self, key);
  }
#endif
  return v;
}

/**
   C-string equivalent of s2_get_v_proxy().
*/
static cwal_value * s2_get_proxy( s2_engine * se,
                                  cwal_value * self,
                                  char const * key,
                                  cwal_size_t keyLen ){
    cwal_value * v = 0;
    assert(se && self && key);
#if 0
    self = cwal_container_part(se->e, self);
    v = self ? cwal_prop_get(self, key, keyLen) : 0;
#else
    /* s2_dump_val(self,key); */
    while(!v && self){
      if(cwal_props_can(self)){
        v = cwal_prop_get(self, key, keyLen);
      }
      self = cwal_value_prototype_get(se->e, self);
      /* s2_dump_val(self,key); */
    }
#endif
    /* s2_dump_val(v,key); */
    return v;
}


cwal_value * s2_var_get_v( s2_engine * se, int scopeDepth,
                           cwal_value * key ){
  return cwal_scope_search_v( cwal_scope_current_get(se->e),
                              scopeDepth, key, 0 );
}

cwal_value * s2_var_get( s2_engine * se, int scopeDepth,
                         char const * key, cwal_size_t keyLen ){
  return (!se || !key)
        ? 0
        : cwal_scope_search( cwal_scope_current_get(se->e),
                             scopeDepth, key,
                             keyLen, 0 );
}

int s2_var_set( s2_engine * se, int scopeDepth,
                char const * key, cwal_size_t keyLen,
                cwal_value * v ){
    return key
      ? cwal_scope_chain_set( cwal_scope_current_get(se->e),
                              scopeDepth, key, keyLen, v )
      : CWAL_RC_MISUSE;
}

int s2_var_set_v( s2_engine * se, int scopeDepth,
                  cwal_value * key, cwal_value * v ){
  return key
    ? cwal_scope_chain_set_v( cwal_scope_current_get(se->e),
                              scopeDepth, key, v )
    : CWAL_RC_MISUSE;
}


int s2_set_v( s2_engine * se, cwal_value * self,
              cwal_value * key, cwal_value * v ){
  int rc = 0;
  char const *errMsg = 0;
  if(self){
    if(cwal_props_can(self)){
      cwal_array * ar;
      if(cwal_value_is_integer(key)
         && (ar=cwal_value_array_part(se->e,self))){
        /*
          Reminder: cwal_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);
            s2_dump_val(v,"array entry");*/
          rc = cwal_array_set(ar, (cwal_size_t)i, v);
          if(rc) errMsg = "cwal_array_set() failed.";
        }
      }
      else if(0==cwal_compare_str_cstr( cwal_value_get_string(key),
                                        "prototype", 9 )){
        if(cwal_value_null()==v || cwal_value_undefined()==v){
          /* Special case: allow unsetting the prototype via
             assignment to these. Normally this would fail in
             cwal_value_prototype_set() because it expects a container
             type.
          */
          v = 0;
        }else if(!cwal_props_can(self)){
          /* Normally never reached b/c assignment ops catch
             this case, but non-assignment ops can also call
             this. */
          rc = CWAL_RC_ACCESS;
          errMsg = "Cannot re-assign prototypes of non-container types "
            "- they are fixed in place at the C level.";
        }
        if(!rc){
          rc = cwal_value_prototype_set( self, v );
          switch(rc){
            case 0: break;
            case CWAL_RC_CYCLES_DETECTED:
              errMsg = "Setting prototype would introduce a cycle in the prototype chain.";
              break;
            case CWAL_RC_TYPE:
              errMsg = "Invalid type for a prototype (only containers "
                "allowed, or null/undefined to remove the prototype).";
              break;
            default:
              errMsg = "cwal_value_prototype_set() failed";
              break;
          }
        }
      }else{
        rc = cwal_prop_set_v_with_flags( self, key, v,
                                         CWAL_VAR_F_PRESERVE );
        switch(rc){
          case CWAL_RC_TYPE:
            return s2_engine_err_set(se, rc,
                                     "Invalid target type (%s) "
                                     "for assignment.",
                                     cwal_value_type_name(self));
          case CWAL_RC_ACCESS:{
   
            if(self&&!cwal_value_may_iterate(self)){
              return s2_engine_err_set(se, rc,
                                       "Cannot modify properties "
                                       "during traversal.");
            }else{
              cwal_size_t keyLen = 0;
              char const * keyStr = cwal_value_get_cstr(key,&keyLen);
              if(keyStr){
                return s2_engine_err_set(se, rc,
                                         "Cannot assign to const '%.*s'.",
                                         (int)keyLen, keyStr);
              }else{
                return s2_engine_err_set(se, rc,
                                         "Cannot assign to const property.",
                                         cwal_value_type_name(self));
              }
            }
          }
        }
      }
    }else{
      cwal_size_t tlen = 0;
      char const * tn = cwal_value_type_name2(self, &tlen);
      return s2_engine_err_set(se, CWAL_RC_TYPE,
                               "Cannot set properties on "
                               "non-container type '%.*s'.",
                               (int)tlen, tn);
    }
  }else{
    /* Scope-level set */
#if 0
    s2_dump_val(key,"KEY Setting scope var");
    s2_dump_val(v,"VALUE 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 = s2_var_set_v( se,
                       v ? -1 : 0 /* don't allow 'unset'
                                     across scopes*/,
                       key, v );
    switch(rc){
      case 0:
      case CWAL_RC_EXCEPTION:
        break;
      case CWAL_RC_NOT_FOUND:
        assert(!v && "But the code said so!");
        /* if(!v) rc = 0; */
        break;
      case CWAL_RC_ACCESS:{
        cwal_size_t keyLen = 0;
        char const * keyStr = cwal_value_get_cstr(key,&keyLen);
        if(keyStr){
          rc = s2_engine_err_set(se, rc,
                                 "Cannot assign to const '%.*s'.",
                                 (int)keyLen, keyStr);
        }else{
          errMsg = "Cannot assign to a const.";
        }
        break;
      }
      default:
        errMsg = "s2_var_set_v() failed.";
        break;
    }
  }
  if(errMsg && (CWAL_RC_EXCEPTION!=rc)){
    assert(rc);
    rc = s2_engine_err_set(se, rc, "%s", errMsg );
  }
  return rc;
}


int s2_set( s2_engine * se, cwal_value * self,
            char const * key, cwal_size_t keyLen,
            cwal_value * v ){
  /**
     ACHTUNG: 95% duplicate code: s2_set_v().
  */
  int rc;
  char const *errMsg = 0;
  if(!key) return CWAL_RC_MISUSE;
  else if(!keyLen && *key) keyLen = cwal_strlen(key);
  if(self){
    if(!cwal_props_can(self)){
      cwal_size_t tlen = 0;
      char const * tn = cwal_value_type_name2(self, &tlen);
      return s2_engine_err_set(se, CWAL_RC_TYPE,
                               "Cannot set properties on "
                               "non-container type '%s'.",
                               (int)tlen, tn);
    }
    else if((9==keyLen) && 'p'==*key && 0==memcmp( key, "prototype", 9 )){
      rc = cwal_value_prototype_set( self, v );
      if(rc) errMsg = "cwal_value_prototype_set() failed.";
    }else{
      rc = cwal_prop_set_with_flags( self, key, keyLen, v,
                                     CWAL_VAR_F_PRESERVE );
      switch(rc){
        case CWAL_RC_TYPE:
          return s2_engine_err_set(se, rc,
                                   "Invalid target type (%s) "
                                   "for assignment.",
                                   cwal_value_type_name(self));
        case CWAL_RC_ACCESS:
          return s2_engine_err_set(se, rc,
                                   "Cannot assign to const property '%.*s'.",
                                   (int)keyLen, key);
      }
    }
  }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 = s2_var_set( se, v ? -1 : 0 /* don't allow 'unset'
                                       across scopes*/,
                     key, keyLen, v );
    switch(rc){
      case 0:
      case CWAL_RC_EXCEPTION:
        break;
      case CWAL_RC_ACCESS:
        errMsg = "Cannot assign to a const variable.";
        break;
      case CWAL_RC_NOT_FOUND:
        assert(!v && "But the code said so!");
        if(!v) rc = 0;
        break;
      default:
        errMsg = "s2_var_set_v() failed.";
        break;
    }
  }
  if(errMsg && (CWAL_RC_EXCEPTION!=rc)){
    rc = s2_engine_err_set(se, rc, "%s", errMsg );
  }
  return rc;
}

int s2_get_v( s2_engine * se, cwal_value * self,
              cwal_value * key, cwal_value ** rv ){
  int rc;
  char const *errMsg = 0;
  if(!self){
    /* do a scope lookup */
    *rv = s2_var_get_v( se, -1, key );
    rc = 0;
  }else{
    cwal_array * ar;
    assert(key);
    if(cwal_value_is_integer(key)
       && (ar=cwal_value_array_part(se->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;
      }
    }
    else if(0==cwal_compare_str_cstr( cwal_value_get_string(key),
                                      "prototype", 9 )){
      *rv = cwal_value_prototype_get( se->e, self );
      rc = 0;
    }
    else {
      *rv = s2_get_v_proxy( se, self, key );
      rc = 0;
    }
  }

  if(errMsg && (CWAL_RC_EXCEPTION!=rc)){
    assert(rc);
    rc = s2_engine_err_set(se, rc, "%s", errMsg );
  }
  return rc;
}

int s2_get( s2_engine * se, 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 = cwal_strlen(key);
  if(self && (keyLen==9) && 'p'==*key && (0==memcmp( key, "prototype", 9 ))){
    *rv = cwal_value_prototype_get( se->e, self );
    rc = 0;
  }
  else if(self){
    *rv = s2_get_proxy( se, self, key, keyLen );
    rc = 0;
  }else{
    *rv = s2_var_get( se, -1, key, keyLen );
    rc = 0;
  }
  return rc;
}


int s2_install_core_prototypes(s2_engine * se){
  int rc = CWAL_RC_OOM;
  cwal_value * v;
  if(! (v = s2_prototype_object(se)) ) goto end;
  /* Object installs the other core ones, to try to stave off
     potential chicken/egg timing issues. */
  rc = 0;
  end:
  return rc;
}

char s2_value_may_preceed_dot(s2_engine * se, cwal_value * v){
  return cwal_container_part(se->e, v) ? 1 : 0;
}

char const * s2_rc_cstr(int rc){
  if(rc < CWAL_RC_CLIENT_BEGIN) return cwal_rc_cstr(rc);
  switch(rc){
#define CASE(X) case X: return #X
    CASE(S2_RC_placeholder);
    CASE(S2_RC_TOSS);
#undef CASE
#if 0
    default:
      return "Unknown result code";
#endif
  }
  return 0;
}

int s2_throw_err_ptoker( s2_engine * se, s2_ptoker const * pr ){
  char const * errPos;
  int line = 0, col = 0;
  if(!pr) pr = se->currentScript;
  assert(se->err.code);
  assert(pr);
  errPos = se->opErrPos
    ? se->opErrPos
    : s2_ptoker_err_pos_first(pr);
  if(!errPos){
    errPos = pr->token.begin
      ? pr->token.begin
      : pr->pbToken.begin;
  }
  /* se->opErrPos = errPos; */
  s2_ptoker_count_lines( pr, errPos, &line, &col);
  return s2_throw_err(se, &se->err, s2_ptoker_name_first(pr, 0),
                      line, col);
}

static int s2_err_ptoker_impl( s2_engine * se, char throwIt,
                               s2_ptoker const * st,
                               int code, char const * fmt,
                               va_list vargs ){
  int rc = 0;
  char const * begin;
  char const * end;
  char const * name;
  cwal_size_t nameLen = 0;
  cwal_buffer * obuf = &se->err.msg;
  s2_ptoker const * top = s2_ptoker_top_parent( st );
  char const * errPos = se->opErrPos
    ? se->opErrPos
    : st->errPos/* s2_ptoker_err_pos_first(st) */;
  int line = -1, col = -1;
  if(!errPos){
    errPos = st->token.begin
        ? st->token.begin
      : st->pbToken.begin;
  }
  s2_engine_err_reset(se);
  se->err.code = code ? code : CWAL_RC_EXCEPTION;
  name = s2_ptoker_name_first(st, &nameLen);
  /* expand source range to include parent parsers,
     so that we get the right line/column numbers.
  */
  begin = top->begin;
  end = top->end;
#if 1
  if(errPos) s2_ptoker_count_lines(st, errPos, &line, &col);
#else
  /* nope - not enough info by this point */
  if(errPos){
    /* s2_ptoker const * parent = st->parent; */
    line = st->currentLine + st->lineOffset;
    col = st->currentCol + st->colOffset;
    /*for(; parent; parent = parent->parent){
      line += parent->lineOffset;
      col += parent->colOffset;
      }*/
  }
#endif
  if(!throwIt && errPos
     && !se->currentScript
     /*  ^^^^^^ elide location info from error string when it looks
         like a script-side exception is up-coming

         Means we lose file name info in assertions. To fix.
     */
     ) {
    char const * tailPart = ": ";
    /* s2_ptoker_count_lines(st, errPos, &line, &col); */
    if(name){
      rc = cwal_buffer_printf( se->e, obuf, "%s:", name );
    }
    if(!rc && errPos<end){
      s2_count_lines( begin, end, errPos,
                      &line, &col);
      if(name){
        rc = cwal_buffer_printf( se->e, obuf,
                                 "%d:%d%s",
                                 line, col, tailPart);
      }else{
        rc = cwal_buffer_printf( se->e, obuf,
                                 "line %d, col %d%s",
                                 line, col, tailPart);
      }
    }else if(errPos==end){
      rc = cwal_buffer_printf( se->e, obuf,
                               "@ EOF%s", tailPart);
    }else{
      rc = cwal_buffer_printf( se->e, obuf,
                               "@ unknown source position%s",
                               tailPart);
    }
    /* se->opErrPos = errPos; */
  }

  if(!rc){
    if(fmt && *fmt){
      rc = cwal_buffer_printfv(se->e, obuf, fmt, vargs);
    }else{
      rc = cwal_buffer_printf(se->e, obuf,
                              "Error #%d (%s)%s%s",
                              code, s2_rc_cstr(code),
                              (st->errMsg ? ": " : ""),
                              st->errMsg ? st->errMsg : "");
    }
  }
  se->err.line = line;
  se->err.col = col;
  se->err.script.used = 0;
  if(!rc && name){
    rc = cwal_buffer_append( se->e, &se->err.script, name, nameLen );
  }
  if(!rc && throwIt){
    rc = s2_throw_err(se, &se->err, name, line, col);
  }
  return rc ? rc : se->err.code;
}

int s2_err_ptoker( s2_engine * se, s2_ptoker const * st,
                   int code, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = s2_err_ptoker_impl(se, 0, st, code, fmt, args);
  va_end(args);
  return rc;
}

int s2_throw_ptoker( s2_engine * se, s2_ptoker const * st,
                    int code, char const * fmt, ... ){
  int rc;
  va_list args;
  va_start(args,fmt);
  rc = s2_err_ptoker_impl(se, 1, st, code, fmt, args);
  va_end(args);
  return rc;
}


int s2_slurp_braces( s2_engine *se, s2_ptoker * st,
                     s2_ptoken * out ){
  int const opener = st->token.ttype;
  int closer;
  int rc = 0;
  int level = 1;
  s2_ptoken origSrc;
  s2_ptoken origPb;
  int adjustedOpener = opener;
  char const * typeLabel = 0;
  char const * end = 0;
  if(!out) out = &st->token;
  switch(opener){
    case S2_T_ParenOpen:
      closer = S2_T_ParenClose;
      adjustedOpener = S2_T_ParenGroup;
      typeLabel = "(PARENS)";
      break;
    case S2_T_BraceOpen:
      closer = S2_T_BraceClose;
      adjustedOpener = S2_T_BraceGroup;
      typeLabel = "[BRACES]";
      break;
    case S2_T_SquigglyOpen:
      adjustedOpener = S2_T_SquigglyString;
      closer = S2_T_SquigglyClose;
      typeLabel = "{SQUIGGLY}";
      break;
    default:
      return s2_err_ptoker(se, st, CWAL_RC_TYPE,
                                  "Invalid token type #%d (%s) for "
                                  "s2_slurp_braces()",
                                  opener, s2_ttype_cstr(opener));
  }
  origSrc = st->token;
  origPb = st->pbToken;
  switch(opener){
    case S2_T_SquigglyOpen:{
#if 0
      char const * pos = end = st->token.begin + 1;
      for( ; pos < st->end; ++pos ){
        /*
          consider: { ..., <<<X ... X, ... }
    
          The heredoc might contain stuff which confuses the parens.
          Because of that, we can't simply do a byte-traversal here,
          but have to go through s2_next_token().
        */
        if(opener == (int)*pos){
          ++level;
        }else if(closer == (int)*pos){
          assert(level>0);
          if(!--level) break;
        }
      }
      if(pos == st->end) end = 0;
      else{
        end = pos+1;
        assert(closer == (int)*pos);
      }
      break;
#else
      /* fall through */
#endif
    }
    case S2_T_ParenOpen:
    case S2_T_BraceOpen:{
      char const * errPos = st->token.end;
      for( ; (0==(rc=s2_next_token(se, st, 0, 0))); ){
        /*
          consider: ( ..., <<<X ... X, ... )
    
          The heredoc might contain stuff which confuses the parens.
          Because of that, we can't simply do a byte-traversal here,
          but have to go through s2_next_token().
        */
        int const ttype = st->token.ttype;
        end = st->token.end;
        if(s2_ttype_is_eof(ttype)){
          end = 0;
          break;
        }else{
          if(opener == ttype){
            ++level;
            errPos = end;
          }
          else if(closer == ttype){
            assert(level>0);
            if(!--level) break;
          }
        }
      }
      if(!rc && 0 != level){
        st->errPos = errPos;
        assert(typeLabel);
        rc = s2_err_ptoker( se, st, CWAL_RC_RANGE,
                            "Unexpected EOF slurping %s block.",
                            typeLabel);
      }
      break;
    }/*Parens/Braces*/
  }/* switch */

  if(!rc && !end){
    st->errPos = origSrc.begin;

#if 0
    MARKER(("slurped <<%.*s>>\n",
            /* (int)(out->adjEnd - out->adjBegin), out->adjBegin, */
            (int)(st->token.end - origSrc.begin), origSrc.begin));
#endif

    rc = s2_err_ptoker( se, st, CWAL_RC_RANGE,
                        "Unexpected EOF or mismatched braces "
                        "while slurping %s block.",
                        typeLabel);
  }else if(!rc){
    assert(end);
    out->ttype = adjustedOpener;
    out->begin = out->adjBegin = origSrc.begin;
    out->end = end;
    assert((out->end - out->begin) >= 2);
    assert(opener == (int)*out->begin);
    assert(closer == (int)*(out->end-1));
    /* Skip leading whitespaces */
    while((++out->adjBegin < (end-1))
          && s2_is_space((int)*out->adjBegin)){
    }
    /* Skip trailing whitespaces */
    out->adjEnd = end - 1/* == closer */;
    while( (--out->adjEnd>out->adjBegin)
           && s2_is_space((int)*out->adjEnd) ){
    }
    ++out->adjEnd /* possibly back to the '}' byte */;
    out->ttype = adjustedOpener;
    rc = 0;
#if 0
    MARKER(("slurped <<%.*s>> out->adjEnd=%d\n",
            /* (int)(out->adjEnd - out->adjBegin), out->adjBegin, */
            (int)(out->end - out->begin), out->begin,
            *out->adjEnd));
#endif
    assert(opener == *out->begin);
    assert(closer == *(out->end-1));
    assert( out->adjEnd >= out->adjBegin );
    assert( out->adjEnd <= out->end );
  }

  if(out != &st->token){
    st->token = origSrc;
    st->pbToken = origPb;
  }
  return rc;
}


int s2_slurp_heredoc( s2_engine * se, s2_ptoker * st,
                      s2_ptoken * tgt ){
  int rc = 0;
  char const * docBegin;
  char const * docEnd;
  char const * idBegin;
  char const * idEnd;
  char const * theEnd = NULL;
  s2_ptoken const origin = st->token;
  cwal_size_t idLen;
  int modeFlag = 0;
  /* char const * idEnd; */
  assert(S2_T_HeredocStart==st->token.ttype);
  if(!tgt) tgt = &st->token;

  rc = s2_next_token(se, st, 0, 0);
  if(rc) return rc;

  if(S2_T_Colon==st->token.ttype){
    modeFlag = st->token.ttype;
    rc = s2_next_token(se, st, 0, 0);
    if(rc) return rc;
  }

  switch(st->token.ttype){
    case S2_T_Identifier:
    case S2_T_LiteralStringDQ:
    case S2_T_LiteralStringSQ:
      break;
    default:
      return s2_err_ptoker(se, st,
                           CWAL_SCR_SYNTAX,
                           "Expecting identifier or "
                           "quoted string "
                           "at start of HEREDOC.");
  }
  idBegin = st->token.begin;
  idEnd = st->token.end;
  idLen = idEnd - idBegin;
  docBegin = idEnd;
  switch(modeFlag){
    case S2_T_Colon:
      if('\n'==*docBegin || s2_is_space((int)*docBegin)) ++docBegin;
      break;
    default:
      /* if('\n'==*docBegin) ++docBegin; */
      /* else  */while(s2_is_space((int)*docBegin)) ++docBegin;
  }
  rc = CWAL_SCR_SYNTAX;
  for( docEnd = docBegin; docEnd < st->end; ++docEnd ){
    if(*docEnd != *idBegin) continue;
    else if(0 == memcmp( docEnd, idBegin, idLen)){
      char const * back = docEnd-1;
      theEnd = docEnd + idLen;
      switch(modeFlag){
        case S2_T_Colon:
          if(back>=docBegin && ('\n'==*back || s2_is_space((int)*back))) --docEnd;
          break;
        default:
          /* if(back>=docBegin && '\n'==*docEnd) --docEnd; */
          /* else  */for( ; back>=docBegin && s2_is_space((int)*back); --back) --docEnd;
      }
      rc = 0;
      break;
    }
  }
  if(rc){
    st->errPos = idBegin;
    rc = s2_err_ptoker(se, st,
                       rc, "Did not find end of HEREDOC "
                       "starting at '%.*s'.",
                       (int)idLen, idBegin);
  }else{
    tgt->begin = origin.begin;
    tgt->end = theEnd;
    tgt->ttype = S2_T_SquigglyString;
    tgt->adjBegin = docBegin;
    tgt->adjEnd = docEnd;
    assert(docEnd>=docBegin);
#if 0
    MARKER(("HEREDOC<%.*s> body: %.*s\n",
            (int)(idEnd-idBegin), idBegin,
            (int)(docEnd-docBegin), docBegin));
#endif
  }
  return rc;
}


int s2_ptoker_next_is_ttype( s2_engine * se, s2_ptoker * pr, int nextFlags, int ttype, char consumeIt ){
  s2_ptoken next = s2_ptoken_empty;
  s2_next_token( se, pr, nextFlags, &next);
  if(next.ttype==ttype){
    if(consumeIt) pr->token = next;
    return ttype;
  }else{
    return 0;
  }
}

static int s2_next_token_eol_skipper( int ttype ){
  switch(ttype){
    case S2_T_EOL:
    case S2_T_NL:
      return ttype;
    default:
      return s2_ttype_is_junk(ttype);
  }
}

static int s2_next_token_eol_noskipper( int ttype ){
  switch(ttype){
    case S2_T_EOL:
    case S2_T_NL:
      return 0;
    default:
      return s2_ttype_is_junk(ttype);
  }
}


int s2_next_token( s2_engine * se, s2_ptoker * st,
                   int flags,
                   s2_ptoken * tgt ){
  s2_ptoken tt = s2_ptoken_empty;
  s2_ptoken const oldT = st->token;
  s2_ptoken const oldP = st->pbToken;
  s2_ttype_predicate_f const skipper =
    (S2_NEXT_NO_SKIP_EOL& flags)
    ? s2_next_token_eol_noskipper
    : s2_next_token_eol_skipper;
  int rc;
  ++se->metrics.nextTokenCalls;
  st->errPos = 0;
  rc = s2_ptoker_lookahead_skip( st, &tt, skipper);
  if(rc){
    assert(st->errMsg);
    return s2_err_ptoker( se, st, rc, "%s", st->errMsg );
  }else{
    s2_ptoker_token_set(st, &tt);
    switch(tt.ttype){
      case S2_T_Semicolon:
        st->token.ttype = S2_T_EOX;
        break;
      case S2_T_CR:
        assert(!"skipped by the junk-skipper!");
      case S2_T_NL:
        assert( S2_NEXT_NO_SKIP_EOL & flags );
        st->token.ttype = S2_T_EOL;
        break;
      case S2_T_SquigglyOpen:
      case S2_T_ParenOpen:
      case S2_T_BraceOpen:
        /* Group these at the tokenization level to simplify
           evaluation. This completely removes parens from the
           evaluation-side grammar by evaluating these groups as a
           Value by recursively eval'ing their content.
        */
        rc = s2_slurp_braces(se, st, 0 );
        break;
      case S2_T_HeredocStart:
        rc = s2_slurp_heredoc(se, st, 0);
        if(!rc){
          assert(S2_T_SquigglyString==st->token.ttype);
        }
        break;
      case S2_T_Identifier:
        /* TODO: tag keywords */
        break;
      default:
        break;
    }
  }
  if(rc && !st->errPos) st->errPos = st->token.begin;
  if(tgt){
    *tgt = st->token;
    st->token = oldT;
    st->pbToken = oldP;
  }else if(!rc){
    st->pbToken = oldT;
  }
#if 1
  else{
    switch(rc){
      case CWAL_RC_RANGE:
        rc = CWAL_SCR_SYNTAX;
        break;
      default:
        break;
    }
  }
#endif

  return rc;
}


int s2_ptoken_create_value( s2_engine * se,
                            s2_ptoken const * t,
                            cwal_value ** rv ){
  int rc = CWAL_RC_TYPE;
  cwal_value * v = NULL;
  cwal_engine * e = se->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 S2_T_LiteralIntHex:
    case S2_T_LiteralIntOct:
    case S2_T_LiteralIntDec:{
      cwal_int_t dd = 0;
      s2_ptoken_parse_int( t, &dd);
      v = cwal_new_integer(e, dd);
      RC;
      break;
    }
    case S2_T_LiteralDouble:{
      cwal_double_t dd = 0;
      s2_ptoken_parse_double( t, &dd);
      v = cwal_new_double(e, dd);
      RC;
      break;
    }
    case S2_T_LiteralStringSQ:
    case S2_T_LiteralStringDQ:{
      cwal_buffer * escapeBuf = &se->buffer;
      cwal_size_t const oldUsed = escapeBuf->used;
      assert((t->end - t->begin)>=2 /* for the quotes */);
      rc = s2_unescape_string(e,
                              t->begin + 1 /*quote*/,
                              t->end - 1 /*quote*/,
                              escapeBuf );
      if(rc) break;
      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;
      /* s2_dump_val(v,"string literal"); */
      RC;
      break;
    }
    case S2_T_SquigglyString:{
      int len = (int)(t->end - t->begin);
      char const * beg = t->adjBegin;
      char const * end = t->adjEnd;
      assert(beg);
      assert(end);
      assert(beg <= end);
      len = (int)(end - beg);
      v = cwal_new_string_value(e, len ? beg : "",
                                (cwal_size_t)len);
      /*MARKER(("SquigglyString ==> cwal_string: '{' %s '}'\n",
        cwal_value_get_cstr(v, 0)));*/
      RC;
      break;
    }
#if 0
    case S2_T_Identifier:{
      char const * begin = t->adjBegin ? t->adjBegin : t->begin;
      char const * end = t->adjEnd ? t->adjEnd : t->end;
      int len = (int)(end - begin);
      assert(len>0);
      v = s2_var_get( se, -1, begin, (cwal_size_t)len);
      if(!v){
        rc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                               "Could not resolve identifier '%.*s'.",
                               len, begin);
      }
      s2_dump_val(v, "from Identifier");
      break;
    }
#endif
    default:{
      /* Use the raw token bytes as a string... */
      char const * begin = t->adjBegin ? t->adjBegin : t->begin;
      char const * end = t->adjEnd ? t->adjEnd : t->end;
      if((long)(end - begin) >= 0){
        cwal_size_t len = end - begin;
        /* MARKER("STRING: [%.*s]\n", (int)(end - begin), begin); */
        /* MARKER("STRING: len=%ld\n", (long) (end - begin) ); */
        v = cwal_new_string_value(e, len ? begin : 0, len);
        /* s2_dump_val(v,"scope string"); */
        RC;
      }else{
        MARKER(("STRING: ttype=%d len=%ld\n", t->ttype, (long) (t->end - t->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
}/* s2_ptoken_create_value() */

#if 0
/** @internal

    Deprecated, currenly unused.

    A helper for binary and unary operator overloading. All of the
    pointer arguments must point to valid memory.

    This routine looks for an operator Function in one of lhs or rhs
    (as described below) and, if found, calls it. opName is the name
    of the operator (the property name in script space) and opLen
    must be the non-0 length of that operator.

    Binary mode:

    If neither lhs nor rhs are NULL then:

    - the operator is treated like a binary call with lhs as the 'this'
    for the call and parameters (lhs, rhs)


    Unary prefix mode:

    If lhs is NULL then:

    - rhs must not be NULL

    - the operator is assumed to be treated like a unary prefix call on the
    rhs, and the operator function (if any) is called with rhs as the 'this'
    and _no_ arguments.


    Unary suffix mode:

    If lhs is not NULL and rhs is:

    - assume the op is a unary suffix operator for lhs and call it with
    lhs as 'this' and itself as an arg.

    If lhs (or rhs) has no such function, or finds a non-function,
    *theRc is set to 0 and 0 is returned.


    If it finds a function, it calls that function and sets *rv to its
    result value, *theRc to its return value, then returns non-0.

    On any error (running the function or fetching the operator) then
    *theRc is set to the error value and this function will return 0
    if the operator was not found or not called, and non-0 if it was
    called (in which case *theRc came from the operator function).
*/
char s2_value_op_proxy( s2_engine * se, char const * opName,
                        cwal_size_t opLen, cwal_value * lhs,
                        cwal_value * rhs, 
                        cwal_value **rv, int * theRc );
char s2_value_op_proxy( s2_engine * se, char const * opName, cwal_size_t opLen,
                        cwal_value * lhs, cwal_value * rhs, 
                        cwal_value **rv, int * theRc ){
  cwal_value * fv = 0;
  int rc;
  cwal_value * theThis = lhs ? lhs : rhs;
  cwal_function * fn;
  assert(se && se->e);
  assert(opName && opLen);
  assert(theRc);
  assert(rv);
  assert(lhs || rhs);
  /*
    Check for member function...
  */
  rc = s2_get( se, theThis, opName, opLen, &fv );
  if(rc || !fv){
    *theRc = rc;
    return 0;
  }
  else if((fn = cwal_value_function_part(se->e, fv))){
    /* th1ish_stack_entry sta = th1ish_stack_entry_empty; */
    cwal_value * argv[2] = {0,0};
    int argc = (lhs && rhs) ? 2 : (lhs ? 1 : 0);
    argv[0] = argc ? lhs : 0;
    argv[1] = argc ? rhs : 0;
    /* if(srcPos) th1ish_strace_push_pos( ie, srcPos, &sta ); */
    /* s2_dump_val(theThis,opName); */
    /* s2_dump_val(argv[0],opName); */
    /* s2_dump_val(argv[1],opName); */
    *theRc = cwal_function_call( fn, theThis, rv, argc, argv );
    /* if(srcPos) th1ish_strace_pop( ie ); */
    return 1;
  }else{
    /* A non-function value with that name? */
    *theRc = 0;
    return 0;
  }
}
#endif
/* currently unused, but potentially still useful, code */

int s2_value_to_buffer( cwal_engine * e, cwal_buffer * buf, cwal_value * arg ){
  int rc = 0;
  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 = 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;
}

int s2_cb_value_to_string( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_buffer buf = cwal_buffer_empty;
  rc = s2_value_to_buffer(args->engine, &buf, args->self);
  if(!rc
     && !(*rv = cwal_string_value(cwal_buffer_to_zstring( args->engine, &buf)))
     ){
    rc = CWAL_RC_OOM;
  }
  cwal_buffer_reserve(args->engine, &buf, 0);
  return rc;
}

static int s2_cb_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;
    if(useSelf){
      jb = cwal_value_buffer_part(args->engine, 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 s2_cb_this_to_json_token( cwal_callback_args const * args, cwal_value **rv ){
    return s2_cb_to_json_token_impl( args, rv, 1 );
}

int s2_cb_arg_to_json_token( cwal_callback_args const * args, cwal_value **rv ){
    return s2_cb_to_json_token_impl( args, rv, 0 );
}




int s2_cb_value_compare( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  if(!args->argc || (args->argc>2)){
    /* FIXME: error location info */
    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;
}

cwal_engine * s2_engine_engine(s2_engine * se){
  return se ? se->e : 0;
}

s2_scope * s2_scope_current( s2_engine * se ){
  return (se && se->e) ? se->currentScope : 0;
}

int s2_scope_push( s2_engine * se, s2_scope * tgt ){
  int rc;
  cwal_scope * pScope = &tgt->scope;
  assert(se);
  assert(se->e);
  assert(tgt);
  assert(!tgt->parent);
  rc = cwal_scope_push( se->e, &pScope );
  if(!rc){
    /* assert(tgt->scope.parent); */
    if((int)tgt->scope.level > se->metrics.maxScopeDepth){
      se->metrics.maxScopeDepth = (int)tgt->scope.level;
    }
    tgt->parent = se->currentScope;
    se->currentScope = tgt;
    se->sguard = &tgt->sguard;
    /* MARKER(("pushed %p\n",(void const *)tgt));  */
  }else{
    assert(!"This can't happen if the args are valid!");
  }
  return rc;
}

int s2_scope_pop( s2_engine * se ){
  int rc;
  assert(se);
  assert(se->e);
  assert(se->e->current);
  if(se->currentScope==&se->topScope){
    cwal_exception_set(se->e, 0);
    cwal_propagating_set(se->e, 0);
  }
  assert(se->e->current == &se->currentScope->scope);
  rc = cwal_scope_pop( se->e );
  if(!rc){
    s2_scope * p;
    /* MARKER(("popping %p\n",(void const *)se->currentScope));  */
    assert(se->currentScope);
    assert(!se->currentScope->scope.e);
    assert(!se->currentScope->scope.parent);
    assert(!se->currentScope->scope.props);
    assert(!se->currentScope->scope.mine.headPod);
    assert(!se->currentScope->scope.mine.headObj);
    assert(!se->currentScope->scope.mine.headSafe);
    assert(!se->currentScope->scope.mine.r0);
    p = se->currentScope->parent;
    se->currentScope->parent = 0;
    /* *se->currentScope = s2_scope_empty; */
    se->sguard = p
      ? &p->sguard /* workaround until we finish transition of se->sguard */
      : 0 /* not quite right until transition is done */;
    se->currentScope = p;
    if(!se->currentScope){ /* top scope popped. */
      assert(!se->topScope.scope.e);
      assert(!se->topScope.scope.parent);
      assert(!se->topScope.scope.props);
      assert(!se->topScope.scope.mine.headPod);
      assert(!se->topScope.scope.mine.headObj);
      assert(!se->topScope.scope.mine.headSafe);
      assert(!se->topScope.scope.mine.r0);
    }
  }else{
    assert(!"This can't happen if the args are valid!");
  }
  return rc;
}

s2_engine * s2_engine_from_state( cwal_engine * e ){
  return (s2_engine *) cwal_engine_client_state_get( e, &s2_engine_empty );
}

s2_engine * s2_engine_from_args( cwal_callback_args const * args ){
  return (s2_engine *) cwal_engine_client_state_get( args->engine, &s2_engine_empty );
}

static int s2_cb_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;
  char const * cstr;
  cwal_value * arg = args->argc ? args->argv[0] : args->self;
  cwal_engine * e = args->engine;
  cstr = cwal_value_get_cstr(arg, &slen);
  if(!cstr){
    cwal_buffer * b = cwal_value_buffer_part(e, arg);
    if(b){
      cstr = (char const *)b->mem;
      slen = b->used;
    }
    if(!cstr){
      return cwal_exception_setf(e, CWAL_RC_MISUSE,
                                 "Expecting a %s as argument or 'this'.",
                                 isFilename
                                 ? "filename" : "JSON (string|Buffer)");
    }
  }
  rc = isFilename
    ? cwal_json_parse_filename( e, cstr, &root, &pInfo )
    : cwal_json_parse_cstr( e, cstr, slen, &root, &pInfo );
  if(rc){
    if(pInfo.errorCode){
      return cwal_exception_setf(e, 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, pInfo.errorCode,
                                 cwal_rc_cstr(pInfo.errorCode));
    }else{
      return cwal_exception_setf(e, rc,
                                 "Parsing JSON failed with code %d (%s).",
                                 rc, cwal_rc_cstr(rc));
    }
  }
  assert(root);
  *rv = root;
  return 0;
}

int s2_cb_json_parse_string( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_json_parse_impl(args, rv, 0);
}

int s2_cb_json_parse_file( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_json_parse_impl(args, rv, 1);
}

int s2_install_json( s2_engine * se, cwal_value * tgt, char const * key ){
  cwal_value * mod = NULL;
  cwal_value * v;
  int rc;
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;

  if(key && *key){
    mod = cwal_new_object_value(se->e);
    if(!mod) return CWAL_RC_OOM;
    if( (rc = cwal_prop_set(tgt, key, cwal_strlen(key), mod)) ){
      cwal_value_unref(mod);
      return rc;
    }
  }else{
    mod = tgt;
  }

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  CHECKV;                                                      \
  rc = cwal_prop_set( mod, NAME, cwal_strlen(NAME), v );       \
  if(rc) {cwal_value_unref(v); goto end;}(void)0
#define FUNC2(NAME,FP)                  \
  v = cwal_new_function_value(se->e, FP, 0, 0, 0); \
  SET(NAME)

  FUNC2("parse", s2_cb_json_parse_string);
  FUNC2("parseFile", s2_cb_json_parse_file);

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

int s2_cb_getenv( cwal_callback_args const * args, cwal_value **rv ){
  cwal_size_t len = 0;
  char const * key = args->argc
    ? cwal_value_get_cstr(args->argv[0], &len)
    : 0;
  if(!key){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting a string argument.");
  }else{
    char const * val = getenv(key);
    *rv = val
      ? cwal_new_string_value(args->engine, val,
                               cwal_strlen(val))
      /* ^^^ we "could" use x-strings and save a few bytes
         of memory, but if we add setenv() we'd likely shoot
         ourselves in the foot here.
      */
      : cwal_value_undefined();
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

int s2_cb_is_callable(cwal_callback_args const * args, cwal_value **rv){
  uint16_t i;
  char checkProtos = 1;
  *rv = args->argc ? cwal_value_true() : cwal_value_false();
  for(i = 0; i < args->argc; ++i){
    cwal_value * arg = args->argv[i];
    if(!i && args->argc>1 && cwal_value_is_bool(arg)){
      /* Called with leading boolean. */
      checkProtos = cwal_value_get_bool(arg);
    }else if(!(checkProtos
               ? cwal_value_function_part(args->engine, arg)
               : cwal_value_get_function(arg))
             ){
      *rv = cwal_value_false();
      break;
    }
  }
  return 0;
}

int s2_interrupt( s2_engine * se ){
  if(!se->flags.interrupted){
    se->flags.interrupted = CWAL_RC_INTERRUPTED;
    s2_engine_err_set(se, se->flags.interrupted,
                      "Interrupted by s2_interrupt().");
  }
  return se->err.code;
}

#if S2_USE_SIGNALS
static void s2_sigc_handler(int s){
  if(s2Interruptable){
    s2_engine_err_set(s2Interruptable, CWAL_RC_INTERRUPTED,
                      "Interrupted by signal #%d.", s);
    s2Interruptable->flags.interrupted = CWAL_RC_INTERRUPTED;
  }
}
#endif
/* S2_USE_SIGNALS */

int s2_check_interrupted( s2_engine * se, int rc ){
#if S2_USE_SIGNALS
  return (CWAL_RC_INTERRUPTED==se->flags.interrupted)
    ? CWAL_RC_INTERRUPTED
    : rc;
#else
  return rc;
#endif
}

void s2_set_interrupt_handlable( s2_engine * se ){
#if S2_USE_SIGNALS
  if((s2Interruptable = se)){
    struct sigaction sigIntHandler;
    sigIntHandler.sa_handler = s2_sigc_handler;
    sigemptyset(&sigIntHandler.sa_mask);
    sigIntHandler.sa_flags = 0;
    sigaction(SIGINT, &sigIntHandler, NULL);
  }
#endif
}

#undef MARKER
/* end of file s2.c */
/* start of file s2_array.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

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

#define ARGS_SE s2_engine * se = s2_args_state(args); \
    assert(se)

#define THIS_ARRAY \
    cwal_array * self = 0;                          \
    ARGS_SE; \
    self = cwal_value_array_part(se->e, args->self); \
    if(!self){ \
        return s2_throw( se, CWAL_RC_TYPE, \
                         "'this' is-not-an Array." );        \
    } (void)0

static int s2_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);
  *rv = args->self;
  return 0;
}

static int s2_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 s2_throw(se, CWAL_RC_MISUSE,
                      "length argument must be "
                      "a non-negative integer.");
    }
    rc = cwal_array_length_set( self, (cwal_size_t)len );
    if(rc){
      return s2_throw(se, 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 s2_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 s2_throw(se, 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 s2_throw(se, CWAL_RC_MISUSE, "%s", usage);
    }
    rc = cwal_array_reserve( self, (cwal_size_t)len );
    if(rc){
      return s2_throw(se, rc, "cwal_array_reserve() failed!");
    }
    *rv = args->self;
  }
  return 0;
}

static int s2_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 s2_throw(se, CWAL_RC_MISUSE,
                      "Appending to array failed with "
                      "code %d (%s).",
                      rc, cwal_rc_cstr(rc));
    }
  }
  return 0;
}

static int s2_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;
#if 1
    *rv = cwal_array_take(self, aLen);
    cwal_array_length_set(self, aLen);
#else
    *rv = cwal_array_get(self, aLen);
    if(!*rv) *rv = cwal_value_undefined();
    cwal_value_ref(*rv);
    cwal_array_length_set(self, aLen);
    cwal_value_unhand(*rv);
#endif

  }
  return 0;
}


static int s2_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 s2_throw(se, 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 s2_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 = 0;
    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 /* reminder to self: we're relying on underflow here. Ugly. */;
         --i ){
      rc = cwal_array_prepend( self, args->argv[i] );
      if(rc) break;
    }
    if(!rc) *rv = args->self;
    return rc;
  }
}

static int s2_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 s2_cb_array_sort( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_function * cmp;
  THIS_ARRAY;
  if(!args->argc || !(cmp = cwal_value_function_part(args->engine,
                                                     args->argv[0]))){
    rc = cwal_array_sort(self, cwal_compare_value_void);
  }else{
    rc = cwal_array_sort_func( self, args->self, cmp);
    if(!rc && cwal_exception_get(args->engine)){
      /* This is the only way to propagate an exception thrown from the
         sort routine for the time being. */
      rc = CWAL_RC_EXCEPTION;
    }
  }
  if(!rc) *rv = args->self;
  return rc;
}

static int s2_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 s2_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 s2_throw(se, 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. We'll re-use
         it momentarily (next iteration) if recycling is on.
      */;
    if(!rc && rv && cwal_value_is_bool(cbRv)
       && !cwal_value_get_bool(cbRv)){
      /* Treat a "real" false return value as signal to end the
         loop. */
      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 s2_array_get_index( cwal_callback_args const * args,
                                   uint16_t atIndex,
                                   cwal_int_t * rv ){
  ARGS_SE;
  if(args->argc<atIndex){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "Too few arguments.");
  }
  *rv = cwal_value_get_integer(args->argv[atIndex]);
  return (*rv<0)
    ? s2_throw(se, CWAL_RC_RANGE,
               "Array indexes may not be be negative.")
    : 0;
}

int s2_cb_array_get_index( cwal_callback_args const * args,
                               cwal_value **rv ){
  int rc;
  cwal_int_t i = 0;
  THIS_ARRAY;
  rc = s2_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 s2_cb_array_set_index( cwal_callback_args const * args,
                           cwal_value **rv ){
  int rc;
  cwal_int_t i;
  THIS_ARRAY;
  rc = s2_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 s2_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 s2_throw(se, 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;
}

static int s2_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 = &se->buffer;
  oldUsed = buf->used;
  if(!args->argc){
    misuse:
    return s2_throw(se, 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 = s2_value_to_buffer(se->e, 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;
}

cwal_value * s2_prototype_array( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_ARRAY );
    if(proto) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_ARRAY, proto );
    if(!rc) rc = s2_prototype_stash(se, "array", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_ARRAY));
    /* MARKER(("Setting up OBJECT prototype.\n")); */

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#if 0
    v = cwal_new_string_value(se->e, "Array", 6);
    CHECKV;
    rc = cwal_prop_set( proto, "__typename", 10, v );
    RC;
#endif

#define FUNC(NAME,FP)                              \
    v = s2_new_function2( se, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );        \
    if(rc) goto end

    FUNC("clear", s2_cb_array_clear);
    FUNC("eachIndex", s2_cb_array_each);
    FUNC("getIndex", s2_cb_array_get_index);
    FUNC("indexOf", s2_cb_array_index_of);
    FUNC("isEmpty", s2_cb_array_isempty);
    FUNC("join",s2_cb_array_join);
    FUNC("length",th1ish_cb_array_length);
    FUNC("push", s2_cb_array_push);
    FUNC("pop", s2_cb_array_pop);
    FUNC("reserve", s2_cb_array_reserve);
    FUNC("reverse", s2_cb_array_reverse);
    FUNC("setIndex", s2_cb_array_set_index);
    FUNC("shift", s2_cb_array_shift);
    FUNC("slice", s2_cb_array_slice);
    FUNC("sort", s2_cb_array_sort);
    FUNC("toString", s2_cb_value_to_string);
    FUNC("unshift", s2_cb_array_unshift);

#undef FUNC
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}


#undef MARKER
#undef ARGS_SE
#undef THIS_ARRAY
/* end of file s2_array.c */
/* start of file s2_eval.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

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


typedef struct s2_keyword s2_keyword;
typedef int (*s2_keyword_f)( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
const s2_func_state s2_func_state_empty = {
0/*se*/,
0/*keySrc*/,0/*keyScriptName*/,
0/*line*/,0/*col*/,
0/*flags*/,
0/*next*/
};

enum s2_func_state_flags_t {
S2_FUNCSTATE_F_NONE = 0,
S2_FUNCSTATE_F_EMPTY_PARAMS = 0x01,
S2_FUNCSTATE_F_EMPTY_BODY = 0x02
};

cwal_hash * s2_fstash( s2_engine * se ){
  if(!se->funcStash){
    se->funcStash = cwal_new_hash(se->e, 311);
    if(se->funcStash){
      cwal_value * v = cwal_hash_value(se->funcStash);
      cwal_value_ref(v);
      cwal_value_make_vacuum_proof(v, 1);
      cwal_value_rescope( se->e->top, v );
    }
  }
  return se->funcStash;
}

/**
   Returns the next available function source code id key, or 0 on
   allocation error. On a collision (overflow) it keeps looking until
   it finds an empty slot. It's not possible, except on 16-bit cwal
   builds, to fill up all slots before running out of memory, but a
   long (long, long)-running script could eventually overflow
   nonetheless.
*/
static cwal_value * s2_fstash_next_id( s2_engine * se ){
  cwal_value * id = 0;
  do{
    id = cwal_new_integer(se->e, (cwal_int_t)se->funcId++);
    if(!id) break;
    else if(se->funcStash && cwal_hash_search_v(se->funcStash, id)){
      /* Collision. Try again. */
      cwal_value_unref(id);
      id = 0;
    }
  }while(!id);
  return id;
}

static int s2_strace_push_pos( s2_engine * se,
                               s2_ptoker const * pr,
                               char const * srcPos,
                               s2_strace_entry * ent ){
  if(se->strace.max && se->strace.count == se->strace.max-1){
    return s2_engine_err_set(se, CWAL_RC_RANGE,
                             "Stack depth too deep. Max is %"CWAL_SIZE_T_PFMT". "
                             "Potentially caused by infinite recursion.",
                             (cwal_size_t)se->strace.max);
  }
  /**
     Reminder to self: we could potentially check for infinite
     recursion by using a combination of strace.count limits and
     comparing srcPos to prior entries in the list. It would turn this
     into an O(N) algorithm, though, with N=stack depth. Note that
     this only catches script-originated calls, not calls made into
     Functions via native code.
  */
  ent->pos = srcPos;
  ent->pr = pr;
  if(se->strace.tail){
    se->strace.tail->down = ent;
    ent->up = se->strace.tail;
    se->strace.tail = ent;
  }else{
    assert(!se->strace.head);
    se->strace.head = se->strace.tail = ent;
  }
  ++se->strace.count;
  return 0;
}

void s2_strace_pop( s2_engine * se ){
  assert(se->strace.count);
  assert(se->strace.tail);
  if(se->strace.count){
    s2_strace_entry * x = se->strace.tail;
    assert(!x->down);
    if(x->up){
      assert(x->up->down == x);
      se->strace.tail = x->up;
      x->up->down = NULL;
      x->up = NULL;
    }else{
      se->strace.head = se->strace.tail = NULL;
    }
    --se->strace.count;
  }else{
    s2_fatal( CWAL_RC_RANGE, "internal error: "
              "s2_strace_pop() called on empty stack.");
  }
}


int s2_strace_generate( s2_engine * se, cwal_value ** rv ){
  cwal_array * ar = 0;
  int rc = 0;
  s2_strace_entry * ent = se->strace.tail;
  cwal_size_t const oldCount = se->strace.count;
  /* MARKER(("se->strace.count=%u ent=%p\n", se->strace.count, (void const *)ent)); */
  if(!ent){
    *rv = 0;
    return 0;
  }
  se->strace.count = 0
    /* Workaround for co-dependency via
       s2_add_script_props() */;
  for( ; !rc && ent; ent = ent->up ){
    cwal_value * c;
    if(!ar){
      ar = cwal_new_array(se->e);;
      if(!ar){
        rc = CWAL_RC_OOM;
        break;
      }
#if 0
      rc = cwal_array_reserve(ar, oldCount);
      if(rc){
        break;
      }
#endif
    }
    c = cwal_new_object_value(se->e);
    if(!c){
      rc = CWAL_RC_OOM;
      break;
    }
    rc = cwal_array_append(ar, c);
    if(rc){
      cwal_value_unref(c);
      break;
    }
    rc = s2_add_script_props(se, c, ent->pr);
  }

  se->strace.count = oldCount;
  if(rc) cwal_array_unref(ar);
  else *rv = cwal_array_value(ar);
  return rc;
}


static s2_func_state * s2_func_state_malloc( s2_engine * se ){
  s2_func_state * rc = se->recycler.scriptFuncs.head;
  ++se->metrics.funcStateRequests;
  if(rc){
    assert(se->recycler.scriptFuncs.count>0);
    se->recycler.scriptFuncs.head = rc->next;
    --se->recycler.scriptFuncs.count;
    rc->next = 0; 
    rc->se = se;
  }else{
    rc = (s2_func_state *)cwal_malloc(se->e, sizeof(s2_func_state));
    if(rc){
      se->metrics.funcStateMemory += sizeof(s2_func_state);
      cwal_engine_adjust_client_mem(se->e, (cwal_int_t)sizeof(s2_func_state));
      ++se->metrics.funcStateAllocs;
      *rc = s2_func_state_empty;
      rc->se = se;
    }
  }
  return rc;
}

static void s2_func_state_free( s2_engine * se, s2_func_state * fs ){
  /* MARKER(("Finalizing function @ %p.\n", (void const *)fs)); */
  assert(!fs->next);
  if(se->funcStash){
    /* cwal_value_unref(cwal_hash_value(se->funcStash)); */
    if(fs->keySrc) cwal_hash_remove( se->funcStash, fs->keySrc );
#if 0
    /*
      We have to leave the script names in the hashtable for the time being.
      We only allocate each one once, though. */
    if(fs->keyScriptName) cwal_hash_remove( se->funcStash, fs->keyScriptName );
#endif
    /* if(fs->keyName) cwal_hash_remove( se->funcStash, fs->keyName ); */
  }else{
    /* Corner case: cleanup of se->e during s2_engine_finalize().
       se->funcStash might be gone by then, meaning our
       pointers would be dangling. No big deal here. */
    /* assert(!fs->keySrc); */
    /* assert(!fs->keyName); */
  }
  *fs = s2_func_state_empty;
  if(se->recycler.scriptFuncs.max>0
     && se->recycler.scriptFuncs.count < se->recycler.scriptFuncs.max
     ){
    fs->next = se->recycler.scriptFuncs.head;
    se->recycler.scriptFuncs.head = fs;
    ++se->recycler.scriptFuncs.count;
  }else{
    cwal_free(se->e, fs);
    cwal_engine_adjust_client_mem(se->e,
                                  -((cwal_int_t)sizeof(s2_func_state)));
  }
}


void s2_engine_free_recycled_funcs( s2_engine * se ){
  int const oldMax = se->recycler.scriptFuncs.max;
  s2_func_state * fs;
  se->recycler.scriptFuncs.max = 0;
  for( ; (fs = se->recycler.scriptFuncs.head); ){
    se->recycler.scriptFuncs.head = fs->next;
    fs->next = 0;
    assert(se->recycler.scriptFuncs.count);
    --se->recycler.scriptFuncs.count;
    s2_func_state_free( se, fs );
  }
  assert(!se->recycler.scriptFuncs.count);
  se->recycler.scriptFuncs.max = oldMax;
}

/**
   cwal finalizer for Function state. m must be a (s2_func_state*).
*/
static void cwal_finalizer_f_func_state( cwal_engine * e, void * m ){
  s2_func_state *fs = (s2_func_state*)m;
  assert(m);
  assert(fs->se);
  s2_func_state_free( fs->se, fs );
}


struct s2_keyword {
  int /* const */ id;
  char const * word;
  int /* const */ wordLen; /* has to be non-const for our bsearch() setup :/ */
  s2_keyword_f call;
};


static int s2_keyword_f_eval( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_builtin_vals( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_assert( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_typename( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_nameof( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_var( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_unset( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_FLC( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_refcount( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_if( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_while( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_dowhile( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_continue( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_function( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_for( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);
static int s2_keyword_f_breakpoint( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv);

static const struct S2_KEYWORDS__ {
/*
  Keep sorted on keyword name (string) so that we can eventual binary-search it.

  When updating, update all of:

  - the following struct entries

  - s2_ttype_keyword()

  - s2_ptoken_keyword()
*/
  s2_keyword const _breakpoint;
  s2_keyword const _col;
  s2_keyword const _file;
  s2_keyword const _filedir;
  s2_keyword const _flc;
  s2_keyword const _line;
  s2_keyword const affirm;
  s2_keyword const assert_;
  s2_keyword const break_;
  s2_keyword const catch_;
  s2_keyword const const_;
  s2_keyword const continue_;
  s2_keyword const doWhile;
  s2_keyword const eval;
  s2_keyword const exit_;
  s2_keyword const false_;
  s2_keyword const fatal_;
  s2_keyword const for_;
  s2_keyword const function_;
  s2_keyword const if_;
  s2_keyword const inherits;
  s2_keyword const nameof;
  s2_keyword const null_;
  s2_keyword const proc_;
  s2_keyword const refcount_;
  s2_keyword const return_;
  s2_keyword const scope;
  s2_keyword const throw_;
  s2_keyword const true_;
  s2_keyword const typeName;
  s2_keyword const undef_;
  s2_keyword const unset;
  s2_keyword const var;
  s2_keyword const while_;
  s2_keyword const _sentinel_;
} S2_KWDS = {
  /* { id                   word,         wordLen,  call() } */
  { S2_T_KeywordBREAKPOINT, "__BREAKPOINT", 12, s2_keyword_f_breakpoint },
  { S2_T_KeywordCOLUMN,     "__COLUMN",   8, s2_keyword_f_FLC },
  { S2_T_KeywordFILE,       "__FILE",     6, s2_keyword_f_FLC },
  { S2_T_KeywordFILEDIR,    "__FILEDIR",  9, s2_keyword_f_FLC },
  { S2_T_KeywordSRCPOS,     "__FLC",      5, s2_keyword_f_FLC },
  { S2_T_KeywordLINE,       "__LINE",     6, s2_keyword_f_FLC },

  { S2_T_KeywordAffirm,     "affirm",     6, s2_keyword_f_assert },
  { S2_T_KeywordAssert,     "assert",     6, s2_keyword_f_assert },
  { S2_T_KeywordBreak,      "break",      5, s2_keyword_f_eval },
  { S2_T_KeywordCatch,      "catch",      5, s2_keyword_f_eval },
  { S2_T_KeywordConst,      "const",      5, s2_keyword_f_var },
  { S2_T_KeywordContinue,   "continue",   8, s2_keyword_f_continue },
  { S2_T_KeywordDo,         "do",         2, s2_keyword_f_dowhile },
  { S2_T_KeywordEval,       "eval",       4, s2_keyword_f_eval },
  { S2_T_KeywordExit,       "exit",       4, s2_keyword_f_eval },
  { S2_T_KeywordFalse,      "false",      5, s2_keyword_f_builtin_vals },
  { S2_T_KeywordFatal,      "fatal",      5, s2_keyword_f_eval },
  { S2_T_KeywordFor,        "for",        3, s2_keyword_f_for },
  { S2_T_KeywordFunction,   "function",   8, s2_keyword_f_function },
  { S2_T_KeywordIf,         "if",         2, s2_keyword_f_if },
  { S2_T_OpInherits,        "inherits",   8, 0 /* handled as an operator */ },
  { S2_T_KeywordNameof,     "nameof",     6, s2_keyword_f_nameof },
  { S2_T_KeywordNull,       "null",       4, s2_keyword_f_builtin_vals },
  { S2_T_KeywordProc,       "proc",       4, s2_keyword_f_function },
  { S2_T_KeywordRefcount,   "refcount",   8, s2_keyword_f_refcount },
  { S2_T_KeywordReturn,     "return",     6, s2_keyword_f_eval },
  { S2_T_KeywordScope,      "scope",      5, s2_keyword_f_eval },
  { S2_T_KeywordThrow,      "throw",      5, s2_keyword_f_eval },
  { S2_T_KeywordTrue,       "true",       4, s2_keyword_f_builtin_vals },
  { S2_T_KeywordTypename,   "typename",   8, s2_keyword_f_typename },
  { S2_T_KeywordUndefined,  "undefined",  9, s2_keyword_f_builtin_vals },
  { S2_T_KeywordUnset,      "unset",      5, s2_keyword_f_unset },
  { S2_T_KeywordVar,        "var",        3, s2_keyword_f_var },
  { S2_T_KeywordWhile,      "while",      5, s2_keyword_f_while },

  {/*_sentinel_*/0,0,0,0}
};

static s2_keyword const * s2_ttype_keyword( int ttype ){
  switch( ttype ){
#define WORD(E,Member) case E: return &S2_KWDS.Member
    WORD(S2_T_KeywordBREAKPOINT,_breakpoint);
    WORD(S2_T_KeywordCOLUMN,_col);
    WORD(S2_T_KeywordFILE,_file);
    WORD(S2_T_KeywordFILEDIR,_filedir);
    WORD(S2_T_KeywordSRCPOS,_flc);
    WORD(S2_T_KeywordLINE,_line);

    WORD(S2_T_KeywordAffirm,affirm);
    WORD(S2_T_KeywordAssert,assert_);
    WORD(S2_T_KeywordBreak,break_);
    WORD(S2_T_KeywordCatch,catch_);
    WORD(S2_T_KeywordConst,const_);
    WORD(S2_T_KeywordContinue,continue_);
    WORD(S2_T_KeywordDo,doWhile);
    WORD(S2_T_KeywordEval,eval);
    WORD(S2_T_KeywordExit,exit_);
    WORD(S2_T_KeywordFalse,false_);
    WORD(S2_T_KeywordFatal,fatal_);
    WORD(S2_T_KeywordFor,for_);
    WORD(S2_T_KeywordFunction,function_);
    WORD(S2_T_KeywordIf,if_);
    WORD(S2_T_OpInherits,inherits);
    WORD(S2_T_KeywordNameof,nameof);
    WORD(S2_T_KeywordNull,null_);
    WORD(S2_T_KeywordProc,proc_);
    WORD(S2_T_KeywordRefcount,refcount_);
    WORD(S2_T_KeywordReturn,return_);
    WORD(S2_T_KeywordScope,scope);
    WORD(S2_T_KeywordThrow,throw_);
    WORD(S2_T_KeywordTrue,true_);
    WORD(S2_T_KeywordTypename,typeName);
    WORD(S2_T_KeywordUndefined,undef_);
    WORD(S2_T_KeywordUnset,unset);
    WORD(S2_T_KeywordVar,var);
    WORD(S2_T_KeywordWhile,while_);

#undef WORD
    default:
      return 0;
  }
}

/**
   Comparison func for bsearch(), comparing (s2_keyword *)
   key with (s2_keyword **) on s2_keyword::word.
*/
static int s2_keyword_cmp(void const * key, void const * kw){
  s2_keyword const * k = ((s2_keyword const *)key);
  s2_keyword const * w = *((s2_keyword const **)kw);
  if(k->wordLen == w->wordLen) return memcmp(k->word, w->word,
                                             (size_t)k->wordLen);
  else if(!w->word) return 1 /* sentinel entry */;
  else if(k->word[0] != w->word[0]) return (int)k->word[0] - (int)w->word[0];
  else{
    int const len = (k->wordLen<w->wordLen) ? k->wordLen : w->wordLen;
    int const cmp = memcmp(k->word, w->word, (size_t)len);
    return cmp ? cmp : ((len==k->wordLen) ? -1 : 1);
  }
}

static s2_keyword const * s2_ptoken_keyword( s2_ptoken const * pt ){
  static const s2_keyword * const kwlist[] = {
  /* KEEP THESE SORTED on s2_keyword::word */
#define WORD(W) &S2_KWDS.W
  WORD(_breakpoint),
  WORD(_col),
  WORD(_file),
  WORD(_filedir),
  WORD(_flc),
  WORD(_line),
  WORD(affirm),
  WORD(assert_),
  WORD(break_),
  WORD(catch_),
  WORD(const_),
  WORD(continue_),
  WORD(doWhile),
  WORD(eval),
  WORD(exit_),
  WORD(false_),
  WORD(fatal_),
  WORD(for_),
  WORD(function_),
  WORD(if_),
  WORD(inherits),
  WORD(nameof),
  WORD(null_),
  WORD(proc_),
  WORD(refcount_),
  WORD(return_),
  WORD(scope),
  WORD(throw_),
  WORD(true_),
  WORD(typeName),
  WORD(undef_),
  WORD(unset),
  WORD(var),
  WORD(while_),
  WORD(_sentinel_)
#undef WORD
  };
  int const plen = (int)(pt->begin ? pt->end - pt->begin : 0);
  s2_keyword const ** k;
  s2_keyword dummy;
  if(pt->ttype != S2_T_Identifier) return 0;
  assert(plen>0 && "This can't possibly be an identifier.");
  dummy.word = pt->begin;
  dummy.wordLen = plen;
  dummy.call = 0;
  dummy.id = 0;
  k = (s2_keyword const **)bsearch(&dummy, kwlist,
                                   sizeof(kwlist)/sizeof(kwlist[0])-1/*sentinel*/,
                                   sizeof(s2_keyword*),
                                   s2_keyword_cmp);
  return k ? *k : 0;
}


int s2_ttype_is_keyword( int ttype ){
  return s2_ttype_keyword(ttype) ? ttype : 0;
}

/**
   Interal-only flags for s2_eval_expr() and friends.
*/
enum s2_eval_flags2 {
/*
   Maintenance reminder: must be greater than the highest
   flag in s2_eval_flags.
*/
/**
   Tells s2_eval_expr() to treat the _first_ unresolved identifier as
   the undefined value instead of an error. Used by 'typename'.
*/
S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED = 1 << S2_EVAL_flag_bits,
S2_EVAL_NO_SKIP_FIRST_EOL = 2 << S2_EVAL_flag_bits,
S2_EVAL_PRE_SWEEP = 4 << S2_EVAL_flag_bits,
S2_EVAL_EMPTY_GROUP_OK = 8 << S2_EVAL_flag_bits
};


static int s2_eval_expr_impl( s2_engine * se,
                              s2_ptoker * st,
                              s2_op const * fromLhsOp,
                              int evalFlags,
                              cwal_value ** rv);

/**
   If code is NOT 0 this assigns *rv (if not NULL) to 0, otherwise it rescopes *rv (if
   not NULL) to sc (if not NULL).

   Always returns rc.
*/
static int s2_rv_maybe_accept( s2_engine * se, cwal_scope * sc,
                                int rc, cwal_value ** rv ){
  switch(rc){
    case 0:
      assert(se->e->current);
      if(rv && *rv && sc) cwal_value_rescope(sc, *rv);
      break;
    default:
      if(rv) *rv = 0;
      break;
  }
  return rc;
}

/**
   Internal helper for converting stack-level errors to
   exceptions. Preconditions:

   - A stack-processing op must just have failed.

   - se->err.code must be non-0.

   Preferably, se->err.msg is not empty,

   Returns, on success, CWAL_RC_EXCEPTION, and some other non-0 value
   if throwing the exception led to a more serious error (e.g. out of
   memory). It will only return 0 if it is called when se->err.code is
   0, and in that case it will assert() in debug builds.
*/
static int s2_throw_op_err(s2_engine * se, s2_ptoker const * pr){
  int rc = se->err.code;
  assert(se->err.code);
  assert(pr || se->currentScript);
  switch(rc){
    case CWAL_RC_EXCEPTION:
      break;
    default:
      /* MARKER(("opErrPos=%p\n", (void const *)se->opErrPos)); */
      rc = s2_throw_err_ptoker(se, pr ? pr : se->currentScript);
  }
  return rc;

}

/**
   Internal helper for s2_eval_expr().

   Possibly processes pending operators in se's stack, depending on op
   and its precedence in relation to the operator (if any) to the
   left. Returns 0 on success (which includes it doing nothing of
   note).
*/
static int s2_eval_lhs_ops(s2_engine * se, s2_ptoker const * pr, s2_op const * op){
  s2_stoken * topOpTok = s2_engine_peek_op(se);
  s2_op const * topOp = topOpTok ? s2_stoken_op(topOpTok) : 0;
  int rc = 0;
  assert(op);
  if(topOp
     && topOp->placement!=0
     && topOp->arity>0
     && (s2_ttype_is_assignment(op->id) || s2_ttype_is_assignment_combo(op->id))
     ){
    return s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                          "Invalid operator '%s' preceeding '%s'.",
                          topOp->sym, op->sym);
    /* This is admittedly a workaround for the X.Y=Z family of ops,
       which do not get a chance to set se->dotLhsOp and se->dotLhsKey
       before the ++ is run. It turns out that JavaScript disallows
       such ops on the LHS of an assignment, too.
     */
  }
  while(topOp &&
        ((op->prec < topOp->prec)
         || (op->assoc<0 && (op->prec==topOp->prec)))
        ){
    if(se->flags.traceStack){
      MARKER(("Processing ge-precedent op '%s' to "
              "the left of '%s'.\n",
              topOp->sym, op->sym));
      s2_dump_val(se->dotOpLhs,"se->dotOpLhs");
      s2_dump_val(se->dotOpKey,"se->dotOpKey");
    }
    rc = s2_process_top(se);
    if(rc){
      if(CWAL_RC_EXCEPTION!=rc){
        assert(se->err.code);
        rc = s2_throw_op_err(se, 0);
        assert(rc);
      }
      break;
    }
    assert(se->st.vals.size>0);
    topOpTok = s2_engine_peek_op(se);
    topOp = topOpTok ? s2_stoken_op(topOpTok) : 0;
  }
  if(rc){
    assert(se->err.code || cwal_exception_get(se->e));
  }
  return rc;
}

/**
   Evaluates the contents of pr->token.

   If asExpr is true it uses s2_eval_expr_impl() to process one
   expression, otherwise it uses s2_eval_ptoker() to parse it all as a
   collection of expressions.

*/
static int s2_eval_current_sub( s2_engine * se, s2_ptoker * pr,
                                char asExpr,
                                cwal_value **rv ){
  char const * begin;
  char const * end;
  pr->errPos = 0;
  if(pr->token.adjBegin){
    begin = pr->token.adjBegin;
    end = pr->token.adjEnd;
  }else{
    begin = pr->token.begin;
    end = pr->token.end;
  }
  /* assert(!name || nameLen>0); */
  assert(end >= begin);
  if(begin==end){
    /* we know it's empty, so don't bother.

       TODO: int s2_ptoken_has_content(s2_ptoken const *)

       tokenizes the given token's range and which returns the ttype
       of the first non-noise token symbol it encounters.
     */
    if(rv) *rv = 0;
    return 0;
  }else{
    s2_ptoker sub = s2_ptoker_empty;
    int rc;
    /* s2_ptoker const * oldScript = se->currentScript; */
    s2_ptoker_init( &sub, begin, (int)(end - begin));
    sub.parent = pr;
    /*sub.name = pr->name;
      sub.nameLen = pr->nameLen;
      sub.lineOffset = pr->lineOffset;
      sub.colOffset = pr->colOffset;
*/
    /* se->currentScript = &sub; */
    rc = asExpr
      ? s2_eval_expr_impl( se, &sub, 0, 0, rv )
      : s2_eval_ptoker( se, &sub, rv );
    /* se->currentScript = oldScript; */
    if(sub.errPos) pr->errPos = sub.errPos;
    return s2_check_interrupted(se, rc);
  }
}

int s2_eval_expr( s2_engine * se, s2_ptoker * st,
                  int evalFlags,
                  cwal_value ** rv){
  return s2_eval_expr_impl(se, st, 0, evalFlags, rv);
}

/**
   Expects pr to be a comma-separated list of expressions, until its EOF. Evaluates
   each token and appends it to dest. Returns 0 on success. Its own errors are
   thrown as exceptions, but propagated errors might not be exceptions.
*/
static int s2_eval_to_array( s2_engine * se, s2_ptoker * pr, cwal_array * dest ){
  int rc = 0;
  s2_op const * opComma = s2_ttype_op(S2_T_Comma);
  s2_ptoken prev = s2_ptoken_empty;
  char const * errMsg = 0;
  assert(!se->skipLevel);
  while( !rc ){
    s2_ptoken next = s2_ptoken_empty;
    cwal_value * v = 0;
    if( (rc = s2_eval_expr_impl( se, pr, opComma, 0, &v)) ) break;
    if(!v){
      if(s2_ptoker_is_eof(pr)){
        if(S2_T_Comma==prev.ttype){
          pr->errPos = prev.begin;
          errMsg = "Unexpected, comma at end, of list,";
          goto bad_comma;
        }
        else break;
      }
      else if(/*(S2_T_Comma==pr->token.ttype)
                &&*/ (!prev.ttype || (S2_T_Comma == pr->token.ttype))
              ){
        errMsg = "Unexpected, comma in, list.";
        goto bad_comma;
      }
      else return s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                  "Unexpected empty expression result.");
    }else{
      if( (rc = cwal_array_append(dest, v)) ) break;
      else if( (rc = s2_next_token( se, pr, 0, &next )) ) break;
      else if(s2_ttype_is_eof(next.ttype)){
        if(S2_T_Comma==pr->token.ttype){
          /* pr->errPos = next.begin; */
          errMsg = "Unexpected, comma at end, of list,";
          goto bad_comma;
        }
        else break;
      }
      else if(S2_T_Comma != next.ttype){
        return s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                               "Expecting ',' after list element.");
      }else{
        prev = next;
        pr->token = next /* consume comma */;
      }
    }
    rc = s2_check_interrupted(se, 0);
  }
  return rc;
  bad_comma:
  assert(errMsg);
  return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX, "%s", errMsg);
}

/**
   Parser for [array, literals]. Requires pr->token.ttype to be S2_T_BraceGroup. In skip
   mode, *rv is set to the undefined value and 0 is returned, else the [...] block
   is parsed as a comma-separated list of expressions and (on success) *rv is set to
   a new Array value.
*/
static int s2_eval_array_literal( s2_engine * se, s2_ptoker * pr, cwal_value ** rv ){
  assert(S2_T_BraceGroup==pr->token.ttype);
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    int rc;
    s2_ptoker sub = s2_ptoker_empty;
    cwal_array * ar = 0;
    cwal_value * av = 0;
    s2_scope scope = s2_scope_empty;
    if(!s2_ptoken_has_content(&pr->token)){
      return (*rv = cwal_new_array_value(se->e)) ? 0 : CWAL_RC_OOM;
    }
    else if((rc = s2_scope_push(se, &scope))) return rc;
    s2_ptoker_init( &sub, pr->token.adjBegin, (int)(pr->token.adjEnd - pr->token.adjBegin) );
    sub.parent = pr;
    ar = cwal_new_array(se->e);
    if(!ar){
      rc = CWAL_RC_OOM;
    }
    else{
      av = cwal_array_value(ar);
      rc = s2_eval_to_array( se, &sub, ar );
      if(rc){
        cwal_value_unref(av);
      }
    }
    if(!rc){
      assert(av);
      cwal_value_rescope(&scope.parent->scope, av);
      *rv = av;
    }
    s2_scope_pop(se);
    return rc;
    
  }
}

/**
   Expects pr to contain, until EOF, JavaScript-style object key/value pairs:

   k1: v1, k2: v2 ...

   Returns 0 on success, throws or propagates an error on error.
*/
static int s2_eval_to_object( s2_engine * se, s2_ptoker * pr, cwal_object * dest ){
  int rc = 0;
  cwal_value * destV = cwal_object_value(dest);
  s2_ptoken prev = s2_ptoken_empty;
  s2_op const * opComma = s2_ttype_op(S2_T_Comma);
  assert(!se->skipLevel);
  assert(destV);
  assert(opComma);
  while( !rc && !s2_ptoker_is_eof(pr)){
    s2_ptoken ident = s2_ptoken_empty;
    s2_ptoken startPos = pr->token;
    cwal_value * v = 0;
    if( (rc = s2_next_token(se, pr, 0, 0)) ) return rc;
    else if((S2_T_Comma == pr->token.ttype)
            && (!prev.ttype || (S2_T_Comma == prev.ttype))
            ){
      goto bad_comma;
    }
    else if(s2_ptoker_is_eof(pr)){
      if(S2_T_Comma == prev.ttype){
        pr->errPos = prev.begin;
        goto bad_comma;
      }
      else break;
    }
#if 0
    MARKER(("OBJ KEY: %s %.*s\n", s2_ttype_cstr(pr->token.ttype),
            (int)(pr->token.end-pr->token.begin),
            pr->token.begin));
#endif
    if(!s2_ttype_is_pod(pr->token.ttype)){
      pr->errPos = startPos.begin;
      return s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "Expecting identifier or 'simple' "
                             "value as object key.");
    }
    ident = pr->token;
    if( (rc = s2_next_token(se, pr, 0, 0)) ) return rc;
    else if(S2_T_Colon != pr->token.ttype){
      return s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "Expecting ':' after object key.");
      
    }
    if( (rc = s2_eval_expr_impl( se, pr, opComma, 0, &v)) ) return rc;
    else if(!v){
      return s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "Unexpected empty result.");
    }else{
      s2_ptoken next = s2_ptoken_empty;
      if( (rc = s2_next_token( se, pr, 0, &next )) ) return rc;
      else if(!s2_ttype_is_eof(next.ttype) && S2_T_Comma != next.ttype){
        return s2_throw_ptoker(se, pr, CWAL_SCR_SYNTAX,
                               "Got %s, but expected ',' or "
                               "end-of-object after object entry.",
                               s2_ttype_cstr(next.ttype));
      }else{
        cwal_value * key = 0;
        if( (rc = s2_ptoken_create_value(se, &ident, &key)) ) return rc;
        assert(key);
        cwal_value_ref(key);
#if 0
        rc = cwal_prop_set_v(destV, key, v);
#else
        rc = s2_set_v(se, destV, key, v)
          /* use s2_set_v() for the 'prototype' handling. */
          ;
#endif
        cwal_value_unref(key);
        if(rc) return rc;
        prev = next;
        pr->token = next /* consume comma */;
      }
    }
    rc = s2_check_interrupted(se, 0);
  }
  return rc;
  bad_comma:
  return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Unexpected comma in object literal.");
}

/**
   Evaluator for object literal blocks. Asserts that
   s2_ptoken_is_true_squiggly(pr->token.ttype).

   On success, *rv will hold the parsed object.

   Returns 0 on success, of course.
*/
static int s2_eval_object_literal( s2_engine * se, s2_ptoker * pr,
                                   cwal_value ** rv ){
  assert(s2_ptoken_is_true_squiggly(&pr->token));
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    int rc;
    s2_ptoker sub = s2_ptoker_empty;
    cwal_object * obj = 0;
    cwal_value * ov = 0;
    s2_scope scope = s2_scope_empty;
    if(!s2_ptoken_has_content(&pr->token)){
      return (*rv = cwal_new_object_value(se->e)) ? 0 : CWAL_RC_OOM;
    }
    else if((rc = s2_scope_push(se, &scope))) return rc;
    rc = s2_ptoker_sub_from_token(&sub, pr);
    assert(!rc);
    obj = cwal_new_object(se->e);
    if(!obj){
      rc = CWAL_RC_OOM;
    }else{
      ov = cwal_object_value(obj);
      rc = s2_eval_to_object( se, &sub, obj );
      if(rc) cwal_object_unref(obj);
    }
    if(!rc){
      assert(ov);
      cwal_value_rescope(&scope.parent->scope, ov);
      *rv = ov;
    }
    s2_scope_pop(se);
    return rc;
  }
}


/**
   Iternal impl for ternary op. Expects the top of the value stack to hold
   the LHS value. On success it replaces that value with the result of either
   its THEN or ELSE parts. On success, pr->token.end will be just after the
   ELSE expression.
*/
static int s2_eval_ternary( s2_engine * se, s2_ptoker * pr ){
  int rc;
  cwal_value * lhs;
  cwal_value * rhs1 = 0,  * rhs2 = 0;
  cwal_value ** rhs = 0;
  char buul;
  s2_ptoken pos = pr->token;
  s2_ptoken const origin = pos;
  s2_op const * fromLhsOp = s2_ttype_op(S2_T_Question);
  assert(fromLhsOp);
  assert(S2_T_Question==pr->token.ttype);

  rc = s2_eval_lhs_ops(se, pr, s2_ttype_op(S2_T_Question));
  if(rc) return rc;

  lhs = s2_engine_peek_value(se);
  if(!lhs){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "Expecting value before ?: op");
  }
  buul = cwal_value_get_bool(lhs);

  rhs = buul ? &rhs1 : &rhs2;
  rc = s2_eval_expr_impl(se, pr, fromLhsOp,
                         buul ? 0 : S2_EVAL_SKIP,
                         &rhs1);
  /*MARKER(("Got rhs1 rc=%d: [[[%.*s]]\n", rc,
    (int)(pr->capture.end-pr->capture.begin), pr->capture.begin));*/

  if(rc) return rc;

  rc = s2_next_token(se, pr, 0, 0);
  if( !rc && (S2_T_Colon != pr->token.ttype) ){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                              "Expecting ':' after the "
                              "Y part of (X ? Y : Z)");
  }
  if(rc) return rc;
  pos = pr->token;
  rc = s2_eval_expr_impl(se, pr, fromLhsOp,
                         buul ? S2_EVAL_SKIP : 0,
                         &rhs2);

  /*MARKER(("Got rhs2 rc=%d: [[[%.*s]]\n", rc,
    (int)(pr->capture.end-pr->capture.begin), pr->capture.begin));*/
  if(rc) return rc;
  else if(!(rhs1 && rhs2)){
    pr->errPos = pos.begin;
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "Unexpected empty expression "
                                "in (X ? Y : Z)");
  }
  else{
    /* s2_dump_val(*rhs,"Ternary result"); */
    s2_engine_pop_token(se, 0) /* previous LHS */;
    s2_engine_push_val(se, *rhs)->srcPos = origin.begin
      /* Trivia: that push cannot fail as long as token
         recycling is enabled, because the pop just freed
         one up for us. */;
    return 0;
  }
}

#if 0
static int s2_next_is_assignment( s2_engine * se, s2_ptoker * pr ){
  s2_ptoken tgt = s2_ptoken_empty;
  int rc;
  s2_next_token( se, pr, 0, &tgt );
  return (rc = s2_ttype_is_assignment(tgt.ttype))
    ? rc
    : s2_ttype_is_assignment_combo(tgt.ttype);
}
#endif

static int s2_next_wants_identifier( s2_engine * se, s2_ptoker * pr ){
  s2_ptoken tgt = s2_ptoken_empty;
  int rc;
  s2_next_token( se, pr, 0, &tgt );
  /* MARKER(("Next-wants-identifier? ttype=%s\n", s2_ttype_cstr(tgt.ttype))); */
  switch(tgt.ttype){
    case S2_T_OpIncr:
    case S2_T_OpDecr:
      return tgt.ttype;
  }
  if( (rc = s2_ttype_is_assignment(tgt.ttype)) ) return rc;
  else if((rc = s2_ttype_is_assignment_combo(tgt.ttype))) return rc;
  else rc = s2_ttype_is_identifier_prefix(tgt.ttype);
  return rc;
}

/**
   Returns ttype if it refers to a "dereferencing" operator,
   else returns 0.

   If this returns non-0:

   - assignment operators translate the stack into a ternary
   operation.

   - In skip mode, a call operator after a dereferenced
   value is assumed to be valid, and skipped.

   - It is assumed (and may be assert()ed) that the
   operator sets s2_engine::dotOpLhs and dotOpKey
   when run.

*/
static int s2_ttype_is_deref( int ttype ){
  switch(ttype){
#if 0
    case S2_T_OpArrow:
#endif
#if 0
    case S2_T_OpHash:
#endif
    case S2_T_OpDot:
      return ttype;
    default:
      return 0;
  }
}


/**
   Returns ttype if it is a dot-op-like id, else returns 0.
*/
static int s2_ttype_is_dotish( int ttype ){
  switch(ttype){
#if 1
    case S2_T_OpArrow:
#endif
#if 1
    case S2_T_OpHash:
#endif
    case S2_T_OpDot:
    case S2_T_BraceGroup:
      return ttype;
    default:
      return 0;
  }
}

/**
   Returns the token type id of the next token in pr is a dot-op-like
   token, else returns 0.
*/
static int s2_next_is_dotish( s2_engine * se, s2_ptoker * pr ){
  s2_ptoken tgt = s2_ptoken_empty;
  return s2_next_token( se, pr, 0, &tgt )
    ? 0
    : s2_ttype_is_dotish(tgt.ttype);
}

/**
   Internal helper for s2_eval_expr_impl(). Requires pr->token to be
   a S2_T_ParenGroup and prevOp to be the operator associated with
   the previous expression token (may be 0). This function inspects
   prevOp and se's stack to determine if this paren group looks
   like it should be function call arguments.

   Returns 0 if the parens do not look like a function call, else
   non-0. Assigns *rc to 0 on success, non-0 if it detects a syntax
   error. If *rc is non-0 upon returning, evaluation must stop and
   the error must be propagated.

   It is only okay to call s2_eval_fcall() if this returns non-0 and
   sets *rc to 0, and that call (if made) must be made before doing any
   further tokenization or stack processing.
*/
static char s2_looks_like_fcall( s2_engine * se, s2_ptoker * pr,
                                 s2_op const * prevOp, int * rc ){
  /* MARKER(("Looks like call?\n")); */
  assert(S2_T_ParenGroup==pr->token.ttype);
  *rc = 0;
  if(!prevOp){
    if( se->skipLevel>0 ){
      return s2_engine_peek_value(se) ? 1 : 0;
    }else{
      cwal_value * v = s2_engine_peek_value(se);
      s2_stoken const * opT = v ? s2_engine_peek_op(se) : 0;
      if(!v){
        return 0;
      }else if(opT && s2_ttype_is_dotish(opT->ttype)){
        return 1;
        /* We'll assume a function is possible and fail
           after evaluating the top op if it isn't one. */
      }
      else if(se->dotOpLhs && se->dotOpKey){
        /* So that (obj.prop)(...) can work */
        return 1;
      }else if(!cwal_value_function_part(se->e, v)){
        s2_dump_val(v, "non-function before call()");
        *rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                              "Non-function value type '%s' "
                              "before a call() operator.",
                              cwal_value_type_name(v));
        return 0;
      }else{
        return 1;
      }
    }
  }
#if 0
  else if(S2_T_OpDot == prevOp->id){
    *rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                        "Illegal '.' operator before (...).");
    return 0;
  }
#endif
  else return 0;
}

/**
   Internal helper for s2_eval_expr_impl(). Requires that s2_looks_like_fcall()
   has just succeeded, else results are undefined.

   Treats the current S2_T_ParenGroup token as function call arguments and
   calls the function, which may be either of these forms on the stack:

   FUNC
   or
   OBJ.FUNC

   In the former form, the 'this' value for the call() will be the
   function itself.

   Remember that OBJ[PROP] gets translated at parse time to OBJ.PROP.
 */
static int s2_eval_fcall( s2_engine * se, s2_ptoker * pr, cwal_value ** rv ){
  int rc = 0;
  s2_stoken const * opTok;
  s2_op const * op;
  s2_ptoken const origin = pr->token;
  cwal_value * fv = 0;
  cwal_value * vSelf = 0;
  s2_scope _SCOPE = s2_scope_empty;
  s2_scope * scope = &_SCOPE;
  s2_strace_entry strace = s2_strace_entry_empty;
  cwal_function * theFunc;
  s2_func_state * fst = 0;

  /* cwal_value * vKey; */
  assert(S2_T_ParenGroup==pr->token.ttype);

  if(!se->skipLevel){
    rc = s2_scope_push(se, scope);
    if(rc) return rc;
  }


  /* Check if this is a FUNC() or OBJ.FUNC() call... */
  opTok = s2_engine_peek_op(se);
  op = opTok ? s2_ttype_op( opTok->ttype ) : 0;

  /*
    To consider: if dot is not a dot op but se->dotOpLhs
    is set, then some LHS (possibly a (subexpr)) possibly
    has given us a 'this' to work with. It's not yet clear
    whether we can really know if that's the proper 'this',
    though.

    (obj.func)(...) // OK

    obj.prop.func(...) // OK b/c the stack will never have more than 1 dot op;

    obj.prop, func(...) // not OK

    What if we just reset se->dotOpLhs on each new operator?

    obj.prop['x'] ==> obj.prop.x ==> OK

    obj.prop + 1 (...) // invalid - not-a Function ==> OK

    obj[obj.prop](...) // [...]==>dot op ==> OK
  */
  if(op &&
#if 1
     s2_ttype_is_dotish(op->id)
#else
     s2_ttype_is_deref(op->id)
#endif
     ){
    /* If a dot-like operator is pending, run it
       to get the function and 'this'. */
    if( (rc = s2_process_top(se)) ){
      if(CWAL_RC_EXCEPTION!=rc){
        assert(se->err.code);
        rc = s2_throw_op_err(se, pr);
        assert(rc);
      }
      goto end;
    }
  }
  if(se->dotOpLhs){
    /*
      To allow (obj.func)(...) to work...
    */
    /* MARKER(("Possibly stealing a 'this'\n")); */
    assert(!fv);
    assert(!vSelf);
    vSelf = se->dotOpSelf;
    /* s2_dump_val(vSelf, "se->dotOpThis"); */
    se->dotOpSelf = se->dotOpKey = se->dotOpLhs = 0;
    fv = s2_engine_pop_value(se) /* presumably a dot-op result */;
    assert(fv);
    if(vSelf) cwal_value_ref( vSelf );
    cwal_value_ref( fv );
  }else{
    fv = s2_engine_pop_value(se) /* hopefully a function */;
    cwal_value_ref( fv );
  }

  /* In skip mode, we're done. We had to get the values off the stack,
     though, so we couldn't do this first. */
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    goto end;
  }

  rc = s2_strace_push_pos(se, pr, origin.begin, &strace )
    /* we have to do this fairly late so that the
       the (still pending) call doesn't confuse the
       the trace. */
    ;
  if(rc) goto end;

  if(!fv || !(theFunc = cwal_value_function_part(se->e, fv))) {
    rc = s2_throw_ptoker(se, pr, CWAL_RC_TYPE,
                         "Non-function (%s) LHS before "
                         "a call() operation.",
                         fv ? cwal_value_type_name(fv)
                         : "<NULL>");
    goto end;
  }
  /* MARKER(("Function call args: %.*s\n", (int)(pr->token.end-pr->token.begin), pr->token.begin)); */
  /* s2_dump_val(vSelf,"call() vSelf"); */
  /* s2_dump_val(fv,"call() func"); */

  /* See if it's an empty script-side function. */
  fst = (s2_func_state *)cwal_function_state_get(theFunc, &s2_func_state_empty);
  if(fst && (fst->flags & S2_FUNCSTATE_F_EMPTY_BODY
             && fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)){
      /* If a script function has neither parameters nor a body, we
         don't to do any of the following work. However, in order to
         get fst, we have to get theFunc, which requires setting up a
         scope and all that.
      */
    *rv = cwal_value_undefined();
  }else{
    /* Process the args and call() the func... */
    s2_ptoker sub = s2_ptoker_empty;
    cwal_value * arV;
    cwal_array * oldArgV;
    s2_func_state const * oldScriptFunc = se->currentScriptFunc;
    cwal_array * ar = cwal_new_array(se->e);
    if(!ar){
      rc = CWAL_RC_OOM;
      goto end;
    }
    arV = cwal_array_value(ar);
    cwal_value_ref(arV);
    rc = s2_ptoker_sub_from_token( &sub, pr );
    assert(!rc);
    assert(pr==sub.parent);
    /* sub.name = "call(args)"; */
    /* sub.nameLen = 10; */
    rc = s2_eval_to_array( se, &sub, ar );
    if(rc){
      cwal_value_unref(arV);
      goto end;
    }
    oldArgV = se->callArgV;
    se->callArgV = ar;
    se->currentScriptFunc = 0;
    pr->errPos = origin.begin;
    if(0){
      MARKER(("Function call()...\n"));
      s2_dump_val(vSelf ? vSelf : fv, "vSelf ? vSelf : fv");
      s2_dump_val(cwal_array_value(ar), "argv");
    }
    rc = cwal_function_call_array( &scope->scope, theFunc,
                                   vSelf ? vSelf : fv,
                                   rv, ar );
    se->currentScriptFunc = oldScriptFunc;
    se->callArgV = oldArgV;
    /* assert(0==oldArgV); */
    cwal_value_unhand(arV)
      /* Need unhand instead of unref in case this array is the value
         being returned. Array entry removal will handle the case that
         the returned value is a member of that array which was upscoped
         by the call().
      */;
  }
  end:
  switch(rc){
    case 0:
      assert(*rv);
      if( !*rv ) *rv = cwal_value_undefined();
      break;
    case CWAL_RC_BREAK:
    case CWAL_RC_CONTINUE:
      assert(se->err.code);
      rc = s2_throw_err(se, 0, s2_ptoker_name_first(pr, 0), 0, 0);
      break;
#if 0
      rc = s2_throw_ptoker(se, pr, rc,
                           "Unhandled 'continue'.");
#endif
      break;
#if 0
    case CWAL_RC_RETURN:
      rc = 0;
      *rv = s2_propagating_take(se);
      assert(*rv);
      break;
#endif
    case CWAL_RC_EXCEPTION:
#if 1
      s2_exception_add_script_props(se, pr)
        /* Needed to decorate calls to native functions. */
        ;
#else
      if(!pr->errPos) pr->errPos = origin.begin;
      if(!s2_exception_add_script_props(se, pr)){
        cwal_value * stackTrace = 0;
        s2_strace_generate(se, &stackTrace);
        if(stackTrace){
          cwal_value * ex = cwal_exception_get(se->e);
          assert(ex);
          if(cwal_prop_set(ex, "stackTrace", 10, stackTrace)){
            cwal_value_unref(stackTrace);
          }
        }
      }
#endif
      /* fall through */
    case CWAL_RC_OOM:
    case CWAL_RC_EXIT:
    case CWAL_RC_FATAL:
    case CWAL_RC_ASSERT:
      /* do we want assertion errors to translate to exceptions
         if they go up the call stack? */
      *rv = 0;
      break;
    default:
      *rv = 0;
      if(se->err.code){
        /* Likely a syntax-related error pending */
        rc = s2_throw_err(se, 0, 0, 0, 0);
      }else{
        rc = s2_throw_ptoker(se, pr, rc,
                             "Function call returned non-exception "
                             "error code #%d (%s).",
                             rc, s2_rc_cstr(rc));
      }
      break;
  }
  if(strace.pr){
    s2_strace_pop(se);
  }
  if(vSelf) cwal_value_unhand(vSelf);
  if(fv) cwal_value_unhand(fv);
  if(scope->parent){
    if(!rc){
      assert(*rv);
      cwal_value_rescope(scope->scope.parent, *rv);
    }
    assert(scope == se->currentScope);
    s2_scope_pop(se);
    assert(scope != se->currentScope);
  }
  return rc;
}


/**
   Internal impl of s2_eval_expr().

   If fromLhsOp is not 0, this call is assumed to be the RHS of that
   operator. Upon encountering an operator of that precedence or less,
   that operator is put back into the tokenizer and evaluation
   finishes. This is used in implementing short-circuit logic.

   TODO: break this beast down into smaller chunks! It's over 700 lines
   long!
*/
int s2_eval_expr_impl( s2_engine * se,
                       s2_ptoker * st,
                       s2_op const * fromLhsOp,
                       int evalFlags,
                       cwal_value ** rv){
  int rc = 0;
  s2_ptoken pt = s2_ptoken_empty;
  s2_ptoken prevTok = s2_ptoken_empty;
  s2_ptoken const pOrigin = st->token;
  s2_ptoken const pbOrigin = st->pbToken;
  int tlen = 0;
  int const oldSkipLevel = se->skipLevel;
  s2_op const * op = 0;
  s2_op const * prevOp = 0;
  s2_estack priorStack = s2_estack_empty;
  char const * capBegin = st->token.end ? st->token.end : 0;
  char const consumeIt = (evalFlags & S2_EVAL_NO_CONSUME) ? 0 : 1;
  char const evalIt = (evalFlags & S2_EVAL_SKIP) ? 0 : 1;
  s2_keyword const * kword = 0 /* keyword for the token, if any */;
  s2_ptoker const * oldScript = se->currentScript;
  char const ownStack =
#if 1
    1
#else
    /*
      Bug: currently leads to extra stack items when short-circuit
      logic and sub-parses activate. Caused by how we unwind the
      stack (in full) at the end.
    */
    (fromLhsOp ? 0 : 1)
#endif
    ;
  int totalValCount = 0 /* just for some error checks */;
  int nextTokenFlags = /* The s2_next_token() flags for the very next token */
    (evalFlags & S2_EVAL_NO_SKIP_FIRST_EOL/* fromLhsOp && S2_T_RHSEvalNoEOL!=fromLhsOp->id */)
    ? S2_NEXT_NO_SKIP_EOL
    : 0;
  s2_scope _SCOPE = s2_scope_empty /* cwal stack, if (flags&S2_EVAL_PUSH_SCOPE) */;
  s2_scope * scope = &_SCOPE;
  cwal_array * refContainer = 0;
#ifndef NDEBUG
  /* Just for sanity checking */
  int const oldValCount = se->st.vals.size;
  int const oldOpCount = se->st.ops.size;
#endif
  if( (rc = s2_check_interrupted(se, 0)) ) return rc;
  /* se->dotOpLhs = 0; */
  if(S2_EVAL_PUSH_SCOPE & evalFlags){
    /* assert(!"This isn't used, is it?"); */
    rc = s2_scope_push(se, scope);
    if(rc) return rc;
    assert(scope->parent);
    assert(0==se->sguard->sweep);
    assert(0==se->sguard->vacuum);
    /* se->sguard->sweep = 0; */
    /* REMINDER: we cannot blindly disable se->sguard->vacuum because it
       can nuke non-script-visible values in use by the client. Been there,
       done that.
    */
  }

  if( st->parent ){
    if(++se->metrics.subexpDepth > se->metrics.peakSubexpDepth){
      se->metrics.peakSubexpDepth = se->metrics.subexpDepth;
    }
  }

  st->errPos = 0;

  /* if(rv) *rv = 0; */
  if((S2_EVAL_PRE_SWEEP & evalFlags)
     && !fromLhsOp
     && !se->st.vals.size
     && !se->st.ops.size
     ){
    /* MARKER(("SWEEPING from eval_impl\n")); */
    /* We cannot know if a vacuum is safe from here :/,
     and it certainly isn't if s2_eval_ptoker() is waiting
     on a pending EOX. */
    ++se->sguard->vacuum;
    s2_engine_sweep(se);
    --se->sguard->vacuum;
  }
  if(!scope->parent) ++se->sguard->sweep;

  se->currentScript = st;

  s2_engine_err_reset(se);
  if(!evalIt) ++se->skipLevel;
  if(ownStack){
    s2_engine_stack_swap(se, &priorStack);
  }

#if 0
  /* This isn't working right - some value or other
     is getting cleaned up at the wrong time.
  */
  if(!se->skipLevel){
    if(! (refContainer = cwal_new_array(se->e)) ){
      rc = CWAL_RC_OOM;
      goto end;
    }else{
      cwal_value * av = cwal_array_value(refContainer);
      cwal_value_make_vacuum_proof( av, 1 );
      cwal_value_ref( av );
    }
  }
#endif

  st->capture = s2_ptoken_empty;
  pt = st->token;
  /*
    Adapted from:

    http://en.wikipedia.org/wiki/Stack_(data_structure)#Expression_evaluation_and_syntax_parsing

    http://en.wikipedia.org/wiki/Shunting-yard_algorithm
  */
  for( ; !rc &&
         (
#if 1
          (prevTok.ttype
           && !op
           && s2_ttype_is_eox(st->token.ttype)
           /*
             Sub-tokenizations can end on an EOX and still produce a
             value. i would love to be able to do without this check
             here (and it might be a side-effect of older EOL rules?).

             The problem appears once keywords and short-circuiting
             do (i.e. sub-parses).

             Note that we allow EOX's through in some cases just so
             that we can do some more error checking/reporting.
           */)
          ? 0
          :
#endif
          (0 == (rc = s2_next_token(se, st, nextTokenFlags, 0)))
          )
         ;
       prevOp = op, prevTok = pt
       ){
    cwal_value * tVal = 0 /* Value for the token, if any */;
    if( (rc = s2_check_interrupted(se, 0)) ) break;
    if(!capBegin){
      capBegin = st->token.begin;
    }
    if(s2_ttype_is_eox(st->token.ttype)){
      if(!fromLhsOp && prevOp
         && ((prevOp->placement<0 && prevOp->arity>0)/*prefix op*/
             ||(prevOp->placement==0 && prevOp->arity>1)/*infix op*/)
         ){
        /* infix/prefix operator preceeding EOX */
        /* st->errPos = prevTok.begin ? prevTok.begin : st->token.begin; */
        rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                                  "Invalid operator '%s' before "
                                  "end of expression",
                                  prevOp->sym);
      }
      else if(s2_ttype_is_eof(pt.ttype)){
        if(prevOp
           && prevOp->arity>0
           && prevOp->placement<=0
           ){
          rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                              "Non-nullary operator '%s' "
                              "before EOF", prevOp->sym);
        }else if(fromLhsOp){
          rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                              "Expecting RHS but got EOF");
        }
      }
      /*
        Would like to do this but is currently endlessly looping
        in s2_eval_ptoker():

        s2_ptoker_putback(st);
      */
      break;
    }else if(kword
             && (S2_NEXT_NO_SKIP_EOL & nextTokenFlags)
             && s2_ttype_is_eol(st->token.ttype)
             ){
      /* Special case for keywords with a {script} operand.
         Treat this EOL as EOX */
      break;
    }
    nextTokenFlags = 0;
    kword = 0;
    pt = st->token;
    tlen = (int)(pt.end - pt.begin);
    if((tlen>0) && se->flags.traceStack){
      MARKER(("Token: lhsPart=%s type=%s [%.*s]\n",
              fromLhsOp ? fromLhsOp->sym : "none",
              s2_ttype_cstr(pt.ttype), tlen, pt.begin));
    }
    assert(tlen>0);
    
    switch(pt.ttype){ /* Some basic sanity checks */
      case S2_T_SquigglyOpen:
      case S2_T_HeredocStart:
      case S2_T_BraceOpen:
      case S2_T_ParenOpen:
        assert(!"Cannot happen - gets handled by s2_next_token()");
        /* But in case it does, fall through... */
      case S2_T_SquigglyClose:
      case S2_T_BraceClose:
      case S2_T_ParenClose:
        rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                                  "Mismatched '%.*s' token",
                                  tlen, pt.begin);
        break;
      case S2_T_Question:{
        cwal_value * lhs = prevOp ? 0 : s2_engine_peek_value(se);
        if(!lhs){
          /* This may rule out having keywords on the LHS? */
          rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                                     "Invalid LHS %s for '?' operator",
                                     prevOp ? prevOp->sym
                                     : (lhs ? cwal_value_type_name(lhs)
                                        : "<NULL>"));
          break;
        }
        rc = s2_eval_ternary(se, st);
        if(rc) break;
        else continue;
      }
      case S2_T_Identifier:{
        /* If an Identifier is the RHS of a dot operator, change its
           type to PropertyKey so that identifier expansion is not
           done for this property access. The raw token bytes
           will be converted to a string value later on in this
           routine. Implications:

           obj.prop ==> Identifier DOT LiteralString

           obj.'prop' ==> effectively the same thing

           obj.(prop) ==> Identifier DOT ( Identifier )

           Seems reasonable to me.
        */
#if 0
        s2_stoken const * topOpTok = s2_engine_peek_op(se);
        if(topOpTok && (S2_T_OpDot==topOpTok->ttype)){
          pt.ttype = S2_T_PropertyKey;
        }
#else
        if(prevOp && S2_T_OpDot==prevOp->id
           /*s2_ttype_is_deref(prevOp->id)*/){
          pt.ttype = S2_T_PropertyKey;
        }
#endif
        break;
      }
      case S2_T_OpIncr:
      case S2_T_OpDecr:
        /* Convert -- and ++ to either prefix or postfix form, depending
           on the state of the stack... */
        if(prevOp || !se->st.vals.size){
          /* MARKER(("Changing token '%s' to prefix form.\n", op->sym)); */
          pt.ttype = (S2_T_OpIncr==pt.ttype)
            ? S2_T_OpIncrPre
            : S2_T_OpDecrPre;
        }else{
          pt.ttype = (S2_T_OpIncr==pt.ttype)
            ? S2_T_OpIncrPost
              : S2_T_OpDecrPost;
        }
#if 0
        op = s2_ttype_op(pt.ttype);
        assert(op);
        MARKER(("Changing token '%s' to %s form.\n", op->sym,
                op->placement>0 ? "postfix" : "prefix"));
#endif
        break;
      default:
        break;
    }
    if(rc) break;

    op = s2_ttype_op( pt.ttype );
    if(op && !fromLhsOp){
      se->dotOpSelf = se->dotOpKey = se->dotOpLhs = 0
        /* If these get stale, Very Bad Things happen in the cwal core
             (assertions, if we're lucky). */;
      /* se->dotOpId = 0 */ /* keep Other Bad Things from happening */;
      /* Reminder: this means that assignment ops cannot tell
         which dot-like op triggered them. */
    }

    if((se->metrics.subexpDepth>0
        && S2_T_Colon==pt.ttype
        /* workaround for keyword sub-parses
           in ternary op. */)
       ||
       (fromLhsOp && op
        && (op->prec < fromLhsOp->prec
            || (op->prec==fromLhsOp->prec
                /* So (a=b=c=d) can work. */
                && op->assoc<0)))
       ){
      /* This invocation was catching an RHS, but we
         can stop now. */
      /*MARKER(("This '%s' is where a sub(ish)-eval for RHS "
        "skipping would break off.\n", op->sym));*/
      s2_ptoker_putback(st);
      /* FIXME? we break capturing somewhat (i think?) w/ the
         putback */
      break;
    }else if(fromLhsOp
             && S2_T_Question==fromLhsOp->id
             && S2_T_Colon==pt.ttype
             /* KLUDGE! */){
      s2_ptoker_putback(st);
      break;
    }

    /* Fiddle with consecutive ops for some cases... */
    if(op && (prevOp || !s2_engine_peek_token(se))){
      /* Leading operator or two consecutive operators.
         See if op can be made into a unary op. */
      switch(op->id){
        case S2_T_OpPlus:
        case S2_T_OpMinus:
          if(
             !prevOp || s2_ttype_may_precede_unary(prevOp->id)
             ){
            /* MARKER(("Changing token '%s' to unary form.\n", op->sym)); */
            pt.ttype = (S2_T_OpPlus==op->id)
              ? S2_T_OpPlusUnary
              : S2_T_OpMinusUnary;
            op = s2_ttype_op(pt.ttype);
          }
          break;
        case S2_T_OpNegateBitwise:
        case S2_T_OpNot:
          break;
        default:
          break;
      }
      if(!rc
         && prevOp && prevOp->arity>0 && prevOp->placement<=0
         && op->placement>=0 /* && op->assoc<=0 */ && op->arity!=0
         /* e.g. OpPlus ==> OpMultiply */){
        /* consecutive non-nullary operators */
        if(op->assoc<=0){
          rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                                    "Unexpected consecutive operators: "
                                    "'%s' ==> '%s'",
                                    prevOp->sym, op->sym);
        }
      }
    }/*op vs prevOp check*/

    if(rc) break;
    else if(!op){
      /* A non-operator ("value") token */
      if(prevOp && prevOp->placement>0 && prevOp->arity>0){
        /* e.g. a++a */
        rc =  s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                            "Unexpected token after postfix operator: "
                           "(%s) ==> (%s)",
                            s2_ttype_cstr(prevOp->id),
                            s2_ttype_cstr(pt.ttype));
      }
      kword = s2_ptoken_keyword(&pt);
      if(!prevOp
         && prevTok.ttype
         && (S2_T_BraceGroup!=pt.ttype) /* for obj[prop] access */
         && (S2_T_ParenGroup!=pt.ttype) /* so func() can work */
         && (!kword || kword->call /* for 'inherits' and potential
                                      future operator-like keywords */)
         ){
        rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                           "Unexpected consecutive non-operators: "
                           "#%d (%s) ==> #%d (%s)" /* " [%.*s]" */,
                           prevTok.ttype,
                           s2_ttype_cstr(prevTok.ttype),
                           pt.ttype, s2_ttype_cstr(pt.ttype)
                           /* tlen, pt.begin */
                           );
        break;
      }
    }

    /* Some checks and pre-processing... */
    switch(pt.ttype){
      case S2_T_OpOr:
      case S2_T_OpOr3:
      case S2_T_OpAnd:{
        /*
          For short-circuit logic we need both this loop and the
          stack to be in skip mode, but we also have to know when
          to back out of skip mode.

          Evaluate RHS expression in skip mode when:

          - op==OpOR and topVal is truthy,
          - or topVal is falsy
        */
        cwal_value * lhs;
        char buul;
        char shortIt = 0;
        int const theOpId = pt.ttype;
        rc = s2_eval_lhs_ops(se, st, op);
        if( (rc = s2_check_interrupted(se, rc)) ) break;
        lhs = s2_engine_peek_value(se);
        if(!lhs) break /* will error out below */;
        else if(se->skipLevel>0) break;
        buul = cwal_value_get_bool(lhs);
        if(!buul){
          if(pt.ttype==S2_T_OpAnd){
            shortIt = 1;
          }
        }else{
          if(pt.ttype!=S2_T_OpAnd){
            shortIt = 1;
          }
        }
        if(shortIt){
          /*
            short-circuit the RHS by calling back into this function
            and passing it the current op as a cut-off precedence.
          */
          cwal_value * rhs = 0;
          /* MARKER(("op %s should short circuit RHS here.\n", op->sym)); */
          rc = s2_eval_expr_impl(se, st, op, S2_EVAL_SKIP, &rhs);
          /*MARKER(("rhs-eval rc=%s. capture=%.*s\n", s2_rc_cstr(rc),
            (int)(st->capture.end-st->capture.begin), st->capture.begin
            ));*/
          /*if( (rc = s2_check_interrupted(se, rc)) ) break;
          else */
          if(!rhs){
            st->errPos = pt.begin;
            rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                                      "Empty RHS for logic operator");
          }else{
            assert(cwal_value_undefined()==rhs && "Current API convention for skip-mode evals");
            s2_engine_pop_token(se, 0) /* the short-circuited value */;
            s2_engine_push_val(se, (S2_T_OpOr3==theOpId)
                               ? lhs
                               : cwal_new_bool(cwal_value_get_bool(lhs))
                               )->srcPos = pt.begin;
              /* cannot fail b/c will recycle the popped one */;
            /* s2_dump_val(lhs, "new LHS"); */
            /* assert(lhs == s2_engine_peek_value(se)); */
            op = 0 /* so prevOp does not expose our trickery to the next
                      iteration */;
            if(refContainer && S2_T_OpOr3==theOpId
               && !cwal_value_is_builtin(lhs)
               ){
              rc = cwal_array_append( refContainer, lhs );
            }
            continue;
          }
        }
        break;
      }/* end &&, ||, ||| */
      case S2_T_Identifier:{
        /*
          Identifiers on the RHS of a dot are re-tagged (above) as
          PropertyKey, which will cause them not to be seen as
          identifiers or keywords here.

          Try a keyword...
        */
        if(kword){
          if(!kword->call){
            /* Keywords which get treated as operators... */
            s2_op const * kOp = s2_ttype_op(kword->id);
            assert(kOp);
            pt.ttype = kword->id;
            op = kOp;
          }else{
            pt.ttype = st->token.ttype = kword->id;
            rc = kword->call(kword, se, st, &tVal);
            rc = s2_check_interrupted(se, rc);
            if(!rc && !tVal){
              st->errPos = pt.begin;
              rc = s2_err_ptoker(se, st, CWAL_RC_ASSERT,
                                 "Keyword '%s' eval'd to <NULL>, "
                                 "which is currently verboten",
                                 kword->word);
              /* Reminder to self: maybe we could abuse a NULL keyword
                 result value for keywords which should expand to nothing,
                 e.g. __sweep and __BREAKPOINT.
              */
            }
            else if(!rc){
              /* If the keyword ends in a {script} and its the only
                 expression (so far), treat a trailing newline as
                 EOX. This is primarily so that scope/if/while/for/etc
                 do not require a semicolon, but it might bleed into
                 some other potentially unexpected cases.
              */
              if(S2_T_SquigglyString==st->token.ttype
                 && !se->st.vals.size /* keyword is the only thing on the stack */
                 ){
                /* In order to avoid breaking certain cases, we have
                   to check the next token. Heuristic: if the next
                   token is an infix or postfix op, assume that it's
                   to be applied to this result and treat EOL as
                   normal, otherwise treat EOL as an implicit EOX.

                   e.g.:

                   print(catch{...}
                         .message);
                */
                s2_op const * opCheck = 0;
                s2_ptoken check = s2_ptoken_empty;
                s2_next_token( se, st, 0, &check );
                if(!(opCheck = s2_ttype_op(check.ttype))
                   || (opCheck->placement<0 /* prefix op */)){
                  nextTokenFlags = S2_NEXT_NO_SKIP_EOL;
                  /* MARKER(("Squiggly workaround for %s\n", kword->word)); */
                }
              }
            }
          }
        }else if(se->skipLevel>0){
          /*
            In skip mode, we are obligated to resolve as much as we
            optimally can, so we resolve everything to undefined in
            that case.
          */
          tVal = cwal_value_undefined();
        }else{
          tVal = s2_var_get(se, -1, pt.begin, (cwal_size_t)tlen);
          if(!tVal){
            if(S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED & evalFlags){
              evalFlags &= ~S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED;
              tVal = cwal_value_undefined();
            }else{
              rc = s2_throw_ptoker(se, st, CWAL_RC_NOT_FOUND,
                                   "Could not resolve identifier "
                                   "'%.*s'",
                                   tlen, pt.begin);
            }
          }else if(prevOp && s2_ttype_is_identifier_prefix(prevOp->id)){
            /* Previous token was prefix ++/-- */
#if 1
            if(!s2_next_is_dotish(se, st)){
              tVal = cwal_new_string_value(se->e, pt.begin, (cwal_size_t)tlen);
              if(!tVal){
                rc = CWAL_RC_OOM;
              }
            }else{
              pt.ttype = S2_T_Value;              
            }
#else
            if(cwal_props_can(tVal)){
              pt.ttype = S2_T_Value;
            }else{
              tVal = cwal_new_string_value(se->e, pt.begin, (cwal_size_t)tlen);
              if(!tVal){
                rc = CWAL_RC_OOM;
              }
            }
#endif
          }else if(s2_next_wants_identifier(se, st)){
            /* Required so that assignment gets the identifier's string value,
               but we let it get validated above so that we get precise
               error location info. We just hope that pending ops don't
               invalidate it (potential corner case?).
            */
            tVal = cwal_new_string_value(se->e, pt.begin, (cwal_size_t)tlen);
            if(!tVal){
              rc = CWAL_RC_OOM;
            }
          }else{
            pt.ttype = S2_T_Value /* So as to not confuse
                                     the result with its identifier */;
          }
        }
        break;
      }/*end Identifier*/
      case S2_T_SquigglyString:{
        if(!s2_ptoken_is_true_squiggly(&pt)) break;
        else{
          rc = s2_eval_object_literal( se, st, &tVal );
          if( !(rc = s2_check_interrupted(se, rc)) ){
            assert(tVal);
            pt.ttype = S2_T_Value;
            assert(tVal);
          }
        }
        break;
      }
      case S2_T_BraceGroup:{
        /*
          Here's how we do VALUE[...] ...

          Sub-parse the [...] exactly like for a (...) block, but also
          (on success) push a Dot operator onto the stack. That will
          resolve to (VALUE DOT RESULT), which is actually exactly
          what we want.
        */
        cwal_value * lhs;
        s2_stoken const * topOpTok = s2_engine_peek_op(se);
        s2_op const * topOp = s2_stoken_op(topOpTok);
        if(!prevOp /* prev part (if any) was a value */
           && topOp && s2_ttype_is_deref(topOp->id) /* last op is a pending dot */
           ){
          /* Need to process any pending (single) LHS dot operator
             to support chaining. */
          /* MARKER(("Processing pending(?) LHS dot op\n")); */
          rc = s2_process_top(se);
          if( (rc = s2_check_interrupted(se, rc)) ){
            if(CWAL_RC_EXCEPTION!=rc && CWAL_RC_INTERRUPTED!=rc){
              assert(se->err.code);
              rc = s2_throw_op_err(se, st);
              assert(rc);
            }
          }
          topOpTok = 0;
        }
        if(rc) break;
        lhs = s2_engine_peek_value(se);
        if((prevOp || !totalValCount)
           && !(topOp && s2_ttype_is_deref(topOp->id))
           /* just-procesed dot deref resp. [property] access */){
          rc = s2_eval_array_literal( se, st, &tVal );
          if( !(rc = s2_check_interrupted(se, rc)) ){
            pt.ttype = S2_T_Value;
          }
          break /* avoid ParenGroup fall-through below */;
        }
        else if(!se->skipLevel
                && (!lhs
                    || prevOp
                    /* || !cwal_props_can(lhs) */
                    || !cwal_container_part(se->e, lhs)
                    )
                ){
          if(prevOp) st->errPos = prevTok.begin;
          rc = s2_throw_ptoker( se, st, CWAL_RC_TYPE,
                                "Invalid LHS (%s %s) "
                                "preceeding '%c...%c' block.",
                                s2_ttype_cstr(prevTok.ttype),
                                prevOp
                                ? prevOp->sym
                                : (lhs
                                   ? cwal_value_type_name(lhs)
                                   : "<NULL>"),
                                *pt.begin, *(pt.end-1)
                                );
        }
        if(rc) break;
        /* else FALL THROUGH... */
      }
      case S2_T_ParenGroup:{
        /*
          We expand these into Value tokens by doing a recursive eval
          on the byte range enclosed by the group.
        */
        /*
          TODO: somewhere around here is where the LHS-is-a-Function
          check would go, and parse differently in that case.
        */
        int const braceType = pt.ttype;
        char const braceOpen = *pt.begin;
        char const braceClose = *(pt.end-1);
        char emptySet = 1;
        assert(S2_T_ParenGroup || S2_T_BraceGroup==pt.ttype);
        assert(S2_T_ParenGroup==pt.ttype
               ? ('('==braceOpen && ')'==braceClose)
               : ('['==braceOpen && ']'==braceClose));
#if 0
        /* ??? */
        if(prevTok.ttype
           /* && prevTok.ttype != S2_T_OpDot */
           && prevTok.ttype == S2_T_ParenGroup
           /* ^^^^ allow [...][...] with only a small bit of fudgery */
           && !s2_ttype_may_precede_unary(prevTok.ttype)){
          rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                             "Illegal adjacent tokens: "
                             "ttype %s ==> '%c...%c'",
                             s2_ttype_cstr(prevTok.ttype),
                             braceOpen, braceClose
                             );
          break;
        }
#endif
        if(S2_T_ParenGroup==pt.ttype &&
           (s2_looks_like_fcall(se, st, prevOp, &rc) || rc)){
          /* MARKER(("Looks like func call? rc=%s prevOp=%s\n",
             s2_rc_cstr(rc), prevOp?prevOp->sym:"<NULL>")); */
          if(!rc) rc = s2_eval_fcall(se, st, &tVal);
          break;
        }


        assert(pt.adjBegin);
        assert(pt.adjEnd);
        assert(pt.adjEnd >= pt.adjBegin);
        if(pt.adjEnd > pt.adjBegin){
          /* In order to know whether it's really empty, and fail
             consistently across both skipping and non-skipping mode,
             we have to parse the subexpr regardless of skip level
             :/.
          */
          assert(pt.begin == st->token.begin);
          st->token = pt;
          rc = s2_eval_current_sub(se, st, 0, &tVal)
            /* Reminder to self: passing true as the 3rd arg
               breaks this arguable feature: (a;b).
             */;
          if(!rc){
            emptySet = tVal ? 0 : 1;
            /* s2_dump_val(tVal, "post-() tVal"); */
          }
        }else{
          emptySet = 1;
        }
        if(rc) break;
        else if(emptySet){
          if(se->skipLevel
             || (!prevOp
                 && S2_T_BraceGroup==braceType
                 && cwal_value_is_array(s2_engine_peek_value(se)))){
            /*
              PHP-style array-append operator:

              If LHS val is-a array and braceOpen=='[' then push a
              an ArrayAppend op and assert that the next token
              is an assignment op. Alternately, we could just assume
              an assignment op and elide one if it appears. But we'll
              go ahead and be strict for now.
            */
            s2_ptoken next = s2_ptoken_empty;
            op = s2_ttype_op( pt.ttype = S2_T_ArrayAppend );
            s2_next_token( se, st, 0, &next );
            if(!s2_ttype_is_assignment(next.ttype)){
              st->errPos = pt.end;
              rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                                 "Missing assignment op "
                                 "after '%s' operator.",
                                 op->sym);
              break;
            }else{
              st->token = next /* consume/skip it */;
              /* Fall through and push this op */
            }
          }else if(!se->st.vals.size
                   && !se->st.ops.size
                   && (evalFlags & S2_EVAL_EMPTY_GROUP_OK)){
            /* We're doing this for the sake of return(). */
            evalFlags &= ~S2_EVAL_EMPTY_GROUP_OK;
            tVal = cwal_value_undefined();
          }else{
            /*
              We (generally) disallow empty () and [] groups because
              they would otherwise be ambiguous with (undefined). We
              do indeed special-case them in places.
            */
            rc = s2_throw_ptoker(se, st, CWAL_SCR_SYNTAX,
                                 "Empty '%c%c' block is not "
                                 "allowed here.",
                                 braceOpen, braceClose);
            break;
          }
        }else{
          assert(!op);
          assert(tVal || se->skipLevel);
          pt.ttype = S2_T_Value;
          /* FALL THROUGH and be picked up as a VALUE via tVal */
          if(S2_T_BraceGroup==braceType){
            /*
              OBJ[KEY] is transformed to (OBJ DOT KEY)...
            */
            s2_stoken * topOpTok = s2_engine_push_ttype( se, S2_T_OpDot );
            if(!topOpTok){
              rc = CWAL_RC_OOM;
            }
            else topOpTok->srcPos = pt.begin;
            /* MARKER(("Added DOT OP for [] group.\n")); */
          }
        }
        /*
          Hypothetical problem with _not_ having a '(' operator token in the
          stack:

          pending LHS ops _might_ not be evaluated in the proper order.

          Something to consider doing here:

          op = s2_ttype_op(S2_T_ParenOpen)

          Then, in the if(op) block below, recognize that particular
          token and do _NOT_ push it onto the stack. Instead, set op=0
          and jump into the is-a-Value block.

          Then again, parens are supposed to have amongst the highest
          precedence, so there can be no pending LHS operators to the
          left with with >= precedence, can there? Pending LHS groups,
          OTOH, which have == precedence have already been evaluated
          left-to-right, so the effect would seem to be the same.
        */
        break;
      }/*end parens/brace groups*/
      case S2_T_OpAssign:
      case S2_T_OpPlusAssign:
      case S2_T_OpMinusAssign:
      case S2_T_OpXOrAssign:
      case S2_T_OpAndAssign:
      case S2_T_OpOrAssign:
      case S2_T_OpMultiplyAssign:
      case S2_T_OpDivideAssign:
      case S2_T_OpModuloAssign:
      case S2_T_OpShiftLeftAssign:
      case S2_T_OpShiftRightAssign:{
        /**
           The binary assign works without us assisting it here, but in
           the case of a property assignment, we need to adjust the
           operator from the binary assignment (ident=expr) to the
           ternary assignment (obj DOT prop = expr), which we translate
           to (obj prop .= expr), where '.=' is the member assignment
           operator.

           Reminder to self: for compound assignments, e.g. +=

           we can translate (A+=B) to (A = A + B) and (A.B+=C)
           to (A.B = A.B+C). (But we don't do it that way.)

           Prefix/postfix incr/decr can be done similarly:
           --A ==> (var tmp=A; A = A-1; tmp)
           Except that we want overloaded ops to know if
           they're being called in unary form. Hmm.
        */
        s2_stoken * topOpTok = s2_engine_peek_op(se);
        if(prevOp /* && prevOp->arity>0 && prevOp->placement<=0 */
           /* How about postfix ++/--? Can we reasonably chain those
              with assignments?*/
           ){
          st->errPos = pt.begin;
          rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                             "Invalid adjacent operators: "
                             "'%s' ==> '%s'", prevOp->sym,
                             op->sym);
        }

        if(topOpTok && s2_ttype_is_deref(topOpTok->ttype)){
          /*
            Assume the stack has (lhs,rhs) operands for the dot
            operator, then replace the dot op with a ternary
            assignment op: X.Y = Z.
          */
          int newOp = 0;
          switch(pt.ttype){
#define CASE(T) case T: newOp = T##3; break
            CASE(S2_T_OpAssign);
            CASE(S2_T_OpPlusAssign);
            CASE(S2_T_OpMinusAssign);
            CASE(S2_T_OpXOrAssign);
            CASE(S2_T_OpAndAssign);
            CASE(S2_T_OpOrAssign);
            CASE(S2_T_OpMultiplyAssign);
            CASE(S2_T_OpDivideAssign);
            CASE(S2_T_OpModuloAssign);
            CASE(S2_T_OpShiftLeftAssign);
            CASE(S2_T_OpShiftRightAssign);
#undef CASE
            default:
              assert(!"Missing operator mapping.");
              s2_fatal(CWAL_RC_FATAL, "Missing operator mapping.");
          }
          assert(newOp);
          op = s2_ttype_op( pt.ttype = newOp );
          assert(op);
          s2_engine_pop_op(se,0)/*==topOpTok, a.k.a. OpDot */;
          /* Fall through and allow new op to be pushed to the stack */
        }
        break;
      }/*Assignment*/
    }/*switch(pt.ttype)*/
    
    if(rc) break;
    else if(op){
      if(/* Script starts with an invalid operator.  "Invalid"
            basically means any which do not look like they can
            legally be used at the start of an expression. */
         op->arity>0
         && op->placement>=0
         /* && !fromLhsOp */
         && se->st.vals.size < op->arity-1 /*no values the op can work with*/
         ){
        rc = s2_err_ptoker( se, st, CWAL_SCR_SYNTAX,
                            "Illegal operator '%s' at start "
                            "of expression", op->sym);
      }else{
        rc = s2_eval_lhs_ops(se, st, op);
        if(!rc){
          s2_stoken * topOpTok = s2_engine_push_ttype(se, pt.ttype);
          if(!topOpTok){
            rc = CWAL_RC_OOM;
          }
          else{
            topOpTok->srcPos = pt.begin;
            if(op->placement==0 && op->arity>1){
              /*
                Reminder: i do not really want EOL-skipping for
                prefix/postfix ops.  Prefix should cause a syntax
                error if they exist at EOL and postfix already has its
                operand on the stack.
              */
            }
          }
        }
      }
    }else{/* a Value token */
      if(!tVal){
        if(se->skipLevel>0) tVal = cwal_value_undefined();
        else{
          rc = s2_ptoken_create_value(se, &pt, &tVal);
          if(!rc){
            assert(tVal);
          }
        }
      }
      if(!rc){
        s2_stoken * vtok;
        assert(tVal);
        if((se->flags.traceStack>0) && (S2_T_Identifier==pt.ttype)){
          MARKER(("Identifier token: %.*s\n", tlen, pt.begin));
        }
        vtok = s2_engine_push_tv(se, pt.ttype, tVal);
        if(!vtok){
          rc = CWAL_RC_OOM;
        }
        else if(refContainer
                && !cwal_value_is_builtin(tVal)
                && (rc = cwal_array_append( refContainer, tVal ))){
          break;
        }else{
          ++totalValCount;
          vtok->srcPos = pt.begin;
          if(se->skipLevel>0){
#if 1
            /* s2_dump_val(tVal, "what is this"); */
            assert((tVal == cwal_value_undefined())
                   && "current internal convention for "
                   "skip-mode evaluation was violated");
#endif
          }
        }
      }
    }/*if op else value */
  }/* for-each token */

  if(rc && !st->errPos){
    st->errPos = (pt.begin && (pt.begin != pt.end)) ? pt.begin : prevTok.begin;
  }else if(!rc){
    assert(capBegin);
  }
  st->capture.begin = capBegin ? capBegin : st->token.begin;
  st->capture.end = st->token.begin;

  /* MARKER(("Tokenization rc=%d\n", rc)); */
  /* MARKER(("Stack sizes: op=%d, token=%d\n", se->st.ops.size, se->st.vals.size)); */
  if(rc) goto end;

  /*
    Now process what's left of the stack...
  */
  if(!rc){
    while(!rc && se->st.ops.size){
      rc = s2_process_top(se);
    }
    if(rc){
      if(CWAL_RC_EXCEPTION!=rc){
        assert(se->err.code);
        rc = s2_throw_op_err(se, st);
        assert(rc);
      }
      goto end;
    }
  }
  /* MARKER(("Stack sizes: op=%d, token=%d\n", se->st.ops.size, se->st.vals.size)); */
  if(!rc && (1 != se->st.vals.size)){ /* too many or too few items in stack */
    if(!se->st.vals.size
       && s2_ttype_is_eox(st->token.ttype)
       /* ==> empty expression */
       ){
      /*MARKER(("Truly empty expression? totalValCount=%d %.*s\n",
        totalValCount, (int)(capEnd - capBegin), capBegin));*/
#if 0
      if(!totalValCount){
        /* A truly empty expression with no expected value part
           before the EOX. */
        /* if(evalIt) */xrv = 0;
      } 
#endif
      /*else{
          
          Leave previous expression value, if any, because we
          currently need it for:

          X; ==> X

          but it also means that (X;;; ==> X), which we really
          don't want. Catching that has to be done from the code
          calling this - it needs to remember seeing (or not)
          multiple EOX tokens.
        }*/
    }else if((se->st.vals.size > 0) && totalValCount){
      st->errPos = pt.begin;
      rc = s2_err_ptoker(se, st, CWAL_SCR_SYNTAX,
                         "Unexpected stack size "
                         "(%d) after expression. "
                         "Normally caused by a missing ';' "
                         "in the previous line.",
                         se->st.vals.size);
    }
  }

  if(!rc){
    cwal_value * xrv = s2_engine_pop_value(se);
    /* assert(xrv); */
    if(se->skipLevel>0){
#if 1
      /* s2_dump_val(xrv, "what is this"); */
      assert((!xrv || (xrv == cwal_value_undefined()))
             && "current internal convention for "
             "skip-mode evaluation was violated");
#endif
    }
    if(se->flags.traceStack && se->skipLevel>0){
      s2_dump_val(xrv, "eval_expr result");
    }
    if(rv) *rv = xrv;
  }

  end:
  if(rc){
      rc=rc /* put breakpoint here */;
  }
  /* Clean up... */
  if(!fromLhsOp){
    /* We must clear these avoid picking them up after they're stale.
       However, they's needed by, e.g. (unset x.y).
    */
    se->dotOpSelf = se->dotOpKey = se->dotOpLhs = 0;
  }
  if(rc && !se->err.code && st->errMsg
     && (CWAL_RC_EXCEPTION!=rc)){
    /* Error from the tokenizer. */
    /* se->err.code = rc; */
    /* cwal_buffer_printf(se->e, &se->err.msg, "%s", st->errMsg); */
    rc = s2_err_ptoker(se, st, rc, 0);
  }

  s2_rv_maybe_accept(se, scope->scope.parent /* 0 is okay */, rc, rv);

  if(!evalIt){
    assert(se->skipLevel==1+oldSkipLevel);
    se->skipLevel = oldSkipLevel;
  }
  if(ownStack){
    s2_engine_stack_swap(se, &priorStack);
    s2_estack_clear(se, &priorStack, 1);
    assert(oldOpCount == se->st.ops.size);
    assert(oldValCount == se->st.vals.size);
  }
  if(!consumeIt){
    st->token = pOrigin;
    st->pbToken = pbOrigin;
  }else{
    st->pbToken = pOrigin
      /* yes, pOrigin (not pbOrigin) - we want to be able to putback
         the whole expression */;
  }
  if( st->parent ) --se->metrics.subexpDepth;

  if(refContainer){
    char const doRef = rv && *rv && !cwal_value_is_builtin(*rv);
    if(doRef) cwal_value_ref(*rv);
    cwal_array_length_set(refContainer, 0) /* paranoia */;
    cwal_value_unref(cwal_array_value(refContainer));
    refContainer = 0;
    if(doRef) cwal_value_unhand(*rv);
  }
  se->currentScript = oldScript;
  if(S2_EVAL_PUSH_SCOPE & evalFlags){
    assert(scope->parent);
    assert(s2_scope_current(se)==scope);
    s2_scope_pop(se);
  }else{
    --se->sguard->sweep;
  }
  return rc;
}

int s2_eval_cstr( s2_engine * se,
                  char newScope,
                  char const * name,
                  char const * src, int srcLen,
                  cwal_value **rv ){
  s2_ptoker pt = s2_ptoker_empty;
  int rc = s2_ptoker_init( &pt, src, srcLen );
  if(!rc){
    s2_scope SC = s2_scope_empty;
    if(!newScope || !(rc=s2_scope_push(se, &SC))){
      pt.name = (name && *name) ? name : 0;
      pt.nameLen = name ? cwal_strlen(name) : 0;
      rc = s2_eval_ptoker( se, &pt, rv );
      if(SC.scope.parent){
        if(rv && *rv) cwal_value_rescope( SC.scope.parent, *rv );
        s2_scope_pop(se);
      }
    }
  }
  return rc;
}

int s2_eval_ptoker( s2_engine * se, s2_ptoker * pr, cwal_value **rv ){
  int rc = 0;
  int const oldTStackSize = se->st.vals.size;
  cwal_value * xrv = 0;
  int hardEoxCount = 0;
  int const srcLen = (int) (pr->end - pr->begin);
  s2_ptoker const * oldScript = se->currentScript;
  cwal_array * holder = 0;

  if(srcLen<0){
    return s2_engine_err_set(se, CWAL_RC_MISUSE, "Invalid s2_ptoker range.");
  }else if(!srcLen || !*pr->begin){
    if(rv) *rv = 0;
    return 0;
  }

  /*
    We have on notable problem in the sweepup mechanism with regard to
    iterating over expressions and keeping the most recent result:
    vacuuming can steal the result value from us. So we either have to
    make the value itself vacuum-proof (which doesn't work for PODs
    and would have unwanted side-effects in some cases) or we can
    disallow vacuuming so long as we have a pending
    expression. Bummer.

    The solution uses here is to allocate an array, make _it_
    vacuum-proof, and then store each pending result value as the
    first element of the array (always storing only one). That will
    indirectly vacuum-proof the result value while not unduly
    extending its lifetime because (A) each new result overwrites it,
    potentially cleaning it up immediately, and (B) at the end of the
    block we set the references straight (in a kosher manner) for the
    return via *rv.

    It turns out that this is very cheap as long as recycling is on.

    TODOs:

    - don't bother with the array if !*rv.

    - Doh, except that we might need it for some recursive
      evaluations?
  */

  holder = cwal_new_array(se->e);
  if(!holder){
    return CWAL_RC_OOM;
  }
  cwal_value_ref(cwal_array_value(holder));
#if 0
  /* This reserve actually backfires, bypassing an internal list
     re-use optimization! */
  rc = cwal_array_reserve(holder, 2);
  if(rc){
    cwal_array_unref(holder);
    return rc;
  }
#endif
  cwal_value_make_vacuum_proof(cwal_array_value(holder), 1);
  s2_engine_err_reset(se);
  se->currentScript = pr;
#if 0
  if(pr->nameLen){
    MARKER(("Running script [%.*s]\n", (int)pr->nameLen, pr->name))
      /* for helping in finding a script name bug in interactive
         mode. */
      ;
  }
#endif
  for( ; !rc ; ){
    int isEof, isEox;
    xrv = 0;
    s2_engine_sweep(se);

    rc = s2_eval_expr_impl(se, pr, 0, 0, rv ? &xrv : 0);
    if(rc) break;
#if 0
    MARKER(("Ran expr: value=%p, capture=[[[%.*s]]]\n",
            (void const *)xrv2, 
            (int)(pr->capture.end - pr->capture.begin), pr->capture.begin));
#endif
    if(xrv){
      cwal_array_set(holder, 0, xrv);
      hardEoxCount = 0;
      /* s2_dump_val(xrv, "s2_eval_expr result"); */
    }
    isEof = s2_ptoker_is_eof(pr);
    isEox = isEof ? isEof : s2_ttype_is_eox(pr->token.ttype);
    if(!isEof && isEox){
      /*
        Reminder; "3 ;" ends in an EOX but has a value. It evals
        differently than "3 ; ;", which evals to two expressions, the
        second one empty.

        We accept one ';' as "keep previous value", but a series of
        semicolons, possibly separated by other empty expressions,
        evals to NULL.
      */
      if(++hardEoxCount>1){
        xrv = 0;
        cwal_array_set(holder, 0, 0);
      }
    }else if(!isEox){
      hardEoxCount = 0;
    }
    if(isEof) break;
  }/* for-each token */
  /* MARKER(("Stack sizes: op=%d, token=%d\n", se->pr->ops.size, se->pr->vals.size)); */
  if(!rc){
    if(oldTStackSize != se->st.vals.size){
      /*MARKER(("Unexpected stack sizes: op=%d, token=%d\n",
        se->pr->ops.size, se->pr->vals.size));*/
      /* Reminder: this would not be an error for empty inputs */
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Unexpected stack size in s2_eval_ptoker(): %d",
                         se->st.vals.size);
    }
  }
  /* s2_dump_val(xrv, "s2_eval_ptoker result"); */
  /* s2_dump_val(xrv, "s2_eval_ptoker result"); */
  if(!pr->parent && CWAL_RC_RETURN==rc){
    /* Top-level parser shall accept a RETURN as a non-error, and
       stuff it in *rv.

       We do not handle EXIT here because this routine will be used to
       import files from other files, without having the parent
       sources as pr->parent, and exit() needs to work cross-file
       whereas we can justify RETURN stopping at a file boundary
       (PHP-style).
    */
    cwal_value * rr = s2_propagating_take(se);
    assert(rr);
    xrv = rr;
    rc = 0;
    /* Potential corner: the returned value is the previous
       one in holder[0]... */
    cwal_value_ref(xrv);
    cwal_array_set(holder, 0, 0);
    cwal_value_unhand(xrv);
  }else{
    xrv = rc ? 0 : cwal_array_take(holder, 0);
  }

  if(!rc && rv){
    *rv  = xrv;
    assert((!xrv || cwal_value_scope(xrv) || cwal_value_is_builtin(xrv))
           && "Seems like we cleaned up xrv too early.");
    if(se->flags.traceStack){
      s2_dump_val(*rv, "s2_eval_ptoker result");
    }
  }
  cwal_array_unref(holder);
  se->currentScript = oldScript;
  return rc;
}

int s2_keyword_f_breakpoint( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc;
  *rv = cwal_value_undefined();
  rc = 0 /* place breakpoint here */;
  return rc;
}

int s2_keyword_f_builtin_vals( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  if(se->skipLevel){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    switch(pr->token.ttype){
      case S2_T_KeywordUndefined: *rv = cwal_value_undefined(); return 0;
      case S2_T_KeywordNull: *rv = cwal_value_null(); return 0;
      case S2_T_KeywordTrue: *rv = cwal_value_true(); return 0;
      case S2_T_KeywordFalse: *rv = cwal_value_false(); return 0;
      default:
        assert(!"Invalid keyword mapping");
        s2_fatal(CWAL_RC_RANGE, "Invalid keyword mapping in s2_keyword_f_builtin_vals()")
          /* does not return, but your compiler doesn't know that, so... */;
        return CWAL_RC_ERROR;
    }
  }
}

int s2_keyword_f_FLC( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int line = 0, col = 0;
  char const * script = 0;
  cwal_size_t scriptLen = 0;
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }
  /* s2_ptoker_err_info( pr, &script, pr->token.begin, &line, &col ); */
  switch(kw->id){
    case S2_T_KeywordFILE:
      script = s2_ptoker_name_first(pr, &scriptLen);
      *rv = script
        ? cwal_new_string_value(se->e, script, scriptLen)
        : cwal_value_undefined();
      break;
    case S2_T_KeywordFILEDIR:{
      char const * sepPos;
      cwal_size_t len;
      script = s2_ptoker_name_first(pr, &scriptLen);
      sepPos = scriptLen ? s2_last_path_sep(script, scriptLen) : 0;
      len = sepPos ? (cwal_size_t)(sepPos - script) : 0;
      assert(len <= scriptLen);
      *rv = len
        ? cwal_new_string_value(se->e, script, len)
        : (sepPos /* root dir */
           ? cwal_new_string_value(se->e, sepPos, 1)
           : cwal_new_string_value(se->e, 0, 0))
        /* this is why i regret the "0==strlen" semantics prevelant in
           the core cwal string APIs. */;
      break;
    }
    case S2_T_KeywordLINE:
      s2_ptoker_count_lines( pr, pr->token.begin, &line, 0);
      *rv = cwal_new_integer(se->e, (cwal_int_t)line);
      break;
    case S2_T_KeywordCOLUMN:
      s2_ptoker_count_lines( pr, pr->token.begin, 0, &col);
      *rv = cwal_new_integer(se->e, (cwal_int_t)col);
      break;
    case S2_T_KeywordSRCPOS:
      s2_ptoker_count_lines( pr, pr->token.begin, &line, &col);
      script = s2_ptoker_name_first(pr, &scriptLen);
      *rv = (line>0)
        ? cwal_string_value(cwal_new_stringf(se->e, "%s:%d:%d",
                                             script ? script : "unnamed script",
                                             line, col))
        : cwal_value_undefined();
      break;
    default:
      assert(!"Invalid operator mapping");
      return CWAL_RC_ERROR;
  }
  return *rv ? 0 : CWAL_RC_OOM;
}



int s2_keyword_f_eval( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_scope scope_ = s2_scope_empty;
  s2_scope * scope = 0 /* gets set if we push a scope */;
  cwal_value * xrv = 0 /* internal result value */;
  int sourceType = 0 /* logical token type of the part after the keyword */;
  int modifier = 0 /* set if the -> modifier is specified */;
  int phase = 0 /* evaluation phase: 1st or 2nd (where we eval string content fetched in phase 1) */;
  s2_ptoken const origin = pr->token;
  s2_ptoken exprPos = s2_ptoken_empty;
  int evalFlags = 0;
  switch(kw->id){
    /* Reminder to self: we might want to re-use this impl for running bodies
       of 'if' blocks, and want them to get a scope. */

    /* These push a scope... */
    case S2_T_KeywordCatch:
    case S2_T_KeywordScope:
      if(!se->skipLevel) scope = &scope_;
      break;
    /* These do not push a scope or do so at their own level... */
    case S2_T_KeywordEval:
    case S2_T_KeywordFatal:
    case S2_T_KeywordThrow:
      break;
    case S2_T_KeywordExit:
    case S2_T_KeywordBreak:
    case S2_T_KeywordReturn:
      evalFlags = S2_EVAL_EMPTY_GROUP_OK;
      break;
    /* And these are just plain wrong... */
    default:
      assert(!"Invalid keyword mapping!");
      return s2_err_ptoker(se, pr, CWAL_RC_FATAL,
                           "Invalid keyword mapping.");
  }

  /*
    Check if the following expression is a {squiggly} or not...
  */
  {
    s2_ptoker next = *pr;
    next_token:
    rc = s2_next_token(se, &next, 0, 0);
    if(rc) return rc;
    else{
      switch(next.token.ttype){
        case S2_T_SquigglyString:
          if(s2_ptoken_is_true_squiggly(&next.token)){
            sourceType = next.token.ttype;
            pr->token = next.token;
          }
          break;
        case S2_T_OpArrow:
#if 0
        case S2_T_Colon:
        case S2_T_OpNegateBitwise:
#endif
          if(!modifier){
            modifier = next.token.ttype;
            /* pr->token.end = next.token.end; */
            pr->token = next.token;
            goto next_token;
          }
          break;
      }
    }
  }

#if 0
  /* arguable! */
  if(scope && !modifier && (S2_T_SquigglyString != sourceType)){
    /* Optimization: don't push a new scope, as this will "presumably"
       be a small expression. */
    scope = 0;
  }
#endif
  if(scope){
    rc = s2_scope_push(se, scope);
    if(rc) return rc;
    assert(scope->parent);
  }

  exprPos = pr->token;
  /* No return as of here: use goto end. */

  /*
    - peek next expr w/ flags S2_EVAL_NO_CONSUME | S2_EVAL_SKIP

    - if type == SquigglyString:
    
    - - In skip mode, move pr->token.end, set *rv=undefined and return 0

    - - Else sub-eval_expr(se, pr->token, rv)

    - If not a squiggly:

    - - eval_expr(se, pr, rv)

   */
  if(sourceType == S2_T_SquigglyString
     && s2_ptoken_is_true_squiggly(&pr->token)
    ){
    if(se->skipLevel>0){
      /* Skip it! */
      goto end;
    }else{
      switch(kw->id){
        case S2_T_KeywordBreak:
        case S2_T_KeywordContinue:
        case S2_T_KeywordExit:
        case S2_T_KeywordFatal:
        case S2_T_KeywordReturn:
        case S2_T_KeywordThrow:
          /* keyword {} treats {} as an Object. This is incidentally
             aligns with keyword [...] seeing an array literal. */
#if 0
          /* doesn't handle: return {...}.foo() */
          rc = s2_eval_object_literal(se, pr, &xrv);
#else
          /* eval it as a normal expression to pick up
             object literals and following operators. */
          pr->token.end = origin.end /* back up just a tick */;
          rc = s2_eval_expr_impl(se, pr, 0, 0, &xrv);
          /*
            This leads to a syntactic inconsistency between eval/scope/catch
            and the other variants:

             eval {1},2; // two separate expressions: (eval==>1), literal 2
             return {a:1},2; // one expresion (object, 2) ==> 2
          */
#endif
          break;
        default:
          /* Treat {} as a script block */
          rc = s2_eval_current_sub( se, pr, 0, &xrv );
          break;
      }
      /* s2_dump_val(xrv, "EVAL result"); */
      if(!rc && !xrv) xrv = cwal_value_undefined();
    }
  }else{
    /* Behave as if the keyword wasn't there (except that we ignore LHS operators). */
    s2_op const * pseudoOp =
#if 1
      0
#else
      (sourceType == S2_T_SquigglyString/*actually a heredoc*/)
      ? s2_ttype_op(S2_T_Comma)
      : 0
      /* So that we stop eval'ing the RHS at a comma or higher prec,
         AND because the special RHSEval.

         But doing that breaks:

         return 1, 2, 3;

         And fixing that causes some broken (wrong script name)
         error messages in code broken by this.

         Consider:

         eval {1+2}, 3

         because of the {block}, that "probably really" should be
         (1+2), 3, as this is the only context where we allow a
         {block} like this.
      */
#endif
      ;
    /* assert(pseudoOp); */
    /* MARKER(("%s'ing around %.10s ...\n", kw->word, exprPos.begin)); */
    rc = s2_eval_expr_impl( se, pr, pseudoOp, evalFlags, &xrv );
    /* s2_dump_val(xrv, "EVAL result"); */
    if(!rc && !xrv){
      switch(kw->id){
        /* These are allowed to have empty expressions... */
        case S2_T_KeywordReturn:
        case S2_T_KeywordExit:
        case S2_T_KeywordBreak:
        case S2_T_KeywordContinue:
        case S2_T_KeywordFatal:
          xrv = cwal_value_undefined();
          break;
        /* The rest are not... */
        default:{
          char const * errMsg;
          switch(kw->id){
            case S2_T_KeywordThrow:
              errMsg = "%s requires a non-empty expression operand.";
              break;
            default:
              errMsg = "%s requires a non-empty expression or {script} operand.";
          }
          pr->errPos = origin.begin;
          rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             errMsg, kw->word);
        }
      }
    }
  }
  ++phase;

  if(!rc && !se->skipLevel){
    if(modifier && !se->skipLevel
       /* && sourceType != S2_T_SquigglyString */){
      cwal_size_t vlen = 0;
      char const * vstr = cwal_value_get_cstr(xrv, &vlen);
      cwal_value * xrv2 = 0;
      if(vstr){
        s2_ptoker sub = s2_ptoker_empty;
        /* We have to ref and vacuum-guard to keep this follow-up eval
           from vacuuming xrv out from under us. Thanks again,
           valgrind. We also have to ref xrv (thanks, cwal
           assertions).
        */
        cwal_value_ref( xrv );
        ++se->sguard->vacuum;
        s2_ptoker_init( &sub, vstr, (int)vlen );
        s2_ptoker_count_lines( pr, exprPos.end, &sub.lineOffset,
                               &sub.colOffset);
        sub.name = s2_ptoker_name_first(pr, &sub.nameLen)
          /* Needed when functions are created in this code. It kinda
             (well... totally) confuses the __FLC-related bits,
             though, as well as exceptions and parse errors.
           */;
        /* sub.parent = pr; */
        rc = s2_eval_ptoker(se, &sub, &xrv2);
        --se->sguard->vacuum;
        cwal_value_unhand( xrv );
        xrv = xrv2;
        ++phase;
      }
    }
  }
  end:
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
  }else{
    switch(kw->id){
      case S2_T_KeywordBreak:
      case S2_T_KeywordExit:
      case S2_T_KeywordFatal:
      case S2_T_KeywordReturn:
        if(!rc){
          *rv = xrv ? xrv : cwal_value_undefined();
          cwal_propagating_set(se->e, *rv);
          switch(kw->id){
            case S2_T_KeywordExit: rc = CWAL_RC_EXIT; break;
            case S2_T_KeywordFatal: rc = CWAL_RC_FATAL; break;
            case S2_T_KeywordReturn: rc = CWAL_RC_RETURN; break;
            case S2_T_KeywordBreak: rc = CWAL_RC_BREAK; break;
          }
          /*
            But we need to keep the error location...
          */
          pr->errPos = origin.end;
          rc = s2_err_ptoker(se, pr, rc, 0);
          se->opErrPos = pr->errPos = 0;
        }
        break;
      case S2_T_KeywordThrow:
        if(!rc){
          *rv = xrv ? xrv : cwal_value_undefined();
          pr->errPos = origin.begin;
          rc = s2_throw_value( se, pr, CWAL_RC_EXCEPTION, *rv)
            /* rc will be CWAL_RC_EXCEPTION or something more serious
               (CWAL_RC_OOM) */;
          pr->errPos = 0;
        }
        break;
      case S2_T_KeywordCatch:
        switch(rc){ /* Convert certain errors to exceptions... */
          case 0:
            *rv = cwal_value_undefined();
            break;
          case CWAL_SCR_SYNTAX:
            if(!se->err.code){
              break /* we have nothing to report */;
            }else if(phase < 2 && S2_T_SquigglyString != sourceType){
              /* Don't convert errors which came from EXPR input
                 unless the error came from its contents (via the
                 -> modifier), because we cannot know from here if the
                 EXPR part itself has a syntax error.
              */
              break;
            }
            else if( CWAL_RC_EXCEPTION !=
                     (rc = s2_throw_err_ptoker(se, pr))){
              /* a more serious error */
              break;
            }
            /*
              Else fall through and treat it like the exception we
              just threw. We know that the error is somewhere
              contained in the _contents_, but the contents got to us
              okay (i.e. they're syntactically legal). In the EXPR
              form, the error might be somewhere in the top of the
              expr (we don't know), so we have to pass those on as
              potentially fatal to the current script.
            */
          case CWAL_RC_EXCEPTION:
            xrv = cwal_exception_get(se->e);
            assert(xrv);
            /* assert(0==cwal_value_refcount(xrv) && "This is only temporary while debugging something"); */
            /* s2_dump_val(xrv,"exception before rescope"); */
            if(scope){
              assert(scope->parent);
              cwal_value_rescope(scope->scope.parent, xrv);
            }
            cwal_exception_set(se->e, 0);
            /* s2_dump_val(xrv,"exception after set-exception 0"); */
            assert((cwal_value_scope(xrv) || cwal_value_is_builtin(xrv))
                   && "Premature cleanup on isle 7.");
            *rv = xrv;
            assert(!cwal_exception_get(se->e));
            rc = 0;
            break;
        }
        break;
        /* end catch/CWAL_RC_EXCEPTION */
      default:
        /* Propagate anything else... */
        if(!rc){
          /* we'll allow the NULL==>undefined translation here for sanity's
             sake of sanity: eval -> "", where "" comes from a foreign source.
          */
          *rv = xrv ? xrv : cwal_value_undefined();
        }
        s2_rv_maybe_accept(se, scope ? scope->scope.parent : 0, rc, rv);
        break;
    }
  }
  if(scope){
    /* We pushed a scope */
    assert(se->currentScope == scope);
    assert(s2_scope_current(se)==scope);
    s2_scope_pop(se);
    assert(!scope->scope.e);
    assert(!scope->scope.props);
  }
  return rc;
}

static int s2_keyword_f_assert( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ptoken const pos = pr->token;
  assert(S2_T_KeywordAssert==pos.ttype
         || S2_T_KeywordAffirm==pos.ttype);
  rc = s2_eval_expr_impl(se, pr, 0, 0, rv);
  if(rc) return rc;
  else if(!rv){
    pr->errPos = pos.begin;
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "Unexpected empty expression");
  }
  else if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    char const * begin = pr->capture.begin;
    char const * end = pr->capture.end;
    char const isAssert = (S2_T_KeywordAssert==pos.ttype) ? 1 : 0;
    if(!cwal_value_get_bool(*rv)){
      if(se->flags.traceAssertions > 1){
        cwal_outputf(se->e, "ASSERT FAILED:%.*s\n",
                     (int)(end-begin), begin);
      }
      pr->errPos = pos.begin;
      return isAssert
        ? s2_err_ptoker(se, pr, CWAL_RC_ASSERT,
                        "Assertion failed:%.*s",
                        (int)(end-begin), begin)
        : s2_throw_ptoker(se, pr, CWAL_RC_ASSERT,
                          "Affirmation failed:%.*s",
                          (int)(end-begin), begin);
    }else{
      if(isAssert){
          ++se->metrics.assertionCount;
          if(se->flags.traceAssertions){
            cwal_outputf(se->e, "%s passed:%.*s\n",
                         isAssert ? "Assertion" : "Affirmation",
                         (int)(end-begin), begin);
          }
      }
      *rv = cwal_value_true();
      return 0;
    }
  }
}

int s2_keyword_f_typename( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc;
  cwal_value * xrv = 0;
  s2_ptoken const origin = pr->token;
  assert(S2_T_KeywordTypename==pr->token.ttype);
  /*
    TODO:

    - peek and see if the next value is-a identifier. If so, use its string value
    to resolve
  */
  /* Behave as if the keyword wasn't there. */
  rc = s2_eval_expr_impl( se, pr, s2_ttype_op(S2_T_Comma),
                          S2_EVAL_UNKNOWN_IDENTIFIER_AS_UNDEFINED
                          /* | S2_EVAL_NO_SKIP_FIRST_EOL */,
                          &xrv );
  /* s2_dump_val(xrv, "TYPENAME result"); */
  if(!rc){
    if(!xrv){
      pr->errPos = origin.begin;
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "%s requires an RHS expression",
                                kw->word);
    }else if(se->skipLevel>0){
      *rv = cwal_value_undefined();
    }else{
      /* potential malloc optimization: if cwal_props_can(xrv) then
         see if it has a __typename property, and return that
         directly. Also consider extending cwal's typename mechanism
         to use Values, instead of C-strings, so that allocation is
         not required.
      */
      cwal_size_t tlen = 0;
      char const * tn = cwal_value_type_name2( xrv, &tlen );
      if(!tn){
        assert(!"Can't really happen, can it?");
        tn = cwal_value_type_id_name(CWAL_TYPE_UNDEF);
        tlen = cwal_strlen(tn);
      }
      assert(tlen>=0 /* zero's legal, i guess */);
      *rv = cwal_new_string_value(se->e, tn, tlen);
      rc = *rv ? 0 : CWAL_RC_OOM;
    }
  }
  return rc;
}

int s2_keyword_f_refcount( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  int rc;
  cwal_value * xrv = 0;
  s2_ptoken const origin = pr->token;
  rc = s2_eval_expr_impl( se, pr, s2_ttype_op(S2_T_Comma),
                          /* S2_EVAL_NO_SKIP_FIRST_EOL */ 0, &xrv );
  if(!rc){
    if(!xrv){
      pr->errPos = origin.begin;
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "%s requires an RHS expression",
                                kw->word);
    }else if(se->skipLevel>0){
      *rv = cwal_value_undefined();
    }else{
      *rv = cwal_new_integer(se->e, (cwal_int_t)cwal_value_refcount(xrv));
      rc = *rv ? 0 : CWAL_RC_OOM;
    }
  }
  return rc;
}

int s2_keyword_f_nameof( s2_keyword const * kw, s2_engine * se, s2_ptoker * pr, cwal_value **rv){
  s2_ptoken next = s2_ptoken_empty;
  int rc;
  assert(S2_T_KeywordNameof==pr->token.ttype);
  rc = s2_next_token( se, pr, 0, &next );
  if(rc) return rc;
  else if(S2_T_Identifier != next.ttype){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "%s requires an IDENTIFIER argument",
                                kw->word);
  }else if(se->skipLevel>0){
    pr->token = next;
    *rv = cwal_value_undefined();
    return 0;
  }else{
    cwal_value * xrv = 0;
    cwal_size_t tlen = (cwal_size_t)(next.end - next.begin);
    assert(next.begin<next.end);
    rc = s2_get( se, 0, next.begin, tlen, &xrv );
    if(rc) return rc;
    else if(!xrv){
      pr->errPos = next.begin;
      return s2_err_ptoker(se, pr, CWAL_RC_NOT_FOUND,
                                  "Cannot resolve identifier '%.*s'",
                                  (int)tlen, next.begin);
    }else{
      assert((S2_T_KeywordNameof==pr->token.ttype) && "lookahead broken?");
      pr->token = next;
      *rv = cwal_new_string_value(se->e, next.begin, tlen);
      return *rv ? 0 : CWAL_RC_OOM;
    }
  }
}  

/**
   Internal helper for collecting lists of declared variables, namely
   var/const and function parameter lists.

   Evaluates a comma-separated list of IDENTIFIER [= EXPR] tokens
   from pr until EOX, declaring each one in the current scope.
   If isConst, the vars are declared const.

   If argv is not 0 then it is assumed to be the arguments array
   passed to a function call, and it is populated with any default
   values not accounted for by that array (exception: in skip mode it
   is not modified). Default parameter values are not processed for
   slots filled by argv. Contrariwise, if the var list contains more
   entries than argv, any extras are appended to argv.  If rv is not
   0, the last-evaluated value is put in *rv.

*/
static int s2_keyword_f_var_impl( s2_keyword const * kw, s2_engine * se,
                                  s2_ptoker * pr, cwal_value **rv,
                                  char isConst,
                                  cwal_array * argv){
  s2_ptoken next = s2_ptoken_empty;
  s2_ptoken ident = s2_ptoken_empty;
  cwal_value * v;
  int rc;
  char gotComma;
  cwal_size_t argPos = 0;
  uint16_t const declFlags = isConst ? CWAL_VAR_F_CONST : 0;
  s2_op const * pseudoOp = s2_ttype_op(S2_T_Comma);
  cwal_size_t argc = argv ? cwal_array_length_get(argv): 0;
  assert(pseudoOp);
  next_part:
  v = 0;
  rc = s2_next_token( se, pr, 0, 0 );
  if(rc) return rc;
  else if(S2_T_Identifier != pr->token.ttype){
    if(argv && s2_ttype_is_eof(pr->token.ttype)){
      /* what was this for? */
      return 0;
    }else{
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "'%s' list requires an IDENTIFIER argument, "
                           "but got a %s.",
                           kw->word, s2_ttype_cstr(pr->token.ttype));
    }
  }
  gotComma = 0;
  ident = pr->token;
  if(!se->skipLevel && s2_ptoken_keyword(&ident)){
    /* confirm that it's not a keyword, as the user would not
       be able to use the var/param properly.
    */
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Cannot use keyword '%.*s' "
                         "as a %s name.",
                         (int)(ident.end-ident.begin),
                         ident.begin,
                         argv ? "parameter" : "variable");

  }

  rc = s2_next_token( se, pr, 0, &next);
  if(rc) return rc;
  switch(next.ttype){
    case S2_T_Comma:
      gotComma = 1;
      /* fall through */
    case S2_T_EOF:
    case S2_T_EOX:
      v = isConst ? 0 : cwal_value_undefined();
      pr->token = next /* consume it */;
      break;
    case S2_T_OpAssign:
      pr->token = next;
      if(argv && argPos<argc){
        /* Do not process default values for argument positions
           already filled by argv. The var decl below will use argv's
           value for this position.
        */
        rc = s2_eval_expr_impl(se, pr, pseudoOp, S2_EVAL_SKIP, 0);
        if(!rc) v = cwal_value_undefined();
      }else{
        rc = s2_eval_expr_impl(se, pr, pseudoOp, 0, &v);
      }
      if(rc) return rc;
      else if (!v /* && !s2_ptoker_is_eof(pr) */){
        return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                             "Unexpected empty expression "
                             "after %s assignment.",
                             argv
                             ? "parameter"
                             : (isConst?"const":"var"));
      }
      break;
    default:
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Unexpected token '%.*s' in %s decl '%.*s'.",
                           (int)(next.end-next.begin), next.begin,
                           argv
                             ? "parameter"
                             : (isConst?"const":"var"),
                           (int)(ident.end-ident.begin), ident.begin
                           );
  }
  if(!v){
    if(isConst){
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "%s decl '%.*s' requires a value.",
                           kw->word, (int)(ident.end-ident.begin),
                           ident.begin);
    }
    v = cwal_value_undefined();
  }

  assert(!rc);
  if(!se->skipLevel){
    cwal_size_t const tlen = (cwal_size_t)(ident.end-ident.begin);
    /* s2_dump_val(v, "var decl before"); */
    if(argv && argPos<argc){
      v = cwal_array_get(argv, argPos);
    }
    rc = s2_var_decl(se, ident.begin, tlen, v, declFlags);
    if(CWAL_RC_ALREADY_EXISTS==rc){
      pr->errPos = ident.begin;
      return s2_throw_ptoker(se, pr, rc,
                            "Var '%.*s' is already declared "
                            "in the current scope.",
                            (int)tlen, ident.begin);
    }
    else if(rc) return rc;
    /*MARKER(("Declared %s '%.*s'\n", kw->word, (int)tlen, ident.begin)); */
    /* s2_dump_val(v, "var decl after"); */
    ++argPos;
  }
  if(!s2_ptoker_is_eof(pr) && !gotComma){
    /* Check for a follow-up token */
    if(s2_ptoker_next_is_ttype(se, pr, 0, S2_T_Comma, 1)){
      gotComma = 1;
    }
  }
  if(gotComma) goto next_part;
  assert(0==rc);
  assert(v);
  if(rv) *rv = v;
  return rc;
}


int s2_keyword_f_var( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  char const isConst = (S2_T_KeywordConst==pr->token.ttype) ? 1 : 0;
  return s2_keyword_f_var_impl( kw, se, pr, rv, isConst, 0 );
}

int s2_keyword_f_unset( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  s2_ptoken next = s2_ptoken_empty;
  s2_ptoken origin = s2_ptoken_empty;
  s2_ptoken ident = s2_ptoken_empty;
  int rc;
  int nextTokenFlags = 0;
  char gotComma;
  int identLen;
  assert(S2_T_KeywordUnset==pr->token.ttype);
  next_part:
  origin = pr->token;
  rc = s2_next_token( se, pr, nextTokenFlags, 0 );
  if(rc) return rc;
  else if(S2_T_Identifier != pr->token.ttype){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                                "%s requires an IDENTIFIER argument",
                                kw->word);
  }
  gotComma = 0;
  nextTokenFlags = 0;
  ident = pr->token;
  identLen = (int)(ident.end - ident.begin);
  /* Check next token first to avoid unsetting the obj part of obj.prop. */
  rc = s2_next_token( se, pr, 0, &next);
  if(rc) return rc;
  switch(next.ttype){
    case S2_T_Comma:
      gotComma = 1;
      pr->token = next /* consume it */;
      /* Fall through */
    case S2_T_EOF:
    case S2_T_EOX:
    case S2_T_EOL:{
      if(!se->skipLevel){
        cwal_kvp * kvp = cwal_scope_search_kvp(se->e->current, 0,
                                               ident.begin,
                                               (cwal_size_t)identLen,
                                               0);
        if(!kvp){
          return s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND,
                                 "%s could not resolve identifier '%.*s' "
                                 "in the local scope.",
                                 kw->word, identLen, ident.begin);
        }else if(CWAL_VAR_F_CONST & cwal_kvp_flags(kvp)){
          return s2_throw_ptoker(se, pr, CWAL_RC_NOT_FOUND,
                                 "Cannot %s const '%.*s'.",
                                 kw->word, identLen, ident.begin);
        }
        rc = s2_set( se, 0, ident.begin, (cwal_size_t)identLen, 0);
        /* MARKER(("Unsetting ident [%.*s] rc=%d\n",
           (int)(ident.end-ident.begin), ident.begin, rc)); */
        if(rc && !cwal_exception_get(se->e)){
          rc = s2_throw_err_ptoker( se, pr );
        }
        if(rc) return rc;
      }
      if(gotComma) goto next_part;
      break;
    }
#if 0
    case S2_T_OpArrow:
#endif
    case S2_T_OpDot:
    case S2_T_BraceGroup:{
      s2_op const * pseudoOp = s2_ttype_op(S2_T_Comma);
      assert(pseudoOp);
      /* MARKER(("Unsetting a property?\n")); */
      /*
        Treat this as a property unset:

        OpDot | BraceGroup: eval expr with Comma
        precedence; get se->dotOpLhs and se->dotOpKey;

        That's full of weird corner cases, though:

        unset a.b = c.d

        would unset a.b but return no error.
      */
      pr->token = origin;
      rc = s2_eval_expr_impl(se, pr, pseudoOp, 0, 0);
      if(rc) return rc;
      else if(!se->skipLevel){
        if(!se->dotOpLhs || !se->dotOpKey){
          pr->errPos = ident.begin;
          return s2_throw_ptoker( se, pr, CWAL_SCR_SYNTAX,
                                  "Illegal RHS for %s operation.",
                                  kw->word);
        }
        cwal_value_ref(se->dotOpLhs);
        cwal_value_ref(se->dotOpKey);
        /* Do we want to support: unset hash # key? */
        s2_set_v( se, se->dotOpLhs, se->dotOpKey, 0 );
        cwal_value_unhand(se->dotOpLhs);
        cwal_value_unhand(se->dotOpKey);
      }
      if(s2_ptoker_next_is_ttype(se, pr, 0, S2_T_Comma, 1)){
        goto next_part;
      }
      break;
    }
    default:
      pr->errPos = next.begin;
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Unexpected token type %s in %s.",
                           s2_ttype_cstr(next.ttype),
                           kw->word
                           /*(int)(ident.end-ident.begin),
                             ident.begin*/);
  }
  assert(0==rc);
  *rv = cwal_value_undefined();
  return 0;
}

int s2_keyword_f_if( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ptoken next = s2_ptoken_empty;
  s2_ptoken tCondition = s2_ptoken_empty;
  s2_ptoken tBody = s2_ptoken_empty;
  s2_scope _SCOPE = s2_scope_empty;
  s2_scope * scope = 0;
  cwal_value * xrv = 0;
  char buul = 0;
  char finalElse = 0;
  char hasTrued = 0;
  int runCount = 0;
  int tlen;
  char bodyIsExpr = 0;
  next_if:
  ++runCount;
  /* Get the condition part... */
  rc = s2_next_token(se, pr, 0, 0);
  if(S2_T_ParenGroup != pr->token.ttype){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Expecting (...) after '%s'.",
                       kw->word);
    goto end;
  }
  tCondition = pr->token;

  /* Get the body part... */
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) goto end;

  capture_body:
  bodyIsExpr = 0;
  tBody = pr->token;
  while(S2_T_SquigglyString != tBody.ttype){
    /*^^^^ ??? s2_ptoken_is_true_squiggly(&pr->token) ??? */
    if(s2_ttype_is_eox(pr->token.ttype)){
      /* special case: empty body. */
#if 0
      tBody.begin = tCondition.end;
      if( (tBody.end = pr->token.end) > tBody.begin ) tBody.end = tBody.begin;
      bodyIsExpr = 1;
      break;
#else
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Empty non-block expression is not allowed.");
      goto end;
#endif
    }
    else{
      /* Check for an expression...

         Remember that tCondition.end is a bit screwy because it's based
         on the s2_ptoken::adjEnd of its input token.
      */
      if(!finalElse){
        pr->token.end = tCondition.end; 
      }
      /* MARKER(("pr->token=%.*s...\n",10, pr->token.begin)); */
      if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))){
        goto end;
      }else if(s2_ttype_is_eox(pr->token.ttype)
              /* is an expression ending with an EOX */){
        tBody = pr->capture;
        /*MARKER(("CAPTURE: %.*s\n", (int)(tBody.end - tBody.begin),
          tBody.begin));*/
        bodyIsExpr = 1;
        assert(tBody.end >= tBody.begin);
        break;
      }
    }
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Expecting {...} or a single "
                       "expression after %s(...).",
                       kw->word);
    goto end;
  }
  if(finalElse) goto the_body;

  if(1==runCount && !se->skipLevel){
    assert(!scope);
    scope = &_SCOPE;
    rc = s2_scope_push(se, scope);
    if(rc) return rc;
  }

  /* Eval the condition... */
  pr->token = tCondition;
  /* Reminder: we need to eval-sub, even in skip mode,
     for the corner case of an empty (). */
  rc = s2_eval_current_sub( se, pr, 0, &xrv );
  if(rc) goto end;
  else if(!xrv){
    rc = s2_err_ptoker( se, pr, CWAL_SCR_SYNTAX,
                        "Empty '%s' condition is not allowed.",
                        kw->word);
    goto end;
  }
  buul = (se->skipLevel>0) ? 0 : cwal_value_get_bool(xrv);
  xrv = 0;

  the_body:
  /* assert(S2_T_SquigglyString==pr->token.ttype || bodyIsExpr); */
  if(!finalElse && (!buul || hasTrued || se->skipLevel>0)){
    pr->token = tBody;
    if(0) pr->token.end = tBody.end /* kludge for single-expression cases */;
    if(bodyIsExpr){
      s2_next_token(se, pr, 0, 0)
        /* Slurp up the semicolon */;
      assert(s2_ttype_is_eox(pr->token.ttype));
    }
    goto check_else;
  }

  if(bodyIsExpr){
    pr->token.end = tBody.begin;
    rc = s2_eval_expr_impl(se, pr, 0, 0, 0);
  }else{
    pr->token = tBody;
    rc = s2_eval_current_sub(se, pr, 0, 0);
  }

  if(rc) goto end;
  else if(!hasTrued && !finalElse){
    hasTrued = 1;
    ++se->skipLevel;
  }
 
  check_else:
  if(finalElse) goto end;
  bodyIsExpr = 0;
  next = s2_ptoken_empty;
  rc = s2_next_token(se, pr, 0, &next);
  /*MARKER(("after IF: <<<%.*s>>>\n", (int)(next.end-next.begin),
    next.begin));*/
  if(!rc
     && 4==(tlen=(int)(next.end-next.begin))
     && 0==memcmp(next.begin,"else",4)){
    s2_keyword const * ifCheck;
    pr->token = next /*consume it*/;
    next = s2_ptoken_empty;
    rc = s2_next_token(se, pr, 0, &next);
    if(rc) goto end;
    else if((ifCheck=s2_ptoken_keyword(&next))
            && (S2_T_KeywordIf==ifCheck->id)){
      pr->token = next /*consume it*/;
      goto next_if;
    }else{
      /*MARKER(("final else: %s <<<%.*s>>>\n",
        s2_ttype_cstr(next.ttype),
        (int)(next.end-next.begin), next.begin));*/
      pr->token = next /* make sure we've got the right token type */;
      if(S2_T_SquigglyString!=next.ttype){
        pr->token.end = next.begin /*fudge starting point for pending eval*/;
      }
      finalElse = 1;
      goto capture_body;
    }
  }

  end:
  if(hasTrued) --se->skipLevel;
  if(scope){
    assert(scope->parent);
    s2_scope_pop(se);
  }
  if(!rc) *rv = se->skipLevel
            ? cwal_value_undefined()
            : (hasTrued
               ? cwal_value_true()
               : cwal_value_false())
            ;
  /*MARKER(("tokenizer at: %.*s\n",
    (int)(pr->end - pr->token.end), pr->token.end));*/
  return rc;
}

int s2_keyword_f_continue( s2_keyword const * kw, s2_engine * se,
                           s2_ptoker * pr, cwal_value **rv){
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    return s2_err_ptoker(se, pr, CWAL_RC_CONTINUE, 0);
  }
}


int s2_keyword_f_while( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ptoken tCondition = s2_ptoken_empty;
  s2_ptoken tBody = s2_ptoken_empty;
  s2_scope _SCOPE = s2_scope_empty;
  s2_scope * scope = &_SCOPE;
  cwal_value * xrv = 0;
  char bodyIsExpr = 0;

  /* Get the condition part... */
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  else if(S2_T_ParenGroup!=pr->token.ttype){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting (...) after '%s'.",
                         kw->word);
  }
  tCondition = pr->token;

  /* Get the body part...

     This block of the function is almost 100% identical to that in
     the for-loop (including this comment).  Maybe we can consolidate
     them.
  */
  rc = s2_next_token(se, pr, 0, 0);
  tBody = pr->token;
  while(S2_T_SquigglyString != tBody.ttype){
    /*^^^^ ??? s2_ptoken_is_true_squiggly(&pr->token) ??? */
    if(s2_ttype_is_eox(pr->token.ttype)){
      /* special case: empty body. */
      tBody.begin = tCondition.end;
      bodyIsExpr = 1;
      break;
    }
    else{
      /* Check for an expression right after tCondition... */
      pr->token.end = tCondition.end;
      /* MARKER(("pr->token=%.*s...\n",10, pr->token.begin)); */
      if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))) return rc;
      else if(s2_ttype_is_eox(pr->token.ttype)
              /* is an expression ending with an EOX */){
        tBody = pr->capture;
        /* MARKER(("CAPTURE: %.*s\n", (int)(tBody.end - tBody.begin), tBody.begin)); */
        bodyIsExpr = 1;
        assert(tBody.end >= tBody.begin);
        break;
      }
    }
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting {...} or a single "
                         "expression after %s(...).",
                         kw->word);
  }

  /* Run the tBody while the tCondition evaluates
     to truthy... */
  while(!rc){
    if(scope->parent){
      s2_scope_pop(se);
      assert(!scope->parent);
    }
    rc = s2_scope_push(se, scope);
    if(rc) break;
    assert(scope->parent);
    pr->token = tCondition;
    assert(!xrv);
    rc = s2_eval_current_sub( se, pr, 0, &xrv );
    if(rc) break;
    else if(!xrv){
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Invalid empty expression in %s().",
                         kw->word);
      break;
    }else if(!cwal_value_get_bool(xrv)){
      xrv = 0;
      pr->token = tBody /* consume the body. err... you know what i mean. */;
      break;
    }
    else{
      xrv = 0;
      pr->token = tBody /* set body up for eval */;
      if(se->skipLevel>0){
        /* we only need to get this far to ensure
           syntax rules are met. */
        break;
      }
      if(bodyIsExpr){
        pr->token.end = tBody.begin;
        rc = s2_eval_expr_impl(se, pr, 0, 0, 0);
      }else{
        rc = s2_eval_current_sub(se, pr, 0, 0);
      }
      if(rc){
        if(CWAL_RC_BREAK==rc){
          xrv = s2_propagating_take(se);
          assert(xrv);
          assert(scope->parent);
          cwal_value_rescope(scope->scope.parent, xrv);
          rc = 0;
          s2_engine_err_reset(se);
          break;
        }else if(CWAL_RC_CONTINUE==rc){
          rc = 0;
          s2_engine_err_reset(se);
        }
      }
    }
  }
  
  if(scope->parent){
    s2_scope_pop(se);
    assert(!scope->parent);
  }
  if(!rc) *rv = se->skipLevel
            ? cwal_value_undefined()
            : (xrv ? xrv : cwal_value_undefined());
  return rc;
}

int s2_keyword_f_dowhile( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ptoken tCondition = s2_ptoken_empty;
  s2_ptoken tBody = s2_ptoken_empty;
  s2_ptoken const tOrigin = pr->token;
  s2_scope _SCOPE = s2_scope_empty;
  s2_scope * scope = &_SCOPE;
  cwal_value * xrv = 0;
  char bodyIsExpr = 0;
  /* Get the body part...

     This block of the function is almost 100% identical to that in
     the for- and while-loops (including this comment).  Maybe we can
     consolidate them.
  */
  rc = s2_next_token(se, pr, 0, 0);
  tBody = pr->token;
  while(S2_T_SquigglyString != tBody.ttype){
    /*^^^^ ??? s2_ptoken_is_true_squiggly(&pr->token) ??? */
    if(s2_ttype_is_eox(pr->token.ttype)){
      /* special case: empty body. */
      bodyIsExpr = 1;
      break;
    }
    else{
      /* Check for an expression right after tCondition... */
      /* MARKER(("pr->token=%.*s...\n",10, pr->token.begin)); */
      pr->token.end = tOrigin.end /* reset parse pos */;
      if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))) return rc;
      else if(s2_ttype_is_eox(pr->token.ttype)
              /* is an expression ending with an EOX */){
        tBody = pr->capture;
        /* MARKER(("CAPTURE: %.*s\n", (int)(tBody.end - tBody.begin), tBody.begin)); */
        bodyIsExpr = 1;
        assert(tBody.end >= tBody.begin);
        break;
      }
    }
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting {...} or a single "
                         "expression after %s(...).",
                         kw->word);
  }

  /* Get the while(condition) parts... */
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  else{
    s2_keyword const * kWhile = s2_ptoken_keyword(&pr->token);
    if(!kWhile){
      return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                           "Expecting while(...) after '%s' body.",
                           kw->word);
    }
  }
  /* Get the (condition) part... */
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  else if(S2_T_ParenGroup != pr->token.ttype){
    rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                       "Expecting (...) after %s...while.",
                       kw->word);
  }
  tCondition = pr->token;
  if(se->skipLevel){
    *rv = cwal_value_undefined();
    return 0;
  }
  /* Run the tBody while the tCondition evaluates
     to truthy... */
  do{
    char doBreak = 0;
    if(scope->parent){
      s2_scope_pop(se);
      assert(!scope->parent);
    }
    rc = s2_scope_push(se, scope);
    if(rc) break;
    assert(scope->parent);

    /* Run the body... */
    xrv = 0;
    pr->token = tBody /* set body up for eval */;
    if(bodyIsExpr){
      pr->token = tOrigin;
      rc = s2_eval_expr_impl(se, pr, 0, 0, 0);
    }else{
      rc = s2_eval_current_sub(se, pr, 0, 0);
    }
    if(rc){
      if(CWAL_RC_BREAK==rc){
        xrv = s2_propagating_take(se);
        assert(xrv);
        assert(scope->parent);
        cwal_value_rescope(scope->scope.parent, xrv);
        rc = 0;
        s2_engine_err_reset(se);
        doBreak = 1;
        break;
      }else if(CWAL_RC_CONTINUE==rc){
        rc = 0;
        s2_engine_err_reset(se);
        /* fall through... */
      }else{
        break /* other, more serious, error,
                 or a 'return' or similar */;
      }
    }
    if(doBreak) break;
    pr->token = tCondition;
    xrv = 0;
    rc = s2_eval_current_sub( se, pr, 0, &xrv );
    if(rc) break;
    else if(!xrv){
      rc = s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Invalid empty expression in %s...while().",
                         kw->word);
      break;
    }else if(!cwal_value_get_bool(xrv)){
      xrv = 0;
      break;
    }
    /* else continue */
  }while(!rc);
  
  if(scope->parent){
    s2_scope_pop(se);
    assert(!scope->parent);
  }
  if(!rc){
    pr->token = tCondition /* consume the final (condition) part */;
    *rv = se->skipLevel
      ? cwal_value_undefined()
      : (xrv ? xrv : cwal_value_undefined());
  }
  return rc;
}


int s2_keyword_f_for( s2_keyword const * kw, s2_engine * se,
                      s2_ptoker * pr, cwal_value **rv){
  int rc = 0;
  s2_ptoken tCondition = s2_ptoken_empty;
  s2_ptoken tPre = s2_ptoken_empty;
  s2_ptoken tPost = s2_ptoken_empty;
  s2_ptoken tBody = s2_ptoken_empty;
  s2_ptoker prParens = s2_ptoker_empty;
  s2_scope _SCOPEOUT = s2_scope_empty;
  s2_scope * scopeOut = &_SCOPEOUT;
  s2_scope _SCOPEIN = s2_scope_empty;
  s2_scope * scopeIn = &_SCOPEIN;
  cwal_value * xrv = 0;
  char bodyIsExpr = 0;
  unsigned int runCount = 0;
   /* Get the condition part... */
  rc = s2_next_token(se, pr, 0, 0);
  if(rc) return rc;
  else if(S2_T_ParenGroup!=pr->token.ttype){
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting (...) after '%s'.",
                         kw->word);
  }
  s2_ptoker_sub_from_token( &prParens, pr );
  prParens.end = pr->token.end-1 /* re-adjust for pr->token.adjEnd,
                                    for non-{} loop bodies */;

  /* Get the body part...

     This block of the function is almost 100% identical to that in
     the while-loop (including this comment).  Maybe we can
     consolidate them.
  */
  rc = s2_next_token(se, pr, 0, 0);
  tBody = pr->token;
  while(S2_T_SquigglyString != tBody.ttype){
    /*^^^^ ??? s2_ptoken_is_true_squiggly(&pr->token) ??? */
    if(s2_ttype_is_eox(pr->token.ttype)){
      /* special case: empty body. */
      tBody.begin = prParens.end+1;
      if( (tBody.end = pr->token.end) > tBody.begin ) tBody.end = tBody.begin;
      bodyIsExpr = 1;
      break;
    }
    else{
      /* Check for an expression right after prParens...

         Remember that prParens.end is a bit screwy because it's based
         on the s2_ptoken::adjEnd of its input token.
      */
      pr->token.end = prParens.end+1;
      /* MARKER(("pr->token=%.*s...\n",10, pr->token.begin)); */
      if((rc = s2_eval_expr_impl(se, pr, 0, S2_EVAL_SKIP, 0))) return rc;
      else if(s2_ttype_is_eox(pr->token.ttype)
              /* is an expression ending with an EOX */){
        tBody = pr->capture;
        /* MARKER(("CAPTURE: %.*s\n", (int)(tBody.end - tBody.begin), tBody.begin)); */
        bodyIsExpr = 1;
        assert(tBody.end >= tBody.begin);
        break;
      }
    }
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting {...} or a single "
                         "expression after %s(...).",
                         kw->word);
  }

  /* Capture the initial expr of (x;y;z) */
  tPre = prParens.token;
  rc = s2_eval_expr_impl(se, &prParens, 0, S2_EVAL_SKIP, 0);
  if(rc) return rc;
  if( S2_T_EOX != prParens.token.ttype){
    pr->errPos = prParens.token.begin;
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting ';' at start of %s(...), "
                         "but got %s.",
                         kw->word,
                         s2_ttype_cstr(prParens.token.ttype));
  }
  /* reminder to self: tPre, being assigned before prParens' first token
     is fetched, has no 'end'. We probably don't need it. */

  /* Capture the condition expr of (x;y;z) */
  tCondition = prParens.token;
  rc = s2_eval_expr_impl(se, &prParens, 0, S2_EVAL_SKIP, 0);
  if(rc) return rc;
  if( S2_T_EOX != prParens.token.ttype){
    pr->errPos = prParens.token.begin;
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Expecting ';' after condition part "
                         "of %s(...), but got %s.",
                         kw->word,
                         s2_ttype_cstr(prParens.token.ttype));
  }

  /* Capture the post-loop expr of (x;y;z) and make sure it's sound */
  tPost = prParens.token;
  rc = s2_eval_expr_impl(se, &prParens, 0, S2_EVAL_SKIP, 0);
  if(rc) return rc;
  if( !s2_ttype_is_eof(prParens.token.ttype)){
    pr->errPos = prParens.token.begin;
    return s2_err_ptoker(se, pr, CWAL_SCR_SYNTAX,
                         "Extra junk after post-loop part of "
                         "of %s(...): got a %s.",
                         kw->word,
                         s2_ttype_cstr(prParens.token.ttype));
  }

  /* Now we know that the ( pre; condition; post ) part is well-formed */

  if(se->skipLevel>0){
    goto end;
  }

  /*
    We need two scopes here if we want to support var decls
    in both the prefix part and loop body. The first scope
    opens just before the (...) is run, the second is pushed
    before each loop iteration, before the condition is run,
    and popped after the post-loop is run.
  */
  rc = s2_scope_push(se, scopeOut);
  if(rc) return rc;
  /* No RETURNs as of here... */

  /* Run the prefix part */
  prParens.token = tPre;
  rc = s2_eval_expr_impl( se, &prParens, 0, 0, 0 );
  if(rc) goto end;

  /* Run the tBody while the tCondition evaluates
     to truthy... */
  while( !rc ){
    cwal_value * condRv = 0;
    if(scopeIn->parent){
      /* Inner scope needs to be re-initialized on each
         iter.
      */
      s2_scope_pop(se);
      assert(!scopeIn->parent);
    }
    if(0==(++runCount % 5 /* # arbitrarily chosen, the point being
                           only to not sweep on every iteration
                           (would be overkill).*/)){
      s2_engine_sweep(se)
        /* Outer scope: we need this to clean up overwritten loop vars
           declared in this scope, though i'm not sure why refcounting
           isn't doing it for us? Simple loop/metrics tests imply
           that it's not happening, anyway, or that refs are being
           held elsewhere. Specifically, it's easy to see with:

           var y = 100;
           for(var x = 0; x<y; ++x);
           s2.dumpMetrics();

           for various values of y. The metrics report more allocs than
           expected, starting when the recycling bin size for integers
           is (not incidentally!) y-1.

           Oh, i am pretty sure it leads back to: the ++ op (and
           similar ops) holds a ref to all the operands because
           overloads can do funny things with them. When it's done, it
           unhand()s them, which makes them available for sweep but
           does not destroy them outright.

           Changing operator++ and friends to not do ref/unhand
           combinations looks like it might be tricky, largely because
           of overload side-effects. So we'll sweep for now and look
           into fixing that later.
        */;
    }
    rc = s2_scope_push(se, scopeIn);
    if(rc) break;
    assert(scopeIn->parent)

    /* Check the condition part */;
    prParens.token = tCondition;
    rc = s2_eval_expr_impl( se, &prParens, 0, 0, &condRv );
    if(rc) break;
    if( condRv /* Treat empty expr as true here */
        && !cwal_value_get_bool(condRv) ) break;

    /* Run the body ... */;
    xrv = 0;

    if(bodyIsExpr){
      pr->token.end = tBody.begin;
      rc = s2_eval_expr_impl(se, pr, 0, 0, 0);
    }else{
      pr->token = tBody;
      rc = s2_eval_current_sub(se, pr, 0, 0);
    }
    if(rc){
      if(CWAL_RC_BREAK==rc){
        xrv = s2_propagating_take(se);
        assert(xrv);
        assert(scopeOut->scope.parent);
        cwal_value_rescope(scopeOut->scope.parent, xrv);
        rc = 0;
        s2_engine_err_reset(se);
        break;
      }else if(CWAL_RC_CONTINUE==rc){
        rc = 0;
        s2_engine_err_reset(se);
      }
      else break;
    }

    /*
      Run the post part. By an accident of design, this runs while the
      per-iteration scope is active, meaning it can reference vars
      declared there. Feature or bug?
     */
    prParens.token = tPost;
    rc = s2_eval_expr_impl( se, &prParens, 0, 0, 0 );
  }

  end:
  if(scopeIn->parent){
    assert(scopeIn == se->currentScope);
    s2_scope_pop(se);
    assert(!scopeIn->parent);
  }
  if(scopeOut->parent){
    assert(scopeOut == se->currentScope);
    s2_scope_pop(se);
    assert(!scopeOut->parent);
  }
  if(!rc){
    pr->token = tBody;
    *rv = se->skipLevel
      ? cwal_value_undefined()
      : (xrv ? xrv : cwal_value_undefined());
  }
  return rc;
}


int s2_eval_filename( s2_engine * se, char pushScope,
                      char const * fname,
                      cwal_int_t fnlen,
                      cwal_value ** rv ){
  int rc;
  cwal_buffer buf = cwal_buffer_empty;
  cwal_value * xrv = 0;
  s2_scope _SCOPE = s2_scope_empty;
  s2_scope * scope = pushScope ? &_SCOPE : 0;
  cwal_size_t const nFname = (fnlen<0) ? cwal_strlen(fname) : (cwal_size_t)fnlen;
  if(rv) *rv = 0;
  rc = cwal_buffer_fill_from_filename2( se->e, &buf, fname, nFname );
  if(rc){
    rc = s2_engine_err_set(se, rc, "%s: %s",
                           buf.used ? "Could not open" : "Could not read",
                           fname);
  }else{
    if(scope){
      rc = s2_scope_push(se, scope);
      if(!rc){
        assert(0==se->sguard->sweep);
        assert(0==se->sguard->vacuum);
      }
    }
    if(!rc){
      rc = s2_eval_cstr( se, 0, fname, (char const *)buf.mem,
                         (int)buf.used, rv ? &xrv : 0 );
      switch(rc){
        case CWAL_RC_EXCEPTION:
        case CWAL_RC_OOM:
        case CWAL_RC_EXIT:
        case CWAL_RC_FATAL:
        case CWAL_RC_ASSERT:
          break;
        case CWAL_RC_RETURN:
          rc = 0;
          xrv = s2_propagating_take(se);
          /* fall through */
        case 0:
          if(scope && xrv && rv) cwal_value_rescope(scope->scope.parent, xrv);
          if(rv) *rv = xrv;
          break;
        default:
          if(se->err.code){
            rc = s2_throw_err(se, 0, 0, 0, 0);
          }          
      }
    }
    if(scope && scope->parent){
      s2_scope_pop(se);
    }
  }
  cwal_buffer_clear(se->e, &buf);
  /* MARKER(("se->err.script=%.*s\n", (int)se->err.script.used, (char const *)se->err.script.mem)); */

  if(!rc && rv){
    assert((!*rv || cwal_value_scope(*rv) || cwal_value_is_builtin(*rv))
           && "Seems like we've cleaned up a value too early.");
  }
  return rc;
}

/**
   The cwal_callback_f() impl used by script-side functions
   created by s2_keyword_f_function().
*/
static int s2_callback_f( cwal_callback_args const * args, cwal_value ** rv ){
  int rc = 0;
  s2_func_state * fst = (s2_func_state *)cwal_args_state(args, &s2_func_state_empty);
  s2_ptoker _pr = s2_ptoker_empty;
  s2_ptoker * pr = &_pr;
  s2_ptoken tBody = s2_ptoken_empty;
  s2_ptoken tParams = s2_ptoken_empty ;
  s2_engine * se = fst ? fst->se : 0;
  cwal_array * cbArgv = se ? se->callArgV : 0
    /* Should be the array form of args->argv */
    ;
  char const * src = 0;
  cwal_size_t srcLen = 0;
  cwal_value * v;
  s2_ptoker const * oldScript = se->currentScript;
  s2_func_state const * oldScriptFunc = se->currentScriptFunc;
  se->callArgV = 0
    /* Necessary so that this array does not "leak" into certain
       nested call constructs. Triggered by calling an overloaded
       operator inside a script function - the _first_ set of call
       args were wrong (leaked in from a higher call).
    */;

  assert(fst);
  assert(fst->se);
  assert(se->funcStash);

  if(fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS
     && fst->flags & S2_FUNCSTATE_F_EMPTY_BODY){
    /* We have neither a body nor params, so let's
       go the easy route.

       One might think that we could do this if the body
       is empty but the parameter list is not, but
       default param values can have side-effects, so we
       need to evaluate those.
    */
    assert(!fst->keyScriptName);
    assert(!fst->keySrc);
    *rv = cwal_value_undefined();
    return 0;
  }
  assert(cbArgv || (fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS));

  assert(fst->keyScriptName);
  assert(cwal_value_scope(fst->keyScriptName));

  assert(fst->keySrc);
  assert(cwal_value_scope(fst->keySrc) || cwal_value_is_builtin(fst->keySrc));

  /* As of here, no 'return' - use goto end. */
  v = cwal_hash_search_v(se->funcStash, fst->keySrc);
  assert(v);
  src = cwal_value_get_cstr(v, &srcLen);
  assert(src);
  assert(srcLen>=8);

  rc = s2_ptoker_init( pr, src, (cwal_int_t)srcLen );
  assert(!rc);

  se->currentScriptFunc = fst;
  /* pr->parent = oldScript */ /* does weird, weird things? */;
  se->currentScript = pr;
  pr->name = cwal_value_get_cstr(fst->keyScriptName, &pr->nameLen);
  pr->lineOffset = fst->line;
  pr->colOffset = fst->col;
  /* MARKER(("line=%d, col=%d\n", pr->lineOffset, pr->colOffset)); */
  /* s2_dump_val(fst->keyScriptName, "fst->keyScriptName"); */
  /* MARKER(("Calling func from script [%s]\n", pr->name)); */

  /* Skip over keyword */
  rc = s2_next_token( se, pr, 0, 0 );
  assert(!rc && "Should have failed at decl time!");
  if(rc) goto end;

  /* Get the params */
  rc = s2_next_token( se, pr, 0, 0 );
  assert(!rc && "Should have failed at decl time!");
  if(rc) goto end;
  if(S2_T_Identifier==pr->token.ttype){ /* The name part */
    rc = s2_next_token( se, pr, 0, 0 );
    assert(!rc && "Should have failed at decl time!");
    if(rc) goto end;
  }
  tParams = pr->token;
  assert(S2_T_ParenGroup==tParams.ttype);

  /* The body... */
  rc = s2_next_token( se, pr, 0, 0 );
  assert(!rc && "Should have failed at decl time!");
  if(rc) goto end;
  tBody = pr->token;
  assert(S2_T_SquigglyString==tBody.ttype);

  assert(pr->begin<tParams.begin);
  assert(pr->begin<tBody.begin);
  assert(pr->end>tParams.end);
  assert(pr->end>=tBody.end);

  if(!(fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)){
    /* Collect parameter list */
    s2_ptoker sub = s2_ptoker_empty;
    pr->token = tParams;
    s2_ptoker_sub_from_token(&sub, pr);
    /* MARKER(("PARAMS: %.*s\n", (int)(tParams.end-tParams.begin), tParams.begin)); */
    rc = s2_keyword_f_var_impl( s2_ttype_keyword(S2_T_KeywordFunction),
                                se, &sub, 0, 0, cbArgv ); 
    /* s2_dump_val(cwal_array_value(cbArgv), "func->argv"); */
    if(rc) goto end;
  }
  /* MARKER(("CALLING:\n%.*s\n", (int)srcLen, cwal_string_cstr(srcStr))); */
  if(!(fst->flags & S2_FUNCSTATE_F_EMPTY_BODY)){
    pr->token = tBody;
    rc = s2_eval_current_sub( se, pr, 0, rv );
    switch(rc){
      case 0:
        *rv = cwal_value_undefined();
        break;
      case CWAL_RC_RETURN:
        rc = 0;
        *rv = s2_propagating_take(se);
        assert(*rv);
        break;
    }
    /* s2_dump_val(*rv, "call result"); */
  }
  end:
  se->currentScriptFunc = oldScriptFunc;
  se->currentScript = oldScript;
  return rc;
}

/**
   cwal_callback_hook_pre_f() impl which installs the following
   vars in argv->scope:

   this
   argv
   argv.callee
   funcNameIfSetInFuncDecl

   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().

   s2_engine_from_state(args) must return a (s2_interp*) or an
   assertion may be triggered (or a NULL pointer deref).
*/
int s2_callback_hook_pre(cwal_callback_args const * args,
                         void * state){
  s2_func_state * fs = args->callee
    ? (s2_func_state *)cwal_args_callee_state(args, &s2_func_state_empty)
    : NULL;
  /* MARKER(("PRE-FUNC HOOK fstate=%p argc=%d!\n", (void const *)fs, (int)args->argc)); */
  if(!fs || (fs->flags & S2_FUNCSTATE_F_EMPTY_BODY
             && fs->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)){
    /* MARKER(("Not a script function. Not injecting args/this.\n")); */
    s2_engine * se = s2_engine_from_state(args->engine);
    se->callArgV = 0 /* Make sure this doesn't propagate through to a script
                        func that this native function calls. Been
                        there, debugged that! */;
    return 0;
  }
#if 0
  else if(0 && cwal_scope_search(args->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;
  }
#endif
  else{
    static char const * importedSymbolsSym = "%imported%"
      /* ACHTUNG: this symbol string is duplicated in s2_func.c */
      ;
    static const cwal_size_t importedSymbolsSymLen = 10 /* as is this length! */;
    int rc = 0;
    cwal_array * ar;
    cwal_value * av;
    cwal_value * calleeV;
    cwal_scope * s = args->scope;
    s2_engine * se = fs ? fs->se : s2_engine_from_state(args->engine);
    cwal_value * v;
    assert(se);
    calleeV = cwal_function_value(args->callee);
    assert(calleeV);
    if(!fs /* native call, which _might_ eval script code */
       ||
       !(/* empty function */
         fs->flags & S2_FUNCSTATE_F_EMPTY_BODY
         && fs->flags & S2_FUNCSTATE_F_EMPTY_PARAMS)
       ){

      ar = se->callArgV;
      /* s2_dump_val(cwal_array_value(ar),"ar"); */
      if(ar){
        /* This call is coming from a script-parsed call() point
           about to call into s2_callback_f().*/
        av = cwal_array_value(ar);
        cwal_value_ref(av);
      }else{
        /* This is probably being called from C code. */
        int i = (int)args->argc;
        /* MARKER(("Script func called from C code? argc=%d\n", i)); */
        ar = cwal_new_array(args->engine);
        if(!ar){
          return CWAL_RC_OOM;
        }
        av = cwal_array_value(ar);
        cwal_value_ref(av);
#if 0
        cwal_value_make_vacuum_proof(av, 1)
          /* can _potentially_ lead to a leak if the client holds a
             ref, but evaling calls via C-strings passed in via
             cwal_callback_f()'s might need this. */
          ;
#endif
        for( ; !rc && i > 0; --i ){
          /* s2_dump_val(args->argv[i-1],"args->argv[i-1]"); */
          rc = cwal_array_set(ar, (cwal_size_t)i-1, args->argv[i-1]);
        }
        if(rc){
          cwal_value_unref(av);
          return rc;
        }
        se->callArgV = ar /* i don't like this, but currently needed */;
      }
      if(0){
        MARKER(("Function call() argc=%d...\n", (int)args->argc));
        s2_dump_val(args->self, "args->self");
        s2_dump_val(av, "argv");
      }

      /* s2_dump_val(cwal_array_value(ar),"args array"); */
      rc = cwal_scope_chain_set( s, 0, "argv", 4, av);
      cwal_value_unref(av);
      if(rc) return rc;

      if( (v = cwal_prop_get(calleeV, "name", 4))
          &&
          (rc = cwal_scope_chain_set_v(s, 0, v, calleeV)) ){
        return rc;
      }

      rc = cwal_scope_chain_set( s, 0, "this", 4, args->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;
      else{
        /* Import "closured" properties (Function.importSymbols())
           into the current scope.
        */
        cwal_value * imported = cwal_prop_get( calleeV, importedSymbolsSym,
                                               importedSymbolsSymLen );
        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;
  }
}

int s2_callback_hook_post(cwal_callback_args const * args,
                          void * state,
                          int fRc, cwal_value * rv){
  int rc = 0;
  s2_engine * se = s2_engine_from_state(args->engine);
  /* s2_func_state * fs = (s2_func_state *)cwal_args_callee_state(args, &s2_func_state_empty); */
  assert(se);
  /* MARKER(("Callback post-hook\n")); */
  /* s2_dump_val(cwal_array_value(se->callArgV), "se->callArgV"); */
  se->callArgV = 0
    /* required for certain calling combinations of native vs script funcs
       to work. */;
  se->dotOpSelf = se->dotOpKey = se->dotOpLhs = 0 /* Just in case */;
#if 0
  if(!fs) return 0;
  if(fRc && fs){
    /* Script function: if it threw an exception with no location info,
       let's ammend the location info now where we have the input
       script source. */
    cwal_value * ex = cwal_exception_get(args->engine):
    assert(ex);
    if(!cwal_prop_get(ex, "line", 4)){
      rc = s2_ammend_script_func_exception(se, fs, ex);
    }
  }
#endif
  return rc;
}
    

/**
   Internal helper to populate s2_func_state on behalf of a function
   declation.
*/
static int s2_function_setup_srcinfo( s2_engine * se, s2_ptoker const * pr,
                                      s2_ptoken const * tOrigin,
                                      s2_ptoken const * tName,
                                      s2_ptoken const * tBody,
                                      s2_func_state * fst,
                                      cwal_value * func ){
  int tlen = (int)(tBody->end - tOrigin->begin);
  int rc = 0;
  cwal_hash * h = s2_fstash(se);
  cwal_value * v;
  cwal_size_t nameLen = 0;
  char const * scriptName = s2_ptoker_name_first(pr, &nameLen);
  if(!h){
    return CWAL_RC_OOM;
  }
  assert(tlen >=8 /*proc(){}*/);
  /* MARKER(("setting up srcinfo for:\n%.*s\n", tlen, tOrigin->begin)); */

#if 0
  {
    char const * oldPos = pr->errPos;
    pr->errPos = tOrigin->begin;
    rc = s2_add_script_props( se, vInfo, pr );
    pr->errPos = oldPos;
    if(rc) goto end;
  }
#endif

  /* Calculate script/line/column info, as we need those for
     error reporting. */
  s2_ptoker_count_lines( pr, tOrigin->begin, &fst->line, &fst->col );
#if 1
  /* currenly used as the mechanism for holding a func's name. */
  if(tName->begin){
    assert(tName->end>tName->begin);
    v = cwal_new_string_value(se->e, tName->begin,
                              (cwal_size_t)(tName->end - tName->begin));
    if(!v){
      return CWAL_RC_OOM;
    }
    rc = cwal_prop_set_with_flags(func, "name", 4, v,
                                  CWAL_VAR_F_HIDDEN | CWAL_VAR_F_CONST);
    if(rc){
      cwal_value_unref(v);
      return rc;
    }
  }
#endif

  if(!(fst->flags & S2_FUNCSTATE_F_EMPTY_PARAMS
       && fst->flags & S2_FUNCSTATE_F_EMPTY_BODY)
     ){
    /* For "empty" functions (no non-junk tokens),
       we don't really need any of these string bits...
    */
    v = cwal_new_string_value(se->e, tOrigin->begin, (cwal_size_t)tlen);
    if(!v){
      return CWAL_RC_OOM;
    }
    fst->keySrc = s2_fstash_next_id(se);
    if(!fst->keySrc){
      cwal_value_unref(v);
      assert(!"function source code disappeared?");
      return CWAL_RC_OOM;
    }
    rc = cwal_hash_insert_v( h, fst->keySrc, v, 1 );
    if(rc){
      cwal_value_unref(v);
      cwal_value_unref(fst->keySrc);
      fst->keySrc = 0;
      return rc;
    }
    if(scriptName && *scriptName){
      v = cwal_hash_search( h, scriptName, nameLen );
      if(!v){
        v = cwal_new_string_value(se->e, scriptName, nameLen);
        if(!v){
          return CWAL_RC_OOM;
        }
        rc = cwal_hash_insert_v( h, v, v, 0 );
        if(rc){
          cwal_value_unref(v);
          return rc;
        }
      }
      fst->keyScriptName = v;
      /* s2_dump_val(v, "fst->keyScriptName"); */
    }
  }
  return 0;
}

int s2_keyword_f_function( s2_keyword const * kw, s2_engine * se,
                           s2_ptoker * pr, cwal_value **rv){
  s2_ptoken tParams = s2_ptoken_empty;
  s2_ptoken tBody = s2_ptoken_empty;
  s2_ptoken tName = s2_ptoken_empty;
  s2_ptoken const tOrigin = pr->token;
  int rc;
  cwal_function * cf = 0;
  cwal_value * vf = 0;
  s2_func_state * fst = 0;
  int fFlags = 0;
  rc = s2_next_token( se, pr, 0, &tParams );
  if(rc) goto end;
  if(S2_T_Identifier==tParams.ttype){
    tName = tParams;
    pr->token = tParams;
    rc = s2_next_token( se, pr, 0, &tParams );
    if(rc) goto end;
  }
  if(S2_T_ParenGroup != tParams.ttype){
    rc = s2_throw_ptoker( se, pr, CWAL_SCR_SYNTAX,
                          "Expecting [identifier](...) after '%s', "
                          "but got token type %s.",
                          kw->word,
                          s2_ttype_cstr(tBody.ttype));
    goto end;
  }
  pr->token = tParams;
  if(!s2_ptoken_has_content(&pr->token)){
    fFlags |= S2_FUNCSTATE_F_EMPTY_PARAMS;
  }
  /*
    TODO:

    - ? skip-eval the params and body for syntactic correctness here.

    - If body is empty, install a shared instance stored in se's stash.
  */

  rc = s2_next_token( se, pr, 0, &tBody );
  if(rc) goto end;
  else if(S2_T_SquigglyString != tBody.ttype){
    rc = s2_throw_ptoker( se, pr, CWAL_SCR_SYNTAX,
                          "Expecting {...} after %s(...), "
                          "but got token type %s.",
                          kw->word,
                          s2_ttype_cstr(tBody.ttype));
    goto end;
  }else if(!s2_ptoken_has_content(&tBody)){
    fFlags |= S2_FUNCSTATE_F_EMPTY_BODY;
  }

  if(se->skipLevel){
    *rv = cwal_value_undefined();
    rc = 0;
    pr->token = tBody;
    goto end;
  }

  /*MARKER(("Got func:\n%.*s\n",
          (int)(tBody.end - tOrigin.begin),
          tOrigin.begin));*/
  *rv = cwal_value_undefined();

  fst = s2_func_state_malloc(se);
  if(!fst){
    rc = CWAL_RC_OOM;
    goto end;
  }
  cf = cwal_new_function( se->e, s2_callback_f, fst,
                          cwal_finalizer_f_func_state,
                          &s2_func_state_empty);
  if(!cf){
    s2_func_state_free( se, fst );
    rc = CWAL_RC_OOM;
    goto end;
  }
  /* As of here fst belongs to cf. */
  fst->flags = fFlags;
  vf = cwal_function_value(cf);

  rc = s2_function_setup_srcinfo( se, pr, &tOrigin,
                                  &tName, &tBody, fst, vf );
  if(rc) goto end;

  *rv = vf;
  pr->token = tBody;
  end:

  if(rc && vf){
    cwal_value_unref(vf);
  }
  return rc;
}

cwal_value * s2_propagating_get( s2_engine * se ){
  return cwal_propagating_get(se->e);
}

cwal_value * s2_propagating_take( s2_engine * se ){
  cwal_value * v = cwal_propagating_take(se->e);
  if(v) s2_engine_err_reset(se);
  return v;
}

#undef MARKER
/* end of file s2_eval.c */
/* start of file s2_func.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

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

#define ARGS_SE s2_engine * se = s2_args_state(args); \
    assert(se)

#define THIS_FUNCTION                                  \
    cwal_function * self = 0;                          \
    ARGS_SE; \
    self = cwal_value_function_part(se->e, args->self); \
    if(!self){ \
        return s2_throw( se, CWAL_RC_TYPE, \
                         "'this' is-not-a Function." ); \
    } (void)0

/**
   Internal property key for storing "imported symbols" in a Function
   instance.

   ACHTUNG: this symbol string is duplicated in s2_eval.c.
*/
#define FUNC_SYM_PROP "%imported%"
#define FUNC_SYM_PROP_LEN ((cwal_size_t)(sizeof(FUNC_SYM_PROP)-1))

static int s2_cb_func_source_code( cwal_callback_args const * args, cwal_value **rv ){
  s2_func_state * fst;
  THIS_FUNCTION;
  fst = (s2_func_state *)cwal_function_state_get(self, &s2_func_state_empty);
  if(fst){
    cwal_value * src = cwal_hash_search_v(fst->se->funcStash, fst->keySrc);
    if(src || fst->flags){
      /* Reminder: we really need to return a copy because the
         funcStash implicitly moves src into the global scope. If the
         client holds a ref after the function is destroyed then the
         string will never be cleaned up until until/unless the global
         scope is re-entered and swept (which doesn't happen in s2, at
         least not via s2sh).
      */
      if(!src){
        static const char * const shortestSrc = "proc(){}";
        static const cwal_size_t shortestLen = 8/*^^^ strlen*/;
        assert(fst->flags && "Expecting empty-body script function");
        *rv = cwal_new_xstring_value(args->engine, shortestSrc, shortestLen);
      }else{
        cwal_size_t len = 0;
        char const * str = cwal_value_get_cstr(src, &len);
        assert(str);
        assert(len>=8 /*proc(){}*/);
        *rv = cwal_new_string_value(args->engine, str, len);
      }
      return *rv ? 0 : CWAL_RC_OOM;
    }else{
      return cwal_exception_setf(args->engine, CWAL_RC_ERROR,
                                 "Missing expected reference to this function's source code!");
    }
  }else{
    /* Non-script function */
    return 0;
  }
}

/**
   Implements Function.importSymbols().
*/
static int s2_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 s2_throw(se, 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(se->e);
    if(!props) return CWAL_RC_OOM;
    rc = cwal_prop_set_with_flags(ce, FUNC_SYM_PROP, FUNC_SYM_PROP_LEN, props,
                                  CWAL_VAR_F_CONST | CWAL_VAR_F_HIDDEN);
    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 = s2_var_get_v(se, -1, key);
    if(!v){
      cwal_string const * str = cwal_value_get_string(key);
      char const * cstr = str ? cwal_string_cstr(str) : NULL;
      return s2_throw(se, 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 s2_cb_func_apply( cwal_callback_args const * args, cwal_value **rv ){
  /* FIXME: consolidate with s2_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() */;
  THIS_FUNCTION;
  if(!args->argc){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "'apply' expects (OBJ[, ARRAY]) argument(s).");
  }
  fwdSelf = args->argv[0];

#if 0
  /* Why limit this? 'this' is 'undefined' in non-member calls. */
  if(!cwal_props_can(fwdSelf)
     && !cwal_value_prototype_get(args->engine, fwdSelf)){
    return s2_throw(se, CWAL_RC_TYPE,
                    "Invalid first argument to 'apply' - "
                    "expecting an Object or at least "
                    "something with a prototype.");
  }
#endif
  theList = (args->argc>1)
    ? cwal_value_array_part(se->e, args->argv[1])
    : NULL;
  if((args->argc>1) && !theList){
    return s2_throw(se, CWAL_RC_TYPE,
                    "Second argument to 'apply' must be an array.");
  }
  /* assert(0 == cwal_array_length_get(origArgvA)); */
  rc = theList
    ? cwal_function_call_array( 0, self, fwdSelf, rv, theList )
    : cwal_function_call( self, fwdSelf, rv, 0, 0 );
  return rc;
}

static int s2_cb_func_call( cwal_callback_args const * args, cwal_value **rv ){
  /* FIXME: consolidate with s2_cb_func_call(). 90% the same code. */
  int rc;
  cwal_value * fwdSelf /* args->argv[0], the 'this' object for the next call() */;
  THIS_FUNCTION;
  if(!args->argc){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "'call' expects (OBJ[, ...]) argument(s).");
  }
  fwdSelf = args->argv[0];
#if 0
  /* TODO: th1ish_props_can() equivalent */
  if(!th1ish_props_can(ie, fwdSelf)){
    return s2_throw(se, CWAL_RC_TYPE,
                    "Invalid first argument to 'call' - "
                    "expecting an Object or at least "
                    "something with a prototype.");
  }
#else
  if(!cwal_props_can(fwdSelf)
     && !cwal_value_prototype_get(args->engine, fwdSelf)){
    return s2_throw(se, CWAL_RC_TYPE,
                    "Invalid first argument to 'call' - "
                    "expecting an Object or at least "
                    "something with a prototype.");
  }
#endif
  /* assert(0 == cwal_array_length_get(origArgvA)); */
  rc = cwal_function_call( self, fwdSelf, rv,
                           args->argc-1, args->argv+1 );
  return rc;
}


cwal_value * s2_prototype_function( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_FUNCTION );
    if(proto
       || !s2_prototype_object(se) /* timing hack */
       ) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_FUNCTION, proto );
    if(!rc) rc = s2_prototype_stash(se, "Function", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_FUNCTION));
    /* MARKER(("Setting up OBJECT prototype.\n")); */

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#if 0
    v = cwal_new_string_value(se->e, "function", 6);
    CHECKV;
    rc = cwal_prop_set( proto, "__typename", 10, v );
    RC;
#endif

#define FUNC2(NAME,FP)                              \
    v = s2_new_function2( se, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );    \
    if(rc) goto end

    FUNC2("apply", s2_cb_func_apply);
    FUNC2("call", s2_cb_func_call);
    FUNC2("importSymbols", s2_cb_func_import_symbols);
    FUNC2("sourceCode", s2_cb_func_source_code);

#undef FUNC2
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}


#undef MARKER
#undef THIS_FUNCTION
#undef ARGS_SE
#undef FUNC_SYM_PROP
#undef FUNC_SYM_PROP_LEN
/* end of file s2_func.c */
/* start of file s2_hash.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>

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


/**
   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 s2_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
}

static int s2_new_hash( cwal_engine * e, cwal_int_t hashSize, cwal_value ** rv ){
  cwal_hash * h;
  cwal_value * hv;
  if(hashSize<=0) hashSize = 17;
  else if(hashSize>7919) hashSize = 7919;
  else hashSize = s2_hash_next_prime(hashSize);
  h = cwal_new_hash(e, hashSize);
  hv = h
    ? cwal_hash_value(h)
    : NULL;
  if(!hv){
    assert(!h);
    return CWAL_RC_OOM;
  }
  else {
    cwal_value * v;
    int rc;
    assert(cwal_value_prototype_get(e,hv));
    v = cwal_new_integer(e, (cwal_int_t)hashSize);
    if(!v) rc = CWAL_RC_OOM;
    else if( (rc = cwal_prop_set_with_flags(hv, "tableSize", 9, v,
                                            CWAL_VAR_F_CONST)) ){
      cwal_value_unref(v);
    }
    else *rv = hv;
    return rc;
  }
}


#define ARGS_SE s2_engine * se = s2_args_state(args); \
    assert(se)

#define THIS_HASH                                  \
    cwal_hash * h = 0;                          \
    cwal_value * hv = 0;                        \
    ARGS_SE; \
    h = cwal_value_hash_part(se->e, args->self); \
    hv = h ? cwal_hash_value(h) : 0;               \
    if(!h || !hv){ \
        return s2_throw( se, CWAL_RC_TYPE, \
                         "'this' is-not-a Hash." ); \
    } (void)0

static int s2_cb_hash_create( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t hsz = 0;
  hsz = (args->argc>0)
    ? cwal_value_get_integer(args->argv[0])
    : 0;
  return (hsz<0)
    ? cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                          "Expecting a positive integer value "
                          "for hash table size.")
    : s2_new_hash(args->engine, hsz, rv );
}

/**
   Internal impl of th1ish_cp_hash_keys/values(). mode==0 means
   keys, anything else means values.
*/
static int s2_cb_hash_kv( cwal_callback_args const * args,
                          char mode,
                          cwal_value **rv ){
  int rc;
  cwal_array * ar;
  THIS_HASH;
  ar = cwal_new_array(args->engine);
  if(!ar) return CWAL_RC_OOM;
  if(!mode){
    rc = cwal_hash_visit_keys( h,
                               s2_value_visit_append_to_array,
                               ar );
  }else{
    rc = cwal_hash_visit_values( h,
                                 s2_value_visit_append_to_array,
                                 ar );
  }
  if(!rc) *rv = cwal_array_value(ar);
  else cwal_array_unref(ar);
  return rc;
}

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

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

static int s2_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;
}


int s2_cb_hash_insert( cwal_callback_args const * args, cwal_value **rv ){
  THIS_HASH;
  if(2!=args->argc) return s2_throw(se,
                                    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 = s2_throw(se, rc, "May not modify a hash while it "
                        "is being iterated over.");
          break;
        default:
          break;
      }
    /* if(!rc) *rv = args->self; */
    return rc;
  }
}

int s2_cb_hash_get( cwal_callback_args const * args, cwal_value **rv ){
  cwal_value * v;
  THIS_HASH;
  if(1!=args->argc){
    return s2_throw(se, 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 s2_cb_hash_has( cwal_callback_args const * args, cwal_value **rv ){
  THIS_HASH;
  if(!args->argc) return s2_throw(se, 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 s2_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);
  *rv = args->self;
  return 0;
}


int s2_cb_hash_remove( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  THIS_HASH;
  if(1!=args->argc){
    return s2_throw(se, 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 = s2_throw(se, 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;
    } /* seriously, emacs? You're going to do that to me? */
  return rc;
}


/**
   Internal cwal_kvp_visitor_f() implementation which requires state
   to be a (cwal_hash*), into which it inserts/overwrites kvp's
   key/value.
*/
static int s2_kvp_visitor_hash_insert( cwal_kvp const * kvp, void * state ){
  return cwal_hash_insert_v( (cwal_hash *)state,
                             cwal_kvp_key(kvp), cwal_kvp_value(kvp), 1 );
}

/* in s2_protos.c */
int s2_kvp_visitor_prop_each( cwal_kvp const * kvp, void * state_ );

/**
   Script usages:

   obj.eachEntry(Function):
   
   The given function is called once per property, passed the key and
   value.

   obj.eachEntry(Object, Function):

   Functionally equivalent to obj.eachEntry(proc(k,v){otherObj.func(k,v)})

   obj.eachEntry(Hash):

   Functionally equivalent to: obj.eachEntry(targetHash, targetHash.insert)

   *rv will be set to args->self on success.
*/
static int s2_cb_hash_each_entry( cwal_callback_args const * args, cwal_value **rv ){
  cwal_function * f;
  int fIndex = (args->argc>1) ? 1 : args->argc ? 0 : -1;
  cwal_value * theThis = (args->argc>1) ? args->argv[0] : args->self;
  cwal_hash * otherHash = 0;
  THIS_HASH;

  f = (fIndex>=0)
    ? cwal_value_get_function(args->argv[fIndex])
    : 0;
  if(!f){
    if(! (otherHash = (1==args->argc)
          ? cwal_value_get_hash(args->argv[0])
          : 0) ){
      return s2_throw(se, CWAL_RC_MISUSE,
                      "'eachEntry' expects (Hash|Function) "
                      "or (Object, Function) arguments.");
    }
  }else if(!cwal_value_may_iterate(hv)){
    return cwal_exception_setf(args->engine, CWAL_RC_ACCESS,
                               "Hashtable is currently iterating.");
  }

  if(otherHash){
    return cwal_hash_visit_kvp(h, s2_kvp_visitor_hash_insert, otherHash);
  }else{
    s2_kvp_each_state state;
    int rc;
    state.e = args->engine;
    state.callback = f;
    state.self = theThis;
    rc = cwal_hash_visit_kvp( h, s2_kvp_visitor_prop_each, &state );
    if(S2_RC_END_EACH_ITERATION==rc) rc = 0;
    if(!rc) *rv = args->self;
    return rc;
  }
}


#if 0
static int s2_cb_hash_size( cwal_callback_args const * args, cwal_value **rv ){
  THIS_HASH;
  *rv = cwal_new_integer(args->engine, (cwal_int_t)cwal_hash_size(h->hashSize));
  return *rv ? 0 : CWAL_RC_OOM;
}
#endif

cwal_value * s2_prototype_hash( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_HASH );
    if(proto
       || !s2_prototype_object(se) /* timing hack */
       ) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_HASH, proto );
    if(!rc) rc = s2_prototype_stash(se, "Hash", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_HASH));
    /* MARKER(("Setting up OBJECT prototype.\n")); */

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#define FUNC2(NAME,FP)                              \
    v = s2_new_function2( se, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("clearEntries", s2_cb_hash_clear);
    FUNC2("containsEntry", s2_cb_hash_has);
    FUNC2("eachEntry", s2_cb_hash_each_entry);
    FUNC2("entryCount", s2_cb_hash_entry_count);
    FUNC2("entryKeys", s2_cb_hash_keys);
    FUNC2("entryValues", s2_cb_hash_values);
    FUNC2("insert", s2_cb_hash_insert);
    FUNC2("new", s2_cb_hash_create);
    FUNC2("remove", s2_cb_hash_remove);
    FUNC2("search", s2_cb_hash_get);
    /* FUNC2("hashSize", s2_cb_hash_size); */

#undef FUNC2
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}


#undef MARKER
#undef THIS_HASH
#undef ARGS_SE
/* end of file s2_hash.c */
/* start of file s2_io.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

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

#ifndef HAVE_REAPATH
#  if defined(_BSD_SOURCE)
#    define HAVE_REALPATH 1
#  elif defined(_XOPEN_SOURCE) && _XOPEN_SOURCE>=500
#    define HAVE_REALPATH 1
#  elif defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED)
#    if _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED
#      define HAVE_REALPATH 1
#    endif
#  endif
#  ifndef HAVE_REALPATH
#    define HAVE_REALPATH 0
#  endif
#endif


#if HAVE_REALPATH
#  include <limits.h>
#endif

#ifndef _WIN32
#  include <unistd.h> /* W_OK, R_OK */
#else
#  include  <io.h>
#endif

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

int s2_cb_flush( cwal_callback_args const * args, cwal_value **rv ){
  int rc = cwal_output_flush(args->engine);
  if(rc) rc = cwal_exception_setf(args->engine, rc,
                                  "Flushing output failed with code %d (%s).",
                                  rc, s2_rc_cstr(rc));
  /* 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;
}

static int s2_cb_print_impl( cwal_callback_args const * args, cwal_value **rv,
                             char addSpace ){
    uint16_t i;
    int rc = 0;
    char const * sep = " ";
    cwal_engine * e = args->engine;
    cwal_size_t const sepLen = strlen(sep);
    /* dump_val(args->self, "'this' for print()"); */
    /* MARKER(("s2_cb_print() called with %"PRIu16" arg(s).\n", args->argc)); */
    for(i = 0; !rc && (i < args->argc); ++i ){
        cwal_value * v = args->argv[i];
        if(addSpace && i) rc = cwal_output(e, sep, sepLen);
        if(rc) break;
        /* s2_dump_val(v,"arg"); */
        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, NULL );
              break;
          case CWAL_TYPE_UNDEF:
              rc = cwal_output(e, "undefined", 9);
              break;
          case CWAL_TYPE_STRING:{
              cwal_size_t slen = 0;
              char const * cstr = cwal_value_get_cstr(v, &slen);
              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_HASH:
          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;
        }
    }
    if(rc && (CWAL_RC_EXCEPTION!=rc)){
        rc = cwal_exception_setf(args->engine, rc, "Output error #%d (%s).",
                                 rc, s2_rc_cstr(rc));
    }
    else if(!rc && addSpace){
        cwal_output(args->engine, "\n", 1);
    }
    *rv = cwal_function_value(args->callee);
    cwal_output_flush(args->engine);
    return rc;
}


int s2_cb_print( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_print_impl(args, rv, 1);
}

int s2_cb_write( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_print_impl(args, rv, 0);
}

int s2_cb_import_script(cwal_callback_args const * args, cwal_value ** rv){
  int i, rc = 0;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);

#if 0
  /*
    experimenting with a use for this in require.s2. Doesn't
    work - this gets overwritten by that point and passing this
    through the API would go through way too many layers and impact
    too many things.
  */
  rc = cwal_scope_chain_set( cwal_scope_current_get(args->engine),
                             0, "this", 4, args->self);
  if(rc) return rc;
#endif

  /*
    TODO:

    - Create a local array to hold a list of all filenames passed in. 

    - Before running each script, see if it's in that list. If it is,
    assume recursion and fail.

    - Else eval it.

    - At the end of the loop, free the name list.

    Hmmm. Each sub-import needs access to the same list. Where to store it
    so that it's reachable recursively? In args->self['importList'] and
    args->self['importDepth']?
  */
  *rv = 0;
  for( i = 0; !rc && i < args->argc; ++i ){
    cwal_value const * arg = args->argv[i];
    cwal_size_t nLen = 0;
    char const * fn = arg ? cwal_value_get_cstr(arg, &nLen) : 0;
    if(!fn){
      rc = s2_cb_throw( args, CWAL_RC_TYPE,
                        "Expecting a STRING value, but got '%s'.",
                        arg ? cwal_value_type_name(arg) : "<NULL>");
    }else{
      rc = s2_eval_filename(se, 1, fn, nLen, rv);
    }
  }
  switch(rc){
    case 0: break;
    case CWAL_RC_RETURN:
      *rv = cwal_propagating_take(se->e);
      assert(*rv);
      s2_engine_err_reset(se);
      rc = 0;
      break;
  }
  if(!rc){
    if(!*rv) *rv = cwal_value_undefined();
    else{
      assert((cwal_value_scope(*rv) || cwal_value_is_builtin(*rv))
             && "Seems like we cleaned up *rv too early.");
    }
  }
  return rc;
}

char s2_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
    */
#else
  /* assume unix-like */
#  define CHECKACCESS access
#  define CHECKRIGHTS (checkWrite ? W_OK : R_OK)
#endif
  return (0 == CHECKACCESS( fn, CHECKRIGHTS ));
#undef CHECKACCESS
#undef CHECKRIGHTS

}

int s2_cb_file_accessible( cwal_callback_args const * args, cwal_value **rv ){
  char const * fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], 0)
    : 0;
  if(!fn) return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                     "Expecting a string argument.");
  else{
    char const checkWrite = (args->argc>1)
      ? cwal_value_get_bool(args->argv[1])
      : 0;
    assert(fn);
    *rv = s2_file_is_accessible(fn, checkWrite)
      ? cwal_value_true()
      : cwal_value_false();
    return 0;
  }
}

int s2_errno_to_cwal_rc(int errNo, int dflt){
  switch(errNo){
    case EACCES: return CWAL_RC_ACCESS;
    case EINVAL: return CWAL_RC_MISUSE;
    case ELOOP: return CWAL_RC_CYCLES_DETECTED;
    case ENAMETOOLONG: return CWAL_RC_RANGE;
    case EIO: return CWAL_RC_IO;
    case ENOTDIR: return CWAL_RC_TYPE;
    case ENOENT: return CWAL_RC_NOT_FOUND;
    default: return dflt;
  }
}

int s2_cb_realpath( cwal_callback_args const * args, cwal_value **rv ){
#if !HAVE_REALPATH
  return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED,
                               "realpath() not supported in this build.");
#else
  enum { BufSize = PATH_MAX + 1 };
  char buf[BufSize];
  char const * p;
  cwal_size_t strLen = 0;
  char const * str = args->argc
    ? cwal_value_get_cstr(args->argv[0], &strLen)
    : 0;
  if(!str) return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                      "Expecting a string argument.");
  p = realpath(str, buf);
  if(!p){
    return cwal_exception_setf(args->engine,
                               s2_errno_to_cwal_rc( errno,
                                                    CWAL_RC_ERROR ),
                               "realpath('%.*s') failed with errno %d (%s)",
                               (int)strLen, str, errno, strerror(errno));
  }
  *rv = cwal_new_string_value(args->engine, p, cwal_strlen(p));
  return *rv ? 0 : CWAL_RC_OOM;
#endif
}

int s2_install_io( s2_engine * se, cwal_value * tgt,
                   char const * name ){
  cwal_value * v;
  cwal_value * sub;
  int rc;
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;

  if(name && *name){
    sub = cwal_new_object_value(se->e);
    if(!sub) return CWAL_RC_OOM;
    if( (rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub)) ){
      cwal_value_unref(sub);
      return rc;
    }
  }else{
    sub = tgt;
  }

#define CHECKV if(!v) return CWAL_RC_OOM
#define RC if(rc) return rc
#define SET(NAME)                                              \
  CHECKV;                                                      \
  rc = cwal_prop_set( sub, NAME, cwal_strlen(NAME), v );            \
  if(rc) { cwal_value_unref(v); return rc; }(void)0

#define FUNC2(NAME,FP)                              \
  v = s2_new_function2( se, FP );                   \
  SET(NAME)

  FUNC2("output", s2_cb_write);
  FUNC2("print", s2_cb_print);
  FUNC2("flush", s2_cb_flush);
  FUNC2("realpath", s2_cb_realpath);

  v = cwal_new_string_value(se->e,
#ifdef S2_OS_WINDOWS
                            "\\",
#else
                            "/",
#endif
                            1 );
  SET("dirSeparator");

#undef FUNC2
#undef CHECKV
#undef RC
#undef SET
  return 0;
}

#undef MARKER
/* end of file s2_io.c */
/* start of file s2_mod.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <memory.h> /* strlen() */


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



/** @def S2_HAVE_DLOPEN

    If set to true, use dlopen() and friends. Requires
    linking to -ldl on most platforms.

    Only one of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN may be
    true.
*/
/** @def S2_HAVE_LTDLOPEN

    If set to true, use lt_dlopen() and friends. Requires
    linking to -lltdl on most platforms.

    Only one of S2_HAVE_DLOPEN and S2_HAVE_LTDLOPEN may be
    true.
*/

/*
  #define S2_HAVE_DLOPEN 1
  #define S2_HAVE_LTDLOPEN 0
*/

#if !defined(S2_HAVE_DLOPEN)
#define S2_HAVE_DLOPEN 0
#endif

#if !defined(S2_HAVE_LTDLOPEN)
#define S2_HAVE_LTDLOPEN 0
#endif

#if !defined(S2_ENABLE_MODULES)
#  define S2_ENABLE_MODULES (S2_HAVE_LTDLOPEN || S2_HAVE_DLOPEN)
#endif

#ifdef S2_OS_WINDOWS
#  include  <io.h>
#else
#  include <unistd.h> /* access() */
#endif

#if S2_HAVE_LTDLOPEN
#  include <ltdl.h>
typedef lt_dlhandle dl_handle_t;
#elif S2_HAVE_DLOPEN
typedef void * dl_handle_t;
#  include <dlfcn.h> /* this actually has a different name on some platforms! */
#elif !S2_ENABLE_MODULES
typedef int dl_handle_t;
#else
#  error "We have no dlopen() impl for this configuration."
#endif

#if S2_ENABLE_MODULES
static void dl_init(){
  static char once  = 0;
  if(!once){
    once = 1;
#  if S2_HAVE_LTDLOPEN
    lt_dlinit();
    lt_dlopen( 0 );
#  elif S2_HAVE_DLOPEN
    dlopen( 0, RTLD_NOW | RTLD_GLOBAL );
#  endif
  }
}
#endif

#if S2_ENABLE_MODULES
static dl_handle_t dl_open( char const * fname, char const **errMsg ){
  dl_handle_t soh;
#if S2_HAVE_LTDLOPEN
  soh = lt_dlopen( fname );
#elif S2_HAVE_DLOPEN
  soh = dlopen( fname, RTLD_NOW | RTLD_GLOBAL );
#endif
  if(!soh && errMsg){
#if S2_HAVE_LTDLOPEN
    *errMsg = lt_dlerror();
#elif S2_HAVE_DLOPEN
    *errMsg = dlerror();
#endif
  }
  return soh;
}
#endif

#if S2_ENABLE_MODULES
static s2_loadable_module const * dl_sym( dl_handle_t soh, char const * mname ){
  dl_handle_t sym =
#if S2_HAVE_LTDLOPEN
    lt_dlsym( soh, mname )
#elif S2_HAVE_DLOPEN
    dlsym( soh, mname )
#else
    NULL
#endif
    ;
  return sym ? *((s2_loadable_module const **)sym) : NULL;
}
#endif

#define S2_CLOSE_DLLS 1
/*
  Years of practice have shown that it is literally impossible to
  safely close DLLs because simply opening one may trigger arbitrary
  code (at least for C++ DLLs) which "might" be used by the
  application. e.g. some classloaders use DLL initialization to inject
  new classes into the application without the app having to do anything
  more than open the DLL.

  So s2 does not close DLLs. Except to try to please valgrind.
*/
#if S2_ENABLE_MODULES
static void dl_close( dl_handle_t soh ){
#if S2_CLOSE_DLLS
  /* MARKER(("Closing loaded module @%p.\n", (void const *)soh)); */
#if S2_HAVE_LTDLOPEN
  lt_dlclose( soh );
#elif S2_HAVE_DLOPEN
  dlclose( soh );
#endif
#endif
}
#endif

int s2_module_load( s2_engine * se,
                    char const * fname,
                    char const * symName,
                    cwal_value * injectPoint,
                    char const ** errMsg ){
#if !S2_ENABLE_MODULES
  if(errMsg) *errMsg =
               "No dlopen() equivalent is installed for this build configuration.";
  return CWAL_RC_UNSUPPORTED;
#else
#if !S2_HAVE_LTDLOPEN && !S2_HAVE_DLOPEN
#   error "We have no dlopen() and friends impl for this configuration."
#endif
  if(!se || !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 = 100 };
      char buf[MaxLen] = {0};
      cwal_size_t const slen = symName ? cwal_strlen(symName) : 0;
      s2_loadable_module const * mod;
      if(slen > (MaxLen-20)) return CWAL_RC_RANGE;
      if(!slen){
        memcpy(buf, "s2_module", 9);
        buf[9] = 0;
      }else{
        sprintf(buf,"s2_module_%s", symName);
      }
      mod = dl_sym( soh, buf );
      /* MARKER(("module=%s ==> %p\n",buf, (void const *)mod)); */
      if(!mod){
        dl_close(soh);
        if(errMsg) *errMsg = "Did not find module entry point symbol.";
        return CWAL_RC_NOT_FOUND;
      }else if(S2_CLOSE_DLLS){
        cwal_size_t i = 0;
        char found = 0;
        for( ; i < se->modules.count; ++i ){
          if(soh == se->modules.list[i]){
            found = 1;
            break;
          }
        }
        if(!found){
          int const rc = cwal_list_append(se->e,
                                          &se->modules, soh);
          if(rc){
            dl_close(soh);
            return rc;
          }
        }
      }
      return mod->init( se, injectPoint );
    }
  }
#endif
}

int s2_cb_module_load( cwal_callback_args const * args,
                      cwal_value **rv ){
#if !S2_ENABLE_MODULES
  return cwal_exception_setf(args->engine, 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;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  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 = s2_module_load(se, fn, sym, injectPoint, &errMsg);
  /* MARKER(("load_module(%s, %s) rc=%d\n", fn, sym, rc)); */
  if(rc){
    if(errMsg){
      rc = cwal_exception_setf(args->engine, rc,
                               "Opening DLL failed with code "
                               "%d (%s): %s", rc, cwal_rc_cstr(rc),
                               errMsg);
    }else{
      rc = cwal_exception_setf(args->engine, rc,
                               "Loading module failed with code "
                               "%d (%s).", rc, cwal_rc_cstr(rc));
    }
  }else{
    *rv = injectPoint;
  }
  return rc;
  misuse:
  return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                             "Expecting a (String,String,Object) "
                             "or (String,Object) arguments.");
#endif
}


void s2_modules_close( s2_engine * se ){
    if(se){
#if !S2_ENABLE_MODULES
      assert(!se->modules.list);
#else
      if(se->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 S2_CLOSE_DLLS
            int i;
            assert(se->modules.count);
            i = ((int)se->modules.count) - 1;
            for( ; i >= 0; --i ){
                void * soh = se->modules.list[i];
                assert(soh);
                dl_close(soh);
            }
            cwal_list_reserve( se->e, &se->modules, 0 );
#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
            assert(!se->modules.list);
            assert(!se->modules.count);
            assert(!se->modules.alloced);
        }
#endif
    }
}


#undef MARKER
#undef S2_CLOSE_DLLS
/* end of file s2_mod.c */
/* start of file s2_number.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h> /* sprintf() */

#if 1
#define MARKER(pfexp) if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf pfexp
#else
#define MARKER(pfexp) (void)0
#endif


#define ARGS_SE s2_engine * se = s2_args_state(args)


#define THIS_DOUBLE                             \
    cwal_double_t self; \
    ARGS_SE; \
    assert(se); \
    if(!cwal_value_is_number(args->self)){                      \
        return s2_throw( se, CWAL_RC_TYPE, \
                         "'this' is-not-a Number." );   \
    }\
    self = cwal_value_get_double(args->self)

#define THIS_INT                             \
    cwal_int_t self; \
    ARGS_SE; \
    assert(se); \
    if(!cwal_value_is_number(args->self)){                      \
        return s2_throw( se, CWAL_RC_TYPE,                   \
                         "'this' is-not-a Number." );        \
    }\
    self = cwal_value_get_integer(args->self)

static int s2_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 s2_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 s2_throw(se, CWAL_RC_RANGE,
                    "Integer value %"CWAL_INT_T_PFMT" is not "
                    "a valid UTF8 character.", (cwal_int_t)self);
    }
  *rv = cwal_new_string_value(args->engine,
                              (char const *)buf,
                              (cwal_size_t)sz);
  return *rv ? 0 : CWAL_RC_OOM;
}

static int s2_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 s2_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 s2_throw(se, 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 s2_cb_int_to_utf8char( cwal_callback_args const * args, cwal_value **rv ){
  enum { BufLen = 7 };
  unsigned char buf[BufLen] = {0,0,0, 0,0,0, 0};
  cwal_size_t bLen = (cwal_size_t)BufLen;
  int rc;
  THIS_INT;
  rc = cwal_utf8_char_to_cstr((unsigned int)self, buf, bLen);
  if(rc<0){
    *rv = cwal_value_undefined();
    return 0 /* arguably we should throw */;
  }
  *rv = cwal_new_string_value(args->engine, (char const *)buf,
                              cwal_strlen((char const *)buf));
  return *rv ? 0 : CWAL_RC_OOM;
}


int s2_cstr_parse_number( cwal_engine * e, char const * src,
                          cwal_int_t slen, cwal_value ** rv ){
  cwal_int_t inty = 0;
  if(s2_cstr_parse_int(src, slen, &inty)){
    *rv = cwal_new_integer(e, inty);
    return *rv ? 0 : CWAL_RC_OOM;
  }else{
    cwal_double_t dbl = 0.0;
    if(s2_cstr_parse_double(src, slen, &dbl)){
      *rv = cwal_new_double(e, dbl);
      return *rv ? 0 : CWAL_RC_OOM;
    }else{
      *rv = 0;
      return 0;
    }
  }
}

static int s2_cb_parse_int( cwal_callback_args const * args, cwal_value **rv ){
  cwal_value * arg;
  if(!args->argc){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting an argument to parse as an integer.");
  }
  arg = args->argv[0];
  switch(cwal_value_type_id(arg)){
    case CWAL_TYPE_INTEGER:
      *rv = args->argv[0];
      return 0;
    case CWAL_TYPE_DOUBLE:
      *rv = cwal_new_integer(args->engine,
                             (cwal_double_t)cwal_value_get_double(arg));
      return *rv ? 0 : CWAL_RC_OOM;
    case CWAL_TYPE_BOOL:
      *rv = cwal_new_integer(args->engine,
                             cwal_value_get_bool(arg) ? 1 : 0);
      return 0;
    default:{
      cwal_size_t slen = 0;
      char const * str = cwal_value_get_cstr(arg, &slen);
      if(str && slen){
        cwal_int_t inty = 0;
        if(s2_cstr_parse_int(str, (cwal_int_t)slen, &inty)){
          *rv = cwal_new_integer(args->engine, inty);
          return *rv ? 0 : CWAL_RC_OOM;
        }else{
          cwal_double_t dbl = 0.0;
          if(s2_cstr_parse_double(str, (cwal_int_t)slen, &dbl)){
            *rv = cwal_new_integer(args->engine, (cwal_int_t)dbl);
            return *rv ? 0 : CWAL_RC_OOM;
          }
        }
      }
      *rv = cwal_value_undefined();
      return 0;
    }
  }
}


static int s2_cb_parse_double( cwal_callback_args const * args, cwal_value **rv ){
  cwal_value * arg;
  if(!args->argc){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting an argument to parse as a number.");
  }
  arg = args->argv[0];
  switch(cwal_value_type_id(arg)){
    case CWAL_TYPE_INTEGER:
      *rv = cwal_new_double(args->engine,
                            (cwal_double_t)cwal_value_get_integer(arg));
      return *rv ? 0 : CWAL_RC_OOM;
    case CWAL_TYPE_DOUBLE:
      *rv = args->argv[0];
      return 0;
    case CWAL_TYPE_BOOL:
      *rv = cwal_new_double(args->engine,
                            cwal_value_get_bool(arg) ? 1 : 0);
      return 0;
    default:{
      cwal_size_t slen = 0;
      char const * str = cwal_value_get_cstr(arg, &slen);
      if(str){
        cwal_double_t dbl = 0.0;
        if(s2_cstr_parse_double(str, (cwal_int_t)slen, &dbl)){
          *rv = cwal_new_double(args->engine, dbl);
          return *rv ? 0 : CWAL_RC_OOM;
        }else{
          cwal_int_t dd = 0;
          if(s2_cstr_parse_int(str, (cwal_int_t)slen, &dd)){
            *rv = cwal_new_double(args->engine, (cwal_double_t)dd);
            return *rv ? 0 : CWAL_RC_OOM;
          }
        }
      }
      *rv = cwal_value_undefined();
      return 0;
    }
  }
}

static int s2_cb_parse_number( cwal_callback_args const * args, cwal_value **rv ){
  cwal_value * arg;
  if(!args->argc){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting an argument to parse as a number.");
  }
  arg = args->argv[0];
  switch(cwal_value_type_id(arg)){
    case CWAL_TYPE_INTEGER:
    case CWAL_TYPE_DOUBLE:
      *rv = args->argv[0];
      return 0;
    case CWAL_TYPE_BOOL:
      *rv = cwal_new_integer(args->engine,
                             cwal_value_get_bool(arg) ? 1 : 0);
      return 0;
    default:{
      cwal_size_t slen = 0;
      char const * str = cwal_value_get_cstr(arg, &slen);
      if(str && slen){
        int rc;
        *rv = 0;
        rc = s2_cstr_parse_number(args->engine, str,
                                  (cwal_int_t)slen, rv);
        if(!rc && !*rv) *rv = cwal_value_undefined();
        assert( *rv ? 1 : rc );
        return *rv ? 0 : rc;
      }else{
        *rv = cwal_value_undefined();
        return 0;
      }
    }
  }
}


int s2_cb_rand_int( cwal_callback_args const * args, cwal_value **rv ){
  static unsigned int seed = 0;
  cwal_int_t v;
  if( !seed ){
    seed = (unsigned int)
#if 1
      time(0) * 999983U/*prime, btw*/
#else
      (((unsigned long)args->self) * 999983U/*prime, btw*/)
#endif
      ;
    srand( seed );
  }
  v = (cwal_int_t)(rand() % CWAL_INT_T_MAX /* in case we're running on a "small" build */);
  *rv = cwal_new_integer( args->engine, v);
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Base prototype for integer and double.
*/
static cwal_value * s2_prototype_number( s2_engine * se ){
  static char const * stashKey = "Number";
  int rc = 0;
  cwal_value * proto;
  cwal_value * v = 0;
  proto = s2_prototype_stashed(se, stashKey);
  if(proto
     || !s2_prototype_object(se) /* timing hack */
     ) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  cwal_value_prototype_set(proto, 0 /* so we don't inherit Object!*/);
  if(!rc) rc = s2_prototype_stash(se, stashKey, proto);
  if(rc) goto end;

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;
#define SETV(NAME) \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );   \
  if(rc) {cwal_value_unref(v); goto end; } (void)0

#define FUNC2(NAME,FP)                                  \
  v = s2_new_function2( se, FP );                       \
  CHECKV;                                               \
  SETV(NAME)

  FUNC2("compare", s2_cb_value_compare);
  FUNC2("parseDouble", s2_cb_parse_double);
  FUNC2("parseInt", s2_cb_parse_int);
  FUNC2("parseNumber", s2_cb_parse_number);
  FUNC2("toDouble", s2_cb_int_to_dbl);
  FUNC2("toInt", s2_cb_dbl_to_int);
  FUNC2("toJSONString", s2_cb_this_to_json_token);
  FUNC2("toString", s2_cb_num_to_string);

#undef SETV
#undef FUNC2
#undef CHECKV
#undef RC
  end:
  return rc ? NULL : proto;
}



cwal_value * s2_prototype_integer( s2_engine * se ){
  static char const * stashKey = "Integer";
  int rc = 0;
  cwal_value * proto;
  cwal_value * v = 0;
  cwal_value * numProto = s2_prototype_number(se);
  if(!numProto) return 0;
  proto = s2_prototype_stashed(se, stashKey);
  if(proto
     || !s2_prototype_object(se) /* timing hack */
     ) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash(se, stashKey, proto);
  if(!rc) rc = cwal_prototype_base_set(se->e, CWAL_TYPE_INTEGER, proto );
  if(rc) goto end;
  cwal_value_prototype_set(proto, numProto);
  assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_INTEGER));

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;
#define SETV(NAME) \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );   \
  if(rc) {cwal_value_unref(v); goto end; } (void)0

#define FUNC2(NAME,FP)                                  \
  v = s2_new_function2( se, FP );                       \
  CHECKV;                                               \
  SETV(NAME)

  v = cwal_new_integer(se->e, CWAL_INT_T_MIN);
  SETV("INT_MIN");
  v = cwal_new_integer(se->e, CWAL_INT_T_MAX);
  SETV("INT_MAX");

  FUNC2("toChar", s2_cb_int_to_utf8char);
  FUNC2("toUtf8Char", s2_cb_int_to_utf8_char);
  FUNC2("rand", s2_cb_rand_int);

#undef SETV
#undef FUNC2
#undef CHECKV
#undef RC
  end:
  return rc ? NULL : proto;
}


static int s2_cb_dbl_floor( cwal_callback_args const * args, cwal_value **rv ){
  THIS_DOUBLE;
#if 1
  if(!self){
    *rv = cwal_new_integer( args->engine, (cwal_int_t)self );
    return 0;
  }
  *rv = cwal_new_integer(args->engine,
                        (cwal_int_t)(self>0 ? self : (self-1)));
#else
  /* Requires C99 */
  *rv = cwal_new_double( args->engine, (cwal_int_t)floor((double)self));
#endif
  return *rv ? 0 : CWAL_RC_OOM;
}

static int s2_cb_dbl_ceil( cwal_callback_args const * args, cwal_value **rv ){
  THIS_DOUBLE;
#if 1
  if(!self){
    *rv = cwal_new_integer( args->engine, (cwal_int_t)self);
    return 0;
  }
  *rv = cwal_new_integer(args->engine,
                        (cwal_int_t)(self>0 ? self+1 : self));
#else
  /* Requires C99 */
  *rv = cwal_new_integer( args->engine, (cwal_int_t)ceil((double)self));
#endif
  return *rv ? 0 : CWAL_RC_OOM;
}

static int s2_cb_dbl_format( cwal_callback_args const * args, cwal_value **rv ){
  int left = 0, right = 0;
  enum { BufLen = 200 };
  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( args->engine, buf, self ) );
  return *rv ? 0 : CWAL_RC_OOM;
}

cwal_value * s2_prototype_double( s2_engine * se ){
  static char const * stashKey = "Double";
  int rc = 0;
  cwal_value * proto;
  cwal_value * v = 0;
  cwal_value * numProto = s2_prototype_number(se);
  if(!numProto) return 0;
  proto = s2_prototype_stashed(se, stashKey);
  if(proto
     || !s2_prototype_object(se) /* timing hack */
     ) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash(se, stashKey, proto);
  if(!rc) rc = cwal_prototype_base_set(se->e, CWAL_TYPE_DOUBLE, proto );
  if(rc) goto end;
  cwal_value_prototype_set(proto, numProto);
  assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_DOUBLE));

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#define FUNC2(NAME,FP)                                  \
  v = s2_new_function2( se, FP );                       \
  CHECKV;                                               \
  rc = cwal_prop_set( proto, NAME, strlen(NAME), v );   \
  if(rc) goto end


  FUNC2("ceil", s2_cb_dbl_ceil);
  FUNC2("floor", s2_cb_dbl_floor);
  FUNC2("format", s2_cb_dbl_format);

#if 0
  /* FUNC2("round", s2_cb_dbl_round); */
#endif

#undef FUNC2
#undef CHECKV
#undef RC
  end:
  return rc ? NULL : proto;
}

#undef MARKER
#undef ARGS_SE
#undef THIS_INT
#undef THIS_DOUBLE
/* end of file s2_number.c */
/* start of file s2_ob.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

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

/**
   Internal state for the OB APIs.
*/
struct S2ObBuffer {
    s2_engine * se;
    cwal_buffer buf;
};
typedef struct S2ObBuffer S2ObBuffer;

static void s2_S2ObBuffer_finalize( cwal_engine * e, void * m ){
  S2ObBuffer * st = (S2ObBuffer *)m;
  cwal_buffer_reserve(e, &st->buf, 0);
  st->se = NULL;
  cwal_free(e, m);
}

static int s2_S2ObBuffer_output_f( void * state, void const * src, cwal_size_t n ){
  int rc = 0;
  S2ObBuffer * ob = (S2ObBuffer*) state;
  if((ob->buf.used+n) >= ob->buf.capacity
     /* >= b/c of implicitly added NUL */){
    cwal_size_t nSize = ob->buf.capacity
      ? (ob->buf.capacity * 3)
      : ((ob->buf.used+n+1) * 3)
      ;
    while(nSize < (ob->buf.used+n)){
      cwal_size_t oflow = nSize;
      nSize *= 2;
      if(oflow > nSize) goto overflow;
      /* MARKER(("nSize=%d\n", (int)nSize)); */
    }
    if(nSize == (ob->buf.used+n)){
      /* corner case: the buffer API appends a NUL, so account for
         that here instead of above (where it would cause another
         doubling of the size). */
      ++nSize;
      if(!nSize) goto overflow;
    }
    /* MARKER(("nSize=%d\n", (int)nSize)); */
    rc = cwal_buffer_reserve(ob->se->e, &ob->buf, nSize);
  }
  return rc ? rc : cwal_buffer_append(ob->se->e, &ob->buf, src, n);
  overflow:
  return cwal_exception_setf(ob->se->e, CWAL_RC_RANGE,
                             "cwal_size_t overflow in buffer "
                             "size calculation.");
}

static int s2_ob_push_outputer( s2_engine * se, cwal_outputer const * out ){
  if(!se || !out) return CWAL_RC_MISUSE;
  else{
    int rc;
    cwal_outputer * co =
      (cwal_outputer *)cwal_malloc(se->e,
                                   sizeof(cwal_outputer));
    if(!co){
      rc = CWAL_RC_OOM;
      goto end;
    }
    rc = cwal_list_append( se->e, &se->ob, co );
    end:
    if(rc){
      if(co) cwal_free(se->e, co);
    }else{
      *co = se->e->vtab->outputer;
      se->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 s2.io.flush()
   becomes a no-op while buffering is on.
 */
int s2_ob_flush( s2_engine * ie );
static int th1ish_S2ObBuffer_flush(void * ob){
  return s2_ob_flush( ((S2ObBuffer*)ob)->ie );
}
#endif

cwal_size_t s2_ob_level( s2_engine * se ){
  return se ? se->ob.count : 0;
}

int s2_ob_push( s2_engine * se ){
  int rc;
  cwal_outputer out = cwal_outputer_empty;
  S2ObBuffer * ob;
  assert(se);
  ob = (S2ObBuffer *)cwal_malloc(se->e, sizeof(S2ObBuffer));
  if(!ob) return CWAL_RC_OOM;
  ob->se = se;
  ob->buf = cwal_buffer_empty;
  out.state.data = ob;
  out.state.finalize = s2_S2ObBuffer_finalize;
  out.output = s2_S2ObBuffer_output_f;
  /* out.flush = s2_S2ObBuffer_flush; */
  rc = s2_ob_push_outputer( se, &out );
  if(rc){
    cwal_free( se->e, ob );
  }
  return rc;   
}

int s2_ob_pop( s2_engine * se ){
  if(!se) return CWAL_RC_MISUSE;
  else if(!se->ob.count) return CWAL_RC_RANGE;
  else{
    cwal_size_t const i = se->ob.count-1;
    cwal_outputer * ob = (cwal_outputer *)se->ob.list[i];
    cwal_outputer * io = &se->e->vtab->outputer;
    if(io->state.finalize){
      io->state.finalize(se->e, io->state.data);
    }
    *io = *ob;
    cwal_free(se->e, ob);
    --se->ob.count;
    return 0;
  }
}


int s2_cb_ob_pop( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_int_t take = 0;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  if(!s2_ob_level(se)){
    return cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                               "Cannot pop output buffer: stack is empty.");
  }
  else if(args->argc) take = cwal_value_get_integer(args->argv[0]);

  if(!take){
    rc = s2_ob_pop( se );
    *rv = args->self;
  }else{
    rc = (take<0)
      ? s2_cb_ob_take_string(args,rv)
      : s2_cb_ob_take_buffer(args,rv);
    if(!rc) rc = s2_ob_pop(se);
  }
  if(rc && (CWAL_RC_OOM!=rc) && (CWAL_RC_EXCEPTION!=rc)){
    rc = cwal_exception_setf(args->engine,
                             rc, "Error #%d (%s) popping buffer.",
                             rc, cwal_rc_cstr(rc));
  }
  return rc;
}    

int s2_cb_ob_push( cwal_callback_args const * args, cwal_value **rv ){
  s2_engine * se = s2_engine_from_args(args);
  int rc;
  assert(se);
  rc = s2_ob_push( se );
  if(rc){
    return cwal_exception_setf(args->engine, rc,
                               "Push failed.");
  }else{
    *rv = args->self;
    return 0;
  }
}    

int s2_ob_get( s2_engine * se, cwal_buffer ** tgt ){
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!se->ob.count) return CWAL_RC_RANGE;
  else{
    cwal_outputer * io = &se->e->vtab->outputer;
    S2ObBuffer * ob;
    assert(io->state.data);
    ob = (S2ObBuffer*)io->state.data;
    *tgt = &ob->buf;
    return 0;
  }
}

int s2_ob_take( s2_engine * se, cwal_buffer * tgt ){
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!se->ob.count) return CWAL_RC_RANGE;
  else{
    cwal_outputer * io = &se->e->vtab->outputer;
    S2ObBuffer * ob;
    assert(io->state.data);
    ob = (S2ObBuffer*)io->state.data;
    if(tgt != &ob->buf) *tgt = ob->buf;
    ob->buf = cwal_buffer_empty;
    return 0;
  }
}

int s2_ob_clear( s2_engine * se, char releaseBufferMem ){
  if(!se) return CWAL_RC_MISUSE;
  else if(!se->ob.count) return CWAL_RC_RANGE;
  else{
    cwal_outputer * io = &se->e->vtab->outputer;
    S2ObBuffer * ob;
    assert(io->state.data);
    ob = (S2ObBuffer*)io->state.data;
    if(releaseBufferMem) cwal_buffer_reserve(se->e, &ob->buf, 0);
    else ob->buf.used = 0;
    return 0;
  }
}

int s2_ob_flush( s2_engine * se ){
  if(!se) return CWAL_RC_MISUSE;
  else if(!se->ob.count) return CWAL_RC_RANGE;
  else{
    int rc = 0;
    cwal_size_t const i = se->ob.count-1;
    cwal_outputer * to = (cwal_outputer *)se->ob.list[i];
    cwal_outputer * io = &se->e->vtab->outputer;
    S2ObBuffer * ob;
    assert(io->state.data);
    assert(to != io);
    ob = (S2ObBuffer*)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 s2_cb_ob_level( cwal_callback_args const * args, cwal_value **rv ){
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  *rv = cwal_new_integer(args->engine, (cwal_int_t)se->ob.count);
  return *rv ? 0 : CWAL_RC_OOM;
}    

int s2_cb_ob_get( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  cwal_buffer * buf = NULL;
  assert(se);
  rc = s2_ob_get(se, &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 s2_cb_ob_take_string( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  cwal_buffer buf = cwal_buffer_empty;
  assert(se);
  rc = s2_ob_take(se, &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 s2_cb_ob_take_buffer( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  cwal_buffer * buf;
  cwal_value * bv;
  assert(se);
  bv = cwal_new_buffer_value(args->engine, 0);
  if(!bv) return CWAL_RC_OOM;
  buf = cwal_value_get_buffer(bv);
  rc = s2_ob_take(se, buf);
  if(rc) cwal_value_unref(bv);
  else *rv = bv;
  return rc;
}    

int s2_cb_ob_clear( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  rc = s2_ob_clear( se, 0 );
  if(rc) rc = cwal_exception_setf(args->engine, rc,
                                  "Clear failed.");
  else *rv = args->self;
  return rc;
}

int s2_cb_ob_flush( cwal_callback_args const * args, cwal_value **rv ){
  s2_engine * se = s2_engine_from_args(args);
  int rc;
  assert(se);
  rc = s2_ob_flush( se );
  if(rc) rc = cwal_exception_setf(args->engine, rc,
                                  "Flush failed.");
  else *rv = args->self;
  return rc;
}    

int s2_install_ob( s2_engine * se, cwal_value * tgt ){
  if(!se || !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 = s2_new_function2( se, FP );                      \
    CHECKV;                                                  \
    rc = cwal_prop_set( tgt, NAME, cwal_strlen(NAME), v );   \
    if(rc) goto end;

    FUNC("clear", s2_cb_ob_clear);
    FUNC("flush", s2_cb_ob_flush);
    FUNC("getString", s2_cb_ob_get);
    FUNC("pop", s2_cb_ob_pop);
    FUNC("push", s2_cb_ob_push);
    FUNC("level", s2_cb_ob_level);
    FUNC("takeBuffer", s2_cb_ob_take_buffer);
    FUNC("takeString", s2_cb_ob_take_string);
#undef FUNC
#undef CHECKV
    end:
    if(rc && v) cwal_value_unref(v);
    return rc;
  }
}

int s2_install_ob_2( s2_engine * se, cwal_value * tgt,
                     char const * name ){
  if(!se || !tgt || !name || !*name) return CWAL_RC_MISUSE;
  else {
    int rc;
    cwal_value * ob = cwal_new_object_value(se->e);
    if(!ob) rc = CWAL_RC_OOM;
    else if((rc = s2_install_ob(se, ob))
            ||
            (rc = cwal_prop_set(tgt, name, cwal_strlen(name), ob))){
      cwal_value_unref(ob);
    }
    return rc;
  }
}


#undef MARKER
/* end of file s2_ob.c */
/* start of file s2_ops.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

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

#define s2__pop_val s2_engine_pop_value
#define s2__pop_tok s2_engine_pop_token


/**
   Operator for unary and binary addition and subtraction.

   Required stack (from top to bottom):
   Binary: RHS LHS
   Unary: RHS
*/
static int s2_op_f_addsub( s2_op const * self, s2_engine * se,
                              int argc, cwal_value **rv );

/**
   Operator for multiplication, divisision, and modulus.

   Required stack (from top to bottom): RHS LHS
*/
static int s2_op_f_multdivmod( s2_op const * self, s2_engine * se,
                                  int argc, cwal_value **rv );

/**
   Operator for bitwise OR, AND, XOR, ~, and bitshift << and >>.

   Required stack (from top to bottom): RHS LHS
   except for the unary ~ op, which expects 1 operand.
*/
static int s2_op_f_bitwiseshift( s2_op const * self, s2_engine * se,
                               int argc, cwal_value **rv );

static int s2_op_f_not( s2_op const * self, s2_engine * se,
                           int argc, cwal_value **rv );

static int s2_op_f_andor( s2_op const * self, s2_engine * se,
                             int argc, cwal_value **rv );

/**
   Operator for comparison ops.

   Required stack (from top to bottom): RHS LHS
*/
static int s2_op_f_cmp( s2_op const * self, s2_engine * se,
                           int argc, cwal_value **rv );

static int s2_op_f_parens( s2_op const * self, s2_engine * se,
                              int argc, cwal_value **rv );

/**
   Operator for comma. It simply discards its lhs operand
   and evaluates to its rhs.

   Required stack (from top to bottom): RHS LHS
*/
static int s2_op_f_comma( s2_op const * self, s2_engine * se,
                             int argc, cwal_value **rv );


static int s2_op_f_dot( s2_op const * self, s2_engine * se,
                           int argc, cwal_value **rv );

/**
   Op impl for the arrow operator (X->Y). Fail if used on any value
   which does not overload it.
*/
static int s2_op_f_oload_arrow( s2_op const * self, s2_engine * se,
                                 int argc, cwal_value **rv );

/**
   Impl for X=Y and X.Y=Z operators.

   Required stack (top to bottom): RHS, KEY
   Required stack (top to bottom): RHS, KEY, OBJECT
*/
static int s2_op_f_assign( s2_op const * self, s2_engine * se,
                           int argc, cwal_value **rv );

static int s2_op_f_incrdecr( s2_op const * self, s2_engine * se,
                             int argc, cwal_value **rv );

static int s2_op_f_array_append( s2_op const * self, s2_engine * se,
                                 int argc, cwal_value **rv );

/**
   A dummy op for experimentation.

   Required stack (from top to bottom):

   ... S2_T_MarkVariadicStart
*/
static int s2_op_f_foo( s2_op const * self, s2_engine * se,
                           int argc, cwal_value **rv );


/**
   Where the global list of operators is stored. When adding new
   membmers, MAKE 100% SURE that:

   a) the entries in the following array line up with the members.

   b) update s2_ttype_op() to return the new member for the appropriate
   token type(s).
 */
static const struct S2_OPS__ {
  const s2_op add1;
  const s2_op add2;
  const s2_op sub1;
  const s2_op sub2;
  const s2_op multiply;
  const s2_op divide;
  const s2_op modulo;

  const s2_op bitOr;
  const s2_op bitXOr;
  const s2_op bitAnd;
  const s2_op bitNegate;
  const s2_op bitShiftLeft;
  const s2_op bitShiftRight;

  const s2_op logNot;
  const s2_op logAnd;
  const s2_op logOr;
  const s2_op logOr3;
  /*
    logical && and || are partially handled at the tokenizer level to
    support short-circuiting.
  */

  const s2_op cmpLT;
  const s2_op cmpLE;
  const s2_op cmpEq;
  const s2_op cmpEqStrict;
  const s2_op cmpNotEq;
  const s2_op cmpNotEqStrict;
  const s2_op cmpGT;
  const s2_op cmpGE;
  const s2_op inherits;
  const s2_op contains;
  const s2_op notContains;

  const s2_op assign;
  const s2_op assignMember;
  const s2_op assignPlus;
  const s2_op assignPlus3;
  const s2_op assignMinus;
  const s2_op assignMinus3;
  const s2_op assignMult;
  const s2_op assignMult3;
  const s2_op assignDiv;
  const s2_op assignDiv3;
  const s2_op assignModulo;
  const s2_op assignModulo3;
  const s2_op assignShiftLeft;
  const s2_op assignShiftLeft3;
  const s2_op assignShiftRight;
  const s2_op assignShiftRight3;
  const s2_op assignXOr;
  const s2_op assignXOr3;
  const s2_op assignAnd;
  const s2_op assignAnd3;
  const s2_op assignOr;
  const s2_op assignOr3;

  const s2_op incrPre;
  const s2_op incrPost;
  const s2_op decrPre;
  const s2_op decrPost;

  const s2_op parenOpen;
  const s2_op parenClose;

  const s2_op comma;
  const s2_op dot;
  const s2_op dotHash;
  const s2_op arrow;
  const s2_op arrayAppend;

  /**
     We need a ternary s2_op to support the ternary operator, but it
     is not used by the stack machine itself.
  */
  const s2_op ternary;

  const s2_op rhsEval;

  const s2_op foo;
  const s2_op _sentry_;
} S2_OPS = {
/*{ sym,     id,                arity, assoc, prec,      placement, call(), inferLeft, inferRight } */
  { "+X",    S2_T_OpPlusUnary,  1,  1, S2_PR_PlusUnary,  -1,  s2_op_f_addsub, 0, 0 },
  { "X+Y",   S2_T_OpPlus,       2, -1, S2_PR_Plus,        0,  s2_op_f_addsub, 0, 0 },

  { "-X",    S2_T_OpMinusUnary, 1,  1, S2_PR_MinusUnary, -1,  s2_op_f_addsub, 0, 0 },
  { "X-Y",   S2_T_OpMinus,      2, -1, S2_PR_Minus,       0,  s2_op_f_addsub, 0, 0 },

  { "X*Y",   S2_T_OpMultiply,   2, -1, S2_PR_Multiply,    0,  s2_op_f_multdivmod, 0, 0 },
  { "X/Y",   S2_T_OpDivide,     2, -1, S2_PR_Divide,      0,  s2_op_f_multdivmod, 0, 0 },
  { "X%Y",   S2_T_OpModulo,     2, -1, S2_PR_Modulo,      0,  s2_op_f_multdivmod, 0, 0 },

  { "X|Y",   S2_T_OpOrBitwise,  2, -1, S2_PR_BitwiseOr,   0,  s2_op_f_bitwiseshift, 0, 0 },
  { "X^Y",   S2_T_OpXOr,        2, -1, S2_PR_BitwiseXor,  0,  s2_op_f_bitwiseshift, 0, 0 },
  { "X&Y",   S2_T_OpAndBitwise, 2, -1, S2_PR_BitwiseAnd,  0,  s2_op_f_bitwiseshift, 0, 0 },
  { "~X",    S2_T_OpNegateBitwise, 1, 1, S2_PR_BitwiseNegate, -1, s2_op_f_bitwiseshift, 0, 0 },
  { "X<<Y",  S2_T_OpShiftLeft,  2, -1, S2_PR_ShiftLeft,   0, s2_op_f_bitwiseshift, 0, 0 },
  { "X>>Y",  S2_T_OpShiftRight, 2, -1, S2_PR_ShiftRight,  0, s2_op_f_bitwiseshift, 0, 0 },

  { "!X",    S2_T_OpNot,        1,  1, S2_PR_LogicalNot, -1, s2_op_f_not, 0, 0 },
  /* &&, ||, and ||| are handled at the parser level for short-cicuiting, but
     the stack machine supports them directly without short-circuiting. Why?
     It's a stack machine, so the RHS has already been processed (or the stack
     wouldn't be running), so short-circuiting evaluation is not possible at
     that point.
  */
  { "X&&Y",  S2_T_OpAnd,          2, -1, S2_PR_LogicalAnd,  0, s2_op_f_andor, 0, 0 },
  { "X||Y",  S2_T_OpOr,           2, -1, S2_PR_LogicalOr,   0, s2_op_f_andor, 0, 0 },
  { "X|||Y", S2_T_OpOr3,          2, -1, S2_PR_LogicalOr3,  0, s2_op_f_andor, 0, 0 },

  { "X<Y",   S2_T_CmpLT,          2, -1, S2_PR_CmpLT,       0, s2_op_f_cmp, 0, 0 },
  { "X<=Y",  S2_T_CmpLE,          2, -1, S2_PR_CmpLE,       0, s2_op_f_cmp,
    S2_T_CmpLT, S2_T_CmpEq },
  { "X==Y",  S2_T_CmpEq,          2, -1, S2_PR_CmpEq,       0, s2_op_f_cmp, 0, 0 },
  { "X===Y", S2_T_CmpEqStrict,    2, -1, S2_PR_CmpEqStrict, 0, s2_op_f_cmp, 0, 0 },
  { "X!=Y",  S2_T_CmpNotEq,       2, -1, S2_PR_CmpNotEq,    0, s2_op_f_cmp, 0, 0 },
  { "X!==Y", S2_T_CmpNotEqStrict, 2, -1, S2_PR_CmpNotEqStrict,  0, s2_op_f_cmp, 0, 0 },
  { "X>Y",   S2_T_CmpGT,          2, -1, S2_PR_CmpGT,       0, s2_op_f_cmp, 0, 0 },
  { "X>=Y",  S2_T_CmpGE,          2, -1, S2_PR_CmpGE,       0, s2_op_f_cmp,
    S2_T_CmpGT, S2_T_CmpEq },
  { "inherits", S2_T_OpInherits,  2, -1, S2_PR_OpInherits,  0, s2_op_f_cmp, 0, 0 },
  { "X=~Y", S2_T_OpContains,      2, -1, S2_PR_Contains,    0, s2_op_f_cmp, 0, 0 },
  { "X!~Y", S2_T_OpNotContains,   2, -1, S2_PR_NotContains, 0, s2_op_f_cmp, 0, 0 },

  { "X=Y",     S2_T_OpAssign,       2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y=Z",   S2_T_OpAssign3,      3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X+=Y",    S2_T_OpPlusAssign,   2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y+=Z",  S2_T_OpPlusAssign3,  3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X-=Y",    S2_T_OpMinusAssign,  2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y-=Z",  S2_T_OpMinusAssign,  3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X*=Y",    S2_T_OpMultiplyAssign,  2, 1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y*=Z",  S2_T_OpMultiplyAssign3, 3, 1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X/=Y",    S2_T_OpDivideAssign,    2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y/=Z",  S2_T_OpDivideAssign3,   3, 1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X%=Y",    S2_T_OpModuloAssign,    2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y%=Z",  S2_T_OpModuloAssign3,   3,  1, S2_PR_OpAssign,   0, s2_op_f_assign, 0, 0 },
  { "X<<=Y",   S2_T_OpShiftLeftAssign, 2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y<<=Z", S2_T_OpShiftLeftAssign3, 3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X>>=Y",   S2_T_OpShiftRightAssign, 2, 1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y>>=Z", S2_T_OpShiftRightAssign3, 3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X^=Y",    S2_T_OpXOrAssign,    2, 1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y^=Z",  S2_T_OpXOrAssign3,   3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X&=Y",    S2_T_OpAndAssign,    2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y&=Z",  S2_T_OpAndAssign3,   3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X|=Y",    S2_T_OpOrAssign,     2,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },
  { "X.Y|=Z",  S2_T_OpOrAssign3,    3,  1, S2_PR_OpAssign,    0, s2_op_f_assign, 0, 0 },

  { "++X", S2_T_OpIncrPre,        1,  1, S2_PR_IncrDecr,   -1, s2_op_f_incrdecr, 0, 0 },
  { "X++", S2_T_OpIncrPost,       1, -1, S2_PR_IncrDecr,    1, s2_op_f_incrdecr, 0, 0 },
  { "--X", S2_T_OpDecrPre,        1,  1, S2_PR_IncrDecr,   -1, s2_op_f_incrdecr, 0, 0 },
  { "X--", S2_T_OpDecrPost,       1, -1, S2_PR_IncrDecr,    1, s2_op_f_incrdecr, 0, 0 },

  { "(",   S2_T_ParenOpen,        0, -1, S2_PR_ParensOpen, -1, s2_op_f_parens, 0, 0 },
  { ")",   S2_T_ParenClose,       0, -1, S2_PR_ParensClose, 1, s2_op_f_parens, 0, 0 },

  { "X,Y",   S2_T_Comma,          2, -1, S2_PR_Comma,       0, s2_op_f_comma, 0, 0 },
  { "X.Y",   S2_T_OpDot,          2, -1, S2_PR_DotDeref,    0, s2_op_f_dot, 0, 0 },
  { "X#Y",   S2_T_OpHash,         2, -1, S2_PR_DotDeref,    0, s2_op_f_dot, 0, 0 },
  { "X->Y",  S2_T_OpArrow,        2, -1, S2_PR_OpArrow,     0, s2_op_f_oload_arrow, 0, 0 },

  { "X[]=Y", S2_T_ArrayAppend,    2,  1, S2_PR_ArrayAppend, 0, s2_op_f_array_append, 0, 0 },

  { "X?Y:Z",  S2_T_Question,      3,  1, S2_PR_TernaryIf,   0, 0/*handled at parser level */, 0, 0 },
  { "...RHS", S2_T_RHSEval,       1,  1, S2_PR_RHSEval,     0, 0/*handled at parser level */ , 0, 0 },
  { "FOO...", S2_T_Foo,          -1,  1, S2_PR_Keyword,     0, s2_op_f_foo, 0, 0 },

  /*_sentry_*/s2_op_empty_m
};


s2_op const * s2_ttype_op( int ttype ){
  switch( ttype ){
#define OP(E,M) case E: return &S2_OPS.M
    OP(S2_T_OpPlusUnary,add1);
    OP(S2_T_OpPlus,add2);
    OP(S2_T_OpMinusUnary,sub1);
    OP(S2_T_OpMinus,sub2);

    OP(S2_T_OpMultiply,multiply);
    OP(S2_T_OpDivide,divide);
    OP(S2_T_OpModulo,modulo);

    OP(S2_T_OpOrBitwise,bitOr);
    OP(S2_T_OpXOr,bitXOr);
    OP(S2_T_OpAndBitwise,bitAnd);
    OP(S2_T_OpNegateBitwise,bitNegate);
    OP(S2_T_OpShiftLeft,bitShiftLeft);
    OP(S2_T_OpShiftRight,bitShiftRight);

    OP(S2_T_OpNot,logNot);
    OP(S2_T_OpAnd,logAnd);
    OP(S2_T_OpOr,logOr);
    OP(S2_T_OpOr3,logOr3);

    OP(S2_T_CmpLT,cmpLT);
    OP(S2_T_CmpLE,cmpLE);
    OP(S2_T_CmpEq,cmpEq);
    OP(S2_T_CmpEqStrict,cmpEqStrict);
    OP(S2_T_CmpNotEq,cmpNotEq);
    OP(S2_T_CmpNotEqStrict,cmpNotEqStrict);
    OP(S2_T_CmpGT,cmpGT);
    OP(S2_T_CmpGE,cmpGE);
    OP(S2_T_OpInherits,inherits);
    OP(S2_T_OpContains,contains);
    OP(S2_T_OpNotContains,notContains);

    OP(S2_T_OpAssign,assign);
    OP(S2_T_OpAssign3,assignMember);
    OP(S2_T_OpPlusAssign,assignPlus);
    OP(S2_T_OpPlusAssign3,assignPlus3);
    OP(S2_T_OpMinusAssign,assignMinus);
    OP(S2_T_OpMinusAssign3,assignMinus3);
    OP(S2_T_OpMultiplyAssign,assignMult);
    OP(S2_T_OpMultiplyAssign3,assignMult3);
    OP(S2_T_OpDivideAssign, assignDiv);
    OP(S2_T_OpDivideAssign3, assignDiv3);
    OP(S2_T_OpModuloAssign, assignModulo);
    OP(S2_T_OpModuloAssign3, assignModulo3);
    OP(S2_T_OpShiftLeftAssign, assignShiftLeft);
    OP(S2_T_OpShiftLeftAssign3, assignShiftLeft3);
    OP(S2_T_OpShiftRightAssign, assignShiftRight);
    OP(S2_T_OpShiftRightAssign3, assignShiftRight3);
    OP(S2_T_OpXOrAssign, assignXOr);
    OP(S2_T_OpXOrAssign3, assignXOr3);
    OP(S2_T_OpAndAssign, assignAnd);
    OP(S2_T_OpAndAssign3, assignAnd3);
    OP(S2_T_OpOrAssign, assignOr);
    OP(S2_T_OpOrAssign3, assignOr3);

    OP(S2_T_OpIncrPre, incrPre);
    OP(S2_T_OpIncrPost, incrPost);
    OP(S2_T_OpDecrPre, decrPre);
    OP(S2_T_OpDecrPost, decrPost);

    OP(S2_T_ParenOpen,parenOpen);
    OP(S2_T_ParenClose,parenClose);

    OP(S2_T_Comma,comma);
    OP(S2_T_OpDot,dot);
    OP(S2_T_OpHash,dotHash);
    OP(S2_T_OpArrow,arrow);
    OP(S2_T_ArrayAppend,arrayAppend);
    OP(S2_T_Question,ternary);
    OP(S2_T_RHSEval,rhsEval);
    OP(S2_T_Foo,foo);
#undef OP
    default:
      return 0;
  }
}

s2_op const * s2_stoken_op( s2_stoken const * t ){
  return t ? s2_ttype_op( t->ttype ) : 0;
}

int s2_op_is_unary_prefix( s2_op const * op ){
  return (1==op->arity && op->placement<0) ? op->id : 0;
}


int s2_op_is_math( s2_op const * op ){
  switch(op ? op->id : 0){
    case S2_T_OpPlus:
    case S2_T_OpMinus:
    case S2_T_OpMultiply:
    case S2_T_OpDivide:
    case S2_T_OpModulo:
    case S2_T_OpOrBitwise:
    case S2_T_OpXOr:
    case S2_T_OpAndBitwise:
    case S2_T_OpShiftRight:
    case S2_T_OpShiftLeft:
      return op->id;
    default:
      return 0;
  }
}

int s2_op_is_expr_border( s2_op const * op ){
  switch(op ? op->id : 0){
    case S2_T_ParenOpen:
    case S2_T_ParenClose:
    case S2_T_BraceOpen:
    case S2_T_BraceClose:
    case S2_T_Comma:
    case S2_T_Semicolon:
    case S2_T_Colon:
      return op->id;
    default:
      return 0;
  }
}

int s2_op_short_circuits( s2_op const * op ){
#if 1
  return op ? s2_ttype_short_circuits(op->id) : 0;
#else
  switch( op ? op->id : 0 ){
    case S2_T_OpOr:
    case S2_T_OpAnd:
    case S2_T_Question:
      return op->id;
    default: return 0;
  }
#endif
}

/**
   If ttype refers to an overloadable operator type and v is _capable_
   of overloading that operator, this function returns the string form
   of the overloaded operator method and sets *len (if not NULL) to
   the length of that name. It does not confirm that v actually does
   overload it, only that the type/operator combination is potentially
   legal.

   Returns 0 if v is of a type or type/operator combination for which
   we prohibit overloading.
*/
char const * s2_overload_name( int ttype, cwal_value const * v, cwal_size_t * len ){
  if(len) *len = 0;

  switch(ttype){
    /* overloadable for any types (these only exist for overloading)... */
#define CASE(W,T) case T: if(len) *len=sizeof(W)-1/*NUL!*/; return W
    CASE("operator->", S2_T_OpArrow);
    CASE("operator=~", S2_T_OpContains);
    CASE("operator!~", S2_T_OpNotContains);
#undef CASE
    default:
      break;
  };

  switch(cwal_value_type_id(v)){
    /* These type/operator combinations are prohibited */
    case CWAL_TYPE_UNDEF:
    case CWAL_TYPE_NULL:
    case CWAL_TYPE_BOOL:
    case CWAL_TYPE_INTEGER:
    case CWAL_TYPE_DOUBLE:
      return 0;
    case CWAL_TYPE_STRING:
      /* This subset is up for reconsideration. Is there a compelling
         reason to disallow the others, aside from the convenience of
         relying on default string-to-number coercion we get with
         them? Maybe just prohibit overloading unary -/+?
      */
      switch(ttype){
        case S2_T_OpPlus: break;
        case S2_T_OpMultiply: break;
        case S2_T_OpModulo: break;
        case S2_T_OpDivide: break;
        default:
          if(s2_ttype_is_assignment_combo(ttype)) break;
          else return 0;
      }
    default:
      break;
  }

  switch(ttype){
    /* These ops require overloads for all types not ruled out
       above. */
#define CASE(W,T) case T: if(len) *len=sizeof(W)-1/*NUL!*/; return W
#define COMBO(T, W,W2) CASE(W, T); CASE(W2, T##Assign); CASE(W2, T##Assign3)
    COMBO(S2_T_OpPlus, "operator+", "operator+=");
    COMBO(S2_T_OpMinus, "operator-", "operator-=");
    COMBO(S2_T_OpMultiply, "operator*", "operator*=");
    COMBO(S2_T_OpDivide, "operator/", "operator/=");
    COMBO(S2_T_OpModulo, "operator%", "operator%=");
    COMBO(S2_T_OpShiftLeft, "operator<<", "operator<<=");
    COMBO(S2_T_OpShiftRight, "operator>>", "operator>>=");
    COMBO(S2_T_OpXOr, "operator^", "operator^=");
    CASE("operator&", S2_T_OpAndBitwise);
    CASE("operator&=", S2_T_OpAndAssign);
    CASE("operator&=", S2_T_OpAndAssign3);
    CASE("operator|", S2_T_OpOrBitwise);
    CASE("operator|=", S2_T_OpOrAssign);
    CASE("operator|=", S2_T_OpOrAssign3);
#undef COMBO

    CASE("+operator", S2_T_OpPlusUnary);
    CASE("-operator", S2_T_OpMinusUnary);

    CASE("operator++", S2_T_OpIncrPre);
    CASE("operator++", S2_T_OpIncrPost) /* called w/ 'this' as its argument */;

    CASE("operator--", S2_T_OpDecrPre);
    CASE("operator--", S2_T_OpDecrPost) /* called w/ 'this' as its argument */;

    CASE("operator<", S2_T_CmpLT);
    CASE("operator<=", S2_T_CmpLE);

    CASE("operator>", S2_T_CmpGT);
    CASE("operator>=", S2_T_CmpGE);

    CASE("operator==", S2_T_CmpEq);
    CASE("operator!=", S2_T_CmpNotEq);

    /* CASE("operator#", S2_T_OpHash);

       Overloading of # was removed, as it (A) will complicate
       extending # to support assignment (insert()) and (B) it
       essentially negates the reason for having the # operator: an
       access method for hashes which makes them competative with
       Objects (using Hash.search() has function call overhead which
       obj[prop] does not, making objects faster for most cases).
    */
    /* CASE("operator.", S2_T_OpDot); */
#undef CASE
    default:
      if(len) *len = 0;
      return 0;
  };
}

/**
   Internal helper for overloadable operators.

   op is the operator being acted upon and it must have an arity of 1
   or 2. se is the interpreter. lhs and rhs are the LHS and RHS of the
   operator (their requirements are described below).

   If no overload of op is legal for this type (see
   s2_overload_name()) then 0 is returned and *theRc is set to 0.
   i.e. a non-error.

   The POD types either do not participate, or participate in
   castrated form (e.g. CWAL_TYPE_STRING only observes a limited set
   of overloads).

   If non-0 is returned and *theRc is 0 after returning, it means an
   overload was successfully called and its result value is stored in
   *rv.

   If *theRc is set to non-0 after returning, it means an error was
   triggered, and the caller must not proceed with the operation.

   If an overload is legal but (lhs ? lhs : rhs) does not contain it,
   an error is triggered describing the missing operator (if requireIt
   is true), or *theRc set it to 0 and 0 is returned if !requireIt.

   We require explicit overloading for all non-POD types, and treat
   missing ops as an error unless requireIt is false. The thinking is:
   rather that using built-in ops in senseless contexts. i don't want
   s2 to turn into a truly type-sloppy language, despite being
   "loosely typed." If this proves to be a hindrance, we can always
   make it more sloppy by calling back to the built-in operators
   (i.e. coercion to numeric types).

   Conventions:

   For overload calls, the 'this' is always (lhs ? lhs : rhs),
   and it may not be 0.

   if op->arity==2:

   - lhs!=0, rhs!=0, assumed to be an infix binary operator. The
   overload is called with lhs as the 'this' and (lhs,rhs) arguments.

   If op->arity==1: 

   - One of lhs or rhs must be 0.

   - If op->placement<0 (prefix op) then the overload is called
   without parameters.

   - If op->placement>0 (postfix) then the overload is called with
   (lhs?lhs:rhs) as its parameter. This is similar to C++'s convention
   of passing a no-meaning integer to the postfix ++/-- operators as a
   way of distinguishing them from prefix ops. The difference is that
   in C++ prefix/postfix operators are separate overloads, but in s2
   they're a single function instance.

*/
static char s2_op_check_overload( s2_op const * op, s2_engine * se,
                                  char requireIt,
                                  cwal_value * lhs, cwal_value * rhs,
                                  cwal_value ** rv, int * theRc ){
  cwal_value * theThis = lhs ? lhs : rhs;
  cwal_size_t opLen = 0;
  char const * opName = s2_overload_name( op->id, theThis, &opLen );
  cwal_value * fv = 0;
  cwal_function * fn;
  int rc;
  /* s2_dump_val(theThis,op->sym); */
  /* MARKER(("op->sym=%s\n", op->sym)); */
  if(!opName) {
#if 0
    switch(cwal_value_type_id(theThis)){
      case CWAL_TYPE_STRING:
        if(s2_ttype_is_assignment_combo(op->id)){
          *theRc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED,
                                     "Immutable strings may not "
                                     "use the %s operator.",
                                     op->sym);
          break;
        }
        /* else fall through */
      default:
        *theRc = 0;
    }
#else
    *theRc = 0;
#endif
    return 0;
  }
  /* MARKER(("opName=%s\n", opName)); */
  rc = s2_get( se, theThis, opName, opLen, &fv );
  if(rc){
    *theRc = rc;
    return 0;
  }
  else if(!fv || !(fn = cwal_value_function_part(se->e, fv))){
    /* Not found or non-function property */

    /*
      TODO: check op->inferLeft and/or op->inferRight and see if we
      can create a combo operator out of one or two others which
      theThis does overload:

      != and !== can be inferred by applying ! to the ==/=== operators.

      Other ops can specify op->inferLeft/Right to tell us which
      two ops to combine.
    */
    if(!requireIt) *theRc = 0;
    else *theRc = s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                                    "LHS (type %s) has no '%s' (%s) operator.",
                                    cwal_value_type_name(theThis),
                                    opName, op->sym);
    return 0;
  }else{
    cwal_value * argv[2] = {0,0};
    int argc = 0;
    switch(op->arity){
      case 1:
        if(op->placement<0){/*prefix op*/
          argc = 0;
        }else{/*postfix*/
          assert(op->placement!=0);
          argc = 1;
          argv[0] = theThis;
        }
        break;
      case 2:
        argc = 2;
        argv[0] = lhs;
        argv[1] = rhs;
        break;
      default:
        /*
          Happens when:

          var obj = {a:1};
          obj.a += 1;

          At this point in the processing we only have (a, 1), but that's
          okay, as the caller has the obj part and will apply the result back
          to it after this operation.
        */
        assert(3==op->arity);
        assert(s2_ttype_is_assignment_combo(op->id));
        argc = 2;
        argv[0] = lhs;
        argv[1] = rhs;
        break;
    }
    if(lhs) cwal_value_ref(lhs);
    if(rhs) cwal_value_ref(rhs);
    if(0){
      MARKER(("overload %s: argc=%d\n", op->sym, (int)argc));
      s2_dump_val(theThis,"theThis");
      s2_dump_val(argv[0],"argv[0]");
      s2_dump_val(argv[1],"argv[1]");
    }
    *theRc = cwal_function_call( fn, theThis, rv, argc, argv );
    if(0){
      s2_dump_val(*rv, "overload call *rv");
    }
    if(lhs) cwal_value_unhand(lhs);
    if(rhs) cwal_value_unhand(rhs);
    return 1;
  }
}


int s2_op_f_addsub( s2_op const * op, s2_engine * se, int argc,
                    cwal_value **rv ){
  cwal_value * rhs;
  cwal_value * lhs;
  char isAdd = 0;
  switch(op->id){
    case S2_T_OpPlus:
      isAdd = 1;
    case S2_T_OpMinus:
      assert( 2 == argc );
      rhs = s2__pop_val( se );
      lhs = s2__pop_val( se );
      break;
    case S2_T_OpPlusUnary:
      isAdd = 1;
    case S2_T_OpMinusUnary:
      assert( 1 == argc );
      rhs = s2__pop_val( se );
      lhs = 0;
      /* s2_dump_val(rhs, "unary addsub rhs"); */
      break;
    default:
      assert(!"WRONG!");
      return CWAL_RC_ASSERT;
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    int rc = 0;
    if(s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc)
       || rc) return rc;
    else return s2_values_addsub( se, isAdd, lhs, rhs, rv);
  }
}

int s2_op_f_multdivmod( s2_op const * op, s2_engine * se, int argc,
                        cwal_value **rv ){
  cwal_value * rhs;
  cwal_value * lhs;
  int mode;
  assert( 2 == argc );
  rhs = s2__pop_val( se );
  lhs = s2__pop_val( se );
  switch( op->id ){
    case S2_T_OpMultiply: mode = 1; break;
    case S2_T_OpDivide: mode = -1; break;
    case S2_T_OpModulo: mode = 0; break;
    default:
      assert(!"Invalid op binding!");
      return CWAL_RC_ASSERT;
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    int rc = 0;
    if(s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc)
       || rc) return rc;
    else return s2_values_multdivmod( se, mode, lhs, rhs, rv);
  }
}

int s2_op_f_bitwiseshift( s2_op const * op, s2_engine * se, int argc,
                          cwal_value **rv ){
  cwal_value * vlhs = 0, * vrhs = 0;
  assert((S2_T_OpNegateBitwise==op->id) ? (1==argc) : (2==argc));
  vrhs = s2__pop_val(se);
  assert(vrhs);
  if(S2_T_OpNegateBitwise != op->id){
    vlhs = s2__pop_val(se);
    assert(vlhs);
  }
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    int rc = 0;
    if(s2_op_check_overload(op, se, 1, vlhs, vrhs, rv, &rc)
       || rc) return rc;
    else return s2_values_bitwiseshift( se, op->id, vlhs, vrhs, rv );
  }
}


int s2_op_f_not( s2_op const * op, s2_engine * se, int argc,
                    cwal_value **rv ){
  cwal_value * rhs;
  assert(1==argc);
  rhs = s2__pop_val(se);
  *rv = (se->skipLevel>0)
    ? cwal_value_undefined()
    : cwal_new_bool( !cwal_value_get_bool(rhs) );
  return 0;
}

int s2_op_f_andor( s2_op const * op, s2_engine * se, int argc,
                      cwal_value **rv ){
  cwal_value * rhs, * lhs;
  assert(2==argc);
  rhs = s2__pop_val(se);
  lhs = s2__pop_val(se);
  assert(rhs);
  assert(lhs);
  /**
     In theory, if se->skipLevel>0 and if all operators comply and use
     cwal_value_undefined() for all results in skip mode, then rhs is
     guaranteed to be the Undefined value and lhs might (depending on
     where skip-mode was invoked) be Undefined. We "might" want to
     assert here that rhs===Undefined, to enforce the
     must-be-undefined convention, but i've also half a mind to always
     return (AND ? TRUE : FALSE) from this op on short-circuit mode,
     the logic being that short-circuit mode "should" (i think)
     consume as much as possible, and returning that bool from here
     might help ensure that the downstream can do so. Something to
     think about.
  */
  switch((se->skipLevel>0) ? 0 : op->id){
    case 0:
      *rv = cwal_value_undefined();
      break;
    case S2_T_OpAnd:
      *rv = cwal_new_bool( cwal_value_get_bool(lhs)
                           && cwal_value_get_bool(rhs) );
      break;
    case S2_T_OpOr:
      *rv = cwal_new_bool( cwal_value_get_bool(lhs)
                           || cwal_value_get_bool(rhs) );
      break;
    case S2_T_OpOr3:
      *rv = cwal_value_get_bool(lhs) ? lhs : rhs;
      break;
    default:
      assert(!"Invalid op mapping!");
  }
  return 0;
}

static int s2_op_f_cmp( s2_op const * op, s2_engine * se,
                           int argc, cwal_value **rv ){
  cwal_value * rhs;
  cwal_value * lhs;
  int rc = 0;
  assert(2==argc);
  rhs = s2__pop_val(se);
  lhs = s2__pop_val(se);
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    return 0;
  }
#if 0
  MARKER(("COMPARE op %s\n", op->sym));
  s2_dump_val(lhs, "LHS");
  s2_dump_val(rhs, "RHS");
#endif
  switch(op->id){
    case S2_T_OpInherits:
      *rv = cwal_value_derives_from( se->e, lhs, rhs )
        ? cwal_value_true()
        : cwal_value_false()
      ;
      return 0;
    case S2_T_CmpEqStrict:
    case S2_T_CmpNotEqStrict:{
      if(rhs == lhs){
        rc = 0;
      }else{
        int const tidL = cwal_value_type_id(lhs);
        int const tidR = cwal_value_type_id(rhs);
        if(tidL == tidR){
          rc = cwal_value_compare(lhs, rhs);
        }else{
          rc = tidL - tidR;
        }
      }
      rc = (S2_T_CmpNotEqStrict==op->id)
        ? (0!=rc)
        : (0==rc);
      break;
    }
    default:{
      if(s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc)
         || rc) return rc;
      rc = cwal_value_compare(lhs, rhs);
      switch(op->id){
        case S2_T_CmpLT: rc = rc < 0; break;
        case S2_T_CmpLE: rc = rc <= 0; break;
        case S2_T_CmpGT: rc = rc > 0; break;
        case S2_T_CmpGE: rc = rc >= 0; break;
        case S2_T_CmpEq: rc = 0 == rc; break;
        case S2_T_CmpNotEq: rc = 0 != rc; break;
        default:
          assert(!"CANNOT HAPPEN");
          return CWAL_RC_CANNOT_HAPPEN;
      }
    }
  }
  *rv = rc ? cwal_value_true() : cwal_value_false();
  return 0;
}


int s2_op_f_parens( s2_op const * op, s2_engine * se,
                       int argc, cwal_value **rv ){

  /*
    TODO someday: re-add parens support into the stack machine, so
    that they can be used independently of script code. Not likely to
    happen until i want to use the stack machine that way, though.
  */
  assert(!"')' is handled higher up!\n");
  if(S2_T_ParenOpen==op->id){
    MARKER(("We've hit the internal parens impl!\n"));
    assert(!"'(' is handled higher up.");
    assert(0==argc);
    /* Tell s2_process_op() to keep the result
       of the prior expression (presumably to the
       right of this operator).
    */
    *rv = 0;
    return 0;
  }
  return 0;
}

int s2_op_f_comma( s2_op const * op, s2_engine * se,
                      int argc, cwal_value **rv ){
  assert(2==argc);
  assert(S2_T_Comma==op->id);
  if(se->skipLevel){
    /* discard both args */
    s2__pop_tok(se, 0);
    s2__pop_tok(se, 0);
    *rv = cwal_value_undefined();
  }else{
    *rv = s2__pop_val(se) /* RHS */;
    s2__pop_tok(se, 0) /* discard LHS */;
  }
  return 0;
}

int s2_op_f_dot( s2_op const * op, s2_engine * se,
                 int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * rhs, * lhs;
  assert(2==argc);
  assert(S2_T_OpDot==op->id || S2_T_OpHash==op->id);
  rhs = s2__pop_val(se);
  lhs = s2__pop_val(se);

  /* s2_dump_val(rhs,"se->dotOpKey"); */
  /* s2_dump_val(lhs,"se->dotOpLhs"); */
  if(!se->skipLevel
     && !cwal_props_can(lhs)
     && !cwal_value_prototype_get(se->e, lhs)){
    rc = s2_engine_err_set(se, CWAL_RC_TYPE,
                           "Invalid LHS value (type %s) for "
                           "'%s' operator.",
                           cwal_value_type_name(lhs),
                           op->sym);
  }else if(se->skipLevel){
    if(S2_T_OpDot==op->id){
      se->dotOpSelf = se->dotOpLhs = lhs;
      se->dotOpKey = rhs;
    }
    *rv = cwal_value_undefined();
    rc = 0;
  }else{
    cwal_value * xrv = 0;
    se->dotOpLhs = se->dotOpKey = se->dotOpSelf = 0;
    /* s2_dump_val(lhs,"dot op lhs"); */
    if(S2_T_OpDot==op->id){
      const int isArrayAccess =
        cwal_value_is_integer(rhs)
        && cwal_value_array_part(se->e,lhs)
        /* Workaround to keep array.int's
           array from becoming 'this' if it
           resolves to a function which gets
           called.
        */
        ;
      rc = s2_get_v(se, lhs, rhs, &xrv);
      if(!rc && !isArrayAccess) se->dotOpSelf = lhs;
    }else{ /* X#Y */
      cwal_hash * h;
      assert(S2_T_OpHash==op->id);
#if 0
      if(!s2_op_check_overload(op, se, 0, lhs, rhs, &xrv, &rc)
         && !rc){
#endif
        h = cwal_value_hash_part(se->e, lhs);
        if(!h){
          rc = s2_engine_err_set(se, CWAL_RC_TYPE,
                                 "Invalid (non-Hash) LHS for '%s' operator.",
                                 op->sym);
        }else{
          xrv = cwal_hash_search_v( h, rhs );
        }
#if 0
      }
#endif
    }
    if(!rc){
      se->dotOpLhs = lhs;
      se->dotOpKey = rhs;
      *rv = xrv ? xrv : cwal_value_undefined();
    }
  }
  return rc;
}

int s2_op_f_oload_arrow( s2_op const * op, s2_engine * se,
                         int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * rhs, * lhs;
  assert(2==argc);
  rhs = s2__pop_val(se);
  lhs = s2__pop_val(se);
  if(se->skipLevel){
    *rv = cwal_value_undefined();
  }else{
    assert(s2_overload_name(op->id, lhs, 0));
    if(!s2_op_check_overload(op, se, 1, lhs, rhs, rv, &rc)){
      assert(rc);
    }
#if 0
    /*??? can of worms ??? */
    if(!rc){
      /* Has to be done afterwards b/c overload can overwrite these,
         triggering an assertion in fcall() */
      se->dotOpKey = rhs;
      se->dotOpLhs = lhs /* *rv */;
      /* s2_dump_val(rhs,"se->dotOpKey"); */
      /* s2_dump_val(lhs,"se->dotOpLhs"); */
    }
#endif
  }
  return rc;
}

/*static int s2_assign_oload_getter( s2_engine * se, s2_op const * op,
  cwal_value * self, cwal_value * );*/

int s2_op_f_assign( s2_op const * op, s2_engine * se,
                    int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * rhs, * lhs, * self /* lhs=rhs resp. self[lhs]=rhs */;
  s2_stoken * vTok;
  int ttype;
  char gotOverload = 0;
  rhs = s2__pop_val(se);
  vTok = s2__pop_tok(se, 1);
  lhs = vTok->value;
  self = (3==op->arity)
    ? s2__pop_val(se)
    : 0;
  assert( self ? 3==op->arity : 2==op->arity );
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    s2_stoken_free(se, vTok, 1);
    return 0;
  }
  ttype = vTok->ttype;
  s2_stoken_free(se, vTok, 1);
  vTok = 0;
  if((!self && (S2_T_Identifier != ttype))
     /* || (self && (S2_T_PropertyKey != ttype)) */
     ){
    return s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                             "Invalid LHS (%s) for '%s' op.",
                             s2_ttype_cstr(ttype), op->sym);
  }

  if((!self && (op->id != S2_T_OpAssign) /* not: X = Y */)
     || (self && (op->id != S2_T_OpAssign3) /* not: X.Y = Z */)){
    /* For combo ops (all but X=Y and X.Y=Z) we need to resolve
       the LHS first because it's needed for calculating the
       result. For plain assignments we don't need to do this,
       and we don't allow overloading of those, so thos can skip
       all this.
    */
    /*
      Reminder: this doesn't play well with the -> op. For that one,
      we really want to use the overload to get the resolved value.
      But at this point we have lost the info that there was a
      -> op. The same applies to S2_T_OpHash.
    */
    cwal_value * lhsResolved = 0;
    /* assert((op->id==S2_T_OpAssign3) ? se->dotOpId : !se->dotOpId); */
    /* assert(self ? se->dotOpId : 1); */
    /* assert(!self ? !se->dotOpId : 1); */
#if 0
    if(self && (s2_op_check_overload( s2_ttype_op(se->dotOpId), se, 0,
                                     self, lhs, &lhsResolved, &rc)
                || rc)){
      /* fall through */
    }else 
#endif
    if( (rc = s2_get_v( se, self, lhs, &lhsResolved ))
        /* Reminder to self: if we want full X->Y and X#Y support, we
           need to adjust this to use those overloads. The problem is
           that by the time this op is triggered, we no longer know
           which dot-like op triggered the access, as se->dotOpLhs and
           friends _might_ (depending on stack conditions) have been
           cleared by now.
        */){
      return rc;
    }else if(!lhsResolved){
      if(!self){
        cwal_size_t idLen = 0;
        char const * sym = cwal_value_get_cstr(lhs, &idLen);
        return s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                                 "'%s' operator cannot resolve "
                                 "identifier '%.*s'",
                                 op->sym, (int)idLen, sym );

      }else{
        /* unknown object property */
        lhsResolved = cwal_value_undefined();
      }
    }
    if(!(gotOverload=s2_op_check_overload(op, se, 1, lhsResolved, rhs, &rhs, &rc))
       && !rc){
      switch(op->id){
        case S2_T_OpPlusAssign:
        case S2_T_OpPlusAssign3:
          rc = s2_values_addsub( se, 1, lhsResolved, rhs, &rhs);
          break;
        case S2_T_OpMinusAssign:
        case S2_T_OpMinusAssign3:
          rc = s2_values_addsub( se, 0, lhsResolved, rhs, &rhs);
          break;
        case S2_T_OpMultiplyAssign:
        case S2_T_OpMultiplyAssign3:
          rc = s2_values_multdivmod( se, 1, lhsResolved, rhs, &rhs);
          break;
        case S2_T_OpDivideAssign:
        case S2_T_OpDivideAssign3:
          rc = s2_values_multdivmod( se, -1, lhsResolved, rhs, &rhs);
          break;
        case S2_T_OpModuloAssign:
        case S2_T_OpModuloAssign3:
          rc = s2_values_multdivmod( se, 0, lhsResolved, rhs, &rhs);
          break;
        default:
          rc = s2_values_bitwiseshift( se, op->id, lhsResolved, rhs, &rhs );
          break;
      }/* the combo-assignment operators */
      if(!rc){
        assert(rhs);
      }
    }
  }/* combo assignments */
  if(!rc){
    assert(lhs);
    assert(rhs);
    /* s2_dump_val(rhs,"rhs"); */
    /* s2_dump_val(lhs,"lhs"); */
    /* ref/unhand just in case the assignment would otherwise
       nuke one or both of them */
    if(1 || !gotOverload
       /* do not assign back over _overridden_
          assignments. e.g.

          obj += 3

          must not re-assign the result over obj, as doing so
          results in non-intuitive results.

          Turns out that breaks:

          var a = "a";
          a += "b";

          which we would expect to have the value 'ab'
       */){
      /* Reminder to self:

         holding/unhand'ing the lhs/self reference can lead to
         values not being cleaned up when overwritten.
         e.g.:

         for( var x = 0; x < 1000; ++x ){}

         If we ref/unhand lhs then each x which gets overwritten gets
         unhand()ed here, and thus not cleaned up until sweep-up.
       */
      /* cwal_value_ref(lhs); */
      /* cwal_value_ref(rhs); */
      /* if(self) cwal_value_ref(self); */
      rc = s2_set_v(se, self, lhs, rhs)
        /* Reminder: for full X#Y support this needs to be
           different. */
        ;
      /* cwal_value_unhand(lhs); */
      /* cwal_value_unhand(rhs); */
      /* if(self) cwal_value_unhand(self); */
    }
    if(!rc) *rv = rhs;
  }
  return rc;
}


int s2_op_f_incrdecr( s2_op const * op, s2_engine * se,
                      int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * self, * key;
  s2_stoken * vTok;
  int identType;
  int direction = 0;
  cwal_value * vResolved = 0, * vResult = 0;
  cwal_int_t addVal = 0;
  char gotOverLoad = 0;
  char resolvedIsContainer = 0;
  assert(1==op->arity);
  self = se->dotOpLhs;
  key = self ? se->dotOpKey : 0;
  se->dotOpLhs = se->dotOpKey = 0;
  se->dotOpId = 0;
  vTok = s2__pop_tok(se, 1);
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
    s2_stoken_free(se, vTok, 1);
    return 0;
  }
  if(!self) key = vTok->value;
  identType = vTok->ttype;
  s2_stoken_free(se, vTok, 1);
  vTok = 0;
  /*
    if (self) then we know (well, hope) this was a prefix op to
    X.Y.

    if (!self) and op->id==Incr/DecrPost, we need to check if the top-most op is a dot and
    op. If it is, this is a postfix op. self = s2__pop_val(se), then pop the top op.
  */

  /* s2_dump_val(key, "key"); */
  /* s2_dump_val(self, "self"); */

#if 0
  if(self &&
     (S2_T_OpIncrPre==op->id || S2_T_OpDecrPre==op->id)
     /* && cwal_value_is_string(self) */){
    /* Workaround for the parser's inability to know that ++X.Y needs
       to resolve X. It does indeed resolve it, but it then stores the
       identifier string because a prefix op followed by an identifier
       normally must do so. An alternative impl uses lookahead to
       figure out what to do, but that solution seems more fragile, so
       this is it for the time being. It means duplicated lookups for
       these ops, but i can live with that for now.
    */
    self = s2_var_get_v( se, -1, self);
  }
#endif
  switch(op->id){
    case S2_T_OpIncrPre: addVal = 1; direction = -1; break;
    case S2_T_OpDecrPre: addVal = -1; direction = -1; break;
    case S2_T_OpIncrPost: addVal = 1; direction = 1; break;
    case S2_T_OpDecrPost: addVal = -1; direction = 1; break;
    default:
      assert(!"invalid op mapping");
      s2_fatal(CWAL_RC_FATAL,"Invalid op mapping for incr/decr: %s",
               op->sym);
  }
  assert(direction==1 || direction==-1);
  assert(addVal==1 || addVal==-1);

  if(!self && (S2_T_Identifier != identType)){
    return s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                             "Invalid LHS (%s) for '%s' op. "
                             "Expecting identifier or object "
                             "property access.",
                             s2_ttype_cstr(identType),
                             op->sym);
  }
#if 0
  else if(!self && (S2_T_OpIncrPre==op->id || S2_T_OpDecrPre==op->id)
           && S2_T_Identifier==identType){
    /* Workaround for: ++obj; */
    /* key = s2_var_get_v( se, -1, key); */
    /* if(!key) goto cannot_resolve; */
  }
#endif
  else if(self && !cwal_props_can(self)){
    return s2_engine_err_set(se, CWAL_RC_TYPE,
                             "Invalid non-container type LHS (%s) "
                             "for '%s' op.",
                             cwal_value_type_name(self),
                             op->sym);
  }
  assert(self ? !!key : cwal_value_is_string(key)
         /* Expecting the parser to pass the string value of the
            identifier. */);
  if( (rc = s2_get_v( se, self, key, &vResolved )) ) return rc;
  else if(!vResolved){
    /* cannot_resolve: */
    if(!self){
      cwal_size_t idLen = 0;
      char const * sym = cwal_value_get_cstr(key, &idLen);
      assert(idLen && sym && "this _is_ an identifier, right?");
      return s2_engine_err_set(se, CWAL_RC_NOT_FOUND,
                               "'%s' operator cannot resolve "
                               "identifier '%.*s'",
                               op->sym, (int)idLen, sym );
    }else{
      /* Unknown _properties_ always resolve to undefined. */
      vResolved = cwal_value_undefined();
    }
  }
  /*
    The ref/unhanding here is needed in operator chaining, e.g. (a =
    a++) to keep the values from being destroyed before the pending
    LHS op(s) (yes, it happened via (a = ++a)). We do the key, too, so
    that it's safe whether or not string interning is fooling the
    reference count. Likewise, vResult could be an alias for any other
    value which might otherwise be nuked by the assignment.

    Don't forget that operator overloading might change behaviours
    here, causing the key/value/self references to do unpredictable
    things.
  */
  cwal_value_ref(key);
  cwal_value_ref(vResolved);
  resolvedIsContainer = cwal_props_can(vResolved);
  if(!resolvedIsContainer){
    /* Bypass overload checks for non-container types.

       Reminder: Buffers also go this route, but that's
       arguable.
    */
    rc = s2_values_addsub( se, addVal>0, vResolved,
                           cwal_new_integer(se->e, 1),
                           &vResult);
  }else{
    switch(op->id){
      case S2_T_OpIncrPre:
        if((gotOverLoad = s2_op_check_overload(op, se, 1, 0, vResolved,
                                               &vResult, &rc))
           || rc) break;
        else rc = s2_values_addsub( se, 1, 0, vResolved, &vResult );
        break;
      case S2_T_OpDecrPre:
        if((gotOverLoad = s2_op_check_overload(op, se, 1, 0, vResolved,
                                               &vResult, &rc))
           || rc) break;
        else rc = s2_values_addsub( se, 0, 0, vResolved, &vResult );
        break;
      case S2_T_OpIncrPost:
        if((gotOverLoad = s2_op_check_overload(op, se, 1, vResolved, 0,
                                               &vResult, &rc))
           || rc) break;
        else rc = s2_values_addsub( se, 1, vResolved, 0, &vResult );
        break;
      case S2_T_OpDecrPost:
        if((gotOverLoad = s2_op_check_overload(op, se, 1, vResolved, 0,
                                               &vResult, &rc))
           || rc) break;
        else rc = s2_values_addsub( se, 0, vResolved, 0, &vResult );
        break;
    }
  }
  while(!rc){
    assert(vResult);
#if 0
    s2_dump_val(self,"self");
    s2_dump_val(key,"key");
    s2_dump_val(vResolved,"vResolved");
    s2_dump_val(vResult,"vResult");
#endif
    if(!gotOverLoad && resolvedIsContainer){
      /* For sanity's sake. */
      assert(!"This should have been caught by s2_op_check_overload().");
      assert(!vResult);
      rc = s2_engine_err_set(se, CWAL_RC_TYPE,
                             "Container type '%s' has no '%s' operator.",
                             cwal_value_type_name(vResolved),
                             op->sym);
      break;
    }
    if(1 || !gotOverLoad /* don't assign back over _overridden_ ++/--
                            because the results are unintuitive and
                            normally quite useless.
                            
                            Turns out this breaks other stuff.
                         */
       /* && vResolved != vResult */){
      /* cwal_value_ref(vResult); */
      rc = s2_set_v(se, self, key, vResult);
      /* cwal_value_unhand(vResult); */
    }else if(gotOverLoad){
      /* s2_dump_val(vResolved,"NOT assigning due to overload"); */
      /* vResult = vResolved = self; */
      vResolved = vResult /* so *rv gets set right */;
    }
    if(!rc){
      switch(op->id){
        case S2_T_OpIncrPre:
        case S2_T_OpDecrPre:
          *rv = vResult ? vResult : cwal_value_undefined();
          break;
        case S2_T_OpIncrPost:
        case S2_T_OpDecrPost:
          *rv = vResolved ? vResolved : cwal_value_undefined();
          break;
      }
    }
    break;
  }
  cwal_value_unhand(vResolved);
  cwal_value_unhand(key);
  /*
    Reminder to self: our extra refs/unhand here cause for-loops
    to require extra sweepups:

    for( var x = 0; x < 2000; ++x);

    each assignment back over x doesn't get cleaned up immediately
    because of our unhand(). One can see this in s2sh s2.dumpMetrics()
    and the above loop. Compare the metrics of multiple runs
    against multiple runs of:

    for( var x = 0; x < 2000; x=x+1);

    which currently does not exhibit this behaviour because
    it doesn't have the same ref requirements.
  */
  return rc;
}

int s2_op_f_array_append( s2_op const * op, s2_engine * se,
                          int argc, cwal_value **rv ){
  int rc = 0;
  cwal_value * rhs, * lhs;
  s2_stoken * vTok;
  assert(2==argc);
  rhs = s2__pop_val(se);
  vTok = s2__pop_tok(se, 1);
  lhs = vTok->value;
  if(se->skipLevel>0){
    *rv = cwal_value_undefined();
  }else if(!cwal_value_is_array(lhs)){
    rc = s2_engine_err_set(se, CWAL_SCR_SYNTAX,
                           "Invalid (non-array) LHS (%s) for '%s' op.",
                           s2_ttype_cstr(vTok->ttype),
                           op->sym);
  }else{
    cwal_array * ar = cwal_value_get_array(lhs);
    assert(lhs);
    assert(ar);
    /* s2_dump_val(rhs,"rhs"); */
    /* s2_dump_val(lhs,"lhs"); */
    rc = cwal_array_append(ar, rhs);
    if(!rc) *rv = rhs;
  }
  s2_stoken_free(se, vTok, 1);
  return rc;
}


int s2_op_f_foo( s2_op const * op, s2_engine * se, int argc,
                 cwal_value **rv ){
  MARKER(("FOO operator: argc=%d%s\n", argc, argc ? " Args stack:" : "" ));
  for( ; argc>0; --argc){
    cwal_value * v = s2__pop_val(se);
    assert(v);
    MARKER(("Arg type: %s\n", cwal_value_type_name(v)));
  }
  *rv = cwal_value_true();
  return 0;
}

int s2_values_addsub( s2_engine * se, char doAdd, cwal_value * lhs,
                       cwal_value * rhs, cwal_value **rv ){
#if 0
  /* Currently done at the operator level, but we might want to move that into
     these helper routines so that C code can use overloads. */
  switch(cwal_value_type_id(lhs ? lhs : rhs)){
    case CWAL_TYPE_UNDEF:
    case CWAL_TYPE_NULL:
    case CWAL_TYPE_BOOL:
    case CWAL_TYPE_INTEGER:
    case CWAL_TYPE_DOUBLE:
      break;
    case CWAL_TYPE_STRING:
      if(!lhs) break /* Disable overloading of unary prefix +/- on strings,
                        for sanity's sake. */;
      /* Else fall through because string+... is implemented via
         an overloaded operator+. */
    default:{
      static cwal_size_t opLen = 9;
      char const * opStr = doAdd
        ? ((lhs&&rhs) ? "operator+" : (opLen=10, "operator++"))
        : ((lhs&&rhs) ? "operator-" : (opLen=10, "operator--"));
      int rc = 0;
      char gotProxy;
      MARKER(("op=%.*s\n", (int)opLen, opStr));
      if(!gotOverLoader) gotOverLoader = &gotProxy;
      /* Check for operator overloads... */
      /* s2_dump_val(lhs, opStr); */
      /* s2_dump_val(rhs, opStr); */
      if((*gotOverLoader = s2_value_op_proxy( se, opStr, opLen,
                                              lhs, rhs,
                                              rv, &rc ))
         || rc) return rc;
      /* else fall through... */
      /* s2_dump_val(lhs, "op not overloaded"); */
      /* break; */
      return s2_engine_err_set(se, CWAL_RC_TYPE,
                               "Value (type=%s) has no '%s' operator.",
                               cwal_value_type_name(lhs ? lhs : rhs),
                               opStr);
    }
  }
#endif
  if(!lhs){
    /* Unary prefix op */
    if(cwal_value_is_string(rhs)/*unfortunate special case*/){
      cwal_size_t slen = 0;
      char const * cstr = cwal_value_get_cstr(rhs, &slen);
      if(!slen) *rv = cwal_new_integer(se->e, 0);
      else{
        cwal_int_t inty = 0;
        if(s2_cstr_parse_int(cstr, (cwal_int_t)slen, &inty)){
          *rv = cwal_new_integer(se->e, doAdd ? inty : -inty);
        }else{
          cwal_double_t dbl = 0.0;
          if(s2_cstr_parse_double(cstr, (cwal_int_t)slen, &dbl)){
            *rv = cwal_new_double(se->e, doAdd ? dbl : -dbl);
          }else{
            *rv = cwal_new_integer(se->e, 0);
          }
        }
      }
    }else if(!cwal_value_is_double(rhs)){
      /* Special case for integer math on integers >48 bits.
         This is not a complete solution.*/
      cwal_int_t const iR = cwal_value_get_integer(rhs);
      *rv = iR
        ? (doAdd
           ? (cwal_value_is_integer(rhs)
              ? rhs /* already an int, make this a no-op. */
              : cwal_new_integer(se->e, iR) /* coerce to an int */
              )
           : cwal_new_integer(se->e, -iR))
        : cwal_new_integer(se->e, 0)
        ;
    }else{
      const cwal_double_t iR = cwal_value_get_double( rhs );
      *rv = doAdd
        ? (cwal_value_is_double(rhs)
           ? rhs
           : cwal_new_double(se->e, iR))
        : cwal_new_double(se->e, -iR);
    }
  }else{
    /* Binary op */
    /*
      Using double for all map here breaks for large integers (>48(?)
      bits). We need to first determine whether the LHS is an int, and
      use the int type for that, as that can hold larger numbers.
    */
    /* Reminder: ("1"+n) is caught via string op overload. */
    if(!cwal_value_is_double(lhs)){
      cwal_int_t const iL = cwal_value_get_integer(lhs);
      cwal_int_t const iR = cwal_value_get_integer(rhs);
      *rv = cwal_new_integer(se->e, doAdd ? (iL+iR) : (iL-iR));
    }else{
      cwal_double_t const iL = cwal_value_get_double( lhs );
      /* FIXME: check if RHS is an integer, and do no to-double
         conversion if it is. Needed for sane(?)  behaviour of
         integers larger than the integer part of a double
      */
      cwal_double_t const iR = cwal_value_get_double( rhs );
      cwal_double_t const res = doAdd ? (iL+iR) : (iL-iR);
      *rv = cwal_new_double(se->e, res);
    }
  }
  return *rv ? 0 : CWAL_RC_OOM;
}

int s2_values_multdivmod( s2_engine * se, int mode,
                          cwal_value * lhs, cwal_value * rhs,
                          cwal_value **rv ){
#if 0
  /* Currently done at the operator level */
  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:{
      int rc = 0;
      /* Check for operator overloads... */
      if(s2_value_op_proxy(se, buf, 9, lhs, rhs,
                           rv, &rc)
         || rc) return rc;
      /* else fall through... */
    }
  }
#endif
  if(mode<0){ /* Division */
    if(!cwal_value_is_double(lhs)){
      /* Integer math */
      cwal_int_t const iL = cwal_value_get_integer(lhs);
      /* We need to special-case the RHS in case it is an integer with more
         than 48 bits. */
      if(!cwal_value_is_double(rhs)){
        cwal_int_t const iR = cwal_value_get_integer( rhs );
        if(0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO,
                                           "Divide by 0.");
        *rv = cwal_new_integer(se->e, iL / iR);
      }else{
        cwal_double_t const iR = cwal_value_get_double( rhs );
        if(0.0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO,
                                             "Divide by 0.0.");
        *rv = cwal_new_integer(se->e, (cwal_int_t)(iL / iR));
      }
    }else{
      cwal_double_t const iL = cwal_value_get_double( lhs );
      if(!cwal_value_is_double(rhs)){
        cwal_int_t const iR = cwal_value_get_integer( rhs );
        if(0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO,
                                           "Divide by 0.");
        *rv = cwal_new_double(se->e, iL / iR);
      }else{
        cwal_double_t const iR = cwal_value_get_double( rhs );
        if(0.0==iR) return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO,
                                             "Divide by 0.0.");
        *rv = cwal_new_double(se->e, iL / iR);
      }
    }
  }else if(0==mode){
    /* Modulo */
    cwal_int_t const iR = cwal_value_get_integer( rhs );
    if(0==iR){
      return s2_engine_err_set(se, CWAL_SCR_DIV_BY_ZERO,
                               "Modulo by 0.");
    }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
         values (-1,0,1).
      */
      *rv = ((1==iL) && (iR!=1))
        ? (cwal_value_is_integer(lhs)
           ? lhs
           : cwal_new_integer(se->e, iL))
        : cwal_new_integer(se->e, (cwal_int_t)(iL % iR));
    }
  }else{
    /* Multiplication */
    if(!cwal_value_is_double(lhs)){
      /* Integer math */
      cwal_int_t const iL = cwal_value_get_integer(lhs);
      if(!cwal_value_is_double(rhs)){
        cwal_int_t const iR = cwal_value_get_integer( rhs );
        *rv = cwal_new_integer(se->e, iL * iR);
      }else{
        cwal_double_t const iR = cwal_value_get_double( rhs );
        *rv = cwal_new_integer(se->e, (cwal_int_t)(iL * iR));
      }
    }else{
      cwal_double_t const iL = cwal_value_get_double( lhs );
      if(!cwal_value_is_double(rhs)){
        cwal_int_t const iR = cwal_value_get_integer( rhs );
        *rv = cwal_new_double(se->e, iL * iR);
      }else{
        cwal_double_t const iR = cwal_value_get_double( rhs );
        *rv = cwal_new_double(se->e, iL * iR);
      }
    }
  }
  return *rv ? 0 : CWAL_RC_OOM;
}


int s2_values_bitwiseshift( s2_engine * se, int op, cwal_value * vlhs,
                            cwal_value * vrhs, cwal_value ** rv ){
  cwal_int_t lhs = 0, rhs = 0, res = 0;
  lhs = vlhs ? cwal_value_get_integer(vlhs) : 0;
  rhs = cwal_value_get_integer(vrhs);
  switch( op ){
    case S2_T_OpNegateBitwise:{
      assert(!vlhs);
      res = ~rhs;
      break;
    }
    case S2_T_OpShiftLeft:
    case S2_T_OpShiftLeftAssign:
    case S2_T_OpShiftLeftAssign3:
      res = lhs << rhs; break;
    case S2_T_OpShiftRight:
    case S2_T_OpShiftRightAssign:
    case S2_T_OpShiftRightAssign3:
      res = lhs >> rhs; break;
    case S2_T_OpOrBitwise:
    case S2_T_OpOrAssign:
    case S2_T_OpOrAssign3:
      res = lhs | rhs; break;
    case S2_T_OpAndBitwise:
    case S2_T_OpAndAssign:
    case S2_T_OpAndAssign3:
      res = lhs & rhs; break;
    case S2_T_OpXOr:
    case S2_T_OpXOrAssign:
    case S2_T_OpXOrAssign3:
      res = lhs ^ rhs; break;
    default:
      return s2_engine_err_set(se, CWAL_RC_RANGE,
                               "Invalid operator for "
                               "s2_values_bitwiseshift(): %s",
                               s2_ttype_cstr(op));
  }
  /*
    Optimizations: re-use the LHS or RHS if the result equals one of
    them...
  */
  if(res==rhs && cwal_value_is_integer(vrhs)) *rv = vrhs;
  else if(vlhs && (res==lhs) && cwal_value_is_integer(vlhs)) *rv = vlhs;
  else *rv = cwal_new_integer(se->e, res);
  return *rv ? 0 : CWAL_RC_OOM;
}

#undef MARKER
#undef s2__pop_val
#undef s2__pop_tok
/* end of file s2_ops.c */
/* start of file s2_pf.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#ifdef S2_OS_UNIX
/* for stat(2) */
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#endif

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

struct s2_pf {
  s2_engine * se;
  cwal_value * self;
  cwal_buffer buf;
    /* char (*predicate)( char const * fn ); */
};
static const s2_pf s2_pf_empty = {0,0,cwal_buffer_empty_m};

#define ARGS_SE s2_engine * se = s2_engine_from_args(args); \
    assert(se)

static void s2_pf_finalizer(cwal_engine * e, void * m){
  s2_pf * pf = (s2_pf*)m;
  cwal_buffer_reserve(e, &pf->buf, 0);
  cwal_free( e, m );
}

s2_pf * s2_pf_new(s2_engine * se){
  s2_pf * pf;
  cwal_value * vSelf;
  if(!se || !se->e) return NULL;
  pf = (s2_pf*)cwal_malloc(se->e, sizeof(s2_pf));
  if(!pf) return NULL;
  *pf = s2_pf_empty;
  vSelf = cwal_new_native_value(se->e, pf, s2_pf_finalizer,
                                &s2_pf_empty);
  if(!vSelf){
    s2_pf_finalizer(se->e, pf);
    pf = NULL;
  }else{
    pf->self = vSelf;
    pf->se = se;
    assert(cwal_props_can(vSelf));
#if 0
    s2_pf_dirs(pf);
    s2_pf_exts(pf);
    assert(cwal_prop_get(vSelf,"suffix",6));
    assert(cwal_prop_get(vSelf,"prefix",6));
#endif
    cwal_value_prototype_set( vSelf, s2_prototype_pf(se) );
  }
  return pf;
}

int s2_install_pf( s2_engine * se, cwal_value * ns ){
  return cwal_props_can(ns)
    ?  cwal_prop_set(ns, "PathFinder", 10, s2_prototype_pf(se))
    : CWAL_RC_MISUSE;
}

cwal_array * s2_pf_exts(s2_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->se->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 * s2_pf_dirs(s2_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->se->e);
    if(ar && (0!=cwal_prop_set(pf->self, "prefix", 6, ar))){
      cwal_value_unref(ar);
      ar = NULL;
    }
  }
  return cwal_value_get_array(ar);
}

int s2_pf_dirs_set( s2_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 s2_pf_exts_set( s2_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 s2_pf_dir_add_v( s2_pf * pf, cwal_value * v ){
  if(!pf || !pf->self || !v) return CWAL_RC_MISUSE;
  else{
    cwal_array * ar = s2_pf_dirs(pf);
    return ar
      ? cwal_array_append(ar, v)
      : CWAL_RC_OOM;
  }
}

int s2_pf_dir_add( s2_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->se->e,
                                           dirLen ? dir : NULL,
                                           dirLen);
    if(!v) rc = CWAL_RC_OOM;
    else {
      rc = s2_pf_dir_add_v( pf, v);
      if(rc) cwal_value_unref(v);
    }
    return rc;
  }
}

int s2_pf_ext_add_v( s2_pf * pf, cwal_value * v ){
  if(!pf || !pf->self || !v) return CWAL_RC_MISUSE;
  else{
    cwal_array * ar = s2_pf_exts(pf);
    return ar
      ? cwal_array_append(ar, v)
      : CWAL_RC_OOM;
  }
}

int s2_pf_ext_add( s2_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->se->e,
                                           dirLen ? dir : NULL,
                                           dirLen);
    if(!v) rc = CWAL_RC_OOM;
    else {
      rc = s2_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
   s2_pf part of v or one of its prototypes.
*/
static s2_pf * s2_value_pf_part(cwal_value *v){
  cwal_native * n;
  s2_pf * pf = NULL;
  while(v){
    n = cwal_value_get_native(v);
    if(n){
      pf = (s2_pf *)cwal_native_get(n, &s2_pf_empty);
      if(pf) break;
    }
    v = cwal_value_prototype_get(NULL,v);
  }
  return pf;
}

#define THIS_PF \
  s2_pf * pf = s2_value_pf_part(args->self);                        \
  if(!pf) return                                                    \
            cwal_exception_setf(args->engine, CWAL_RC_TYPE,          \
                                "'this' is not a PathFinder instance.")

static int s2_cb_pf_add( cwal_callback_args const * args, cwal_value **rv,
                         cwal_array * dest ){
  THIS_PF;
  if(!args->argc){
    return cwal_exception_setf(args->engine, 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 s2_cb_pf_dir_add( cwal_callback_args const * args, cwal_value **rv ){
  THIS_PF;
  return s2_cb_pf_add( args, rv, s2_pf_dirs(pf) );
}

static int s2_cb_pf_ext_add( cwal_callback_args const * args, cwal_value **rv ){
  THIS_PF;
  return s2_cb_pf_add( args, rv, s2_pf_exts(pf) );
}


static void s2_pf_separator( s2_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;
}

/* a very incomplete impl - only works on Unix */
static char s2_pf_is_dir( char const * name ){
#ifdef S2_OS_UNIX
  struct stat buf;
  return stat(name, &buf) ? 0 : (S_ISDIR(buf.st_mode) ? 1 : 0);
#else
  /*
    MISSING IMPL for Windows. Potentially requires re-encoding
    string bytes, since we cannot know whether the ones we
    are given came from a Windows console (in which case they might
    have some Code Page encoding). Interested readers are referred
    to the fossil(1) and fossil(3) source trees, which jump through
    several hoops in that regard.
   */
  return 0;
#endif
}

char const * s2_pf_search( s2_pf * pf, char const * base,
                           cwal_size_t baseLen, cwal_size_t * rcLen,
                           int directoryPolicy){
  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;
  buf = &pf->buf;
  buf->used = 0;
  e = pf->se->e;

#define CHECK_FILE(NAME) (s2_file_is_accessible(NAME, 0) &&             \
                          ((directoryPolicy < 0 \
                            ? s2_pf_is_dir(NAME) \
                            : (directoryPolicy > 0 \
                               ? 1 \
                               : !s2_pf_is_dir(NAME)))))
  if(CHECK_FILE(base)){
    rc = cwal_buffer_append(e, buf, base, baseLen);
    if(rc) return NULL;
    goto gotone;
  }

  s2_pf_separator(pf, &pathSep, &sepLen);
  assert(pathSep);
  assert(sepLen);
  ad = s2_pf_dirs(pf);
  ax = s2_pf_exts(pf);
  nD = cwal_array_length_get(ad);
  nX = cwal_array_length_get(ax);
  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 = s2_value_to_buffer(e, buf, vD);
      if(rc) break;
      else if(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(CHECK_FILE( (char const *)buf->mem )){
      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 = s2_value_to_buffer(e, buf, vX);
        if(rc) break;
      }
      assert(buf->used < buf->capacity);
      buf->mem[buf->used] = 0;
      if(CHECK_FILE((char const *)buf->mem)){
        goto gotone;
      }
    }
  }
#undef CHECK_FILE
  return NULL;
  gotone:
  if(rcLen) *rcLen = buf->used;
  return (char const *)buf->mem;
}

static int s2_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;
  int directoryPolicy = 0;
  THIS_PF;
  if(!args->argc) goto misuse;
  base = cwal_value_get_cstr(args->argv[0], &baseLen);
  if(!base || !baseLen) goto misuse;
  else if(args->argc>1){
    /* Second arg: bool|integer directoryPolicy */
    cwal_value const * a1 = args->argv[1];
    if(cwal_value_is_bool(a1)){
      directoryPolicy = cwal_value_get_bool(a1) ? 1 : 0;
    }else{
      cwal_int_t const n = cwal_value_get_integer(a1);
      directoryPolicy = (int)(n>0 ? 1 : n<0 ? -1 : 0);
    }
  }
  rc = s2_pf_search( pf, base, baseLen, &rcLen, directoryPolicy );
  if(!rc) *rv = cwal_value_undefined();
  else {
    *rv = cwal_new_string_value(args->engine, rc, rcLen);
  }
  return *rv ? 0 : CWAL_RC_OOM;
  misuse:
  return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                             "Expecting a non-empty string argument.");
}

int s2_cb_pf_new( cwal_callback_args const * args, cwal_value **rv ){
  /* Constructor... */
  int rc = 0;
  s2_pf * pf;
  ARGS_SE;
  pf = s2_pf_new(se);
  if(!pf) return CWAL_RC_OOM;
  else if(args->argc){
    cwal_array * ar = cwal_value_get_array(args->argv[0]);
    if(ar){
      rc = s2_pf_dirs_set(pf, ar);
      ar = (!rc && (args->argc > 1))
        ? cwal_value_get_array(args->argv[1])
        : NULL;
      if(ar) rc = s2_pf_exts_set(pf, ar);
    }
  }
  *rv = pf->self;
  return rc;
}

cwal_value * s2_prototype_pf( s2_engine * se ){
  int rc = 0;
  cwal_value * v;
  cwal_value * proto;
  char const * pKey = "class.PathFinder";
  assert(se && se->e);
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, 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, cwal_strlen(NAME), v );          \
  if(rc) goto end
#define FUNC2(NAME,FP)              \
  v = s2_new_function2( se, FP );   \
    SET(NAME)

  v = cwal_new_string_value(se->e, "PathFinder", 10);
  SET("__typename");
  FUNC2("new", s2_cb_pf_new);
  FUNC2("addDir", s2_cb_pf_dir_add);
  FUNC2("addExt", s2_cb_pf_ext_add);
  FUNC2("search", s2_cb_pf_search);
  FUNC2("fileIsAccessible", s2_cb_file_accessible);
#undef SET
#undef FUNC2
#undef CHECKV

    end:
    return rc ? NULL : proto;
}

#undef ARGS_SE
#undef THIS_PF
#undef MARKER
/* end of file s2_pf.c */
/* start of file s2_protos.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>

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

static char const * s2_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;
}

cwal_value * s2_prototype_stashed( s2_engine * se, char const * typeName ){
  enum { BufLen = 128 };
  char buf[BufLen];
  cwal_size_t const slen = cwal_strlen(typeName);
  assert(slen && (slen < BufLen - 11));
  sprintf(buf, "prototype.%s", typeName);
  return s2_stash_get( se, buf );
}

int s2_prototype_stash( s2_engine * se,
                        char const * typeName,
                        cwal_value * proto ){
  enum { BufLen = 128 };
  char buf[BufLen];
  cwal_size_t const slen = cwal_strlen(typeName);
  assert(slen && (slen < BufLen - 11));
  sprintf(buf, "prototype.%s", typeName);
  return s2_stash_set( se, buf, proto );
}

#define ARGS_SE s2_engine * se = s2_args_state(args); \
    assert(se)

static int s2_cb_prop_has_own( cwal_callback_args const * args,
                               cwal_value **rv ){
  if(!args->argc)
    return s2_cb_throw(args, 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 s2_cb_prop_clear( cwal_callback_args const * args, cwal_value **rv ){
    int rc;
    if( (rc = cwal_props_clear( args->self )) ){
      rc = s2_cb_throw(args, rc, "Clearing properties failed "
                       "with code %s.", s2_rc_cstr(rc));
    }
    if(!rc) *rv = args->self;
    return rc;
}

int s2_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 keys for all key/value pairs for
   obj.

*/
static int s2_cb_prop_keys( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_array * ar;
  if(!cwal_props_can(args->self)){
    return s2_cb_throw(args, CWAL_RC_TYPE,
                       "This value (of type %s) cannot hold properties.",
                       cwal_value_type_name(args->self));
  }
  ar = cwal_new_array(args->engine);
  if(!ar) return CWAL_RC_OOM;
  rc = cwal_props_visit_keys( args->self,
                              s2_value_visit_append_to_array, ar );
  if(!rc) *rv = cwal_array_value(ar);
  else {
    cwal_array_unref(ar);
  }
  return rc;
}


/**
   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 s2_cb_prop_unset( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  uint16_t i = 0;
  int b = -1;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  if(!args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "'unset' requires (INDEX|KEY,...) arguments.");
  }
  for( ; !rc && (i < args->argc); ++i ){
    rc = s2_set_v( se, args->self, args->argv[i], 0 );
    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]
*/
static int s2_cb_prop_get( cwal_callback_args const * args,
                           cwal_value **rv ){
  ARGS_SE;
  if(!args->argc){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "'get' requires (INDEX|KEY) argument.");
  }else{
    /* Reminder: we use s2_get_v() instead of cwal_prop_get_v()
       for the prototype pseudo-property and integer array
       index support.
    */
    int const rc = s2_get_v(se, args->self, args->argv[0], rv);
    if(!rc && !*rv) *rv = cwal_value_undefined();
    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 s2_cb_prop_set( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  ARGS_SE;
  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 = s2_set_v( se, args->self,
                   args->argv[0], args->argv[1] );
    if(!rc) *rv = args->argv[1];
    return rc;
  }
  /* Else fall through */
  misuse:
  assert(rc);
  return s2_engine_err_set(se, rc,
                           "'set' requires (INDEX, VALUE) "
                           "or (OBJECT) arguments.");
}

static int s2_cb_props_copy_to( cwal_callback_args const * args,
                                cwal_value **rv ){
  if(!args->argc){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "Expecting one or more "
                               "container-type arguments.");
  }else if(!cwal_props_can(args->self)){
    return cwal_exception_setf(args->engine, CWAL_RC_TYPE,
                               "'this' is not a container type.");
  }else{
    int rc = 0;
    uint16_t i = 0;
    for( ; !rc && i < args->argc; ++i ){
      cwal_value * arg = args->argv[i];
      if(!arg/*can only happen via from-C calls*/
         || !cwal_props_can(arg)){
        rc = cwal_exception_setf(args->engine, CWAL_RC_TYPE,
                                 "Argument #%d is of type (%s), "
                                 "but we require a container type.",
                                 (int)i+1,
                                 arg ? cwal_value_type_name(arg) : "<NULL>");
      }else{
        rc = cwal_props_copy(args->self, arg);
        if(!rc) *rv = arg;
      }
    }
    return rc;
  }
}


static int s2_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;
}

static int s2_cb_container_is_empty( cwal_callback_args const * args,
                                     cwal_value **rv ){
  *rv = cwal_props_has_any(args->self)
    ? cwal_value_false()
    : cwal_value_true();
  return 0;
}

static int s2_cb_props_count( cwal_callback_args const * args,
                                     cwal_value **rv ){
  return (*rv = cwal_new_integer(args->engine,
                                 (cwal_int_t)cwal_props_count(args->self)))
    ? 0 : CWAL_RC_OOM;
}

static int s2_cb_props_sort( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  rc = cwal_props_sort(args->self);
  if(rc){
    switch(rc){
      case CWAL_RC_CYCLES_DETECTED:
        rc = cwal_exception_setf(args->engine, rc,
                                 "Cannot sort properties "
                                 "while object is being iterated over.");
        break;
    }
  }else{
    *rv = args->self;
  }
  return rc;
}


/**
   Internal cwal_kvp_visitor_f() implementation which requires state
   to be a full-populated (s2_kvp_each_state*).
*/
int s2_kvp_visitor_prop_each( cwal_kvp const * kvp, void * state_ ){
  cwal_value * av[2] = {NULL,NULL} /* (key, value) arguments for callback */;
  s2_kvp_each_state * state = (s2_kvp_each_state*)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);
  assert(cwal_props_can(state->self));
  cwal_engine_sweep(state->e);
#if 0
  s2_dump_val(av[0],"visiting key");
  s2_dump_val(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 = S2_RC_END_EACH_ITERATION;
  }
  return rc;
}

static int s2_cb_prop_each( cwal_callback_args const * args, cwal_value **rv ){
  cwal_function * f;
  f = args->argc
    ? cwal_value_function_part(args->engine, args->argv[0])
    /* cwal_value_get_function(args->argv[0]) */
    : NULL;
  if(!f){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "'eachProperty' expects a Function argument, "
                       "but got a %s.",
                       args->argc
                       ? cwal_value_type_name(args->argv[0])
                       : "<NULL>");
  }else{
    s2_kvp_each_state state;
    int rc;
    state.e = args->engine;
    state.callback = f;
    state.self = args->self /*better option???*/;
    rc = cwal_props_visit_kvp( args->self,
                               s2_kvp_visitor_prop_each,
                               &state );
    if(S2_RC_END_EACH_ITERATION==rc) rc = 0;
    if(!rc) *rv = args->self;
    return rc;
  }
}


cwal_value * s2_prototype_object( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_OBJECT );
    if(proto) return proto;
    cwal_engine_type_name_proxy( se->e, s2_type_name_proxy );
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    assert(!cwal_value_prototype_get(se->e, proto));
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_OBJECT, proto );
    if(!rc) rc = s2_prototype_stash(se, "object", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_OBJECT));

    /* MARKER(("Setting up OBJECT prototype.\n")); */
    /*
      Chicken/egg: in order to ensure that, e.g., prototypes get added
      to Object methods, we require the Function prototype to be in
      place before those functions are installed. This applies to all
      Container-level prototypes, but not to PODs because those are
      looked up on demand, as opposed to being set when the value is
      created, because non-Containers don't have a place to store their
      prototype.

      So we'll init the other prototypes right after we set the base
      Object prototype (which is used, indirectly, by all other
      types).
     */
    rc = CWAL_RC_OOM;
#define PROTO(F) if(!(v = F)) goto end
    PROTO(s2_prototype_function(se));
    PROTO(s2_prototype_array(se));
    PROTO(s2_prototype_exception(se));
    PROTO(s2_prototype_hash(se));
    PROTO(s2_prototype_buffer(se));
    PROTO(s2_prototype_string(se));
    PROTO(s2_prototype_integer(se));
    PROTO(s2_prototype_double(se));
    rc = 0;
#undef PROTO


#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#if 0
    v = cwal_new_string_value(se->e, "Objekt", 6);
    CHECKV;
    rc = cwal_prop_set( proto, "__typename", 10, v );
    RC;
#endif

#define FUNC2(NAME,FP)                              \
    v = s2_new_function2( se, FP );                 \
    CHECKV;                                                    \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("clearProperties", s2_cb_prop_clear);
    FUNC2("copyPropertiesTo", s2_cb_props_copy_to);
    FUNC2("compare", s2_cb_value_compare);
    FUNC2("eachProperty", s2_cb_prop_each);
    FUNC2("get", s2_cb_prop_get);
    FUNC2("hasOwnProperty", s2_cb_prop_has_own);
    FUNC2("mayIterate", s2_cb_value_may_iterate);
    FUNC2("isEmpty", s2_cb_container_is_empty);
    FUNC2("propertyKeys", s2_cb_prop_keys);
    FUNC2("propertyCount", s2_cb_props_count);
    FUNC2("set", s2_cb_prop_set);
    FUNC2("sortProperties", s2_cb_props_sort);
    FUNC2("toJSONString", s2_cb_this_to_json_token);
    FUNC2("toString", s2_cb_value_to_string);
    FUNC2("unset", s2_cb_prop_unset);

#undef FUNC2
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}

static int s2_cb_exception_code_string( cwal_callback_args const * args,
                                        cwal_value **rv ){
  int rc = 0;
  cwal_value * v;
  v = cwal_prop_get(args->self, "code", 4);
  if(v){
    cwal_int_t const code = cwal_value_get_integer(v);
    char const * str = s2_rc_cstr(code);
    *rv = str
      ? cwal_new_xstring_value(args->engine, str,
                               cwal_strlen(str))
      /* Reminder to self: cwal recycles that xstring shell
         to those static bytes, so this doesn't actually
         allocate anything so long as we've got an x/z-string
         shell in the recycler bin :-D.
      */
      : cwal_value_null();
    if(str && !*rv) rc = CWAL_RC_OOM;
  }else{
    *rv = cwal_value_undefined();
  }
  return rc;
}

cwal_value * s2_prototype_exception( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_EXCEPTION );
    if(proto
       || !s2_prototype_object(se) /* timing hack */
       ) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_EXCEPTION, proto );
    if(!rc) rc = s2_prototype_stash(se, "Exception", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_EXCEPTION));
    /* MARKER(("Setting up EXCEPTION prototype.\n")); */

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#define FUNC2(NAME,FP)                              \
    v = s2_new_function2( se, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("codeString", s2_cb_exception_code_string);

#undef FUNC2
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}


#undef MARKER
#undef ARGS_SE
/* end of file s2_protos.c */
/* start of file s2_str.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

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

#define ARGS_SE s2_engine * se = s2_args_state(args)

#define THIS_STRING                                                    \
  cwal_string * self; ARGS_SE;                                         \
  if(!se) return CWAL_RC_MISUSE;                                        \
  if(!(self=cwal_value_string_part(args->engine, args->self)) && args->argc) \
    self = cwal_value_string_part(args->engine, args->argv[0]);         \
  if(!self){                                                            \
    return cwal_exception_setf( args->engine, CWAL_RC_TYPE,             \
                                "Expecting a string 'this' or first argument." ); \
  } (void)0


#define THIS_BUFFER                                             \
  cwal_buffer * self; ARGS_SE;                                  \
  if(!se) return CWAL_RC_MISUSE;                                \
  else if(!(self=cwal_value_get_buffer(args->self))){         \
    return cwal_exception_setf( args->engine, CWAL_RC_TYPE,      \
                                "'this' is-not-a Buffer." );     \
  } (void)0


static int s2_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 least one argument.");
  }else{
    int rc = 0;
    cwal_size_t i = 0, argc = args->argc;
    cwal_buffer buf = cwal_buffer_empty;
    s2_engine * se = s2_args_state(args);
    assert(se);
    if(cwal_value_is_string(args->self)){
      rc = s2_value_to_buffer( se->e, &buf, args->self );
    }
    for( ; !rc && (i < argc); ++i ){
      rc = s2_value_to_buffer( se->e, &buf, args->argv[i] );
    }
    if(!rc && !(*rv = cwal_string_value(cwal_buffer_to_zstring(args->engine,
                                                               &buf)))
       ){
      rc = CWAL_RC_OOM;
    }
    cwal_buffer_reserve( args->engine, &buf, 0 );
    return rc;
  }
}


/**
   Impl of (STRING+VALUE) operator.

   Assumes binary call form with the string on the LHS.
*/
static int s2_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;
    assert(2==args->argc);
    for( ; !rc && (i < argc); ++i ){
      /* s2_dump_val( args->argv[i], "arg"); */
      rc = s2_value_to_buffer( args->engine, &buf, args->argv[i] );
    }
    if(!rc){
      *rv = cwal_string_value(cwal_buffer_to_zstring(args->engine, &buf));
      if(!*rv) rc = CWAL_RC_OOM;
      else{
        assert(!buf.mem);
        assert(!buf.capacity);
      }
    }
    cwal_buffer_reserve( args->engine, &buf, 0 );
    return rc;
  }
}

#if 0
/* TODO? Move s2 op-level support for unary +/-STRING
   to here?
*/

/**
   Impl of +STRING and -STRING operators.

   Assumes unary call form with the string on the RHS.
*/
static int s2_cb_str_op_unarypm( int mode /*<0==minus, >0==plus*/,
                                 cwal_callback_args const * args,
                                 cwal_value **rv ){
}
#endif


static int s2_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 cwal_exception_setf( args->engine, 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 s2_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 s2_throw( se, 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 && at<end; ++i ){
        cwal_utf8_read_char( at, end, &at );
      }
      if(at>=end){
        *rv = cwal_value_undefined();
      }else{
        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 s2_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;
    cwal_buffer buf = cwal_buffer_empty;
    if(!len){
      *rv = cwal_string_value(self);
      return 0;
    }
    rc = cwal_buffer_reserve( se->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_string_value(cwal_buffer_to_zstring(se->e, &buf));
    if(!*rv) rc = CWAL_RC_OOM;
    else{
      assert(!buf.mem && "taken over by z-string");
    }
    cwal_buffer_reserve(se->e, &buf, 0);
    return rc;
  }
}

static int s2_cb_str_tolower( cwal_callback_args const * args, cwal_value **rv ){
    return s2_cb_str_toupperlower( args, rv, 0 );
}
static int s2_cb_str_toupper( cwal_callback_args const * args, cwal_value **rv ){
    return s2_cb_str_toupperlower( args, rv, 1 );
}


static int s2_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 s2_throw( se, 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;
    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(limit>0 && ++count==limit){
            pos = eof;
            last = 1;
          }
          else 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;
  }
}


/**
   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 s2_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 s2_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 s2_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 s2_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;
  }
}


/**
   Impl for trim/trimLeft/trimRight(). mode determines which:

   <0 == left
   0 == both
   >0 == right
*/
static int s2_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 && s2_is_space((int)*cs); ++cs){}
    if(mode>=0){
      for( --end; (end>cs) && s2_is_space((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 s2_cb_str_trim_left( cwal_callback_args const * args,
                                    cwal_value **rv ){
    return s2_cb_str_trim_impl( args, rv, -1 );
}
static int s2_cb_str_trim_right( cwal_callback_args const * args,
                                    cwal_value **rv ){
    return s2_cb_str_trim_impl( args, rv, 1 );
}

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

static int s2_cb_str_to_string( cwal_callback_args const * args, cwal_value **rv ){
  cwal_string * str;
  ARGS_SE;
  str = cwal_value_string_part( args->engine, args->self );
  if(!str){
    return s2_throw(se, CWAL_RC_TYPE,
                    "FIXME: check for a different toString() impl in this case.");
  }else{
    *rv = cwal_string_value(str);
    return 0;
  }
}

static int s2_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,
                               "'unescape' 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;
    s2_engine * se = s2_args_state(args);
    cwal_buffer * buf = &se->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 s2_throw(se, 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 = s2_unescape_string(args->engine, (char const *)begin, (char const *)begin + sLen, buf );
    assert(buf->used >= oldUsed);
    if(rc){
      return s2_throw(se, 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;
  }
}


/**
   Internal helper for Buffer.appendf() and String.applyFormat()
*/
static int s2_helper_appendf( s2_engine * se, 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( se->e, buf, fmt, fmtLen, argc, argv);
  if(rc && (CWAL_RC_OOM != rc)){
    if(buf->used > oldUsed){
      /* Error string is embedded in the buffer... */
      rc = s2_throw(se, rc, "%s",
                    (char const *)(buf->mem + oldUsed));
      buf->used = oldUsed;
    }else{
      rc = s2_throw(se, rc, "String formatting failed with code: %s",
                    cwal_rc_cstr(rc));
    }
  }
  return rc;
}

static int s2_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 = &se->buffer;
  oldUsed = buf->used;
  rc = s2_helper_appendf( se, 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 s2_cb_strish_eval_contents( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  char const * fname;
  cwal_size_t nameLen = 0, slen = 0;
  char catchReturn = 1;
  char const * src;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);
  src = cwal_value_get_cstr(args->self, &slen);
  if(!src){
    if(cwal_value_buffer_part(args->engine, args->self)){
      *rv = cwal_value_undefined();
      return 0;
    }else{
      return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                                "'this' type (%s) is not valid for this function.",
                                cwal_value_type_name(args->self));
    }
  }
  if(!slen){
    /* empty buffer/string */
    *rv = cwal_value_undefined();
    return 0;
  }

  fname = args->argc
    ? cwal_value_get_cstr(args->argv[0], &nameLen)
    : NULL;
  if(!fname){
    fname = cwal_value_type_name(args->self);
    assert(fname);
    nameLen = cwal_strlen(fname);
  }
#if 0
  /* this is a nice idea, but the internal script-func-forwarding
     callback will catch a propagated 'return', so we don't get the
     effect i was going for. */
  if(args->argc>0){
    catchReturn = cwal_value_get_bool(args->argv[1]);
  }
#endif

  /*
    Reminder: this call() is almost certainly already in its own
    scope, so we'll go ahed and re-use it. Doh - the public non-file
    eval APIs don't offer a flag to _not_ push a scope. Will need to
    remedy that for use here.
  */
  rc = s2_eval_cstr(se, 1, fname, src, (int)slen, rv)
    /* Reminder: if we don't use a new scope or explicitly
       ++se->sguard->vacuum for the eval, vacuuming gets us
       somewhere (seems to be the argv array). That really should
       be vacuum-safe, at least for the duration of the call().
       Toggling vacuum-safeness after the call requires a flag
       in s2_engine. Doing that doesn't seem to solve it - someone else
       is getting vacuumed, but the argv is suspect because the stack
       trace generation for exceptions throw via here includes
       a prior instance of the argv array, complete with the arguments
       intact. i.e. effectively memory corruption.
    */;
  if(catchReturn && CWAL_RC_RETURN==rc){
    *rv = s2_propagating_take(se);
    assert(*rv);
    rc = 0;
  }else if(CWAL_RC_EXCEPTION==rc){
    assert(cwal_exception_get(se->e));
  }else{
    assert(*rv && (cwal_value_is_builtin(*rv) || cwal_value_scope(*rv)));
  }
  return rc;
}


cwal_value * s2_prototype_string( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v = 0;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_STRING );
    if(proto
       || !s2_prototype_object(se) /* timing hack */
       ) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    cwal_value_prototype_set(proto, 0 /* so we don't inherit Object!*/);
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_STRING, proto );
    if(!rc) rc = s2_prototype_stash(se, "String", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_STRING));
    /* MARKER(("Setting up STRING prototype.\n")); */

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#if 0
    v = cwal_new_string_value(se->e, "string", 6);
    CHECKV;
    rc = cwal_prop_set( proto, "__typename", 10, v );
    RC;
#endif


#define FUNC3(NAME,FP,FLAGS)             \
    v = s2_new_function2( se, FP ); \
    CHECKV; \
    rc = cwal_prop_set_with_flags( proto, NAME, cwal_strlen(NAME), v, FLAGS ); \
    if(rc) goto end
#define FUNC2(NAME,FP) FUNC3(NAME,FP,0)

    FUNC2("applyFormat", s2_cb_str_apply_format);
    FUNC2("byteAt", s2_cb_str_byte_at);
    FUNC2("charAt", s2_cb_str_char_at);
    FUNC2("compare", s2_cb_value_compare);
    FUNC2("concat", s2_cb_str_concat);
    FUNC2("evalContents", s2_cb_strish_eval_contents);
    FUNC2("indexOf", s2_cb_str_indexof);
    FUNC2("length", s2_cb_str_length_utf8);
    FUNC2("lengthBytes", s2_cb_str_length);
    FUNC2("lengthUtf8", s2_cb_str_length_utf8);
    FUNC3("operator+", s2_cb_str_op_concat, CWAL_VAR_F_CONST);
    FUNC3("operator+=", s2_cb_str_op_concat, CWAL_VAR_F_CONST);
    FUNC2("split", s2_cb_str_split);
    FUNC2("substr", s2_cb_str_substr);
    FUNC2("toLower", s2_cb_str_tolower);
    FUNC2("toString", s2_cb_str_to_string);
    FUNC2("toUpper", s2_cb_str_toupper);
    FUNC2("trim", s2_cb_str_trim_both);
    FUNC2("trimLeft", s2_cb_str_trim_left);
    FUNC2("trimRight", s2_cb_str_trim_right);
    FUNC2("unescape", s2_cb_str_unescape_c);

#undef FUNC2
#undef FUNC3
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}


/**
   Callback handler for buffer.length() (if isCapacity is false) and
   buffer.capacity (if isCapacity is true).
*/
static int s2_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 s2_cb_buffer_length_u( cwal_callback_args const * args, cwal_value **rv ){
  return s2_cb_buffer_length_uc( args, rv, 0 );
}

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

static int s2_cb_buffer_length_utf8( cwal_callback_args const * args, cwal_value **rv ){
  cwal_size_t ulen;
  THIS_BUFFER;
  ulen = cwal_cstr_length_utf8((char const *)self->mem, self->used);
  *rv = cwal_new_integer(args->engine, (cwal_int_t) ulen);
  return *rv ? 0 : CWAL_RC_OOM;
}


static int s2_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 = s2_value_to_buffer(args->engine, self, args->argv[i]);
  }
  if(!rc) *rv = args->self;
  return rc;
}

static int s2_cb_buffer_appendf( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  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 s2_throw(se, CWAL_RC_MISUSE,
                    "Expecting (String,...) arguments.");
  }
  /* oldUsed = self->used; */
  rc = s2_helper_appendf(se, self, fmt, fmtlen, args->argc-1, args->argv+1);
  if(!rc){
#if 1
    *rv = args->self;
#else
    cwal_size_t const newLen = self->used - oldUsed;
    *rv = cwal_new_integer(args->engine, (int)(newLen));
    rc = *rv ? 0 : CWAL_RC_OOM;
#endif
  }
  return rc;
}

/**
   Assumes binary call form with the buffer on the LHS.
*/
static int s2_cb_buffer_op_append( cwal_callback_args const * args, cwal_value **rv ){
  THIS_BUFFER;
  if(!args->argc){
    return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                               "operator+ requires at least one argument.");
  }else{
    int rc = 0;
    cwal_size_t i = 1, argc = args->argc;
    assert(2==argc);
    for( ; !rc && (i < argc); ++i ){
      /* s2_dump_val( args->argv[i], "arg"); */
      rc = s2_value_to_buffer( args->engine, self, args->argv[i] );
    }
    if(!rc){
      *rv = args->self;
    }
    return rc;
  }
}


static int s2_cb_construct_buffer( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t len;
  ARGS_SE;
  assert(se);
  if(cwal_value_buffer_part(args->engine, args->self)){
    /* assert(!"This doesn't seem to be possible any more."); */
    return s2_throw(se, 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 = cwal_new_buffer_value(args->engine, (cwal_size_t) len);
  return *rv ? 0 : CWAL_RC_OOM;
}

static int s2_cb_buffer_take_string( cwal_callback_args const * args, cwal_value **rv ){
  THIS_BUFFER;
  *rv = self->mem
    ? cwal_string_value( cwal_buffer_to_zstring(args->engine, self) )
    : cwal_value_null();
  return *rv ? 0 : CWAL_RC_OOM;
}

static int s2_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 s2_throw(se, CWAL_RC_MISUSE,
                  "Buffer.toString() arguments must 0 or "
                  "positive integers.");
}

static int s2_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 s2_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 cwal_int_t s2_get_byte(cwal_value const *v){
  char const * cstr = cwal_value_get_cstr(v, NULL);
  return cstr ? *cstr : cwal_value_get_integer(v);
}

int s2_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 & s2_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 s2_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_SE;
  assert(se);
  fname = args->argc
    ? cwal_string_cstr(cwal_value_get_string(args->argv[0]))
    : NULL;
  if(!fname){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "Expecting a filename argument.");
  }
  if(!self || (cwal_buffer_value(self) == s2_prototype_buffer(se))){
    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 = s2_throw(se, rc,
                  "Reading file [%s] failed.",
                  fname);
  }else {
    *rv = newBuf ? cwal_buffer_value(newBuf) : args->self;
  }
  return rc;
}


int s2_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 s2_throw(se, 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 = s2_throw(se, 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 = s2_throw(se, 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 = s2_throw(se, CWAL_RC_IO,
                      "Error writing %"CWAL_SIZE_T_PFMT" byte(s) "
                      "to file [%.*s].",
                      self->used, (int)nameLen, fname);
      }
      fclose(f);
    }
    if(!rc) *rv = args->self;
    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 s2_cb_buffer_fill( cwal_callback_args const * args, cwal_value **rv ){
  cwal_int_t byte;
  THIS_BUFFER;
  if(!args->argc){
    return s2_throw(se, CWAL_RC_MISUSE,
                    "Expecting a byte value argument.");
  }
  byte = 0xFF & s2_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 s2_throw(se, 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 );
    }
#if 0
    *rv = cwal_new_integer( args->engine, len );
    return *rv ? 0 : CWAL_RC_OOM;
#else
    *rv = args->self;
    return 0;
#endif
  }
}



cwal_value * s2_prototype_buffer( s2_engine * se ){
    int rc = 0;
    cwal_value * proto;
    cwal_value * v = 0;
    proto = cwal_prototype_base_get( se->e, CWAL_TYPE_BUFFER );
    if(proto
       || !s2_prototype_object(se) /* timing hack */
       ) return proto;
    proto = cwal_new_object_value(se->e);
    if(!proto){
        rc = CWAL_RC_OOM;
        goto end;
    }
    rc = cwal_prototype_base_set(se->e, CWAL_TYPE_BUFFER, proto );
    if(!rc) rc = s2_prototype_stash(se, "Buffer", proto);
    if(rc) goto end;
    assert(proto == cwal_prototype_base_get(se->e, CWAL_TYPE_BUFFER));
    /* MARKER(("Setting up BUFFER prototype.\n")); */

#define CHECKV if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define RC if(rc) goto end;

#define FUNC2(NAME,FP)                              \
    v = s2_new_function2( se, FP ); \
    CHECKV; \
    rc = cwal_prop_set( proto, NAME, strlen(NAME), v );        \
    if(rc) goto end

    FUNC2("append", s2_cb_buffer_append);
    FUNC2("appendJSON", s2_cb_this_to_json_token);
    FUNC2("appendf", s2_cb_buffer_appendf);
    FUNC2("byteAt", s2_cb_buffer_byte_at);
    FUNC2("capacity", s2_cb_buffer_length_c);
    FUNC2("evalContents", s2_cb_strish_eval_contents);
    FUNC2("fill", s2_cb_buffer_fill);
    FUNC2("length", s2_cb_buffer_length_u);
    FUNC2("lengthUtf8", s2_cb_buffer_length_utf8);
    FUNC2("new", s2_cb_construct_buffer);
    FUNC2("operator<<", s2_cb_buffer_op_append);
    FUNC2("readFile", s2_cb_buffer_file_read);
    FUNC2("reserve", s2_cb_buffer_length_c);
    FUNC2("reset", s2_cb_buffer_reset);
    FUNC2("resize", s2_cb_buffer_resize);
    FUNC2("takeString", s2_cb_buffer_take_string);
    FUNC2("toString", s2_cb_buffer_to_string);
    FUNC2("writeToFile", s2_cb_buffer_file_write);

#undef FUNC2
#undef CHECKV
#undef RC
    end:
    return rc ? NULL : proto;
}


#undef MARKER
#undef ARGS_SE
#undef THIS_STRING
#undef THIS_BUFFER
/* end of file s2_str.c */
/* start of file s2_t10n.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

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


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

/* const s2_byte_range s2_byte_range_empty = s2_byte_range_empty_m; */
const s2_ptoker s2_ptoker_empty = s2_ptoker_empty_m;
const s2_ptoken s2_ptoken_empty = s2_ptoken_empty_m;

int s2_ptoker_init( s2_ptoker * t, char const * src, cwal_int_t len ){
  if(!t||!src) return CWAL_RC_MISUSE;
  else{
    *t = s2_ptoker_empty;
    if(len<0) len = cwal_strlen(src);
    t->begin = src;
    t->end = src+len;
    t->token.begin = src;
    return 0;
  }
}

int s2_ptoker_sub_from_token( s2_ptoker * t, s2_ptoker const * parent ){
  s2_ptoken const * tok = &parent->token;
  char const * begin;
  char const * end;
  if(tok->adjBegin){
    begin = tok->adjBegin;
    end = tok->adjEnd;
  }else{
    begin = tok->begin;
    end = tok->end;
  }
  if(!begin || (begin>end)) return CWAL_RC_RANGE;
  else{
    int const rc = s2_ptoker_init( t, begin, (cwal_int_t)(end - begin) );
    if(!rc) t->parent = parent;
    return rc;
  }
}

s2_ptoker const * s2_ptoker_top_parent( s2_ptoker const * t ){
  while( t && t->parent ){
    t = t->parent;
  }
  return t;
}
char const * s2_ptoker_name_first( s2_ptoker const * t, cwal_size_t * len ){
  while(t && !t->name){
    t = t->parent;
  }
  if(t && len && t->name) *len = t->nameLen;
  return t ? t->name : 0;
}

char const * s2_ptoker_err_pos_first( s2_ptoker const * t ){
  while(t && !t->errPos){
    t = t->parent;
  }
  return t ? t->errPos : 0;
}

char const * s2_ptoker_name_top( s2_ptoker const * t ){
  char const * n = t ? t->name : 0;
  while(t && t->parent){
    t = t->parent;
    if(t && t->name) n = t->name;
  }
  return n;
}

int s2_ttype_is_junk( int ttype ){
  switch(ttype){
    case ' ':
    case '\t':
    case '\r':
    case S2_T_Blank:
    case S2_T_CommentC:
    case S2_T_CommentCpp:
    case S2_T_Whitespace:
    case S2_T_Shebang:
    case S2_T_UTFBOM:
#if 0
    case S2_T_EOL:
    case S2_T_NL:
#endif
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_assignment( int ttype ){
  switch(ttype){
    case S2_T_OpAssign:
    case S2_T_OpAssign3:
    case S2_T_ArrayAppend:
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_assignment_combo( int ttype ){
  switch(ttype){
    case S2_T_OpPlusAssign:
    case S2_T_OpPlusAssign3:
    case S2_T_OpMinusAssign:
    case S2_T_OpMinusAssign3:
    case S2_T_OpModuloAssign:
    case S2_T_OpModuloAssign3:
    case S2_T_OpDivideAssign:
    case S2_T_OpDivideAssign3:
    case S2_T_OpMultiplyAssign:
    case S2_T_OpMultiplyAssign3:
    case S2_T_OpShiftLeftAssign:
    case S2_T_OpShiftLeftAssign3:
    case S2_T_OpShiftRightAssign:
    case S2_T_OpShiftRightAssign3:
    case S2_T_OpXOrAssign:
    case S2_T_OpXOrAssign3:
    case S2_T_OpOrAssign:
    case S2_T_OpOrAssign3:
    case S2_T_OpAndAssign:
    case S2_T_OpAndAssign3:
      return ttype;
    default:
      return 0;
  }
}


int s2_ttype_is_group( int ttype ){
  switch(ttype){
    case S2_T_ParenGroup:
    case S2_T_BraceGroup:
      return ttype;
    default:
      return 0;
  }
}


int s2_ttype_is_eof( int ttype ){
  switch(ttype){
    case S2_T_EOF:
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_eol( int ttype ){
  switch(ttype){
    case S2_T_EOF:
    case S2_T_EOL:
    case S2_T_CR:
    case S2_T_NL:
    /* case S2_T_CommentCpp: */
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_eox( int ttype ){
  switch(ttype){
    case S2_T_EOF:
    case S2_T_EOX:
      /* i [think i] really want comma as EOX, but it breaks current
         code.

         case S2_T_Comma:
      */
    case S2_T_Semicolon:
    /* case S2_T_CommentCpp: */
#if 0
    case S2_T_EOL:
    case S2_T_CR:
    case S2_T_NL:
#endif
      return ttype;
    default:
      return 0;
  }
}

int s2_ttype_is_hard_eox( int ttype ){
  switch(ttype){
    case S2_T_EOF:
    case S2_T_Semicolon:
    case S2_T_EOX:
      return ttype;
    default:
      return 0;
  }  
}

int s2_ttype_is_space( int ttype ){
  switch(ttype){
    case S2_T_NL:
    case S2_T_CR:
    case S2_T_EOL:
    case S2_T_Whitespace:
    /* case S2_T_CommentCpp: */
    /* case S2_T_CommentC: */
    case ' ':
    case '\t':
    case '\v':
    case '\f':
      return 1;
    default:
      return 0;
  }
}

int s2_ttype_is_pod( int ttype ){
  switch(ttype){
    case S2_T_LiteralIntDec:
    case S2_T_LiteralIntOct:
    case S2_T_LiteralIntHex:
    case S2_T_LiteralDouble:
    case S2_T_LiteralStringSQ:
    case S2_T_LiteralStringDQ:
    case S2_T_LiteralString:
    case S2_T_Identifier:
      return ttype;
    default:
      return 0;
  }
}

int s2_ptoker_is_eof( s2_ptoker const * st ){
  return s2_ttype_is_eof(st->token.ttype);
}

int s2_ptoker_is_eox( s2_ptoker const * st ){
  return s2_ttype_is_eox(st->token.ttype);
}

int s2_ptoken_is_true_squiggly( s2_ptoken const * t ){
  return (t->begin && t->begin<t->end && '{'==*t->begin)
    ? t->ttype : 0;
}

int s2_ttype_short_circuits( int ttype ){
  switch( ttype ){
    case S2_T_OpOr:
    case S2_T_OpAnd:
    case S2_T_Question:
      return ttype;
    default: return 0;
  }
}


int s2_ttype_is_identifier_prefix( int ttype ){
  switch( ttype ){
    case S2_T_OpIncr:
    case S2_T_OpIncrPre:
    case S2_T_OpIncrPost:
    case S2_T_OpDecr:
    case S2_T_OpDecrPre:
    case S2_T_OpDecrPost:
      return ttype;
    default: return 0;
  }
}

int s2_ttype_may_precede_unary( int ttype ){
  switch(ttype){
#if 1
    /* (...) and [...] are handled at the eval level,
       and are simply treated as values. */
    case S2_T_ParenOpen:
    case S2_T_ParenGroup:
      /* No! case S2_T_ParenClose: */
    case S2_T_BraceOpen:
    case S2_T_BraceGroup:
      /* No! case S2_T_BraceClose: */
#endif
    case S2_T_Comma:
    case S2_T_Semicolon:
    case S2_T_Colon:
    case S2_T_OpArrow: /* So x-> -1 can work. */
    case S2_T_OpNot:
    case S2_T_OpAnd:
    case S2_T_OpAssign:
    case S2_T_OpAssign3:
    case S2_T_OpOr:
    case S2_T_OpOr3:
    case S2_T_OpPlus:
    case S2_T_OpPlusUnary:
    case S2_T_OpMinus:
    case S2_T_OpMinusUnary:
    case S2_T_OpMultiply:
    case S2_T_OpDivide:
    case S2_T_OpModulo:
    case S2_T_OpOrBitwise:
    case S2_T_OpXOr:
    case S2_T_CmpLT:
    case S2_T_CmpGT:
    case S2_T_CmpLE:
    case S2_T_CmpGE:
    case S2_T_CmpEq:
    case S2_T_CmpNotEq:
    case S2_T_CmpEqStrict:
    case S2_T_CmpNotEqStrict:
    case S2_T_OpContains:
    case S2_T_OpNotContains:
    case S2_T_OpAndBitwise:
    case S2_T_KeywordThrow:
    case S2_T_KeywordAssert:
    case S2_T_KeywordReturn:
    case S2_T_ArrayAppend /* b/c this op slides over its '=' part */:
      return ttype;
    default:
      return s2_ttype_is_assignment_combo(ttype);
  }
}

/**
   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 s2_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 '$':
        /* case '@': */
        return 1;
      default:
        return isStart
          ? s2_is_alpha(ch)
          : s2_is_alnum(ch);
    }
}

char s2_ptoker_putback( s2_ptoker * st ){
  char const rc = st->pbToken.begin ? 1 : 0;
  if(rc){
    st->token = st->pbToken;
    st->pbToken = s2_ptoken_empty;
  }
  return rc;
}

int s2_ptoker_lookahead( s2_ptoker * st, s2_ptoken * tgt ){
  s2_ptoken const oldT = st->token;
  s2_ptoken const oldP = st->pbToken;
  int rc = s2_ptoker_next_token( st );
  *tgt = st->token;
  st->token = oldT;
  st->pbToken = oldP;
  return rc;
}

/**
   Internal impl for s2_ptoker_lookahead_xxx_pred().

   matchMode: true means skip as long as the predicate matches. False
   means skip until the predicate matches. i.e. true implements a 'while'
   loop and false implements a 'do-until' loop.

   Returns 0 on success.
*/
static int s2_ptoker_lookahead_pred( s2_ptoker * st, s2_ptoken * tgt,
                                 s2_ttype_predicate_f pred, char matchMode ){
  s2_ptoken const oldT = st->token;
  s2_ptoken const oldP = st->pbToken;
  int rc = 0;
  assert(tgt);
  while( !(rc = s2_ptoker_next_token( st ))
         && (matchMode ? pred(st->token.ttype) : !pred(st->token.ttype))
         && !s2_ptoker_is_eof(st)){
  }
  *tgt = st->token;
  st->token = oldT;
  st->pbToken = oldP;
  return rc;
}

int s2_ptoker_lookahead_skip( s2_ptoker * st, s2_ptoken * tgt,
                               s2_ttype_predicate_f pred ){
  return s2_ptoker_lookahead_pred( st, tgt, pred, 1 );
}

int s2_ptoker_lookahead_until( s2_ptoker * st, s2_ptoken * tgt,
                               s2_ttype_predicate_f pred ){
  return s2_ptoker_lookahead_pred( st, tgt, pred, 0 );
}

void s2_ptoker_token_set( s2_ptoker * st, s2_ptoken const * t ){
  /* Need a temp in case t== one of st's tokens */
  s2_ptoken const tmp = *t;
  st->pbToken = st->token;
  st->token = tmp;
}


int s2_ptoker_next_token( s2_ptoker * t ){
  int rc = 0;
  s2_ptoken * pt = &t->token;
  char const * curpos = t->token.end
    ? t->token.end /* for the 2nd and subsequent calls */
    : t->token.begin /* for the first run through this function */;
  assert(curpos);
  assert(curpos >= t->begin);
  assert(curpos <= t->end);
  t->pbToken = t->token;
  t->token.adjBegin = t->token.adjEnd = 0;
  t->errPos = 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->ttype = TT_EOF; return 0; }(void)0
#else
#define CHECKEND (void)0
#endif
#define BUMP(X) curpos+=(X); CHECKEND; t->currentCol+=(X)
#define RETURN_ERR(RC,MSG) pt->ttype = S2_T_TokErr; t->errMsg = MSG; return (RC)
#define NEXT_LINE  ++t->currentLine; t->currentCol = 0


  if( curpos >= t->end ) {
    pt->ttype = S2_T_EOF;
    t->token.begin = t->token.end = t->end;
    return 0;
  }

  if(!t->parent && curpos == t->begin){
    /* Check for some things which can only appear at the start of a
       script. The if() condition above isn't quite 100% accurate,
       considering how s2 uses sub-tokenizers at times, but it's
       pretty close.
    */
    if('#'==*curpos && '!'==*(curpos+1)){
      /* Workaround: strip shebang line from start of scripts. TODO:
         return this as its own token type? */
      for( ; *(++curpos) && ('\n' != *curpos); ++t->currentCol){
      }
      ++curpos /* skip NL */;
      if( curpos >= t->end ) {
        pt->ttype = S2_T_EOF;
        t->token.begin = t->token.end = t->end;
        return 0;
      }
      t->token.end = curpos;
      t->token.ttype = S2_T_Shebang;
      ++t->currentLine;
      t->currentCol = 0;
      return 0;
    }else{
      /* Check for a BOM marker, and skip it. */
      unsigned char const * ccp = (unsigned char const*)curpos;
      if(0xEF==*ccp && 0xBB==ccp[1] && 0xBF==ccp[2]){
        curpos += 3;
        t->token.end = curpos;
        t->token.ttype = S2_T_UTFBOM;
        t->currentCol += 3;
        return 0;
      }
    }
  }
    

  pt->ttype = S2_T_INVALID;
  t->token.begin = curpos;
    
  switch(*curpos){
    case 0:
      pt->ttype = S2_T_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)))){
        pt->ttype = S2_T_Whitespace;
        BUMP(('\r'==*(curpos+1)) ? 3 : 2);
        NEXT_LINE;
      }
      break;
    case '\r':
      if('\n'==*(curpos+1)){
        pt->ttype = S2_T_EOL;
        BUMP(2);
        NEXT_LINE;
      }
      else{
        pt->ttype = S2_T_CR;
        BUMP(1);
      }
      break;
    case '\n':
      pt->ttype = S2_T_NL;
      BUMP(1);
      NEXT_LINE;
      break;
    case ' ':
    case '\t':
    case '\v':
    case '\f':
      pt->ttype = *curpos /*S2_T_Blank*/;
      BUMP(1);
#if 1
      while( *curpos && s2_is_blank(*curpos) ){
        pt->ttype = S2_T_Blank;
        BUMP(1);
      }
#endif
      break;
    case ':': /* colon or namespace */
      if( ':' == *(curpos+1) ){
        pt->ttype = S2_T_Colon2;
        BUMP(2);
      }else{
        pt->ttype = S2_T_Colon;
        BUMP(1);
      }
      break;
    case '~': /* ~ or ~= */
      pt->ttype = *curpos;
      BUMP(1);
#if 0
      if('=' == *curpos){
        pt->ttype = S2_T_NotYet;
        BUMP(1);
      }
#endif
      break;
    case '/': /* numeric division or C-style comment block */
      pt->ttype = S2_T_OpDivide /* *curpos */;
      BUMP(1);
      if('=' == *curpos){
        pt->ttype = S2_T_OpDivideAssign;
        BUMP(1);
      }
      else if( '*' == *curpos) /* C-style comment block */{
        pt->ttype = S2_T_CommentC;
        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_SCR_SYNTAX,"End of C-style comment not found."); 
        }
        BUMP(1); /* get that last slash */
      }
      else if( '/' == *curpos ) /* C++-style comment line */{
        BUMP(1);
        pt->ttype = S2_T_CommentCpp;
        while( *curpos && ('\n' != *curpos) ){
          BUMP(1);
        }
#if 0
        if(*curpos){
          /*
            Consume the EOL with the comment, since a CPP
            comment always implies an EOL or EOF.

            Re-try this when other changes aren't pending
            committing.
          */
          BUMP(1);
        }
#endif
      }
      break;
    case '"':
    case '\'': /* read string literal */{
      char const quote = *curpos;
      pt->ttype = ('"' == *curpos)
        ? S2_T_LiteralStringDQ
        : S2_T_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_SCR_SYNTAX,
                       "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_SCR_SYNTAX,
                   "Unexpected end of string literal.");
      }
      BUMP(1)/*trailing quote*/;
      break;
    } /* end literal string */
    case '&': /* & or && */
      pt->ttype = S2_T_OpAndBitwise;
      BUMP(1);
      if( '&' == *curpos ){
        pt->ttype = S2_T_OpAnd;
        BUMP(1);
      }
      else if( '=' == *curpos ){
        pt->ttype = S2_T_OpAndAssign;
        BUMP(1);
      }
      break;
    case '|': /* | or || or ||| */
      pt->ttype = S2_T_OpOrBitwise;
      BUMP(1);
      if( '|' == *curpos ){
        pt->ttype = S2_T_OpOr;
        BUMP(1);
        if( '|' == *curpos ){
          pt->ttype = S2_T_OpOr3;
          BUMP(1);
        }
      }
      else if( '=' == *curpos ){
        pt->ttype = S2_T_OpOrAssign;
        BUMP(1);
      }
      break;
    case '^': /* ^ or ^= */
      pt->ttype = S2_T_OpXOr;
      BUMP(1);
      if( '=' == *curpos ){
        pt->ttype = S2_T_OpXOrAssign;
        BUMP(1);
      }
      break;
    case '+': /* + or ++ or += */{
      pt->ttype = S2_T_OpPlus;
      BUMP(1);
      switch(*curpos){
        case '+': /* ++ */
          pt->ttype = S2_T_OpIncr;
          BUMP(1);
          break;
        case '=': /* += */
          pt->ttype = S2_T_OpPlusAssign;
          BUMP(1);
          break;
        default: break;
      }
      break;
    }
    case '*': /* multiply operator */
      pt->ttype = S2_T_OpMultiply;
      BUMP(1);
      if('=' == *curpos){
        pt->ttype = S2_T_OpMultiplyAssign;
        BUMP(1);
      }
      else if('/' == *curpos){
        pt->ttype = S2_T_INVALID;
        RETURN_ERR(CWAL_SCR_SYNTAX,
                   "Comment closer (*/) not inside a comment.");
      }
      break;
          
    case '-': /* - or -- or -= */{
      pt->ttype = S2_T_OpMinus;
      BUMP(1);
      switch( *curpos ){
        case '-': /* -- */
          pt->ttype = S2_T_OpDecr;
          BUMP(1);
          break;
        case '=':
          pt->ttype = S2_T_OpMinusAssign;
          BUMP(1);
          break;
        case '>':
          pt->ttype = S2_T_OpArrow;
          BUMP(1);
          break;
        default: break;
      }
      break;
    }
    case '<': /* LT or << or <= or <<= or <<< */{
      pt->ttype = S2_T_CmpLT;
      BUMP(1);
      switch( *curpos ){
        case '<': /* tok= << */ {
          pt->ttype = S2_T_OpShiftLeft;
          BUMP(1);
          switch(*curpos){
            case '=': /* <<= */
              pt->ttype = S2_T_OpShiftLeftAssign;
              BUMP(1);
              break;
            case '<': /* <<< */
              pt->ttype = S2_T_HeredocStart;
              BUMP(1);
              break;
            default: break;
          }
          break;
        }
        case '=': /* tok= <= */
          pt->ttype = S2_T_CmpLE;
          BUMP(1);
          break;
        default: break;
      }
      break;
    }
    case '>': /* GT or >> or >= */{
      pt->ttype = S2_T_CmpGT;
      BUMP(1);
      switch(*curpos){
        case '>': /* tok= >> */
          pt->ttype = S2_T_OpShiftRight;
          BUMP(1);
          if( '=' == *curpos ){/* >>= */
            pt->ttype = S2_T_OpShiftRightAssign;
            BUMP(1);
          }
          break;
        case '=': /* tok= >= */
          pt->ttype = S2_T_CmpGE;
          BUMP(1);
          break;
        default: break;
      }
      break;
    }
    case '=': /* = or == or === or =~ */
      pt->ttype = S2_T_OpAssign;
      BUMP(1);
      if( '~' == *curpos ){
        pt->ttype = S2_T_OpContains;
        BUMP(1);
      }else if( '=' == *curpos ){
        pt->ttype = S2_T_CmpEq;
        BUMP(1);
        if( '=' == *curpos ){
          pt->ttype = S2_T_CmpEqStrict;
          BUMP(1);
        }
      }
      break;
    case '%': /* % or %= */
      pt->ttype = S2_T_OpModulo /* *curpos */;
      BUMP(1);
      if( '=' == *curpos ){
        pt->ttype = S2_T_OpModuloAssign;
        BUMP(1);
      }
      break;
    case '!': /* ! or != or !== or !~ */
      pt->ttype = S2_T_OpNot;
      BUMP(1);
      if( '~' == *curpos ){
        pt->ttype = S2_T_OpNotContains;
        BUMP(1);
      }else if( '=' == *curpos ){
        pt->ttype = S2_T_CmpNotEq;
        BUMP(1);
        if( '=' == *curpos ){
          pt->ttype = S2_T_CmpNotEqStrict;
          BUMP(1);
        }
      }
      break;
    case '{': /* { or {{ */
      pt->ttype = S2_T_SquigglyOpen;
      BUMP(1);
#if 0
      if('{' == *curpos){
        pt->ttype = TT_LiteralObjOpen;
        BUMP(1);
      }
#endif
      break;
    case '}': /* } or }} */
      pt->ttype = S2_T_SquigglyClose;
      BUMP(1);
#if 0
      if('}' == *curpos){
        pt->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. */
        int digitCount = 0;
        BUMP(1);
        while( *curpos && s2_is_xdigit(*curpos) ){
          ++digitCount;
          BUMP(1);
        }
        if(!digitCount){
          RETURN_ERR(CWAL_SCR_SYNTAX,
                     "No digits in hexidecimal int literal.");
        }else{
          pt->ttype = S2_T_LiteralIntHex;
          if( *curpos && (
                          s2_is_alnum(*curpos)
                          || (*curpos == '_'))
              ){
            BUMP(1); /* make sure it shows up in the error string. */
            RETURN_ERR(CWAL_SCR_SYNTAX,
                       "Malformed hexidecimal int literal.");
          }
        }
      }
      else if(*curpos=='o'){
        /* try octal... */
        int digitCount = 0;
        BUMP(1);
        while( *curpos && (*curpos>='0') && (*curpos<='7') ){
          ++digitCount;
          BUMP(1);
        }
        if(!digitCount){
          RETURN_ERR(CWAL_SCR_SYNTAX,
                     "No digits in octal int literal.");
        }else{
          pt->ttype = S2_T_LiteralIntOct;
          if( *curpos && (
                          s2_is_alnum(*curpos)
                          || (*curpos == '_'))
              ){
            BUMP(1); /* make sure it shows up in the error string. */
            RETURN_ERR(CWAL_SCR_SYNTAX,
                       "Malformed octal int literal.");
          }
        }
      }
      else if( *curpos && (
                           s2_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_SCR_SYNTAX,
                   "Malformed numeric literal starts "
                   "with '0' but is neither octal nor hex.");
      }
      else if('.'==*curpos){
        if(!s2_is_digit(curpos[1])){
          /* curpos -= 1 */ /* Assume this might be NUMBER.FUNC(),
                         back up and leave it for next time */;
          pt->ttype = S2_T_LiteralIntDec;
          break;
        }
        BUMP(1);
        while( s2_is_digit(*curpos) ){
          BUMP(1);
        }
        if('.'==*(curpos-1)){
#if 0
                  
#else
          RETURN_ERR(CWAL_SCR_SYNTAX,
                     "Mis-terminated floating point value.");
#endif
        }
        pt->ttype = S2_T_LiteralDouble;
      }
      else {
        pt->ttype = S2_T_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 && s2_is_digit(*curpos) ){
        /* integer or first part of a double. */
        BUMP(1);
      }
      if( ('.' == *curpos) && (s2_is_digit(*(curpos+1))) ){
        /* double number */
        pt->ttype = S2_T_LiteralDouble;
        BUMP(1);
        while( *curpos && s2_is_digit(*curpos) ){
          BUMP(1);
        }
      }
      else {
        pt->ttype = S2_T_LiteralIntDec;
      }
      if( *curpos &&
          (s2_is_alnum(*curpos)
           || (*curpos == '_'))) {
        BUMP(1); /* make sure it shows up in the error string. */
        RETURN_ERR(CWAL_SCR_SYNTAX,
                   "Malformed numeric literal.");
      }
      break;
  }/* end of switch(*curpos) */

  if(0==rc
     && (curpos == t->token.begin)
     && (S2_T_EOF != pt->ttype) ){
    /* keep trying... */
    if( s2_is_id_char(*curpos,1) ) /* identifier string */{
#if 1
      s2_read_identifier( curpos, t->end, &curpos );
#else
      BUMP(1);
      while( s2_is_identifier_char(*curpos,0) ){
        BUMP(1);
      }
#endif
      pt->ttype = S2_T_Identifier;
    }
    else if( (*curpos >= 32) && (*curpos < 127) ) {
      pt->ttype = *curpos;
      BUMP(1);
    }
    else {
      assert(curpos == t->token.begin);
      assert(S2_T_INVALID==pt->ttype);
    }
  }

  if(S2_T_INVALID == pt->ttype){
    unsigned char const cCh = (unsigned char)*curpos;
    if( cCh > 0x7F )
      /* _hope_ for a UTF8 identifier string! */{
      s2_read_identifier( curpos, t->end, &curpos );
      pt->ttype = S2_T_Identifier;
    }else{
      pt->ttype = S2_T_TokErr;
      /* MARKER(("byte=%02x\n", (unsigned char)*curpos)); */
      t->errMsg = "Don't know how to tokenize this.";
      rc = CWAL_RC_ERROR;
    }
  }
  t->token.end = (curpos > t->end) ? t->end : curpos;
#undef CHECKEND
#undef BUMP
#undef RETURN_ERR
#undef NEXT_LINE
  if(rc){
    t->errPos = curpos;
    assert(t->errMsg);
  }
  return rc;
}

char s2_is_space( int ch ){
  switch(ch){
    case ' ':
    case '\n':
    case '\r':
    case '\t':
    case '\v':
    case '\f':
      return 1;
    default:
      return 0;
  }
}

char s2_is_blank( int ch ){
  switch(ch){
    case ' ':
    case '\t':
      return 1;
    default:
      return 0;
  }
}

char s2_is_digit( int ch ){
  return '0'<=ch && '9'>=ch;
}

char s2_is_xdigit( int ch ){
  return ('a'<=ch && 'f'>=ch)
    ? 1
    : (('A'<=ch && 'F'>=ch)
       ? 1
       :s2_is_digit(ch));
}

char s2_is_alpha( int ch ){
  return ('a'<=ch && 'z'>=ch)
    ? 1
    : ('A'<=ch && 'Z'>=ch);
}

char s2_is_alnum( int ch ){
  return s2_is_alpha(ch) ? 1 : s2_is_digit(ch);
}

void s2_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) || !s2_is_id_char(ch, (pos==start) ? 1 : 0)) break;
    pos = endChar;
  }
  *zIdEnd = zPos + (pos - start);
}


#if 0
static int s2_fast_char_size( unsigned char const * c ){
  if(!*c) return 0;
  else if (*c < 0x80) return 1;
  else if (*++c < 0x08) return 2;
  else if (!*++c) return 3;
  else return 4;
}
#endif

int s2_count_lines( char const * src, char const * end_,
                    char const * pos_,
                    int *line, int *col ){
  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_;
  if((pos<x) || (pos>=end)) {
    return CWAL_RC_RANGE;
  }
  else{
    /* profiling shows that cwal_utf8_read_char() is, by leaps and
       bounds, the most oft-called func in this whole constellation,
       largely due to this routine. We need a faster, file-local
       multi-byte char skipping routine.

       And yet this function still takes more time than many other
       functions doing much more work and called more often?!?!?
       e.g. cwal_engine_vacuum(). Heh?

       Need to see if we can build line/column counting into
       s2_ptoker and s2_ptoker_next_token().
    */
#if 1
    /* Derived from:
       http://www.daemonology.net/blog/2008-06-05-faster-utf8-strlen.html
    */
    for( ; (x < pos) && *x; ++x ){
      switch(*x){
        case '\n':
          ++ln;
          c = 0;
          break;
        case '\r':
          if('\n'==x[1]){
            ++ln; 
            c = 0;
            ++x;
          }
          else ++c;
          break;
        default:
          ++c;
          if(*x>127U){
            switch(0xF0 & *x) {
              case 0xF0: /* length 4 */
                x += 3;
                break;
              case 0xE0: /* length 3 */
                x+= 2;
                break;
              default: /* length 2 */
                x += 1;
                break;
            }
            break;
          }
      }
	}
#else
    unsigned char const * tail = end;
    int ch;
    for( ; (x < pos) && *x; ++x ){
      if('\n'==*x){
        ++ln;
        c = 0;
      }else if('\r'==*x && '\n'==x[1]){
        ++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 */;
        }
      }
    }
#endif
  }
  if(line) *line = ln;
  if(col) *col = c;
  /* MARKER(("Updated line/col: %u, %u\n", ln, c )); */
  return 0;
}

int s2_ptoker_count_lines( s2_ptoker const * pt, char const * pos,
                           int * line, int * col ){
  s2_ptoker const * top = s2_ptoker_top_parent(pt);
  int rc, li = 0, c = 0;
  if( !(rc = s2_count_lines( top->begin, top->end, pos, &li, &c )) ){
    if(line || col){
      int oL = 0;
      for( top = pt; top; top = top->parent ){
        if(top->lineOffset){
          oL = top->lineOffset - 1;
          if(1==li) c+= top->colOffset;
          /*MARKER(("top->lineOffset=%d, top->colOffset=%d\n",
            top->lineOffset, top->colOffset));*/
          break;
        }
      }
      li += oL;
      /* MARKER(("line=%d, col=%d\n",li, c)); */
      if(line) *line = li;
      if(col) *col = c;
    }
  }
  return rc;
}

void s2_ptoker_err_info( s2_ptoker const * pt, char const ** name,
                         char const * pos,
                         int * line, int * col ){
  if(!pos) pos = s2_ptoker_err_pos_first(pt);
  if(!pos) pos = pt->token.begin
             ? pt->token.begin
             : (pt->pbToken.begin ? pt->pbToken.begin : 0);
  if(name) *name = s2_ptoker_name_first(pt, 0);
  if(pos){
    s2_ptoker_count_lines( pt, pos, line, col );
  }
}


static int s2_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 int s2_read_hex_bytes(unsigned char const *zPos,
                             unsigned int howMany,
                             unsigned int * rv ){
  unsigned int rc = 0;
  int ch1, ch2;
  unsigned int n = 0;
  assert(!(howMany%2) && "howMany must be an even number!");
  assert(zPos && rv);
  while(n<howMany /*8*/){
    ch1 = *zPos ? s2_hexbyte(*zPos++) : -1;
    ch2 = (ch1>=0) ? s2_hexbyte(*zPos++) : -1;
    if(ch2<0) return -1 /*break*/;
    rc = (rc<<8) | (0xF0 & (ch1<<4)) | (0x0F & ch2);
    n += 2;
  }
  *rv = rc;
  return n;
}

/**
   TODO: return the byte offset of the error, a negative value
   on success.
 */
int s2_unescape_string( cwal_engine * e,
                        char const * _begin,
                        char const * _end,
                        cwal_buffer * dest ){
  cwal_size_t sz;
  unsigned char const * begin = (unsigned char const*)_begin;
  unsigned char const * end = (unsigned char const*)_end;
  unsigned char const * p = begin;
  unsigned char * out;
  int check;
  cwal_size_t oldUsed;
  unsigned char uCount = 0;
  unsigned int uChar = 0;
  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 'b': *(out++) = '\b'; break;
          case 't': *(out++) = '\t'; break;
          case 'n': *(out++) = '\n'; break;
          case 'r': *(out++) = '\r'; break;
          case 'u':
          case 'U':
            /* \uXXXX and \UXXXXXXXX */
            uCount = s2_read_hex_bytes(p+1, 'u'==*p ? 4 : 8, &uChar);
            if(uCount != ('u'==*p ? 4 : 8)) 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 /* length of the char, in bytes */;
            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;
}


    
char const * s2_ttype_cstr( int ttype ){
  switch((enum s2_token_types)ttype){
    /* ^^^^ reminder: that cast is so that gcc will warn
       when i forget to update this function after adding
       new entries to that enum. */
#define CASE(TT) case TT: return #TT+5
    CASE(S2_T_TokErr);
    CASE(S2_T_INVALID);
    CASE(S2_T_EOF);
    CASE(S2_T_EOX);
    CASE(S2_T_Tab);
    CASE(S2_T_NL);
    CASE(S2_T_VTab);
    CASE(S2_T_FF);
    CASE(S2_T_CR);
    CASE(S2_T_EOL);
    CASE(S2_T_Space);
    CASE(S2_T_Blank);
    CASE(S2_T_Whitespace);
    CASE(S2_T_UTFBOM);
    CASE(S2_T_OpNot);
    CASE(S2_T_OpHash);
    CASE(S2_T_Shebang);
    CASE(S2_T_OpModulo);
    CASE(S2_T_OpModuloAssign);
    CASE(S2_T_OpModuloAssign3);
    CASE(S2_T_OpAndBitwise);
    CASE(S2_T_OpAnd);
    CASE(S2_T_OpAndAssign);
    CASE(S2_T_OpAndAssign3);
    CASE(S2_T_ParenOpen);
    CASE(S2_T_ParenGroup);
    CASE(S2_T_ParenClose);
    CASE(S2_T_OpMultiply);
    CASE(S2_T_OpMultiplyAssign);
    CASE(S2_T_OpMultiplyAssign3);
    CASE(S2_T_OpPlus);
    CASE(S2_T_OpPlusUnary);
    CASE(S2_T_OpPlusAssign);
    CASE(S2_T_OpPlusAssign3);
    CASE(S2_T_OpIncr);
    CASE(S2_T_OpIncrPre);
    CASE(S2_T_OpIncrPost);
    CASE(S2_T_Comma);
    CASE(S2_T_RHSEval);
    CASE(S2_T_OpMinus);
    CASE(S2_T_OpMinusUnary);
    CASE(S2_T_OpMinusAssign);
    CASE(S2_T_OpMinusAssign3);
    CASE(S2_T_OpDecr);
    CASE(S2_T_OpDecrPre);
    CASE(S2_T_OpDecrPost);
    CASE(S2_T_OpDot);
    CASE(S2_T_OpInherits);
    CASE(S2_T_OpContains);
    CASE(S2_T_OpNotContains);
    CASE(S2_T_OpArrow);
    CASE(S2_T_OpDivide);
    CASE(S2_T_OpDivideAssign);
    CASE(S2_T_OpDivideAssign3);
    CASE(S2_T_Colon);
    CASE(S2_T_Colon2);
    CASE(S2_T_Semicolon);
    CASE(S2_T_CmpLT);
    CASE(S2_T_CmpLE);
    CASE(S2_T_OpShiftLeft);
    CASE(S2_T_OpShiftLeftAssign);
    CASE(S2_T_OpShiftLeftAssign3);
    CASE(S2_T_HeredocStart);
    CASE(S2_T_OpAssign);
    CASE(S2_T_OpAssign3);
    CASE(S2_T_CmpEq);
    CASE(S2_T_CmpNotEq);
    CASE(S2_T_CmpEqStrict);
    CASE(S2_T_CmpNotEqStrict);
    CASE(S2_T_CmpGT);
    CASE(S2_T_CmpGE);
    CASE(S2_T_OpShiftRight);
    CASE(S2_T_OpShiftRightAssign);
    CASE(S2_T_OpShiftRightAssign3);
    CASE(S2_T_Question);
    CASE(S2_T_BraceOpen);
    CASE(S2_T_Backslash);
    CASE(S2_T_BraceClose);
    CASE(S2_T_BraceGroup);
    CASE(S2_T_ArrayAppend);
    CASE(S2_T_OpXOr);
    CASE(S2_T_OpXOrAssign);
    CASE(S2_T_OpXOrAssign3);
    CASE(S2_T_SquigglyOpen);
    CASE(S2_T_SquigglyString);
    CASE(S2_T_OpOrBitwise);
    CASE(S2_T_OpOr);
    CASE(S2_T_OpOr3);
    CASE(S2_T_OpOrAssign);
    CASE(S2_T_OpOrAssign3);
    CASE(S2_T_SquigglyClose);
    CASE(S2_T_OpNegateBitwise);

    CASE(S2_T_Literal__);
    CASE(S2_T_LiteralInt);
    CASE(S2_T_LiteralIntDec);
    CASE(S2_T_LiteralIntHex);
    CASE(S2_T_LiteralIntOct);
    CASE(S2_T_LiteralDouble);
    CASE(S2_T_LiteralStringDQ);
    CASE(S2_T_LiteralStringSQ);
    CASE(S2_T_LiteralString);
    CASE(S2_T_PropertyKey);
    CASE(S2_T_Identifier);
    CASE(S2_T_ValueTypes__);
    CASE(S2_T_Value);
    CASE(S2_T_Undefined);
    CASE(S2_T_Null);
    CASE(S2_T_False);
    CASE(S2_T_True);
    CASE(S2_T_Object);
    CASE(S2_T_Array);
    CASE(S2_T_Function);

    CASE(S2_T_Keyword__);
    CASE(S2_T_KeywordAffirm);
    CASE(S2_T_KeywordAssert);
    CASE(S2_T_KeywordBREAKPOINT);
    CASE(S2_T_KeywordBreak);
    CASE(S2_T_KeywordCOLUMN);
    CASE(S2_T_KeywordCatch);
    CASE(S2_T_KeywordConst);
    CASE(S2_T_KeywordContinue);
    CASE(S2_T_KeywordDo);
    CASE(S2_T_KeywordEval);
    CASE(S2_T_KeywordExit);
    CASE(S2_T_KeywordFILE);
    CASE(S2_T_KeywordFILEDIR);
    CASE(S2_T_KeywordFalse);
    CASE(S2_T_KeywordFatal);
    CASE(S2_T_KeywordFor);
    CASE(S2_T_KeywordFunction);
    CASE(S2_T_KeywordIf);
    CASE(S2_T_KeywordLINE);
    CASE(S2_T_KeywordNameof);
    CASE(S2_T_KeywordNull);
    CASE(S2_T_KeywordProc);
    CASE(S2_T_KeywordReturn);
    CASE(S2_T_KeywordRefcount);
    CASE(S2_T_KeywordSRCPOS);
    CASE(S2_T_KeywordScope);
    CASE(S2_T_KeywordThrow);
    CASE(S2_T_KeywordTrue);
    CASE(S2_T_KeywordTypename);
    CASE(S2_T_KeywordUndefined);
    CASE(S2_T_KeywordUnset);
    CASE(S2_T_KeywordVar);
    CASE(S2_T_KeywordWhile);

    CASE(S2_T_Comment__);
    CASE(S2_T_CommentC);
    CASE(S2_T_CommentCpp);
    CASE(S2_T_Mark__);
    CASE(S2_T_MarkVariadicStart);
    CASE(S2_T_Misc__);
    CASE(S2_T_Foo);
    CASE(S2_T_comma_kludge_);
#undef CASE
  }
  return 0;
}

int s2_ptoken_has_content( s2_ptoken const * tok ){
  char const * begin;
  char const * end;
  if(tok->adjBegin){
    begin = tok->adjBegin;
    end = tok->adjEnd;
  }else{
    begin = tok->begin;
    end = tok->end;
  }
  /* assert(!name || nameLen>0); */
  assert(end >= begin);
  if(begin == end) return 0;
  else{
    s2_ptoker pr = s2_ptoker_empty;
    s2_ptoker_init( &pr, begin, (int)(end - begin) );
    do {
      if( s2_ptoker_next_token(&pr) ) return 0;
    }while( s2_ttype_is_junk(pr.token.ttype) );
    return s2_ttype_is_eof(pr.token.ttype) ? 0 : pr.token.ttype;
  }
}

char s2_ptoken_parse_int( s2_ptoken const * t, cwal_int_t * rc ){
 switch(t->ttype){
    case S2_T_LiteralIntDec:
      assert(t->end>t->begin);
      cwal_cstr_to_int(t->begin, (cwal_size_t)(t->end - t->begin), rc);
      return 1;
    case S2_T_LiteralIntOct:
      assert( (t->end-t->begin) >= 2 /*"0o" prefix */);
      if(rc) *rc = (cwal_int_t)strtol(t->begin+2/*0*/, NULL, 8)
               /* FIXME: strtol() is not big enough for large values,
                  but strtoll() is not C89. */;
      return 1;
    case S2_T_LiteralIntHex:
      assert( (t->end-t->begin) > 2 /*"0x" prefix */);
      if(rc) *rc = (cwal_int_t)strtol(t->begin+2/*0x*/, NULL, 16)
               /* FIXME: strtol() is not big enough for large values. */;
      return 1;
    default:
      return 0;
  }
}

char s2_ptoken_parse_double( s2_ptoken const * t, cwal_double_t * rc ){
  if(t->ttype == S2_T_LiteralDouble){
    if(rc) *rc = (cwal_double_t)strtod(t->begin, NULL);
    return 1;
  }else{
#if 1
    return 0;
#else
    cwal_int_t dd = 0;
    if(s2_ptoken_parse_int(t, &dd)){
      if(rc) *rc = (cwal_double_t)dd;
      return 1;
    }else{
      return 0;
    }
#endif
  }
}

char s2_cstr_parse_int( char const * str, cwal_int_t slen, cwal_int_t * result ){
  s2_ptoker pr = s2_ptoker_empty;
  int sign = 0;
  while( slen && s2_is_space(*str) ){
    ++str;
    --slen;
  }
  if(str && slen){
    if( '-' == *str || '+' == *str ){
      sign = ('-'==*str) ? -1 : 1;
      ++str;
      --slen;
    }
  }
  while( slen && s2_is_space(*str) ){
    ++str;
    --slen;
  }
  if(!slen) return 0;
  s2_ptoker_init( &pr, str, slen );
  if(s2_ptoker_next_token(&pr)
     || !s2_ptoken_parse_int( &pr.token, result )
     ){
    return 0;
  }else{
    pr.token.begin = pr.token.end;
    pr.token.end = pr.end;
    if(s2_ptoken_has_content(&pr.token)){
      /* trailing junk */
      return 0;
    }else{
      if(result && sign < 0) *result = -*result;
      return 1;
    }
  }
}


char s2_cstr_parse_double( char const * str, cwal_int_t slen, cwal_double_t * result ){
  s2_ptoker pr = s2_ptoker_empty;
  int sign = 0;
  while( slen && s2_is_space(*str) ){
    ++str;
    --slen;
  }
  if(str && slen){
    if( '-' == *str || '+' == *str ){
      sign = ('-'==*str) ? -1 : 1;
      ++str;
      --slen;
    }
  }
  while( slen && s2_is_space(*str) ){
    ++str;
    --slen;
  }
  if(!slen) return 0;
  s2_ptoker_init( &pr, str, slen );
  if(s2_ptoker_next_token(&pr)
     || !s2_ptoken_parse_double( &pr.token, result )){
    return 0;
  }else{
    pr.token.begin = pr.token.end;
    pr.token.end = pr.end;
    if(s2_ptoken_has_content(&pr.token)){
      /* trailing junk */
      return 0;
    }else{
      if(result && sign < 0) *result = -*result;
      return 1;
    }
  }
}

char const * s2_last_path_sep(char const * str, cwal_size_t slen ){
  unsigned char const * pos = (unsigned char const *)str;
  unsigned char const * end = pos ? pos + slen : 0;
  unsigned char const * prevSep = 0;
  int sep = 0;
  if(!str || !slen) return 0;
  while( pos && pos<end ){
    int const ch = cwal_utf8_read_char( pos, end, &pos );
    if(end==pos) break;
    else if(!sep && ((int)'/'==ch || (int)'\\'==ch)){
      sep = ch;
    }
    if(sep == ch) prevSep = pos;
  }
  return (char const *)prevSep;
}

#undef MARKER
/* end of file s2_t10n.c */
/* start of file s2_tmpl.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>
/* #include <stdlib.h> */
#include <string.h>

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

const s2_tmpl_opt s2_tmpl_opt_empty = s2_tmpl_opt_empty_m;

int s2_tmpl_to_code( cwal_engine *e, cwal_buffer const * src,
                     cwal_buffer * dest,
                     s2_tmpl_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 blocks */;
  char const * errMsg = 0;
  char const * openStart = 0;
  char const * outsymLong = "TMPLOUT";
  int rc = 0;
  int hIsOpen = 0 /* heredoc currently opened? */;
  cwal_buffer hTag = cwal_buffer_empty;
  if(!e || !src || !dest) return CWAL_RC_MISUSE;
  if(!opt) opt = &s2_tmpl_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 & S2_TMPL_ELIDE_TMPLOUT)){
      /* Set up local vars, if not done already */
      rc = cwal_buffer_printf( e, dest,
                               "(('undefined'===typename %s) && var %s),\n"
                               "((undefined === %s) "
                               "&& (affirm s2 && s2.io && s2.io.output)"
                               "&& (%s=s2.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 1
    if(!rc) cwal_buffer_append( e, dest, ");\n", 3);
#else
    if(!rc) cwal_buffer_appendf( e, dest,
                                "),(%s.'operator<<'=proc(l,r){this(r); return this;});\n",
                                 outAlias);
    /* The problem with that is that it might interfere with (have unexpected side effects
       wrt) code in the <% %> blocks, e.g.:

       TMPLOUT << 1 << 2

       Will not behave the same as TMPLOUT(1<<2). The -> op has similar problems with
       unary values: <% -1 +2 %>.
    */
#endif
    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<end && *x && ('\n'==*x || s2_is_space(*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);\n%s( ", /*eval-><<<:%s*/
                                hDocId, outAlias/* , hDocId */);
        hIsOpen = 0;
      }else if(openStart == origin
               /*workaround for <% at starting pos. */){
        rc = cwal_buffer_printf(e, dest, "%s( ", /* <<<:%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, " );\n%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, " %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:
  {
    int line = 1;
    int col = 0;
    assert(errMsg);
    assert(0 != rc);
    s2_count_lines( (char const *)src->mem,
                    (char const *)src->mem+src->used,
                    pos, &line, &col);
    return cwal_exception_setf(e, rc,
                               "template error at line %d, col %d: %s",
                               line, col, errMsg);
    }
}

int s2_cb_tmpl_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;
  s2_tmpl_opt topt = s2_tmpl_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, "TMPLOUT", 10, 0)){
    /* Kind of a cheap (and not always valid) optimization */
    topt.flags |= S2_TMPL_ELIDE_TMPLOUT;
  }

  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 = s2_tmpl_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 s2_tmpl_to_code() API "
                                 "docs.");
        break;
      default:
        break;
    }
  }
  cwal_buffer_clear(e, &xbuf);
  return rc;
}



#undef MARKER
/* end of file s2_tmpl.c */
/* start of file s2_time.c */
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include <assert.h>

#if defined(S2_OS_WINDOWS)
#define S2_ENABLE_USLEEP 0
#else
#define S2_ENABLE_USLEEP 1
#endif


#if S2_ENABLE_USLEEP
#  include <string.h> /* strerror() */
#  include <errno.h>
#  if defined(S2_OS_UNIX)
#    include <unistd.h> /* usleep() */
#  endif
#endif

#include <stdlib.h>

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


/**
   Internal impl of sleep()/mssleep(). delayMult must be:

   sleep(): 1000*1000
   mssleep(): 1000
   usleep(): 1
*/
static int s2_cb_sleep_impl(cwal_callback_args const * args, cwal_value ** rv,
                            unsigned int delayMult){
#if !defined(S2_ENABLE_USLEEP)
  return cwal_exception_setf(args->engine, CWAL_RC_UNSUPPORTED,
                             "usleep()-based 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){
      if( (rc = usleep( t * delayMult )) ){
        rc = cwal_exception_setf(args->engine, CWAL_RC_ERROR,
                                 "usleep() failed with errno %d: %s",
                                 errno, strerror(errno));
      }else{
        *rv = cwal_value_undefined();
      }
    }else{
      rc = cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                               "Expecting a positive sleep time value.");
    }
    return rc;
  }
#endif
}

int s2_cb_sleep(cwal_callback_args const * args, cwal_value ** rv){
#if !defined(S2_OS_UNIX)
  return cwal_exception_setf(args->engine, CWAL_RC_MISUSE,
                             "sleep() 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( (unsigned int)t );
      *rv = cwal_new_integer(args->engine, rc);
      rc = 0;
    }else{
      rc = cwal_exception_setf(args->engine, CWAL_RC_RANGE,
                               "Expecting a positive sleep time value.");
    }
    return rc;
  }
#endif
}

int s2_cb_mssleep(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_sleep_impl( args, rv, 1000 );
}

int s2_cb_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 s2_cb_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 = s2_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 s2_install_time( s2_engine * se, cwal_value * tgt,
                     char const * name ){
  cwal_value * v;
  cwal_value * sub;
  int rc;
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;

  if(name && *name){
    sub = cwal_new_object_value(se->e);
    if(!sub) return CWAL_RC_OOM;
    if( (rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub)) ){
      cwal_value_unref(sub);
      return rc;
    }
  }else{
    sub = tgt;
  }

#define CHECKV if(!v) return CWAL_RC_OOM
#define RC if(rc) return rc
#define FUNC2(NAME,FP)                              \
  v = s2_new_function2( se, FP );                   \
  CHECKV;                                           \
  rc = cwal_prop_set( sub, NAME, cwal_strlen(NAME), v ); \
  if(rc) { cwal_value_unref(v); return rc; }(void)0

  FUNC2("sleep", s2_cb_sleep);
  FUNC2("time", s2_cb_time);
  FUNC2("mssleep", s2_cb_mssleep);
  FUNC2("strftime", s2_cb_strftime);

#undef FUNC2
#undef CHECKV
#undef RC
  return 0;
}


#undef MARKER
/* end of file s2_time.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 <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
s2_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 */
            s2_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 */
            s2_strftime(tbuf, sizeof tbuf, "%I:%M:%S %p", timeptr);
            break;

        case 'R':    /* time as %H:%M */
            s2_strftime(tbuf, sizeof tbuf, "%H:%M", timeptr);
            break;

        case 'T':    /* time as %H:%M:%S */
            s2_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 */
